mirror of
https://github.com/mod-playerbots/mod-playerbots
synced 2025-11-29 15:58:20 +08:00
Merge pull request #463 from liyunfan1223/rewrite-equip-score2
Rewrite item score calculation for gear initialization
This commit is contained in:
@@ -519,13 +519,13 @@ AiPlayerbot.MinEnchantingBotLevel = 60
|
|||||||
|
|
||||||
# Enable expansion limitation for enchants - ie: level <= 60 bot only uses enchants
|
# Enable expansion limitation for enchants - ie: level <= 60 bot only uses enchants
|
||||||
# available in vanilla, level <= 70 bot only uses enchants available in TBC)
|
# available in vanilla, level <= 70 bot only uses enchants available in TBC)
|
||||||
# Default: 0
|
# Default: 1
|
||||||
AiPlayerbot.LimitEnchantExpansion = 0
|
AiPlayerbot.LimitEnchantExpansion = 1
|
||||||
|
|
||||||
# Enable expansion limitation for gear - ie: level <= 60 bot only uses gear
|
# Enable expansion limitation for gear - ie: level <= 60 bot only uses gear
|
||||||
# available in vanilla, level <= 70 bot only uses gear available in TBC)
|
# available in vanilla, level <= 70 bot only uses gear available in TBC)
|
||||||
# Default: 0
|
# Default: 1
|
||||||
AiPlayerbot.LimitGearExpansion = 0
|
AiPlayerbot.LimitGearExpansion = 1
|
||||||
|
|
||||||
# Change random bot has lower gear
|
# Change random bot has lower gear
|
||||||
AiPlayerbot.RandomGearLoweringChance = 0
|
AiPlayerbot.RandomGearLoweringChance = 0
|
||||||
|
|||||||
@@ -1677,7 +1677,7 @@ bool PlayerbotAI::IsTank(Player* player)
|
|||||||
switch (player->getClass())
|
switch (player->getClass())
|
||||||
{
|
{
|
||||||
case CLASS_DEATH_KNIGHT:
|
case CLASS_DEATH_KNIGHT:
|
||||||
if (tab == DEATHKNIGT_TAB_BLOOD)
|
if (tab == DEATHKNIGHT_TAB_BLOOD)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1781,7 +1781,7 @@ bool PlayerbotAI::IsDps(Player* player)
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case CLASS_DEATH_KNIGHT:
|
case CLASS_DEATH_KNIGHT:
|
||||||
if (tab != DEATHKNIGT_TAB_BLOOD)
|
if (tab != DEATHKNIGHT_TAB_BLOOD)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -233,11 +233,11 @@ enum PRIEST_TABS
|
|||||||
PRIEST_TAB_SHADOW,
|
PRIEST_TAB_SHADOW,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum DEATHKNIGT_TABS
|
enum DEATHKNIGHT_TABS
|
||||||
{
|
{
|
||||||
DEATHKNIGT_TAB_BLOOD,
|
DEATHKNIGHT_TAB_BLOOD,
|
||||||
DEATHKNIGT_TAB_FROST,
|
DEATHKNIGHT_TAB_FROST,
|
||||||
DEATHKNIGT_TAB_UNHOLY,
|
DEATHKNIGHT_TAB_UNHOLY,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum DRUID_TABS
|
enum DRUID_TABS
|
||||||
|
|||||||
@@ -343,8 +343,8 @@ bool PlayerbotAIConfig::Initialize()
|
|||||||
randombotsWalkingRPG = sConfigMgr->GetOption<bool>("AiPlayerbot.RandombotsWalkingRPG", false);
|
randombotsWalkingRPG = sConfigMgr->GetOption<bool>("AiPlayerbot.RandombotsWalkingRPG", false);
|
||||||
randombotsWalkingRPGInDoors = sConfigMgr->GetOption<bool>("AiPlayerbot.RandombotsWalkingRPG.InDoors", false);
|
randombotsWalkingRPGInDoors = sConfigMgr->GetOption<bool>("AiPlayerbot.RandombotsWalkingRPG.InDoors", false);
|
||||||
minEnchantingBotLevel = sConfigMgr->GetOption<int32>("AiPlayerbot.MinEnchantingBotLevel", 60);
|
minEnchantingBotLevel = sConfigMgr->GetOption<int32>("AiPlayerbot.MinEnchantingBotLevel", 60);
|
||||||
limitEnchantExpansion = sConfigMgr->GetOption<int32>("AiPlayerbot.LimitEnchantExpansion", 0);
|
limitEnchantExpansion = sConfigMgr->GetOption<int32>("AiPlayerbot.LimitEnchantExpansion", 1);
|
||||||
limitGearExpansion = sConfigMgr->GetOption<int32>("AiPlayerbot.LimitGearExpansion", 0);
|
limitGearExpansion = sConfigMgr->GetOption<int32>("AiPlayerbot.LimitGearExpansion", 1);
|
||||||
randombotStartingLevel = sConfigMgr->GetOption<int32>("AiPlayerbot.RandombotStartingLevel", 5);
|
randombotStartingLevel = sConfigMgr->GetOption<int32>("AiPlayerbot.RandombotStartingLevel", 5);
|
||||||
enableRotation = sConfigMgr->GetOption<bool>("AiPlayerbot.EnableRotation", false);
|
enableRotation = sConfigMgr->GetOption<bool>("AiPlayerbot.EnableRotation", false);
|
||||||
rotationPoolSize = sConfigMgr->GetOption<int32>("AiPlayerbot.RotationPoolSize", 500);
|
rotationPoolSize = sConfigMgr->GetOption<int32>("AiPlayerbot.RotationPoolSize", 500);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -116,9 +116,7 @@ public:
|
|||||||
void InitSkills();
|
void InitSkills();
|
||||||
|
|
||||||
static uint32 tradeSkills[];
|
static uint32 tradeSkills[];
|
||||||
static float CalculateItemScore(uint32 item_id, Player* bot);
|
|
||||||
static float CalculateEnchantScore(uint32 enchant_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);
|
void InitTalentsTree(bool incremental = false, bool use_template = true, bool reset = false);
|
||||||
static void InitTalentsBySpecNo(Player* bot, int specNo, bool reset);
|
static void InitTalentsBySpecNo(Player* bot, int specNo, bool reset);
|
||||||
static void InitTalentsByParsedSpecLink(Player* bot, std::vector<std::vector<uint32>> parsedSpecLink, bool reset);
|
static void InitTalentsByParsedSpecLink(Player* bot, std::vector<std::vector<uint32>> parsedSpecLink, bool reset);
|
||||||
@@ -160,6 +158,7 @@ private:
|
|||||||
void ResetQuests();
|
void ResetQuests();
|
||||||
void InitPotions();
|
void InitPotions();
|
||||||
|
|
||||||
|
std::vector<uint32> GetCurrentGemsCount();
|
||||||
bool CanEquipArmor(ItemTemplate const* proto);
|
bool CanEquipArmor(ItemTemplate const* proto);
|
||||||
bool CanEquipWeapon(ItemTemplate const* proto);
|
bool CanEquipWeapon(ItemTemplate const* proto);
|
||||||
void EnchantItem(Item* item);
|
void EnchantItem(Item* item);
|
||||||
@@ -181,8 +180,6 @@ private:
|
|||||||
void ApplyEnchantTemplate();
|
void ApplyEnchantTemplate();
|
||||||
void ApplyEnchantTemplate(uint8 spec);
|
void ApplyEnchantTemplate(uint8 spec);
|
||||||
std::vector<InventoryType> GetPossibleInventoryTypeListBySlot(EquipmentSlots slot);
|
std::vector<InventoryType> 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 IterateItems(IterateItemsVisitor* visitor, IterateItemsMask mask = ITERATE_ITEMS_IN_BAGS);
|
||||||
void IterateItemsInBags(IterateItemsVisitor* visitor);
|
void IterateItemsInBags(IterateItemsVisitor* visitor);
|
||||||
void IterateItemsInEquip(IterateItemsVisitor* visitor);
|
void IterateItemsInEquip(IterateItemsVisitor* visitor);
|
||||||
661
src/factory/StatsCollector.cpp
Normal file
661
src/factory/StatsCollector.cpp
Normal file
@@ -0,0 +1,661 @@
|
|||||||
|
#include "StatsCollector.h"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
83
src/factory/StatsCollector.h
Normal file
83
src/factory/StatsCollector.h
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, 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
|
||||||
540
src/factory/StatsWeightCalculator.cpp
Normal file
540
src/factory/StatsWeightCalculator.cpp
Normal file
@@ -0,0 +1,540 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, 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 <memory>
|
||||||
|
|
||||||
|
#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<StatsCollector>(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
61
src/factory/StatsWeightCalculator.h
Normal file
61
src/factory/StatsWeightCalculator.h
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, 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<StatsCollector> 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
|
||||||
@@ -18,8 +18,6 @@ bool ChangeTalentsAction::Execute(Event event)
|
|||||||
|
|
||||||
std::ostringstream out;
|
std::ostringstream out;
|
||||||
|
|
||||||
TalentSpec botSpec(bot);
|
|
||||||
|
|
||||||
if (!param.empty())
|
if (!param.empty())
|
||||||
{
|
{
|
||||||
if (param.find("help") != std::string::npos)
|
if (param.find("help") != std::string::npos)
|
||||||
|
|||||||
@@ -172,6 +172,7 @@ public:
|
|||||||
creators["naxx chat shortcut"] = &ChatActionContext::naxx_chat_shortcut;
|
creators["naxx chat shortcut"] = &ChatActionContext::naxx_chat_shortcut;
|
||||||
creators["bwl chat shortcut"] = &ChatActionContext::bwl_chat_shortcut;
|
creators["bwl chat shortcut"] = &ChatActionContext::bwl_chat_shortcut;
|
||||||
creators["tell expected dps"] = &ChatActionContext::tell_expected_dps;
|
creators["tell expected dps"] = &ChatActionContext::tell_expected_dps;
|
||||||
|
creators["calc"] = &ChatActionContext::calc;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -268,6 +269,7 @@ private:
|
|||||||
static Action* naxx_chat_shortcut(PlayerbotAI* ai) { return new NaxxChatShortcutAction(ai); }
|
static Action* naxx_chat_shortcut(PlayerbotAI* ai) { return new NaxxChatShortcutAction(ai); }
|
||||||
static Action* bwl_chat_shortcut(PlayerbotAI* ai) { return new BwlChatShortcutAction(ai); }
|
static Action* bwl_chat_shortcut(PlayerbotAI* ai) { return new BwlChatShortcutAction(ai); }
|
||||||
static Action* tell_expected_dps(PlayerbotAI* ai) { return new TellExpectedDpsAction(ai); }
|
static Action* tell_expected_dps(PlayerbotAI* ai) { return new TellExpectedDpsAction(ai); }
|
||||||
|
static Action* calc(PlayerbotAI* ai) { return new TellCalculateItemAction(ai); }
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -4,10 +4,16 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "TellLosAction.h"
|
#include "TellLosAction.h"
|
||||||
|
#include <istream>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
#include "ChatHelper.h"
|
#include "ChatHelper.h"
|
||||||
|
#include "DBCStores.h"
|
||||||
#include "Event.h"
|
#include "Event.h"
|
||||||
|
#include "ItemTemplate.h"
|
||||||
|
#include "ObjectMgr.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
|
#include "StatsWeightCalculator.h"
|
||||||
#include "World.h"
|
#include "World.h"
|
||||||
|
|
||||||
bool TellLosAction::Execute(Event event)
|
bool TellLosAction::Execute(Event event)
|
||||||
@@ -130,3 +136,21 @@ bool TellExpectedDpsAction::Execute(Event event)
|
|||||||
botAI->TellMaster("Expected Group DPS: " + std::to_string(dps));
|
botAI->TellMaster("Expected Group DPS: " + std::to_string(dps));
|
||||||
return true;
|
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;
|
||||||
|
}
|
||||||
@@ -37,4 +37,13 @@ public:
|
|||||||
|
|
||||||
virtual bool Execute(Event event);
|
virtual bool Execute(Event event);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class TellCalculateItemAction : public Action
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TellCalculateItemAction(PlayerbotAI* ai) : Action(ai, "calculate item") {}
|
||||||
|
|
||||||
|
virtual bool Execute(Event event);
|
||||||
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -157,4 +157,5 @@ ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : Pas
|
|||||||
supported.push_back("guild leave");
|
supported.push_back("guild leave");
|
||||||
supported.push_back("rtsc");
|
supported.push_back("rtsc");
|
||||||
supported.push_back("drink");
|
supported.push_back("drink");
|
||||||
|
supported.push_back("calc");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,6 +121,7 @@ public:
|
|||||||
creators["bwl"] = &ChatTriggerContext::bwl;
|
creators["bwl"] = &ChatTriggerContext::bwl;
|
||||||
creators["dps"] = &ChatTriggerContext::dps;
|
creators["dps"] = &ChatTriggerContext::dps;
|
||||||
creators["disperse"] = &ChatTriggerContext::disperse;
|
creators["disperse"] = &ChatTriggerContext::disperse;
|
||||||
|
creators["calc"] = &ChatTriggerContext::calc;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -221,6 +222,7 @@ private:
|
|||||||
static Trigger* bwl(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "bwl"); }
|
static Trigger* bwl(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "bwl"); }
|
||||||
static Trigger* dps(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "dps"); }
|
static Trigger* dps(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "dps"); }
|
||||||
static Trigger* disperse(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "disperse"); }
|
static Trigger* disperse(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "disperse"); }
|
||||||
|
static Trigger* calc(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "calc"); }
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
#include "RandomItemMgr.h"
|
#include "RandomItemMgr.h"
|
||||||
#include "ServerFacade.h"
|
#include "ServerFacade.h"
|
||||||
|
#include "StatsWeightCalculator.h"
|
||||||
|
|
||||||
ItemUsage ItemUsageValue::Calculate()
|
ItemUsage ItemUsageValue::Calculate()
|
||||||
{
|
{
|
||||||
@@ -190,7 +191,8 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto)
|
|||||||
|
|
||||||
bool shouldEquip = false;
|
bool shouldEquip = false;
|
||||||
// uint32 statWeight = sRandomItemMgr->GetLiveStatWeight(bot, itemProto->ItemId);
|
// 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)
|
if (itemScore)
|
||||||
shouldEquip = true;
|
shouldEquip = true;
|
||||||
|
|
||||||
@@ -214,7 +216,7 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto)
|
|||||||
}
|
}
|
||||||
|
|
||||||
ItemTemplate const* oldItemProto = oldItem->GetTemplate();
|
ItemTemplate const* oldItemProto = oldItem->GetTemplate();
|
||||||
float oldScore = PlayerbotFactory::CalculateItemScore(oldItemProto->ItemId, bot);
|
float oldScore = calculator.CalculateItem(oldItemProto->ItemId);
|
||||||
if (oldItem)
|
if (oldItem)
|
||||||
{
|
{
|
||||||
// uint32 oldStatWeight = sRandomItemMgr->GetLiveStatWeight(bot, oldItemProto->ItemId);
|
// uint32 oldStatWeight = sRandomItemMgr->GetLiveStatWeight(bot, oldItemProto->ItemId);
|
||||||
|
|||||||
Reference in New Issue
Block a user