mangos/src/modules/Bots/playerbot/PlayerbotFactory.cpp
Foereaper 6ee9bc9e9d Fix bot PCH
On unix builds, the PCH would collide with Games PCH
2015-04-04 11:38:55 +02:00

1524 lines
45 KiB
C++

#include "../botpch.h"
#include "playerbot.h"
#include "PlayerbotFactory.h"
#include "SQLStorages.h"
#include "ItemPrototype.h"
#include "PlayerbotAIConfig.h"
#include "AccountMgr.h"
#include "DBCStore.h"
#include "SharedDefines.h"
#include "ahbot/AhBot.h"
using namespace ai;
using namespace std;
uint32 PlayerbotFactory::tradeSkills[] =
{
SKILL_ALCHEMY,
SKILL_ENCHANTING,
SKILL_SKINNING,
SKILL_TAILORING,
SKILL_LEATHERWORKING,
SKILL_ENGINEERING,
SKILL_HERBALISM,
SKILL_MINING,
SKILL_BLACKSMITHING,
SKILL_COOKING,
SKILL_FIRST_AID,
SKILL_FISHING
};
void PlayerbotFactory::Randomize()
{
Randomize(true);
}
void PlayerbotFactory::Refresh()
{
Prepare();
InitEquipment(true);
InitAmmo();
InitFood();
InitPotions();
uint32 money = urand(level * 1000, level * 5 * 1000);
if (bot->GetMoney() < money)
bot->SetMoney(money);
bot->SaveToDB();
}
void PlayerbotFactory::CleanRandomize()
{
Randomize(false);
}
void PlayerbotFactory::Prepare()
{
if (!itemQuality)
{
if (level <= 10)
itemQuality = urand(ITEM_QUALITY_NORMAL, ITEM_QUALITY_UNCOMMON);
else if (level <= 20)
itemQuality = urand(ITEM_QUALITY_UNCOMMON, ITEM_QUALITY_RARE);
else if (level <= 40)
itemQuality = urand(ITEM_QUALITY_UNCOMMON, ITEM_QUALITY_EPIC);
else if (level < 60)
itemQuality = urand(ITEM_QUALITY_UNCOMMON, ITEM_QUALITY_EPIC);
else
itemQuality = urand(ITEM_QUALITY_RARE, ITEM_QUALITY_EPIC);
}
if (bot->IsDead())
bot->ResurrectPlayer(1.0f, false);
bot->CombatStop(true);
bot->SetLevel(level);
bot->SetFlag(PLAYER_FLAGS, PLAYER_FLAGS_HIDE_HELM);
bot->SetFlag(PLAYER_FLAGS, PLAYER_FLAGS_HIDE_CLOAK);
}
void PlayerbotFactory::Randomize(bool incremental)
{
Prepare();
bot->resetTalents(true);
ClearSpells();
ClearInventory();
bot->SaveToDB();
InitQuests();
// quest rewards boost bot level, so reduce back
bot->SetLevel(level);
ClearInventory();
bot->SetUInt32Value(PLAYER_XP, 0);
CancelAuras();
bot->SaveToDB();
InitAvailableSpells();
InitSkills();
InitTradeSkills();
InitTalents();
InitAvailableSpells();
InitSpecialSpells();
InitMounts();
UpdateTradeSkills();
bot->SaveToDB();
InitEquipment(incremental);
InitBags();
InitAmmo();
InitFood();
InitPotions();
InitSecondEquipmentSet();
InitInventory();
bot->SetMoney(urand(level * 1000, level * 5 * 1000));
bot->SaveToDB();
InitPet();
bot->SaveToDB();
}
void PlayerbotFactory::InitPet()
{
Pet* pet = bot->GetPet();
if (!pet)
{
if (bot->getClass() != CLASS_HUNTER)
return;
Map* map = bot->GetMap();
if (!map)
return;
vector<uint32> ids;
for (uint32 id = 0; id < sCreatureStorage.GetMaxEntry(); ++id)
{
CreatureInfo const* co = sCreatureStorage.LookupEntry<CreatureInfo>(id);
if (!co || !co->isTameable())
continue;
if (co->MinLevel > bot->getLevel())
continue;
PetLevelInfo const* petInfo = sObjectMgr.GetPetLevelInfo(co->Entry, bot->getLevel());
if (!petInfo)
continue;
ids.push_back(id);
}
if (ids.empty())
{
sLog.outError("No pets available for bot %s (%d level)", bot->GetName(), bot->getLevel());
return;
}
for (int i = 0; i < 100; i++)
{
int index = urand(0, ids.size() - 1);
CreatureInfo const* co = sCreatureStorage.LookupEntry<CreatureInfo>(ids[index]);
PetLevelInfo const* petInfo = sObjectMgr.GetPetLevelInfo(co->Entry, bot->getLevel());
if (!petInfo)
continue;
uint32 guid = map->GenerateLocalLowGuid(HIGHGUID_PET);
CreatureCreatePos pos(map, bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), bot->GetOrientation());
pet = new Pet(HUNTER_PET);
if (!pet->Create(guid, pos, co, 0))
{
delete pet;
pet = NULL;
continue;
}
pet->SetOwnerGuid(bot->GetObjectGuid());
pet->SetCreatorGuid(bot->GetObjectGuid());
pet->setFaction(bot->getFaction());
pet->SetLevel(bot->getLevel());
bot->SetPet(pet);
sLog.outDetail("Bot %s: assign pet %d (%d level)", bot->GetName(), co->Entry, bot->getLevel());
pet->SavePetToDB(PET_SAVE_AS_CURRENT);
break;
}
}
if (!pet)
{
sLog.outError("Cannot create pet for bot %s", bot->GetName());
return;
}
for (PetSpellMap::const_iterator itr = pet->m_spells.begin(); itr != pet->m_spells.end(); ++itr)
{
if(itr->second.state == PETSPELL_REMOVED)
continue;
uint32 spellId = itr->first;
if(IsPassiveSpell(spellId))
continue;
pet->ToggleAutocast(spellId, true);
}
}
void PlayerbotFactory::ClearSpells()
{
list<uint32> spells;
for(PlayerSpellMap::iterator itr = bot->GetSpellMap().begin(); itr != bot->GetSpellMap().end(); ++itr)
{
uint32 spellId = itr->first;
if(itr->second.state == PLAYERSPELL_REMOVED || itr->second.disabled || IsPassiveSpell(spellId))
continue;
spells.push_back(spellId);
}
for (list<uint32>::iterator i = spells.begin(); i != spells.end(); ++i)
{
bot->removeSpell(*i);
}
}
void PlayerbotFactory::InitSpells()
{
for (int i = 0; i < 15; i++)
InitAvailableSpells();
}
void PlayerbotFactory::InitTalents()
{
uint32 point = urand(0, 100);
uint8 cls = bot->getClass();
uint32 p1 = sPlayerbotAIConfig.specProbability[cls][0];
uint32 p2 = p1 + sPlayerbotAIConfig.specProbability[cls][1];
uint32 specNo = (point < p1 ? 0 : (point < p2 ? 1 : 2));
InitTalents(specNo);
if (bot->GetFreeTalentPoints())
InitTalents(2 - specNo);
}
class DestroyItemsVisitor : public IterateItemsVisitor
{
public:
DestroyItemsVisitor(Player* bot) : IterateItemsVisitor(), bot(bot) {}
virtual bool Visit(Item* item)
{
uint32 id = item->GetProto()->ItemId;
if (CanKeep(id))
{
keep.insert(id);
return true;
}
bot->DestroyItem(item->GetBagSlot(), item->GetSlot(), true);
return true;
}
private:
bool CanKeep(uint32 id)
{
if (keep.find(id) != keep.end())
return false;
if (sPlayerbotAIConfig.IsInRandomQuestItemList(id))
return true;
ItemPrototype const* proto = sItemStorage.LookupEntry<ItemPrototype>(id);
if (proto->Class == ITEM_CLASS_MISC && proto->SubClass == ITEM_SUBCLASS_JUNK)
return true;
return false;
}
private:
Player* bot;
set<uint32> keep;
};
bool PlayerbotFactory::CanEquipArmor(ItemPrototype const* proto)
{
if (bot->HasSkill(SKILL_SHIELD) && proto->SubClass == ITEM_SUBCLASS_ARMOR_SHIELD)
return true;
if (bot->HasSkill(SKILL_PLATE_MAIL))
{
if (proto->SubClass != ITEM_SUBCLASS_ARMOR_PLATE)
return false;
}
else if (bot->HasSkill(SKILL_MAIL))
{
if (proto->SubClass != ITEM_SUBCLASS_ARMOR_MAIL)
return false;
}
else if (bot->HasSkill(SKILL_LEATHER))
{
if (proto->SubClass != ITEM_SUBCLASS_ARMOR_LEATHER)
return false;
}
if (proto->Quality <= ITEM_QUALITY_NORMAL)
return true;
uint8 sp = 0, ap = 0, tank = 0;
for (int 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(sp, ap, tank);
}
bool PlayerbotFactory::CheckItemStats(uint8 sp, uint8 ap, uint8 tank)
{
switch (bot->getClass())
{
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;
}
void PlayerbotFactory::AddItemStats(uint32 mod, uint8 &sp, uint8 &ap, uint8 &tank)
{
switch (mod)
{
//FOEREAPER
//case ITEM_MOD_HIT_RATING:
//case ITEM_MOD_CRIT_RATING:
//case ITEM_MOD_HASTE_RATING:
case ITEM_MOD_HEALTH:
case ITEM_MOD_STAMINA:
//case ITEM_MOD_HEALTH_REGEN:
case ITEM_MOD_MANA:
case ITEM_MOD_INTELLECT:
case ITEM_MOD_SPIRIT:
//case ITEM_MOD_MANA_REGENERATION:
//case ITEM_MOD_SPELL_POWER:
//case ITEM_MOD_SPELL_PENETRATION:
//case ITEM_MOD_HIT_SPELL_RATING:
//case ITEM_MOD_CRIT_SPELL_RATING:
//case ITEM_MOD_HASTE_SPELL_RATING:
sp++;
break;
}
switch (mod)
{
//case ITEM_MOD_HIT_RATING:
//case ITEM_MOD_CRIT_RATING:
//case ITEM_MOD_HASTE_RATING:
case ITEM_MOD_AGILITY:
case ITEM_MOD_STRENGTH:
case ITEM_MOD_HEALTH:
case ITEM_MOD_STAMINA:
//case ITEM_MOD_HEALTH_REGEN:
//case ITEM_MOD_DEFENSE_SKILL_RATING:
//case ITEM_MOD_DODGE_RATING:
//case ITEM_MOD_PARRY_RATING:
//case ITEM_MOD_BLOCK_RATING:
//case ITEM_MOD_HIT_TAKEN_MELEE_RATING:
//case ITEM_MOD_HIT_TAKEN_RANGED_RATING:
//case ITEM_MOD_HIT_TAKEN_SPELL_RATING:
//case ITEM_MOD_CRIT_TAKEN_MELEE_RATING:
//case ITEM_MOD_CRIT_TAKEN_RANGED_RATING:
//case ITEM_MOD_CRIT_TAKEN_SPELL_RATING:
//case ITEM_MOD_HIT_TAKEN_RATING:
//case ITEM_MOD_CRIT_TAKEN_RATING:
//case ITEM_MOD_RESILIENCE_RATING:
//case ITEM_MOD_BLOCK_VALUE:
tank++;
break;
}
switch (mod)
{
case ITEM_MOD_HEALTH:
case ITEM_MOD_STAMINA:
//case ITEM_MOD_HEALTH_REGEN:
case ITEM_MOD_AGILITY:
case ITEM_MOD_STRENGTH:
//case ITEM_MOD_HIT_MELEE_RATING:
//case ITEM_MOD_HIT_RANGED_RATING:
//case ITEM_MOD_CRIT_MELEE_RATING:
//case ITEM_MOD_CRIT_RANGED_RATING:
//case ITEM_MOD_HASTE_MELEE_RATING:
//case ITEM_MOD_HASTE_RANGED_RATING:
//case ITEM_MOD_HIT_RATING:
//case ITEM_MOD_CRIT_RATING:
//case ITEM_MOD_HASTE_RATING:
//case ITEM_MOD_EXPERTISE_RATING:
//case ITEM_MOD_ATTACK_POWER:
//case ITEM_MOD_RANGED_ATTACK_POWER:
//case ITEM_MOD_ARMOR_PENETRATION_RATING:
ap++;
break;
}
}
bool PlayerbotFactory::CanEquipWeapon(ItemPrototype const* proto)
{
switch (bot->getClass())
{
case CLASS_PRIEST:
if (proto->SubClass != ITEM_SUBCLASS_WEAPON_STAFF &&
proto->SubClass != ITEM_SUBCLASS_WEAPON_WAND &&
proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE)
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_SWORD)
return false;
break;
case CLASS_WARRIOR:
if (proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE2 &&
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)
return false;
break;
case CLASS_PALADIN:
if (proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE2 &&
proto->SubClass != ITEM_SUBCLASS_WEAPON_SWORD2 &&
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_MACE2 &&
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)
return false;
break;
case CLASS_HUNTER:
if (proto->SubClass != ITEM_SUBCLASS_WEAPON_AXE2 &&
proto->SubClass != ITEM_SUBCLASS_WEAPON_SWORD2 &&
proto->SubClass != ITEM_SUBCLASS_WEAPON_GUN &&
proto->SubClass != ITEM_SUBCLASS_WEAPON_CROSSBOW &&
proto->SubClass != ITEM_SUBCLASS_WEAPON_BOW)
return false;
break;
case CLASS_ROGUE:
if (proto->SubClass != ITEM_SUBCLASS_WEAPON_DAGGER &&
proto->SubClass != ITEM_SUBCLASS_WEAPON_SWORD &&
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)
return false;
break;
}
return true;
}
bool PlayerbotFactory::CanEquipItem(ItemPrototype const* proto, uint32 desiredQuality)
{
if (proto->Duration & 0x80000000)
return false;
if (proto->Quality != desiredQuality)
return false;
if (proto->Bonding == BIND_QUEST_ITEM || proto->Bonding == BIND_WHEN_USE)
return false;
if (proto->Class == ITEM_CLASS_CONTAINER)
return true;
uint32 requiredLevel = proto->RequiredLevel;
if (!requiredLevel)
return false;
uint32 level = bot->getLevel();
uint32 delta = 2;
if (level < 15)
delta = urand(7, 15);
else if (proto->Class == ITEM_CLASS_WEAPON || proto->SubClass == ITEM_SUBCLASS_ARMOR_SHIELD)
delta = urand(2, 3);
else if (!(level % 10) || (level % 10) == 9)
delta = 2;
else if (level < 40)
delta = urand(5, 10);
else if (level < 60)
delta = urand(3, 7);
else if (level < 70)
delta = urand(2, 5);
else if (level < 80)
delta = urand(2, 4);
if (desiredQuality > 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;
}
void PlayerbotFactory::InitEquipment(bool incremental)
{
DestroyItemsVisitor visitor(bot);
IterateItems(&visitor, ITERATE_ALL_ITEMS);
map<uint8, vector<uint32> > items;
for(uint8 slot = 0; slot < EQUIPMENT_SLOT_END; ++slot)
{
if (slot == EQUIPMENT_SLOT_TABARD || slot == EQUIPMENT_SLOT_BODY)
continue;
uint32 desiredQuality = itemQuality;
if (urand(0, 100) < 100 * sPlayerbotAIConfig.randomGearLoweringChance && desiredQuality > ITEM_QUALITY_NORMAL) {
desiredQuality--;
}
do
{
for (uint32 itemId = 0; itemId < sItemStorage.GetMaxEntry(); ++itemId)
{
ItemPrototype const* proto = sObjectMgr.GetItemPrototype(itemId);
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(proto, desiredQuality))
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(proto))
continue;
if (proto->Class == ITEM_CLASS_WEAPON && !CanEquipWeapon(proto))
continue;
if (slot == EQUIPMENT_SLOT_OFFHAND && bot->getClass() == CLASS_ROGUE && proto->Class != ITEM_CLASS_WEAPON)
continue;
uint16 dest = 0;
if (CanEquipUnseenItem(slot, dest, itemId))
items[slot].push_back(itemId);
}
} while (items[slot].empty() && desiredQuality-- > ITEM_QUALITY_NORMAL);
}
for(uint8 slot = 0; slot < EQUIPMENT_SLOT_END; ++slot)
{
if (slot == EQUIPMENT_SLOT_TABARD || slot == EQUIPMENT_SLOT_BODY)
continue;
vector<uint32>& ids = items[slot];
if (ids.empty())
{
sLog.outDetail("%s: no items to equip for slot %d", bot->GetName(), slot);
continue;
}
for (int attempts = 0; attempts < 15; attempts++)
{
uint32 index = urand(0, ids.size() - 1);
uint32 newItemId = ids[index];
Item* oldItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot);
if (incremental && !IsDesiredReplacement(oldItem)) {
continue;
}
uint16 dest;
if (!CanEquipUnseenItem(slot, dest, newItemId))
continue;
if (oldItem)
{
bot->RemoveItem(INVENTORY_SLOT_BAG_0, slot, true);
oldItem->DestroyForPlayer(bot);
}
Item* newItem = bot->EquipNewItem(dest, newItemId, true);
if (newItem)
{
newItem->AddToWorld();
newItem->AddToUpdateQueueOf(bot);
bot->AutoUnequipOffhandIfNeed();
EnchantItem(newItem);
break;
}
}
}
}
bool PlayerbotFactory::IsDesiredReplacement(Item* item)
{
if (!item)
return true;
ItemPrototype const* proto = item->GetProto();
int delta = 1 + (80 - bot->getLevel()) / 10;
return (int)bot->getLevel() - (int)proto->RequiredLevel > delta;
}
void PlayerbotFactory::InitSecondEquipmentSet()
{
if (bot->getClass() == CLASS_MAGE || bot->getClass() == CLASS_WARLOCK || bot->getClass() == CLASS_PRIEST)
return;
map<uint32, vector<uint32> > items;
uint32 desiredQuality = itemQuality;
while (urand(0, 100) < 100 * sPlayerbotAIConfig.randomGearLoweringChance && desiredQuality > ITEM_QUALITY_NORMAL) {
desiredQuality--;
}
do
{
for (uint32 itemId = 0; itemId < sItemStorage.GetMaxEntry(); ++itemId)
{
ItemPrototype const* proto = sObjectMgr.GetItemPrototype(itemId);
if (!proto)
continue;
if (!CanEquipItem(proto, desiredQuality))
continue;
if (proto->Class == ITEM_CLASS_WEAPON)
{
if (!CanEquipWeapon(proto))
continue;
Item* existingItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND);
if (existingItem)
{
switch (existingItem->GetProto()->SubClass)
{
case ITEM_SUBCLASS_WEAPON_AXE:
case ITEM_SUBCLASS_WEAPON_DAGGER:
case ITEM_SUBCLASS_WEAPON_FIST:
case ITEM_SUBCLASS_WEAPON_MACE:
case ITEM_SUBCLASS_WEAPON_SWORD:
if (proto->SubClass == ITEM_SUBCLASS_WEAPON_AXE || proto->SubClass == ITEM_SUBCLASS_WEAPON_DAGGER ||
proto->SubClass == ITEM_SUBCLASS_WEAPON_FIST || proto->SubClass == ITEM_SUBCLASS_WEAPON_MACE ||
proto->SubClass == ITEM_SUBCLASS_WEAPON_SWORD)
continue;
break;
default:
if (proto->SubClass != ITEM_SUBCLASS_WEAPON_AXE && proto->SubClass != ITEM_SUBCLASS_WEAPON_DAGGER &&
proto->SubClass != ITEM_SUBCLASS_WEAPON_FIST && proto->SubClass != ITEM_SUBCLASS_WEAPON_MACE &&
proto->SubClass != ITEM_SUBCLASS_WEAPON_SWORD)
continue;
break;
}
}
}
else if (proto->Class == ITEM_CLASS_ARMOR && proto->SubClass == ITEM_SUBCLASS_ARMOR_SHIELD)
{
if (!CanEquipArmor(proto))
continue;
Item* existingItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND);
if (existingItem && existingItem->GetProto()->SubClass == ITEM_SUBCLASS_ARMOR_SHIELD)
continue;
}
else
continue;
items[proto->Class].push_back(itemId);
}
} while (items[ITEM_CLASS_ARMOR].empty() && items[ITEM_CLASS_WEAPON].empty() && desiredQuality-- > ITEM_QUALITY_NORMAL);
for (map<uint32, vector<uint32> >::iterator i = items.begin(); i != items.end(); ++i)
{
vector<uint32>& ids = i->second;
if (ids.empty())
{
sLog.outDetail("%s: no items to make second equipment set for slot %d", bot->GetName(), i->first);
continue;
}
for (int attempts = 0; attempts < 15; attempts++)
{
uint32 index = urand(0, ids.size() - 1);
uint32 newItemId = ids[index];
Item* newItem = bot->StoreNewItemInInventorySlot(newItemId, 1);
if (newItem)
{
EnchantItem(newItem);
newItem->AddToWorld();
newItem->AddToUpdateQueueOf(bot);
break;
}
}
}
}
void PlayerbotFactory::InitBags()
{
vector<uint32> ids;
for (uint32 itemId = 0; itemId < sItemStorage.GetMaxEntry(); ++itemId)
{
ItemPrototype const* proto = sObjectMgr.GetItemPrototype(itemId);
if (!proto || proto->Class != ITEM_CLASS_CONTAINER)
continue;
if (!CanEquipItem(proto, ITEM_QUALITY_NORMAL))
continue;
ids.push_back(itemId);
}
if (ids.empty())
{
sLog.outError("%s: no bags found", bot->GetName());
return;
}
for (uint8 slot = INVENTORY_SLOT_BAG_START; slot < INVENTORY_SLOT_BAG_END; ++slot)
{
for (int attempts = 0; attempts < 15; attempts++)
{
uint32 index = urand(0, ids.size() - 1);
uint32 newItemId = ids[index];
uint16 dest;
if (!CanEquipUnseenItem(slot, dest, newItemId))
continue;
Item* newItem = bot->EquipNewItem(dest, newItemId, true);
if (newItem)
{
newItem->AddToWorld();
newItem->AddToUpdateQueueOf(bot);
break;
}
}
}
}
void PlayerbotFactory::EnchantItem(Item* item)
{
if (urand(0, 100) < 100 * sPlayerbotAIConfig.randomGearLoweringChance)
return;
if (bot->getLevel() < urand(40, 50))
return;
ItemPrototype const* proto = item->GetProto();
int32 itemLevel = proto->ItemLevel;
vector<uint32> ids;
for (int id = 0; id < sSpellStore.GetNumRows(); ++id)
{
SpellEntry const *entry = sSpellStore.LookupEntry(id);
if (!entry)
continue;
int32 requiredLevel = (int32)entry->baseLevel;
if (requiredLevel && (requiredLevel > itemLevel || requiredLevel < itemLevel - 35))
continue;
if (entry->maxLevel && level > entry->maxLevel)
continue;
uint32 spellLevel = entry->spellLevel;
if (spellLevel && (spellLevel > level || spellLevel < level - 10))
continue;
for (int j = 0; j < 3; ++j)
{
if (entry->Effect[j] != SPELL_EFFECT_ENCHANT_ITEM)
continue;
uint32 enchant_id = entry->EffectMiscValue[j];
if (!enchant_id)
continue;
SpellItemEnchantmentEntry const* enchant = sSpellItemEnchantmentStore.LookupEntry(enchant_id);
if (!enchant || enchant->slot != PERM_ENCHANTMENT_SLOT)
continue;
uint8 sp = 0, ap = 0, tank = 0;
for (int i = 0; i < 3; ++i)
{
if (enchant->type[i] != ITEM_ENCHANTMENT_TYPE_STAT)
continue;
AddItemStats(enchant->spellid[i], sp, ap, tank);
}
if (!CheckItemStats(sp, ap, tank))
continue;
if (!item->IsFitToSpellRequirements(entry))
continue;
ids.push_back(enchant_id);
}
}
if (ids.empty())
{
sLog.outDetail("%s: no enchantments found for item %d", bot->GetName(), item->GetProto()->ItemId);
return;
}
int index = urand(0, ids.size() - 1);
uint32 id = ids[index];
SpellItemEnchantmentEntry const* enchant = sSpellItemEnchantmentStore.LookupEntry(id);
if (!enchant)
return;
bot->ApplyEnchantment(item, PERM_ENCHANTMENT_SLOT, false);
item->SetEnchantment(PERM_ENCHANTMENT_SLOT, id, 0, 0);
bot->ApplyEnchantment(item, PERM_ENCHANTMENT_SLOT, true);
}
bool PlayerbotFactory::CanEquipUnseenItem(uint8 slot, uint16 &dest, uint32 item)
{
dest = 0;
Item *pItem = Item::CreateItem(item, 1, bot);
if (pItem)
{
InventoryResult result = bot->CanEquipItem(slot, dest, pItem, true, false);
pItem->RemoveFromUpdateQueueOf(bot);
delete pItem;
return result == EQUIP_ERR_OK;
}
return false;
}
void PlayerbotFactory::InitTradeSkills()
{
for (int i = 0; i < sizeof(tradeSkills) / sizeof(uint32); ++i)
{
bot->SetSkill(tradeSkills[i], 0, 0);
}
vector<uint32> firstSkills;
vector<uint32> secondSkills;
switch (bot->getClass())
{
case CLASS_WARRIOR:
case CLASS_PALADIN:
firstSkills.push_back(SKILL_MINING);
secondSkills.push_back(SKILL_BLACKSMITHING);
secondSkills.push_back(SKILL_ENGINEERING);
break;
case CLASS_SHAMAN:
case CLASS_DRUID:
case CLASS_HUNTER:
case CLASS_ROGUE:
firstSkills.push_back(SKILL_SKINNING);
secondSkills.push_back(SKILL_LEATHERWORKING);
break;
default:
firstSkills.push_back(SKILL_TAILORING);
secondSkills.push_back(SKILL_ENCHANTING);
}
SetRandomSkill(SKILL_FIRST_AID);
SetRandomSkill(SKILL_FISHING);
SetRandomSkill(SKILL_COOKING);
switch (urand(0, 1))
{
case 0:
SetRandomSkill(SKILL_HERBALISM);
SetRandomSkill(SKILL_ALCHEMY);
break;
/*case 1:
SetRandomSkill(SKILL_HERBALISM);
SetRandomSkill(SKILL_INSCRIPTION);
break;
case 2:
SetRandomSkill(SKILL_MINING);
SetRandomSkill(SKILL_JEWELCRAFTING);
break;*/
case 1://3:
SetRandomSkill(firstSkills[urand(0, firstSkills.size() - 1)]);
SetRandomSkill(secondSkills[urand(0, secondSkills.size() - 1)]);
break;
}
}
void PlayerbotFactory::UpdateTradeSkills()
{
for (int i = 0; i < sizeof(tradeSkills) / sizeof(uint32); ++i)
{
if (bot->GetSkillValue(tradeSkills[i]) == 1)
bot->SetSkill(tradeSkills[i], 0, 0);
}
}
void PlayerbotFactory::InitSkills()
{
uint32 maxValue = level * 5;
SetRandomSkill(SKILL_DEFENSE);
SetRandomSkill(SKILL_SWORDS);
SetRandomSkill(SKILL_AXES);
SetRandomSkill(SKILL_BOWS);
SetRandomSkill(SKILL_GUNS);
SetRandomSkill(SKILL_MACES);
SetRandomSkill(SKILL_2H_SWORDS);
SetRandomSkill(SKILL_STAVES);
SetRandomSkill(SKILL_2H_MACES);
SetRandomSkill(SKILL_2H_AXES);
SetRandomSkill(SKILL_DAGGERS);
SetRandomSkill(SKILL_THROWN);
SetRandomSkill(SKILL_CROSSBOWS);
SetRandomSkill(SKILL_WANDS);
SetRandomSkill(SKILL_POLEARMS);
SetRandomSkill(SKILL_FIST_WEAPONS);
if (bot->getLevel() >= 70)
bot->SetSkill(SKILL_RIDING, 300, 300);
else if (bot->getLevel() >= 60)
bot->SetSkill(SKILL_RIDING, 225, 225);
else if (bot->getLevel() >= 40)
bot->SetSkill(SKILL_RIDING, 150, 150);
else if (bot->getLevel() >= 20)
bot->SetSkill(SKILL_RIDING, 75, 75);
else
bot->SetSkill(SKILL_RIDING, 0, 0);
uint32 skillLevel = bot->getLevel() < 40 ? 0 : 1;
switch (bot->getClass())
{
//case CLASS_DEATH_KNIGHT:
case CLASS_WARRIOR:
case CLASS_PALADIN:
bot->SetSkill(SKILL_PLATE_MAIL, skillLevel, skillLevel);
break;
case CLASS_SHAMAN:
case CLASS_HUNTER:
bot->SetSkill(SKILL_MAIL, skillLevel, skillLevel);
}
}
void PlayerbotFactory::SetRandomSkill(uint16 id)
{
uint32 maxValue = level * 5;
uint32 curValue = urand(maxValue - level, maxValue);
bot->SetSkill(id, curValue, maxValue);
}
void PlayerbotFactory::InitAvailableSpells()
{
bot->learnDefaultSpells();
for (uint32 id = 0; id < sCreatureStorage.GetMaxEntry(); ++id)
{
CreatureInfo const* co = sCreatureStorage.LookupEntry<CreatureInfo>(id);
if (!co)
continue;
if (co->TrainerType != TRAINER_TYPE_TRADESKILLS && co->TrainerType != TRAINER_TYPE_CLASS)
continue;
if (co->TrainerType == TRAINER_TYPE_CLASS && co->TrainerClass != bot->getClass())
continue;
uint32 trainerId = co->TrainerTemplateId;
if (!trainerId)
trainerId = co->Entry;
TrainerSpellData const* trainer_spells = sObjectMgr.GetNpcTrainerTemplateSpells(trainerId);
if (!trainer_spells)
trainer_spells = sObjectMgr.GetNpcTrainerSpells(trainerId);
if (!trainer_spells)
continue;
for (TrainerSpellMap::const_iterator itr = trainer_spells->spellList.begin(); itr != trainer_spells->spellList.end(); ++itr)
{
TrainerSpell const* tSpell = &itr->second;
if (!tSpell)
continue;
uint32 reqLevel = 0;
reqLevel = tSpell->isProvidedReqLevel ? tSpell->reqLevel : std::max(reqLevel, tSpell->reqLevel);
TrainerSpellState state = bot->GetTrainerSpellState(tSpell, reqLevel);
if (state != TRAINER_SPELL_GREEN)
continue;
ai->CastSpell(tSpell->spell, bot);
}
}
}
void PlayerbotFactory::InitSpecialSpells()
{
for (list<uint32>::iterator i = sPlayerbotAIConfig.randomBotSpellIds.begin(); i != sPlayerbotAIConfig.randomBotSpellIds.end(); ++i)
{
uint32 spellId = *i;
bot->learnSpell(spellId, false);
}
}
void PlayerbotFactory::InitTalents(uint32 specNo)
{
uint32 classMask = bot->getClassMask();
map<uint32, vector<TalentEntry const*> > spells;
for (uint32 i = 0; i < sTalentStore.GetNumRows(); ++i)
{
TalentEntry const *talentInfo = sTalentStore.LookupEntry(i);
if(!talentInfo)
continue;
TalentTabEntry const *talentTabInfo = sTalentTabStore.LookupEntry( talentInfo->TalentTab );
if(!talentTabInfo || talentTabInfo->tabpage != specNo)
continue;
if( (classMask & talentTabInfo->ClassMask) == 0 )
continue;
spells[talentInfo->Row].push_back(talentInfo);
}
uint32 freePoints = bot->GetFreeTalentPoints();
for (map<uint32, vector<TalentEntry const*> >::iterator i = spells.begin(); i != spells.end(); ++i)
{
vector<TalentEntry const*> &spells = i->second;
if (spells.empty())
{
sLog.outError("%s: No spells for talent row %d", bot->GetName(), i->first);
continue;
}
int attemptCount = 0;
while (!spells.empty() && (int)freePoints - (int)bot->GetFreeTalentPoints() < 5 && attemptCount++ < 3 && bot->GetFreeTalentPoints())
{
int index = urand(0, spells.size() - 1);
TalentEntry const *talentInfo = spells[index];
for (int rank = 0; rank < MAX_TALENT_RANK && bot->GetFreeTalentPoints(); ++rank)
{
uint32 spellId = talentInfo->RankID[rank];
if (!spellId)
continue;
bot->learnSpell(spellId, false);
bot->UpdateFreeTalentPoints(false);
}
spells.erase(spells.begin() + index);
}
freePoints = bot->GetFreeTalentPoints();
}
}
ObjectGuid PlayerbotFactory::GetRandomBot()
{
vector<ObjectGuid> guids;
for (list<uint32>::iterator i = sPlayerbotAIConfig.randomBotAccounts.begin(); i != sPlayerbotAIConfig.randomBotAccounts.end(); i++)
{
uint32 accountId = *i;
if (!sAccountMgr.GetCharactersCount(accountId))
continue;
QueryResult *result = CharacterDatabase.PQuery("SELECT guid FROM characters WHERE account = '%u'", accountId);
if (!result)
continue;
do
{
Field* fields = result->Fetch();
ObjectGuid guid = ObjectGuid(fields[0].GetUInt64());
if (!sObjectMgr.GetPlayer(guid))
guids.push_back(guid);
} while (result->NextRow());
delete result;
}
if (guids.empty())
return ObjectGuid();
int index = urand(0, guids.size() - 1);
return guids[index];
}
void PlayerbotFactory::InitQuests()
{
QueryResult *results = WorldDatabase.PQuery("SELECT entry, RequiredClasses, RequiredRaces FROM quest_template where QuestLevel = -1 and MinLevel <= '%u'",
bot->getLevel());
if (!results)
return;
list<uint32> ids;
do
{
Field* fields = results->Fetch();
uint32 questId = fields[0].GetUInt32();
uint32 requiredClasses = fields[1].GetUInt32();
uint32 requiredRaces = fields[2].GetUInt32();
if ((requiredClasses & bot->getClassMask()) && (requiredRaces & bot->getRaceMask()))
ids.push_back(questId);
} while (results->NextRow());
delete results;
for (int i = 0; i < 15; i++)
{
for (list<uint32>::iterator i = ids.begin(); i != ids.end(); ++i)
{
uint32 questId = *i;
Quest const *quest = sObjectMgr.GetQuestTemplate(questId);
bot->SetQuestStatus(questId, QUEST_STATUS_NONE);
if (!bot->SatisfyQuestClass(quest, false) ||
!bot->SatisfyQuestRace(quest, false) ||
!bot->SatisfyQuestStatus(quest, false))
continue;
if (quest->IsRepeatable())
continue;
bot->SetQuestStatus(questId, QUEST_STATUS_COMPLETE);
bot->RewardQuest(quest, 0, bot, false);
ClearInventory();
}
}
}
void PlayerbotFactory::ClearInventory()
{
DestroyItemsVisitor visitor(bot);
IterateItems(&visitor);
}
void PlayerbotFactory::InitAmmo()
{
if (bot->getClass() != CLASS_HUNTER && bot->getClass() != CLASS_ROGUE && bot->getClass() != CLASS_WARRIOR)
return;
Item* const pItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_RANGED);
if (!pItem)
return;
uint32 subClass = 0;
switch (pItem->GetProto()->SubClass)
{
case ITEM_SUBCLASS_WEAPON_GUN:
subClass = ITEM_SUBCLASS_BULLET;
break;
case ITEM_SUBCLASS_WEAPON_BOW:
case ITEM_SUBCLASS_WEAPON_CROSSBOW:
subClass = ITEM_SUBCLASS_ARROW;
break;
}
if (!subClass)
return;
QueryResult *results = WorldDatabase.PQuery("select max(entry), max(RequiredLevel) from item_template where class = '%u' and subclass = '%u' and RequiredLevel <= '%u'",
ITEM_CLASS_PROJECTILE, subClass, bot->getLevel());
if (!results)
return;
Field* fields = results->Fetch();
if (fields)
{
uint32 entry = fields[0].GetUInt32();
for (int i = 0; i < 5; i++)
{
Item* newItem = bot->StoreNewItemInInventorySlot(entry, 1000);
if (newItem)
newItem->AddToUpdateQueueOf(bot);
}
bot->SetAmmo(entry);
}
delete results;
}
void PlayerbotFactory::InitMounts()
{
map<int32, vector<uint32> > spells;
for (uint32 spellId = 0; spellId < sSpellStore.GetNumRows(); ++spellId)
{
SpellEntry const *spellInfo = sSpellStore.LookupEntry(spellId);
if (!spellInfo || spellInfo->EffectApplyAuraName[0] != SPELL_AURA_MOUNTED)
continue;
if (GetSpellCastTime(spellInfo) < 500 || GetSpellDuration(spellInfo) != -1)
continue;
int32 effect = max(spellInfo->EffectBasePoints[1], spellInfo->EffectBasePoints[2]);
if (effect < 50)
continue;
spells[effect].push_back(spellId);
}
for (uint32 type = 0; type < 2; ++type)
{
for (map<int32, vector<uint32> >::iterator i = spells.begin(); i != spells.end(); ++i)
{
int32 effect = i->first;
vector<uint32>& ids = i->second;
uint32 index = urand(0, ids.size() - 1);
if (index >= ids.size())
continue;
bot->learnSpell(ids[index], false);
}
}
}
void PlayerbotFactory::InitPotions()
{
map<uint32, vector<uint32> > items;
for (uint32 itemId = 0; itemId < sItemStorage.GetMaxEntry(); ++itemId)
{
ItemPrototype const* proto = sObjectMgr.GetItemPrototype(itemId);
if (!proto)
continue;
if (proto->Class != ITEM_CLASS_CONSUMABLE ||
proto->SubClass != ITEM_SUBCLASS_POTION ||
proto->Spells[0].SpellCategory != 4 ||
proto->Bonding != NO_BIND)
continue;
if (proto->RequiredLevel > bot->getLevel() || proto->RequiredLevel < bot->getLevel() - 10)
continue;
if (proto->RequiredSkill && !bot->HasSkill(proto->RequiredSkill))
continue;
if (proto->Area || proto->Map || proto->RequiredCityRank || proto->RequiredHonorRank)
continue;
for (int j = 0; j < MAX_ITEM_PROTO_SPELLS; j++)
{
const SpellEntry* const spellInfo = sSpellStore.LookupEntry(proto->Spells[j].SpellId);
if (!spellInfo)
continue;
for (int i = 0 ; i < 3; i++)
{
if (spellInfo->Effect[i] == SPELL_EFFECT_HEAL || spellInfo->Effect[i] == SPELL_EFFECT_ENERGIZE)
{
items[spellInfo->Effect[i]].push_back(itemId);
break;
}
}
}
}
uint32 effects[] = { SPELL_EFFECT_HEAL, SPELL_EFFECT_ENERGIZE };
for (int i = 0; i < sizeof(effects) / sizeof(uint32); ++i)
{
uint32 effect = effects[i];
vector<uint32>& ids = items[effect];
uint32 index = urand(0, ids.size() - 1);
if (index >= ids.size())
continue;
uint32 itemId = ids[index];
ItemPrototype const* proto = sObjectMgr.GetItemPrototype(itemId);
Item* newItem = bot->StoreNewItemInInventorySlot(itemId, urand(1, proto->GetMaxStackSize()));
if (newItem)
newItem->AddToUpdateQueueOf(bot);
}
}
void PlayerbotFactory::InitFood()
{
map<uint32, vector<uint32> > items;
for (uint32 itemId = 0; itemId < sItemStorage.GetMaxEntry(); ++itemId)
{
ItemPrototype const* proto = sObjectMgr.GetItemPrototype(itemId);
if (!proto)
continue;
if (proto->Class != ITEM_CLASS_CONSUMABLE ||
proto->SubClass != ITEM_SUBCLASS_FOOD ||
(proto->Spells[0].SpellCategory != 11 && proto->Spells[0].SpellCategory != 59) ||
proto->Bonding != NO_BIND)
continue;
if (proto->RequiredLevel > bot->getLevel() || proto->RequiredLevel < bot->getLevel() - 10)
continue;
if (proto->RequiredSkill && !bot->HasSkill(proto->RequiredSkill))
continue;
if (proto->Area || proto->Map || proto->RequiredCityRank || proto->RequiredHonorRank)
continue;
items[proto->Spells[0].SpellCategory].push_back(itemId);
}
uint32 categories[] = { 11, 59 };
for (int i = 0; i < sizeof(categories) / sizeof(uint32); ++i)
{
uint32 category = categories[i];
vector<uint32>& ids = items[category];
uint32 index = urand(0, ids.size() - 1);
if (index >= ids.size())
continue;
uint32 itemId = ids[index];
ItemPrototype const* proto = sObjectMgr.GetItemPrototype(itemId);
Item* newItem = bot->StoreNewItemInInventorySlot(itemId, urand(1, proto->GetMaxStackSize()));
if (newItem)
newItem->AddToUpdateQueueOf(bot);
}
}
void PlayerbotFactory::CancelAuras()
{
bot->RemoveAllAuras();
}
void PlayerbotFactory::InitInventory()
{
InitInventoryTrade();
InitInventoryEquip();
InitInventorySkill();
}
void PlayerbotFactory::InitInventorySkill()
{
if (bot->HasSkill(SKILL_MINING)) {
StoreItem(2901, 1); // Mining Pick
}
/*if (bot->HasSkill(SKILL_JEWELCRAFTING)) {
StoreItem(20815, 1); // Jeweler's Kit
StoreItem(20824, 1); // Simple Grinder
}*/
if (bot->HasSkill(SKILL_BLACKSMITHING) || bot->HasSkill(SKILL_ENGINEERING)) {
StoreItem(5956, 1); // Blacksmith Hammer
}
if (bot->HasSkill(SKILL_ENGINEERING)) {
StoreItem(6219, 1); // Arclight Spanner
}
if (bot->HasSkill(SKILL_ENCHANTING)) {
StoreItem(16207, 1); // Runed Arcanite Rod
}
/*if (bot->HasSkill(SKILL_INSCRIPTION)) {
StoreItem(39505, 1); // Virtuoso Inking Set
}*/
if (bot->HasSkill(SKILL_SKINNING)) {
StoreItem(7005, 1); // Skinning Knife
}
}
Item* PlayerbotFactory::StoreItem(uint32 itemId, uint32 count)
{
ItemPrototype const* proto = sObjectMgr.GetItemPrototype(itemId);
Item* newItem = bot->StoreNewItemInInventorySlot(itemId, min(count, proto->GetMaxStackSize()));
if (newItem)
newItem->AddToUpdateQueueOf(bot);
return newItem;
}
void PlayerbotFactory::InitInventoryTrade()
{
vector<uint32> ids;
for (uint32 itemId = 0; itemId < sItemStorage.GetMaxEntry(); ++itemId)
{
ItemPrototype const* proto = sObjectMgr.GetItemPrototype(itemId);
if (!proto)
continue;
if (proto->Class != ITEM_CLASS_TRADE_GOODS || proto->Bonding != NO_BIND)
continue;
if (proto->ItemLevel < bot->getLevel())
continue;
if (proto->RequiredLevel > bot->getLevel() || proto->RequiredLevel < bot->getLevel() - 10)
continue;
if (proto->RequiredSkill && !bot->HasSkill(proto->RequiredSkill))
continue;
ids.push_back(itemId);
}
if (ids.empty())
{
sLog.outError("No trade items available for bot %s (%d level)", bot->GetName(), bot->getLevel());
return;
}
uint32 index = urand(0, ids.size() - 1);
if (index >= ids.size())
return;
uint32 itemId = ids[index];
ItemPrototype const* proto = sObjectMgr.GetItemPrototype(itemId);
if (!proto)
return;
uint32 count = 1, stacks = 1;
switch (proto->Quality)
{
case ITEM_QUALITY_NORMAL:
count = proto->GetMaxStackSize();
stacks = urand(1, 7) / auctionbot.GetRarityPriceMultiplier(proto);
break;
case ITEM_QUALITY_UNCOMMON:
stacks = 1;
count = urand(1, proto->GetMaxStackSize());
break;
case ITEM_QUALITY_RARE:
stacks = 1;
count = urand(1, min(uint32(3), proto->GetMaxStackSize()));
break;
}
for (uint32 i = 0; i < stacks; i++)
StoreItem(itemId, count);
}
void PlayerbotFactory::InitInventoryEquip()
{
vector<uint32> ids;
uint32 desiredQuality = itemQuality;
if (urand(0, 100) < 100 * sPlayerbotAIConfig.randomGearLoweringChance && desiredQuality > ITEM_QUALITY_NORMAL) {
desiredQuality--;
}
for (uint32 itemId = 0; itemId < sItemStorage.GetMaxEntry(); ++itemId)
{
ItemPrototype const* proto = sObjectMgr.GetItemPrototype(itemId);
if (!proto)
continue;
if (proto->Class != ITEM_CLASS_ARMOR && proto->Class != ITEM_CLASS_WEAPON || (proto->Bonding == BIND_WHEN_PICKED_UP ||
proto->Bonding == BIND_WHEN_USE))
continue;
if (proto->Class == ITEM_CLASS_ARMOR && !CanEquipArmor(proto))
continue;
if (proto->Class == ITEM_CLASS_WEAPON && !CanEquipWeapon(proto))
continue;
if (!CanEquipItem(proto, desiredQuality))
continue;
ids.push_back(itemId);
}
int maxCount = urand(0, 3);
int count = 0;
for (int attempts = 0; attempts < 15; attempts++)
{
uint32 index = urand(0, ids.size() - 1);
if (index >= ids.size())
continue;
uint32 itemId = ids[index];
if (StoreItem(itemId, 1) && count++ >= maxCount)
break;
}
}