refactor(entity): each component type has an allocator, as the client

DoObjectComponentLoad walks the client's load order and calls the type's
allocator from a table, like ObjectLoader2, instead of one 76-case switch.
Components with rules of their own have a static Allocator (the client's
LWOxComponent::Allocator); the rest share a registry allocator. The load
state moves to ObjectLoadState.h so the allocators can take it.

Construction bytes are identical for all 15,962 LOTs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-10-02 08:11:58 -05:00
parent 3a79286666
commit 260e6cb1b1
52 changed files with 634 additions and 684 deletions

View File

@@ -11,6 +11,9 @@
#include "Amf3.h"
#include <ranges>
#include "ObjectLoadState.h"
#include "dZoneManager.h"
#include "CDZoneTableTable.h"
ScriptComponent::ScriptComponent(Entity* parent, const int32_t componentID, const std::string& scriptName, bool serialized, bool client) : Component(parent, componentID) {
m_Serialized = serialized;
@@ -109,3 +112,33 @@ bool ScriptComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& r
return true;
}
void ScriptComponent::Allocator(Entity& entity, ObjectLoadState& state) {
// LWOScriptComponent's allocator: a script component for a registry entry, a character, or a custom script in
// the level config; the component picks its script
CDScriptComponentTable* const scriptCompTable = CDClientManager::GetTable<CDScriptComponentTable>();
const int32_t scriptComponentID = state.GetComponentID(eReplicaComponentType::SCRIPT, -1);
const bool hasCustomScript = !entity.GetVarAsString(u"custom_script_server").empty() || !entity.GetVarAsString(u"custom_script_client").empty();
if (scriptComponentID >= 0 || entity.GetCharacter() || hasCustomScript) {
entity.AddComponent<ScriptComponent>(scriptComponentID);
// The client's script allocator adds these only when it makes a script component: a registry script with a
// non-zero id or a custom script in the config it is sent (a character's script is the server's own)
const auto clientCustomScript = [&entity](const std::u16string& key) { return entity.IsVarSentToClient(key) && !entity.GetVarAsString(key).empty(); };
if (scriptComponentID > 0 || (entity.GetLOT() != 176 && (clientCustomScript(u"custom_script_server") || clientCustomScript(u"custom_script_client")))) {
ObjectLoadState::AddProximityAndPathfinding(entity);
}
}
// ZoneControl script
if (entity.GetLOT() == 2365) {
const auto& zoneID = Game::zoneManager->GetZoneID();
const CDZoneTable* const zoneData = CDZoneTableTable::Query(zoneID.GetMapID());
if (zoneData != nullptr) {
const int32_t zoneScriptID = zoneData->scriptID;
CDScriptComponent zoneScriptData = scriptCompTable->GetByID(zoneScriptID);
// A server script only: the client has no script component on the zone control object (live sent none)
entity.AddComponent<ScriptComponent>(zoneScriptID, zoneScriptData.script_name, false, false);
}
}
}