diff --git a/dGame/Entity.cpp b/dGame/Entity.cpp index 5f4337daf..211b7f08c 100644 --- a/dGame/Entity.cpp +++ b/dGame/Entity.cpp @@ -436,6 +436,18 @@ void Entity::DoObjectComponentLoad(ObjectLoadState& state) { if (!triggerInfo.empty()) AddComponent(-1, triggerInfo); } +void Entity::LoadTemplateData() { + const auto& object = CDClientManager::GetTable()->GetByID(m_TemplateID); + if (object.id == m_TemplateID && object.interactionDistance >= 0.0f) m_InteractionDistance = object.interactionDistance; +} + +// The client erases interaction_distance from the config once read; server scripts still read it, so it stays +void Entity::LoadConfigData() { + if (HasVar(u"interaction_distance")) m_InteractionDistance = GetVarAs(u"interaction_distance"); + if (HasVar(u"objectWorldState")) m_ObjectWorldState = GetVarAs(u"objectWorldState"); + if (HasVar(u"CheckPrecondition")) m_CheckPrecondition = GetVar(u"CheckPrecondition"); +} + void Entity::Initialize() { RegisterMsg(&Entity::MsgRequestServerObjectInfo); RegisterMsg(&Entity::MsgDropClientLoot); @@ -464,13 +476,16 @@ void Entity::Initialize() { m_ParentEntity->AddChild(this); } + LoadTemplateData(); + m_LoadingComponents = true; auto loadState = DoObjectPreLoad(); DoObjectComponentLoad(loadState); m_LoadingComponents = false; - // LWOGameObject::MakeReadyForSerialization (0x0101c230): every component exists; each loads the object's config, - // then a character's save, in the order they were made + // LWOGameObject::MakeReadyForSerialization (0x0101c230): every component exists; the object loads its config, then + // each component the config and a character's save, in the order they were made + LoadConfigData(); for (auto* const component : m_ComponentList) { component->LoadConfigData(); if (m_Character) component->LoadFromSaveData(m_Character->GetXMLDoc()); diff --git a/dGame/Entity.h b/dGame/Entity.h index ed785a6f3..7c9b31912 100644 --- a/dGame/Entity.h +++ b/dGame/Entity.h @@ -370,6 +370,18 @@ public: // Scale will only be communicated to the client when the construction packet is sent void SetScale(const float scale) { m_Scale = scale; }; + // How close a player must be to use the object (LWOGameObject::interactionDistance): the Objects row's, else 16, + // over which the level's interaction_distance goes + float GetInteractionDistance() const { return m_InteractionDistance; } + + // The precondition expression for using the object (LWOGameObject::CheckPrecondition), from the level config + const std::u16string& GetCheckPrecondition() const { return m_CheckPrecondition; } + // The client's SetPreconditions message + void SetCheckPrecondition(const std::u16string& precondition) { m_CheckPrecondition = precondition; } + + // The object's world state (LWOGameObject::objectWorldState), from the level config's objectWorldState + int32_t GetObjectWorldState() const { return m_ObjectWorldState; } + // The client moves the object with its parent (LWOGameObject::updatesWithParentPosition), sent with the parent; // the spawn behavior's updatePositionWithParent sets it (live: the Engineer Personal Fortress, LOT 9479) void SetUpdatesWithParentPosition(const bool value) { m_UpdatesWithParentPosition = value; m_IsParentChildDirty = true; } @@ -403,6 +415,11 @@ public: private: + // LWOGameObject::LoadTemplateData (0x00f997c0) and LoadConfigData (0x00f86f60): the object's own data from its + // Objects row and its level config + void LoadTemplateData(); + void LoadConfigData(); + // ObjectLoader2::DoObjectPreLoad: what the components will be, before any is made ObjectLoadState DoObjectPreLoad(); // ObjectLoader2::DoObjectComponentLoad: the components in the client's order, then the ones it adds after @@ -471,6 +488,9 @@ private: bool m_IsParentChildDirty = true; bool m_UpdatesWithParentPosition = false; + float m_InteractionDistance = 16.0f; + std::u16string m_CheckPrecondition; + int32_t m_ObjectWorldState = 0; // When this object was created on this server. Construction sends its age from this, as live did. std::chrono::steady_clock::time_point m_CreationTime = std::chrono::steady_clock::now(); diff --git a/dGame/dComponents/Activity/QuickBuildComponent.cpp b/dGame/dComponents/Activity/QuickBuildComponent.cpp index ee79c024b..c30f99208 100644 --- a/dGame/dComponents/Activity/QuickBuildComponent.cpp +++ b/dGame/dComponents/Activity/QuickBuildComponent.cpp @@ -59,7 +59,7 @@ void QuickBuildComponent::LoadDataFromTemplate() { } void QuickBuildComponent::LoadConfigData() { - std::u16string checkPreconditions = m_Parent->GetVar(u"CheckPrecondition"); + const auto& checkPreconditions = m_Parent->GetCheckPrecondition(); if (!checkPreconditions.empty()) { m_Precondition = new PreconditionExpression(GeneralUtils::UTF16ToWTF8(checkPreconditions)); diff --git a/dGame/dComponents/PetComponent.cpp b/dGame/dComponents/PetComponent.cpp index 43579dad1..76ba539b5 100644 --- a/dGame/dComponents/PetComponent.cpp +++ b/dGame/dComponents/PetComponent.cpp @@ -79,7 +79,7 @@ void PetComponent::LoadDataFromTemplate() { // The level config: the taming preconditions void PetComponent::LoadConfigData() { - std::string checkPreconditions = GeneralUtils::UTF16ToWTF8(m_Parent->GetVar(u"CheckPrecondition")); + std::string checkPreconditions = GeneralUtils::UTF16ToWTF8(m_Parent->GetCheckPrecondition()); if (!checkPreconditions.empty()) { SetPreconditions(checkPreconditions); diff --git a/dScripts/ai/GF/GfRaceInstancer.cpp b/dScripts/ai/GF/GfRaceInstancer.cpp index 45c863007..b751949f4 100644 --- a/dScripts/ai/GF/GfRaceInstancer.cpp +++ b/dScripts/ai/GF/GfRaceInstancer.cpp @@ -3,5 +3,5 @@ #include "Entity.h" void GfRaceInstancer::OnStartup(Entity* self) { - self->SetProximityRadius(self->HasVar(u"interaction_distance") ? self->GetVar(u"interaction_distance") : 16.0f, "Interaction_Distance"); + self->SetProximityRadius(self->GetInteractionDistance(), "Interaction_Distance"); } diff --git a/tests/dGameTests/PlayerStatisticTests.cpp b/tests/dGameTests/PlayerStatisticTests.cpp index e7e36ccc6..108af93af 100644 --- a/tests/dGameTests/PlayerStatisticTests.cpp +++ b/tests/dGameTests/PlayerStatisticTests.cpp @@ -133,6 +133,7 @@ TEST_F(PlayerStatisticTest, SmashingCountsEnemiesAndSmashables) { // Smash finds the killer through the entity manager, which looks the new entity up in the CDClient CDClientDatabase::Connect(":memory:"); CDClientDatabase::ExecuteDML("CREATE TABLE ComponentsRegistry (id INTEGER, component_type INTEGER, component_id INTEGER);"); + CDClientDatabase::ExecuteDML("CREATE TABLE Objects (id INTEGER, name TEXT, type TEXT, interactionDistance REAL);"); ASSERT_NE(Game::entityManager->CreateEntity(info, nullptr, nullptr, true, 0x3FFFFFFFFFFELL), nullptr); // the zone control object loot asks auto* killer = Game::entityManager->CreateEntity(info, nullptr, nullptr, false, PLAYER + 1); ASSERT_NE(killer, nullptr); diff --git a/tests/dGameTests/dComponentsTests/LootMetricsTests.cpp b/tests/dGameTests/dComponentsTests/LootMetricsTests.cpp index 904144971..c552b55ab 100644 --- a/tests/dGameTests/dComponentsTests/LootMetricsTests.cpp +++ b/tests/dGameTests/dComponentsTests/LootMetricsTests.cpp @@ -40,7 +40,7 @@ protected: SetUpDependencies(); CDClientDatabase::Connect(":memory:"); CDClientDatabase::ExecuteDML("CREATE TABLE ItemSets (setID INTEGER, itemIDs TEXT);"); - CDClientDatabase::ExecuteDML("CREATE TABLE Objects (id INTEGER, name TEXT, type TEXT);"); + CDClientDatabase::ExecuteDML("CREATE TABLE Objects (id INTEGER, name TEXT, type TEXT, interactionDistance REAL);"); CDClientDatabase::ExecuteDML("CREATE TABLE ComponentsRegistry (id INTEGER, component_type INTEGER, component_id INTEGER);"); CDClientDatabase::ExecuteDML("CREATE TABLE ObjectSkills (objectTemplate INTEGER, skillID INTEGER, castOnType INTEGER, AICombatWeight INTEGER);"); CDClientDatabase::ExecuteDML("CREATE TABLE SkillBehavior (skillID INTEGER, behaviorID INTEGER);"); @@ -228,7 +228,7 @@ TEST_F(LootMetricsTests, QuickbuildTakesItsCostAsTheSource) { auto pedestalInfo = info; pedestalInfo.lot = 8551; auto pedestal = std::make_unique(0x0102030405060708LL, pedestalInfo); - pedestal->SetVar(u"CheckPrecondition", GeneralUtils::ASCIIToUTF16(std::to_string(PRECONDITION))); + pedestal->SetCheckPrecondition(GeneralUtils::ASCIIToUTF16(std::to_string(PRECONDITION))); auto* const quickBuild = pedestal->AddComponent(-1); quickBuild->SetResetTime(20.0f); quickBuild->SetCompleteTime(10.0f); diff --git a/tests/dGameTests/dComponentsTests/QuickBuildCompleteTests.cpp b/tests/dGameTests/dComponentsTests/QuickBuildCompleteTests.cpp index 289ab695d..c126bdda9 100644 --- a/tests/dGameTests/dComponentsTests/QuickBuildCompleteTests.cpp +++ b/tests/dGameTests/dComponentsTests/QuickBuildCompleteTests.cpp @@ -26,6 +26,7 @@ namespace { CDClientDatabase::Connect(":memory:"); for (const auto* table : { "ComponentsRegistry (id INTEGER, component_type INTEGER, component_id INTEGER)", + "Objects (id INTEGER, name TEXT, type TEXT, interactionDistance REAL)", "ObjectSkills (objectTemplate INTEGER, skillID INTEGER, castOnType INTEGER, AICombatWeight INTEGER)", "SkillBehavior (skillID INTEGER, behaviorID INTEGER)", "ItemSets (setID INTEGER, itemIDs TEXT)", diff --git a/tests/dGameTests/dComponentsTests/ReplicaComponentOrderTests.cpp b/tests/dGameTests/dComponentsTests/ReplicaComponentOrderTests.cpp index 6430eefb9..caa34c288 100644 --- a/tests/dGameTests/dComponentsTests/ReplicaComponentOrderTests.cpp +++ b/tests/dGameTests/dComponentsTests/ReplicaComponentOrderTests.cpp @@ -294,6 +294,7 @@ namespace { CDClientDatabase::Connect(":memory:"); for (const auto* table : { "ComponentsRegistry (id INTEGER, component_type INTEGER, component_id INTEGER)", + "Objects (id INTEGER, name TEXT, type TEXT, interactionDistance REAL)", "BaseCombatAIComponent (id INTEGER, aggroRadius REAL, tetherSpeed REAL, pursuitSpeed REAL, softTetherRadius REAL, hardTetherRadius REAL, minRoundLength REAL, maxRoundLength REAL, combatRoundLength REAL)", "ItemSets (setID INTEGER, itemIDs TEXT)", "ObjectSkills (objectTemplate INTEGER, skillID INTEGER, castOnType INTEGER, AICombatWeight INTEGER)", diff --git a/tests/dGameTests/dGameMessagesTests/ClientMessagesTests.cpp b/tests/dGameTests/dGameMessagesTests/ClientMessagesTests.cpp index e712a5fcb..150129fe1 100644 --- a/tests/dGameTests/dGameMessagesTests/ClientMessagesTests.cpp +++ b/tests/dGameTests/dGameMessagesTests/ClientMessagesTests.cpp @@ -105,6 +105,7 @@ TEST_F(ClientMessagesTests, CasterDeadMatchesLiveCapture) { TEST_F(ClientMessagesTests, CasterDeadEndsTheDeadCastersSkill) { CDClientDatabase::Connect(":memory:"); // the entity manager looks the new entity up CDClientDatabase::ExecuteDML("CREATE TABLE ComponentsRegistry (id INTEGER, component_type INTEGER, component_id INTEGER);"); + CDClientDatabase::ExecuteDML("CREATE TABLE Objects (id INTEGER, name TEXT, type TEXT, interactionDistance REAL);"); auto* const caster = Game::entityManager->CreateEntity(info, nullptr, nullptr, false, 0x40040000000bdb5LL); ASSERT_NE(caster, nullptr); auto* const destroyable = caster->AddComponent(-1); @@ -130,6 +131,7 @@ TEST_F(ClientMessagesTests, ResyncEquipmentResendsTheEquipment) { auto msg = FromLiveClientCapture(std::string(PLAYER_HEADER) + "d604"); CDClientDatabase::Connect(":memory:"); // the inventory looks its component up CDClientDatabase::ExecuteDML("CREATE TABLE ComponentsRegistry (id INTEGER, component_type INTEGER, component_id INTEGER);"); + CDClientDatabase::ExecuteDML("CREATE TABLE Objects (id INTEGER, name TEXT, type TEXT, interactionDistance REAL);"); auto* const inventory = player->AddComponent(-1); RakNet::BitStream first; inventory->Serialize(first, false); // clears the dirty flag it starts with