diff --git a/conf/playerbots.conf.dist b/conf/playerbots.conf.dist index 68ce2c2c..4fbf9c77 100644 --- a/conf/playerbots.conf.dist +++ b/conf/playerbots.conf.dist @@ -525,13 +525,13 @@ 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 +# 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 -AiPlayerbot.LimitGearExpansion = 0 +# Default: 1 +AiPlayerbot.LimitGearExpansion = 1 # Change random bot has lower gear AiPlayerbot.RandomGearLoweringChance = 0 diff --git a/src/PlayerbotAI.cpp b/src/PlayerbotAI.cpp index 67055987..b9d14573 100644 --- a/src/PlayerbotAI.cpp +++ b/src/PlayerbotAI.cpp @@ -809,11 +809,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 && @@ -1869,7 +1865,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; } @@ -1973,7 +1969,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 857d39ce..f0be101f 100644 --- a/src/PlayerbotAI.h +++ b/src/PlayerbotAI.h @@ -285,11 +285,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/PlayerbotAIConfig.cpp b/src/PlayerbotAIConfig.cpp index 4edcf6df..3782735e 100644 --- a/src/PlayerbotAIConfig.cpp +++ b/src/PlayerbotAIConfig.cpp @@ -434,8 +434,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); diff --git a/src/PlayerbotSecurity.cpp b/src/PlayerbotSecurity.cpp index e7241004..0a045cdb 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 2655052c..29bc672d 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/PlayerbotFactory.cpp b/src/factory/PlayerbotFactory.cpp similarity index 71% rename from src/PlayerbotFactory.cpp rename to src/factory/PlayerbotFactory.cpp index 84812822..78744c50 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, @@ -1558,6 +1556,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) @@ -1586,9 +1585,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; @@ -1598,14 +1600,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; @@ -2268,6 +2290,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: @@ -2797,6 +2820,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) @@ -3618,6 +3687,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); @@ -3653,28 +3725,7 @@ void PlayerbotFactory::ApplyEnchantAndGemsNew(bool destoryOld) { continue; } - - float score = CalculateEnchantScore(enchant_id, bot); - 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) @@ -3726,17 +3777,16 @@ 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; } - - float score = CalculateEnchantScore(enchant_id, bot); + StatsWeightCalculator calculator(bot); + float score = calculator.CalculateEnchant(enchant_id); if (score >= bestScore) { bestScore = score; @@ -3761,20 +3811,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(); } } } @@ -3871,1091 +3953,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..65de201b 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); @@ -160,6 +158,7 @@ private: void ResetQuests(); void InitPotions(); + std::vector GetCurrentGemsCount(); bool CanEquipArmor(ItemTemplate const* proto); bool CanEquipWeapon(ItemTemplate const* proto); void EnchantItem(Item* item); @@ -181,8 +180,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..70965177 --- /dev/null +++ b/src/factory/StatsCollector.cpp @@ -0,0 +1,661 @@ +#include "StatsCollector.h" + +#include + +#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(); } + +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++) + { + 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) + { + if (const SpellItemEnchantmentEntry *enchant = sSpellItemEnchantmentStore.LookupEntry(proto->socketBonus)) + CollectEnchantStats(enchant); + } +} + +void StatsCollector::CollectSpellStats(uint32 spellId, float multiplier, int32 spellCooldown) +{ + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); + + if (!spellInfo) + return; + + if (SpecialSpellFilter(spellId)) + return; + + const SpellProcEventEntry* eventEntry = sSpellMgr->GetSpellProcEvent(spellInfo->Id); + + uint32 triggerCooldown = eventEntry ? eventEntry->cooldown : 0; + + bool canNextTrigger = true; + + uint32 procFlags; + if (eventEntry && eventEntry->procFlags) + procFlags = eventEntry->procFlags; + else + procFlags = spellInfo->ProcFlags; + + if (procFlags && !CanBeTriggeredByType(spellInfo, procFlags)) + canNextTrigger = false; + if (spellInfo->StackAmount) + { + // 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; + else + multiplier *= 10; + } + + for (int i = 0; i < MAX_SPELL_EFFECTS; i++) + { + const SpellEffectInfo& effectInfo = spellInfo->Effects[i]; + switch (effectInfo.Effect) + { + case SPELL_EFFECT_APPLY_AURA: + { + /// @todo Handle negative spell + if (!spellInfo->IsPositive()) + break; + + float coverage; + if (spellCooldown <= 2000 || spellInfo->GetDuration() == -1) + coverage = 1.0f; + else + coverage = std::min(1.0f, (float)spellInfo->GetDuration() / (spellInfo->GetDuration() + spellCooldown)); + + multiplier *= coverage; + HandleApplyAura(effectInfo, multiplier, canNextTrigger, triggerCooldown); + break; + } + case SPELL_EFFECT_HEAL: + { + /// @todo Handle spell without cooldown + if (!spellCooldown) + break; + float normalizedCd = std::max((float)spellCooldown / 1000, 5.0f); + int32 val = AverageValue(effectInfo); + 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((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); + 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: + 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 (SpecialEnchantFilter(enchant_spell_id)) + continue; + + switch (enchant_display_type) + { + case ITEM_ENCHANTMENT_TYPE_COMBAT_SPELL: + { + if (type_ == CollectorType::MELEE) + CollectSpellStats(enchant_spell_id, 0.25f); + break; + } + case ITEM_ENCHANTMENT_TYPE_EQUIP_SPELL: + { + CollectSpellStats(enchant_spell_id, 1.0f); + break; + } + case ITEM_ENCHANTMENT_TYPE_STAT: + { + if (!enchant_amount) + { + break; + } + CollectByItemStatType(enchant_spell_id, enchant_amount); + break; + } + default: + break; + } + } +} + +/// @todo Special case for some spell that hard to calculate, like trinket, relic, etc. +bool StatsCollector::SpecialSpellFilter(uint32 spellId) { + // trinket + switch (spellId) + { + case 39442: // 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; + 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; + } + // switch (spellId) + // { + // case 50457: // Idol of the Lunar Eclipse + // stats[STATS_TYPE_CRIT] += 150; + // return true; + // default: + // break; + // } + return false; +} + +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] += 75; + } + return true; + case 62157: // Rune of the Stoneskin Gargoyle + if (type_ == CollectorType::MELEE) + { + stats[STATS_TYPE_DEFENSE] += 25; + stats[STATS_TYPE_STAMINA] += 50; + } + return true; + case 64571: // Blood draining + if (type_ == CollectorType::MELEE) + { + stats[STATS_TYPE_STAMINA] += 50; + } + return true; + default: + break; + } + 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: + { + triggerMask |= MELEE_PROC_FLAG_MASK; + triggerMask |= SPELL_PROC_FLAG_MASK; + triggerMask |= PERIODIC_PROC_FLAG_MASK; + if (procFlags & triggerMask) + return true; + break; + } + case CollectorType::RANGED: + { + 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; +} + +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_STAMINA] += val / 15; + 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; + stats[STATS_TYPE_HEAL_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; + } +} + +void StatsCollector::HandleApplyAura(const SpellEffectInfo& effectInfo, float multiplier, bool canNextTrigger, uint32 triggerCooldown) +{ + if (effectInfo.Effect != SPELL_EFFECT_APPLY_AURA) + return; + + int32 val = AverageValue(effectInfo); + + 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_INCREASE_HEALTH: + stats[STATS_TYPE_STAMINA] += val * multiplier / 15; + 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; + } + } + } + } + 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, multiplier, triggerCooldown); + break; + } + case SPELL_AURA_PERIODIC_TRIGGER_SPELL: + { + if (canNextTrigger) + 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 new file mode 100644 index 00000000..fa0634ce --- /dev/null +++ b/src/factory/StatsCollector.h @@ -0,0 +1,83 @@ +/* + * 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 + 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, + STATS_TYPE_ARMOR_PENETRATION, + STATS_TYPE_EXPERTISE, + // Stats for weapon dps + STATS_TYPE_MELEE_DPS, + STATS_TYPE_RANGED_DPS, + STATS_TYPE_MAX = 25 +}; + +enum CollectorType : uint8 +{ + MELEE = 1, + RANGED = 2, + SPELL_DMG = 4, + SPELL_HEAL = 8, + SPELL = SPELL_DMG | SPELL_HEAL +}; + +class StatsCollector +{ +public: + StatsCollector(CollectorType type); + StatsCollector(StatsCollector& stats) = default; + void Reset(); + void CollectItemStats(ItemTemplate const* proto); + void CollectSpellStats(uint32 spellId, float multiplier = 1.0f, int32 spellCooldown = -1); + 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 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_; +}; + +#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..19dd85a0 --- /dev/null +++ b/src/factory/StatsWeightCalculator.cpp @@ -0,0 +1,540 @@ +/* + * 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::IsHeal(player)) + type_ = CollectorType::SPELL_HEAL; + else if (PlayerbotAI::IsCaster(player)) + type_ = CollectorType::SPELL_DMG; + 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; + enable_quality_blend_ = 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); + + if (enable_quality_blend_) + // 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 && (tab == HUNTER_TAB_BEASTMASTER || tab == HUNTER_TAB_SURVIVAL)) + { + stats_weights_[STATS_TYPE_AGILITY] += 2.4f; + stats_weights_[STATS_TYPE_ATTACK_POWER] += 1.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] += 1.8f; + stats_weights_[STATS_TYPE_RANGED_DPS] += 5.0f; + } + else if ((cls == CLASS_ROGUE && tab == ROGUE_TAB_COMBAT) || (cls == CLASS_DRUID && tab == DRUID_TAB_FERAL && !PlayerbotAI::IsTank(player))) + { + 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.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 == PALADIN_TAB_RETRIBUTION) // retribution + { + 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_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] += 7.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.2f; + } + 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.4f; + stats_weights_[STATS_TYPE_SPELL_POWER] += 1.0f; + stats_weights_[STATS_TYPE_SPELL_PENETRATION] += 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; + } + 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.8f; + stats_weights_[STATS_TYPE_SPIRIT] += 0.8f; + stats_weights_[STATS_TYPE_HEAL_POWER] += 1.0f; + 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 == WARRIOR_TAB_PROTECTION) || (cls == CLASS_PALADIN && tab == PALADIN_TAB_PROTECTION)) + { + stats_weights_[STATS_TYPE_AGILITY] += 2.0f; + stats_weights_[STATS_TYPE_STRENGTH] += 1.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; + 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.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 == DEATHKNIGHT_TAB_BLOOD) + { + stats_weights_[STATS_TYPE_AGILITY] += 2.0f; + stats_weights_[STATS_TYPE_STRENGTH] += 1.0f; + 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; + stats_weights_[STATS_TYPE_DODGE] += 2.0f; + stats_weights_[STATS_TYPE_RESILIENCE] += 2.0f; + 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; + stats_weights_[STATS_TYPE_EXPERTISE] += 3.5f; + stats_weights_[STATS_TYPE_MELEE_DPS] += 2.0f; + } + else + { + // BEAR DRUID TANK + 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; + } +} + +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.1f; + } + 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.1f; + } +} + +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 != 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))) + { + 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 == DEATHKNIGHT_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 = DEFENSE_OVERFLOW; + if (defense_current >= defense_overflow) + stats_weights_[STATS_TYPE_DEFENSE] /= 2; + else if (defense_current >= defense_overflow * 0.8) + stats_weights_[STATS_TYPE_DEFENSE] /= 1.5; + } + } + + { + if (type_ == CollectorType::MELEE || type_ == CollectorType::RANGED) + { + 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_penetration_current >= armor_penetration_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..030f1540 --- /dev/null +++ b/src/factory/StatsWeightCalculator.h @@ -0,0 +1,61 @@ +/* + * 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_; + bool enable_quality_blend_; + + 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/actions/ChatActionContext.h b/src/strategy/actions/ChatActionContext.h index 0da2aa3e..0513c930 100644 --- a/src/strategy/actions/ChatActionContext.h +++ b/src/strategy/actions/ChatActionContext.h @@ -173,6 +173,7 @@ public: creators["bwl chat shortcut"] = &ChatActionContext::bwl_chat_shortcut; creators["tell expected dps"] = &ChatActionContext::tell_expected_dps; creators["join"] = &ChatActionContext::join; + creators["calc"] = &ChatActionContext::calc; } private: @@ -270,6 +271,7 @@ private: static Action* bwl_chat_shortcut(PlayerbotAI* ai) { return new BwlChatShortcutAction(ai); } static Action* tell_expected_dps(PlayerbotAI* ai) { return new TellExpectedDpsAction(ai); } static Action* join(PlayerbotAI* ai) { return new JoinGroupAction(ai); } + static Action* calc(PlayerbotAI* ai) { return new TellCalculateItemAction(ai); } }; #endif diff --git a/src/strategy/actions/MovementActions.cpp b/src/strategy/actions/MovementActions.cpp index 149773a7..ea460bc1 100644 --- a/src/strategy/actions/MovementActions.cpp +++ b/src/strategy/actions/MovementActions.cpp @@ -827,15 +827,21 @@ bool MovementAction::ReachCombatTo(Unit* target, float distance) ty = target->GetPositionY(); tz = target->GetPositionZ(); } + // Prediction may cause this, which makes ShortenPathUntilDist fail + if (bot->GetExactDist(tx, ty, tz) <= distance) + { + tx = target->GetPositionX(); + ty = target->GetPositionY(); + tz = target->GetPositionZ(); + } } if (bot->GetExactDist(tx, ty, tz) <= distance) - { return false; - } PathGenerator path(bot); path.CalculatePath(tx, ty, tz, false); PathType type = path.GetPathType(); - if (type != PATHFIND_NORMAL && type != PATHFIND_INCOMPLETE) + int typeOk = PATHFIND_NORMAL | PATHFIND_INCOMPLETE; + if (!(type & typeOk)) return false; path.ShortenPathUntilDist(G3D::Vector3(tx, ty, tz), distance); G3D::Vector3 endPos = path.GetPath().back(); @@ -881,7 +887,7 @@ bool MovementAction::IsMovingAllowed(Unit* target) bool MovementAction::IsMovingAllowed(uint32 mapId, float x, float y, float z) { - // removed sqrt as means distance limit was effectively 22500 (ReactDistance²) + // removed sqrt as means distance limit was effectively 22500 (ReactDistance�) // leaving it commented incase we find ReactDistance limit causes problems // float distance = sqrt(bot->GetDistance(x, y, z)); float distance = bot->GetDistance(x, y, z); @@ -2351,10 +2357,15 @@ bool MoveRandomAction::Execute(Event event) float angle = (float)rand_norm() * static_cast(M_PI); x += urand(0, distance) * cos(angle); y += urand(0, distance) * sin(angle); + float ox = x; + float oy = y; + float oz = z; if (!bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), x, y, z)) { - continue; + x = ox; + y = oy; + z = oz; } if (map->IsInWater(bot->GetPhaseMask(), x, y, z, bot->GetCollisionHeight())) continue; diff --git a/src/strategy/actions/TellLosAction.cpp b/src/strategy/actions/TellLosAction.cpp index 1443ff43..159fb845 100644 --- a/src/strategy/actions/TellLosAction.cpp +++ b/src/strategy/actions/TellLosAction.cpp @@ -4,10 +4,16 @@ */ #include "TellLosAction.h" +#include +#include #include "ChatHelper.h" +#include "DBCStores.h" #include "Event.h" +#include "ItemTemplate.h" +#include "ObjectMgr.h" #include "Playerbots.h" +#include "StatsWeightCalculator.h" #include "World.h" bool TellLosAction::Execute(Event event) @@ -130,3 +136,21 @@ bool TellExpectedDpsAction::Execute(Event event) botAI->TellMaster("Expected Group DPS: " + std::to_string(dps)); return true; } + +bool TellCalculateItemAction::Execute(Event event) +{ + std::string const text = event.getParam(); + ItemIds ids = chat->parseItems(text); + StatsWeightCalculator calculator(bot); + for (const uint32 &id : ids) + { + const ItemTemplate* proto = sObjectMgr->GetItemTemplate(id); + if (!proto) + continue; + float score = calculator.CalculateItem(id); + std::ostringstream out; + out << "Calculated score of " << chat->FormatItem(proto) << " : " << score; + botAI->TellMasterNoFacing(out.str()); + } + return true; +} \ No newline at end of file diff --git a/src/strategy/actions/TellLosAction.h b/src/strategy/actions/TellLosAction.h index a760b921..1adc56d0 100644 --- a/src/strategy/actions/TellLosAction.h +++ b/src/strategy/actions/TellLosAction.h @@ -37,4 +37,13 @@ public: virtual bool Execute(Event event); }; + +class TellCalculateItemAction : public Action +{ +public: + TellCalculateItemAction(PlayerbotAI* ai) : Action(ai, "calculate item") {} + + virtual bool Execute(Event event); +}; + #endif diff --git a/src/strategy/actions/TradeStatusAction.cpp b/src/strategy/actions/TradeStatusAction.cpp index b1284a80..4076277c 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; diff --git a/src/strategy/generic/ChatCommandHandlerStrategy.cpp b/src/strategy/generic/ChatCommandHandlerStrategy.cpp index 09a08fca..58ee9549 100644 --- a/src/strategy/generic/ChatCommandHandlerStrategy.cpp +++ b/src/strategy/generic/ChatCommandHandlerStrategy.cpp @@ -157,4 +157,5 @@ ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : Pas supported.push_back("guild leave"); supported.push_back("rtsc"); supported.push_back("drink"); + supported.push_back("calc"); } diff --git a/src/strategy/triggers/ChatTriggerContext.h b/src/strategy/triggers/ChatTriggerContext.h index a63f1a2b..7def8b14 100644 --- a/src/strategy/triggers/ChatTriggerContext.h +++ b/src/strategy/triggers/ChatTriggerContext.h @@ -121,6 +121,7 @@ public: creators["bwl"] = &ChatTriggerContext::bwl; creators["dps"] = &ChatTriggerContext::dps; creators["disperse"] = &ChatTriggerContext::disperse; + creators["calc"] = &ChatTriggerContext::calc; } private: @@ -221,6 +222,7 @@ private: static Trigger* bwl(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "bwl"); } static Trigger* dps(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "dps"); } static Trigger* disperse(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "disperse"); } + static Trigger* calc(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "calc"); } }; #endif diff --git a/src/strategy/values/Arrow.cpp b/src/strategy/values/Arrow.cpp index a981eade..f52dc670 100644 --- a/src/strategy/values/Arrow.cpp +++ b/src/strategy/values/Arrow.cpp @@ -47,11 +47,14 @@ WorldLocation ArrowFormation::GetLocationInternal() float x = master->GetPositionX() - masterUnit->GetX() + botUnit->GetX(); float y = master->GetPositionY() - masterUnit->GetY() + botUnit->GetY(); float z = master->GetPositionZ(); - if (!master->GetMap()->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(), master->GetPositionZ(), x, y, z)) - return Formation::NullLocation; - // master->UpdateGroundPositionZ(x, y, z); + { + x = master->GetPositionX() - masterUnit->GetX() + botUnit->GetX(); + y = master->GetPositionY() - masterUnit->GetY() + botUnit->GetY(); + z = master->GetPositionZ() + master->GetHoverHeight(); + z = master->GetMapHeight(x, y, z); + } return WorldLocation(master->GetMapId(), x, y, z); } diff --git a/src/strategy/values/Formations.cpp b/src/strategy/values/Formations.cpp index 9418b441..61ec1b3d 100644 --- a/src/strategy/values/Formations.cpp +++ b/src/strategy/values/Formations.cpp @@ -7,6 +7,7 @@ #include "Arrow.h" #include "Event.h" +#include "Map.h" #include "Playerbots.h" #include "ServerFacade.h" @@ -88,10 +89,14 @@ public: float x = master->GetPositionX() + cos(angle) * range; float y = master->GetPositionY() + sin(angle) * range; float z = master->GetPositionZ(); - if (!master->GetMap()->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(), - master->GetPositionZ(), x, y, z)) - return Formation::NullLocation; + master->GetPositionZ(), x, y, z)) + { + x = master->GetPositionX() + cos(angle) * range; + y = master->GetPositionY() + sin(angle) * range; + z = master->GetPositionZ() + master->GetHoverHeight(); + master->UpdateAllowedPositionZ(x, y, z); + } return WorldLocation(master->GetMapId(), x, y, z); } @@ -134,9 +139,15 @@ public: float x = master->GetPositionX() + cos(angle) * range + dx; float y = master->GetPositionY() + sin(angle) * range + dy; float z = master->GetPositionZ(); - if (!master->GetMap()->CheckCollisionAndGetValidCoords( - master, master->GetPositionX(), master->GetPositionY(), master->GetPositionZ(), x, y, z)) - return Formation::NullLocation; + z = bot->GetMapHeight(x, y, z + 5.0f); + if (!master->GetMap()->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(), + master->GetPositionZ(), x, y, z)) + { + x = master->GetPositionX() + cos(angle) * range + dx; + y = master->GetPositionY() + sin(angle) * range + dy; + z = master->GetPositionZ() + master->GetHoverHeight(); + z = master->GetMapHeight(x, y, z); + } // bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(), // bot->GetPositionZ(), x, y, z); return WorldLocation(master->GetMapId(), x, y, z); @@ -145,9 +156,15 @@ public: float x = master->GetPositionX() + cos(angle) * range + dx; float y = master->GetPositionY() + sin(angle) * range + dy; float z = master->GetPositionZ(); + z = bot->GetMapHeight(x, y, z + 5.0f); if (!master->GetMap()->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(), - master->GetPositionZ(), x, y, z)) - return Formation::NullLocation; + master->GetPositionZ(), x, y, z)) + { + x = master->GetPositionX() + cos(angle) * range + dx; + y = master->GetPositionY() + sin(angle) * range + dy; + z = master->GetPositionZ() + master->GetHoverHeight(); + z = master->GetMapHeight(x, y, z); + } return WorldLocation(master->GetMapId(), x, y, z); } @@ -200,9 +217,13 @@ public: float y = target->GetPositionY() + sin(angle) * range; float z = target->GetPositionZ(); if (!master->GetMap()->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(), - master->GetPositionZ(), x, y, z)) - return Formation::NullLocation; - + master->GetPositionZ(), x, y, z)) + { + x = target->GetPositionX() + cos(angle) * range; + y = target->GetPositionY() + sin(angle) * range; + z = target->GetPositionZ() + target->GetHoverHeight(); + z = master->GetMapHeight(x, y, z); + } return WorldLocation(bot->GetMapId(), x, y, z); } }; @@ -362,9 +383,14 @@ public: if (minDist) { - if (!master->GetMap()->CheckCollisionAndGetValidCoords( - master, master->GetPositionX(), master->GetPositionY(), master->GetPositionZ(), x, y, z)) - return Formation::NullLocation; + if (!master->GetMap()->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(), + master->GetPositionZ(), x, y, z)) + { + x = master->GetPositionX() + cos(angle) * range + cos(followAngle) * followRange; + y = master->GetPositionY() + sin(angle) * range + sin(followAngle) * followRange; + z = master->GetPositionZ() + master->GetHoverHeight(); + z = master->GetMapHeight(x, y, z); + } return WorldLocation(bot->GetMapId(), minX, minY, z); } @@ -372,8 +398,13 @@ public: } if (!master->GetMap()->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(), - master->GetPositionZ(), x, y, z)) - return Formation::NullLocation; + master->GetPositionZ(), x, y, z)) + { + x = master->GetPositionX() + cos(angle) * range + cos(followAngle) * followRange; + y = master->GetPositionY() + sin(angle) * range + sin(followAngle) * followRange; + z = master->GetPositionZ() + master->GetHoverHeight(); + z = master->GetMapHeight(x, y, z); + } return WorldLocation(bot->GetMapId(), x, y, z); } }; @@ -636,7 +667,11 @@ WorldLocation MoveFormation::MoveSingleLine(std::vector line, float dif Player* master = botAI->GetMaster(); if (!master->GetMap()->CheckCollisionAndGetValidCoords( master, master->GetPositionX(), master->GetPositionY(), master->GetPositionZ(), lx, ly, lz)) - return Formation::NullLocation; + { + lx = x + cos(angle) * radius; + ly = y + sin(angle) * radius; + lz = cz; + } return WorldLocation(bot->GetMapId(), lx, ly, lz); } 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);