From 0aa5e010ae42b9e14dd41e10ba062a1428c75759 Mon Sep 17 00:00:00 2001 From: jonschz <17198703+jonschz@users.noreply.github.com> Date: Sat, 23 Nov 2024 20:33:15 +0100 Subject: [PATCH] Implement `CarRace` (#1165) * Implement `CarRace` * Improve `CarRace::Create` * Fix arrays * Clean up array * Fix CI errors * Implement `LegoRaceCar::FUN_10012e00()` * Implement `Doors::VTable0xcc` * Address review comments, part 1 * Address review comments, part 2 --------- Co-authored-by: jonschz --- LEGO1/lego/legoomni/include/carrace.h | 14 +- LEGO1/lego/legoomni/include/doors.h | 2 +- LEGO1/lego/legoomni/include/jetskirace.h | 2 - LEGO1/lego/legoomni/include/legoentity.h | 9 +- .../lego/legoomni/include/legonavcontroller.h | 9 + LEGO1/lego/legoomni/include/legopathactor.h | 1 + LEGO1/lego/legoomni/include/legoracers.h | 1 + LEGO1/lego/legoomni/src/actors/doors.cpp | 39 +- .../lego/legoomni/src/build/legocarbuild.cpp | 3 +- LEGO1/lego/legoomni/src/common/misc.cpp | 6 + LEGO1/lego/legoomni/src/entity/legoactor.cpp | 1 + LEGO1/lego/legoomni/src/entity/legoentity.cpp | 29 +- LEGO1/lego/legoomni/src/main/scripts.cpp | 1 + LEGO1/lego/legoomni/src/race/carrace.cpp | 405 +++++++++++++++++- LEGO1/lego/legoomni/src/race/jetskirace.cpp | 37 +- LEGO1/lego/legoomni/src/race/legoracers.cpp | 12 + .../legoomni/src/race/legoracespecial.cpp | 4 + LEGO1/library_msvc.h | 3 + .../omni/include/mxactionnotificationparam.h | 1 + LEGO1/omni/include/mxentity.h | 4 + reccmp-project.yml | 11 + 21 files changed, 533 insertions(+), 61 deletions(-) diff --git a/LEGO1/lego/legoomni/include/carrace.h b/LEGO1/lego/legoomni/include/carrace.h index 38a6e77a..03e7a958 100644 --- a/LEGO1/lego/legoomni/include/carrace.h +++ b/LEGO1/lego/legoomni/include/carrace.h @@ -66,12 +66,22 @@ public: // FUNCTION: BETA10 0x100f16f0 void SetSkeleton(RaceSkel* p_skeleton) { m_skeleton = p_skeleton; } + void FUN_10017820(MxS32 p_param1, MxS16 p_param2); + // SYNTHETIC: LEGO1 0x10016c70 // CarRace::`scalar deleting destructor' private: - undefined m_unk0x144[12]; // 0x144 - RaceSkel* m_skeleton; // 0x150 + static MxS32 g_unk0x100d5d10[]; + static MxS32 g_unk0x100d5d30[]; + static MxS32 g_unk0x100d5d40[]; + static MxS32 g_unk0x100d5d50[]; + static MxS32 g_unk0x100d5d60[]; + + MxS32 m_unk0x144; // 0x144 + MxS32 m_unk0x148; // 0x148 + MxS32 m_unk0x14c; // 0x14c + RaceSkel* m_skeleton; // 0x150 }; #endif // CARRACE_H diff --git a/LEGO1/lego/legoomni/include/doors.h b/LEGO1/lego/legoomni/include/doors.h index 7fb2c392..8be10963 100644 --- a/LEGO1/lego/legoomni/include/doors.h +++ b/LEGO1/lego/legoomni/include/doors.h @@ -27,7 +27,7 @@ public: void ParseAction(char* p_extra) override; // vtable+0x20 void VTable0x70(float p_float) override; // vtable+0x70 MxResult VTable0x94(LegoPathActor* p_actor, MxBool p_bool) override; // vtable+0x94 - virtual double VTable0xcc(float p_float); // vtable+0xcc + virtual MxFloat VTable0xcc(float p_float); // vtable+0xcc // SYNTHETIC: LEGO1 0x1000e580 // Doors::`scalar deleting destructor' diff --git a/LEGO1/lego/legoomni/include/jetskirace.h b/LEGO1/lego/legoomni/include/jetskirace.h index b666868f..e880086f 100644 --- a/LEGO1/lego/legoomni/include/jetskirace.h +++ b/LEGO1/lego/legoomni/include/jetskirace.h @@ -65,8 +65,6 @@ public: void FUN_10016930(MxS32 p_param1, MxS16 p_param2); private: - inline MxS32 PossiblyGetPlaceOfPlayer(); - static MxS32 g_unk0x100f0c78; }; diff --git a/LEGO1/lego/legoomni/include/legoentity.h b/LEGO1/lego/legoomni/include/legoentity.h index c984e097..ae9aadab 100644 --- a/LEGO1/lego/legoomni/include/legoentity.h +++ b/LEGO1/lego/legoomni/include/legoentity.h @@ -10,6 +10,7 @@ class MxDSAction; class Vector3; // VTABLE: LEGO1 0x100d4858 +// VTABLE: BETA10 0x101b9388 // SIZE 0x68 class LegoEntity : public MxEntity { public: @@ -38,6 +39,7 @@ public: MxLong Notify(MxParam& p_param) override; // vtable+0x04 // FUNCTION: LEGO1 0x1000c2f0 + // FUNCTION: BETA10 0x10012730 const char* ClassName() const override // vtable+0x0c { // STRING: LEGO1 0x100f0064 @@ -116,8 +118,11 @@ protected: // For tokens from the extra string that look like this: // "Action:openram;\lego\scripts\Race\CarRaceR;0" Extra::ActionType m_actionType; // 0x5c - char* m_filename; // 0x60 - MxS32 m_targetEntityId; // 0x64 + + // variable name verified by BETA10 0x1007eddf + char* m_siFile; // 0x60 + + MxS32 m_targetEntityId; // 0x64 }; // SYNTHETIC: LEGO1 0x1000c3b0 diff --git a/LEGO1/lego/legoomni/include/legonavcontroller.h b/LEGO1/lego/legoomni/include/legonavcontroller.h index 216b7b89..c99ab3fe 100644 --- a/LEGO1/lego/legoomni/include/legonavcontroller.h +++ b/LEGO1/lego/legoomni/include/legonavcontroller.h @@ -82,6 +82,12 @@ public: // FUNCTION: BETA10 0x100b0f40 void SetLinearVel(MxFloat p_linearVel) { m_linearVel = p_linearVel; } + // FUNCTION: BETA10 0x100c99e0 + void SetDeadZone(MxS32 p_deadZone) { m_deadZone = p_deadZone; } + + // FUNCTION: BETA10 0x100c7880 + void SetTrackDefault(MxS32 p_trackDefault) { m_trackDefault = p_trackDefault; } + MxFloat GetLinearVel() { return m_linearVel; } MxFloat GetRotationalVel() { return m_rotationalVel; } MxFloat GetMaxLinearVel() { return m_maxLinearVel; } @@ -91,6 +97,9 @@ public: m_trackDefault = 0; } + // FUNCTION: BETA10 0x100c9a10 + int GetDefaultDeadZone() { return g_defdeadZone; } + // SYNTHETIC: LEGO1 0x10054c10 // LegoNavController::`scalar deleting destructor' diff --git a/LEGO1/lego/legoomni/include/legopathactor.h b/LEGO1/lego/legoomni/include/legopathactor.h index c4d5cd8d..d82e771c 100644 --- a/LEGO1/lego/legoomni/include/legopathactor.h +++ b/LEGO1/lego/legoomni/include/legopathactor.h @@ -15,6 +15,7 @@ struct LegoUnknown100db7f4; class LegoWEEdge; extern MxLong g_unk0x100f3308; +extern const char* g_strHIT_WALL_SOUND; // VTABLE: LEGO1 0x100d6e28 // SIZE 0x154 diff --git a/LEGO1/lego/legoomni/include/legoracers.h b/LEGO1/lego/legoomni/include/legoracers.h index 3648987f..3ccfc06f 100644 --- a/LEGO1/lego/legoomni/include/legoracers.h +++ b/LEGO1/lego/legoomni/include/legoracers.h @@ -77,6 +77,7 @@ public: virtual MxU32 HandleSkeletonKicks(float p_param1); static void FUN_10012de0(); + static void FUN_10012e00(); static void FUN_10013670(); // SYNTHETIC: LEGO1 0x10014240 diff --git a/LEGO1/lego/legoomni/src/actors/doors.cpp b/LEGO1/lego/legoomni/src/actors/doors.cpp index f2eb68ee..3ff0306d 100644 --- a/LEGO1/lego/legoomni/src/actors/doors.cpp +++ b/LEGO1/lego/legoomni/src/actors/doors.cpp @@ -4,11 +4,24 @@ #include "mxmisc.h" #include "mxtimer.h" #include "roi/legoroi.h" +#include "tgl/tglvector.h" #include DECOMP_SIZE_ASSERT(Doors, 0x1f8) +// GLOBAL: LEGO1 0x100d8e7c +// GLOBAL: BETA10 0x101b954c +MxFloat g_unk0x100d8e7c = 1000.0f; + +// GLOBAL: LEGO1 0x100d8e80 +// GLOBAL: BETA10 0x101b9550 +MxFloat g_unk0x100d8e80 = 4000.0f; + +// GLOBAL: LEGO1 0x100d8e84 +// GLOBAL: BETA10 0x101b9554 +MxFloat g_unk0x100d8e84 = 6000.0f; + // FUNCTION: LEGO1 0x10066100 // FUNCTION: BETA10 0x10026850 MxResult Doors::VTable0x94(LegoPathActor* p_actor, MxBool p_bool) @@ -25,11 +38,29 @@ MxResult Doors::VTable0x94(LegoPathActor* p_actor, MxBool p_bool) return m_unk0x1f4 < 0.001 ? SUCCESS : FAILURE; } -// STUB: LEGO1 0x10066190 +// FUNCTION: LEGO1 0x10066190 // FUNCTION: BETA10 0x1002696b -double Doors::VTable0xcc(float p_float) +MxFloat Doors::VTable0xcc(float p_float) { - return 0.0; + MxFloat fVar1; + + fVar1 = p_float - m_unk0x158; + + if (fVar1 <= 0.0f) { + return 0.0f; + } + + if (fVar1 <= g_unk0x100d8e7c) { + return fVar1 * 1.570796 / g_unk0x100d8e7c; + } + else if (fVar1 <= g_unk0x100d8e7c + g_unk0x100d8e80) { + return 1.570796012878418; // Pi / 2 + } + else if (fVar1 <= g_unk0x100d8e84) { + return (1.0 - ((fVar1 - g_unk0x100d8e80) - g_unk0x100d8e7c) / g_unk0x100d8e7c) * 1.570796; + } + + return 0.0f; } // FUNCTION: LEGO1 0x10066250 @@ -71,7 +102,7 @@ void Doors::VTable0x70(float p_float) m_unk0x1f4 = local8; } - if (m_unk0x158 + 6000.0f < p_float) { + if (m_unk0x158 + g_unk0x100d8e84 < p_float) { m_ltDoor->FUN_100a58f0(m_ltDoorLocal); m_rtDoor->FUN_100a58f0(m_rtDoorLocal); m_ltDoor->VTable0x14(); diff --git a/LEGO1/lego/legoomni/src/build/legocarbuild.cpp b/LEGO1/lego/legoomni/src/build/legocarbuild.cpp index d981be73..b86fecd4 100644 --- a/LEGO1/lego/legoomni/src/build/legocarbuild.cpp +++ b/LEGO1/lego/legoomni/src/build/legocarbuild.cpp @@ -12,6 +12,7 @@ #include "legocontrolmanager.h" #include "legogamestate.h" #include "legoinputmanager.h" +#include "legomain.h" #include "legosoundmanager.h" #include "legoutils.h" #include "misc.h" @@ -1254,7 +1255,7 @@ void LegoCarBuild::FUN_10024ef0() m_buildState->m_animationState = LegoVehicleBuildState::e_cutscene; FUN_10025720(FUN_10025d70()); m_buildState->m_unk0x4c += 1; - FUN_10015820(FALSE, 7); + FUN_10015820(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); } // FUNCTION: LEGO1 0x10024f30 diff --git a/LEGO1/lego/legoomni/src/common/misc.cpp b/LEGO1/lego/legoomni/src/common/misc.cpp index 61399b00..a0ff5d0a 100644 --- a/LEGO1/lego/legoomni/src/common/misc.cpp +++ b/LEGO1/lego/legoomni/src/common/misc.cpp @@ -41,8 +41,10 @@ MxBackgroundAudioManager* BackgroundAudioManager() } // FUNCTION: LEGO1 0x10015740 +// FUNCTION: BETA10 0x100e4895 LegoInputManager* InputManager() { + assert(LegoOmni::GetInstance()); return LegoOmni::GetInstance()->GetInputManager(); } @@ -95,8 +97,10 @@ LegoWorld* CurrentWorld() } // FUNCTION: LEGO1 0x100157b0 +// FUNCTION: BETA10 0x100e4a8d LegoCharacterManager* CharacterManager() { + assert(LegoOmni::GetInstance()); return LegoOmni::GetInstance()->GetCharacterManager(); } @@ -131,8 +135,10 @@ ViewLODListManager* GetViewLODListManager() } // FUNCTION: LEGO1 0x10015820 +// FUNCTION: BETA10 0x100e4c92 void FUN_10015820(MxBool p_disable, MxU16 p_flags) { + assert(LegoOmni::GetInstance()); LegoOmni::GetInstance()->FUN_1005b4f0(p_disable, p_flags); } diff --git a/LEGO1/lego/legoomni/src/entity/legoactor.cpp b/LEGO1/lego/legoomni/src/entity/legoactor.cpp index 2377302e..927ad4a4 100644 --- a/LEGO1/lego/legoomni/src/entity/legoactor.cpp +++ b/LEGO1/lego/legoomni/src/entity/legoactor.cpp @@ -140,6 +140,7 @@ void LegoActor::SetROI(LegoROI* p_roi, MxBool p_bool1, MxBool p_bool2) } // FUNCTION: LEGO1 0x1002d6e0 +// FUNCTION: BETA10 0x1003d6f2 void LegoActor::Mute(MxBool p_muted) { if (m_sound != NULL) { diff --git a/LEGO1/lego/legoomni/src/entity/legoentity.cpp b/LEGO1/lego/legoomni/src/entity/legoentity.cpp index 81cb3fe3..7442d189 100644 --- a/LEGO1/lego/legoomni/src/entity/legoentity.cpp +++ b/LEGO1/lego/legoomni/src/entity/legoentity.cpp @@ -28,7 +28,7 @@ void LegoEntity::Init() m_worldSpeed = 0; m_roi = NULL; m_cameraFlag = FALSE; - m_filename = NULL; + m_siFile = NULL; m_unk0x10 = 0; m_flags = 0; m_actionType = Extra::ActionType::e_unknown; @@ -37,6 +37,7 @@ void LegoEntity::Init() } // FUNCTION: LEGO1 0x10010650 +// FUNCTION: BETA10 0x1007e39a void LegoEntity::ResetWorldTransform(MxBool p_cameraFlag) { LegoWorld* world = CurrentWorld(); @@ -66,6 +67,7 @@ void LegoEntity::ResetWorldTransform(MxBool p_cameraFlag) } // FUNCTION: LEGO1 0x10010790 +// FUNCTION: BETA10 0x1007e4f6 void LegoEntity::SetWorldTransform(const Vector3& p_location, const Vector3& p_direction, const Vector3& p_up) { LegoWorld* world = CurrentWorld(); @@ -78,6 +80,7 @@ void LegoEntity::SetWorldTransform(const Vector3& p_location, const Vector3& p_d } // FUNCTION: LEGO1 0x100107e0 +// FUNCTION: BETA10 0x1007e572 MxResult LegoEntity::Create(MxDSAction& p_dsAction) { m_entityId = p_dsAction.GetObjectId(); @@ -87,6 +90,7 @@ MxResult LegoEntity::Create(MxDSAction& p_dsAction) } // FUNCTION: LEGO1 0x10010810 +// FUNCTION: BETA10 0x1007e5b9 void LegoEntity::Destroy(MxBool p_fromDestructor) { if (m_roi) { @@ -103,11 +107,12 @@ void LegoEntity::Destroy(MxBool p_fromDestructor) } } - delete[] m_filename; + delete[] m_siFile; Init(); } // FUNCTION: LEGO1 0x10010880 +// FUNCTION: BETA10 0x1007e6e1 void LegoEntity::SetWorld() { LegoWorld* world = CurrentWorld(); @@ -118,6 +123,7 @@ void LegoEntity::SetWorld() } // FUNCTION: LEGO1 0x100108a0 +// FUNCTION: BETA10 0x1007e724 void LegoEntity::SetROI(LegoROI* p_roi, MxBool p_bool1, MxBool p_bool2) { m_roi = p_roi; @@ -225,6 +231,7 @@ Mx3DPointFloat LegoEntity::GetWorldPosition() } // FUNCTION: LEGO1 0x10010e10 +// FUNCTION: BETA10 0x1007ec97 void LegoEntity::ParseAction(char* p_extra) { char copy[1024]; @@ -232,16 +239,22 @@ void LegoEntity::ParseAction(char* p_extra) strcpy(copy, p_extra); if (KeyValueStringParse(actionValue, g_strACTION, copy)) { - m_actionType = MatchActionString(strtok(actionValue, g_parseExtraTokens)); + char* token = strtok(actionValue, g_parseExtraTokens); + assert(token); + m_actionType = MatchActionString(token); if (m_actionType != Extra::ActionType::e_exit) { - char* token = strtok(NULL, g_parseExtraTokens); + token = strtok(NULL, g_parseExtraTokens); + assert(token); - m_filename = new char[strlen(token) + 1]; - strcpy(m_filename, token); + m_siFile = new char[strlen(token) + 1]; + assert(m_siFile); + strcpy(m_siFile, token); if (m_actionType != Extra::ActionType::e_run) { - m_targetEntityId = atoi(strtok(NULL, g_parseExtraTokens)); + token = strtok(NULL, g_parseExtraTokens); + assert(token); + m_targetEntityId = atoi(token); } } } @@ -458,7 +471,7 @@ MxLong LegoEntity::Notify(MxParam& p_param) } if (m_actionType != Extra::e_unknown) { - InvokeAction(m_actionType, MxAtomId(m_filename, e_lowerCase2), m_targetEntityId, this); + InvokeAction(m_actionType, MxAtomId(m_siFile, e_lowerCase2), m_targetEntityId, this); } else { switch (GameState()->GetActorId()) { diff --git a/LEGO1/lego/legoomni/src/main/scripts.cpp b/LEGO1/lego/legoomni/src/main/scripts.cpp index b5b8f9fe..ce3956cb 100644 --- a/LEGO1/lego/legoomni/src/main/scripts.cpp +++ b/LEGO1/lego/legoomni/src/main/scripts.cpp @@ -15,6 +15,7 @@ MxAtomId* g_jetskiScript = NULL; MxAtomId* g_racecarScript = NULL; // GLOBAL: LEGO1 0x100f452c +// GLOBAL: BETA10 0x10211514 MxAtomId* g_carraceScript = NULL; // GLOBAL: LEGO1 0x100f4530 diff --git a/LEGO1/lego/legoomni/src/race/carrace.cpp b/LEGO1/lego/legoomni/src/race/carrace.cpp index 6ffb2ebd..332a4064 100644 --- a/LEGO1/lego/legoomni/src/race/carrace.cpp +++ b/LEGO1/lego/legoomni/src/race/carrace.cpp @@ -1,9 +1,73 @@ #include "carrace.h" +#include "actions/carrace_actions.h" +#include "actions/jukebox_actions.h" +#include "dunebuggy.h" +#include "isle.h" +#include "legoanimationmanager.h" +#include "legobackgroundcolor.h" +#include "legocontrolmanager.h" +#include "legohideanimpresenter.h" +#include "legomain.h" +#include "legonavcontroller.h" +#include "legopathstruct.h" +#include "legoracers.h" +#include "legoutils.h" +#include "misc.h" #include "mxactionnotificationparam.h" +#include "mxbackgroundaudiomanager.h" +#include "mxmisc.h" +#include "mxnotificationmanager.h" +#include "mxstillpresenter.h" +#include "mxtransitionmanager.h" +#include "mxvariabletable.h" +#include "scripts.h" DECOMP_SIZE_ASSERT(CarRace, 0x154) +// GLOBAL: LEGO1 0x100d5d10 +MxS32 CarRace::g_unk0x100d5d10[] = { + CarraceScript::c_srt001sl_RunAnim, + CarraceScript::c_srt002sl_RunAnim, + CarraceScript::c_srt003sl_RunAnim, + CarraceScript::c_srt004sl_RunAnim, + CarraceScript::c_srt005sl_RunAnim, + CarraceScript::c_srt001rh_RunAnim, + CarraceScript::c_srt002rh_RunAnim, + CarraceScript::c_srt003rh_RunAnim +}; + +// GLOBAL: LEGO1 0x100d5d30 +MxS32 CarRace::g_unk0x100d5d30[] = { + CarraceScript::c_srt011sl_RunAnim, + CarraceScript::c_srt012sl_RunAnim, + CarraceScript::c_srt013sl_RunAnim, + CarraceScript::c_srt014sl_RunAnim +}; + +// GLOBAL: LEGO1 0x100d5d40 +MxS32 CarRace::g_unk0x100d5d40[] = + {CarraceScript::c_srt015sl_RunAnim, CarraceScript::c_srt016sl_RunAnim, CarraceScript::c_srt017sl_RunAnim}; + +// GLOBAL: LEGO1 0x100d5d50 +MxS32 CarRace::g_unk0x100d5d50[] = + {CarraceScript::c_srt007rh_RunAnim, CarraceScript::c_srt008rh_RunAnim, CarraceScript::c_srt009rh_RunAnim}; + +// GLOBAL: LEGO1 0x100d5d60 +MxS32 CarRace::g_unk0x100d5d60[] = + {CarraceScript::c_srt010rh_RunAnim, CarraceScript::c_srt011rh_RunAnim, CarraceScript::c_srt012rh_RunAnim}; + +// GLOBAL: LEGO1 0x100f0c70 +// STRING: LEGO1 0x100f0c48 +LegoChar* g_strCRCFRNTY6 = "C_RCFRNTY6"; + +// GLOBAL: LEGO1 0x100f0c74 +// STRING: LEGO1 0x100f0c3c +LegoChar* g_strCRCEDGEY0 = "C_RCEDGEY0"; + +// GLOBAL: LEGO1 0x100f0c7c +static MxS32 g_unk0x100f0c7c = 2; + // FUNCTION: LEGO1 0x10016a90 CarRace::CarRace() { @@ -11,50 +75,345 @@ CarRace::CarRace() this->m_unk0x130 = MxRect32(0x16c, 0x154, 0x1ec, 0x15e); } -// STUB: LEGO1 0x10016ce0 +// FUNCTION: LEGO1 0x10016ce0 +// FUNCTION: BETA10 0x100c8364 MxResult CarRace::Create(MxDSAction& p_dsAction) { - // TODO - return SUCCESS; + MxResult result = LegoRace::Create(p_dsAction); + + NavController()->SetDeadZone(1); + NavController()->SetTrackDefault(0); + GameState()->m_currentArea = LegoGameState::e_carrace; + GameState()->StopArea(LegoGameState::e_undefined); + + LegoGameState* state = GameState(); + + RaceState* raceState = (RaceState*) state->GetState("CarRaceState"); + + if (!raceState) { + raceState = (RaceState*) state->CreateState("CarRaceState"); + } + + m_raceState = raceState; + + m_act1State->m_unk0x018 = 6; + m_unk0x144 = -1; + m_unk0x148 = -1; + m_unk0x14c = -1; + + LegoRaceCar::FUN_10012e00(); + + MxS32 streamId = + DuneBuggy::GetColorOffset(g_strCRCEDGEY0) + (DuneBuggy::GetColorOffset(g_strCRCFRNTY6) * 5 + 15) * 2; + InvokeAction(Extra::e_start, m_atomId, streamId, NULL); + InvokeAction(Extra::e_start, m_atomId, CarraceScript::c_RaceCarDashboard, NULL); + + return result; } -// STUB: LEGO1 0x10016dd0 +// FUNCTION: LEGO1 0x10016dd0 +// FUNCTION: BETA10 0x100c8490 void CarRace::ReadyWorld() { - // TODO + assert(m_hideAnim); + LegoWorld::ReadyWorld(); + m_hideAnim->FUN_1006db40(0); + + MxDSAction action; + action.SetAtomId(*g_jukeboxScript); + action.SetObjectId(JukeboxScript::c_RaceTrackRoad_Music); + + BackgroundAudioManager()->PlayMusic(action, 5, MxPresenter::e_repeating); + AnimationManager()->Resume(); + FUN_10015820(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); + + m_unk0x144 = g_unk0x100d5d10[rand() & 7]; + + AnimationManager()->FUN_10060dc0(m_unk0x144, NULL, TRUE, FALSE, NULL, FALSE, TRUE, FALSE, TRUE); + + m_unk0x128 = (MxStillPresenter*) Find("MxPresenter", "CarLocator2"); + m_unk0x128->SetPosition(m_unk0x130.GetLeft(), m_unk0x130.GetTop()); + + m_unk0x12c = (MxStillPresenter*) Find("MxPresenter", "CarLocator3"); + m_unk0x12c->SetPosition(m_unk0x130.GetLeft(), m_unk0x130.GetTop()); + VariableTable()->SetVariable("DISTANCE", "0.036"); } -// STUB: LEGO1 0x10016f60 -MxLong CarRace::HandleEndAction(MxEndActionNotificationParam&) +// FUNCTION: LEGO1 0x10016f60 +// FUNCTION: BETA10 0x100c85eb +MxLong CarRace::HandleEndAction(MxEndActionNotificationParam& p_param) { - // TODO - return 0; + MxLong result = 0; + + if (p_param.GetAction()) { + MxDSAction* action = p_param.GetAction(); + MxU32 objectId = action->GetObjectId(); + + if (m_unk0x144 == objectId) { + InvokeAction(Extra::e_start, *g_carraceScript, CarraceScript::c_irtx08ra_PlayWav, NULL); + result = 1; + } + else if (objectId == CarraceScript::c_irtx08ra_PlayWav && m_destLocation == LegoGameState::e_undefined) { + m_unk0x110[0]->Mute(FALSE); + m_unk0x110[1]->Mute(FALSE); + m_unk0x110[2]->Mute(FALSE); + + VariableTable()->SetVariable(g_raceState, g_racing); + result = 1; + } + else if (m_unk0x148 == objectId) { + AnimationManager()->FUN_10060dc0(m_unk0x14c, NULL, TRUE, FALSE, NULL, FALSE, TRUE, FALSE, TRUE); + } + else if (m_unk0x14c == objectId) { + NotificationManager()->Send(this, MxNotificationParam()); + } + } + + return result; } -// STUB: LEGO1 0x100170e0 -MxLong CarRace::HandlePathStruct(LegoPathStructNotificationParam&) +// FUNCTION: LEGO1 0x100170e0 +// FUNCTION: BETA10 0x100c87ac +MxLong CarRace::HandlePathStruct(LegoPathStructNotificationParam& p_param) { - // TODO - return 0; + MxLong result = 0; + + if (p_param.GetTrigger() == 68) { + MxEntity* sender = (MxEntity*) p_param.GetSender(); + MxS32 paramData = p_param.GetData(); + + switch (sender->GetEntityId()) { + case 10: + if (paramData <= m_unk0x104 || paramData >= m_unk0x104 + 5) { + break; + } + + m_unk0x104 = paramData; + LegoChar buffer[20]; + sprintf(buffer, "%g", 0.036 + 0.928 * (m_unk0xf8 * 20.0 + m_unk0x104) / (g_unk0x100f0c7c * 20.0)); + VariableTable()->SetVariable("DISTANCE", buffer); + + if (m_unk0x104 == 0x14) { + m_unk0x104 = 0; + m_unk0xf8++; + + if (g_unk0x100f0c7c == m_unk0xf8) { + VariableTable()->SetVariable(g_raceState, ""); + + m_unk0x110[0]->Mute(TRUE); + m_unk0x110[1]->Mute(TRUE); + m_unk0x110[2]->Mute(TRUE); + + m_unk0x110[0]->SetMaxLinearVel(-1.0); + m_unk0x110[1]->SetMaxLinearVel(-1.0); + m_unk0x110[2]->SetMaxLinearVel(-1.0); + + RemoveActor(m_unk0x110[1]); + m_unk0x110[1]->ClearMaps(); + + RemoveActor(m_unk0x110[2]); + m_unk0x110[2]->ClearMaps(); + + MxS32 position; + + if (m_unk0xfc < m_unk0xf8 && m_unk0x100 < m_unk0xf8) { + position = 3; + m_unk0x148 = g_unk0x100d5d40[rand() % 3]; + m_unk0x14c = g_unk0x100d5d60[rand() % 3]; + } + else if (m_unk0xfc < m_unk0xf8 || m_unk0x100 < m_unk0xf8) { + position = 2; + if (m_unk0xfc == g_unk0x100f0c7c) { + m_unk0x148 = g_unk0x100d5d30[rand() % 4]; + m_unk0x14c = g_unk0x100d5d60[rand() % 3]; + } + else { + m_unk0x148 = g_unk0x100d5d50[rand() % 3]; + m_unk0x14c = g_unk0x100d5d40[rand() % 3]; + } + } + else { + position = 1; + m_unk0x148 = g_unk0x100d5d30[rand() % 4]; + m_unk0x14c = g_unk0x100d5d50[rand() % 3]; + } + + InputManager()->DisableInputProcessing(); + InputManager()->SetUnknown336(TRUE); + VariableTable()->SetVariable(g_strHIT_WALL_SOUND, ""); + NavController()->SetDeadZone(NavController()->GetDefaultDeadZone()); + NavController()->SetTrackDefault(1); + LegoRaceCar::FUN_10012de0(); + m_raceState->m_unk0x28 = 2; + + RaceState::Entry* raceState = m_raceState->GetState(GameState()->GetActorId()); + raceState->m_unk0x02 = position; + + if (raceState->m_score < (MxS16) position) { + raceState->m_score = position; + } + + AnimationManager()->FUN_10060dc0(m_unk0x148, NULL, TRUE, FALSE, NULL, FALSE, TRUE, FALSE, TRUE); + } + + result = 1; + } + + break; + case 11: + if (paramData <= m_unk0x108 || paramData >= m_unk0x108 + 5) { + break; + } + + FUN_10017820(11, paramData); + m_unk0x108 = paramData; + + if (m_unk0x108 == 0x14) { + m_unk0x108 = 0; + m_unk0xfc++; + + if (g_unk0x100f0c7c == m_unk0xfc) { + m_unk0x110[1]->SetMaxLinearVel(-1.0); + RemoveActor(m_unk0x110[1]); + m_unk0x110[1]->ClearMaps(); + m_unk0x110[1]->GetROI()->SetVisibility(FALSE); + + LegoROI* roi = FindROI("rcblack"); + + if (roi) { + roi->SetVisibility(FALSE); + } + } + } + + break; + case 12: + if (paramData <= m_unk0x10c || paramData >= m_unk0x10c + 5) { + break; + } + + FUN_10017820(12, paramData); + + m_unk0x10c = paramData; + + if (m_unk0x10c == 0x14) { + m_unk0x10c = 0; + m_unk0x100++; + + if (g_unk0x100f0c7c == m_unk0x100) { + m_unk0x110[2]->SetMaxLinearVel(-1.0); + RemoveActor(m_unk0x110[2]); + m_unk0x110[2]->ClearMaps(); + m_unk0x110[2]->GetROI()->SetVisibility(FALSE); + + LegoROI* roi = FindROI("rcgreen"); + + if (roi) { + roi->SetVisibility(FALSE); + } + } + } + + break; + } + } + + return result; } -// STUB: LEGO1 0x10017650 -MxLong CarRace::HandleClick(LegoEventNotificationParam&) +// FUNCTION: LEGO1 0x10017650 +MxLong CarRace::HandleClick(LegoEventNotificationParam& p_param) { - // TODO - return 0; + LegoControlManagerNotificationParam* param = (LegoControlManagerNotificationParam*) &p_param; + + if (param->m_unk0x28 == 1) { + switch (param->m_clickedObjectId) { + case 3: + InvokeAction(Extra::e_stop, *g_carraceScript, CarraceScript::c_irtx08ra_PlayWav, NULL); + m_act1State->m_unk0x018 = 0; + VariableTable()->SetVariable(g_raceState, ""); + VariableTable()->SetVariable(g_strHIT_WALL_SOUND, ""); + NavController()->SetDeadZone(NavController()->GetDefaultDeadZone()); + NavController()->SetTrackDefault(1); + LegoRaceCar::FUN_10012de0(); + m_destLocation = LegoGameState::e_infomain; + TransitionManager()->StartTransition(MxTransitionManager::e_mosaic, 50, FALSE, FALSE); + GameState()->GetBackgroundColor()->SetValue("reset"); + break; + case 98: + InvokeAction(Extra::e_stop, *g_carraceScript, CarraceScript::c_irtx08ra_PlayWav, NULL); + m_act1State->m_unk0x018 = 0; + VariableTable()->SetVariable(g_raceState, ""); + VariableTable()->SetVariable(g_strHIT_WALL_SOUND, ""); + NavController()->SetDeadZone(NavController()->GetDefaultDeadZone()); + NavController()->SetTrackDefault(1); + LegoRaceCar::FUN_10012de0(); + m_destLocation = LegoGameState::e_carraceExterior; + TransitionManager()->StartTransition(MxTransitionManager::e_mosaic, 50, FALSE, FALSE); + GameState()->GetBackgroundColor()->SetValue("reset"); + break; + default: + break; + } + } + return 1; } -// STUB: LEGO1 0x100177e0 +// FUNCTION: LEGO1 0x100177e0 +// FUNCTION: BETA10 0x100c8f59 MxLong CarRace::HandleType0Notification(MxNotificationParam&) { - // TODO - return 0; + if (m_raceState->m_unk0x28 == 2) { + m_destLocation = LegoGameState::e_unk21; + TransitionManager()->StartTransition(MxTransitionManager::e_mosaic, 50, FALSE, FALSE); + } + + return 1; } -// STUB: LEGO1 0x10017900 +// FUNCTION: LEGO1 0x10017820 +void CarRace::FUN_10017820(MxS32 p_param1, MxS16 p_param2) +{ + MxS32 local4; + MxStillPresenter* presenter; + MxS32 x, y; + + if (p_param1 == 11) { + presenter = m_unk0x128; + local4 = m_unk0xfc; + } + else if (p_param1 == 12) { + presenter = m_unk0x12c; + local4 = m_unk0x100; + } + + if (presenter) { + x = m_unk0x130.GetLeft() + 0.5 + + (m_unk0x130.GetRight() - m_unk0x130.GetLeft() + 1) * (local4 * 20.0 + p_param2) / (g_unk0x100f0c7c * 20.0); + y = m_unk0x130.GetTop() + 0.5 + + (m_unk0x130.GetBottom() - m_unk0x130.GetTop() + 1) * (local4 * 20.0 + p_param2) / (g_unk0x100f0c7c * 20.0); + + presenter->SetPosition(x, y); + } +} + +// FUNCTION: LEGO1 0x10017900 MxBool CarRace::Escape() { - // TODO - return FALSE; + InvokeAction(Extra::e_stop, *g_carraceScript, CarraceScript::c_irtx08ra_PlayWav, NULL); + + AnimationManager()->FUN_10061010(FALSE); + DeleteObjects(&m_atomId, 500, 999); + m_act1State->m_unk0x018 = 0; + VariableTable()->SetVariable(g_strHIT_WALL_SOUND, ""); + + VariableTable()->SetVariable(g_raceState, ""); + NavController()->SetDeadZone(NavController()->GetDefaultDeadZone()); + + NavController()->SetTrackDefault(1); + LegoRaceCar::FUN_10012de0(); + + GameState()->GetBackgroundColor()->SetValue("reset"); + m_destLocation = LegoGameState::e_infomain; + return TRUE; } diff --git a/LEGO1/lego/legoomni/src/race/jetskirace.cpp b/LEGO1/lego/legoomni/src/race/jetskirace.cpp index b4ffa03e..65696e5b 100644 --- a/LEGO1/lego/legoomni/src/race/jetskirace.cpp +++ b/LEGO1/lego/legoomni/src/race/jetskirace.cpp @@ -152,18 +152,6 @@ MxLong JetskiRace::HandleClick(LegoEventNotificationParam& p_param) return result; } -inline MxS32 JetskiRace::PossiblyGetPlaceOfPlayer() -{ - if (m_unk0xfc < m_unk0xf8 && m_unk0x100 < m_unk0xf8) { - return 3; - } - else if (m_unk0xfc < m_unk0xf8 || m_unk0x100 < m_unk0xf8) { - return 2; - } - - return 1; -} - // FUNCTION: LEGO1 0x100166a0 // FUNCTION: BETA10 0x100c8085 MxLong JetskiRace::HandlePathStruct(LegoPathStructNotificationParam& p_param) @@ -190,7 +178,17 @@ MxLong JetskiRace::HandlePathStruct(LegoPathStructNotificationParam& p_param) m_unk0xf8++; if (g_unk0x100f0c78 == m_unk0xf8) { - MxS16 sVar6 = PossiblyGetPlaceOfPlayer(); + MxS32 position; + + if (m_unk0xfc < m_unk0xf8 && m_unk0x100 < m_unk0xf8) { + position = 3; + } + else if (m_unk0xfc < m_unk0xf8 || m_unk0x100 < m_unk0xf8) { + position = 2; + } + else { + position = 1; + } VariableTable()->SetVariable(g_raceState, ""); VariableTable()->SetVariable(g_strHIT_WALL_SOUND, ""); @@ -198,10 +196,10 @@ MxLong JetskiRace::HandlePathStruct(LegoPathStructNotificationParam& p_param) m_raceState->m_unk0x28 = 2; RaceState::Entry* raceStateEntry = m_raceState->GetState(GameState()->GetActorId()); - raceStateEntry->m_unk0x02 = sVar6; + raceStateEntry->m_unk0x02 = position; - if (raceStateEntry->m_score < sVar6) { - raceStateEntry->m_score = sVar6; + if (raceStateEntry->m_score < (MxS16) position) { + raceStateEntry->m_score = position; } m_destLocation = LegoGameState::e_jetrace2; @@ -215,13 +213,14 @@ MxLong JetskiRace::HandlePathStruct(LegoPathStructNotificationParam& p_param) m_hideAnim->FUN_1006db40(m_unk0xf8 * 200 + 100); result = 1; } + break; case 11: if (paramData <= m_unk0x108 || paramData >= m_unk0x108 + 5) { break; } - FUN_10016930(0xb, paramData); + FUN_10016930(11, paramData); m_unk0x108 = paramData; if (m_unk0x108 == 0x14) { @@ -232,13 +231,14 @@ MxLong JetskiRace::HandlePathStruct(LegoPathStructNotificationParam& p_param) ((LegoPathActor*) p_param.GetSender())->SetMaxLinearVel(0.1); } } + break; case 12: if (paramData <= m_unk0x10c || paramData >= m_unk0x10c + 5) { break; } - FUN_10016930(0xc, paramData); + FUN_10016930(12, paramData); m_unk0x10c = paramData; @@ -250,6 +250,7 @@ MxLong JetskiRace::HandlePathStruct(LegoPathStructNotificationParam& p_param) ((LegoPathActor*) p_param.GetSender())->SetMaxLinearVel(0.1); } } + break; } } diff --git a/LEGO1/lego/legoomni/src/race/legoracers.cpp b/LEGO1/lego/legoomni/src/race/legoracers.cpp index 73a070cd..a313c610 100644 --- a/LEGO1/lego/legoomni/src/race/legoracers.cpp +++ b/LEGO1/lego/legoomni/src/race/legoracers.cpp @@ -180,6 +180,18 @@ void LegoRaceCar::FUN_10012de0() g_unk0x100f0b88 = 0; } +// FUNCTION: LEGO1 0x10012e00 +// FUNCTION: BETA10 0x100cb129 +void LegoRaceCar::FUN_10012e00() +{ + // Note the (likely unintentional) order of operations: `%` is executed before `/`, + // so the division is performed at runtime. + g_srtsl18to29Index = rand() % sizeof(g_srtsl18to29) / sizeof(g_srtsl18to29[0]); + g_srtsl6to10Index = rand() % sizeof(g_srtsl6to10) / sizeof(g_srtsl6to10[0]); + g_emptySoundKeyListIndex = rand() % sizeof(g_emptySoundKeyList) / sizeof(g_emptySoundKeyList[0]); + g_srtrhIndex = rand() % sizeof(g_srtrh) / sizeof(g_srtrh[0]); +} + // FUNCTION: LEGO1 0x10012e60 // FUNCTION: BETA10 0x100cb191 void LegoRaceCar::SetWorldSpeed(MxFloat p_worldSpeed) diff --git a/LEGO1/lego/legoomni/src/race/legoracespecial.cpp b/LEGO1/lego/legoomni/src/race/legoracespecial.cpp index 6bfb2565..e81bf457 100644 --- a/LEGO1/lego/legoomni/src/race/legoracespecial.cpp +++ b/LEGO1/lego/legoomni/src/race/legoracespecial.cpp @@ -16,6 +16,8 @@ DECOMP_SIZE_ASSERT(LegoCarRaceActor, 0x1a0) // GLOBAL: LEGO1 0x100f0c68 // STRING: LEGO1 0x100f0c5c +// GLOBAL: BETA10 0x101f5b04 +// STRING: BETA10 0x101f5b14 const char* g_raceState = "RACE_STATE"; // GLOBAL: LEGO1 0x100f7af0 @@ -24,6 +26,8 @@ const char* g_fuel = "FUEL"; // GLOBAL: LEGO1 0x100f0c6c // STRING: LEGO1 0x100f0c54 +// GLOBAL: BETA10 0x101f5b08 +// STRING: BETA10 0x101f5b20 const char* g_racing = "RACING"; // GLOBAL: LEGO1 0x100f7aec diff --git a/LEGO1/library_msvc.h b/LEGO1/library_msvc.h index f50de490..179f4222 100644 --- a/LEGO1/library_msvc.h +++ b/LEGO1/library_msvc.h @@ -744,4 +744,7 @@ // LIBRARY: BETA10 0x100f8ad0 // strcmp +// LIBRARY: BETA10 0x100f9610 +// rand + #endif diff --git a/LEGO1/omni/include/mxactionnotificationparam.h b/LEGO1/omni/include/mxactionnotificationparam.h index e110888e..79c18ee0 100644 --- a/LEGO1/omni/include/mxactionnotificationparam.h +++ b/LEGO1/omni/include/mxactionnotificationparam.h @@ -47,6 +47,7 @@ public: return new MxActionNotificationParam(m_type, m_sender, m_action, m_realloc); } // vtable+0x04 + // FUNCTION: BETA10 0x10017970 MxDSAction* GetAction() { return m_action; } protected: diff --git a/LEGO1/omni/include/mxentity.h b/LEGO1/omni/include/mxentity.h index 96348a65..7ca2c8b7 100644 --- a/LEGO1/omni/include/mxentity.h +++ b/LEGO1/omni/include/mxentity.h @@ -8,6 +8,7 @@ #include "mxtypes.h" // VTABLE: LEGO1 0x100d5390 +// VTABLE: BETA10 0x101b93e8 // SIZE 0x10 class MxEntity : public MxCore { public: @@ -33,6 +34,7 @@ public: } // FUNCTION: LEGO1 0x10001070 + // FUNCTION: BETA10 0x1000f3a0 virtual MxResult Create(MxS32 p_entityId, const MxAtomId& p_atomId) { m_entityId = p_entityId; @@ -47,7 +49,9 @@ public: return SUCCESS; } + // FUNCTION: BETA10 0x10031c60 MxS32 GetEntityId() { return m_entityId; } + MxAtomId& GetAtomId() { return m_atomId; } void SetEntityId(MxS32 p_entityId) { m_entityId = p_entityId; } diff --git a/reccmp-project.yml b/reccmp-project.yml index c4213582..e91334c8 100644 --- a/reccmp-project.yml +++ b/reccmp-project.yml @@ -19,3 +19,14 @@ targets: source-root: LEGO1 hash: sha256: d91435a40fa31f405fba33b03bd3bd40dcd4ca36ccf8ef6162c6c5ca0d7190e7 + ghidra: + ignore-types: + # these classes have been changed by hand to account for changes between LEGO1 and BETA10 + - LegoCarBuild + - LegoCarBuildAnimPresenter + - LegoRace + - LegoWorld + ignore-functions: + # strcpy, strlen, ... (arguments are imported incorrectly) + - 0x100fa200 + - 0x100f9780