From eb6d2b872877101c68e09e6c4e5020082fbc656e Mon Sep 17 00:00:00 2001 From: foxtacles Date: Sat, 7 Mar 2026 20:55:00 -0800 Subject: [PATCH] Sync sky light (#9) * Sync sky color and light position in multiplayer Add ENTITY_SKY and ENTITY_LIGHT to the WorldEvent system so the host controls sky color (hue/saturation via observatory sun/moon/palette buttons) and light position (globe arrows) with the same host-authoritative pattern used for plants and buildings. Non-host players send requests to the host who applies and broadcasts. Sky/light state is appended to the world snapshot so joining players get the current values. https://claude.ai/code/session_01X2cPVQEo7c92wpWA7QPPMG * Clean up sky/light sync: remove debug logging, DRY apply logic, fix host routing - Extract ApplySkyLightState helper to deduplicate sky/light apply code between RestoreSkyLightState and HandleWorldSnapshot - Remove all SDL_Log debug calls and SDL_log.h includes - Remove dead OnWorldEnabled method from WorldStateSync (replaced by OnHostChanged in OnSaveLoaded) - Fix HandleSkyLightMutation host path: return FALSE to let local switch case proceed, instead of duplicating via ApplyWorldEvent - Simplify isle.cpp HandleControl: split observatory cases into individual switch arms with single early-return multiplayer hook - Add save load hooks to sync world state with multiplayer peers - Fix player count to exclude local player without valid actor - Support broadcast snapshots (targetPeerId=0) in relay server --------- --- LEGO1/lego/legoomni/include/isle.h | 6 + .../legoomni/src/common/legogamestate.cpp | 7 ++ LEGO1/lego/legoomni/src/worlds/isle.cpp | 5 + extensions/include/extensions/multiplayer.h | 16 +++ .../extensions/multiplayer/networkmanager.h | 6 + .../include/extensions/multiplayer/protocol.h | 17 ++- .../extensions/multiplayer/worldstatesync.h | 19 +++ extensions/src/multiplayer.cpp | 52 ++++++++ extensions/src/multiplayer/networkmanager.cpp | 41 ++++++- extensions/src/multiplayer/server/gameroom.ts | 7 +- extensions/src/multiplayer/worldstatesync.cpp | 111 +++++++++++++++++- 11 files changed, 282 insertions(+), 5 deletions(-) diff --git a/LEGO1/lego/legoomni/include/isle.h b/LEGO1/lego/legoomni/include/isle.h index eafec8fe..ca9185af 100644 --- a/LEGO1/lego/legoomni/include/isle.h +++ b/LEGO1/lego/legoomni/include/isle.h @@ -23,6 +23,11 @@ class RaceCar; class SkateBoard; class TowTrack; +namespace Multiplayer +{ +class WorldStateSync; +} + // VTABLE: LEGO1 0x100d7028 // VTABLE: BETA10 0x101b9d40 // SIZE 0x26c @@ -164,6 +169,7 @@ class Isle : public LegoWorld { void SwitchToInfocenter(); friend class Act1State; + friend class Multiplayer::WorldStateSync; // SYNTHETIC: LEGO1 0x10030a30 // Isle::`scalar deleting destructor' diff --git a/LEGO1/lego/legoomni/src/common/legogamestate.cpp b/LEGO1/lego/legoomni/src/common/legogamestate.cpp index efdcbf8b..fca681cc 100644 --- a/LEGO1/lego/legoomni/src/common/legogamestate.cpp +++ b/LEGO1/lego/legoomni/src/common/legogamestate.cpp @@ -12,6 +12,7 @@ #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" @@ -64,6 +65,8 @@ #include #include +using namespace Extensions; + DECOMP_SIZE_ASSERT(LegoGameState::Username, 0x0e) DECOMP_SIZE_ASSERT(LegoGameState::ScoreItem, 0x2c) DECOMP_SIZE_ASSERT(LegoGameState::History, 0x374) @@ -364,6 +367,8 @@ MxResult LegoGameState::DeleteState() // FUNCTION: BETA10 0x10084329 MxResult LegoGameState::Load(MxULong p_slot) { + Extension::Call(HandleBeforeSaveLoad); + MxResult result = FAILURE; LegoFile storage; MxVariableTable* variableTable = VariableTable(); @@ -456,6 +461,8 @@ MxResult LegoGameState::Load(MxULong p_slot) result = SUCCESS; m_isDirty = FALSE; + Extension::Call(HandleSaveLoaded); + done: if (result != SUCCESS) { OmniError("Game state loading was not successful!", 0); diff --git a/LEGO1/lego/legoomni/src/worlds/isle.cpp b/LEGO1/lego/legoomni/src/worlds/isle.cpp index d1908fe3..3165f594 100644 --- a/LEGO1/lego/legoomni/src/worlds/isle.cpp +++ b/LEGO1/lego/legoomni/src/worlds/isle.cpp @@ -5,6 +5,7 @@ #include "bike.h" #include "carrace.h" #include "dunebuggy.h" +#include "extensions/multiplayer.h" #include "extensions/siloader.h" #include "helicopter.h" #include "isle_actions.h" @@ -296,6 +297,10 @@ void Isle::ReadyWorld() MxLong Isle::HandleControl(LegoControlManagerNotificationParam& p_param) { if (p_param.m_enabledChild == 1) { + if (Extension::Call(HandleSkyLightControl, (MxU32) p_param.m_clickedObjectId).value_or(FALSE)) { + return 1; + } + MxDSAction action; switch (p_param.m_clickedObjectId) { diff --git a/extensions/include/extensions/multiplayer.h b/extensions/include/extensions/multiplayer.h index 214809f2..c860b0e2 100644 --- a/extensions/include/extensions/multiplayer.h +++ b/extensions/include/extensions/multiplayer.h @@ -33,6 +33,10 @@ class MultiplayerExt { // Returns TRUE if the click should be suppressed locally (non-host). static MxBool HandleEntityNotify(LegoEntity* p_entity); + // Intercepts observatory sky/light controls for multiplayer routing. + // Returns TRUE if the local action should be suppressed (non-host). + static MxBool HandleSkyLightControl(MxU32 p_controlId); + // Handles clicks on entity-less ROIs (remote players, display actor overrides). static MxBool HandleROIClick(LegoROI* p_rootROI, LegoEventNotificationParam& p_param); @@ -50,6 +54,12 @@ class MultiplayerExt { // Returns TRUE if the name belongs to a multiplayer clone (entity-less ROI). static MxBool IsClonedCharacter(const char* p_name); + // Called before a save file is loaded. Captures current sky/light state. + static void HandleBeforeSaveLoad(); + + // Called after a save file is loaded. Re-syncs world state with multiplayer peers. + static void HandleSaveLoaded(); + // Returns true if the multiplayer connection was rejected (e.g. room full). static MxBool CheckRejected(); @@ -68,23 +78,29 @@ LEGO1_EXPORT bool IsMultiplayerRejected(); constexpr auto HandleCreate = &MultiplayerExt::HandleCreate; constexpr auto HandleWorldEnable = &MultiplayerExt::HandleWorldEnable; constexpr auto HandleEntityNotify = &MultiplayerExt::HandleEntityNotify; +constexpr auto HandleSkyLightControl = &MultiplayerExt::HandleSkyLightControl; constexpr auto HandleROIClick = &MultiplayerExt::HandleROIClick; constexpr auto HandleActorEnter = &MultiplayerExt::HandleActorEnter; constexpr auto HandleActorExit = &MultiplayerExt::HandleActorExit; constexpr auto HandleCamAnimEnd = &MultiplayerExt::HandleCamAnimEnd; constexpr auto ShouldInvertMovement = &MultiplayerExt::ShouldInvertMovement; constexpr auto IsClonedCharacter = &MultiplayerExt::IsClonedCharacter; +constexpr auto HandleBeforeSaveLoad = &MultiplayerExt::HandleBeforeSaveLoad; +constexpr auto HandleSaveLoaded = &MultiplayerExt::HandleSaveLoaded; constexpr auto CheckRejected = &MultiplayerExt::CheckRejected; #else constexpr decltype(&MultiplayerExt::HandleCreate) HandleCreate = nullptr; constexpr decltype(&MultiplayerExt::HandleWorldEnable) HandleWorldEnable = nullptr; constexpr decltype(&MultiplayerExt::HandleEntityNotify) HandleEntityNotify = nullptr; +constexpr decltype(&MultiplayerExt::HandleSkyLightControl) HandleSkyLightControl = nullptr; constexpr decltype(&MultiplayerExt::HandleROIClick) HandleROIClick = nullptr; constexpr decltype(&MultiplayerExt::HandleActorEnter) HandleActorEnter = nullptr; constexpr decltype(&MultiplayerExt::HandleActorExit) HandleActorExit = nullptr; constexpr decltype(&MultiplayerExt::HandleCamAnimEnd) HandleCamAnimEnd = nullptr; constexpr decltype(&MultiplayerExt::ShouldInvertMovement) ShouldInvertMovement = nullptr; constexpr decltype(&MultiplayerExt::IsClonedCharacter) IsClonedCharacter = nullptr; +constexpr decltype(&MultiplayerExt::HandleBeforeSaveLoad) HandleBeforeSaveLoad = nullptr; +constexpr decltype(&MultiplayerExt::HandleSaveLoaded) HandleSaveLoaded = nullptr; constexpr decltype(&MultiplayerExt::CheckRejected) CheckRejected = nullptr; #endif diff --git a/extensions/include/extensions/multiplayer/networkmanager.h b/extensions/include/extensions/multiplayer/networkmanager.h index 08855432..2d68a575 100644 --- a/extensions/include/extensions/multiplayer/networkmanager.h +++ b/extensions/include/extensions/multiplayer/networkmanager.h @@ -67,6 +67,8 @@ class NetworkManager : public MxCore { void OnWorldEnabled(LegoWorld* p_world); void OnWorldDisabled(LegoWorld* p_world); + void OnBeforeSaveLoad(); + void OnSaveLoaded(); ThirdPersonCamera& GetThirdPersonCamera() { return m_thirdPersonCamera; } @@ -74,6 +76,10 @@ class NetworkManager : public MxCore { // Returns TRUE if the mutation should be suppressed locally (non-host). MxBool HandleEntityMutation(LegoEntity* p_entity, MxU8 p_changeType); + // Called from multiplayer extension when a sky/light control is used. + // Returns TRUE if the local action should be suppressed (non-host). + MxBool HandleSkyLightMutation(uint8_t p_entityType, uint8_t p_changeType); + bool IsHost() const { return m_localPeerId != 0 && m_localPeerId == m_hostPeerId; } uint32_t GetLocalPeerId() const { return m_localPeerId; } diff --git a/extensions/include/extensions/multiplayer/protocol.h b/extensions/include/extensions/multiplayer/protocol.h index f8374af5..e2736a29 100644 --- a/extensions/include/extensions/multiplayer/protocol.h +++ b/extensions/include/extensions/multiplayer/protocol.h @@ -40,7 +40,9 @@ enum VehicleType : int8_t { // Entity types for world events enum WorldEntityType : uint8_t { ENTITY_PLANT = 0, - ENTITY_BUILDING = 1 + ENTITY_BUILDING = 1, + ENTITY_SKY = 2, + ENTITY_LIGHT = 3 }; // Change types for world events (maps to Switch* methods on LegoEntity) @@ -53,6 +55,19 @@ enum WorldChangeType : uint8_t { CHANGE_DECREMENT = 5 }; +// Change types for ENTITY_SKY +enum SkyChangeType : uint8_t { + SKY_TOGGLE_COLOR = 0, + SKY_DAY = 1, + SKY_NIGHT = 2 +}; + +// Change types for ENTITY_LIGHT +enum LightChangeType : uint8_t { + LIGHT_INCREMENT = 0, + LIGHT_DECREMENT = 1 +}; + #pragma pack(push, 1) struct MessageHeader { diff --git a/extensions/include/extensions/multiplayer/worldstatesync.h b/extensions/include/extensions/multiplayer/worldstatesync.h index b4eab8db..6c55db31 100644 --- a/extensions/include/extensions/multiplayer/worldstatesync.h +++ b/extensions/include/extensions/multiplayer/worldstatesync.h @@ -5,6 +5,7 @@ #include "mxtypes.h" #include +#include #include class LegoEntity; @@ -24,6 +25,15 @@ class WorldStateSync { // Called when the host peer changes. Requests a snapshot if we're not host. void OnHostChanged(); + // Captures current sky/light state before a save load (for non-host restore). + void SaveSkyLightState(); + + // Restores previously saved sky/light state (non-host only, prevents flicker). + void RestoreSkyLightState(); + + // Sends a snapshot to a specific peer, or broadcasts to all if p_targetPeerId is 0. + void SendWorldSnapshotTo(uint32_t p_targetPeerId); + // Incoming message handlers (called from NetworkManager::ProcessIncomingPackets) void HandleRequestSnapshot(const RequestSnapshotMsg& p_msg); void HandleWorldSnapshot(const uint8_t* p_data, size_t p_length); @@ -34,7 +44,12 @@ class WorldStateSync { // Returns TRUE if the mutation should be suppressed locally (non-host). MxBool HandleEntityMutation(LegoEntity* p_entity, MxU8 p_changeType); + // Called from multiplayer extension when a sky/light control is used. + // Returns TRUE if the local action should be suppressed (non-host). + MxBool HandleSkyLightMutation(uint8_t p_entityType, uint8_t p_changeType); + private: + void ApplySkyLightState(const char* p_skyColor, int p_lightPos); void SendSnapshotRequest(); void SendWorldSnapshot(uint32_t p_targetPeerId); void BroadcastWorldEvent(uint8_t p_entityType, uint8_t p_changeType, uint8_t p_entityIndex); @@ -51,6 +66,10 @@ class WorldStateSync { bool m_inIsleWorld; bool m_snapshotRequested; std::vector m_pendingWorldEvents; + + // Saved sky/light state for non-host restore across save loads. + std::string m_savedSkyColor; + int m_savedLightPos; }; } // namespace Multiplayer diff --git a/extensions/src/multiplayer.cpp b/extensions/src/multiplayer.cpp index a756fb58..89e31b4c 100644 --- a/extensions/src/multiplayer.cpp +++ b/extensions/src/multiplayer.cpp @@ -4,6 +4,7 @@ #include "extensions/multiplayer/networkmanager.h" #include "extensions/multiplayer/networktransport.h" #include "extensions/multiplayer/protocol.h" +#include "isle_actions.h" #include "islepathactor.h" #include "legoactor.h" #include "legoactors.h" @@ -213,6 +214,57 @@ MxBool MultiplayerExt::HandleEntityNotify(LegoEntity* p_entity) return s_networkManager->HandleEntityMutation(p_entity, changeType); } +MxBool MultiplayerExt::HandleSkyLightControl(MxU32 p_controlId) +{ + if (!s_networkManager) { + return FALSE; + } + + uint8_t entityType; + uint8_t changeType; + + switch (p_controlId) { + case IsleScript::c_Observe_SkyColor_Ctl: + entityType = Multiplayer::ENTITY_SKY; + changeType = Multiplayer::SKY_TOGGLE_COLOR; + break; + case IsleScript::c_Observe_Sun_Ctl: + entityType = Multiplayer::ENTITY_SKY; + changeType = Multiplayer::SKY_DAY; + break; + case IsleScript::c_Observe_Moon_Ctl: + entityType = Multiplayer::ENTITY_SKY; + changeType = Multiplayer::SKY_NIGHT; + break; + case IsleScript::c_Observe_GlobeRArrow_Ctl: + entityType = Multiplayer::ENTITY_LIGHT; + changeType = Multiplayer::LIGHT_INCREMENT; + break; + case IsleScript::c_Observe_GlobeLArrow_Ctl: + entityType = Multiplayer::ENTITY_LIGHT; + changeType = Multiplayer::LIGHT_DECREMENT; + break; + default: + return FALSE; + } + + return s_networkManager->HandleSkyLightMutation(entityType, changeType); +} + +void MultiplayerExt::HandleBeforeSaveLoad() +{ + if (s_networkManager) { + s_networkManager->OnBeforeSaveLoad(); + } +} + +void MultiplayerExt::HandleSaveLoaded() +{ + if (s_networkManager) { + s_networkManager->OnSaveLoaded(); + } +} + void MultiplayerExt::HandleActorEnter(IslePathActor* p_actor) { if (s_networkManager) { diff --git a/extensions/src/multiplayer/networkmanager.cpp b/extensions/src/multiplayer/networkmanager.cpp index 35b81507..fdbe651c 100644 --- a/extensions/src/multiplayer/networkmanager.cpp +++ b/extensions/src/multiplayer/networkmanager.cpp @@ -192,11 +192,42 @@ void NetworkManager::OnWorldDisabled(LegoWorld* p_world) } } +void NetworkManager::OnBeforeSaveLoad() +{ + if (m_transport && m_transport->IsConnected() && !IsHost()) { + m_worldSync.SaveSkyLightState(); + } +} + +void NetworkManager::OnSaveLoaded() +{ + if (!m_transport || !m_transport->IsConnected()) { + return; + } + + // After a save file load, the local plant/building/sky/light state comes + // from the save file and may diverge from the host's state. + // Host broadcasts to all peers (targetPeerId=0); non-host restores the + // pre-load sky/light to avoid visual flicker, then requests a fresh snapshot. + if (IsHost()) { + m_worldSync.SendWorldSnapshotTo(0); + } + else { + m_worldSync.RestoreSkyLightState(); + m_worldSync.OnHostChanged(); + } +} + MxBool NetworkManager::HandleEntityMutation(LegoEntity* p_entity, MxU8 p_changeType) { return m_worldSync.HandleEntityMutation(p_entity, p_changeType); } +MxBool NetworkManager::HandleSkyLightMutation(uint8_t p_entityType, uint8_t p_changeType) +{ + return m_worldSync.HandleSkyLightMutation(p_entityType, p_changeType); +} + void NetworkManager::ProcessPendingRequests() { if (m_pendingToggleThirdPerson.exchange(false, std::memory_order_relaxed)) { @@ -608,7 +639,15 @@ void NetworkManager::NotifyPlayerCountChanged() int count = -1; if (m_inIsleWorld) { - count = 1; // local player + count = 0; + + // Only count the local player if they have a valid actor + // (players who enter Isle without selecting a save have no actor). + LegoPathActor* userActor = UserActor(); + if (userActor && IsValidActorId(static_cast(userActor)->GetActorId())) { + count = 1; + } + for (auto& [peerId, player] : m_remotePlayers) { if (player->GetWorldId() == (int8_t) LegoOmni::e_act1) { count++; diff --git a/extensions/src/multiplayer/server/gameroom.ts b/extensions/src/multiplayer/server/gameroom.ts index 6b3b1316..e036b20c 100644 --- a/extensions/src/multiplayer/server/gameroom.ts +++ b/extensions/src/multiplayer/server/gameroom.ts @@ -178,7 +178,12 @@ export class GameRoom implements DurableObject { msgType === MSG_WORLD_SNAPSHOT && data.length >= SNAPSHOT_MIN_SIZE ) { - this.sendToTarget(stamped); + const targetId = readTargetPeerId(stamped); + if (targetId === 0) { + this.broadcastExcept(stamped.buffer, peerId); + } else { + this.sendToTarget(stamped); + } } else if (msgType === MSG_CUSTOMIZE) { // Broadcast to all including sender so the clicker sees effects // on the target's clone on their own screen. diff --git a/extensions/src/multiplayer/worldstatesync.cpp b/extensions/src/multiplayer/worldstatesync.cpp index 7fce46be..d9696e25 100644 --- a/extensions/src/multiplayer/worldstatesync.cpp +++ b/extensions/src/multiplayer/worldstatesync.cpp @@ -1,15 +1,22 @@ #include "extensions/multiplayer/worldstatesync.h" +#include "isle.h" #include "legobuildingmanager.h" #include "legoentity.h" +#include "legogamestate.h" #include "legomain.h" #include "legoplantmanager.h" #include "legoplants.h" +#include "legoutils.h" #include "legoworld.h" #include "misc.h" #include "misc/legostorage.h" +#include "mxmisc.h" +#include "mxvariable.h" #include +#include +#include #include extern MxU8 g_counters[]; @@ -33,10 +40,33 @@ void WorldStateSync::SendMessage(const T& p_msg) WorldStateSync::WorldStateSync() : m_transport(nullptr), m_localPeerId(0), m_sequence(0), m_isHost(false), m_inIsleWorld(false), - m_snapshotRequested(false) + m_snapshotRequested(false), m_savedLightPos(2) { } +void WorldStateSync::SaveSkyLightState() +{ + const char* bgValue = GameState()->GetBackgroundColor()->GetValue()->GetData(); + m_savedSkyColor = bgValue ? bgValue : "set 56 54 68"; + m_savedLightPos = atoi(VariableTable()->GetVariable("lightposition")); +} + +void WorldStateSync::RestoreSkyLightState() +{ + ApplySkyLightState(m_savedSkyColor.c_str(), m_savedLightPos); +} + +void WorldStateSync::ApplySkyLightState(const char* p_skyColor, int p_lightPos) +{ + GameState()->GetBackgroundColor()->SetValue(p_skyColor); + GameState()->GetBackgroundColor()->SetLightColor(); + SetLightPosition(p_lightPos); + + char buf[32]; + sprintf(buf, "%d", p_lightPos); + VariableTable()->SetVariable("lightposition", buf); +} + void WorldStateSync::OnHostChanged() { if (!m_isHost) { @@ -46,6 +76,11 @@ void WorldStateSync::OnHostChanged() } } +void WorldStateSync::SendWorldSnapshotTo(uint32_t p_targetPeerId) +{ + SendWorldSnapshot(p_targetPeerId); +} + void WorldStateSync::HandleRequestSnapshot(const RequestSnapshotMsg& p_msg) { if (!m_isHost) { @@ -74,6 +109,18 @@ void WorldStateSync::HandleWorldSnapshot(const uint8_t* p_data, size_t p_length) PlantManager()->Read(&memory); BuildingManager()->Read(&memory); + // Read sky/light state appended after plant + building data. + LegoU32 memPos; + memory.GetPosition(memPos); + const uint8_t* extraData = snapshotData + memPos; + size_t remaining = header.dataLength - memPos; + + if (remaining >= 4) { + char skyBuffer[32]; + sprintf(skyBuffer, "set %d %d %d", extraData[0], extraData[1], extraData[2]); + ApplySkyLightState(skyBuffer, extraData[3]); + } + // Read() updates data arrays but not entity positions; reload to refresh. if (m_inIsleWorld) { LegoWorld* world = CurrentWorld(); @@ -163,6 +210,22 @@ MxBool WorldStateSync::HandleEntityMutation(LegoEntity* p_entity, MxU8 p_changeT } } +MxBool WorldStateSync::HandleSkyLightMutation(uint8_t p_entityType, uint8_t p_changeType) +{ + if (!m_transport || !m_transport->IsConnected()) { + return FALSE; + } + + if (m_isHost) { + BroadcastWorldEvent(p_entityType, p_changeType, 0); + return FALSE; + } + else { + SendWorldEventRequest(p_entityType, p_changeType, 0); + return TRUE; + } +} + // ---- Send helpers ---- void WorldStateSync::SendSnapshotRequest() @@ -181,7 +244,7 @@ void WorldStateSync::SendWorldSnapshot(uint32_t p_targetPeerId) return; } - // Serialize plant + building state (~1133 bytes max, use 4096 for safety). + // Serialize plant + building + sky/light state (~1149 bytes max, use 4096 for safety). uint8_t stateBuffer[4096]; LegoMemory memory(stateBuffer, sizeof(stateBuffer)); @@ -191,6 +254,20 @@ void WorldStateSync::SendWorldSnapshot(uint32_t p_targetPeerId) LegoU32 dataLength; memory.GetPosition(dataLength); + // Append sky color HSV (parse from "set H S V" string) and light position. + int skyH = 56, skyS = 54, skyV = 68; // defaults matching "set 56 54 68" + const char* bgValue = GameState()->GetBackgroundColor()->GetValue()->GetData(); + if (bgValue) { + sscanf(bgValue, "set %d %d %d", &skyH, &skyS, &skyV); + } + + int lightPos = atoi(VariableTable()->GetVariable("lightposition")); + + stateBuffer[dataLength++] = (uint8_t) skyH; + stateBuffer[dataLength++] = (uint8_t) skyS; + stateBuffer[dataLength++] = (uint8_t) skyV; + stateBuffer[dataLength++] = (uint8_t) lightPos; + WorldSnapshotMsg msg{}; msg.header = {MSG_WORLD_SNAPSHOT, m_localPeerId, m_sequence++}; msg.targetPeerId = p_targetPeerId; @@ -385,4 +462,34 @@ void WorldStateSync::ApplyWorldEvent(uint8_t p_entityType, uint8_t p_changeType, } } } + else if (p_entityType == ENTITY_SKY) { + switch (p_changeType) { + case SKY_TOGGLE_COLOR: + GameState()->GetBackgroundColor()->ToggleSkyColor(); + break; + case SKY_DAY: + GameState()->GetBackgroundColor()->ToggleDayNight(TRUE); + break; + case SKY_NIGHT: + GameState()->GetBackgroundColor()->ToggleDayNight(FALSE); + break; + } + } + else if (p_entityType == ENTITY_LIGHT) { + switch (p_changeType) { + case LIGHT_INCREMENT: + UpdateLightPosition(1); + break; + case LIGHT_DECREMENT: + UpdateLightPosition(-1); + break; + } + + if (m_inIsleWorld) { + LegoWorld* world = CurrentWorld(); + if (world && world->GetWorldId() == LegoOmni::e_act1) { + ((Isle*) world)->UpdateGlobe(); + } + } + } }