#include "BuffComponent.h" #include #include "CDClientDatabase.h" #include #include "DestroyableComponent.h" #include "Game.h" #include "dLogger.h" #include "GameMessages.h" #include "SkillComponent.h" #include "ControllablePhysicsComponent.h" #include "EntityManager.h" #include "CDClientManager.h" #include "CDSkillBehaviorTable.h" std::unordered_map> BuffComponent::m_Cache{}; BuffComponent::BuffComponent(Entity* parent) : Component(parent) { } BuffComponent::~BuffComponent() { } void BuffComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags) { if (!bIsInitialUpdate) return; if (m_Buffs.empty()) { outBitStream->Write0(); } else { outBitStream->Write1(); outBitStream->Write(m_Buffs.size()); for (const auto& buff : m_Buffs) { outBitStream->Write(buff.first); outBitStream->Write0(); outBitStream->Write0(); outBitStream->Write0(); outBitStream->Write0(); outBitStream->Write0(); outBitStream->Write0(); outBitStream->Write0(); outBitStream->Write0(); outBitStream->Write0(); outBitStream->Write0(); outBitStream->Write0(); outBitStream->Write(0); } } outBitStream->Write0(); } void BuffComponent::Update(float deltaTime) { /** * Loop through all buffs and apply deltaTime to ther time. * If they have expired, remove the buff and break. */ for (auto& buff : m_Buffs) { // For damage buffs if (buff.second.tick != 0.0f && buff.second.stacks > 0) { buff.second.tickTime -= deltaTime; if (buff.second.tickTime <= 0.0f) { buff.second.tickTime = buff.second.tick; buff.second.stacks--; SkillComponent::HandleUnmanaged(buff.second.behaviorID, m_ParentEntity->GetObjectID(), buff.second.source); } } // These are indefinate buffs, don't update them. if (buff.second.time == 0.0f) { continue; } buff.second.time -= deltaTime; if (buff.second.time <= 0.0f) { RemoveBuff(buff.first); break; } } } void BuffComponent::ApplyBuff(const int32_t id, const float duration, const LWOOBJID source, bool addImmunity, bool cancelOnDamaged, bool cancelOnDeath, bool cancelOnLogout, bool cancelOnRemoveBuff, bool cancelOnUi, bool cancelOnUnequip, bool cancelOnZone) { // Prevent buffs from stacking. if (HasBuff(id)) { return; } GameMessages::SendAddBuff(m_ParentEntity->GetObjectID(), source, (uint32_t)id, (uint32_t)duration * 1000, addImmunity, cancelOnDamaged, cancelOnDeath, cancelOnLogout, cancelOnRemoveBuff, cancelOnUi, cancelOnUnequip, cancelOnZone); float tick = 0; float stacks = 0; int32_t behaviorID = 0; const auto& parameters = GetBuffParameters(id); for (const auto& parameter : parameters) { if (parameter.name == "overtime") { auto* behaviorTemplateTable = CDClientManager::Instance().GetTable(); behaviorID = behaviorTemplateTable->GetSkillByID(parameter.values[0]).behaviorID; stacks = static_cast(parameter.values[1]); tick = parameter.values[2]; const auto unknown2 = parameter.values[3]; // Always 0 } } ApplyBuffEffect(id); Buff buff; buff.id = id; buff.time = duration; buff.tick = tick; buff.tickTime = tick; buff.stacks = stacks; buff.source = source; buff.behaviorID = behaviorID; m_Buffs.emplace(id, buff); } void BuffComponent::RemoveBuff(int32_t id, bool fromUnEquip, bool removeImmunity) { const auto& iter = m_Buffs.find(id); if (iter == m_Buffs.end()) { return; } GameMessages::SendRemoveBuff(m_ParentEntity, fromUnEquip, removeImmunity, id); m_Buffs.erase(iter); RemoveBuffEffect(id); } bool BuffComponent::HasBuff(int32_t id) { return m_Buffs.find(id) != m_Buffs.end(); } void BuffComponent::ApplyBuffEffect(int32_t id) { const auto& parameters = GetBuffParameters(id); for (const auto& parameter : parameters) { if (parameter.name == "max_health") { const auto maxHealth = parameter.value; auto* destroyable = this->GetParentEntity()->GetComponent(); if (destroyable == nullptr) return; destroyable->SetMaxHealth(destroyable->GetMaxHealth() + maxHealth); } else if (parameter.name == "max_armor") { const auto maxArmor = parameter.value; auto* destroyable = this->GetParentEntity()->GetComponent(); if (destroyable == nullptr) return; destroyable->SetMaxArmor(destroyable->GetMaxArmor() + maxArmor); } else if (parameter.name == "max_imagination") { const auto maxImagination = parameter.value; auto* destroyable = this->GetParentEntity()->GetComponent(); if (destroyable == nullptr) return; destroyable->SetMaxImagination(destroyable->GetMaxImagination() + maxImagination); } else if (parameter.name == "speed") { auto* controllablePhysicsComponent = this->GetParentEntity()->GetComponent(); if (!controllablePhysicsComponent) return; const auto speed = parameter.value; controllablePhysicsComponent->AddSpeedboost(speed); } } } void BuffComponent::RemoveBuffEffect(int32_t id) { const auto& parameters = GetBuffParameters(id); for (const auto& parameter : parameters) { if (parameter.name == "max_health") { const auto maxHealth = parameter.value; auto* destroyable = this->GetParentEntity()->GetComponent(); if (destroyable == nullptr) return; destroyable->SetMaxHealth(destroyable->GetMaxHealth() - maxHealth); } else if (parameter.name == "max_armor") { const auto maxArmor = parameter.value; auto* destroyable = this->GetParentEntity()->GetComponent(); if (destroyable == nullptr) return; destroyable->SetMaxArmor(destroyable->GetMaxArmor() - maxArmor); } else if (parameter.name == "max_imagination") { const auto maxImagination = parameter.value; auto* destroyable = this->GetParentEntity()->GetComponent(); if (destroyable == nullptr) return; destroyable->SetMaxImagination(destroyable->GetMaxImagination() - maxImagination); } else if (parameter.name == "speed") { auto* controllablePhysicsComponent = this->GetParentEntity()->GetComponent(); if (!controllablePhysicsComponent) return; const auto speed = parameter.value; controllablePhysicsComponent->RemoveSpeedboost(speed); } } } void BuffComponent::RemoveAllBuffs() { for (const auto& buff : m_Buffs) { RemoveBuffEffect(buff.first); } m_Buffs.clear(); } void BuffComponent::Reset() { RemoveAllBuffs(); } void BuffComponent::ReApplyBuffs() { for (const auto& buff : m_Buffs) { ApplyBuffEffect(buff.first); } } Entity* BuffComponent::GetParentEntity() const { return m_ParentEntity; } void BuffComponent::LoadFromXml(tinyxml2::XMLDocument* doc) { // Load buffs auto* dest = doc->FirstChildElement("obj")->FirstChildElement("dest"); // Make sure we have a clean buff element. auto* buffElement = dest->FirstChildElement("buff"); // Old character, no buffs to load if (buffElement == nullptr) { return; } auto* buffEntry = buffElement->FirstChildElement("b"); while (buffEntry != nullptr) { int32_t id = buffEntry->IntAttribute("id"); float t = buffEntry->FloatAttribute("t"); float tk = buffEntry->FloatAttribute("tk"); int32_t s = buffEntry->FloatAttribute("s"); LWOOBJID sr = buffEntry->Int64Attribute("sr"); int32_t b = buffEntry->IntAttribute("b"); Buff buff; buff.id = id; buff.time = t; buff.tick = tk; buff.stacks = s; buff.source = sr; buff.behaviorID = b; m_Buffs.emplace(id, buff); buffEntry = buffEntry->NextSiblingElement("b"); } } void BuffComponent::UpdateXml(tinyxml2::XMLDocument* doc) { // Save buffs auto* dest = doc->FirstChildElement("obj")->FirstChildElement("dest"); // Make sure we have a clean buff element. auto* buffElement = dest->FirstChildElement("buff"); if (buffElement == nullptr) { buffElement = doc->NewElement("buff"); dest->LinkEndChild(buffElement); } else { buffElement->DeleteChildren(); } for (const auto& buff : m_Buffs) { auto* buffEntry = doc->NewElement("b"); buffEntry->SetAttribute("id", buff.first); buffEntry->SetAttribute("t", buff.second.time); buffEntry->SetAttribute("tk", buff.second.tick); buffEntry->SetAttribute("s", buff.second.stacks); buffEntry->SetAttribute("sr", buff.second.source); buffEntry->SetAttribute("b", buff.second.behaviorID); buffElement->LinkEndChild(buffEntry); } } const std::vector& BuffComponent::GetBuffParameters(int32_t buffId) { const auto& pair = m_Cache.find(buffId); if (pair != m_Cache.end()) { return pair->second; } auto query = CDClientDatabase::CreatePreppedStmt( "SELECT * FROM BuffParameters WHERE BuffID = ?;"); query.bind(1, (int)buffId); auto result = query.execQuery(); std::vector parameters{}; while (!result.eof()) { BuffParameter param; param.buffId = buffId; param.name = result.getStringField(1); param.value = result.getFloatField(2); if (!result.fieldIsNull(3)) { std::istringstream stream(result.getStringField(3)); std::string token; while (std::getline(stream, token, ',')) { try { const auto value = std::stof(token); param.values.push_back(value); } catch (std::invalid_argument& exception) { Game::logger->Log("BuffComponent", "Failed to parse value (%s): (%s)!", token.c_str(), exception.what()); } } } parameters.push_back(param); result.nextRow(); } m_Cache.insert_or_assign(buffId, parameters); return m_Cache.find(buffId)->second; }