mirror of
https://github.com/mod-playerbots/mod-playerbots
synced 2025-11-29 15:58:20 +08:00
498 lines
14 KiB
C++
498 lines
14 KiB
C++
/*
|
|
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it and/or modify it under version 2 of the License, or (at your option), any later version.
|
|
*/
|
|
|
|
#include "UseItemAction.h"
|
|
#include "ChatHelper.h"
|
|
#include "Event.h"
|
|
#include "ItemUsageValue.h"
|
|
#include "Playerbots.h"
|
|
|
|
bool UseItemAction::Execute(Event event)
|
|
{
|
|
std::string name = event.getParam();
|
|
if (name.empty())
|
|
name = getName();
|
|
|
|
std::vector<Item*> items = AI_VALUE2(std::vector<Item*>, "inventory items", name);
|
|
GuidVector gos = chat->parseGameobjects(name);
|
|
|
|
if (gos.empty())
|
|
{
|
|
if (!items.empty()) {
|
|
return UseItemAuto(*items.begin());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (items.empty())
|
|
return UseGameObject(*gos.begin());
|
|
else
|
|
return UseItemOnGameObject(*items.begin(), *gos.begin());
|
|
}
|
|
|
|
botAI->TellError("No items (or game objects) available");
|
|
return false;
|
|
}
|
|
|
|
bool UseItemAction::UseGameObject(ObjectGuid guid)
|
|
{
|
|
GameObject* go = botAI->GetGameObject(guid);
|
|
if (!go || !go->isSpawned()/* || go->GetGoState() != GO_STATE_READY*/)
|
|
return false;
|
|
|
|
go->Use(bot);
|
|
|
|
std::ostringstream out;
|
|
out << "Using " << chat->FormatGameobject(go);
|
|
botAI->TellMasterNoFacing(out.str());
|
|
return true;
|
|
}
|
|
|
|
bool UseItemAction::UseItemAuto(Item* item)
|
|
{
|
|
return UseItem(item, ObjectGuid::Empty, nullptr);
|
|
}
|
|
|
|
bool UseItemAction::UseItemOnGameObject(Item* item, ObjectGuid go)
|
|
{
|
|
return UseItem(item, go, nullptr);
|
|
}
|
|
|
|
bool UseItemAction::UseItemOnItem(Item* item, Item* itemTarget)
|
|
{
|
|
return UseItem(item, ObjectGuid::Empty, itemTarget);
|
|
}
|
|
|
|
bool UseItemAction::UseItem(Item* item, ObjectGuid goGuid, Item* itemTarget, Unit* unitTarget)
|
|
{
|
|
if (bot->CanUseItem(item) != EQUIP_ERR_OK)
|
|
return false;
|
|
|
|
if (bot->IsNonMeleeSpellCast(true))
|
|
return false;
|
|
|
|
uint8 bagIndex = item->GetBagSlot();
|
|
uint8 slot = item->GetSlot();
|
|
uint8 spell_index = 0;
|
|
uint8 cast_count = 1;
|
|
ObjectGuid item_guid = item->GetGUID();
|
|
uint32 glyphIndex = 0;
|
|
uint8 castFlags = 0;
|
|
uint32 targetFlag = TARGET_FLAG_NONE;
|
|
uint32 spellId = 0;
|
|
for (uint8 i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i)
|
|
{
|
|
if (item->GetTemplate()->Spells[i].SpellId > 0)
|
|
{
|
|
spellId = item->GetTemplate()->Spells[i].SpellId;
|
|
if (!botAI->CanCastSpell(spellId, bot, false, itemTarget, item)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
WorldPacket packet(CMSG_USE_ITEM);
|
|
packet << bagIndex << slot << cast_count << spellId << item_guid << glyphIndex << castFlags;
|
|
|
|
bool targetSelected = false;
|
|
|
|
std::ostringstream out;
|
|
out << "Using " << chat->FormatItem(item->GetTemplate());
|
|
|
|
if (item->GetTemplate()->Stackable > 1)
|
|
{
|
|
uint32 count = item->GetCount();
|
|
if (count > 1)
|
|
out << " (" << count << " available) ";
|
|
else
|
|
out << " (the last one!)";
|
|
}
|
|
|
|
if (goGuid)
|
|
{
|
|
GameObject* go = botAI->GetGameObject(goGuid);
|
|
if (!go || !go->isSpawned())
|
|
return false;
|
|
|
|
targetFlag = TARGET_FLAG_GAMEOBJECT;
|
|
|
|
packet << targetFlag;
|
|
packet << goGuid.WriteAsPacked();
|
|
out << " on " << chat->FormatGameobject(go);
|
|
targetSelected = true;
|
|
}
|
|
|
|
if (itemTarget)
|
|
{
|
|
if (item->GetTemplate()->Class == ITEM_CLASS_GEM)
|
|
{
|
|
bool fit = SocketItem(itemTarget, item) || SocketItem(itemTarget, item, true);
|
|
if (!fit)
|
|
botAI->TellMaster("Socket does not fit");
|
|
|
|
return fit;
|
|
}
|
|
else
|
|
{
|
|
targetFlag = TARGET_FLAG_ITEM;
|
|
packet << targetFlag;
|
|
packet << itemTarget->GetGUID().WriteAsPacked();
|
|
out << " on " << chat->FormatItem(itemTarget->GetTemplate());
|
|
targetSelected = true;
|
|
}
|
|
}
|
|
|
|
Player* master = GetMaster();
|
|
if (!targetSelected && item->GetTemplate()->Class != ITEM_CLASS_CONSUMABLE && master && botAI->HasActivePlayerMaster() && !selfOnly)
|
|
{
|
|
if (ObjectGuid masterSelection = master->GetTarget())
|
|
{
|
|
Unit* unit = botAI->GetUnit(masterSelection);
|
|
if (unit)
|
|
{
|
|
targetFlag = TARGET_FLAG_UNIT;
|
|
packet << targetFlag << masterSelection.WriteAsPacked();
|
|
out << " on " << unit->GetName();
|
|
targetSelected = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!targetSelected && item->GetTemplate()->Class != ITEM_CLASS_CONSUMABLE && unitTarget)
|
|
{
|
|
targetFlag = TARGET_FLAG_UNIT;
|
|
packet << targetFlag << unitTarget->GetGUID().WriteAsPacked();
|
|
out << " on " << unitTarget->GetName();
|
|
targetSelected = true;
|
|
}
|
|
|
|
|
|
if (uint32 questid = item->GetTemplate()->StartQuest)
|
|
{
|
|
if (Quest const* qInfo = sObjectMgr->GetQuestTemplate(questid))
|
|
{
|
|
WorldPacket packet(CMSG_QUESTGIVER_ACCEPT_QUEST, 8 + 4 + 4);
|
|
packet << item_guid;
|
|
packet << questid;
|
|
packet << uint32(0);
|
|
bot->GetSession()->HandleQuestgiverAcceptQuestOpcode(packet);
|
|
|
|
std::ostringstream out;
|
|
out << "Got quest " << chat->FormatQuest(qInfo);
|
|
botAI->TellMasterNoFacing(out.str());
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bot->ClearUnitState(UNIT_STATE_CHASE);
|
|
bot->ClearUnitState(UNIT_STATE_FOLLOW);
|
|
|
|
if (bot->isMoving())
|
|
{
|
|
bot->StopMoving();
|
|
botAI->SetNextCheckDelay(sPlayerbotAIConfig->globalCoolDown);
|
|
return false;
|
|
}
|
|
|
|
for (uint8 i = 0; i < MAX_ITEM_PROTO_SPELLS; i++)
|
|
{
|
|
uint32 spellId = item->GetTemplate()->Spells[i].SpellId;
|
|
if (!spellId)
|
|
continue;
|
|
|
|
if (!botAI->CanCastSpell(spellId, bot, false))
|
|
continue;
|
|
|
|
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
|
|
if (spellInfo->Targets & TARGET_FLAG_ITEM)
|
|
{
|
|
Item* itemForSpell = AI_VALUE2(Item*, "item for spell", spellId);
|
|
if (!itemForSpell)
|
|
continue;
|
|
|
|
if (itemForSpell->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT))
|
|
continue;
|
|
|
|
if (bot->GetTrader())
|
|
{
|
|
if (selfOnly)
|
|
return false;
|
|
|
|
targetFlag = TARGET_FLAG_TRADE_ITEM;
|
|
packet << targetFlag << (uint8)1 << ObjectGuid((uint64)TRADE_SLOT_NONTRADED).WriteAsPacked();
|
|
targetSelected = true;
|
|
out << " on traded item";
|
|
}
|
|
else
|
|
{
|
|
targetFlag = TARGET_FLAG_ITEM;
|
|
packet << targetFlag;
|
|
packet << itemForSpell->GetGUID().WriteAsPacked();
|
|
targetSelected = true;
|
|
out << " on "<< chat->FormatItem(itemForSpell->GetTemplate());
|
|
}
|
|
uint32 castTime = spellInfo->CalcCastTime();
|
|
botAI->SetNextCheckDelay(castTime + sPlayerbotAIConfig->reactDelay);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if (!targetSelected)
|
|
{
|
|
targetFlag = TARGET_FLAG_NONE;
|
|
packet << targetFlag;
|
|
packet << bot->GetPackGUID();
|
|
targetSelected = true;
|
|
out << " on self";
|
|
}
|
|
|
|
ItemTemplate const* proto = item->GetTemplate();
|
|
bool isDrink = proto->Spells[0].SpellCategory == 59;
|
|
bool isFood = proto->Spells[0].SpellCategory == 11;
|
|
if (proto->Class == ITEM_CLASS_CONSUMABLE && (proto->SubClass == ITEM_SUBCLASS_FOOD || proto->SubClass == ITEM_SUBCLASS_CONSUMABLE) && (isFood || isDrink))
|
|
{
|
|
if (bot->IsInCombat())
|
|
return false;
|
|
|
|
// bot->SetStandState(UNIT_STAND_STATE_SIT);
|
|
botAI->InterruptSpell();
|
|
float hp = bot->GetHealthPct();
|
|
float mp = bot->GetPower(POWER_MANA) * 100.0f / bot->GetMaxPower(POWER_MANA);
|
|
float p = 0.f;
|
|
if (isDrink && isFood)
|
|
{
|
|
p = std::min(hp, mp);
|
|
TellConsumableUse(item, "Feasting", p);
|
|
}
|
|
else if (isDrink)
|
|
{
|
|
p = mp;
|
|
TellConsumableUse(item, "Drinking", p);
|
|
}
|
|
else if (isFood)
|
|
{
|
|
p = std::min(hp, mp);
|
|
TellConsumableUse(item, "Eating", p);
|
|
}
|
|
|
|
if (!bot->IsInCombat() && !bot->InBattleground())
|
|
botAI->SetNextCheckDelay(std::max(10000.0f, 27000.0f * (100 - p) / 100.0f));
|
|
|
|
if (!bot->IsInCombat() && bot->InBattleground())
|
|
botAI->SetNextCheckDelay(std::max(10000.0f,20000.0f * (100 - p) / 100.0f));
|
|
|
|
//botAI->SetNextCheckDelay(27000.0f * (100 - p) / 100.0f);
|
|
// botAI->SetNextCheckDelay(20000);
|
|
bot->GetSession()->HandleUseItemOpcode(packet);
|
|
|
|
return true;
|
|
}
|
|
|
|
if (!spellId)
|
|
return false;
|
|
|
|
// botAI->SetNextCheckDelay(sPlayerbotAIConfig->globalCoolDown);
|
|
botAI->TellMasterNoFacing(out.str());
|
|
bot->GetSession()->HandleUseItemOpcode(packet);
|
|
return true;
|
|
}
|
|
|
|
void UseItemAction::TellConsumableUse(Item* item, std::string const action, float percent)
|
|
{
|
|
std::ostringstream out;
|
|
out << action << " " << chat->FormatItem(item->GetTemplate());
|
|
|
|
if (item->GetTemplate()->Stackable > 1)
|
|
out << "/x" << item->GetCount();
|
|
|
|
out << " (" << round(percent) << "%)";
|
|
botAI->TellMasterNoFacing(out.str());
|
|
}
|
|
|
|
bool UseItemAction::SocketItem(Item* item, Item* gem, bool replace)
|
|
{
|
|
WorldPacket* const packet = new WorldPacket(CMSG_SOCKET_GEMS);
|
|
*packet << item->GetGUID();
|
|
|
|
bool fits = false;
|
|
for (uint32 enchant_slot = SOCK_ENCHANTMENT_SLOT; enchant_slot < SOCK_ENCHANTMENT_SLOT + MAX_GEM_SOCKETS; ++enchant_slot)
|
|
{
|
|
uint8 SocketColor = item->GetTemplate()->Socket[enchant_slot - SOCK_ENCHANTMENT_SLOT].Color;
|
|
GemPropertiesEntry const* gemProperty = sGemPropertiesStore.LookupEntry(gem->GetTemplate()->GemProperties);
|
|
if (gemProperty && (gemProperty->color & SocketColor))
|
|
{
|
|
if (fits)
|
|
{
|
|
*packet << ObjectGuid::Empty;
|
|
continue;
|
|
}
|
|
|
|
uint32 enchant_id = item->GetEnchantmentId(EnchantmentSlot(enchant_slot));
|
|
if (!enchant_id)
|
|
{
|
|
*packet << gem->GetGUID();
|
|
fits = true;
|
|
continue;
|
|
}
|
|
|
|
SpellItemEnchantmentEntry const* enchantEntry = sSpellItemEnchantmentStore.LookupEntry(enchant_id);
|
|
if (!enchantEntry || !enchantEntry->GemID)
|
|
{
|
|
*packet << gem->GetGUID();
|
|
fits = true;
|
|
continue;
|
|
}
|
|
|
|
if (replace && enchantEntry->GemID != gem->GetTemplate()->ItemId)
|
|
{
|
|
*packet << gem->GetGUID();
|
|
fits = true;
|
|
continue;
|
|
}
|
|
|
|
}
|
|
|
|
*packet << ObjectGuid::Empty;
|
|
}
|
|
|
|
if (fits)
|
|
{
|
|
std::ostringstream out;
|
|
out << "Socketing " << chat->FormatItem(item->GetTemplate());
|
|
out << " with " << chat->FormatItem(gem->GetTemplate());
|
|
botAI->TellMaster(out);
|
|
|
|
bot->GetSession()->HandleSocketOpcode(*packet);
|
|
}
|
|
|
|
return fits;
|
|
}
|
|
|
|
bool UseItemAction::isPossible()
|
|
{
|
|
return getName() == "use" || AI_VALUE2(uint32, "item count", getName()) > 0;
|
|
}
|
|
|
|
bool UseSpellItemAction::isUseful()
|
|
{
|
|
return AI_VALUE2(bool, "spell cast useful", getName());
|
|
}
|
|
|
|
bool UseHealingPotion::isUseful()
|
|
{
|
|
return AI_VALUE2(bool, "combat", "self target");
|
|
}
|
|
|
|
bool UseManaPotion::isUseful()
|
|
{
|
|
return AI_VALUE2(bool, "combat", "self target");
|
|
}
|
|
|
|
bool UseHearthStone::Execute(Event event)
|
|
{
|
|
if (bot->isMoving())
|
|
{
|
|
MotionMaster& mm = *bot->GetMotionMaster();
|
|
bot->StopMoving();
|
|
mm.Clear();
|
|
}
|
|
|
|
bool used = UseItemAction::Execute(event);
|
|
if (used)
|
|
{
|
|
RESET_AI_VALUE(bool, "combat::self target");
|
|
RESET_AI_VALUE(WorldPosition, "current position");
|
|
botAI->SetNextCheckDelay(10 * IN_MILLISECONDS);
|
|
}
|
|
|
|
return used;
|
|
}
|
|
|
|
bool UseHearthStone::isUseful()
|
|
{
|
|
return !bot->InBattleground();
|
|
}
|
|
|
|
bool UseRandomRecipe::isUseful()
|
|
{
|
|
return !bot->IsInCombat() && !botAI->HasActivePlayerMaster() && !bot->InBattleground();
|
|
}
|
|
|
|
bool UseRandomRecipe::isPossible()
|
|
{
|
|
return AI_VALUE2(uint32, "item count", "recipe") > 0;
|
|
}
|
|
|
|
bool UseRandomRecipe::Execute(Event event)
|
|
{
|
|
std::vector<Item*> recipes = AI_VALUE2(std::vector<Item*>, "inventory items", "recipe");
|
|
|
|
std::string recipeName = "";
|
|
|
|
for (auto& recipe : recipes)
|
|
{
|
|
recipeName = recipe->GetTemplate()->Name1;
|
|
}
|
|
|
|
if (recipeName.empty())
|
|
return false;
|
|
|
|
bool used = UseItemAction::Execute(Event(name,recipeName));
|
|
|
|
if (used)
|
|
botAI->SetNextCheckDelay(3.0 * IN_MILLISECONDS);
|
|
|
|
return used;
|
|
}
|
|
|
|
bool UseRandomQuestItem::isUseful()
|
|
{
|
|
return !botAI->HasActivePlayerMaster() && !bot->InBattleground() && !bot->HasUnitState(UNIT_STATE_IN_FLIGHT);
|
|
}
|
|
|
|
bool UseRandomQuestItem::isPossible()
|
|
{
|
|
return AI_VALUE2(uint32, "item count", "quest") > 0;
|
|
}
|
|
|
|
bool UseRandomQuestItem::Execute(Event event)
|
|
{
|
|
Unit* unitTarget = nullptr;
|
|
ObjectGuid goTarget;
|
|
|
|
std::vector<Item*> questItems = AI_VALUE2(std::vector<Item*>, "inventory items", "quest");
|
|
if (questItems.empty())
|
|
return false;
|
|
|
|
Item* item = nullptr;
|
|
for (uint8 i = 0; i < 5; i++)
|
|
{
|
|
auto itr = questItems.begin();
|
|
std::advance(itr, urand(0, questItems.size() - 1));
|
|
Item* questItem = *itr;
|
|
|
|
ItemTemplate const* proto = questItem->GetTemplate();
|
|
if (proto->StartQuest)
|
|
{
|
|
Quest const* qInfo = sObjectMgr->GetQuestTemplate(proto->StartQuest);
|
|
if (bot->CanTakeQuest(qInfo, false))
|
|
{
|
|
item = questItem;
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
if (!item)
|
|
return false;
|
|
|
|
bool used = UseItem(item, goTarget, nullptr, unitTarget);
|
|
if (used)
|
|
botAI->SetNextCheckDelay(sPlayerbotAIConfig->globalCoolDown);
|
|
|
|
return used;
|
|
}
|