From 477dd9f3e38e3b93237dcec10bdd2c0504ed4648 Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Thu, 1 Oct 2026 16:52:15 -0500 Subject: [PATCH] refactor(entity): load an object in the client's two steps Entity::Initialize now runs DoObjectPreLoad (what the components will be: the player's own components, markedAsPhantom, model behaviors, the component whitelist) and DoObjectComponentLoad (the walk in the client's order and what the client adds after it), as ObjectLoader2 does, with an ObjectLoadState that answers the registry through the whitelist. Same components and bytes for every LOT. Co-Authored-By: Claude Opus 5.5 --- dGame/Entity.cpp | 1410 +++++++++++++++++++++++----------------------- dGame/Entity.h | 22 + 2 files changed, 735 insertions(+), 697 deletions(-) diff --git a/dGame/Entity.cpp b/dGame/Entity.cpp index 959b180ad..f65366bce 100644 --- a/dGame/Entity.cpp +++ b/dGame/Entity.cpp @@ -260,6 +260,717 @@ namespace { }(); } +int32_t Entity::ObjectLoadState::GetComponentID(const eReplicaComponentType type, const int32_t defaultValue) const { + if (whitelist && std::ranges::find(*whitelist, type) == whitelist->end()) return defaultValue; + return CDClientManager::GetTable()->GetByIDAndType(lot, type, defaultValue); +} + +// ObjectLoader2::DoObjectPreLoad (0x01054d30): what the object's components will be before any is made +Entity::ObjectLoadState Entity::DoObjectPreLoad() { + ObjectLoadState state; + state.lot = m_TemplateID; + + // The player's own components (HandleLocalPlayer makes them for the local player, not from the registry) + if (m_Character && m_Character->GetParentUser()) { + AddComponent(-1)->LoadFromSaveData(m_Character->GetXMLDoc()); + } + + // markedAsPhantom doesn't count when the registry has phantom physics + state.markedAsPhantom = HasVar(u"markedAsPhantom") && state.GetComponentID(eReplicaComponentType::PHANTOM_PHYSICS, -1) == -1; + + // A model with behaviors (0x01055667: not inInventory, and modelType and modelBehaviors in its config) gets its + // physics from the model case, which takes the registry's simple physics id + state.hasModelBehaviors = !GetVar(u"inInventory") && HasVar(u"modelType") && HasVar(u"modelBehaviors"); + + // ObjectLoader2::ApplyComponentWhitelist (0x0103e300): the config's componentWhitelist keeps only these registry + // components (the lists ObjectLoader2's constructor builds; property models use 1) + static constexpr std::array, 5> COMPONENT_WHITELISTS{ { + { eReplicaComponentType::CONTROLLABLE_PHYSICS, eReplicaComponentType::SIMPLE_PHYSICS, eReplicaComponentType::RENDER }, + { eReplicaComponentType::RENDER, eReplicaComponentType::DESTROYABLE, eReplicaComponentType::ITEM, eReplicaComponentType::BLUEPRINT, + eReplicaComponentType::MODEL, eReplicaComponentType::CONTROLLABLE_PHYSICS, eReplicaComponentType::SIMPLE_PHYSICS, eReplicaComponentType::SPAWNER }, + { eReplicaComponentType::RENDER, eReplicaComponentType::ITEM, eReplicaComponentType::BLUEPRINT }, + { eReplicaComponentType::PET, eReplicaComponentType::SKILL, eReplicaComponentType::DESTROYABLE, eReplicaComponentType::RENDER, eReplicaComponentType::CONTROLLABLE_PHYSICS }, + { eReplicaComponentType::CONTROLLABLE_PHYSICS, eReplicaComponentType::SIMPLE_PHYSICS, eReplicaComponentType::RENDER }, + } }; + const auto whitelistIndex = HasVar(u"componentWhitelist") ? GetVarAs(u"componentWhitelist") : -1; + state.whitelist = + whitelistIndex >= 0 && whitelistIndex < static_cast(COMPONENT_WHITELISTS.size()) ? &COMPONENT_WHITELISTS[whitelistIndex] : nullptr; + + return state; +} + +// ObjectLoader2::DoObjectComponentLoad (0x0105ebb0): the components in the client's order, then the ones it adds after +void Entity::DoObjectComponentLoad(ObjectLoadState& state) { + + + /** + * The components in the client's load order (ObjectLoader2::DoObjectComponentLoad 0x0105ebb0 walks + * ComponentOrderVector::Initialize 0x0101f8e0's list): a component that looks at another one finds it made when the + * client's does. Ids a component needs from a later one come from the registry. + */ + const auto petComponentID = state.GetComponentID(eReplicaComponentType::PET); + const auto collectibleComponentID = state.GetComponentID(eReplicaComponentType::COLLECTIBLE); + const auto combatAiID = state.GetComponentID(eReplicaComponentType::BASE_COMBAT_AI); + // LWOScriptComponent::Allocator (0x0104d710) and LWOBaseCombatAiComponent::Allocator (0x0104c7b0) add a proximity + // monitor when there is none, and pathfinding control when the object has movement physics (controllable, + // projectile, vehicle or havok vehicle) and none yet + const auto addProximityAndPathfinding = [this]() { + if (!HasComponent(eReplicaComponentType::PROXIMITY_MONITOR)) AddComponent(-1); + const bool hasMovementPhysics = HasComponent(eReplicaComponentType::CONTROLLABLE_PHYSICS) || HasComponent(eReplicaComponentType::PROJECTILE_PHYSICS) + || HasComponent(eReplicaComponentType::VEHICLE_PHYSICS) || HasComponent(eReplicaComponentType::HAVOK_VEHICLE_PHYSICS); + if (hasMovementPhysics && !HasComponent(eReplicaComponentType::PATHFINDING)) AddComponent(-1); + }; + + for (const auto type : ClientComponentLoadOrder) { + if (state.whitelist && std::ranges::find(*state.whitelist, type) == state.whitelist->end()) continue; + switch (type) { + case eReplicaComponentType::POSSESSABLE: { + const auto possessableComponentID = state.GetComponentID(eReplicaComponentType::POSSESSABLE); + if (possessableComponentID > 0) { + AddComponent(possessableComponentID); + } + break; + } + case eReplicaComponentType::MODULE_ASSEMBLY: { + const auto moduleAssemblyID = state.GetComponentID(eReplicaComponentType::MODULE_ASSEMBLY); + if (moduleAssemblyID > 0) { + AddComponent(moduleAssemblyID); + } + break; + } + case eReplicaComponentType::CONTROLLABLE_PHYSICS: { + const auto controllablePhysicsComponentID = state.GetComponentID(eReplicaComponentType::CONTROLLABLE_PHYSICS); + if (controllablePhysicsComponentID > 0) { + // The component places it: where the level put an object, where a character's save and the zone put it + auto* controllablePhysics = AddComponent(controllablePhysicsComponentID); + if (m_Character) controllablePhysics->LoadFromSaveData(m_Character->GetXMLDoc()); + } + break; + } + case eReplicaComponentType::HAVOK_VEHICLE_PHYSICS: { + const auto havokVehiclePhysicsComponentID = state.GetComponentID(eReplicaComponentType::HAVOK_VEHICLE_PHYSICS); + // The client makes simple physics instead for use_simple_physics, and none for objectWorldState 2 + if (havokVehiclePhysicsComponentID > 0 && HasVar(u"use_simple_physics")) { + if (!HasComponent(eReplicaComponentType::SIMPLE_PHYSICS)) AddComponent(havokVehiclePhysicsComponentID); + } else if (havokVehiclePhysicsComponentID > 0 && !(HasVar(u"objectWorldState") && GetVarAs(u"objectWorldState") == 2)) { + auto* havokVehiclePhysicsComponent = AddComponent(havokVehiclePhysicsComponentID); + havokVehiclePhysicsComponent->SetPosition(m_DefaultPosition); + havokVehiclePhysicsComponent->SetRotation(m_DefaultRotation); + } + break; + } + case eReplicaComponentType::RIGID_BODY_PHANTOM_PHYSICS: { + const auto rigidBodyPhantomPhysicsComponentID = state.GetComponentID(eReplicaComponentType::RIGID_BODY_PHANTOM_PHYSICS); + if (rigidBodyPhantomPhysicsComponentID > 0) { + AddComponent(rigidBodyPhantomPhysicsComponentID); + } + break; + } + case eReplicaComponentType::SIMPLE_PHYSICS: { + const auto simplePhysicsComponentID = state.GetComponentID(eReplicaComponentType::SIMPLE_PHYSICS); + if (state.hasModelBehaviors) { + // LWOSimplePhysicsComponent::Allocator (0x0104ded0): left to the model case + if (simplePhysicsComponentID > 0) state.modelPhysicsComponentID = simplePhysicsComponentID; + } else if (simplePhysicsComponentID > 0) { + // state.markedAsPhantom makes its simple physics phantom physics (with the simple physics row) + if (state.markedAsPhantom) AddComponent(simplePhysicsComponentID)->SetPhysicsEffectActive(false); + else AddComponent(simplePhysicsComponentID); + } + break; + } + case eReplicaComponentType::PHANTOM_PHYSICS: { + const auto phantomPhysicsComponentID = state.GetComponentID(eReplicaComponentType::PHANTOM_PHYSICS); + if (phantomPhysicsComponentID > 0 && !HasComponent(eReplicaComponentType::PHANTOM_PHYSICS)) { + AddComponent(phantomPhysicsComponentID)->SetPhysicsEffectActive(false); + } + break; + } + case eReplicaComponentType::DESTROYABLE: { + /** + * The destroyable component. The client makes one (with a buff component in front of it) for a DESTROYABLE + * registry entry, whose id is the DestructibleComponent row. Collectibles and smashable (is_smashable) objects + * without that entry get one too, with no row. + */ + const auto destroyableComponentID = state.GetComponentID(eReplicaComponentType::DESTROYABLE); + if (destroyableComponentID > 0) { + AddComponent(destroyableComponentID); + } + + bool isSmashable = GetVarAs(u"is_smashable") != 0; + if (destroyableComponentID > 0 || collectibleComponentID > 0 || isSmashable) { + const int32_t componentID = destroyableComponentID > 0 ? destroyableComponentID : -1; + // The row, the level config and a character's save, in the client's order + DestroyableComponent* comp = AddComponent(componentID); + if (m_Character) comp->LoadFromSaveData(m_Character->GetXMLDoc()); + } + break; + } + case eReplicaComponentType::MINIFIG: { + const auto minifigID = state.GetComponentID(eReplicaComponentType::MINIFIG); + if (minifigID > 0) AddComponent(minifigID); + break; + } + case eReplicaComponentType::CHARACTER: { + const auto characterID = state.GetComponentID(eReplicaComponentType::CHARACTER); + if (characterID > 0 || m_Character) { + // LWOCharacterComponent::Factory (0x0104db40): possession control, mount control, pet creator, level + // progression and forced movement, then the character, all componentID -1 + AddComponent(-1); + AddComponent(-1); + AddComponent(-1); + AddComponent(-1)->LoadFromSaveData(m_Character->GetXMLDoc()); + AddComponent(-1); + + auto& systemAddress = m_Character->GetParentUser() ? m_Character->GetParentUser()->GetSystemAddress() : UNASSIGNED_SYSTEM_ADDRESS; + AddComponent(-1, m_Character, systemAddress)->LoadFromSaveData(m_Character->GetXMLDoc()); + + } + break; + } + case eReplicaComponentType::PET: { + // LWOPetComponent::Factory (0x01031240): no pet in an inventory; otherwise pathfinding control first + if (petComponentID > 0 && !GetVar(u"inInventory")) { + if (!HasComponent(eReplicaComponentType::PATHFINDING)) AddComponent(-1); + AddComponent(petComponentID); + } + break; + } + case eReplicaComponentType::INVENTORY: { + const auto inventoryID = state.GetComponentID(eReplicaComponentType::INVENTORY); + if (inventoryID > 0 || m_Character) { + AddComponent(inventoryID); + } + break; + } + case eReplicaComponentType::PROXIMITY_MONITOR: { + const auto proximityMonitorID = state.GetComponentID(eReplicaComponentType::PROXIMITY_MONITOR); + if (proximityMonitorID > 0) AddComponent(proximityMonitorID); + break; + } + case eReplicaComponentType::MOVEMENT_AI: { + const int movementAIID = state.GetComponentID(eReplicaComponentType::MOVEMENT_AI); + if (movementAIID > 0) { + // Its row and the level's wanderRadius load in the component + AddComponent(movementAIID, MovementAIInfo{}); + } else if (petComponentID > 0 || combatAiID > 0 && BaseCombatAIComponent::TetherSpeedOf(combatAiID) > 0) { + MovementAIInfo moveInfo{ + .movementType = "", + .wanderRadius = 16, + .wanderSpeed = 2.5f, + .wanderChance = 0, + .wanderDelayMin = 2, + .wanderDelayMax = 5, + }; + + AddComponent(-1, moveInfo); + } + break; + } + case eReplicaComponentType::SCRIPT: { + // 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(); + const int32_t scriptComponentID = state.GetComponentID(eReplicaComponentType::SCRIPT, -1); + const bool hasCustomScript = !GetVarAsString(u"custom_script_server").empty() || !GetVarAsString(u"custom_script_client").empty(); + if (scriptComponentID >= 0 || m_Character || hasCustomScript) { + AddComponent(scriptComponentID); + addProximityAndPathfinding(); + } + + // ZoneControl script + if (m_TemplateID == 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) + AddComponent(zoneScriptID, zoneScriptData.script_name, false, false); + } + } + break; + } + case eReplicaComponentType::SKILL: { + const auto skillID = state.GetComponentID(eReplicaComponentType::SKILL, -1); + if (skillID > -1 || m_Character) { + auto* const skillComponent = AddComponent(skillID); + if (m_Character) skillComponent->LoadFromSaveData(m_Character->GetXMLDoc()); + } + break; + } + case eReplicaComponentType::BASE_COMBAT_AI: { + if (combatAiID > 0) { + addProximityAndPathfinding(); + AddComponent(combatAiID); + } + break; + } + case eReplicaComponentType::ITEM: { + // A pet takes the item (and model) component away (LWOPetComponent::Factory); models keep theirs (live: an + // item block before the model's) + PetComponent* petComponent{}; + const auto itemID = state.GetComponentID(eReplicaComponentType::ITEM); + if (itemID > 0 && !TryGetComponent(eReplicaComponentType::PET, petComponent)) { + AddComponent(itemID); + } + break; + } + case eReplicaComponentType::BUILD_BORDER: { + const auto buildBorderID = state.GetComponentID(eReplicaComponentType::BUILD_BORDER, -1); + if (buildBorderID > -1) { + AddComponent(buildBorderID); + } + break; + } + case eReplicaComponentType::QUICK_BUILD: { + const auto quickBuildComponentID = state.GetComponentID(eReplicaComponentType::QUICK_BUILD); + if (quickBuildComponentID > 0) { + AddComponent(quickBuildComponentID); + + // A quick build without a destroyable gets one (componentID -1) and is smashable, as the client makes it + // (DoObjectComponentLoad); live: health 1 / 1.0, armor 1 / 0, faction -1, smashable (AG LOT 7804) + if (!HasComponent(eReplicaComponentType::DESTROYABLE)) { + AddComponent(-1, true); + } + } + break; + } + case eReplicaComponentType::MISSION_OFFER: { + const auto missionOfferComponentID = state.GetComponentID(eReplicaComponentType::MISSION_OFFER, -1); + if (missionOfferComponentID > -1 || m_Character) { + AddComponent(missionOfferComponentID); + } + break; + } + case eReplicaComponentType::VENDOR: { + const auto vendorID = state.GetComponentID(eReplicaComponentType::VENDOR); + // The vendor allocators add a proximity monitor first when there is none + if (vendorID > 0) { + if (!HasComponent(eReplicaComponentType::PROXIMITY_MONITOR)) AddComponent(-1); + AddComponent(vendorID); + } + break; + } + case eReplicaComponentType::DONATION_VENDOR: { + const auto donationVendorID = state.GetComponentID(eReplicaComponentType::DONATION_VENDOR, -1); + if (donationVendorID > -1) { + if (!HasComponent(eReplicaComponentType::PROXIMITY_MONITOR)) AddComponent(-1); + AddComponent(donationVendorID); + } + break; + } + case eReplicaComponentType::ACHIEVEMENT_VENDOR: { + const auto achievementVendorID = state.GetComponentID(eReplicaComponentType::ACHIEVEMENT_VENDOR, -1); + if (achievementVendorID > -1) { + if (!HasComponent(eReplicaComponentType::PROXIMITY_MONITOR)) AddComponent(-1); + AddComponent(achievementVendorID); + } + break; + } + case eReplicaComponentType::SHOOTING_GALLERY: { + // Shooting gallery component + const auto shootingGalleryComponentID = state.GetComponentID(eReplicaComponentType::SHOOTING_GALLERY); + if (shootingGalleryComponentID > 0) { + AddComponent(shootingGalleryComponentID); + } + break; + } + case eReplicaComponentType::SCRIPTED_ACTIVITY: { + // Scripted activity component + const auto scriptedActivityID = state.GetComponentID(eReplicaComponentType::SCRIPTED_ACTIVITY, -1); + if (scriptedActivityID > -1) { + AddComponent(scriptedActivityID); + } + break; + } + case eReplicaComponentType::COLLECTIBLE: { + + if (collectibleComponentID > 0) { + AddComponent(collectibleComponentID, GetVarAs(u"collectible_id")); + } + break; + } + case eReplicaComponentType::MOVING_PLATFORM: { + const std::string pathName = GetVarAsString(u"attached_path"); + const Path* const path = Game::zoneManager->GetZone()->GetPath(pathName); + + //Check to see if we have an attached path and add the appropiate component to handle it: + if (path) { + // if we have a moving platform path, then we need a moving platform component + if (path->pathType == PathType::MovingPlatform) { + // The registry component ID picks the subcomponent (a simple mover when it has one), path or not + AddComponent(state.GetComponentID(eReplicaComponentType::MOVING_PLATFORM, -1), pathName); + } else if (path->pathType == PathType::Movement) { + auto* const movementAIcomponent = GetComponent(); + if (movementAIcomponent && combatAiID == 0) { + movementAIcomponent->SetPath(pathName); + } else { + MovementAIInfo moveInfo{ + .movementType = "", + .wanderRadius = 16, + .wanderSpeed = 2.5f, + .wanderChance = 0, + .wanderDelayMin = 2, + .wanderDelayMax = 5, + }; + + AddComponent(-1, moveInfo); + } + } + } else { + // else we still need to setup moving platform if it has a moving platform comp but no path + const auto movingPlatformComponentID = state.GetComponentID(eReplicaComponentType::MOVING_PLATFORM, -1); + if (movingPlatformComponentID >= 0) { + AddComponent(movingPlatformComponentID, pathName); + } + } + break; + } + case eReplicaComponentType::LUP_EXHIBIT: { + const auto lupExhibitID = state.GetComponentID(eReplicaComponentType::LUP_EXHIBIT, -1); + if (lupExhibitID >= 0) { + AddComponent(lupExhibitID); + } + break; + } + case eReplicaComponentType::PROPERTY: { + const auto propertyID = state.GetComponentID(eReplicaComponentType::PROPERTY, -1); + if (propertyID > -1) { + AddComponent(propertyID); + } + break; + } + case eReplicaComponentType::MODEL: { + const auto modelID = state.GetComponentID(eReplicaComponentType::MODEL, -1); + if (modelID > -1 && !GetComponent()) { + // ObjectLoader2::LoadModelBehaviorsComponent (0x0104d1f0): a model without physics gets it here (BBB models, + // LOT 14, have none in the registry): simple physics 4247 without behaviors; with behaviors controllable + // (4246) for modelType 0, else simple (4247), unless the registry gave an id. modelType 0 also gets + // pathfinding control. + const bool hasPhysics = HasComponent(eReplicaComponentType::CONTROLLABLE_PHYSICS) || HasComponent(eReplicaComponentType::SIMPLE_PHYSICS) + || HasComponent(eReplicaComponentType::PROJECTILE_PHYSICS) || HasComponent(eReplicaComponentType::RIGID_BODY_PHANTOM_PHYSICS) + || HasComponent(eReplicaComponentType::HAVOK_VEHICLE_PHYSICS) || HasComponent(eReplicaComponentType::PHANTOM_PHYSICS) + || HasComponent(eReplicaComponentType::VEHICLE_PHYSICS) || HasComponent(eReplicaComponentType::PHYSICS_SYSTEM); + const int32_t modelType = HasVar(u"modelType") ? GetVarAs(u"modelType") : -1; + if (!hasPhysics) { + if (!state.hasModelBehaviors) AddComponent(4247); + else if (modelType == 0) AddComponent(state.modelPhysicsComponentID == -1 ? 4246 : state.modelPhysicsComponentID); + else AddComponent(state.modelPhysicsComponentID == -1 ? 4247 : state.modelPhysicsComponentID); + } + if (modelType == 0 && !HasComponent(eReplicaComponentType::PATHFINDING)) AddComponent(-1); + AddComponent(modelID)->LoadBehaviors(); + if (!HasComponent(eReplicaComponentType::DESTROYABLE)) { + AddComponent(-1, true); + } + } + break; + } + case eReplicaComponentType::PROPERTY_ENTRANCE: { + const auto propertyEntranceComponentID = state.GetComponentID(eReplicaComponentType::PROPERTY_ENTRANCE); + if (propertyEntranceComponentID > 0) { + AddComponent(propertyEntranceComponentID); + } + break; + } + case eReplicaComponentType::MULTI_ZONE_ENTRANCE: { + // if this component exists, then we initialize it. it's value is always 0 + const auto multiZoneEntranceID = state.GetComponentID(eReplicaComponentType::MULTI_ZONE_ENTRANCE, -1); + if (multiZoneEntranceID > -1) { + AddComponent(multiZoneEntranceID); + } + break; + } + case eReplicaComponentType::PROPERTY_MANAGEMENT: { + const auto propertyManagementID = state.GetComponentID(eReplicaComponentType::PROPERTY_MANAGEMENT, -1); + if (propertyManagementID > -1) { + AddComponent(propertyManagementID); + } + break; + } + case eReplicaComponentType::RAIL_ACTIVATOR: { + const auto railComponentID = state.GetComponentID(eReplicaComponentType::RAIL_ACTIVATOR); + if (railComponentID > 0) { + AddComponent(railComponentID); + } + break; + } + case eReplicaComponentType::BOUNCER: { + const auto bouncerID = state.GetComponentID(eReplicaComponentType::BOUNCER, -1); + if (bouncerID > -1) { // you have to determine it like this because all bouncers have a componentID of 0 + AddComponent(bouncerID); + } + break; + } + case eReplicaComponentType::SWITCH: { + const auto switchID = state.GetComponentID(eReplicaComponentType::SWITCH, -1); + if (switchID > -1) { + AddComponent(switchID); + } + break; + } + case eReplicaComponentType::MINI_GAME_CONTROL: { + // The client makes it from the registry (the zone control object's id is 0) + const auto minigameControlID = state.GetComponentID(eReplicaComponentType::MINI_GAME_CONTROL, -1); + if (minigameControlID > -1) { + AddComponent(minigameControlID); + } + break; + } + case eReplicaComponentType::RENDER: { + const auto renderComponentID = state.GetComponentID(eReplicaComponentType::RENDER); + if ((renderComponentID > 0 && m_TemplateID != 2365) || m_Character) { + AddComponent(renderComponentID); + } + break; + } + case eReplicaComponentType::SOUND_TRIGGER: { + const auto soundTriggerID = state.GetComponentID(eReplicaComponentType::SOUND_TRIGGER, -1); + const auto racingSoundTriggerID = state.GetComponentID(eReplicaComponentType::RACING_SOUND_TRIGGER, -1); + if (soundTriggerID > -1) { + AddComponent(soundTriggerID); + } else if (racingSoundTriggerID > -1) { + AddComponent(racingSoundTriggerID); + } + break; + } + case eReplicaComponentType::PROPERTY_VENDOR: { + const auto propertyVendorID = state.GetComponentID(eReplicaComponentType::PROPERTY_VENDOR, -1); + if (propertyVendorID > -1) { + AddComponent(propertyVendorID); + } + break; + } + case eReplicaComponentType::ROCKET_LAUNCH: { + const auto rocketID = state.GetComponentID(eReplicaComponentType::ROCKET_LAUNCH); + if ((rocketID > 0)) { + AddComponent(rocketID); + } + break; + } + case eReplicaComponentType::RACING_CONTROL: { + const auto racingControlID = state.GetComponentID(eReplicaComponentType::RACING_CONTROL); + if (racingControlID > 0) { + AddComponent(racingControlID); + } + break; + } + case eReplicaComponentType::SOUND_REPEATER: { + // LWOSoundRepeaterComponent: the client makes it from the registry + const auto id = state.GetComponentID(eReplicaComponentType::SOUND_REPEATER, -1); + if (id > -1) AddComponent(id); + break; + } + case eReplicaComponentType::SOUND_AMBIENT_2D: { + // LWOSoundAmbient2DComponent: the client makes it from the registry + const auto id = state.GetComponentID(eReplicaComponentType::SOUND_AMBIENT_2D, -1); + if (id > -1) AddComponent(id); + break; + } + case eReplicaComponentType::SOUND_AMBIENT_3D: { + // LWOSoundAmbient3DComponent: the client makes it from the registry + const auto id = state.GetComponentID(eReplicaComponentType::SOUND_AMBIENT_3D, -1); + if (id > -1) AddComponent(id); + break; + } + case eReplicaComponentType::CULLING_PLANE: { + // LoadCullingPlaneComponent: the client makes it from the registry + const auto id = state.GetComponentID(eReplicaComponentType::CULLING_PLANE, -1); + if (id > -1) AddComponent(id); + break; + } + case eReplicaComponentType::PLATFORM_BOUNDARY: { + // LWOPlatformBoundaryComponent: the client makes it from the registry + const auto id = state.GetComponentID(eReplicaComponentType::PLATFORM_BOUNDARY, -1); + if (id > -1) AddComponent(id); + break; + } + case eReplicaComponentType::REBUILD: { + // LWOModularBuildComponent: the client makes it from the registry + const auto id = state.GetComponentID(eReplicaComponentType::REBUILD, -1); + if (id > -1) AddComponent(id); + break; + } + case eReplicaComponentType::REBUILD_START: { + // LWOBuildControllerComponent: the client makes it from the registry + const auto id = state.GetComponentID(eReplicaComponentType::REBUILD_START, -1); + if (id > -1) AddComponent(id); + break; + } + case eReplicaComponentType::REBUILD_ACTIVATOR: { + // LWORebuildActivatorComponent: the client makes it from the registry + const auto id = state.GetComponentID(eReplicaComponentType::REBUILD_ACTIVATOR, -1); + if (id > -1) AddComponent(id); + break; + } + case eReplicaComponentType::CHEST: { + // LWOChestComponent: the client makes it from the registry + const auto id = state.GetComponentID(eReplicaComponentType::CHEST, -1); + if (id > -1) AddComponent(id); + break; + } + case eReplicaComponentType::BLUEPRINT: { + // LWOBlueprintComponent: the client makes it from the registry + const auto id = state.GetComponentID(eReplicaComponentType::BLUEPRINT, -1); + if (id > -1) AddComponent(id); + break; + } + case eReplicaComponentType::MODULE: { + // LWOModuleComponent: the client makes it from the registry + const auto id = state.GetComponentID(eReplicaComponentType::MODULE, -1); + if (id > -1) AddComponent(id); + break; + } + case eReplicaComponentType::ARCADE: { + // LWOArcadeComponent: the client makes it from the registry + const auto id = state.GetComponentID(eReplicaComponentType::ARCADE, -1); + if (id > -1) AddComponent(id); + break; + } + case eReplicaComponentType::MODEL_BUILDER: { + // LWOModelBuilderComponent: the client makes it from the registry + const auto id = state.GetComponentID(eReplicaComponentType::MODEL_BUILDER, -1); + if (id > -1) AddComponent(id); + break; + } + case eReplicaComponentType::SPRINGPAD: { + // LWOSpringpadComponent: the client makes it from the registry + const auto id = state.GetComponentID(eReplicaComponentType::SPRINGPAD, -1); + if (id > -1) AddComponent(id); + break; + } + case eReplicaComponentType::CHANGLING: { + // LWOChangelingComponent: the client makes it from the registry + const auto id = state.GetComponentID(eReplicaComponentType::CHANGLING, -1); + if (id > -1) AddComponent(id); + break; + } + case eReplicaComponentType::CHOICE_BUILD: { + // LWOChoiceBuildComponent: the client makes it from the registry + const auto id = state.GetComponentID(eReplicaComponentType::CHOICE_BUILD, -1); + if (id > -1) AddComponent(id); + break; + } + case eReplicaComponentType::PACKAGE: { + // LWOPackageComponent: the client makes it from the registry + const auto id = state.GetComponentID(eReplicaComponentType::PACKAGE, -1); + if (id > -1) AddComponent(id); + break; + } + case eReplicaComponentType::CUSTOM_BUILD_ASSEMBLY: { + // LWOCustomBuildAssemblyComponent: the client makes it from the registry + const auto id = state.GetComponentID(eReplicaComponentType::CUSTOM_BUILD_ASSEMBLY, -1); + if (id > -1) AddComponent(id); + break; + } + case eReplicaComponentType::SHOWCASE_MODEL_HANDLER: { + // LWOShowcaseModelHandlerComponent: the client makes it from the registry + const auto id = state.GetComponentID(eReplicaComponentType::SHOWCASE_MODEL_HANDLER, -1); + if (id > -1) AddComponent(id); + break; + } + case eReplicaComponentType::GENERIC_ACTIVATOR: { + // LWOGenericActivatorComponent: the client makes it from the registry + const auto id = state.GetComponentID(eReplicaComponentType::GENERIC_ACTIVATOR, -1); + if (id > -1) AddComponent(id); + break; + } + case eReplicaComponentType::PROJECTILE_PHYSICS: { + // LWOProjectilePhysicsComponent: the client makes it from the registry + const auto id = state.GetComponentID(eReplicaComponentType::PROJECTILE_PHYSICS, -1); + if (id > -1) AddComponent(id); + break; + } + case eReplicaComponentType::VEHICLE_PHYSICS: { + // LWOVehiclePhysicsComponent: the client makes it from the registry + const auto id = state.GetComponentID(eReplicaComponentType::VEHICLE_PHYSICS, -1); + if (id > -1) AddComponent(id); + break; + } + case eReplicaComponentType::PHYSICS_SYSTEM: { + // The client's physics system component: the client makes it from the registry + const auto id = state.GetComponentID(eReplicaComponentType::PHYSICS_SYSTEM, -1); + if (id > -1) AddComponent(id); + break; + } + case eReplicaComponentType::SPAWNER: { + // A spawner object's spawner component, from the registry as the client makes it + const auto id = state.GetComponentID(eReplicaComponentType::SPAWNER, -1); + if (id > -1) AddComponent(id); + break; + } + case eReplicaComponentType::HF_LIGHT_DIRECTION_GADGET: { + // The client makes it from the registry (an editor object) + const auto id = state.GetComponentID(eReplicaComponentType::HF_LIGHT_DIRECTION_GADGET, -1); + if (id > -1) AddComponent(id); + break; + } + case eReplicaComponentType::PLAYER_FORCED_MOVEMENT: { + // From the registry, as the client makes it (a character got one with its character component) + const auto id = state.GetComponentID(eReplicaComponentType::PLAYER_FORCED_MOVEMENT, -1); + if (id > -1 && !HasComponent(eReplicaComponentType::PLAYER_FORCED_MOVEMENT)) AddComponent(id); + break; + } + case eReplicaComponentType::ROCKET_LANDING: { + // LWORocketAnimationControlComponent: the client makes it from the registry + const auto id = state.GetComponentID(eReplicaComponentType::ROCKET_LANDING, -1); + if (id > -1) AddComponent(id); + break; + } + case eReplicaComponentType::FACTION_TRIGGER: { + // LWOFactionTriggerComponent: the client makes it from the registry + const auto id = state.GetComponentID(eReplicaComponentType::FACTION_TRIGGER, -1); + if (id > -1) AddComponent(id); + break; + } + case eReplicaComponentType::GATE_RUSH_CONTROL: { + // LWOGateRushComponent: the client makes it from the registry + const auto id = state.GetComponentID(eReplicaComponentType::GATE_RUSH_CONTROL, -1); + if (id > -1) AddComponent(id); + break; + } + case eReplicaComponentType::ROLLER: { + // LWORollerComponent: the client makes it from the registry + const auto id = state.GetComponentID(eReplicaComponentType::ROLLER, -1); + if (id > -1) AddComponent(id); + break; + } + case eReplicaComponentType::PROPERTY_PLAQUE: { + // LoadPropertyPlaqueComponent: the client makes it from the registry + const auto id = state.GetComponentID(eReplicaComponentType::PROPERTY_PLAQUE, -1); + if (id > -1) AddComponent(id); + break; + } + default: + break; + } + } + + /** + * Components DLU has that the client's load order doesn't make + */ + const auto racingStatsID = state.GetComponentID(eReplicaComponentType::RACING_STATS); + if (racingStatsID > 0) { + AddComponent(racingStatsID); + } + + /** + * Added after the walk, as the client does (DoObjectComponentLoad): the ghost of a character (LWOGhostComponent's + * allocator 0x0101c010 makes one for characters with physics; level and plain spawned objects get none), then the + * trigger from the level config + */ + if (m_Character && HasComponent(eReplicaComponentType::CONTROLLABLE_PHYSICS)) { + AddComponent(-1)->LoadFromSaveData(m_Character->GetXMLDoc()); + } + // A mission offer's object gets a proximity monitor after the walk when it has none (the registry's mission offer; + // a character's is DLU's own) + if (!m_Character && HasComponent(eReplicaComponentType::MISSION_OFFER) && !HasComponent(eReplicaComponentType::PROXIMITY_MONITOR)) { + AddComponent(-1); + } + const auto triggerInfo = GetVarAsString(u"trigger_id"); + if (!triggerInfo.empty()) AddComponent(-1, triggerInfo); +} + void Entity::Initialize() { RegisterMsg(&Entity::MsgRequestServerObjectInfo); RegisterMsg(&Entity::MsgDropClientLoot); @@ -288,703 +999,8 @@ void Entity::Initialize() { m_ParentEntity->AddChild(this); } - // Get the registry table - CDComponentsRegistryTable* const compRegistryTable = CDClientManager::GetTable(); - - /** - * Player components (the client makes them with the character, not from the registry) - */ - if (m_Character && m_Character->GetParentUser()) { - AddComponent(-1)->LoadFromSaveData(m_Character->GetXMLDoc()); - } - - /** - * The components in the client's load order (ObjectLoader2::DoObjectComponentLoad 0x0105ebb0 walks - * ComponentOrderVector::Initialize 0x0101f8e0's list): a component that looks at another one finds it made when the - * client's does. Ids a component needs from a later one come from the registry. - */ - const auto petComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::PET); - const auto collectibleComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::COLLECTIBLE); - const auto combatAiID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::BASE_COMBAT_AI); - // LWOScriptComponent::Allocator (0x0104d710) and LWOBaseCombatAiComponent::Allocator (0x0104c7b0) add a proximity - // monitor when there is none, and pathfinding control when the object has movement physics (controllable, - // projectile, vehicle or havok vehicle) and none yet - const auto addProximityAndPathfinding = [this]() { - if (!HasComponent(eReplicaComponentType::PROXIMITY_MONITOR)) AddComponent(-1); - const bool hasMovementPhysics = HasComponent(eReplicaComponentType::CONTROLLABLE_PHYSICS) || HasComponent(eReplicaComponentType::PROJECTILE_PHYSICS) - || HasComponent(eReplicaComponentType::VEHICLE_PHYSICS) || HasComponent(eReplicaComponentType::HAVOK_VEHICLE_PHYSICS); - if (hasMovementPhysics && !HasComponent(eReplicaComponentType::PATHFINDING)) AddComponent(-1); - }; - // A model with behaviors (DoObjectPreLoad 0x01055667: not inInventory, and modelType and modelBehaviors in its - // config) gets its physics from the model case, which takes the registry's simple physics id - const bool hasModelBehaviors = !GetVar(u"inInventory") && HasVar(u"modelType") && HasVar(u"modelBehaviors"); - int32_t modelPhysicsComponentID = -1; - // If an entity is marked a phantom, simple physics is made into phantom phyics. - // DoObjectPreLoad: markedAsPhantom doesn't count when the registry has phantom physics - const bool markedAsPhantom = HasVar(u"markedAsPhantom") && compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::PHANTOM_PHYSICS, -1) == -1; - - // ObjectLoader2::ApplyComponentWhitelist (0x0103e300): the config's componentWhitelist keeps only these registry - // components (the lists ObjectLoader2's constructor builds; property models use 1) - static constexpr std::array, 5> COMPONENT_WHITELISTS{ { - { eReplicaComponentType::CONTROLLABLE_PHYSICS, eReplicaComponentType::SIMPLE_PHYSICS, eReplicaComponentType::RENDER }, - { eReplicaComponentType::RENDER, eReplicaComponentType::DESTROYABLE, eReplicaComponentType::ITEM, eReplicaComponentType::BLUEPRINT, - eReplicaComponentType::MODEL, eReplicaComponentType::CONTROLLABLE_PHYSICS, eReplicaComponentType::SIMPLE_PHYSICS, eReplicaComponentType::SPAWNER }, - { eReplicaComponentType::RENDER, eReplicaComponentType::ITEM, eReplicaComponentType::BLUEPRINT }, - { eReplicaComponentType::PET, eReplicaComponentType::SKILL, eReplicaComponentType::DESTROYABLE, eReplicaComponentType::RENDER, eReplicaComponentType::CONTROLLABLE_PHYSICS }, - { eReplicaComponentType::CONTROLLABLE_PHYSICS, eReplicaComponentType::SIMPLE_PHYSICS, eReplicaComponentType::RENDER }, - } }; - const auto whitelistIndex = HasVar(u"componentWhitelist") ? GetVarAs(u"componentWhitelist") : -1; - const std::initializer_list* const whitelist = - whitelistIndex >= 0 && whitelistIndex < static_cast(COMPONENT_WHITELISTS.size()) ? &COMPONENT_WHITELISTS[whitelistIndex] : nullptr; - - for (const auto type : ClientComponentLoadOrder) { - if (whitelist && std::ranges::find(*whitelist, type) == whitelist->end()) continue; - switch (type) { - case eReplicaComponentType::POSSESSABLE: { - const auto possessableComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::POSSESSABLE); - if (possessableComponentID > 0) { - AddComponent(possessableComponentID); - } - break; - } - case eReplicaComponentType::MODULE_ASSEMBLY: { - const auto moduleAssemblyID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::MODULE_ASSEMBLY); - if (moduleAssemblyID > 0) { - AddComponent(moduleAssemblyID); - } - break; - } - case eReplicaComponentType::CONTROLLABLE_PHYSICS: { - const auto controllablePhysicsComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::CONTROLLABLE_PHYSICS); - if (controllablePhysicsComponentID > 0) { - // The component places it: where the level put an object, where a character's save and the zone put it - auto* controllablePhysics = AddComponent(controllablePhysicsComponentID); - if (m_Character) controllablePhysics->LoadFromSaveData(m_Character->GetXMLDoc()); - } - break; - } - case eReplicaComponentType::HAVOK_VEHICLE_PHYSICS: { - const auto havokVehiclePhysicsComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::HAVOK_VEHICLE_PHYSICS); - // The client makes simple physics instead for use_simple_physics, and none for objectWorldState 2 - if (havokVehiclePhysicsComponentID > 0 && HasVar(u"use_simple_physics")) { - if (!HasComponent(eReplicaComponentType::SIMPLE_PHYSICS)) AddComponent(havokVehiclePhysicsComponentID); - } else if (havokVehiclePhysicsComponentID > 0 && !(HasVar(u"objectWorldState") && GetVarAs(u"objectWorldState") == 2)) { - auto* havokVehiclePhysicsComponent = AddComponent(havokVehiclePhysicsComponentID); - havokVehiclePhysicsComponent->SetPosition(m_DefaultPosition); - havokVehiclePhysicsComponent->SetRotation(m_DefaultRotation); - } - break; - } - case eReplicaComponentType::RIGID_BODY_PHANTOM_PHYSICS: { - const auto rigidBodyPhantomPhysicsComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::RIGID_BODY_PHANTOM_PHYSICS); - if (rigidBodyPhantomPhysicsComponentID > 0) { - AddComponent(rigidBodyPhantomPhysicsComponentID); - } - break; - } - case eReplicaComponentType::SIMPLE_PHYSICS: { - const auto simplePhysicsComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::SIMPLE_PHYSICS); - if (hasModelBehaviors) { - // LWOSimplePhysicsComponent::Allocator (0x0104ded0): left to the model case - if (simplePhysicsComponentID > 0) modelPhysicsComponentID = simplePhysicsComponentID; - } else if (simplePhysicsComponentID > 0) { - // markedAsPhantom makes its simple physics phantom physics (with the simple physics row) - if (markedAsPhantom) AddComponent(simplePhysicsComponentID)->SetPhysicsEffectActive(false); - else AddComponent(simplePhysicsComponentID); - } - break; - } - case eReplicaComponentType::PHANTOM_PHYSICS: { - const auto phantomPhysicsComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::PHANTOM_PHYSICS); - if (phantomPhysicsComponentID > 0 && !HasComponent(eReplicaComponentType::PHANTOM_PHYSICS)) { - AddComponent(phantomPhysicsComponentID)->SetPhysicsEffectActive(false); - } - break; - } - case eReplicaComponentType::DESTROYABLE: { - /** - * The destroyable component. The client makes one (with a buff component in front of it) for a DESTROYABLE - * registry entry, whose id is the DestructibleComponent row. Collectibles and smashable (is_smashable) objects - * without that entry get one too, with no row. - */ - const auto destroyableComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::DESTROYABLE); - if (destroyableComponentID > 0) { - AddComponent(destroyableComponentID); - } - - bool isSmashable = GetVarAs(u"is_smashable") != 0; - if (destroyableComponentID > 0 || collectibleComponentID > 0 || isSmashable) { - const int32_t componentID = destroyableComponentID > 0 ? destroyableComponentID : -1; - // The row, the level config and a character's save, in the client's order - DestroyableComponent* comp = AddComponent(componentID); - if (m_Character) comp->LoadFromSaveData(m_Character->GetXMLDoc()); - } - break; - } - case eReplicaComponentType::MINIFIG: { - const auto minifigID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::MINIFIG); - if (minifigID > 0) AddComponent(minifigID); - break; - } - case eReplicaComponentType::CHARACTER: { - const auto characterID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::CHARACTER); - if (characterID > 0 || m_Character) { - // LWOCharacterComponent::Factory (0x0104db40): possession control, mount control, pet creator, level - // progression and forced movement, then the character, all componentID -1 - AddComponent(-1); - AddComponent(-1); - AddComponent(-1); - AddComponent(-1)->LoadFromSaveData(m_Character->GetXMLDoc()); - AddComponent(-1); - - auto& systemAddress = m_Character->GetParentUser() ? m_Character->GetParentUser()->GetSystemAddress() : UNASSIGNED_SYSTEM_ADDRESS; - AddComponent(-1, m_Character, systemAddress)->LoadFromSaveData(m_Character->GetXMLDoc()); - - } - break; - } - case eReplicaComponentType::PET: { - // LWOPetComponent::Factory (0x01031240): no pet in an inventory; otherwise pathfinding control first - if (petComponentID > 0 && !GetVar(u"inInventory")) { - if (!HasComponent(eReplicaComponentType::PATHFINDING)) AddComponent(-1); - AddComponent(petComponentID); - } - break; - } - case eReplicaComponentType::INVENTORY: { - const auto inventoryID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::INVENTORY); - if (inventoryID > 0 || m_Character) { - AddComponent(inventoryID); - } - break; - } - case eReplicaComponentType::PROXIMITY_MONITOR: { - const auto proximityMonitorID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::PROXIMITY_MONITOR); - if (proximityMonitorID > 0) AddComponent(proximityMonitorID); - break; - } - case eReplicaComponentType::MOVEMENT_AI: { - const int movementAIID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::MOVEMENT_AI); - if (movementAIID > 0) { - // Its row and the level's wanderRadius load in the component - AddComponent(movementAIID, MovementAIInfo{}); - } else if (petComponentID > 0 || combatAiID > 0 && BaseCombatAIComponent::TetherSpeedOf(combatAiID) > 0) { - MovementAIInfo moveInfo{ - .movementType = "", - .wanderRadius = 16, - .wanderSpeed = 2.5f, - .wanderChance = 0, - .wanderDelayMin = 2, - .wanderDelayMax = 5, - }; - - AddComponent(-1, moveInfo); - } - break; - } - case eReplicaComponentType::SCRIPT: { - // 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(); - const int32_t scriptComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::SCRIPT, -1); - const bool hasCustomScript = !GetVarAsString(u"custom_script_server").empty() || !GetVarAsString(u"custom_script_client").empty(); - if (scriptComponentID >= 0 || m_Character || hasCustomScript) { - AddComponent(scriptComponentID); - addProximityAndPathfinding(); - } - - // ZoneControl script - if (m_TemplateID == 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) - AddComponent(zoneScriptID, zoneScriptData.script_name, false, false); - } - } - break; - } - case eReplicaComponentType::SKILL: { - const auto skillID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::SKILL, -1); - if (skillID > -1 || m_Character) { - auto* const skillComponent = AddComponent(skillID); - if (m_Character) skillComponent->LoadFromSaveData(m_Character->GetXMLDoc()); - } - break; - } - case eReplicaComponentType::BASE_COMBAT_AI: { - if (combatAiID > 0) { - addProximityAndPathfinding(); - AddComponent(combatAiID); - } - break; - } - case eReplicaComponentType::ITEM: { - // A pet takes the item (and model) component away (LWOPetComponent::Factory); models keep theirs (live: an - // item block before the model's) - PetComponent* petComponent{}; - const auto itemID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::ITEM); - if (itemID > 0 && !TryGetComponent(eReplicaComponentType::PET, petComponent)) { - AddComponent(itemID); - } - break; - } - case eReplicaComponentType::BUILD_BORDER: { - const auto buildBorderID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::BUILD_BORDER, -1); - if (buildBorderID > -1) { - AddComponent(buildBorderID); - } - break; - } - case eReplicaComponentType::QUICK_BUILD: { - const auto quickBuildComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::QUICK_BUILD); - if (quickBuildComponentID > 0) { - AddComponent(quickBuildComponentID); - - // A quick build without a destroyable gets one (componentID -1) and is smashable, as the client makes it - // (DoObjectComponentLoad); live: health 1 / 1.0, armor 1 / 0, faction -1, smashable (AG LOT 7804) - if (!HasComponent(eReplicaComponentType::DESTROYABLE)) { - AddComponent(-1, true); - } - } - break; - } - case eReplicaComponentType::MISSION_OFFER: { - const auto missionOfferComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::MISSION_OFFER, -1); - if (missionOfferComponentID > -1 || m_Character) { - AddComponent(missionOfferComponentID); - } - break; - } - case eReplicaComponentType::VENDOR: { - const auto vendorID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::VENDOR); - // The vendor allocators add a proximity monitor first when there is none - if (vendorID > 0) { - if (!HasComponent(eReplicaComponentType::PROXIMITY_MONITOR)) AddComponent(-1); - AddComponent(vendorID); - } - break; - } - case eReplicaComponentType::DONATION_VENDOR: { - const auto donationVendorID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::DONATION_VENDOR, -1); - if (donationVendorID > -1) { - if (!HasComponent(eReplicaComponentType::PROXIMITY_MONITOR)) AddComponent(-1); - AddComponent(donationVendorID); - } - break; - } - case eReplicaComponentType::ACHIEVEMENT_VENDOR: { - const auto achievementVendorID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::ACHIEVEMENT_VENDOR, -1); - if (achievementVendorID > -1) { - if (!HasComponent(eReplicaComponentType::PROXIMITY_MONITOR)) AddComponent(-1); - AddComponent(achievementVendorID); - } - break; - } - case eReplicaComponentType::SHOOTING_GALLERY: { - // Shooting gallery component - const auto shootingGalleryComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::SHOOTING_GALLERY); - if (shootingGalleryComponentID > 0) { - AddComponent(shootingGalleryComponentID); - } - break; - } - case eReplicaComponentType::SCRIPTED_ACTIVITY: { - // Scripted activity component - const auto scriptedActivityID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::SCRIPTED_ACTIVITY, -1); - if (scriptedActivityID > -1) { - AddComponent(scriptedActivityID); - } - break; - } - case eReplicaComponentType::COLLECTIBLE: { - - if (collectibleComponentID > 0) { - AddComponent(collectibleComponentID, GetVarAs(u"collectible_id")); - } - break; - } - case eReplicaComponentType::MOVING_PLATFORM: { - const std::string pathName = GetVarAsString(u"attached_path"); - const Path* const path = Game::zoneManager->GetZone()->GetPath(pathName); - - //Check to see if we have an attached path and add the appropiate component to handle it: - if (path) { - // if we have a moving platform path, then we need a moving platform component - if (path->pathType == PathType::MovingPlatform) { - // The registry component ID picks the subcomponent (a simple mover when it has one), path or not - AddComponent(compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::MOVING_PLATFORM, -1), pathName); - } else if (path->pathType == PathType::Movement) { - auto* const movementAIcomponent = GetComponent(); - if (movementAIcomponent && combatAiID == 0) { - movementAIcomponent->SetPath(pathName); - } else { - MovementAIInfo moveInfo{ - .movementType = "", - .wanderRadius = 16, - .wanderSpeed = 2.5f, - .wanderChance = 0, - .wanderDelayMin = 2, - .wanderDelayMax = 5, - }; - - AddComponent(-1, moveInfo); - } - } - } else { - // else we still need to setup moving platform if it has a moving platform comp but no path - const auto movingPlatformComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::MOVING_PLATFORM, -1); - if (movingPlatformComponentID >= 0) { - AddComponent(movingPlatformComponentID, pathName); - } - } - break; - } - case eReplicaComponentType::LUP_EXHIBIT: { - const auto lupExhibitID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::LUP_EXHIBIT, -1); - if (lupExhibitID >= 0) { - AddComponent(lupExhibitID); - } - break; - } - case eReplicaComponentType::PROPERTY: { - const auto propertyID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::PROPERTY, -1); - if (propertyID > -1) { - AddComponent(propertyID); - } - break; - } - case eReplicaComponentType::MODEL: { - const auto modelID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::MODEL, -1); - if (modelID > -1 && !GetComponent()) { - // ObjectLoader2::LoadModelBehaviorsComponent (0x0104d1f0): a model without physics gets it here (BBB models, - // LOT 14, have none in the registry): simple physics 4247 without behaviors; with behaviors controllable - // (4246) for modelType 0, else simple (4247), unless the registry gave an id. modelType 0 also gets - // pathfinding control. - const bool hasPhysics = HasComponent(eReplicaComponentType::CONTROLLABLE_PHYSICS) || HasComponent(eReplicaComponentType::SIMPLE_PHYSICS) - || HasComponent(eReplicaComponentType::PROJECTILE_PHYSICS) || HasComponent(eReplicaComponentType::RIGID_BODY_PHANTOM_PHYSICS) - || HasComponent(eReplicaComponentType::HAVOK_VEHICLE_PHYSICS) || HasComponent(eReplicaComponentType::PHANTOM_PHYSICS) - || HasComponent(eReplicaComponentType::VEHICLE_PHYSICS) || HasComponent(eReplicaComponentType::PHYSICS_SYSTEM); - const int32_t modelType = HasVar(u"modelType") ? GetVarAs(u"modelType") : -1; - if (!hasPhysics) { - if (!hasModelBehaviors) AddComponent(4247); - else if (modelType == 0) AddComponent(modelPhysicsComponentID == -1 ? 4246 : modelPhysicsComponentID); - else AddComponent(modelPhysicsComponentID == -1 ? 4247 : modelPhysicsComponentID); - } - if (modelType == 0 && !HasComponent(eReplicaComponentType::PATHFINDING)) AddComponent(-1); - AddComponent(modelID)->LoadBehaviors(); - if (!HasComponent(eReplicaComponentType::DESTROYABLE)) { - AddComponent(-1, true); - } - } - break; - } - case eReplicaComponentType::PROPERTY_ENTRANCE: { - const auto propertyEntranceComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::PROPERTY_ENTRANCE); - if (propertyEntranceComponentID > 0) { - AddComponent(propertyEntranceComponentID); - } - break; - } - case eReplicaComponentType::MULTI_ZONE_ENTRANCE: { - // if this component exists, then we initialize it. it's value is always 0 - const auto multiZoneEntranceID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::MULTI_ZONE_ENTRANCE, -1); - if (multiZoneEntranceID > -1) { - AddComponent(multiZoneEntranceID); - } - break; - } - case eReplicaComponentType::PROPERTY_MANAGEMENT: { - const auto propertyManagementID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::PROPERTY_MANAGEMENT, -1); - if (propertyManagementID > -1) { - AddComponent(propertyManagementID); - } - break; - } - case eReplicaComponentType::RAIL_ACTIVATOR: { - const auto railComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::RAIL_ACTIVATOR); - if (railComponentID > 0) { - AddComponent(railComponentID); - } - break; - } - case eReplicaComponentType::BOUNCER: { - const auto bouncerID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::BOUNCER, -1); - if (bouncerID > -1) { // you have to determine it like this because all bouncers have a componentID of 0 - AddComponent(bouncerID); - } - break; - } - case eReplicaComponentType::SWITCH: { - const auto switchID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::SWITCH, -1); - if (switchID > -1) { - AddComponent(switchID); - } - break; - } - case eReplicaComponentType::MINI_GAME_CONTROL: { - // 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; - } - case eReplicaComponentType::RENDER: { - const auto renderComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::RENDER); - if ((renderComponentID > 0 && m_TemplateID != 2365) || m_Character) { - AddComponent(renderComponentID); - } - break; - } - case eReplicaComponentType::SOUND_TRIGGER: { - const auto soundTriggerID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::SOUND_TRIGGER, -1); - const auto racingSoundTriggerID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::RACING_SOUND_TRIGGER, -1); - if (soundTriggerID > -1) { - AddComponent(soundTriggerID); - } else if (racingSoundTriggerID > -1) { - AddComponent(racingSoundTriggerID); - } - break; - } - case eReplicaComponentType::PROPERTY_VENDOR: { - const auto propertyVendorID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::PROPERTY_VENDOR, -1); - if (propertyVendorID > -1) { - AddComponent(propertyVendorID); - } - break; - } - case eReplicaComponentType::ROCKET_LAUNCH: { - const auto rocketID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::ROCKET_LAUNCH); - if ((rocketID > 0)) { - AddComponent(rocketID); - } - break; - } - case eReplicaComponentType::RACING_CONTROL: { - const auto racingControlID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::RACING_CONTROL); - if (racingControlID > 0) { - AddComponent(racingControlID); - } - break; - } - case eReplicaComponentType::SOUND_REPEATER: { - // LWOSoundRepeaterComponent: the client makes it from the registry - const auto id = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::SOUND_REPEATER, -1); - if (id > -1) AddComponent(id); - break; - } - case eReplicaComponentType::SOUND_AMBIENT_2D: { - // LWOSoundAmbient2DComponent: the client makes it from the registry - const auto id = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::SOUND_AMBIENT_2D, -1); - if (id > -1) AddComponent(id); - break; - } - case eReplicaComponentType::SOUND_AMBIENT_3D: { - // LWOSoundAmbient3DComponent: the client makes it from the registry - const auto id = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::SOUND_AMBIENT_3D, -1); - if (id > -1) AddComponent(id); - break; - } - case eReplicaComponentType::CULLING_PLANE: { - // LoadCullingPlaneComponent: the client makes it from the registry - const auto id = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::CULLING_PLANE, -1); - if (id > -1) AddComponent(id); - break; - } - case eReplicaComponentType::PLATFORM_BOUNDARY: { - // LWOPlatformBoundaryComponent: the client makes it from the registry - const auto id = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::PLATFORM_BOUNDARY, -1); - if (id > -1) AddComponent(id); - break; - } - case eReplicaComponentType::REBUILD: { - // LWOModularBuildComponent: the client makes it from the registry - const auto id = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::REBUILD, -1); - if (id > -1) AddComponent(id); - break; - } - case eReplicaComponentType::REBUILD_START: { - // LWOBuildControllerComponent: the client makes it from the registry - const auto id = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::REBUILD_START, -1); - if (id > -1) AddComponent(id); - break; - } - case eReplicaComponentType::REBUILD_ACTIVATOR: { - // LWORebuildActivatorComponent: the client makes it from the registry - const auto id = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::REBUILD_ACTIVATOR, -1); - if (id > -1) AddComponent(id); - break; - } - case eReplicaComponentType::CHEST: { - // LWOChestComponent: the client makes it from the registry - const auto id = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::CHEST, -1); - if (id > -1) AddComponent(id); - break; - } - case eReplicaComponentType::BLUEPRINT: { - // LWOBlueprintComponent: the client makes it from the registry - const auto id = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::BLUEPRINT, -1); - if (id > -1) AddComponent(id); - break; - } - case eReplicaComponentType::MODULE: { - // LWOModuleComponent: the client makes it from the registry - const auto id = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::MODULE, -1); - if (id > -1) AddComponent(id); - break; - } - case eReplicaComponentType::ARCADE: { - // LWOArcadeComponent: the client makes it from the registry - const auto id = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::ARCADE, -1); - if (id > -1) AddComponent(id); - break; - } - case eReplicaComponentType::MODEL_BUILDER: { - // LWOModelBuilderComponent: the client makes it from the registry - const auto id = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::MODEL_BUILDER, -1); - if (id > -1) AddComponent(id); - break; - } - case eReplicaComponentType::SPRINGPAD: { - // LWOSpringpadComponent: the client makes it from the registry - const auto id = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::SPRINGPAD, -1); - if (id > -1) AddComponent(id); - break; - } - case eReplicaComponentType::CHANGLING: { - // LWOChangelingComponent: the client makes it from the registry - const auto id = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::CHANGLING, -1); - if (id > -1) AddComponent(id); - break; - } - case eReplicaComponentType::CHOICE_BUILD: { - // LWOChoiceBuildComponent: the client makes it from the registry - const auto id = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::CHOICE_BUILD, -1); - if (id > -1) AddComponent(id); - break; - } - case eReplicaComponentType::PACKAGE: { - // LWOPackageComponent: the client makes it from the registry - const auto id = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::PACKAGE, -1); - if (id > -1) AddComponent(id); - break; - } - case eReplicaComponentType::CUSTOM_BUILD_ASSEMBLY: { - // LWOCustomBuildAssemblyComponent: the client makes it from the registry - const auto id = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::CUSTOM_BUILD_ASSEMBLY, -1); - if (id > -1) AddComponent(id); - break; - } - case eReplicaComponentType::SHOWCASE_MODEL_HANDLER: { - // LWOShowcaseModelHandlerComponent: the client makes it from the registry - const auto id = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::SHOWCASE_MODEL_HANDLER, -1); - if (id > -1) AddComponent(id); - break; - } - case eReplicaComponentType::GENERIC_ACTIVATOR: { - // LWOGenericActivatorComponent: the client makes it from the registry - const auto id = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::GENERIC_ACTIVATOR, -1); - if (id > -1) AddComponent(id); - break; - } - case eReplicaComponentType::PROJECTILE_PHYSICS: { - // LWOProjectilePhysicsComponent: the client makes it from the registry - const auto id = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::PROJECTILE_PHYSICS, -1); - if (id > -1) AddComponent(id); - break; - } - case eReplicaComponentType::VEHICLE_PHYSICS: { - // LWOVehiclePhysicsComponent: the client makes it from the registry - const auto id = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::VEHICLE_PHYSICS, -1); - if (id > -1) AddComponent(id); - break; - } - case eReplicaComponentType::PHYSICS_SYSTEM: { - // The client's physics system component: the client makes it from the registry - const auto id = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::PHYSICS_SYSTEM, -1); - if (id > -1) AddComponent(id); - break; - } - case eReplicaComponentType::SPAWNER: { - // A spawner object's spawner component, from the registry as the client makes it - const auto id = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::SPAWNER, -1); - if (id > -1) AddComponent(id); - break; - } - case eReplicaComponentType::HF_LIGHT_DIRECTION_GADGET: { - // The client makes it from the registry (an editor object) - const auto id = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::HF_LIGHT_DIRECTION_GADGET, -1); - if (id > -1) AddComponent(id); - break; - } - case eReplicaComponentType::PLAYER_FORCED_MOVEMENT: { - // From the registry, as the client makes it (a character got one with its character component) - const auto id = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::PLAYER_FORCED_MOVEMENT, -1); - if (id > -1 && !HasComponent(eReplicaComponentType::PLAYER_FORCED_MOVEMENT)) AddComponent(id); - break; - } - case eReplicaComponentType::ROCKET_LANDING: { - // LWORocketAnimationControlComponent: the client makes it from the registry - const auto id = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::ROCKET_LANDING, -1); - if (id > -1) AddComponent(id); - break; - } - case eReplicaComponentType::FACTION_TRIGGER: { - // LWOFactionTriggerComponent: the client makes it from the registry - const auto id = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::FACTION_TRIGGER, -1); - if (id > -1) AddComponent(id); - break; - } - case eReplicaComponentType::GATE_RUSH_CONTROL: { - // LWOGateRushComponent: the client makes it from the registry - const auto id = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::GATE_RUSH_CONTROL, -1); - if (id > -1) AddComponent(id); - break; - } - case eReplicaComponentType::ROLLER: { - // LWORollerComponent: the client makes it from the registry - const auto id = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::ROLLER, -1); - if (id > -1) AddComponent(id); - break; - } - case eReplicaComponentType::PROPERTY_PLAQUE: { - // LoadPropertyPlaqueComponent: the client makes it from the registry - const auto id = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::PROPERTY_PLAQUE, -1); - if (id > -1) AddComponent(id); - break; - } - default: - break; - } - } - - /** - * Components DLU has that the client's load order doesn't make - */ - const auto racingStatsID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::RACING_STATS); - if (racingStatsID > 0) { - AddComponent(racingStatsID); - } - - /** - * Added after the walk, as the client does (DoObjectComponentLoad): the ghost of a character (LWOGhostComponent's - * allocator 0x0101c010 makes one for characters with physics; level and plain spawned objects get none), then the - * trigger from the level config - */ - if (m_Character && HasComponent(eReplicaComponentType::CONTROLLABLE_PHYSICS)) { - AddComponent(-1)->LoadFromSaveData(m_Character->GetXMLDoc()); - } - // A mission offer's object gets a proximity monitor after the walk when it has none (the registry's mission offer; - // a character's is DLU's own) - if (!m_Character && HasComponent(eReplicaComponentType::MISSION_OFFER) && !HasComponent(eReplicaComponentType::PROXIMITY_MONITOR)) { - AddComponent(-1); - } - const auto triggerInfo = GetVarAsString(u"trigger_id"); - if (!triggerInfo.empty()) AddComponent(-1, triggerInfo); + auto loadState = DoObjectPreLoad(); + DoObjectComponentLoad(loadState); // Every component exists: the client's Startup step for (auto* const component : m_Components | std::views::values) if (component) component->Startup(); diff --git a/dGame/Entity.h b/dGame/Entity.h index 2d28368ec..1bb612572 100644 --- a/dGame/Entity.h +++ b/dGame/Entity.h @@ -396,6 +396,28 @@ public: static Observable OnPlayerPositionUpdate; private: + /** + * The client's ObjectLoadState (ObjectLoader2): the object's components as its registry gives them, after the + * pre-load rules and the component whitelist, and what the component walk has found so far + */ + struct ObjectLoadState { + LOT lot{}; + // The config's componentWhitelist (ApplyComponentWhitelist): only these registry components, when set + const std::initializer_list* whitelist = nullptr; + bool hasModelBehaviors = false; + bool markedAsPhantom = false; + // A model with behaviors' physics id, which the physics cases leave to the model case + int32_t modelPhysicsComponentID = -1; + + // The registry's id for type, defaultValue when it has none or the whitelist drops it + int32_t GetComponentID(eReplicaComponentType type, int32_t defaultValue = 0) const; + }; + + // ObjectLoader2::DoObjectPreLoad: what the components will be, before any is made + ObjectLoadState DoObjectPreLoad(); + // ObjectLoader2::DoObjectComponentLoad: the components in the client's order, then the ones it adds after + void DoObjectComponentLoad(ObjectLoadState& state); + /** * Get the LDF data.