Files
mod-playerbots/src/Mgr/Item/RandomItemMgr.cpp
Keleborn 439293e100 Warnings PR 2 clean unused variables (#2107)
# Pull Request

Removed unused variables and fixed styling issues. 


## How to Test the Changes

- Step-by-step instructions to test the change
- Any required setup (e.g. multiple players, bots, specific
configuration)
- Expected behavior and how to verify it

## Complexity & Impact

- Does this change add new decision branches?
    - [x] No
    - [] Yes (**explain below**)

- Does this change increase per-bot or per-tick processing?
    - [x] No
    - [ ] Yes (**describe and justify impact**)

- Could this logic scale poorly under load?
    - [x] No
    - [ ] Yes (**explain why**)

---

## Defaults & Configuration

- Does this change modify default bot behavior?
    - [x] No
    - [ ] Yes (**explain why**)

If this introduces more advanced or AI-heavy logic:

- [ ] Lightweight mode remains the default
- [ ] More complex behavior is optional and thereby configurable

---

## AI Assistance

- Was AI assistance (e.g. ChatGPT or similar tools) used while working
on this change?
    - [x] No
    - [ ] Yes (**explain below**)


---

## Final Checklist

- [x] Stability is not compromised
- [x] Performance impact is understood, tested, and acceptable
- [x] Added logic complexity is justified and explained
- [x] Documentation updated if needed

---

## Notes for Reviewers

This was filtered from the code provided by SmashingQuasar. Eliminated
variables were confirmed to be not used, but unclear at times if that is
due to mistakes in writing.

---------

Co-authored-by: bashermens <31279994+hermensbas@users.noreply.github.com>
2026-02-27 16:04:33 -08:00

2904 lines
104 KiB
C++

/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "RandomItemMgr.h"
#include "ItemTemplate.h"
#include "LootValues.h"
#include "Playerbots.h"
char* strstri(char const* str1, char const* str2);
std::set<uint32> RandomItemMgr::itemCache;
uint64 BotEquipKey::GetKey() { return level + 100 * clazz + 10000 * slot + 1000000 * quality; }
class RandomItemGuildTaskPredicate : public RandomItemPredicate
{
public:
bool Apply(ItemTemplate const* proto) override
{
if (proto->Bonding == BIND_WHEN_PICKED_UP || proto->Bonding == BIND_QUEST_ITEM ||
proto->Bonding == BIND_WHEN_USE)
return false;
if (proto->Quality < ITEM_QUALITY_NORMAL)
return false;
if ((proto->Class == ITEM_CLASS_ARMOR || proto->Class == ITEM_CLASS_WEAPON) &&
proto->Quality >= ITEM_QUALITY_RARE)
return true;
if (proto->Class == ITEM_CLASS_TRADE_GOODS || proto->Class == ITEM_CLASS_CONSUMABLE)
return true;
return false;
}
};
class RandomItemGuildTaskRewardPredicate : public RandomItemPredicate
{
public:
RandomItemGuildTaskRewardPredicate(bool equip, bool rare) : equip(equip), rare(rare) {}
bool Apply(ItemTemplate const* proto) override
{
if (proto->Bonding == BIND_WHEN_PICKED_UP || proto->Bonding == BIND_QUEST_ITEM ||
proto->Bonding == BIND_WHEN_USE)
return false;
if (proto->Class == ITEM_CLASS_QUEST)
return false;
if (equip)
{
uint32 desiredQuality = rare ? ITEM_QUALITY_RARE : ITEM_QUALITY_UNCOMMON;
if (proto->Quality < desiredQuality || proto->Quality >= ITEM_QUALITY_EPIC)
return false;
if (proto->Class == ITEM_CLASS_ARMOR || proto->Class == ITEM_CLASS_WEAPON)
return true;
}
else
{
uint32 desiredQuality = rare ? ITEM_QUALITY_UNCOMMON : ITEM_QUALITY_NORMAL;
if (proto->Quality < desiredQuality || proto->Quality >= ITEM_QUALITY_RARE)
return false;
if (proto->Class == ITEM_CLASS_TRADE_GOODS || proto->Class == ITEM_CLASS_CONSUMABLE)
return true;
}
return false;
}
private:
bool equip;
bool rare;
};
RandomItemMgr::RandomItemMgr()
{
predicates[RANDOM_ITEM_GUILD_TASK] = new RandomItemGuildTaskPredicate();
predicates[RANDOM_ITEM_GUILD_TASK_REWARD_EQUIP_GREEN] = new RandomItemGuildTaskRewardPredicate(true, false);
predicates[RANDOM_ITEM_GUILD_TASK_REWARD_EQUIP_BLUE] = new RandomItemGuildTaskRewardPredicate(true, true);
predicates[RANDOM_ITEM_GUILD_TASK_REWARD_TRADE] = new RandomItemGuildTaskRewardPredicate(false, false);
predicates[RANDOM_ITEM_GUILD_TASK_REWARD_TRADE_RARE] = new RandomItemGuildTaskRewardPredicate(false, true);
viableSlots[EQUIPMENT_SLOT_HEAD].insert(INVTYPE_HEAD);
viableSlots[EQUIPMENT_SLOT_NECK].insert(INVTYPE_NECK);
viableSlots[EQUIPMENT_SLOT_SHOULDERS].insert(INVTYPE_SHOULDERS);
viableSlots[EQUIPMENT_SLOT_BODY].insert(INVTYPE_BODY);
viableSlots[EQUIPMENT_SLOT_CHEST].insert(INVTYPE_CHEST);
viableSlots[EQUIPMENT_SLOT_CHEST].insert(INVTYPE_ROBE);
viableSlots[EQUIPMENT_SLOT_WAIST].insert(INVTYPE_WAIST);
viableSlots[EQUIPMENT_SLOT_LEGS].insert(INVTYPE_LEGS);
viableSlots[EQUIPMENT_SLOT_FEET].insert(INVTYPE_FEET);
viableSlots[EQUIPMENT_SLOT_WRISTS].insert(INVTYPE_WRISTS);
viableSlots[EQUIPMENT_SLOT_HANDS].insert(INVTYPE_HANDS);
viableSlots[EQUIPMENT_SLOT_FINGER1].insert(INVTYPE_FINGER);
viableSlots[EQUIPMENT_SLOT_FINGER2].insert(INVTYPE_FINGER);
viableSlots[EQUIPMENT_SLOT_TRINKET1].insert(INVTYPE_TRINKET);
viableSlots[EQUIPMENT_SLOT_TRINKET2].insert(INVTYPE_TRINKET);
viableSlots[EQUIPMENT_SLOT_MAINHAND].insert(INVTYPE_WEAPON);
viableSlots[EQUIPMENT_SLOT_MAINHAND].insert(INVTYPE_2HWEAPON);
viableSlots[EQUIPMENT_SLOT_MAINHAND].insert(INVTYPE_WEAPONMAINHAND);
viableSlots[EQUIPMENT_SLOT_OFFHAND].insert(INVTYPE_WEAPON);
viableSlots[EQUIPMENT_SLOT_OFFHAND].insert(INVTYPE_2HWEAPON);
viableSlots[EQUIPMENT_SLOT_OFFHAND].insert(INVTYPE_SHIELD);
viableSlots[EQUIPMENT_SLOT_OFFHAND].insert(INVTYPE_WEAPONMAINHAND);
viableSlots[EQUIPMENT_SLOT_OFFHAND].insert(INVTYPE_HOLDABLE);
viableSlots[EQUIPMENT_SLOT_RANGED].insert(INVTYPE_RANGED);
viableSlots[EQUIPMENT_SLOT_RANGED].insert(INVTYPE_THROWN);
viableSlots[EQUIPMENT_SLOT_RANGED].insert(INVTYPE_RANGEDRIGHT);
viableSlots[EQUIPMENT_SLOT_RANGED].insert(INVTYPE_RELIC);
viableSlots[EQUIPMENT_SLOT_TABARD].insert(INVTYPE_TABARD);
viableSlots[EQUIPMENT_SLOT_BACK].insert(INVTYPE_CLOAK);
weightStatLink["sta"] = ITEM_MOD_STAMINA;
weightStatLink["str"] = ITEM_MOD_STRENGTH;
weightStatLink["agi"] = ITEM_MOD_AGILITY;
weightStatLink["int"] = ITEM_MOD_INTELLECT;
weightStatLink["spi"] = ITEM_MOD_SPIRIT;
weightStatLink["splpwr"] = ITEM_MOD_SPELL_POWER;
weightStatLink["atkpwr"] = ITEM_MOD_ATTACK_POWER;
weightStatLink["exprtng"] = ITEM_MOD_EXPERTISE_RATING;
weightStatLink["critstrkrtng"] = ITEM_MOD_CRIT_RATING;
weightStatLink["hitrtng"] = ITEM_MOD_HIT_RATING;
weightStatLink["hastertng"] = ITEM_MOD_HASTE_RATING;
weightStatLink["armorpenrtng"] = ITEM_MOD_ARMOR_PENETRATION_RATING;
weightStatLink["defrtng"] = ITEM_MOD_DEFENSE_SKILL_RATING;
weightStatLink["dodgertng"] = ITEM_MOD_DODGE_RATING;
weightStatLink["block"] = ITEM_MOD_BLOCK_VALUE;
weightStatLink["blockrtng"] = ITEM_MOD_BLOCK_RATING;
weightStatLink["parryrtng"] = ITEM_MOD_PARRY_RATING;
weightStatLink["manargn"] = ITEM_MOD_MANA_REGENERATION;
weightRatingLink["exprtng"] = CR_EXPERTISE;
weightRatingLink["critstrkrtng"] = CR_CRIT_MELEE;
weightRatingLink["hitrtng"] = CR_HIT_MELEE;
weightRatingLink["hastertng"] = CR_HASTE_MELEE;
weightRatingLink["armorpenrtng"] = CR_ARMOR_PENETRATION;
weightRatingLink["defrtng"] = CR_DEFENSE_SKILL;
weightRatingLink["dodgertng"] = CR_DODGE;
weightRatingLink["blockrtng"] = CR_BLOCK;
weightRatingLink["parryrtng"] = CR_PARRY;
}
void RandomItemMgr::Init()
{
BuildItemInfoCache();
// BuildEquipCache();
BuildEquipCacheNew();
BuildAmmoCache();
BuildPotionCache();
BuildFoodCache();
BuildTradeCache();
}
void RandomItemMgr::InitAfterAhBot()
{
BuildRandomItemCache();
// BuildRarityCache();
}
RandomItemMgr::~RandomItemMgr()
{
for (std::map<RandomItemType, RandomItemPredicate*>::iterator i = predicates.begin(); i != predicates.end(); ++i)
delete i->second;
predicates.clear();
}
bool RandomItemMgr::HandleConsoleCommand(ChatHandler* handler, char const* args)
{
if (!args || !*args)
{
LOG_ERROR("playerbots", "Usage: rnditem");
return false;
}
return false;
}
RandomItemList RandomItemMgr::Query(uint32 level, RandomItemType type, RandomItemPredicate* predicate)
{
RandomItemList& list = randomItemCache[(level - 1) / 10][type];
RandomItemList result;
for (RandomItemList::iterator i = list.begin(); i != list.end(); ++i)
{
uint32 itemId = *i;
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
if (!proto)
continue;
if (predicate && !predicate->Apply(proto))
continue;
result.push_back(itemId);
}
return result;
}
void RandomItemMgr::BuildRandomItemCache()
{
if (PreparedQueryResult result =
PlayerbotsDatabase.Query(PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_SEL_RNDITEM_CACHE)))
{
LOG_INFO("server.loading", "Loading random item cache");
uint32 count = 0;
do
{
Field* fields = result->Fetch();
uint32 level = fields[0].Get<uint32>();
uint32 type = fields[1].Get<uint32>();
uint32 itemId = fields[2].Get<uint32>();
RandomItemType rit = (RandomItemType)type;
randomItemCache[level][rit].push_back(itemId);
++count;
} while (result->NextRow());
LOG_INFO("server.loading", "Equipment cache loaded from {} records", count);
}
else
{
ItemTemplateContainer const* itemTemplates = sObjectMgr->GetItemTemplateStore();
LOG_INFO("server.loading", "Building random item cache from {} items", itemTemplates->size());
for (auto const& itr : *itemTemplates)
{
ItemTemplate const* proto = &itr.second;
if (!proto)
continue;
if (proto->Duration & 0x80000000)
continue;
if (strstri(proto->Name1.c_str(), "qa") || strstri(proto->Name1.c_str(), "test") ||
strstri(proto->Name1.c_str(), "deprecated"))
continue;
if (!proto->ItemLevel)
continue;
if (!proto->SellPrice)
continue;
uint32 level = proto->ItemLevel;
for (uint32 type = RANDOM_ITEM_GUILD_TASK; type <= RANDOM_ITEM_GUILD_TASK_REWARD_TRADE_RARE; type++)
{
RandomItemType rit = (RandomItemType)type;
if (predicates[rit] && !predicates[rit]->Apply(proto))
continue;
randomItemCache[level / 10][rit].push_back(itr.first);
PlayerbotsDatabasePreparedStatement* stmt =
PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_INS_RNDITEM_CACHE);
stmt->SetData(0, level / 10);
stmt->SetData(1, type);
stmt->SetData(2, itr.first);
PlayerbotsDatabase.Execute(stmt);
}
}
uint32 maxLevel = sPlayerbotAIConfig.randomBotMaxLevel;
if (maxLevel > sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL))
maxLevel = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL);
for (uint32 level = 0; level < maxLevel / 10; level++)
{
for (uint32 type = RANDOM_ITEM_GUILD_TASK; type <= RANDOM_ITEM_GUILD_TASK_REWARD_TRADE_RARE; type++)
{
RandomItemList list = randomItemCache[level][(RandomItemType)type];
LOG_INFO("playerbots", " Level {}..{} Type {} - {} random items cached", level * 10, level * 10 + 9,
type, list.size());
for (RandomItemList::iterator i = list.begin(); i != list.end(); ++i)
{
uint32 itemId = *i;
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
if (!proto)
continue;
LOG_DEBUG("playerbots", " [{}] {}", itemId, proto->Name1.c_str());
}
}
}
}
}
uint32 RandomItemMgr::GetRandomItem(uint32 level, RandomItemType type, RandomItemPredicate* predicate)
{
RandomItemList const& list = Query(level, type, predicate);
if (list.empty())
return 0;
uint32 index = urand(0, list.size() - 1);
uint32 itemId = list[index];
return itemId;
}
bool RandomItemMgr::CanEquipItem(BotEquipKey key, ItemTemplate const* proto)
{
if (proto->Duration & 0x80000000)
return false;
if (proto->Quality != key.quality)
return false;
if (proto->Bonding == BIND_QUEST_ITEM || proto->Bonding == BIND_WHEN_USE)
return false;
if (proto->Class == ITEM_CLASS_CONTAINER)
return true;
std::set<InventoryType> slots = viableSlots[(EquipmentSlots)key.slot];
if (slots.find((InventoryType)proto->InventoryType) == slots.end())
return false;
uint32 requiredLevel = proto->RequiredLevel;
if (!requiredLevel)
{
requiredLevel = GetMinLevelFromCache(proto->ItemId);
}
if (!requiredLevel)
requiredLevel = key.level;
uint32 level = key.level;
uint32 delta = 2;
if (level < 15)
delta = 15;
else if (level < 40)
delta = 10; // urand(5, 10);
else if (level < 60)
delta = 6; // urand(3, 7);
else if (level < 70)
delta = 9; // urand(2, 5);
else if (level < 80)
delta = 9; // urand(2, 4);
else if (level == 80)
delta = 9; // urand(2, 4);
if (key.quality > ITEM_QUALITY_NORMAL && (requiredLevel > level || requiredLevel < level - delta))
return false;
// for (uint32 gap = 60; gap <= 80; gap += 10)
// {
// if (level > gap && requiredLevel <= gap)
// return false;
// }
return true;
}
bool RandomItemMgr::CanEquipItemNew(ItemTemplate const* proto)
{
if (proto->Duration & 0x80000000)
return false;
if (proto->Bonding == BIND_QUEST_ITEM || proto->Bonding == BIND_WHEN_USE)
return false;
if (proto->Class == ITEM_CLASS_CONTAINER)
return false;
bool properSlot = false;
for (std::map<EquipmentSlots, std::set<InventoryType> >::iterator i = viableSlots.begin(); i != viableSlots.end();
++i)
{
std::set<InventoryType> const& slots = viableSlots[(EquipmentSlots)i->first];
if (slots.find((InventoryType)proto->InventoryType) != slots.end())
properSlot = true;
}
return properSlot;
}
void RandomItemMgr::AddItemStats(uint32 mod, uint8& sp, uint8& ap, uint8& tank)
{
switch (mod)
{
case ITEM_MOD_HEALTH:
case ITEM_MOD_STAMINA:
case ITEM_MOD_MANA:
case ITEM_MOD_INTELLECT:
case ITEM_MOD_SPIRIT:
++sp;
break;
}
switch (mod)
{
case ITEM_MOD_AGILITY:
case ITEM_MOD_STRENGTH:
case ITEM_MOD_HEALTH:
case ITEM_MOD_STAMINA:
++tank;
break;
}
switch (mod)
{
case ITEM_MOD_HEALTH:
case ITEM_MOD_STAMINA:
case ITEM_MOD_AGILITY:
case ITEM_MOD_STRENGTH:
++ap;
break;
}
}
bool RandomItemMgr::CheckItemStats(uint8 clazz, uint8 sp, uint8 ap, uint8 tank)
{
switch (clazz)
{
case CLASS_PRIEST:
case CLASS_MAGE:
case CLASS_WARLOCK:
if (!sp || ap > sp || tank > sp)
return false;
break;
case CLASS_PALADIN:
case CLASS_WARRIOR:
if ((!ap && !tank) || sp > ap || sp > tank)
return false;
break;
case CLASS_HUNTER:
case CLASS_ROGUE:
if (!ap || sp > ap || sp > tank)
return false;
break;
}
return sp || ap || tank;
}
std::vector<uint32> RandomItemMgr::GetCachedEquipments(uint32 requiredLevel, uint32 inventoryType)
{
return equipCacheNew[requiredLevel][inventoryType];
}
bool RandomItemMgr::ShouldEquipArmorForSpec(uint8 playerclass, uint8 spec, ItemTemplate const* proto)
{
if (proto->InventoryType == INVTYPE_TABARD)
return true;
if (!m_weightScales[playerclass][spec].info.id)
return false;
std::unordered_set<uint32> resultArmorSubClass = {ITEM_SUBCLASS_ARMOR_CLOTH};
switch (playerclass)
{
case CLASS_WARRIOR:
{
if (proto->InventoryType == INVTYPE_HOLDABLE)
return false;
if (m_weightScales[playerclass][spec].info.name == "arms" ||
m_weightScales[playerclass][spec].info.name == "fury")
{
resultArmorSubClass = {ITEM_SUBCLASS_ARMOR_LEATHER, ITEM_SUBCLASS_ARMOR_MAIL,
ITEM_SUBCLASS_ARMOR_PLATE};
}
else
resultArmorSubClass = {ITEM_SUBCLASS_ARMOR_MAIL, ITEM_SUBCLASS_ARMOR_PLATE};
break;
}
case CLASS_DEATH_KNIGHT:
{
if (proto->InventoryType == INVTYPE_HOLDABLE)
return false;
resultArmorSubClass = {ITEM_SUBCLASS_ARMOR_SIGIL, ITEM_SUBCLASS_ARMOR_PLATE};
}
case CLASS_PALADIN:
{
if (m_weightScales[playerclass][spec].info.name != "holy" && proto->InventoryType == INVTYPE_HOLDABLE)
return false;
if (m_weightScales[playerclass][spec].info.name != "holy")
resultArmorSubClass = {ITEM_SUBCLASS_ARMOR_MAIL, ITEM_SUBCLASS_ARMOR_PLATE, ITEM_SUBCLASS_ARMOR_LIBRAM};
else
resultArmorSubClass = {ITEM_SUBCLASS_ARMOR_CLOTH, ITEM_SUBCLASS_ARMOR_LEATHER, ITEM_SUBCLASS_ARMOR_MAIL,
ITEM_SUBCLASS_ARMOR_PLATE, ITEM_SUBCLASS_ARMOR_LIBRAM};
break;
}
case CLASS_HUNTER:
{
if (proto->InventoryType == INVTYPE_HOLDABLE)
return false;
resultArmorSubClass = {ITEM_SUBCLASS_ARMOR_CLOTH, ITEM_SUBCLASS_ARMOR_LEATHER, ITEM_SUBCLASS_ARMOR_MAIL};
break;
}
case CLASS_ROGUE:
{
if (proto->InventoryType == INVTYPE_HOLDABLE)
return false;
resultArmorSubClass = {ITEM_SUBCLASS_ARMOR_CLOTH, ITEM_SUBCLASS_ARMOR_LEATHER};
break;
}
case CLASS_PRIEST:
{
resultArmorSubClass = {ITEM_SUBCLASS_ARMOR_CLOTH};
break;
}
case CLASS_SHAMAN:
{
if (m_weightScales[playerclass][spec].info.name == "enhance" && proto->InventoryType == INVTYPE_HOLDABLE)
return false;
resultArmorSubClass = {ITEM_SUBCLASS_ARMOR_TOTEM, ITEM_SUBCLASS_ARMOR_CLOTH, ITEM_SUBCLASS_ARMOR_LEATHER,
ITEM_SUBCLASS_ARMOR_MAIL};
break;
}
case CLASS_MAGE:
case CLASS_WARLOCK:
{
resultArmorSubClass = {ITEM_SUBCLASS_ARMOR_CLOTH};
break;
}
case CLASS_DRUID:
{
if ((m_weightScales[playerclass][spec].info.name == "feraltank" ||
m_weightScales[playerclass][spec].info.name == "feraldps") &&
proto->InventoryType == INVTYPE_HOLDABLE)
return false;
resultArmorSubClass = {ITEM_SUBCLASS_ARMOR_IDOL, ITEM_SUBCLASS_ARMOR_CLOTH, ITEM_SUBCLASS_ARMOR_LEATHER};
break;
}
}
return resultArmorSubClass.find(proto->SubClass) != resultArmorSubClass.end();
}
bool RandomItemMgr::ShouldEquipWeaponForSpec(uint8 playerclass, uint8 spec, ItemTemplate const* proto)
{
EquipmentSlots slot_mh = EQUIPMENT_SLOT_START;
EquipmentSlots slot_oh = EQUIPMENT_SLOT_START;
EquipmentSlots slot_rh = EQUIPMENT_SLOT_START;
for (std::map<EquipmentSlots, std::set<InventoryType> >::iterator i = viableSlots.begin(); i != viableSlots.end();
++i)
{
std::set<InventoryType> slots = viableSlots[(EquipmentSlots)i->first];
if (slots.find((InventoryType)proto->InventoryType) != slots.end())
{
if (i->first == EQUIPMENT_SLOT_MAINHAND)
slot_mh = i->first;
if (i->first == EQUIPMENT_SLOT_OFFHAND)
slot_oh = i->first;
if (i->first == EQUIPMENT_SLOT_RANGED)
slot_rh = i->first;
}
}
if (slot_mh == EQUIPMENT_SLOT_START && slot_oh == EQUIPMENT_SLOT_START && slot_rh == EQUIPMENT_SLOT_START)
return false;
if (!m_weightScales[playerclass][spec].info.id)
return false;
std::unordered_set<uint32> mh_weapons;
std::unordered_set<uint32> oh_weapons;
std::unordered_set<uint32> r_weapons;
switch (playerclass)
{
case CLASS_WARRIOR:
{
if (m_weightScales[playerclass][spec].info.name == "prot")
{
mh_weapons = {ITEM_SUBCLASS_WEAPON_SWORD, ITEM_SUBCLASS_WEAPON_AXE, ITEM_SUBCLASS_WEAPON_MACE,
ITEM_SUBCLASS_WEAPON_DAGGER, ITEM_SUBCLASS_WEAPON_FIST};
oh_weapons = {ITEM_SUBCLASS_ARMOR_SHIELD};
r_weapons = {ITEM_SUBCLASS_WEAPON_BOW, ITEM_SUBCLASS_WEAPON_CROSSBOW, ITEM_SUBCLASS_WEAPON_GUN};
}
else
{
mh_weapons = {ITEM_SUBCLASS_WEAPON_SWORD2, ITEM_SUBCLASS_WEAPON_AXE2, ITEM_SUBCLASS_WEAPON_MACE2,
ITEM_SUBCLASS_WEAPON_STAFF, ITEM_SUBCLASS_WEAPON_POLEARM};
r_weapons = {ITEM_SUBCLASS_WEAPON_BOW, ITEM_SUBCLASS_WEAPON_CROSSBOW, ITEM_SUBCLASS_WEAPON_GUN};
}
break;
}
case CLASS_PALADIN:
{
if (m_weightScales[playerclass][spec].info.name == "prot")
{
mh_weapons = {ITEM_SUBCLASS_WEAPON_SWORD, ITEM_SUBCLASS_WEAPON_AXE, ITEM_SUBCLASS_WEAPON_MACE};
oh_weapons = {ITEM_SUBCLASS_ARMOR_SHIELD};
r_weapons = {ITEM_SUBCLASS_ARMOR_LIBRAM};
}
else if (m_weightScales[playerclass][spec].info.name == "holy")
{
mh_weapons = {ITEM_SUBCLASS_WEAPON_SWORD, ITEM_SUBCLASS_WEAPON_AXE, ITEM_SUBCLASS_WEAPON_MACE};
oh_weapons = {ITEM_SUBCLASS_ARMOR_SHIELD, ITEM_SUBCLASS_ARMOR_MISC};
r_weapons = {ITEM_SUBCLASS_ARMOR_LIBRAM};
}
else
{
mh_weapons = {ITEM_SUBCLASS_WEAPON_SWORD2, ITEM_SUBCLASS_WEAPON_AXE2, ITEM_SUBCLASS_WEAPON_MACE2,
ITEM_SUBCLASS_WEAPON_POLEARM};
r_weapons = {ITEM_SUBCLASS_ARMOR_LIBRAM};
}
break;
}
case CLASS_HUNTER:
{
mh_weapons = {ITEM_SUBCLASS_WEAPON_SWORD2, ITEM_SUBCLASS_WEAPON_AXE2, ITEM_SUBCLASS_WEAPON_STAFF,
ITEM_SUBCLASS_WEAPON_POLEARM};
r_weapons = {ITEM_SUBCLASS_WEAPON_BOW, ITEM_SUBCLASS_WEAPON_CROSSBOW, ITEM_SUBCLASS_WEAPON_GUN};
break;
}
case CLASS_ROGUE:
{
mh_weapons = {ITEM_SUBCLASS_WEAPON_DAGGER};
oh_weapons = {ITEM_SUBCLASS_WEAPON_DAGGER};
r_weapons = {ITEM_SUBCLASS_WEAPON_BOW, ITEM_SUBCLASS_WEAPON_CROSSBOW, ITEM_SUBCLASS_WEAPON_GUN};
break;
}
case CLASS_PRIEST:
{
mh_weapons = {ITEM_SUBCLASS_WEAPON_STAFF, ITEM_SUBCLASS_WEAPON_DAGGER, ITEM_SUBCLASS_WEAPON_MACE};
oh_weapons = {ITEM_SUBCLASS_ARMOR_MISC};
r_weapons = {ITEM_SUBCLASS_WEAPON_WAND};
break;
}
case CLASS_SHAMAN:
{
if (m_weightScales[playerclass][spec].info.name == "resto")
{
mh_weapons = {ITEM_SUBCLASS_WEAPON_DAGGER, ITEM_SUBCLASS_WEAPON_AXE, ITEM_SUBCLASS_WEAPON_MACE,
ITEM_SUBCLASS_WEAPON_FIST};
oh_weapons = {ITEM_SUBCLASS_ARMOR_MISC, ITEM_SUBCLASS_ARMOR_SHIELD};
r_weapons = {ITEM_SUBCLASS_ARMOR_TOTEM};
}
else if (m_weightScales[playerclass][spec].info.name == "enhance")
{
mh_weapons = {ITEM_SUBCLASS_WEAPON_MACE2, ITEM_SUBCLASS_WEAPON_AXE2, ITEM_SUBCLASS_WEAPON_DAGGER,
ITEM_SUBCLASS_WEAPON_AXE, ITEM_SUBCLASS_WEAPON_MACE, ITEM_SUBCLASS_WEAPON_FIST};
oh_weapons = {ITEM_SUBCLASS_ARMOR_SHIELD};
r_weapons = {ITEM_SUBCLASS_ARMOR_TOTEM};
}
else
{
mh_weapons = {ITEM_SUBCLASS_WEAPON_STAFF};
r_weapons = {ITEM_SUBCLASS_ARMOR_TOTEM};
}
break;
}
case CLASS_MAGE:
case CLASS_WARLOCK:
{
mh_weapons = {ITEM_SUBCLASS_WEAPON_STAFF, ITEM_SUBCLASS_WEAPON_DAGGER, ITEM_SUBCLASS_WEAPON_SWORD};
oh_weapons = {ITEM_SUBCLASS_ARMOR_MISC};
r_weapons = {ITEM_SUBCLASS_WEAPON_WAND};
break;
}
case CLASS_DRUID:
{
if (m_weightScales[playerclass][spec].info.name == "feraltank")
{
mh_weapons = {ITEM_SUBCLASS_WEAPON_STAFF, ITEM_SUBCLASS_WEAPON_MACE2};
r_weapons = {ITEM_SUBCLASS_ARMOR_IDOL};
}
else if (m_weightScales[playerclass][spec].info.name == "resto")
{
mh_weapons = {ITEM_SUBCLASS_WEAPON_STAFF, ITEM_SUBCLASS_WEAPON_DAGGER, ITEM_SUBCLASS_WEAPON_FIST,
ITEM_SUBCLASS_WEAPON_MACE};
oh_weapons = {ITEM_SUBCLASS_ARMOR_MISC};
r_weapons = {ITEM_SUBCLASS_ARMOR_IDOL};
}
else if (m_weightScales[playerclass][spec].info.name == "feraldps")
{
mh_weapons = {ITEM_SUBCLASS_WEAPON_STAFF, ITEM_SUBCLASS_WEAPON_MACE2};
r_weapons = {ITEM_SUBCLASS_ARMOR_IDOL};
}
else
{
mh_weapons = {ITEM_SUBCLASS_WEAPON_STAFF};
r_weapons = {ITEM_SUBCLASS_ARMOR_IDOL};
}
break;
}
}
if (slot_mh == EQUIPMENT_SLOT_MAINHAND)
{
return mh_weapons.find(proto->SubClass) != mh_weapons.end();
}
if (slot_oh == EQUIPMENT_SLOT_OFFHAND)
{
return oh_weapons.find(proto->SubClass) != oh_weapons.end();
}
if (slot_rh == EQUIPMENT_SLOT_RANGED)
{
return r_weapons.find(proto->SubClass) != r_weapons.end();
}
return false;
}
bool RandomItemMgr::CanEquipArmor(uint8 clazz, uint32 level, ItemTemplate const* proto)
{
if (proto->InventoryType == INVTYPE_TABARD)
return true;
if ((clazz == CLASS_WARRIOR || clazz == CLASS_PALADIN || clazz == CLASS_SHAMAN) &&
proto->SubClass == ITEM_SUBCLASS_ARMOR_SHIELD)
return true;
if ((clazz == CLASS_WARRIOR || clazz == CLASS_PALADIN) && level >= 40)
{
if (proto->SubClass != ITEM_SUBCLASS_ARMOR_PLATE && proto->InventoryType != INVTYPE_CLOAK)
return false;
}
if (((clazz == CLASS_WARRIOR || clazz == CLASS_PALADIN) && level < 40) ||
((clazz == CLASS_HUNTER || clazz == CLASS_SHAMAN) && level >= 40))
{
if (proto->SubClass != ITEM_SUBCLASS_ARMOR_MAIL && proto->InventoryType != INVTYPE_CLOAK)
return false;
}
if (((clazz == CLASS_HUNTER || clazz == CLASS_SHAMAN) && level < 40) ||
(clazz == CLASS_DRUID || clazz == CLASS_ROGUE))
{
if (proto->SubClass != ITEM_SUBCLASS_ARMOR_LEATHER && proto->InventoryType != INVTYPE_CLOAK)
return false;
}
if (proto->Quality <= ITEM_QUALITY_NORMAL)
return true;
uint8 sp = 0, ap = 0, tank = 0;
for (uint8 j = 0; j < MAX_ITEM_PROTO_STATS; ++j)
{
// for ItemStatValue != 0
if (!proto->ItemStat[j].ItemStatValue)
continue;
AddItemStats(proto->ItemStat[j].ItemStatType, sp, ap, tank);
}
return CheckItemStats(clazz, sp, ap, tank);
}
bool RandomItemMgr::CanEquipWeapon(uint8 clazz, ItemTemplate const* proto)
{
switch (clazz)
{
case CLASS_PRIEST:
if (proto->SubClass != ITEM_SUBCLASS_WEAPON_STAFF && proto->SubClass != ITEM_SUBCLASS_WEAPON_WAND &&
proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE && proto->SubClass != ITEM_SUBCLASS_WEAPON_DAGGER)
return false;
break;
case CLASS_MAGE:
case CLASS_WARLOCK:
if (proto->SubClass != ITEM_SUBCLASS_WEAPON_STAFF && proto->SubClass != ITEM_SUBCLASS_WEAPON_WAND &&
proto->SubClass != ITEM_SUBCLASS_WEAPON_DAGGER && proto->SubClass != ITEM_SUBCLASS_WEAPON_SWORD)
return false;
break;
case CLASS_WARRIOR:
if (proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE2 && proto->SubClass != ITEM_SUBCLASS_WEAPON_AXE &&
proto->SubClass != ITEM_SUBCLASS_WEAPON_POLEARM && proto->SubClass != ITEM_SUBCLASS_WEAPON_SWORD2 &&
proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE && proto->SubClass != ITEM_SUBCLASS_WEAPON_SWORD &&
proto->SubClass != ITEM_SUBCLASS_WEAPON_GUN && proto->SubClass != ITEM_SUBCLASS_WEAPON_CROSSBOW &&
proto->SubClass != ITEM_SUBCLASS_WEAPON_BOW && proto->SubClass != ITEM_SUBCLASS_WEAPON_THROWN &&
proto->SubClass != ITEM_SUBCLASS_WEAPON_AXE2 && proto->SubClass != ITEM_SUBCLASS_WEAPON_FIST &&
proto->SubClass != ITEM_SUBCLASS_WEAPON_DAGGER && proto->SubClass != ITEM_SUBCLASS_WEAPON_STAFF)
return false;
break;
case CLASS_PALADIN:
case CLASS_DEATH_KNIGHT:
if (proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE2 && proto->SubClass != ITEM_SUBCLASS_WEAPON_POLEARM &&
proto->SubClass != ITEM_SUBCLASS_WEAPON_SWORD2 && proto->SubClass != ITEM_SUBCLASS_WEAPON_AXE2 &&
proto->SubClass != ITEM_SUBCLASS_WEAPON_AXE && proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE &&
proto->SubClass != ITEM_SUBCLASS_WEAPON_SWORD)
return false;
break;
case CLASS_SHAMAN:
if (proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE && proto->SubClass != ITEM_SUBCLASS_WEAPON_AXE &&
proto->SubClass != ITEM_SUBCLASS_WEAPON_FIST && proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE2 &&
proto->SubClass != ITEM_SUBCLASS_WEAPON_AXE2 && proto->SubClass != ITEM_SUBCLASS_WEAPON_DAGGER &&
proto->SubClass != ITEM_SUBCLASS_WEAPON_STAFF)
return false;
break;
case CLASS_DRUID:
if (proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE && proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE2 &&
proto->SubClass != ITEM_SUBCLASS_WEAPON_DAGGER && proto->SubClass != ITEM_SUBCLASS_WEAPON_STAFF &&
proto->SubClass != ITEM_SUBCLASS_WEAPON_POLEARM)
return false;
break;
case CLASS_HUNTER:
if (proto->SubClass != ITEM_SUBCLASS_WEAPON_DAGGER && proto->SubClass != ITEM_SUBCLASS_WEAPON_BOW &&
proto->SubClass != ITEM_SUBCLASS_WEAPON_AXE2 && proto->SubClass != ITEM_SUBCLASS_WEAPON_AXE &&
proto->SubClass != ITEM_SUBCLASS_WEAPON_SWORD2 && proto->SubClass != ITEM_SUBCLASS_WEAPON_SWORD &&
proto->SubClass != ITEM_SUBCLASS_WEAPON_FIST && proto->SubClass != ITEM_SUBCLASS_WEAPON_GUN &&
proto->SubClass != ITEM_SUBCLASS_WEAPON_CROSSBOW && proto->SubClass != ITEM_SUBCLASS_WEAPON_STAFF &&
proto->SubClass != ITEM_SUBCLASS_WEAPON_POLEARM)
return false;
break;
case CLASS_ROGUE:
if (proto->SubClass != ITEM_SUBCLASS_WEAPON_DAGGER && proto->SubClass != ITEM_SUBCLASS_WEAPON_SWORD &&
proto->SubClass != ITEM_SUBCLASS_WEAPON_FIST && proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE &&
proto->SubClass != ITEM_SUBCLASS_WEAPON_GUN && proto->SubClass != ITEM_SUBCLASS_WEAPON_CROSSBOW &&
proto->SubClass != ITEM_SUBCLASS_WEAPON_BOW && proto->SubClass != ITEM_SUBCLASS_WEAPON_THROWN &&
proto->SubClass != ITEM_SUBCLASS_WEAPON_AXE)
return false;
break;
}
return true;
}
void RandomItemMgr::BuildItemInfoCache()
{
//uint32 maxLevel = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL); //not used, line marked for removal.
// load weightscales
LOG_INFO("playerbots", "Loading weightscales info");
uint32 counter = 1;
uint32 totalcount = 0;
uint32 statcount = 0;
uint32 curClass = CLASS_WARRIOR;
PlayerbotsDatabasePreparedStatement* stmt = PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_SEL_WEIGHTSCALES);
if (PreparedQueryResult result = PlayerbotsDatabase.Query(stmt))
{
do
{
Field* fields = result->Fetch();
uint32 id = fields[0].Get<uint32>();
std::string name = fields[1].Get<std::string>();
uint8 clazz = fields[2].Get<uint8>();
if (clazz != curClass)
{
counter = 1;
curClass = clazz;
}
WeightScale scale;
scale.info.id = id;
scale.info.name = name;
m_weightScales[clazz][counter] = std::move(scale);
counter++;
totalcount++;
} while (result->NextRow());
LOG_INFO("playerbots", "Loaded {} weightscale class specs", totalcount);
}
stmt = PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_SEL_WEIGHTSCALE_DATA);
if (PreparedQueryResult result = PlayerbotsDatabase.Query(stmt))
{
do
{
Field* fields = result->Fetch();
uint32 id = fields[0].Get<uint32>();
std::string field = fields[1].Get<std::string>();
uint32 weight = fields[2].Get<uint32>();
WeightScaleStat stat;
stat.stat = field;
stat.weight = weight;
for (uint8 cls = CLASS_WARRIOR; cls < MAX_CLASSES; ++cls)
{
for (uint32 spec = 1; spec < 5; ++spec)
{
if (m_weightScales[cls][spec].info.id == id)
m_weightScales[cls][spec].stats.push_back(std::move(stat));
}
}
statcount++;
} while (result->NextRow());
LOG_INFO("playerbots", "Loaded {} weightscale stat weights", statcount);
}
if (m_weightScales[1].empty())
{
LOG_ERROR("playerbots", "Error loading item weight scales");
return;
}
// vendor items
LOG_INFO("playerbots", "Loading vendor item list...");
std::set<uint32> vendorItems;
vendorItems.clear();
if (QueryResult result = WorldDatabase.Query("SELECT item FROM npc_vendor"))
{
do
{
Field* fields = result->Fetch();
int32 entry = fields[0].Get<int32>();
if (entry <= 0)
continue;
vendorItems.insert(entry);
} while (result->NextRow());
}
LOG_INFO("playerbots", "Loaded {} vendor items...", vendorItems.size());
// calculate drop source
LOG_INFO("playerbots", "Loading loot templates...");
DropMap* dropMap = new DropMap;
if (CreatureTemplateContainer const* creatures = sObjectMgr->GetCreatureTemplates())
{
for (CreatureTemplateContainer::const_iterator itr = creatures->begin(); itr != creatures->end(); ++itr)
{
uint32 sEntry = itr->first;
if (LootTemplateAccess const* lTemplateA =
DropMapValue::GetLootTemplate(ObjectGuid::Create<HighGuid::Unit>(sEntry, uint32(1)), LOOT_CORPSE))
for (auto const& lItem : lTemplateA->Entries)
dropMap->insert(std::make_pair(lItem->itemid, sEntry));
}
}
if (GameObjectTemplateContainer const* gameobjects = sObjectMgr->GetGameObjectTemplates())
{
for (auto const& itr : *gameobjects)
{
uint32 sEntry = itr.first;
if (LootTemplateAccess const* lTemplateA = DropMapValue::GetLootTemplate(
ObjectGuid::Create<HighGuid::GameObject>(sEntry, uint32(1)), LOOT_CORPSE))
for (auto const& lItem : lTemplateA->Entries)
dropMap->insert(std::make_pair(lItem->itemid, sEntry));
}
}
LOG_INFO("playerbots", "Loaded {} loot templates...", dropMap->size());
ItemTemplateContainer const* itemTemplate = sObjectMgr->GetItemTemplateStore();
LOG_INFO("playerbots", "Calculating stat weights for {} items...", itemTemplate->size());
PlayerbotsDatabaseTransaction trans = PlayerbotsDatabase.BeginTransaction();
for (auto const& itr : *itemTemplate)
{
ItemTemplate const* proto = &itr.second;
if (!proto)
continue;
// skip test items
if (strstr(proto->Name1.c_str(), "(Test)") || strstr(proto->Name1.c_str(), "(TEST)") ||
strstr(proto->Name1.c_str(), "(test)") || strstr(proto->Name1.c_str(), "(JEFFTEST)") ||
strstr(proto->Name1.c_str(), "Test ") || strstr(proto->Name1.c_str(), "Test") ||
strstr(proto->Name1.c_str(), "TEST") || strstr(proto->Name1.c_str(), "TEST ") ||
strstr(proto->Name1.c_str(), " TEST") || strstr(proto->Name1.c_str(), "2200 ") ||
strstr(proto->Name1.c_str(), "Deprecated ") || strstr(proto->Name1.c_str(), "Unused ") ||
strstr(proto->Name1.c_str(), "Monster ") || strstr(proto->Name1.c_str(), "[PH]") ||
strstr(proto->Name1.c_str(), "(OLD)") || strstr(proto->Name1.c_str(), "QR") ||
strstr(proto->Name1.c_str(), "zzOLD"))
{
itemForTest.insert(proto->ItemId);
continue;
}
if (proto->HasFlag(ITEM_FLAG_DEPRECATED))
{
itemForTest.insert(proto->ItemId);
continue;
}
// skip items with rank/rep requirements
/*if (proto->RequiredHonorRank > 0 ||
proto->RequiredSkillRank > 0 ||
proto->RequiredCityRank > 0 ||
proto->RequiredReputationRank > 0)
continue;*/
// if (proto->RequiredHonorRank > 0 || proto->RequiredSkillRank > 0 || proto->RequiredCityRank > 0)
// continue;
// // skip random enchant items
// if (proto->RandomProperty)
// continue;
// // skip heirloom items
// if (proto->Quality == ITEM_QUALITY_HEIRLOOM)
// continue;
// // check possible equip slots
// EquipmentSlots slot = EQUIPMENT_SLOT_START;
// for (std::map<EquipmentSlots, std::set<InventoryType> >::iterator i = viableSlots.begin();
// i != viableSlots.end(); ++i)
// {
// std::set<InventoryType> slots = viableSlots[(EquipmentSlots)i->first];
// if (slots.find((InventoryType)proto->InventoryType) != slots.end())
// slot = i->first;
// }
// if (slot == EQUIPMENT_SLOT_START)
// continue;
// Init Item cache
// ItemInfoEntry cacheInfo;
// for (uint8 clazz = CLASS_WARRIOR; clazz < MAX_CLASSES; ++clazz)
// {
// // skip nonexistent classes
// if (!((1 << (clazz - 1)) & CLASSMASK_ALL_PLAYABLE) || !sChrClassesStore.LookupEntry(clazz))
// continue;
// // skip wrong classes
// if ((proto->AllowableClass & (1 << (clazz - 1))) == 0)
// continue;
// for (uint32 spec = 1; spec < 5; ++spec)
// {
// if (!m_weightScales[clazz][spec].info.id)
// continue;
// // check possible armor for spec
// if (m_weightScales)
// if (proto->Class == ITEM_CLASS_ARMOR && (
// slot == EQUIPMENT_SLOT_HEAD ||
// slot == EQUIPMENT_SLOT_SHOULDERS ||
// slot == EQUIPMENT_SLOT_CHEST ||
// slot == EQUIPMENT_SLOT_WAIST ||
// slot == EQUIPMENT_SLOT_LEGS ||
// slot == EQUIPMENT_SLOT_FEET ||
// slot == EQUIPMENT_SLOT_WRISTS ||
// slot == EQUIPMENT_SLOT_HANDS) &&
// !ShouldEquipArmorForSpec(clazz, spec, proto))
// continue;
// // check possible weapon for spec
// if ((proto->Class == ITEM_CLASS_WEAPON || (proto->SubClass == ITEM_SUBCLASS_ARMOR_SHIELD ||
// (proto->SubClass == ITEM_SUBCLASS_ARMOR_MISC && proto->InventoryType == INVTYPE_HOLDABLE))) &&
// !ShouldEquipWeaponForSpec(clazz, spec, proto))
// continue;
// StatWeight statWeight;
// statWeight.id = m_weightScales[clazz][spec].info.id;
// uint32 statW = CalculateStatWeight(clazz, spec, proto);
// // set stat weight = 1 for items that can be equipped but have no proper stats
// statWeight.weight = statW ? statW : 1;
// //statWeight.weight = statW;
// // save item statWeight into ItemCache
// cacheInfo.weights[statWeight.id] = statWeight.weight;
// LOG_DEBUG("playerbots", "Item: {}, weight: {}, class: {}, spec: {}", proto->ItemId,
// statWeight.weight, clazz, m_weightScales[clazz][spec].info.name);
// }
// }
// cacheInfo.team = TEAM_NEUTRAL;
// // check faction
// if (proto->HasFlag2(ITEM_FLAG2_FACTION_HORDE))
// cacheInfo.team = TEAM_HORDE;
// if (proto->HasFlag2(ITEM_FLAG2_FACTION_ALLIANCE))
// cacheInfo.team = TEAM_ALLIANCE;
// if (cacheInfo.team == TEAM_NEUTRAL && proto->AllowableRace > 1 && proto->AllowableRace < 8388607)
// {
// if (FactionEntry const* faction = sFactionStore.LookupEntry(HORDE))
// if ((proto->AllowableRace & faction->BaseRepRaceMask[0]) != 0)
// cacheInfo.team = TEAM_HORDE;
// if (FactionEntry const* faction = sFactionStore.LookupEntry(ALLIANCE))
// if ((proto->AllowableRace & faction->BaseRepRaceMask[0]) != 0)
// cacheInfo.team = TEAM_ALLIANCE;
// }
// if (cacheInfo.team < TEAM_NEUTRAL)
// LOG_DEBUG("playerbots", "Item: {}, team (item): {}", proto->ItemId, cacheInfo.team == TEAM_ALLIANCE ?
// "Alliance" : "Horde");
// // check min level
// if (proto->RequiredLevel)
// cacheInfo.minLevel = proto->RequiredLevel;
// // check item source
// if (proto->HasFlag(ITEM_FLAG_NO_DISENCHANT))
// {
// cacheInfo.source = ITEM_SOURCE_PVP;
// LOG_DEBUG("playerbots", "Item: {}, source: PvP Reward", proto->ItemId);
// }
// // check quests
// if (cacheInfo.source == ITEM_SOURCE_NONE)
// {
// std::vector<uint32> questIds = GetQuestIdsForItem(proto->ItemId);
// if (questIds.size())
// {
// bool isAlly = false;
// bool isHorde = false;
// for (std::vector<uint32>::iterator i = questIds.begin(); i != questIds.end(); ++i)
// {
// Quest const* quest = sObjectMgr->GetQuestTemplate(*i);
// if (quest)
// {
// cacheInfo.source = ITEM_SOURCE_QUEST;
// cacheInfo.sourceId = *i;
// if (!cacheInfo.minLevel)
// cacheInfo.minLevel = quest->GetMinLevel();
// // check quest team
// if (cacheInfo.team == TEAM_NEUTRAL)
// {
// uint32 reqRace = quest->GetAllowableRaces();
// if (reqRace)
// {
// if ((reqRace & RACEMASK_ALLIANCE) != 0)
// isAlly = true;
// else if ((reqRace & RACEMASK_HORDE) != 0)
// isHorde = true;
// }
// }
// }
// }
// if (isAlly && isHorde)
// cacheInfo.team = TEAM_NEUTRAL;
// else if (isAlly)
// cacheInfo.team = TEAM_ALLIANCE;
// else if (isHorde)
// cacheInfo.team = TEAM_HORDE;
// LOG_DEBUG("playerbots", "Item: {}, team (quest): {}", proto->ItemId, cacheInfo.team == TEAM_ALLIANCE
// ? "Alliance" : cacheInfo.team == TEAM_HORDE ? "Horde" : "Both"); LOG_DEBUG("playerbots", "Item: {},
// source: quest {}, minlevel: {}", proto->ItemId, cacheInfo.sourceId, cacheInfo.minLevel);
// }
// }
// if (cacheInfo.minLevel)
// LOG_DEBUG("playerbots", "Item: {}, minlevel: {}", proto->ItemId, cacheInfo.minLevel);
// // check vendors
// if (cacheInfo.source == ITEM_SOURCE_NONE)
// {
// for (std::set<uint32>::iterator i = vendorItems.begin(); i != vendorItems.end(); ++i)
// {
// if (proto->ItemId == *i)
// {
// cacheInfo.source = ITEM_SOURCE_VENDOR;
// LOG_DEBUG("playerbots", "Item: {} source: vendor", proto->ItemId);
// break;
// }
// }
// }
// // check drops
// std::vector<int32> creatures;
// std::vector<int32> gameobjects;
// auto range = dropMap->equal_range(itr.first);
// for (auto iter = range.first; iter != range.second; ++iter)
// {
// if (iter->second > 0)
// creatures.push_back(iter->second);
// else
// gameobjects.push_back(abs(iter->second));
// }
// // check creature drop
// if (cacheInfo.source == ITEM_SOURCE_NONE)
// {
// if (creatures.size())
// {
// if (creatures.size() == 1)
// {
// cacheInfo.source = ITEM_SOURCE_DROP;
// cacheInfo.sourceId = creatures.front();
// LOG_DEBUG("playerbots", "Item: {}, source: creature drop, ID: {}", proto->ItemId,
// creatures.front());
// }
// else
// {
// cacheInfo.source = ITEM_SOURCE_DROP;
// LOG_DEBUG("playerbots", "Item: {}, source: creatures drop, number: {}", proto->ItemId,
// creatures.size());
// }
// }
// }
// // check gameobject drop
// if (cacheInfo.source == ITEM_SOURCE_NONE || (cacheInfo.source == ITEM_SOURCE_DROP && !cacheInfo.sourceId))
// {
// if (gameobjects.size())
// {
// if (gameobjects.size() == 1)
// {
// cacheInfo.source = ITEM_SOURCE_DROP;
// cacheInfo.sourceId = gameobjects.front();
// LOG_INFO("playerbots", "Item: {}, source: gameobject, ID: {}", proto->ItemId,
// gameobjects.front());
// }
// else
// {
// cacheInfo.source = ITEM_SOURCE_DROP;
// LOG_INFO("playerbots", "Item: {}, source: gameobjects, number: {}", proto->ItemId,
// gameobjects.size());
// }
// }
// }
// // check faction
// if (proto->RequiredReputationFaction > 0 && proto->RequiredReputationFaction != 35 &&
// proto->RequiredReputationRank < 15)
// {
// cacheInfo.repFaction = proto->RequiredReputationFaction;
// cacheInfo.repRank = proto->RequiredReputationRank;
// }
// cacheInfo.quality = proto->Quality;
// cacheInfo.itemId = proto->ItemId;
// cacheInfo.slot = slot;
// // save cache
// PlayerbotsDatabasePreparedStatement* stmt =
// PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_DEL_EQUIP_CACHE_NEW); stmt->SetData(0, proto->ItemId);
// trans->Append(stmt);
// stmt = PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_INS_EQUIP_CACHE_NEW);
// stmt->SetData(0, cacheInfo.itemId);
// stmt->SetData(1, cacheInfo.quality);
// stmt->SetData(2, cacheInfo.slot);
// stmt->SetData(3, cacheInfo.source);
// stmt->SetData(4, cacheInfo.sourceId);
// stmt->SetData(5, cacheInfo.team);
// stmt->SetData(6, cacheInfo.repFaction);
// stmt->SetData(7, cacheInfo.repRank);
// stmt->SetData(8, cacheInfo.minLevel);
// for (uint8 i = 1; i <= MAX_STAT_SCALES; ++i)
// {
// if (cacheInfo.weights[i])
// stmt->SetData(8 + i, cacheInfo.weights[i]);
// else
// stmt->SetData(8 + i, 0);
// }
// trans->Append(stmt);
// itemInfoCache[cacheInfo.itemId] = std::move(cacheInfo);
}
PlayerbotsDatabase.CommitTransaction(trans);
}
uint32 RandomItemMgr::CalculateStatWeight(uint8 playerclass, uint8 spec, ItemTemplate const* proto)
{
uint32 statWeight = 0;
bool isCasterItem = false;
bool isAttackItem = false;
bool noCaster = (Classes)playerclass == CLASS_WARRIOR || (Classes)playerclass == CLASS_ROGUE ||
(Classes)playerclass == CLASS_DEATH_KNIGHT || (Classes)playerclass == CLASS_HUNTER;
uint32 spellPower = 0;
uint32 spellHeal = 0;
uint32 attackPower = 0;
bool hasInt = false;
bool hasMana = !((Classes)playerclass == CLASS_WARRIOR || (Classes)playerclass == CLASS_ROGUE ||
(Classes)playerclass == CLASS_DEATH_KNIGHT);
if (proto->SubClass == ITEM_SUBCLASS_ARMOR_LIBRAM || proto->SubClass == ITEM_SUBCLASS_ARMOR_IDOL ||
proto->SubClass == ITEM_SUBCLASS_ARMOR_TOTEM || proto->SubClass == ITEM_SUBCLASS_ARMOR_SIGIL)
return (uint32)(proto->Quality + proto->ItemLevel);
// check basic item stats
int32 basicStatsWeight = 0;
for (uint8 j = 0; j < MAX_ITEM_PROTO_STATS; ++j)
{
uint32 statType;
int32 val;
std::string weightName;
// if (j >= proto->StatsCount)
// continue;
statType = proto->ItemStat[j].ItemStatType;
val = proto->ItemStat[j].ItemStatValue;
if (val == 0)
continue;
for (std::map<std::string, uint32>::iterator i = weightStatLink.begin(); i != weightStatLink.end(); ++i)
{
uint32 modd = i->second;
if (modd == statType)
{
weightName = i->first;
break;
}
}
if (weightName.empty())
continue;
uint32 singleStat = CalculateSingleStatWeight(playerclass, spec, weightName, val);
basicStatsWeight += singleStat;
if (val)
{
if (weightName == "int" && !noCaster)
isCasterItem = true;
if (weightName == "int")
hasInt = true;
if (weightName == "splpwr")
isCasterItem = true;
if (weightName == "str")
isAttackItem = true;
if (weightName == "agi")
isAttackItem = true;
if (weightName == "atkpwr")
isAttackItem = true;
}
}
// check armor & block
statWeight += CalculateSingleStatWeight(playerclass, spec, "armor", proto->Armor);
statWeight += CalculateSingleStatWeight(playerclass, spec, "block", proto->Block);
// check weapon dps
if (proto->IsWeaponVellum())
{
//WeaponAttackType attType = BASE_ATTACK; //not used, line marked for removal.
uint32 dps = 0;
for (uint8 i = 0; i < MAX_ITEM_PROTO_DAMAGES; i++)
{
if (proto->Damage[i].DamageMax == 0)
break;
dps = (proto->Damage[i].DamageMin + proto->Damage[i].DamageMax) / (float)(proto->Delay / 1000.0f) / 2;
if (dps)
{
if (proto->IsRangedWeapon())
statWeight += CalculateSingleStatWeight(playerclass, spec, "rgddps", dps);
else
statWeight += CalculateSingleStatWeight(playerclass, spec, "mledps", dps);
}
}
}
// check item spells
for (auto const& spellData : proto->Spells)
{
// no spell
if (!spellData.SpellId)
continue;
// apply only at-equip spells
if (spellData.SpellTrigger != ITEM_SPELLTRIGGER_ON_EQUIP)
continue;
// check if it is valid spell
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellData.SpellId);
if (!spellInfo)
continue;
uint32 spellDamage = 0;
uint32 spellHealing = 0;
for (uint32 j = 0; j < MAX_SPELL_EFFECTS; j++)
{
if (spellInfo->Effects[j].Effect == SPELL_EFFECT_APPLY_AURA && spellInfo->Effects[j].BasePoints)
{
// spell damage
// SPELL_AURA_MOD_DAMAGE_DONE
if (spellInfo->Effects[j].ApplyAuraName == SPELL_AURA_MOD_DAMAGE_DONE)
{
spellDamage = spellInfo->Effects[j].BasePoints + 1;
}
// spell healing
// SPELL_AURA_MOD_HEALING_DONE
if (spellInfo->Effects[j].ApplyAuraName == SPELL_AURA_MOD_HEALING_DONE)
{
spellHealing = spellInfo->Effects[j].BasePoints + 1;
}
// check spell power
if (spellDamage && spellDamage == spellHealing)
{
isCasterItem = true;
spellPower += CalculateSingleStatWeight(playerclass, spec, "splpwr", spellDamage);
}
// spell hit rating (pre tbc)
// SPELL_AURA_MOD_SPELL_HIT_CHANCE
if (spellInfo->Effects[j].ApplyAuraName == SPELL_AURA_MOD_SPELL_HIT_CHANCE)
{
statWeight += CalculateSingleStatWeight(playerclass, spec, "spellhitrtng",
spellInfo->Effects[j].BasePoints + 1);
}
// spell crit rating (pre tbc)
// SPELL_AURA_MOD_SPELL_CRIT_CHANCE
if (spellInfo->Effects[j].ApplyAuraName == SPELL_AURA_MOD_SPELL_CRIT_CHANCE)
{
statWeight += CalculateSingleStatWeight(playerclass, spec, "spellcritstrkrtng",
spellInfo->Effects[j].BasePoints + 1);
}
// spell penetration
// SPELL_AURA_MOD_TARGET_RESISTANCE
if (spellInfo->Effects[j].ApplyAuraName == SPELL_AURA_MOD_TARGET_RESISTANCE)
{
// check if magic type
if (spellInfo->Effects[j].MiscValue == SPELL_SCHOOL_MASK_SPELL)
statWeight += CalculateSingleStatWeight(playerclass, spec, "spellpenrtng",
abs(spellInfo->Effects[j].BasePoints + 1));
}
// check attack power
if (spellInfo->Effects[j].ApplyAuraName == SPELL_AURA_MOD_ATTACK_POWER)
{
isAttackItem = true;
attackPower +=
CalculateSingleStatWeight(playerclass, spec, "atkpwr", spellInfo->Effects[j].BasePoints + 1);
}
// check ranged ap
// SPELL_AURA_MOD_RANGED_ATTACK_POWER
if (spellInfo->Effects[j].ApplyAuraName == SPELL_AURA_MOD_RANGED_ATTACK_POWER)
{
isAttackItem = true;
attackPower +=
CalculateSingleStatWeight(playerclass, spec, "atkpwr", spellInfo->Effects[j].BasePoints + 1);
}
// check block
if (spellInfo->Effects[j].ApplyAuraName == SPELL_AURA_MOD_SHIELD_BLOCKVALUE)
{
statWeight +=
CalculateSingleStatWeight(playerclass, spec, "block", spellInfo->Effects[j].BasePoints + 1);
}
// block chance
// SPELL_AURA_MOD_BLOCK_PERCENT
if (spellInfo->Effects[j].ApplyAuraName == SPELL_AURA_MOD_BLOCK_PERCENT)
{
statWeight +=
CalculateSingleStatWeight(playerclass, spec, "blockrtng", spellInfo->Effects[j].BasePoints + 1);
}
// armor penetration
// SPELL_AURA_MOD_TARGET_RESISTANCE
if (spellInfo->Effects[j].ApplyAuraName == SPELL_AURA_MOD_TARGET_RESISTANCE)
{
// check if physical type
if (spellInfo->Effects[j].MiscValue == SPELL_SCHOOL_MASK_NORMAL)
statWeight += CalculateSingleStatWeight(playerclass, spec, "armorpenrtng",
abs(spellInfo->Effects[j].BasePoints + 1));
}
// hit rating (pre tbc)
// SPELL_AURA_MOD_HIT_CHANCE
if (spellInfo->Effects[j].ApplyAuraName == SPELL_AURA_MOD_HIT_CHANCE)
{
statWeight +=
CalculateSingleStatWeight(playerclass, spec, "hitrtng", spellInfo->Effects[j].BasePoints + 1);
}
// crit rating (pre tbc)
// SPELL_AURA_MOD_HIT_CHANCE
if (spellInfo->Effects[j].ApplyAuraName == SPELL_AURA_MOD_CRIT_PCT)
{
statWeight += CalculateSingleStatWeight(playerclass, spec, "critstrkrtng",
spellInfo->Effects[j].BasePoints + 1);
}
// check defense SPELL_AURA_MOD_SKILL
// check block
// if (spellInfo->Effects[j].ApplyAuraName == SPELL_AURA_MOD_SKILL)
//{
// statWeight += CalculateSingleStatWeight(playerclass, spec, "block",
// spellInfo->Effects[j].BasePoints + 1);
//}
// ratings
// enum CombatRating
if (spellInfo->Effects[j].ApplyAuraName == SPELL_AURA_MOD_RATING)
{
for (uint32 rating = 0; rating < MAX_COMBAT_RATING; ++rating)
{
if (spellInfo->Effects[j].MiscValue & (1 << rating))
{
int32 val = spellInfo->Effects[j].BasePoints + 1;
std::string weightName;
for (std::map<std::string, uint32>::iterator i = weightRatingLink.begin();
i != weightRatingLink.end(); ++i)
{
uint32 modd = i->second;
if (modd == rating)
{
weightName = i->first;
break;
}
}
if (weightName.empty())
continue;
statWeight += CalculateSingleStatWeight(playerclass, spec, weightName, val);
}
}
}
// mana regen
// SPELL_AURA_MOD_POWER_REGEN
if (spellInfo->Effects[j].ApplyAuraName == SPELL_AURA_MOD_POWER_REGEN)
{
statWeight +=
CalculateSingleStatWeight(playerclass, spec, "manargn", spellInfo->Effects[j].BasePoints + 1);
}
}
}
}
// check for caster item
if (isCasterItem || hasInt)
{
if ((!hasMana || noCaster) && spellPower)
return 0;
if (!hasMana && hasInt)
return 0;
if ((!hasMana && noCaster && playerclass != CLASS_PALADIN) && spellPower > attackPower)
return 0;
bool playerCaster = false;
for (std::vector<WeightScaleStat>::iterator i = m_weightScales[playerclass][spec].stats.begin();
i != m_weightScales[playerclass][spec].stats.end(); ++i)
{
if (i->stat == "splpwr" || i->stat == "int" || i->stat == "manargn")
{
playerCaster = true;
}
}
if (!playerCaster)
return 0;
}
// check for caster item
if (isAttackItem)
{
if (hasMana && !noCaster && !(hasInt || spellPower))
return 0;
// if (!noCaster && attackPower)
// return 0;
bool playerAttacker = false;
for (std::vector<WeightScaleStat>::iterator i = m_weightScales[playerclass][spec].stats.begin();
i != m_weightScales[playerclass][spec].stats.end(); ++i)
{
if (i->stat == "str" || i->stat == "agi" || i->stat == "atkpwr" || i->stat == "mledps" ||
i->stat == "rgddps")
{
playerAttacker = true;
}
}
if (!playerAttacker)
return 0;
}
statWeight += spellPower;
statWeight += spellHeal;
statWeight += attackPower;
// handle negative stats
if (basicStatsWeight < 0 && (abs(basicStatsWeight) >= statWeight))
statWeight = 0;
else
statWeight += basicStatsWeight;
return statWeight;
}
uint32 RandomItemMgr::CalculateSingleStatWeight(uint8 playerclass, uint8 spec, std::string stat, uint32 value)
{
uint32 statWeight = 0;
for (std::vector<WeightScaleStat>::iterator i = m_weightScales[playerclass][spec].stats.begin();
i != m_weightScales[playerclass][spec].stats.end(); ++i)
{
if (stat == i->stat)
{
statWeight = i->weight * value;
// if (statWeight)
// LOG_INFO("playerbots", "stat: {}, val: {}, weight: {}, total: {}, class: {}, spec: {}",
// stat, value, i->weight, statWeight, playerclass, m_weightScales[playerclass][spec].info.name);
return statWeight;
}
}
return statWeight;
}
uint32 RandomItemMgr::GetQuestIdForItem(uint32 itemId)
{
bool isQuest = false;
uint32 questId = 0;
ObjectMgr::QuestMap const& questTemplates = sObjectMgr->GetQuestTemplates();
for (ObjectMgr::QuestMap::const_iterator i = questTemplates.begin(); i != questTemplates.end(); ++i)
{
Quest const* quest = i->second;
uint32 rewItemCount = quest->GetRewItemsCount();
for (uint32 i = 0; i < rewItemCount; ++i)
{
if (!quest->RewardItemId[i])
continue;
if (quest->RewardItemId[i] == itemId)
{
isQuest = true;
questId = quest->GetQuestId();
break;
}
}
uint32 rewChocieItemCount = quest->GetRewChoiceItemsCount();
for (uint32 i = 0; i < rewChocieItemCount; ++i)
{
if (!quest->RewardChoiceItemId[i])
continue;
if (quest->RewardChoiceItemId[i] == itemId)
{
isQuest = true;
questId = quest->GetQuestId();
break;
}
}
if (isQuest)
break;
}
return questId;
}
std::vector<uint32> RandomItemMgr::GetQuestIdsForItem(uint32 itemId)
{
std::vector<uint32> questIds;
ObjectMgr::QuestMap const& questTemplates = sObjectMgr->GetQuestTemplates();
for (ObjectMgr::QuestMap::const_iterator i = questTemplates.begin(); i != questTemplates.end(); ++i)
{
Quest const* quest = i->second;
uint32 rewItemCount = quest->GetRewItemsCount();
for (uint32 i = 0; i < rewItemCount; ++i)
{
if (!quest->RewardItemId[i])
continue;
if (quest->RewardItemId[i] == itemId)
{
questIds.push_back(quest->GetQuestId());
break;
}
}
uint32 rewChocieItemCount = quest->GetRewChoiceItemsCount();
for (uint32 i = 0; i < rewChocieItemCount; ++i)
{
if (!quest->RewardChoiceItemId[i])
continue;
if (quest->RewardChoiceItemId[i] == itemId)
{
questIds.push_back(quest->GetQuestId());
break;
}
}
}
return questIds;
}
uint32 RandomItemMgr::GetUpgrade(Player* player, std::string spec, uint8 slot, uint32 quality, uint32 itemId)
{
if (!player)
return 0;
// get old item statWeight
uint32 oldStatWeight = 0;
uint32 specId = 0;
uint32 closestUpgrade = 0;
uint32 closestUpgradeWeight = 0;
std::vector<uint32> classspecs;
for (uint32 specNum = 1; specNum < 5; ++specNum)
{
if (!m_weightScales[player->getClass()][specNum].info.id)
continue;
classspecs.push_back(m_weightScales[player->getClass()][specNum].info.id);
if (m_weightScales[player->getClass()][specNum].info.name == spec)
specId = m_weightScales[player->getClass()][specNum].info.id;
}
if (!specId)
return 0;
if (itemId && itemInfoCache.find(itemId) != itemInfoCache.end())
{
oldStatWeight = itemInfoCache[itemId].weights[specId];
if (oldStatWeight)
LOG_INFO("playerbots", "Old Item: {}, weight: {}", itemId, oldStatWeight);
else
LOG_INFO("playerbots", "Old item has no stat weight");
}
for (std::map<uint32, ItemInfoEntry>::iterator i = itemInfoCache.begin(); i != itemInfoCache.end(); ++i)
{
ItemInfoEntry& info = i->second;
// skip useless items
if (info.weights[specId] == 0)
continue;
// skip higher lvl
if (info.minLevel > player->GetLevel())
continue;
// skip too low level
if (info.minLevel < (player->GetLevel() - 10))
continue;
// skip wrong team
if (info.team != TEAM_NEUTRAL && info.team != player->GetTeamId())
continue;
// skip wrong slot
if ((EquipmentSlots)info.slot != (EquipmentSlots)slot)
continue;
// skip higher quality
if (quality && info.quality != quality)
continue;
// skip worse items
if (info.weights[specId] <= oldStatWeight)
continue;
// skip items that only fit in slot, but not stats
if (!itemId && info.weights[specId] == 1 && player->GetLevel() > 40)
continue;
// skip quest items
if (info.source == ITEM_SOURCE_QUEST)
{
if (player->GetQuestRewardStatus(info.sourceId) != QUEST_STATUS_COMPLETE)
continue;
}
// skip no stats trinkets
if (info.weights[specId] == 1 && info.slot == EQUIPMENT_SLOT_NECK || info.slot == EQUIPMENT_SLOT_TRINKET1 ||
info.slot == EQUIPMENT_SLOT_TRINKET2 || info.slot == EQUIPMENT_SLOT_FINGER1 ||
info.slot == EQUIPMENT_SLOT_FINGER2)
continue;
// check if item stat score is the best among class specs
uint32 bestSpecId = 0;
uint32 bestSpecScore = 0;
for (std::vector<uint32>::iterator i = classspecs.begin(); i != classspecs.end(); ++i)
{
if (info.weights[*i] > bestSpecScore)
{
bestSpecId = *i;
bestSpecScore = info.weights[specId];
}
}
if (bestSpecId && bestSpecId != specId && player->GetLevel() > 40)
return 0;
if (!closestUpgrade)
{
closestUpgrade = info.itemId;
closestUpgradeWeight = info.weights[specId];
}
// pick closest upgrade
if (info.weights[specId] < closestUpgradeWeight)
{
closestUpgrade = info.itemId;
closestUpgradeWeight = info.weights[specId];
}
}
if (closestUpgrade)
LOG_INFO("playerbots", "New Item: {}, weight: {}", closestUpgrade, closestUpgradeWeight);
return closestUpgrade;
}
std::vector<uint32> RandomItemMgr::GetUpgradeList(Player* player, std::string spec, uint8 slot, uint32 quality,
uint32 itemId, uint32 amount)
{
std::vector<uint32> listItems;
if (!player)
return listItems;
// get old item statWeight
uint32 oldStatWeight = 0;
uint32 specId = 0;
uint32 closestUpgradeWeight = 0;
std::vector<uint32> classspecs;
for (uint32 specNum = 1; specNum < 5; ++specNum)
{
if (!m_weightScales[player->getClass()][specNum].info.id)
continue;
classspecs.push_back(m_weightScales[player->getClass()][specNum].info.id);
if (m_weightScales[player->getClass()][specNum].info.name == spec)
specId = m_weightScales[player->getClass()][specNum].info.id;
}
if (!specId)
return listItems;
if (itemId && itemInfoCache.find(itemId) != itemInfoCache.end())
{
oldStatWeight = itemInfoCache[itemId].weights[specId];
if (oldStatWeight)
LOG_INFO("playerbots", "Old Item: {}, weight: {}", itemId, oldStatWeight);
else
LOG_INFO("playerbots", "Old item has no stat weight");
}
for (std::map<uint32, ItemInfoEntry>::iterator i = itemInfoCache.begin(); i != itemInfoCache.end(); ++i)
{
ItemInfoEntry& info = i->second;
// skip useless items
if (info.weights[specId] == 0)
continue;
// skip higher lvl
if (info.minLevel > player->GetLevel())
continue;
// skip too low level
if ((int32)info.minLevel < (int32)(player->GetLevel() - 20))
continue;
// skip wrong team
if (info.team != TEAM_NEUTRAL && info.team != player->GetTeamId())
continue;
// skip wrong slot
if ((EquipmentSlots)info.slot != (EquipmentSlots)slot)
continue;
// skip higher quality
if (quality && info.quality != quality)
continue;
// skip worse items
if (info.weights[specId] <= oldStatWeight)
continue;
// skip items that only fit in slot, but not stats
if (!itemId && info.weights[specId] == 1 && player->GetLevel() > 40)
continue;
// skip quest items
if (info.source == ITEM_SOURCE_QUEST)
{
if (player->GetQuestRewardStatus(info.sourceId) != QUEST_STATUS_COMPLETE)
continue;
}
// skip no stats trinkets
if (info.weights[specId] < 2 && (info.slot == EQUIPMENT_SLOT_NECK || info.slot == EQUIPMENT_SLOT_TRINKET1 ||
info.slot == EQUIPMENT_SLOT_TRINKET2 || info.slot == EQUIPMENT_SLOT_FINGER1 ||
info.slot == EQUIPMENT_SLOT_FINGER2))
continue;
// if (player->GetLevel() >= 40)
//{
// // check if item stat score is the best among class specs
// uint32 bestSpecId = 0;
// uint32 bestSpecScore = 0;
// for (vector<uint32>::iterator i = classspecs.begin(); i != classspecs.end(); ++i)
// {
// if (info->weights[*i] > bestSpecScore)
// {
// bestSpecId = *i;
// bestSpecScore = info->weights[specId];
// }
// }
// if (bestSpecId && bestSpecId != specId)
// continue;
//}
listItems.push_back(info.itemId);
// continue;
// pick closest upgrade
if (info.weights[specId] > closestUpgradeWeight)
{
closestUpgradeWeight = info.weights[specId];
}
}
if (listItems.size())
LOG_INFO("playerbots", "New Items: {}, Old item:%d, New items max: {}", listItems.size(), oldStatWeight,
closestUpgradeWeight);
return listItems;
}
bool RandomItemMgr::HasStatWeight(uint32 itemId)
{
auto itr = itemInfoCache.find(itemId);
return itr != itemInfoCache.end();
}
uint32 RandomItemMgr::GetMinLevelFromCache(uint32 itemId)
{
auto itr = itemInfoCache.find(itemId);
if (itr == itemInfoCache.end())
return 0;
return itr->second.minLevel;
}
uint32 RandomItemMgr::GetStatWeight(Player* player, uint32 itemId)
{
if (!player || !itemId)
return 0;
auto itr = itemInfoCache.find(itemId);
if (itr == itemInfoCache.end())
return 0;
uint32 statWeight = 0;
uint32 specId = 0;
std::vector<uint32> classspecs;
std::string const& specName = AiFactory::GetPlayerSpecName(player);
if (specName.empty())
return 0;
for (uint32 specNum = 1; specNum < 5; ++specNum)
{
if (!m_weightScales[player->getClass()][specNum].info.id)
continue;
classspecs.push_back(m_weightScales[player->getClass()][specNum].info.id);
if (m_weightScales[player->getClass()][specNum].info.name == specName)
specId = m_weightScales[player->getClass()][specNum].info.id;
if (m_weightScales[player->getClass()][specNum].info.name == specName)
{
specId = specNum;
break;
}
}
if (!specId)
return 0;
statWeight = itr->second.weights[specId];
return statWeight;
}
uint32 RandomItemMgr::GetLiveStatWeight(Player* player, uint32 itemId)
{
if (!player || !itemId)
return 0;
auto itr = itemInfoCache.find(itemId);
if (itr == itemInfoCache.end())
return 0;
uint32 statWeight = 0;
uint32 specId = 0;
std::vector<uint32> classspecs;
std::string const& specName = AiFactory::GetPlayerSpecName(player);
if (specName.empty())
return 0;
for (uint32 specNum = 1; specNum < 5; ++specNum)
{
if (!m_weightScales[player->getClass()][specNum].info.id)
continue;
// for bestSpec check
// classspecs.push_back(m_weightScales[player->getClass()][specNum].info.id);
if (m_weightScales[player->getClass()][specNum].info.name == specName)
specId = m_weightScales[player->getClass()][specNum].info.id;
}
if (!specId)
return 0;
statWeight = itr->second.weights[specId];
// skip higher lvl
if (itr->second.minLevel > player->GetLevel())
return 0;
// skip too low level
// if ((int32)info->minLevel < (int32)(player->GetLevel() - 20))
// return 0;
// skip wrong team
if (itr->second.team != TEAM_NEUTRAL && itr->second.team != player->GetTeamId())
return 0;
// skip quest items
if (itr->second.source == ITEM_SOURCE_QUEST && itr->second.sourceId)
{
if (player->GetQuestRewardStatus(itr->second.sourceId) != QUEST_STATUS_COMPLETE)
return 0;
}
// skip pvp items
if (itr->second.source == ITEM_SOURCE_PVP)
{
if (!player->GetHonorPoints() && !player->GetArenaPoints())
return 0;
}
// skip no stats trinkets
if (itr->second.weights[specId] == 1 &&
(itr->second.slot == EQUIPMENT_SLOT_NECK || itr->second.slot == EQUIPMENT_SLOT_TRINKET1 ||
itr->second.slot == EQUIPMENT_SLOT_TRINKET2 || itr->second.slot == EQUIPMENT_SLOT_FINGER1 ||
itr->second.slot == EQUIPMENT_SLOT_FINGER2))
return 0;
// skip items that only fit in slot, but not stats
if (!itemId && itr->second.weights[specId] == 1 && player->GetLevel() > 20)
return 0;
// check if item stat score is the best among class specs
/*uint32 bestSpecId = 0;
uint32 bestSpecScore = 0;
for (vector<uint32>::iterator i = classspecs.begin(); i != classspecs.end(); ++i)
{
if (itemCache[itemId]->weights[*i] > bestSpecScore && itemCache[itemId]->weights[*i] > 1)
{
bestSpecId = *i;
bestSpecScore = itemCache[itemId]->weights[specId];
}
}
if (bestSpecId && bestSpecId != specId && player->GetLevel() >= 60)
return 0;*/
return statWeight;
}
void RandomItemMgr::BuildEquipCache()
{
uint32 maxLevel = sPlayerbotAIConfig.randomBotMaxLevel;
if (maxLevel > sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL))
maxLevel = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL);
ItemTemplateContainer const* itemTemplates = sObjectMgr->GetItemTemplateStore();
PlayerbotsDatabasePreparedStatement* stmt = PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_SEL_EQUIP_CACHE);
if (PreparedQueryResult result = PlayerbotsDatabase.Query(stmt))
{
LOG_INFO("server.loading",
"Loading equipment cache for {} classes, {} levels, {} slots, {} quality from {} items", MAX_CLASSES,
maxLevel, EQUIPMENT_SLOT_END, ITEM_QUALITY_ARTIFACT, itemTemplates->size());
uint32 count = 0;
do
{
Field* fields = result->Fetch();
uint8 clazz = fields[0].Get<uint8>();
uint32 level = fields[1].Get<uint32>();
uint8 slot = fields[2].Get<uint8>();
uint32 quality = fields[3].Get<uint32>();
uint32 itemId = fields[4].Get<uint32>();
BotEquipKey key(level, clazz, slot, quality);
equipCache[key].push_back(itemId);
++count;
} while (result->NextRow());
LOG_INFO("playerbots", "Equipment cache loaded from {} records", count);
}
else
{
uint64 total = MAX_CLASSES * maxLevel * EQUIPMENT_SLOT_END * ITEM_QUALITY_ARTIFACT;
LOG_INFO("server.loading",
"Building equipment cache for {} classes, {} levels, {} slots, {} quality from {} items ({} total)",
MAX_CLASSES, maxLevel, EQUIPMENT_SLOT_END, ITEM_QUALITY_ARTIFACT, itemTemplates->size(), total);
for (uint8 class_ = CLASS_WARRIOR; class_ < MAX_CLASSES; ++class_)
{
// skip nonexistent classes
if (!((1 << (class_ - 1)) & CLASSMASK_ALL_PLAYABLE) || !sChrClassesStore.LookupEntry(class_))
continue;
for (uint32 level = 1; level <= maxLevel; ++level)
{
for (uint8 slot = 0; slot < EQUIPMENT_SLOT_END; ++slot)
{
for (uint32 quality = ITEM_QUALITY_POOR; quality <= ITEM_QUALITY_ARTIFACT; ++quality)
{
BotEquipKey key(level, class_, slot, quality);
RandomItemList items;
for (auto const& itr : *itemTemplates)
{
ItemTemplate const* proto = &itr.second;
if (!proto)
continue;
if (proto->Class != ITEM_CLASS_WEAPON && proto->Class != ITEM_CLASS_ARMOR &&
proto->Class != ITEM_CLASS_CONTAINER && proto->Class != ITEM_CLASS_PROJECTILE)
continue;
if (!CanEquipItem(key, proto))
continue;
if (proto->Class == ITEM_CLASS_ARMOR &&
(slot == EQUIPMENT_SLOT_HEAD || slot == EQUIPMENT_SLOT_SHOULDERS ||
slot == EQUIPMENT_SLOT_CHEST || slot == EQUIPMENT_SLOT_WAIST ||
slot == EQUIPMENT_SLOT_LEGS || slot == EQUIPMENT_SLOT_FEET ||
slot == EQUIPMENT_SLOT_WRISTS || slot == EQUIPMENT_SLOT_HANDS) &&
!CanEquipArmor(key.clazz, key.level, proto))
continue;
if (proto->Class == ITEM_CLASS_WEAPON && !CanEquipWeapon(key.clazz, proto))
continue;
if (slot == EQUIPMENT_SLOT_OFFHAND && key.clazz == CLASS_ROGUE &&
proto->Class != ITEM_CLASS_WEAPON)
continue;
items.push_back(itr.first);
PlayerbotsDatabasePreparedStatement* stmt =
PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_INS_EQUIP_CACHE);
stmt->SetData(0, class_);
stmt->SetData(1, level);
stmt->SetData(2, slot);
stmt->SetData(3, quality);
stmt->SetData(4, proto->ItemId);
PlayerbotsDatabase.Execute(stmt);
}
equipCache[key] = items;
LOG_DEBUG("playerbots",
"Equipment cache for class: {}, level {}, slot {}, quality {}: {} items", class_,
level, slot, quality, items.size());
}
}
}
}
LOG_INFO("server.loading", "Equipment cache saved to DB");
}
}
void RandomItemMgr::BuildEquipCacheNew()
{
LOG_INFO("playerbots", "Loading equipments cache...");
std::unordered_set<uint32> questItemIds;
ObjectMgr::QuestMap const& questTemplates = sObjectMgr->GetQuestTemplates();
for (ObjectMgr::QuestMap::const_iterator i = questTemplates.begin(); i != questTemplates.end(); ++i)
{
//uint32 questId = i->first; //not used in this scope, line marked for removal.
Quest const* quest = i->second;
if (quest->IsRepeatable())
continue;
if (quest->GetQuestLevel() <= 0)
continue;
if (quest->GetRequiredClasses())
continue;
for (int j = 0; j < quest->GetRewChoiceItemsCount(); j++)
if (uint32 itemId = quest->RewardChoiceItemId[j])
{
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
if (proto->Class != ITEM_CLASS_WEAPON && proto->Class != ITEM_CLASS_ARMOR)
continue;
int requiredLevel = std::max((int)proto->RequiredLevel, quest->GetQuestLevel());
equipCacheNew[requiredLevel][proto->InventoryType].push_back(itemId);
questItemIds.insert(itemId);
}
for (int j = 0; j < quest->GetRewItemsCount(); j++)
if (uint32 itemId = quest->RewardItemId[j])
{
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
if (proto->Class != ITEM_CLASS_WEAPON && proto->Class != ITEM_CLASS_ARMOR)
continue;
int requiredLevel = std::max((int)proto->RequiredLevel, quest->GetQuestLevel());
equipCacheNew[requiredLevel][proto->InventoryType].push_back(itemId);
questItemIds.insert(itemId);
}
}
ItemTemplateContainer const* itemTemplates = sObjectMgr->GetItemTemplateStore();
for (auto const& itr : *itemTemplates)
{
ItemTemplate const* proto = &itr.second;
if (!proto)
continue;
uint32 itemId = proto->ItemId;
if (questItemIds.find(itemId) != questItemIds.end())
continue;
if (IsTestItem(itemId))
{
continue;
}
// Unobtainable or unusable items
if (itemId == 12468 || // Chilton Wand
itemId == 22784 || // Sunwell Orb
itemId == 46978) // Totem of the Earthen Ring
continue;
equipCacheNew[proto->RequiredLevel][proto->InventoryType].push_back(itemId);
}
}
RandomItemList RandomItemMgr::Query(uint32 level, uint8 clazz, uint8 slot, uint32 quality)
{
// return equipCache[key];
BotEquipKey key(level, clazz, slot, quality);
RandomItemList items;
ItemTemplateContainer const* itemTemplates = sObjectMgr->GetItemTemplateStore();
for (auto const& itr : *itemTemplates)
{
ItemTemplate const* proto = &itr.second;
if (!proto)
continue;
if (proto->Class != ITEM_CLASS_WEAPON && proto->Class != ITEM_CLASS_ARMOR &&
proto->Class != ITEM_CLASS_CONTAINER && proto->Class != ITEM_CLASS_PROJECTILE)
continue;
if (!CanEquipItem(key, proto))
continue;
if (proto->Class == ITEM_CLASS_ARMOR &&
(slot == EQUIPMENT_SLOT_HEAD || slot == EQUIPMENT_SLOT_SHOULDERS || slot == EQUIPMENT_SLOT_CHEST ||
slot == EQUIPMENT_SLOT_WAIST || slot == EQUIPMENT_SLOT_LEGS || slot == EQUIPMENT_SLOT_FEET ||
slot == EQUIPMENT_SLOT_WRISTS || slot == EQUIPMENT_SLOT_HANDS) &&
!CanEquipArmor(key.clazz, key.level, proto))
continue;
if (proto->Class == ITEM_CLASS_WEAPON && !CanEquipWeapon(key.clazz, proto))
continue;
if (slot == EQUIPMENT_SLOT_OFFHAND && key.clazz == CLASS_ROGUE && proto->Class != ITEM_CLASS_WEAPON)
continue;
items.push_back(itr.first);
}
return items;
}
void RandomItemMgr::BuildAmmoCache()
{
uint32 maxLevel = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL);
LOG_INFO("server.loading", "Building ammo cache for {} levels", maxLevel);
uint32 counter = 0;
for (uint32 level = 1; level <= maxLevel; level += 1)
{
for (uint32 subClass = ITEM_SUBCLASS_ARROW; subClass <= ITEM_SUBCLASS_BULLET; subClass++)
{
QueryResult results = WorldDatabase.Query(
"SELECT entry FROM item_template WHERE class = {} AND subclass = {} AND RequiredLevel <= {} AND duration = 0 "
"AND (Flags & 16) = 0 AND dmg_min1 != 0 AND RequiredLevel != 0 "
"ORDER BY stackable DESC, ItemLevel DESC",
ITEM_CLASS_PROJECTILE, subClass, level);
if (!results)
continue;
do
{
Field* fields = results->Fetch();
uint32 entry = fields[0].Get<uint32>();
ammoCache[level][subClass].push_back(entry);
++counter;
} while (results->NextRow());
}
}
LOG_INFO("server.loading", "Cached {} ammo", counter); // TEST
}
std::vector<uint32> RandomItemMgr::GetAmmo(uint32 level, uint32 subClass) { return ammoCache[level][subClass]; }
void RandomItemMgr::BuildPotionCache()
{
uint32 maxLevel = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL);
LOG_INFO("playerbots", "Building potion cache for {} levels", maxLevel);
ItemTemplateContainer const* itemTemplates = sObjectMgr->GetItemTemplateStore();
uint32 counter = 0;
for (uint32 level = 1; level <= maxLevel; level++)
{
uint32 effects[] = {SPELL_EFFECT_HEAL, SPELL_EFFECT_ENERGIZE};
for (uint8 i = 0; i < 2; ++i)
{
uint32 effect = effects[i];
for (auto const& itr : *itemTemplates)
{
ItemTemplate const* proto = &itr.second;
if (!proto)
continue;
if (proto->Class != ITEM_CLASS_CONSUMABLE ||
(proto->SubClass != ITEM_SUBCLASS_POTION && proto->SubClass != ITEM_SUBCLASS_FLASK) ||
proto->Bonding != NO_BIND)
continue;
uint32 requiredLevel = proto->RequiredLevel;
if (requiredLevel > level || (level > 13 && requiredLevel < level - 13))
continue;
if (proto->RequiredSkill)
continue;
if (proto->Area || proto->Map || proto->RequiredCityRank || proto->RequiredHonorRank)
continue;
if (proto->Duration & 0x80000000)
continue;
if (proto->AllowableClass != -1)
continue;
bool hybrid = false;
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(proto->Spells[0].SpellId);
if (!spellInfo)
continue;
// do not accept hybrid potion
for (uint8 i = 1; i < 3; i++)
{
if (spellInfo->Effects[i].Effect != 0)
{
hybrid = true;
break;
}
}
if (hybrid)
continue;
if (spellInfo->Effects[0].Effect == effect)
potionCache[level][effect].push_back(itr.first);
}
}
}
for (uint32 level = 1; level <= maxLevel; level++)
{
uint32 effects[] = {SPELL_EFFECT_HEAL, SPELL_EFFECT_ENERGIZE};
for (uint8 i = 0; i < 2; ++i)
{
uint32 effect = effects[i];
uint32 size = potionCache[level][effect].size();
counter += size;
}
}
LOG_INFO("playerbots", "Cached {} potions", counter);
}
void RandomItemMgr::BuildFoodCache()
{
uint32 maxLevel = sPlayerbotAIConfig.randomBotMaxLevel;
if (maxLevel > sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL))
maxLevel = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL);
LOG_INFO("server.loading", "Building food cache for {} levels", maxLevel);
ItemTemplateContainer const* itemTemplates = sObjectMgr->GetItemTemplateStore();
uint32 counter = 0;
for (uint32 level = 1; level <= maxLevel + 1; level += 10)
{
uint32 categories[] = {11, 59};
for (int i = 0; i < 2; ++i)
{
uint32 category = categories[i];
for (auto const& itr : *itemTemplates)
{
ItemTemplate const* proto = &itr.second;
if (!proto)
continue;
if (proto->Class != ITEM_CLASS_CONSUMABLE ||
(proto->SubClass != ITEM_SUBCLASS_FOOD && proto->SubClass != ITEM_SUBCLASS_CONSUMABLE) ||
(proto->Spells[0].SpellCategory != category) || proto->Bonding != NO_BIND)
continue;
if (proto->RequiredLevel && (proto->RequiredLevel > level || proto->RequiredLevel < level - 10))
continue;
if (proto->RequiredSkill)
continue;
if (proto->Area || proto->Map || proto->RequiredCityRank || proto->RequiredHonorRank)
continue;
if (proto->Duration & 0x80000000)
continue;
foodCache[level / 10][category].push_back(itr.first);
}
}
}
for (uint32 level = 1; level <= maxLevel + 1; level += 10)
{
uint32 categories[] = {11, 59};
for (uint8 i = 0; i < 2; ++i)
{
uint32 category = categories[i];
uint32 size = foodCache[level / 10][category].size();
++counter;
LOG_DEBUG("server.loading", "Food cache for level={}, category={}: {} items", level, category, size);
}
}
LOG_INFO("server.loading", "Cached {} types of food", counter);
}
uint32 RandomItemMgr::GetRandomPotion(uint32 level, uint32 effect)
{
const std::vector<uint32> &potions = potionCache[level][effect];
if (potions.empty())
return 0;
return potions[urand(0, potions.size() - 1)];
}
uint32 RandomItemMgr::GetFood(uint32 level, uint32 category)
{
std::initializer_list<uint32> items;
std::vector<uint32> food;
if (category == 11)
{
if (level < 5)
items = {787, 117, 4540, 2680};
else if (level < 15)
items = {2287, 4592, 4541, 21072};
else if (level < 25)
items = {3770, 16170, 4542, 20074};
else if (level < 35)
items = {4594, 3771, 1707, 4457};
else if (level < 45)
items = {4599, 4601, 21552, 17222 /*21030, 16168 */};
else if (level < 55)
items = {8950, 8952, 8957, 21023 /*21033, 21031 */};
else if (level < 65)
items = {29292, 27859, 30458, 27662};
else if (level < 75)
items = {29450, 29451, 29452};
else
items = {35947};
}
if (category == 59)
{
if (level < 5)
items = {159, 117};
else if (level < 15)
items = {1179, 21072};
else if (level < 25)
items = {1205};
else if (level < 35)
items = {1708};
else if (level < 45)
items = {1645};
else if (level < 55)
items = {8766};
else if (level < 65)
items = {28399};
else if (level < 75)
items = {27860};
else
items = {33445};
}
food.insert(food.end(), items);
if (food.empty())
return 0;
return food[urand(0, food.size() - 1)];
}
uint32 RandomItemMgr::GetRandomFood(uint32 level, uint32 category)
{
std::vector<uint32> food = foodCache[(level - 1) / 10][category];
if (food.empty())
return 0;
return food[urand(0, food.size() - 1)];
}
void RandomItemMgr::BuildTradeCache()
{
uint32 maxLevel = sPlayerbotAIConfig.randomBotMaxLevel;
if (maxLevel > sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL))
maxLevel = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL);
LOG_INFO("server.loading", "Building trade cache for {} levels", maxLevel);
ItemTemplateContainer const* itemTemplates = sObjectMgr->GetItemTemplateStore();
uint32 counter = 0;
for (uint32 level = 1; level <= maxLevel + 1; level += 10)
{
for (auto const& itr : *itemTemplates)
{
ItemTemplate const* proto = &itr.second;
if (!proto)
continue;
if (proto->Class != ITEM_CLASS_TRADE_GOODS || proto->Bonding != NO_BIND)
continue;
if (proto->ItemLevel < level)
continue;
if (proto->RequiredLevel && (proto->RequiredLevel > level || proto->RequiredLevel < level - 10))
continue;
if (proto->RequiredSkill)
continue;
tradeCache[level / 10].push_back(itr.first);
}
}
for (uint32 level = 1; level <= maxLevel + 1; level += 10)
{
uint32 size = tradeCache[level / 10].size();
LOG_DEBUG("server.loading", "Trade cache for level={}: {} items", level, size);
++counter;
}
LOG_INFO("server.loading", "Cached {} trade items", counter); // TEST
}
uint32 RandomItemMgr::GetRandomTrade(uint32 level)
{
std::vector<uint32> trade = tradeCache[(level - 1) / 10];
if (trade.empty())
return 0;
return trade[urand(0, trade.size() - 1)];
}
void RandomItemMgr::BuildRarityCache()
{
if (PreparedQueryResult result =
PlayerbotsDatabase.Query(PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_SEL_RARITY_CACHE)))
{
LOG_INFO("playerbots", "Loading item rarity cache");
uint32 count = 0;
do
{
Field* fields = result->Fetch();
uint32 itemId = fields[0].Get<uint32>();
float rarity = fields[1].Get<float>();
rarityCache[itemId] = rarity;
++count;
} while (result->NextRow());
LOG_INFO("playerbots", "Item rarity cache loaded from {} records", count);
}
else
{
ItemTemplateContainer const* itemTemplates = sObjectMgr->GetItemTemplateStore();
LOG_INFO("playerbots", "Building item rarity cache from {} items", itemTemplates->size());
for (auto const& itr : *itemTemplates)
{
ItemTemplate const* proto = &itr.second;
if (!proto)
continue;
if (proto->Duration & 0x80000000)
continue;
if (proto->Quality == ITEM_QUALITY_POOR)
continue;
if (strstri(proto->Name1.c_str(), "qa") || strstri(proto->Name1.c_str(), "test") ||
strstri(proto->Name1.c_str(), "deprecated"))
continue;
if (!proto->ItemLevel)
continue;
QueryResult results = WorldDatabase.Query(
"SELECT MAX(q.chance) FROM ( "
// "-- Creature "
"SELECT "
"AVG ( "
" CASE "
" WHEN lt.groupid = 0 THEN lt.ChanceOrQuestChance "
" WHEN lt.ChanceOrQuestChance > 0 THEN lt.ChanceOrQuestChance "
" ELSE "
" IFNULL(100 - (SELECT SUM(ChanceOrQuestChance) FROM creature_loot_template lt1 WHERE lt1.groupid = "
"lt.groupid AND lt1.entry = lt.entry AND lt1.ChanceOrQuestChance > 0), 100) "
" / (SELECT COUNT(*) FROM creature_loot_template lt1 WHERE lt1.groupid = lt.groupid AND lt1.entry = "
"lt.entry AND lt1.ChanceOrQuestChance = 0) "
" END "
") chance, 'creature' type "
"FROM creature_loot_template lt "
"JOIN creature_template ct ON ct.LootId = lt.entry "
"JOIN creature c ON c.id1 = ct.entry "
"WHERE lt.item = {} "
"union all "
// "-- Gameobject "
"SELECT "
"AVG ( "
" CASE "
" WHEN lt.groupid = 0 THEN lt.ChanceOrQuestChance "
" WHEN lt.ChanceOrQuestChance > 0 THEN lt.ChanceOrQuestChance "
" ELSE "
" IFNULL(100 - (SELECT SUM(ChanceOrQuestChance) FROM gameobject_loot_template lt1 WHERE lt1.groupid "
"= lt.groupid AND lt1.entry = lt.entry AND lt1.ChanceOrQuestChance > 0), 100) "
" / (SELECT COUNT(*) FROM gameobject_loot_template lt1 WHERE lt1.groupid = lt.groupid AND lt1.entry "
"= lt.entry AND lt1.ChanceOrQuestChance = 0) "
" END "
") chance, 'gameobject' type "
"FROM gameobject_loot_template lt "
"JOIN gameobject_template ct ON ct.data1 = lt.entry "
"JOIN gameobject c ON c.id1 = ct.entry "
"WHERE lt.item = {} "
"union all "
// "-- Disenchant "
"SELECT "
"AVG ( "
" CASE "
" WHEN lt.groupid = 0 THEN lt.ChanceOrQuestChance "
" WHEN lt.ChanceOrQuestChance > 0 THEN lt.ChanceOrQuestChance "
" ELSE "
" IFNULL(100 - (SELECT SUM(ChanceOrQuestChance) FROM disenchant_loot_template lt1 WHERE lt1.groupid "
"= lt.groupid AND lt1.entry = lt.entry AND lt1.ChanceOrQuestChance > 0), 100) "
" / (SELECT COUNT(*) FROM disenchant_loot_template lt1 WHERE lt1.groupid = lt.groupid AND lt1.entry "
"= lt.entry AND lt1.ChanceOrQuestChance = 0) "
" END "
") chance, 'disenchant' type "
"FROM disenchant_loot_template lt "
"JOIN item_template ct ON ct.DisenchantID = lt.entry "
"WHERE lt.item = {} "
"union all "
// "-- Fishing "
"SELECT "
"AVG ( "
" CASE "
" WHEN lt.groupid = 0 THEN lt.ChanceOrQuestChance "
" WHEN lt.ChanceOrQuestChance > 0 THEN lt.ChanceOrQuestChance "
" ELSE "
" IFNULL(100 - (SELECT SUM(ChanceOrQuestChance) FROM fishing_loot_template lt1 WHERE lt1.groupid = "
"lt.groupid AND lt1.entry = lt.entry AND lt1.ChanceOrQuestChance > 0), 100) "
" / (SELECT COUNT(*) FROM fishing_loot_template lt1 WHERE lt1.groupid = lt.groupid AND lt1.entry = "
"lt.entry AND lt1.ChanceOrQuestChance = 0) "
" END "
") chance, 'fishing' type "
"FROM fishing_loot_template lt "
"WHERE lt.item = {} "
"union all "
// "-- Skinning "
"SELECT "
"AVG ( "
" CASE "
" WHEN lt.groupid = 0 THEN lt.ChanceOrQuestChance "
" WHEN lt.ChanceOrQuestChance > 0 THEN lt.ChanceOrQuestChance "
" ELSE "
" IFNULL(100 - (SELECT SUM(ChanceOrQuestChance) FROM skinning_loot_template lt1 WHERE lt1.groupid = "
"lt.groupid AND lt1.entry = lt.entry AND lt1.ChanceOrQuestChance > 0), 100) "
" * IFNULL((SELECT 1/COUNT(*) FROM skinning_loot_template lt1 WHERE lt1.groupid = lt.groupid AND "
"lt1.entry = lt.entry AND lt1.ChanceOrQuestChance = 0), 1) "
" END "
") chance, 'skinning' type "
"FROM skinning_loot_template lt "
"JOIN creature_template ct ON ct.SkinningLootId = lt.entry "
"JOIN creature c ON c.id1 = ct.entry "
"WHERE lt.item = {}) q; ",
itr.first, itr.first, itr.first, itr.first, itr.first);
if (results)
{
Field* fields = results->Fetch();
float rarity = fields[0].Get<float>();
if (rarity > 0.01)
{
rarityCache[itr.first] = rarity;
PlayerbotsDatabasePreparedStatement* stmt =
PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_INS_RARITY_CACHE);
stmt->SetData(0, itr.first);
stmt->SetData(1, rarity);
PlayerbotsDatabase.Execute(stmt);
}
}
}
LOG_INFO("playerbots", "Item rarity cache built from {} items", itemTemplates->size());
}
}
float RandomItemMgr::GetItemRarity(uint32 itemId) { return rarityCache[itemId]; }
inline bool IsCraftedBySpellInfo(ItemTemplate const* proto, SpellInfo const* spellInfo)
{
for (uint32 x = 0; x < MAX_SPELL_REAGENTS; ++x)
{
if (spellInfo->Reagent[x] <= 0)
{
continue;
}
if (proto->ItemId == spellInfo->Reagent[x])
return true;
}
for (uint8 i = 0; i < 3; ++i)
{
if (spellInfo->Effects[i].Effect == SPELL_EFFECT_CREATE_ITEM)
{
if (spellInfo->Effects[i].ItemType == proto->ItemId)
return true;
}
}
return false;
}
inline bool IsCraftedBySpell(ItemTemplate const* proto, uint32 spellId)
{
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
if (!spellInfo)
return false;
return IsCraftedBySpellInfo(proto, spellInfo);
}
inline bool IsCraftedBy(ItemTemplate const* proto, uint32 spellId)
{
if (IsCraftedBySpell(proto, spellId))
return true;
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
if (!spellInfo)
return false;
for (uint32 effect = 0; effect < 3; ++effect)
{
uint32 craftId = spellInfo->Effects[effect].TriggerSpell;
SpellInfo const* craftSpellInfo = sSpellMgr->GetSpellInfo(craftId);
if (!craftSpellInfo)
continue;
if (IsCraftedBySpellInfo(proto, craftSpellInfo))
return true;
}
return false;
}
inline bool ContainsInternal(ItemTemplate const* proto, uint32 skillId)
{
for (uint32 j = 0; j < sSkillLineAbilityStore.GetNumRows(); ++j)
{
SkillLineAbilityEntry const* skillLine = sSkillLineAbilityStore.LookupEntry(j);
if (!skillLine || skillLine->ID != skillId)
continue;
if (IsCraftedBy(proto, skillLine->Spell))
return true;
}
CreatureTemplateContainer const* creatures = sObjectMgr->GetCreatureTemplates();
for (CreatureTemplateContainer::const_iterator itr = creatures->begin(); itr != creatures->end(); ++itr)
{
Trainer::Trainer* trainer = sObjectMgr->GetTrainer(itr->first);
if (!trainer)
continue;
if (trainer->GetTrainerType() != Trainer::Type::Tradeskill)
continue;
for (auto& spell : trainer->GetSpells())
{
if (spell.ReqSkillLine != skillId)
continue;
if (IsCraftedBy(proto, spell.SpellId))
return true;
}
}
std::vector<ItemTemplate*> const* itemTemplates = sObjectMgr->GetItemTemplateStoreFast();
for (ItemTemplate const* recipe : *itemTemplates)
{
if (!recipe)
continue;
if (recipe->Class == ITEM_CLASS_RECIPE &&
((recipe->SubClass == ITEM_SUBCLASS_LEATHERWORKING_PATTERN && skillId == SKILL_LEATHERWORKING) ||
(recipe->SubClass == ITEM_SUBCLASS_TAILORING_PATTERN && skillId == SKILL_TAILORING) ||
(recipe->SubClass == ITEM_SUBCLASS_ENGINEERING_SCHEMATIC && skillId == SKILL_ENGINEERING) ||
(recipe->SubClass == ITEM_SUBCLASS_BLACKSMITHING && skillId == SKILL_BLACKSMITHING) ||
(recipe->SubClass == ITEM_SUBCLASS_COOKING_RECIPE && skillId == SKILL_COOKING) ||
(recipe->SubClass == ITEM_SUBCLASS_ALCHEMY_RECIPE && skillId == SKILL_ALCHEMY) ||
(recipe->SubClass == ITEM_SUBCLASS_FIRST_AID_MANUAL && skillId == SKILL_FIRST_AID) ||
(recipe->SubClass == ITEM_SUBCLASS_ENCHANTING_FORMULA && skillId == SKILL_ENCHANTING) ||
(recipe->SubClass == ITEM_SUBCLASS_JEWELCRAFTING_RECIPE && skillId == SKILL_JEWELCRAFTING) ||
(recipe->SubClass == ITEM_SUBCLASS_FISHING_MANUAL && skillId == SKILL_FISHING)))
{
for (uint32 i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i)
{
if (IsCraftedBy(proto, recipe->Spells[i].SpellId))
return true;
}
}
}
return false;
}
bool RandomItemMgr::IsUsedBySkill(ItemTemplate const* proto, uint32 skillId)
{
if (itemCache.find(proto->ItemId) != itemCache.end())
return true;
switch (proto->Class)
{
case ITEM_CLASS_TRADE_GOODS:
case ITEM_CLASS_MISC:
case ITEM_CLASS_REAGENT:
case ITEM_CLASS_GEM:
break;
default:
return false;
}
bool contains = ContainsInternal(proto, skillId);
if (contains)
itemCache.insert(proto->ItemId);
return contains;
}