diff --git a/src/game/Object/SpellMgr.h b/src/game/Object/SpellMgr.h index ad4fb6090..748e72988 100644 --- a/src/game/Object/SpellMgr.h +++ b/src/game/Object/SpellMgr.h @@ -47,7 +47,9 @@ struct SpellModifier; enum SpellCategories { SPELLCATEGORY_HEALTH_MANA_POTIONS = 4, + SPELLCATEGORY_FOOD = 11, SPELLCATEGORY_DEVOUR_MAGIC = 12, + SPELLCATEGORY_DRINK = 59 }; /** diff --git a/src/modules/Bots/playerbot/PlayerbotAI.cpp b/src/modules/Bots/playerbot/PlayerbotAI.cpp index fb7cf20ba..6e8695767 100644 --- a/src/modules/Bots/playerbot/PlayerbotAI.cpp +++ b/src/modules/Bots/playerbot/PlayerbotAI.cpp @@ -77,7 +77,8 @@ void PacketHandlingHelper::AddPacket(const WorldPacket& packet) * Default constructor for PlayerbotAI. */ PlayerbotAI::PlayerbotAI() : PlayerbotAIBase(), bot(NULL), aiObjectContext(NULL), - currentEngine(NULL), chatHelper(this), chatFilter(this), accountId(0), security(NULL), master(NULL), currentState(BOT_STATE_NON_COMBAT) + currentEngine(NULL), chatHelper(this), chatFilter(this), accountId(0), security(NULL), master(NULL), currentState(BOT_STATE_NON_COMBAT), + m_eatingUntil(0), m_drinkingUntil(0) { for (int i = 0 ; i < BOT_STATE_MAX; i++) { @@ -90,7 +91,8 @@ PlayerbotAI::PlayerbotAI() : PlayerbotAIBase(), bot(NULL), aiObjectContext(NULL) * @param bot The player bot. */ PlayerbotAI::PlayerbotAI(Player* bot) : - PlayerbotAIBase(), chatHelper(this), chatFilter(this), security(bot), master(NULL) + PlayerbotAIBase(), chatHelper(this), chatFilter(this), security(bot), master(NULL), + m_eatingUntil(0), m_drinkingUntil(0) { this->bot = bot; @@ -184,6 +186,15 @@ void PlayerbotAI::UpdateAI(uint32 elapsed) nextAICheckDelay = sPlayerbotAIConfig.maxWaitForMove; } + if (m_drinkingUntil || m_eatingUntil) + { + if (bot->IsInCombat() || !bot->IsSitState()) + { + m_drinkingUntil = 0; + m_eatingUntil = 0; + } + } + PlayerbotAIBase::UpdateAI(elapsed); } @@ -404,6 +415,9 @@ void PlayerbotAI::HandleBotOutgoingPacket(const WorldPacket& packet) */ void PlayerbotAI::SpellInterrupted(uint32 spellid) { + if (!spellid) + return; + LastSpellCast& lastSpell = aiObjectContext->GetValue("last spell cast")->Get(); if (lastSpell.id != spellid) { diff --git a/src/modules/Bots/playerbot/PlayerbotAI.h b/src/modules/Bots/playerbot/PlayerbotAI.h index 1ecf58f14..011b99b35 100644 --- a/src/modules/Bots/playerbot/PlayerbotAI.h +++ b/src/modules/Bots/playerbot/PlayerbotAI.h @@ -179,6 +179,22 @@ class PlayerbotAI : public PlayerbotAIBase static bool IsOpposing(uint8 race1, uint8 race2); PlayerbotSecurity* GetSecurity() { return &security; } + bool IsEating() const + { + return m_eatingUntil && time(0) <= m_eatingUntil + && bot->GetHealth() < bot->GetMaxHealth(); + } + bool IsDrinking() const + { + if (!m_drinkingUntil) return false; + time_t now = time(0); + uint32 mana = bot->GetPower(POWER_MANA); + uint32 maxMana = bot->GetMaxPower(POWER_MANA); + return now <= m_drinkingUntil && mana < maxMana; + } + void SetEating() { m_eatingUntil = time(0) + 30; } + void SetDrinking() { m_drinkingUntil = time(0) + 30; } + protected: Player* bot; Player* master; @@ -194,5 +210,7 @@ class PlayerbotAI : public PlayerbotAIBase PacketHandlingHelper masterOutgoingPacketHandlers; CompositeChatFilter chatFilter; PlayerbotSecurity security; + time_t m_eatingUntil; + time_t m_drinkingUntil; }; diff --git a/src/modules/Bots/playerbot/PlayerbotAIConfig.cpp b/src/modules/Bots/playerbot/PlayerbotAIConfig.cpp index a83db32de..83c86906b 100644 --- a/src/modules/Bots/playerbot/PlayerbotAIConfig.cpp +++ b/src/modules/Bots/playerbot/PlayerbotAIConfig.cpp @@ -36,8 +36,10 @@ PlayerbotAIConfig::PlayerbotAIConfig() lowHealth(0), mediumHealth(0), almostFullHealth(0), + hungryHealth(0), lowMana(0), mediumMana(0), + thirstyMana(0), randomBotAutologin(false), randomBotTeleportDistance(0), randomGearLoweringChance(0.0f), @@ -142,8 +144,10 @@ bool PlayerbotAIConfig::Initialize() lowHealth = config.GetIntDefault("AiPlayerbot.LowHealth", 50); mediumHealth = config.GetIntDefault("AiPlayerbot.MediumHealth", 70); almostFullHealth = config.GetIntDefault("AiPlayerbot.AlmostFullHealth", 85); + hungryHealth = config.GetIntDefault("AiPlayerbot.HungryHealth", 65); lowMana = config.GetIntDefault("AiPlayerbot.LowMana", 15); mediumMana = config.GetIntDefault("AiPlayerbot.MediumMana", 40); + thirstyMana = config.GetIntDefault("AiPlayerbot.ThirstyMana", 65); randomGearLoweringChance = config.GetFloatDefault("AiPlayerbot.RandomGearLoweringChance", 0.15f); randomBotMaxLevelChance = config.GetFloatDefault("AiPlayerbot.RandomBotMaxLevelChance", 0.4f); diff --git a/src/modules/Bots/playerbot/PlayerbotAIConfig.h b/src/modules/Bots/playerbot/PlayerbotAIConfig.h index b137032b6..0399aca18 100644 --- a/src/modules/Bots/playerbot/PlayerbotAIConfig.h +++ b/src/modules/Bots/playerbot/PlayerbotAIConfig.h @@ -46,8 +46,8 @@ class PlayerbotAIConfig float sightDistance, spellDistance, reactDistance, grindDistance, lootDistance, fleeDistance, tooCloseDistance, meleeDistance, followDistance, whisperDistance, contactDistance; bool whisperToZoneOnly; - uint32 criticalHealth, lowHealth, mediumHealth, almostFullHealth; - uint32 lowMana, mediumMana; + uint32 criticalHealth, lowHealth, mediumHealth, almostFullHealth, hungryHealth; + uint32 lowMana, mediumMana, thirstyMana; bool randomBotAutologin; ///< Indicates if random bots should auto-login. std::string randomBotMapsAsString; ///< Comma-separated string of random bot maps. diff --git a/src/modules/Bots/playerbot/aiplayerbot.conf.dist.in b/src/modules/Bots/playerbot/aiplayerbot.conf.dist.in index b3f591e7c..76b134c68 100644 --- a/src/modules/Bots/playerbot/aiplayerbot.conf.dist.in +++ b/src/modules/Bots/playerbot/aiplayerbot.conf.dist.in @@ -93,8 +93,10 @@ AiPlayerbot.CommandPrefix = ~ #AiPlayerbot.LowHealth = 45 #AiPlayerbot.MediumHealth = 65 #AiPlayerbot.AlmostFullHealth = 85 +#AiPlayerbot.HungryHealth = 65 #AiPlayerbot.LowMana = 15 #AiPlayerbot.MediumMana = 40 +#AiPlayerbot.ThirstyMana = 65 # Enable random bot system #AiPlayerbot.RandomBotAutologin = 1 diff --git a/src/modules/Bots/playerbot/strategy/ItemVisitors.h b/src/modules/Bots/playerbot/strategy/ItemVisitors.h index 8fe03d8ca..14d01dcbe 100644 --- a/src/modules/Bots/playerbot/strategy/ItemVisitors.h +++ b/src/modules/Bots/playerbot/strategy/ItemVisitors.h @@ -279,4 +279,38 @@ namespace ai public: map count; }; + + class FindFoodVisitor : public FindUsableItemVisitor + { + public: + FindFoodVisitor(Player* bot, uint32 spellCategory) : FindUsableItemVisitor(bot) + { + this->spellCategory = spellCategory; + } + + virtual bool Accept(const ItemPrototype* proto) + { + return proto->Class == ITEM_CLASS_CONSUMABLE && + proto->Spells[0].SpellCategory == spellCategory; + } + private: + uint32 spellCategory; + }; + + class FindConjuredFoodVisitor : public FindUsableItemVisitor + { + public: + FindConjuredFoodVisitor(Player* bot, uint32 spellCategory) : FindUsableItemVisitor(bot) + { + this->spellCategory = spellCategory; + } + + virtual bool Accept(const ItemPrototype* proto) + { + return proto->IsConjuredConsumable() && + proto->Spells[0].SpellCategory == spellCategory; + } + private: + uint32 spellCategory; + }; } diff --git a/src/modules/Bots/playerbot/strategy/actions/InventoryAction.cpp b/src/modules/Bots/playerbot/strategy/actions/InventoryAction.cpp index 4e9fe24fa..829c48f06 100644 --- a/src/modules/Bots/playerbot/strategy/actions/InventoryAction.cpp +++ b/src/modules/Bots/playerbot/strategy/actions/InventoryAction.cpp @@ -42,25 +42,44 @@ class FindPotionVisitor : public FindUsableItemVisitor uint32 effectId; }; -class FindFoodVisitor : public FindUsableItemVisitor +class FindManaGemVisitor : public FindUsableItemVisitor { public: - FindFoodVisitor(Player* bot, uint32 spellCategory) : FindUsableItemVisitor(bot) - { - this->spellCategory = spellCategory; - } + FindManaGemVisitor(Player* bot) : FindUsableItemVisitor(bot) {} virtual bool Accept(const ItemPrototype* proto) { - return proto->Class == ITEM_CLASS_CONSUMABLE && - proto->SubClass == ITEM_SUBCLASS_FOOD && - proto->Spells[0].SpellCategory == spellCategory; - } + if (proto->Class == ITEM_CLASS_CONSUMABLE && + (proto->Flags & ITEM_FLAG_CONJURED) && + proto->SubClass != ITEM_SUBCLASS_POTION) + { + for (int j = 0; j < MAX_ITEM_PROTO_SPELLS; j++) + { + if (!proto->Spells[j].SpellId) + { + continue; + } -private: - uint32 spellCategory; + 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_ENERGIZE) + { + return true; + } + } + } + } + return false; + } }; + void InventoryAction::IterateItems(IterateItemsVisitor* visitor, IterateItemsMask mask) { if (mask & ITERATE_ITEMS_IN_BAGS) @@ -148,6 +167,28 @@ bool compare_items_by_level(const Item* item1, const Item* item2) return compare_items(item1->GetProto(), item2->GetProto()); } + +Item* InventoryAction::FindPlayerItem(Player *bot, FindItemVisitor *visitor) +{ + for (int i = INVENTORY_SLOT_ITEM_START; i < INVENTORY_SLOT_ITEM_END; ++i) + { + if (Item* pItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + visitor->Visit(pItem); + } + for (int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i) + { + if (Bag* pBag = (Bag*)bot->GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + { + for (uint32 j = 0; j < pBag->GetBagSize(); ++j) + { + if (Item* pItem = pBag->GetItemByPos(j)) + visitor->Visit(pItem); + } + } + } + return visitor->GetResult().empty() ? NULL : visitor->GetResult().front(); +} + void InventoryAction::TellItems(map itemMap) { list items; @@ -232,14 +273,28 @@ list InventoryAction::parseItems(string text) if (text == "food") { - FindFoodVisitor visitor(bot, 11); + FindFoodVisitor visitor(bot, SPELLCATEGORY_FOOD); IterateItems(&visitor, ITERATE_ITEMS_IN_BAGS); found.insert(visitor.GetResult().begin(), visitor.GetResult().end()); } if (text == "drink") { - FindFoodVisitor visitor(bot, 59); + FindFoodVisitor visitor(bot, SPELLCATEGORY_DRINK); + IterateItems(&visitor, ITERATE_ITEMS_IN_BAGS); + found.insert(visitor.GetResult().begin(), visitor.GetResult().end()); + } + + if (text == "conjured food") + { + FindConjuredFoodVisitor visitor(bot, SPELLCATEGORY_FOOD); + IterateItems(&visitor, ITERATE_ITEMS_IN_BAGS); + found.insert(visitor.GetResult().begin(), visitor.GetResult().end()); + } + + if (text == "conjured drink") + { + FindConjuredFoodVisitor visitor(bot, SPELLCATEGORY_DRINK); IterateItems(&visitor, ITERATE_ITEMS_IN_BAGS); found.insert(visitor.GetResult().begin(), visitor.GetResult().end()); } @@ -258,6 +313,13 @@ list InventoryAction::parseItems(string text) found.insert(visitor.GetResult().begin(), visitor.GetResult().end()); } + if (text == "mana gem") + { + FindManaGemVisitor visitor(bot); + IterateItems(&visitor, ITERATE_ITEMS_IN_BAGS); + found.insert(visitor.GetResult().begin(), visitor.GetResult().end()); + } + FindUsableNamedItemVisitor visitor(bot, text); IterateItems(&visitor, ITERATE_ITEMS_IN_BAGS); found.insert(visitor.GetResult().begin(), visitor.GetResult().end()); diff --git a/src/modules/Bots/playerbot/strategy/actions/InventoryAction.h b/src/modules/Bots/playerbot/strategy/actions/InventoryAction.h index ab90bf6f9..aea0219dd 100644 --- a/src/modules/Bots/playerbot/strategy/actions/InventoryAction.h +++ b/src/modules/Bots/playerbot/strategy/actions/InventoryAction.h @@ -10,6 +10,7 @@ namespace ai class InventoryAction : public Action { public: InventoryAction(PlayerbotAI* ai, string name) : Action(ai, name) {} + static Item* FindPlayerItem(Player* player, FindItemVisitor* visitor); protected: void IterateItems(IterateItemsVisitor* visitor, IterateItemsMask mask = ITERATE_ITEMS_IN_BAGS); diff --git a/src/modules/Bots/playerbot/strategy/actions/NonCombatActions.cpp b/src/modules/Bots/playerbot/strategy/actions/NonCombatActions.cpp index 48be5c3be..f2de889c8 100644 --- a/src/modules/Bots/playerbot/strategy/actions/NonCombatActions.cpp +++ b/src/modules/Bots/playerbot/strategy/actions/NonCombatActions.cpp @@ -1,6 +1,5 @@ #include "botpch.h" -//#include "../../playerbot.h" -//#include "NonCombatActions.h" +#include "../../playerbot.h" +#include "NonCombatActions.h" using namespace ai; - diff --git a/src/modules/Bots/playerbot/strategy/actions/NonCombatActions.h b/src/modules/Bots/playerbot/strategy/actions/NonCombatActions.h index ee17deb2c..c75b77c62 100644 --- a/src/modules/Bots/playerbot/strategy/actions/NonCombatActions.h +++ b/src/modules/Bots/playerbot/strategy/actions/NonCombatActions.h @@ -18,12 +18,25 @@ namespace ai return false; } - return UseItemAction::Execute(event); + if (ai->IsDrinking()) + return true; + + bool result = UseItemAction::Execute(event); + if (result) + ai->SetDrinking(); + return result; + } + + virtual bool isPossible() + { + return ai->IsDrinking() || UseItemAction::isPossible(); } virtual bool isUseful() { - return UseItemAction::isUseful() && AI_VALUE2(uint8, "mana", "self target") < sPlayerbotAIConfig.lowMana; + if (ai->IsDrinking()) + return true; + return UseItemAction::isUseful() && AI_VALUE2(uint8, "mana", "self target") < sPlayerbotAIConfig.thirstyMana; } }; @@ -39,12 +52,25 @@ namespace ai return false; } - return UseItemAction::Execute(event); + if (ai->IsEating()) + return true; + + bool result = UseItemAction::Execute(event); + if (result) + ai->SetEating(); + return result; + } + + virtual bool isPossible() + { + return ai->IsEating() || UseItemAction::isPossible(); } virtual bool isUseful() { - return UseItemAction::isUseful() && AI_VALUE2(uint8, "health", "self target") < sPlayerbotAIConfig.lowHealth; + if (ai->IsEating()) + return true; + return UseItemAction::isUseful() && AI_VALUE2(uint8, "health", "self target") < sPlayerbotAIConfig.hungryHealth; } }; diff --git a/src/modules/Bots/playerbot/strategy/actions/TradeStatusAction.cpp b/src/modules/Bots/playerbot/strategy/actions/TradeStatusAction.cpp index b702902bc..3d2d08fb3 100644 --- a/src/modules/Bots/playerbot/strategy/actions/TradeStatusAction.cpp +++ b/src/modules/Bots/playerbot/strategy/actions/TradeStatusAction.cpp @@ -1,6 +1,7 @@ #include "botpch.h" #include "../../playerbot.h" #include "TradeStatusAction.h" +#include "UseItemAction.h" #include "../ItemVisitors.h" #include "../../PlayerbotAIConfig.h" diff --git a/src/modules/Bots/playerbot/strategy/actions/UseItemAction.cpp b/src/modules/Bots/playerbot/strategy/actions/UseItemAction.cpp index 11c909f99..6afd725f0 100644 --- a/src/modules/Bots/playerbot/strategy/actions/UseItemAction.cpp +++ b/src/modules/Bots/playerbot/strategy/actions/UseItemAction.cpp @@ -242,17 +242,6 @@ bool UseItemAction::UseItem(Item* item, ObjectGuid goGuid, Item* itemTarget) return false; } - if (item->GetProto()->Class == ITEM_CLASS_CONSUMABLE && item->GetProto()->SubClass == ITEM_SUBCLASS_FOOD) - { - if (bot->IsInCombat()) - { - return false; - } - - ai->InterruptSpell(); - ai->SetNextCheckDelay(30000); - } - ai->TellMasterNoFacing(out.str()); bot->GetSession()->QueuePacket(packet); return true; diff --git a/src/modules/Bots/playerbot/strategy/actions/UseItemAction.h b/src/modules/Bots/playerbot/strategy/actions/UseItemAction.h index 5afb80b31..929f28c1d 100644 --- a/src/modules/Bots/playerbot/strategy/actions/UseItemAction.h +++ b/src/modules/Bots/playerbot/strategy/actions/UseItemAction.h @@ -12,8 +12,10 @@ namespace ai virtual bool Execute(Event event); virtual bool isPossible(); - private: + protected: bool UseItemAuto(Item* item); + + private: bool UseItemOnGameObject(Item* item, ObjectGuid go); bool UseItemOnItem(Item* item, Item* itemTarget); bool UseItem(Item* item, ObjectGuid go, Item* itemTarget); @@ -43,5 +45,12 @@ namespace ai public: UseManaPotion(PlayerbotAI* ai) : UseItemAction(ai, "mana potion") {} virtual bool isUseful() { return AI_VALUE2(bool, "combat", "self target"); } + virtual bool Execute(Event event) + { + list gems = AI_VALUE2(list, "inventory items", "mana gem"); + if (!gems.empty()) + return UseItemAuto(*gems.begin()); + return UseItemAction::Execute(event); + } }; } diff --git a/src/modules/Bots/playerbot/strategy/generic/UseFoodStrategy.cpp b/src/modules/Bots/playerbot/strategy/generic/UseFoodStrategy.cpp index 081ed1c6e..5e80c3e92 100644 --- a/src/modules/Bots/playerbot/strategy/generic/UseFoodStrategy.cpp +++ b/src/modules/Bots/playerbot/strategy/generic/UseFoodStrategy.cpp @@ -9,10 +9,10 @@ void UseFoodStrategy::InitTriggers(std::list &triggers) Strategy::InitTriggers(triggers); triggers.push_back(new TriggerNode( - "critical health", + "hungry", NextAction::array(0, new NextAction("food", 2.0f), NULL))); triggers.push_back(new TriggerNode( - "low mana", + "thirsty", NextAction::array(0, new NextAction("drink", 2.0f), NULL))); } diff --git a/src/modules/Bots/playerbot/strategy/mage/GenericMageNonCombatStrategy.cpp b/src/modules/Bots/playerbot/strategy/mage/GenericMageNonCombatStrategy.cpp index cec62094b..d4ef4d1ac 100644 --- a/src/modules/Bots/playerbot/strategy/mage/GenericMageNonCombatStrategy.cpp +++ b/src/modules/Bots/playerbot/strategy/mage/GenericMageNonCombatStrategy.cpp @@ -63,6 +63,10 @@ void GenericMageNonCombatStrategy::InitTriggers(std::list &trigger "no food", NextAction::array(0, new NextAction("conjure food", 15.0f), NULL))); + triggers.push_back(new TriggerNode( + "no mana gem", + NextAction::array(0, new NextAction("conjure mana gem", 14.0f), NULL))); + triggers.push_back(new TriggerNode( "remove curse", NextAction::array(0, new NextAction("remove curse", 41.0f), NULL))); @@ -70,6 +74,14 @@ void GenericMageNonCombatStrategy::InitTriggers(std::list &trigger triggers.push_back(new TriggerNode( "remove curse on party", NextAction::array(0, new NextAction("remove curse on party", 40.0f), NULL))); + + triggers.push_back(new TriggerNode( + "party member needs food", + NextAction::array(0, new NextAction("give conjured food", 13.0f), NULL))); + + triggers.push_back(new TriggerNode( + "party member needs water", + NextAction::array(0, new NextAction("give conjured water", 13.0f), NULL))); } void MageBuffManaStrategy::InitTriggers(std::list &triggers) diff --git a/src/modules/Bots/playerbot/strategy/mage/GenericMageStrategy.cpp b/src/modules/Bots/playerbot/strategy/mage/GenericMageStrategy.cpp index a115b6e9f..60a4391bd 100644 --- a/src/modules/Bots/playerbot/strategy/mage/GenericMageStrategy.cpp +++ b/src/modules/Bots/playerbot/strategy/mage/GenericMageStrategy.cpp @@ -133,5 +133,6 @@ void GenericMageStrategy::InitTriggers(std::list &triggers) triggers.push_back(new TriggerNode( "low mana", - NextAction::array(0, new NextAction("evocation", ACTION_EMERGENCY + 5), NULL))); + NextAction::array(0, new NextAction("mana potion", ACTION_EMERGENCY + 6), + new NextAction("evocation", ACTION_EMERGENCY + 5), NULL))); } diff --git a/src/modules/Bots/playerbot/strategy/mage/MageActions.cpp b/src/modules/Bots/playerbot/strategy/mage/MageActions.cpp index 438802219..fd917d9de 100644 --- a/src/modules/Bots/playerbot/strategy/mage/MageActions.cpp +++ b/src/modules/Bots/playerbot/strategy/mage/MageActions.cpp @@ -8,3 +8,210 @@ Value* CastPolymorphAction::GetTargetValue() { return context->GetValue("cc target", getName()); } + +static Player* FindPartyMemberWithoutSustenance(Player* bot, bool food) +{ + Group* group = bot->GetGroup(); + if (!group) + return NULL; + + for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next()) + { + Player* player = gref->getSource(); + if (!player || player == bot || !player->IsAlive()) + { + continue; + } + if (player->GetMapId() != bot->GetMapId()) + { + continue; + } + if (!bot->IsWithinDist(player, sPlayerbotAIConfig.sightDistance)) + { + continue; + } + if (!food && player->GetPowerType() != POWER_MANA) + { + continue; + } + bool hasItem; + if (food) + { + FindConjuredFoodVisitor foodVisitor(player, SPELLCATEGORY_FOOD); + hasItem = InventoryAction::FindPlayerItem(player, &foodVisitor) != NULL; + } + else + { + FindConjuredFoodVisitor drinkVisitor(player, SPELLCATEGORY_DRINK); + hasItem = InventoryAction::FindPlayerItem(player, &drinkVisitor) != NULL; + } + if (!hasItem) + return player; + } + + return NULL; +} + +bool GiveConjuredFoodAction::isUseful() +{ + if (bot->IsInCombat()) + return false; + if (!bot->GetGroup()) + return false; + + return !AI_VALUE2(list, "inventory items", "conjured food").empty(); +} + +bool GiveConjuredFoodAction::Execute(Event event) +{ + list foods = AI_VALUE2(list, "inventory items", "conjured food"); + if (foods.empty()) + { + return false; + } + Item* food = foods.front(); + + Player* target = FindPartyMemberWithoutSustenance(bot, true); + if (!target) + { + return false; + } + + uint32 itemId = food->GetEntry(); + uint32 count = food->GetCount(); + + Item* newItem = target->StoreNewItemInInventorySlot(itemId, count); + if (!newItem) + return false; + + bot->DestroyItem(food->GetBagSlot(), food->GetSlot(), true); + newItem->AddToUpdateQueueOf(target); + + Group* group = bot->GetGroup(); + if (group) + { + ostringstream out; + out << "Have some food, " << target->GetName(); + WorldPacket data; + ChatHandler::BuildChatPacket(data, CHAT_MSG_PARTY, out.str().c_str(), + LANG_UNIVERSAL, bot->GetChatTag(), bot->GetObjectGuid(), bot->GetName()); + group->BroadcastPacket(&data, false); + } + return true; +} + +bool GiveConjuredWaterAction::isUseful() +{ + if (bot->IsInCombat()) + return false; + if (!bot->GetGroup()) + return false; + + return !AI_VALUE2(list, "inventory items", "conjured drink").empty(); +} + +bool GiveConjuredWaterAction::Execute(Event event) +{ + list drinks = AI_VALUE2(list, "inventory items", "conjured drink"); + if (drinks.empty()) + return false; + Item* water = drinks.front(); + + Player* target = FindPartyMemberWithoutSustenance(bot, false); + if (!target) + return false; + + uint32 itemId = water->GetEntry(); + uint32 count = water->GetCount(); + + Item* newItem = target->StoreNewItemInInventorySlot(itemId, count); + if (!newItem) + return false; + + bot->DestroyItem(water->GetBagSlot(), water->GetSlot(), true); + newItem->AddToUpdateQueueOf(target); + + Group* group = bot->GetGroup(); + if (group) + { + ostringstream out; + out << "Have some water, " << target->GetName(); + WorldPacket data; + ChatHandler::BuildChatPacket(data, CHAT_MSG_PARTY, out.str().c_str(), + LANG_UNIVERSAL, bot->GetChatTag(), bot->GetObjectGuid(), bot->GetName()); + group->BroadcastPacket(&data, false); + } + return true; +} + +uint32 CastConjureManaGemAction::FindBestConjureManaSpell() +{ + uint32 bestSpellId = 0; + + 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; + + const SpellEntry* pSpellInfo = sSpellStore.LookupEntry(spellId); + if (!pSpellInfo) + continue; + + for (int i = 0; i < MAX_EFFECT_INDEX; i++) + { + if (pSpellInfo->Effect[i] != SPELL_EFFECT_CREATE_ITEM) + continue; + + uint32 itemId = pSpellInfo->EffectItemType[i]; + if (!itemId) + continue; + + ItemPrototype const* proto = sObjectMgr.GetItemPrototype(itemId); + if (!proto || + !(proto->Flags & ITEM_FLAG_CONJURED) || + proto->Class != ITEM_CLASS_CONSUMABLE || + proto->SubClass == ITEM_SUBCLASS_POTION) + continue; + + bool isManaGem = false; + for (int j = 0; j < MAX_ITEM_PROTO_SPELLS && !isManaGem; j++) + { + const SpellEntry* itemSpell = sSpellStore.LookupEntry(proto->Spells[j].SpellId); + if (!itemSpell) + continue; + for (int k = 0; k < MAX_EFFECT_INDEX; k++) + { + if (itemSpell->Effect[k] == SPELL_EFFECT_ENERGIZE) + { + isManaGem = true; + break; + } + } + } + + if (isManaGem && spellId > bestSpellId) + bestSpellId = spellId; + + break; + } + } + + return bestSpellId; +} + +bool CastConjureManaGemAction::isPossible() +{ + m_bestSpellId = FindBestConjureManaSpell(); + return m_bestSpellId != 0; +} + +bool CastConjureManaGemAction::Execute(Event event) +{ + if (!m_bestSpellId) + return false; + + return ai->CastSpell(m_bestSpellId, GetTarget()); +} + diff --git a/src/modules/Bots/playerbot/strategy/mage/MageActions.h b/src/modules/Bots/playerbot/strategy/mage/MageActions.h index 5282ffa2c..ac10d6585 100644 --- a/src/modules/Bots/playerbot/strategy/mage/MageActions.h +++ b/src/modules/Bots/playerbot/strategy/mage/MageActions.h @@ -1,6 +1,8 @@ #pragma once #include "../actions/GenericActions.h" +#include "../actions/InventoryAction.h" +#include "../actions/UseItemAction.h" namespace ai { @@ -92,18 +94,34 @@ namespace ai CastRemoveCurseOnPartyAction(PlayerbotAI* ai) : CurePartyMemberAction(ai, "remove curse", DISPEL_CURSE) {} }; - // Temp disable conjuration - /*class CastConjureFoodAction : public CastBuffSpellAction + class CastConjureFoodAction : public CastBuffSpellAction { public: CastConjureFoodAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "conjure food") {} + virtual string GetTargetName() { return "self target"; } + virtual bool isUseful() { return AI_VALUE2(list, "inventory items", "conjured food").empty(); } }; class CastConjureWaterAction : public CastBuffSpellAction { public: CastConjureWaterAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "conjure water") {} - };*/ + virtual string GetTargetName() { return "self target"; } + virtual bool isUseful() { return AI_VALUE2(list, "inventory items", "conjured drink").empty(); } + }; + + class CastConjureManaGemAction : public CastSpellAction + { + public: + CastConjureManaGemAction(PlayerbotAI* ai) : CastSpellAction(ai, "conjure mana gem"), m_bestSpellId(0) {} + virtual string GetTargetName() { return "self target"; } + virtual bool isUseful() { return AI_VALUE2(uint8, "item count", "mana gem") == 0; } + virtual bool Execute(Event event); + virtual bool isPossible(); + private: + uint32 FindBestConjureManaSpell(); + uint32 m_bestSpellId; + }; class CastIceBlockAction : public CastBuffSpellAction { @@ -154,4 +172,21 @@ namespace ai public: CastCounterspellOnEnemyHealerAction(PlayerbotAI* ai) : CastSpellOnEnemyHealerAction(ai, "counterspell") {} }; + + + class GiveConjuredFoodAction : public InventoryAction + { + public: + GiveConjuredFoodAction(PlayerbotAI* ai) : InventoryAction(ai, "give conjured food") {} + virtual bool Execute(Event event); + virtual bool isUseful(); + }; + + class GiveConjuredWaterAction : public InventoryAction + { + public: + GiveConjuredWaterAction(PlayerbotAI* ai) : InventoryAction(ai, "give conjured water") {} + virtual bool Execute(Event event); + virtual bool isUseful(); + }; } diff --git a/src/modules/Bots/playerbot/strategy/mage/MageAiObjectContext.cpp b/src/modules/Bots/playerbot/strategy/mage/MageAiObjectContext.cpp index 94dc6f499..1e3d8a703 100644 --- a/src/modules/Bots/playerbot/strategy/mage/MageAiObjectContext.cpp +++ b/src/modules/Bots/playerbot/strategy/mage/MageAiObjectContext.cpp @@ -99,6 +99,9 @@ namespace ai creators["missile barrage"] = &TriggerFactoryInternal::missile_barrage; creators["arcane blast"] = &TriggerFactoryInternal::arcane_blast; creators["counterspell on enemy healer"] = &TriggerFactoryInternal::counterspell_enemy_healer; + creators["no mana gem"] = &TriggerFactoryInternal::no_mana_gem; + creators["party member needs food"] = &TriggerFactoryInternal::party_member_needs_food; + creators["party member needs water"] = &TriggerFactoryInternal::party_member_needs_water; } @@ -120,6 +123,9 @@ namespace ai static Trigger* missile_barrage(PlayerbotAI* ai) { return new MissileBarrageTrigger(ai); } static Trigger* arcane_blast(PlayerbotAI* ai) { return new ArcaneBlastTrigger(ai); } static Trigger* counterspell_enemy_healer(PlayerbotAI* ai) { return new CounterspellEnemyHealerTrigger(ai); } + static Trigger* no_mana_gem(PlayerbotAI* ai) { return new NoManaGemTrigger(ai); } + static Trigger* party_member_needs_food(PlayerbotAI* ai) { return new PartyMemberNeedsFoodTrigger(ai); } + static Trigger* party_member_needs_water(PlayerbotAI* ai) { return new PartyMemberNeedsWaterTrigger(ai); } }; }; }; @@ -141,8 +147,11 @@ namespace ai creators["frost nova"] = &AiObjectContextInternal::frost_nova; creators["arcane intellect"] = &AiObjectContextInternal::arcane_intellect; creators["arcane intellect on party"] = &AiObjectContextInternal::arcane_intellect_on_party; - //creators["conjure water"] = &AiObjectContextInternal::conjure_water; - //creators["conjure food"] = &AiObjectContextInternal::conjure_food; + creators["conjure water"] = &AiObjectContextInternal::conjure_water; + creators["conjure food"] = &AiObjectContextInternal::conjure_food; + creators["conjure mana gem"] = &AiObjectContextInternal::conjure_mana_gem; + creators["give conjured food"] = &AiObjectContextInternal::give_conjured_food; + creators["give conjured water"] = &AiObjectContextInternal::give_conjured_water; creators["mage armor"] = &AiObjectContextInternal::mage_armor; creators["ice armor"] = &AiObjectContextInternal::ice_armor; creators["frost armor"] = &AiObjectContextInternal::frost_armor; @@ -170,8 +179,11 @@ namespace ai static Action* frost_nova(PlayerbotAI* ai) { return new CastFrostNovaAction(ai); } static Action* arcane_intellect(PlayerbotAI* ai) { return new CastArcaneIntellectAction(ai); } static Action* arcane_intellect_on_party(PlayerbotAI* ai) { return new CastArcaneIntellectOnPartyAction(ai); } - //static Action* conjure_water(PlayerbotAI* ai) { return new CastConjureWaterAction(ai); } - //static Action* conjure_food(PlayerbotAI* ai) { return new CastConjureFoodAction(ai); } + static Action* conjure_water(PlayerbotAI* ai) { return new CastConjureWaterAction(ai); } + static Action* conjure_food(PlayerbotAI* ai) { return new CastConjureFoodAction(ai); } + static Action* conjure_mana_gem(PlayerbotAI* ai) { return new CastConjureManaGemAction(ai); } + static Action* give_conjured_food(PlayerbotAI* ai) { return new GiveConjuredFoodAction(ai); } + static Action* give_conjured_water(PlayerbotAI* ai) { return new GiveConjuredWaterAction(ai); } static Action* mage_armor(PlayerbotAI* ai) { return new CastMageArmorAction(ai); } static Action* ice_armor(PlayerbotAI* ai) { return new CastIceArmorAction(ai); } static Action* frost_armor(PlayerbotAI* ai) { return new CastFrostArmorAction(ai); } diff --git a/src/modules/Bots/playerbot/strategy/mage/MageTriggers.cpp b/src/modules/Bots/playerbot/strategy/mage/MageTriggers.cpp index f42c47ec7..322f41d5b 100644 --- a/src/modules/Bots/playerbot/strategy/mage/MageTriggers.cpp +++ b/src/modules/Bots/playerbot/strategy/mage/MageTriggers.cpp @@ -13,3 +13,40 @@ bool MageArmorTrigger::IsActive() !ai->HasAura("molten armor", target) && !ai->HasAura("mage armor", target); } + +bool PartyMemberNeedsSustenanceTrigger::IsActive() +{ + uint32 now = getMSTime(); + uint32 interval = m_lastResult ? ACTIVE_SCAN_MS : IDLE_SCAN_MS; + if (getMSTimeDiff(m_lastScanTime, now) < interval) + return m_lastResult; + + m_lastScanTime = now; + m_lastResult = false; + + Group* group = bot->GetGroup(); + if (!group) + return false; + + for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next()) + { + Player* player = gref->getSource(); + if (!player || player == bot || !player->IsAlive()) + continue; + if (player->GetMapId() != bot->GetMapId()) + continue; + if (!bot->IsWithinDist(player, sPlayerbotAIConfig.sightDistance)) + continue; + if (m_spellCategory == SPELLCATEGORY_DRINK && player->GetPowerType() != POWER_MANA) + continue; + FindConjuredFoodVisitor visitor(player, m_spellCategory); + bool hasItem = InventoryAction::FindPlayerItem(player, &visitor) != 0; + if (!hasItem) + { + m_lastResult = true; + return true; + } + } + + return false; +} diff --git a/src/modules/Bots/playerbot/strategy/mage/MageTriggers.h b/src/modules/Bots/playerbot/strategy/mage/MageTriggers.h index 48bc84651..9c3e020e3 100644 --- a/src/modules/Bots/playerbot/strategy/mage/MageTriggers.h +++ b/src/modules/Bots/playerbot/strategy/mage/MageTriggers.h @@ -89,4 +89,39 @@ namespace ai public: CounterspellEnemyHealerTrigger(PlayerbotAI* ai) : InterruptEnemyHealerTrigger(ai, "counterspell") {} }; + + class NoManaGemTrigger : public ItemCountTrigger + { + public: + NoManaGemTrigger(PlayerbotAI* ai) : ItemCountTrigger(ai, "mana gem", 1) {} + }; + + class PartyMemberNeedsSustenanceTrigger : public Trigger + { + public: + PartyMemberNeedsSustenanceTrigger(PlayerbotAI* ai, string name, uint32 spellCategory) + : Trigger(ai, name, 5), m_spellCategory(spellCategory), + m_lastScanTime(0), m_lastResult(false) {} + virtual bool IsActive(); + private: + static const uint32 IDLE_SCAN_MS = 30000; + static const uint32 ACTIVE_SCAN_MS = 2500; + uint32 m_spellCategory; + uint32 m_lastScanTime; + bool m_lastResult; + }; + + class PartyMemberNeedsFoodTrigger : public PartyMemberNeedsSustenanceTrigger + { + public: + PartyMemberNeedsFoodTrigger(PlayerbotAI* ai) + : PartyMemberNeedsSustenanceTrigger(ai, "party member needs food", SPELLCATEGORY_FOOD) {} + }; + + class PartyMemberNeedsWaterTrigger : public PartyMemberNeedsSustenanceTrigger + { + public: + PartyMemberNeedsWaterTrigger(PlayerbotAI* ai) + : PartyMemberNeedsSustenanceTrigger(ai, "party member needs water", SPELLCATEGORY_DRINK) {} + }; } diff --git a/src/modules/Bots/playerbot/strategy/triggers/GenericTriggers.cpp b/src/modules/Bots/playerbot/strategy/triggers/GenericTriggers.cpp index 815a1b13a..825b85011 100644 --- a/src/modules/Bots/playerbot/strategy/triggers/GenericTriggers.cpp +++ b/src/modules/Bots/playerbot/strategy/triggers/GenericTriggers.cpp @@ -16,6 +16,11 @@ bool MediumManaTrigger::IsActive() return AI_VALUE2(bool, "has mana", "self target") && AI_VALUE2(uint8, "mana", "self target") < sPlayerbotAIConfig.mediumMana; } +bool ThirstyTrigger::IsActive() +{ + return ai->IsDrinking() || + (AI_VALUE2(bool, "has mana", "self target") && AI_VALUE2(uint8, "mana", "self target") < sPlayerbotAIConfig.thirstyMana); +} bool RageAvailable::IsActive() { diff --git a/src/modules/Bots/playerbot/strategy/triggers/GenericTriggers.h b/src/modules/Bots/playerbot/strategy/triggers/GenericTriggers.h index d7a8cc58e..85e6cd515 100644 --- a/src/modules/Bots/playerbot/strategy/triggers/GenericTriggers.h +++ b/src/modules/Bots/playerbot/strategy/triggers/GenericTriggers.h @@ -366,6 +366,14 @@ namespace ai virtual bool IsActive(); }; + class ThirstyTrigger : public Trigger + { + public: + ThirstyTrigger(PlayerbotAI* ai) : Trigger(ai, "thirsty") {} + + virtual bool IsActive(); + }; + BEGIN_TRIGGER(PanicTrigger, Trigger) virtual string getName() { return "panic"; } END_TRIGGER() diff --git a/src/modules/Bots/playerbot/strategy/triggers/HealthTriggers.h b/src/modules/Bots/playerbot/strategy/triggers/HealthTriggers.h index 1e72eba31..145ec569e 100644 --- a/src/modules/Bots/playerbot/strategy/triggers/HealthTriggers.h +++ b/src/modules/Bots/playerbot/strategy/triggers/HealthTriggers.h @@ -60,6 +60,18 @@ namespace ai LowHealthTrigger(ai, "medium health", sPlayerbotAIConfig.mediumHealth, sPlayerbotAIConfig.lowHealth) {} }; + class HungryTrigger : public Trigger + { + public: + HungryTrigger(PlayerbotAI* ai) : Trigger(ai, "hungry") {} + + virtual bool IsActive() + { + return ai->IsEating() || + AI_VALUE2(uint8, "health", "self target") < sPlayerbotAIConfig.hungryHealth; + } + }; + class AlmostFullHealthTrigger : public LowHealthTrigger { public: diff --git a/src/modules/Bots/playerbot/strategy/triggers/TriggerContext.h b/src/modules/Bots/playerbot/strategy/triggers/TriggerContext.h index 9906cae02..7da6ba0d6 100644 --- a/src/modules/Bots/playerbot/strategy/triggers/TriggerContext.h +++ b/src/modules/Bots/playerbot/strategy/triggers/TriggerContext.h @@ -25,9 +25,11 @@ namespace ai creators["low health"] = &TriggerContext::LowHealth; creators["medium health"] = &TriggerContext::MediumHealth; creators["almost full health"] = &TriggerContext::AlmostFullHealth; + creators["hungry"] = &TriggerContext::Hungry; creators["low mana"] = &TriggerContext::LowMana; creators["medium mana"] = &TriggerContext::MediumMana; + creators["thirsty"] = &TriggerContext::Thirsty; creators["party member critical health"] = &TriggerContext::PartyMemberCriticalHealth; creators["party member low health"] = &TriggerContext::PartyMemberLowHealth; @@ -118,9 +120,11 @@ namespace ai static Trigger* MediumHealth(PlayerbotAI* ai) { return new MediumHealthTrigger(ai); } static Trigger* AlmostFullHealth(PlayerbotAI* ai) { return new AlmostFullHealthTrigger(ai); } static Trigger* CriticalHealth(PlayerbotAI* ai) { return new CriticalHealthTrigger(ai); } + static Trigger* Hungry(PlayerbotAI* ai) { return new HungryTrigger(ai); } static Trigger* TargetCriticalHealth(PlayerbotAI* ai) { return new TargetCriticalHealthTrigger(ai); } static Trigger* LowMana(PlayerbotAI* ai) { return new LowManaTrigger(ai); } static Trigger* MediumMana(PlayerbotAI* ai) { return new MediumManaTrigger(ai); } + static Trigger* Thirsty(PlayerbotAI* ai) { return new ThirstyTrigger(ai); } static Trigger* LightRageAvailable(PlayerbotAI* ai) { return new LightRageAvailableTrigger(ai); } static Trigger* MediumRageAvailable(PlayerbotAI* ai) { return new MediumRageAvailableTrigger(ai); } static Trigger* HighRageAvailable(PlayerbotAI* ai) { return new HighRageAvailableTrigger(ai); }