2021-12-05 17:54:36 +00:00
|
|
|
#include "BuffComponent.h"
|
|
|
|
#include <BitStream.h>
|
|
|
|
#include "CDClientDatabase.h"
|
|
|
|
#include <stdexcept>
|
|
|
|
#include "DestroyableComponent.h"
|
|
|
|
#include "Game.h"
|
2023-10-21 23:31:55 +00:00
|
|
|
#include "Logger.h"
|
2021-12-05 17:54:36 +00:00
|
|
|
#include "GameMessages.h"
|
|
|
|
#include "SkillComponent.h"
|
|
|
|
#include "ControllablePhysicsComponent.h"
|
|
|
|
#include "EntityManager.h"
|
2022-12-18 15:46:04 +00:00
|
|
|
#include "CDClientManager.h"
|
2023-03-17 14:36:21 +00:00
|
|
|
#include "CDSkillBehaviorTable.h"
|
2021-12-05 17:54:36 +00:00
|
|
|
|
|
|
|
std::unordered_map<int32_t, std::vector<BuffParameter>> BuffComponent::m_Cache{};
|
|
|
|
|
|
|
|
BuffComponent::BuffComponent(Entity* parent) : Component(parent) {
|
|
|
|
}
|
|
|
|
|
|
|
|
BuffComponent::~BuffComponent() {
|
|
|
|
}
|
|
|
|
|
2023-08-10 21:33:15 +00:00
|
|
|
void BuffComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate) {
|
2021-12-05 17:54:36 +00:00
|
|
|
if (!bIsInitialUpdate) return;
|
|
|
|
if (m_Buffs.empty()) {
|
|
|
|
outBitStream->Write0();
|
|
|
|
} else {
|
|
|
|
outBitStream->Write1();
|
|
|
|
outBitStream->Write<uint32_t>(m_Buffs.size());
|
|
|
|
|
|
|
|
for (const auto& buff : m_Buffs) {
|
|
|
|
outBitStream->Write<uint32_t>(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<uint32_t>(0);
|
|
|
|
}
|
|
|
|
}
|
2022-07-25 02:26:51 +00:00
|
|
|
|
2021-12-05 17:54:36 +00:00
|
|
|
outBitStream->Write0();
|
|
|
|
}
|
|
|
|
|
2022-07-25 02:26:51 +00:00
|
|
|
void BuffComponent::Update(float deltaTime) {
|
2021-12-05 17:54:36 +00:00
|
|
|
/**
|
|
|
|
* 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--;
|
|
|
|
|
2022-01-01 09:46:05 +00:00
|
|
|
SkillComponent::HandleUnmanaged(buff.second.behaviorID, m_Parent->GetObjectID(), buff.second.source);
|
2021-12-05 17:54:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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(const_cast<LWOOBJID&>(m_Parent->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") {
|
2023-03-17 14:36:21 +00:00
|
|
|
auto* behaviorTemplateTable = CDClientManager::Instance().GetTable<CDSkillBehaviorTable>();
|
2021-12-05 17:54:36 +00:00
|
|
|
|
|
|
|
behaviorID = behaviorTemplateTable->GetSkillByID(parameter.values[0]).behaviorID;
|
|
|
|
stacks = static_cast<int32_t>(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);
|
|
|
|
}
|
|
|
|
|
2022-11-28 00:40:14 +00:00
|
|
|
void BuffComponent::RemoveBuff(int32_t id, bool fromUnEquip, bool removeImmunity) {
|
2021-12-05 17:54:36 +00:00
|
|
|
const auto& iter = m_Buffs.find(id);
|
|
|
|
|
|
|
|
if (iter == m_Buffs.end()) {
|
|
|
|
return;
|
|
|
|
}
|
2022-07-25 02:26:51 +00:00
|
|
|
|
2022-11-28 00:40:14 +00:00
|
|
|
GameMessages::SendRemoveBuff(m_Parent, fromUnEquip, removeImmunity, id);
|
|
|
|
|
2021-12-05 17:54:36 +00:00
|
|
|
m_Buffs.erase(iter);
|
|
|
|
|
|
|
|
RemoveBuffEffect(id);
|
|
|
|
}
|
|
|
|
|
2022-07-25 02:26:51 +00:00
|
|
|
bool BuffComponent::HasBuff(int32_t id) {
|
2021-12-05 17:54:36 +00:00
|
|
|
return m_Buffs.find(id) != m_Buffs.end();
|
|
|
|
}
|
|
|
|
|
2022-07-25 02:26:51 +00:00
|
|
|
void BuffComponent::ApplyBuffEffect(int32_t id) {
|
2021-12-05 17:54:36 +00:00
|
|
|
const auto& parameters = GetBuffParameters(id);
|
|
|
|
for (const auto& parameter : parameters) {
|
|
|
|
if (parameter.name == "max_health") {
|
|
|
|
const auto maxHealth = parameter.value;
|
|
|
|
|
|
|
|
auto* destroyable = this->GetParent()->GetComponent<DestroyableComponent>();
|
|
|
|
|
|
|
|
if (destroyable == nullptr) return;
|
|
|
|
|
|
|
|
destroyable->SetMaxHealth(destroyable->GetMaxHealth() + maxHealth);
|
|
|
|
} else if (parameter.name == "max_armor") {
|
|
|
|
const auto maxArmor = parameter.value;
|
|
|
|
|
|
|
|
auto* destroyable = this->GetParent()->GetComponent<DestroyableComponent>();
|
|
|
|
|
|
|
|
if (destroyable == nullptr) return;
|
|
|
|
|
|
|
|
destroyable->SetMaxArmor(destroyable->GetMaxArmor() + maxArmor);
|
|
|
|
} else if (parameter.name == "max_imagination") {
|
|
|
|
const auto maxImagination = parameter.value;
|
|
|
|
|
|
|
|
auto* destroyable = this->GetParent()->GetComponent<DestroyableComponent>();
|
|
|
|
|
|
|
|
if (destroyable == nullptr) return;
|
|
|
|
|
|
|
|
destroyable->SetMaxImagination(destroyable->GetMaxImagination() + maxImagination);
|
|
|
|
} else if (parameter.name == "speed") {
|
|
|
|
auto* controllablePhysicsComponent = this->GetParent()->GetComponent<ControllablePhysicsComponent>();
|
2022-12-24 08:49:31 +00:00
|
|
|
if (!controllablePhysicsComponent) return;
|
|
|
|
const auto speed = parameter.value;
|
|
|
|
controllablePhysicsComponent->AddSpeedboost(speed);
|
2021-12-05 17:54:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-25 02:26:51 +00:00
|
|
|
void BuffComponent::RemoveBuffEffect(int32_t id) {
|
2021-12-05 17:54:36 +00:00
|
|
|
const auto& parameters = GetBuffParameters(id);
|
|
|
|
for (const auto& parameter : parameters) {
|
|
|
|
if (parameter.name == "max_health") {
|
|
|
|
const auto maxHealth = parameter.value;
|
|
|
|
|
|
|
|
auto* destroyable = this->GetParent()->GetComponent<DestroyableComponent>();
|
|
|
|
|
|
|
|
if (destroyable == nullptr) return;
|
|
|
|
|
|
|
|
destroyable->SetMaxHealth(destroyable->GetMaxHealth() - maxHealth);
|
|
|
|
} else if (parameter.name == "max_armor") {
|
|
|
|
const auto maxArmor = parameter.value;
|
|
|
|
|
|
|
|
auto* destroyable = this->GetParent()->GetComponent<DestroyableComponent>();
|
|
|
|
|
|
|
|
if (destroyable == nullptr) return;
|
|
|
|
|
|
|
|
destroyable->SetMaxArmor(destroyable->GetMaxArmor() - maxArmor);
|
|
|
|
} else if (parameter.name == "max_imagination") {
|
|
|
|
const auto maxImagination = parameter.value;
|
|
|
|
|
|
|
|
auto* destroyable = this->GetParent()->GetComponent<DestroyableComponent>();
|
|
|
|
|
|
|
|
if (destroyable == nullptr) return;
|
|
|
|
|
|
|
|
destroyable->SetMaxImagination(destroyable->GetMaxImagination() - maxImagination);
|
|
|
|
} else if (parameter.name == "speed") {
|
|
|
|
auto* controllablePhysicsComponent = this->GetParent()->GetComponent<ControllablePhysicsComponent>();
|
2022-12-24 08:49:31 +00:00
|
|
|
if (!controllablePhysicsComponent) return;
|
|
|
|
const auto speed = parameter.value;
|
|
|
|
controllablePhysicsComponent->RemoveSpeedboost(speed);
|
2021-12-05 17:54:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-25 02:26:51 +00:00
|
|
|
void BuffComponent::RemoveAllBuffs() {
|
2021-12-05 17:54:36 +00:00
|
|
|
for (const auto& buff : m_Buffs) {
|
|
|
|
RemoveBuffEffect(buff.first);
|
|
|
|
}
|
|
|
|
|
|
|
|
m_Buffs.clear();
|
|
|
|
}
|
|
|
|
|
2022-07-25 02:26:51 +00:00
|
|
|
void BuffComponent::Reset() {
|
2021-12-05 17:54:36 +00:00
|
|
|
RemoveAllBuffs();
|
|
|
|
}
|
|
|
|
|
2022-07-25 02:26:51 +00:00
|
|
|
void BuffComponent::ReApplyBuffs() {
|
2021-12-05 17:54:36 +00:00
|
|
|
for (const auto& buff : m_Buffs) {
|
|
|
|
ApplyBuffEffect(buff.first);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Entity* BuffComponent::GetParent() const {
|
|
|
|
return m_Parent;
|
|
|
|
}
|
|
|
|
|
2022-07-25 02:03:22 +00:00
|
|
|
void BuffComponent::LoadFromXml(tinyxml2::XMLDocument* doc) {
|
2021-12-05 17:54:36 +00:00
|
|
|
// Load buffs
|
|
|
|
auto* dest = doc->FirstChildElement("obj")->FirstChildElement("dest");
|
2022-07-25 02:26:51 +00:00
|
|
|
|
2021-12-05 17:54:36 +00:00
|
|
|
// Make sure we have a clean buff element.
|
|
|
|
auto* buffElement = dest->FirstChildElement("buff");
|
2022-07-25 02:26:51 +00:00
|
|
|
|
2021-12-05 17:54:36 +00:00
|
|
|
// 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");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-25 02:26:51 +00:00
|
|
|
void BuffComponent::UpdateXml(tinyxml2::XMLDocument* doc) {
|
2021-12-05 17:54:36 +00:00
|
|
|
// Save buffs
|
|
|
|
auto* dest = doc->FirstChildElement("obj")->FirstChildElement("dest");
|
2022-07-25 02:26:51 +00:00
|
|
|
|
2021-12-05 17:54:36 +00:00
|
|
|
// Make sure we have a clean buff element.
|
|
|
|
auto* buffElement = dest->FirstChildElement("buff");
|
2022-07-25 02:26:51 +00:00
|
|
|
|
2021-12-05 17:54:36 +00:00
|
|
|
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);
|
2022-07-25 02:26:51 +00:00
|
|
|
|
2021-12-05 17:54:36 +00:00
|
|
|
buffElement->LinkEndChild(buffEntry);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-25 02:26:51 +00:00
|
|
|
const std::vector<BuffParameter>& BuffComponent::GetBuffParameters(int32_t buffId) {
|
2021-12-05 17:54:36 +00:00
|
|
|
const auto& pair = m_Cache.find(buffId);
|
|
|
|
|
|
|
|
if (pair != m_Cache.end()) {
|
|
|
|
return pair->second;
|
|
|
|
}
|
|
|
|
|
2022-01-13 03:48:27 +00:00
|
|
|
auto query = CDClientDatabase::CreatePreppedStmt(
|
|
|
|
"SELECT * FROM BuffParameters WHERE BuffID = ?;");
|
|
|
|
query.bind(1, (int)buffId);
|
2021-12-05 17:54:36 +00:00
|
|
|
|
2022-01-13 03:48:27 +00:00
|
|
|
auto result = query.execQuery();
|
2022-07-25 02:26:51 +00:00
|
|
|
|
2021-12-05 17:54:36 +00:00
|
|
|
std::vector<BuffParameter> 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) {
|
2023-10-21 23:31:55 +00:00
|
|
|
LOG("Failed to parse value (%s): (%s)!", token.c_str(), exception.what());
|
2021-12-05 17:54:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
parameters.push_back(param);
|
|
|
|
|
|
|
|
result.nextRow();
|
|
|
|
}
|
2022-07-25 02:26:51 +00:00
|
|
|
|
2021-12-05 17:54:36 +00:00
|
|
|
m_Cache.insert_or_assign(buffId, parameters);
|
|
|
|
|
|
|
|
return m_Cache.find(buffId)->second;
|
|
|
|
}
|