diff --git a/dGame/Entity.cpp b/dGame/Entity.cpp index 143fe0af9..b689de8bf 100644 --- a/dGame/Entity.cpp +++ b/dGame/Entity.cpp @@ -259,15 +259,104 @@ namespace { for (size_t i = 0; i < types.size(); i++) order[i] = static_cast(types[i]); return order; }(); + + // A component the client makes from its registry entry alone (most LWOxComponent::Allocator functions): ids from + // MinID up make one + template + void RegistryAllocator(Entity& entity, ObjectLoadState& state) { + const auto id = state.GetComponentID(Type, -1); + if (id >= MinID) entity.AddComponent(id); + } + + // The allocator for each type DoObjectComponentLoad walks (the client's allocator table); a type with none makes + // nothing + const std::unordered_map ComponentAllocators{ + { eReplicaComponentType::POSSESSABLE, &RegistryAllocator }, + { eReplicaComponentType::MODULE_ASSEMBLY, &RegistryAllocator }, + { eReplicaComponentType::CONTROLLABLE_PHYSICS, &ControllablePhysicsComponent::Allocator }, + { eReplicaComponentType::HAVOK_VEHICLE_PHYSICS, &HavokVehiclePhysicsComponent::Allocator }, + { eReplicaComponentType::RIGID_BODY_PHANTOM_PHYSICS, &RegistryAllocator }, + { eReplicaComponentType::SIMPLE_PHYSICS, &SimplePhysicsComponent::Allocator }, + { eReplicaComponentType::PHANTOM_PHYSICS, &PhantomPhysicsComponent::Allocator }, + { eReplicaComponentType::DESTROYABLE, &DestroyableComponent::Allocator }, + { eReplicaComponentType::MINIFIG, &RegistryAllocator }, + { eReplicaComponentType::CHARACTER, &CharacterComponent::Allocator }, + { eReplicaComponentType::PET, &PetComponent::Allocator }, + { eReplicaComponentType::INVENTORY, &InventoryComponent::Allocator }, + { eReplicaComponentType::PROXIMITY_MONITOR, &RegistryAllocator }, + { eReplicaComponentType::MOVEMENT_AI, &MovementAIComponent::Allocator }, + { eReplicaComponentType::SCRIPT, &ScriptComponent::Allocator }, + { eReplicaComponentType::SKILL, &SkillComponent::Allocator }, + { eReplicaComponentType::BASE_COMBAT_AI, &BaseCombatAIComponent::Allocator }, + { eReplicaComponentType::ITEM, &ItemComponent::Allocator }, + { eReplicaComponentType::BUILD_BORDER, &RegistryAllocator }, + { eReplicaComponentType::QUICK_BUILD, &QuickBuildComponent::Allocator }, + { eReplicaComponentType::MISSION_OFFER, &RegistryAllocator }, + { eReplicaComponentType::VENDOR, &VendorComponent::Allocator }, + { eReplicaComponentType::DONATION_VENDOR, &DonationVendorComponent::Allocator }, + { eReplicaComponentType::ACHIEVEMENT_VENDOR, &AchievementVendorComponent::Allocator }, + { eReplicaComponentType::SHOOTING_GALLERY, &RegistryAllocator }, + { eReplicaComponentType::SCRIPTED_ACTIVITY, &RegistryAllocator }, + { eReplicaComponentType::COLLECTIBLE, &CollectibleComponent::Allocator }, + { eReplicaComponentType::MOVING_PLATFORM, &MovingPlatformComponent::Allocator }, + { eReplicaComponentType::LUP_EXHIBIT, &RegistryAllocator }, + { eReplicaComponentType::PROPERTY, &RegistryAllocator }, + { eReplicaComponentType::MODEL, &ModelComponent::Allocator }, + { eReplicaComponentType::PROPERTY_ENTRANCE, &RegistryAllocator }, + { eReplicaComponentType::MULTI_ZONE_ENTRANCE, &RegistryAllocator }, // always id 0 + { eReplicaComponentType::PROPERTY_MANAGEMENT, &RegistryAllocator }, + { eReplicaComponentType::RAIL_ACTIVATOR, &RegistryAllocator }, + { eReplicaComponentType::BOUNCER, &RegistryAllocator }, // every bouncer's id is 0 + { eReplicaComponentType::SWITCH, &RegistryAllocator }, + { eReplicaComponentType::MINI_GAME_CONTROL, &RegistryAllocator }, // the zone control object's id is 0 + { eReplicaComponentType::RENDER, &RenderComponent::Allocator }, + { eReplicaComponentType::SOUND_TRIGGER, &SoundTriggerComponent::Allocator }, + { eReplicaComponentType::PROPERTY_VENDOR, &RegistryAllocator }, + { eReplicaComponentType::ROCKET_LAUNCH, &RegistryAllocator }, + { eReplicaComponentType::RACING_CONTROL, &RegistryAllocator }, + { eReplicaComponentType::SOUND_REPEATER, &RegistryAllocator }, + { eReplicaComponentType::SOUND_AMBIENT_2D, &RegistryAllocator }, + { eReplicaComponentType::SOUND_AMBIENT_3D, &RegistryAllocator }, + { eReplicaComponentType::CULLING_PLANE, &RegistryAllocator }, + { eReplicaComponentType::PLATFORM_BOUNDARY, &RegistryAllocator }, + { eReplicaComponentType::REBUILD, &RegistryAllocator }, + { eReplicaComponentType::REBUILD_START, &RegistryAllocator }, + { eReplicaComponentType::REBUILD_ACTIVATOR, &RegistryAllocator }, + { eReplicaComponentType::CHEST, &RegistryAllocator }, + { eReplicaComponentType::BLUEPRINT, &RegistryAllocator }, + { eReplicaComponentType::MODULE, &RegistryAllocator }, + { eReplicaComponentType::ARCADE, &RegistryAllocator }, + { eReplicaComponentType::MODEL_BUILDER, &RegistryAllocator }, + { eReplicaComponentType::SPRINGPAD, &RegistryAllocator }, + { eReplicaComponentType::CHANGLING, &RegistryAllocator }, + { eReplicaComponentType::CHOICE_BUILD, &RegistryAllocator }, + { eReplicaComponentType::PACKAGE, &RegistryAllocator }, + { eReplicaComponentType::CUSTOM_BUILD_ASSEMBLY, &RegistryAllocator }, + { eReplicaComponentType::SHOWCASE_MODEL_HANDLER, &RegistryAllocator }, + { eReplicaComponentType::GENERIC_ACTIVATOR, &RegistryAllocator }, + { eReplicaComponentType::PROJECTILE_PHYSICS, &RegistryAllocator }, + { eReplicaComponentType::VEHICLE_PHYSICS, &RegistryAllocator }, + { eReplicaComponentType::PHYSICS_SYSTEM, &RegistryAllocator }, + { eReplicaComponentType::SPAWNER, &RegistryAllocator }, + { eReplicaComponentType::HF_LIGHT_DIRECTION_GADGET, &RegistryAllocator }, + { eReplicaComponentType::PLAYER_FORCED_MOVEMENT, &PlayerForcedMovementComponent::Allocator }, + { eReplicaComponentType::EXHIBIT, &RegistryAllocator }, // 1.10.64 makes nothing here (showcases were cut); it sends nothing, so unchanged clients are unaffected + { eReplicaComponentType::BBB, &BBBComponent::Allocator }, + { eReplicaComponentType::ROCKET_LANDING, &RegistryAllocator }, + { eReplicaComponentType::FACTION_TRIGGER, &RegistryAllocator }, + { eReplicaComponentType::GATE_RUSH_CONTROL, &RegistryAllocator }, + { eReplicaComponentType::ROLLER, &RegistryAllocator }, + { eReplicaComponentType::PROPERTY_PLAQUE, &RegistryAllocator }, + }; } -int32_t Entity::ObjectLoadState::GetComponentID(const eReplicaComponentType type, const int32_t defaultValue) const { +int32_t 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 Entity::DoObjectPreLoad() { ObjectLoadState state; state.lot = m_TemplateID; @@ -307,678 +396,25 @@ Entity::ObjectLoadState Entity::DoObjectPreLoad() { } // ObjectLoader2::DoObjectComponentLoad (0x0105ebb0): the components in the client's order, then the ones it adds after +void ObjectLoadState::AddProximityAndPathfinding(Entity& entity) { + if (!entity.HasComponent(eReplicaComponentType::PROXIMITY_MONITOR)) entity.AddComponent(-1); + const bool hasMovementPhysics = entity.HasComponent(eReplicaComponentType::CONTROLLABLE_PHYSICS) || entity.HasComponent(eReplicaComponentType::PROJECTILE_PHYSICS) + || entity.HasComponent(eReplicaComponentType::VEHICLE_PHYSICS) || entity.HasComponent(eReplicaComponentType::HAVOK_VEHICLE_PHYSICS); + if (hasMovementPhysics && !entity.HasComponent(eReplicaComponentType::PATHFINDING)) entity.AddComponent(-1); +} + 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. + * ComponentOrderVector::Initialize 0x0101f8e0's list), each by its type's allocator: 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 (state.hasModelBehaviors) { - // LWOControllablePhysComponent::Allocator (0x0104e070): left to the model case - if (controllablePhysicsComponentID > 0) state.modelPhysicsComponentID = controllablePhysicsComponentID; - } else if (controllablePhysicsComponentID > 0) { - // The component places it: where the level put an object, where a character's save and the zone put it - AddComponent(controllablePhysicsComponentID); - } - 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 (state.hasModelBehaviors) { - // LWOPhantomPhysicsComponent::Allocator (0x010383b0): left to the model case - if (phantomPhysicsComponentID > 0) state.modelPhysicsComponentID = phantomPhysicsComponentID; - } else 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); - } - - // A collectible's (right after it) and a smashable object's (after the walk) come later, as the client makes them - if (destroyableComponentID > 0) { - // The row, the level config and a character's save, in the client's order - AddComponent(destroyableComponentID); - } - 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); - AddComponent(-1); - - auto& systemAddress = m_Character->GetParentUser() ? m_Character->GetParentUser()->GetSystemAddress() : UNASSIGNED_SYSTEM_ADDRESS; - AddComponent(-1, m_Character, systemAddress); - - } - 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); - } else if (state.addsMovementAI) { - // ApplyMovementAiIfNeeded: a scripted object or one with an attached path has one (id 0: no row) - AddComponent(0, MovementAIInfo{}); - } - 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); - // 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 = [this](const std::u16string& key) { return IsVarSentToClient(key) && !GetVarAsString(key).empty(); }; - if (scriptComponentID > 0 || (m_TemplateID != 176 && (clientCustomScript(u"custom_script_server") || clientCustomScript(u"custom_script_client")))) { - 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) { - AddComponent(skillID); - } - 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) { - 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")); - // The client makes a collectible's destroyable right after it when there is none - if (!HasComponent(eReplicaComponentType::DESTROYABLE)) AddComponent(-1); - } - 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); - 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::EXHIBIT: { - // 1.10.64's loader has this case but makes nothing (dead code; showcases were cut); DLU makes the exhibit, - // which sends nothing in a construction, so unchanged clients are unaffected - const auto id = state.GetComponentID(eReplicaComponentType::EXHIBIT, -1); - if (id > -1) AddComponent(id); - break; - } - case eReplicaComponentType::BBB: { - // DoObjectPreLoad gives a character (LOT 1) one with id 0; any other object the registry's - const auto id = m_TemplateID == 1 ? 0 : state.GetComponentID(eReplicaComponentType::BBB, -1); - if (id > -1) 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; - } + const auto allocator = ComponentAllocators.find(type); + if (allocator != ComponentAllocators.end()) allocator->second(*this, state); } - /** * 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 diff --git a/dGame/Entity.h b/dGame/Entity.h index 67bae3a1a..5906d6448 100644 --- a/dGame/Entity.h +++ b/dGame/Entity.h @@ -16,6 +16,7 @@ #include "eKillType.h" #include "Observable.h" #include "Profiler.h" +#include "ObjectLoadState.h" namespace GameMessages { struct GameMsg; @@ -397,24 +398,6 @@ 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; - // ApplyMovementAiIfNeeded: movement AI for a scripted object or one with an attached path - bool addsMovementAI = 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(); diff --git a/dGame/ObjectLoadState.h b/dGame/ObjectLoadState.h new file mode 100644 index 000000000..741197801 --- /dev/null +++ b/dGame/ObjectLoadState.h @@ -0,0 +1,40 @@ +#ifndef OBJECTLOADSTATE_H +#define OBJECTLOADSTATE_H + +#include +#include + +#include "dCommonVars.h" +#include "eReplicaComponentType.h" + +class Entity; + +/** + * 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. Each component type's + * Allocator reads it. + */ +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; + // ApplyMovementAiIfNeeded: movement AI for a scripted object or one with an attached path + bool addsMovementAI = 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; + + // LWOScriptComponent::Allocator (0x0104d710) and LWOBaseCombatAiComponent::Allocator (0x0104c7b0): 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 + static void AddProximityAndPathfinding(Entity& entity); +}; + +// An allocator for a component type: makes the object's component(s) of that type (the client's LWOxComponent::Allocator) +using ComponentAllocator = void (*)(Entity& entity, ObjectLoadState& state); + +#endif //!OBJECTLOADSTATE_H diff --git a/dGame/dComponents/Activity/QuickBuildComponent.cpp b/dGame/dComponents/Activity/QuickBuildComponent.cpp index 477496ab4..ee79c024b 100644 --- a/dGame/dComponents/Activity/QuickBuildComponent.cpp +++ b/dGame/dComponents/Activity/QuickBuildComponent.cpp @@ -32,6 +32,7 @@ #include "StringifiedEnum.h" #include "Amf3.h" #include "EconomyLedger.h" +#include "ObjectLoadState.h" QuickBuildComponent::QuickBuildComponent(Entity* const entity, const int32_t componentID) : ActivityComponent{ entity, componentID } { RegisterMsg(&QuickBuildComponent::OnRequestUse); @@ -779,3 +780,16 @@ bool QuickBuildComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInf quickbuild.PushDebug("Builder", "LWOOBJID") = std::to_string(m_Builder); return true; } + +void QuickBuildComponent::Allocator(Entity& entity, ObjectLoadState& state) { + const auto quickBuildComponentID = state.GetComponentID(eReplicaComponentType::QUICK_BUILD); + if (quickBuildComponentID > 0) { + entity.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 (!entity.HasComponent(eReplicaComponentType::DESTROYABLE)) { + entity.AddComponent(-1, true); + } + } +} diff --git a/dGame/dComponents/Activity/QuickBuildComponent.h b/dGame/dComponents/Activity/QuickBuildComponent.h index f897e0fcf..08ea4b232 100644 --- a/dGame/dComponents/Activity/QuickBuildComponent.h +++ b/dGame/dComponents/Activity/QuickBuildComponent.h @@ -13,6 +13,8 @@ #include "eReplicaComponentType.h" #include "eQuickBuildState.h" +struct ObjectLoadState; + class Entity; enum class eQuickBuildFailReason : uint32_t; @@ -25,6 +27,9 @@ class QuickBuildComponent final : public ActivityComponent { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::QUICK_BUILD; + // The client's allocator for this type (ObjectLoader2::DoObjectComponentLoad calls it in load order) + static void Allocator(Entity& entity, ObjectLoadState& state); + QuickBuildComponent(Entity* const entity, const int32_t componentID); void LoadDataFromTemplate() override; diff --git a/dGame/dComponents/BBBComponent.cpp b/dGame/dComponents/BBBComponent.cpp new file mode 100644 index 000000000..382b808be --- /dev/null +++ b/dGame/dComponents/BBBComponent.cpp @@ -0,0 +1,9 @@ +#include "BBBComponent.h" +#include "Entity.h" +#include "ObjectLoadState.h" + +void BBBComponent::Allocator(Entity& entity, ObjectLoadState& state) { + // DoObjectPreLoad gives a character (LOT 1) one with id 0; any other object the registry's + const auto id = entity.GetLOT() == 1 ? 0 : state.GetComponentID(eReplicaComponentType::BBB, -1); + if (id > -1) entity.AddComponent(id); +} diff --git a/dGame/dComponents/BBBComponent.h b/dGame/dComponents/BBBComponent.h index ae214c835..511e1df8e 100644 --- a/dGame/dComponents/BBBComponent.h +++ b/dGame/dComponents/BBBComponent.h @@ -6,6 +6,8 @@ #include "dCommonVars.h" #include "eReplicaComponentType.h" +struct ObjectLoadState; + /** * The BBB component (76), as the client's LWOBBBComponent: DoObjectPreLoad gives every character one (and the registry * any object listing it). It sends whether there is a metadata source item, then the item's ID. @@ -14,6 +16,9 @@ class BBBComponent final : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::BBB; + // The client's allocator for this type (ObjectLoader2::DoObjectComponentLoad calls it in load order) + static void Allocator(Entity& entity, ObjectLoadState& state); + BBBComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) {} // LWOBBBComponent::Deserialize 0x00bd6690 diff --git a/dGame/dComponents/BaseCombatAIComponent.cpp b/dGame/dComponents/BaseCombatAIComponent.cpp index 1cfbf5be0..986bd7471 100644 --- a/dGame/dComponents/BaseCombatAIComponent.cpp +++ b/dGame/dComponents/BaseCombatAIComponent.cpp @@ -31,6 +31,7 @@ #include "dNavMesh.h" #include "Amf3.h" #include "dpCollisionFilter.h" +#include "ObjectLoadState.h" namespace { // The collision groups of players and enemies in PhysicsComponent @@ -984,3 +985,10 @@ bool BaseCombatAIComponent::MsgGetObjectReportInfo(GameMessages::GetObjectReport return true; } + +void BaseCombatAIComponent::Allocator(Entity& entity, ObjectLoadState& state) { + if (state.GetComponentID(eReplicaComponentType::BASE_COMBAT_AI) > 0) { + ObjectLoadState::AddProximityAndPathfinding(entity); + entity.AddComponent(state.GetComponentID(eReplicaComponentType::BASE_COMBAT_AI)); + } +} diff --git a/dGame/dComponents/BaseCombatAIComponent.h b/dGame/dComponents/BaseCombatAIComponent.h index 4ca1977bf..4e2994fcb 100644 --- a/dGame/dComponents/BaseCombatAIComponent.h +++ b/dGame/dComponents/BaseCombatAIComponent.h @@ -13,6 +13,8 @@ #include #include +struct ObjectLoadState; + class MovementAIComponent; class Entity; @@ -51,6 +53,9 @@ class BaseCombatAIComponent final : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::BASE_COMBAT_AI; + // The client's allocator for this type (ObjectLoader2::DoObjectComponentLoad calls it in load order) + static void Allocator(Entity& entity, ObjectLoadState& state); + BaseCombatAIComponent(Entity* parentEntity, int32_t componentID); void LoadDataFromTemplate() override; diff --git a/dGame/dComponents/CMakeLists.txt b/dGame/dComponents/CMakeLists.txt index 62818a3ae..18d82a797 100644 --- a/dGame/dComponents/CMakeLists.txt +++ b/dGame/dComponents/CMakeLists.txt @@ -48,6 +48,8 @@ set(DGAME_DCOMPONENTS_SOURCES "TriggerComponent.cpp" "Physics/HavokVehiclePhysicsComponent.cpp" "Vendor/BaseVendorComponent.cpp" + "Vendor/VendorComponent.cpp" + "BBBComponent.cpp" "ScriptComponent.cpp" ) diff --git a/dGame/dComponents/Character/CharacterComponent.cpp b/dGame/dComponents/Character/CharacterComponent.cpp index 1604849b3..f363841e9 100644 --- a/dGame/dComponents/Character/CharacterComponent.cpp +++ b/dGame/dComponents/Character/CharacterComponent.cpp @@ -38,6 +38,13 @@ #include #include #include +#include "Entity.h" +#include "ObjectLoadState.h" +#include "LevelProgressionComponent.h" +#include "MountControlComponent.h" +#include "PetCreatorComponent.h" +#include "PlayerForcedMovementComponent.h" +#include "PossessorComponent.h" CharacterComponent::CharacterComponent(Entity* parent, const int32_t componentID, Character* character, const SystemAddress& systemAddress) : Component(parent, componentID) { m_Character = character; @@ -1076,3 +1083,20 @@ void CharacterComponent::SetUScore(int64_t uscore, uint32_t source) { if (const auto* character = m_Parent->GetCharacter()) EconomyLedger::RecordUScore(character->GetID(), uscore - m_Uscore, source); m_Uscore = uscore; } + +void CharacterComponent::Allocator(Entity& entity, ObjectLoadState& state) { + const auto characterID = state.GetComponentID(eReplicaComponentType::CHARACTER); + if (characterID > 0 || entity.GetCharacter()) { + // LWOCharacterComponent::Factory (0x0104db40): possession control, mount control, pet creator, level + // progression and forced movement, then the character, all componentID -1 + entity.AddComponent(-1); + entity.AddComponent(-1); + entity.AddComponent(-1); + entity.AddComponent(-1); + entity.AddComponent(-1); + + auto& systemAddress = entity.GetCharacter()->GetParentUser() ? entity.GetCharacter()->GetParentUser()->GetSystemAddress() : UNASSIGNED_SYSTEM_ADDRESS; + entity.AddComponent(-1, entity.GetCharacter(), systemAddress); + + } +} diff --git a/dGame/dComponents/Character/CharacterComponent.h b/dGame/dComponents/Character/CharacterComponent.h index 6965e5c97..74e20b532 100644 --- a/dGame/dComponents/Character/CharacterComponent.h +++ b/dGame/dComponents/Character/CharacterComponent.h @@ -17,6 +17,8 @@ #include #include +struct ObjectLoadState; + enum class eGameActivity : uint32_t; class Item; @@ -45,6 +47,9 @@ class CharacterComponent final : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::CHARACTER; + // The client's allocator for this type (ObjectLoader2::DoObjectComponentLoad calls it in load order) + static void Allocator(Entity& entity, ObjectLoadState& state); + CharacterComponent(Entity* parent, const int32_t componentID, Character* character, const SystemAddress& systemAddress); ~CharacterComponent() override; diff --git a/dGame/dComponents/Character/PlayerForcedMovementComponent.cpp b/dGame/dComponents/Character/PlayerForcedMovementComponent.cpp index 4198a6dc9..82c46a6d5 100644 --- a/dGame/dComponents/Character/PlayerForcedMovementComponent.cpp +++ b/dGame/dComponents/Character/PlayerForcedMovementComponent.cpp @@ -1,4 +1,6 @@ #include "PlayerForcedMovementComponent.h" +#include "Entity.h" +#include "ObjectLoadState.h" PlayerForcedMovementComponent::PlayerForcedMovementComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) { m_Parent = parent; @@ -14,3 +16,9 @@ void PlayerForcedMovementComponent::Serialize(RakNet::BitStream& outBitStream, b } m_DirtyInfo = false; } + +void PlayerForcedMovementComponent::Allocator(Entity& entity, ObjectLoadState& state) { + // 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 && !entity.HasComponent(eReplicaComponentType::PLAYER_FORCED_MOVEMENT)) entity.AddComponent(id); +} diff --git a/dGame/dComponents/Character/PlayerForcedMovementComponent.h b/dGame/dComponents/Character/PlayerForcedMovementComponent.h index a39e2d399..1cd162b6a 100644 --- a/dGame/dComponents/Character/PlayerForcedMovementComponent.h +++ b/dGame/dComponents/Character/PlayerForcedMovementComponent.h @@ -4,6 +4,8 @@ #include "Component.h" #include "eReplicaComponentType.h" +struct ObjectLoadState; + /** * Component that handles player forced movement * @@ -12,6 +14,9 @@ class PlayerForcedMovementComponent final : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::PLAYER_FORCED_MOVEMENT; + // The client's allocator for this type (ObjectLoader2::DoObjectComponentLoad calls it in load order) + static void Allocator(Entity& entity, ObjectLoadState& state); + /** * Constructor for this component * @param parent parent that contains this component diff --git a/dGame/dComponents/CollectibleComponent.cpp b/dGame/dComponents/CollectibleComponent.cpp index aa3f5b233..fe9d2759a 100644 --- a/dGame/dComponents/CollectibleComponent.cpp +++ b/dGame/dComponents/CollectibleComponent.cpp @@ -7,6 +7,9 @@ #include "CDCollectibleComponentTable.h" #include "CDMissionsTable.h" #include "eMissionState.h" +#include "Entity.h" +#include "ObjectLoadState.h" +#include "DestroyableComponent.h" CollectibleComponent::CollectibleComponent(Entity* parentEntity, const int32_t componentID, const int32_t collectibleId) : Component(parentEntity, componentID), m_CollectibleId(collectibleId) { @@ -66,3 +69,11 @@ bool CollectibleComponent::MsgGetObjectReportInfo(GameMessages::GetObjectReportI cmptType.PushDebug("Has been collected") = collected; return true; } + +void CollectibleComponent::Allocator(Entity& entity, ObjectLoadState& state) { + if (state.GetComponentID(eReplicaComponentType::COLLECTIBLE) > 0) { + entity.AddComponent(state.GetComponentID(eReplicaComponentType::COLLECTIBLE), entity.GetVarAs(u"collectible_id")); + // The client makes a collectible's destroyable right after it when there is none + if (!entity.HasComponent(eReplicaComponentType::DESTROYABLE)) entity.AddComponent(-1); + } +} diff --git a/dGame/dComponents/CollectibleComponent.h b/dGame/dComponents/CollectibleComponent.h index 6d92bb2ee..74a708615 100644 --- a/dGame/dComponents/CollectibleComponent.h +++ b/dGame/dComponents/CollectibleComponent.h @@ -4,9 +4,14 @@ #include "Component.h" #include "eReplicaComponentType.h" +struct ObjectLoadState; + class CollectibleComponent final : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::COLLECTIBLE; + + // The client's allocator for this type (ObjectLoader2::DoObjectComponentLoad calls it in load order) + static void Allocator(Entity& entity, ObjectLoadState& state); CollectibleComponent(Entity* parentEntity, const int32_t componentID, const int32_t collectibleId); void LoadDataFromTemplate() override; diff --git a/dGame/dComponents/DestroyableComponent.cpp b/dGame/dComponents/DestroyableComponent.cpp index 05a950595..88dc24d64 100644 --- a/dGame/dComponents/DestroyableComponent.cpp +++ b/dGame/dComponents/DestroyableComponent.cpp @@ -50,6 +50,8 @@ #include "CDComponentsRegistryTable.h" #include "InventoryMessages.h" +#include "Entity.h" +#include "ObjectLoadState.h" Implementation DestroyableComponent::IsEnemyImplentation; Implementation DestroyableComponent::IsFriendImplentation; @@ -1406,3 +1408,21 @@ void DestroyableComponent::Resurrect(Entity& entityRef) { resurrect.bRezImmediately = false; resurrect.Send(UNASSIGNED_SYSTEM_ADDRESS); } + +void DestroyableComponent::Allocator(Entity& entity, ObjectLoadState& state) { + /** + * 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) { + entity.AddComponent(destroyableComponentID); + } + + // A collectible's (right after it) and a smashable object's (after the walk) come later, as the client makes them + if (destroyableComponentID > 0) { + // The row, the level config and a character's save, in the client's order + entity.AddComponent(destroyableComponentID); + } +} diff --git a/dGame/dComponents/DestroyableComponent.h b/dGame/dComponents/DestroyableComponent.h index 69dffb868..1ab62b1c8 100644 --- a/dGame/dComponents/DestroyableComponent.h +++ b/dGame/dComponents/DestroyableComponent.h @@ -10,6 +10,8 @@ #include "eReplicaComponentType.h" #include "Implementation.h" +struct ObjectLoadState; + namespace GameMessages { struct GetObjectReportInfo; struct SetFaction; @@ -44,6 +46,9 @@ class DestroyableComponent final : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::DESTROYABLE; + // The client's allocator for this type (ObjectLoader2::DoObjectComponentLoad calls it in load order) + static void Allocator(Entity& entity, ObjectLoadState& state); + // smashableDefaults: a destroyable the loader adds to a model or quickbuild, with live's defaults and no row DestroyableComponent(Entity* parentEntity, const int32_t componentID, const bool smashableDefaults = false); ~DestroyableComponent() override; diff --git a/dGame/dComponents/InventoryComponent.cpp b/dGame/dComponents/InventoryComponent.cpp index 4090b1019..ae850fdd3 100644 --- a/dGame/dComponents/InventoryComponent.cpp +++ b/dGame/dComponents/InventoryComponent.cpp @@ -60,6 +60,7 @@ #include "Amf3.h" #include +#include "ObjectLoadState.h" namespace { // The equipped-item entry for a player's item. Live wrote its bound state and its inventory; DLU keeps proxies in @@ -2460,3 +2461,10 @@ bool InventoryComponent::CheckDeletionRestriction(const LWOOBJID itemId, const i return true; } } + +void InventoryComponent::Allocator(Entity& entity, ObjectLoadState& state) { + const auto inventoryID = state.GetComponentID(eReplicaComponentType::INVENTORY); + if (inventoryID > 0 || entity.GetCharacter()) { + entity.AddComponent(inventoryID); + } +} diff --git a/dGame/dComponents/InventoryComponent.h b/dGame/dComponents/InventoryComponent.h index aea57a8a2..363c9de61 100644 --- a/dGame/dComponents/InventoryComponent.h +++ b/dGame/dComponents/InventoryComponent.h @@ -25,6 +25,8 @@ #include "Loot.h" #include "LootMetrics.h" +struct ObjectLoadState; + class Entity; class ItemSet; @@ -99,6 +101,9 @@ public: static constexpr uint32_t MaximumGroupCount = 50; static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::INVENTORY; + + // The client's allocator for this type (ObjectLoader2::DoObjectComponentLoad calls it in load order) + static void Allocator(Entity& entity, ObjectLoadState& state); InventoryComponent(Entity* parent, const int32_t componentID); void LoadDataFromTemplate() override; diff --git a/dGame/dComponents/ItemComponent.cpp b/dGame/dComponents/ItemComponent.cpp index 16543bfbc..1bd7918a3 100644 --- a/dGame/dComponents/ItemComponent.cpp +++ b/dGame/dComponents/ItemComponent.cpp @@ -1,4 +1,7 @@ #include "ItemComponent.h" +#include "Entity.h" +#include "ObjectLoadState.h" +#include "PetComponent.h" void ItemComponent::Serialize(RakNet::BitStream& outBitStream, bool isConstruction) { // Live wrote the user-generated-content info on every construction: the model's userModelID (a brick built model, @@ -10,3 +13,13 @@ void ItemComponent::Serialize(RakNet::BitStream& outBitStream, bool isConstructi outBitStream.Write0(); // ug_description } } + +void ItemComponent::Allocator(Entity& entity, ObjectLoadState& state) { + // 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 && !entity.TryGetComponent(eReplicaComponentType::PET, petComponent)) { + entity.AddComponent(itemID); + } +} diff --git a/dGame/dComponents/ItemComponent.h b/dGame/dComponents/ItemComponent.h index 73e1ac838..75da132a0 100644 --- a/dGame/dComponents/ItemComponent.h +++ b/dGame/dComponents/ItemComponent.h @@ -4,10 +4,15 @@ #include "Component.h" #include "eReplicaComponentType.h" +struct ObjectLoadState; + class ItemComponent final : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::ITEM; + // The client's allocator for this type (ObjectLoader2::DoObjectComponentLoad calls it in load order) + static void Allocator(Entity& entity, ObjectLoadState& state); + ItemComponent(Entity* entity, const int32_t componentID) : Component(entity, componentID) {} void Serialize(RakNet::BitStream& bitStream, bool isConstruction) override; diff --git a/dGame/dComponents/ModelComponent.cpp b/dGame/dComponents/ModelComponent.cpp index b92185c87..bad6106bc 100644 --- a/dGame/dComponents/ModelComponent.cpp +++ b/dGame/dComponents/ModelComponent.cpp @@ -18,6 +18,11 @@ #include "Database.h" #include "DluAssert.h" #include "ObjectMessages.h" +#include "ObjectLoadState.h" +#include "PetComponent.h" +#include "ControllablePhysicsComponent.h" +#include "DestroyableComponent.h" +#include "PathfindingComponent.h" ModelComponent::ModelComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) { m_OriginalPosition = m_Parent->GetDefaultPosition(); @@ -375,3 +380,28 @@ bool ModelComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& re return true; } + +void ModelComponent::Allocator(Entity& entity, ObjectLoadState& state) { + const auto modelID = state.GetComponentID(eReplicaComponentType::MODEL, -1); + if (modelID > -1 && !entity.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 = entity.HasComponent(eReplicaComponentType::CONTROLLABLE_PHYSICS) || entity.HasComponent(eReplicaComponentType::SIMPLE_PHYSICS) + || entity.HasComponent(eReplicaComponentType::PROJECTILE_PHYSICS) || entity.HasComponent(eReplicaComponentType::RIGID_BODY_PHANTOM_PHYSICS) + || entity.HasComponent(eReplicaComponentType::HAVOK_VEHICLE_PHYSICS) || entity.HasComponent(eReplicaComponentType::PHANTOM_PHYSICS) + || entity.HasComponent(eReplicaComponentType::VEHICLE_PHYSICS) || entity.HasComponent(eReplicaComponentType::PHYSICS_SYSTEM); + const int32_t modelType = entity.HasVar(u"modelType") ? entity.GetVarAs(u"modelType") : -1; + if (!hasPhysics) { + if (!state.hasModelBehaviors) entity.AddComponent(4247); + else if (modelType == 0) entity.AddComponent(state.modelPhysicsComponentID == -1 ? 4246 : state.modelPhysicsComponentID); + else entity.AddComponent(state.modelPhysicsComponentID == -1 ? 4247 : state.modelPhysicsComponentID); + } + if (modelType == 0 && !entity.HasComponent(eReplicaComponentType::PATHFINDING)) entity.AddComponent(-1); + entity.AddComponent(modelID); + if (!entity.HasComponent(eReplicaComponentType::DESTROYABLE)) { + entity.AddComponent(-1, true); + } + } +} diff --git a/dGame/dComponents/ModelComponent.h b/dGame/dComponents/ModelComponent.h index 9cfabe428..0ef001311 100644 --- a/dGame/dComponents/ModelComponent.h +++ b/dGame/dComponents/ModelComponent.h @@ -16,6 +16,8 @@ #include "StripUiPosition.h" #include "ObjectMessages.h" +struct ObjectLoadState; + class AddMessage; class AMFArrayValue; class BehaviorMessageBase; @@ -29,6 +31,9 @@ class ModelComponent final : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::MODEL; + // The client's allocator for this type (ObjectLoader2::DoObjectComponentLoad calls it in load order) + static void Allocator(Entity& entity, ObjectLoadState& state); + ModelComponent(Entity* parent, const int32_t componentID); void LoadConfigData() override; diff --git a/dGame/dComponents/MovementAIComponent.cpp b/dGame/dComponents/MovementAIComponent.cpp index 4af8b3989..792139eec 100644 --- a/dGame/dComponents/MovementAIComponent.cpp +++ b/dGame/dComponents/MovementAIComponent.cpp @@ -28,6 +28,8 @@ #include "GeneralUtils.h" #include "RenderComponent.h" #include "InventoryComponent.h" +#include "Entity.h" +#include "ObjectLoadState.h" namespace { /** @@ -732,3 +734,25 @@ float MovementAIComponent::GetGroundHeight(const NiPoint3& point) const { if (dpWorld::IsLoaded()) return dpWorld::GetNavMesh()->GetHeightAtPoint(point); return m_KnockbackStartHeight; } + +void MovementAIComponent::Allocator(Entity& entity, ObjectLoadState& state) { + const int movementAIID = state.GetComponentID(eReplicaComponentType::MOVEMENT_AI); + if (movementAIID > 0) { + // Its row and the level's wanderRadius load in the component + entity.AddComponent(movementAIID, MovementAIInfo{}); + } else if (state.GetComponentID(eReplicaComponentType::PET) > 0 || state.GetComponentID(eReplicaComponentType::BASE_COMBAT_AI) > 0 && BaseCombatAIComponent::TetherSpeedOf(state.GetComponentID(eReplicaComponentType::BASE_COMBAT_AI)) > 0) { + MovementAIInfo moveInfo{ + .movementType = "", + .wanderRadius = 16, + .wanderSpeed = 2.5f, + .wanderChance = 0, + .wanderDelayMin = 2, + .wanderDelayMax = 5, + }; + + entity.AddComponent(-1, moveInfo); + } else if (state.addsMovementAI) { + // ApplyMovementAiIfNeeded: a scripted object or one with an attached path has one (id 0: no row) + entity.AddComponent(0, MovementAIInfo{}); + } +} diff --git a/dGame/dComponents/MovementAIComponent.h b/dGame/dComponents/MovementAIComponent.h index 61f312089..91fce3184 100644 --- a/dGame/dComponents/MovementAIComponent.h +++ b/dGame/dComponents/MovementAIComponent.h @@ -19,6 +19,8 @@ #include #include +struct ObjectLoadState; + class ControllablePhysicsComponent; class BaseCombatAIComponent; @@ -64,6 +66,9 @@ class MovementAIComponent final : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::MOVEMENT_AI; + // The client's allocator for this type (ObjectLoader2::DoObjectComponentLoad calls it in load order) + static void Allocator(Entity& entity, ObjectLoadState& state); + MovementAIComponent(Entity* parentEntity, const int32_t componentID, MovementAIInfo info); void LoadDataFromTemplate() override; diff --git a/dGame/dComponents/MovingPlatformComponent.cpp b/dGame/dComponents/MovingPlatformComponent.cpp index db16d0894..f4b7e36c3 100644 --- a/dGame/dComponents/MovingPlatformComponent.cpp +++ b/dGame/dComponents/MovingPlatformComponent.cpp @@ -17,6 +17,9 @@ #include "MovementMessages.h" #include +#include "Entity.h" +#include "ObjectLoadState.h" +#include "MovementAIComponent.h" void PlatformSubComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) { outBitStream.Write(true); @@ -598,3 +601,39 @@ void MovingPlatformComponent::FireArrivalHooks(const uint32_t index, const bool if (atDesiredWaypoint) script->OnArrivedAtDesiredWaypoint(m_Parent, index); if (atLastWaypoint) script->OnPlatformAtLastWaypoint(m_Parent); } + +void MovingPlatformComponent::Allocator(Entity& entity, ObjectLoadState& state) { + const std::string pathName = entity.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 + entity.AddComponent(state.GetComponentID(eReplicaComponentType::MOVING_PLATFORM, -1), pathName); + } else if (path->pathType == PathType::Movement) { + auto* const movementAIcomponent = entity.GetComponent(); + if (movementAIcomponent && state.GetComponentID(eReplicaComponentType::BASE_COMBAT_AI) == 0) { + movementAIcomponent->SetPath(pathName); + } else { + MovementAIInfo moveInfo{ + .movementType = "", + .wanderRadius = 16, + .wanderSpeed = 2.5f, + .wanderChance = 0, + .wanderDelayMin = 2, + .wanderDelayMax = 5, + }; + + entity.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) { + entity.AddComponent(movingPlatformComponentID, pathName); + } + } +} diff --git a/dGame/dComponents/MovingPlatformComponent.h b/dGame/dComponents/MovingPlatformComponent.h index 8c5d0ce53..d78595fe3 100644 --- a/dGame/dComponents/MovingPlatformComponent.h +++ b/dGame/dComponents/MovingPlatformComponent.h @@ -20,6 +20,8 @@ #include "eMovementPlatformState.h" #include "eReplicaComponentType.h" +struct ObjectLoadState; + class Path; namespace GameMessages { struct PlatformResync; } @@ -171,6 +173,9 @@ class MovingPlatformComponent final : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::MOVING_PLATFORM; + // The client's allocator for this type (ObjectLoader2::DoObjectComponentLoad calls it in load order) + static void Allocator(Entity& entity, ObjectLoadState& state); + MovingPlatformComponent(Entity* parent, const int32_t componentID, const std::string& pathName); void LoadDataFromTemplate() override; diff --git a/dGame/dComponents/PetComponent.cpp b/dGame/dComponents/PetComponent.cpp index 795332a62..43579dad1 100644 --- a/dGame/dComponents/PetComponent.cpp +++ b/dGame/dComponents/PetComponent.cpp @@ -43,6 +43,9 @@ #include "dNavMesh.h" #include "eGameActivity.h" #include "eStateChangeType.h" +#include "Entity.h" +#include "ObjectLoadState.h" +#include "PathfindingComponent.h" std::unordered_map PetComponent::currentActivities{}; std::unordered_map PetComponent::activePets{}; @@ -1452,3 +1455,11 @@ void PetComponent::ApplyNameModeration(const bool approved) { } Game::entityManager->SerializeEntity(m_Parent); } + +void PetComponent::Allocator(Entity& entity, ObjectLoadState& state) { + // LWOPetComponent::Factory (0x01031240): no pet in an inventory; otherwise pathfinding control first + if (state.GetComponentID(eReplicaComponentType::PET) > 0 && !entity.GetVar(u"inInventory")) { + if (!entity.HasComponent(eReplicaComponentType::PATHFINDING)) entity.AddComponent(-1); + entity.AddComponent(state.GetComponentID(eReplicaComponentType::PET)); + } +} diff --git a/dGame/dComponents/PetComponent.h b/dGame/dComponents/PetComponent.h index 6286fc544..a52b35e3f 100644 --- a/dGame/dComponents/PetComponent.h +++ b/dGame/dComponents/PetComponent.h @@ -10,6 +10,8 @@ #include "CDPetComponentTable.h" #include "eHelpType.h" +struct ObjectLoadState; + /** * Represents an entity that is a pet. This pet can be tamed and consequently follows the tamer around, allowing it * to dig for treasure and activate pet bouncers. @@ -19,6 +21,9 @@ class PetComponent final : public Component public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::PET; + // The client's allocator for this type (ObjectLoader2::DoObjectComponentLoad calls it in load order) + static void Allocator(Entity& entity, ObjectLoadState& state); + // Bits of the serialized pet status. State number N is bit 1 << (N - 1) (the client's StateNumToIndex, // 0x00bd19b0); the meanings are what the live server sent (2014 packet captures) and what the client does with them. diff --git a/dGame/dComponents/Physics/ControllablePhysicsComponent.cpp b/dGame/dComponents/Physics/ControllablePhysicsComponent.cpp index 407e3afb0..cb87bbda6 100644 --- a/dGame/dComponents/Physics/ControllablePhysicsComponent.cpp +++ b/dGame/dComponents/Physics/ControllablePhysicsComponent.cpp @@ -19,6 +19,7 @@ #include "eStateChangeType.h" #include "StringifiedEnum.h" #include "Amf3.h" +#include "ObjectLoadState.h" ControllablePhysicsComponent::ControllablePhysicsComponent(Entity* entity, const int32_t componentID) : PhysicsComponent(entity, componentID) { RegisterMsg(&ControllablePhysicsComponent::OnGetObjectReportInfo); @@ -460,3 +461,14 @@ bool ControllablePhysicsComponent::OnGetObjectReportInfo(GameMessages::GetObject immunity.PushDebug("Immune to Stun Jump") = m_ImmuneToStunJumpCount != 0; return true; } + +void ControllablePhysicsComponent::Allocator(Entity& entity, ObjectLoadState& state) { + const auto controllablePhysicsComponentID = state.GetComponentID(eReplicaComponentType::CONTROLLABLE_PHYSICS); + if (state.hasModelBehaviors) { + // LWOControllablePhysComponent::Allocator (0x0104e070): left to the model case + if (controllablePhysicsComponentID > 0) state.modelPhysicsComponentID = controllablePhysicsComponentID; + } else if (controllablePhysicsComponentID > 0) { + // The component places it: where the level put an object, where a character's save and the zone put it + entity.AddComponent(controllablePhysicsComponentID); + } +} diff --git a/dGame/dComponents/Physics/ControllablePhysicsComponent.h b/dGame/dComponents/Physics/ControllablePhysicsComponent.h index cd9c1a6a9..f2fd199f2 100644 --- a/dGame/dComponents/Physics/ControllablePhysicsComponent.h +++ b/dGame/dComponents/Physics/ControllablePhysicsComponent.h @@ -10,6 +10,8 @@ #include "dpCollisionChecks.h" #include "eBubbleType.h" +struct ObjectLoadState; + namespace tinyxml2 { class XMLDocument; } @@ -25,6 +27,9 @@ class ControllablePhysicsComponent : public PhysicsComponent { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::CONTROLLABLE_PHYSICS; + // The client's allocator for this type (ObjectLoader2::DoObjectComponentLoad calls it in load order) + static void Allocator(Entity& entity, ObjectLoadState& state); + ControllablePhysicsComponent(Entity* entity, const int32_t componentID); void LoadConfigData() override; diff --git a/dGame/dComponents/Physics/HavokVehiclePhysicsComponent.cpp b/dGame/dComponents/Physics/HavokVehiclePhysicsComponent.cpp index 299c2c5a5..1bda7c62b 100644 --- a/dGame/dComponents/Physics/HavokVehiclePhysicsComponent.cpp +++ b/dGame/dComponents/Physics/HavokVehiclePhysicsComponent.cpp @@ -1,6 +1,9 @@ #include "HavokVehiclePhysicsComponent.h" #include "EntityManager.h" #include "Amf3.h" +#include "Entity.h" +#include "ObjectLoadState.h" +#include "SimplePhysicsComponent.h" HavokVehiclePhysicsComponent::HavokVehiclePhysicsComponent(Entity* parent, const int32_t componentID) : PhysicsComponent(parent, componentID) { RegisterMsg(&HavokVehiclePhysicsComponent::OnGetObjectReportInfo); @@ -125,3 +128,15 @@ bool HavokVehiclePhysicsComponent::OnGetObjectReportInfo(GameMessages::GetObject remoteInputInfo.PushDebug("Is Modified") = m_RemoteInputInfo.m_IsModified; return true; } + +void HavokVehiclePhysicsComponent::Allocator(Entity& entity, ObjectLoadState& state) { + 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 && entity.HasVar(u"use_simple_physics")) { + if (!entity.HasComponent(eReplicaComponentType::SIMPLE_PHYSICS)) entity.AddComponent(havokVehiclePhysicsComponentID); + } else if (havokVehiclePhysicsComponentID > 0 && !(entity.HasVar(u"objectWorldState") && entity.GetVarAs(u"objectWorldState") == 2)) { + auto* havokVehiclePhysicsComponent = entity.AddComponent(havokVehiclePhysicsComponentID); + havokVehiclePhysicsComponent->SetPosition(entity.GetDefaultPosition()); + havokVehiclePhysicsComponent->SetRotation(entity.GetDefaultRotation()); + } +} diff --git a/dGame/dComponents/Physics/HavokVehiclePhysicsComponent.h b/dGame/dComponents/Physics/HavokVehiclePhysicsComponent.h index 3b2aba786..58eb219aa 100644 --- a/dGame/dComponents/Physics/HavokVehiclePhysicsComponent.h +++ b/dGame/dComponents/Physics/HavokVehiclePhysicsComponent.h @@ -6,6 +6,8 @@ #include "eReplicaComponentType.h" #include "PositionUpdate.h" +struct ObjectLoadState; + /** * Physics component for vehicles. */ @@ -13,6 +15,9 @@ class HavokVehiclePhysicsComponent : public PhysicsComponent { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::HAVOK_VEHICLE_PHYSICS; + // The client's allocator for this type (ObjectLoader2::DoObjectComponentLoad calls it in load order) + static void Allocator(Entity& entity, ObjectLoadState& state); + HavokVehiclePhysicsComponent(Entity* parentEntity, const int32_t componentID); void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; diff --git a/dGame/dComponents/Physics/PhantomPhysicsComponent.cpp b/dGame/dComponents/Physics/PhantomPhysicsComponent.cpp index f6e52a4ec..6e56088f6 100644 --- a/dGame/dComponents/Physics/PhantomPhysicsComponent.cpp +++ b/dGame/dComponents/Physics/PhantomPhysicsComponent.cpp @@ -28,6 +28,7 @@ #include "dpEntity.h" #include "dpShapeBox.h" #include "dpShapeSphere.h" +#include "ObjectLoadState.h" PhantomPhysicsComponent::PhantomPhysicsComponent(Entity* parent, const int32_t componentID) : PhysicsComponent(parent, componentID) { RegisterMsg(&PhantomPhysicsComponent::OnGetObjectReportInfo); @@ -293,3 +294,13 @@ bool PhantomPhysicsComponent::OnGetObjectReportInfo(GameMessages::GetObjectRepor return true; } + +void PhantomPhysicsComponent::Allocator(Entity& entity, ObjectLoadState& state) { + const auto phantomPhysicsComponentID = state.GetComponentID(eReplicaComponentType::PHANTOM_PHYSICS); + if (state.hasModelBehaviors) { + // LWOPhantomPhysicsComponent::Allocator (0x010383b0): left to the model case + if (phantomPhysicsComponentID > 0) state.modelPhysicsComponentID = phantomPhysicsComponentID; + } else if (phantomPhysicsComponentID > 0 && !entity.HasComponent(eReplicaComponentType::PHANTOM_PHYSICS)) { + entity.AddComponent(phantomPhysicsComponentID)->SetPhysicsEffectActive(false); + } +} diff --git a/dGame/dComponents/Physics/PhantomPhysicsComponent.h b/dGame/dComponents/Physics/PhantomPhysicsComponent.h index 9d6eed0ab..5fdb9d8be 100644 --- a/dGame/dComponents/Physics/PhantomPhysicsComponent.h +++ b/dGame/dComponents/Physics/PhantomPhysicsComponent.h @@ -12,6 +12,8 @@ #include "eReplicaComponentType.h" #include "PhysicsComponent.h" +struct ObjectLoadState; + class Entity; class dpEntity; enum class ePhysicsEffectType : uint32_t ; @@ -27,6 +29,9 @@ class PhantomPhysicsComponent final : public PhysicsComponent { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::PHANTOM_PHYSICS; + // The client's allocator for this type (ObjectLoader2::DoObjectComponentLoad calls it in load order) + static void Allocator(Entity& entity, ObjectLoadState& state); + PhantomPhysicsComponent(Entity* parent, const int32_t componentID); void LoadConfigData() override; diff --git a/dGame/dComponents/Physics/SimplePhysicsComponent.cpp b/dGame/dComponents/Physics/SimplePhysicsComponent.cpp index 4970fafab..bf223cb27 100644 --- a/dGame/dComponents/Physics/SimplePhysicsComponent.cpp +++ b/dGame/dComponents/Physics/SimplePhysicsComponent.cpp @@ -14,6 +14,8 @@ #include "Entity.h" #include "StringifiedEnum.h" #include "Amf3.h" +#include "ObjectLoadState.h" +#include "PhantomPhysicsComponent.h" SimplePhysicsComponent::SimplePhysicsComponent(Entity* parent, const int32_t componentID) : PhysicsComponent(parent, componentID) { RegisterMsg(&SimplePhysicsComponent::OnGetObjectReportInfo); @@ -97,3 +99,15 @@ bool SimplePhysicsComponent::OnGetObjectReportInfo(GameMessages::GetObjectReport info.PushDebug("Climbable Type") = StringifiedEnum::ToString(m_ClimbableType).data(); return true; } + +void SimplePhysicsComponent::Allocator(Entity& entity, ObjectLoadState& state) { + 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) entity.AddComponent(simplePhysicsComponentID)->SetPhysicsEffectActive(false); + else entity.AddComponent(simplePhysicsComponentID); + } +} diff --git a/dGame/dComponents/Physics/SimplePhysicsComponent.h b/dGame/dComponents/Physics/SimplePhysicsComponent.h index e3f1d8fa0..681a0e779 100644 --- a/dGame/dComponents/Physics/SimplePhysicsComponent.h +++ b/dGame/dComponents/Physics/SimplePhysicsComponent.h @@ -13,6 +13,8 @@ #include "PhysicsComponent.h" #include "eReplicaComponentType.h" +struct ObjectLoadState; + class Entity; enum class eClimbableType : int32_t { @@ -30,6 +32,9 @@ class SimplePhysicsComponent : public PhysicsComponent { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::SIMPLE_PHYSICS; + // The client's allocator for this type (ObjectLoader2::DoObjectComponentLoad calls it in load order) + static void Allocator(Entity& entity, ObjectLoadState& state); + // Havok motion types the client reads as the physics motion state static constexpr uint32_t MOTION_TYPE_DYNAMIC = 1; static constexpr uint32_t MOTION_TYPE_KEYFRAMED = 4; diff --git a/dGame/dComponents/RenderComponent.cpp b/dGame/dComponents/RenderComponent.cpp index 0bfc6e643..d6dfee34a 100644 --- a/dGame/dComponents/RenderComponent.cpp +++ b/dGame/dComponents/RenderComponent.cpp @@ -14,6 +14,7 @@ #include "Game.h" #include "Logger.h" #include "CDAnimationsTable.h" +#include "ObjectLoadState.h" std::unordered_map RenderComponent::m_DurationCache{}; @@ -194,3 +195,10 @@ float RenderComponent::DoAnimation(Entity* self, const std::string& animation, b if (returnlength == 0.0f) LOG("WARNING: Unable to find animation %s for lot %i in any group.", animation.c_str(), self->GetLOT()); return returnlength; } + +void RenderComponent::Allocator(Entity& entity, ObjectLoadState& state) { + const auto renderComponentID = state.GetComponentID(eReplicaComponentType::RENDER); + if ((renderComponentID > 0 && entity.GetLOT() != 2365) || entity.GetCharacter()) { + entity.AddComponent(renderComponentID); + } +} diff --git a/dGame/dComponents/RenderComponent.h b/dGame/dComponents/RenderComponent.h index 46d1a4a8b..ceee44e40 100644 --- a/dGame/dComponents/RenderComponent.h +++ b/dGame/dComponents/RenderComponent.h @@ -10,6 +10,8 @@ #include "Component.h" #include "eReplicaComponentType.h" +struct ObjectLoadState; + class Entity; /** @@ -63,6 +65,9 @@ class RenderComponent final : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::RENDER; + // The client's allocator for this type (ObjectLoader2::DoObjectComponentLoad calls it in load order) + static void Allocator(Entity& entity, ObjectLoadState& state); + RenderComponent(Entity* const parentEntity, const int32_t componentID); void LoadDataFromTemplate() override; diff --git a/dGame/dComponents/ScriptComponent.cpp b/dGame/dComponents/ScriptComponent.cpp index d13922e41..dd0b1a604 100644 --- a/dGame/dComponents/ScriptComponent.cpp +++ b/dGame/dComponents/ScriptComponent.cpp @@ -11,6 +11,9 @@ #include "Amf3.h" #include +#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(); + 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(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(zoneScriptID, zoneScriptData.script_name, false, false); + } + } +} diff --git a/dGame/dComponents/ScriptComponent.h b/dGame/dComponents/ScriptComponent.h index 2fc16fa25..eb0cf8791 100644 --- a/dGame/dComponents/ScriptComponent.h +++ b/dGame/dComponents/ScriptComponent.h @@ -14,6 +14,8 @@ #include #include "eReplicaComponentType.h" +struct ObjectLoadState; + class Entity; /** @@ -24,6 +26,9 @@ class ScriptComponent final : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::SCRIPT; + // The client's allocator for this type (ObjectLoader2::DoObjectComponentLoad calls it in load order) + static void Allocator(Entity& entity, ObjectLoadState& state); + ScriptComponent(Entity* parent, const int32_t componentID, const std::string& scriptName, bool serialized, bool client = false); // One the object's registry or level config gives (LWOScriptComponent's allocator): its script comes from the diff --git a/dGame/dComponents/SkillComponent.cpp b/dGame/dComponents/SkillComponent.cpp index 2bf1a2c43..c8b0c5feb 100644 --- a/dGame/dComponents/SkillComponent.cpp +++ b/dGame/dComponents/SkillComponent.cpp @@ -26,6 +26,8 @@ #include "CDSkillBehaviorTable.h" #include "ServiceType.h" #include "MessageType/Client.h" +#include "Entity.h" +#include "ObjectLoadState.h" ProjectileSyncEntry::ProjectileSyncEntry() { } @@ -565,3 +567,10 @@ void SkillComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialU uint32_t SkillComponent::GetUniqueSkillId() { return ++this->m_skillUid; } + +void SkillComponent::Allocator(Entity& entity, ObjectLoadState& state) { + const auto skillID = state.GetComponentID(eReplicaComponentType::SKILL, -1); + if (skillID > -1 || entity.GetCharacter()) { + entity.AddComponent(skillID); + } +} diff --git a/dGame/dComponents/SkillComponent.h b/dGame/dComponents/SkillComponent.h index 95e2498d4..ebc8ad935 100644 --- a/dGame/dComponents/SkillComponent.h +++ b/dGame/dComponents/SkillComponent.h @@ -15,6 +15,8 @@ #include "Logger.h" #include "eReplicaComponentType.h" +struct ObjectLoadState; + struct ProjectileSyncEntry { LWOOBJID id = LWOOBJID_EMPTY; @@ -63,6 +65,9 @@ class SkillComponent final : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::SKILL; + // The client's allocator for this type (ObjectLoader2::DoObjectComponentLoad calls it in load order) + static void Allocator(Entity& entity, ObjectLoadState& state); + explicit SkillComponent(Entity* parent, const int32_t componentID); ~SkillComponent() override; diff --git a/dGame/dComponents/Sound/SoundTriggerComponent.cpp b/dGame/dComponents/Sound/SoundTriggerComponent.cpp index ef7aa8f64..44fb1a007 100644 --- a/dGame/dComponents/Sound/SoundTriggerComponent.cpp +++ b/dGame/dComponents/Sound/SoundTriggerComponent.cpp @@ -1,6 +1,9 @@ #include "SoundTriggerComponent.h" #include "Game.h" #include "Logger.h" +#include "Entity.h" +#include "ObjectLoadState.h" +#include "RacingSoundTriggerComponent.h" void MusicCue::Serialize(RakNet::BitStream& outBitStream){ outBitStream.Write(name.size()); @@ -115,3 +118,13 @@ void SoundTriggerComponent::DeactivateMusicCue(const std::string& name) { Game::entityManager->SerializeEntity(m_Parent); } } + +void SoundTriggerComponent::Allocator(Entity& entity, ObjectLoadState& state) { + const auto soundTriggerID = state.GetComponentID(eReplicaComponentType::SOUND_TRIGGER, -1); + const auto racingSoundTriggerID = state.GetComponentID(eReplicaComponentType::RACING_SOUND_TRIGGER, -1); + if (soundTriggerID > -1) { + entity.AddComponent(soundTriggerID); + } else if (racingSoundTriggerID > -1) { + entity.AddComponent(racingSoundTriggerID); + } +} diff --git a/dGame/dComponents/Sound/SoundTriggerComponent.h b/dGame/dComponents/Sound/SoundTriggerComponent.h index a05ea0914..8c605b432 100644 --- a/dGame/dComponents/Sound/SoundTriggerComponent.h +++ b/dGame/dComponents/Sound/SoundTriggerComponent.h @@ -6,6 +6,8 @@ #include "Component.h" #include "eReplicaComponentType.h" +struct ObjectLoadState; + struct MusicCue { std::string name; uint32_t result; @@ -60,6 +62,9 @@ struct MixerProgram { class SoundTriggerComponent : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::SOUND_TRIGGER; + + // The client's allocator for this type (ObjectLoader2::DoObjectComponentLoad calls it in load order) + static void Allocator(Entity& entity, ObjectLoadState& state); explicit SoundTriggerComponent(Entity* parent, const int32_t componentID); void LoadConfigData() override; diff --git a/dGame/dComponents/Vendor/AchievementVendorComponent.cpp b/dGame/dComponents/Vendor/AchievementVendorComponent.cpp index d3dc906f1..c518503f6 100644 --- a/dGame/dComponents/Vendor/AchievementVendorComponent.cpp +++ b/dGame/dComponents/Vendor/AchievementVendorComponent.cpp @@ -8,6 +8,9 @@ #include "CheatDetection.h" #include "UserManager.h" #include "CDMissionsTable.h" +#include "Entity.h" +#include "ObjectLoadState.h" +#include "ProximityMonitorComponent.h" AchievementVendorComponent::AchievementVendorComponent(Entity* parent, const int32_t componentID) : BaseVendorComponent(parent, componentID) { } @@ -83,3 +86,11 @@ void AchievementVendorComponent::RefreshInventory(bool isCreation) { SetHasStandardCostItems(true); Game::entityManager->SerializeEntity(m_Parent); } + +void AchievementVendorComponent::Allocator(Entity& entity, ObjectLoadState& state) { + const auto achievementVendorID = state.GetComponentID(eReplicaComponentType::ACHIEVEMENT_VENDOR, -1); + if (achievementVendorID > -1) { + if (!entity.HasComponent(eReplicaComponentType::PROXIMITY_MONITOR)) entity.AddComponent(-1); + entity.AddComponent(achievementVendorID); + } +} diff --git a/dGame/dComponents/Vendor/AchievementVendorComponent.h b/dGame/dComponents/Vendor/AchievementVendorComponent.h index 95fb49201..4cadeeb15 100644 --- a/dGame/dComponents/Vendor/AchievementVendorComponent.h +++ b/dGame/dComponents/Vendor/AchievementVendorComponent.h @@ -6,11 +6,16 @@ #include #include +struct ObjectLoadState; + class Entity; class AchievementVendorComponent final : public BaseVendorComponent { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::ACHIEVEMENT_VENDOR; + + // The client's allocator for this type (ObjectLoader2::DoObjectComponentLoad calls it in load order) + static void Allocator(Entity& entity, ObjectLoadState& state); AchievementVendorComponent(Entity* parent, const int32_t componentID); void LoadDataFromTemplate() override; diff --git a/dGame/dComponents/Vendor/DonationVendorComponent.cpp b/dGame/dComponents/Vendor/DonationVendorComponent.cpp index 6505b311e..ff6129a1a 100644 --- a/dGame/dComponents/Vendor/DonationVendorComponent.cpp +++ b/dGame/dComponents/Vendor/DonationVendorComponent.cpp @@ -13,6 +13,9 @@ #include "MissionComponent.h" #include "User.h" #include "UserManager.h" +#include "Entity.h" +#include "ObjectLoadState.h" +#include "ProximityMonitorComponent.h" DonationVendorComponent::DonationVendorComponent(Entity* parent, const int32_t componentID) : BaseVendorComponent(parent, componentID) { } @@ -108,3 +111,11 @@ void DonationVendorComponent::ConfirmDonation(Entity& player) { } characterComponent->SetCurrentInteracting(LWOOBJID_EMPTY); } + +void DonationVendorComponent::Allocator(Entity& entity, ObjectLoadState& state) { + const auto donationVendorID = state.GetComponentID(eReplicaComponentType::DONATION_VENDOR, -1); + if (donationVendorID > -1) { + if (!entity.HasComponent(eReplicaComponentType::PROXIMITY_MONITOR)) entity.AddComponent(-1); + entity.AddComponent(donationVendorID); + } +} diff --git a/dGame/dComponents/Vendor/DonationVendorComponent.h b/dGame/dComponents/Vendor/DonationVendorComponent.h index b43fa2a35..3df387da2 100644 --- a/dGame/dComponents/Vendor/DonationVendorComponent.h +++ b/dGame/dComponents/Vendor/DonationVendorComponent.h @@ -4,11 +4,16 @@ #include "BaseVendorComponent.h" #include "eReplicaComponentType.h" +struct ObjectLoadState; + class Entity; class DonationVendorComponent final : public BaseVendorComponent { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::DONATION_VENDOR; + + // The client's allocator for this type (ObjectLoader2::DoObjectComponentLoad calls it in load order) + static void Allocator(Entity& entity, ObjectLoadState& state); DonationVendorComponent(Entity* parent, const int32_t componentID); void LoadConfigData() override; diff --git a/dGame/dComponents/Vendor/VendorComponent.cpp b/dGame/dComponents/Vendor/VendorComponent.cpp new file mode 100644 index 000000000..c7cdb2962 --- /dev/null +++ b/dGame/dComponents/Vendor/VendorComponent.cpp @@ -0,0 +1,13 @@ +#include "VendorComponent.h" +#include "Entity.h" +#include "ObjectLoadState.h" +#include "ProximityMonitorComponent.h" + +void VendorComponent::Allocator(Entity& entity, ObjectLoadState& state) { + const auto vendorID = state.GetComponentID(eReplicaComponentType::VENDOR); + // The vendor allocators add a proximity monitor first when there is none + if (vendorID > 0) { + if (!entity.HasComponent(eReplicaComponentType::PROXIMITY_MONITOR)) entity.AddComponent(-1); + entity.AddComponent(vendorID); + } +} diff --git a/dGame/dComponents/Vendor/VendorComponent.h b/dGame/dComponents/Vendor/VendorComponent.h index f5693a005..32b5c8d2e 100644 --- a/dGame/dComponents/Vendor/VendorComponent.h +++ b/dGame/dComponents/Vendor/VendorComponent.h @@ -3,6 +3,8 @@ #include "BaseVendorComponent.h" +struct ObjectLoadState; + /** * A vendor (16), as the client's LWOVendorComponent: a BaseVendorComponent. */ @@ -10,6 +12,9 @@ class VendorComponent final : public BaseVendorComponent { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::VENDOR; + // The client's allocator for this type (ObjectLoader2::DoObjectComponentLoad calls it in load order) + static void Allocator(Entity& entity, ObjectLoadState& state); + VendorComponent(Entity* parent, const int32_t componentID) : BaseVendorComponent(parent, componentID) {} };