diff --git a/dDatabase/CDClientDatabase/CDClientTables/CDObjectSkillsTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDObjectSkillsTable.cpp index a07446b5..522c3e88 100644 --- a/dDatabase/CDClientDatabase/CDClientTables/CDObjectSkillsTable.cpp +++ b/dDatabase/CDClientDatabase/CDClientTables/CDObjectSkillsTable.cpp @@ -38,3 +38,11 @@ std::vector CDObjectSkillsTable::Query(std::function CDObjectSkillsTable::Get(const LOT lot) const { + std::vector toReturn; + for (const auto& entry : GetEntries()) { + if (entry.objectTemplate == lot) toReturn.push_back(entry); + } + return toReturn; +} diff --git a/dDatabase/CDClientDatabase/CDClientTables/CDObjectSkillsTable.h b/dDatabase/CDClientDatabase/CDClientTables/CDObjectSkillsTable.h index 731f6657..ed314212 100644 --- a/dDatabase/CDClientDatabase/CDClientTables/CDObjectSkillsTable.h +++ b/dDatabase/CDClientDatabase/CDClientTables/CDObjectSkillsTable.h @@ -4,12 +4,13 @@ #include "CDTable.h" #include +#include struct CDObjectSkills { uint32_t objectTemplate; //!< The LOT of the item uint32_t skillID; //!< The Skill ID of the object uint32_t castOnType; //!< ??? - uint32_t AICombatWeight; //!< ??? + int32_t AICombatWeight; //!< ??? }; class CDObjectSkillsTable : public CDTable> { @@ -17,5 +18,6 @@ public: void LoadValuesFromDatabase(); // Queries the table with a custom "where" clause std::vector Query(std::function predicate); + std::vector Get(const LOT lot) const; }; diff --git a/dGame/dComponents/BaseCombatAIComponent.cpp b/dGame/dComponents/BaseCombatAIComponent.cpp index ed4bf70d..d4d70025 100644 --- a/dGame/dComponents/BaseCombatAIComponent.cpp +++ b/dGame/dComponents/BaseCombatAIComponent.cpp @@ -13,6 +13,8 @@ #include "CDClientDatabase.h" #include "CDClientManager.h" +#include "CDObjectSkillsTable.h" +#include "CDSkillBehaviorTable.h" #include "DestroyableComponent.h" #include @@ -43,7 +45,7 @@ BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const int32_t compo //Grab the aggro information from BaseCombatAI: auto componentQuery = CDClientDatabase::CreatePreppedStmt( - "SELECT aggroRadius, tetherSpeed, pursuitSpeed, softTetherRadius, hardTetherRadius FROM BaseCombatAIComponent WHERE id = ?;"); + "SELECT aggroRadius, tetherSpeed, pursuitSpeed, softTetherRadius, hardTetherRadius, minRoundLength, maxRoundLength, combatRoundLength FROM BaseCombatAIComponent WHERE id = ?;"); componentQuery.bind(1, static_cast(componentID)); auto componentResult = componentQuery.execQuery(); @@ -63,44 +65,37 @@ BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const int32_t compo if (!componentResult.fieldIsNull("hardTetherRadius")) m_HardTetherRadius = componentResult.getFloatField("hardTetherRadius"); + + m_MinRoundLength = componentResult.getFloatField("minRoundLength"); + m_MaxRoundLength = componentResult.getFloatField("maxRoundLength"); + m_CombatRoundLength = componentResult.getFloatField("combatRoundLength"); } - componentResult.finalize(); - // Get aggro and tether radius from settings and use this if it is present. Only overwrite the // radii if it is greater than the one in the database. - if (m_Parent) { - auto aggroRadius = m_Parent->GetVar(u"aggroRadius"); - m_AggroRadius = aggroRadius != 0 ? aggroRadius : m_AggroRadius; - auto tetherRadius = m_Parent->GetVar(u"tetherRadius"); - m_HardTetherRadius = tetherRadius != 0 ? tetherRadius : m_HardTetherRadius; - } + m_AggroRadius = m_Parent->HasVar(u"aggroRadius") ? m_Parent->GetVar(u"aggroRadius") : m_AggroRadius; + m_HardTetherRadius = m_Parent->HasVar(u"tetherRadius") ? m_Parent->GetVar(u"tetherRadius") : m_HardTetherRadius; /* * Find skills */ - auto skillQuery = CDClientDatabase::CreatePreppedStmt( - "SELECT skillID, cooldown, behaviorID FROM SkillBehavior WHERE skillID IN (SELECT skillID FROM ObjectSkills WHERE objectTemplate = ?);"); - skillQuery.bind(1, static_cast(parent->GetLOT())); + for (const auto objectSkill : CDClientManager::GetTable()->Get(parent->GetLOT())) { + const auto skillBehavior = CDClientManager::GetTable()->GetSkillByID(objectSkill.skillID); + if (skillBehavior.skillID == objectSkill.skillID) { + const auto skillId = skillBehavior.skillID; - auto result = skillQuery.execQuery(); + const auto abilityCooldown = skillBehavior.cooldown; - while (!result.eof()) { - const auto skillId = static_cast(result.getIntField("skillID")); + const auto behaviorId = skillBehavior.behaviorID; - const auto abilityCooldown = static_cast(result.getFloatField("cooldown")); + const auto combatWeight = objectSkill.AICombatWeight; - const auto behaviorId = static_cast(result.getIntField("behaviorID")); + auto* behavior = Behavior::CreateBehavior(behaviorId); - auto* behavior = Behavior::CreateBehavior(behaviorId); + AiSkillEntry entry = { .skillId = skillId, .cooldown = 0.0f, .abilityCooldown = abilityCooldown, .behavior = behavior, .combatWeight = combatWeight }; - std::stringstream behaviorQuery; - - AiSkillEntry entry = { skillId, 0, abilityCooldown, behavior }; - - m_SkillEntries.push_back(entry); - - result.nextRow(); + m_SkillEntries.push_back(entry); + } } Stun(1.0f); @@ -248,10 +243,12 @@ void BaseCombatAIComponent::Update(const float deltaTime) { void BaseCombatAIComponent::CalculateCombat(const float deltaTime) { bool hasSkillToCast = false; + int32_t maxSkillWeights = 0; for (auto& entry : m_SkillEntries) { if (entry.cooldown > 0.0f) { entry.cooldown -= deltaTime; } else { + maxSkillWeights += entry.combatWeight; hasSkillToCast = true; } } @@ -337,10 +334,19 @@ void BaseCombatAIComponent::CalculateCombat(const float deltaTime) { LookAt(target->GetPosition()); } - for (auto i = 0; i < m_SkillEntries.size(); ++i) { - auto entry = m_SkillEntries.at(i); + // Roll to find which skill we'll try to cast + auto randomizedWeight = GeneralUtils::GenerateRandomNumber(0, maxSkillWeights); - if (entry.cooldown > 0) { + for (auto& entry : m_SkillEntries) { + // Skill isn't cooled off yet + if (entry.cooldown > 0.0f) { + continue; + } + + randomizedWeight -= entry.combatWeight; + + // if the weight is still greater than 0 continue to the next rolled skill + if (randomizedWeight > 0) { continue; } @@ -359,8 +365,6 @@ void BaseCombatAIComponent::CalculateCombat(const float deltaTime) { entry.cooldown = entry.abilityCooldown + m_SkillTime; - m_SkillEntries[i] = entry; - break; } } @@ -914,8 +918,16 @@ bool BaseCombatAIComponent::MsgGetObjectReportInfo(GameMessages::GetObjectReport } auto& ignoredThreats = cmptType.PushDebug("Temp Ignored Threats"); - for (const auto& [id, threat] : m_ThreatEntries) { + for (const auto& [id, threat] : m_RemovedThreatList) { ignoredThreats.PushDebug(std::to_string(id) + " - Time") = threat; } + auto& skillInfo = cmptType.PushDebug("Skill Info"); + for (const auto& skill : m_SkillEntries) { + auto& skillDebug = skillInfo.PushDebug("Skill ID " + std::to_string(skill.skillId)); + skillDebug.PushDebug("Cooldown") = skill.cooldown; + skillDebug.PushDebug("Ability Cooldown") = skill.abilityCooldown; + skillDebug.PushDebug("AI Combat Weight") = skill.combatWeight; + } + return true; } diff --git a/dGame/dComponents/BaseCombatAIComponent.h b/dGame/dComponents/BaseCombatAIComponent.h index 3485ac16..18de88cb 100644 --- a/dGame/dComponents/BaseCombatAIComponent.h +++ b/dGame/dComponents/BaseCombatAIComponent.h @@ -33,13 +33,15 @@ enum class AiState : uint32_t { */ struct AiSkillEntry { - uint32_t skillId; + uint32_t skillId{}; - float cooldown; + float cooldown{}; - float abilityCooldown; + float abilityCooldown{}; - Behavior* behavior; + Behavior* behavior{}; + + int32_t combatWeight{}; }; /** @@ -396,9 +398,17 @@ private: */ bool m_DirtyStateOrTarget = false; + // Min amount of time to remain as in combat after casting a skill + float m_MinRoundLength = 0.0f; + + // max amount of time to remain as in combat after casting a skill + float m_MaxRoundLength = 0.0f; + // The amount of time the entity will be forced to tether for float m_ForcedTetherTime = 0.0f; + float m_CombatRoundLength = 0.0f; + // The amount of time a removed threat will be ignored for. std::map m_RemovedThreatList;