From 52b9dec2cd381dcc54b3a0114dafd4af85ffe5e1 Mon Sep 17 00:00:00 2001 From: Yunfan Li Date: Sun, 11 Aug 2024 02:10:50 +0800 Subject: [PATCH] Item spell coverage calculation --- src/factory/StatsCollector.cpp | 198 ++++++++---------- src/factory/StatsCollector.h | 12 +- src/factory/StatsWeightCalculator.cpp | 24 +-- src/strategy/actions/ChatActionContext.h | 2 + src/strategy/actions/TellLosAction.cpp | 24 +++ src/strategy/actions/TellLosAction.h | 9 + .../generic/ChatCommandHandlerStrategy.cpp | 1 + src/strategy/triggers/ChatTriggerContext.h | 2 + 8 files changed, 147 insertions(+), 125 deletions(-) diff --git a/src/factory/StatsCollector.cpp b/src/factory/StatsCollector.cpp index 9cce4fad..f940d7a9 100644 --- a/src/factory/StatsCollector.cpp +++ b/src/factory/StatsCollector.cpp @@ -43,7 +43,7 @@ void StatsCollector::CollectItemStats(ItemTemplate const* proto) } for (uint8 j = 0; j < MAX_ITEM_PROTO_SPELLS; j++) { - CollectSpellStats(proto->Spells[j].SpellId, proto->Spells[j].SpellTrigger != ITEM_SPELLTRIGGER_ON_EQUIP); + CollectSpellStats(proto->Spells[j].SpellId, 1.0f, proto->Spells[j].SpellCooldown); } if (proto->socketBonus) @@ -53,23 +53,23 @@ void StatsCollector::CollectItemStats(ItemTemplate const* proto) } } -void StatsCollector::CollectSpellStats(uint32 spellId, bool isTriggered) +void StatsCollector::CollectSpellStats(uint32 spellId, float multiplier, int32 spellCooldown) { SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); if (!spellInfo) return; - if (CollectSpecialCaseSpellStats(spellId)) + if (SpecialSpellFilter(spellId)) return; - /// @todo Not all triggered spell need this penalty - float multiplier = isTriggered ? 0.25f : 1.0f; + const SpellProcEventEntry* eventEntry = sSpellMgr->GetSpellProcEvent(spellInfo->Id); + + uint32 triggerCooldown = eventEntry ? eventEntry->cooldown : 0; bool canNextTrigger = true; uint32 procFlags; - const SpellProcEventEntry* eventEntry = sSpellMgr->GetSpellProcEvent(spellInfo->Id); if (eventEntry && eventEntry->procFlags) procFlags = eventEntry->procFlags; else @@ -77,37 +77,55 @@ void StatsCollector::CollectSpellStats(uint32 spellId, bool isTriggered) if (procFlags && !CanBeTriggeredByType(spellInfo, procFlags)) canNextTrigger = false; - - // if (!eventEntry || eventEntry->cooldown == 0) - // { - - // } - // if (spellInfo->ProcChance == 100) - if (spellInfo->StackAmount) - multiplier *= spellInfo->StackAmount; + { + multiplier *= 0.2f + spellInfo->StackAmount * 0.8; + } for (int i = 0; i < MAX_SPELL_EFFECTS; i++) { - if (spellInfo->IsPositive()) - CollectPositiveSpellEffectStats(spellInfo->Effects[i], multiplier, canNextTrigger); - } -} + const SpellEffectInfo& effectInfo = spellInfo->Effects[i]; + switch (effectInfo.Effect) + { + case SPELL_EFFECT_APPLY_AURA: + { + /// @todo Handle negative spell + if (!spellInfo->IsPositive()) + break; -void StatsCollector::CollectPositiveSpellEffectStats(const SpellEffectInfo& effectInfo, float multiplier, bool canNextTrigger) -{ + float coverage; + if (spellCooldown <= 2000 || spellInfo->GetDuration() == -1) + coverage = 1.0f; + else + coverage = std::min(1.0f, (float)spellInfo->GetDuration() / (spellInfo->GetDuration() + spellCooldown)); - switch (effectInfo.Effect) - { - case SPELL_EFFECT_APPLY_AURA: - HandleApplyAura(effectInfo, multiplier, canNextTrigger); - break; - // case SPELL_EFFECT_HEAL: - // int32 val = effectInfo.CalcValue(); - // stats[STATS_TYPE_HEAL_POWER] += (float)val / 5 * multiplier; - // break; - default: - break; + multiplier *= coverage; + HandleApplyAura(effectInfo, multiplier, canNextTrigger, triggerCooldown); + break; + } + case SPELL_EFFECT_HEAL: + { + if (!spellCooldown) + break; + float normalizedCd = std::max(spellCooldown, 5000); + int32 val = AverageValue(effectInfo); + stats[STATS_TYPE_HEAL_POWER] += (float)val / (normalizedCd / 1000) * multiplier; + break; + } + case SPELL_EFFECT_ENERGIZE: + { + if (!spellCooldown) + break; + if (effectInfo.MiscValue != POWER_MANA) + break; + float normalizedCd = std::max(spellCooldown, 5000); + int32 val = AverageValue(effectInfo); + stats[STATS_TYPE_MANA_REGENERATION] += (float)val / (normalizedCd / 1000 / 5) * multiplier; + break; + } + default: + break; + } } } @@ -119,7 +137,7 @@ void StatsCollector::CollectEnchantStats(SpellItemEnchantmentEntry const* enchan uint32 enchant_amount = enchant->amount[s]; uint32 enchant_spell_id = enchant->spellid[s]; - if (CollectSpecialEnchantSpellStats(enchant_spell_id)) + if (SpecialEnchantFilter(enchant_spell_id)) continue; switch (enchant_display_type) @@ -150,16 +168,27 @@ void StatsCollector::CollectEnchantStats(SpellItemEnchantmentEntry const* enchan } /// @todo Special case for some spell that hard to calculate, like trinket, relic, etc. -bool StatsCollector::CollectSpecialCaseSpellStats(uint32 spellId) { +bool StatsCollector::SpecialSpellFilter(uint32 spellId) { // trinket switch (spellId) { + case 67702: // Death's Verdict + stats[STATS_TYPE_ATTACK_POWER] += 225; + return true; + case 67771: // Death's Verdict (heroic) + stats[STATS_TYPE_ATTACK_POWER] += 260; + return true; case 71519: // Deathbringer's Will stats[STATS_TYPE_ATTACK_POWER] += 350; return true; case 71562: // Deathbringer's Will (heroic) stats[STATS_TYPE_ATTACK_POWER] += 400; return true; + case 71602: // Dislodged Foreign Object + /// @todo The item can be triggered by heal spell, which mismatch with it's description + /// Noticing that heroic item can not be triggered, probably a bug to report to AC + if (type_ == CollectorType::SPELL_HEAL) + return true; default: break; } @@ -174,34 +203,10 @@ bool StatsCollector::CollectSpecialCaseSpellStats(uint32 spellId) { return false; } -bool StatsCollector::CollectSpecialEnchantSpellStats(uint32 enchantSpellId) +bool StatsCollector::SpecialEnchantFilter(uint32 enchantSpellId) { switch (enchantSpellId) { - // case 28093: // mongoose - // if (type_ == CollectorType::MELEE) - // { - // stats[STATS_TYPE_AGILITY] += 40; - // } - // return true; - // case 20007: // crusader - // if (type_ == CollectorType::MELEE) - // { - // stats[STATS_TYPE_STRENGTH] += 30; - // } - // return true; - // case 59620: // Berserk - // if (type_ == CollectorType::MELEE) - // { - // stats[STATS_TYPE_ATTACK_POWER] += 120; - // } - // return true; - // case 64440: // Blade Warding - // if (type_ == CollectorType::MELEE) - // { - // stats[STATS_TYPE_PARRY] += 50; - // } - // return true; case 53365: // Rune of the Fallen Crusader if (type_ == CollectorType::MELEE) { @@ -212,7 +217,7 @@ bool StatsCollector::CollectSpecialEnchantSpellStats(uint32 enchantSpellId) if (type_ == CollectorType::MELEE) { stats[STATS_TYPE_DEFENSE] += 25; - stats[STATS_TYPE_STAMINA] += 40; + stats[STATS_TYPE_STAMINA] += 50; } return true; case 64571: // Blood draining @@ -224,49 +229,6 @@ bool StatsCollector::CollectSpecialEnchantSpellStats(uint32 enchantSpellId) default: break; } - // { - // int allStatsAmount = 0; - // switch (enchantSpellId) - // { - // case 13624: - // allStatsAmount = 1; - // break; - // case 13625: - // allStatsAmount = 2; - // break; - // case 13824: - // allStatsAmount = 3; - // break; - // case 19988: - // case 44627: - // case 56527: - // allStatsAmount = 4; - // break; - // case 27959: - // case 56529: - // allStatsAmount = 6; - // break; - // case 44624: - // allStatsAmount = 8; - // break; - // case 60694: - // case 68251: - // allStatsAmount = 10; - // break; - // default: - // break; - // } - // if (allStatsAmount != 0) - // { - // stats[STATS_TYPE_AGILITY] += allStatsAmount; - // stats[STATS_TYPE_STRENGTH] += allStatsAmount; - // stats[STATS_TYPE_INTELLECT] += allStatsAmount; - // stats[STATS_TYPE_SPIRIT] += allStatsAmount; - // stats[STATS_TYPE_STAMINA] += allStatsAmount; - // return true; - // } - // } - return false; } @@ -331,9 +293,10 @@ void StatsCollector::CollectByItemStatType(uint32 itemStatType, int32 val) switch (itemStatType) { case ITEM_MOD_MANA: + stats[STATS_TYPE_MANA_REGENERATION] += val / 10; break; case ITEM_MOD_HEALTH: - stats[STATS_TYPE_AGILITY] += val / 12; + stats[STATS_TYPE_STAMINA] += val / 12; break; case ITEM_MOD_AGILITY: stats[STATS_TYPE_AGILITY] += val; @@ -446,12 +409,12 @@ void StatsCollector::CollectByItemStatType(uint32 itemStatType, int32 val) } } -void StatsCollector::HandleApplyAura(const SpellEffectInfo& effectInfo, float multiplier, bool canNextTrigger) +void StatsCollector::HandleApplyAura(const SpellEffectInfo& effectInfo, float multiplier, bool canNextTrigger, uint32 triggerCooldown) { if (effectInfo.Effect != SPELL_EFFECT_APPLY_AURA) return; - int32 val = effectInfo.CalcValue(); + int32 val = AverageValue(effectInfo); switch (effectInfo.ApplyAuraName) { @@ -600,16 +563,37 @@ void StatsCollector::HandleApplyAura(const SpellEffectInfo& effectInfo, float mu case SPELL_AURA_PROC_TRIGGER_SPELL: { if (canNextTrigger) - CollectSpellStats(effectInfo.TriggerSpell, true); + CollectSpellStats(effectInfo.TriggerSpell, multiplier, triggerCooldown); break; } case SPELL_AURA_PERIODIC_TRIGGER_SPELL: { if (canNextTrigger) - CollectSpellStats(effectInfo.TriggerSpell, true); + CollectSpellStats(effectInfo.TriggerSpell, multiplier, triggerCooldown); break; } default: break; } +} + +int32 StatsCollector::AverageValue(const SpellEffectInfo& effectInfo) +{ + float basePointsPerLevel = effectInfo.RealPointsPerLevel; + int32 basePoints = effectInfo.BasePoints; + int32 randomPoints = int32(effectInfo.DieSides); + + switch (randomPoints) + { + case 0: + break; + case 1: + basePoints += 1; + break; + default: + int32 randvalue = (1 + randomPoints) / 2; + basePoints += randvalue; + break; + } + return basePoints; } \ No newline at end of file diff --git a/src/factory/StatsCollector.h b/src/factory/StatsCollector.h index 95c81f21..fa0634ce 100644 --- a/src/factory/StatsCollector.h +++ b/src/factory/StatsCollector.h @@ -61,8 +61,7 @@ public: StatsCollector(StatsCollector& stats) = default; void Reset(); void CollectItemStats(ItemTemplate const* proto); - void CollectSpellStats(uint32 spellId, bool isTriggered = false); - void CollectPositiveSpellEffectStats(const SpellEffectInfo& effectInfo, float multiplier = 1.0f, bool canNextTrigger = true); + void CollectSpellStats(uint32 spellId, float multiplier = 1.0f, int32 spellCooldown = -1); void CollectEnchantStats(SpellItemEnchantmentEntry const* enchant); public: @@ -71,11 +70,12 @@ public: private: bool CanBeTriggeredByType(SpellInfo const* spellInfo, uint32 procFlags); void CollectByItemStatType(uint32 itemStatType, int32 val); - bool CollectSpecialCaseSpellStats(uint32 spellId); - bool CollectSpecialEnchantSpellStats(uint32 enchantSpellId); - - void HandleApplyAura(const SpellEffectInfo& effectInfo, float multiplier, bool canNextTrigger); + bool SpecialSpellFilter(uint32 spellId); + bool SpecialEnchantFilter(uint32 enchantSpellId); + void HandleApplyAura(const SpellEffectInfo& effectInfo, float multiplier, bool canNextTrigger, uint32 triggerCooldown); + int32 AverageValue(const SpellEffectInfo& effectInfo); + private: CollectorType type_; }; diff --git a/src/factory/StatsWeightCalculator.cpp b/src/factory/StatsWeightCalculator.cpp index 0fea5ef4..267a5aef 100644 --- a/src/factory/StatsWeightCalculator.cpp +++ b/src/factory/StatsWeightCalculator.cpp @@ -179,11 +179,11 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player) (cls == CLASS_SHAMAN && tab == SHAMAN_TAB_RESTORATION) || // heal (cls == CLASS_DRUID && tab == DRUID_TAB_RESTORATION)) { - stats_weights_[STATS_TYPE_INTELLECT] += 0.5f; - stats_weights_[STATS_TYPE_SPIRIT] += 0.5f; + stats_weights_[STATS_TYPE_INTELLECT] += 0.8f; + stats_weights_[STATS_TYPE_SPIRIT] += 0.8f; stats_weights_[STATS_TYPE_HEAL_POWER] += 1.0f; - stats_weights_[STATS_TYPE_MANA_REGENERATION] += 0.5f; - stats_weights_[STATS_TYPE_CRIT] += 0.5f; + stats_weights_[STATS_TYPE_MANA_REGENERATION] += 1.5f; + stats_weights_[STATS_TYPE_CRIT] += 0.7f; stats_weights_[STATS_TYPE_HASTE] += 1.0f; stats_weights_[STATS_TYPE_RANGED_DPS] += 1.0f; } @@ -446,23 +446,23 @@ void StatsWeightCalculator::ApplyOverflowPenalty(Player* player) { float defense_current, defense_overflow; defense_current = player->GetRatingBonusValue(CR_DEFENSE_SKILL); - defense_overflow = EXPERTISE_OVERFLOW; + defense_overflow = DEFENSE_OVERFLOW; if (defense_current >= defense_overflow) - stats_weights_[STATS_TYPE_EXPERTISE] /= 2; + stats_weights_[STATS_TYPE_DEFENSE] /= 2; else if (defense_current >= defense_overflow * 0.8) - stats_weights_[STATS_TYPE_EXPERTISE] /= 1.5; + stats_weights_[STATS_TYPE_DEFENSE] /= 1.5; } } { if (type_ == CollectorType::MELEE || type_ == CollectorType::RANGED) { - float armor_pnetration_current, armor_pnetration_overflow; - armor_pnetration_current = player->GetRatingBonusValue(CR_ARMOR_PENETRATION); - armor_pnetration_overflow = EXPERTISE_OVERFLOW; - if (armor_pnetration_current >= armor_pnetration_overflow) + float armor_penetration_current, armor_penetration_overflow; + armor_penetration_current = player->GetRatingBonusValue(CR_ARMOR_PENETRATION); + armor_penetration_overflow = ARMOR_PENETRATION_OVERFLOW; + if (armor_penetration_current >= armor_penetration_overflow) stats_weights_[STATS_TYPE_ARMOR_PENETRATION] = 0.0f; - if (armor_pnetration_current >= armor_pnetration_overflow * 0.8) + if (armor_penetration_current >= armor_penetration_overflow * 0.8) stats_weights_[STATS_TYPE_ARMOR_PENETRATION] /= 1.5; } } diff --git a/src/strategy/actions/ChatActionContext.h b/src/strategy/actions/ChatActionContext.h index 56597747..615760e2 100644 --- a/src/strategy/actions/ChatActionContext.h +++ b/src/strategy/actions/ChatActionContext.h @@ -172,6 +172,7 @@ public: creators["naxx chat shortcut"] = &ChatActionContext::naxx_chat_shortcut; creators["bwl chat shortcut"] = &ChatActionContext::bwl_chat_shortcut; creators["tell expected dps"] = &ChatActionContext::tell_expected_dps; + creators["calc"] = &ChatActionContext::calc; } private: @@ -268,6 +269,7 @@ private: static Action* naxx_chat_shortcut(PlayerbotAI* ai) { return new NaxxChatShortcutAction(ai); } static Action* bwl_chat_shortcut(PlayerbotAI* ai) { return new BwlChatShortcutAction(ai); } static Action* tell_expected_dps(PlayerbotAI* ai) { return new TellExpectedDpsAction(ai); } + static Action* calc(PlayerbotAI* ai) { return new TellCalculateItemAction(ai); } }; #endif diff --git a/src/strategy/actions/TellLosAction.cpp b/src/strategy/actions/TellLosAction.cpp index 1443ff43..159fb845 100644 --- a/src/strategy/actions/TellLosAction.cpp +++ b/src/strategy/actions/TellLosAction.cpp @@ -4,10 +4,16 @@ */ #include "TellLosAction.h" +#include +#include #include "ChatHelper.h" +#include "DBCStores.h" #include "Event.h" +#include "ItemTemplate.h" +#include "ObjectMgr.h" #include "Playerbots.h" +#include "StatsWeightCalculator.h" #include "World.h" bool TellLosAction::Execute(Event event) @@ -130,3 +136,21 @@ bool TellExpectedDpsAction::Execute(Event event) botAI->TellMaster("Expected Group DPS: " + std::to_string(dps)); return true; } + +bool TellCalculateItemAction::Execute(Event event) +{ + std::string const text = event.getParam(); + ItemIds ids = chat->parseItems(text); + StatsWeightCalculator calculator(bot); + for (const uint32 &id : ids) + { + const ItemTemplate* proto = sObjectMgr->GetItemTemplate(id); + if (!proto) + continue; + float score = calculator.CalculateItem(id); + std::ostringstream out; + out << "Calculated score of " << chat->FormatItem(proto) << " : " << score; + botAI->TellMasterNoFacing(out.str()); + } + return true; +} \ No newline at end of file diff --git a/src/strategy/actions/TellLosAction.h b/src/strategy/actions/TellLosAction.h index a760b921..1adc56d0 100644 --- a/src/strategy/actions/TellLosAction.h +++ b/src/strategy/actions/TellLosAction.h @@ -37,4 +37,13 @@ public: virtual bool Execute(Event event); }; + +class TellCalculateItemAction : public Action +{ +public: + TellCalculateItemAction(PlayerbotAI* ai) : Action(ai, "calculate item") {} + + virtual bool Execute(Event event); +}; + #endif diff --git a/src/strategy/generic/ChatCommandHandlerStrategy.cpp b/src/strategy/generic/ChatCommandHandlerStrategy.cpp index 09a08fca..58ee9549 100644 --- a/src/strategy/generic/ChatCommandHandlerStrategy.cpp +++ b/src/strategy/generic/ChatCommandHandlerStrategy.cpp @@ -157,4 +157,5 @@ ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : Pas supported.push_back("guild leave"); supported.push_back("rtsc"); supported.push_back("drink"); + supported.push_back("calc"); } diff --git a/src/strategy/triggers/ChatTriggerContext.h b/src/strategy/triggers/ChatTriggerContext.h index a63f1a2b..7def8b14 100644 --- a/src/strategy/triggers/ChatTriggerContext.h +++ b/src/strategy/triggers/ChatTriggerContext.h @@ -121,6 +121,7 @@ public: creators["bwl"] = &ChatTriggerContext::bwl; creators["dps"] = &ChatTriggerContext::dps; creators["disperse"] = &ChatTriggerContext::disperse; + creators["calc"] = &ChatTriggerContext::calc; } private: @@ -221,6 +222,7 @@ private: static Trigger* bwl(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "bwl"); } static Trigger* dps(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "dps"); } static Trigger* disperse(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "disperse"); } + static Trigger* calc(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "calc"); } }; #endif