
This is the first, rough implementation of Playerbot AI by Ike3, originally playerbot by blueboy There's still TONS of work left to do on this, but it should compile and build This introduces a new cmake option, playerbots. Disabled by default
1524 lines
45 KiB
C++
1524 lines
45 KiB
C++
#include "../pchdef.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;
|
|
}
|
|
} |