refactor(component): the client's component lifecycle

Components get the client's per-component functions: LoadDataFromTemplate,
PrepareConfigData, LoadConfigData, LoadFromSaveData, Startup, Run,
OnEnterScope, OnLeaveScope, Preshutdown and Shutdown, called by the entity in
the client's order. Update is renamed Run, LoadFromXml LoadFromSaveData and
UpdateXml WriteSaveData. Nothing sent changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-10-01 07:45:16 -05:00
parent e8406cc829
commit 50bfa623ca
58 changed files with 181 additions and 132 deletions

View File

@@ -217,6 +217,10 @@ Entity::~Entity() {
CancelAllTimers();
CancelCallbackTimers();
// The client's Preshutdown, then Shutdown, for every component before any is deleted
for (auto* const component : m_Components | std::views::values) if (component) component->Preshutdown();
for (auto* const component : m_Components | std::views::values) if (component) component->Shutdown();
for (auto& component : m_Components | std::views::values) {
if (component) {
delete component;
@@ -309,7 +313,7 @@ void Entity::Initialize() {
* Player components (the client makes them with the character, not from the registry)
*/
if (m_Character && m_Character->GetParentUser()) {
AddComponent<MissionComponent>(-1)->LoadFromXml(m_Character->GetXMLDoc());
AddComponent<MissionComponent>(-1)->LoadFromSaveData(m_Character->GetXMLDoc());
}
/**
@@ -347,7 +351,7 @@ void Entity::Initialize() {
auto* controllablePhysics = AddComponent<ControllablePhysicsComponent>(controllablePhysicsComponentID);
if (m_Character) {
controllablePhysics->LoadFromXml(m_Character->GetXMLDoc());
controllablePhysics->LoadFromSaveData(m_Character->GetXMLDoc());
const auto mapID = Game::server->GetZoneID();
const auto& targetSceneName = m_Character->GetTargetScene();
@@ -430,7 +434,7 @@ void Entity::Initialize() {
std::vector<CDDestructibleComponent> destCompData = destCompTable->Query([componentID](const CDDestructibleComponent& entry) { return (entry.id == componentID); });
if (m_Character) {
comp->LoadFromXml(m_Character->GetXMLDoc());
comp->LoadFromSaveData(m_Character->GetXMLDoc());
} else {
// extraInfo overrides. Client ORs the database smashable and the luz smashable.
comp->SetIsSmashable(comp->GetIsSmashable() || isSmashable);
@@ -519,14 +523,14 @@ void Entity::Initialize() {
AddComponent<PossessorComponent>(characterID);
// load in the xml for the level
AddComponent<LevelProgressionComponent>(characterID)->LoadFromXml(m_Character->GetXMLDoc());
AddComponent<LevelProgressionComponent>(characterID)->LoadFromSaveData(m_Character->GetXMLDoc());
AddComponent<PlayerForcedMovementComponent>(characterID);
auto& systemAddress = m_Character->GetParentUser() ? m_Character->GetParentUser()->GetSystemAddress() : UNASSIGNED_SYSTEM_ADDRESS;
AddComponent<CharacterComponent>(characterID, m_Character, systemAddress)->LoadFromXml(m_Character->GetXMLDoc());
AddComponent<CharacterComponent>(characterID, m_Character, systemAddress)->LoadFromSaveData(m_Character->GetXMLDoc());
AddComponent<GhostComponent>(characterID)->LoadFromXml(m_Character->GetXMLDoc());
AddComponent<GhostComponent>(characterID)->LoadFromSaveData(m_Character->GetXMLDoc());
}
break;
}
@@ -670,7 +674,7 @@ void Entity::Initialize() {
const auto skillID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::SKILL, -1);
if (skillID > -1 || m_Character) {
auto* const skillComponent = AddComponent<SkillComponent>(skillID);
if (m_Character) skillComponent->LoadFromXml(m_Character->GetXMLDoc());
if (m_Character) skillComponent->LoadFromSaveData(m_Character->GetXMLDoc());
}
break;
}
@@ -1125,6 +1129,10 @@ void Entity::Initialize() {
const auto triggerInfo = GetVarAsString(u"trigger_id");
if (!triggerInfo.empty()) AddComponent<TriggerComponent>(-1, triggerInfo);
// Every component exists: the client's Startup step
for (auto* const component : m_Components | std::views::values) if (component) component->Startup();
m_ComponentsStarted = true;
// Hacky way to trigger these when the object has had a chance to get constructed
AddCallbackTimer(0, [this]() {
this->GetScript()->OnStartup(this);
@@ -1186,6 +1194,14 @@ no_ghosting:
}
}
void Entity::OnEnterScope(Entity& player) {
for (auto* const component : m_Components | std::views::values) if (component) component->OnEnterScope(player);
}
void Entity::OnLeaveScope(Entity& player) {
for (auto* const component : m_Components | std::views::values) if (component) component->OnLeaveScope(player);
}
bool Entity::operator==(const Entity& other) const {
return other.m_ObjectID == m_ObjectID;
}
@@ -1436,7 +1452,7 @@ void Entity::UpdateXMLDoc(tinyxml2::XMLDocument& doc) {
for (const auto& component : m_Components | std::views::values) {
if (!component) continue;
component->UpdateXml(doc);
component->WriteSaveData(doc);
}
}
@@ -1516,7 +1532,7 @@ void Entity::Update(const float deltaTime) {
for (const auto& component : m_Components | std::views::values) {
if (!component) continue;
component->Update(deltaTime);
component->Run(deltaTime);
}
if (m_ShouldDestroyAfterUpdate) {

View File

@@ -229,6 +229,10 @@ public:
void UpdateXMLDoc(tinyxml2::XMLDocument& doc);
void Update(float deltaTime);
// A player starts or stops seeing this object: its components' OnEnterScope / OnLeaveScope
void OnEnterScope(Entity& player);
void OnLeaveScope(Entity& player);
// Events
void OnCollisionProximity(LWOOBJID otherEntity, const std::string& proxName, const std::string& status);
void OnCollisionPhantom(LWOOBJID otherEntity);
@@ -421,6 +425,9 @@ private:
std::vector<std::function<void(Entity* target)>> m_PhantomCollisionCallbacks;
std::unordered_map<eReplicaComponentType, Component*> m_Components;
// Every component has had its Startup (the end of Initialize)
bool m_ComponentsStarted = false;
std::vector<EntityTimer> m_Timers;
std::vector<EntityTimer> m_PendingTimers;
std::vector<EntityCallbackTimer> m_CallbackTimers;
@@ -608,6 +615,13 @@ inline ComponentType* Entity::AddComponent(VaArgs... args) {
new(componentToReturn) ComponentType(this, std::forward<VaArgs>(args)...);
}
// The client's load steps for a new component (DoObjectComponentLoad: the allocator reads the CDClient row, then
// PrepareConfigData); one added after the object started up starts up at once
componentToReturn->LoadDataFromTemplate();
componentToReturn->PrepareConfigData();
componentToReturn->LoadConfigData();
if (m_ComponentsStarted) componentToReturn->Startup();
// Finally return the created or already existing component.
// Because of the assert above, this should always be a ComponentType* but I need a way to guarantee the map cannot be modifed outside this function
// To allow a static cast here instead of a dynamic one.

View File

@@ -450,6 +450,8 @@ void EntityManager::ConstructAllEntities(const SystemAddress& sysAddr) {
for (auto* entity : m_Entities | std::views::values) {
if (entity && (entity->GetSpawnerID() != 0 || entity->GetLOT() == 1) && !entity->GetIsGhostingCandidate()) {
ConstructEntity(entity, sysAddr);
// Objects that are never ghosted enter a joining player's scope here
if (auto* const player = PlayerManager::GetPlayer(sysAddr)) entity->OnEnterScope(*player);
}
}
@@ -583,6 +585,7 @@ void EntityManager::UpdateGhosting(Entity* player) {
DestructEntity(entity, player->GetSystemAddress());
entity->SetObservers(entity->GetObservers() - 1);
entity->OnLeaveScope(*player);
} else if (!observed && nearEnough) {
// Check collectables, don't construct if it has been collected
uint32_t collectionId = entity->GetCollectibleID();
@@ -600,6 +603,7 @@ void EntityManager::UpdateGhosting(Entity* player) {
entity->SetObservers(entity->GetObservers() + 1);
ConstructEntity(entity, player->GetSystemAddress());
entity->OnEnterScope(*player);
}
}
}
@@ -632,10 +636,12 @@ void EntityManager::CheckGhosting(Entity* entity) {
ghostComponent->GhostEntity(id);
DestructEntity(entity, player->GetSystemAddress());
entity->SetObservers(entity->GetObservers() - 1);
entity->OnLeaveScope(*player);
} else if (!observed && nearEnough) {
ghostComponent->ObserveEntity(id);
ConstructEntity(entity, player->GetSystemAddress());
entity->SetObservers(entity->GetObservers() + 1);
entity->OnEnterScope(*player);
}
}
}

View File

@@ -163,7 +163,7 @@ BaseCombatAIComponent::~BaseCombatAIComponent() {
dpWorld::RemoveEntity(m_dpEntityEnemy);
}
void BaseCombatAIComponent::Update(const float deltaTime) {
void BaseCombatAIComponent::Run(const float deltaTime) {
//First, we need to process physics:
if (!m_dpEntity) return;

View File

@@ -57,7 +57,7 @@ public:
static float TetherSpeedOf(int32_t componentID);
~BaseCombatAIComponent() override;
void Update(float deltaTime) override;
void Run(float deltaTime) override;
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;
/**

View File

@@ -70,7 +70,7 @@ void BuffComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUp
outBitStream.Write0(); // something to do with immunity buffs?
}
void BuffComponent::Update(float deltaTime) {
void BuffComponent::Run(float deltaTime) {
/**
* Loop through all buffs and apply deltaTime to ther time.
* If they have expired, remove the buff and break.
@@ -353,7 +353,7 @@ Entity* BuffComponent::GetParent() const {
return m_Parent;
}
void BuffComponent::LoadFromXml(const tinyxml2::XMLDocument& doc) {
void BuffComponent::LoadFromSaveData(const tinyxml2::XMLDocument& doc) {
// Load buffs
auto* dest = doc.FirstChildElement("obj")->FirstChildElement("dest");
@@ -413,7 +413,7 @@ void BuffComponent::LoadFromXml(const tinyxml2::XMLDocument& doc) {
}
}
void BuffComponent::UpdateXml(tinyxml2::XMLDocument& doc) {
void BuffComponent::WriteSaveData(tinyxml2::XMLDocument& doc) {
// Save buffs
auto* dest = doc.FirstChildElement("obj")->FirstChildElement("dest");

View File

@@ -57,13 +57,13 @@ public:
Entity* GetParent() const;
void LoadFromXml(const tinyxml2::XMLDocument& doc) override;
void LoadFromSaveData(const tinyxml2::XMLDocument& doc) override;
void UpdateXml(tinyxml2::XMLDocument& doc) override;
void WriteSaveData(tinyxml2::XMLDocument& doc) override;
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;
void Update(float deltaTime) override;
void Run(float deltaTime) override;
/**
* Applies a buff to the parent entity

View File

@@ -292,7 +292,7 @@ void CharacterComponent::SetGMLevel(eGameMasterLevel gmlevel) {
m_GMLevel = gmlevel;
}
void CharacterComponent::LoadFromXml(const tinyxml2::XMLDocument& doc) {
void CharacterComponent::LoadFromSaveData(const tinyxml2::XMLDocument& doc) {
auto* character = doc.FirstChildElement("obj")->FirstChildElement("char");
if (!character) {
@@ -398,7 +398,7 @@ void CharacterComponent::LoadFromXml(const tinyxml2::XMLDocument& doc) {
}
}
void CharacterComponent::UpdateXml(tinyxml2::XMLDocument& doc) {
void CharacterComponent::WriteSaveData(tinyxml2::XMLDocument& doc) {
tinyxml2::XMLElement* minifig = doc.FirstChildElement("obj")->FirstChildElement("mf");
if (!minifig) {
LOG("Failed to find mf tag while updating XML!");

View File

@@ -48,8 +48,8 @@ public:
CharacterComponent(Entity* parent, const int32_t componentID, Character* character, const SystemAddress& systemAddress);
~CharacterComponent() override;
void LoadFromXml(const tinyxml2::XMLDocument& doc) override;
void UpdateXml(tinyxml2::XMLDocument& doc) override;
void LoadFromSaveData(const tinyxml2::XMLDocument& doc) override;
void WriteSaveData(tinyxml2::XMLDocument& doc) override;
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;

View File

@@ -15,7 +15,13 @@ namespace GameMessages {
class Entity;
/**
* Component base class, provides methods for game loop updates, usage events and loading and saving to XML.
* Component base class, with the client's component lifecycle (LWOGameObjComponent's vtable,
* LWOGameObjComponentFunctions), which the entity calls in the client's order (ObjectLoader2::DoObjectComponentLoad):
* constructed, LoadDataFromTemplate (its CDClient row), PrepareConfigData and LoadConfigData (the object's level
* config), LoadFromSaveData (a character's save), then Startup once every component of the object exists; Run every
* frame; OnEnterScope / OnLeaveScope when a player starts or stops seeing the object; Preshutdown and Shutdown before it
* is deleted. Serialize is the server side of the client's Deserialize. Client-only slots (GetSizeInMemory,
* GraphicsDeviceReset, GetPickTypePriority) are left out.
*/
class Component {
public:
@@ -30,11 +36,26 @@ public:
[[nodiscard]] int32_t GetComponentID() const noexcept { return m_ComponentID; }
/**
* Updates the component in the game loop
* @param deltaTime time passed since last update
*/
virtual void Update(float deltaTime) {}
// The CDClient row (componentID) of the component's table
virtual void LoadDataFromTemplate() {}
// The object's level config (LDF): PrepareConfigData first, then LoadConfigData
virtual void PrepareConfigData() {}
virtual void LoadConfigData() {}
// After every component of the object exists
virtual void Startup() {}
// Every frame
virtual void Run(float deltaTime) {}
// A player starts or stops seeing the object (ghosting)
virtual void OnEnterScope(Entity& player) {}
virtual void OnLeaveScope(Entity& player) {}
// Before the object is deleted
virtual void Preshutdown() {}
virtual void Shutdown() {}
/**
* Event called when this component is being used, e.g. when some entity interacted with it
@@ -42,17 +63,9 @@ public:
*/
virtual void OnUse(Entity* originator) {}
/**
* Save data from this componennt to character XML
* @param doc the document to write data to
*/
virtual void UpdateXml(tinyxml2::XMLDocument& doc) {}
/**
* Load base data for this component from character XML
* @param doc the document to read data from
*/
virtual void LoadFromXml(const tinyxml2::XMLDocument& doc) {}
// A character's save (its XML): read on load, written on save
virtual void LoadFromSaveData(const tinyxml2::XMLDocument& doc) {}
virtual void WriteSaveData(tinyxml2::XMLDocument& doc) {}
virtual void Serialize(RakNet::BitStream& outBitStream, bool isConstruction) {}
protected:

View File

@@ -70,7 +70,7 @@ ControllablePhysicsComponent::~ControllablePhysicsComponent() {
}
}
void ControllablePhysicsComponent::Update(float deltaTime) {
void ControllablePhysicsComponent::Run(float deltaTime) {
}
@@ -166,7 +166,7 @@ void ControllablePhysicsComponent::Serialize(RakNet::BitStream& outBitStream, bo
}
}
void ControllablePhysicsComponent::LoadFromXml(const tinyxml2::XMLDocument& doc) {
void ControllablePhysicsComponent::LoadFromSaveData(const tinyxml2::XMLDocument& doc) {
auto* character = doc.FirstChildElement("obj")->FirstChildElement("char");
if (!character) {
LOG("Failed to find char tag!");
@@ -186,7 +186,7 @@ void ControllablePhysicsComponent::LoadFromXml(const tinyxml2::XMLDocument& doc)
m_DirtyPosition = true;
}
void ControllablePhysicsComponent::UpdateXml(tinyxml2::XMLDocument& doc) {
void ControllablePhysicsComponent::WriteSaveData(tinyxml2::XMLDocument& doc) {
tinyxml2::XMLElement* character = doc.FirstChildElement("obj")->FirstChildElement("char");
if (!character) {
LOG("Failed to find char tag while updating XML!");

View File

@@ -28,10 +28,10 @@ public:
ControllablePhysicsComponent(Entity* entity, const int32_t componentID);
~ControllablePhysicsComponent() override;
void Update(float deltaTime) override;
void Run(float deltaTime) override;
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;
void LoadFromXml(const tinyxml2::XMLDocument& doc) override;
void UpdateXml(tinyxml2::XMLDocument& doc) override;
void LoadFromSaveData(const tinyxml2::XMLDocument& doc) override;
void WriteSaveData(tinyxml2::XMLDocument& doc) override;
/**
* Sets the position of this entity, also ensures this update is serialized next tick.

View File

@@ -158,11 +158,11 @@ void DestroyableComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsIn
}
}
void DestroyableComponent::Update(float deltaTime) {
void DestroyableComponent::Run(float deltaTime) {
m_DamageCooldownTimer -= deltaTime;
}
void DestroyableComponent::LoadFromXml(const tinyxml2::XMLDocument& doc) {
void DestroyableComponent::LoadFromSaveData(const tinyxml2::XMLDocument& doc) {
auto* dest = doc.FirstChildElement("obj")->FirstChildElement("dest");
if (!dest) {
LOG("Failed to find dest tag!");
@@ -172,7 +172,7 @@ void DestroyableComponent::LoadFromXml(const tinyxml2::XMLDocument& doc) {
auto* buffComponent = m_Parent->GetComponent<BuffComponent>();
if (buffComponent != nullptr) {
buffComponent->LoadFromXml(doc);
buffComponent->LoadFromSaveData(doc);
}
dest->QueryAttribute("hc", &m_iHealth);
@@ -184,7 +184,7 @@ void DestroyableComponent::LoadFromXml(const tinyxml2::XMLDocument& doc) {
m_DirtyHealth = true;
}
void DestroyableComponent::UpdateXml(tinyxml2::XMLDocument& doc) {
void DestroyableComponent::WriteSaveData(tinyxml2::XMLDocument& doc) {
tinyxml2::XMLElement* dest = doc.FirstChildElement("obj")->FirstChildElement("dest");
if (!dest) {
LOG("Failed to find dest tag!");
@@ -194,7 +194,7 @@ void DestroyableComponent::UpdateXml(tinyxml2::XMLDocument& doc) {
auto* buffComponent = m_Parent->GetComponent<BuffComponent>();
if (buffComponent != nullptr) {
buffComponent->UpdateXml(doc);
buffComponent->WriteSaveData(doc);
}
dest->SetAttribute("hc", m_iHealth);

View File

@@ -47,10 +47,10 @@ public:
DestroyableComponent(Entity* parentEntity, const int32_t componentID);
~DestroyableComponent() override;
void Update(float deltaTime) override;
void Run(float deltaTime) override;
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;
void LoadFromXml(const tinyxml2::XMLDocument& doc) override;
void UpdateXml(tinyxml2::XMLDocument& doc) override;
void LoadFromSaveData(const tinyxml2::XMLDocument& doc) override;
void WriteSaveData(tinyxml2::XMLDocument& doc) override;
/**
* Sets the health of this entity. Makes sure this is serialized on the next tick and if this is a character its

View File

@@ -798,7 +798,7 @@ void InventoryComponent::LoadXml(const tinyxml2::XMLDocument& document) {
}
}
void InventoryComponent::UpdateXml(tinyxml2::XMLDocument& document) {
void InventoryComponent::WriteSaveData(tinyxml2::XMLDocument& document) {
UpdatePetXml(document);
auto* inventoryElement = document.FirstChildElement("obj")->FirstChildElement("inv");
@@ -964,7 +964,7 @@ void InventoryComponent::Serialize(RakNet::BitStream& outBitStream, const bool b
if (bIsInitialUpdate) outBitStream.Write<uint32_t>(0);
}
void InventoryComponent::Update(float deltaTime) {
void InventoryComponent::Run(float deltaTime) {
for (auto* set : m_Itemsets) {
set->Update(deltaTime);
}

View File

@@ -101,10 +101,10 @@ public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::INVENTORY;
InventoryComponent(Entity* parent, const int32_t componentID);
void Update(float deltaTime) override;
void Run(float deltaTime) override;
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;
void LoadXml(const tinyxml2::XMLDocument& document);
void UpdateXml(tinyxml2::XMLDocument& document) override;
void WriteSaveData(tinyxml2::XMLDocument& document) override;
/**
* Returns an inventory of the specified type, if it exists

View File

@@ -1,7 +1,7 @@
#include "LUPExhibitComponent.h"
#include "EntityManager.h"
void LUPExhibitComponent::Update(float deltaTime) {
void LUPExhibitComponent::Run(float deltaTime) {
m_UpdateTimer += deltaTime;
if (m_UpdateTimer > 20.0f) {
NextLUPExhibit();

View File

@@ -17,7 +17,7 @@ public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::LUP_EXHIBIT;
LUPExhibitComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) {};
void Update(float deltaTime) override;
void Run(float deltaTime) override;
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;
void NextLUPExhibit();
private:

View File

@@ -14,7 +14,7 @@ LevelProgressionComponent::LevelProgressionComponent(Entity* parent, const int32
m_CharacterVersion = eCharacterVersion::LIVE;
}
void LevelProgressionComponent::UpdateXml(tinyxml2::XMLDocument& doc) {
void LevelProgressionComponent::WriteSaveData(tinyxml2::XMLDocument& doc) {
tinyxml2::XMLElement* level = doc.FirstChildElement("obj")->FirstChildElement("lvl");
if (!level) {
LOG("Failed to find lvl tag while updating XML!");
@@ -25,7 +25,7 @@ void LevelProgressionComponent::UpdateXml(tinyxml2::XMLDocument& doc) {
level->SetAttribute("cv", static_cast<uint32_t>(m_CharacterVersion));
}
void LevelProgressionComponent::LoadFromXml(const tinyxml2::XMLDocument& doc) {
void LevelProgressionComponent::LoadFromSaveData(const tinyxml2::XMLDocument& doc) {
auto* level = doc.FirstChildElement("obj")->FirstChildElement("lvl");
if (!level) {
LOG("Failed to find lvl tag while loading XML!");

View File

@@ -27,13 +27,13 @@ public:
* Save data from this componennt to character XML
* @param doc the document to write data to
*/
void UpdateXml(tinyxml2::XMLDocument& doc) override;
void WriteSaveData(tinyxml2::XMLDocument& doc) override;
/**
* Load base data for this component from character XML
* @param doc the document to read data from
*/
void LoadFromXml(const tinyxml2::XMLDocument& doc) override;
void LoadFromSaveData(const tinyxml2::XMLDocument& doc) override;
/**
* Gets the current level of the entity

View File

@@ -446,7 +446,7 @@ bool MissionComponent::RequiresItem(const LOT lot) {
}
void MissionComponent::LoadFromXml(const tinyxml2::XMLDocument& doc) {
void MissionComponent::LoadFromSaveData(const tinyxml2::XMLDocument& doc) {
auto* mis = doc.FirstChildElement("obj")->FirstChildElement("mis");
if (mis == nullptr) return;
@@ -512,7 +512,7 @@ void MissionComponent::LoadFromXml(const tinyxml2::XMLDocument& doc) {
}
void MissionComponent::UpdateXml(tinyxml2::XMLDocument& doc) {
void MissionComponent::WriteSaveData(tinyxml2::XMLDocument& doc) {
auto shouldInsertMis = false;
auto* obj = doc.FirstChildElement("obj");

View File

@@ -33,8 +33,8 @@ public:
explicit MissionComponent(Entity* parent, const int32_t componentID);
~MissionComponent() override;
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate, unsigned int& flags);
void LoadFromXml(const tinyxml2::XMLDocument& doc) override;
void UpdateXml(tinyxml2::XMLDocument& doc) override;
void LoadFromSaveData(const tinyxml2::XMLDocument& doc) override;
void WriteSaveData(tinyxml2::XMLDocument& doc) override;
/**
* Returns all the missions for this entity, mapped by mission ID

View File

@@ -73,7 +73,7 @@ bool ModelComponent::OnRequestUse(GameMessages::RequestUseEvent& event) {
return toReturn;
}
void ModelComponent::Update(float deltaTime) {
void ModelComponent::Run(float deltaTime) {
if (m_IsPaused) return;
for (auto& behavior : m_Behaviors) {

View File

@@ -32,7 +32,7 @@ public:
ModelComponent(Entity* parent, const int32_t componentID);
void LoadBehaviors();
void Update(float deltaTime) override;
void Run(float deltaTime) override;
bool OnRequestUse(GameMessages::RequestUseEvent& event);
bool OnResetModelToDefaults(GameMessages::ResetModelToDefaults& resetModelToDefaults);

View File

@@ -64,7 +64,7 @@ void ModuleAssemblyComponent::Serialize(RakNet::BitStream& outBitStream, bool bI
}
}
void ModuleAssemblyComponent::Update(float deltaTime) {
void ModuleAssemblyComponent::Run(float deltaTime) {
}

View File

@@ -18,7 +18,7 @@ public:
~ModuleAssemblyComponent() override;
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;
void Update(float deltaTime) override;
void Run(float deltaTime) override;
/**
* Sets the subkey of this entity

View File

@@ -107,7 +107,7 @@ void MovementAIComponent::Resume() {
Game::entityManager->SerializeEntity(m_Parent);
}
void MovementAIComponent::Update(const float deltaTime) {
void MovementAIComponent::Run(const float deltaTime) {
if (IsKnockedBack()) {
UpdateKnockback(deltaTime);
return;

View File

@@ -68,7 +68,7 @@ public:
void SetPath(const std::string pathName);
void Update(float deltaTime) override;
void Run(float deltaTime) override;
/**
* Returns the basic settings that this entity uses to move around

View File

@@ -449,7 +449,7 @@ void MovingPlatformComponent::MoverArrive() {
FireArrivalHooks(arrived, atDesired, atEnd);
}
void MovingPlatformComponent::Update(const float deltaTime) {
void MovingPlatformComponent::Run(const float deltaTime) {
if (!IsMover()) return;
auto* const mover = GetMoverSubComponent();
if ((mover->mState & ePlatformStateFlag::Travelling) != 0) {

View File

@@ -282,7 +282,7 @@ private:
void MoverArrive();
public:
void Update(float deltaTime) override;
void Run(float deltaTime) override;
// The mover's state as a PlatformResync gives it (RequestPlatformResync)
void FillResync(GameMessages::PlatformResync& resync) const;

View File

@@ -256,7 +256,7 @@ void PetComponent::OnUse(Entity* originator) {
}
}
void PetComponent::Update(float deltaTime) {
void PetComponent::Run(float deltaTime) {
if (m_StartPosition == NiPoint3Constant::ZERO) {
m_StartPosition = m_Parent->GetPosition();
m_StartRotation = m_Parent->GetRotation();

View File

@@ -40,7 +40,7 @@ public:
~PetComponent() override;
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;
void Update(float deltaTime) override;
void Run(float deltaTime) override;
/**
* Handles an OnUse event from another entity, initializing the pet taming minigame if this pet is untamed.

View File

@@ -179,7 +179,7 @@ void ApplyCollisionEffect(const LWOOBJID& target, const ePhysicsEffectType effec
// The other types are not handled by the server and are here to handle all cases of the enum.
}
void PhantomPhysicsComponent::Update(float deltaTime) {
void PhantomPhysicsComponent::Run(float deltaTime) {
if (!m_dpEntity || !m_PhysicsActive) return;
//Process enter events

View File

@@ -29,7 +29,7 @@ public:
PhantomPhysicsComponent(Entity* parent, const int32_t componentID);
~PhantomPhysicsComponent() override;
void Update(float deltaTime) override;
void Run(float deltaTime) override;
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;
/**

View File

@@ -100,7 +100,7 @@ bool ProximityMonitorComponent::OnGetObjectReportInfo(GameMessages::GetObjectRep
return true;
}
void ProximityMonitorComponent::Update(float deltaTime) {
void ProximityMonitorComponent::Run(float deltaTime) {
for (const auto& prox : m_ProximitiesData) {
if (!prox.second) continue;

View File

@@ -25,7 +25,7 @@ public:
ProximityMonitorComponent(Entity* parentEntity, const int32_t componentID, int smallRadius = -1, int largeRadius = -1);
~ProximityMonitorComponent() override;
void Update(float deltaTime) override;
void Run(float deltaTime) override;
/**
* Creates an entry to check proximity for, given a name

View File

@@ -111,7 +111,7 @@ void QuickBuildComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsIni
}
}
void QuickBuildComponent::Update(float deltaTime) {
void QuickBuildComponent::Run(float deltaTime) {
SetActivator(GetActivator());
switch (m_State) {

View File

@@ -28,7 +28,7 @@ public:
~QuickBuildComponent() override;
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;
void Update(float deltaTime) override;
void Run(float deltaTime) override;
/**
* Handles a OnUse event from some entity, initiating the quick build

View File

@@ -572,7 +572,7 @@ RacingPlayerInfo* RacingControlComponent::GetPlayerData(LWOOBJID playerID) {
return nullptr;
}
void RacingControlComponent::Update(float deltaTime) {
void RacingControlComponent::Run(float deltaTime) {
// This method is a mess.
// Pre-load routine

View File

@@ -129,7 +129,7 @@ public:
~RacingControlComponent();
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;
void Update(float deltaTime) override;
void Run(float deltaTime) override;
/**
* Invoked when a player loads into the zone.

View File

@@ -83,7 +83,7 @@ void RenderComponent::RemoveEffect(const std::string& name) {
m_Effects.pop_back();
}
void RenderComponent::Update(const float deltaTime) {
void RenderComponent::Run(const float deltaTime) {
for (auto& effect : m_Effects) {
if (effect.time == 0) continue; // Skip persistent effects

View File

@@ -66,7 +66,7 @@ public:
RenderComponent(Entity* const parentEntity, const int32_t componentID);
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;
void Update(float deltaTime) override;
void Run(float deltaTime) override;
/**
* Adds an effect to this entity, if successful the effect is returned

View File

@@ -37,7 +37,7 @@ void RigidbodyPhantomPhysicsComponent::Serialize(RakNet::BitStream& outBitStream
PhysicsComponent::Serialize(outBitStream, bIsInitialUpdate);
}
void RigidbodyPhantomPhysicsComponent::Update(const float deltaTime) {
void RigidbodyPhantomPhysicsComponent::Run(const float deltaTime) {
if (!m_dpEntity) return;
//Process enter events

View File

@@ -23,7 +23,7 @@ public:
RigidbodyPhantomPhysicsComponent(Entity* parent, const int32_t componentID);
void Update(const float deltaTime) override;
void Run(const float deltaTime) override;
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;

View File

@@ -41,7 +41,7 @@ SimplePhysicsComponent::SimplePhysicsComponent(Entity* parent, const int32_t com
SimplePhysicsComponent::~SimplePhysicsComponent() {
}
void SimplePhysicsComponent::Update(const float deltaTime) {
void SimplePhysicsComponent::Run(const float deltaTime) {
if (m_Velocity == NiPoint3Constant::ZERO) return;
m_Position += m_Velocity * deltaTime;
m_DirtyPosition = true;

View File

@@ -38,7 +38,7 @@ public:
SimplePhysicsComponent(Entity* parent, const int32_t componentID);
~SimplePhysicsComponent() override;
void Update(const float deltaTime) override;
void Run(const float deltaTime) override;
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;

View File

@@ -63,7 +63,7 @@ void SkillComponent::StartCooldown(const uint32_t skillID) {
left = std::max(left, skill.cooldown);
}
void SkillComponent::LoadFromXml(const tinyxml2::XMLDocument& doc) {
void SkillComponent::LoadFromSaveData(const tinyxml2::XMLDocument& doc) {
m_CooldownGroups.clear();
const auto* const obj = doc.FirstChildElement("obj");
const auto* const skil = obj ? obj->FirstChildElement("skil") : nullptr;
@@ -81,7 +81,7 @@ void SkillComponent::LoadFromXml(const tinyxml2::XMLDocument& doc) {
}
}
void SkillComponent::UpdateXml(tinyxml2::XMLDocument& doc) {
void SkillComponent::WriteSaveData(tinyxml2::XMLDocument& doc) {
auto* const obj = doc.FirstChildElement("obj");
if (!obj) return;
auto* skil = obj->FirstChildElement("skil");
@@ -184,7 +184,7 @@ void SkillComponent::RegisterPlayerProjectile(const LWOOBJID projectileId, Behav
this->m_managedProjectiles.push_back(entry);
}
void SkillComponent::Update(const float deltaTime) {
void SkillComponent::Run(const float deltaTime) {
for (auto it = m_CooldownGroups.begin(); it != m_CooldownGroups.end();) {
it->second -= deltaTime;
it = it->second <= 0.0f ? m_CooldownGroups.erase(it) : std::next(it);

View File

@@ -71,18 +71,18 @@ public:
/**
* Computes skill updates. Invokes CalculateUpdate.
*/
void Update(float deltaTime) override;
void Run(float deltaTime) override;
/**
* Loads the cooldown groups still running at the last save: <skil sc="group:seconds;group:seconds;"/>
*/
void LoadFromXml(const tinyxml2::XMLDocument& doc) override;
void LoadFromSaveData(const tinyxml2::XMLDocument& doc) override;
/**
* Saves the running cooldown groups the way live did and the client reads them (LWOSkillComponent::LoadFromSaveData
* 0x00c2a9c0 splits sc on ';' and ':' into a cooldown group and the seconds left)
*/
void UpdateXml(tinyxml2::XMLDocument& doc) override;
void WriteSaveData(tinyxml2::XMLDocument& doc) override;
/**
* Starts the cooldown of a skill's cooldown group (SkillBehavior.cooldowngroup, cooldown) when the player casts it

View File

@@ -94,7 +94,7 @@ void SwitchComponent::EntityLeave(Entity* entity) {
}
void SwitchComponent::Update(float deltaTime) {
void SwitchComponent::Run(float deltaTime) {
if (m_Active) {
m_Timer -= deltaTime;

View File

@@ -21,7 +21,7 @@ public:
SwitchComponent(Entity* parent, const int32_t componentID);
~SwitchComponent() override;
void Update(float deltaTime) override;
void Run(float deltaTime) override;
void OnUse(Entity* originator) override;
Entity* GetParentEntity() const;

View File

@@ -50,7 +50,7 @@ TEST_F(CharacterSaveFieldsTests, MissionTypeStatesRoundTrip) {
Entity player(20, info);
auto* const missions = player.AddComponent<MissionComponent>(-1);
missions->LoadFromXml(doc);
missions->LoadFromSaveData(doc);
const auto& states = missions->GetMissionTypeStates();
ASSERT_EQ(states.size(), 2);
EXPECT_EQ(states.at("Build").at(""), eMissionLockState::NEW);
@@ -58,12 +58,12 @@ TEST_F(CharacterSaveFieldsTests, MissionTypeStatesRoundTrip) {
EXPECT_EQ(states.at("Location").at("Nimbus Station"), eMissionLockState::UNLOCKED);
missions->SetMissionTypeState("Battle", "General", eMissionLockState::NEW);
missions->UpdateXml(doc);
missions->WriteSaveData(doc);
EXPECT_EQ(Print(doc), R"(<obj v="1"><mis><done/><cur/><ts><type v="Battle"><st sub="General" val="1"/></type><type v="Build"><st sub="" val="1"/></type><type v="Location"><st sub="Avant Gardens" val="1"/><st sub="Nimbus Station" val="2"/></type></ts></mis></obj>)");
Entity reloaded(21, info);
auto* const reloadedMissions = reloaded.AddComponent<MissionComponent>(-1);
reloadedMissions->LoadFromXml(doc);
reloadedMissions->LoadFromSaveData(doc);
EXPECT_EQ(reloadedMissions->GetMissionTypeStates(), states);
}
@@ -74,9 +74,9 @@ TEST_F(CharacterSaveFieldsTests, MissionTypeStatesMissingInOldSave) {
Entity player(22, info);
auto* const missions = player.AddComponent<MissionComponent>(-1);
missions->LoadFromXml(doc);
missions->LoadFromSaveData(doc);
EXPECT_TRUE(missions->GetMissionTypeStates().empty());
missions->UpdateXml(doc);
missions->WriteSaveData(doc);
EXPECT_EQ(Print(doc), R"(<obj v="1"><mis><done/><cur/><ts/></mis></obj>)");
}
@@ -88,16 +88,16 @@ TEST_F(CharacterSaveFieldsTests, TooltipFlagsRoundTrip) {
Character character(1, nullptr);
Entity player(23, info);
auto* const characterComponent = player.AddComponent<CharacterComponent>(-1, &character, UNASSIGNED_SYSTEM_ADDRESS);
characterComponent->LoadFromXml(doc);
characterComponent->LoadFromSaveData(doc);
EXPECT_EQ(characterComponent->GetTooltipFlags(), 16777216u);
characterComponent->SetTooltipFlag(2, true);
characterComponent->UpdateXml(doc);
characterComponent->WriteSaveData(doc);
EXPECT_STREQ(doc.FirstChildElement("obj")->FirstChildElement("char")->Attribute("ttip"), "16777220");
Entity reloaded(24, info);
auto* const reloadedComponent = reloaded.AddComponent<CharacterComponent>(-1, &character, UNASSIGNED_SYSTEM_ADDRESS);
reloadedComponent->LoadFromXml(doc);
reloadedComponent->LoadFromSaveData(doc);
EXPECT_EQ(reloadedComponent->GetTooltipFlags(), 16777220u);
}
@@ -109,9 +109,9 @@ TEST_F(CharacterSaveFieldsTests, TooltipFlagsMissingInOldSave) {
Character character(1, nullptr);
Entity player(25, info);
auto* const characterComponent = player.AddComponent<CharacterComponent>(-1, &character, UNASSIGNED_SYSTEM_ADDRESS);
characterComponent->LoadFromXml(doc);
characterComponent->LoadFromSaveData(doc);
EXPECT_EQ(characterComponent->GetTooltipFlags(), 0u);
characterComponent->UpdateXml(doc);
characterComponent->WriteSaveData(doc);
EXPECT_STREQ(doc.FirstChildElement("obj")->FirstChildElement("char")->Attribute("ttip"), "0");
}
@@ -134,19 +134,19 @@ TEST_F(CharacterSaveFieldsTests, SkillCooldownsRoundTrip) {
Entity player(26, info);
auto* const skills = player.AddComponent<SkillComponent>(-1);
skills->LoadFromXml(doc);
skills->LoadFromSaveData(doc);
ASSERT_EQ(skills->GetCooldownGroups().size(), 2u);
EXPECT_FLOAT_EQ(skills->GetCooldownGroups().at(8), 26.6559f);
skills->Update(26.7f); // group 8 runs out
skills->Run(26.7f); // group 8 runs out
skills->StartCooldown(394);
skills->StartCooldown(395); // no group: not saved
skills->UpdateXml(doc);
skills->WriteSaveData(doc);
EXPECT_EQ(Print(doc), R"(<obj v="1"><skil sc="17:30;78:4.0363;"/></obj>)");
Entity reloaded(27, info);
auto* const reloadedSkills = reloaded.AddComponent<SkillComponent>(-1);
reloadedSkills->LoadFromXml(doc);
reloadedSkills->LoadFromSaveData(doc);
EXPECT_EQ(reloadedSkills->GetCooldownGroups().size(), 2u);
EXPECT_FLOAT_EQ(reloadedSkills->GetCooldownGroups().at(17), 30.0f);
@@ -160,9 +160,9 @@ TEST_F(CharacterSaveFieldsTests, SkillCooldownsMissingInOldSave) {
Entity player(28, info);
auto* const skills = player.AddComponent<SkillComponent>(-1);
skills->LoadFromXml(doc);
skills->LoadFromSaveData(doc);
EXPECT_TRUE(skills->GetCooldownGroups().empty());
skills->UpdateXml(doc);
skills->WriteSaveData(doc);
EXPECT_EQ(Print(doc), R"(<obj v="1"><skil/></obj>)");
}
@@ -177,6 +177,6 @@ TEST_F(CharacterSaveFieldsTests, PetsAsLiveWroteThem) {
auto* const inventory = player.AddComponent<InventoryComponent>(-1);
inventory->LoadXml(doc); // the pets load first; there is no <inv> here
EXPECT_EQ(inventory->GetDatabasePet(1152921510000000001LL).lot, 3254);
inventory->UpdateXml(doc);
inventory->WriteSaveData(doc);
EXPECT_EQ(Print(doc), R"(<obj v="1"><pet a="0"><p id="1152921510000000001" l="3254" m="2" n="Fluffy" t="0"/></pet></obj>)");
}

View File

@@ -49,7 +49,7 @@ protected:
tinyxml2::XMLDocument doc;
const std::string xml = std::string(R"(<obj v="1"><mis><done/><cur><m id="2040" state=")") + state + R"("/></cur></mis></obj>)";
ASSERT_EQ(doc.Parse(xml.c_str()), tinyxml2::XML_SUCCESS);
missions.LoadFromXml(doc);
missions.LoadFromSaveData(doc);
}
};

View File

@@ -577,7 +577,7 @@ TEST_F(DestroyableTest, DestroyableComponentDamageCooldownTest) {
ASSERT_TRUE(destroyableComponent->IsCooldownImmune());
// Test that the Update() function correctly decrements the damage cooldown timer
destroyableComponent->Update(0.5f);
destroyableComponent->Run(0.5f);
EXPECT_FLOAT_EQ(destroyableComponent->GetDamageCooldownTimer(), 0.5f);
ASSERT_TRUE(destroyableComponent->IsCooldownImmune());

View File

@@ -61,7 +61,7 @@ protected:
// The "eq" attribute the save wrote for an item
bool SavedEquipped(const LWOOBJID id) {
inventory->UpdateXml(doc);
inventory->WriteSaveData(doc);
for (auto* bag = doc.FirstChildElement("obj")->FirstChildElement("inv")->FirstChildElement("items")->FirstChildElement("in"); bag; bag = bag->NextSiblingElement("in")) {
for (auto* element = bag->FirstChildElement("i"); element; element = element->NextSiblingElement("i")) {
if (element->Int64Attribute("id") == id) return element->BoolAttribute("eq");

View File

@@ -60,7 +60,7 @@ TEST_F(ItemRemovalTests, ARemovedItemIsUsableUntilTheNextUpdate) {
EXPECT_EQ(item->GetId(), id);
EXPECT_EQ(item->GetCount(), 0u);
inventoryComponent->Update(0.0f);
inventoryComponent->Run(0.0f);
EXPECT_FALSE(items->HasRetiredItems());
}
@@ -75,7 +75,7 @@ TEST_F(ItemRemovalTests, RemovingSeveralItemsInALoop) {
for (auto* const item : held | std::views::values) EXPECT_EQ(item->GetCount(), 0u);
EXPECT_TRUE(items->GetItems().empty());
inventoryComponent->Update(0.0f);
inventoryComponent->Run(0.0f);
EXPECT_FALSE(items->HasRetiredItems());
}

View File

@@ -182,14 +182,14 @@ TEST_F(MovingPlatformComponentTest, MoverRunsLikeLive) {
EXPECT_EQ(mover->mCurrentWaypointIndex, 0u);
EXPECT_EQ(mover->mNextWaypointIndex, 1u);
platform->Update(1.9f);
platform->Run(1.9f);
EXPECT_EQ(mover->mState, 25u);
EXPECT_FLOAT_EQ(mover->mIdleTimeElapsed, 1.9f);
platform->Update(0.2f);
platform->Run(0.2f);
EXPECT_EQ(mover->mState, 2u);
// 5 units a second over 10
platform->Update(1.0f);
platform->Run(1.0f);
EXPECT_NEAR(mover->mPercentBetweenPoints, 0.5f, 0.001f);
EXPECT_NEAR(mover->mPosition.x, 5.0f, 0.01f);
GameMessages::PlatformResync resync;
@@ -199,7 +199,7 @@ TEST_F(MovingPlatformComponentTest, MoverRunsLikeLive) {
EXPECT_EQ(resync.iNextIndex, 1);
EXPECT_NEAR(resync.fPercentBetweenPoints, 0.5f, 0.001f);
platform->Update(1.1f);
platform->Run(1.1f);
EXPECT_EQ(mover->mState, 25u);
EXPECT_EQ(mover->mCurrentWaypointIndex, 1u);
EXPECT_EQ(mover->mNextWaypointIndex, 0u);
@@ -210,23 +210,23 @@ TEST_F(MovingPlatformComponentTest, MoverRunsLikeLive) {
platform->GotoWaypoint(0, true);
EXPECT_EQ(mover->mState, 2u);
EXPECT_EQ(mover->mDesiredWaypointIndex, 0);
platform->Update(2.5f);
platform->Run(2.5f);
EXPECT_EQ(mover->mState, 25u);
EXPECT_EQ(mover->mCurrentWaypointIndex, 0u);
EXPECT_EQ(mover->mDesiredWaypointIndex, -1);
platform->Update(10.0f);
platform->Run(10.0f);
EXPECT_EQ(mover->mState, 25u);
// Started at a waypoint it first waits there (issue 964), then stopped mid way and started again
platform->StartPathing();
EXPECT_EQ(mover->mState, 25u);
platform->Update(2.1f);
platform->Run(2.1f);
EXPECT_EQ(mover->mState, 2u);
platform->Update(1.0f);
platform->Run(1.0f);
platform->StopPathing();
EXPECT_EQ(mover->mState & 1u, 1u);
const auto percent = mover->mPercentBetweenPoints;
platform->Update(1.0f);
platform->Run(1.0f);
EXPECT_EQ(mover->mPercentBetweenPoints, percent);
platform->StartPathing();
EXPECT_EQ(mover->mState, 2u);

View File

@@ -62,7 +62,7 @@ protected:
std::vector<CapturedPacket> Build() {
quickBuild->OnUse(player.get());
return Capture([&] {
for (int i = 0; i < 3 && quickBuild->GetState() != eQuickBuildState::COMPLETED; i++) quickBuild->Update(0.1f);
for (int i = 0; i < 3 && quickBuild->GetState() != eQuickBuildState::COMPLETED; i++) quickBuild->Run(0.1f);
});
}

View File

@@ -33,7 +33,7 @@ protected:
character->_doQuickXMLDataParse();
character->LoadXmlRespawnCheckpoints();
entity->AddComponent<CharacterComponent>(-1, character.get(), UNASSIGNED_SYSTEM_ADDRESS)->LoadFromXml(entity->GetCharacter()->GetXMLDoc());
entity->AddComponent<CharacterComponent>(-1, character.get(), UNASSIGNED_SYSTEM_ADDRESS)->LoadFromSaveData(entity->GetCharacter()->GetXMLDoc());
}
void TearDown() override {
@@ -63,7 +63,7 @@ TEST_F(SavingTest, CharacterComponentTest) {
const auto prevTotalTime = characterComponent->GetTotalTimePlayed();
character->_doQuickXMLDataParse();
entity->AddComponent<CharacterComponent>(-1, character.get(), UNASSIGNED_SYSTEM_ADDRESS);
characterComponent->LoadFromXml(entity->GetCharacter()->GetXMLDoc());
characterComponent->LoadFromSaveData(entity->GetCharacter()->GetXMLDoc());
// Check that the buff component is the same as before which means resaving data and loading it back in didn't change anything
ASSERT_EQ("32114;69;343;13;163;2;181;2;388;252;146;24451;25;9022;41898;42186;42524;4404;0;0;0;0;0;0;0;0;0;", characterComponent->StatisticsToString());