From 0138d45c580bae69d5a945d0393c0074ce92a0ad Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Thu, 1 Oct 2026 14:29:02 -0500 Subject: [PATCH] fix(component): the zone control object constructs as on live Minigame control (50) is an activity component in the client (LWOMinigameComponent) and is made from the registry, so the zone control object (LOT 2365, registry id 0) has one; it sends what an activity sends (dirty, no players), not a fixed 0x40000000 (which nothing sent: the old id > 0 check never made it). The zone's script on that object is server only: the client makes no script component for it, so it sends nothing. DLU's zone control construction now equals live's byte for byte (209 captures, e.g. GruntMonkey world2); the decoder reads 50 as an activity. Co-Authored-By: Claude Opus 5.5 --- dGame/Entity.cpp | 8 +++++--- dGame/dComponents/CMakeLists.txt | 1 - dGame/dComponents/MiniGameControlComponent.cpp | 5 ----- dGame/dComponents/MiniGameControlComponent.h | 11 +++++++---- dGame/dComponents/ScriptComponent.cpp | 2 ++ dGame/dComponents/ScriptComponent.h | 4 ++-- dGame/dUtilities/ReplicaDecoder.cpp | 3 ++- 7 files changed, 18 insertions(+), 16 deletions(-) delete mode 100644 dGame/dComponents/MiniGameControlComponent.cpp diff --git a/dGame/Entity.cpp b/dGame/Entity.cpp index 198a584fb..897fde7c6 100644 --- a/dGame/Entity.cpp +++ b/dGame/Entity.cpp @@ -585,7 +585,8 @@ void Entity::Initialize() { if (zoneData != nullptr) { const int32_t zoneScriptID = zoneData->scriptID; CDScriptComponent zoneScriptData = scriptCompTable->GetByID(zoneScriptID); - AddComponent(zoneScriptID, zoneScriptData.script_name, true, false); + // A server script only: the client has no script component on the zone control object (live sent none) + AddComponent(zoneScriptID, zoneScriptData.script_name, false, false); } } break; @@ -797,8 +798,9 @@ void Entity::Initialize() { break; } case eReplicaComponentType::MINI_GAME_CONTROL: { - const auto minigameControlID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::MINI_GAME_CONTROL); - if (minigameControlID > 0) { + // The client makes it from the registry (the zone control object's id is 0) + const auto minigameControlID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::MINI_GAME_CONTROL, -1); + if (minigameControlID > -1) { AddComponent(minigameControlID); } break; diff --git a/dGame/dComponents/CMakeLists.txt b/dGame/dComponents/CMakeLists.txt index b92fea97e..d833dca12 100644 --- a/dGame/dComponents/CMakeLists.txt +++ b/dGame/dComponents/CMakeLists.txt @@ -47,7 +47,6 @@ set(DGAME_DCOMPONENTS_SOURCES "TriggerComponent.cpp" "HavokVehiclePhysicsComponent.cpp" "VendorComponent.cpp" - "MiniGameControlComponent.cpp" "ScriptComponent.cpp" ) diff --git a/dGame/dComponents/MiniGameControlComponent.cpp b/dGame/dComponents/MiniGameControlComponent.cpp deleted file mode 100644 index 088ee3540..000000000 --- a/dGame/dComponents/MiniGameControlComponent.cpp +++ /dev/null @@ -1,5 +0,0 @@ -#include "MiniGameControlComponent.h" - -void MiniGameControlComponent::Serialize(RakNet::BitStream& outBitStream, bool isConstruction) { - outBitStream.Write(0x40000000); -} diff --git a/dGame/dComponents/MiniGameControlComponent.h b/dGame/dComponents/MiniGameControlComponent.h index 742142d3e..65d07f3ed 100644 --- a/dGame/dComponents/MiniGameControlComponent.h +++ b/dGame/dComponents/MiniGameControlComponent.h @@ -1,15 +1,18 @@ #ifndef __MINIGAMECONTROLCOMPONENT__H__ #define __MINIGAMECONTROLCOMPONENT__H__ -#include "Component.h" +#include "ActivityComponent.h" #include "eReplicaComponentType.h" -class MiniGameControlComponent final : public Component { +/** + * Minigame control (50), the zone control object's: an activity component as in the client (LWOMinigameComponent), + * which also sends what an activity sends. + */ +class MiniGameControlComponent final : public ActivityComponent { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::MINI_GAME_CONTROL; - MiniGameControlComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) {} - void Serialize(RakNet::BitStream& outBitStream, bool isConstruction); + MiniGameControlComponent(Entity* parent, const int32_t componentID) : ActivityComponent(parent, componentID) {} }; #endif //!__MINIGAMECONTROLCOMPONENT__H__ diff --git a/dGame/dComponents/ScriptComponent.cpp b/dGame/dComponents/ScriptComponent.cpp index d4a2a9d4e..fbb79ca85 100644 --- a/dGame/dComponents/ScriptComponent.cpp +++ b/dGame/dComponents/ScriptComponent.cpp @@ -20,6 +20,8 @@ ScriptComponent::ScriptComponent(Entity* parent, const int32_t componentID, cons } void ScriptComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) { + // A server-only script (the zone control object's) sends nothing: the client made no script component for it + if (!m_Serialized) return; if (bIsInitialUpdate) { const auto& networkSettings = m_Parent->GetNetworkSettings(); auto hasNetworkSettings = !networkSettings.values.empty(); diff --git a/dGame/dComponents/ScriptComponent.h b/dGame/dComponents/ScriptComponent.h index 723df7393..69f22268e 100644 --- a/dGame/dComponents/ScriptComponent.h +++ b/dGame/dComponents/ScriptComponent.h @@ -35,7 +35,7 @@ public: CppScripts::Script* const GetScript(); /** - * Sets whether the entity should be serialized, unused + * Sets whether the component is serialized (false: a server-only script the client has no component for) * @param var whether the entity should be serialized */ void SetSerialized(const bool var) { m_Serialized = var; } @@ -69,7 +69,7 @@ private: CppScripts::Script* m_Script; /** - * Whether or not the comp should be serialized, unused + * Whether the component is serialized (false: a server-only script the client has no component for) */ bool m_Serialized; diff --git a/dGame/dUtilities/ReplicaDecoder.cpp b/dGame/dUtilities/ReplicaDecoder.cpp index f2fa8f645..6a2acfde4 100644 --- a/dGame/dUtilities/ReplicaDecoder.cpp +++ b/dGame/dUtilities/ReplicaDecoder.cpp @@ -690,7 +690,8 @@ namespace { } j["effects"] = effects; } }, - { MINI_GAME_CONTROL, [](Reader& r, json& j, bool, const auto&) { j["value"] = r.Get(); } }, + // An activity component (LWOMinigameComponent), as the scripted activity + { MINI_GAME_CONTROL, [](Reader& r, json& j, bool, const auto&) { if (r.Bit()) j["players"] = ActivityPlayers(r); } }, }; return readers; }