diff --git a/dGame/dBehaviors/ApplyBuffBehavior.cpp b/dGame/dBehaviors/ApplyBuffBehavior.cpp index c94762aa..26b3da84 100644 --- a/dGame/dBehaviors/ApplyBuffBehavior.cpp +++ b/dGame/dBehaviors/ApplyBuffBehavior.cpp @@ -15,7 +15,7 @@ void ApplyBuffBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bitS if (buffComponent == nullptr) return; buffComponent->ApplyBuff(m_BuffId, m_Duration, context->originator, addImmunity, cancelOnDamaged, cancelOnDeath, - cancelOnLogout, cancelonRemoveBuff, cancelOnUi, cancelOnUnequip, cancelOnZone); + cancelOnLogout, cancelonRemoveBuff, cancelOnUi, cancelOnUnequip, cancelOnZone, m_ApplyOnTeammates); } void ApplyBuffBehavior::UnCast(BehaviorContext* context, BehaviorBranchContext branch) { @@ -45,4 +45,5 @@ void ApplyBuffBehavior::Load() { cancelOnUi = GetBoolean("cancel_on_ui"); cancelOnUnequip = GetBoolean("cancel_on_unequip"); cancelOnZone = GetBoolean("cancel_on_zone"); + m_ApplyOnTeammates = GetBoolean("apply_on_teammates"); } diff --git a/dGame/dBehaviors/ApplyBuffBehavior.h b/dGame/dBehaviors/ApplyBuffBehavior.h index 139082df..e01a238e 100644 --- a/dGame/dBehaviors/ApplyBuffBehavior.h +++ b/dGame/dBehaviors/ApplyBuffBehavior.h @@ -31,4 +31,6 @@ public: void Calculate(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) override; void Load() override; +private: + bool m_ApplyOnTeammates; }; diff --git a/dGame/dComponents/BuffComponent.cpp b/dGame/dComponents/BuffComponent.cpp index 24124f6e..be33527f 100644 --- a/dGame/dComponents/BuffComponent.cpp +++ b/dGame/dComponents/BuffComponent.cpp @@ -11,9 +11,21 @@ #include "EntityManager.h" #include "CDClientManager.h" #include "CDSkillBehaviorTable.h" +#include "TeamManager.h" std::unordered_map> BuffComponent::m_Cache{}; +namespace { + std::map BuffFx = { + { "overtime", "OTB_" }, + { "max_health", "HEALTH_" }, + { "max_imagination", "IMAGINATION_" }, + { "max_armor", "ARMOR_" }, + { "speed", "SPEED_" }, + { "loot", "LOOT_" } + }; +} + BuffComponent::BuffComponent(Entity* parent) : Component(parent) { } @@ -22,32 +34,38 @@ BuffComponent::~BuffComponent() { void BuffComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate) { if (!bIsInitialUpdate) return; - if (m_Buffs.empty()) { - outBitStream->Write0(); - } else { - outBitStream->Write1(); + outBitStream->Write(!m_Buffs.empty()); + if (!m_Buffs.empty()) { 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(); + for (const auto& [id, buff] : m_Buffs) { + outBitStream->Write(id); + outBitStream->Write(buff.time != 0.0f); + if (buff.time != 0.0f) outBitStream->Write(static_cast(buff.time * 1000.0f)); + outBitStream->Write(buff.cancelOnDeath); + outBitStream->Write(buff.cancelOnZone); + outBitStream->Write(buff.cancelOnDamaged); + outBitStream->Write(buff.cancelOnRemoveBuff); + outBitStream->Write(buff.cancelOnUi); + outBitStream->Write(buff.cancelOnLogout); + outBitStream->Write(buff.cancelOnUnequip); + outBitStream->Write0(); // Cancel on Damage Absorb Ran Out. Generally false from what I can tell - outBitStream->Write0(); - outBitStream->Write0(); + auto* team = TeamManager::Instance()->GetTeam(buff.source); + bool addedByTeammate = false; + if (team) { + addedByTeammate = std::count(team->members.begin(), team->members.end(), m_Parent->GetObjectID()) > 0; + } - outBitStream->Write(0); + outBitStream->Write(addedByTeammate); // Added by teammate. If source is in the same team as the target, this is true. Otherwise, false. + outBitStream->Write(buff.applyOnTeammates); + if (addedByTeammate) outBitStream->Write(buff.source); + + outBitStream->Write(buff.refCount); } } - outBitStream->Write0(); + outBitStream->Write0(); // something to do with immunity buffs? } void BuffComponent::Update(float deltaTime) { @@ -83,17 +101,55 @@ void BuffComponent::Update(float deltaTime) { } } +const std::string& GetFxName(const std::string& buffname) { + const auto& toReturn = BuffFx[buffname]; + if (toReturn.empty()) { + LOG_DEBUG("No fx name for %s", buffname.c_str()); + } + return toReturn; +} + +void BuffComponent::ApplyBuffFx(uint32_t buffId, const BuffParameter& buff) { + std::string fxToPlay; + const auto& buffName = GetFxName(buff.name); + + if (buffName.empty()) return; + + fxToPlay += std::to_string(buffId); + LOG_DEBUG("Playing %s %i", fxToPlay.c_str(), buff.effectId); + GameMessages::SendPlayFXEffect(m_Parent->GetObjectID(), buff.effectId, u"cast", fxToPlay, LWOOBJID_EMPTY, 1.07f, 1.0f, false); +} + +void BuffComponent::RemoveBuffFx(uint32_t buffId, const BuffParameter& buff) { + std::string fxToPlay; + const auto& buffName = GetFxName(buff.name); + + if (buffName.empty()) return; + + fxToPlay += std::to_string(buffId); + LOG_DEBUG("Stopping %s", fxToPlay.c_str()); + GameMessages::SendStopFXEffect(m_Parent, false, fxToPlay); +} + 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) { + bool cancelOnUi, bool cancelOnUnequip, bool cancelOnZone, bool applyOnTeammates) { // Prevent buffs from stacking. if (HasBuff(id)) { + m_Buffs[id].refCount++; + m_Buffs[id].time = duration; return; } + auto* team = TeamManager::Instance()->GetTeam(source); + bool addedByTeammate = false; + if (team) { + addedByTeammate = std::count(team->members.begin(), team->members.end(), m_Parent->GetObjectID()) > 0; + } + GameMessages::SendAddBuff(const_cast(m_Parent->GetObjectID()), source, (uint32_t)id, (uint32_t)duration * 1000, addImmunity, cancelOnDamaged, cancelOnDeath, - cancelOnLogout, cancelOnRemoveBuff, cancelOnUi, cancelOnUnequip, cancelOnZone); + cancelOnLogout, cancelOnRemoveBuff, cancelOnUi, cancelOnUnequip, cancelOnZone, addedByTeammate, applyOnTeammates); float tick = 0; float stacks = 0; @@ -121,17 +177,43 @@ void BuffComponent::ApplyBuff(const int32_t id, const float duration, const LWOO buff.stacks = stacks; buff.source = source; buff.behaviorID = behaviorID; + buff.cancelOnDamaged = cancelOnDamaged; + buff.cancelOnDeath = cancelOnDeath; + buff.cancelOnLogout = cancelOnLogout; + buff.cancelOnRemoveBuff = cancelOnRemoveBuff; + buff.cancelOnUi = cancelOnUi; + buff.cancelOnUnequip = cancelOnUnequip; + buff.cancelOnZone = cancelOnZone; + buff.refCount = 1; m_Buffs.emplace(id, buff); + + auto* parent = GetParent(); + if (!cancelOnDeath) return; + + m_Parent->AddDieCallback([parent, id]() { + LOG_DEBUG("Removing buff %i because parent died", id); + if (!parent) return; + auto* buffComponent = parent->GetComponent(); + if (buffComponent) buffComponent->RemoveBuff(id, false, false, true); + }); } -void BuffComponent::RemoveBuff(int32_t id, bool fromUnEquip, bool removeImmunity) { +void BuffComponent::RemoveBuff(int32_t id, bool fromUnEquip, bool removeImmunity, bool ignoreRefCount) { const auto& iter = m_Buffs.find(id); if (iter == m_Buffs.end()) { return; } + if (!ignoreRefCount && !iter->second.cancelOnRemoveBuff) { + iter->second.refCount--; + LOG_DEBUG("refCount for buff %i is now %i", id, iter->second.refCount); + if (iter->second.refCount > 0) { + return; + } + } + GameMessages::SendRemoveBuff(m_Parent, fromUnEquip, removeImmunity, id); m_Buffs.erase(iter); @@ -146,6 +228,7 @@ bool BuffComponent::HasBuff(int32_t id) { void BuffComponent::ApplyBuffEffect(int32_t id) { const auto& parameters = GetBuffParameters(id); for (const auto& parameter : parameters) { + ApplyBuffFx(id, parameter); if (parameter.name == "max_health") { const auto maxHealth = parameter.value; @@ -182,6 +265,7 @@ void BuffComponent::ApplyBuffEffect(int32_t id) { void BuffComponent::RemoveBuffEffect(int32_t id) { const auto& parameters = GetBuffParameters(id); for (const auto& parameter : parameters) { + RemoveBuffFx(id, parameter); if (parameter.name == "max_health") { const auto maxHealth = parameter.value; @@ -251,13 +335,25 @@ void BuffComponent::LoadFromXml(tinyxml2::XMLDocument* doc) { auto* buffEntry = buffElement->FirstChildElement("b"); - while (buffEntry != nullptr) { + while (buffEntry) { int32_t id = buffEntry->IntAttribute("id"); float t = buffEntry->FloatAttribute("t"); float tk = buffEntry->FloatAttribute("tk"); + float tt = buffEntry->FloatAttribute("tt"); int32_t s = buffEntry->FloatAttribute("s"); LWOOBJID sr = buffEntry->Int64Attribute("sr"); int32_t b = buffEntry->IntAttribute("b"); + int32_t refCount = buffEntry->IntAttribute("refCount"); + + bool cancelOnDamaged = buffEntry->BoolAttribute("cancelOnDamaged"); + bool cancelOnDeath = buffEntry->BoolAttribute("cancelOnDeath"); + bool cancelOnLogout = buffEntry->BoolAttribute("cancelOnLogout"); + bool cancelOnRemoveBuff = buffEntry->BoolAttribute("cancelOnRemoveBuff"); + bool cancelOnUi = buffEntry->BoolAttribute("cancelOnUi"); + bool cancelOnUnequip = buffEntry->BoolAttribute("cancelOnUnequip"); + bool cancelOnZone = buffEntry->BoolAttribute("cancelOnZone"); + bool applyOnTeammates = buffEntry->BoolAttribute("applyOnTeammates"); + Buff buff; buff.id = id; @@ -266,6 +362,18 @@ void BuffComponent::LoadFromXml(tinyxml2::XMLDocument* doc) { buff.stacks = s; buff.source = sr; buff.behaviorID = b; + buff.refCount = refCount; + buff.tickTime = tt; + + buff.cancelOnDamaged = cancelOnDamaged; + buff.cancelOnDeath = cancelOnDeath; + buff.cancelOnLogout = cancelOnLogout; + buff.cancelOnRemoveBuff = cancelOnRemoveBuff; + buff.cancelOnUi = cancelOnUi; + buff.cancelOnUnequip = cancelOnUnequip; + buff.cancelOnZone = cancelOnZone; + buff.applyOnTeammates = applyOnTeammates; + m_Buffs.emplace(id, buff); @@ -288,15 +396,27 @@ void BuffComponent::UpdateXml(tinyxml2::XMLDocument* doc) { buffElement->DeleteChildren(); } - for (const auto& buff : m_Buffs) { + for (const auto& [id, buff] : m_Buffs) { auto* buffEntry = doc->NewElement("b"); + if (buff.cancelOnZone || buff.cancelOnLogout) continue; - 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); + buffEntry->SetAttribute("id", id); + buffEntry->SetAttribute("t", buff.time); + buffEntry->SetAttribute("tk", buff.tick); + buffEntry->SetAttribute("tt", buff.tickTime); + buffEntry->SetAttribute("s", buff.stacks); + buffEntry->SetAttribute("sr", buff.source); + buffEntry->SetAttribute("b", buff.behaviorID); + buffEntry->SetAttribute("refCount", buff.refCount); + + buffEntry->SetAttribute("cancelOnDamaged", buff.cancelOnDamaged); + buffEntry->SetAttribute("cancelOnDeath", buff.cancelOnDeath); + buffEntry->SetAttribute("cancelOnLogout", buff.cancelOnLogout); + buffEntry->SetAttribute("cancelOnRemoveBuff", buff.cancelOnRemoveBuff); + buffEntry->SetAttribute("cancelOnUi", buff.cancelOnUi); + buffEntry->SetAttribute("cancelOnUnequip", buff.cancelOnUnequip); + buffEntry->SetAttribute("cancelOnZone", buff.cancelOnZone); + buffEntry->SetAttribute("applyOnTeammates", buff.applyOnTeammates); buffElement->LinkEndChild(buffEntry); } @@ -309,8 +429,7 @@ const std::vector& BuffComponent::GetBuffParameters(int32_t buffI return pair->second; } - auto query = CDClientDatabase::CreatePreppedStmt( - "SELECT * FROM BuffParameters WHERE BuffID = ?;"); + auto query = CDClientDatabase::CreatePreppedStmt("SELECT * FROM BuffParameters WHERE BuffID = ?;"); query.bind(1, (int)buffId); auto result = query.execQuery(); @@ -321,11 +440,12 @@ const std::vector& BuffComponent::GetBuffParameters(int32_t buffI BuffParameter param; param.buffId = buffId; - param.name = result.getStringField(1); - param.value = result.getFloatField(2); + param.name = result.getStringField("ParameterName"); + param.value = result.getFloatField("NumberValue"); + param.effectId = result.getIntField("EffectID"); if (!result.fieldIsNull(3)) { - std::istringstream stream(result.getStringField(3)); + std::istringstream stream(result.getStringField("StringValue")); std::string token; while (std::getline(stream, token, ',')) { diff --git a/dGame/dComponents/BuffComponent.h b/dGame/dComponents/BuffComponent.h index 5b6f8fd6..7187f8f7 100644 --- a/dGame/dComponents/BuffComponent.h +++ b/dGame/dComponents/BuffComponent.h @@ -14,8 +14,7 @@ class Entity; /** * Extra information on effects to apply after applying a buff, for example whether to buff armor, imag or health and by how much */ -struct BuffParameter -{ +struct BuffParameter { int32_t buffId; std::string name; float value; @@ -26,8 +25,7 @@ struct BuffParameter /** * Meta information about a buff that can be applied, e.g. how long it's applied, who applied it, etc. */ -struct Buff -{ +struct Buff { int32_t id = 0; float time = 0; float tick = 0; @@ -35,6 +33,15 @@ struct Buff int32_t stacks = 0; LWOOBJID source = 0; int32_t behaviorID = 0; + bool cancelOnDamaged = false; + bool cancelOnDeath = false; + bool cancelOnLogout = false; + bool cancelOnRemoveBuff = false; + bool cancelOnUi = false; + bool cancelOnUnequip = false; + bool cancelOnZone = false; + bool applyOnTeammates = false; + uint32_t refCount = 0; }; /** @@ -74,14 +81,17 @@ public: */ void ApplyBuff(int32_t id, float duration, LWOOBJID source, bool addImmunity = false, bool cancelOnDamaged = false, bool cancelOnDeath = true, bool cancelOnLogout = false, bool cancelOnRemoveBuff = true, - bool cancelOnUi = false, bool cancelOnUnequip = false, bool cancelOnZone = false); + bool cancelOnUi = false, bool cancelOnUnequip = false, bool cancelOnZone = false, bool applyOnTeammates = false); + + void ApplyBuffFx(uint32_t buffId, const BuffParameter& buffName); + void RemoveBuffFx(uint32_t buffId, const BuffParameter& buffName); /** * Removes a buff from the parent entity, reversing its effects * @param id the id of the buff to remove * @param removeImmunity whether or not to remove immunity on removing the buff */ - void RemoveBuff(int32_t id, bool fromUnEquip = false, bool removeImmunity = false); + void RemoveBuff(int32_t id, bool fromUnEquip = false, bool removeImmunity = false, bool ignoreRefCount = false); /** * Returns whether or not the entity has a buff identified by `id` diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index b506e3a0..fe2ad720 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -4477,7 +4477,7 @@ void GameMessages::SendVehicleNotifyFinishedRace(LWOOBJID objectId, const System void GameMessages::SendAddBuff(LWOOBJID& objectID, const LWOOBJID& casterID, uint32_t buffID, uint32_t msDuration, bool addImmunity, bool cancelOnDamaged, bool cancelOnDeath, bool cancelOnLogout, - bool cancelOnRemoveBuff, bool cancelOnUi, bool cancelOnUnequip, bool cancelOnZone, + bool cancelOnRemoveBuff, bool cancelOnUi, bool cancelOnUnequip, bool cancelOnZone, bool addedByTeammate, bool applyOnTeammates, const SystemAddress& sysAddr) { CBITSTREAM; CMSGHEADER; @@ -4485,27 +4485,29 @@ void GameMessages::SendAddBuff(LWOOBJID& objectID, const LWOOBJID& casterID, uin bitStream.Write(objectID); bitStream.Write(eGameMessageType::ADD_BUFF); - bitStream.Write(false); // Added by teammate - bitStream.Write(false); // Apply on teammates - bitStream.Write(false); // Cancel on damage absorb ran out + bitStream.Write(addedByTeammate); // Added by teammate + bitStream.Write(applyOnTeammates); // Apply on teammates bitStream.Write(cancelOnDamaged); bitStream.Write(cancelOnDeath); bitStream.Write(cancelOnLogout); + bitStream.Write(false); // Cancel on move bitStream.Write(cancelOnRemoveBuff); - bitStream.Write(cancelOnUi); bitStream.Write(cancelOnUnequip); bitStream.Write(cancelOnZone); + bitStream.Write(false); // Ignore immunities bitStream.Write(addImmunity); bitStream.Write(false); // Use ref count - bitStream.Write(buffID); - bitStream.Write(msDuration); + bitStream.Write(casterID != LWOOBJID_EMPTY); + if (casterID != LWOOBJID_EMPTY) bitStream.Write(casterID); - bitStream.Write(casterID); - bitStream.Write(casterID); + bitStream.Write(buffID); + + bitStream.Write(msDuration != 0); + if (msDuration != 0) bitStream.Write(msDuration); if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; SEND_PACKET; diff --git a/dGame/dGameMessages/GameMessages.h b/dGame/dGameMessages/GameMessages.h index 6a572d97..5199a72a 100644 --- a/dGame/dGameMessages/GameMessages.h +++ b/dGame/dGameMessages/GameMessages.h @@ -206,7 +206,7 @@ namespace GameMessages { void SendAddBuff(LWOOBJID& objectID, const LWOOBJID& casterID, uint32_t buffID, uint32_t msDuration, bool addImmunity = false, bool cancelOnDamaged = false, bool cancelOnDeath = true, bool cancelOnLogout = false, bool cancelOnRemoveBuff = true, bool cancelOnUi = false, - bool cancelOnUnequip = false, bool cancelOnZone = false, const SystemAddress& sysAddr = UNASSIGNED_SYSTEM_ADDRESS); + bool cancelOnUnequip = false, bool cancelOnZone = false, bool addedByTeammate = false, bool applyOnTeammates = false, const SystemAddress& sysAddr = UNASSIGNED_SYSTEM_ADDRESS); void SendToggleGMInvis(LWOOBJID objectId, bool enabled, const SystemAddress& sysAddr);