isle-portable/LEGO1/lego/legoomni/src/common/legogamestate.cpp
foxtacles 7b114bbe59
Add multiplayer extension (#789)
* Add multiplayer extension

* Fix animation system to work when host is outside ISLE world

- Move TickHostSessions outside m_inIsleWorld gate so the host can
  coordinate animations from any world
- Load animation catalog early in HandleCreate so the host can
  coordinate before entering the ISLE world
- Use network-reported positions for remote player location detection
  instead of requiring spawned ROIs
- Always erase sessions at launch — the host's job ends when the
  animation starts; clients play and complete independently
- Replace BroadcastAnimComplete with locally-driven completion
  callbacks: host generates eventId at launch, clients cache
  completion JSON at start time, fire it when ScenePlayer finishes
- Make StopAnimation only do local cleanup (stop playback, cancel
  own interest, reset coordinator) without destroying the session
  host, so other players' sessions survive world transitions
- Broadcast state=0 in ResetAnimationState for full teardown paths
  (shutdown, reconnect, host migration) so clients aren't left with
  stale session state

* Fix use-after-free crash in ScenePlayer when remote player disconnects mid-animation

When a remote player's ROI is destroyed (disconnect, timeout, or respawn),
notify all active ScenePlayer instances to null out dangling references
before the ROI is freed. The animation engine already handles null ROI map
entries gracefully, so playback continues for remaining participants.

* Fix crash when performer's child ROIs are left dangling in ScenePlayer

NotifyROIDestroyed now walks the parent chain to also invalidate child
ROIs of the destroyed performer (head, limbs, etc.) that were placed
into the roiMap by BuildROIMap. The ancestor walk happens once; all
other fields are cleaned with simple pointer equality.

* Allow spectator to play click animation during scene playback

* Make PTATCAM track spectator ROI instead of camera in ScenePlayer

* Only regenerate emscripten version files when git state changes

Replace add_custom_target(ALL) with add_custom_command(OUTPUT) so the
version script only runs when .git/HEAD or the current branch ref file
changes, instead of on every build.

* Fix ROI name collision causing dangling pointers in NPC locomotion roiMaps

When ScenePlayer created cloned NPC ROIs for cooperative animations, it
renamed them to match the original character name and added them to the
ViewManager. This created a name collision: two ROIs with the same name.
The original game's AppendROIToScene searches by name and stops at the
first match, so if a locomotion BuildROIMap ran while the clone existed,
it could capture pointers to the clone's child ROIs. When the clone was
later destroyed (CleanupProps), those roiMap entries became dangling
pointers, crashing in AnimateWithTransform at roi.h:151 (SetVisibility).

Fix: use the alias mechanism (already supported by AnimUtils::BuildROIMap)
instead of renaming clones. Also unify all ROI name generation behind a
shared counter to prevent character manager key collisions.
2026-04-05 17:13:15 +02:00

1685 lines
48 KiB
C++

#include "legogamestate.h"
#include "3dmanager/lego3dmanager.h"
#include "act2main_actions.h"
#include "act3_actions.h"
#include "ambulance.h"
#include "carrace.h"
#include "carrace_actions.h"
#include "carracer_actions.h"
#include "copter_actions.h"
#include "define.h"
#include "dunebuggy.h"
#include "dunecar_actions.h"
#include "elevbott_actions.h"
#include "extensions/multiplayer.h"
#include "garage_actions.h"
#include "helicopter.h"
#include "histbook_actions.h"
#include "hospital_actions.h"
#include "infocenter.h"
#include "infodoor_actions.h"
#include "infomain_actions.h"
#include "infoscor_actions.h"
#include "isle.h"
#include "isle_actions.h"
#include "islepathactor.h"
#include "jetrace_actions.h"
#include "jetracer_actions.h"
#include "jetski.h"
#include "jetski_actions.h"
#include "jetskirace.h"
#include "jukebox_actions.h"
#include "jukeboxw_actions.h"
#include "legoanimationmanager.h"
#include "legobuildingmanager.h"
#include "legocharactermanager.h"
#include "legomain.h"
#include "legonavcontroller.h"
#include "legoplantmanager.h"
#include "legostate.h"
#include "legoutils.h"
#include "legovideomanager.h"
#include "legoworld.h"
#include "misc.h"
#include "mxbackgroundaudiomanager.h"
#include "mxmisc.h"
#include "mxnotificationmanager.h"
#include "mxnotificationparam.h"
#include "mxobjectfactory.h"
#include "mxstring.h"
#include "mxutilities.h"
#include "mxvariabletable.h"
#include "pizza.h"
#include "police_actions.h"
#include "racecar.h"
#include "racecar_actions.h"
#include "regbook_actions.h"
#include "roi/legoroi.h"
#include "scripts.h"
#include "sndanim_actions.h"
#include "towtrack.h"
#include <SDL3/SDL_filesystem.h>
#include <SDL3/SDL_stdinc.h>
#include <assert.h>
#include <stdio.h>
using namespace Extensions;
DECOMP_SIZE_ASSERT(LegoGameState::Username, 0x0e)
DECOMP_SIZE_ASSERT(LegoGameState::ScoreItem, 0x2c)
DECOMP_SIZE_ASSERT(LegoGameState::History, 0x374)
DECOMP_SIZE_ASSERT(LegoGameState, 0x430)
DECOMP_SIZE_ASSERT(ColorStringStruct, 0x08)
DECOMP_SIZE_ASSERT(LegoBackgroundColor, 0x30)
DECOMP_SIZE_ASSERT(LegoFullScreenMovie, 0x24)
// GLOBAL: LEGO1 0x100f3e40
// STRING: LEGO1 0x100f3e3c
const char* g_fileExtensionGS = ".GS";
// GLOBAL: LEGO1 0x100f3e44
// STRING: LEGO1 0x100f3e30
const char* g_playersGSI = "Players.gsi";
// GLOBAL: LEGO1 0x100f3e48
// STRING: LEGO1 0x100f3e24
const char* g_historyGSI = "History.gsi";
// This is a pointer to the end of the global variable name table, which has
// the text "END_OF_VARIABLES" in it.
// TODO: make g_endOfVariables reference the actual end of the variable array.
// GLOBAL: LEGO1 0x100f3e50
// STRING: LEGO1 0x100f3e00
// GLOBAL: BETA10 0x101ed5dc
// STRING: BETA10 0x101ed768
const char* g_endOfVariables = "END_OF_VARIABLES";
// GLOBAL: LEGO1 0x100f3e58
ColorStringStruct g_colorSaveData[43] = {
{"c_dbbkfny0", "lego red"}, {"c_dbbkxly0", "lego white"}, // dunebuggy back fender, dunebuggy back axle
{"c_chbasey0", "lego black"}, {"c_chbacky0", "lego black"}, // copter base, copter back
{"c_chdishy0", "lego white"}, {"c_chhorny0", "lego black"}, // copter dish, copter horn
{"c_chljety1", "lego black"}, {"c_chrjety1", "lego black"}, // copter left jet, copter right jet
{"c_chmidly0", "lego black"}, {"c_chmotry0", "lego blue"}, // copter middle, copter motor
{"c_chsidly0", "lego black"}, {"c_chsidry0", "lego black"}, // copter side left, copter side right
{"c_chstuty0", "lego black"}, {"c_chtaily0", "lego black"}, // copter skids, copter tail
{"c_chwindy1", "lego black"}, {"c_dbfbrdy0", "lego red"}, // copter windshield, dunebuggy body
{"c_dbflagy0", "lego yellow"}, {"c_dbfrfny4", "lego red"}, // dunebuggy flag, dunebuggy front fender
{"c_dbfrxly0", "lego white"}, {"c_dbhndly0", "lego white"}, // dunebuggy front axle, dunebuggy handlebar
{"c_dbltbry0", "lego white"}, {"c_jsdashy0", "lego white"}, // dunebuggy rear lights, jetski dash
{"c_jsexhy0", "lego black"}, {"c_jsfrnty5", "lego black"}, // jetski exhaust, jetski front
{"c_jshndly0", "lego red"}, {"c_jslsidy0", "lego black"}, // jetski handlebar, jetski left side
{"c_jsrsidy0", "lego black"}, {"c_jsskiby0", "lego red"}, // jetski right side, jetski base
{"c_jswnshy5", "lego white"}, {"c_rcbacky6", "lego green"}, // jetski windshield, racecar back
{"c_rcedgey0", "lego green"}, {"c_rcfrmey0", "lego red"}, // racecar edge, racecar frame
{"c_rcfrnty6", "lego green"}, {"c_rcmotry0", "lego white"}, // racecar front, racecar motor
{"c_rcsidey0", "lego green"}, {"c_rcstery0", "lego white"}, // racecar side, racecar steering wheel
{"c_rcstrpy0", "lego yellow"}, {"c_rctailya", "lego white"}, // racecar stripe, racecar tail
{"c_rcwhl1y0", "lego white"}, {"c_rcwhl2y0", "lego white"}, // racecar wheels 1, racecar wheels 2
{"c_jsbasey0", "lego white"}, {"c_chblady0", "lego black"}, // jetski base, copter blades
{"c_chseaty0", "lego white"}, // copter seat
};
// NOTE: This offset = the end of the variables table, the last entry
// in that table is a special entry, the string "END_OF_VARIABLES"
extern const char* g_endOfVariables;
// GLOBAL: LEGO1 0x100f3fb0
// STRING: LEGO1 0x100f3a18
const char* g_delimiter = " \t";
// GLOBAL: LEGO1 0x100f3fb4
// STRING: LEGO1 0x100f3bf0
const char* g_set = "set";
// GLOBAL: LEGO1 0x100f3fb8
// STRING: LEGO1 0x100f0cdc
const char* g_reset = "reset";
// GLOBAL: LEGO1 0x100f3fbc
// STRING: LEGO1 0x100f3be8
const char* g_strEnable = "enable";
// GLOBAL: LEGO1 0x100f3fc0
// STRING: LEGO1 0x100f3bf4
const char* g_strDisable = "disable";
const InternationalCharacter LegoGameState::g_intCharacters[8] = {
{0xe4, "Aum_Bitmap", 29},
{0xf6, "Oum_Bitmap", 30},
{0xdf, "Beta_Bitmap", 31},
{0xfc, "Uum_Bitmap", 32},
{0xe5, "Ao_Bitmap", 29},
{0xe6, "Ae_Bitmap", 30},
{0xf8, "Oz_Bitmap", 31},
{0xf1, "Ene_Bitmap", 29}
};
// FUNCTION: LEGO1 0x10039550
LegoGameState::LegoGameState()
{
SetColors();
SetROIColorOverride();
m_stateCount = 0;
m_actorId = 0;
m_savePath = NULL;
m_stateArray = NULL;
m_jukeboxMusic = JukeboxScript::c_noneJukebox;
m_currentArea = e_undefined;
m_previousArea = e_undefined;
m_savedPreviousArea = e_undefined;
m_playerCount = 0;
m_isDirty = FALSE;
m_loadedAct = e_actNotFound;
SetCurrentAct(e_act1);
m_backgroundColor = new LegoBackgroundColor("backgroundcolor", "set 56 54 68");
VariableTable()->SetVariable(m_backgroundColor);
m_tempBackgroundColor = new LegoBackgroundColor("tempBackgroundColor", "set 56 54 68");
VariableTable()->SetVariable(m_tempBackgroundColor);
m_fullScreenMovie = new LegoFullScreenMovie("fsmovie", "disable");
VariableTable()->SetVariable(m_fullScreenMovie);
VariableTable()->SetVariable("lightposition", "2");
SerializeScoreHistory(LegoFile::c_read);
}
// FUNCTION: LEGO1 0x10039720
LegoGameState::~LegoGameState()
{
LegoROI::SetColorOverride(NULL);
if (m_stateCount) {
for (MxS16 i = 0; i < m_stateCount; i++) {
LegoState* state = m_stateArray[i];
if (state) {
delete state;
}
}
delete[] m_stateArray;
}
delete[] m_savePath;
delete LegoOmni::GetInstance()->GetUserActor();
}
// FUNCTION: LEGO1 0x10039780
// FUNCTION: BETA10 0x10083d43
void LegoGameState::SetActor(MxU8 p_actorId)
{
if (p_actorId) {
m_actorId = p_actorId;
}
LegoPathActor* oldActor = UserActor();
SetUserActor(NULL);
IslePathActor* newActor = new IslePathActor();
const char* actorName = LegoActor::GetActorName(m_actorId);
LegoROI* roi = CharacterManager()->GetActorROI(actorName, FALSE);
MxDSAction action;
action.SetAtomId(*g_isleScript);
action.SetObjectId(100000);
newActor->Create(action);
newActor->SetActorId(p_actorId);
newActor->SetROI(roi, FALSE, FALSE);
if (oldActor) {
newActor->GetROI()->SetLocal2World(oldActor->GetROI()->GetLocal2World());
newActor->SetBoundary(oldActor->GetBoundary());
delete oldActor;
}
newActor->ClearFlag(LegoEntity::c_managerOwned);
SetUserActor(newActor);
}
// FUNCTION: LEGO1 0x10039910
void LegoGameState::RemoveActor()
{
LegoPathActor* actor = UserActor();
SetUserActor(NULL);
delete actor;
m_actorId = 0;
}
// FUNCTION: LEGO1 0x10039940
// FUNCTION: BETA10 0x10084069
void LegoGameState::ResetROI()
{
if (m_actorId) {
LegoPathActor* actor = UserActor();
if (actor) {
LegoROI* roi = actor->GetROI();
if (roi) {
VideoManager()->Get3DManager()->Remove(*roi);
VideoManager()->Get3DManager()->Add(*roi);
}
}
}
}
// FUNCTION: LEGO1 0x10039980
// FUNCTION: BETA10 0x100840e4
MxResult LegoGameState::Save(MxULong p_slot)
{
InfocenterState* infocenterState = (InfocenterState*) GameState()->GetState("InfocenterState");
if (!infocenterState || !infocenterState->HasRegistered()) {
return SUCCESS;
}
MxResult result = FAILURE;
LegoFile storage;
MxVariableTable* variableTable = VariableTable();
MxS16 count = 0;
MxU32 i;
MxS32 j;
MxU16 area;
MxString savePath;
GetFileSavePath(&savePath, p_slot);
if (storage.Open(savePath.GetData(), LegoFile::c_write) == FAILURE) {
goto done;
}
storage.WriteS32(0x1000c);
storage.WriteS16(m_currentPlayerId);
storage.WriteU16(m_currentAct);
storage.WriteU8(m_actorId);
for (i = 0; i < sizeOfArray(g_colorSaveData); i++) {
if (WriteVariable(&storage, variableTable, g_colorSaveData[i].m_targetName) == FAILURE) {
goto done;
}
}
if (WriteVariable(&storage, variableTable, "backgroundcolor") == FAILURE) {
goto done;
}
if (WriteVariable(&storage, variableTable, "lightposition") == FAILURE) {
goto done;
}
WriteEndOfVariables(&storage);
CharacterManager()->Write(&storage);
PlantManager()->Write(&storage);
result = BuildingManager()->Write(&storage);
for (j = 0; j < m_stateCount; j++) {
if (m_stateArray[j]->IsSerializable()) {
count++;
}
}
storage.WriteS16(count);
for (j = 0; j < m_stateCount; j++) {
if (m_stateArray[j]->IsSerializable()) {
m_stateArray[j]->Serialize(&storage);
}
}
area = m_savedPreviousArea;
storage.WriteU16(area);
SerializeScoreHistory(LegoFile::c_write);
m_isDirty = FALSE;
EmitGameEvent(e_saveSlotWritten, (void*) (intptr_t) p_slot);
done:
return result;
}
// FUNCTION: LEGO1 0x10039bf0
MxResult LegoGameState::DeleteState()
{
MxS16 stateCount = m_stateCount;
LegoState** stateArray = m_stateArray;
m_stateCount = 0;
m_stateArray = NULL;
for (MxS32 count = 0; count < stateCount; count++) {
if (!stateArray[count]->Reset() && stateArray[count]->IsSerializable()) {
delete stateArray[count];
}
else {
RegisterState(stateArray[count]);
stateArray[count] = NULL;
}
}
delete[] stateArray;
return SUCCESS;
}
// FUNCTION: LEGO1 0x10039c60
// FUNCTION: BETA10 0x10084329
MxResult LegoGameState::Load(MxULong p_slot)
{
Extension<MultiplayerExt>::Call(MP::HandleBeforeSaveLoad);
MxResult result = FAILURE;
LegoFile storage;
MxVariableTable* variableTable = VariableTable();
MxString savePath;
GetFileSavePath(&savePath, p_slot);
if (storage.Open(savePath.GetData(), LegoFile::c_read) == FAILURE) {
goto done;
}
MxS32 version;
MxU32 status;
MxS16 count, actArea;
const char* lightPosition;
storage.ReadS32(version);
assert(version == 0x1000c);
if (version != 0x1000c) {
OmniError("Saved game version mismatch", 0);
goto done;
}
storage.ReadS16(m_currentPlayerId);
storage.ReadS16(actArea);
SetCurrentAct((Act) actArea);
storage.ReadU8(m_actorId);
if (m_actorId) {
SetActor(m_actorId);
}
do {
status = ReadVariable(&storage, variableTable);
if (status == 1) {
goto done;
}
} while (status != 2);
m_backgroundColor->SetLightColor();
lightPosition = VariableTable()->GetVariable("lightposition");
if (lightPosition) {
SetLightPosition(atoi(lightPosition));
}
if (CharacterManager()->Read(&storage) == FAILURE) {
goto done;
}
if (PlantManager()->Read(&storage) == FAILURE) {
goto done;
}
if (BuildingManager()->Read(&storage) == FAILURE) {
goto done;
}
if (DeleteState() != SUCCESS) {
goto done;
}
char stateName[80];
storage.ReadS16(count);
if (count) {
for (MxS16 i = 0; i < count; i++) {
storage.ReadString(stateName);
LegoState* state = GetState(stateName);
if (!state) {
state = CreateState(stateName);
if (!state) {
goto done;
}
}
state->Serialize(&storage);
}
}
storage.ReadS16(actArea);
if (m_currentAct == e_act1) {
m_savedPreviousArea = e_undefined;
}
else {
m_savedPreviousArea = (Area) actArea;
}
result = SUCCESS;
m_isDirty = FALSE;
Extension<MultiplayerExt>::Call(MP::HandleSaveLoaded);
done:
if (result != SUCCESS) {
OmniError("Game state loading was not successful!", 0);
}
return result;
}
// FUNCTION: LEGO1 0x10039f00
void LegoGameState::SetSavePath(char* p_savePath)
{
if (m_savePath != NULL) {
delete[] m_savePath;
}
if (p_savePath) {
m_savePath = new char[strlen(p_savePath) + 1];
strcpy(m_savePath, p_savePath);
}
else {
m_savePath = NULL;
}
}
// FUNCTION: LEGO1 0x10039f70
// FUNCTION: BETA10 0x1008483b
MxResult LegoGameState::WriteVariable(LegoStorage* p_storage, MxVariableTable* p_from, const char* p_variableName)
{
MxResult result = FAILURE;
const char* variableValue = p_from->GetVariable(p_variableName);
if (variableValue) {
MxU8 length = strlen(p_variableName);
if (p_storage->Write(&length, sizeof(length)) != SUCCESS) {
goto done;
}
if (p_storage->Write(p_variableName, length) != SUCCESS) {
goto done;
}
length = strlen(variableValue);
if (p_storage->Write(&length, sizeof(length)) != SUCCESS) {
goto done;
}
result = p_storage->Write(variableValue, length);
}
done:
return result;
}
// FUNCTION: LEGO1 0x1003a020
// FUNCTION: BETA10 0x10084928
MxResult LegoGameState::WriteEndOfVariables(LegoStorage* p_storage)
{
MxU8 len = strlen(g_endOfVariables);
if (p_storage->Write(&len, sizeof(len)) == SUCCESS) {
return p_storage->Write(g_endOfVariables, len);
}
return FAILURE;
}
// FUNCTION: LEGO1 0x1003a080
// FUNCTION: BETA10 0x1008498b
MxS32 LegoGameState::ReadVariable(LegoStorage* p_storage, MxVariableTable* p_to)
{
MxS32 result = 1;
MxU8 len;
if (p_storage->Read(&len, sizeof(MxU8)) != SUCCESS) {
goto done;
}
char varName[256];
assert(len < sizeof(varName));
if (p_storage->Read(varName, len) != SUCCESS) {
goto done;
}
varName[len] = '\0';
if (strcmp(varName, g_endOfVariables) == 0) {
// 2 -> "This was the last entry, done reading."
result = 2;
goto done;
}
if (p_storage->Read(&len, sizeof(MxU8)) != SUCCESS) {
goto done;
}
char value[256];
assert(len < sizeof(value));
if (p_storage->Read(value, len) != SUCCESS) {
goto done;
}
value[len] = '\0';
p_to->SetVariable(varName, value);
result = SUCCESS;
done:
return result;
}
// FUNCTION: LEGO1 0x1003a170
// FUNCTION: BETA10 0x10084b45
void LegoGameState::GetFileSavePath(MxString* p_outPath, MxS16 p_slotn)
{
char baseForSlot[2] = "0";
char path[1024] = "";
// Save path base
if (m_savePath != NULL) {
strcpy(path, m_savePath);
}
// Slot: "G0", "G1", ...
strcat(path, "\\G");
baseForSlot[0] += p_slotn;
strcat(path, baseForSlot);
// Extension: ".GS"
strcat(path, g_fileExtensionGS);
*p_outPath = MxString(path);
p_outPath->MapPathToFilesystem();
}
// FUNCTION: LEGO1 0x1003a2e0
void LegoGameState::SerializePlayersInfo(MxS16 p_flags)
{
LegoFile storage;
MxString playersGSI = MxString(m_savePath);
playersGSI += "\\";
playersGSI += g_playersGSI;
if (storage.Open(playersGSI.GetData(), p_flags) == SUCCESS) {
if (storage.IsReadMode()) {
storage.ReadS16(m_playerCount);
}
else if (storage.IsWriteMode()) {
storage.WriteS16(m_playerCount);
}
for (MxS16 i = 0; i < m_playerCount; i++) {
m_players[i].Serialize(&storage);
}
}
}
// FUNCTION: LEGO1 0x1003a3f0
MxResult LegoGameState::AddPlayer(Username& p_player)
{
MxString from, to;
if (m_playerCount == 9) {
GetFileSavePath(&from, 8);
SDL_RemovePath(from.GetData());
m_playerCount--;
}
for (MxS16 i = m_playerCount; i > 0; i--) {
m_players[i] = m_players[i - 1];
GetFileSavePath(&from, i - 1);
GetFileSavePath(&to, i);
SDL_RenamePath(from.GetData(), to.GetData());
}
m_playerCount++;
m_players[0].Set(p_player);
m_currentPlayerId = m_history.m_nextPlayerId;
m_history.m_nextPlayerId = m_currentPlayerId + 1;
m_history.WriteScoreHistory();
SetCurrentAct(e_act1);
return DeleteState();
}
// FUNCTION: LEGO1 0x1003a540
// FUNCTION: BETA10 0x10084fc4
void LegoGameState::SwitchPlayer(MxS16 p_playerId)
{
if (p_playerId > 0) {
MxString from, temp, to;
GetFileSavePath(&from, p_playerId);
GetFileSavePath(&temp, 36);
Username selectedName(m_players[p_playerId]);
SDL_RenamePath(from.GetData(), temp.GetData());
for (MxS16 i = p_playerId; i > 0; i--) {
m_players[i] = m_players[i - 1];
GetFileSavePath(&from, i - 1);
GetFileSavePath(&to, i);
SDL_RenamePath(from.GetData(), to.GetData());
}
m_players[0] = selectedName;
GetFileSavePath(&from, 0);
SDL_RenamePath(temp.GetData(), from.GetData());
}
if (Load(0) != SUCCESS) {
Init();
}
}
// FUNCTION: LEGO1 0x1003a6e0
MxS16 LegoGameState::FindPlayer(Username& p_player)
{
for (MxS16 i = 0; i < m_playerCount; i++) {
if (memcmp(&m_players[i], &p_player, sizeof(p_player)) == 0) {
return i;
}
}
return -1;
}
// FUNCTION: LEGO1 0x1003a720
// FUNCTION: BETA10 0x10085211
void LegoGameState::StopArea(Area p_area)
{
if (p_area == e_previousArea) {
p_area = m_previousArea;
}
switch (p_area) {
case e_isle:
InvokeAction(Extra::e_stop, *g_isleScript, IsleScript::c__Isle, NULL);
InvokeAction(Extra::e_close, *g_isleScript, IsleScript::c__Isle, NULL);
InvokeAction(Extra::e_close, *g_sndAnimScript, SndanimScript::c_SoundAndAnim_Action, NULL);
break;
case e_infomain:
InvokeAction(Extra::e_stop, *g_infomainScript, InfomainScript::c__InfoMain, NULL);
InvokeAction(Extra::e_close, *g_infomainScript, InfomainScript::c__InfoMain, NULL);
break;
case e_infodoor:
InvokeAction(Extra::e_stop, *g_infodoorScript, InfodoorScript::c__StartUp, NULL);
InvokeAction(Extra::e_close, *g_infodoorScript, InfodoorScript::c__StartUp, NULL);
break;
case e_elevbott:
InvokeAction(Extra::e_stop, *g_elevbottScript, ElevbottScript::c__StartUp, NULL);
InvokeAction(Extra::e_close, *g_elevbottScript, ElevbottScript::c__StartUp, NULL);
break;
case e_elevride:
case e_elevride2:
RemoveFromWorld(*g_isleScript, IsleScript::c_ElevRide_Background_Bitmap, *g_isleScript, IsleScript::c__Isle);
RemoveFromWorld(*g_isleScript, IsleScript::c_ElevRide_Info_Ctl, *g_isleScript, IsleScript::c__Isle);
RemoveFromWorld(*g_isleScript, IsleScript::c_ElevRide_Two_Ctl, *g_isleScript, IsleScript::c__Isle);
RemoveFromWorld(*g_isleScript, IsleScript::c_ElevRide_Three_Ctl, *g_isleScript, IsleScript::c__Isle);
RemoveFromWorld(*g_isleScript, IsleScript::c_Meter1_3_Bitmap, *g_isleScript, IsleScript::c__Isle);
RemoveFromWorld(*g_isleScript, IsleScript::c_Meter2_3_Bitmap, *g_isleScript, IsleScript::c__Isle);
RemoveFromWorld(*g_isleScript, IsleScript::c_Meter3_1_Bitmap, *g_isleScript, IsleScript::c__Isle);
RemoveFromWorld(*g_isleScript, IsleScript::c_Meter3_2_Bitmap, *g_isleScript, IsleScript::c__Isle);
RemoveFromWorld(*g_isleScript, IsleScript::c_Meter2_1_Bitmap, *g_isleScript, IsleScript::c__Isle);
RemoveFromWorld(*g_isleScript, IsleScript::c_Meter1_2_Bitmap, *g_isleScript, IsleScript::c__Isle);
RemoveFromWorld(*g_isleScript, IsleScript::c_Meter3_Bitmap, *g_isleScript, IsleScript::c__Isle);
break;
case e_elevopen:
RemoveFromWorld(*g_isleScript, IsleScript::c_ElevOpen_Background_Bitmap, *g_isleScript, IsleScript::c__Isle);
RemoveFromWorld(*g_isleScript, IsleScript::c_ElevOpen_LeftArrow_Ctl, *g_isleScript, IsleScript::c__Isle);
RemoveFromWorld(*g_isleScript, IsleScript::c_ElevOpen_RightArrow_Ctl, *g_isleScript, IsleScript::c__Isle);
break;
case e_seaview:
RemoveFromWorld(*g_isleScript, IsleScript::c_SeaView_Background_Bitmap, *g_isleScript, IsleScript::c__Isle);
RemoveFromWorld(*g_isleScript, IsleScript::c_SeaView_LeftArrow_Ctl, *g_isleScript, IsleScript::c__Isle);
RemoveFromWorld(*g_isleScript, IsleScript::c_SeaView_RightArrow_Ctl, *g_isleScript, IsleScript::c__Isle);
break;
case e_observe:
RemoveFromWorld(*g_isleScript, IsleScript::c_Observe_Background_Bitmap, *g_isleScript, IsleScript::c__Isle);
RemoveFromWorld(*g_isleScript, IsleScript::c_Observe_LeftArrow_Ctl, *g_isleScript, IsleScript::c__Isle);
RemoveFromWorld(*g_isleScript, IsleScript::c_Observe_RightArrow_Ctl, *g_isleScript, IsleScript::c__Isle);
RemoveFromWorld(*g_isleScript, IsleScript::c_Observe_Plane_Ctl, *g_isleScript, IsleScript::c__Isle);
RemoveFromWorld(*g_isleScript, IsleScript::c_Observe_Sun_Ctl, *g_isleScript, IsleScript::c__Isle);
RemoveFromWorld(*g_isleScript, IsleScript::c_Observe_Moon_Ctl, *g_isleScript, IsleScript::c__Isle);
RemoveFromWorld(*g_isleScript, IsleScript::c_Observe_SkyColor_Ctl, *g_isleScript, IsleScript::c__Isle);
RemoveFromWorld(*g_isleScript, IsleScript::c_Observe_LCab_Ctl, *g_isleScript, IsleScript::c__Isle);
RemoveFromWorld(*g_isleScript, IsleScript::c_Observe_RCab_Ctl, *g_isleScript, IsleScript::c__Isle);
RemoveFromWorld(*g_isleScript, IsleScript::c_Observe_GlobeRArrow_Ctl, *g_isleScript, IsleScript::c__Isle);
RemoveFromWorld(*g_isleScript, IsleScript::c_Observe_GlobeLArrow_Ctl, *g_isleScript, IsleScript::c__Isle);
RemoveFromWorld(*g_isleScript, IsleScript::c_Observe_Globe1_Bitmap, *g_isleScript, IsleScript::c__Isle);
RemoveFromWorld(*g_isleScript, IsleScript::c_Observe_Globe2_Bitmap, *g_isleScript, IsleScript::c__Isle);
RemoveFromWorld(*g_isleScript, IsleScript::c_Observe_Globe3_Bitmap, *g_isleScript, IsleScript::c__Isle);
RemoveFromWorld(*g_isleScript, IsleScript::c_Observe_Globe4_Bitmap, *g_isleScript, IsleScript::c__Isle);
RemoveFromWorld(*g_isleScript, IsleScript::c_Observe_Globe5_Bitmap, *g_isleScript, IsleScript::c__Isle);
RemoveFromWorld(*g_isleScript, IsleScript::c_Observe_Globe6_Bitmap, *g_isleScript, IsleScript::c__Isle);
RemoveFromWorld(*g_isleScript, IsleScript::c_Observe_Draw1_Ctl, *g_isleScript, IsleScript::c__Isle);
RemoveFromWorld(*g_isleScript, IsleScript::c_Observe_Draw2_Ctl, *g_isleScript, IsleScript::c__Isle);
RemoveFromWorld(*g_isleScript, IsleScript::c_Radio_Ctl, *g_isleScript, IsleScript::c__Isle);
break;
case e_elevdown:
RemoveFromWorld(*g_isleScript, IsleScript::c_ElevDown_Background_Bitmap, *g_isleScript, IsleScript::c__Isle);
RemoveFromWorld(*g_isleScript, IsleScript::c_ElevDown_LeftArrow_Ctl, *g_isleScript, IsleScript::c__Isle);
RemoveFromWorld(*g_isleScript, IsleScript::c_ElevDown_RightArrow_Ctl, *g_isleScript, IsleScript::c__Isle);
RemoveFromWorld(*g_isleScript, IsleScript::c_ElevDown_Elevator_Ctl, *g_isleScript, IsleScript::c__Isle);
break;
case e_regbook:
InvokeAction(Extra::e_stop, *g_regbookScript, RegbookScript::c__StartUp, NULL);
InvokeAction(Extra::e_close, *g_regbookScript, RegbookScript::c__StartUp, NULL);
break;
case e_infoscor:
InvokeAction(Extra::e_stop, *g_infoscorScript, InfoscorScript::c__StartUp, NULL);
InvokeAction(Extra::e_close, *g_infoscorScript, InfoscorScript::c__StartUp, NULL);
break;
case e_jetrace:
InvokeAction(Extra::e_stop, *g_jetraceScript, JetraceScript::c__JetRace_World, NULL);
InvokeAction(Extra::e_close, *g_jetraceScript, JetraceScript::c__JetRace_World, NULL);
InvokeAction(Extra::e_close, *g_jetracerScript, 0, NULL);
break;
case e_carrace:
InvokeAction(Extra::e_stop, *g_carraceScript, CarraceScript::c__CarRace_World, NULL);
InvokeAction(Extra::e_close, *g_carraceScript, CarraceScript::c__CarRace_World, NULL);
InvokeAction(Extra::e_close, *g_carracerScript, CarracerScript::c_nrt002pz_Anim, NULL);
break;
case e_garage:
Lego()->RemoveWorld(*g_garageScript, 0);
InvokeAction(Extra::e_stop, *g_garageScript, GarageScript::c__StartUp, NULL);
InvokeAction(Extra::e_close, *g_garageScript, GarageScript::c__StartUp, NULL);
break;
case e_garadoor:
RemoveFromWorld(*g_isleScript, IsleScript::c_GaraDoor_Background_Bitmap, *g_isleScript, IsleScript::c__Isle);
RemoveFromWorld(*g_isleScript, IsleScript::c_GaraDoor_LeftArrow_Ctl, *g_isleScript, IsleScript::c__Isle);
RemoveFromWorld(*g_isleScript, IsleScript::c_GaraDoor_RightArrow_Ctl, *g_isleScript, IsleScript::c__Isle);
RemoveFromWorld(*g_isleScript, IsleScript::c_GaraDoor_Door_Ctl, *g_isleScript, IsleScript::c__Isle);
break;
case e_hospital:
InvokeAction(Extra::e_stop, *g_hospitalScript, HospitalScript::c__StartUp, NULL);
InvokeAction(Extra::e_close, *g_hospitalScript, HospitalScript::c__StartUp, NULL);
break;
case e_police:
InvokeAction(Extra::e_stop, *g_policeScript, PoliceScript::c__StartUp, NULL);
InvokeAction(Extra::e_close, *g_policeScript, PoliceScript::c__StartUp, NULL);
break;
case e_polidoor:
RemoveFromWorld(*g_isleScript, IsleScript::c_PoliDoor_Background_Bitmap, *g_isleScript, IsleScript::c__Isle);
RemoveFromWorld(*g_isleScript, IsleScript::c_PoliDoor_LeftArrow_Ctl, *g_isleScript, IsleScript::c__Isle);
RemoveFromWorld(*g_isleScript, IsleScript::c_PoliDoor_RightArrow_Ctl, *g_isleScript, IsleScript::c__Isle);
RemoveFromWorld(*g_isleScript, IsleScript::c_PoliDoor_Door_Ctl, *g_isleScript, IsleScript::c__Isle);
break;
case e_copterbuild:
InvokeAction(Extra::e_stop, *g_jukeboxScript, JukeboxScript::c_HelicopterBuild_Movie, NULL);
InvokeAction(Extra::e_stop, *g_copterScript, CopterScript::c__StartUp, NULL);
InvokeAction(Extra::e_close, *g_copterScript, CopterScript::c__StartUp, NULL);
break;
case e_dunecarbuild:
InvokeAction(Extra::e_stop, *g_jukeboxScript, JukeboxScript::c_DuneCarBuild_Movie, NULL);
InvokeAction(Extra::e_stop, *g_dunecarScript, DunecarScript::c__StartUp, NULL);
InvokeAction(Extra::e_close, *g_dunecarScript, DunecarScript::c__StartUp, NULL);
break;
case e_jetskibuild:
InvokeAction(Extra::e_stop, *g_jukeboxScript, JukeboxScript::c_JetskiBuild_Movie, NULL);
InvokeAction(Extra::e_stop, *g_jetskiScript, JetskiScript::c__StartUp, NULL);
InvokeAction(Extra::e_close, *g_jetskiScript, JetskiScript::c__StartUp, NULL);
break;
case e_racecarbuild:
InvokeAction(Extra::e_stop, *g_jukeboxScript, JukeboxScript::c_RaceCarBuild_Movie, NULL);
InvokeAction(Extra::e_stop, *g_racecarScript, RacecarScript::c__StartUp, NULL);
InvokeAction(Extra::e_close, *g_racecarScript, RacecarScript::c__StartUp, NULL);
break;
case e_act2main:
if (m_currentArea != e_infomain) {
InvokeAction(Extra::e_stop, *g_act2mainScript, Act2mainScript::c__Act2Main, NULL);
InvokeAction(Extra::e_close, *g_act2mainScript, Act2mainScript::c__Act2Main, NULL);
}
break;
case e_act3script:
if (m_currentArea != e_infomain) {
InvokeAction(Extra::e_stop, *g_act3Script, Act3Script::c__Act3, NULL);
InvokeAction(Extra::e_close, *g_act3Script, Act3Script::c__Act3, NULL);
}
break;
case e_jukeboxw:
InvokeAction(Extra::e_stop, *g_jukeboxwScript, JukeboxwScript::c__StartUp, NULL);
InvokeAction(Extra::e_close, *g_jukeboxwScript, JukeboxwScript::c__StartUp, NULL);
break;
case e_histbook:
InvokeAction(Extra::e_disable, *g_histbookScript, HistbookScript::c__StartUp, NULL);
InvokeAction(Extra::e_stop, *g_histbookScript, HistbookScript::c__StartUp, NULL);
InvokeAction(Extra::e_close, *g_histbookScript, HistbookScript::c__StartUp, NULL);
break;
}
}
inline void LoadIsle()
{
LegoWorld* world = FindWorld(*g_isleScript, IsleScript::c__Isle);
if (world != NULL) {
if (!world->NoDisabledObjects()) {
NotificationManager()->Send(world, MxNotificationParam(c_notificationType20, NULL));
}
}
else {
InvokeAction(Extra::ActionType::e_opendisk, *g_isleScript, IsleScript::c__Isle, NULL);
}
}
// FUNCTION: LEGO1 0x1003b060
// FUNCTION: BETA10 0x10085adf
void LegoGameState::SwitchArea(Area p_area)
{
m_previousArea = m_currentArea;
m_currentArea = p_area;
Disable(TRUE, LegoOmni::c_disableInput | LegoOmni::c_disable3d);
BackgroundAudioManager()->Stop();
AnimationManager()->Suspend();
VideoManager()->SetUnk0x554(FALSE);
switch (p_area) {
case e_isle:
InvokeAction(Extra::ActionType::e_opendisk, *g_isleScript, IsleScript::c__Isle, NULL);
break;
case e_infomain:
VideoManager()->SetUnk0x554(TRUE);
InvokeAction(Extra::ActionType::e_opendisk, *g_infomainScript, InfomainScript::c__InfoMain, NULL);
break;
case e_infodoor:
VideoManager()->SetUnk0x554(TRUE);
InvokeAction(Extra::ActionType::e_opendisk, *g_infodoorScript, InfodoorScript::c__StartUp, NULL);
break;
case e_infocenterExited:
case e_jetraceFinished:
case e_jetraceExterior:
case e_jetskibuildExited:
case e_carraceExterior:
case e_racecarbuildExited:
case e_carraceFinished:
case e_pizzeriaExterior:
case e_garageExterior:
case e_hospitalExterior:
case e_hospitalExited:
case e_policeExterior:
case e_bike:
case e_dunecar:
case e_motocycle:
case e_copter:
case e_skateboard:
case e_jetski:
case e_vehicleExited:
LoadIsle();
break;
case e_elevbott:
InvokeAction(Extra::ActionType::e_opendisk, *g_elevbottScript, ElevbottScript::c__StartUp, NULL);
break;
case e_elevride:
case e_elevride2:
LoadIsle();
InvokeAction(Extra::ActionType::e_start, *g_isleScript, IsleScript::c_ElevRide, NULL);
break;
case e_elevopen:
VideoManager()->SetUnk0x554(TRUE);
InvokeAction(Extra::ActionType::e_start, *g_isleScript, IsleScript::c_ElevOpen, NULL);
break;
case e_seaview:
InvokeAction(Extra::ActionType::e_start, *g_isleScript, IsleScript::c_SeaView, NULL);
break;
case e_observe:
InvokeAction(Extra::ActionType::e_start, *g_isleScript, IsleScript::c_Observe, NULL);
break;
case e_elevdown:
InvokeAction(Extra::ActionType::e_start, *g_isleScript, IsleScript::c_ElevDown, NULL);
break;
case e_regbook:
VideoManager()->SetUnk0x554(TRUE);
InvokeAction(Extra::ActionType::e_opendisk, *g_regbookScript, RegbookScript::c__StartUp, NULL);
break;
case e_infoscor:
VideoManager()->SetUnk0x554(TRUE);
InvokeAction(Extra::ActionType::e_opendisk, *g_infoscorScript, InfoscorScript::c__StartUp, NULL);
break;
case e_jetrace:
if (m_previousArea == e_infomain) {
m_currentArea = e_jetraceFinished;
LoadIsle();
}
else {
InvokeAction(Extra::ActionType::e_opendisk, *g_jetraceScript, JetraceScript::c__JetRace_World, NULL);
}
break;
case e_carrace:
if (m_previousArea == e_infomain) {
m_currentArea = e_carraceExterior;
LoadIsle();
}
else {
InvokeAction(Extra::ActionType::e_opendisk, *g_carraceScript, CarraceScript::c__CarRace_World, NULL);
}
break;
case e_garage:
VideoManager()->SetUnk0x554(TRUE);
InvokeAction(Extra::ActionType::e_opendisk, *g_garageScript, GarageScript::c__StartUp, NULL);
break;
case e_garadoor:
LoadIsle();
VariableTable()->SetVariable("VISIBILITY", "Hide Gas");
UserActor()->ResetWorldTransform(FALSE);
NavController()->UpdateLocation(59); // LCAMZG1 in g_cameraLocations
VideoManager()->Get3DManager()->SetFrustrum(90, 0.1f, 250.0f);
InvokeAction(Extra::ActionType::e_start, *g_isleScript, IsleScript::c_GaraDoor, NULL);
break;
case e_garageExited: {
Act1State* state = (Act1State*) GameState()->GetState("Act1State");
LoadIsle();
if (state->GetState() == Act1State::e_transitionToTowtrack) {
VideoManager()->Get3DManager()->SetFrustrum(90, 0.1f, 250.0f);
}
else {
SetCameraControllerFromIsle();
UserActor()->ResetWorldTransform(TRUE);
AnimationManager()->Resume();
}
((IslePathActor*) UserActor())
->SpawnPlayer(
p_area,
TRUE,
IslePathActor::c_spawnBit1 | IslePathActor::c_playMusic | IslePathActor::c_spawnBit3
);
break;
}
case e_hospital:
VideoManager()->SetUnk0x554(TRUE);
InvokeAction(Extra::ActionType::e_opendisk, *g_hospitalScript, HospitalScript::c__StartUp, NULL);
break;
case e_policeExited:
LoadIsle();
SetCameraControllerFromIsle();
UserActor()->ResetWorldTransform(TRUE);
AnimationManager()->Resume();
((IslePathActor*) UserActor())
->SpawnPlayer(
p_area,
TRUE,
IslePathActor::c_spawnBit1 | IslePathActor::c_playMusic | IslePathActor::c_spawnBit3
);
break;
case e_police:
VideoManager()->SetUnk0x554(TRUE);
InvokeAction(Extra::ActionType::e_opendisk, *g_policeScript, PoliceScript::c__StartUp, NULL);
break;
case e_polidoor:
LoadIsle();
InvokeAction(Extra::ActionType::e_start, *g_isleScript, IsleScript::c_PoliDoor, NULL);
break;
case e_copterbuild:
VideoManager()->SetUnk0x554(TRUE);
InvokeAction(Extra::ActionType::e_opendisk, *g_copterScript, CopterScript::c__StartUp, NULL);
break;
case e_dunecarbuild:
VideoManager()->SetUnk0x554(TRUE);
InvokeAction(Extra::ActionType::e_opendisk, *g_dunecarScript, DunecarScript::c__StartUp, NULL);
break;
case e_jetskibuild:
VideoManager()->SetUnk0x554(TRUE);
InvokeAction(Extra::ActionType::e_opendisk, *g_jetskiScript, JetskiScript::c__StartUp, NULL);
break;
case e_racecarbuild:
VideoManager()->SetUnk0x554(TRUE);
InvokeAction(Extra::ActionType::e_opendisk, *g_racecarScript, RacecarScript::c__StartUp, NULL);
break;
case e_act2main: {
LegoWorld* act2main = FindWorld(*g_act2mainScript, Act2mainScript::c__Act2Main);
if (act2main == NULL) {
InvokeAction(Extra::ActionType::e_opendisk, *g_act2mainScript, Act2mainScript::c__Act2Main, NULL);
}
else {
act2main->Enable(TRUE);
}
break;
}
case e_act3script: {
LegoWorld* act3 = FindWorld(*g_act3Script, Act3Script::c__Act3);
if (act3 == NULL) {
InvokeAction(Extra::ActionType::e_opendisk, *g_act3Script, Act3Script::c__Act3, NULL);
}
else {
act3->Enable(TRUE);
}
break;
}
case e_jukeboxw:
VideoManager()->SetUnk0x554(TRUE);
InvokeAction(Extra::ActionType::e_opendisk, *g_jukeboxwScript, JukeboxwScript::c__StartUp, NULL);
break;
case e_jukeboxExterior:
LoadIsle();
break;
case e_histbook:
VideoManager()->SetUnk0x554(TRUE);
InvokeAction(Extra::ActionType::e_opendisk, *g_histbookScript, HistbookScript::c__StartUp, NULL);
break;
default:
break;
}
}
// FUNCTION: LEGO1 0x1003ba90
void LegoGameState::SetColors()
{
MxVariableTable* variableTable = VariableTable();
for (MxS32 i = 0; i < sizeOfArray(g_colorSaveData); i++) {
variableTable->SetVariable(g_colorSaveData[i].m_targetName, g_colorSaveData[i].m_colorName);
}
}
// FUNCTION: LEGO1 0x1003bac0
void LegoGameState::SetROIColorOverride()
{
LegoROI::SetColorOverride(&ROIColorOverride);
}
// FUNCTION: LEGO1 0x1003bad0
MxBool ROIColorOverride(const char* p_input, char* p_output, MxU32 p_copyLen)
{
if (p_output != NULL && p_copyLen != 0 &&
(SDL_strncasecmp(p_input, "INDIR-F-", strlen("INDIR-F-")) == 0 ||
SDL_strncasecmp(p_input, "INDIR-G-", strlen("INDIR-F-")) == 0)) {
char buf[256];
sprintf(buf, "c_%s", &p_input[strlen("INDIR-F-")]);
const char* value = VariableTable()->GetVariable(buf);
if (value != NULL) {
strncpy(p_output, value, p_copyLen);
p_output[p_copyLen - 1] = '\0';
return TRUE;
}
}
return FALSE;
}
// FUNCTION: LEGO1 0x1003bbb0
// FUNCTION: BETA10 0x10086280
LegoState* LegoGameState::GetState(const char* p_stateName)
{
for (MxS32 i = 0; i < m_stateCount; ++i) {
if (m_stateArray[i]->IsA(p_stateName)) {
return m_stateArray[i];
}
}
return NULL;
}
// FUNCTION: LEGO1 0x1003bc00
// FUNCTION: BETA10 0x100862fc
LegoState* LegoGameState::CreateState(const char* p_stateName)
{
// variable name verified by BETA10 0x10086341
LegoState* state = (LegoState*) ObjectFactory()->Create(p_stateName);
assert(state);
RegisterState(state);
return state;
}
// FUNCTION: LEGO1 0x1003bc30
// FUNCTION: BETA10 0x1008636e
void LegoGameState::RegisterState(LegoState* p_state)
{
MxS32 targetIndex;
for (targetIndex = 0; targetIndex < m_stateCount; ++targetIndex) {
if (m_stateArray[targetIndex]->IsA(p_state->ClassName())) {
break;
}
}
if (targetIndex == m_stateCount) {
LegoState** newBuffer = new LegoState*[m_stateCount + 1];
if (m_stateCount != 0) {
memcpy(newBuffer, m_stateArray, m_stateCount * sizeof(LegoState*));
delete[] m_stateArray;
}
newBuffer[m_stateCount++] = p_state;
m_stateArray = newBuffer;
}
else {
delete m_stateArray[targetIndex];
m_stateArray[targetIndex] = p_state;
}
}
// FUNCTION: LEGO1 0x1003bd00
void LegoGameState::Init()
{
m_backgroundColor->SetValue("set 56 54 68");
m_backgroundColor->SetLightColor();
m_tempBackgroundColor->SetValue("set 56 54 68");
VariableTable()->SetVariable("lightposition", "2");
SetLightPosition(2);
PlantManager()->Init();
BuildingManager()->Init();
CharacterManager()->Init();
AnimationManager()->Reset(TRUE);
SetColors();
RemoveActor();
DeleteState();
m_isDirty = FALSE;
FindLoadedAct();
SetCurrentAct(e_act1);
if (m_loadedAct == e_act1) {
Isle* isle = (Isle*) FindWorld(*g_isleScript, IsleScript::c__Isle);
Helicopter* copter = (Helicopter*) isle->Find(*g_copterScript, CopterScript::c_Helicopter_Actor);
if (copter) {
isle->RemoveActor(copter);
isle->RemoveVehicle(copter);
delete copter;
}
DuneBuggy* dunebuggy = (DuneBuggy*) isle->Find(*g_dunecarScript, DunecarScript::c_DuneBugy_Actor);
if (dunebuggy) {
isle->RemoveActor(dunebuggy);
isle->RemoveVehicle(dunebuggy);
delete dunebuggy;
}
Jetski* jetski = (Jetski*) isle->Find(*g_jetskiScript, JetskiScript::c_Jetski_Actor);
if (jetski) {
isle->RemoveActor(jetski);
isle->RemoveVehicle(jetski);
delete jetski;
}
RaceCar* racecar = (RaceCar*) isle->Find(*g_racecarScript, RacecarScript::c_RaceCar_Actor);
if (racecar) {
isle->RemoveActor(racecar);
isle->RemoveVehicle(racecar);
delete racecar;
}
}
m_savedPreviousArea = e_undefined;
}
// FUNCTION: BETA10 0x10086510
LegoBackgroundColor::LegoBackgroundColor()
{
m_h = 0.0f;
m_s = 0.0f;
m_v = 0.0f;
}
// FUNCTION: LEGO1 0x1003bfb0
// FUNCTION: BETA10 0x1008659d
LegoBackgroundColor::LegoBackgroundColor(const char* p_key, const char* p_value)
{
m_key = p_key;
m_key.ToUpperCase();
SetValue(p_value);
}
// FUNCTION: LEGO1 0x1003c070
// FUNCTION: BETA10 0x10086634
void LegoBackgroundColor::SetValue(const char* p_colorString)
{
m_value = p_colorString;
m_value.ToLowerCase();
LegoVideoManager* videomanager = VideoManager();
if (!videomanager || !p_colorString) {
return;
}
float convertedR, convertedG, convertedB;
char* colorStringCopy = strcpy(new char[strlen(p_colorString) + 1], p_colorString);
char* colorStringSplit = strtok(colorStringCopy, g_delimiter);
if (!strcmp(colorStringSplit, g_set)) {
colorStringSplit = strtok(0, g_delimiter);
if (colorStringSplit) {
m_h = (float) (atoi(colorStringSplit) * 0.01);
}
colorStringSplit = strtok(0, g_delimiter);
if (colorStringSplit) {
m_s = (float) (atoi(colorStringSplit) * 0.01);
}
colorStringSplit = strtok(0, g_delimiter);
if (colorStringSplit) {
m_v = (float) (atoi(colorStringSplit) * 0.01);
}
ConvertHSVToRGB(m_h, m_s, m_v, &convertedR, &convertedG, &convertedB);
videomanager->SetSkyColor(convertedR, convertedG, convertedB);
}
else if (!strcmp(colorStringSplit, g_reset)) {
ConvertHSVToRGB(m_h, m_s, m_v, &convertedR, &convertedG, &convertedB);
videomanager->SetSkyColor(convertedR, convertedG, convertedB);
}
delete[] colorStringCopy;
}
// FUNCTION: LEGO1 0x1003c230
// FUNCTION: BETA10 0x100867f9
void LegoBackgroundColor::ToggleDayNight(MxBool p_sun)
{
char buffer[30];
if (p_sun) {
m_s += 0.1;
if (m_s > 0.9) {
m_s = 1.0;
}
}
else {
m_s -= 0.1;
if (m_s < 0.1) {
m_s = 0.1;
}
}
sprintf(buffer, "set %d %d %d", (MxU32) (m_h * 100.0f), (MxU32) (m_s * 100.0f), (MxU32) (m_v * 100.0f));
m_value = buffer;
float convertedR, convertedG, convertedB;
ConvertHSVToRGB(m_h, m_s, m_v, &convertedR, &convertedG, &convertedB);
VideoManager()->SetSkyColor(convertedR, convertedG, convertedB);
SetLightColor(convertedR, convertedG, convertedB);
}
// FUNCTION: LEGO1 0x1003c330
// FUNCTION: BETA10 0x100868de
void LegoBackgroundColor::ToggleSkyColor()
{
char buffer[30];
m_h += 0.05;
if (m_h > 1.0) {
m_h -= 1.0;
}
sprintf(buffer, "set %d %d %d", (MxU32) (m_h * 100.0f), (MxU32) (m_s * 100.0f), (MxU32) (m_v * 100.0f));
m_value = buffer;
float convertedR, convertedG, convertedB;
ConvertHSVToRGB(m_h, m_s, m_v, &convertedR, &convertedG, &convertedB);
VideoManager()->SetSkyColor(convertedR, convertedG, convertedB);
SetLightColor(convertedR, convertedG, convertedB);
}
// FUNCTION: LEGO1 0x1003c400
// FUNCTION: BETA10 0x10086984
void LegoBackgroundColor::SetLightColor(float p_r, float p_g, float p_b)
{
if (!VideoManager()->GetVideoParam().Flags().GetLacksLightSupport()) {
// TODO: Computed constants based on what?
p_r *= 1. / 0.23;
p_g *= 1. / 0.63;
p_b *= 1. / 0.85;
if (p_r > 1.0) {
p_r = 1.0;
}
if (p_g > 1.0) {
p_g = 1.0;
}
if (p_b > 1.0) {
p_b = 1.0;
}
VideoManager()->Get3DManager()->GetLego3DView()->SetLightColor(FALSE, p_r, p_g, p_b);
VideoManager()->Get3DManager()->GetLego3DView()->SetLightColor(TRUE, p_r, p_g, p_b);
}
}
// FUNCTION: LEGO1 0x1003c4b0
void LegoBackgroundColor::SetLightColor()
{
float convertedR, convertedG, convertedB;
ConvertHSVToRGB(m_h, m_s, m_v, &convertedR, &convertedG, &convertedB);
SetLightColor(convertedR, convertedG, convertedB);
}
// FUNCTION: BETA10 0x10086a87
LegoFullScreenMovie::LegoFullScreenMovie()
{
}
// FUNCTION: LEGO1 0x1003c500
// FUNCTION: BETA10 0x10086af6
LegoFullScreenMovie::LegoFullScreenMovie(const char* p_key, const char* p_value)
{
m_key = p_key;
m_key.ToUpperCase();
SetValue(p_value);
}
// FUNCTION: LEGO1 0x1003c5c0
// FUNCTION: BETA10 0x10086b8d
void LegoFullScreenMovie::SetValue(const char* p_option)
{
m_value = p_option;
m_value.ToLowerCase();
LegoVideoManager* videomanager = VideoManager();
if (videomanager) {
if (!strcmp(m_value.GetData(), g_strEnable)) {
videomanager->EnableFullScreenMovie(TRUE);
}
else if (!strcmp(m_value.GetData(), g_strDisable)) {
videomanager->EnableFullScreenMovie(FALSE);
}
}
}
// FUNCTION: LEGO1 0x1003c670
LegoGameState::Username::Username()
{
memset(m_letters, -1, sizeof(m_letters));
}
// FUNCTION: LEGO1 0x1003c690
// FUNCTION: BETA10 0x10086c57
MxResult LegoGameState::Username::Serialize(LegoStorage* p_storage)
{
if (p_storage->IsReadMode()) {
for (MxS16 i = 0; i < (MxS16) sizeOfArray(m_letters); i++) {
p_storage->ReadS16(m_letters[i]);
}
}
else if (p_storage->IsWriteMode()) {
for (MxS16 i = 0; i < (MxS16) sizeOfArray(m_letters); i++) {
p_storage->WriteS16(m_letters[i]);
}
}
return SUCCESS;
}
// FUNCTION: LEGO1 0x1003c710
// FUNCTION: BETA10 0x10086d0c
LegoGameState::Username& LegoGameState::Username::operator=(const Username& p_other)
{
memcpy(m_letters, p_other.m_letters, sizeof(m_letters));
return *this;
}
// FUNCTION: LEGO1 0x1003c740
// FUNCTION: BETA10 0x10086d39
MxResult LegoGameState::ScoreItem::Serialize(LegoStorage* p_storage)
{
if (p_storage->IsReadMode()) {
p_storage->ReadS16(m_totalScore);
for (MxS32 i = 0; i < 5; i++) {
for (MxS32 j = 0; j < 5; j++) {
p_storage->ReadU8(m_scores[i][j]);
}
}
m_name.Serialize(p_storage);
p_storage->ReadS16(m_playerId);
}
else if (p_storage->IsWriteMode()) {
p_storage->WriteS16(m_totalScore);
for (MxS32 i = 0; i < 5; i++) {
for (MxS32 j = 0; j < 5; j++) {
p_storage->WriteU8(m_scores[i][j]);
}
}
m_name.Serialize(p_storage);
p_storage->WriteS16(m_playerId);
}
return SUCCESS;
}
// FUNCTION: LEGO1 0x1003c830
// FUNCTION: BETA10 0x10086e87
LegoGameState::History::History()
{
m_count = 0;
m_nextPlayerId = 0;
}
// FUNCTION: LEGO1 0x1003c870
// FUNCTION: BETA10 0x10086ec9
void LegoGameState::History::WriteScoreHistory()
{
MxS16 totalScore = 0;
MxU8 scores[5][5];
InfocenterState* state = (InfocenterState*) GameState()->GetState("InfocenterState");
if (!state->m_letters[0]) {
return;
}
JetskiRaceState* jetskiRaceState = (JetskiRaceState*) GameState()->GetState("JetskiRaceState");
CarRaceState* carRaceState = (CarRaceState*) GameState()->GetState("CarRaceState");
TowTrackMissionState* towTrackMissionState = (TowTrackMissionState*) GameState()->GetState("TowTrackMissionState");
PizzaMissionState* pizzaMissionState = (PizzaMissionState*) GameState()->GetState("PizzaMissionState");
AmbulanceMissionState* ambulanceMissionState =
(AmbulanceMissionState*) GameState()->GetState("AmbulanceMissionState");
for (MxS32 actor = 1; actor <= 5; actor++) {
scores[0][actor - 1] = carRaceState ? carRaceState->GetState(actor)->GetHighScore() : 0;
totalScore += scores[0][actor - 1];
#ifdef BETA10
// likely a bug in BETA10
scores[1][actor - 1] = carRaceState ? carRaceState->GetState(actor)->GetHighScore() : 0;
#else
scores[1][actor - 1] = jetskiRaceState ? jetskiRaceState->GetState(actor)->GetHighScore() : 0;
#endif
totalScore += scores[1][actor - 1];
scores[2][actor - 1] = pizzaMissionState ? pizzaMissionState->GetHighScore(actor) : 0;
totalScore += scores[2][actor - 1];
scores[3][actor - 1] = towTrackMissionState ? towTrackMissionState->GetHighScore(actor) : 0;
totalScore += scores[3][actor - 1];
scores[4][actor - 1] = ambulanceMissionState ? ambulanceMissionState->GetHighScore(actor) : 0;
totalScore += scores[4][actor - 1];
}
MxS32 playerScoreHistoryIndex;
ScoreItem* p_scorehist =
FindPlayerInScoreHistory(GameState()->m_players, GameState()->m_currentPlayerId, playerScoreHistoryIndex);
#ifdef BETA10
if (!p_scorehist) {
MxS32 playerScoreRank;
// LINE: BETA10 0x100870ee
for (playerScoreRank = 0; playerScoreRank < m_count; playerScoreRank++) {
if (totalScore > m_scores[m_indices[playerScoreRank]].m_totalScore) {
break;
}
}
// LINE: BETA10 0x1008713f
if (playerScoreRank < m_count) {
if (m_count < 20) {
playerScoreHistoryIndex = m_count++;
}
else {
playerScoreHistoryIndex = m_indices[19];
}
MxS32 max = m_count - 1;
for (MxS32 j = max; playerScoreRank < j; j--) {
m_indices[j - 1] = m_indices[j - 2];
}
m_indices[playerScoreRank] = playerScoreHistoryIndex;
p_scorehist = &m_scores[playerScoreHistoryIndex];
}
else if (playerScoreRank < 20) {
m_indices[m_count] = m_count;
p_scorehist = &m_scores[m_count++];
}
}
else if (p_scorehist->m_totalScore != totalScore) {
assert(totalScore > p_scorehist->m_totalScore);
for (MxS32 i = playerScoreHistoryIndex; i > 0 && m_indices[i - 1] < m_indices[i]; i--) {
MxU8 tmp = m_indices[i - 1];
m_indices[i - 1] = m_indices[i];
m_indices[i] = tmp;
}
}
if (p_scorehist) {
p_scorehist->m_totalScore = totalScore;
memcpy(p_scorehist->m_scores[0], scores[0], sizeof(scores));
p_scorehist->m_name = GameState()->m_players[0];
p_scorehist->m_playerId = GameState()->m_currentPlayerId;
}
#else
if (p_scorehist != NULL) {
p_scorehist->m_totalScore = totalScore;
memcpy(p_scorehist->m_scores, scores, sizeof(p_scorehist->m_scores));
}
else {
if (m_count < (MxS16) sizeOfArray(m_scores)) {
m_scores[m_count].m_totalScore = totalScore;
memcpy(m_scores[m_count].m_scores, scores, sizeof(m_scores[m_count].m_scores));
m_scores[m_count].m_name = GameState()->m_players[0];
m_scores[m_count].m_playerId = GameState()->m_currentPlayerId;
m_count++;
}
else if (m_scores[19].m_totalScore <= totalScore) {
m_scores[19].m_totalScore = totalScore;
memcpy(m_scores[19].m_scores, scores, sizeof(m_scores[19].m_scores));
m_scores[19].m_name = GameState()->m_players[0];
m_scores[19].m_playerId = GameState()->m_currentPlayerId;
}
}
ScoreItem tmpItem;
for (MxS32 i = m_count - 1; i >= 0; i--) {
for (MxS32 j = 1; j <= i; j++) {
if (m_scores[j].m_totalScore > m_scores[j - 1].m_totalScore) {
tmpItem = m_scores[j - 1];
m_scores[j - 1] = m_scores[j];
m_scores[j] = tmpItem;
}
}
}
#endif
}
// FUNCTION: LEGO1 0x1003cc90
// FUNCTION: BETA10 0x1008732a
LegoGameState::ScoreItem* LegoGameState::History::FindPlayerInScoreHistory(
LegoGameState::Username* p_player,
MxS16 p_playerId,
MxS32& p_playerScoreHistoryIndex
)
{
MxS32 i = 0;
for (; i < m_count; i++) {
if (!memcmp(p_player, &m_scores[i].m_name, sizeof(*p_player)) && m_scores[i].m_playerId == p_playerId) {
break;
}
}
p_playerScoreHistoryIndex = i;
if (i >= m_count) {
return NULL;
}
return &m_scores[i];
}
// FUNCTION: LEGO1 0x1003ccf0
// FUNCTION: BETA10 0x100873e7
MxResult LegoGameState::History::Serialize(LegoStorage* p_storage)
{
if (p_storage->IsReadMode()) {
p_storage->ReadS16(m_nextPlayerId);
p_storage->ReadS16(m_count);
for (MxS16 i = 0; i < m_count; i++) {
MxS16 j;
p_storage->ReadS16(j);
m_scores[i].Serialize(p_storage);
}
}
else if (p_storage->IsWriteMode()) {
p_storage->WriteS16(m_nextPlayerId);
p_storage->WriteS16(m_count);
for (MxS16 i = 0; i < m_count; i++) {
p_storage->WriteS16(i);
m_scores[i].Serialize(p_storage);
}
}
return SUCCESS;
}
// FUNCTION: LEGO1 0x1003cdd0
void LegoGameState::SerializeScoreHistory(MxS16 p_flags)
{
LegoFile storage;
MxString savePath(m_savePath);
savePath += "\\";
savePath += g_historyGSI;
if (p_flags == LegoFile::c_write) {
m_history.WriteScoreHistory();
}
if (storage.Open(savePath.GetData(), p_flags) == SUCCESS) {
m_history.Serialize(&storage);
}
}
// FUNCTION: LEGO1 0x1003cea0
// FUNCTION: BETA10 0x10017840
void LegoGameState::SetCurrentAct(Act p_currentAct)
{
m_currentAct = p_currentAct;
}
// FUNCTION: LEGO1 0x1003ceb0
void LegoGameState::FindLoadedAct()
{
if (FindWorld(*g_isleScript, IsleScript::c__Isle)) {
m_loadedAct = e_act1;
}
else if (FindWorld(*g_act2mainScript, Act2mainScript::c__Act2Main)) {
m_loadedAct = e_act2;
}
else if (FindWorld(*g_act3Script, Act3Script::c__Act3)) {
m_loadedAct = e_act3;
}
else {
m_loadedAct = e_actNotFound;
}
}