mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-11-15 16:27:14 +00:00
refactor(component): the destroyable loads itself in the client's phases
The DestructibleComponent row moves from Entity::Initialize into LoadDataFromTemplate and the level keys into LoadConfigData; a character's save loads after both. Like the client (LWODestroyableComponent::LoadConfigData reads them for every destroyable), the destroyables added for models and quickbuilds now also read set_faction, is_smashable and the level loot matrix; their live defaults become their template. Nothing changes for objects without those keys. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
105
dGame/Entity.cpp
105
dGame/Entity.cpp
@@ -298,13 +298,7 @@ void Entity::Initialize() {
|
||||
|
||||
AddComponent<RenderComponent>(-1);
|
||||
|
||||
auto* destroyableComponent = AddComponent<DestroyableComponent>(-1);
|
||||
destroyableComponent->SetHealth(1);
|
||||
destroyableComponent->SetMaxHealth(1.0f);
|
||||
// Live: armor 1 (max 0), as for every componentID -1 destroyable (LOT 14 and 8069 on a Raven Bluff property)
|
||||
destroyableComponent->SetArmor(1);
|
||||
destroyableComponent->SetFaction(-1, true);
|
||||
destroyableComponent->SetIsSmashable(true);
|
||||
AddComponent<DestroyableComponent>(-1, true);
|
||||
// We have all our components.
|
||||
return;
|
||||
}
|
||||
@@ -324,8 +318,6 @@ void Entity::Initialize() {
|
||||
const auto petComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::PET);
|
||||
const auto collectibleComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::COLLECTIBLE);
|
||||
const auto combatAiID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::BASE_COMBAT_AI);
|
||||
// A race car's destroyable has 60 imagination
|
||||
const bool isRacingCar = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::RACING_STATS) > 0;
|
||||
// If an entity is marked a phantom, simple physics is made into phantom phyics.
|
||||
const bool markedAsPhantom = GetVar<bool>(u"markedAsPhantom");
|
||||
|
||||
@@ -429,85 +421,9 @@ void Entity::Initialize() {
|
||||
bool isSmashable = GetVarAs<int32_t>(u"is_smashable") != 0;
|
||||
if (destroyableComponentID > 0 || collectibleComponentID > 0 || isSmashable) {
|
||||
const int32_t componentID = destroyableComponentID > 0 ? destroyableComponentID : -1;
|
||||
// The row, the level config and a character's save, in the client's order
|
||||
DestroyableComponent* comp = AddComponent<DestroyableComponent>(componentID);
|
||||
auto* const destCompTable = CDClientManager::GetTable<CDDestructibleComponentTable>();
|
||||
std::vector<CDDestructibleComponent> destCompData = destCompTable->Query([componentID](const CDDestructibleComponent& entry) { return (entry.id == componentID); });
|
||||
|
||||
if (m_Character) {
|
||||
comp->LoadFromSaveData(m_Character->GetXMLDoc());
|
||||
} else {
|
||||
// extraInfo overrides. Client ORs the database smashable and the luz smashable.
|
||||
comp->SetIsSmashable(comp->GetIsSmashable() || isSmashable);
|
||||
|
||||
if (componentID > 0) {
|
||||
if (!destCompData.empty()) {
|
||||
if (isRacingCar) {
|
||||
destCompData[0].imagination = 60;
|
||||
}
|
||||
|
||||
comp->SetHealth(destCompData[0].life);
|
||||
comp->SetImagination(destCompData[0].imagination);
|
||||
comp->SetArmor(destCompData[0].armor);
|
||||
|
||||
comp->SetMaxHealth(destCompData[0].life);
|
||||
comp->SetMaxImagination(destCompData[0].imagination);
|
||||
comp->SetMaxArmor(destCompData[0].armor);
|
||||
comp->SetDeathBehavior(destCompData[0].death_behavior);
|
||||
|
||||
comp->SetIsSmashable(comp->GetIsSmashable() || destCompData[0].isSmashable);
|
||||
comp->SetIsNPC(destCompData[0].isnpc);
|
||||
comp->SetAttackPriority(destCompData[0].attack_priority);
|
||||
|
||||
comp->SetLootMatrixID(destCompData[0].LootMatrixIndex);
|
||||
comp->SetCurrencyIndex(destCompData[0].CurrencyIndex);
|
||||
Loot::CacheMatrix(destCompData[0].LootMatrixIndex);
|
||||
|
||||
// Now get currency information
|
||||
const uint32_t npcMinLevel = destCompData[0].level;
|
||||
const uint32_t currencyIndex = destCompData[0].CurrencyIndex;
|
||||
|
||||
CDCurrencyTableTable* const currencyTable = CDClientManager::GetTable<CDCurrencyTableTable>();
|
||||
const std::vector<CDCurrencyTable> currencyValues = currencyTable->Query([currencyIndex, npcMinLevel](const CDCurrencyTable& entry) { return (entry.currencyIndex == currencyIndex && entry.npcminlevel == npcMinLevel); });
|
||||
|
||||
if (!currencyValues.empty()) {
|
||||
// Set the coins
|
||||
comp->SetMinCoins(currencyValues[0].minvalue);
|
||||
comp->SetMaxCoins(currencyValues[0].maxvalue);
|
||||
}
|
||||
}
|
||||
} else if (isSmashable) {
|
||||
// A smashable object without a DestructibleComponent row
|
||||
comp->SetHealth(1);
|
||||
comp->SetArmor(0);
|
||||
|
||||
comp->SetMaxHealth(1);
|
||||
comp->SetMaxArmor(0);
|
||||
|
||||
comp->SetIsSmashable(true);
|
||||
comp->AddFaction(-1);
|
||||
comp->AddFaction(6); //Smashables
|
||||
|
||||
// A race car has 60 imagination, other entities defaults to 0.
|
||||
comp->SetImagination(isRacingCar ? 60 : 0);
|
||||
comp->SetMaxImagination(isRacingCar ? 60 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (!destCompData.empty()) comp->AddTemplateFactions(destCompData[0].factionList);
|
||||
|
||||
// Level files can replace the factions with set_faction, unless override_faction is 0
|
||||
const auto levelFactions = DestroyableComponent::GetLevelFactions(*this);
|
||||
if (levelFactions && !levelFactions->empty()) {
|
||||
comp->SetFaction(levelFactions->front(), true);
|
||||
for (const auto faction : *levelFactions | std::views::drop(1)) comp->AddFaction(faction, true);
|
||||
}
|
||||
|
||||
// Level files can give a smashable its own loot matrix in place of the template's
|
||||
const auto levelLootMatrix = DestroyableComponent::GetLevelLootMatrix(*this);
|
||||
if (!m_Character && levelLootMatrix) {
|
||||
comp->SetLootMatrixID(levelLootMatrix.value());
|
||||
Loot::CacheMatrix(levelLootMatrix.value());
|
||||
}
|
||||
if (m_Character) comp->LoadFromSaveData(m_Character->GetXMLDoc());
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -709,12 +625,7 @@ void Entity::Initialize() {
|
||||
// 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)) {
|
||||
auto* const destroyable = AddComponent<DestroyableComponent>(-1);
|
||||
destroyable->SetMaxHealth(1.0f);
|
||||
destroyable->SetHealth(1);
|
||||
destroyable->SetArmor(1);
|
||||
destroyable->SetFaction(-1, true);
|
||||
destroyable->SetIsSmashable(true);
|
||||
AddComponent<DestroyableComponent>(-1, true);
|
||||
}
|
||||
|
||||
CDRebuildComponentTable* const rebCompTable = CDClientManager::GetTable<CDRebuildComponentTable>();
|
||||
@@ -861,13 +772,7 @@ void Entity::Initialize() {
|
||||
if (modelID > -1 && !GetComponent<PetComponent>()) {
|
||||
AddComponent<ModelComponent>(modelID)->LoadBehaviors();
|
||||
if (!HasComponent(eReplicaComponentType::DESTROYABLE)) {
|
||||
auto* const destroyableComponent = AddComponent<DestroyableComponent>(-1);
|
||||
destroyableComponent->SetHealth(1);
|
||||
destroyableComponent->SetMaxHealth(1.0f);
|
||||
// Live: armor 1 (max 0), as for every componentID -1 destroyable (LOT 8069 on a Raven Bluff property)
|
||||
destroyableComponent->SetArmor(1);
|
||||
destroyableComponent->SetFaction(-1, true);
|
||||
destroyableComponent->SetIsSmashable(true);
|
||||
AddComponent<DestroyableComponent>(-1, true);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "User.h"
|
||||
#include "CDClientManager.h"
|
||||
#include "CDDestructibleComponentTable.h"
|
||||
#include "CDCurrencyTableTable.h"
|
||||
#include "EntityManager.h"
|
||||
#include "QuickBuildComponent.h"
|
||||
#include "CppScripts.h"
|
||||
@@ -53,7 +54,7 @@
|
||||
Implementation<bool, const Entity*> DestroyableComponent::IsEnemyImplentation;
|
||||
Implementation<bool, const Entity*> DestroyableComponent::IsFriendImplentation;
|
||||
|
||||
DestroyableComponent::DestroyableComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) {
|
||||
DestroyableComponent::DestroyableComponent(Entity* parent, const int32_t componentID, const bool smashableDefaults) : Component(parent, componentID), m_SmashableDefaults{ smashableDefaults } {
|
||||
m_iArmor = 0;
|
||||
m_fMaxArmor = 0.0f;
|
||||
m_iImagination = 0;
|
||||
@@ -96,6 +97,110 @@ DestroyableComponent::DestroyableComponent(Entity* parent, const int32_t compone
|
||||
RegisterMsg(&DestroyableComponent::OnIsDead);
|
||||
}
|
||||
|
||||
// The DestructibleComponent row (a character's stats come from its save after this): stats, loot and coins, then the
|
||||
// row's factionList. A destroyable the loader adds to a model or quickbuild has no row and live's defaults instead.
|
||||
void DestroyableComponent::LoadDataFromTemplate() {
|
||||
if (m_SmashableDefaults) {
|
||||
// Live: health 1 / 1.0, armor 1 / 0, faction -1, smashable (AG LOT 7804, LOTs 14 and 8069 on a property)
|
||||
SetMaxHealth(1.0f);
|
||||
SetHealth(1);
|
||||
SetArmor(1);
|
||||
SetFaction(-1, true);
|
||||
SetIsSmashable(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_ComponentID <= 0) return;
|
||||
|
||||
auto* const destCompTable = CDClientManager::GetTable<CDDestructibleComponentTable>();
|
||||
const auto componentID = m_ComponentID;
|
||||
std::vector<CDDestructibleComponent> destCompData = destCompTable->Query([componentID](const CDDestructibleComponent& entry) { return (entry.id == componentID); });
|
||||
if (destCompData.empty()) return;
|
||||
|
||||
if (!m_Parent->GetCharacter()) {
|
||||
// A race car's destroyable has 60 imagination
|
||||
const bool isRacingCar = CDClientManager::GetTable<CDComponentsRegistryTable>()->GetByIDAndType(m_Parent->GetLOT(), eReplicaComponentType::RACING_STATS) > 0;
|
||||
if (isRacingCar) {
|
||||
destCompData[0].imagination = 60;
|
||||
}
|
||||
|
||||
SetHealth(destCompData[0].life);
|
||||
SetImagination(destCompData[0].imagination);
|
||||
SetArmor(destCompData[0].armor);
|
||||
|
||||
SetMaxHealth(destCompData[0].life);
|
||||
SetMaxImagination(destCompData[0].imagination);
|
||||
SetMaxArmor(destCompData[0].armor);
|
||||
SetDeathBehavior(destCompData[0].death_behavior);
|
||||
|
||||
SetIsSmashable(GetIsSmashable() || destCompData[0].isSmashable);
|
||||
SetIsNPC(destCompData[0].isnpc);
|
||||
SetAttackPriority(destCompData[0].attack_priority);
|
||||
|
||||
SetLootMatrixID(destCompData[0].LootMatrixIndex);
|
||||
SetCurrencyIndex(destCompData[0].CurrencyIndex);
|
||||
Loot::CacheMatrix(destCompData[0].LootMatrixIndex);
|
||||
|
||||
// Now get currency information
|
||||
const uint32_t npcMinLevel = destCompData[0].level;
|
||||
const uint32_t currencyIndex = destCompData[0].CurrencyIndex;
|
||||
|
||||
CDCurrencyTableTable* const currencyTable = CDClientManager::GetTable<CDCurrencyTableTable>();
|
||||
const std::vector<CDCurrencyTable> currencyValues = currencyTable->Query([currencyIndex, npcMinLevel](const CDCurrencyTable& entry) { return (entry.currencyIndex == currencyIndex && entry.npcminlevel == npcMinLevel); });
|
||||
|
||||
if (!currencyValues.empty()) {
|
||||
// Set the coins
|
||||
SetMinCoins(currencyValues[0].minvalue);
|
||||
SetMaxCoins(currencyValues[0].maxvalue);
|
||||
}
|
||||
}
|
||||
|
||||
AddTemplateFactions(destCompData[0].factionList);
|
||||
}
|
||||
|
||||
// The level config (LWODestroyableComponent::LoadConfigData 0x00c44cb0 reads it for every destroyable): is_smashable,
|
||||
// set_faction / override_faction and the smashable loot matrix
|
||||
void DestroyableComponent::LoadConfigData() {
|
||||
const bool isSmashable = m_Parent->GetVarAs<int32_t>(u"is_smashable") != 0;
|
||||
const bool isCharacter = m_Parent->GetCharacter() != nullptr;
|
||||
if (!isCharacter) {
|
||||
// The client ORs the database smashable and the level's
|
||||
SetIsSmashable(GetIsSmashable() || isSmashable);
|
||||
|
||||
if (!m_SmashableDefaults && m_ComponentID <= 0 && isSmashable) {
|
||||
// A smashable object without a DestructibleComponent row
|
||||
SetHealth(1);
|
||||
SetArmor(0);
|
||||
|
||||
SetMaxHealth(1);
|
||||
SetMaxArmor(0);
|
||||
|
||||
SetIsSmashable(true);
|
||||
AddFaction(-1);
|
||||
AddFaction(6); //Smashables
|
||||
|
||||
// A race car has 60 imagination, other entities defaults to 0.
|
||||
const bool isRacingCar = CDClientManager::GetTable<CDComponentsRegistryTable>()->GetByIDAndType(m_Parent->GetLOT(), eReplicaComponentType::RACING_STATS) > 0;
|
||||
SetImagination(isRacingCar ? 60 : 0);
|
||||
SetMaxImagination(isRacingCar ? 60 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Level files can replace the factions with set_faction, unless override_faction is 0
|
||||
const auto levelFactions = GetLevelFactions(*m_Parent);
|
||||
if (levelFactions && !levelFactions->empty()) {
|
||||
SetFaction(levelFactions->front(), true);
|
||||
for (const auto faction : *levelFactions | std::views::drop(1)) AddFaction(faction, true);
|
||||
}
|
||||
|
||||
// Level files can give a smashable its own loot matrix in place of the template's
|
||||
const auto levelLootMatrix = GetLevelLootMatrix(*m_Parent);
|
||||
if (!isCharacter && levelLootMatrix) {
|
||||
SetLootMatrixID(levelLootMatrix.value());
|
||||
Loot::CacheMatrix(levelLootMatrix.value());
|
||||
}
|
||||
}
|
||||
|
||||
DestroyableComponent::~DestroyableComponent() {
|
||||
}
|
||||
|
||||
|
||||
@@ -44,9 +44,13 @@ class DestroyableComponent final : public Component {
|
||||
public:
|
||||
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::DESTROYABLE;
|
||||
|
||||
DestroyableComponent(Entity* parentEntity, const int32_t componentID);
|
||||
// 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;
|
||||
|
||||
void LoadDataFromTemplate() override;
|
||||
void LoadConfigData() override;
|
||||
|
||||
void Run(float deltaTime) override;
|
||||
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;
|
||||
void LoadFromSaveData(const tinyxml2::XMLDocument& doc) override;
|
||||
@@ -700,6 +704,9 @@ private:
|
||||
* Damage immunity cooldown timer. Set to a value that then counts down to create a damage cooldown for players
|
||||
*/
|
||||
float m_DamageCooldownTimer;
|
||||
|
||||
// Made by the loader for a model or quickbuild: live's smashable defaults in place of a row
|
||||
bool m_SmashableDefaults{ false };
|
||||
};
|
||||
|
||||
#endif // DESTROYABLECOMPONENT_H
|
||||
|
||||
Reference in New Issue
Block a user