mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-11-14 07:47:13 +00:00
feat(entity): the object's interaction distance, preconditions and world state, as the client
LWOGameObject loads these itself: interactionDistance from its Objects row (16 without one) and the level's interaction_distance over it, CheckPrecondition and objectWorldState from the level config, before its components load theirs. The quickbuild, the pet and the race instancer read them from the object; the race instancer now uses the Objects row's distance like the client does, instead of a fixed 16. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -436,6 +436,18 @@ void Entity::DoObjectComponentLoad(ObjectLoadState& state) {
|
||||
if (!triggerInfo.empty()) AddComponent<TriggerComponent>(-1, triggerInfo);
|
||||
}
|
||||
|
||||
void Entity::LoadTemplateData() {
|
||||
const auto& object = CDClientManager::GetTable<CDObjectsTable>()->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<float>(u"interaction_distance");
|
||||
if (HasVar(u"objectWorldState")) m_ObjectWorldState = GetVarAs<int32_t>(u"objectWorldState");
|
||||
if (HasVar(u"CheckPrecondition")) m_CheckPrecondition = GetVar<std::u16string>(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());
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -59,7 +59,7 @@ void QuickBuildComponent::LoadDataFromTemplate() {
|
||||
}
|
||||
|
||||
void QuickBuildComponent::LoadConfigData() {
|
||||
std::u16string checkPreconditions = m_Parent->GetVar<std::u16string>(u"CheckPrecondition");
|
||||
const auto& checkPreconditions = m_Parent->GetCheckPrecondition();
|
||||
|
||||
if (!checkPreconditions.empty()) {
|
||||
m_Precondition = new PreconditionExpression(GeneralUtils::UTF16ToWTF8(checkPreconditions));
|
||||
|
||||
@@ -79,7 +79,7 @@ void PetComponent::LoadDataFromTemplate() {
|
||||
|
||||
// The level config: the taming preconditions
|
||||
void PetComponent::LoadConfigData() {
|
||||
std::string checkPreconditions = GeneralUtils::UTF16ToWTF8(m_Parent->GetVar<std::u16string>(u"CheckPrecondition"));
|
||||
std::string checkPreconditions = GeneralUtils::UTF16ToWTF8(m_Parent->GetCheckPrecondition());
|
||||
|
||||
if (!checkPreconditions.empty()) {
|
||||
SetPreconditions(checkPreconditions);
|
||||
|
||||
@@ -3,5 +3,5 @@
|
||||
#include "Entity.h"
|
||||
|
||||
void GfRaceInstancer::OnStartup(Entity* self) {
|
||||
self->SetProximityRadius(self->HasVar(u"interaction_distance") ? self->GetVar<float>(u"interaction_distance") : 16.0f, "Interaction_Distance");
|
||||
self->SetProximityRadius(self->GetInteractionDistance(), "Interaction_Distance");
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<Entity>(0x0102030405060708LL, pedestalInfo);
|
||||
pedestal->SetVar<std::u16string>(u"CheckPrecondition", GeneralUtils::ASCIIToUTF16(std::to_string(PRECONDITION)));
|
||||
pedestal->SetCheckPrecondition(GeneralUtils::ASCIIToUTF16(std::to_string(PRECONDITION)));
|
||||
auto* const quickBuild = pedestal->AddComponent<QuickBuildComponent>(-1);
|
||||
quickBuild->SetResetTime(20.0f);
|
||||
quickBuild->SetCompleteTime(10.0f);
|
||||
|
||||
@@ -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)",
|
||||
|
||||
@@ -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)",
|
||||
|
||||
@@ -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<DestroyableComponent>(-1);
|
||||
@@ -130,6 +131,7 @@ TEST_F(ClientMessagesTests, ResyncEquipmentResendsTheEquipment) {
|
||||
auto msg = FromLiveClientCapture<GameMessages::ResyncEquipment>(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<InventoryComponent>(-1);
|
||||
RakNet::BitStream first;
|
||||
inventory->Serialize(first, false); // clears the dirty flag it starts with
|
||||
|
||||
Reference in New Issue
Block a user