Merge pull request #2359 from mod-playerbots/test-staging

Test staging
This commit is contained in:
Keleborn 2026-05-08 10:36:36 -07:00 committed by GitHub
commit 531282e4be
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
59 changed files with 522 additions and 343 deletions

View File

@ -32,7 +32,7 @@
# LEVELS # LEVELS
# GEAR # GEAR
# QUESTS # QUESTS
# ACTIVITIES # ACTIVITY
# SPELLS # SPELLS
# STRATEGIES # STRATEGIES
# RPG STRATEGY # RPG STRATEGY

View File

@ -343,7 +343,7 @@ bool BGJoinAction::isUseful()
return false; return false;
// check Deserter debuff // check Deserter debuff
if (!bot->CanJoinToBattleground()) if (bot->IsDeserter())
return false; return false;
// check if has free queue slots (pointless as already making sure not in queue) // check if has free queue slots (pointless as already making sure not in queue)

View File

@ -213,13 +213,7 @@ bool BuyAction::Execute(Event event)
} }
} }
if (!vendored) return vendored;
{
botAI->TellError("There are no vendors nearby");
return false;
}
return true;
} }
bool BuyAction::BuyItem(VendorItemData const* tItems, ObjectGuid vendorguid, ItemTemplate const* proto) bool BuyAction::BuyItem(VendorItemData const* tItems, ObjectGuid vendorguid, ItemTemplate const* proto)

View File

@ -296,7 +296,7 @@ bool PetitionTurnInAction::isUseful()
bool BuyTabardAction::Execute(Event /*event*/) bool BuyTabardAction::Execute(Event /*event*/)
{ {
bool canBuy = botAI->DoSpecificAction("buy", Event("buy tabard", "Hitem:5976:")); bool canBuy = botAI->DoSpecificAction("buy", Event("buy tabard", "Hitem:5976:"), true);
if (canBuy && AI_VALUE2(uint32, "item count", chat->FormatQItem(5976))) if (canBuy && AI_VALUE2(uint32, "item count", chat->FormatQItem(5976)))
return true; return true;

View File

@ -0,0 +1,39 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "TellEmblemsAction.h"
#include <array>
#include "Event.h"
#include "Playerbots.h"
bool TellEmblemsAction::Execute(Event /*event*/)
{
static std::array<uint32, 6> const emblemIds = {
29434, // Badge of Justice
40752, // Emblem of Heroism
40753, // Emblem of Valor
45624, // Emblem of Conquest
47241, // Emblem of Triumph
49426 // Emblem of Frost
};
botAI->TellMaster("=== Emblems ===");
for (uint32 itemId : emblemIds)
{
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
if (!proto)
continue;
uint32 count = bot->GetItemCount(itemId, false);
std::ostringstream out;
out << chat->FormatItem(proto, count);
botAI->TellMaster(out);
}
return true;
}

View File

@ -0,0 +1,21 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_TELLEMBLEMSACTION_H
#define _PLAYERBOT_TELLEMBLEMSACTION_H
#include "InventoryAction.h"
class PlayerbotAI;
class TellEmblemsAction : public InventoryAction
{
public:
TellEmblemsAction(PlayerbotAI* botAI) : InventoryAction(botAI, "emblems") {}
bool Execute(Event event) override;
};
#endif

View File

@ -5,34 +5,23 @@
#include "TellReputationAction.h" #include "TellReputationAction.h"
#include <algorithm>
#include "Event.h" #include "Event.h"
#include "PlayerbotAI.h" #include "PlayerbotAI.h"
#include "ReputationMgr.h" #include "ReputationMgr.h"
bool TellReputationAction::Execute(Event /*event*/) #include "SharedDefines.h"
std::string TellReputationAction::BuildReputationLine(FactionEntry const* entry)
{ {
Player* master = GetMaster(); ReputationMgr& repMgr = bot->GetReputationMgr();
if (!master) ReputationRank rank = repMgr.GetRank(entry);
return false; int32 reputation = repMgr.GetReputation(entry->ID);
ObjectGuid selection = master->GetTarget();
if (selection.IsEmpty())
return false;
Unit* unit = ObjectAccessor::GetUnit(*master, selection);
if (!unit)
return false;
FactionTemplateEntry const* factionTemplate = unit->GetFactionTemplateEntry();
uint32 faction = factionTemplate->faction;
FactionEntry const* entry = sFactionStore.LookupEntry(faction);
int32 reputation = bot->GetReputationMgr().GetReputation(faction);
std::ostringstream out; std::ostringstream out;
out << entry->name[0] << ": "; out << entry->name[0] << ": |cff";
out << "|cff";
ReputationRank rank = bot->GetReputationMgr().GetRank(entry);
switch (rank) switch (rank)
{ {
case REP_HATED: case REP_HATED:
@ -71,7 +60,65 @@ bool TellReputationAction::Execute(Event /*event*/)
base -= ReputationMgr::PointsInRank[i]; base -= ReputationMgr::PointsInRank[i];
out << " (" << (reputation - base) << "/" << ReputationMgr::PointsInRank[rank] << ")"; out << " (" << (reputation - base) << "/" << ReputationMgr::PointsInRank[rank] << ")";
botAI->TellMaster(out); return out.str();
}
bool TellReputationAction::Execute(Event event)
{
std::string const param = event.getParam();
if (param == "all")
{
ReputationMgr& repMgr = bot->GetReputationMgr();
std::vector<std::string> lines;
FactionStateList const& stateList = repMgr.GetStateList();
lines.reserve(stateList.size());
for (auto const& itr : stateList)
{
FactionState const& faction = itr.second;
if (!(faction.Flags & FACTION_FLAG_VISIBLE))
continue;
if (faction.Flags & (FACTION_FLAG_HIDDEN | FACTION_FLAG_INVISIBLE_FORCED) &&
!(faction.Flags & FACTION_FLAG_SPECIAL))
continue;
FactionEntry const* entry = sFactionStore.LookupEntry(faction.ID);
if (!entry)
continue;
lines.push_back(BuildReputationLine(entry));
}
std::sort(lines.begin(), lines.end());
botAI->TellMaster("=== Reputations ===");
for (auto const& line : lines)
botAI->TellMaster(line);
return true;
}
Player* master = GetMaster();
if (!master)
return false;
ObjectGuid selection = master->GetTarget();
if (selection.IsEmpty())
return false;
Unit* unit = ObjectAccessor::GetUnit(*master, selection);
if (!unit)
return false;
FactionTemplateEntry const* factionTemplate = unit->GetFactionTemplateEntry();
FactionEntry const* entry = sFactionStore.LookupEntry(factionTemplate->faction);
if (!entry)
return false;
botAI->TellMaster(BuildReputationLine(entry));
return true; return true;
} }

View File

@ -6,8 +6,11 @@
#ifndef _PLAYERBOT_TELLREPUTATIONACTION_H #ifndef _PLAYERBOT_TELLREPUTATIONACTION_H
#define _PLAYERBOT_TELLREPUTATIONACTION_H #define _PLAYERBOT_TELLREPUTATIONACTION_H
#include <string>
#include "Action.h" #include "Action.h"
struct FactionEntry;
class PlayerbotAI; class PlayerbotAI;
class TellReputationAction : public Action class TellReputationAction : public Action
@ -16,6 +19,9 @@ public:
TellReputationAction(PlayerbotAI* botAI) : Action(botAI, "reputation") {} TellReputationAction(PlayerbotAI* botAI) : Action(botAI, "reputation") {}
bool Execute(Event event) override; bool Execute(Event event) override;
private:
std::string BuildReputationLine(FactionEntry const* entry);
}; };
#endif #endif

View File

@ -66,6 +66,7 @@
#include "TaxiAction.h" #include "TaxiAction.h"
#include "TeleportAction.h" #include "TeleportAction.h"
#include "TellCastFailedAction.h" #include "TellCastFailedAction.h"
#include "TellEmblemsAction.h"
#include "TellItemCountAction.h" #include "TellItemCountAction.h"
#include "TellLosAction.h" #include "TellLosAction.h"
#include "TellReputationAction.h" #include "TellReputationAction.h"
@ -120,6 +121,7 @@ public:
creators["teleport"] = &ChatActionContext::teleport; creators["teleport"] = &ChatActionContext::teleport;
creators["taxi"] = &ChatActionContext::taxi; creators["taxi"] = &ChatActionContext::taxi;
creators["repair"] = &ChatActionContext::repair; creators["repair"] = &ChatActionContext::repair;
creators["emblems"] = &ChatActionContext::emblems;
creators["use"] = &ChatActionContext::use; creators["use"] = &ChatActionContext::use;
creators["item count"] = &ChatActionContext::item_count; creators["item count"] = &ChatActionContext::item_count;
creators["equip"] = &ChatActionContext::equip; creators["equip"] = &ChatActionContext::equip;
@ -276,6 +278,7 @@ private:
static Action* item_count(PlayerbotAI* botAI) { return new TellItemCountAction(botAI); } static Action* item_count(PlayerbotAI* botAI) { return new TellItemCountAction(botAI); }
static Action* use(PlayerbotAI* botAI) { return new UseItemAction(botAI); } static Action* use(PlayerbotAI* botAI) { return new UseItemAction(botAI); }
static Action* repair(PlayerbotAI* botAI) { return new RepairAllAction(botAI); } static Action* repair(PlayerbotAI* botAI) { return new RepairAllAction(botAI); }
static Action* emblems(PlayerbotAI* botAI) { return new TellEmblemsAction(botAI); }
static Action* taxi(PlayerbotAI* botAI) { return new TaxiAction(botAI); } static Action* taxi(PlayerbotAI* botAI) { return new TaxiAction(botAI); }
static Action* teleport(PlayerbotAI* botAI) { return new TeleportAction(botAI); } static Action* teleport(PlayerbotAI* botAI) { return new TeleportAction(botAI); }
static Action* release(PlayerbotAI* botAI) { return new ReleaseSpiritAction(botAI); } static Action* release(PlayerbotAI* botAI) { return new ReleaseSpiritAction(botAI); }

View File

@ -41,6 +41,7 @@ public:
creators["teleport"] = &ChatTriggerContext::teleport; creators["teleport"] = &ChatTriggerContext::teleport;
creators["taxi"] = &ChatTriggerContext::taxi; creators["taxi"] = &ChatTriggerContext::taxi;
creators["repair"] = &ChatTriggerContext::repair; creators["repair"] = &ChatTriggerContext::repair;
creators["emblems"] = &ChatTriggerContext::emblems;
creators["u"] = &ChatTriggerContext::use; creators["u"] = &ChatTriggerContext::use;
creators["use"] = &ChatTriggerContext::use; creators["use"] = &ChatTriggerContext::use;
creators["c"] = &ChatTriggerContext::item_count; creators["c"] = &ChatTriggerContext::item_count;
@ -235,6 +236,7 @@ private:
static Trigger* item_count(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "c"); } static Trigger* item_count(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "c"); }
static Trigger* use(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "use"); } static Trigger* use(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "use"); }
static Trigger* repair(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "repair"); } static Trigger* repair(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "repair"); }
static Trigger* emblems(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "emblems"); }
static Trigger* taxi(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "taxi"); } static Trigger* taxi(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "taxi"); }
static Trigger* teleport(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "teleport"); } static Trigger* teleport(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "teleport"); }
static Trigger* q(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "q"); } static Trigger* q(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "q"); }

View File

