From 07c5cf78de36e77bb6f22c2b126350ec29a017a1 Mon Sep 17 00:00:00 2001 From: Yunfan Li Date: Wed, 7 Aug 2024 17:45:21 +0800 Subject: [PATCH 01/14] Handle command and trade check for summoned bot --- src/PlayerbotAI.cpp | 6 +--- src/PlayerbotSecurity.cpp | 33 +++++++++++----------- src/RandomPlayerbotMgr.cpp | 3 -- src/strategy/actions/TradeStatusAction.cpp | 10 +++++-- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/PlayerbotAI.cpp b/src/PlayerbotAI.cpp index 0b4db3ef8..34fba5ed6 100644 --- a/src/PlayerbotAI.cpp +++ b/src/PlayerbotAI.cpp @@ -655,11 +655,7 @@ void PlayerbotAI::HandleCommand(uint32 type, std::string const text, Player* fro } if (!IsAllowedCommand(filtered) && - (master != fromPlayer || - !GetSecurity()->CheckLevelFor(PLAYERBOT_SECURITY_ALLOW_ALL, type != CHAT_MSG_WHISPER, fromPlayer))) - return; - - if (!IsAllowedCommand(filtered) && master != fromPlayer) + (!GetSecurity()->CheckLevelFor(PLAYERBOT_SECURITY_ALLOW_ALL, type != CHAT_MSG_WHISPER, fromPlayer))) return; if (type == CHAT_MSG_RAID_WARNING && filtered.find(bot->GetName()) != std::string::npos && diff --git a/src/PlayerbotSecurity.cpp b/src/PlayerbotSecurity.cpp index e72410040..0a045cdb7 100644 --- a/src/PlayerbotSecurity.cpp +++ b/src/PlayerbotSecurity.cpp @@ -55,14 +55,16 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea // } Group* group = from->GetGroup(); - if (group) + if (group && group == bot->GetGroup() && !ignoreGroup && botAI->GetMaster() == from) { - for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next()) - { - Player* player = gref->GetSource(); - if (player == bot && !ignoreGroup) - return PLAYERBOT_SECURITY_ALLOW_ALL; - } + return PLAYERBOT_SECURITY_ALLOW_ALL; + } + + if (group && group == bot->GetGroup() && !ignoreGroup && botAI->GetMaster() != from) + { + if (reason) + *reason = PLAYERBOT_DENY_NOT_YOURS; + return PLAYERBOT_SECURITY_TALK; } if (sPlayerbotAIConfig->groupInvitationPermission <= 0) @@ -137,13 +139,6 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea return PLAYERBOT_SECURITY_INVITE; } - for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next()) - { - Player* player = gref->GetSource(); - if (player == from) - return PLAYERBOT_SECURITY_ALLOW_ALL; - } - if (group->IsFull()) { if (reason) @@ -169,11 +164,17 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea if (reason) *reason = PLAYERBOT_DENY_INVITE; - + return PLAYERBOT_SECURITY_INVITE; } - return PLAYERBOT_SECURITY_ALLOW_ALL; + if (botAI->GetMaster() == from) + return PLAYERBOT_SECURITY_ALLOW_ALL; + + if (reason) + *reason = PLAYERBOT_DENY_NOT_YOURS; + + return PLAYERBOT_SECURITY_INVITE; } bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent, Player* from, bool ignoreGroup) diff --git a/src/RandomPlayerbotMgr.cpp b/src/RandomPlayerbotMgr.cpp index 2655052ce..29bc672d5 100644 --- a/src/RandomPlayerbotMgr.cpp +++ b/src/RandomPlayerbotMgr.cpp @@ -1215,9 +1215,6 @@ void RandomPlayerbotMgr::Revive(Player* player) void RandomPlayerbotMgr::RandomTeleport(Player* bot, std::vector& locs, bool hearth) { - if (bot->IsBeingTeleported()) - return; - if (bot->InBattleground()) return; diff --git a/src/strategy/actions/TradeStatusAction.cpp b/src/strategy/actions/TradeStatusAction.cpp index b1284a808..4076277c9 100644 --- a/src/strategy/actions/TradeStatusAction.cpp +++ b/src/strategy/actions/TradeStatusAction.cpp @@ -10,8 +10,10 @@ #include "GuildTaskMgr.h" #include "ItemUsageValue.h" #include "ItemVisitors.h" +#include "PlayerbotMgr.h" #include "PlayerbotSecurity.h" #include "Playerbots.h" +#include "RandomPlayerbotMgr.h" #include "SetCraftAction.h" bool TradeStatusAction::Execute(Event event) @@ -180,8 +182,12 @@ bool TradeStatusAction::CheckTrade() } return isGettingItem; } - - if (!sRandomPlayerbotMgr->IsRandomBot(bot)) + if (!bot->GetSession()) + { + return false; + } + uint32 accountId = bot->GetSession()->GetAccountId(); + if (!sPlayerbotAIConfig->IsInRandomAccountList(accountId)) { int32 botItemsMoney = CalculateCost(bot, true); int32 botMoney = bot->GetTradeData()->GetMoney() + botItemsMoney; From 52bd378719025fc642463f3027ad01798036d382 Mon Sep 17 00:00:00 2001 From: Yunfan Li Date: Thu, 8 Aug 2024 20:36:32 +0800 Subject: [PATCH 02/14] Rewrite stats weight and item score calculation --- src/{ => factory}/PlayerbotFactory.cpp | 1129 +----------------- src/{ => factory}/PlayerbotFactory.h | 4 - src/factory/StatsCollector.cpp | 407 +++++++ src/factory/StatsCollector.h | 76 ++ src/factory/StatsWeightCalculator.cpp | 467 ++++++++ src/factory/StatsWeightCalculator.h | 60 + src/strategy/actions/ChangeTalentsAction.cpp | 2 - src/strategy/values/ItemUsageValue.cpp | 6 +- 8 files changed, 1047 insertions(+), 1104 deletions(-) rename src/{ => factory}/PlayerbotFactory.cpp (71%) rename src/{ => factory}/PlayerbotFactory.h (95%) create mode 100644 src/factory/StatsCollector.cpp create mode 100644 src/factory/StatsCollector.h create mode 100644 src/factory/StatsWeightCalculator.cpp create mode 100644 src/factory/StatsWeightCalculator.h diff --git a/src/PlayerbotFactory.cpp b/src/factory/PlayerbotFactory.cpp similarity index 71% rename from src/PlayerbotFactory.cpp rename to src/factory/PlayerbotFactory.cpp index 4591471b8..3ecc46d24 100644 --- a/src/PlayerbotFactory.cpp +++ b/src/factory/PlayerbotFactory.cpp @@ -35,11 +35,9 @@ #include "RandomPlayerbotFactory.h" #include "SharedDefines.h" #include "SpellAuraDefines.h" +#include "StatsWeightCalculator.h" #define PLAYER_SKILL_INDEX(x) (PLAYER_SKILL_INFO_1_1 + ((x)*3)) -#define ITEM_SUBCLASS_MASK_SINGLE_HAND \ - ((1 << ITEM_SUBCLASS_WEAPON_AXE) | (1 << ITEM_SUBCLASS_WEAPON_MACE) | (1 << ITEM_SUBCLASS_WEAPON_SWORD) | \ - (1 << ITEM_SUBCLASS_WEAPON_DAGGER) | (1 << ITEM_SUBCLASS_WEAPON_FIST)) uint32 PlayerbotFactory::tradeSkills[] = {SKILL_ALCHEMY, SKILL_ENCHANTING, SKILL_SKINNING, SKILL_TAILORING, SKILL_LEATHERWORKING, SKILL_ENGINEERING, SKILL_HERBALISM, SKILL_MINING, @@ -1559,6 +1557,7 @@ void PlayerbotFactory::InitEquipment(bool incremental) } while (items[slot].size() < 25 && desiredQuality-- > ITEM_QUALITY_NORMAL); } + StatsWeightCalculator calculator(bot); for (uint8 slot = 0; slot < EQUIPMENT_SLOT_END; ++slot) { if (slot == EQUIPMENT_SLOT_TABARD || slot == EQUIPMENT_SLOT_BODY) @@ -1587,9 +1586,12 @@ void PlayerbotFactory::InitEquipment(bool incremental) float bestScoreForSlot = -1; uint32 bestItemForSlot = 0; - for (int attempts = 0; attempts < std::max((int)(ids.size() * 0.75), 1); attempts++) + for (int index = 0; index < ids.size(); index++) { - uint32 index = urand(0, ids.size() - 1); + uint32 skipProb = 25; + if (urand(0, 100) <= skipProb) + continue; + uint32 newItemId = ids[index]; uint16 dest; @@ -1599,14 +1601,34 @@ void PlayerbotFactory::InitEquipment(bool incremental) if (!CanEquipUnseenItem(slot, dest, newItemId)) continue; - - float cur_score = CalculateItemScore(newItemId, bot); + + float cur_score = calculator.CalculateItem(newItemId); if (cur_score > bestScoreForSlot) { bestScoreForSlot = cur_score; bestItemForSlot = newItemId; } } + // for (int attempts = 0; attempts < std::max((int)(ids.size() * 0.75), 1); attempts++) + // { + // uint32 index = urand(0, ids.size() - 1); + // uint32 newItemId = ids[index]; + + // uint16 dest; + + // if (oldItem && oldItem->GetTemplate()->ItemId == newItemId) + // continue; + + // if (!CanEquipUnseenItem(slot, dest, newItemId)) + // continue; + + // float cur_score = calculator.CalculateItem(newItemId); + // if (cur_score > bestScoreForSlot) + // { + // bestScoreForSlot = cur_score; + // bestItemForSlot = newItemId; + // } + // } if (bestItemForSlot == 0) { continue; @@ -3654,8 +3676,8 @@ void PlayerbotFactory::ApplyEnchantAndGemsNew(bool destoryOld) { continue; } - - float score = CalculateEnchantScore(enchant_id, bot); + StatsWeightCalculator calculator(bot); + float score = calculator.CalculateEnchant(enchant_id); if ((gemProperties->color & 1) && score >= bestGemScore[0]) { bestGemScore[0] = score; @@ -3736,8 +3758,8 @@ void PlayerbotFactory::ApplyEnchantAndGemsNew(bool destoryOld) { continue; } - - float score = CalculateEnchantScore(enchant_id, bot); + StatsWeightCalculator calculator(bot); + float score = calculator.CalculateEnchant(enchant_id); if (score >= bestScore) { bestScore = score; @@ -3872,1091 +3894,6 @@ void PlayerbotFactory::LoadEnchantContainer() } } -float PlayerbotFactory::CalculateItemScore(uint32 item_id, Player* bot) -{ - int tab = AiFactory::GetPlayerSpecTab(bot); - ItemTemplateContainer const* itemTemplates = sObjectMgr->GetItemTemplateStore(); - ItemTemplate const* proto = &itemTemplates->at(item_id); - uint8 cls = bot->getClass(); - int agility = 0, strength = 0, intellect = 0, spirit = 0; - int stamina = 0, defense = 0, dodge = 0, parry = 0, block = 0, resilience = 0; - int hit = 0, crit = 0, haste = 0, expertise = 0, attack_power = 0; - int mana_regeneration = 0, spell_power = 0, armor_penetration = 0, spell_penetration = 0; - int armor = 0; - int itemLevel = proto->ItemLevel; - int quality = proto->Quality; - int meleeDps = 0, rangeDps = 0; - float score = 0; - if (proto->IsRangedWeapon()) - { - rangeDps = (proto->Damage[0].DamageMin + proto->Damage[0].DamageMax) / 2 * 1000 / proto->Delay; - } - else if (proto->IsWeapon()) - { - meleeDps = (proto->Damage[0].DamageMin + proto->Damage[0].DamageMax) / 2 * 1000 / proto->Delay; - } - armor += proto->Armor; - block += proto->Block; - for (int i = 0; i < proto->StatsCount; i++) - { - const _ItemStat& stat = proto->ItemStat[i]; - const int32& value = stat.ItemStatValue; - switch (stat.ItemStatType) - { - case ITEM_MOD_AGILITY: - agility += value; - break; - case ITEM_MOD_STRENGTH: - strength += value; - break; - case ITEM_MOD_INTELLECT: - intellect += value; - break; - case ITEM_MOD_SPIRIT: - spirit += value; - break; - case ITEM_MOD_STAMINA: - stamina += value; - break; - case ITEM_MOD_DEFENSE_SKILL_RATING: - defense += value; - break; - case ITEM_MOD_PARRY_RATING: - parry += value; - break; - case ITEM_MOD_BLOCK_RATING: - case ITEM_MOD_BLOCK_VALUE: - block += value; - break; - case ITEM_MOD_RESILIENCE_RATING: - resilience += value; - break; - case ITEM_MOD_HIT_RATING: - hit += value; - break; - case ITEM_MOD_CRIT_RATING: - crit += value; - break; - case ITEM_MOD_HASTE_RATING: - haste += value; - break; - case ITEM_MOD_EXPERTISE_RATING: - expertise += value; - break; - case ITEM_MOD_ATTACK_POWER: - attack_power += value; - break; - case ITEM_MOD_SPELL_POWER: - spell_power += value; - break; - case ITEM_MOD_MANA_REGENERATION: - mana_regeneration += value; - break; - default: - break; - } - } - for (uint8 j = 0; j < MAX_ITEM_PROTO_SPELLS; j++) - { - score += CalculateSpellScore(proto->Spells[j].SpellId, bot, proto->Spells[j].SpellTrigger); - // SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(proto->Spells[j].SpellId); - // if (!spellInfo) - // continue; - // // spell category check? - // for (uint8 i = 0; i < 3; i++) - // { - // float multiplier = proto->Spells[j].SpellTrigger == ITEM_SPELLTRIGGER_ON_EQUIP ? 1.0f : 0.2f; - // if (spellInfo->Effects[i].Effect == SPELL_EFFECT_APPLY_AURA) - // { - // switch (spellInfo->Effects[i].ApplyAuraName) - // { - // case SPELL_AURA_MOD_DAMAGE_DONE: - // // case SPELL_AURA_MOD_HEALING_DONE: duplicated - // spell_power += (spellInfo->Effects[i].BasePoints + 1) * multiplier; - // break; - // case SPELL_AURA_MOD_ATTACK_POWER: - // attack_power += (spellInfo->Effects[i].BasePoints + 1) * multiplier; - // break; - // case SPELL_AURA_MOD_SHIELD_BLOCKVALUE: - // block += (spellInfo->Effects[i].BasePoints + 1) * multiplier; - // break; - // case SPELL_AURA_MOD_RATING: - // { - // for (uint32 rating = CR_WEAPON_SKILL; rating < MAX_COMBAT_RATING; ++rating) - // { - // if (spellInfo->Effects[j].MiscValue & (1 << rating)) - // { - // int32 val = spellInfo->Effects[j].BasePoints + 1; - // switch (rating) - // { - // case CR_DEFENSE_SKILL: - // defense += (spellInfo->Effects[i].BasePoints + 1) * multiplier; - // break; - // case CR_DODGE: - // dodge += (spellInfo->Effects[i].BasePoints + 1) * multiplier; - // break; - // case CR_PARRY: - // parry += (spellInfo->Effects[i].BasePoints + 1) * multiplier; - // break; - // case CR_BLOCK: - // block += (spellInfo->Effects[i].BasePoints + 1) * multiplier; - // break; - // case CR_HIT_MELEE: - // // if () - // break; - // case CR_HIT_RANGED: - // break; - // case CR_HIT_SPELL: - // break; - // case CR_CRIT_MELEE: - // break; - // case CR_CRIT_RANGED: - // break; - // case CR_CRIT_SPELL: - // break; - // case CR_HIT_TAKEN_MELEE: - // break; - // case CR_HIT_TAKEN_RANGED: - // break; - // // CR_PARRY = 3, - // // CR_BLOCK = 4, - // // CR_HIT_MELEE = 5, - // // CR_HIT_RANGED = 6, - // // CR_HIT_SPELL = 7, - // // CR_CRIT_MELEE = 8, - // // CR_CRIT_RANGED = 9, - // // CR_CRIT_SPELL = 10, - // // CR_HIT_TAKEN_MELEE = 11, - // // CR_HIT_TAKEN_RANGED = 12, - // // CR_HIT_TAKEN_SPELL = 13, - // // CR_CRIT_TAKEN_MELEE = 14, - // // CR_CRIT_TAKEN_RANGED = 15, - // // CR_CRIT_TAKEN_SPELL = 16, - // // CR_HASTE_MELEE = 17, - // // CR_HASTE_RANGED = 18, - // // CR_HASTE_SPELL = 19, - // // CR_WEAPON_SKILL_MAINHAND = 20, - // // CR_WEAPON_SKILL_OFFHAND = 21, - // // CR_WEAPON_SKILL_RANGED = 22, - // // CR_EXPERTISE = 23, - // // CR_ARMOR_PENETRATION = 24 - // // } - // } - // } - // break; - // } - // } - // case SPELL_AURA_PROC_TRIGGER_SPELL: - // { - // multiplier = 0.2f; - // if (spellInfo->Effects[i].TriggerSpell) { - // SpellInfo const* triggerSpellInfo = - // sSpellMgr->GetSpellInfo(spellInfo->Effects[i].TriggerSpell); if (!triggerSpellInfo) - // continue; - // for (uint8 k = 0; k < 3; i++) - // { - // if (triggerSpellInfo->Effects[k].Effect == SPELL_EFFECT_APPLY_AURA) - // { - // switch (triggerSpellInfo->Effects[k].ApplyAuraName) - // { - // case SPELL_AURA_MOD_DAMAGE_DONE: - // // case SPELL_AURA_MOD_HEALING_DONE: duplicated - // spell_power += (spellInfo->Effects[k].BasePoints + 1) * multiplier; - // break; - // case SPELL_AURA_MOD_ATTACK_POWER: - // attack_power += (spellInfo->Effects[k].BasePoints + 1) * multiplier; - // case SPELL_AURA_MOD_SHIELD_BLOCKVALUE: - // block += (spellInfo->Effects[k].BasePoints + 1) * multiplier; - // default: - // break; - // } - // } - // } - // } - // break; - // } - // default: - // break; - // } - // } - } - // Basic score - score += (agility + strength + intellect + spirit + stamina + defense + dodge + parry + block + resilience + hit + - crit + haste + expertise + attack_power + mana_regeneration + spell_power + armor_penetration + - spell_penetration + armor + rangeDps + meleeDps) * - 0.001; - // todo: remove duplicate code - if (cls == CLASS_HUNTER) - { - // AGILITY only - score += agility * 2.5 + attack_power + armor_penetration * 2 + rangeDps * 5 + hit * 2 + crit * 2 + haste * 2 + - intellect; - } - else if (cls == CLASS_WARLOCK || cls == CLASS_MAGE || (cls == CLASS_PRIEST && tab == 2) || // shadow - (cls == CLASS_SHAMAN && tab == 0) || // element - (cls == CLASS_DRUID && tab == 0) // balance - ) - { - // SPELL DPS - score += intellect * 0.5 + spirit * 0.5 + spell_power + spell_penetration + hit * 1 + crit * 0.7 + haste * 1 + - rangeDps; - } - else if ((cls == CLASS_PALADIN && tab == 0) || // holy - (cls == CLASS_PRIEST && tab != 2) || // discipline / holy - (cls == CLASS_SHAMAN && tab == 2) || // heal - (cls == CLASS_DRUID && tab == 2)) - { - // HEALER - score += - intellect * 0.5 + spirit * 0.5 + spell_power + mana_regeneration * 0.5 + crit * 0.5 + haste * 1 + rangeDps; - } - else if (cls == CLASS_ROGUE || (cls == CLASS_DRUID && tab == 2 && !PlayerbotAI::IsTank(bot))) - { - // AGILITY mainly (STRENGTH also) - score += agility * 2 + strength + attack_power + armor_penetration * 1 + meleeDps * 5 + hit * 1.5 + crit * 1.5 + - haste * 1.5 + expertise * 2.5; - } - else if ((cls == CLASS_PALADIN && tab == 2) || // retribution - (cls == CLASS_WARRIOR && tab != 2) || // arm / fury - (cls == CLASS_DEATH_KNIGHT && tab != 0) // ice / unholy - ) - { - // STRENGTH mainly (AGILITY also) - score += strength * 2 + agility + attack_power + armor_penetration + meleeDps * 5 + hit * 1.5 + crit * 1.5 + - haste * 1.5 + expertise * 2; - } - else if ((cls == CLASS_SHAMAN && tab == 1)) - { // enhancement - // STRENGTH mainly (AGILITY, INTELLECT also) - score += strength * 1 + agility * 1.5 + intellect * 1.5 + attack_power + spell_power * 1.5 + - armor_penetration * 0.5 + meleeDps * 5 + hit * 1.5 + crit * 1.5 + haste * 1.5 + expertise * 2; - } - else if ((cls == CLASS_WARRIOR && tab == 2) || (cls == CLASS_PALADIN && tab == 1)) - { - // TANK WITH SHIELD - score += strength * 1 + agility * 2 + attack_power * 0.2 + defense * 2.5 + parry * 2 + dodge * 2 + - resilience * 2 + block * 2 + armor * 0.3 + stamina * 3 + hit * 0.5 + crit * 0.2 + haste * 0.5 + - expertise * 3; - } - else if (cls == CLASS_DEATH_KNIGHT && tab == 0) - { - // BLOOD DK TANK - score += strength * 1 + agility * 2 + attack_power * 0.2 + defense * 3.5 + parry * 2 + dodge * 2 + - resilience * 2 + armor * 0.3 + stamina * 2.5 + hit * 0.5 + crit * 0.5 + haste * 0.5 + expertise * 3.5; - } - else - { - // BEAR DRUID TANK - score += agility * 1.5 + strength * 1 + attack_power * 0.5 + armor_penetration * 0.5 + meleeDps * 2 + - defense * 0.25 + dodge * 0.25 + armor * 0.3 + stamina * 1.5 + hit * 1 + crit * 1 + haste * 0.5 + - expertise * 3; - } - // penalty for different type armor - if (proto->Class == ITEM_CLASS_ARMOR && proto->SubClass >= ITEM_SUBCLASS_ARMOR_CLOTH && - proto->SubClass <= ITEM_SUBCLASS_ARMOR_PLATE && NotSameArmorType(proto->SubClass, bot)) - { - score *= 0.8; - } - // double hand - if (proto->Class == ITEM_CLASS_WEAPON) - { - bool isDoubleHand = proto->Class == ITEM_CLASS_WEAPON && - !(ITEM_SUBCLASS_MASK_SINGLE_HAND & (1 << proto->SubClass)) && - !(ITEM_SUBCLASS_MASK_WEAPON_RANGED & (1 << proto->SubClass)); - - if (isDoubleHand) - { - score *= 0.5; - } - // spec without double hand - // enhancement, rogue, ice dk, unholy dk, shield tank, fury warrior without titan's grip but with duel wield - if (isDoubleHand && - ((cls == CLASS_SHAMAN && tab == 1 && bot->CanDualWield()) || (cls == CLASS_ROGUE) || - (cls == CLASS_DEATH_KNIGHT && tab != 0) || - (cls == CLASS_WARRIOR && tab == 1 && !bot->CanTitanGrip() && bot->CanDualWield()) || IsShieldTank(bot))) - { - score *= 0.1; - } - // spec with double hand - // fury without duel wield, arms, bear, retribution, blood dk - if (isDoubleHand && ((cls == CLASS_WARRIOR && tab == WARRIOR_TAB_FURY && !bot->CanDualWield()) || - (cls == CLASS_WARRIOR && tab == WARRIOR_TAB_ARMS) || (cls == CLASS_DRUID && tab == 1) || - (cls == CLASS_PALADIN && tab == 2) || (cls == CLASS_DEATH_KNIGHT && tab == 0) || - (cls == CLASS_SHAMAN && tab == 1 && !bot->CanDualWield()))) - { - score *= 10; - } - // fury with titan's grip - if (isDoubleHand && proto->SubClass != ITEM_SUBCLASS_WEAPON_POLEARM && - (cls == CLASS_WARRIOR && tab == WARRIOR_TAB_FURY && bot->CanTitanGrip())) - { - score *= 10; - } - } - if (proto->Class == ITEM_CLASS_WEAPON) - { - if (cls == CLASS_HUNTER && proto->SubClass == ITEM_SUBCLASS_WEAPON_THROWN) - { - score *= 0.1; - } - if (cls == CLASS_ROGUE && tab == ROGUE_TAB_ASSASSINATION && proto->SubClass != ITEM_SUBCLASS_WEAPON_DAGGER) - { - score *= 0.1; - } - } - if (proto->ItemSet != 0) - { - score *= 1.1; - } - return (0.0001 + score) * itemLevel * (quality + 1); -} - -float PlayerbotFactory::CalculateEnchantScore(uint32 enchant_id, Player* bot) -{ - SpellItemEnchantmentEntry const* enchant = sSpellItemEnchantmentStore.LookupEntry(enchant_id); - if (!enchant) - { - return 0; - } - int agility = 0, strength = 0, intellect = 0, spirit = 0; - int stamina = 0, defense = 0, dodge = 0, parry = 0, block = 0, resilience = 0; - int hit = 0, crit = 0, haste = 0, expertise = 0, attack_power = 0; - int mana_regeneration = 0, spell_power = 0, armor_penetration = 0, spell_penetration = 0; - int armor = 0, dps = 0; - // int mongoose = 0, crusader = 0; - for (int s = 0; s < MAX_SPELL_ITEM_ENCHANTMENT_EFFECTS; ++s) - { - uint32 enchant_display_type = enchant->type[s]; - uint32 enchant_amount = enchant->amount[s]; - uint32 enchant_spell_id = enchant->spellid[s]; - - switch (enchant_display_type) - { - case ITEM_ENCHANTMENT_TYPE_NONE: - break; - case ITEM_ENCHANTMENT_TYPE_COMBAT_SPELL: - { - if (PlayerbotAI::IsMelee(bot) && enchant_spell_id == 28093) - { // mongoose - agility += 40; - } - else if (PlayerbotAI::IsMelee(bot) && enchant_spell_id == 20007) - { // crusader - strength += 30; - } - else if (PlayerbotAI::IsMelee(bot) && enchant_spell_id == 59620) - { // Berserk - attack_power += 120; - } - else if (PlayerbotAI::IsMelee(bot) && enchant_spell_id == 64440) - { // Blade Warding - parry += 50; - } - break; - } - // processed in Player::CastItemCombatSpell - case ITEM_ENCHANTMENT_TYPE_DAMAGE: - // if (botAI->IsRanged(bot) && !botAI->IsCaster(bot)) { - // dps += float(enchant_amount); - // } - // if (item->GetSlot() == EQUIPMENT_SLOT_MAINHAND) - // HandleStatModifier(UNIT_MOD_DAMAGE_MAINHAND, TOTAL_VALUE, float(enchant_amount), apply); - // else if (item->GetSlot() == EQUIPMENT_SLOT_OFFHAND) - // HandleStatModifier(UNIT_MOD_DAMAGE_OFFHAND, TOTAL_VALUE, float(enchant_amount), apply); - // else if (item->GetSlot() == EQUIPMENT_SLOT_RANGED) - // HandleStatModifier(UNIT_MOD_DAMAGE_RANGED, TOTAL_VALUE, float(enchant_amount), apply); - break; - case ITEM_ENCHANTMENT_TYPE_EQUIP_SPELL: - { - int allStatsAmount = 0; - switch (enchant_spell_id) - { - 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) - { - agility += allStatsAmount; - strength += allStatsAmount; - intellect += allStatsAmount; - spirit += allStatsAmount; - stamina += allStatsAmount; - } - if (PlayerbotAI::IsMelee(bot) && enchant_spell_id == 64571) - { // Blood Draining - stamina += 80; - } - // if (enchant_spell_id) - // { - // if (apply) - // { - // int32 basepoints = 0; - // // Random Property Exist - try found basepoints for spell (basepoints depends from item - // suffix factor) if (item->GetItemRandomPropertyId()) - // { - // ItemRandomSuffixEntry const* item_rand = - // sItemRandomSuffixStore.LookupEntry(std::abs(item->GetItemRandomPropertyId())); if - // (item_rand) - // { - // // Search enchant_amount - // for (int k = 0; k < MAX_ITEM_ENCHANTMENT_EFFECTS; ++k) - // { - // if (item_rand->Enchantment[k] == enchant_id) - // { - // basepoints = int32((item_rand->AllocationPct[k] * - // item->GetItemSuffixFactor()) / 10000); break; - // } - // } - // } - // } - // // Cast custom spell vs all equal basepoints got from enchant_amount - // if (basepoints) - // CastCustomSpell(this, enchant_spell_id, &basepoints, &basepoints, &basepoints, true, - // item); - // else - // CastSpell(this, enchant_spell_id, true, item); - // } - // else - // RemoveAurasDueToItemSpell(enchant_spell_id, item->GetGUID()); - // } - break; - } - case ITEM_ENCHANTMENT_TYPE_RESISTANCE: - // if (!enchant_amount) - // { - // ItemRandomSuffixEntry const* item_rand = - // sItemRandomSuffixStore.LookupEntry(std::abs(item->GetItemRandomPropertyId())); if (item_rand) - // { - // for (int k = 0; k < MAX_ITEM_ENCHANTMENT_EFFECTS; ++k) - // { - // if (item_rand->Enchantment[k] == enchant_id) - // { - // enchant_amount = uint32((item_rand->AllocationPct[k] * item->GetItemSuffixFactor()) / - // 10000); break; - // } - // } - // } - // } - - // HandleStatModifier(UnitMods(UNIT_MOD_RESISTANCE_START + enchant_spell_id), TOTAL_VALUE, - // float(enchant_amount), apply); - break; - case ITEM_ENCHANTMENT_TYPE_STAT: - { - if (!enchant_amount) - { - // ItemRandomSuffixEntry const* item_rand_suffix = - // sItemRandomSuffixStore.LookupEntry(std::abs(item->GetItemRandomPropertyId())); if - // (item_rand_suffix) - // { - // for (int k = 0; k < MAX_ITEM_ENCHANTMENT_EFFECTS; ++k) - // { - // if (item_rand_suffix->Enchantment[k] == enchant_id) - // { - // enchant_amount = uint32((item_rand_suffix->AllocationPct[k] * - // item->GetItemSuffixFactor()) / 10000); break; - // } - // } - // } - break; - } - switch (enchant_spell_id) - { - case ITEM_MOD_MANA: - break; - case ITEM_MOD_HEALTH: - break; - case ITEM_MOD_AGILITY: - agility += float(enchant_amount); - break; - case ITEM_MOD_STRENGTH: - strength += float(enchant_amount); - break; - case ITEM_MOD_INTELLECT: - intellect += float(enchant_amount); - break; - case ITEM_MOD_SPIRIT: - spirit += float(enchant_amount); - break; - case ITEM_MOD_STAMINA: - stamina += float(enchant_amount); - break; - case ITEM_MOD_DEFENSE_SKILL_RATING: - defense += float(enchant_amount); - break; - case ITEM_MOD_DODGE_RATING: - dodge += float(enchant_amount); - break; - case ITEM_MOD_PARRY_RATING: - parry += float(enchant_amount); - break; - case ITEM_MOD_BLOCK_RATING: - block += float(enchant_amount); - break; - case ITEM_MOD_HIT_MELEE_RATING: - if (PlayerbotAI::IsMelee(bot)) - { - hit += float(enchant_amount); - } - break; - case ITEM_MOD_HIT_RANGED_RATING: - if (PlayerbotAI::IsRanged(bot) && !PlayerbotAI::IsCaster(bot)) - { - hit += float(enchant_amount); - } - break; - case ITEM_MOD_HIT_SPELL_RATING: - if (PlayerbotAI::IsCaster(bot)) - { - hit += float(enchant_amount); - } - break; - case ITEM_MOD_CRIT_MELEE_RATING: - if (PlayerbotAI::IsMelee(bot)) - { - crit += float(enchant_amount); - } - break; - case ITEM_MOD_CRIT_RANGED_RATING: - if (PlayerbotAI::IsRanged(bot) && !PlayerbotAI::IsCaster(bot)) - { - crit += float(enchant_amount); - } - break; - case ITEM_MOD_CRIT_SPELL_RATING: - if (PlayerbotAI::IsCaster(bot)) - { - crit += float(enchant_amount); - } - break; - case ITEM_MOD_HASTE_RANGED_RATING: - if (PlayerbotAI::IsRanged(bot) && !PlayerbotAI::IsCaster(bot)) - { - crit += float(enchant_amount); - } - break; - case ITEM_MOD_HASTE_SPELL_RATING: - if (PlayerbotAI::IsCaster(bot)) - { - haste += float(enchant_amount); - } - break; - case ITEM_MOD_HIT_RATING: - hit += float(enchant_amount); - break; - case ITEM_MOD_CRIT_RATING: - crit += float(enchant_amount); - break; - case ITEM_MOD_RESILIENCE_RATING: - resilience += float(enchant_amount); - break; - case ITEM_MOD_HASTE_RATING: - haste += float(enchant_amount); - break; - case ITEM_MOD_EXPERTISE_RATING: - expertise += float(enchant_amount); - break; - case ITEM_MOD_ATTACK_POWER: - attack_power += float(enchant_amount); - break; - case ITEM_MOD_RANGED_ATTACK_POWER: - if (PlayerbotAI::IsRanged(bot) && !PlayerbotAI::IsCaster(bot)) - { - attack_power += float(enchant_amount); - } - break; - case ITEM_MOD_MANA_REGENERATION: - mana_regeneration += float(enchant_amount); - break; - case ITEM_MOD_ARMOR_PENETRATION_RATING: - armor_penetration += float(enchant_amount); - break; - case ITEM_MOD_SPELL_POWER: - spell_power += float(enchant_amount); - break; - case ITEM_MOD_HEALTH_REGEN: - // no calculation - break; - case ITEM_MOD_SPELL_PENETRATION: - spell_penetration += float(enchant_amount); - break; - case ITEM_MOD_BLOCK_VALUE: - block += float(enchant_amount); - break; - case ITEM_MOD_SPELL_HEALING_DONE: // deprecated - case ITEM_MOD_SPELL_DAMAGE_DONE: // deprecated - default: - break; - } - break; - } - case ITEM_ENCHANTMENT_TYPE_TOTEM: // Shaman Rockbiter Weapon - { - // if (IsClass(CLASS_SHAMAN, CLASS_CONTEXT_ABILITY)) - // { - // float addValue = 0.0f; - // if (item->GetSlot() == EQUIPMENT_SLOT_MAINHAND) - // { - // addValue = float(enchant_amount * item->GetTemplate()->Delay / 1000.0f); - // HandleStatModifier(UNIT_MOD_DAMAGE_MAINHAND, TOTAL_VALUE, addValue, apply); - // } - // else if (item->GetSlot() == EQUIPMENT_SLOT_OFFHAND) - // { - // addValue = float(enchant_amount * item->GetTemplate()->Delay / 1000.0f); - // HandleStatModifier(UNIT_MOD_DAMAGE_OFFHAND, TOTAL_VALUE, addValue, apply); - // } - // } - break; - } - case ITEM_ENCHANTMENT_TYPE_USE_SPELL: - // processed in Player::CastItemUseSpell - break; - case ITEM_ENCHANTMENT_TYPE_PRISMATIC_SOCKET: - // nothing do.. - break; - default: - // LOG_ERROR("entities.player", "Unknown item enchantment (id = {}) display type: {}", enchant_id, - // enchant_display_type); - break; - } /*switch (enchant_display_type)*/ - } - int tab = AiFactory::GetPlayerSpecTab(bot); - uint8 cls = bot->getClass(); - // Basic score - float score = (agility + strength + intellect + spirit + stamina + defense + dodge + parry + block + resilience + - hit + crit + haste + expertise + attack_power + mana_regeneration + spell_power + armor_penetration + - spell_penetration + armor + dps) * - 0.001; - // todo: remove duplicate code - if (cls == CLASS_HUNTER) - { - // AGILITY only - score += agility * 2.5 + attack_power + armor_penetration * 2 + dps * 5 + hit * 2 + crit * 2 + haste * 2.5 + - intellect; - } - else if (cls == CLASS_WARLOCK || cls == CLASS_MAGE || (cls == CLASS_PRIEST && tab == 2) || // shadow - (cls == CLASS_SHAMAN && tab == 0) || // element - (cls == CLASS_DRUID && tab == 0) // balance - ) - { - // SPELL DPS - score += intellect * 0.5 + spirit * 0.5 + spell_power + spell_penetration + hit * 1 + crit * 0.7 + haste * 1; - } - else if ((cls == CLASS_PALADIN && tab == 0) || // holy - (cls == CLASS_PRIEST && tab != 2) || // discipline / holy - (cls == CLASS_SHAMAN && tab == 2) || // heal - (cls == CLASS_DRUID && tab == 2)) - { - // HEALER - score += intellect * 0.5 + spirit * 0.5 + spell_power + mana_regeneration * 0.5 + crit * 0.5 + haste * 1; - } - else if (cls == CLASS_ROGUE || (cls == CLASS_DRUID && tab == 2 && !PlayerbotAI::IsTank(bot))) - { - // AGILITY mainly (STRENGTH also) - score += agility * 2 + strength + attack_power + armor_penetration * 1 + dps * 5 + hit * 1.5 + crit * 1.5 + - haste * 1.5 + expertise * 2.5; - } - else if ((cls == CLASS_PALADIN && tab == 2) || // retribution - (cls == CLASS_WARRIOR && tab != 2) || // arm / fury - (cls == CLASS_DEATH_KNIGHT && tab != 0) // ice / unholy - ) - { - // STRENGTH mainly (AGILITY also) - score += strength * 2 + agility + attack_power + armor_penetration + dps * 5 + hit * 1.5 + crit * 1.5 + - haste * 1.5 + expertise * 2; - } - else if ((cls == CLASS_SHAMAN && tab == 1)) - { // enhancement - // STRENGTH mainly (AGILITY, INTELLECT also) - score += strength * 1 + agility * 1.5 + intellect * 1.5 + attack_power + spell_power * 1.5 + - armor_penetration * 0.5 + dps * 5 + hit * 1.5 + crit * 1.5 + haste * 1.5 + expertise * 2; - } - else if ((cls == CLASS_WARRIOR && tab == 2) || (cls == CLASS_PALADIN && tab == 1)) - { - // TANK WITH SHIELD - score += strength * 1 + agility * 2 + attack_power * 0.2 + defense * 2.5 + parry * 2 + dodge * 2 + - resilience * 2 + block * 2 + armor * 0.3 + stamina * 3 + hit * 0.5 + crit * 0.2 + haste * 0.5 + - expertise * 3; - } - else if (cls == CLASS_DEATH_KNIGHT && tab == 0) - { - // BLOOD DK TANK - score += strength * 1 + agility * 2 + attack_power * 0.2 + defense * 3.5 + parry * 2 + dodge * 2 + - resilience * 2 + armor * 0.3 + stamina * 2.5 + hit * 0.5 + crit * 0.5 + haste * 0.5 + expertise * 3.5; - } - else - { - // BEAR DRUID TANK - score += agility * 1.5 + strength * 1 + attack_power * 0.5 + armor_penetration * 0.5 + dps * 2 + - defense * 0.25 + dodge * 0.25 + armor * 0.3 + stamina * 1.5 + hit * 1 + crit * 1 + haste * 0.5 + - expertise * 3; - } - return score; -} - -float PlayerbotFactory::CalculateSpellScore(uint32 spell_id, Player* bot, uint32 trigger) -{ - SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spell_id); - if (!spellInfo) - return 0.0f; - int tab = AiFactory::GetPlayerSpecTab(bot); - uint8 cls = bot->getClass(); - int agility = 0, strength = 0, intellect = 0, spirit = 0; - int stamina = 0, defense = 0, dodge = 0, parry = 0, block = 0, resilience = 0; - int hit = 0, crit = 0, haste = 0, expertise = 0, attack_power = 0; - int mana_regeneration = 0, spell_power = 0, armor_penetration = 0, spell_penetration = 0; - int armor = 0; - int meleeDps = 0, rangeDps = 0; - bool isMelee = PlayerbotAI::IsMelee(bot); - bool isRanged = PlayerbotAI::IsRanged(bot); - bool isCaster = PlayerbotAI::IsCaster(bot); - for (int i = 0; i < MAX_SPELL_EFFECTS; i++) - { - float multiplier = trigger == ITEM_SPELLTRIGGER_ON_EQUIP ? 1.0f : 0.2f; - if (spellInfo->Effects[i].Effect == SPELL_EFFECT_APPLY_AURA) - { - int32 val = spellInfo->Effects[i].BasePoints + 1; - switch (spellInfo->Effects[i].ApplyAuraName) - { - case SPELL_AURA_MOD_DAMAGE_DONE: - // case SPELL_AURA_MOD_HEALING_DONE: duplicated - spell_power += val * multiplier; - break; - case SPELL_AURA_MOD_ATTACK_POWER: - attack_power += val * multiplier; - break; - case SPELL_AURA_MOD_SHIELD_BLOCKVALUE: - block += val * multiplier; - break; - case SPELL_AURA_MOD_RATING: - { - for (uint32 rating = CR_WEAPON_SKILL; rating < MAX_COMBAT_RATING; ++rating) - { - if (spellInfo->Effects[i].MiscValue & (1 << rating)) - { - switch (rating) - { - case CR_DEFENSE_SKILL: - defense += val * multiplier; - break; - case CR_DODGE: - dodge += val * multiplier; - break; - case CR_PARRY: - parry += val * multiplier; - break; - case CR_BLOCK: - block += val * multiplier; - break; - case CR_HIT_MELEE: - if (isMelee) - { - hit += val * multiplier; - } - break; - case CR_HIT_RANGED: - if (isRanged && !isCaster) - { - hit += val * multiplier; - } - break; - case CR_HIT_SPELL: - if (isCaster) - { - hit += val * multiplier; - } - break; - case CR_CRIT_MELEE: - if (isMelee) - { - crit += val * multiplier; - } - break; - case CR_CRIT_RANGED: - if (isRanged && !isCaster) - { - crit += val * multiplier; - } - break; - case CR_CRIT_SPELL: - if (isCaster) - { - crit += val * multiplier; - } - break; - case CR_HASTE_MELEE: - if (isMelee) - { - haste += val * multiplier; - } - break; - case CR_HASTE_RANGED: - if (isRanged && !isCaster) - { - haste += val * multiplier; - } - break; - case CR_HASTE_SPELL: - if (isCaster) - { - haste += val * multiplier; - } - break; - case CR_EXPERTISE: - expertise += val * multiplier; - break; - case CR_ARMOR_PENETRATION: - armor_penetration += val * multiplier; - break; - default: - break; - } - } - break; - } - } - case SPELL_AURA_PROC_TRIGGER_SPELL: - { - multiplier = 0.2f; - if (spellInfo->Effects[i].TriggerSpell) - { - SpellInfo const* triggerSpellInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[i].TriggerSpell); - if (!triggerSpellInfo) - continue; - for (uint8 k = 0; k < MAX_SPELL_EFFECTS; k++) - { - if (triggerSpellInfo->Effects[k].Effect == SPELL_EFFECT_APPLY_AURA) - { - switch (triggerSpellInfo->Effects[k].ApplyAuraName) - { - case SPELL_AURA_MOD_DAMAGE_DONE: - // case SPELL_AURA_MOD_HEALING_DONE: duplicated - spell_power += val * multiplier; - break; - case SPELL_AURA_MOD_ATTACK_POWER: - attack_power += val * multiplier; - break; - case SPELL_AURA_MOD_SHIELD_BLOCKVALUE: - block += val * multiplier; - break; - case SPELL_AURA_MOD_RATING: - { - for (uint32 rating = CR_WEAPON_SKILL; rating < MAX_COMBAT_RATING; ++rating) - { - if (triggerSpellInfo->Effects[k].MiscValue & (1 << rating)) - { - switch (rating) - { - case CR_DEFENSE_SKILL: - defense += val * multiplier; - break; - case CR_DODGE: - dodge += val * multiplier; - break; - case CR_PARRY: - parry += val * multiplier; - break; - case CR_BLOCK: - block += val * multiplier; - break; - case CR_HIT_MELEE: - if (isMelee) - { - hit += val * multiplier; - } - break; - case CR_HIT_RANGED: - if (isRanged && !isCaster) - { - hit += val * multiplier; - } - break; - case CR_HIT_SPELL: - if (isCaster) - { - hit += val * multiplier; - } - break; - case CR_CRIT_MELEE: - if (isMelee) - { - crit += val * multiplier; - } - break; - case CR_CRIT_RANGED: - if (isRanged && !isCaster) - { - crit += val * multiplier; - } - break; - case CR_CRIT_SPELL: - if (isCaster) - { - crit += val * multiplier; - } - break; - case CR_HASTE_MELEE: - if (isMelee) - { - haste += val * multiplier; - } - break; - case CR_HASTE_RANGED: - if (isRanged && !isCaster) - { - haste += val * multiplier; - } - break; - case CR_HASTE_SPELL: - if (isCaster) - { - haste += val * multiplier; - } - break; - case CR_EXPERTISE: - expertise += val * multiplier; - break; - case CR_ARMOR_PENETRATION: - armor_penetration += val * multiplier; - break; - default: - break; - } - } - break; - } - } - default: - break; - } - } - } - } - break; - } - default: - break; - } - } - } - float score = 0; - // todo: remove duplicate code - if (cls == CLASS_HUNTER) - { - // AGILITY only - score += agility * 2.5 + attack_power + armor_penetration * 2 + rangeDps * 5 + hit * 2 + crit * 2 + - haste * 2.5 + intellect; - } - else if (cls == CLASS_WARLOCK || cls == CLASS_MAGE || (cls == CLASS_PRIEST && tab == 2) || // shadow - (cls == CLASS_SHAMAN && tab == 0) || // element - (cls == CLASS_DRUID && tab == 0) // balance - ) - { - // SPELL DPS - score += intellect * 0.5 + spirit * 0.5 + spell_power + spell_penetration + hit * 1 + crit * 0.7 + haste * 1 + - rangeDps; - } - else if ((cls == CLASS_PALADIN && tab == 0) || // holy - (cls == CLASS_PRIEST && tab != 2) || // discipline / holy - (cls == CLASS_SHAMAN && tab == 2) || // heal - (cls == CLASS_DRUID && tab == 2)) - { - // HEALER - score += - intellect * 0.5 + spirit * 0.5 + spell_power + mana_regeneration * 0.5 + crit * 0.5 + haste * 1 + rangeDps; - } - else if (cls == CLASS_ROGUE || (cls == CLASS_DRUID && tab == 2 && !PlayerbotAI::IsTank(bot))) - { - // AGILITY mainly (STRENGTH also) - score += agility * 2 + strength + attack_power + armor_penetration * 1 + meleeDps * 5 + hit * 1.5 + crit * 1.5 + - haste * 1.5 + expertise * 2.5; - } - else if ((cls == CLASS_PALADIN && tab == 2) || // retribution - (cls == CLASS_WARRIOR && tab != 2) || // arm / fury - (cls == CLASS_DEATH_KNIGHT && tab != 0) // ice / unholy - ) - { - // STRENGTH mainly (AGILITY also) - score += strength * 2 + agility + attack_power + armor_penetration + meleeDps * 5 + hit * 1.5 + crit * 1.5 + - haste * 1.5 + expertise * 2; - } - else if ((cls == CLASS_SHAMAN && tab == 1)) - { // enhancement - // STRENGTH mainly (AGILITY, INTELLECT also) - score += strength * 1 + agility * 1.5 + intellect * 1.5 + attack_power + spell_power * 1.5 + - armor_penetration * 0.5 + meleeDps * 5 + hit * 1.5 + crit * 1.5 + haste * 1.5 + expertise * 2; - } - else if ((cls == CLASS_WARRIOR && tab == 2) || (cls == CLASS_PALADIN && tab == 1)) - { - // TANK WITH SHIELD - score += strength * 1 + agility * 2 + attack_power * 0.2 + defense * 2.5 + parry * 2 + dodge * 2 + - resilience * 2 + block * 2 + armor * 0.3 + stamina * 3 + hit * 0.5 + crit * 0.2 + haste * 0.5 + - expertise * 3; - } - else if (cls == CLASS_DEATH_KNIGHT && tab == 0) - { - // BLOOD DK TANK - score += strength * 1 + agility * 2 + attack_power * 0.2 + defense * 3.5 + parry * 2 + dodge * 2 + - resilience * 2 + armor * 0.3 + stamina * 2.5 + hit * 0.5 + crit * 0.5 + haste * 0.5 + expertise * 3.5; - } - else - { - // BEAR DRUID TANK - score += agility * 1.5 + strength * 1 + attack_power * 0.5 + armor_penetration * 0.5 + meleeDps * 2 + - defense * 0.25 + dodge * 0.25 + armor * 0.3 + stamina * 1.5 + hit * 1 + crit * 1 + haste * 0.5 + - expertise * 3; - } - return score; -} - -bool PlayerbotFactory::IsShieldTank(Player* bot) -{ - int tab = AiFactory::GetPlayerSpecTab(bot); - return (bot->getClass() == CLASS_WARRIOR && tab == 2) || (bot->getClass() == CLASS_PALADIN && tab == 1); -} - -bool PlayerbotFactory::NotSameArmorType(uint32 item_subclass_armor, Player* bot) -{ - if (bot->HasSkill(SKILL_PLATE_MAIL)) - { - return item_subclass_armor != ITEM_SUBCLASS_ARMOR_PLATE; - } - if (bot->HasSkill(SKILL_MAIL)) - { - return item_subclass_armor != ITEM_SUBCLASS_ARMOR_MAIL; - } - if (bot->HasSkill(SKILL_LEATHER)) - { - return item_subclass_armor != ITEM_SUBCLASS_ARMOR_LEATHER; - } - return false; -} - void PlayerbotFactory::IterateItems(IterateItemsVisitor* visitor, IterateItemsMask mask) { if (mask & ITERATE_ITEMS_IN_BAGS) diff --git a/src/PlayerbotFactory.h b/src/factory/PlayerbotFactory.h similarity index 95% rename from src/PlayerbotFactory.h rename to src/factory/PlayerbotFactory.h index 841c5afbf..664c2b3de 100644 --- a/src/PlayerbotFactory.h +++ b/src/factory/PlayerbotFactory.h @@ -116,9 +116,7 @@ class PlayerbotFactory void InitSkills(); static uint32 tradeSkills[]; - static float CalculateItemScore(uint32 item_id, Player* bot); static float CalculateEnchantScore(uint32 enchant_id, Player* bot); - static float CalculateSpellScore(uint32 spell_id, Player* bot, uint32 trigger = ITEM_SPELLTRIGGER_ON_EQUIP); void InitTalentsTree(bool incremental = false, bool use_template = true, bool reset = false); static void InitTalentsBySpecNo(Player* bot, int specNo, bool reset); static void InitTalentsByParsedSpecLink(Player* bot, std::vector> parsedSpecLink, bool reset); @@ -181,8 +179,6 @@ class PlayerbotFactory void ApplyEnchantTemplate(); void ApplyEnchantTemplate(uint8 spec); std::vector GetPossibleInventoryTypeListBySlot(EquipmentSlots slot); - static bool IsShieldTank(Player* bot); - static bool NotSameArmorType(uint32 item_subclass_armor, Player* bot); void IterateItems(IterateItemsVisitor* visitor, IterateItemsMask mask = ITERATE_ITEMS_IN_BAGS); void IterateItemsInBags(IterateItemsVisitor* visitor); void IterateItemsInEquip(IterateItemsVisitor* visitor); diff --git a/src/factory/StatsCollector.cpp b/src/factory/StatsCollector.cpp new file mode 100644 index 000000000..86c54476b --- /dev/null +++ b/src/factory/StatsCollector.cpp @@ -0,0 +1,407 @@ +#include "StatsCollector.h" + +#include + +#include "DBCStores.h" +#include "ItemTemplate.h" +#include "ObjectMgr.h" +#include "SpellInfo.h" +#include "SpellMgr.h" + +StatsCollector::StatsCollector(CollectorType type) : type_(type) { Reset(); } + +void StatsCollector::Reset() +{ + for (uint32 i = 0; i < STATS_TYPE_MAX; i++) + { + stats[i] = 0; + } +} + +void StatsCollector::CollectItemStats(ItemTemplate const* proto) +{ + if (proto->IsRangedWeapon()) + { + uint32 val = (proto->Damage[0].DamageMin + proto->Damage[0].DamageMax) * 1000 / 2 / proto->Delay; + stats[STATS_TYPE_RANGED_DPS] += val; + } + else if (proto->IsWeapon()) + { + uint32 val = (proto->Damage[0].DamageMin + proto->Damage[0].DamageMax) * 1000 / 2 / proto->Delay; + stats[STATS_TYPE_MELEE_DPS] += val; + } + stats[STATS_TYPE_ARMOR] += proto->Armor; + stats[STATS_TYPE_BLOCK_VALUE] += proto->Block; + for (int i = 0; i < proto->StatsCount; i++) + { + const _ItemStat& stat = proto->ItemStat[i]; + const int32& val = stat.ItemStatValue; + CollectByItemStatType(stat.ItemStatType, val); + } + for (uint8 j = 0; j < MAX_ITEM_PROTO_SPELLS; j++) + { + CollectSpellStats(proto->Spells[j].SpellId, proto->Spells[j].SpellTrigger); + } + + if (proto->socketBonus) + { + if (const SpellItemEnchantmentEntry *enchant = sSpellItemEnchantmentStore.LookupEntry(proto->socketBonus)) + CollectEnchantStats(enchant); + } +} + +void StatsCollector::CollectSpellStats(uint32 spellId, uint32 trigger) +{ + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); + + if (!spellInfo) + return; + + if (CollectSpecialCaseSpellStats(spellId)) + return; + + for (int i = 0; i < MAX_SPELL_EFFECTS; i++) + { + float multiplier = trigger == ITEM_SPELLTRIGGER_ON_EQUIP ? 1.0f : 0.2f; + CollectSpellEffectStats(spellInfo->Effects[i], multiplier); + } +} + +void StatsCollector::CollectSpellEffectStats(const SpellEffectInfo& effectInfo, float multiplier) +{ + if (effectInfo.Effect != SPELL_EFFECT_APPLY_AURA) + return; + + int32 val = effectInfo.BasePoints + 1; + switch (effectInfo.ApplyAuraName) + { + case SPELL_AURA_MOD_DAMAGE_DONE: + // case SPELL_AURA_MOD_HEALING_DONE is duplicated + stats[STATS_TYPE_SPELL_POWER] += val * multiplier; + break; + case SPELL_AURA_MOD_ATTACK_POWER: + stats[STATS_TYPE_ATTACK_POWER] += val * multiplier; + break; + case SPELL_AURA_MOD_SHIELD_BLOCKVALUE: + stats[STATS_TYPE_BLOCK_VALUE] += val * multiplier; + break; + case SPELL_AURA_MOD_RATING: + { + for (uint32 rating = CR_WEAPON_SKILL; rating < MAX_COMBAT_RATING; ++rating) + { + if (effectInfo.MiscValue & (1 << rating)) + { + switch (rating) + { + case CR_DEFENSE_SKILL: + stats[STATS_TYPE_DEFENSE] += val * multiplier; + break; + case CR_DODGE: + stats[STATS_TYPE_DODGE] += val * multiplier; + break; + case CR_PARRY: + stats[STATS_TYPE_PARRY] += val * multiplier; + break; + case CR_BLOCK: + stats[STATS_TYPE_BLOCK_RATING] += val * multiplier; + break; + case CR_HIT_MELEE: + if (type_ == CollectorType::MELEE) + stats[STATS_TYPE_HIT] += val * multiplier; + break; + case CR_HIT_RANGED: + if (type_ == CollectorType::RANGED) + stats[STATS_TYPE_HIT] += val * multiplier; + break; + case CR_HIT_SPELL: + if (type_ == CollectorType::SPELL) + stats[STATS_TYPE_HIT] += val * multiplier; + break; + case CR_CRIT_MELEE: + if (type_ == CollectorType::MELEE) + stats[STATS_TYPE_CRIT] += val * multiplier; + break; + case CR_CRIT_RANGED: + if (type_ == CollectorType::RANGED) + stats[STATS_TYPE_CRIT] += val * multiplier; + break; + case CR_CRIT_SPELL: + if (type_ == CollectorType::SPELL) + stats[STATS_TYPE_CRIT] += val * multiplier; + break; + case CR_HASTE_MELEE: + if (type_ == CollectorType::MELEE) + stats[STATS_TYPE_HASTE] += val * multiplier; + break; + case CR_HASTE_RANGED: + if (type_ == CollectorType::RANGED) + stats[STATS_TYPE_HASTE] += val * multiplier; + break; + case CR_HASTE_SPELL: + if (type_ == CollectorType::SPELL) + stats[STATS_TYPE_HASTE] += val * multiplier; + break; + case CR_EXPERTISE: + stats[STATS_TYPE_EXPERTISE] += val * multiplier; + break; + case CR_ARMOR_PENETRATION: + stats[STATS_TYPE_ARMOR_PENETRATION] += val * multiplier; + break; + default: + break; + } + } + break; + } + } + case SPELL_AURA_PROC_TRIGGER_SPELL: + { + multiplier = 0.2f; + if (effectInfo.TriggerSpell) + { + SpellInfo const* triggerSpellInfo = sSpellMgr->GetSpellInfo(effectInfo.TriggerSpell); + if (!triggerSpellInfo) + return; + for (uint8 k = 0; k < MAX_SPELL_EFFECTS; k++) + { + CollectSpellEffectStats(triggerSpellInfo->Effects[k], multiplier); + } + } + break; + } + default: + break; + } +} + +void StatsCollector::CollectEnchantStats(SpellItemEnchantmentEntry const* enchant) +{ + for (int s = 0; s < MAX_SPELL_ITEM_ENCHANTMENT_EFFECTS; ++s) + { + uint32 enchant_display_type = enchant->type[s]; + uint32 enchant_amount = enchant->amount[s]; + uint32 enchant_spell_id = enchant->spellid[s]; + + if (CollectSpecialEnchantSpellStats(enchant_spell_id)) + continue; + + switch (enchant_display_type) + { + case ITEM_ENCHANTMENT_TYPE_STAT: + { + if (!enchant_amount) + { + break; + } + CollectByItemStatType(enchant_spell_id, enchant_amount); + break; + } + default: + break; + } + } +} + +/// @todo Special case for trinket +bool StatsCollector::CollectSpecialCaseSpellStats(uint32 spellId) { return false; } + +bool StatsCollector::CollectSpecialEnchantSpellStats(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 64571: + if (type_ == CollectorType::MELEE) + { + stats[STATS_TYPE_STAMINA] += 50; + } + return true; + 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; +} + +void StatsCollector::CollectByItemStatType(uint32 itemStatType, int32 val) +{ + switch (itemStatType) + { + case ITEM_MOD_MANA: + break; + case ITEM_MOD_HEALTH: + break; + case ITEM_MOD_AGILITY: + stats[STATS_TYPE_AGILITY] += val; + break; + case ITEM_MOD_STRENGTH: + stats[STATS_TYPE_STRENGTH] += val; + break; + case ITEM_MOD_INTELLECT: + stats[STATS_TYPE_INTELLECT] += val; + break; + case ITEM_MOD_SPIRIT: + stats[STATS_TYPE_SPIRIT] += val; + break; + case ITEM_MOD_STAMINA: + stats[STATS_TYPE_STAMINA] += val; + break; + case ITEM_MOD_DEFENSE_SKILL_RATING: + stats[STATS_TYPE_DEFENSE] += val; + break; + case ITEM_MOD_DODGE_RATING: + stats[STATS_TYPE_DODGE] += val; + break; + case ITEM_MOD_PARRY_RATING: + stats[STATS_TYPE_PARRY] += val; + break; + case ITEM_MOD_BLOCK_RATING: + stats[STATS_TYPE_BLOCK_RATING] += val; + break; + case ITEM_MOD_HIT_MELEE_RATING: + if (type_ == CollectorType::MELEE) + stats[STATS_TYPE_HIT] += val; + break; + case ITEM_MOD_HIT_RANGED_RATING: + if (type_ == CollectorType::RANGED) + stats[STATS_TYPE_HIT] += val; + break; + case ITEM_MOD_HIT_SPELL_RATING: + if (type_ == CollectorType::SPELL) + stats[STATS_TYPE_HIT] += val; + break; + case ITEM_MOD_CRIT_MELEE_RATING: + if (type_ == CollectorType::MELEE) + stats[STATS_TYPE_CRIT] += val; + break; + case ITEM_MOD_CRIT_RANGED_RATING: + if (type_ == CollectorType::RANGED) + stats[STATS_TYPE_CRIT] += val; + break; + case ITEM_MOD_CRIT_SPELL_RATING: + if (type_ == CollectorType::SPELL) + stats[STATS_TYPE_CRIT] += val; + break; + case ITEM_MOD_HASTE_MELEE_RATING: + if (type_ == CollectorType::MELEE) + stats[STATS_TYPE_HASTE] += val; + break; + case ITEM_MOD_HASTE_RANGED_RATING: + if (type_ == CollectorType::RANGED) + stats[STATS_TYPE_HASTE] += val; + break; + case ITEM_MOD_HASTE_SPELL_RATING: + if (type_ == CollectorType::SPELL) + stats[STATS_TYPE_HASTE] += val; + break; + case ITEM_MOD_HIT_RATING: + stats[STATS_TYPE_HIT] += val; + break; + case ITEM_MOD_CRIT_RATING: + stats[STATS_TYPE_CRIT] += val; + break; + case ITEM_MOD_RESILIENCE_RATING: + stats[STATS_TYPE_RESILIENCE] += val; + break; + case ITEM_MOD_HASTE_RATING: + stats[STATS_TYPE_HASTE] += val; + break; + case ITEM_MOD_EXPERTISE_RATING: + stats[STATS_TYPE_EXPERTISE] += val; + break; + case ITEM_MOD_ATTACK_POWER: + stats[STATS_TYPE_ATTACK_POWER] += val; + break; + case ITEM_MOD_RANGED_ATTACK_POWER: + if (type_ == CollectorType::RANGED) + stats[STATS_TYPE_ATTACK_POWER] += val; + break; + case ITEM_MOD_MANA_REGENERATION: + stats[STATS_TYPE_MANA_REGENERATION] += val; + break; + case ITEM_MOD_ARMOR_PENETRATION_RATING: + stats[STATS_TYPE_ARMOR_PENETRATION] += val; + break; + case ITEM_MOD_SPELL_POWER: + stats[STATS_TYPE_SPELL_POWER] += val; + break; + case ITEM_MOD_HEALTH_REGEN: + stats[STATS_TYPE_HEALTH_REGENERATION] += val; + break; + case ITEM_MOD_SPELL_PENETRATION: + stats[STATS_TYPE_SPELL_PENETRATION] += val; + break; + case ITEM_MOD_BLOCK_VALUE: + stats[STATS_TYPE_BLOCK_VALUE] += val; + break; + case ITEM_MOD_SPELL_HEALING_DONE: // deprecated + case ITEM_MOD_SPELL_DAMAGE_DONE: // deprecated + default: + break; + } +} \ No newline at end of file diff --git a/src/factory/StatsCollector.h b/src/factory/StatsCollector.h new file mode 100644 index 000000000..795f6a937 --- /dev/null +++ b/src/factory/StatsCollector.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU GPL v2 license, you may redistribute it + * and/or modify it under version 2 of the License, or (at your option), any later version. + */ + +#ifndef _PLAYERBOT_STATSCOLLECTOR_H +#define _PLAYERBOT_STATSCOLLECTOR_H + +#include "ItemTemplate.h" +#include "SpellInfo.h" + +enum StatsType : uint8 +{ + // Basic stats + STATS_TYPE_AGILITY = 0, + STATS_TYPE_STRENGTH, + STATS_TYPE_INTELLECT, + STATS_TYPE_SPIRIT, + STATS_TYPE_STAMINA, + STATS_TYPE_HIT, + STATS_TYPE_CRIT, + STATS_TYPE_HASTE, + // Stats for tank + STATS_TYPE_ARMOR, + STATS_TYPE_DEFENSE, + STATS_TYPE_DODGE, + STATS_TYPE_PARRY, + STATS_TYPE_BLOCK_VALUE, + STATS_TYPE_BLOCK_RATING, + STATS_TYPE_RESILIENCE, + STATS_TYPE_HEALTH_REGENERATION, + // Stats for spell damage and heal + STATS_TYPE_SPELL_POWER, + STATS_TYPE_SPELL_PENETRATION, + STATS_TYPE_MANA_REGENERATION, + // Stats for physical damage and melee + STATS_TYPE_ATTACK_POWER, + STATS_TYPE_ARMOR_PENETRATION, + STATS_TYPE_EXPERTISE, + // Stats for weapon dps + STATS_TYPE_MELEE_DPS, + STATS_TYPE_RANGED_DPS, + STATS_TYPE_MAX = 24 +}; + +enum class CollectorType +{ + MELEE, + RANGED, + SPELL +}; + +class StatsCollector +{ +public: + StatsCollector(CollectorType type); + StatsCollector(StatsCollector& stats) = default; + void Reset(); + void CollectItemStats(ItemTemplate const* proto); + void CollectSpellStats(uint32 spellId, uint32 trigger = ITEM_SPELLTRIGGER_ON_EQUIP); + void CollectSpellEffectStats(const SpellEffectInfo& effectInfo, float multiplier = 1.0f); + void CollectEnchantStats(SpellItemEnchantmentEntry const* enchant); + +public: + uint32 stats[STATS_TYPE_MAX]; + +private: + void CollectByItemStatType(uint32 itemStatType, int32 val); + bool CollectSpecialCaseSpellStats(uint32 spellId); + bool CollectSpecialEnchantSpellStats(uint32 enchantSpellId); + +private: + CollectorType type_; +}; + +#endif \ No newline at end of file diff --git a/src/factory/StatsWeightCalculator.cpp b/src/factory/StatsWeightCalculator.cpp new file mode 100644 index 000000000..7d5e3c219 --- /dev/null +++ b/src/factory/StatsWeightCalculator.cpp @@ -0,0 +1,467 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU GPL v2 license, you may redistribute it + * and/or modify it under version 2 of the License, or (at your option), any later version. + */ + +#include "StatsWeightCalculator.h" + +#include + +#include "AiFactory.h" +#include "DBCStores.h" +#include "ObjectMgr.h" +#include "PlayerbotAI.h" +#include "SharedDefines.h" +#include "StatsCollector.h" +#include "Unit.h" + +StatsWeightCalculator::StatsWeightCalculator(Player* player) : player_(player) +{ + if (PlayerbotAI::IsCaster(player)) + type_ = CollectorType::SPELL; + else if (PlayerbotAI::IsMelee(player)) + type_ = CollectorType::MELEE; + else + type_ = CollectorType::RANGED; + collector_ = std::make_unique(type_); + + cls = player->getClass(); + tab = AiFactory::GetPlayerSpecTab(player); + + enable_overflow_penalty_ = true; + enable_item_set_bonus_ = true; +} + +void StatsWeightCalculator::Reset() +{ + collector_->Reset(); + weight_ = 0; + for (uint32 i = 0; i < STATS_TYPE_MAX; i++) + { + stats_weights_[i] = 0; + } +} + +float StatsWeightCalculator::CalculateItem(uint32 itemId) +{ + ItemTemplate const* proto = &sObjectMgr->GetItemTemplateStore()->at(itemId); + + if (!proto) + return 0.0f; + + Reset(); + + collector_->CollectItemStats(proto); + + GenerateWeights(player_); + for (uint32 i = 0; i < STATS_TYPE_MAX; i++) + { + weight_ += stats_weights_[i] * collector_->stats[i]; + } + + CalculateItemTypePenalty(proto); + + if (enable_item_set_bonus_) + CalculateItemSetBonus(player_, proto); + + CalculateSocketBonus(player_, proto); + + // Blend with item quality and level + weight_ *= (proto->Quality + 1) * proto->ItemLevel; + + return weight_; +} + +float StatsWeightCalculator::CalculateEnchant(uint32 enchantId) +{ + SpellItemEnchantmentEntry const* enchant = sSpellItemEnchantmentStore.LookupEntry(enchantId); + + if (!enchant) + return 0.0f; + + Reset(); + + collector_->CollectEnchantStats(enchant); + + GenerateWeights(player_); + for (uint32 i = 0; i < STATS_TYPE_MAX; i++) + { + weight_ += stats_weights_[i] * collector_->stats[i]; + } + + return weight_; +} + +void StatsWeightCalculator::GenerateWeights(Player* player) +{ + GenerateBasicWeights(player); + GenerateAdditionalWeights(player); + + if (enable_overflow_penalty_) + ApplyOverflowPenalty(player); +} + +void StatsWeightCalculator::GenerateBasicWeights(Player* player) +{ + // Basic weights + stats_weights_[STATS_TYPE_STAMINA] += 0.01f; + stats_weights_[STATS_TYPE_ARMOR] += 0.001f; + + if (cls == CLASS_HUNTER) + { + stats_weights_[STATS_TYPE_AGILITY] += 2.5f; + stats_weights_[STATS_TYPE_ATTACK_POWER] += 1.0f; + stats_weights_[STATS_TYPE_ARMOR_PENETRATION] += 2.5f; + stats_weights_[STATS_TYPE_HIT] += 2.0f; + stats_weights_[STATS_TYPE_CRIT] += 2.0f; + stats_weights_[STATS_TYPE_HASTE] += 2.0f; + stats_weights_[STATS_TYPE_RANGED_DPS] += 5.0f; + } + else if (cls == CLASS_ROGUE || (cls == CLASS_DRUID && tab == DRUID_TAB_FERAL && !PlayerbotAI::IsTank(player))) + { + stats_weights_[STATS_TYPE_AGILITY] += 2.0f; + stats_weights_[STATS_TYPE_STRENGTH] += 1.0f; + stats_weights_[STATS_TYPE_ATTACK_POWER] += 1.0f; + stats_weights_[STATS_TYPE_ARMOR_PENETRATION] += 1.5f; + stats_weights_[STATS_TYPE_HIT] += 1.5f; + stats_weights_[STATS_TYPE_CRIT] += 1.5f; + stats_weights_[STATS_TYPE_HASTE] += 1.5f; + stats_weights_[STATS_TYPE_EXPERTISE] += 2.5f; + stats_weights_[STATS_TYPE_MELEE_DPS] += 5.0f; + } + else if ((cls == CLASS_PALADIN && tab == 2) || // retribution + (cls == CLASS_WARRIOR && tab != 2) || // arm / fury + (cls == CLASS_DEATH_KNIGHT && tab != 0) // ice / unholy + ) + { + stats_weights_[STATS_TYPE_AGILITY] += 1.0f; + stats_weights_[STATS_TYPE_STRENGTH] += 2.0f; + stats_weights_[STATS_TYPE_ATTACK_POWER] += 1.0f; + stats_weights_[STATS_TYPE_ARMOR_PENETRATION] += 2.0f; + stats_weights_[STATS_TYPE_HIT] += 1.5f; + stats_weights_[STATS_TYPE_CRIT] += 1.5f; + stats_weights_[STATS_TYPE_HASTE] += 1.5f; + stats_weights_[STATS_TYPE_EXPERTISE] += 2.0f; + stats_weights_[STATS_TYPE_MELEE_DPS] += 5.0f; + } + else if ((cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT)) // enhancement + { + stats_weights_[STATS_TYPE_AGILITY] += 1.6f; + stats_weights_[STATS_TYPE_STRENGTH] += 1.1f; + stats_weights_[STATS_TYPE_INTELLECT] += 0.5f; + stats_weights_[STATS_TYPE_ATTACK_POWER] += 1.0f; + stats_weights_[STATS_TYPE_SPELL_POWER] += 1.0f; + stats_weights_[STATS_TYPE_ARMOR_PENETRATION] += 1.2f; + stats_weights_[STATS_TYPE_HIT] += 1.7f; + stats_weights_[STATS_TYPE_CRIT] += 1.4f; + stats_weights_[STATS_TYPE_HASTE] += 1.8f; + stats_weights_[STATS_TYPE_EXPERTISE] += 2.0f; + stats_weights_[STATS_TYPE_MELEE_DPS] += 5.0f; + } + else if (cls == CLASS_WARLOCK || cls == CLASS_MAGE || + (cls == CLASS_PRIEST && tab == PRIEST_TAB_SHADOW) || // shadow + (cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ELEMENTAL) || // element + (cls == CLASS_DRUID && tab == DRUID_TAB_BALANCE)) // balance + { + stats_weights_[STATS_TYPE_INTELLECT] += 0.5f; + stats_weights_[STATS_TYPE_SPIRIT] += 0.5f; + stats_weights_[STATS_TYPE_SPELL_POWER] += 1.0f; + stats_weights_[STATS_TYPE_SPELL_PENETRATION] += 1.0f; + stats_weights_[STATS_TYPE_HIT] += 1.0f; + stats_weights_[STATS_TYPE_CRIT] += 1.0f; + stats_weights_[STATS_TYPE_HASTE] += 1.0f; + stats_weights_[STATS_TYPE_RANGED_DPS] += 1.0f; + } + else if ((cls == CLASS_PALADIN && tab == PALADIN_TAB_HOLY) || // holy + (cls == CLASS_PRIEST && tab != PRIEST_TAB_SHADOW) || // discipline / holy + (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_SPELL_POWER] += 1.0f; + stats_weights_[STATS_TYPE_MANA_REGENERATION] += 0.5f; + stats_weights_[STATS_TYPE_CRIT] += 0.5f; + stats_weights_[STATS_TYPE_HASTE] += 1.0f; + stats_weights_[STATS_TYPE_RANGED_DPS] += 1.0f; + } + else if ((cls == CLASS_WARRIOR && tab == 2) || (cls == CLASS_PALADIN && tab == 1)) + { + stats_weights_[STATS_TYPE_AGILITY] += 2.0f; + stats_weights_[STATS_TYPE_STRENGTH] += 1.0f; + stats_weights_[STATS_TYPE_STAMINA] += 3.0f; + stats_weights_[STATS_TYPE_ATTACK_POWER] += 0.2f; + stats_weights_[STATS_TYPE_DEFENSE] += 2.5f; + stats_weights_[STATS_TYPE_PARRY] += 2.0f; + stats_weights_[STATS_TYPE_DODGE] += 2.0f; + stats_weights_[STATS_TYPE_RESILIENCE] += 2.0f; + stats_weights_[STATS_TYPE_BLOCK_RATING] += 1.0f; + stats_weights_[STATS_TYPE_BLOCK_VALUE] += 0.5f; + stats_weights_[STATS_TYPE_ARMOR] += 0.3f; + stats_weights_[STATS_TYPE_HIT] += 2.0f; + stats_weights_[STATS_TYPE_CRIT] += 0.2f; + stats_weights_[STATS_TYPE_HASTE] += 0.5f; + stats_weights_[STATS_TYPE_EXPERTISE] += 3.0f; + stats_weights_[STATS_TYPE_MELEE_DPS] += 2.0f; + } + else if (cls == CLASS_DEATH_KNIGHT && tab == 0) + { + stats_weights_[STATS_TYPE_AGILITY] += 2.0f; + stats_weights_[STATS_TYPE_STRENGTH] += 1.0f; + stats_weights_[STATS_TYPE_STAMINA] += 2.5f; + stats_weights_[STATS_TYPE_ATTACK_POWER] += 0.2f; + stats_weights_[STATS_TYPE_DEFENSE] += 3.5f; + stats_weights_[STATS_TYPE_PARRY] += 2.0f; + stats_weights_[STATS_TYPE_DODGE] += 2.0f; + stats_weights_[STATS_TYPE_RESILIENCE] += 2.0f; + stats_weights_[STATS_TYPE_ARMOR] += 0.3f; + stats_weights_[STATS_TYPE_HIT] += 2.0f; + stats_weights_[STATS_TYPE_CRIT] += 0.5f; + stats_weights_[STATS_TYPE_HASTE] += 0.5f; + stats_weights_[STATS_TYPE_EXPERTISE] += 3.5f; + stats_weights_[STATS_TYPE_MELEE_DPS] += 2.0f; + } + else + { + // BEAR DRUID TANK + stats_weights_[STATS_TYPE_AGILITY] += 1.5f; + stats_weights_[STATS_TYPE_STRENGTH] += 1.0f; + stats_weights_[STATS_TYPE_STAMINA] += 1.5f; + stats_weights_[STATS_TYPE_ATTACK_POWER] += 0.2f; + stats_weights_[STATS_TYPE_DEFENSE] += 2.0f; + stats_weights_[STATS_TYPE_DODGE] += 2.0f; + stats_weights_[STATS_TYPE_RESILIENCE] += 2.0f; + stats_weights_[STATS_TYPE_ARMOR] += 0.3f; + stats_weights_[STATS_TYPE_HIT] += 2.0f; + stats_weights_[STATS_TYPE_CRIT] += 0.5f; + stats_weights_[STATS_TYPE_HASTE] += 0.5f; + stats_weights_[STATS_TYPE_EXPERTISE] += 3.0f; + stats_weights_[STATS_TYPE_MELEE_DPS] += 2.0f; + } +} + +void StatsWeightCalculator::GenerateAdditionalWeights(Player* player) +{ + uint8 cls = player->getClass(); + // int tab = AiFactory::GetPlayerSpecTab(player); + if (cls == CLASS_HUNTER) + { + if (player->HasAura(34484)) + stats_weights_[STATS_TYPE_INTELLECT] += 1.0f; + } + else if (cls == CLASS_WARRIOR) + { + if (player->HasAura(61222)) + stats_weights_[STATS_TYPE_ARMOR] += 0.03f; + } + else if (cls == CLASS_SHAMAN) + { + if (player->HasAura(51885)) + stats_weights_[STATS_TYPE_INTELLECT] += 1.0f; + } +} + +void StatsWeightCalculator::CalculateItemSetBonus(Player* player, ItemTemplate const* proto) +{ + uint32 itemSet = proto->ItemSet; + if (!itemSet) + return; + + float multiplier = 1.0f; + size_t i = 0; + for (i = 0; i < player->ItemSetEff.size(); i++) + { + if (player->ItemSetEff[i]) + { + ItemSetEffect* eff = player->ItemSetEff[i]; + + uint32 setId = eff->setid; + if (itemSet != setId) + continue; + + const ItemSetEntry *setEntry = sItemSetStore.LookupEntry(setId); + if (!setEntry) + continue; + + uint32 itemCount = eff->item_count; + uint32 max_items = 0; + for (size_t j = 0; j < MAX_ITEM_SET_SPELLS; j++) + max_items = std::max(max_items, setEntry->items_to_triggerspell[j]); + if (itemCount < max_items) + { + multiplier += 0.1f * itemCount; // 10% bonus for each item already equipped + } + else + { + multiplier = 1.0f; // All item set effect has been triggerred + } + break; + } + } + + if (i == player->ItemSetEff.size()) + multiplier = 1.05f; // this is the first item in the item set + + weight_ *= multiplier; +} + +void StatsWeightCalculator::CalculateSocketBonus(Player* player, ItemTemplate const* proto) +{ + uint32 socketNum = 0; + for (uint32 enchant_slot = SOCK_ENCHANTMENT_SLOT; enchant_slot < SOCK_ENCHANTMENT_SLOT + MAX_GEM_SOCKETS; ++enchant_slot) + { + uint8 socketColor = proto->Socket[enchant_slot - SOCK_ENCHANTMENT_SLOT].Color; + + if (!socketColor) // no socket slot + continue; + + socketNum++; + } + + float multiplier = 1.0f + socketNum * 0.03f; // 3% bonus for socket + + weight_ *= multiplier; +} + +void StatsWeightCalculator::CalculateItemTypePenalty(ItemTemplate const* proto) +{ + // penalty for different type armor + if (proto->Class == ITEM_CLASS_ARMOR && proto->SubClass >= ITEM_SUBCLASS_ARMOR_CLOTH && + proto->SubClass <= ITEM_SUBCLASS_ARMOR_PLATE && NotBestArmorType(proto->SubClass)) + { + weight_ *= 0.8; + } + // double hand + if (proto->Class == ITEM_CLASS_WEAPON) + { + bool isDoubleHand = proto->Class == ITEM_CLASS_WEAPON && + !(ITEM_SUBCLASS_MASK_SINGLE_HAND & (1 << proto->SubClass)) && + !(ITEM_SUBCLASS_MASK_WEAPON_RANGED & (1 << proto->SubClass)); + + if (isDoubleHand) + { + weight_ *= 0.5; + } + // spec without double hand + // enhancement, rogue, ice dk, unholy dk, shield tank, fury warrior without titan's grip but with duel wield + if (isDoubleHand && + ((cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT && player_->CanDualWield()) || + (cls == CLASS_ROGUE) || (cls == CLASS_DEATH_KNIGHT && tab != DEATHKNIGT_TAB_BLOOD) || + (cls == CLASS_WARRIOR && tab == WARRIOR_TAB_FURY && !player_->CanTitanGrip() && player_->CanDualWield()) || + (cls == CLASS_WARRIOR && tab == WARRIOR_TAB_PROTECTION) || + (cls == CLASS_PALADIN && tab == PALADIN_TAB_PROTECTION))) + { + weight_ *= 0.1; + } + // spec with double hand + // fury without duel wield, arms, bear, retribution, blood dk + if (isDoubleHand && + ((cls == CLASS_WARRIOR && tab == WARRIOR_TAB_FURY && !player_->CanDualWield()) || + (cls == CLASS_WARRIOR && tab == WARRIOR_TAB_ARMS) || (cls == CLASS_DRUID && tab == DRUID_TAB_FERAL) || + (cls == CLASS_PALADIN && tab == PALADIN_TAB_RETRIBUTION) || + (cls == CLASS_DEATH_KNIGHT && tab == DEATHKNIGT_TAB_BLOOD) || + (cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT && !player_->CanDualWield()))) + { + weight_ *= 10; + } + // fury with titan's grip + if (isDoubleHand && proto->SubClass != ITEM_SUBCLASS_WEAPON_POLEARM && + (cls == CLASS_WARRIOR && tab == WARRIOR_TAB_FURY && player_->CanTitanGrip())) + { + weight_ *= 10; + } + } + if (proto->Class == ITEM_CLASS_WEAPON) + { + if (cls == CLASS_HUNTER && proto->SubClass == ITEM_SUBCLASS_WEAPON_THROWN) + { + weight_ *= 0.1; + } + if (cls == CLASS_ROGUE && tab == ROGUE_TAB_ASSASSINATION && proto->SubClass != ITEM_SUBCLASS_WEAPON_DAGGER) + { + weight_ *= 0.1; + } + } +} + +bool StatsWeightCalculator::NotBestArmorType(uint32 item_subclass_armor) +{ + if (player_->HasSkill(SKILL_PLATE_MAIL)) + { + return item_subclass_armor != ITEM_SUBCLASS_ARMOR_PLATE; + } + if (player_->HasSkill(SKILL_MAIL)) + { + return item_subclass_armor != ITEM_SUBCLASS_ARMOR_MAIL; + } + if (player_->HasSkill(SKILL_LEATHER)) + { + return item_subclass_armor != ITEM_SUBCLASS_ARMOR_LEATHER; + } + return false; +} + +void StatsWeightCalculator::ApplyOverflowPenalty(Player* player) +{ + { + float hit_current, hit_overflow; + if (type_ == CollectorType::SPELL) + { + hit_current = player->GetRatingBonusValue(CR_HIT_SPELL); + hit_overflow = SPELL_HIT_OVERFLOW; + } + else if (type_ == CollectorType::MELEE) + { + hit_current = player->GetRatingBonusValue(CR_HIT_MELEE); + hit_overflow = MELEE_HIT_OVERFLOW; + } + else + { + hit_current = player->GetRatingBonusValue(CR_HIT_RANGED); + hit_overflow = RANGED_HIT_OVERFLOW; + } + if (hit_current >= hit_overflow) + stats_weights_[STATS_TYPE_HIT] = 0.0f; + else if (hit_current >= hit_overflow * 0.8) + stats_weights_[STATS_TYPE_HIT] /= 1.5; + } + + { + if (type_ == CollectorType::MELEE) + { + float expertise_current, expertise_overflow; + expertise_current = player->GetRatingBonusValue(CR_EXPERTISE); + expertise_overflow = EXPERTISE_OVERFLOW; + if (expertise_current >= expertise_overflow) + stats_weights_[STATS_TYPE_EXPERTISE] = 0.0f; + else if (expertise_current >= expertise_overflow * 0.8) + stats_weights_[STATS_TYPE_EXPERTISE] /= 1.5; + } + } + + { + if (type_ == CollectorType::MELEE) + { + float defense_current, defense_overflow; + defense_current = player->GetRatingBonusValue(CR_DEFENSE_SKILL); + defense_overflow = EXPERTISE_OVERFLOW; + if (defense_current >= defense_overflow) + stats_weights_[STATS_TYPE_EXPERTISE] /= 2; + else if (defense_current >= defense_overflow * 0.8) + stats_weights_[STATS_TYPE_EXPERTISE] /= 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) + stats_weights_[STATS_TYPE_ARMOR_PENETRATION] = 0.0f; + if (armor_pnetration_current >= armor_pnetration_overflow * 0.8) + stats_weights_[STATS_TYPE_ARMOR_PENETRATION] /= 1.5; + } + } +} \ No newline at end of file diff --git a/src/factory/StatsWeightCalculator.h b/src/factory/StatsWeightCalculator.h new file mode 100644 index 000000000..1bef2a56b --- /dev/null +++ b/src/factory/StatsWeightCalculator.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU GPL v2 license, you may redistribute it + * and/or modify it under version 2 of the License, or (at your option), any later version. + */ + +#ifndef _PLAYERBOT_GEARSCORECALCULATOR_H +#define _PLAYERBOT_GEARSCORECALCULATOR_H + +#include "Player.h" +#include "StatsCollector.h" + +#define ITEM_SUBCLASS_MASK_SINGLE_HAND \ + ((1 << ITEM_SUBCLASS_WEAPON_AXE) | (1 << ITEM_SUBCLASS_WEAPON_MACE) | (1 << ITEM_SUBCLASS_WEAPON_SWORD) | \ + (1 << ITEM_SUBCLASS_WEAPON_DAGGER) | (1 << ITEM_SUBCLASS_WEAPON_FIST)) + +enum StatsOverflowThreshold { + SPELL_HIT_OVERFLOW = 17, + MELEE_HIT_OVERFLOW = 8, + RANGED_HIT_OVERFLOW = 8, + EXPERTISE_OVERFLOW = 26, + DEFENSE_OVERFLOW = 140, + ARMOR_PENETRATION_OVERFLOW = 100 +}; + +class StatsWeightCalculator +{ +public: + StatsWeightCalculator(Player* player); + void Reset(); + float CalculateItem(uint32 itemId); + float CalculateEnchant(uint32 enchantId); + +private: + void GenerateWeights(Player* player); + void GenerateBasicWeights(Player* player); + void GenerateAdditionalWeights(Player* player); + + void CalculateItemSetBonus(Player* player, ItemTemplate const* proto); + void CalculateSocketBonus(Player* player, ItemTemplate const* proto); + + void CalculateItemTypePenalty(ItemTemplate const* proto); + + bool NotBestArmorType(uint32 item_subclass_armor); + + void ApplyOverflowPenalty(Player* player); + +private: + Player* player_; + CollectorType type_; + std::unique_ptr collector_; + uint8 cls; + int tab; + bool enable_overflow_penalty_; + bool enable_item_set_bonus_; + + float weight_; + float stats_weights_[STATS_TYPE_MAX]; +}; + +#endif diff --git a/src/strategy/actions/ChangeTalentsAction.cpp b/src/strategy/actions/ChangeTalentsAction.cpp index cbaa89650..62e036462 100644 --- a/src/strategy/actions/ChangeTalentsAction.cpp +++ b/src/strategy/actions/ChangeTalentsAction.cpp @@ -18,8 +18,6 @@ bool ChangeTalentsAction::Execute(Event event) std::ostringstream out; - TalentSpec botSpec(bot); - if (!param.empty()) { if (param.find("help") != std::string::npos) diff --git a/src/strategy/values/ItemUsageValue.cpp b/src/strategy/values/ItemUsageValue.cpp index f42aca0c1..ca294165e 100644 --- a/src/strategy/values/ItemUsageValue.cpp +++ b/src/strategy/values/ItemUsageValue.cpp @@ -13,6 +13,7 @@ #include "Playerbots.h" #include "RandomItemMgr.h" #include "ServerFacade.h" +#include "StatsWeightCalculator.h" ItemUsage ItemUsageValue::Calculate() { @@ -190,7 +191,8 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto) bool shouldEquip = false; // uint32 statWeight = sRandomItemMgr->GetLiveStatWeight(bot, itemProto->ItemId); - float itemScore = PlayerbotFactory::CalculateItemScore(itemProto->ItemId, bot); + StatsWeightCalculator calculator(bot); + float itemScore = calculator.CalculateItem(itemProto->ItemId); if (itemScore) shouldEquip = true; @@ -214,7 +216,7 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto) } ItemTemplate const* oldItemProto = oldItem->GetTemplate(); - float oldScore = PlayerbotFactory::CalculateItemScore(oldItemProto->ItemId, bot); + float oldScore = calculator.CalculateItem(oldItemProto->ItemId); if (oldItem) { // uint32 oldStatWeight = sRandomItemMgr->GetLiveStatWeight(bot, oldItemProto->ItemId); From eef88c727658999ba43ee8212a8275d371d3d425 Mon Sep 17 00:00:00 2001 From: Yunfan Li Date: Fri, 9 Aug 2024 02:40:03 +0800 Subject: [PATCH 03/14] Stats collector for more spell --- src/factory/StatsCollector.cpp | 167 +++++++++++++++++++------- src/factory/StatsCollector.h | 6 +- src/factory/StatsWeightCalculator.cpp | 30 ++--- 3 files changed, 141 insertions(+), 62 deletions(-) diff --git a/src/factory/StatsCollector.cpp b/src/factory/StatsCollector.cpp index 86c54476b..d424424a5 100644 --- a/src/factory/StatsCollector.cpp +++ b/src/factory/StatsCollector.cpp @@ -5,8 +5,11 @@ #include "DBCStores.h" #include "ItemTemplate.h" #include "ObjectMgr.h" +#include "SharedDefines.h" +#include "SpellAuraDefines.h" #include "SpellInfo.h" #include "SpellMgr.h" +#include "UpdateFields.h" StatsCollector::StatsCollector(CollectorType type) : type_(type) { Reset(); } @@ -40,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); + CollectSpellStats(proto->Spells[j].SpellId, proto->Spells[j].SpellTrigger != ITEM_SPELLTRIGGER_ON_EQUIP); } if (proto->socketBonus) @@ -50,7 +53,7 @@ void StatsCollector::CollectItemStats(ItemTemplate const* proto) } } -void StatsCollector::CollectSpellStats(uint32 spellId, uint32 trigger) +void StatsCollector::CollectSpellStats(uint32 spellId, bool isTrigger) { SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); @@ -59,20 +62,30 @@ void StatsCollector::CollectSpellStats(uint32 spellId, uint32 trigger) if (CollectSpecialCaseSpellStats(spellId)) return; + + float multiplier = isTrigger ? 0.25f : 1.0f; + if (spellInfo->StackAmount) + multiplier *= spellInfo->StackAmount; + // spellInfo->ProcFlags + // const SpellProcEntry* procEntry = sSpellMgr->GetSpellProcEntry(spellId); + // spellInfo->SpellFamilyFlags + // spellInfo->ProcFlags + // procEntry->SpellTypeMask for (int i = 0; i < MAX_SPELL_EFFECTS; i++) { - float multiplier = trigger == ITEM_SPELLTRIGGER_ON_EQUIP ? 1.0f : 0.2f; - CollectSpellEffectStats(spellInfo->Effects[i], multiplier); + if (spellInfo->IsPositive()) + CollectPositiveSpellEffectStats(spellInfo->Effects[i], multiplier); } } -void StatsCollector::CollectSpellEffectStats(const SpellEffectInfo& effectInfo, float multiplier) +void StatsCollector::CollectPositiveSpellEffectStats(const SpellEffectInfo& effectInfo, float multiplier) { if (effectInfo.Effect != SPELL_EFFECT_APPLY_AURA) return; - - int32 val = effectInfo.BasePoints + 1; + + int32 val = effectInfo.CalcValue(); + switch (effectInfo.ApplyAuraName) { case SPELL_AURA_MOD_DAMAGE_DONE: @@ -85,6 +98,45 @@ void StatsCollector::CollectSpellEffectStats(const SpellEffectInfo& effectInfo, case SPELL_AURA_MOD_SHIELD_BLOCKVALUE: stats[STATS_TYPE_BLOCK_VALUE] += val * multiplier; break; + case SPELL_AURA_MOD_STAT: + { + uint32 statType = effectInfo.MiscValue; + switch (statType) + { + case STAT_STRENGTH: + stats[STATS_TYPE_STRENGTH] += val * multiplier; + break; + case STAT_AGILITY: + stats[STATS_TYPE_AGILITY] += val * multiplier; + break; + case STAT_STAMINA: + stats[STATS_TYPE_STAMINA] += val * multiplier; + break; + case STAT_INTELLECT: + stats[STATS_TYPE_INTELLECT] += val * multiplier; + break; + case STAT_SPIRIT: + stats[STATS_TYPE_SPIRIT] += val * multiplier; + break; + case -1: // Stat all + stats[STATS_TYPE_STRENGTH] += val * multiplier; + stats[STATS_TYPE_AGILITY] += val * multiplier; + stats[STATS_TYPE_STAMINA] += val * multiplier; + stats[STATS_TYPE_INTELLECT] += val * multiplier; + stats[STATS_TYPE_SPIRIT] += val * multiplier; + break; + default: + break; + } + break; + } + case SPELL_AURA_MOD_RESISTANCE: + { + uint32 statType = effectInfo.MiscValue; + if (statType & SPELL_SCHOOL_MASK_NORMAL) // physical + stats[STATS_TYPE_ARMOR] += val * multiplier; + break; + } case SPELL_AURA_MOD_RATING: { for (uint32 rating = CR_WEAPON_SKILL; rating < MAX_COMBAT_RATING; ++rating) @@ -156,17 +208,12 @@ void StatsCollector::CollectSpellEffectStats(const SpellEffectInfo& effectInfo, } case SPELL_AURA_PROC_TRIGGER_SPELL: { - multiplier = 0.2f; - if (effectInfo.TriggerSpell) - { - SpellInfo const* triggerSpellInfo = sSpellMgr->GetSpellInfo(effectInfo.TriggerSpell); - if (!triggerSpellInfo) - return; - for (uint8 k = 0; k < MAX_SPELL_EFFECTS; k++) - { - CollectSpellEffectStats(triggerSpellInfo->Effects[k], multiplier); - } - } + CollectSpellStats(effectInfo.TriggerSpell, true); + break; + } + case SPELL_AURA_PERIODIC_TRIGGER_SPELL: + { + CollectSpellStats(effectInfo.TriggerSpell, true); break; } default: @@ -187,6 +234,16 @@ void StatsCollector::CollectEnchantStats(SpellItemEnchantmentEntry const* enchan switch (enchant_display_type) { + case ITEM_ENCHANTMENT_TYPE_COMBAT_SPELL: + { + CollectSpellStats(enchant_spell_id, true); + break; + } + case ITEM_ENCHANTMENT_TYPE_EQUIP_SPELL: + { + CollectSpellStats(enchant_spell_id, false); + break; + } case ITEM_ENCHANTMENT_TYPE_STAT: { if (!enchant_amount) @@ -202,37 +259,59 @@ void StatsCollector::CollectEnchantStats(SpellItemEnchantmentEntry const* enchan } } -/// @todo Special case for trinket -bool StatsCollector::CollectSpecialCaseSpellStats(uint32 spellId) { return false; } +/// @todo Special case for some spell that hard to calculate, like trinket, libram, etc. +bool StatsCollector::CollectSpecialCaseSpellStats(uint32 spellId) { + // trinket + switch (spellId) + { + case 71519: // Deathbringer's Will + stats[STATS_TYPE_ATTACK_POWER] += 400; + return true; + case 71562: // Deathbringer's Will + stats[STATS_TYPE_ATTACK_POWER] += 450; + return true; + default: + break; + } + // switch (spellId) + // { + // case 50457: // Idol of the Lunar Eclipse + // stats[STATS_TYPE_CRIT] += 150; + // return true; + // default: + // break; + // } + return false; +} bool StatsCollector::CollectSpecialEnchantSpellStats(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 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 64571: if (type_ == CollectorType::MELEE) { diff --git a/src/factory/StatsCollector.h b/src/factory/StatsCollector.h index 795f6a937..dd2227cdf 100644 --- a/src/factory/StatsCollector.h +++ b/src/factory/StatsCollector.h @@ -57,12 +57,12 @@ class StatsCollector StatsCollector(StatsCollector& stats) = default; void Reset(); void CollectItemStats(ItemTemplate const* proto); - void CollectSpellStats(uint32 spellId, uint32 trigger = ITEM_SPELLTRIGGER_ON_EQUIP); - void CollectSpellEffectStats(const SpellEffectInfo& effectInfo, float multiplier = 1.0f); + void CollectSpellStats(uint32 spellId, bool isTrigger = false); + void CollectPositiveSpellEffectStats(const SpellEffectInfo& effectInfo, float multiplier = 1.0f); void CollectEnchantStats(SpellItemEnchantmentEntry const* enchant); public: - uint32 stats[STATS_TYPE_MAX]; + int32 stats[STATS_TYPE_MAX]; private: void CollectByItemStatType(uint32 itemStatType, int32 val); diff --git a/src/factory/StatsWeightCalculator.cpp b/src/factory/StatsWeightCalculator.cpp index 7d5e3c219..89f19054c 100644 --- a/src/factory/StatsWeightCalculator.cpp +++ b/src/factory/StatsWeightCalculator.cpp @@ -189,7 +189,7 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player) { stats_weights_[STATS_TYPE_AGILITY] += 2.0f; stats_weights_[STATS_TYPE_STRENGTH] += 1.0f; - stats_weights_[STATS_TYPE_STAMINA] += 3.0f; + stats_weights_[STATS_TYPE_STAMINA] += 3.5f; stats_weights_[STATS_TYPE_ATTACK_POWER] += 0.2f; stats_weights_[STATS_TYPE_DEFENSE] += 2.5f; stats_weights_[STATS_TYPE_PARRY] += 2.0f; @@ -208,7 +208,7 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player) { stats_weights_[STATS_TYPE_AGILITY] += 2.0f; stats_weights_[STATS_TYPE_STRENGTH] += 1.0f; - stats_weights_[STATS_TYPE_STAMINA] += 2.5f; + stats_weights_[STATS_TYPE_STAMINA] += 3.5f; stats_weights_[STATS_TYPE_ATTACK_POWER] += 0.2f; stats_weights_[STATS_TYPE_DEFENSE] += 3.5f; stats_weights_[STATS_TYPE_PARRY] += 2.0f; @@ -224,19 +224,19 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player) else { // BEAR DRUID TANK - stats_weights_[STATS_TYPE_AGILITY] += 1.5f; - stats_weights_[STATS_TYPE_STRENGTH] += 1.0f; - stats_weights_[STATS_TYPE_STAMINA] += 1.5f; - stats_weights_[STATS_TYPE_ATTACK_POWER] += 0.2f; - stats_weights_[STATS_TYPE_DEFENSE] += 2.0f; - stats_weights_[STATS_TYPE_DODGE] += 2.0f; - stats_weights_[STATS_TYPE_RESILIENCE] += 2.0f; - stats_weights_[STATS_TYPE_ARMOR] += 0.3f; - stats_weights_[STATS_TYPE_HIT] += 2.0f; - stats_weights_[STATS_TYPE_CRIT] += 0.5f; - stats_weights_[STATS_TYPE_HASTE] += 0.5f; - stats_weights_[STATS_TYPE_EXPERTISE] += 3.0f; - stats_weights_[STATS_TYPE_MELEE_DPS] += 2.0f; + stats_weights_[STATS_TYPE_AGILITY] += 2.2f; + stats_weights_[STATS_TYPE_STRENGTH] += 2.4f; + stats_weights_[STATS_TYPE_STAMINA] += 4.0f; + stats_weights_[STATS_TYPE_ATTACK_POWER] += 1.0f; + stats_weights_[STATS_TYPE_DEFENSE] += 0.3f; + stats_weights_[STATS_TYPE_DODGE] += 0.7f; + stats_weights_[STATS_TYPE_RESILIENCE] += 1.0f; + stats_weights_[STATS_TYPE_ARMOR] += 0.15f; + stats_weights_[STATS_TYPE_HIT] += 3.0f; + stats_weights_[STATS_TYPE_CRIT] += 1.3f; + stats_weights_[STATS_TYPE_HASTE] += 2.3f; + stats_weights_[STATS_TYPE_EXPERTISE] += 3.7f; + stats_weights_[STATS_TYPE_MELEE_DPS] += 3.0f; } } From 2a2d07b59a5bcdeedea4ef924de6533672e6b8e6 Mon Sep 17 00:00:00 2001 From: Yunfan Li Date: Fri, 9 Aug 2024 03:01:08 +0800 Subject: [PATCH 04/14] Add power regen stats --- src/factory/StatsCollector.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/factory/StatsCollector.cpp b/src/factory/StatsCollector.cpp index d424424a5..2fa0f4530 100644 --- a/src/factory/StatsCollector.cpp +++ b/src/factory/StatsCollector.cpp @@ -206,6 +206,18 @@ void StatsCollector::CollectPositiveSpellEffectStats(const SpellEffectInfo& effe break; } } + case SPELL_AURA_MOD_POWER_REGEN: + { + uint32 powerType = effectInfo.MiscValue; + switch (powerType) + { + case POWER_MANA: + stats[STATS_TYPE_MANA_REGENERATION] += val * multiplier; + default: + break; + } + break; + } case SPELL_AURA_PROC_TRIGGER_SPELL: { CollectSpellStats(effectInfo.TriggerSpell, true); @@ -374,6 +386,7 @@ void StatsCollector::CollectByItemStatType(uint32 itemStatType, int32 val) case ITEM_MOD_MANA: break; case ITEM_MOD_HEALTH: + stats[STATS_TYPE_AGILITY] += val / 12; break; case ITEM_MOD_AGILITY: stats[STATS_TYPE_AGILITY] += val; From 017e36333e2f081f0e5e4ae99c9dfe3f0f413c91 Mon Sep 17 00:00:00 2001 From: Yunfan Li Date: Sat, 10 Aug 2024 02:33:15 +0800 Subject: [PATCH 05/14] Deathnight enchant check --- src/factory/PlayerbotFactory.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/factory/PlayerbotFactory.cpp b/src/factory/PlayerbotFactory.cpp index 3ecc46d24..5af68d030 100644 --- a/src/factory/PlayerbotFactory.cpp +++ b/src/factory/PlayerbotFactory.cpp @@ -3721,6 +3721,9 @@ void PlayerbotFactory::ApplyEnchantAndGemsNew(bool destoryOld) { continue; } + + if (spellInfo->SpellFamilyName == SPELLFAMILY_DEATHKNIGHT && bot->getClass() != CLASS_DEATH_KNIGHT) + continue; uint32 requiredLevel = spellInfo->BaseLevel; if (requiredLevel > bot->GetLevel()) From 7db30c96b8f431f992be0ae8d9aa66a0484b57d5 Mon Sep 17 00:00:00 2001 From: Yunfan Li Date: Sat, 10 Aug 2024 02:33:32 +0800 Subject: [PATCH 06/14] Trigger spell availability check --- src/factory/StatsCollector.cpp | 510 ++++++++++++++++---------- src/factory/StatsCollector.h | 23 +- src/factory/StatsWeightCalculator.cpp | 8 +- 3 files changed, 333 insertions(+), 208 deletions(-) diff --git a/src/factory/StatsCollector.cpp b/src/factory/StatsCollector.cpp index 2fa0f4530..9cce4fadf 100644 --- a/src/factory/StatsCollector.cpp +++ b/src/factory/StatsCollector.cpp @@ -53,7 +53,7 @@ void StatsCollector::CollectItemStats(ItemTemplate const* proto) } } -void StatsCollector::CollectSpellStats(uint32 spellId, bool isTrigger) +void StatsCollector::CollectSpellStats(uint32 spellId, bool isTriggered) { SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); @@ -62,172 +62,50 @@ void StatsCollector::CollectSpellStats(uint32 spellId, bool isTrigger) if (CollectSpecialCaseSpellStats(spellId)) return; + + /// @todo Not all triggered spell need this penalty + float multiplier = isTriggered ? 0.25f : 1.0f; + + bool canNextTrigger = true; + + uint32 procFlags; + const SpellProcEventEntry* eventEntry = sSpellMgr->GetSpellProcEvent(spellInfo->Id); + if (eventEntry && eventEntry->procFlags) + procFlags = eventEntry->procFlags; + else + procFlags = spellInfo->ProcFlags; + + if (procFlags && !CanBeTriggeredByType(spellInfo, procFlags)) + canNextTrigger = false; - float multiplier = isTrigger ? 0.25f : 1.0f; + // if (!eventEntry || eventEntry->cooldown == 0) + // { + // } + // if (spellInfo->ProcChance == 100) + if (spellInfo->StackAmount) multiplier *= spellInfo->StackAmount; - // spellInfo->ProcFlags - // const SpellProcEntry* procEntry = sSpellMgr->GetSpellProcEntry(spellId); - // spellInfo->SpellFamilyFlags - // spellInfo->ProcFlags - // procEntry->SpellTypeMask + for (int i = 0; i < MAX_SPELL_EFFECTS; i++) { if (spellInfo->IsPositive()) - CollectPositiveSpellEffectStats(spellInfo->Effects[i], multiplier); + CollectPositiveSpellEffectStats(spellInfo->Effects[i], multiplier, canNextTrigger); } } -void StatsCollector::CollectPositiveSpellEffectStats(const SpellEffectInfo& effectInfo, float multiplier) +void StatsCollector::CollectPositiveSpellEffectStats(const SpellEffectInfo& effectInfo, float multiplier, bool canNextTrigger) { - if (effectInfo.Effect != SPELL_EFFECT_APPLY_AURA) - return; - - int32 val = effectInfo.CalcValue(); - - switch (effectInfo.ApplyAuraName) + + switch (effectInfo.Effect) { - case SPELL_AURA_MOD_DAMAGE_DONE: - // case SPELL_AURA_MOD_HEALING_DONE is duplicated - stats[STATS_TYPE_SPELL_POWER] += val * multiplier; - break; - case SPELL_AURA_MOD_ATTACK_POWER: - stats[STATS_TYPE_ATTACK_POWER] += val * multiplier; - break; - case SPELL_AURA_MOD_SHIELD_BLOCKVALUE: - stats[STATS_TYPE_BLOCK_VALUE] += val * multiplier; - break; - case SPELL_AURA_MOD_STAT: - { - uint32 statType = effectInfo.MiscValue; - switch (statType) - { - case STAT_STRENGTH: - stats[STATS_TYPE_STRENGTH] += val * multiplier; - break; - case STAT_AGILITY: - stats[STATS_TYPE_AGILITY] += val * multiplier; - break; - case STAT_STAMINA: - stats[STATS_TYPE_STAMINA] += val * multiplier; - break; - case STAT_INTELLECT: - stats[STATS_TYPE_INTELLECT] += val * multiplier; - break; - case STAT_SPIRIT: - stats[STATS_TYPE_SPIRIT] += val * multiplier; - break; - case -1: // Stat all - stats[STATS_TYPE_STRENGTH] += val * multiplier; - stats[STATS_TYPE_AGILITY] += val * multiplier; - stats[STATS_TYPE_STAMINA] += val * multiplier; - stats[STATS_TYPE_INTELLECT] += val * multiplier; - stats[STATS_TYPE_SPIRIT] += val * multiplier; - break; - default: - break; - } - break; - } - case SPELL_AURA_MOD_RESISTANCE: - { - uint32 statType = effectInfo.MiscValue; - if (statType & SPELL_SCHOOL_MASK_NORMAL) // physical - stats[STATS_TYPE_ARMOR] += val * multiplier; - break; - } - case SPELL_AURA_MOD_RATING: - { - for (uint32 rating = CR_WEAPON_SKILL; rating < MAX_COMBAT_RATING; ++rating) - { - if (effectInfo.MiscValue & (1 << rating)) - { - switch (rating) - { - case CR_DEFENSE_SKILL: - stats[STATS_TYPE_DEFENSE] += val * multiplier; - break; - case CR_DODGE: - stats[STATS_TYPE_DODGE] += val * multiplier; - break; - case CR_PARRY: - stats[STATS_TYPE_PARRY] += val * multiplier; - break; - case CR_BLOCK: - stats[STATS_TYPE_BLOCK_RATING] += val * multiplier; - break; - case CR_HIT_MELEE: - if (type_ == CollectorType::MELEE) - stats[STATS_TYPE_HIT] += val * multiplier; - break; - case CR_HIT_RANGED: - if (type_ == CollectorType::RANGED) - stats[STATS_TYPE_HIT] += val * multiplier; - break; - case CR_HIT_SPELL: - if (type_ == CollectorType::SPELL) - stats[STATS_TYPE_HIT] += val * multiplier; - break; - case CR_CRIT_MELEE: - if (type_ == CollectorType::MELEE) - stats[STATS_TYPE_CRIT] += val * multiplier; - break; - case CR_CRIT_RANGED: - if (type_ == CollectorType::RANGED) - stats[STATS_TYPE_CRIT] += val * multiplier; - break; - case CR_CRIT_SPELL: - if (type_ == CollectorType::SPELL) - stats[STATS_TYPE_CRIT] += val * multiplier; - break; - case CR_HASTE_MELEE: - if (type_ == CollectorType::MELEE) - stats[STATS_TYPE_HASTE] += val * multiplier; - break; - case CR_HASTE_RANGED: - if (type_ == CollectorType::RANGED) - stats[STATS_TYPE_HASTE] += val * multiplier; - break; - case CR_HASTE_SPELL: - if (type_ == CollectorType::SPELL) - stats[STATS_TYPE_HASTE] += val * multiplier; - break; - case CR_EXPERTISE: - stats[STATS_TYPE_EXPERTISE] += val * multiplier; - break; - case CR_ARMOR_PENETRATION: - stats[STATS_TYPE_ARMOR_PENETRATION] += val * multiplier; - break; - default: - break; - } - } - break; - } - } - case SPELL_AURA_MOD_POWER_REGEN: - { - uint32 powerType = effectInfo.MiscValue; - switch (powerType) - { - case POWER_MANA: - stats[STATS_TYPE_MANA_REGENERATION] += val * multiplier; - default: - break; - } + case SPELL_EFFECT_APPLY_AURA: + HandleApplyAura(effectInfo, multiplier, canNextTrigger); break; - } - case SPELL_AURA_PROC_TRIGGER_SPELL: - { - CollectSpellStats(effectInfo.TriggerSpell, true); - break; - } - case SPELL_AURA_PERIODIC_TRIGGER_SPELL: - { - CollectSpellStats(effectInfo.TriggerSpell, true); - break; - } + // case SPELL_EFFECT_HEAL: + // int32 val = effectInfo.CalcValue(); + // stats[STATS_TYPE_HEAL_POWER] += (float)val / 5 * multiplier; + // break; default: break; } @@ -271,16 +149,16 @@ void StatsCollector::CollectEnchantStats(SpellItemEnchantmentEntry const* enchan } } -/// @todo Special case for some spell that hard to calculate, like trinket, libram, etc. +/// @todo Special case for some spell that hard to calculate, like trinket, relic, etc. bool StatsCollector::CollectSpecialCaseSpellStats(uint32 spellId) { // trinket switch (spellId) { case 71519: // Deathbringer's Will - stats[STATS_TYPE_ATTACK_POWER] += 400; + stats[STATS_TYPE_ATTACK_POWER] += 350; return true; - case 71562: // Deathbringer's Will - stats[STATS_TYPE_ATTACK_POWER] += 450; + case 71562: // Deathbringer's Will (heroic) + stats[STATS_TYPE_ATTACK_POWER] += 400; return true; default: break; @@ -324,7 +202,20 @@ bool StatsCollector::CollectSpecialEnchantSpellStats(uint32 enchantSpellId) // stats[STATS_TYPE_PARRY] += 50; // } // return true; - case 64571: + case 53365: // Rune of the Fallen Crusader + if (type_ == CollectorType::MELEE) + { + stats[STATS_TYPE_STRENGTH] += 60; + } + return true; + case 62157: // Rune of the Stoneskin Gargoyle + if (type_ == CollectorType::MELEE) + { + stats[STATS_TYPE_DEFENSE] += 25; + stats[STATS_TYPE_STAMINA] += 40; + } + return true; + case 64571: // Blood draining if (type_ == CollectorType::MELEE) { stats[STATS_TYPE_STAMINA] += 50; @@ -333,49 +224,105 @@ bool StatsCollector::CollectSpecialEnchantSpellStats(uint32 enchantSpellId) default: break; } - { - int allStatsAmount = 0; - switch (enchantSpellId) + // { + // 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; +} + +bool StatsCollector::CanBeTriggeredByType(SpellInfo const* spellInfo, uint32 procFlags) +{ + const SpellProcEventEntry* eventEntry = sSpellMgr->GetSpellProcEvent(spellInfo->Id); + uint32 spellFamilyName = eventEntry ? eventEntry->spellFamilyName : 0; + + if (spellFamilyName != 0) + /// @todo Check specific trigger spell by spellFamilyMask + return true; + + uint32 triggerMask = TAKEN_HIT_PROC_FLAG_MASK; // Generic trigger mask + switch (type_) { + case CollectorType::MELEE: { - 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; + triggerMask |= MELEE_PROC_FLAG_MASK; + triggerMask |= SPELL_PROC_FLAG_MASK; + triggerMask |= PERIODIC_PROC_FLAG_MASK; + if (procFlags & triggerMask) + return true; + break; } - if (allStatsAmount != 0) + case CollectorType::RANGED: { - 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; + triggerMask |= RANGED_PROC_FLAG_MASK; + triggerMask |= SPELL_PROC_FLAG_MASK; + triggerMask |= PERIODIC_PROC_FLAG_MASK; + if (procFlags & triggerMask) + return true; + break; + } + case CollectorType::SPELL_DMG: + { + triggerMask |= SPELL_PROC_FLAG_MASK; + // Healing spell cannot trigger + triggerMask &= ~PROC_FLAG_DONE_SPELL_NONE_DMG_CLASS_POS; + triggerMask &= ~PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_POS; + if (procFlags & triggerMask) + return true; + break; + } + case CollectorType::SPELL_HEAL: + { + triggerMask |= SPELL_PROC_FLAG_MASK; + // Dmg spell should not trigger + triggerMask &= ~PROC_FLAG_DONE_SPELL_NONE_DMG_CLASS_NEG; + triggerMask &= ~PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG; + triggerMask &= ~PROC_FLAG_DONE_PERIODIC; // spellFamilyName = 0 and PROC_FLAG_DONE_PERIODIC -> it is a dmg spell + if (procFlags & triggerMask) + return true; + break; } + default: + break; } - return false; } @@ -481,6 +428,7 @@ void StatsCollector::CollectByItemStatType(uint32 itemStatType, int32 val) break; case ITEM_MOD_SPELL_POWER: stats[STATS_TYPE_SPELL_POWER] += val; + stats[STATS_TYPE_HEAL_POWER] += val; break; case ITEM_MOD_HEALTH_REGEN: stats[STATS_TYPE_HEALTH_REGENERATION] += val; @@ -496,4 +444,172 @@ void StatsCollector::CollectByItemStatType(uint32 itemStatType, int32 val) default: break; } +} + +void StatsCollector::HandleApplyAura(const SpellEffectInfo& effectInfo, float multiplier, bool canNextTrigger) +{ + if (effectInfo.Effect != SPELL_EFFECT_APPLY_AURA) + return; + + int32 val = effectInfo.CalcValue(); + + switch (effectInfo.ApplyAuraName) + { + case SPELL_AURA_MOD_DAMAGE_DONE: + { + int32 schoolType = effectInfo.MiscValue; + if (schoolType & SPELL_SCHOOL_MASK_NORMAL) + stats[STATS_TYPE_ATTACK_POWER] += val * multiplier; + if ((schoolType & SPELL_SCHOOL_MASK_MAGIC) == SPELL_SCHOOL_MASK_MAGIC) + stats[STATS_TYPE_SPELL_POWER] += val * multiplier; + break; + } + case SPELL_AURA_MOD_HEALING_DONE: + stats[STATS_TYPE_HEAL_POWER] += val * multiplier; + break; + case SPELL_AURA_MOD_ATTACK_POWER: + stats[STATS_TYPE_ATTACK_POWER] += val * multiplier; + break; + case SPELL_AURA_MOD_RANGED_ATTACK_POWER: + if (type_ == CollectorType::RANGED) + stats[STATS_TYPE_ATTACK_POWER] += val * multiplier; + break; + case SPELL_AURA_MOD_SHIELD_BLOCKVALUE: + stats[STATS_TYPE_BLOCK_VALUE] += val * multiplier; + break; + case SPELL_AURA_MOD_STAT: + { + int32 statType = effectInfo.MiscValue; + switch (statType) + { + case STAT_STRENGTH: + stats[STATS_TYPE_STRENGTH] += val * multiplier; + break; + case STAT_AGILITY: + stats[STATS_TYPE_AGILITY] += val * multiplier; + break; + case STAT_STAMINA: + stats[STATS_TYPE_STAMINA] += val * multiplier; + break; + case STAT_INTELLECT: + stats[STATS_TYPE_INTELLECT] += val * multiplier; + break; + case STAT_SPIRIT: + stats[STATS_TYPE_SPIRIT] += val * multiplier; + break; + case -1: // Stat all + stats[STATS_TYPE_STRENGTH] += val * multiplier; + stats[STATS_TYPE_AGILITY] += val * multiplier; + stats[STATS_TYPE_STAMINA] += val * multiplier; + stats[STATS_TYPE_INTELLECT] += val * multiplier; + stats[STATS_TYPE_SPIRIT] += val * multiplier; + break; + default: + break; + } + break; + } + case SPELL_AURA_MOD_RESISTANCE: + { + int32 statType = effectInfo.MiscValue; + if (statType & SPELL_SCHOOL_MASK_NORMAL) // physical + stats[STATS_TYPE_ARMOR] += val * multiplier; + break; + } + case SPELL_AURA_MOD_RATING: + { + for (uint32 rating = CR_WEAPON_SKILL; rating < MAX_COMBAT_RATING; ++rating) + { + if (effectInfo.MiscValue & (1 << rating)) + { + switch (rating) + { + case CR_DEFENSE_SKILL: + stats[STATS_TYPE_DEFENSE] += val * multiplier; + break; + case CR_DODGE: + stats[STATS_TYPE_DODGE] += val * multiplier; + break; + case CR_PARRY: + stats[STATS_TYPE_PARRY] += val * multiplier; + break; + case CR_BLOCK: + stats[STATS_TYPE_BLOCK_RATING] += val * multiplier; + break; + case CR_HIT_MELEE: + if (type_ == CollectorType::MELEE) + stats[STATS_TYPE_HIT] += val * multiplier; + break; + case CR_HIT_RANGED: + if (type_ == CollectorType::RANGED) + stats[STATS_TYPE_HIT] += val * multiplier; + break; + case CR_HIT_SPELL: + if (type_ == CollectorType::SPELL) + stats[STATS_TYPE_HIT] += val * multiplier; + break; + case CR_CRIT_MELEE: + if (type_ == CollectorType::MELEE) + stats[STATS_TYPE_CRIT] += val * multiplier; + break; + case CR_CRIT_RANGED: + if (type_ == CollectorType::RANGED) + stats[STATS_TYPE_CRIT] += val * multiplier; + break; + case CR_CRIT_SPELL: + if (type_ == CollectorType::SPELL) + stats[STATS_TYPE_CRIT] += val * multiplier; + break; + case CR_HASTE_MELEE: + if (type_ == CollectorType::MELEE) + stats[STATS_TYPE_HASTE] += val * multiplier; + break; + case CR_HASTE_RANGED: + if (type_ == CollectorType::RANGED) + stats[STATS_TYPE_HASTE] += val * multiplier; + break; + case CR_HASTE_SPELL: + if (type_ == CollectorType::SPELL) + stats[STATS_TYPE_HASTE] += val * multiplier; + break; + case CR_EXPERTISE: + stats[STATS_TYPE_EXPERTISE] += val * multiplier; + break; + case CR_ARMOR_PENETRATION: + stats[STATS_TYPE_ARMOR_PENETRATION] += val * multiplier; + break; + default: + break; + } + } + break; + } + } + case SPELL_AURA_MOD_POWER_REGEN: + { + int32 powerType = effectInfo.MiscValue; + switch (powerType) + { + case POWER_MANA: + stats[STATS_TYPE_MANA_REGENERATION] += val * multiplier; + default: + break; + } + break; + } + case SPELL_AURA_PROC_TRIGGER_SPELL: + { + if (canNextTrigger) + CollectSpellStats(effectInfo.TriggerSpell, true); + break; + } + case SPELL_AURA_PERIODIC_TRIGGER_SPELL: + { + if (canNextTrigger) + CollectSpellStats(effectInfo.TriggerSpell, true); + break; + } + default: + break; + } } \ No newline at end of file diff --git a/src/factory/StatsCollector.h b/src/factory/StatsCollector.h index dd2227cdf..95c81f217 100644 --- a/src/factory/StatsCollector.h +++ b/src/factory/StatsCollector.h @@ -29,9 +29,11 @@ enum StatsType : uint8 STATS_TYPE_BLOCK_RATING, STATS_TYPE_RESILIENCE, STATS_TYPE_HEALTH_REGENERATION, - // Stats for spell damage and heal + // Stats for spell damage STATS_TYPE_SPELL_POWER, STATS_TYPE_SPELL_PENETRATION, + // Stats for heal + STATS_TYPE_HEAL_POWER, STATS_TYPE_MANA_REGENERATION, // Stats for physical damage and melee STATS_TYPE_ATTACK_POWER, @@ -40,14 +42,16 @@ enum StatsType : uint8 // Stats for weapon dps STATS_TYPE_MELEE_DPS, STATS_TYPE_RANGED_DPS, - STATS_TYPE_MAX = 24 + STATS_TYPE_MAX = 25 }; -enum class CollectorType +enum CollectorType : uint8 { - MELEE, - RANGED, - SPELL + MELEE = 1, + RANGED = 2, + SPELL_DMG = 4, + SPELL_HEAL = 8, + SPELL = SPELL_DMG | SPELL_HEAL }; class StatsCollector @@ -57,18 +61,21 @@ class StatsCollector StatsCollector(StatsCollector& stats) = default; void Reset(); void CollectItemStats(ItemTemplate const* proto); - void CollectSpellStats(uint32 spellId, bool isTrigger = false); - void CollectPositiveSpellEffectStats(const SpellEffectInfo& effectInfo, float multiplier = 1.0f); + void CollectSpellStats(uint32 spellId, bool isTriggered = false); + void CollectPositiveSpellEffectStats(const SpellEffectInfo& effectInfo, float multiplier = 1.0f, bool canNextTrigger = true); void CollectEnchantStats(SpellItemEnchantmentEntry const* enchant); public: int32 stats[STATS_TYPE_MAX]; 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); + private: CollectorType type_; }; diff --git a/src/factory/StatsWeightCalculator.cpp b/src/factory/StatsWeightCalculator.cpp index 89f19054c..0fea5ef45 100644 --- a/src/factory/StatsWeightCalculator.cpp +++ b/src/factory/StatsWeightCalculator.cpp @@ -17,8 +17,10 @@ StatsWeightCalculator::StatsWeightCalculator(Player* player) : player_(player) { - if (PlayerbotAI::IsCaster(player)) - type_ = CollectorType::SPELL; + if (PlayerbotAI::IsHeal(player)) + type_ = CollectorType::SPELL_HEAL; + else if (PlayerbotAI::IsCaster(player)) + type_ = CollectorType::SPELL_DMG; else if (PlayerbotAI::IsMelee(player)) type_ = CollectorType::MELEE; else @@ -179,7 +181,7 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player) { stats_weights_[STATS_TYPE_INTELLECT] += 0.5f; stats_weights_[STATS_TYPE_SPIRIT] += 0.5f; - stats_weights_[STATS_TYPE_SPELL_POWER] += 1.0f; + 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_HASTE] += 1.0f; From 52b9dec2cd381dcc54b3a0114dafd4af85ffe5e1 Mon Sep 17 00:00:00 2001 From: Yunfan Li Date: Sun, 11 Aug 2024 02:10:50 +0800 Subject: [PATCH 07/14] 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 9cce4fadf..f940d7a9e 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 95c81f217..fa0634ceb 100644 --- a/src/factory/StatsCollector.h +++ b/src/factory/StatsCollector.h @@ -61,8 +61,7 @@ class StatsCollector 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 @@ class StatsCollector 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 0fea5ef45..267a5aefe 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 565977479..615760e2d 100644 --- a/src/strategy/actions/ChatActionContext.h +++ b/src/strategy/actions/ChatActionContext.h @@ -172,6 +172,7 @@ class ChatActionContext : public NamedObjectContext 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 @@ class ChatActionContext : public NamedObjectContext 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 1443ff431..159fb8457 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 a760b921d..1adc56d09 100644 --- a/src/strategy/actions/TellLosAction.h +++ b/src/strategy/actions/TellLosAction.h @@ -37,4 +37,13 @@ class TellExpectedDpsAction : public Action 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 09a08fcae..58ee95491 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 a63f1a2b6..7def8b140 100644 --- a/src/strategy/triggers/ChatTriggerContext.h +++ b/src/strategy/triggers/ChatTriggerContext.h @@ -121,6 +121,7 @@ class ChatTriggerContext : public NamedObjectContext creators["bwl"] = &ChatTriggerContext::bwl; creators["dps"] = &ChatTriggerContext::dps; creators["disperse"] = &ChatTriggerContext::disperse; + creators["calc"] = &ChatTriggerContext::calc; } private: @@ -221,6 +222,7 @@ class ChatTriggerContext : public NamedObjectContext 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 From 03487f83147fb2bcd27e9561ec509adb80e0a3bc Mon Sep 17 00:00:00 2001 From: Yunfan Li Date: Sun, 11 Aug 2024 22:22:16 +0800 Subject: [PATCH 08/14] More spell to stats calculation --- src/PlayerbotAI.cpp | 4 +- src/PlayerbotAI.h | 8 +- src/factory/PlayerbotFactory.cpp | 133 ++++++++++++++++------- src/factory/PlayerbotFactory.h | 1 + src/factory/StatsCollector.cpp | 76 ++++++++++++-- src/factory/StatsWeightCalculator.cpp | 145 +++++++++++++++++++------- src/factory/StatsWeightCalculator.h | 3 +- 7 files changed, 277 insertions(+), 93 deletions(-) diff --git a/src/PlayerbotAI.cpp b/src/PlayerbotAI.cpp index 34fba5ed6..852df3cb2 100644 --- a/src/PlayerbotAI.cpp +++ b/src/PlayerbotAI.cpp @@ -1677,7 +1677,7 @@ bool PlayerbotAI::IsTank(Player* player) switch (player->getClass()) { case CLASS_DEATH_KNIGHT: - if (tab == DEATHKNIGT_TAB_BLOOD) + if (tab == DEATHKNIGHT_TAB_BLOOD) { return true; } @@ -1781,7 +1781,7 @@ bool PlayerbotAI::IsDps(Player* player) } break; case CLASS_DEATH_KNIGHT: - if (tab != DEATHKNIGT_TAB_BLOOD) + if (tab != DEATHKNIGHT_TAB_BLOOD) { return true; } diff --git a/src/PlayerbotAI.h b/src/PlayerbotAI.h index fb2382fd4..51e46d0bb 100644 --- a/src/PlayerbotAI.h +++ b/src/PlayerbotAI.h @@ -233,11 +233,11 @@ enum PRIEST_TABS PRIEST_TAB_SHADOW, }; -enum DEATHKNIGT_TABS +enum DEATHKNIGHT_TABS { - DEATHKNIGT_TAB_BLOOD, - DEATHKNIGT_TAB_FROST, - DEATHKNIGT_TAB_UNHOLY, + DEATHKNIGHT_TAB_BLOOD, + DEATHKNIGHT_TAB_FROST, + DEATHKNIGHT_TAB_UNHOLY, }; enum DRUID_TABS diff --git a/src/factory/PlayerbotFactory.cpp b/src/factory/PlayerbotFactory.cpp index 5af68d030..23c997451 100644 --- a/src/factory/PlayerbotFactory.cpp +++ b/src/factory/PlayerbotFactory.cpp @@ -2291,6 +2291,7 @@ void PlayerbotFactory::InitClassSpells() bot->learnSpell(45462, true); bot->learnSpell(45902, true); // to leave DK starting area + bot->learnSpell(53428, false); bot->learnSpell(50977, false); break; case CLASS_HUNTER: @@ -2820,6 +2821,52 @@ void PlayerbotFactory::InitPotions() } } +std::vector PlayerbotFactory::GetCurrentGemsCount() +{ + std::vector curcount(4); + for (uint8 i = EQUIPMENT_SLOT_START; i < EQUIPMENT_SLOT_END; ++i) + { + Item* pItem2 = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, i); + if (pItem2 && !pItem2->IsBroken() && pItem2->HasSocket()) + { + for (uint32 enchant_slot = SOCK_ENCHANTMENT_SLOT; enchant_slot <= PRISMATIC_ENCHANTMENT_SLOT; ++enchant_slot) + { + if (enchant_slot == BONUS_ENCHANTMENT_SLOT) + continue; + + uint32 enchant_id = pItem2->GetEnchantmentId(EnchantmentSlot(enchant_slot)); + if (!enchant_id) + continue; + + SpellItemEnchantmentEntry const* enchantEntry = sSpellItemEnchantmentStore.LookupEntry(enchant_id); + if (!enchantEntry) + continue; + + uint32 gemid = enchantEntry->GemID; + if (!gemid) + continue; + + ItemTemplate const* gemProto = sObjectMgr->GetItemTemplate(gemid); + if (!gemProto) + continue; + + GemPropertiesEntry const* gemProperty = sGemPropertiesStore.LookupEntry(gemProto->GemProperties); + if (!gemProperty) + continue; + + uint8 GemColor = gemProperty->color; + + for (uint8 b = 0, tmpcolormask = 1; b < 4; b++, tmpcolormask <<= 1) + { + if (tmpcolormask & GemColor) + ++curcount[b]; + } + } + } + } + return curcount; +} + void PlayerbotFactory::InitFood() { if (sPlayerbotAIConfig->freeFood) @@ -3641,6 +3688,9 @@ void PlayerbotFactory::ApplyEnchantAndGemsNew(bool destoryOld) { int32 bestGemEnchantId[4] = {-1, -1, -1, -1}; // 1, 2, 4, 8 color float bestGemScore[4] = {0, 0, 0, 0}; + std::vector curCount = GetCurrentGemsCount(); + int requiredActive = bot->GetLevel() <= 70 ? 2 : 1; + std::vector availableGems; for (const uint32& enchantGem : enchantGemIdCache) { ItemTemplate const* gemTemplate = sObjectMgr->GetItemTemplate(enchantGem); @@ -3676,28 +3726,7 @@ void PlayerbotFactory::ApplyEnchantAndGemsNew(bool destoryOld) { continue; } - StatsWeightCalculator calculator(bot); - float score = calculator.CalculateEnchant(enchant_id); - if ((gemProperties->color & 1) && score >= bestGemScore[0]) - { - bestGemScore[0] = score; - bestGemEnchantId[0] = enchant_id; - } - if ((gemProperties->color & 2) && score >= bestGemScore[1]) - { - bestGemScore[1] = score; - bestGemEnchantId[1] = enchant_id; - } - if ((gemProperties->color & 4) && score >= bestGemScore[2]) - { - bestGemScore[2] = score; - bestGemEnchantId[2] = enchant_id; - } - if ((gemProperties->color & 8) && score >= bestGemScore[3]) - { - bestGemScore[3] = score; - bestGemEnchantId[3] = enchant_id; - } + availableGems.push_back(enchantGem); } for (uint8 slot = 0; slot < EQUIPMENT_SLOT_END; ++slot) @@ -3721,9 +3750,6 @@ void PlayerbotFactory::ApplyEnchantAndGemsNew(bool destoryOld) { continue; } - - if (spellInfo->SpellFamilyName == SPELLFAMILY_DEATHKNIGHT && bot->getClass() != CLASS_DEATH_KNIGHT) - continue; uint32 requiredLevel = spellInfo->BaseLevel; if (requiredLevel > bot->GetLevel()) @@ -3752,11 +3778,10 @@ void PlayerbotFactory::ApplyEnchantAndGemsNew(bool destoryOld) { continue; } - if (enchant->requiredSkill && bot->GetSkillValue(enchant->requiredSkill) < enchant->requiredSkillValue) + if (enchant->requiredSkill && (!bot->HasSkill(enchant->requiredSkill) || (bot->GetSkillValue(enchant->requiredSkill) < enchant->requiredSkillValue))) { continue; } - if (enchant->requiredLevel > bot->GetLevel()) { continue; @@ -3787,20 +3812,52 @@ void PlayerbotFactory::ApplyEnchantAndGemsNew(bool destoryOld) { continue; } - int32 gemId; - if (1 == socketColor) // meta - gemId = bestGemEnchantId[0]; - else if (2 == socketColor) // red - gemId = bestGemEnchantId[1]; - else if (4 == socketColor) // yellow - gemId = bestGemEnchantId[2]; - else if (8 == socketColor) // blue - gemId = bestGemEnchantId[3]; - else + int32 enchantIdChosen = -1; + int32 colorChosen; + float bestGemScore = -1; + for (uint32 &enchantGem : availableGems) + { + ItemTemplate const* gemTemplate = sObjectMgr->GetItemTemplate(enchantGem); + if (!gemTemplate) + continue; + + const GemPropertiesEntry* gemProperties = sGemPropertiesStore.LookupEntry(gemTemplate->GemProperties); + if (!gemProperties) + continue; + + if ((socketColor & gemProperties->color) == 0) + continue; + + uint32 enchant_id = gemProperties->spellitemenchantement; + if (!enchant_id) + continue; + + SpellItemEnchantmentEntry const* enchant = sSpellItemEnchantmentStore.LookupEntry(enchant_id); + StatsWeightCalculator calculator(bot); + float score = calculator.CalculateEnchant(enchant_id); + if (curCount[0] != 0) + // Ensure meta gem activation + for (int i = 1; i < curCount.size(); i++) + { + if (curCount[i] < requiredActive && (gemProperties->color & (1 << i))) + { + score *= 2; + break; + } + } + if (score > bestGemScore) + { + enchantIdChosen = enchant_id; + colorChosen = gemProperties->color; + bestGemScore = score; + } + } + if (enchantIdChosen == -1) continue; bot->ApplyEnchantment(item, EnchantmentSlot(enchant_slot), false); - item->SetEnchantment(EnchantmentSlot(enchant_slot), gemId, 0, 0, bot->GetGUID()); + item->SetEnchantment(EnchantmentSlot(enchant_slot), enchantIdChosen, 0, 0, bot->GetGUID()); bot->ApplyEnchantment(item, EnchantmentSlot(enchant_slot), true); + curCount = GetCurrentGemsCount(); } } } diff --git a/src/factory/PlayerbotFactory.h b/src/factory/PlayerbotFactory.h index 664c2b3de..65de201b4 100644 --- a/src/factory/PlayerbotFactory.h +++ b/src/factory/PlayerbotFactory.h @@ -158,6 +158,7 @@ class PlayerbotFactory void ResetQuests(); void InitPotions(); + std::vector GetCurrentGemsCount(); bool CanEquipArmor(ItemTemplate const* proto); bool CanEquipWeapon(ItemTemplate const* proto); void EnchantItem(Item* item); diff --git a/src/factory/StatsCollector.cpp b/src/factory/StatsCollector.cpp index f940d7a9e..8bfd63dd5 100644 --- a/src/factory/StatsCollector.cpp +++ b/src/factory/StatsCollector.cpp @@ -43,7 +43,26 @@ void StatsCollector::CollectItemStats(ItemTemplate const* proto) } for (uint8 j = 0; j < MAX_ITEM_PROTO_SPELLS; j++) { - CollectSpellStats(proto->Spells[j].SpellId, 1.0f, proto->Spells[j].SpellCooldown); + switch (proto->Spells[j].SpellTrigger) + { + case ITEM_SPELLTRIGGER_ON_USE: + CollectSpellStats(proto->Spells[j].SpellId, 1.0f, proto->Spells[j].SpellCooldown); + break; + case ITEM_SPELLTRIGGER_ON_EQUIP: + CollectSpellStats(proto->Spells[j].SpellId, 1.0f, 0); + break; + case ITEM_SPELLTRIGGER_CHANCE_ON_HIT: + if (type_ == CollectorType::MELEE) + { + if (proto->Spells[j].SpellPPMRate > 0.01f) + CollectSpellStats(proto->Spells[j].SpellId, 1.0f, 60000 / proto->Spells[j].SpellPPMRate); + else + CollectSpellStats(proto->Spells[j].SpellId, 1.0f, 60000 / 1.8f); // Default PPM = 1.8 + } + break; + default: + break; + } } if (proto->socketBonus) @@ -79,7 +98,13 @@ void StatsCollector::CollectSpellStats(uint32 spellId, float multiplier, int32 s canNextTrigger = false; if (spellInfo->StackAmount) { - multiplier *= 0.2f + spellInfo->StackAmount * 0.8; + // Heuristic multiplier for stackAmount since high stackAmount may not be available + if (spellInfo->StackAmount <= 10) + multiplier *= spellInfo->StackAmount * 0.6; + else if (spellInfo->StackAmount <= 20) + multiplier *= 6 + (spellInfo->StackAmount - 10) * 0.4; + else + multiplier *= 10; } for (int i = 0; i < MAX_SPELL_EFFECTS; i++) @@ -105,22 +130,45 @@ void StatsCollector::CollectSpellStats(uint32 spellId, float multiplier, int32 s } case SPELL_EFFECT_HEAL: { + /// @todo Handle spell without cooldown if (!spellCooldown) break; - float normalizedCd = std::max(spellCooldown, 5000); + float normalizedCd = std::max((float)spellCooldown / 1000, 5.0f); int32 val = AverageValue(effectInfo); - stats[STATS_TYPE_HEAL_POWER] += (float)val / (normalizedCd / 1000) * multiplier; + float transfer_multiplier = 1; + stats[STATS_TYPE_HEAL_POWER] += (float)val / normalizedCd * multiplier * transfer_multiplier; break; } case SPELL_EFFECT_ENERGIZE: { + /// @todo Handle spell without cooldown if (!spellCooldown) break; if (effectInfo.MiscValue != POWER_MANA) break; - float normalizedCd = std::max(spellCooldown, 5000); + float normalizedCd = std::max((float)spellCooldown / 1000, 5.0f); + int32 val = AverageValue(effectInfo); + float transfer_multiplier = 0.2; + stats[STATS_TYPE_MANA_REGENERATION] += (float)val / normalizedCd * multiplier * transfer_multiplier; + break; + } + case SPELL_EFFECT_SCHOOL_DAMAGE: + { + /// @todo Handle spell without cooldown + if (!spellCooldown) + break; + float normalizedCd = std::max((float)spellCooldown / 1000, 5.0f); int32 val = AverageValue(effectInfo); - stats[STATS_TYPE_MANA_REGENERATION] += (float)val / (normalizedCd / 1000 / 5) * multiplier; + if (type_ == CollectorType::MELEE || type_ == CollectorType::RANGED) + { + float transfer_multiplier = 1; + stats[STATS_TYPE_ATTACK_POWER] += (float)val / normalizedCd * multiplier * transfer_multiplier; + } + else if (type_ == CollectorType::SPELL_DMG) + { + float transfer_multiplier = 0.5; + stats[STATS_TYPE_SPELL_POWER] += (float)val / normalizedCd * multiplier * transfer_multiplier; + } break; } default: @@ -144,12 +192,13 @@ void StatsCollector::CollectEnchantStats(SpellItemEnchantmentEntry const* enchan { case ITEM_ENCHANTMENT_TYPE_COMBAT_SPELL: { - CollectSpellStats(enchant_spell_id, true); + if (type_ == CollectorType::MELEE) + CollectSpellStats(enchant_spell_id, 0.25f); break; } case ITEM_ENCHANTMENT_TYPE_EQUIP_SPELL: { - CollectSpellStats(enchant_spell_id, false); + CollectSpellStats(enchant_spell_id, 1.0f); break; } case ITEM_ENCHANTMENT_TYPE_STAT: @@ -207,10 +256,16 @@ bool StatsCollector::SpecialEnchantFilter(uint32 enchantSpellId) { switch (enchantSpellId) { + case 64440: + if (type_ == CollectorType::MELEE) + { + stats[STATS_TYPE_PARRY] += 50; + } + return true; case 53365: // Rune of the Fallen Crusader if (type_ == CollectorType::MELEE) { - stats[STATS_TYPE_STRENGTH] += 60; + stats[STATS_TYPE_STRENGTH] += 75; } return true; case 62157: // Rune of the Stoneskin Gargoyle @@ -296,7 +351,7 @@ void StatsCollector::CollectByItemStatType(uint32 itemStatType, int32 val) stats[STATS_TYPE_MANA_REGENERATION] += val / 10; break; case ITEM_MOD_HEALTH: - stats[STATS_TYPE_STAMINA] += val / 12; + stats[STATS_TYPE_STAMINA] += val / 15; break; case ITEM_MOD_AGILITY: stats[STATS_TYPE_AGILITY] += val; @@ -545,7 +600,6 @@ void StatsCollector::HandleApplyAura(const SpellEffectInfo& effectInfo, float mu break; } } - break; } } case SPELL_AURA_MOD_POWER_REGEN: diff --git a/src/factory/StatsWeightCalculator.cpp b/src/factory/StatsWeightCalculator.cpp index 267a5aefe..2797ae0d3 100644 --- a/src/factory/StatsWeightCalculator.cpp +++ b/src/factory/StatsWeightCalculator.cpp @@ -32,6 +32,7 @@ StatsWeightCalculator::StatsWeightCalculator(Player* player) : player_(player) enable_overflow_penalty_ = true; enable_item_set_bonus_ = true; + enable_quality_blend_ = true; } void StatsWeightCalculator::Reset() @@ -68,8 +69,9 @@ float StatsWeightCalculator::CalculateItem(uint32 itemId) CalculateSocketBonus(player_, proto); - // Blend with item quality and level - weight_ *= (proto->Quality + 1) * proto->ItemLevel; + // if (enable_quality_blend_) + // // Blend with item quality and level + // weight_ *= (proto->Quality + 1) * proto->ItemLevel; return weight_; } @@ -109,42 +111,111 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player) stats_weights_[STATS_TYPE_STAMINA] += 0.01f; stats_weights_[STATS_TYPE_ARMOR] += 0.001f; - if (cls == CLASS_HUNTER) + if (cls == CLASS_HUNTER && (tab == HUNTER_TAB_BEASTMASTER || tab == HUNTER_TAB_SURVIVAL)) { - stats_weights_[STATS_TYPE_AGILITY] += 2.5f; + stats_weights_[STATS_TYPE_AGILITY] += 2.4f; stats_weights_[STATS_TYPE_ATTACK_POWER] += 1.0f; - stats_weights_[STATS_TYPE_ARMOR_PENETRATION] += 2.5f; - stats_weights_[STATS_TYPE_HIT] += 2.0f; + stats_weights_[STATS_TYPE_ARMOR_PENETRATION] += 1.3f; + stats_weights_[STATS_TYPE_HIT] += 1.6f; + stats_weights_[STATS_TYPE_CRIT] += 1.5f; + stats_weights_[STATS_TYPE_HASTE] += 1.4f; + stats_weights_[STATS_TYPE_RANGED_DPS] += 5.0f; + } + else if (cls == CLASS_HUNTER && tab == HUNTER_TAB_MARKSMANSHIP) + { + stats_weights_[STATS_TYPE_AGILITY] += 2.2f; + stats_weights_[STATS_TYPE_ATTACK_POWER] += 1.0f; + stats_weights_[STATS_TYPE_ARMOR_PENETRATION] += 2.2f; + stats_weights_[STATS_TYPE_HIT] += 2.1f; stats_weights_[STATS_TYPE_CRIT] += 2.0f; - stats_weights_[STATS_TYPE_HASTE] += 2.0f; + stats_weights_[STATS_TYPE_HASTE] += 1.8f; stats_weights_[STATS_TYPE_RANGED_DPS] += 5.0f; } - else if (cls == CLASS_ROGUE || (cls == CLASS_DRUID && tab == DRUID_TAB_FERAL && !PlayerbotAI::IsTank(player))) + else if ((cls == CLASS_ROGUE && tab == ROGUE_TAB_COMBAT) || (cls == CLASS_DRUID && tab == DRUID_TAB_FERAL && !PlayerbotAI::IsTank(player))) { - stats_weights_[STATS_TYPE_AGILITY] += 2.0f; - stats_weights_[STATS_TYPE_STRENGTH] += 1.0f; + stats_weights_[STATS_TYPE_AGILITY] += 1.8f; + stats_weights_[STATS_TYPE_STRENGTH] += 1.1f; stats_weights_[STATS_TYPE_ATTACK_POWER] += 1.0f; - stats_weights_[STATS_TYPE_ARMOR_PENETRATION] += 1.5f; - stats_weights_[STATS_TYPE_HIT] += 1.5f; - stats_weights_[STATS_TYPE_CRIT] += 1.5f; + stats_weights_[STATS_TYPE_ARMOR_PENETRATION] += 1.2f; + stats_weights_[STATS_TYPE_HIT] += 2.0f; + stats_weights_[STATS_TYPE_CRIT] += 1.6f; + stats_weights_[STATS_TYPE_HASTE] += 1.4f; + stats_weights_[STATS_TYPE_EXPERTISE] += 2.0f; + stats_weights_[STATS_TYPE_MELEE_DPS] += 5.0f; + } + else if (cls == CLASS_ROGUE && (tab == ROGUE_TAB_ASSASSINATION || tab == ROGUE_TAB_SUBTLETY)) + { + stats_weights_[STATS_TYPE_AGILITY] += 1.7f; + stats_weights_[STATS_TYPE_STRENGTH] += 1.1f; + stats_weights_[STATS_TYPE_ATTACK_POWER] += 1.0f; + stats_weights_[STATS_TYPE_ARMOR_PENETRATION] += 1.0f; + stats_weights_[STATS_TYPE_HIT] += 1.6f; + stats_weights_[STATS_TYPE_CRIT] += 1.3f; stats_weights_[STATS_TYPE_HASTE] += 1.5f; + stats_weights_[STATS_TYPE_EXPERTISE] += 2.0f; + stats_weights_[STATS_TYPE_MELEE_DPS] += 5.0f; + } + else if (cls == CLASS_WARRIOR && tab == WARRIOR_TAB_FURY) // fury + { + stats_weights_[STATS_TYPE_AGILITY] += 1.8f; + stats_weights_[STATS_TYPE_STRENGTH] += 2.6f; + stats_weights_[STATS_TYPE_ATTACK_POWER] += 1.0f; + stats_weights_[STATS_TYPE_ARMOR_PENETRATION] += 2.1f; + stats_weights_[STATS_TYPE_HIT] += 2.3f; + stats_weights_[STATS_TYPE_CRIT] += 2.2f; + stats_weights_[STATS_TYPE_HASTE] += 1.8f; + stats_weights_[STATS_TYPE_EXPERTISE] += 2.5f; + stats_weights_[STATS_TYPE_MELEE_DPS] += 7.0f; + } + else if (cls == CLASS_WARRIOR && tab == WARRIOR_TAB_ARMS) // arm + { + stats_weights_[STATS_TYPE_AGILITY] += 1.6f; + stats_weights_[STATS_TYPE_STRENGTH] += 2.3f; + stats_weights_[STATS_TYPE_ATTACK_POWER] += 1.0f; + stats_weights_[STATS_TYPE_ARMOR_PENETRATION] += 1.7f; + stats_weights_[STATS_TYPE_HIT] += 2.0f; + stats_weights_[STATS_TYPE_CRIT] += 1.9f; + stats_weights_[STATS_TYPE_HASTE] += 0.8f; + stats_weights_[STATS_TYPE_EXPERTISE] += 1.4f; + stats_weights_[STATS_TYPE_MELEE_DPS] += 7.0f; + } + else if (cls == CLASS_DEATH_KNIGHT && tab == DEATHKNIGHT_TAB_FROST) // frost dk + { + stats_weights_[STATS_TYPE_AGILITY] += 1.8f; + stats_weights_[STATS_TYPE_STRENGTH] += 2.6f; + stats_weights_[STATS_TYPE_ATTACK_POWER] += 1.0f; + stats_weights_[STATS_TYPE_ARMOR_PENETRATION] += 2.1f; + stats_weights_[STATS_TYPE_HIT] += 2.3f; + stats_weights_[STATS_TYPE_CRIT] += 2.2f; + stats_weights_[STATS_TYPE_HASTE] += 1.8f; stats_weights_[STATS_TYPE_EXPERTISE] += 2.5f; + stats_weights_[STATS_TYPE_MELEE_DPS] += 7.0f; + } + else if (cls == CLASS_DEATH_KNIGHT && tab == DEATHKNIGHT_TAB_UNHOLY) + { + stats_weights_[STATS_TYPE_AGILITY] += 0.5f; + stats_weights_[STATS_TYPE_STRENGTH] += 2.5f; + stats_weights_[STATS_TYPE_ATTACK_POWER] += 1.0f; + stats_weights_[STATS_TYPE_ARMOR_PENETRATION] += 1.0f; + stats_weights_[STATS_TYPE_HIT] += 1.8f; + stats_weights_[STATS_TYPE_CRIT] += 1.0f; + stats_weights_[STATS_TYPE_HASTE] += 1.7f; + stats_weights_[STATS_TYPE_EXPERTISE] += 1.0f; stats_weights_[STATS_TYPE_MELEE_DPS] += 5.0f; } - else if ((cls == CLASS_PALADIN && tab == 2) || // retribution - (cls == CLASS_WARRIOR && tab != 2) || // arm / fury - (cls == CLASS_DEATH_KNIGHT && tab != 0) // ice / unholy - ) + else if (cls == CLASS_PALADIN && tab == PALADIN_TAB_RETRIBUTION) // retribution { - stats_weights_[STATS_TYPE_AGILITY] += 1.0f; - stats_weights_[STATS_TYPE_STRENGTH] += 2.0f; + stats_weights_[STATS_TYPE_AGILITY] += 1.1f; + stats_weights_[STATS_TYPE_STRENGTH] += 2.5f; + stats_weights_[STATS_TYPE_INTELLECT] += 0.15f; stats_weights_[STATS_TYPE_ATTACK_POWER] += 1.0f; - stats_weights_[STATS_TYPE_ARMOR_PENETRATION] += 2.0f; - stats_weights_[STATS_TYPE_HIT] += 1.5f; - stats_weights_[STATS_TYPE_CRIT] += 1.5f; - stats_weights_[STATS_TYPE_HASTE] += 1.5f; + stats_weights_[STATS_TYPE_SPELL_POWER] += 0.3f; + stats_weights_[STATS_TYPE_ARMOR_PENETRATION] += 0.8f; + stats_weights_[STATS_TYPE_HIT] += 1.9f; + stats_weights_[STATS_TYPE_CRIT] += 1.2f; + stats_weights_[STATS_TYPE_HASTE] += 1.3f; stats_weights_[STATS_TYPE_EXPERTISE] += 2.0f; - stats_weights_[STATS_TYPE_MELEE_DPS] += 5.0f; + stats_weights_[STATS_TYPE_MELEE_DPS] += 7.0f; } else if ((cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT)) // enhancement { @@ -158,7 +229,7 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player) stats_weights_[STATS_TYPE_CRIT] += 1.4f; stats_weights_[STATS_TYPE_HASTE] += 1.8f; stats_weights_[STATS_TYPE_EXPERTISE] += 2.0f; - stats_weights_[STATS_TYPE_MELEE_DPS] += 5.0f; + stats_weights_[STATS_TYPE_MELEE_DPS] += 5.2f; } else if (cls == CLASS_WARLOCK || cls == CLASS_MAGE || (cls == CLASS_PRIEST && tab == PRIEST_TAB_SHADOW) || // shadow @@ -166,11 +237,11 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player) (cls == CLASS_DRUID && tab == DRUID_TAB_BALANCE)) // balance { stats_weights_[STATS_TYPE_INTELLECT] += 0.5f; - stats_weights_[STATS_TYPE_SPIRIT] += 0.5f; + stats_weights_[STATS_TYPE_SPIRIT] += 0.4f; stats_weights_[STATS_TYPE_SPELL_POWER] += 1.0f; stats_weights_[STATS_TYPE_SPELL_PENETRATION] += 1.0f; - stats_weights_[STATS_TYPE_HIT] += 1.0f; - stats_weights_[STATS_TYPE_CRIT] += 1.0f; + stats_weights_[STATS_TYPE_HIT] += 1.1f; + stats_weights_[STATS_TYPE_CRIT] += 0.8f; stats_weights_[STATS_TYPE_HASTE] += 1.0f; stats_weights_[STATS_TYPE_RANGED_DPS] += 1.0f; } @@ -182,12 +253,12 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player) 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] += 1.5f; + stats_weights_[STATS_TYPE_MANA_REGENERATION] += 1.2f; stats_weights_[STATS_TYPE_CRIT] += 0.7f; stats_weights_[STATS_TYPE_HASTE] += 1.0f; stats_weights_[STATS_TYPE_RANGED_DPS] += 1.0f; } - else if ((cls == CLASS_WARRIOR && tab == 2) || (cls == CLASS_PALADIN && tab == 1)) + else if ((cls == CLASS_WARRIOR && tab == WARRIOR_TAB_PROTECTION) || (cls == CLASS_PALADIN && tab == PALADIN_TAB_PROTECTION)) { stats_weights_[STATS_TYPE_AGILITY] += 2.0f; stats_weights_[STATS_TYPE_STRENGTH] += 1.0f; @@ -199,14 +270,14 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player) stats_weights_[STATS_TYPE_RESILIENCE] += 2.0f; stats_weights_[STATS_TYPE_BLOCK_RATING] += 1.0f; stats_weights_[STATS_TYPE_BLOCK_VALUE] += 0.5f; - stats_weights_[STATS_TYPE_ARMOR] += 0.3f; + stats_weights_[STATS_TYPE_ARMOR] += 0.15f; stats_weights_[STATS_TYPE_HIT] += 2.0f; stats_weights_[STATS_TYPE_CRIT] += 0.2f; stats_weights_[STATS_TYPE_HASTE] += 0.5f; stats_weights_[STATS_TYPE_EXPERTISE] += 3.0f; stats_weights_[STATS_TYPE_MELEE_DPS] += 2.0f; } - else if (cls == CLASS_DEATH_KNIGHT && tab == 0) + else if (cls == CLASS_DEATH_KNIGHT && tab == DEATHKNIGHT_TAB_BLOOD) { stats_weights_[STATS_TYPE_AGILITY] += 2.0f; stats_weights_[STATS_TYPE_STRENGTH] += 1.0f; @@ -216,7 +287,7 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player) stats_weights_[STATS_TYPE_PARRY] += 2.0f; stats_weights_[STATS_TYPE_DODGE] += 2.0f; stats_weights_[STATS_TYPE_RESILIENCE] += 2.0f; - stats_weights_[STATS_TYPE_ARMOR] += 0.3f; + stats_weights_[STATS_TYPE_ARMOR] += 0.15f; stats_weights_[STATS_TYPE_HIT] += 2.0f; stats_weights_[STATS_TYPE_CRIT] += 0.5f; stats_weights_[STATS_TYPE_HASTE] += 0.5f; @@ -249,7 +320,7 @@ void StatsWeightCalculator::GenerateAdditionalWeights(Player* player) if (cls == CLASS_HUNTER) { if (player->HasAura(34484)) - stats_weights_[STATS_TYPE_INTELLECT] += 1.0f; + stats_weights_[STATS_TYPE_INTELLECT] += 1.1f; } else if (cls == CLASS_WARRIOR) { @@ -259,7 +330,7 @@ void StatsWeightCalculator::GenerateAdditionalWeights(Player* player) else if (cls == CLASS_SHAMAN) { if (player->HasAura(51885)) - stats_weights_[STATS_TYPE_INTELLECT] += 1.0f; + stats_weights_[STATS_TYPE_INTELLECT] += 1.1f; } } @@ -348,7 +419,7 @@ void StatsWeightCalculator::CalculateItemTypePenalty(ItemTemplate const* proto) // enhancement, rogue, ice dk, unholy dk, shield tank, fury warrior without titan's grip but with duel wield if (isDoubleHand && ((cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT && player_->CanDualWield()) || - (cls == CLASS_ROGUE) || (cls == CLASS_DEATH_KNIGHT && tab != DEATHKNIGT_TAB_BLOOD) || + (cls == CLASS_ROGUE) || (cls == CLASS_DEATH_KNIGHT && tab != DEATHKNIGHT_TAB_BLOOD) || (cls == CLASS_WARRIOR && tab == WARRIOR_TAB_FURY && !player_->CanTitanGrip() && player_->CanDualWield()) || (cls == CLASS_WARRIOR && tab == WARRIOR_TAB_PROTECTION) || (cls == CLASS_PALADIN && tab == PALADIN_TAB_PROTECTION))) @@ -361,7 +432,7 @@ void StatsWeightCalculator::CalculateItemTypePenalty(ItemTemplate const* proto) ((cls == CLASS_WARRIOR && tab == WARRIOR_TAB_FURY && !player_->CanDualWield()) || (cls == CLASS_WARRIOR && tab == WARRIOR_TAB_ARMS) || (cls == CLASS_DRUID && tab == DRUID_TAB_FERAL) || (cls == CLASS_PALADIN && tab == PALADIN_TAB_RETRIBUTION) || - (cls == CLASS_DEATH_KNIGHT && tab == DEATHKNIGT_TAB_BLOOD) || + (cls == CLASS_DEATH_KNIGHT && tab == DEATHKNIGHT_TAB_BLOOD) || (cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT && !player_->CanDualWield()))) { weight_ *= 10; diff --git a/src/factory/StatsWeightCalculator.h b/src/factory/StatsWeightCalculator.h index 1bef2a56b..030f15405 100644 --- a/src/factory/StatsWeightCalculator.h +++ b/src/factory/StatsWeightCalculator.h @@ -52,7 +52,8 @@ class StatsWeightCalculator int tab; bool enable_overflow_penalty_; bool enable_item_set_bonus_; - + bool enable_quality_blend_; + float weight_; float stats_weights_[STATS_TYPE_MAX]; }; From e5bd51e39547bafa1829c57b87cc67d2b19bf978 Mon Sep 17 00:00:00 2001 From: Yunfan Li Date: Sun, 11 Aug 2024 22:48:06 +0800 Subject: [PATCH 09/14] Enable quality blend --- src/factory/StatsWeightCalculator.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/factory/StatsWeightCalculator.cpp b/src/factory/StatsWeightCalculator.cpp index 2797ae0d3..19dd85a0d 100644 --- a/src/factory/StatsWeightCalculator.cpp +++ b/src/factory/StatsWeightCalculator.cpp @@ -69,9 +69,9 @@ float StatsWeightCalculator::CalculateItem(uint32 itemId) CalculateSocketBonus(player_, proto); - // if (enable_quality_blend_) - // // Blend with item quality and level - // weight_ *= (proto->Quality + 1) * proto->ItemLevel; + if (enable_quality_blend_) + // Blend with item quality and level + weight_ *= (proto->Quality + 1) * proto->ItemLevel; return weight_; } From 8e9e14923170ba6bf78ccc0e22e9ce86715b1b3c Mon Sep 17 00:00:00 2001 From: Yunfan Li Date: Sun, 11 Aug 2024 22:48:49 +0800 Subject: [PATCH 10/14] Reduce stackable spell score --- src/factory/StatsCollector.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/factory/StatsCollector.cpp b/src/factory/StatsCollector.cpp index 8bfd63dd5..287073caf 100644 --- a/src/factory/StatsCollector.cpp +++ b/src/factory/StatsCollector.cpp @@ -98,15 +98,14 @@ void StatsCollector::CollectSpellStats(uint32 spellId, float multiplier, int32 s canNextTrigger = false; if (spellInfo->StackAmount) { - // Heuristic multiplier for stackAmount since high stackAmount may not be available + // Heuristic multiplier for spell with stackAmount since high stackAmount may not be available if (spellInfo->StackAmount <= 10) multiplier *= spellInfo->StackAmount * 0.6; else if (spellInfo->StackAmount <= 20) - multiplier *= 6 + (spellInfo->StackAmount - 10) * 0.4; + multiplier *= 6 + (spellInfo->StackAmount - 10) * 0.2; else - multiplier *= 10; + multiplier *= 8; } - for (int i = 0; i < MAX_SPELL_EFFECTS; i++) { const SpellEffectInfo& effectInfo = spellInfo->Effects[i]; From 60b765d7c6707c01d7764fe3024e86f6502b2f34 Mon Sep 17 00:00:00 2001 From: Yunfan Li Date: Sun, 11 Aug 2024 23:13:18 +0800 Subject: [PATCH 11/14] Handle increase health aura --- src/factory/StatsCollector.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/factory/StatsCollector.cpp b/src/factory/StatsCollector.cpp index 287073caf..52e222051 100644 --- a/src/factory/StatsCollector.cpp +++ b/src/factory/StatsCollector.cpp @@ -102,10 +102,11 @@ void StatsCollector::CollectSpellStats(uint32 spellId, float multiplier, int32 s if (spellInfo->StackAmount <= 10) multiplier *= spellInfo->StackAmount * 0.6; else if (spellInfo->StackAmount <= 20) - multiplier *= 6 + (spellInfo->StackAmount - 10) * 0.2; + multiplier *= 6 + (spellInfo->StackAmount - 10) * 0.4; else - multiplier *= 8; + multiplier *= 10; } + for (int i = 0; i < MAX_SPELL_EFFECTS; i++) { const SpellEffectInfo& effectInfo = spellInfo->Effects[i]; @@ -220,6 +221,11 @@ bool StatsCollector::SpecialSpellFilter(uint32 spellId) { // trinket switch (spellId) { + case 39942: // Darkmoon Card: Wrath + if (type_ != CollectorType::SPELL_HEAL) + stats[STATS_TYPE_CRIT] += 50; + return true; + break; case 67702: // Death's Verdict stats[STATS_TYPE_ATTACK_POWER] += 225; return true; @@ -484,6 +490,9 @@ void StatsCollector::HandleApplyAura(const SpellEffectInfo& effectInfo, float mu case SPELL_AURA_MOD_HEALING_DONE: stats[STATS_TYPE_HEAL_POWER] += val * multiplier; break; + case SPELL_AURA_MOD_INCREASE_HEALTH: + stats[STATS_TYPE_STAMINA] += val * multiplier / 15; + break; case SPELL_AURA_MOD_ATTACK_POWER: stats[STATS_TYPE_ATTACK_POWER] += val * multiplier; break; From bcd3737f97af432177468bf55dbbfe6ebd07be45 Mon Sep 17 00:00:00 2001 From: Yunfan Li Date: Sun, 11 Aug 2024 23:30:12 +0800 Subject: [PATCH 12/14] Limit expansion for enchant and gear by default --- conf/playerbots.conf.dist | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conf/playerbots.conf.dist b/conf/playerbots.conf.dist index 9be0057ec..7e3551fa1 100644 --- a/conf/playerbots.conf.dist +++ b/conf/playerbots.conf.dist @@ -520,12 +520,12 @@ AiPlayerbot.MinEnchantingBotLevel = 60 # Enable expansion limitation for enchants - ie: level <= 60 bot only uses enchants # available in vanilla, level <= 70 bot only uses enchants available in TBC) # Default: 0 -AiPlayerbot.LimitEnchantExpansion = 0 +AiPlayerbot.LimitEnchantExpansion = 1 # Enable expansion limitation for gear - ie: level <= 60 bot only uses gear # available in vanilla, level <= 70 bot only uses gear available in TBC) # Default: 0 -AiPlayerbot.LimitGearExpansion = 0 +AiPlayerbot.LimitGearExpansion = 1 # Change random bot has lower gear AiPlayerbot.RandomGearLoweringChance = 0 From 840f5b8f2e621710579c445f4b681f64499165ee Mon Sep 17 00:00:00 2001 From: Yunfan Li Date: Sun, 11 Aug 2024 23:39:19 +0800 Subject: [PATCH 13/14] ID fix (Darkmoon Card: Wrath) --- src/factory/StatsCollector.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/factory/StatsCollector.cpp b/src/factory/StatsCollector.cpp index 52e222051..709651772 100644 --- a/src/factory/StatsCollector.cpp +++ b/src/factory/StatsCollector.cpp @@ -221,7 +221,7 @@ bool StatsCollector::SpecialSpellFilter(uint32 spellId) { // trinket switch (spellId) { - case 39942: // Darkmoon Card: Wrath + case 39442: // Darkmoon Card: Wrath if (type_ != CollectorType::SPELL_HEAL) stats[STATS_TYPE_CRIT] += 50; return true; From 32d2ac56a97f7848c24ef77e781f2ffb2d6af8db Mon Sep 17 00:00:00 2001 From: Yunfan Li Date: Mon, 12 Aug 2024 00:53:23 +0800 Subject: [PATCH 14/14] Change config description and default value --- conf/playerbots.conf.dist | 4 ++-- src/PlayerbotAIConfig.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/conf/playerbots.conf.dist b/conf/playerbots.conf.dist index 7e3551fa1..bd43dce57 100644 --- a/conf/playerbots.conf.dist +++ b/conf/playerbots.conf.dist @@ -519,12 +519,12 @@ AiPlayerbot.MinEnchantingBotLevel = 60 # Enable expansion limitation for enchants - ie: level <= 60 bot only uses enchants # available in vanilla, level <= 70 bot only uses enchants available in TBC) -# Default: 0 +# Default: 1 AiPlayerbot.LimitEnchantExpansion = 1 # Enable expansion limitation for gear - ie: level <= 60 bot only uses gear # available in vanilla, level <= 70 bot only uses gear available in TBC) -# Default: 0 +# Default: 1 AiPlayerbot.LimitGearExpansion = 1 # Change random bot has lower gear diff --git a/src/PlayerbotAIConfig.cpp b/src/PlayerbotAIConfig.cpp index 2ec929652..79ed9f7c0 100644 --- a/src/PlayerbotAIConfig.cpp +++ b/src/PlayerbotAIConfig.cpp @@ -343,8 +343,8 @@ bool PlayerbotAIConfig::Initialize() randombotsWalkingRPG = sConfigMgr->GetOption("AiPlayerbot.RandombotsWalkingRPG", false); randombotsWalkingRPGInDoors = sConfigMgr->GetOption("AiPlayerbot.RandombotsWalkingRPG.InDoors", false); minEnchantingBotLevel = sConfigMgr->GetOption("AiPlayerbot.MinEnchantingBotLevel", 60); - limitEnchantExpansion = sConfigMgr->GetOption("AiPlayerbot.LimitEnchantExpansion", 0); - limitGearExpansion = sConfigMgr->GetOption("AiPlayerbot.LimitGearExpansion", 0); + limitEnchantExpansion = sConfigMgr->GetOption("AiPlayerbot.LimitEnchantExpansion", 1); + limitGearExpansion = sConfigMgr->GetOption("AiPlayerbot.LimitGearExpansion", 1); randombotStartingLevel = sConfigMgr->GetOption("AiPlayerbot.RandombotStartingLevel", 5); enableRotation = sConfigMgr->GetOption("AiPlayerbot.EnableRotation", false); rotationPoolSize = sConfigMgr->GetOption("AiPlayerbot.RotationPoolSize", 500);