DarkflameServer/dGame/dComponents/DestroyableComponent.cpp
David Markowitz 6ba9eea993
Added check to prevent accidental sentinel passive proc (#396)
* Added check to prevent accidental sentinel passive proc

Added a boolean to check if the player is at zero armor already and if so, do not trigger the passive ability.

* Renamed variable and condensed armor check
2022-02-05 12:59:07 +01:00

968 lines
25 KiB
C++

#include "DestroyableComponent.h"
#include <BitStream.h>
#include "dLogger.h"
#include "Game.h"
#include "AMFFormat.h"
#include "AMFFormat_BitStream.h"
#include "GameMessages.h"
#include "User.h"
#include "CDClientManager.h"
#include "CDDestructibleComponentTable.h"
#include "EntityManager.h"
#include "RebuildComponent.h"
#include "CppScripts.h"
#include "Loot.h"
#include "Character.h"
#include "Spawner.h"
#include "BaseCombatAIComponent.h"
#include "TeamManager.h"
#include "BuffComponent.h"
#include "SkillComponent.h"
#include "Item.h"
#include <sstream>
#include <algorithm>
#include "MissionComponent.h"
#include "CharacterComponent.h"
DestroyableComponent::DestroyableComponent(Entity* parent) : Component(parent) {
m_iArmor = 0;
m_fMaxArmor = 0.0f;
m_iImagination = 0;
m_fMaxImagination = 0.0f;
m_FactionIDs = std::vector<int32_t>();
m_EnemyFactionIDs = std::vector<int32_t>();
m_IsSmashable = false;
m_IsDead = false;
m_IsSmashed = false;
m_IsGMImmune = false;
m_IsShielded = false;
m_DamageToAbsorb = 0;
m_HasBricks = false;
m_DirtyThreatList = false;
m_HasThreats = false;
m_ExplodeFactor = 1.0f;
m_iHealth = 0;
m_fMaxHealth = 0;
m_AttacksToBlock = 0;
m_LootMatrixID = 0;
m_MinCoins = 0;
m_MaxCoins = 0;
m_ImmuneStacks = 0;
m_DamageReduction = 0;
}
DestroyableComponent::~DestroyableComponent() {
}
void DestroyableComponent::Reinitialize(LOT templateID) {
CDComponentsRegistryTable* compRegistryTable = CDClientManager::Instance()->GetTable<CDComponentsRegistryTable>("ComponentsRegistry");
int32_t buffComponentID = compRegistryTable->GetByIDAndType(templateID, COMPONENT_TYPE_BUFF);
int32_t collectibleComponentID = compRegistryTable->GetByIDAndType(templateID, COMPONENT_TYPE_COLLECTIBLE);
int32_t rebuildComponentID = compRegistryTable->GetByIDAndType(templateID, COMPONENT_TYPE_REBUILD);
int32_t componentID = 0;
if (collectibleComponentID > 0) componentID = collectibleComponentID;
if (rebuildComponentID > 0) componentID = rebuildComponentID;
if (buffComponentID > 0) componentID = buffComponentID;
CDDestructibleComponentTable* destCompTable = CDClientManager::Instance()->GetTable<CDDestructibleComponentTable>("DestructibleComponent");
std::vector<CDDestructibleComponent> destCompData = destCompTable->Query([=](CDDestructibleComponent entry) { return (entry.id == componentID); });
if (componentID > 0) {
std::vector<CDDestructibleComponent> destCompData = destCompTable->Query([=](CDDestructibleComponent entry) { return (entry.id == componentID); });
if (destCompData.size() > 0) {
SetHealth(destCompData[0].life);
SetImagination(destCompData[0].imagination);
SetArmor(destCompData[0].armor);
SetMaxHealth(destCompData[0].life);
SetMaxImagination(destCompData[0].imagination);
SetMaxArmor(destCompData[0].armor);
SetIsSmashable(destCompData[0].isSmashable);
}
}
else {
SetHealth(1);
SetImagination(0);
SetArmor(0);
SetMaxHealth(1);
SetMaxImagination(0);
SetMaxArmor(0);
SetIsSmashable(true);
}
}
void DestroyableComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, uint32_t& flags) {
if (bIsInitialUpdate) {
outBitStream->Write0(); //Contains info about immunities this object has, but it's left out for now.
}
outBitStream->Write(m_DirtyHealth || bIsInitialUpdate);
if (m_DirtyHealth || bIsInitialUpdate) {
outBitStream->Write(m_iHealth);
outBitStream->Write(m_fMaxHealth);
outBitStream->Write(m_iArmor);
outBitStream->Write(m_fMaxArmor);
outBitStream->Write(m_iImagination);
outBitStream->Write(m_fMaxImagination);
outBitStream->Write(m_DamageToAbsorb);
outBitStream->Write(IsImmune());
outBitStream->Write(m_IsGMImmune);
outBitStream->Write(m_IsShielded);
outBitStream->Write(m_fMaxHealth);
outBitStream->Write(m_fMaxArmor);
outBitStream->Write(m_fMaxImagination);
outBitStream->Write(uint32_t(m_FactionIDs.size()));
for (size_t i = 0; i < m_FactionIDs.size(); ++i) {
outBitStream->Write(m_FactionIDs[i]);
}
outBitStream->Write(m_IsSmashable);
if (bIsInitialUpdate) {
outBitStream->Write(m_IsDead);
outBitStream->Write(m_IsSmashed);
if (m_IsSmashable) {
outBitStream->Write(m_HasBricks);
if (m_ExplodeFactor != 1.0f) {
outBitStream->Write1();
outBitStream->Write(m_ExplodeFactor);
} else {
outBitStream->Write0();
}
}
}
m_DirtyHealth = false;
}
if (m_DirtyThreatList || bIsInitialUpdate) {
outBitStream->Write1();
outBitStream->Write(m_HasThreats);
m_DirtyThreatList = false;
} else {
outBitStream->Write0();
}
}
void DestroyableComponent::LoadFromXML(tinyxml2::XMLDocument* doc) {
tinyxml2::XMLElement* dest = doc->FirstChildElement("obj")->FirstChildElement("dest");
if (!dest) {
Game::logger->Log("DestroyableComponent", "Failed to find dest tag!\n");
return;
}
auto* buffComponent = m_Parent->GetComponent<BuffComponent>();
if (buffComponent != nullptr) {
buffComponent->LoadFromXML(doc);
}
dest->QueryAttribute("hc", &m_iHealth);
dest->QueryAttribute("hm", &m_fMaxHealth);
dest->QueryAttribute("im", &m_fMaxImagination);
dest->QueryAttribute("ic", &m_iImagination);
dest->QueryAttribute("ac", &m_iArmor);
dest->QueryAttribute("am", &m_fMaxArmor);
m_DirtyHealth = true;
}
void DestroyableComponent::UpdateXml(tinyxml2::XMLDocument* doc) {
tinyxml2::XMLElement* dest = doc->FirstChildElement("obj")->FirstChildElement("dest");
if (!dest) {
Game::logger->Log("DestroyableComponent", "Failed to find dest tag!\n");
return;
}
auto* buffComponent = m_Parent->GetComponent<BuffComponent>();
if (buffComponent != nullptr) {
buffComponent->UpdateXml(doc);
}
dest->SetAttribute("hc", m_iHealth);
dest->SetAttribute("hm", m_fMaxHealth);
dest->SetAttribute("im", m_fMaxImagination);
dest->SetAttribute("ic", m_iImagination);
dest->SetAttribute("ac", m_iArmor);
dest->SetAttribute("am", m_fMaxArmor);
}
void DestroyableComponent::SetHealth(int32_t value) {
m_DirtyHealth = true;
auto* characterComponent = m_Parent->GetComponent<CharacterComponent>();
if (characterComponent != nullptr) {
characterComponent->TrackHealthDelta(value - m_iHealth);
}
m_iHealth = value;
}
void DestroyableComponent::SetMaxHealth(float value, bool playAnim) {
m_DirtyHealth = true;
m_fMaxHealth = value;
if (m_iHealth > m_fMaxHealth) {
m_iHealth = m_fMaxHealth;
}
if (playAnim) {
// Now update the player bar
if (!m_Parent->GetParentUser()) return;
AMFStringValue* amount = new AMFStringValue();
amount->SetStringValue(std::to_string(value));
AMFStringValue* type = new AMFStringValue();
type->SetStringValue("health");
AMFArrayValue args;
args.InsertValue("amount", amount);
args.InsertValue("type", type);
GameMessages::SendUIMessageServerToSingleClient(m_Parent, m_Parent->GetParentUser()->GetSystemAddress(), "MaxPlayerBarUpdate", &args);
delete amount;
delete type;
}
else {
EntityManager::Instance()->SerializeEntity(m_Parent);
}
}
void DestroyableComponent::SetArmor(int32_t value) {
m_DirtyHealth = true;
// If Destroyable Component already has zero armor do not trigger the passive ability again.
bool hadArmor = m_iArmor > 0;
auto* characterComponent = m_Parent->GetComponent<CharacterComponent>();
if (characterComponent != nullptr) {
characterComponent->TrackArmorDelta(value - m_iArmor);
}
m_iArmor = value;
auto* inventroyComponent = m_Parent->GetComponent<InventoryComponent>();
if (m_iArmor == 0 && inventroyComponent != nullptr && hadArmor) {
inventroyComponent->TriggerPassiveAbility(PassiveAbilityTrigger::SentinelArmor);
}
}
void DestroyableComponent::SetMaxArmor(float value, bool playAnim) {
m_DirtyHealth = true;
m_fMaxArmor = value;
if (m_iArmor > m_fMaxArmor) {
m_iArmor = m_fMaxArmor;
}
if (playAnim) {
// Now update the player bar
if (!m_Parent->GetParentUser()) return;
AMFStringValue* amount = new AMFStringValue();
amount->SetStringValue(std::to_string(value));
AMFStringValue* type = new AMFStringValue();
type->SetStringValue("armor");
AMFArrayValue args;
args.InsertValue("amount", amount);
args.InsertValue("type", type);
GameMessages::SendUIMessageServerToSingleClient(m_Parent, m_Parent->GetParentUser()->GetSystemAddress(), "MaxPlayerBarUpdate", &args);
delete amount;
delete type;
}
else {
EntityManager::Instance()->SerializeEntity(m_Parent);
}
}
void DestroyableComponent::SetImagination(int32_t value) {
m_DirtyHealth = true;
auto* characterComponent = m_Parent->GetComponent<CharacterComponent>();
if (characterComponent != nullptr) {
characterComponent->TrackImaginationDelta(value - m_iImagination);
}
m_iImagination = value;
auto* inventroyComponent = m_Parent->GetComponent<InventoryComponent>();
if (m_iImagination == 0 && inventroyComponent != nullptr) {
inventroyComponent->TriggerPassiveAbility(PassiveAbilityTrigger::AssemblyImagination);
}
}
void DestroyableComponent::SetMaxImagination(float value, bool playAnim) {
m_DirtyHealth = true;
m_fMaxImagination = value;
if (m_iImagination > m_fMaxImagination) {
m_iImagination = m_fMaxImagination;
}
if (playAnim) {
// Now update the player bar
if (!m_Parent->GetParentUser()) return;
AMFStringValue* amount = new AMFStringValue();
amount->SetStringValue(std::to_string(value));
AMFStringValue* type = new AMFStringValue();
type->SetStringValue("imagination");
AMFArrayValue args;
args.InsertValue("amount", amount);
args.InsertValue("type", type);
GameMessages::SendUIMessageServerToSingleClient(m_Parent, m_Parent->GetParentUser()->GetSystemAddress(), "MaxPlayerBarUpdate", &args);
delete amount;
delete type;
}
else {
EntityManager::Instance()->SerializeEntity(m_Parent);
}
}
void DestroyableComponent::SetDamageToAbsorb(int32_t value)
{
m_DirtyHealth = true;
m_DamageToAbsorb = value;
}
void DestroyableComponent::SetDamageReduction(int32_t value)
{
m_DirtyHealth = true;
m_DamageReduction = value;
}
void DestroyableComponent::SetIsImmune(bool value)
{
m_DirtyHealth = true;
m_ImmuneStacks = value ? 1 : 0;
}
void DestroyableComponent::SetIsGMImmune(bool value)
{
m_DirtyHealth = true;
m_IsGMImmune = value;
}
void DestroyableComponent::SetIsShielded(bool value)
{
m_DirtyHealth = true;
m_IsShielded = value;
}
void DestroyableComponent::AddFaction(const int32_t factionID, const bool ignoreChecks) {
// Ignore factionID -1
if (factionID == -1 && !ignoreChecks) {
return;
}
m_FactionIDs.push_back(factionID);
m_DirtyHealth = true;
std::stringstream query;
query << "SELECT enemyList FROM Factions WHERE faction = " << std::to_string(factionID);
auto result = CDClientDatabase::ExecuteQuery(query.str());
if (result.eof()) return;
if (result.fieldIsNull(0)) return;
const auto* list_string = result.getStringField(0);
std::stringstream ss(list_string);
std::string token;
while (std::getline(ss, token, ',')) {
if (token.empty()) continue;
auto id = std::stoi(token);
auto exclude = std::find(m_FactionIDs.begin(), m_FactionIDs.end(), id) != m_FactionIDs.end();
if (!exclude)
{
exclude = std::find(m_EnemyFactionIDs.begin(), m_EnemyFactionIDs.end(), id) != m_EnemyFactionIDs.end();
}
if (exclude)
{
continue;
}
AddEnemyFaction(id);
}
result.finalize();
}
bool DestroyableComponent::IsEnemy (const Entity* other) const {
const auto* otherDestroyableComponent = other->GetComponent<DestroyableComponent>();
if (otherDestroyableComponent != nullptr) {
for (const auto enemyFaction : m_EnemyFactionIDs) {
for (const auto otherFaction : otherDestroyableComponent->GetFactionIDs()) {
if (enemyFaction == otherFaction)
return true;
}
}
}
return false;
}
bool DestroyableComponent::IsFriend (const Entity* other) const {
const auto* otherDestroyableComponent = other->GetComponent<DestroyableComponent>();
if (otherDestroyableComponent != nullptr) {
for (const auto enemyFaction : m_EnemyFactionIDs) {
for (const auto otherFaction : otherDestroyableComponent->GetFactionIDs()) {
if (enemyFaction == otherFaction)
return false;
}
}
return true;
}
return false;
}
void DestroyableComponent::AddEnemyFaction(int32_t factionID)
{
m_EnemyFactionIDs.push_back(factionID);
}
void DestroyableComponent::SetIsSmashable(bool value) {
m_DirtyHealth = true;
m_IsSmashable = value;
//m_HasBricks = value;
}
void DestroyableComponent::SetAttacksToBlock(const uint32_t value)
{
m_AttacksToBlock = value;
}
bool DestroyableComponent::IsImmune() const
{
return m_ImmuneStacks > 0 || m_IsGMImmune;
}
bool DestroyableComponent::IsKnockbackImmune() const
{
auto* characterComponent = m_Parent->GetComponent<CharacterComponent>();
auto* inventoryComponent = m_Parent->GetComponent<InventoryComponent>();
if (characterComponent != nullptr && inventoryComponent != nullptr && characterComponent->GetCurrentActivity() == eGameActivities::ACTIVITY_QUICKBUILDING) {
const auto hasPassive = inventoryComponent->HasAnyPassive({
ItemSetPassiveAbilityID::EngineerRank2, ItemSetPassiveAbilityID::EngineerRank3,
ItemSetPassiveAbilityID::SummonerRank2, ItemSetPassiveAbilityID::SummonerRank3,
ItemSetPassiveAbilityID::InventorRank2, ItemSetPassiveAbilityID::InventorRank3,
}, 5);
if (hasPassive) {
return true;
}
}
return IsImmune() || m_IsShielded || m_AttacksToBlock > 0;
}
bool DestroyableComponent::HasFaction(int32_t factionID) const
{
return std::find(m_FactionIDs.begin(), m_FactionIDs.end(), factionID) != m_FactionIDs.end();
}
LWOOBJID DestroyableComponent::GetKillerID() const
{
return m_KillerID;
}
Entity* DestroyableComponent::GetKiller() const
{
return EntityManager::Instance()->GetEntity(m_KillerID);
}
bool DestroyableComponent::CheckValidity(const LWOOBJID target, const bool ignoreFactions, const bool targetEnemy, const bool targetFriend) const
{
auto* targetEntity = EntityManager::Instance()->GetEntity(target);
if (targetEntity == nullptr)
{
Game::logger->Log("DestroyableComponent", "Invalid entity for checking validity (%llu)!\n", target);
return false;
}
auto* targetDestroyable = targetEntity->GetComponent<DestroyableComponent>();
if (targetDestroyable == nullptr)
{
return false;
}
auto* targetQuickbuild = targetEntity->GetComponent<RebuildComponent>();
if (targetQuickbuild != nullptr)
{
const auto state = targetQuickbuild->GetState();
if (state != REBUILD_COMPLETED)
{
return false;
}
}
if (ignoreFactions)
{
return true;
}
// Get if the target entity is an enemy and friend
bool isEnemy = IsEnemy(targetEntity);
bool isFriend = IsFriend(targetEntity);
// Return true if the target type matches what we are targeting
return (isEnemy && targetEnemy) || (isFriend && targetFriend);
}
void DestroyableComponent::Heal(const uint32_t health)
{
auto current = static_cast<uint32_t>(GetHealth());
const auto max = static_cast<uint32_t>(GetMaxHealth());
current += health;
current = std::min(current, max);
SetHealth(current);
EntityManager::Instance()->SerializeEntity(m_Parent);
}
void DestroyableComponent::Imagine(const int32_t deltaImagination)
{
auto current = static_cast<int32_t>(GetImagination());
const auto max = static_cast<int32_t>(GetMaxImagination());
current += deltaImagination;
current = std::min(current, max);
if (current < 0)
{
current = 0;
}
SetImagination(current);
EntityManager::Instance()->SerializeEntity(m_Parent);
}
void DestroyableComponent::Repair(const uint32_t armor)
{
auto current = static_cast<uint32_t>(GetArmor());
const auto max = static_cast<uint32_t>(GetMaxArmor());
current += armor;
current = std::min(current, max);
SetArmor(current);
EntityManager::Instance()->SerializeEntity(m_Parent);
}
void DestroyableComponent::Damage(uint32_t damage, const LWOOBJID source, bool echo)
{
if (GetHealth() <= 0)
{
return;
}
if (IsImmune())
{
return;
}
if (m_AttacksToBlock > 0)
{
m_AttacksToBlock--;
return;
}
// If this entity has damage reduction, reduce the damage to a minimum of 1
if (m_DamageReduction > 0 && damage > 0)
{
if (damage > m_DamageReduction)
{
damage -= m_DamageReduction;
}
else
{
damage = 1;
}
}
const auto sourceDamage = damage;
auto absorb = static_cast<uint32_t>(GetDamageToAbsorb());
auto armor = static_cast<uint32_t>(GetArmor());
auto health = static_cast<uint32_t>(GetHealth());
const auto absorbDamage = std::min(damage, absorb);
damage -= absorbDamage;
absorb -= absorbDamage;
const auto armorDamage = std::min(damage, armor);
damage -= armorDamage;
armor -= armorDamage;
health -= std::min(damage, health);
SetDamageToAbsorb(absorb);
SetArmor(armor);
SetHealth(health);
SetIsShielded(absorb > 0);
if (m_Parent->GetLOT() != 1)
{
echo = true;
}
if (echo)
{
EntityManager::Instance()->SerializeEntity(m_Parent);
}
auto* attacker = EntityManager::Instance()->GetEntity(source);
m_Parent->OnHit(attacker);
m_Parent->OnHitOrHealResult(attacker, sourceDamage);
for (const auto& cb : m_OnHitCallbacks) {
cb(attacker);
}
if (health != 0)
{
auto* combatComponent = m_Parent->GetComponent<BaseCombatAIComponent>();
if (combatComponent != nullptr)
{
combatComponent->Taunt(source, sourceDamage * 10); // * 10 is arbatrary
}
return;
}
Smash(source);
}
void DestroyableComponent::Smash(const LWOOBJID source, const eKillType killType, const std::u16string& deathType)
{
if (m_iHealth > 0)
{
SetArmor(0);
SetHealth(0);
EntityManager::Instance()->SerializeEntity(m_Parent);
}
m_KillerID = source;
auto* owner = EntityManager::Instance()->GetEntity(source);
if (owner != nullptr)
{
auto* team = TeamManager::Instance()->GetTeam(owner->GetObjectID());
owner = owner->GetOwner(); // If the owner is overwritten, we collect that here
const auto isEnemy = m_Parent->GetComponent<BaseCombatAIComponent>() != nullptr;
auto* inventoryComponent = owner->GetComponent<InventoryComponent>();
if (inventoryComponent != nullptr && isEnemy)
{
inventoryComponent->TriggerPassiveAbility(PassiveAbilityTrigger::EnemySmashed);
}
auto* missions = owner->GetComponent<MissionComponent>();
if (missions != nullptr)
{
if (team != nullptr && isEnemy)
{
for (const auto memberId : team->members)
{
auto* member = EntityManager::Instance()->GetEntity(memberId);
if (member == nullptr) continue;
auto* memberMissions = member->GetComponent<MissionComponent>();
if (memberMissions == nullptr) continue;
memberMissions->Progress(MissionTaskType::MISSION_TASK_TYPE_SMASH, m_Parent->GetLOT());
}
}
else
{
missions->Progress(MissionTaskType::MISSION_TASK_TYPE_SMASH, m_Parent->GetLOT());
}
}
}
const auto isPlayer = m_Parent->IsPlayer();
GameMessages::SendDie(
m_Parent,
source,
source,
true,
killType,
deathType,
0,
0,
0,
isPlayer,
false,
1
);
//NANI?!
if (!isPlayer)
{
if (owner != nullptr)
{
auto* team = TeamManager::Instance()->GetTeam(owner->GetObjectID());
if (team != nullptr && m_Parent->GetComponent<BaseCombatAIComponent>() != nullptr)
{
LWOOBJID specificOwner = LWOOBJID_EMPTY;
auto* scriptedActivityComponent = m_Parent->GetComponent<ScriptedActivityComponent>();
uint32_t teamSize = team->members.size();
uint32_t lootMatrixId = GetLootMatrixID();
if (scriptedActivityComponent) {
lootMatrixId = scriptedActivityComponent->GetLootMatrixForTeamSize(teamSize);
}
if (team->lootOption == 0) { // Round robin
specificOwner = TeamManager::Instance()->GetNextLootOwner(team);
auto* member = EntityManager::Instance()->GetEntity(specificOwner);
if (member) LootGenerator::Instance().DropLoot(member, m_Parent, lootMatrixId, GetMinCoins(), GetMaxCoins());
}
else {
for (const auto memberId : team->members) { // Free for all
auto* member = EntityManager::Instance()->GetEntity(memberId);
if (member == nullptr) continue;
LootGenerator::Instance().DropLoot(member, m_Parent, lootMatrixId, GetMinCoins(), GetMaxCoins());
}
}
}
else { // drop loot for non team user
LootGenerator::Instance().DropLoot(owner, m_Parent, GetLootMatrixID(), GetMinCoins(), GetMaxCoins());
}
}
}
else
{
auto* character = m_Parent->GetCharacter();
uint64_t coinsTotal = character->GetCoins();
if (coinsTotal > 0)
{
uint64_t coinsToLoose = 1;
if (coinsTotal >= 200)
{
float hundreth = (coinsTotal / 100.0f);
coinsToLoose = static_cast<int>(hundreth);
}
if (coinsToLoose > 10000)
{
coinsToLoose = 10000;
}
coinsTotal -= coinsToLoose;
LootGenerator::Instance().DropLoot(m_Parent, m_Parent, -1, coinsToLoose, coinsToLoose);
}
character->SetCoins(coinsTotal, LOOT_SOURCE_PICKUP);
Entity* zoneControl = EntityManager::Instance()->GetZoneControlEntity();
for (CppScripts::Script* script : CppScripts::GetEntityScripts(zoneControl)) {
script->OnPlayerDied(zoneControl, m_Parent);
}
std::vector<Entity*> scriptedActs = EntityManager::Instance()->GetEntitiesByComponent(COMPONENT_TYPE_SCRIPTED_ACTIVITY);
for (Entity* scriptEntity : scriptedActs) {
if (scriptEntity->GetObjectID() != zoneControl->GetObjectID()) { // Don't want to trigger twice on instance worlds
for (CppScripts::Script* script : CppScripts::GetEntityScripts(scriptEntity)) {
script->OnPlayerDied(scriptEntity, m_Parent);
}
}
}
}
m_Parent->Kill(owner);
}
void DestroyableComponent::SetFaction(int32_t factionID) {
m_FactionIDs.clear();
m_EnemyFactionIDs.clear();
AddFaction(factionID);
}
void DestroyableComponent::PushImmunity(int32_t stacks)
{
m_ImmuneStacks += stacks;
}
void DestroyableComponent::PopImmunity(int32_t stacks)
{
m_ImmuneStacks -= stacks;
}
void DestroyableComponent::FixStats()
{
auto* entity = GetParent();
if (entity == nullptr) return;
// Reset skill component and buff component
auto* skillComponent = entity->GetComponent<SkillComponent>();
auto* buffComponent = entity->GetComponent<BuffComponent>();
auto* missionComponent = entity->GetComponent<MissionComponent>();
auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
auto* destroyableComponent = entity->GetComponent<DestroyableComponent>();
// If any of the components are nullptr, return
if (skillComponent == nullptr || buffComponent == nullptr || missionComponent == nullptr || inventoryComponent == nullptr || destroyableComponent == nullptr)
{
return;
}
// Save the current stats
int32_t currentHealth = destroyableComponent->GetHealth();
int32_t currentArmor = destroyableComponent->GetArmor();
int32_t currentImagination = destroyableComponent->GetImagination();
// Unequip all items
auto equipped = inventoryComponent->GetEquippedItems();
for (auto& equippedItem : equipped)
{
// Get the item with the item ID
auto* item = inventoryComponent->FindItemById(equippedItem.second.id);
if (item == nullptr)
{
continue;
}
// Unequip the item
item->UnEquip();
}
// Base stats
int32_t maxHealth = 4;
int32_t maxArmor = 0;
int32_t maxImagination = 0;
// Go through all completed missions and add the reward stats
for (auto& pair : missionComponent->GetMissions())
{
auto* mission = pair.second;
if (!mission->IsComplete())
{
continue;
}
// Add the stats
const auto& info = mission->GetClientInfo();
maxHealth += info.reward_maxhealth;
maxImagination += info.reward_maximagination;
}
// Set the base stats
destroyableComponent->SetMaxHealth(maxHealth);
destroyableComponent->SetMaxArmor(maxArmor);
destroyableComponent->SetMaxImagination(maxImagination);
// Re-apply all buffs
buffComponent->ReApplyBuffs();
// Requip all items
for (auto& equippedItem : equipped)
{
// Get the item with the item ID
auto* item = inventoryComponent->FindItemById(equippedItem.second.id);
if (item == nullptr)
{
continue;
}
// Equip the item
item->Equip();
}
// Fetch correct max stats after everything is done
maxHealth = destroyableComponent->GetMaxHealth();
maxArmor = destroyableComponent->GetMaxArmor();
maxImagination = destroyableComponent->GetMaxImagination();
// If any of the current stats are more than their max, set them to the max
if (currentHealth > maxHealth) currentHealth = maxHealth;
if (currentArmor > maxArmor) currentArmor = maxArmor;
if (currentImagination > maxImagination) currentImagination = maxImagination;
// Restore current stats
destroyableComponent->SetHealth(currentHealth);
destroyableComponent->SetArmor(currentArmor);
destroyableComponent->SetImagination(currentImagination);
// Serialize the entity
EntityManager::Instance()->SerializeEntity(entity);
}
void DestroyableComponent::AddOnHitCallback(const std::function<void(Entity*)>& callback) {
m_OnHitCallbacks.push_back(callback);
}