@ -114,6 +114,7 @@ void ChatCommandHandlerStrategy::InitTriggers(std::vector<TriggerNode*>& trigger
triggers.push_back(new TriggerNode("pet attack", { NextAction("pet attack", relevance) })); triggers.push_back(new TriggerNode("pet attack", { NextAction("pet attack", relevance) }));
triggers.push_back(new TriggerNode("roll", { NextAction("roll", relevance) })); triggers.push_back(new TriggerNode("roll", { NextAction("roll", relevance) }));
triggers.push_back(new TriggerNode("focus heal", { NextAction("focus heal targets", relevance) })); triggers.push_back(new TriggerNode("focus heal", { NextAction("focus heal targets", relevance) }));
triggers.push_back(new TriggerNode("emblems", { NextAction("emblems", relevance) }));
} }
ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : PassTroughStrategy(botAI) ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : PassTroughStrategy(botAI)
@ -138,6 +139,7 @@ ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : Pas
supported.push_back("teleport"); supported.push_back("teleport");
supported.push_back("taxi"); supported.push_back("taxi");
supported.push_back("repair"); supported.push_back("repair");
supported.push_back("emblems");
supported.push_back("talents"); supported.push_back("talents");
supported.push_back("spells"); supported.push_back("spells");
supported.push_back("co"); supported.push_back("co");
@ -202,8 +204,8 @@ ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : Pas
supported.push_back("unlock items"); supported.push_back("unlock items");
supported.push_back("unlock traded item"); supported.push_back("unlock traded item");
supported.push_back("tame"); supported.push_back("tame");
supported.push_back("glyphs"); // Added for custom Glyphs supported.push_back("glyphs");
supported.push_back("glyph equip"); // Added for custom Glyphs supported.push_back("glyph equip");
supported.push_back("pet"); supported.push_back("pet");
supported.push_back("pet attack"); supported.push_back("pet attack");
supported.push_back("wait for attack time"); supported.push_back("wait for attack time");

View File

@ -297,7 +297,7 @@ bool PlayerWantsInBattlegroundTrigger::IsActive()
if (bot->GetBattleground() && bot->GetBattleground()->GetStatus() == STATUS_IN_PROGRESS) if (bot->GetBattleground() && bot->GetBattleground()->GetStatus() == STATUS_IN_PROGRESS)
return false; return false;
if (!bot->CanJoinToBattleground()) if (bot->IsDeserter())
return false; return false;
return true; return true;

View File

@ -180,19 +180,11 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto,
delete pItem; delete pItem;
if (result != EQUIP_ERR_OK && result != EQUIP_ERR_CANT_CARRY_MORE_OF_THIS) if (result != EQUIP_ERR_OK && result != EQUIP_ERR_CANT_CARRY_MORE_OF_THIS)
{
return ITEM_USAGE_NONE; return ITEM_USAGE_NONE;
}
// Check is unique items are equipped or not // Check if unique items are equipped or not
bool needToCheckUnique = false; bool needToCheckUnique = result == EQUIP_ERR_CANT_CARRY_MORE_OF_THIS ||
if (result == EQUIP_ERR_CANT_CARRY_MORE_OF_THIS) itemProto->HasFlag(ITEM_FLAG_UNIQUE_EQUIPPABLE);
{
needToCheckUnique = true;
}
else if (itemProto->HasFlag(ITEM_FLAG_UNIQUE_EQUIPPABLE))
{
needToCheckUnique = true;
}
if (needToCheckUnique) if (needToCheckUnique)
{ {
@ -206,28 +198,27 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto,
bool isEquipped = (totalItemCount > bagItemCount); bool isEquipped = (totalItemCount > bagItemCount);
if (isEquipped) if (isEquipped)
{
return ITEM_USAGE_NONE; // Item is already equipped return ITEM_USAGE_NONE; // Item is already equipped
}
// If not equipped, continue processing // If not equipped, continue processing
} }
if (itemProto->Class == ITEM_CLASS_QUIVER) if (itemProto->Class == ITEM_CLASS_QUIVER && bot->getClass() != CLASS_HUNTER)
if (bot->getClass() != CLASS_HUNTER) return ITEM_USAGE_NONE;
return ITEM_USAGE_NONE;
if (itemProto->Class == ITEM_CLASS_CONTAINER) if (itemProto->Class == ITEM_CLASS_CONTAINER)
{ {
if (itemProto->SubClass != ITEM_SUBCLASS_CONTAINER) if (itemProto->SubClass != ITEM_SUBCLASS_CONTAINER)
return ITEM_USAGE_NONE; // Todo add logic for non-bag containers. We want to look at professions/class and return ITEM_USAGE_NONE; // Todo add logic for non-bag containers. We want to look at professions/class and
// only replace if non-bag is larger than bag. // only replace if non-bag is larger than bag.
if (GetSmallestBagSize() >= itemProto->ContainerSlots) if (GetSmallestBagSize() >= itemProto->ContainerSlots)
return ITEM_USAGE_NONE; return ITEM_USAGE_NONE;
return ITEM_USAGE_EQUIP; return ITEM_USAGE_EQUIP;
} }
if (itemProto->Class == ITEM_CLASS_WEAPON && itemProto->SubClass == ITEM_SUBCLASS_WEAPON_MISC)
return ITEM_USAGE_NONE;
bool shouldEquip = false; bool shouldEquip = false;
// uint32 statWeight = sRandomItemMgr.GetLiveStatWeight(bot, itemProto->ItemId); // uint32 statWeight = sRandomItemMgr.GetLiveStatWeight(bot, itemProto->ItemId);
StatsWeightCalculator calculator(bot); StatsWeightCalculator calculator(bot);
@ -254,19 +245,14 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto,
uint8 dstSlot = botAI->FindEquipSlot(itemProto, NULL_SLOT, true); uint8 dstSlot = botAI->FindEquipSlot(itemProto, NULL_SLOT, true);
// Check if dest wasn't set correctly by CanEquipItem and use FindEquipSlot instead // Check if dest wasn't set correctly by CanEquipItem and use FindEquipSlot instead
// This occurs with unique items that are already in the bots bags when CanEquipItem is called // This occurs with unique items that are already in the bots bags when CanEquipItem is called
if (dest == 0) if (dest == 0 && dstSlot != NULL_SLOT)
{ {
if (dstSlot != NULL_SLOT) // Construct dest from dstSlot
{ dest = (INVENTORY_SLOT_BAG_0 << 8) | dstSlot;
// Construct dest from dstSlot
dest = (INVENTORY_SLOT_BAG_0 << 8) | dstSlot;
}
} }
if (dstSlot == EQUIPMENT_SLOT_FINGER1 || dstSlot == EQUIPMENT_SLOT_TRINKET1) if (dstSlot == EQUIPMENT_SLOT_FINGER1 || dstSlot == EQUIPMENT_SLOT_TRINKET1)
{
possibleSlots = 2; possibleSlots = 2;
}
// Check weapon case separately to keep things a bit cleaner // Check weapon case separately to keep things a bit cleaner
bool have2HWeapon = false; bool have2HWeapon = false;
@ -283,14 +269,9 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto,
itemProto->SubClass == ITEM_SUBCLASS_WEAPON_SWORD2); itemProto->SubClass == ITEM_SUBCLASS_WEAPON_SWORD2);
// If the bot can Titan Grip, ignore any 2H weapon that isn't a 2H sword, mace, or axe. // If the bot can Titan Grip, ignore any 2H weapon that isn't a 2H sword, mace, or axe.
if (bot->CanTitanGrip()) // If this weapon is 2H but not one of the valid TG weapon types, do not equip it at all.
{ if (bot->CanTitanGrip() && itemProto->InventoryType == INVTYPE_2HWEAPON && !isValidTGWeapon)
// If this weapon is 2H but not one of the valid TG weapon types, do not equip it at all. return ITEM_USAGE_NONE;
if (itemProto->InventoryType == INVTYPE_2HWEAPON && !isValidTGWeapon)
{
return ITEM_USAGE_NONE;
}
}
// Now handle the logic for equipping and possible offhand slots // Now handle the logic for equipping and possible offhand slots
// If the bot can Dual Wield and: // If the bot can Dual Wield and:
@ -317,9 +298,7 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto,
if (shouldEquipInSlot) if (shouldEquipInSlot)
return ITEM_USAGE_EQUIP; return ITEM_USAGE_EQUIP;
else else
{
return ITEM_USAGE_BAD_EQUIP; return ITEM_USAGE_BAD_EQUIP;
}
} }
ItemTemplate const* oldItemProto = oldItem->GetTemplate(); ItemTemplate const* oldItemProto = oldItem->GetTemplate();
@ -328,22 +307,16 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto,
{ {
// uint32 oldStatWeight = sRandomItemMgr.GetLiveStatWeight(bot, oldItemProto->ItemId); // uint32 oldStatWeight = sRandomItemMgr.GetLiveStatWeight(bot, oldItemProto->ItemId);
if (itemScore || oldScore) if (itemScore || oldScore)
{
shouldEquipInSlot = itemScore > oldScore * sPlayerbotAIConfig.equipUpgradeThreshold; shouldEquipInSlot = itemScore > oldScore * sPlayerbotAIConfig.equipUpgradeThreshold;
}
} }
// Bigger quiver // Bigger quiver
if (itemProto->Class == ITEM_CLASS_QUIVER) if (itemProto->Class == ITEM_CLASS_QUIVER)
{ {
if (!oldItem || oldItemProto->ContainerSlots < itemProto->ContainerSlots) if (!oldItem || oldItemProto->ContainerSlots < itemProto->ContainerSlots)
{
return ITEM_USAGE_EQUIP; return ITEM_USAGE_EQUIP;
}
else else
{
return ITEM_USAGE_NONE; return ITEM_USAGE_NONE;
}
} }
bool existingShouldEquip = true; bool existingShouldEquip = true;

View File

