From 52bd378719025fc642463f3027ad01798036d382 Mon Sep 17 00:00:00 2001 From: Yunfan Li Date: Thu, 8 Aug 2024 20:36:32 +0800 Subject: [PATCH] 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 4591471b..3ecc46d2 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 841c5afb..664c2b3d 100644 --- a/src/PlayerbotFactory.h +++ b/src/factory/PlayerbotFactory.h @@ -116,9 +116,7 @@ public: 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 @@ private: 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 00000000..86c54476 --- /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 00000000..795f6a93 --- /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 00000000..7d5e3c21 --- /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 00000000..1bef2a56 --- /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 cbaa8965..62e03646 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 f42aca0c..ca294165 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);