@ -11,6 +11,22 @@
#include "AoeValues.h" #include "AoeValues.h"
#include "TargetValue.h" #include "TargetValue.h"
namespace
{
bool PrepareThornsTarget(PlayerbotAI* botAI, Unit* target)
{
if (!target)
return false;
Aura* existingThorns = botAI->GetAura("thorns", target, true);
if (!existingThorns)
return true;
target->RemoveOwnedAura(existingThorns, AURA_REMOVE_BY_CANCEL);
return true;
}
}
std::vector<NextAction> CastAbolishPoisonAction::getAlternatives() std::vector<NextAction> CastAbolishPoisonAction::getAlternatives()
{ {
return NextAction::merge({ NextAction("cure poison") }, return NextAction::merge({ NextAction("cure poison") },
@ -33,6 +49,21 @@ bool CastLifebloomOnMainTankAction::isUseful()
return !lifebloom || lifebloom->GetStackAmount() < 3 || lifebloom->GetDuration() < 2000; return !lifebloom || lifebloom->GetStackAmount() < 3 || lifebloom->GetDuration() < 2000;
} }
bool CastThornsAction::Execute(Event event)
{
return PrepareThornsTarget(botAI, GetTarget()) && CastBuffSpellAction::Execute(event);
}
bool CastThornsOnPartyAction::Execute(Event event)
{
return PrepareThornsTarget(botAI, GetTarget()) && BuffOnPartyAction::Execute(event);
}
bool CastThornsOnMainTankAction::Execute(Event event)
{
return PrepareThornsTarget(botAI, GetTarget()) && BuffOnMainTankAction::Execute(event);
}
Value<Unit*>* CastEntanglingRootsCcAction::GetTargetValue() Value<Unit*>* CastEntanglingRootsCcAction::GetTargetValue()
{ {
return context->GetValue<Unit*>("cc target", "entangling roots"); return context->GetValue<Unit*>("cc target", "entangling roots");

View File

@ -114,18 +114,24 @@ class CastThornsAction : public CastBuffSpellAction
{ {
public: public:
CastThornsAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "thorns") {} CastThornsAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "thorns") {}
bool Execute(Event event) override;
}; };
class CastThornsOnPartyAction : public BuffOnPartyAction class CastThornsOnPartyAction : public BuffOnPartyAction
{ {
public: public:
CastThornsOnPartyAction(PlayerbotAI* botAI) : BuffOnPartyAction(botAI, "thorns") {} CastThornsOnPartyAction(PlayerbotAI* botAI) : BuffOnPartyAction(botAI, "thorns") {}
bool Execute(Event event) override;
}; };
class CastThornsOnMainTankAction : public BuffOnMainTankAction class CastThornsOnMainTankAction : public BuffOnMainTankAction
{ {
public: public:
CastThornsOnMainTankAction(PlayerbotAI* botAI) : BuffOnMainTankAction(botAI, "thorns", false) {} CastThornsOnMainTankAction(PlayerbotAI* botAI) : BuffOnMainTankAction(botAI, "thorns", false) {}
bool Execute(Event event) override;
}; };
class CastLifebloomOnMainTankAction : public BuffOnMainTankAction class CastLifebloomOnMainTankAction : public BuffOnMainTankAction

View File

@ -1,7 +1,6 @@
#ifndef _PLAYERBOT_RAIDAQ20TRIGGERCONTEXT_H #ifndef _PLAYERBOT_RAIDAQ20TRIGGERCONTEXT_H
#define _PLAYERBOT_RAIDAQ20TRIGGERCONTEXT_H #define _PLAYERBOT_RAIDAQ20TRIGGERCONTEXT_H
#include "AiObjectContext.h"
#include "NamedObjectContext.h" #include "NamedObjectContext.h"
#include "RaidAq20Triggers.h" #include "RaidAq20Triggers.h"

View File

@ -1,8 +1,6 @@
#ifndef _PLAYERBOT_RAIDAQ20STRATEGY_H #ifndef _PLAYERBOT_RAIDAQ20STRATEGY_H
#define _PLAYERBOT_RAIDAQ20STRATEGY_H #define _PLAYERBOT_RAIDAQ20STRATEGY_H
#include "AiObjectContext.h"
#include "Multiplier.h"
#include "Strategy.h" #include "Strategy.h"
class RaidAq20Strategy : public Strategy class RaidAq20Strategy : public Strategy

View File

@ -1,7 +1,6 @@
#ifndef _PLAYERBOT_RAIDBWLTRIGGERCONTEXT_H #ifndef _PLAYERBOT_RAIDBWLTRIGGERCONTEXT_H
#define _PLAYERBOT_RAIDBWLTRIGGERCONTEXT_H #define _PLAYERBOT_RAIDBWLTRIGGERCONTEXT_H
#include "AiObjectContext.h"
#include "NamedObjectContext.h" #include "NamedObjectContext.h"
#include "RaidBwlTriggers.h" #include "RaidBwlTriggers.h"

View File

@ -2,8 +2,6 @@
#ifndef _PLAYERBOT_RAIDBWLSTRATEGY_H #ifndef _PLAYERBOT_RAIDBWLSTRATEGY_H
#define _PLAYERBOT_RAIDBWLSTRATEGY_H #define _PLAYERBOT_RAIDBWLSTRATEGY_H
#include "AiObjectContext.h"
#include "Multiplier.h"
#include "Strategy.h" #include "Strategy.h"
class RaidBwlStrategy : public Strategy class RaidBwlStrategy : public Strategy

View File

@ -1,7 +1,6 @@
#ifndef _PLAYERBOT_RAIDEOETRIGGERCONTEXT_H #ifndef _PLAYERBOT_RAIDEOETRIGGERCONTEXT_H
#define _PLAYERBOT_RAIDEOETRIGGERCONTEXT_H #define _PLAYERBOT_RAIDEOETRIGGERCONTEXT_H
#include "AiObjectContext.h"
#include "NamedObjectContext.h" #include "NamedObjectContext.h"
#include "RaidEoETriggers.h" #include "RaidEoETriggers.h"

View File

@ -1,8 +1,6 @@
#ifndef _PLAYERBOT_RAIDEOESTRATEGY_H #ifndef _PLAYERBOT_RAIDEOESTRATEGY_H
#define _PLAYERBOT_RAIDEOESTRATEGY_H #define _PLAYERBOT_RAIDEOESTRATEGY_H
#include "AiObjectContext.h"
#include "Multiplier.h"
#include "Strategy.h" #include "Strategy.h"
class RaidEoEStrategy : public Strategy class RaidEoEStrategy : public Strategy

View File

@ -2,7 +2,7 @@
#define _PLAYERBOT_RAIDGRUULSLAIRTRIGGERCONTEXT_H #define _PLAYERBOT_RAIDGRUULSLAIRTRIGGERCONTEXT_H
#include "RaidGruulsLairTriggers.h" #include "RaidGruulsLairTriggers.h"
#include "AiObjectContext.h" #include "NamedObjectContext.h"
class RaidGruulsLairTriggerContext : public NamedObjectContext<Trigger> class RaidGruulsLairTriggerContext : public NamedObjectContext<Trigger>
{ {

View File

@ -2,7 +2,6 @@
#define _PLAYERBOT_RAIDGRUULSLAIRSTRATEGY_H #define _PLAYERBOT_RAIDGRUULSLAIRSTRATEGY_H
#include "Strategy.h" #include "Strategy.h"
#include "Multiplier.h"
class RaidGruulsLairStrategy : public Strategy class RaidGruulsLairStrategy : public Strategy
{ {

View File

@ -1,7 +1,6 @@
#ifndef _PLAYERBOT_RAIDICCTRIGGERCONTEXT_H #ifndef _PLAYERBOT_RAIDICCTRIGGERCONTEXT_H
#define _PLAYERBOT_RAIDICCTRIGGERCONTEXT_H #define _PLAYERBOT_RAIDICCTRIGGERCONTEXT_H
#include "AiObjectContext.h"
#include "NamedObjectContext.h" #include "NamedObjectContext.h"
#include "RaidIccTriggers.h" #include "RaidIccTriggers.h"

View File

@ -1,10 +1,7 @@
#ifndef _PLAYERBOT_RAIDICCSTRATEGY_H #ifndef _PLAYERBOT_RAIDICCSTRATEGY_H
#define _PLAYERBOT_RAIDICCSTRATEGY_H #define _PLAYERBOT_RAIDICCSTRATEGY_H
#include "AiObjectContext.h"
#include "Multiplier.h"
#include "Strategy.h" #include "Strategy.h"
#include "RaidIccMultipliers.h"
class RaidIccStrategy : public Strategy class RaidIccStrategy : public Strategy
{ {

View File

@ -2,7 +2,7 @@
#define _PLAYERBOT_RAIDKARAZHANTRIGGERCONTEXT_H #define _PLAYERBOT_RAIDKARAZHANTRIGGERCONTEXT_H
#include "RaidKarazhanTriggers.h" #include "RaidKarazhanTriggers.h"
#include "AiObjectContext.h" #include "NamedObjectContext.h"
class RaidKarazhanTriggerContext : public NamedObjectContext<Trigger> class RaidKarazhanTriggerContext : public NamedObjectContext<Trigger>
{ {

View File

@ -2,7 +2,6 @@
#define _PLAYERBOT_RAIDKARAZHANSTRATEGY_H_ #define _PLAYERBOT_RAIDKARAZHANSTRATEGY_H_
#include "Strategy.h" #include "Strategy.h"
#include "Multiplier.h"
class RaidKarazhanStrategy : public Strategy class RaidKarazhanStrategy : public Strategy
{ {

View File

@ -2,7 +2,7 @@
#define _PLAYERBOT_RAIDMAGTHERIDONTRIGGERCONTEXT_H #define _PLAYERBOT_RAIDMAGTHERIDONTRIGGERCONTEXT_H
#include "RaidMagtheridonTriggers.h" #include "RaidMagtheridonTriggers.h"
#include "AiObjectContext.h" #include "NamedObjectContext.h"
class RaidMagtheridonTriggerContext : public NamedObjectContext<Trigger> class RaidMagtheridonTriggerContext : public NamedObjectContext<Trigger>
{ {

View File

@ -2,7 +2,6 @@
#define _PLAYERBOT_RAIDMAGTHERIDONSTRATEGY_H #define _PLAYERBOT_RAIDMAGTHERIDONSTRATEGY_H
#include "Strategy.h" #include "Strategy.h"
#include "Multiplier.h"
class RaidMagtheridonStrategy : public Strategy class RaidMagtheridonStrategy : public Strategy
{ {

View File

@ -1,7 +1,6 @@
#ifndef _PLAYERBOT_RAIDMCTRIGGERCONTEXT_H #ifndef _PLAYERBOT_RAIDMCTRIGGERCONTEXT_H
#define _PLAYERBOT_RAIDMCTRIGGERCONTEXT_H #define _PLAYERBOT_RAIDMCTRIGGERCONTEXT_H
#include "AiObjectContext.h"
#include "BossAuraTriggers.h" #include "BossAuraTriggers.h"
#include "NamedObjectContext.h" #include "NamedObjectContext.h"
#include "RaidMcTriggers.h" #include "RaidMcTriggers.h"

View File

@ -1,8 +1,6 @@
#ifndef _PLAYERBOT_RAIDMCSTRATEGY_H #ifndef _PLAYERBOT_RAIDMCSTRATEGY_H
#define _PLAYERBOT_RAIDMCSTRATEGY_H #define _PLAYERBOT_RAIDMCSTRATEGY_H
#include "AiObjectContext.h"
#include "Multiplier.h"
#include "Strategy.h" #include "Strategy.h"
class RaidMcStrategy : public Strategy class RaidMcStrategy : public Strategy

View File

@ -6,7 +6,6 @@
#ifndef _PLAYERBOT_RAIDNAXXTRIGGERCONTEXT_H #ifndef _PLAYERBOT_RAIDNAXXTRIGGERCONTEXT_H
#define _PLAYERBOT_RAIDNAXXTRIGGERCONTEXT_H #define _PLAYERBOT_RAIDNAXXTRIGGERCONTEXT_H
#include "AiObjectContext.h"
#include "NamedObjectContext.h" #include "NamedObjectContext.h"
#include "RaidNaxxTriggers.h" #include "RaidNaxxTriggers.h"

View File

@ -2,8 +2,6 @@
#ifndef _PLAYERBOT_RAIDNAXXSTRATEGY_H #ifndef _PLAYERBOT_RAIDNAXXSTRATEGY_H
#define _PLAYERBOT_RAIDNAXXSTRATEGY_H #define _PLAYERBOT_RAIDNAXXSTRATEGY_H
#include "AiObjectContext.h"
#include "Multiplier.h"
#include "Strategy.h" #include "Strategy.h"
class RaidNaxxStrategy : public Strategy class RaidNaxxStrategy : public Strategy

View File

@ -1,7 +1,6 @@
#ifndef _PLAYERBOT_RAIDOSTRIGGERCONTEXT_H #ifndef _PLAYERBOT_RAIDOSTRIGGERCONTEXT_H
#define _PLAYERBOT_RAIDOSTRIGGERCONTEXT_H #define _PLAYERBOT_RAIDOSTRIGGERCONTEXT_H
#include "AiObjectContext.h"
#include "NamedObjectContext.h" #include "NamedObjectContext.h"
#include "RaidOsTriggers.h" #include "RaidOsTriggers.h"

View File

@ -1,8 +1,6 @@
#ifndef _PLAYERBOT_RAIDOSSTRATEGY_H #ifndef _PLAYERBOT_RAIDOSSTRATEGY_H
#define _PLAYERBOT_RAIDOSSTRATEGY_H #define _PLAYERBOT_RAIDOSSTRATEGY_H
#include "AiObjectContext.h"
#include "Multiplier.h"
#include "Strategy.h" #include "Strategy.h"
class RaidOsStrategy : public Strategy class RaidOsStrategy : public Strategy

View File

@ -99,8 +99,12 @@ bool RaidOnyxiaMoveToSafeZoneAction::Execute(Event /*event*/)
if (bot->IsWithinDist2d(bestZone->pos.GetPositionX(), bestZone->pos.GetPositionY(), bestZone->radius)) if (bot->IsWithinDist2d(bestZone->pos.GetPositionX(), bestZone->pos.GetPositionY(), bestZone->radius))
return false; // Already safe return false; // Already safe
// Stop current spell first
bot->AttackStop();
bot->InterruptNonMeleeSpells(false);
// bot->Yell("Moving to Safe Zone!", LANG_UNIVERSAL); // bot->Yell("Moving to Safe Zone!", LANG_UNIVERSAL);
return MoveTo(bot->GetMapId(), bestZone->pos.GetPositionX(), bestZone->pos.GetPositionY(), bot->GetPositionZ(), return MoveTo(bot->GetMapId(), bestZone->pos.GetPositionX(), bestZone->pos.GetPositionY(), bestZone->pos.GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT); false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
} }

View File

@ -2,7 +2,6 @@
#ifndef _PLAYERBOT_RAIDONYXIAACTIONS_H_ #ifndef _PLAYERBOT_RAIDONYXIAACTIONS_H_
#define _PLAYERBOT_RAIDONYXIAACTIONS_H_ #define _PLAYERBOT_RAIDONYXIAACTIONS_H_
#include "Action.h"
#include "AttackAction.h" #include "AttackAction.h"
#include "GenericSpellActions.h" #include "GenericSpellActions.h"
#include "MovementActions.h" #include "MovementActions.h"
@ -45,42 +44,45 @@ public:
bool Execute(Event event) override; bool Execute(Event event) override;
private: private:
std::vector<SafeZone> GetSafeZonesForBreath(uint32 spellId) static std::vector<SafeZone> GetSafeZonesForBreath(uint32 spellId)
{ {
// Define your safe zone coordinates based on the map // Safe zone coordinates based on the map
// Example assumes Onyxia's lair map coordinates // Assumes Onyxia's lair map coordinates
float z = bot->GetPositionZ(); // Stay at current height
switch (spellId) switch (spellId)
{ {
case 17086: // N to S case 17086: // N to S
case 18351: // S to N case 18351: // S to N
return {SafeZone{Position(-10.0f, -180.0f, z), 5.0f}, return {
SafeZone{Position(-20.0f, -250.0f, z), 5.0f}}; // Bottom Safe Zone SafeZone{Position(-10.0f, -180.0f, -87.0f), 5.0f},
SafeZone{Position(-20.0f, -250.0f, -88.0f), 5.0f}
}; // Bottom Safe Zone
case 18576: // E to W case 18576: // E to W
case 18609: // W to E case 18609: // W to E
return { return {
SafeZone{Position(20.0f, -210.0f, z), 5.0f}, SafeZone{Position(20.0f, -210.0f, -85.5f), 5.0f},
SafeZone{Position(-75.0f, -210.0f, z), 5.0f}, SafeZone{Position(-75.0f, -210.0f, -83.4f), 5.0f},
}; // Left Safe Zone }; // Left Safe Zone
case 18564: // SE to NW case 18564: // SE to NW
case 18584: // NW to SE case 18584: // NW to SE
return { return {
SafeZone{Position(-60.0f, -195.0f, z), 5.0f}, SafeZone{Position(-60.0f, -195.0f, -85.0f), 5.0f},
SafeZone{Position(10.0f, -240.0f, z), 5.0f}, SafeZone{Position(10.0f, -240.0f, -85.9f), 5.0f},
}; // NW Safe Zone }; // NW Safe Zone
case 18596: // SW to NE case 18596: // SW to NE
case 18617: // NE to SW case 18617: // NE to SW
return { return {
SafeZone{Position(7.0f, -185.0f, z), 5.0f}, SafeZone{Position(7.0f, -185.0f, -86.2f), 5.0f},
SafeZone{Position(-60.0f, -240.0f, z), 5.0f}, SafeZone{Position(-60.0f, -240.0f, -85.2f), 5.0f},
}; // NE Safe Zone }; // NE Safe Zone
default: default:
return {SafeZone{Position(0.0f, 0.0f, z), 5.0f}}; // Fallback center - shouldn't ever happen return {
SafeZone{Position(-40.0f, -214.0f, -86.6f), 5.0f}
}; // Fallback center - shouldn't ever happen
} }
} }
}; };

View File

@ -1,7 +1,6 @@
#ifndef _PLAYERBOT_RAIDONYXIATRIGGERCONTEXT_H #ifndef _PLAYERBOT_RAIDONYXIATRIGGERCONTEXT_H
#define _PLAYERBOT_RAIDONYXIATRIGGERCONTEXT_H #define _PLAYERBOT_RAIDONYXIATRIGGERCONTEXT_H
#include "AiObjectContext.h"
#include "NamedObjectContext.h" #include "NamedObjectContext.h"
#include "RaidOnyxiaTriggers.h" #include "RaidOnyxiaTriggers.h"

View File

@ -7,7 +7,7 @@
#define _PLAYERBOT_RAIDSSCTRIGGERCONTEXT_H #define _PLAYERBOT_RAIDSSCTRIGGERCONTEXT_H
#include "RaidSSCTriggers.h" #include "RaidSSCTriggers.h"
#include "AiObjectContext.h" #include "NamedObjectContext.h"
class RaidSSCTriggerContext : public NamedObjectContext<Trigger> class RaidSSCTriggerContext : public NamedObjectContext<Trigger>
{ {

View File

@ -7,7 +7,6 @@
#define _PLAYERBOT_RAIDSSCSTRATEGY_H_ #define _PLAYERBOT_RAIDSSCSTRATEGY_H_
#include "Strategy.h" #include "Strategy.h"
#include "Multiplier.h"
class RaidSSCStrategy : public Strategy class RaidSSCStrategy : public Strategy
{ {

View File

@ -2,7 +2,7 @@
#define _PLAYERBOT_RAIDTEMPESTKEEPTRIGGERCONTEXT_H #define _PLAYERBOT_RAIDTEMPESTKEEPTRIGGERCONTEXT_H
#include "RaidTempestKeepTriggers.h" #include "RaidTempestKeepTriggers.h"
#include "AiObjectContext.h" #include "NamedObjectContext.h"
class RaidTempestKeepTriggerContext : public NamedObjectContext<Trigger> class RaidTempestKeepTriggerContext : public NamedObjectContext<Trigger>
{ {

View File

@ -2,7 +2,6 @@
#define _PLAYERBOT_RAIDTEMPESTKEEPSTRATEGY_H_ #define _PLAYERBOT_RAIDTEMPESTKEEPSTRATEGY_H_
#include "Strategy.h" #include "Strategy.h"
#include "Multiplier.h"
class RaidTempestKeepStrategy : public Strategy class RaidTempestKeepStrategy : public Strategy
{ {

View File

@ -6,7 +6,6 @@
#ifndef _PLAYERBOT_RAIDULDUARTRIGGERCONTEXT_H #ifndef _PLAYERBOT_RAIDULDUARTRIGGERCONTEXT_H
#define _PLAYERBOT_RAIDULDUARTRIGGERCONTEXT_H #define _PLAYERBOT_RAIDULDUARTRIGGERCONTEXT_H
#include "AiObjectContext.h"
#include "NamedObjectContext.h" #include "NamedObjectContext.h"
#include "RaidUlduarTriggers.h" #include "RaidUlduarTriggers.h"
#include "BossAuraTriggers.h" #include "BossAuraTriggers.h"

View File

@ -2,7 +2,6 @@
#ifndef _PLAYERBOT_RAIDULDUARSTRATEGY_H #ifndef _PLAYERBOT_RAIDULDUARSTRATEGY_H
#define _PLAYERBOT_RAIDULDUARSTRATEGY_H #define _PLAYERBOT_RAIDULDUARSTRATEGY_H
#include "AiObjectContext.h"
#include "Strategy.h" #include "Strategy.h"
class RaidUlduarStrategy : public Strategy class RaidUlduarStrategy : public Strategy

View File

@ -6,7 +6,6 @@
#ifndef _PLAYERBOT_RAIDVOATRIGGERCONTEXT_H #ifndef _PLAYERBOT_RAIDVOATRIGGERCONTEXT_H
#define _PLAYERBOT_RAIDVOATRIGGERCONTEXT_H #define _PLAYERBOT_RAIDVOATRIGGERCONTEXT_H
#include "AiObjectContext.h"
#include "BossAuraTriggers.h" #include "BossAuraTriggers.h"
#include "NamedObjectContext.h" #include "NamedObjectContext.h"
#include "RaidVoATriggers.h" #include "RaidVoATriggers.h"

View File

@ -3,10 +3,6 @@
#define _PLAYERBOT_RAIDVOASTRATEGY_H #define _PLAYERBOT_RAIDVOASTRATEGY_H
#include "Strategy.h" #include "Strategy.h"
#include "PlayerbotAI.h"
#include "string"
#include "Trigger.h"
#include "vector"
class RaidVoAStrategy : public Strategy class RaidVoAStrategy : public Strategy
{ {

View File

@ -7,7 +7,7 @@
#define _PLAYERBOT_RAIDZULAMANTRIGGERCONTEXT_H #define _PLAYERBOT_RAIDZULAMANTRIGGERCONTEXT_H
#include "RaidZulAmanTriggers.h" #include "RaidZulAmanTriggers.h"
#include "AiObjectContext.h" #include "NamedObjectContext.h"
class RaidZulAmanTriggerContext : public NamedObjectContext<Trigger> class RaidZulAmanTriggerContext : public NamedObjectContext<Trigger>
{ {

View File

@ -7,7 +7,6 @@
#define _PLAYERBOT_RAIDZULAMANSTRATEGY_H_ #define _PLAYERBOT_RAIDZULAMANSTRATEGY_H_
#include "Strategy.h" #include "Strategy.h"
#include "Multiplier.h"
class RaidZulAmanStrategy : public Strategy class RaidZulAmanStrategy : public Strategy
{ {

View File

@ -3,6 +3,7 @@
#include <cmath> #include <cmath>
#include <cstdlib> #include <cstdlib>
#include "AreaDefines.h"
#include "BroadcastHelper.h" #include "BroadcastHelper.h"
#include "ChatHelper.h" #include "ChatHelper.h"
#include "G3D/Vector2.h" #include "G3D/Vector2.h"
@ -468,10 +469,14 @@ bool NewRpgTravelFlightAction::Execute(Event /*event*/)
data.inFlight = true; data.inFlight = true;
return false; return false;
} }
Creature* flightMaster = ObjectAccessor::GetCreature(*bot, data.fromFlightMaster);
if (bot->GetDistance(data.flightMasterPos) > INTERACTION_DISTANCE)
return MoveFarTo(data.flightMasterPos);
Creature* flightMaster = bot->FindNearestCreature(data.flightMasterEntry, INTERACTION_DISTANCE * 3);
if (!flightMaster || !flightMaster->IsAlive()) if (!flightMaster || !flightMaster->IsAlive())
{ {
botAI->rpgInfo.ChangeToIdle(); info.ChangeToIdle();
return true; return true;
} }
if (bot->GetDistance(flightMaster) > INTERACTION_DISTANCE) if (bot->GetDistance(flightMaster) > INTERACTION_DISTANCE)
@ -487,7 +492,8 @@ bool NewRpgTravelFlightAction::Execute(Event /*event*/)
{ {
LOG_DEBUG("playerbots", "[New RPG] {} active taxi path {} (from {} to {}) failed", bot->GetName(), LOG_DEBUG("playerbots", "[New RPG] {} active taxi path {} (from {} to {}) failed", bot->GetName(),
flightMaster->GetEntry(), nodes[0], nodes[nodes.size() - 1]); flightMaster->GetEntry(), nodes[0], nodes[nodes.size() - 1]);
botAI->rpgInfo.ChangeToIdle(); info.ChangeToIdle();
return true;
} }
return true; return true;
} }

View File

@ -1027,19 +1027,21 @@ WorldPosition NewRpgBaseAction::SelectRandomCampPos(Player* bot)
return dest; return dest;
} }
bool NewRpgBaseAction::SelectRandomFlightTaxiNode(ObjectGuid& flightMaster, std::vector<uint32>& path) bool NewRpgBaseAction::SelectRandomFlightTaxiNode(uint32& flightMasterEntry, WorldPosition& flightMasterPos, std::vector<uint32>& path)
{ {
flightMaster = sTravelMgr.GetNearestFlightMasterGuid(bot); TravelMgr::FlightMasterInfo const* info = sTravelMgr.GetNearestFlightMasterInfo(bot);
if (!flightMaster) if (!info)
return false; return false;
std::vector<std::vector<uint32>> availablePaths = sTravelMgr.GetOptimalFlightDestinations(bot); std::vector<std::vector<uint32>> availablePaths = sTravelMgr.GetOptimalFlightDestinations(bot);
if (availablePaths.empty()) if (availablePaths.empty())
return false; return false;
flightMasterEntry = info->templateEntry;
flightMasterPos = info->pos;
path = availablePaths[urand(0, availablePaths.size() - 1)]; path = availablePaths[urand(0, availablePaths.size() - 1)];
LOG_DEBUG("playerbots", "[New RPG] Bot {} select random flight taxi node from:{} (node {}) to:{} ({} available)", LOG_DEBUG("playerbots", "[New RPG] Bot {} select random flight taxi node from:{} (node {}) to:{} ({} available)",
bot->GetName(), flightMaster.GetEntry(), path[0], path[path.size() - 1], availablePaths.size()); bot->GetName(), flightMasterEntry, path[0], path[path.size() - 1], availablePaths.size());
return true; return true;
} }
@ -1139,11 +1141,12 @@ bool NewRpgBaseAction::RandomChangeStatus(std::vector<NewRpgStatus> candidateSta
} }
case RPG_TRAVEL_FLIGHT: case RPG_TRAVEL_FLIGHT:
{ {
ObjectGuid flightMaster; uint32 flightMasterEntry = 0;
WorldPosition flightMasterPos;
std::vector<uint32> path; std::vector<uint32> path;
if (SelectRandomFlightTaxiNode(flightMaster, path)) if (SelectRandomFlightTaxiNode(flightMasterEntry, flightMasterPos, path))
{ {
botAI->rpgInfo.ChangeToTravelFlight(flightMaster, path); botAI->rpgInfo.ChangeToTravelFlight(flightMasterEntry, flightMasterPos, path);
return true; return true;
} }
return false; return false;
@ -1220,9 +1223,10 @@ bool NewRpgBaseAction::CheckRpgStatusAvailable(NewRpgStatus status)
} }
case RPG_TRAVEL_FLIGHT: case RPG_TRAVEL_FLIGHT:
{ {
ObjectGuid flightMaster; uint32 flightMasterEntry = 0;
WorldPosition flightMasterPos;
std::vector<uint32> path; std::vector<uint32> path;
return SelectRandomFlightTaxiNode(flightMaster, path); return SelectRandomFlightTaxiNode(flightMasterEntry, flightMasterPos, path);
} }
case RPG_OUTDOOR_PVP: case RPG_OUTDOOR_PVP:
{ {

View File

@ -54,7 +54,7 @@ protected:
bool GetQuestPOIPosAndObjectiveIdx(uint32 questId, std::vector<POIInfo>& poiInfo, bool toComplete = false); bool GetQuestPOIPosAndObjectiveIdx(uint32 questId, std::vector<POIInfo>& poiInfo, bool toComplete = false);
static WorldPosition SelectRandomGrindPos(Player* bot); static WorldPosition SelectRandomGrindPos(Player* bot);
static WorldPosition SelectRandomCampPos(Player* bot); static WorldPosition SelectRandomCampPos(Player* bot);
bool SelectRandomFlightTaxiNode(ObjectGuid& flightMaster, std::vector<uint32>& path); bool SelectRandomFlightTaxiNode(uint32& flightMasterEntry, WorldPosition& flightMasterPos, std::vector<uint32>& path);
bool RandomChangeStatus(std::vector<NewRpgStatus> candidateStatus); bool RandomChangeStatus(std::vector<NewRpgStatus> candidateStatus);
bool CheckRpgStatusAvailable(NewRpgStatus status); bool CheckRpgStatusAvailable(NewRpgStatus status);

View File

@ -37,11 +37,12 @@ void NewRpgInfo::ChangeToDoQuest(uint32 questId, const Quest* quest)
data = do_quest; data = do_quest;
} }
void NewRpgInfo::ChangeToTravelFlight(ObjectGuid fromFlightMaster, std::vector<uint32> path) void NewRpgInfo::ChangeToTravelFlight(uint32 flightMasterEntry, WorldPosition flightMasterPos, std::vector<uint32> path)
{ {
startT = getMSTime(); startT = getMSTime();
TravelFlight flight; TravelFlight flight;
flight.fromFlightMaster = fromFlightMaster; flight.flightMasterEntry = flightMasterEntry;
flight.flightMasterPos = flightMasterPos;
flight.path = std::move(path); flight.path = std::move(path);
flight.inFlight = false; flight.inFlight = false;
data = flight; data = flight;
@ -157,7 +158,7 @@ std::string NewRpgInfo::ToString()
else if constexpr (std::is_same_v<T, TravelFlight>) else if constexpr (std::is_same_v<T, TravelFlight>)
{ {
out << "TRAVEL_FLIGHT"; out << "TRAVEL_FLIGHT";
out << "\nfromFlightMaster: " << arg.fromFlightMaster.GetEntry(); out << "\nflightMasterEntry: " << arg.flightMasterEntry;
out << "\nfromNode: " << arg.path[0]; out << "\nfromNode: " << arg.path[0];
out << "\ntoNode: " << arg.path[arg.path.size() - 1]; out << "\ntoNode: " << arg.path[arg.path.size() - 1];
out << "\ninFlight: " << arg.inFlight; out << "\ninFlight: " << arg.inFlight;

View File

@ -49,7 +49,8 @@ struct NewRpgInfo
// RPG_TRAVEL_FLIGHT // RPG_TRAVEL_FLIGHT
struct TravelFlight struct TravelFlight
{ {
ObjectGuid fromFlightMaster{}; uint32 flightMasterEntry{0};
WorldPosition flightMasterPos{};
std::vector<uint32> path; std::vector<uint32> path;
bool inFlight{false}; bool inFlight{false};
}; };
@ -96,7 +97,7 @@ struct NewRpgInfo
void ChangeToWanderNpc(); void ChangeToWanderNpc();
void ChangeToWanderRandom(); void ChangeToWanderRandom();
void ChangeToDoQuest(uint32 questId, const Quest* quest); void ChangeToDoQuest(uint32 questId, const Quest* quest);
void ChangeToTravelFlight(ObjectGuid fromFlightMaster, std::vector<uint32> path); void ChangeToTravelFlight(uint32 flightMasterEntry, WorldPosition flightMasterPos, std::vector<uint32> path);
void ChangeToOutdoorPvp(ObjectGuid::LowType capturePointSpawnId = 0); void ChangeToOutdoorPvp(ObjectGuid::LowType capturePointSpawnId = 0);
void ChangeToRest(); void ChangeToRest();
void ChangeToIdle(); void ChangeToIdle();

View File

@ -619,7 +619,7 @@ void RandomPlayerbotFactory::CreateRandomBots()
else else
password = accountName; password = accountName;
AccountMgr::CreateAccount(accountName, password); sAccountMgr->CreateAccount(accountName, password);
LOG_DEBUG("playerbots", "Account {} created for random bots", accountName.c_str()); LOG_DEBUG("playerbots", "Account {} created for random bots", accountName.c_str());
} }

View File

@ -54,9 +54,9 @@
#include "Unit.h" #include "Unit.h"
#include "UpdateTime.h" #include "UpdateTime.h"
#include "Vehicle.h" #include "Vehicle.h"
#include "../../../../src/server/scripts/Spells/spell_dk.cpp"
const int SPELL_TITAN_GRIP = 49152; constexpr uint32 SPELL_TITAN_GRIP = 49152;
constexpr uint32 SPELL_DK_FROST_PRESENCE = 48263;
std::vector<std::string> PlayerbotAI::dispel_whitelist = { std::vector<std::string> PlayerbotAI::dispel_whitelist = {
"mutating injection", "mutating injection",

View File

@ -2077,7 +2077,7 @@ void RandomPlayerbotMgr::Refresh(Player* bot)
bot->DurabilityRepairAll(false, 1.0f, false); bot->DurabilityRepairAll(false, 1.0f, false);
bot->SetFullHealth(); bot->SetFullHealth();
bot->SetPvP(true); bot->SetPvP(sWorld->IsPvPRealm());
PlayerbotFactory factory(bot, bot->GetLevel()); PlayerbotFactory factory(bot, bot->GetLevel());
factory.Refresh(); factory.Refresh();
@ -2642,6 +2642,7 @@ void RandomPlayerbotMgr::OnPlayerLogin(Player* player)
{ {
// ObjectGuid::LowType guid = player->GetGUID().GetCounter(); //not used, conditional could be rewritten for // ObjectGuid::LowType guid = player->GetGUID().GetCounter(); //not used, conditional could be rewritten for
// simplicity. line marked for removal. // simplicity. line marked for removal.
player->SetPvP(sWorld->IsPvPRealm());
} }
else else
{ {

View File

@ -8,6 +8,7 @@
#include <iomanip> #include <iomanip>
#include <numeric> #include <numeric>
#include "AreaDefines.h"
#include "Creature.h" #include "Creature.h"
#include "Log.h" #include "Log.h"
#include "ObjectAccessor.h" #include "ObjectAccessor.h"
@ -28,67 +29,60 @@
// Navigation data // Navigation data
enum class CityId : uint8 struct Capital
{ {
STORMWIND, uint32 zoneId;
IRONFORGE, TeamId team;
DARNASSUS, char const* name;
EXODAR, std::vector<uint16> bankers;
ORGRIMMAR,
UNDERCITY,
THUNDER_BLUFF,
SILVERMOON_CITY,
SHATTRATH_CITY,
DALARAN
}; };
static const std::unordered_map<uint16, std::pair<CityId, TeamId>> bankerToCity = { static const std::vector<Capital> capitals = {
{2455, {CityId::STORMWIND, TEAM_ALLIANCE}}, {2456, {CityId::STORMWIND, TEAM_ALLIANCE}}, {2457, {CityId::STORMWIND, TEAM_ALLIANCE}}, { AREA_STORMWIND_CITY, TEAM_ALLIANCE, "Stormwind", {2455, 2456, 2457} },
{2460, {CityId::IRONFORGE, TEAM_ALLIANCE}}, {2461, {CityId::IRONFORGE, TEAM_ALLIANCE}}, {5099, {CityId::IRONFORGE, TEAM_ALLIANCE}}, { AREA_IRONFORGE, TEAM_ALLIANCE, "Ironforge", {2460, 2461, 5099} },
{4155, {CityId::DARNASSUS, TEAM_ALLIANCE}}, {4208, {CityId::DARNASSUS, TEAM_ALLIANCE}}, {4209, {CityId::DARNASSUS, TEAM_ALLIANCE}}, { AREA_DARNASSUS, TEAM_ALLIANCE, "Darnassus", {4155, 4208, 4209} },
{17773, {CityId::EXODAR, TEAM_ALLIANCE}}, {18350, {CityId::EXODAR, TEAM_ALLIANCE}}, {16710, {CityId::EXODAR, TEAM_ALLIANCE}}, { AREA_THE_EXODAR, TEAM_ALLIANCE, "Exodar", {17773, 18350, 16710} },
{3320, {CityId::ORGRIMMAR, TEAM_HORDE}}, {3309, {CityId::ORGRIMMAR, TEAM_HORDE}}, {3318, {CityId::ORGRIMMAR, TEAM_HORDE}}, { AREA_ORGRIMMAR, TEAM_HORDE, "Orgrimmar", {3320, 3309, 3318} },
{4549, {CityId::UNDERCITY, TEAM_HORDE}}, {2459, {CityId::UNDERCITY, TEAM_HORDE}}, {2458, {CityId::UNDERCITY, TEAM_HORDE}}, {4550, {CityId::UNDERCITY, TEAM_HORDE}}, { AREA_UNDERCITY, TEAM_HORDE, "Undercity", {4549, 2459, 2458, 4550} },
{2996, {CityId::THUNDER_BLUFF, TEAM_HORDE}}, {8356, {CityId::THUNDER_BLUFF, TEAM_HORDE}}, {8357, {CityId::THUNDER_BLUFF, TEAM_HORDE}}, { AREA_THUNDER_BLUFF, TEAM_HORDE, "Thunder Bluff", {2996, 8356, 8357} },
{17631, {CityId::SILVERMOON_CITY, TEAM_HORDE}}, {17632, {CityId::SILVERMOON_CITY, TEAM_HORDE}}, {17633, {CityId::SILVERMOON_CITY, TEAM_HORDE}}, { AREA_SILVERMOON_CITY, TEAM_HORDE, "Silvermoon", {17631, 17632, 17633, 16615, 16616, 16617} },
{16615, {CityId::SILVERMOON_CITY, TEAM_HORDE}}, {16616, {CityId::SILVERMOON_CITY, TEAM_HORDE}}, {16617, {CityId::SILVERMOON_CITY, TEAM_HORDE}}, { AREA_SHATTRATH_CITY, TEAM_NEUTRAL, "Shattrath", {19246, 19338, 19034, 19318} },
{19246, {CityId::SHATTRATH_CITY, TEAM_NEUTRAL}}, {19338, {CityId::SHATTRATH_CITY, TEAM_NEUTRAL}}, { AREA_DALARAN, TEAM_NEUTRAL, "Dalaran", {30604, 30605, 30607, 28675, 28676, 28677, 29530} }
{19034, {CityId::SHATTRATH_CITY, TEAM_NEUTRAL}}, {19318, {CityId::SHATTRATH_CITY, TEAM_NEUTRAL}},
{30604, {CityId::DALARAN, TEAM_NEUTRAL}}, {30605, {CityId::DALARAN, TEAM_NEUTRAL}}, {30607, {CityId::DALARAN, TEAM_NEUTRAL}},
{28675, {CityId::DALARAN, TEAM_NEUTRAL}}, {28676, {CityId::DALARAN, TEAM_NEUTRAL}}, {28677, {CityId::DALARAN, TEAM_NEUTRAL}}
}; };
static const std::unordered_map<CityId, std::vector<uint16>> cityToBankers = { static Capital const* FindCapitalByZone(uint32 zoneId)
{CityId::STORMWIND, {2455, 2456, 2457}},
{CityId::IRONFORGE, {2460, 2461, 5099}},
{CityId::DARNASSUS, {4155, 4208, 4209}},
{CityId::EXODAR, {17773, 18350, 16710}},
{CityId::ORGRIMMAR, {3320, 3309, 3318}},
{CityId::UNDERCITY, {4549, 2459, 2458, 4550}},
{CityId::THUNDER_BLUFF, {2996, 8356, 8357}},
{CityId::SILVERMOON_CITY, {17631, 17632, 17633, 16615, 16616, 16617}},
{CityId::SHATTRATH_CITY, {19246, 19338, 19034, 19318}},
{CityId::DALARAN, {30604, 30605, 30607, 28675, 28676, 28677, 29530}}
};
static int GetCityWeight(CityId city)
{ {
int weight = 0; for (Capital const& capital : capitals)
switch (city) if (capital.zoneId == zoneId)
return &capital;
return nullptr;
}
static Capital const* FindCapitalByBanker(uint16 bankerEntry)
{
for (Capital const& capital : capitals)
for (uint16 bankerId : capital.bankers)
if (bankerId == bankerEntry)
return &capital;
return nullptr;
}
static int GetCityWeight(uint32 zoneId)
{
switch (zoneId)
{ {
case CityId::STORMWIND: weight = sPlayerbotAIConfig.weightTeleToStormwind; break; case AREA_STORMWIND_CITY: return sPlayerbotAIConfig.weightTeleToStormwind;
case CityId::IRONFORGE: weight = sPlayerbotAIConfig.weightTeleToIronforge; break; case AREA_IRONFORGE: return sPlayerbotAIConfig.weightTeleToIronforge;
case CityId::DARNASSUS: weight = sPlayerbotAIConfig.weightTeleToDarnassus; break; case AREA_DARNASSUS: return sPlayerbotAIConfig.weightTeleToDarnassus;
case CityId::EXODAR: weight = sPlayerbotAIConfig.weightTeleToExodar; break; case AREA_THE_EXODAR: return sPlayerbotAIConfig.weightTeleToExodar;
case CityId::ORGRIMMAR: weight = sPlayerbotAIConfig.weightTeleToOrgrimmar; break; case AREA_ORGRIMMAR: return sPlayerbotAIConfig.weightTeleToOrgrimmar;
case CityId::UNDERCITY: weight = sPlayerbotAIConfig.weightTeleToUndercity; break; case AREA_UNDERCITY: return sPlayerbotAIConfig.weightTeleToUndercity;
case CityId::THUNDER_BLUFF: weight = sPlayerbotAIConfig.weightTeleToThunderBluff; break; case AREA_THUNDER_BLUFF: return sPlayerbotAIConfig.weightTeleToThunderBluff;
case CityId::SILVERMOON_CITY: weight = sPlayerbotAIConfig.weightTeleToSilvermoonCity; break; case AREA_SILVERMOON_CITY: return sPlayerbotAIConfig.weightTeleToSilvermoonCity;
case CityId::SHATTRATH_CITY: weight = sPlayerbotAIConfig.weightTeleToShattrathCity; break; case AREA_SHATTRATH_CITY: return sPlayerbotAIConfig.weightTeleToShattrathCity;
case CityId::DALARAN: weight = sPlayerbotAIConfig.weightTeleToDalaran; break; case AREA_DALARAN: return sPlayerbotAIConfig.weightTeleToDalaran;
default: weight = 0; break;
} }
return weight; return 0;
} }
WorldPosition::WorldPosition(std::string const str) WorldPosition::WorldPosition(std::string const str)
@ -4369,76 +4363,117 @@ void TravelMgr::Init()
LOG_INFO("playerbots", "Playerbots Taxi graph and destination cache built."); LOG_INFO("playerbots", "Playerbots Taxi graph and destination cache built.");
} }
Creature* TravelMgr::GetNearestFlightMaster(Player* bot) TravelMgr::FlightMasterInfo const* TravelMgr::GetNearestFlightMasterInfo(Player* bot) const
{ {
std::map<uint32, WorldPosition>& flightMasterCache = auto const& flightMasterCache =
(bot->GetTeamId() == TEAM_ALLIANCE) ? allianceFlightMasterCache : hordeFlightMasterCache; (bot->GetTeamId() == TEAM_ALLIANCE) ? allianceFlightMasterCache : hordeFlightMasterCache;
Creature* nearestFlightMaster = nullptr; FlightMasterInfo const* nearest = nullptr;
float nearestDistance = std::numeric_limits<float>::max(); float nearestDistance = std::numeric_limits<float>::max();
for (auto const& [entry, pos] : flightMasterCache) for (auto const& [dbGuid, info] : flightMasterCache)
{ {
if (pos.GetMapId() != bot->GetMapId()) if (info.pos.GetMapId() != bot->GetMapId())
continue; continue;
float distance = bot->GetExactDist2dSq(pos); float distance = bot->GetExactDist2dSq(info.pos);
if (distance > nearestDistance) if (distance < nearestDistance)
continue;
Creature* flightMaster = ObjectAccessor::GetSpawnedCreatureByDBGUID(bot->GetMapId(), entry);
if (flightMaster)
{ {
nearestDistance = distance; nearestDistance = distance;
nearestFlightMaster = flightMaster; nearest = &info;
} }
} }
return nearestFlightMaster; return nearest;
} }
ObjectGuid TravelMgr::GetNearestFlightMasterGuid(Player* bot) std::vector<uint32> TravelMgr::GetFlightNodesInZone(uint32 zoneId, TeamId team, uint32 excludeNode) const
{ {
Creature* nearestFlightMaster = GetNearestFlightMaster(bot); auto const& cache = (team == TEAM_ALLIANCE) ? allianceFlightMasterCache : hordeFlightMasterCache;
if (!nearestFlightMaster) std::unordered_set<uint32> seen;
return ObjectGuid::Empty; std::vector<uint32> result;
for (auto const& [entry, info] : cache)
return nearestFlightMaster->GetGUID(); {
if (info.zoneId != zoneId || info.taxiNodeId == 0 || info.taxiNodeId == excludeNode)
continue;
if (seen.insert(info.taxiNodeId).second)
result.push_back(info.taxiNodeId);
}
return result;
} }
std::vector<std::vector<uint32>> TravelMgr::GetOptimalFlightDestinations(Player* bot) std::vector<std::vector<uint32>> TravelMgr::GetOptimalFlightDestinations(Player* bot)
{ {
std::vector<std::vector<uint32>> validDestinations; std::vector<std::vector<uint32>> validDestinations;
Creature* nearestFlightMaster = GetNearestFlightMaster(bot); FlightMasterInfo const* nearestFlightMaster = GetNearestFlightMasterInfo(bot);
if (!nearestFlightMaster || bot->GetDistance(nearestFlightMaster) > 500.0f) if (!nearestFlightMaster || bot->GetDistance(nearestFlightMaster->pos) > 500.0f)
return validDestinations; return validDestinations;
uint32 fromNode = sObjectMgr->GetNearestTaxiNode(nearestFlightMaster->GetPositionX(), nearestFlightMaster->GetPositionY(), uint32 fromNode = nearestFlightMaster->taxiNodeId;
nearestFlightMaster->GetPositionZ(), nearestFlightMaster->GetMapId(),
bot->GetTeamId());
if (!fromNode) if (!fromNode)
return validDestinations; return validDestinations;
std::vector<WorldLocation> candidateLocations; TaxiNodesEntry const* startNode = sTaxiNodesStore.LookupEntry(fromNode);
if (bot->GetLevel() >= 10 && urand(0, 100) < sPlayerbotAIConfig.probTeleToBankers * 100) if (!startNode)
candidateLocations = GetCityLocations(bot); return validDestinations;
std::vector<WorldLocation> hubLocations = GetTravelHubs(bot); uint32 botLevel = bot->GetLevel();
candidateLocations.insert(candidateLocations.end(), hubLocations.begin(), hubLocations.end());
for (auto const& loc : candidateLocations) // Bots already in a capital shouldn't have another capital picked as a
// flight destination — that just shuffles them between cities.
bool botInCapital = false;
if (AreaTableEntry const* area = sAreaTableStore.LookupEntry(bot->GetZoneId()))
botInCapital = (area->flags & AREA_FLAG_CAPITAL) != 0;
//Simplify destination delection. Its either target cities (Based on config value) or target world.
std::vector<uint32> candidateZones;
if (botLevel >= 10 && !botInCapital && urand(0, 100) < sPlayerbotAIConfig.probTeleToBankers * 100)
{ {
uint32 candidateNode = sObjectMgr->GetNearestTaxiNode(loc.GetPositionX(), loc.GetPositionY(), TeamId botTeam = bot->GetTeamId();
loc.GetPositionZ(), loc.GetMapId(), for (Capital const& capital : capitals)
bot->GetTeamId()); {
if (!candidateNode) if (capital.team != TEAM_NEUTRAL && capital.team != botTeam)
continue; continue;
candidateZones.push_back(capital.zoneId);
std::vector<uint32> path = sTravelNodeMap.FindTaxiPath(fromNode, candidateNode); }
if (!path.empty())
validDestinations.push_back(path);
} }
if (candidateZones.empty())
{
for (auto const& [zoneId, bracket] : zone2LevelBracket)
{
if (botLevel < bracket.low || botLevel > bracket.high)
continue;
if (GetFlightNodesInZone(zoneId, bot->GetTeamId(), fromNode).empty())
continue;
candidateZones.push_back(zoneId);
}
}
if (candidateZones.empty())
return validDestinations;
while (!candidateZones.empty())
{
uint32 zoneIndex = urand(0, candidateZones.size() - 1);
uint32 pickedZone = candidateZones[zoneIndex];
std::vector<uint32> usableNodes = GetFlightNodesInZone(pickedZone, bot->GetTeamId(), fromNode);
if (!usableNodes.empty())
{
uint32 pickedNode = usableNodes[urand(0, usableNodes.size() - 1)];
std::vector<uint32> path = sTravelNodeMap.FindTaxiPath(fromNode, pickedNode);
if (!path.empty())
{
validDestinations.push_back(std::move(path));
return validDestinations;
}
}
candidateZones.erase(candidateZones.begin() + zoneIndex);
}
return validDestinations; return validDestinations;
} }
@ -4472,34 +4507,34 @@ std::vector<WorldLocation> TravelMgr::GetCityLocations(Player* bot)
return fallbackLocations; return fallbackLocations;
TeamId botTeamId = bot->GetTeamId(); TeamId botTeamId = bot->GetTeamId();
std::unordered_set<CityId> validBankerCities; std::unordered_set<uint32> validBankerCities;
for (auto& loc : bankerLocsPerLevelCache[level]) for (auto& loc : bankerLocsPerLevelCache[level])
{ {
auto cityIt = bankerToCity.find(loc.entry); Capital const* capital = FindCapitalByBanker(loc.entry);
if (cityIt == bankerToCity.end()) if (!capital)
continue; continue;
TeamId cityTeamId = cityIt->second.second; TeamId cityTeamId = capital->team;
if (cityTeamId == botTeamId || if (cityTeamId == botTeamId ||
(cityTeamId == TEAM_NEUTRAL) (cityTeamId == TEAM_NEUTRAL)
) )
validBankerCities.insert(cityIt->second.first); validBankerCities.insert(capital->zoneId);
} }
// Fallback if no valid cities // Fallback if no valid cities
if (validBankerCities.empty()) if (validBankerCities.empty())
return fallbackLocations; return fallbackLocations;
// Apply weights to valid cities // Apply weights to valid cities
std::vector<CityId> weightedCities; std::vector<uint32> weightedCities;
for (CityId city : validBankerCities) for (uint32 zoneId : validBankerCities)
{ {
int weight = GetCityWeight(city); int weight = GetCityWeight(zoneId);
if (weight <= 0) if (weight <= 0)
continue; continue;
for (int i = 0; i < weight; ++i) for (int i = 0; i < weight; ++i)
weightedCities.push_back(city); weightedCities.push_back(zoneId);
} }
// Fallback if no valid cities // Fallback if no valid cities
@ -4507,9 +4542,11 @@ std::vector<WorldLocation> TravelMgr::GetCityLocations(Player* bot)
return fallbackLocations; return fallbackLocations;
// Pick a weighted city randomly, then a random banker in that city // Pick a weighted city randomly, then a random banker in that city
CityId selectedCity = weightedCities[urand(0, weightedCities.size() - 1)]; uint32 selectedCity = weightedCities[urand(0, weightedCities.size() - 1)];
Capital const* selectedCapital = FindCapitalByZone(selectedCity);
auto const& bankers = cityToBankers.at(selectedCity); if (!selectedCapital)
return fallbackLocations;
auto const& bankers = selectedCapital->bankers;
uint32 selectedBankerEntry = bankers[urand(0, bankers.size() - 1)]; uint32 selectedBankerEntry = bankers[urand(0, bankers.size() - 1)];
auto locIt = bankerEntryToLocation.find(selectedBankerEntry); auto locIt = bankerEntryToLocation.find(selectedBankerEntry);
if (locIt != bankerEntryToLocation.end()) if (locIt != bankerEntryToLocation.end())
@ -4520,78 +4557,78 @@ std::vector<WorldLocation> TravelMgr::GetCityLocations(Player* bot)
void TravelMgr::PrepareZone2LevelBracket() void TravelMgr::PrepareZone2LevelBracket()
{ {
// Classic WoW - Low - level zones // Classic WoW - starter zones
zone2LevelBracket[1] = {5, 12}; // Dun Morogh zone2LevelBracket[AREA_DUN_MOROGH] = {5, 12};
zone2LevelBracket[12] = {5, 12}; // Elwynn Forest zone2LevelBracket[AREA_ELWYNN_FOREST] = {5, 12};
zone2LevelBracket[14] = {5, 12}; // Durotar zone2LevelBracket[AREA_DUROTAR] = {5, 12};
zone2LevelBracket[85] = {5, 12}; // Tirisfal Glades zone2LevelBracket[AREA_TIRISFAL_GLADES] = {5, 12};
zone2LevelBracket[141] = {5, 12}; // Teldrassil zone2LevelBracket[AREA_TELDRASSIL] = {5, 12};
zone2LevelBracket[215] = {5, 12}; // Mulgore zone2LevelBracket[AREA_MULGORE] = {5, 12};
zone2LevelBracket[3430] = {5, 12}; // Eversong Woods zone2LevelBracket[AREA_EVERSONG_WOODS] = {5, 12};
zone2LevelBracket[3524] = {5, 12}; // Azuremyst Isle zone2LevelBracket[AREA_AZUREMYST_ISLE] = {5, 12};
// Classic WoW - Mid - level zones // Classic WoW - low level zones
zone2LevelBracket[17] = {10, 25}; // Barrens zone2LevelBracket[AREA_THE_BARRENS] = {10, 25};
zone2LevelBracket[38] = {10, 20}; // Loch Modan zone2LevelBracket[AREA_LOCH_MODAN] = {10, 20};
zone2LevelBracket[40] = {10, 21}; // Westfall zone2LevelBracket[AREA_WESTFALL] = {10, 21};
zone2LevelBracket[130] = {10, 23}; // Silverpine Forest zone2LevelBracket[AREA_SILVERPINE_FOREST] = {10, 23};
zone2LevelBracket[148] = {10, 21}; // Darkshore zone2LevelBracket[AREA_DARKSHORE] = {10, 21};
zone2LevelBracket[3433] = {10, 22}; // Ghostlands zone2LevelBracket[AREA_GHOSTLANDS] = {10, 22};
zone2LevelBracket[3525] = {10, 21}; // Bloodmyst Isle zone2LevelBracket[AREA_BLOODMYST_ISLE] = {10, 21};
// Classic WoW - High - level zones // Classic WoW - mid-level zones
zone2LevelBracket[10] = {19, 33}; // Deadwind Pass zone2LevelBracket[AREA_DUSKWOOD] = {19, 33};
zone2LevelBracket[11] = {21, 30}; // Wetlands zone2LevelBracket[AREA_WETLANDS] = {21, 30};
zone2LevelBracket[44] = {16, 28}; // Redridge Mountains zone2LevelBracket[AREA_REDRIDGE_MOUNTAINS] = {16, 28};
zone2LevelBracket[267] = {20, 34}; // Hillsbrad Foothills zone2LevelBracket[AREA_HILLSBRAD_FOOTHILLS] = {20, 34};
zone2LevelBracket[331] = {18, 33}; // Ashenvale zone2LevelBracket[AREA_ASHENVALE] = {18, 33};
zone2LevelBracket[400] = {24, 36}; // Thousand Needles zone2LevelBracket[AREA_THOUSAND_NEEDLES] = {24, 36};
zone2LevelBracket[406] = {16, 29}; // Stonetalon Mountains zone2LevelBracket[AREA_STONETALON_MOUNTAINS] = {16, 29};
// Classic WoW - Higher - level zones // Classic WoW - 30-52 zones
zone2LevelBracket[3] = {36, 46}; // Badlands zone2LevelBracket[AREA_BADLANDS] = {36, 46};
zone2LevelBracket[8] = {36, 46}; // Swamp of Sorrows zone2LevelBracket[AREA_SWAMP_OF_SORROWS] = {36, 46};
zone2LevelBracket[15] = {35, 46}; // Dustwallow Marsh zone2LevelBracket[AREA_DUSTWALLOW_MARSH] = {35, 46};
zone2LevelBracket[16] = {45, 52}; // Azshara zone2LevelBracket[AREA_AZSHARA] = {45, 52};
zone2LevelBracket[33] = {32, 47}; // Stranglethorn Vale zone2LevelBracket[AREA_STRANGLETHORN_VALE] = {32, 47};
zone2LevelBracket[45] = {30, 42}; // Arathi Highlands zone2LevelBracket[AREA_ARATHI_HIGHLANDS] = {30, 42};
zone2LevelBracket[47] = {42, 51}; // Hinterlands zone2LevelBracket[AREA_THE_HINTERLANDS] = {42, 51};
zone2LevelBracket[51] = {45, 51}; // Searing Gorge zone2LevelBracket[AREA_SEARING_GORGE] = {45, 51};
zone2LevelBracket[357] = {40, 52}; // Feralas zone2LevelBracket[AREA_FERALAS] = {40, 52};
zone2LevelBracket[405] = {30, 41}; // Desolace zone2LevelBracket[AREA_DESOLACE] = {30, 41};
zone2LevelBracket[440] = {41, 52}; // Tanaris zone2LevelBracket[AREA_TANARIS] = {41, 52};
// Classic WoW - Top - level zones // Classic WoW - top level zones
zone2LevelBracket[4] = {52, 57}; // Blasted Lands zone2LevelBracket[AREA_BLASTED_LANDS] = {52, 57};
zone2LevelBracket[28] = {50, 60}; // Western Plaguelands zone2LevelBracket[AREA_WESTERN_PLAGUELANDS] = {50, 60};
zone2LevelBracket[46] = {51, 60}; // Burning Steppes zone2LevelBracket[AREA_BURNING_STEPPES] = {51, 60};
zone2LevelBracket[139] = {54, 62}; // Eastern Plaguelands zone2LevelBracket[AREA_EASTERN_PLAGUELANDS] = {54, 62};
zone2LevelBracket[361] = {47, 57}; // Felwood zone2LevelBracket[361] = {47, 57}; // Felwood (no AREA_ define)
zone2LevelBracket[490] = {49, 56}; // Un'Goro Crater zone2LevelBracket[490] = {49, 56}; // Un'Goro Crater (no AREA_ define)
zone2LevelBracket[618] = {54, 61}; // Winterspring zone2LevelBracket[AREA_WINTERSPRING] = {54, 61};
zone2LevelBracket[1377] = {54, 63}; // Silithus zone2LevelBracket[AREA_SILITHUS] = {54, 63};
// The Burning Crusade - Zones // The Burning Crusade zones
zone2LevelBracket[3483] = {58, 66}; // Hellfire Peninsula zone2LevelBracket[AREA_HELLFIRE_PENINSULA] = {58, 66};
zone2LevelBracket[3518] = {64, 70}; // Nagrand zone2LevelBracket[AREA_NAGRAND] = {64, 70};
zone2LevelBracket[3519] = {62, 73}; // Terokkar Forest zone2LevelBracket[AREA_TEROKKAR_FOREST] = {62, 73};
zone2LevelBracket[3520] = {66, 73}; // Shadowmoon Valley zone2LevelBracket[AREA_SHADOWMOON_VALLEY] = {66, 73};
zone2LevelBracket[3521] = {60, 67}; // Zangarmarsh zone2LevelBracket[AREA_ZANGARMARSH] = {60, 67};
zone2LevelBracket[3522] = {64, 73}; // Blade's Edge Mountains zone2LevelBracket[AREA_BLADES_EDGE_MOUNTAINS] = {64, 73};
zone2LevelBracket[3523] = {67, 73}; // Netherstorm zone2LevelBracket[AREA_NETHERSTORM] = {67, 73};
zone2LevelBracket[4080] = {68, 73}; // Isle of Quel'Danas zone2LevelBracket[AREA_ISLE_OF_QUEL_DANAS] = {68, 73};
// Wrath of the Lich King - Zones // Wrath of the Lich King zones
zone2LevelBracket[65] = {71, 77}; // Dragonblight zone2LevelBracket[AREA_DRAGONBLIGHT] = {71, 77};
zone2LevelBracket[66] = {74, 80}; // Zul'Drak zone2LevelBracket[AREA_ZUL_DRAK] = {74, 80};
zone2LevelBracket[67] = {77, 80}; // Storm Peaks zone2LevelBracket[AREA_THE_STORM_PEAKS] = {77, 80};
zone2LevelBracket[210] = {77, 80}; // Icecrown Glacier zone2LevelBracket[210] = {77, 80}; // Icecrown Glacier (no AREA_ define)
zone2LevelBracket[394] = {72, 78}; // Grizzly Hills zone2LevelBracket[AREA_GRIZZLY_HILLS] = {72, 78};
zone2LevelBracket[495] = {68, 74}; // Howling Fjord zone2LevelBracket[AREA_HOWLING_FJORD] = {68, 74};
zone2LevelBracket[2817] = {77, 80}; // Crystalsong Forest zone2LevelBracket[AREA_CRYSTALSONG_FOREST] = {77, 80};
zone2LevelBracket[3537] = {68, 75}; // Borean Tundra zone2LevelBracket[AREA_BOREAN_TUNDRA] = {68, 75};
zone2LevelBracket[3711] = {75, 80}; // Sholazar Basin zone2LevelBracket[AREA_SHOLAZAR_BASIN] = {75, 80};
zone2LevelBracket[4197] = {79, 80}; // Wintergrasp zone2LevelBracket[AREA_WINTERGRASP] = {79, 80};
// Override with values from config // Override with values from config
for (auto const& [zoneId, bracketPair] : sPlayerbotAIConfig.zoneBrackets) for (auto const& [zoneId, bracketPair] : sPlayerbotAIConfig.zoneBrackets)
@ -4650,13 +4687,15 @@ void TravelMgr::PrepareDestinationCache()
(creatureTemplate->unit_flags & 4096) == 0 && (creatureTemplate->unit_flags & 4096) == 0 &&
creatureTemplate->rank == 0) creatureTemplate->rank == 0)
{ {
uint32 roundX = (x / 50.0f) * 10.0f; uint32 roundX = static_cast<uint32>(std::round(x / 50.0f));
uint32 roundY = (y / 50.0f) * 10.0f; uint32 roundY = static_cast<uint32>(std::round(y / 50.0f));
uint32 roundZ = (z / 50.0f) * 10.0f; uint32 roundZ = static_cast<uint32>(std::round(z / 50.0f));
tempLocsCache[std::make_tuple(mapId, roundX, roundY, roundZ)].push_back(creatureData); tempLocsCache[std::make_tuple(mapId, roundX, roundY, roundZ)].push_back(creatureData);
tempCreatureCache[templateEntry][areaId].push_back(WorldLocation(mapId, x, y, z)); tempCreatureCache[templateEntry][areaId].push_back(WorldLocation(mapId, x, y, z));
} }
// FLIGHT MASTERS // FLIGHT MASTERS
// Entry 29480 is Grimwing (Storm Peaks)
// Entry 3838 is Vesprystus in Rut'Theran. Need Travel Node system to resolve this one.
else if ((creatureTemplate->npcflag & UNIT_NPC_FLAG_FLIGHTMASTER || else if ((creatureTemplate->npcflag & UNIT_NPC_FLAG_FLIGHTMASTER ||
creatureTemplate->npcflag & UNIT_NPC_FLAG_INNKEEPER) && creatureTemplate->npcflag & UNIT_NPC_FLAG_INNKEEPER) &&
creatureTemplate->Entry != 3838 && creatureTemplate->Entry != 29480) creatureTemplate->Entry != 3838 && creatureTemplate->Entry != 29480)
@ -4669,23 +4708,39 @@ void TravelMgr::PrepareDestinationCache()
{ {
WorldPosition pos(mapId, x, y, z, orient); WorldPosition pos(mapId, x, y, z, orient);
if (forHorde) if (forHorde)
hordeFlightMasterCache[guid] = pos; {
FlightMasterInfo info;
info.pos = pos;
info.zoneId = areaId;
info.taxiNodeId = sObjectMgr->GetNearestTaxiNode(x, y, z, mapId, TEAM_HORDE);
info.templateEntry = templateEntry;
info.dbGuid = guid;
hordeFlightMasterCache[guid] = info;
}
if (forAlliance) if (forAlliance)
allianceFlightMasterCache[guid] = pos; {
FlightMasterInfo info;
info.pos = pos;
info.zoneId = areaId;
info.taxiNodeId = sObjectMgr->GetNearestTaxiNode(x, y, z, mapId, TEAM_ALLIANCE);
info.templateEntry = templateEntry;
info.dbGuid = guid;
allianceFlightMasterCache[guid] = info;
}
flightMastersCount++; flightMastersCount++;
// Zones that have flight masters but no innkeepers — use flight master as hub // Zones that have flight masters but no innkeepers — use flight master as hub
static const std::set<uint32> zonesWithoutInnkeeper = { static const std::set<uint32> zonesWithoutInnkeeper = {
4, // Blasted Lands (52-57) AREA_BLASTED_LANDS,
16, // Azshara (45-52) AREA_AZSHARA,
28, // Western Plaguelands (50-60) AREA_WESTERN_PLAGUELANDS,
46, // Burning Steppes (51-60) AREA_BURNING_STEPPES,
51, // Searing Gorge (45-51) AREA_SEARING_GORGE,
361, // Felwood (47-57) 361, // Felwood (47-57)
490, // Un'Goro Crater (49-56) 490, // Un'Goro Crater (49-56)
2817, // Crystalsong Forest (77-80) AREA_CRYSTALSONG_FOREST,
4197 // Wintergrasp (79-80) AREA_WINTERGRASP
}; };
if (zonesWithoutInnkeeper.count(areaId)) if (zonesWithoutInnkeeper.count(areaId))
{ {
@ -4756,7 +4811,7 @@ void TravelMgr::PrepareDestinationCache()
// Process temporary caches // Process temporary caches
for (auto const& [gridTuple, creatureDataList] : tempLocsCache) for (auto const& [gridTuple, creatureDataList] : tempLocsCache)
{ {
if (creatureDataList.size() > 2) if (creatureDataList.size() >= 2)
{ {
CreatureTemplate const* creatureTemplate = sObjectMgr->GetCreatureTemplate(creatureDataList[0].id1); CreatureTemplate const* creatureTemplate = sObjectMgr->GetCreatureTemplate(creatureDataList[0].id1);
uint32 level = (creatureTemplate->minlevel + creatureTemplate->maxlevel + 1) / 2; uint32 level = (creatureTemplate->minlevel + creatureTemplate->maxlevel + 1) / 2;

View File

@ -846,6 +846,21 @@ protected:
class TravelMgr class TravelMgr
{ {
public: public:
struct NpcLocation
{
WorldLocation loc;
uint32 entry;
};
struct FlightMasterInfo
{
WorldPosition pos;
uint32 zoneId; // resolved once at cache load
uint32 taxiNodeId; // DBC taxi node nearest to this flight master
uint32 templateEntry; // creature template ID (for ObjectGuid construction)
uint32 dbGuid; // DB spawn GUID (for ObjectGuid construction)
};
static TravelMgr& instance() static TravelMgr& instance()
{ {
static TravelMgr instance; static TravelMgr instance;
@ -858,12 +873,14 @@ public:
// Navigation // Navigation
void Init(); void Init();
Creature* GetNearestFlightMaster(Player* bot);
ObjectGuid GetNearestFlightMasterGuid(Player* bot); FlightMasterInfo const* GetNearestFlightMasterInfo(Player* bot) const;
std::vector<std::vector<uint32>> GetOptimalFlightDestinations(Player* bot); std::vector<std::vector<uint32>> GetOptimalFlightDestinations(Player* bot);
const std::vector<WorldLocation> GetTeleportLocations(Player* bot); const std::vector<WorldLocation> GetTeleportLocations(Player* bot);
const std::vector<WorldLocation> GetTravelHubs(Player* bot); const std::vector<WorldLocation> GetTravelHubs(Player* bot);
std::vector<WorldLocation> GetCityLocations(Player* bot); std::vector<WorldLocation> GetCityLocations(Player* bot);
std::vector<uint32> GetFlightNodesInZone(uint32 zoneId, TeamId team, uint32 excludeNode = 0) const;
bool SelectAuctioneerByMap(Player* bot, NpcLocation& outAuctioneer);
const std::vector<WorldLocation>& GetLocsPerLevelCache(uint8 level) { return locsPerLevelCache[level]; } const std::vector<WorldLocation>& GetLocsPerLevelCache(uint8 level) { return locsPerLevelCache[level]; }
template <class D, class W, class URBG> template <class D, class W, class URBG>
@ -975,8 +992,8 @@ private:
}; };
// Navigation caches // Navigation caches
std::map<uint32, WorldPosition> allianceFlightMasterCache; std::map<uint32, FlightMasterInfo> allianceFlightMasterCache;
std::map<uint32, WorldPosition> hordeFlightMasterCache; std::map<uint32, FlightMasterInfo> hordeFlightMasterCache;
std::map<uint8, std::vector<WorldLocation>> allianceHubsPerLevelCache; std::map<uint8, std::vector<WorldLocation>> allianceHubsPerLevelCache;
std::map<uint8, std::vector<WorldLocation>> hordeHubsPerLevelCache; std::map<uint8, std::vector<WorldLocation>> hordeHubsPerLevelCache;
std::map<uint8, std::vector<BankerLocation>> bankerLocsPerLevelCache; std::map<uint8, std::vector<BankerLocation>> bankerLocsPerLevelCache;

View File

@ -2467,7 +2467,7 @@ std::vector<uint32> TravelNodeMap::FindTaxiPath(uint32 fromNode, uint32 toNode)
TaxiNodesEntry const* startNode = sTaxiNodesStore.LookupEntry(fromNode); TaxiNodesEntry const* startNode = sTaxiNodesStore.LookupEntry(fromNode);
TaxiNodesEntry const* endNode = sTaxiNodesStore.LookupEntry(toNode); TaxiNodesEntry const* endNode = sTaxiNodesStore.LookupEntry(toNode);
if (!startNode || !endNode || startNode->map_id != endNode->map_id) if (!startNode || !endNode)
return {}; return {};
auto cacheItr = taxiPathCache.find(fromNode); auto cacheItr = taxiPathCache.find(fromNode);