mirror of
https://github.com/mod-playerbots/mod-playerbots.git
synced 2026-02-08 05:01:09 +00:00
Merge remote-tracking branch 'upstream/master' into karazhan
This commit is contained in:
@@ -91,6 +91,9 @@ uint8 AiFactory::GetPlayerSpecTab(Player* bot)
|
||||
case CLASS_WARLOCK:
|
||||
tab = WARLOCK_TAB_DEMONOLOGY;
|
||||
break;
|
||||
case CLASS_SHAMAN:
|
||||
tab = SHAMAN_TAB_ELEMENTAL;
|
||||
break;
|
||||
}
|
||||
|
||||
return tab;
|
||||
@@ -304,9 +307,9 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
|
||||
engine->addStrategiesNoInit("dps assist", "cure", nullptr);
|
||||
break;
|
||||
case CLASS_MAGE:
|
||||
if (tab == 0)
|
||||
if (tab == 0) // Arcane
|
||||
engine->addStrategiesNoInit("arcane", nullptr);
|
||||
else if (tab == 1)
|
||||
else if (tab == 1) // Fire
|
||||
{
|
||||
if (player->HasSpell(44614) /*Frostfire Bolt*/ && player->HasAura(15047) /*Ice Shards*/)
|
||||
{
|
||||
@@ -316,8 +319,8 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
|
||||
{
|
||||
engine->addStrategiesNoInit("fire", nullptr);
|
||||
}
|
||||
}
|
||||
else
|
||||
}
|
||||
else // Frost
|
||||
engine->addStrategiesNoInit("frost", nullptr);
|
||||
|
||||
engine->addStrategiesNoInit("dps", "dps assist", "cure", "aoe", nullptr);
|
||||
@@ -331,14 +334,14 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
|
||||
engine->addStrategiesNoInit("fury", "aoe", "dps assist", /*"behind",*/ nullptr);
|
||||
break;
|
||||
case CLASS_SHAMAN:
|
||||
if (tab == 0)
|
||||
engine->addStrategiesNoInit("caster", "caster aoe", "bmana", nullptr);
|
||||
else if (tab == 2)
|
||||
engine->addStrategiesNoInit("heal", "bmana", nullptr);
|
||||
else
|
||||
engine->addStrategiesNoInit("melee", "melee aoe", "bdps", nullptr);
|
||||
if (tab == 0) // Elemental
|
||||
engine->addStrategiesNoInit("ele", "stoneskin", "wrath", "mana spring", "wrath of air", nullptr);
|
||||
else if (tab == 2) // Restoration
|
||||
engine->addStrategiesNoInit("resto", "stoneskin", "flametongue", "mana spring", "wrath of air", nullptr);
|
||||
else // Enhancement
|
||||
engine->addStrategiesNoInit("enh", "strength of earth", "magma", "healing stream", "windfury", nullptr);
|
||||
|
||||
engine->addStrategiesNoInit("dps assist", "cure", "totems", nullptr);
|
||||
engine->addStrategiesNoInit("dps assist", "cure", "aoe", nullptr);
|
||||
break;
|
||||
case CLASS_PALADIN:
|
||||
if (tab == 1)
|
||||
|
||||
@@ -8,6 +8,18 @@
|
||||
#include "Group.h"
|
||||
#include "Playerbots.h"
|
||||
#include "RtiTargetValue.h"
|
||||
#include "AiFactory.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <string>
|
||||
|
||||
static std::string ToLower(const std::string& str)
|
||||
{
|
||||
std::string out = str;
|
||||
std::transform(out.begin(), out.end(), out.begin(), [](unsigned char c){ return std::tolower(c); });
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string const ChatFilter::Filter(std::string& message)
|
||||
{
|
||||
@@ -25,32 +37,33 @@ public:
|
||||
std::string const Filter(std::string& message) override
|
||||
{
|
||||
Player* bot = botAI->GetBot();
|
||||
std::string msgLower = ToLower(message);
|
||||
|
||||
bool tank = message.find("@tank") == 0;
|
||||
bool tank = msgLower.find("@tank") == 0;
|
||||
if (tank && !botAI->IsTank(bot))
|
||||
return "";
|
||||
|
||||
bool dps = message.find("@dps") == 0;
|
||||
bool dps = msgLower.find("@dps") == 0;
|
||||
if (dps && (botAI->IsTank(bot) || botAI->IsHeal(bot)))
|
||||
return "";
|
||||
|
||||
bool heal = message.find("@heal") == 0;
|
||||
bool heal = msgLower.find("@heal") == 0;
|
||||
if (heal && !botAI->IsHeal(bot))
|
||||
return "";
|
||||
|
||||
bool ranged = message.find("@ranged") == 0;
|
||||
bool ranged = msgLower.find("@ranged") == 0;
|
||||
if (ranged && !botAI->IsRanged(bot))
|
||||
return "";
|
||||
|
||||
bool melee = message.find("@melee") == 0;
|
||||
bool melee = msgLower.find("@melee") == 0;
|
||||
if (melee && botAI->IsRanged(bot))
|
||||
return "";
|
||||
|
||||
bool rangeddps = message.find("@rangeddps") == 0;
|
||||
bool rangeddps = msgLower.find("@rangeddps") == 0;
|
||||
if (rangeddps && (!botAI->IsRanged(bot) || botAI->IsTank(bot) || botAI->IsHeal(bot)))
|
||||
return "";
|
||||
|
||||
bool meleedps = message.find("@meleedps") == 0;
|
||||
bool meleedps = msgLower.find("@meleedps") == 0;
|
||||
if (meleedps && (!botAI->IsMelee(bot) || botAI->IsTank(bot) || botAI->IsHeal(bot)))
|
||||
return "";
|
||||
|
||||
@@ -69,11 +82,11 @@ public:
|
||||
std::string const Filter(std::string& message) override
|
||||
{
|
||||
Player* bot = botAI->GetBot();
|
||||
|
||||
if (message[0] != '@')
|
||||
std::string msgLower = ToLower(message);
|
||||
if (msgLower[0] != '@')
|
||||
return message;
|
||||
|
||||
if (message.find("-") != std::string::npos)
|
||||
if (msgLower.find("-") != std::string::npos)
|
||||
{
|
||||
uint32 fromLevel = atoi(message.substr(message.find("@") + 1, message.find("-")).c_str());
|
||||
uint32 toLevel = atoi(message.substr(message.find("-") + 1, message.find(" ")).c_str());
|
||||
@@ -100,9 +113,10 @@ public:
|
||||
std::string const Filter(std::string& message) override
|
||||
{
|
||||
Player* bot = botAI->GetBot();
|
||||
std::string msgLower = ToLower(message);
|
||||
|
||||
bool melee = message.find("@melee") == 0;
|
||||
bool ranged = message.find("@ranged") == 0;
|
||||
bool melee = msgLower.find("@melee") == 0;
|
||||
bool ranged = msgLower.find("@ranged") == 0;
|
||||
|
||||
if (!melee && !ranged)
|
||||
return message;
|
||||
@@ -159,17 +173,17 @@ public:
|
||||
std::string const Filter(std::string& message) override
|
||||
{
|
||||
Player* bot = botAI->GetBot();
|
||||
std::string msgLower = ToLower(message);
|
||||
Group* group = bot->GetGroup();
|
||||
if (!group)
|
||||
return message;
|
||||
|
||||
bool found = false;
|
||||
//bool isRti = false; //not used, shadowed by the next declaration, line marked for removal.
|
||||
for (std::vector<std::string>::iterator i = rtis.begin(); i != rtis.end(); i++)
|
||||
{
|
||||
std::string const rti = *i;
|
||||
|
||||
bool isRti = message.find(rti) == 0;
|
||||
std::string rtiLower = ToLower(rti);
|
||||
bool isRti = msgLower.find(rtiLower) == 0;
|
||||
if (!isRti)
|
||||
continue;
|
||||
|
||||
@@ -204,7 +218,7 @@ class ClassChatFilter : public ChatFilter
|
||||
public:
|
||||
ClassChatFilter(PlayerbotAI* botAI) : ChatFilter(botAI)
|
||||
{
|
||||
classNames["@death_knight"] = CLASS_DEATH_KNIGHT;
|
||||
classNames["@dk"] = CLASS_DEATH_KNIGHT;
|
||||
classNames["@druid"] = CLASS_DRUID;
|
||||
classNames["@hunter"] = CLASS_HUNTER;
|
||||
classNames["@mage"] = CLASS_MAGE;
|
||||
@@ -219,12 +233,12 @@ public:
|
||||
std::string const Filter(std::string& message) override
|
||||
{
|
||||
Player* bot = botAI->GetBot();
|
||||
std::string msgLower = ToLower(message);
|
||||
|
||||
bool found = false;
|
||||
//bool isClass = false; //not used, shadowed by the next declaration, line marked for removal.
|
||||
for (std::map<std::string, uint8>::iterator i = classNames.begin(); i != classNames.end(); i++)
|
||||
{
|
||||
bool isClass = message.find(i->first) == 0;
|
||||
bool isClass = msgLower.find(ToLower(i->first)) == 0;
|
||||
if (isClass && bot->getClass() != i->second)
|
||||
return "";
|
||||
|
||||
@@ -251,8 +265,8 @@ public:
|
||||
std::string const Filter(std::string& message) override
|
||||
{
|
||||
Player* bot = botAI->GetBot();
|
||||
|
||||
if (message.find("@group") == 0)
|
||||
std::string msgLower = ToLower(message);
|
||||
if (msgLower.find("@group") == 0)
|
||||
{
|
||||
size_t spacePos = message.find(" ");
|
||||
if (spacePos == std::string::npos)
|
||||
@@ -295,6 +309,277 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
class SpecChatFilter : public ChatFilter
|
||||
{
|
||||
public:
|
||||
SpecChatFilter(PlayerbotAI* botAI) : ChatFilter(botAI)
|
||||
{
|
||||
// Map (class, specTab) to spec+class string
|
||||
specTabNames[{CLASS_PALADIN, 0}] = "hpal";
|
||||
specTabNames[{CLASS_PALADIN, 1}] = "ppal";
|
||||
specTabNames[{CLASS_PALADIN, 2}] = "rpal";
|
||||
specTabNames[{CLASS_PRIEST, 0}] = "disc";
|
||||
specTabNames[{CLASS_PRIEST, 1}] = "hpr";
|
||||
specTabNames[{CLASS_PRIEST, 2}] = "spr";
|
||||
specTabNames[{CLASS_MAGE, 0}] = "arc";
|
||||
specTabNames[{CLASS_MAGE, 1}] = "frost";
|
||||
specTabNames[{CLASS_MAGE, 2}] = "fire";
|
||||
specTabNames[{CLASS_WARRIOR, 0}] = "arms";
|
||||
specTabNames[{CLASS_WARRIOR, 1}] = "fury";
|
||||
specTabNames[{CLASS_WARRIOR, 2}] = "pwar";
|
||||
specTabNames[{CLASS_WARLOCK, 0}] = "affl";
|
||||
specTabNames[{CLASS_WARLOCK, 1}] = "demo";
|
||||
specTabNames[{CLASS_WARLOCK, 2}] = "dest";
|
||||
specTabNames[{CLASS_SHAMAN, 0}] = "ele";
|
||||
specTabNames[{CLASS_SHAMAN, 1}] = "enh";
|
||||
specTabNames[{CLASS_SHAMAN, 2}] = "rsha";
|
||||
specTabNames[{CLASS_DRUID, 0}] = "bal";
|
||||
// See below for feral druid
|
||||
specTabNames[{CLASS_DRUID, 2}] = "rdru";
|
||||
specTabNames[{CLASS_HUNTER, 0}] = "bmh";
|
||||
specTabNames[{CLASS_HUNTER, 1}] = "mmh";
|
||||
specTabNames[{CLASS_HUNTER, 2}] = "svh";
|
||||
specTabNames[{CLASS_ROGUE, 0}] = "mut";
|
||||
specTabNames[{CLASS_ROGUE, 1}] = "comb";
|
||||
specTabNames[{CLASS_ROGUE, 2}] = "sub";
|
||||
// See below for blood death knight
|
||||
specTabNames[{CLASS_DEATH_KNIGHT, 1}] = "fdk";
|
||||
specTabNames[{CLASS_DEATH_KNIGHT, 2}] = "udk";
|
||||
}
|
||||
|
||||
std::string const Filter(std::string& message) override
|
||||
{
|
||||
std::string msgLower = ToLower(message);
|
||||
std::string specPrefix;
|
||||
std::string rest;
|
||||
if (!ParseSpecPrefix(message, specPrefix, rest))
|
||||
{
|
||||
return message;
|
||||
}
|
||||
|
||||
Player* bot = botAI->GetBot();
|
||||
if (!MatchesSpec(bot, specPrefix))
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string result = ChatFilter::Filter(rest);
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
std::map<std::pair<uint8, int>, std::string> specTabNames;
|
||||
|
||||
bool ParseSpecPrefix(const std::string& message, std::string& specPrefix, std::string& rest)
|
||||
{
|
||||
std::string msgLower = ToLower(message);
|
||||
for (const auto& entry : specTabNames)
|
||||
{
|
||||
std::string prefix = "@" + entry.second;
|
||||
if (msgLower.find(ToLower(prefix)) == 0)
|
||||
{
|
||||
specPrefix = entry.second;
|
||||
size_t spacePos = message.find(' ');
|
||||
rest = (spacePos != std::string::npos) ? message.substr(spacePos + 1) : "";
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MatchesSpec(Player* bot, const std::string& specPrefix)
|
||||
{
|
||||
uint8 cls = bot->getClass();
|
||||
int specTab = AiFactory::GetPlayerSpecTab(bot);
|
||||
std::string botSpecClass;
|
||||
// For druids, specTab==1 is always feral; distinguish bear/cat at runtime by role
|
||||
if (cls == CLASS_DRUID && specTab == 1)
|
||||
{
|
||||
botSpecClass = botAI->IsTank(bot) ? "bear" : "cat";
|
||||
}
|
||||
// For death knights, specTab==0 is always blood; distinguish tank/dps at runtime by role
|
||||
else if (cls == CLASS_DEATH_KNIGHT && specTab == 0)
|
||||
{
|
||||
botSpecClass = botAI->IsTank(bot) ? "bdkt" : "bdkd";
|
||||
}
|
||||
else
|
||||
{
|
||||
auto it = specTabNames.find({cls, specTab});
|
||||
if (it != specTabNames.end())
|
||||
botSpecClass = it->second;
|
||||
}
|
||||
return ToLower(botSpecClass) == ToLower(specPrefix);
|
||||
}
|
||||
};
|
||||
|
||||
class AuraChatFilter : public ChatFilter
|
||||
{
|
||||
public:
|
||||
AuraChatFilter(PlayerbotAI* botAI) : ChatFilter(botAI) {}
|
||||
|
||||
std::string const Filter(std::string& message) override
|
||||
{
|
||||
Player* bot = botAI->GetBot();
|
||||
std::string msgLower = ToLower(message);
|
||||
const std::string auraPrefix = "@aura";
|
||||
const std::string noAuraPrefix = "@noaura";
|
||||
size_t prefixLen = 0;
|
||||
bool isNoAura = false;
|
||||
if (msgLower.find(auraPrefix) == 0)
|
||||
{
|
||||
prefixLen = auraPrefix.length();
|
||||
isNoAura = false;
|
||||
}
|
||||
else if (msgLower.find(noAuraPrefix) == 0)
|
||||
{
|
||||
prefixLen = noAuraPrefix.length();
|
||||
isNoAura = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return message;
|
||||
}
|
||||
|
||||
// Trim any leading spaces after @aura or @noaura (can use space between prefix and spell ID if desired, but not required)
|
||||
std::string auraIdOrName = message.substr(prefixLen);
|
||||
auraIdOrName.erase(0, auraIdOrName.find_first_not_of(' '));
|
||||
if (auraIdOrName.empty())
|
||||
{
|
||||
return message;
|
||||
}
|
||||
|
||||
uint32 auraId = 0;
|
||||
size_t spacePos = auraIdOrName.find(' ');
|
||||
std::string idStr = (spacePos != std::string::npos) ? auraIdOrName.substr(0, spacePos) : auraIdOrName;
|
||||
std::string rest = (spacePos != std::string::npos) ? auraIdOrName.substr(spacePos + 1) : "";
|
||||
if (!idStr.empty())
|
||||
{
|
||||
bool isNumeric = std::all_of(idStr.begin(), idStr.end(), ::isdigit);
|
||||
if (isNumeric)
|
||||
{
|
||||
auraId = atoi(idStr.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
if (auraId == 0)
|
||||
return message;
|
||||
|
||||
bool hasAura = bot->HasAura(auraId);
|
||||
bool match = isNoAura ? !hasAura : hasAura;
|
||||
std::string result = match ? ChatFilter::Filter(rest) : "";
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
class AggroByChatFilter : public ChatFilter
|
||||
{
|
||||
public:
|
||||
AggroByChatFilter(PlayerbotAI* botAI) : ChatFilter(botAI) {}
|
||||
|
||||
std::string const Filter(std::string& message) override
|
||||
{
|
||||
Player* bot = botAI->GetBot();
|
||||
std::string msgLower = ToLower(message);
|
||||
const std::string prefix = "@aggroby";
|
||||
size_t prefixLen = prefix.length();
|
||||
if (msgLower.find(prefix) != 0)
|
||||
{
|
||||
return message;
|
||||
}
|
||||
|
||||
// Trim any leading spaces after @aggroby (can use space between prefix and entry ID/creature name if desired, but not required)
|
||||
std::string enemyStr = message.substr(prefixLen);
|
||||
enemyStr.erase(0, enemyStr.find_first_not_of(' '));
|
||||
if (enemyStr.empty())
|
||||
{
|
||||
return message;
|
||||
}
|
||||
|
||||
// If creature name is more than one word, it must be enclosed in quotes, e.g. @aggroby "Scarlet Commander Mograine" flee
|
||||
std::string rest = "";
|
||||
std::string enemyName = "";
|
||||
bool isName = false;
|
||||
uint32 entryId = 0;
|
||||
|
||||
if (enemyStr[0] == '"')
|
||||
{
|
||||
size_t endQuote = enemyStr.find('"', 1);
|
||||
if (endQuote != std::string::npos)
|
||||
{
|
||||
enemyName = enemyStr.substr(1, endQuote - 1);
|
||||
isName = true;
|
||||
size_t spacePos = enemyStr.find(' ', endQuote + 1);
|
||||
if (spacePos != std::string::npos)
|
||||
{
|
||||
rest = enemyStr.substr(spacePos + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
rest = "";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
enemyName = enemyStr.substr(1);
|
||||
isName = true;
|
||||
rest = "";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t splitPos = enemyStr.find_first_of(" ");
|
||||
std::string idOrName = (splitPos != std::string::npos) ? enemyStr.substr(0, splitPos) : enemyStr;
|
||||
if (splitPos != std::string::npos)
|
||||
{
|
||||
rest = enemyStr.substr(splitPos + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
rest = "";
|
||||
}
|
||||
if (!idOrName.empty())
|
||||
{
|
||||
bool isNumeric = std::all_of(idOrName.begin(), idOrName.end(), ::isdigit);
|
||||
if (isNumeric)
|
||||
{
|
||||
entryId = atoi(idOrName.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
enemyName = idOrName;
|
||||
isName = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const float radius = 100.0f;
|
||||
GuidVector npcs = botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest npcs")->Get();
|
||||
bool match = false;
|
||||
for (const auto& guid : npcs)
|
||||
{
|
||||
Creature* c = botAI->GetCreature(guid);
|
||||
if (!c)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
bool nameMatch = isName && ToLower(c->GetName()) == ToLower(enemyName);
|
||||
bool idMatch = (entryId != 0) && c->GetEntry() == entryId;
|
||||
if ((nameMatch || idMatch) && c->GetDistance2d(bot) <= radius)
|
||||
{
|
||||
Unit* victim = c->GetVictim();
|
||||
if (victim && victim->GetGUID() == bot->GetGUID())
|
||||
{
|
||||
match = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
std::string result = match ? ChatFilter::Filter(rest) : "";
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
CompositeChatFilter::CompositeChatFilter(PlayerbotAI* botAI) : ChatFilter(botAI)
|
||||
{
|
||||
filters.push_back(new StrategyChatFilter(botAI));
|
||||
@@ -303,6 +588,9 @@ CompositeChatFilter::CompositeChatFilter(PlayerbotAI* botAI) : ChatFilter(botAI)
|
||||
filters.push_back(new CombatTypeChatFilter(botAI));
|
||||
filters.push_back(new LevelChatFilter(botAI));
|
||||
filters.push_back(new SubGroupChatFilter(botAI));
|
||||
filters.push_back(new SpecChatFilter(botAI));
|
||||
filters.push_back(new AuraChatFilter(botAI));
|
||||
filters.push_back(new AggroByChatFilter(botAI));
|
||||
}
|
||||
|
||||
CompositeChatFilter::~CompositeChatFilter()
|
||||
@@ -313,14 +601,11 @@ CompositeChatFilter::~CompositeChatFilter()
|
||||
|
||||
std::string const CompositeChatFilter::Filter(std::string& message)
|
||||
{
|
||||
for (uint32 j = 0; j < filters.size(); ++j)
|
||||
for (auto* filter : filters)
|
||||
{
|
||||
for (std::vector<ChatFilter*>::iterator i = filters.begin(); i != filters.end(); i++)
|
||||
{
|
||||
message = (*i)->Filter(message);
|
||||
if (message.empty())
|
||||
break;
|
||||
}
|
||||
message = filter->Filter(message);
|
||||
if (message.empty())
|
||||
break;
|
||||
}
|
||||
|
||||
return message;
|
||||
|
||||
@@ -308,25 +308,25 @@ ItemIds ChatHelper::parseItems(std::string const text)
|
||||
ItemWithRandomProperty ChatHelper::parseItemWithRandomProperty(std::string const text)
|
||||
{
|
||||
ItemWithRandomProperty res;
|
||||
|
||||
|
||||
size_t itemStart = text.find("Hitem:");
|
||||
if (itemStart == std::string::npos)
|
||||
return res;
|
||||
|
||||
|
||||
itemStart += 6;
|
||||
if (itemStart >= text.length())
|
||||
return res;
|
||||
|
||||
|
||||
size_t colonPos = text.find(':', itemStart);
|
||||
if (colonPos == std::string::npos)
|
||||
return res;
|
||||
|
||||
|
||||
std::string itemIdStr = text.substr(itemStart, colonPos - itemStart);
|
||||
res.itemId = atoi(itemIdStr.c_str());
|
||||
|
||||
|
||||
std::vector<std::string> params;
|
||||
size_t currentPos = colonPos + 1;
|
||||
|
||||
|
||||
while (currentPos < text.length()) {
|
||||
size_t nextColon = text.find(':', currentPos);
|
||||
if (nextColon == std::string::npos) {
|
||||
@@ -336,15 +336,15 @@ ItemWithRandomProperty ChatHelper::parseItemWithRandomProperty(std::string const
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
params.push_back(text.substr(currentPos, nextColon - currentPos));
|
||||
currentPos = nextColon + 1;
|
||||
}
|
||||
|
||||
|
||||
if (params.size() >= 6) {
|
||||
res.randomPropertyId = atoi(params[5].c_str());
|
||||
}
|
||||
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -364,7 +364,7 @@ std::string const ChatHelper::FormatQuest(Quest const* quest)
|
||||
|
||||
if (questTitle.empty())
|
||||
questTitle = quest->GetTitle();
|
||||
|
||||
|
||||
out << "|cFFFFFF00|Hquest:" << quest->GetQuestId() << ':' << quest->GetQuestLevel() << "|h[" << questTitle << "]|h|r";
|
||||
return out.str();
|
||||
}
|
||||
@@ -432,7 +432,7 @@ std::string const ChatHelper::FormatItem(ItemTemplate const* proto, uint32 count
|
||||
|
||||
if (locale && locale->Name.size() > sWorld->GetDefaultDbcLocale())
|
||||
itemName = locale->Name[sWorld->GetDefaultDbcLocale()];
|
||||
|
||||
|
||||
if (itemName.empty())
|
||||
itemName = proto->Name1;
|
||||
|
||||
@@ -677,7 +677,7 @@ std::set<uint32> extractGeneric(std::string_view text, std::string_view prefix)
|
||||
std::string_view number_str = text_view.substr(pos, end_pos - pos);
|
||||
|
||||
uint32 number = 0;
|
||||
|
||||
|
||||
auto [ptr, ec] = std::from_chars(number_str.data(), number_str.data() + number_str.size(), number);
|
||||
|
||||
if (ec == std::errc())
|
||||
|
||||
@@ -590,7 +590,7 @@ uint32 GuildTaskMgr::GetTaskValue(uint32 owner, uint32 guildId, std::string cons
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
uint32 value = 0;
|
||||
|
||||
PlayerbotsDatabasePreparedStatement* stmt =
|
||||
|
||||
@@ -1051,7 +1051,7 @@ void PlayerbotAI::HandleBotOutgoingPacket(WorldPacket const& packet)
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (chanName == "World")
|
||||
return;
|
||||
|
||||
@@ -2937,9 +2937,9 @@ bool PlayerbotAI::HasAura(uint32 spellId, Unit const* unit)
|
||||
return unit->HasAura(spellId);
|
||||
// for (uint8 effect = EFFECT_0; effect <= EFFECT_2; effect++)
|
||||
// {
|
||||
// AuraEffect const* aurEff = unit->GetAuraEffect(spellId, effect);
|
||||
// if (IsRealAura(bot, aurEff, unit))
|
||||
// return true;
|
||||
// AuraEffect const* aurEff = unit->GetAuraEffect(spellId, effect);
|
||||
// if (IsRealAura(bot, aurEff, unit))
|
||||
// return true;
|
||||
// }
|
||||
|
||||
// return false;
|
||||
|
||||
@@ -554,7 +554,7 @@ public:
|
||||
bool IsSafe(Player* player);
|
||||
bool IsSafe(WorldObject* obj);
|
||||
ChatChannelSource GetChatChannelSource(Player* bot, uint32 type, std::string channelName);
|
||||
|
||||
|
||||
bool CheckLocationDistanceByLevel(Player* player, const WorldLocation &loc, bool fromStartUp = false);
|
||||
|
||||
bool HasCheat(BotCheatMask mask)
|
||||
|
||||
@@ -81,6 +81,9 @@ bool PlayerbotAIConfig::Initialize()
|
||||
sitDelay = sConfigMgr->GetOption<int32>("AiPlayerbot.SitDelay", 30000);
|
||||
returnDelay = sConfigMgr->GetOption<int32>("AiPlayerbot.ReturnDelay", 7000);
|
||||
lootDelay = sConfigMgr->GetOption<int32>("AiPlayerbot.LootDelay", 1000);
|
||||
// Buff system
|
||||
minBotsForGreaterBuff = sConfigMgr->GetOption<int32>("AiPlayerbot.MinBotsForGreaterBuff", 3);
|
||||
rpWarningCooldown = sConfigMgr->GetOption<int32>("AiPlayerbot.RPWarningCooldown", 30);
|
||||
disabledWithoutRealPlayerLoginDelay = sConfigMgr->GetOption<int32>("AiPlayerbot.DisabledWithoutRealPlayerLoginDelay", 30);
|
||||
disabledWithoutRealPlayerLogoutDelay = sConfigMgr->GetOption<int32>("AiPlayerbot.DisabledWithoutRealPlayerLogoutDelay", 300);
|
||||
|
||||
@@ -167,14 +170,14 @@ bool PlayerbotAIConfig::Initialize()
|
||||
pvpProhibitedAreaIds);
|
||||
fastReactInBG = sConfigMgr->GetOption<bool>("AiPlayerbot.FastReactInBG", true);
|
||||
LoadList<std::vector<uint32>>(
|
||||
sConfigMgr->GetOption<std::string>("AiPlayerbot.RandomBotQuestIds", "7848,3802,5505,6502,7761"),
|
||||
sConfigMgr->GetOption<std::string>("AiPlayerbot.RandomBotQuestIds", "7848,3802,5505,6502,7761,10277,10285,11492,13188,13189,24499,24511,24710,24712"),
|
||||
randomBotQuestIds);
|
||||
|
||||
LoadSet<std::set<uint32>>(
|
||||
sConfigMgr->GetOption<std::string>("AiPlayerbot.DisallowedGameObjects",
|
||||
"176213,17155,2656,74448,19020,3719,3658,3705,3706,105579,75293,2857,"
|
||||
"179490,141596,160836,160845,179516,176224,181085,176112,128308,128403,"
|
||||
"165739,165738,175245,175970,176325,176327,123329"),
|
||||
"165739,165738,175245,175970,176325,176327,123329,2560"),
|
||||
disallowedGameObjects);
|
||||
botAutologin = sConfigMgr->GetOption<bool>("AiPlayerbot.BotAutologin", false);
|
||||
randomBotAutologin = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotAutologin", true);
|
||||
@@ -216,9 +219,9 @@ bool PlayerbotAIConfig::Initialize()
|
||||
"531,532,534,544,548,550,564,565,580,249,533,603,615,616,624,631,649,724"),
|
||||
restrictedHealerDPSMaps);
|
||||
|
||||
//////////////////////////// ICC
|
||||
//////////////////////////// ICC
|
||||
|
||||
EnableICCBuffs = sConfigMgr->GetOption<bool>("AiPlayerbot.EnableICCBuffs", true);
|
||||
EnableICCBuffs = sConfigMgr->GetOption<bool>("AiPlayerbot.EnableICCBuffs", true);
|
||||
|
||||
//////////////////////////// CHAT
|
||||
enableBroadcasts = sConfigMgr->GetOption<bool>("AiPlayerbot.EnableBroadcasts", true);
|
||||
|
||||
@@ -139,6 +139,12 @@ public:
|
||||
uint32 disabledWithoutRealPlayerLoginDelay, disabledWithoutRealPlayerLogoutDelay;
|
||||
bool randomBotJoinLfg;
|
||||
|
||||
// Buff system
|
||||
// Min group size to use Greater buffs (Paladin, Mage, Druid). Default: 3
|
||||
int32 minBotsForGreaterBuff;
|
||||
// Cooldown (seconds) between reagent-missing RP warnings, per bot & per buff. Default: 30
|
||||
int32 rpWarningCooldown;
|
||||
|
||||
// chat
|
||||
bool randomBotTalk;
|
||||
bool randomBotEmote;
|
||||
|
||||
@@ -475,7 +475,7 @@ void PlayerbotHolder::OnBotLogin(Player* const bot)
|
||||
}
|
||||
|
||||
Player* master = botAI->GetMaster();
|
||||
if (master)
|
||||
if (master)
|
||||
{
|
||||
ObjectGuid masterGuid = master->GetGUID();
|
||||
if (master->GetGroup() && !master->GetGroup()->IsLeader(masterGuid))
|
||||
@@ -499,7 +499,7 @@ void PlayerbotHolder::OnBotLogin(Player* const bot)
|
||||
}
|
||||
}
|
||||
|
||||
// Don't disband alt groups when master goes away
|
||||
// Don't disband alt groups when master goes away
|
||||
// Controlled by config
|
||||
if (sPlayerbotAIConfig->KeepAltsInGroup())
|
||||
{
|
||||
@@ -1130,18 +1130,18 @@ std::vector<std::string> PlayerbotHolder::HandlePlayerbotCommand(char const* arg
|
||||
int8 gender = -1; // -1 = gender will be random
|
||||
if (genderArg)
|
||||
{
|
||||
std::string g = genderArg;
|
||||
std::transform(g.begin(), g.end(), g.begin(), ::tolower);
|
||||
|
||||
if (g == "male" || g == "0")
|
||||
gender = GENDER_MALE; // 0
|
||||
else if (g == "female" || g == "1")
|
||||
gender = GENDER_FEMALE; // 1
|
||||
else
|
||||
{
|
||||
messages.push_back("Unknown gender : " + g + " (male/female/0/1)");
|
||||
return messages;
|
||||
}
|
||||
std::string g = genderArg;
|
||||
std::transform(g.begin(), g.end(), g.begin(), ::tolower);
|
||||
|
||||
if (g == "male" || g == "0")
|
||||
gender = GENDER_MALE; // 0
|
||||
else if (g == "female" || g == "1")
|
||||
gender = GENDER_FEMALE; // 1
|
||||
else
|
||||
{
|
||||
messages.push_back("Unknown gender : " + g + " (male/female/0/1)");
|
||||
return messages;
|
||||
}
|
||||
} //end
|
||||
|
||||
if (claz == 6 && master->GetLevel() < sWorld->getIntConfig(CONFIG_START_HEROIC_PLAYER_LEVEL))
|
||||
@@ -1155,7 +1155,7 @@ std::vector<std::string> PlayerbotHolder::HandlePlayerbotCommand(char const* arg
|
||||
{
|
||||
// If the user requested a specific gender, skip any character that doesn't match.
|
||||
if (gender != -1 && GetOfflinePlayerGender(guid) != gender)
|
||||
continue;
|
||||
continue;
|
||||
if (botLoading.find(guid) != botLoading.end())
|
||||
continue;
|
||||
if (ObjectAccessor::FindConnectedPlayer(guid))
|
||||
|
||||
@@ -164,7 +164,7 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea
|
||||
|
||||
if (reason)
|
||||
*reason = PLAYERBOT_DENY_INVITE;
|
||||
|
||||
|
||||
return PLAYERBOT_SECURITY_INVITE;
|
||||
}
|
||||
|
||||
|
||||
@@ -115,7 +115,7 @@ public:
|
||||
roundedTime = roundedTime.substr(0, roundedTime.find('.') + 2);
|
||||
|
||||
ChatHandler(player->GetSession()).SendSysMessage(
|
||||
"|cff00ff00Playerbots:|r bot initialization at server startup takes about '"
|
||||
"|cff00ff00Playerbots:|r bot initialization at server startup takes about '"
|
||||
+ roundedTime + "' minutes.");
|
||||
}
|
||||
}
|
||||
@@ -217,16 +217,20 @@ public:
|
||||
if (!player->GetSession()->IsBot() || !sRandomPlayerbotMgr->IsRandomBot(player))
|
||||
return;
|
||||
|
||||
// no XP multiplier, when bot has group where leader is a real player.
|
||||
// no XP multiplier, when bot is in a group with a real player.
|
||||
if (Group* group = player->GetGroup())
|
||||
{
|
||||
Player* leader = group->GetLeader();
|
||||
if (leader && leader != player)
|
||||
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
|
||||
{
|
||||
if (PlayerbotAI* leaderBotAI = GET_PLAYERBOT_AI(leader))
|
||||
Player* member = gref->GetSource();
|
||||
if (!member)
|
||||
{
|
||||
if (leaderBotAI->HasRealPlayerMaster())
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!member->GetSession()->IsBot())
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -295,7 +299,7 @@ public:
|
||||
LOG_INFO("server.loading", "╚══════════════════════════════════════════════════════════╝");
|
||||
|
||||
uint32 oldMSTime = getMSTime();
|
||||
|
||||
|
||||
LOG_INFO("server.loading", " ");
|
||||
LOG_INFO("server.loading", "Load Playerbots Config...");
|
||||
|
||||
|
||||
@@ -2360,7 +2360,7 @@ void RandomItemMgr::BuildPotionCache()
|
||||
(proto->SubClass != ITEM_SUBCLASS_POTION && proto->SubClass != ITEM_SUBCLASS_FLASK) ||
|
||||
proto->Bonding != NO_BIND)
|
||||
continue;
|
||||
|
||||
|
||||
uint32 requiredLevel = proto->RequiredLevel;
|
||||
if (requiredLevel > level || (level > 13 && requiredLevel < level - 13))
|
||||
continue;
|
||||
@@ -2374,10 +2374,10 @@ void RandomItemMgr::BuildPotionCache()
|
||||
if (proto->Duration & 0x80000000)
|
||||
continue;
|
||||
|
||||
|
||||
|
||||
if (proto->AllowableClass != -1)
|
||||
continue;
|
||||
|
||||
|
||||
bool hybrid = false;
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(proto->Spells[0].SpellId);
|
||||
if (!spellInfo)
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
#include "SharedDefines.h"
|
||||
#include "SocialMgr.h"
|
||||
#include "Timer.h"
|
||||
#include "Guild.h" // EmblemInfo::SaveToDB
|
||||
#include "Log.h"
|
||||
#include "GuildMgr.h"
|
||||
|
||||
std::map<uint8, std::vector<uint8>> RandomPlayerbotFactory::availableRaces;
|
||||
|
||||
@@ -889,8 +892,10 @@ void RandomPlayerbotFactory::CreateRandomGuilds()
|
||||
availableLeaders.push_back(leader);
|
||||
}
|
||||
}
|
||||
|
||||
for (; guildNumber < sPlayerbotAIConfig->randomBotGuildCount; ++guildNumber)
|
||||
|
||||
// Create up to randomBotGuildCount by counting only EFFECTIVE creations
|
||||
uint32 createdThisRun = 0;
|
||||
for (; guildNumber < sPlayerbotAIConfig->randomBotGuildCount; /* ++guildNumber -> done only if creation */)
|
||||
{
|
||||
std::string const guildName = CreateRandomGuildName();
|
||||
if (guildName.empty())
|
||||
@@ -902,21 +907,29 @@ void RandomPlayerbotFactory::CreateRandomGuilds()
|
||||
if (availableLeaders.empty())
|
||||
{
|
||||
LOG_ERROR("playerbots", "No leaders for random guilds available");
|
||||
continue;
|
||||
break; // no more leaders: we can no longer progress without distorting the counter
|
||||
}
|
||||
|
||||
uint32 index = urand(0, availableLeaders.size() - 1);
|
||||
ObjectGuid leader = availableLeaders[index];
|
||||
availableLeaders.erase(availableLeaders.begin() + index); // Removes the chosen leader to avoid re-selecting it repeatedly
|
||||
|
||||
Player* player = ObjectAccessor::FindPlayer(leader);
|
||||
if (!player)
|
||||
{
|
||||
LOG_ERROR("playerbots", "ObjectAccessor Cannot find player to set leader for guild {} . Skipped...",
|
||||
guildName.c_str());
|
||||
// we will try with other leaders in the next round (guildNumber is not incremented)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (player->GetGuildId())
|
||||
{
|
||||
// leader already in guild -> we don't advance the counter, we move on to the next one
|
||||
continue;
|
||||
}
|
||||
|
||||
LOG_DEBUG("playerbots", "Creating guild name='{}' leader='{}'...", guildName.c_str(), player->GetName().c_str());
|
||||
|
||||
Guild* guild = new Guild();
|
||||
if (!guild->Create(player, guildName))
|
||||
@@ -929,6 +942,8 @@ void RandomPlayerbotFactory::CreateRandomGuilds()
|
||||
|
||||
sGuildMgr->AddGuild(guild);
|
||||
|
||||
LOG_DEBUG("playerbots", "Guild created: id={} name='{}'", guild->GetId(), guildName.c_str());
|
||||
|
||||
// create random emblem
|
||||
uint32 st, cl, br, bc, bg;
|
||||
bg = urand(0, 51);
|
||||
@@ -936,13 +951,38 @@ void RandomPlayerbotFactory::CreateRandomGuilds()
|
||||
cl = urand(0, 17);
|
||||
br = urand(0, 7);
|
||||
st = urand(0, 180);
|
||||
EmblemInfo emblemInfo(st, cl, br, bc, bg);
|
||||
guild->HandleSetEmblem(emblemInfo);
|
||||
|
||||
LOG_DEBUG("playerbots",
|
||||
"[TABARD] new guild id={} random -> style={}, color={}, borderStyle={}, borderColor={}, bgColor={}",
|
||||
guild->GetId(), st, cl, br, bc, bg);
|
||||
|
||||
// populate guild table with a random tabard design
|
||||
CharacterDatabase.Execute(
|
||||
"UPDATE guild SET EmblemStyle={}, EmblemColor={}, BorderStyle={}, BorderColor={}, BackgroundColor={} "
|
||||
"WHERE guildid={}",
|
||||
st, cl, br, bc, bg, guild->GetId());
|
||||
LOG_DEBUG("playerbots", "[TABARD] UPDATE done for guild id={}", guild->GetId());
|
||||
|
||||
// Immediate reading for log
|
||||
if (QueryResult qr = CharacterDatabase.Query(
|
||||
"SELECT EmblemStyle,EmblemColor,BorderStyle,BorderColor,BackgroundColor FROM guild WHERE guildid={}",
|
||||
guild->GetId()))
|
||||
{
|
||||
Field* f = qr->Fetch();
|
||||
LOG_DEBUG("playerbots",
|
||||
"[TABARD] DB check guild id={} => style={}, color={}, borderStyle={}, borderColor={}, bgColor={}",
|
||||
guild->GetId(), f[0].Get<uint8>(), f[1].Get<uint8>(), f[2].Get<uint8>(), f[3].Get<uint8>(), f[4].Get<uint8>());
|
||||
}
|
||||
|
||||
sPlayerbotAIConfig->randomBotGuilds.push_back(guild->GetId());
|
||||
// The guild is only counted if it is actually created
|
||||
++guildNumber;
|
||||
++createdThisRun;
|
||||
}
|
||||
|
||||
LOG_INFO("playerbots", "{} random bot guilds available", guildNumber);
|
||||
// Shows the true total and how many were created during this run
|
||||
LOG_INFO("playerbots", "{} random bot guilds available (created this run: {})",
|
||||
uint32(sPlayerbotAIConfig->randomBotGuilds.size()), createdThisRun);
|
||||
}
|
||||
|
||||
std::string const RandomPlayerbotFactory::CreateRandomGuildName()
|
||||
|
||||
@@ -79,8 +79,8 @@ static const std::unordered_map<uint16, std::pair<CityId, FactionId>> bankerToCi
|
||||
{2996, {CityId::THUNDER_BLUFF, FactionId::HORDE}}, {8356, {CityId::THUNDER_BLUFF, FactionId::HORDE}}, {8357, {CityId::THUNDER_BLUFF, FactionId::HORDE}},
|
||||
{17631, {CityId::SILVERMOON_CITY, FactionId::HORDE}}, {17632, {CityId::SILVERMOON_CITY, FactionId::HORDE}}, {17633, {CityId::SILVERMOON_CITY, FactionId::HORDE}},
|
||||
{16615, {CityId::SILVERMOON_CITY, FactionId::HORDE}}, {16616, {CityId::SILVERMOON_CITY, FactionId::HORDE}}, {16617, {CityId::SILVERMOON_CITY, FactionId::HORDE}},
|
||||
{19246, {CityId::SHATTRATH_CITY, FactionId::NEUTRAL}}, {19338, {CityId::SHATTRATH_CITY, FactionId::NEUTRAL}},
|
||||
{19034, {CityId::SHATTRATH_CITY, FactionId::NEUTRAL}}, {19318, {CityId::SHATTRATH_CITY, FactionId::NEUTRAL}},
|
||||
{19246, {CityId::SHATTRATH_CITY, FactionId::NEUTRAL}}, {19338, {CityId::SHATTRATH_CITY, FactionId::NEUTRAL}},
|
||||
{19034, {CityId::SHATTRATH_CITY, FactionId::NEUTRAL}}, {19318, {CityId::SHATTRATH_CITY, FactionId::NEUTRAL}},
|
||||
{30604, {CityId::DALARAN, FactionId::NEUTRAL}}, {30605, {CityId::DALARAN, FactionId::NEUTRAL}}, {30607, {CityId::DALARAN, FactionId::NEUTRAL}},
|
||||
{28675, {CityId::DALARAN, FactionId::NEUTRAL}}, {28676, {CityId::DALARAN, FactionId::NEUTRAL}}, {28677, {CityId::DALARAN, FactionId::NEUTRAL}}
|
||||
};
|
||||
@@ -95,7 +95,7 @@ static const std::unordered_map<CityId, std::vector<uint16>> cityToBankers = {
|
||||
{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::SHATTRATH_CITY, {19246, 19338, 19034, 19318}},
|
||||
{CityId::DALARAN, {30604, 30605, 30607, 28675, 28676, 28677, 29530}}
|
||||
};
|
||||
|
||||
@@ -717,14 +717,14 @@ uint32 RandomPlayerbotMgr::AddRandomBots()
|
||||
|
||||
if (currentBots.size() < maxAllowedBotCount)
|
||||
{
|
||||
// Calculate how many bots to add
|
||||
// Calculate how many bots to add
|
||||
maxAllowedBotCount -= currentBots.size();
|
||||
maxAllowedBotCount = std::min(sPlayerbotAIConfig->randomBotsPerInterval, maxAllowedBotCount);
|
||||
|
||||
// Single RNG instance for all shuffling
|
||||
// Single RNG instance for all shuffling
|
||||
std::mt19937 rng(std::chrono::steady_clock::now().time_since_epoch().count());
|
||||
|
||||
// Only need to track the Alliance count, as it's in Phase 1
|
||||
// Only need to track the Alliance count, as it's in Phase 1
|
||||
uint32 totalRatio = sPlayerbotAIConfig->randomBotAllianceRatio + sPlayerbotAIConfig->randomBotHordeRatio;
|
||||
uint32 allowedAllianceCount = maxAllowedBotCount * (sPlayerbotAIConfig->randomBotAllianceRatio) / totalRatio;
|
||||
|
||||
@@ -877,17 +877,17 @@ uint32 RandomPlayerbotMgr::AddRandomBots()
|
||||
LOG_ERROR("playerbots",
|
||||
"Can't log-in all the requested bots. Try increasing RandomBotAccountCount in your conf file.\n"
|
||||
"{} more accounts needed.", moreAccountsNeeded);
|
||||
missingBotsTimer = 0; // Reset timer so error is not spammed every tick
|
||||
missingBotsTimer = 0; // Reset timer so error is not spammed every tick
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
missingBotsTimer = 0; // Reset timer if logins for this interval were successful
|
||||
missingBotsTimer = 0; // Reset timer if logins for this interval were successful
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
missingBotsTimer = 0; // Reset timer if there's enough bots
|
||||
missingBotsTimer = 0; // Reset timer if there's enough bots
|
||||
}
|
||||
|
||||
return currentBots.size();
|
||||
@@ -897,7 +897,7 @@ void RandomPlayerbotMgr::LoadBattleMastersCache()
|
||||
{
|
||||
BattleMastersCache.clear();
|
||||
|
||||
LOG_INFO("playerbots", "Loading BattleMasters Cache...");
|
||||
LOG_INFO("playerbots", "Loading Battlemasters Cache...");
|
||||
|
||||
QueryResult result = WorldDatabase.Query("SELECT `entry`,`bg_template` FROM `battlemaster_entry`");
|
||||
|
||||
@@ -940,7 +940,7 @@ void RandomPlayerbotMgr::LoadBattleMastersCache()
|
||||
|
||||
BattleMastersCache[bmTeam][BattlegroundTypeId(bgTypeId)].insert(
|
||||
BattleMastersCache[bmTeam][BattlegroundTypeId(bgTypeId)].end(), entry);
|
||||
LOG_DEBUG("playerbots", "Cached Battmemaster #{} for BG Type {} ({})", entry, bgTypeId,
|
||||
LOG_DEBUG("playerbots", "Cached Battlemaster #{} for BG Type {} ({})", entry, bgTypeId,
|
||||
bmTeam == TEAM_ALLIANCE ? "Alliance"
|
||||
: bmTeam == TEAM_HORDE ? "Horde"
|
||||
: "Neutral");
|
||||
@@ -2190,7 +2190,7 @@ void RandomPlayerbotMgr::RandomTeleportForLevel(Player* bot)
|
||||
RandomTeleport(bot, fallbackLocs, true);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Collect valid cities based on bot faction.
|
||||
std::unordered_set<CityId> validBankerCities;
|
||||
for (auto& loc : bankerLocsPerLevelCache[level])
|
||||
|
||||
@@ -2299,7 +2299,7 @@ void TravelNodeMap::printNodeStore()
|
||||
}
|
||||
}
|
||||
|
||||
sPlayerbotAIConfig->log(nodeStore, " }");
|
||||
sPlayerbotAIConfig->log(nodeStore, " }");
|
||||
sPlayerbotAIConfig->log(nodeStore, "};");
|
||||
|
||||
printf("\r [Done] \r\x3D");
|
||||
|
||||
@@ -168,33 +168,33 @@ void PlayerbotFactory::Init()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(gemId);
|
||||
if (!proto)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if (proto->ItemLevel < 60)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if (proto->Flags & ITEM_FLAG_UNIQUE_EQUIPPABLE)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if (sRandomItemMgr->IsTestItem(gemId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if (!sGemPropertiesStore.LookupEntry(proto->GemProperties))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// LOG_INFO("playerbots", "Add {} to enchantment gems", gemId);
|
||||
enchantGemIdCache.push_back(gemId);
|
||||
}
|
||||
@@ -736,7 +736,7 @@ void PlayerbotFactory::InitConsumables()
|
||||
int count = (int)item.second - (int)bot->GetItemCount(item.first);
|
||||
if (count > 0)
|
||||
StoreItem(item.first, count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerbotFactory::InitPetTalents()
|
||||
@@ -1032,10 +1032,10 @@ void PlayerbotFactory::ClearSkills()
|
||||
}
|
||||
bot->SetUInt32Value(PLAYER_SKILL_INDEX(0), 0);
|
||||
bot->SetUInt32Value(PLAYER_SKILL_INDEX(1), 0);
|
||||
|
||||
|
||||
// unlearn default race/class skills
|
||||
if (PlayerInfo const* info = sObjectMgr->GetPlayerInfo(bot->getRace(), bot->getClass()))
|
||||
{
|
||||
{
|
||||
for (PlayerCreateInfoSkills::const_iterator itr = info->skills.begin(); itr != info->skills.end(); ++itr)
|
||||
{
|
||||
uint32 skillId = itr->SkillId;
|
||||
@@ -1043,7 +1043,7 @@ void PlayerbotFactory::ClearSkills()
|
||||
continue;
|
||||
bot->SetSkill(skillId, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerbotFactory::ClearEverything()
|
||||
@@ -1095,7 +1095,7 @@ void PlayerbotFactory::ResetQuests()
|
||||
uint32 entry = quest->GetQuestId();
|
||||
if (bot->GetQuestStatus(entry) == QUEST_STATUS_NONE)
|
||||
continue;
|
||||
|
||||
|
||||
bot->RemoveRewardedQuest(entry);
|
||||
bot->RemoveActiveQuest(entry, false);
|
||||
|
||||
@@ -1171,7 +1171,7 @@ void PlayerbotFactory::InitTalentsTree(bool increment /*false*/, bool use_templa
|
||||
|
||||
if (bot->GetFreeTalentPoints())
|
||||
InitTalents((specTab + 2) % 3);
|
||||
|
||||
|
||||
bot->SendTalentsInfoData(false);
|
||||
}
|
||||
|
||||
@@ -1704,7 +1704,7 @@ void PlayerbotFactory::InitEquipment(bool incremental, bool second_chance)
|
||||
{
|
||||
if (incremental && !sPlayerbotAIConfig->incrementalGearInit)
|
||||
return;
|
||||
|
||||
|
||||
if (level < 5) {
|
||||
// original items
|
||||
if (CharStartOutfitEntry const* oEntry = GetCharStartOutfitEntry(bot->getRace(), bot->getClass(), bot->getGender()))
|
||||
@@ -1715,11 +1715,11 @@ void PlayerbotFactory::InitEquipment(bool incremental, bool second_chance)
|
||||
continue;
|
||||
|
||||
uint32 itemId = oEntry->ItemId[j];
|
||||
|
||||
|
||||
// skip hearthstone
|
||||
if (itemId == 6948)
|
||||
continue;
|
||||
|
||||
|
||||
// just skip, reported in ObjectMgr::LoadItemTemplates
|
||||
ItemTemplate const* iProto = sObjectMgr->GetItemTemplate(itemId);
|
||||
if (!iProto)
|
||||
@@ -1743,10 +1743,10 @@ void PlayerbotFactory::InitEquipment(bool incremental, bool second_chance)
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
std::unordered_map<uint8, std::vector<uint32>> items;
|
||||
// int tab = AiFactory::GetPlayerSpecTab(bot);
|
||||
|
||||
|
||||
uint32 blevel = bot->GetLevel();
|
||||
int32 delta = std::min(blevel, 10u);
|
||||
|
||||
@@ -1814,7 +1814,7 @@ void PlayerbotFactory::InitEquipment(bool incremental, bool second_chance)
|
||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
|
||||
if (!proto)
|
||||
continue;
|
||||
|
||||
|
||||
bool shouldCheckGS = desiredQuality > ITEM_QUALITY_NORMAL;
|
||||
|
||||
if (shouldCheckGS && gearScoreLimit != 0 &&
|
||||
@@ -2923,7 +2923,7 @@ void PlayerbotFactory::InitQuests(std::list<uint32>& questMap, bool withRewardIt
|
||||
// set reward to 5 to skip majority quest reward
|
||||
uint32 reward = withRewardItem ? 0 : 5;
|
||||
bot->RewardQuest(quest, reward, bot, false);
|
||||
|
||||
|
||||
if (!withRewardItem)
|
||||
{
|
||||
// destroy the quest reward item
|
||||
@@ -3010,10 +3010,10 @@ void PlayerbotFactory::InitAmmo()
|
||||
// disable next expansion ammo
|
||||
if (sPlayerbotAIConfig->limitGearExpansion && bot->GetLevel() <= 60 && tEntry >= 23728)
|
||||
continue;
|
||||
|
||||
|
||||
if (sPlayerbotAIConfig->limitGearExpansion && bot->GetLevel() <= 70 && tEntry >= 35570)
|
||||
continue;
|
||||
|
||||
|
||||
entry = tEntry;
|
||||
break;
|
||||
}
|
||||
@@ -3177,7 +3177,7 @@ void PlayerbotFactory::InitPotions()
|
||||
|
||||
if (effect == SPELL_EFFECT_ENERGIZE && !bot->GetPower(POWER_MANA))
|
||||
continue;
|
||||
|
||||
|
||||
FindPotionVisitor visitor(bot, effect);
|
||||
IterateItems(&visitor);
|
||||
if (!visitor.GetResult().empty())
|
||||
@@ -3319,7 +3319,7 @@ void PlayerbotFactory::InitReagents()
|
||||
switch (bot->getClass())
|
||||
{
|
||||
case CLASS_DEATH_KNIGHT:
|
||||
if (level >= 56)
|
||||
if (level >= 56)
|
||||
items.push_back({37201, 40}); // Corpse Dust
|
||||
break;
|
||||
case CLASS_DRUID:
|
||||
@@ -4345,7 +4345,7 @@ void PlayerbotFactory::ApplyEnchantAndGemsNew(bool destoryOld)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if (item->GetTemplate() && item->GetTemplate()->Quality < ITEM_QUALITY_UNCOMMON)
|
||||
continue;
|
||||
int32 bestEnchantId = -1;
|
||||
@@ -4661,12 +4661,12 @@ void PlayerbotFactory::InitKeyring()
|
||||
{
|
||||
if (!bot)
|
||||
return;
|
||||
|
||||
|
||||
if (bot->GetLevel() < 70)
|
||||
return;
|
||||
|
||||
ReputationMgr& repMgr = bot->GetReputationMgr(); // Reference, use . instead of ->
|
||||
|
||||
|
||||
std::vector<std::pair<uint32, uint32>> keysToCheck;
|
||||
|
||||
// Reputation-based Keys (Honored requirement)
|
||||
|
||||
@@ -85,7 +85,7 @@ public:
|
||||
void InitKeyring();
|
||||
void InitReputation();
|
||||
void InitAttunementQuests();
|
||||
|
||||
|
||||
private:
|
||||
void Prepare();
|
||||
// void InitSecondEquipmentSet();
|
||||
|
||||
@@ -571,7 +571,7 @@ void StatsWeightCalculator::CalculateItemTypePenalty(ItemTemplate const* proto)
|
||||
{
|
||||
weight_ *= 0.1;
|
||||
}
|
||||
|
||||
|
||||
if (lvl >= 10 && cls == CLASS_ROGUE && (tab == ROGUE_TAB_ASSASSINATION || tab == ROGUE_TAB_SUBTLETY) &&
|
||||
proto->SubClass == ITEM_SUBCLASS_WEAPON_DAGGER)
|
||||
{
|
||||
|
||||
@@ -30,16 +30,16 @@ public:
|
||||
void Reset();
|
||||
float CalculateItem(uint32 itemId, int32 randomPropertyId = 0);
|
||||
float CalculateEnchant(uint32 enchantId);
|
||||
|
||||
|
||||
void SetOverflowPenalty(bool apply) { enable_overflow_penalty_ = apply; }
|
||||
void SetItemSetBonus(bool apply) { enable_item_set_bonus_ = apply; }
|
||||
void SetQualityBlend(bool apply) { enable_quality_blend_ = apply; }
|
||||
|
||||
|
||||
private:
|
||||
void GenerateWeights(Player* player);
|
||||
void GenerateBasicWeights(Player* player);
|
||||
void GenerateAdditionalWeights(Player* player);
|
||||
|
||||
|
||||
void CalculateRandomProperty(int32 randomPropertyId, uint32 itemId);
|
||||
void CalculateItemSetMod(Player* player, ItemTemplate const* proto);
|
||||
void CalculateSocketBonus(Player* player, ItemTemplate const* proto);
|
||||
|
||||
@@ -88,7 +88,7 @@ Engine::~Engine(void)
|
||||
void Engine::Reset()
|
||||
{
|
||||
strategyTypeMask = 0;
|
||||
|
||||
|
||||
ActionNode* action = nullptr;
|
||||
|
||||
while ((action = queue.Pop()) != nullptr)
|
||||
@@ -154,7 +154,7 @@ bool Engine::DoNextAction(Unit* unit, uint32 depth, bool minimal)
|
||||
|
||||
uint32 iterations = 0;
|
||||
uint32 iterationsPerTick = queue.Size() * (minimal ? 2 : sPlayerbotAIConfig->iterationsPerTick);
|
||||
|
||||
|
||||
while (++iterations <= iterationsPerTick)
|
||||
{
|
||||
basket = queue.Peek();
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
/**
|
||||
* @class Queue
|
||||
* @brief Manages a priority queue of actions for the playerbot system
|
||||
*
|
||||
*
|
||||
* This queue maintains a list of ActionBasket objects, each containing an action
|
||||
* and its relevance score. Actions with higher relevance scores are prioritized.
|
||||
*/
|
||||
@@ -25,7 +25,7 @@ public:
|
||||
/**
|
||||
* @brief Adds an action to the queue or updates existing action's relevance
|
||||
* @param action Pointer to the ActionBasket to be added
|
||||
*
|
||||
*
|
||||
* If an action with the same name exists, updates its relevance if the new
|
||||
* relevance is higher, then deletes the new action. Otherwise, adds the new
|
||||
* action to the queue.
|
||||
@@ -35,7 +35,7 @@ public:
|
||||
/**
|
||||
* @brief Removes and returns the action with highest relevance
|
||||
* @return Pointer to the highest relevance ActionNode, or nullptr if queue is empty
|
||||
*
|
||||
*
|
||||
* Ownership of the returned ActionNode is transferred to the caller.
|
||||
* The associated ActionBasket is deleted.
|
||||
*/
|
||||
@@ -55,7 +55,7 @@ public:
|
||||
|
||||
/**
|
||||
* @brief Removes and deletes expired actions from the queue
|
||||
*
|
||||
*
|
||||
* Uses sPlayerbotAIConfig->expireActionTime to determine if actions have expired.
|
||||
* Both the ActionNode and ActionBasket are deleted for expired actions.
|
||||
*/
|
||||
|
||||
@@ -26,18 +26,18 @@ enum StrategyType : uint32
|
||||
|
||||
// enum ActionPriority
|
||||
// {
|
||||
// ACTION_IDLE = 0,
|
||||
// ACTION_DEFAULT = 5,
|
||||
// ACTION_NORMAL = 10,
|
||||
// ACTION_HIGH = 20,
|
||||
// ACTION_MOVE = 30,
|
||||
// ACTION_INTERRUPT = 40,
|
||||
// ACTION_DISPEL = 50,
|
||||
// ACTION_RAID = 60,
|
||||
// ACTION_LIGHT_HEAL = 10,
|
||||
// ACTION_MEDIUM_HEAL = 20,
|
||||
// ACTION_CRITICAL_HEAL = 30,
|
||||
// ACTION_EMERGENCY = 90
|
||||
// ACTION_IDLE = 0,
|
||||
// ACTION_DEFAULT = 5,
|
||||
// ACTION_NORMAL = 10,
|
||||
// ACTION_HIGH = 20,
|
||||
// ACTION_MOVE = 30,
|
||||
// ACTION_INTERRUPT = 40,
|
||||
// ACTION_DISPEL = 50,
|
||||
// ACTION_RAID = 60,
|
||||
// ACTION_LIGHT_HEAL = 10,
|
||||
// ACTION_MEDIUM_HEAL = 20,
|
||||
// ACTION_CRITICAL_HEAL = 30,
|
||||
// ACTION_EMERGENCY = 90
|
||||
// };
|
||||
|
||||
static float ACTION_IDLE = 0.0f;
|
||||
|
||||
@@ -55,11 +55,11 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
|
||||
{
|
||||
Unit* oldTarget = context->GetValue<Unit*>("current target")->Get();
|
||||
bool shouldMelee = bot->IsWithinMeleeRange(target) || botAI->IsMelee(bot);
|
||||
|
||||
|
||||
bool sameTarget = oldTarget == target && bot->GetVictim() == target;
|
||||
bool inCombat = botAI->GetState() == BOT_STATE_COMBAT;
|
||||
bool sameAttackMode = bot->HasUnitState(UNIT_STATE_MELEE_ATTACKING) == shouldMelee;
|
||||
|
||||
|
||||
if (bot->GetMotionMaster()->GetCurrentMovementGeneratorType() == FLIGHT_MOTION_TYPE ||
|
||||
bot->HasUnitState(UNIT_STATE_IN_FLIGHT))
|
||||
{
|
||||
@@ -84,7 +84,7 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((sPlayerbotAIConfig->IsInPvpProhibitedZone(bot->GetZoneId()) ||
|
||||
if ((sPlayerbotAIConfig->IsInPvpProhibitedZone(bot->GetZoneId()) ||
|
||||
sPlayerbotAIConfig->IsInPvpProhibitedArea(bot->GetAreaId()))
|
||||
&& (target->IsPlayer() || target->IsPet()))
|
||||
{
|
||||
@@ -158,7 +158,7 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
|
||||
sServerFacade->SetFacingTo(bot, target);
|
||||
}
|
||||
botAI->ChangeEngine(BOT_STATE_COMBAT);
|
||||
|
||||
|
||||
bot->Attack(target, shouldMelee);
|
||||
/* prevent pet dead immediately in group */
|
||||
// if (bot->GetMap()->IsDungeon() && bot->GetGroup() && !target->IsInCombat()) {
|
||||
|
||||
@@ -158,7 +158,7 @@ void AutoMaintenanceOnLevelupAction::AutoUpgradeEquip()
|
||||
{
|
||||
if (!sPlayerbotAIConfig->autoUpgradeEquip || !sRandomPlayerbotMgr->IsRandomBot(bot))
|
||||
return;
|
||||
|
||||
|
||||
PlayerbotFactory factory(bot, bot->GetLevel());
|
||||
|
||||
// Clean up old consumables before adding new ones
|
||||
|
||||
@@ -543,7 +543,7 @@ bool BGJoinAction::JoinQueue(uint32 type)
|
||||
}
|
||||
|
||||
botAI->GetAiObjectContext()->GetValue<uint32>("bg type")->Set(0);
|
||||
|
||||
|
||||
if (!isArena)
|
||||
{
|
||||
WorldPacket* packet = new WorldPacket(CMSG_BATTLEMASTER_JOIN, 20);
|
||||
|
||||
@@ -2903,7 +2903,7 @@ bool BGTactics::selectObjective(bool reset)
|
||||
{
|
||||
// just make bot stay where it is if already close
|
||||
// (stops them shifting around between the random spots)
|
||||
if (bot->GetDistance(IC_GATE_ATTACK_POS_HORDE) < 8.0f)
|
||||
if (bot->GetDistance(IC_GATE_ATTACK_POS_HORDE) < 8.0f)
|
||||
pos.Set(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), bot->GetMapId());
|
||||
else
|
||||
pos.Set(IC_GATE_ATTACK_POS_HORDE.GetPositionX() + frand(-5.0f, +5.0f),
|
||||
@@ -3213,7 +3213,7 @@ bool BGTactics::moveToObjective(bool ignoreDist)
|
||||
// sServerFacade->GetDistance2d(bot, pos.x, pos.y); bot->Say(out.str(), LANG_UNIVERSAL);
|
||||
|
||||
// dont increase from 1.5 will cause bugs with horde capping AV towers
|
||||
return MoveNear(bot->GetMapId(), pos.x, pos.y, pos.z, 1.5f);
|
||||
return MoveNear(bot->GetMapId(), pos.x, pos.y, pos.z, 1.5f);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -3549,14 +3549,14 @@ bool BGTactics::startNewPathFree(std::vector<BattleBotPath*> const& vPaths)
|
||||
|
||||
/**
|
||||
* @brief Handles flag/base capturing gameplay in battlegrounds
|
||||
*
|
||||
*
|
||||
* This function manages the logic for capturing flags and bases in various battlegrounds.
|
||||
* It handles:
|
||||
* - Enemy detection and combat near objectives
|
||||
* - Coordination with friendly players who are capturing
|
||||
* - Different capture mechanics for each battleground type
|
||||
* - Proper positioning and movement
|
||||
*
|
||||
*
|
||||
* @param vPaths Vector of possible paths the bot can take
|
||||
* @param vFlagIds Vector of flag/base GameObjects that can be captured
|
||||
* @return true if handling a flag/base action, false otherwise
|
||||
@@ -3687,7 +3687,7 @@ bool BGTactics::atFlag(std::vector<BattleBotPath*> const& vPaths, std::vector<ui
|
||||
float y = bot->GetPositionY() + 5.0f * sin(angle);
|
||||
MoveTo(bot->GetMapId(), x, y, bot->GetPositionZ());
|
||||
}
|
||||
|
||||
|
||||
// Reset objective and take new path for defending
|
||||
resetObjective();
|
||||
if (!startNewPathBegin(vPaths))
|
||||
@@ -3895,7 +3895,7 @@ bool BGTactics::protectFC()
|
||||
float fcY = teamFC->GetPositionY();
|
||||
float fcZ = teamFC->GetPositionZ();
|
||||
uint32 mapId = bot->GetMapId();
|
||||
|
||||
|
||||
return MoveNear(mapId, fcX, fcY, fcZ, 5.0f, MovementPriority::MOVEMENT_NORMAL);
|
||||
}
|
||||
|
||||
|
||||
@@ -67,14 +67,14 @@ bool BuyAction::Execute(Event event)
|
||||
calculator.SetOverflowPenalty(false);
|
||||
|
||||
std::sort(m_items_sorted.begin(), m_items_sorted.end(),
|
||||
[&calculator](VendorItem* i, VendorItem* j)
|
||||
[&calculator](VendorItem* i, VendorItem* j)
|
||||
{
|
||||
ItemTemplate const* item1 = sObjectMgr->GetItemTemplate(i->item);
|
||||
ItemTemplate const* item2 = sObjectMgr->GetItemTemplate(j->item);
|
||||
|
||||
|
||||
if (!item1 || !item2)
|
||||
return false;
|
||||
|
||||
|
||||
float score1 = calculator.CalculateItem(item1->ItemId);
|
||||
float score2 = calculator.CalculateItem(item2->ItemId);
|
||||
|
||||
@@ -88,19 +88,19 @@ bool BuyAction::Execute(Event event)
|
||||
});
|
||||
|
||||
std::unordered_map<uint32, float> bestPurchasedItemScore; // Track best item score per InventoryType
|
||||
|
||||
|
||||
for (auto& tItem : m_items_sorted)
|
||||
{
|
||||
uint32 maxPurchases = 1; // Default to buying once
|
||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(tItem->item);
|
||||
if (!proto)
|
||||
continue;
|
||||
|
||||
|
||||
if (proto->Class == ITEM_CLASS_CONSUMABLE || proto->Class == ITEM_CLASS_PROJECTILE)
|
||||
{
|
||||
maxPurchases = 10; // Allow up to 10 purchases if it's a consumable or projectile
|
||||
}
|
||||
|
||||
|
||||
for (uint32 i = 0; i < maxPurchases; i++)
|
||||
{
|
||||
ItemUsage usage = AI_VALUE2(ItemUsage, "item usage", tItem->item);
|
||||
|
||||
@@ -47,10 +47,10 @@ bool CastCustomSpellAction::Execute(Event event)
|
||||
{
|
||||
if (!target)
|
||||
target = botAI->GetUnit(go);
|
||||
|
||||
|
||||
if (!botAI->GetUnit(go) || !botAI->GetUnit(go)->IsInWorld())
|
||||
continue;
|
||||
|
||||
|
||||
chat->eraseAllSubStr(text, chat->FormatWorldobject(botAI->GetUnit(go)));
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#include "PlayerbotFactory.h"
|
||||
#include "Playerbots.h"
|
||||
#include "AiObjectContext.h"
|
||||
#include "Log.h"
|
||||
#include "Log.h"
|
||||
|
||||
bool ChangeTalentsAction::Execute(Event event)
|
||||
{
|
||||
|
||||
@@ -193,8 +193,8 @@ public:
|
||||
creators["calc"] = &ChatActionContext::calc;
|
||||
creators["wipe"] = &ChatActionContext::wipe;
|
||||
creators["tame"] = &ChatActionContext::tame;
|
||||
creators["glyphs"] = &ChatActionContext::glyphs; // Added for custom Glyphs
|
||||
creators["glyph equip"] = &ChatActionContext::glyph_equip; // Added for custom Glyphs
|
||||
creators["glyphs"] = &ChatActionContext::glyphs; // Added for custom Glyphs
|
||||
creators["glyph equip"] = &ChatActionContext::glyph_equip; // Added for custom Glyphs
|
||||
creators["pet"] = &ChatActionContext::pet;
|
||||
creators["pet attack"] = &ChatActionContext::pet_attack;
|
||||
creators["roll"] = &ChatActionContext::roll_action;
|
||||
@@ -305,8 +305,8 @@ private:
|
||||
static Action* calc(PlayerbotAI* ai) { return new TellCalculateItemAction(ai); }
|
||||
static Action* wipe(PlayerbotAI* ai) { return new WipeAction(ai); }
|
||||
static Action* tame(PlayerbotAI* botAI) { return new TameAction(botAI); }
|
||||
static Action* glyphs(PlayerbotAI* botAI) { return new TellGlyphsAction(botAI); } // Added for custom Glyphs
|
||||
static Action* glyph_equip(PlayerbotAI* ai) { return new EquipGlyphsAction(ai); } // Added for custom Glyphs
|
||||
static Action* glyphs(PlayerbotAI* botAI) { return new TellGlyphsAction(botAI); } // Added for custom Glyphs
|
||||
static Action* glyph_equip(PlayerbotAI* ai) { return new EquipGlyphsAction(ai); } // Added for custom Glyphs
|
||||
static Action* pet(PlayerbotAI* botAI) { return new PetAction(botAI); }
|
||||
static Action* pet_attack(PlayerbotAI* botAI) { return new PetAction(botAI, "attack"); }
|
||||
static Action* roll_action(PlayerbotAI* botAI) { return new RollAction(botAI); }
|
||||
|
||||
@@ -66,7 +66,7 @@ bool CheckMountStateAction::isUseful()
|
||||
|
||||
// Get shapeshift states, only applicable when there's a master
|
||||
if (master)
|
||||
{
|
||||
{
|
||||
botInShapeshiftForm = bot->GetShapeshiftForm();
|
||||
masterInShapeshiftForm = master->GetShapeshiftForm();
|
||||
}
|
||||
|
||||
@@ -195,7 +195,7 @@ bool ChooseRpgTargetAction::Execute(Event event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// if (possiblePlayers.size() > 200 || HasSameTarget(guidP, urand(5, 15), possiblePlayers))
|
||||
// continue;
|
||||
|
||||
|
||||
@@ -86,11 +86,11 @@ bool DropTargetAction::Execute(Event event)
|
||||
if (bot->getClass() == CLASS_HUNTER) // Check for Hunter Class
|
||||
{
|
||||
Spell const* spell = bot->GetCurrentSpell(CURRENT_AUTOREPEAT_SPELL); // Get the current spell being cast by the bot
|
||||
if (spell && spell->m_spellInfo->Id == 75) //Check spell is not nullptr before accessing m_spellInfo
|
||||
if (spell && spell->m_spellInfo->Id == 75) //Check spell is not nullptr before accessing m_spellInfo
|
||||
{
|
||||
bot->InterruptSpell(CURRENT_AUTOREPEAT_SPELL); // Interrupt Auto Shot
|
||||
}
|
||||
}
|
||||
}
|
||||
bot->AttackStop();
|
||||
|
||||
// if (Pet* pet = bot->GetPet())
|
||||
|
||||
@@ -16,7 +16,7 @@ bool ChooseTravelTargetAction::Execute(Event event)
|
||||
//Get the current travel target. This target is no longer active.
|
||||
TravelTarget* oldTarget = context->GetValue<TravelTarget*>("travel target")->Get();
|
||||
|
||||
//Select a new target to travel to.
|
||||
//Select a new target to travel to.
|
||||
TravelTarget newTarget = TravelTarget(botAI);
|
||||
|
||||
if (!oldTarget) return false;
|
||||
@@ -146,7 +146,7 @@ void ChooseTravelTargetAction::getNewTarget(TravelTarget* newTarget, TravelTarge
|
||||
foundTarget = SetBossTarget(newTarget);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//Do quests (start, do, end) 95% chance
|
||||
if (!foundTarget && urand(1, 100) > 5)
|
||||
{
|
||||
|
||||
@@ -110,7 +110,7 @@ void EquipAction::EquipItem(Item* item)
|
||||
WorldPackets::Item::AutoEquipItemSlot nicePacket(std::move(packet));
|
||||
nicePacket.Read();
|
||||
bot->GetSession()->HandleAutoEquipItemSlotOpcode(nicePacket);
|
||||
|
||||
|
||||
std::ostringstream out;
|
||||
out << "Equipping " << chat->FormatItem(itemProto) << " in ranged slot";
|
||||
botAI->TellMaster(out);
|
||||
@@ -123,7 +123,7 @@ void EquipAction::EquipItem(Item* item)
|
||||
bool isWeapon = (itemProto->Class == ITEM_CLASS_WEAPON);
|
||||
bool canTitanGrip = bot->CanTitanGrip();
|
||||
bool canDualWield = bot->CanDualWield();
|
||||
|
||||
|
||||
bool isTwoHander = (invType == INVTYPE_2HWEAPON);
|
||||
bool isValidTGWeapon = false;
|
||||
if (canTitanGrip && isTwoHander)
|
||||
@@ -208,24 +208,24 @@ void EquipAction::EquipItem(Item* item)
|
||||
nicePacket.Read();
|
||||
bot->GetSession()->HandleAutoEquipItemSlotOpcode(nicePacket);
|
||||
}
|
||||
|
||||
|
||||
// Try moving old main hand weapon to offhand if beneficial
|
||||
if (mainHandItem && mainHandCanGoOff && (!offHandItem || mainHandScore > offHandScore))
|
||||
{
|
||||
const ItemTemplate* oldMHProto = mainHandItem->GetTemplate();
|
||||
|
||||
|
||||
WorldPacket offhandPacket(CMSG_AUTOEQUIP_ITEM_SLOT, 2);
|
||||
ObjectGuid oldMHGuid = mainHandItem->GetGUID();
|
||||
offhandPacket << oldMHGuid << uint8(EQUIPMENT_SLOT_OFFHAND);
|
||||
WorldPackets::Item::AutoEquipItemSlot nicePacket(std::move(offhandPacket));
|
||||
nicePacket.Read();
|
||||
bot->GetSession()->HandleAutoEquipItemSlotOpcode(nicePacket);
|
||||
|
||||
|
||||
std::ostringstream moveMsg;
|
||||
moveMsg << "Main hand upgrade found. Moving " << chat->FormatItem(oldMHProto) << " to offhand";
|
||||
botAI->TellMaster(moveMsg);
|
||||
}
|
||||
|
||||
|
||||
std::ostringstream out;
|
||||
out << "Equipping " << chat->FormatItem(itemProto) << " in main hand";
|
||||
botAI->TellMaster(out);
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#include "SpellMgr.h"
|
||||
#include "DBCStores.h"
|
||||
#include "AiObjectContext.h"
|
||||
#include "Log.h"
|
||||
#include "Log.h"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <sstream>
|
||||
@@ -151,9 +151,9 @@ bool EquipGlyphsAction::Execute(Event event)
|
||||
|
||||
botAI->TellMaster("Glyphs updated.");
|
||||
|
||||
// Flag for custom glyphs
|
||||
// Flag for custom glyphs
|
||||
botAI->GetAiObjectContext()->GetValue<bool>("custom_glyphs")->Set(true);
|
||||
LOG_INFO("playerbots", "Custom Glyph Flag set to ON");
|
||||
|
||||
LOG_INFO("playerbots", "Custom Glyph Flag set to ON");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -30,13 +30,13 @@ bool FollowAction::Execute(Event event)
|
||||
WorldLocation loc = formation->GetLocation();
|
||||
if (Formation::IsNullLocation(loc) || loc.GetMapId() == -1)
|
||||
return false;
|
||||
|
||||
|
||||
MovementPriority priority = botAI->GetState() == BOT_STATE_COMBAT ? MovementPriority::MOVEMENT_COMBAT : MovementPriority::MOVEMENT_NORMAL;
|
||||
moved = MoveTo(loc.GetMapId(), loc.GetPositionX(), loc.GetPositionY(), loc.GetPositionZ(), false, false, false,
|
||||
true, priority, true);
|
||||
}
|
||||
|
||||
// This section has been commented out because it was forcing the pet to
|
||||
// This section has been commented out because it was forcing the pet to
|
||||
// follow the bot on every "follow" action tick, overriding any attack or
|
||||
// stay commands that might have been issued by the player.
|
||||
// if (Pet* pet = bot->GetPet())
|
||||
|
||||
153
src/strategy/actions/GenericBuffUtils.cpp
Normal file
153
src/strategy/actions/GenericBuffUtils.cpp
Normal file
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
|
||||
* and/or modify it under version 2 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#include "GenericBuffUtils.h"
|
||||
#include "PlayerbotAIConfig.h"
|
||||
|
||||
#include <map>
|
||||
|
||||
#include "Player.h"
|
||||
#include "Group.h"
|
||||
#include "SpellMgr.h"
|
||||
#include "Chat.h"
|
||||
#include "PlayerbotAI.h"
|
||||
#include "ServerFacade.h"
|
||||
#include "AiObjectContext.h"
|
||||
#include "Value.h"
|
||||
#include "Config.h"
|
||||
#include "PlayerbotTextMgr.h"
|
||||
|
||||
namespace ai::buff
|
||||
{
|
||||
std::string MakeAuraQualifierForBuff(std::string const& name)
|
||||
{
|
||||
// Paladin
|
||||
if (name == "blessing of kings") return "blessing of kings,greater blessing of kings";
|
||||
if (name == "blessing of might") return "blessing of might,greater blessing of might";
|
||||
if (name == "blessing of wisdom") return "blessing of wisdom,greater blessing of wisdom";
|
||||
if (name == "blessing of sanctuary") return "blessing of sanctuary,greater blessing of sanctuary";
|
||||
// Druid
|
||||
if (name == "mark of the wild") return "mark of the wild,gift of the wild";
|
||||
// Mage
|
||||
if (name == "arcane intellect") return "arcane intellect,arcane brilliance";
|
||||
// Priest
|
||||
if (name == "power word: fortitude") return "power word: fortitude,prayer of fortitude";
|
||||
return name;
|
||||
}
|
||||
|
||||
std::string GroupVariantFor(std::string const& name)
|
||||
{
|
||||
// Paladin
|
||||
if (name == "blessing of kings") return "greater blessing of kings";
|
||||
if (name == "blessing of might") return "greater blessing of might";
|
||||
if (name == "blessing of wisdom") return "greater blessing of wisdom";
|
||||
if (name == "blessing of sanctuary") return "greater blessing of sanctuary";
|
||||
// Druid
|
||||
if (name == "mark of the wild") return "gift of the wild";
|
||||
// Mage
|
||||
if (name == "arcane intellect") return "arcane brilliance";
|
||||
// Priest
|
||||
if (name == "power word: fortitude") return "prayer of fortitude";
|
||||
|
||||
return std::string();
|
||||
}
|
||||
|
||||
bool HasRequiredReagents(Player* bot, uint32 spellId)
|
||||
{
|
||||
if (!spellId)
|
||||
return false;
|
||||
|
||||
if (SpellInfo const* info = sSpellMgr->GetSpellInfo(spellId))
|
||||
{ for (int i = 0; i < MAX_SPELL_REAGENTS; ++i)
|
||||
{
|
||||
if (info->Reagent[i] > 0)
|
||||
{
|
||||
uint32 const itemId = info->Reagent[i];
|
||||
int32 const need = info->ReagentCount[i];
|
||||
if ((int32)bot->GetItemCount(itemId, false) < need)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// No reagent required
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string UpgradeToGroupIfAppropriate(
|
||||
Player* bot,
|
||||
PlayerbotAI* botAI,
|
||||
std::string const& baseName,
|
||||
bool announceOnMissing,
|
||||
std::function<void(std::string const&)> announce)
|
||||
{
|
||||
std::string castName = baseName; Group* g = bot->GetGroup();
|
||||
if (!g || g->GetMembersCount() < static_cast<uint32>(sPlayerbotAIConfig->minBotsForGreaterBuff))
|
||||
return castName; // Group too small: stay in solo mode
|
||||
|
||||
if (std::string const groupName = GroupVariantFor(baseName); !groupName.empty())
|
||||
{
|
||||
uint32 const groupVariantSpellId = botAI->GetAiObjectContext()
|
||||
->GetValue<uint32>("spell id", groupName)->Get();
|
||||
|
||||
// We check usefulness on the **basic** buff (not the greater version),
|
||||
// because "spell cast useful" may return false for the greater variant.
|
||||
bool const usefulBase = botAI->GetAiObjectContext()
|
||||
->GetValue<bool>("spell cast useful", baseName)->Get();
|
||||
|
||||
if (groupVariantSpellId && HasRequiredReagents(bot, groupVariantSpellId))
|
||||
{
|
||||
// Learned + reagents OK -> switch to greater
|
||||
return groupName;
|
||||
}
|
||||
|
||||
// Missing reagents -> announce if (a) greater is known, (b) base buff is useful,
|
||||
// (c) announce was requested, (d) a callback is provided.
|
||||
if (announceOnMissing && groupVariantSpellId && usefulBase && announce)
|
||||
{
|
||||
static std::map<std::pair<uint32, std::string>, time_t> s_lastWarn; // par bot & par buff
|
||||
time_t now = std::time(nullptr);
|
||||
uint32 botLow = static_cast<uint32>(bot->GetGUID().GetCounter());
|
||||
time_t& last = s_lastWarn[ std::make_pair(botLow, groupName) ];
|
||||
if (!last || now - last >= sPlayerbotAIConfig->rpWarningCooldown) // Configurable anti-spam
|
||||
{
|
||||
// DB Key choice in regard of the buff
|
||||
std::string key;
|
||||
if (groupName.find("greater blessing") != std::string::npos)
|
||||
key = "rp_missing_reagent_greater_blessing";
|
||||
else if (groupName == "gift of the wild")
|
||||
key = "rp_missing_reagent_gift_of_the_wild";
|
||||
else if (groupName == "arcane brilliance")
|
||||
key = "rp_missing_reagent_arcane_brilliance";
|
||||
else
|
||||
key = "rp_missing_reagent_generic";
|
||||
|
||||
// Placeholders
|
||||
std::map<std::string, std::string> ph;
|
||||
ph["%group_spell"] = groupName;
|
||||
ph["%base_spell"] = baseName;
|
||||
|
||||
// Respecte ai_playerbot_texts_chance if present
|
||||
std::string rp;
|
||||
bool got = sPlayerbotTextMgr->GetBotText(key, rp, ph);
|
||||
if (got && !rp.empty())
|
||||
{
|
||||
announce(rp);
|
||||
last = now;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Minimal Fallback
|
||||
std::ostringstream oss;
|
||||
oss << "Out of components for " << groupName << ". Using " << baseName << "!";
|
||||
announce(oss.str());
|
||||
last = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return castName;
|
||||
}
|
||||
}
|
||||
64
src/strategy/actions/GenericBuffUtils.h
Normal file
64
src/strategy/actions/GenericBuffUtils.h
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
|
||||
* and/or modify it under version 2 of the License, or (at your option), any later version.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include "Common.h"
|
||||
#include "Group.h"
|
||||
#include "Chat.h"
|
||||
#include "Language.h"
|
||||
|
||||
class Player;
|
||||
class PlayerbotAI;
|
||||
|
||||
namespace ai::buff
|
||||
{
|
||||
|
||||
// Build an aura qualifier "single + greater" to avoid double-buffing
|
||||
std::string MakeAuraQualifierForBuff(std::string const& name);
|
||||
|
||||
// Returns the group spell name for a given single-target buff.
|
||||
// If no group equivalent exists, returns "".
|
||||
std::string GroupVariantFor(std::string const& name);
|
||||
|
||||
// Checks if the bot has the required reagents to cast a spell (by its spellId).
|
||||
// Returns false if the spellId is invalid.
|
||||
bool HasRequiredReagents(Player* bot, uint32 spellId);
|
||||
|
||||
|
||||
// Applies the "switch to group buff" policy if: the bot is in a group of size x+,
|
||||
// the group variant is known/useful, and reagents are available. Otherwise, returns baseName.
|
||||
// If announceOnMissing == true and reagents are missing, calls the 'announce' callback
|
||||
// (if provided) to notify the party/raid.
|
||||
std::string UpgradeToGroupIfAppropriate(
|
||||
Player* bot,
|
||||
PlayerbotAI* botAI,
|
||||
std::string const& baseName,
|
||||
bool announceOnMissing = false,
|
||||
std::function<void(std::string const&)> announce = {}
|
||||
);
|
||||
}
|
||||
|
||||
namespace ai::chat {
|
||||
inline std::function<void(std::string const&)> MakeGroupAnnouncer(Player* me)
|
||||
{
|
||||
return [me](std::string const& msg)
|
||||
{
|
||||
if (Group* g = me->GetGroup())
|
||||
{
|
||||
WorldPacket data;
|
||||
ChatMsg type = g->isRaidGroup() ? CHAT_MSG_RAID : CHAT_MSG_PARTY;
|
||||
ChatHandler::BuildChatPacket(data, type, LANG_UNIVERSAL, me, /*receiver=*/nullptr, msg.c_str());
|
||||
g->BroadcastPacket(&data, true, -1, me->GetGUID());
|
||||
}
|
||||
else
|
||||
{
|
||||
me->Say(msg, LANG_UNIVERSAL);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
#include "GenericSpellActions.h"
|
||||
|
||||
#include <ctime>
|
||||
|
||||
#include "Event.h"
|
||||
#include "ItemTemplate.h"
|
||||
#include "ObjectDefines.h"
|
||||
@@ -13,6 +15,14 @@
|
||||
#include "Playerbots.h"
|
||||
#include "ServerFacade.h"
|
||||
#include "WorldPacket.h"
|
||||
#include "Group.h"
|
||||
#include "Chat.h"
|
||||
#include "Language.h"
|
||||
#include "GenericBuffUtils.h"
|
||||
#include "PlayerbotAI.h"
|
||||
|
||||
using ai::buff::MakeAuraQualifierForBuff;
|
||||
using ai::buff::UpgradeToGroupIfAppropriate;
|
||||
|
||||
CastSpellAction::CastSpellAction(PlayerbotAI* botAI, std::string const spell)
|
||||
: Action(botAI, spell), range(botAI->GetRange("spell")), spell(spell)
|
||||
@@ -216,11 +226,24 @@ Value<Unit*>* CurePartyMemberAction::GetTargetValue()
|
||||
return context->GetValue<Unit*>("party member to dispel", dispelType);
|
||||
}
|
||||
|
||||
// Make Bots Paladin, druid, mage use the greater buff rank spell
|
||||
// TODO Priest doen't verify il he have components
|
||||
Value<Unit*>* BuffOnPartyAction::GetTargetValue()
|
||||
{
|
||||
return context->GetValue<Unit*>("party member without aura", spell);
|
||||
return context->GetValue<Unit*>("party member without aura", MakeAuraQualifierForBuff(spell));
|
||||
}
|
||||
|
||||
bool BuffOnPartyAction::Execute(Event event)
|
||||
{
|
||||
std::string castName = spell; // default = mono
|
||||
|
||||
auto SendGroupRP = ai::chat::MakeGroupAnnouncer(bot);
|
||||
castName = ai::buff::UpgradeToGroupIfAppropriate(bot, botAI, castName, /*announceOnMissing=*/true, SendGroupRP);
|
||||
|
||||
return botAI->CastSpell(castName, GetTarget());
|
||||
}
|
||||
// End greater buff fix
|
||||
|
||||
CastShootAction::CastShootAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "shoot")
|
||||
{
|
||||
if (Item* const pItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_RANGED))
|
||||
@@ -299,14 +322,14 @@ bool CastVehicleSpellAction::Execute(Event event)
|
||||
bool UseTrinketAction::Execute(Event event)
|
||||
{
|
||||
Item* trinket1 = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_TRINKET1);
|
||||
|
||||
|
||||
if (trinket1 && UseTrinket(trinket1))
|
||||
return true;
|
||||
|
||||
Item* trinket2 = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_TRINKET2);
|
||||
if (trinket2 && UseTrinket(trinket2))
|
||||
return true;
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -336,7 +359,7 @@ bool UseTrinketAction::UseTrinket(Item* item)
|
||||
|
||||
if (!spellInfo || !spellInfo->IsPositive())
|
||||
return false;
|
||||
|
||||
|
||||
bool applyAura = false;
|
||||
for (int i = 0; i < MAX_SPELL_EFFECTS; i++)
|
||||
{
|
||||
@@ -346,12 +369,12 @@ bool UseTrinketAction::UseTrinket(Item* item)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!applyAura)
|
||||
return false;
|
||||
|
||||
|
||||
uint32 spellProcFlag = spellInfo->ProcFlags;
|
||||
|
||||
|
||||
// Handle items with procflag "if you kill a target that grants honor or experience"
|
||||
// Bots will "learn" the trinket proc, so CanCastSpell() will be true
|
||||
// e.g. on Item https://www.wowhead.com/wotlk/item=44074/oracle-talisman-of-ablution leading to
|
||||
|
||||
@@ -29,7 +29,7 @@ public:
|
||||
|
||||
NextAction** getPrerequisites() override;
|
||||
std::string const getSpell() { return spell; }
|
||||
|
||||
|
||||
protected:
|
||||
std::string spell;
|
||||
float range;
|
||||
@@ -215,17 +215,18 @@ protected:
|
||||
uint32 dispelType;
|
||||
};
|
||||
|
||||
// Make Bots Paladin, druid, mage use the greater buff rank spell
|
||||
class BuffOnPartyAction : public CastBuffSpellAction, public PartyMemberActionNameSupport
|
||||
{
|
||||
public:
|
||||
BuffOnPartyAction(PlayerbotAI* botAI, std::string const spell)
|
||||
: CastBuffSpellAction(botAI, spell), PartyMemberActionNameSupport(spell)
|
||||
{
|
||||
}
|
||||
: CastBuffSpellAction(botAI, spell), PartyMemberActionNameSupport(spell) { }
|
||||
|
||||
Value<Unit*>* GetTargetValue() override;
|
||||
bool Execute(Event event) override;
|
||||
std::string const getName() override { return PartyMemberActionNameSupport::getName(); }
|
||||
};
|
||||
// End Fix
|
||||
|
||||
class CastShootAction : public CastSpellAction
|
||||
{
|
||||
|
||||
@@ -26,55 +26,12 @@ bool GossipHelloAction::Execute(Event event)
|
||||
p >> guid;
|
||||
}
|
||||
|
||||
if (!guid)
|
||||
return false;
|
||||
|
||||
Creature* pCreature = bot->GetNPCIfCanInteractWith(guid, UNIT_NPC_FLAG_NONE);
|
||||
if (!pCreature)
|
||||
{
|
||||
LOG_DEBUG("playerbots",
|
||||
"[PlayerbotMgr]: HandleMasterIncomingPacket - Received CMSG_GOSSIP_HELLO {} not found or you can't "
|
||||
"interact with him.",
|
||||
guid.ToString().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
GossipMenuItemsMapBounds pMenuItemBounds =
|
||||
sObjectMgr->GetGossipMenuItemsMapBounds(pCreature->GetCreatureTemplate()->GossipMenuId);
|
||||
if (pMenuItemBounds.first == pMenuItemBounds.second)
|
||||
return false;
|
||||
|
||||
std::string const text = event.getParam();
|
||||
int32 menuToSelect = -1;
|
||||
if (text.empty())
|
||||
{
|
||||
WorldPacket p1;
|
||||
p1 << guid;
|
||||
bot->GetSession()->HandleGossipHelloOpcode(p1);
|
||||
bot->SetFacingToObject(pCreature);
|
||||
|
||||
std::ostringstream out;
|
||||
out << "--- " << pCreature->GetName() << " ---";
|
||||
botAI->TellMasterNoFacing(out.str());
|
||||
|
||||
TellGossipMenus();
|
||||
}
|
||||
else if (!bot->PlayerTalkClass)
|
||||
{
|
||||
botAI->TellError("I need to talk first");
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!text.empty())
|
||||
menuToSelect = atoi(text.c_str());
|
||||
// if (menuToSelect > 0)
|
||||
// menuToSelect--;
|
||||
|
||||
ProcessGossip(menuToSelect);
|
||||
}
|
||||
|
||||
bot->TalkedToCreature(pCreature->GetEntry(), pCreature->GetGUID());
|
||||
return true;
|
||||
return Execute(guid, menuToSelect, false);
|
||||
}
|
||||
|
||||
void GossipHelloAction::TellGossipText(uint32 textId)
|
||||
@@ -120,16 +77,16 @@ void GossipHelloAction::TellGossipMenus()
|
||||
}
|
||||
}
|
||||
|
||||
bool GossipHelloAction::ProcessGossip(int32 menuToSelect)
|
||||
bool GossipHelloAction::ProcessGossip(int32 menuToSelect, bool silent)
|
||||
{
|
||||
GossipMenu& menu = bot->PlayerTalkClass->GetGossipMenu();
|
||||
if (menuToSelect != -1 && !menu.GetItem(menuToSelect))
|
||||
{
|
||||
botAI->TellError("Unknown gossip option");
|
||||
if (!silent)
|
||||
botAI->TellError("Unknown gossip option");
|
||||
return false;
|
||||
}
|
||||
|
||||
// GossipMenuItem const* item = menu.GetItem(menuToSelect); //not used, line marked for removal.
|
||||
WorldPacket p;
|
||||
std::string code;
|
||||
p << GetMaster()->GetTarget();
|
||||
@@ -137,7 +94,59 @@ bool GossipHelloAction::ProcessGossip(int32 menuToSelect)
|
||||
p << code;
|
||||
bot->GetSession()->HandleGossipSelectOptionOpcode(p);
|
||||
|
||||
TellGossipMenus();
|
||||
if (!silent)
|
||||
TellGossipMenus();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GossipHelloAction::Execute(ObjectGuid guid, int32 menuToSelect, bool silent)
|
||||
{
|
||||
if (!guid)
|
||||
return false;
|
||||
|
||||
Creature* pCreature = bot->GetNPCIfCanInteractWith(guid, UNIT_NPC_FLAG_NONE);
|
||||
if (!pCreature)
|
||||
{
|
||||
LOG_DEBUG("playerbots",
|
||||
"[PlayerbotMgr]: HandleMasterIncomingPacket - Received CMSG_GOSSIP_HELLO {} not found or you can't "
|
||||
"interact with him.",
|
||||
guid.ToString().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
GossipMenuItemsMapBounds pMenuItemBounds =
|
||||
sObjectMgr->GetGossipMenuItemsMapBounds(pCreature->GetCreatureTemplate()->GossipMenuId);
|
||||
if (pMenuItemBounds.first == pMenuItemBounds.second)
|
||||
return false;
|
||||
|
||||
if (menuToSelect == -1)
|
||||
{
|
||||
WorldPacket p1;
|
||||
p1 << guid;
|
||||
bot->GetSession()->HandleGossipHelloOpcode(p1);
|
||||
bot->SetFacingToObject(pCreature);
|
||||
|
||||
if (!silent)
|
||||
{
|
||||
std::ostringstream out;
|
||||
out << "--- " << pCreature->GetName() << " ---";
|
||||
botAI->TellMasterNoFacing(out.str());
|
||||
TellGossipMenus();
|
||||
}
|
||||
}
|
||||
else if (!bot->PlayerTalkClass)
|
||||
{
|
||||
if (!silent)
|
||||
botAI->TellError("I need to talk first");
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!ProcessGossip(menuToSelect, silent))
|
||||
return false;
|
||||
}
|
||||
|
||||
bot->TalkedToCreature(pCreature->GetEntry(), pCreature->GetGUID());
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -16,10 +16,12 @@ public:
|
||||
GossipHelloAction(PlayerbotAI* botAI) : Action(botAI, "gossip hello") {}
|
||||
|
||||
bool Execute(Event event) override;
|
||||
// Overload for direct usage
|
||||
bool Execute(ObjectGuid guid, int32 menuToSelect, bool silent = false);
|
||||
|
||||
private:
|
||||
void TellGossipMenus();
|
||||
bool ProcessGossip(int32 menuToSelect);
|
||||
bool ProcessGossip(int32 menuToSelect, bool silent);
|
||||
void TellGossipText(uint32 textId);
|
||||
};
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "Playerbots.h"
|
||||
#include "RandomPlayerbotFactory.h"
|
||||
#include "ServerFacade.h"
|
||||
#include "SharedDefines.h" // GOLD
|
||||
|
||||
bool BuyPetitionAction::Execute(Event event)
|
||||
{
|
||||
@@ -237,6 +238,15 @@ bool PetitionTurnInAction::Execute(Event event)
|
||||
|
||||
if (bot->GetGuildId())
|
||||
{
|
||||
// Ensure that bot has at least 10g for HandleSetEmblem can be managed core side
|
||||
// (EMBLEM_PRICE = 10 * GOLD in core)
|
||||
static constexpr uint32 REQUIRED = 10 * GOLD;
|
||||
uint32 have = bot->GetMoney(); // actual money earned by bot in copper
|
||||
if (have < REQUIRED)
|
||||
{
|
||||
bot->ModifyMoney(int32(REQUIRED - have)); // add only the missing amount to bot to reach 10g
|
||||
}
|
||||
|
||||
Guild* guild = sGuildMgr->GetGuildById(bot->GetGuildId());
|
||||
|
||||
uint32 st, cl, br, bc, bg;
|
||||
@@ -247,7 +257,7 @@ bool PetitionTurnInAction::Execute(Event event)
|
||||
st = urand(0, 180);
|
||||
EmblemInfo emblemInfo(st, cl, br, bc, bg);
|
||||
|
||||
guild->HandleSetEmblem(emblemInfo);
|
||||
guild->HandleSetEmblem(emblemInfo); // official core handling
|
||||
|
||||
// LANG_GUILD_VETERAN -> can invite
|
||||
guild->HandleSetRankInfo(2, GR_RIGHT_GCHATLISTEN | GR_RIGHT_GCHATSPEAK | GR_RIGHT_INVITE);
|
||||
|
||||
@@ -90,7 +90,11 @@ void GuildInviteAction::SendPacket(WorldPacket packet)
|
||||
bot->GetSession()->HandleGuildInviteOpcode(data);
|
||||
}
|
||||
|
||||
bool GuildInviteAction::PlayerIsValid(Player* member) { return !member->GetGuildId(); }
|
||||
bool GuildInviteAction::PlayerIsValid(Player* member)
|
||||
{
|
||||
return !member->GetGuildId() && (sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_GUILD) ||
|
||||
(bot->GetTeamId() == member->GetTeamId()));
|
||||
}
|
||||
|
||||
bool GuildPromoteAction::isUseful()
|
||||
{
|
||||
@@ -205,7 +209,7 @@ bool GuildManageNearbyAction::Execute(Event event)
|
||||
if (botAi->GetGuilderType() == GuilderType::SOLO && !botAi->HasRealPlayerMaster()) //Do not invite solo players.
|
||||
continue;
|
||||
|
||||
if (botAi->HasActivePlayerMaster() && !sRandomPlayerbotMgr->IsRandomBot(player)) //Do not invite alts of active players.
|
||||
if (botAi->HasActivePlayerMaster() && !sRandomPlayerbotMgr->IsRandomBot(player)) //Do not invite alts of active players.
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -30,8 +30,8 @@ bool PartyCommandAction::Execute(Event event)
|
||||
Player* master = GetMaster();
|
||||
if (master && member == master->GetName())
|
||||
return Leave(bot);
|
||||
|
||||
botAI->Reset();
|
||||
|
||||
botAI->Reset();
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -64,8 +64,8 @@ bool UninviteAction::Execute(Event event)
|
||||
if (bot->GetGUID() == guid)
|
||||
return Leave(bot);
|
||||
}
|
||||
|
||||
botAI->Reset();
|
||||
|
||||
botAI->Reset();
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -162,8 +162,8 @@ bool LeaveFarAwayAction::isUseful()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
botAI->Reset();
|
||||
|
||||
botAI->Reset();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ uint32 LfgJoinAction::GetRoles()
|
||||
|
||||
default:
|
||||
return PLAYER_ROLE_DAMAGE;
|
||||
break;
|
||||
break;
|
||||
}
|
||||
|
||||
return PLAYER_ROLE_DAMAGE;
|
||||
@@ -151,7 +151,7 @@ bool LfgJoinAction::JoinLFG()
|
||||
|
||||
// Set RbotAId Browser comment
|
||||
std::string const _gs = std::to_string(botAI->GetEquipGearScore(bot/*, false, false*/));
|
||||
|
||||
|
||||
// JoinLfg is not threadsafe, so make packet and queue into session
|
||||
// sLFGMgr->JoinLfg(bot, roleMask, list, _gs);
|
||||
|
||||
@@ -180,7 +180,7 @@ bool LfgRoleCheckAction::Execute(Event event)
|
||||
// if (currentRoles == newRoles)
|
||||
// return false;
|
||||
|
||||
|
||||
|
||||
WorldPacket* packet = new WorldPacket(CMSG_LFG_SET_ROLES);
|
||||
*packet << (uint8)newRoles;
|
||||
bot->GetSession()->QueuePacket(packet);
|
||||
@@ -317,8 +317,8 @@ bool LfgJoinAction::isUseful()
|
||||
|
||||
if (bot->GetLevel() < 15)
|
||||
return false;
|
||||
|
||||
// don't use if active player master
|
||||
|
||||
// don't use if active player master
|
||||
if (GET_PLAYERBOT_AI(bot)->IsRealPlayer())
|
||||
return false;
|
||||
|
||||
|
||||
@@ -139,17 +139,17 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
|
||||
{
|
||||
if (itr->second->State == PLAYERSPELL_REMOVED || !itr->second->Active)
|
||||
continue;
|
||||
|
||||
|
||||
if (!(itr->second->specMask & bot->GetActiveSpecMask()))
|
||||
continue;
|
||||
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(itr->first);
|
||||
if (!spellInfo)
|
||||
continue;
|
||||
|
||||
|
||||
if (spellInfo->IsPassive())
|
||||
continue;
|
||||
|
||||
|
||||
SkillLineAbilityEntry const* skillLine = skillSpells[itr->first];
|
||||
if (skill != SKILL_NONE && (!skillLine || skillLine->SkillLine != skill))
|
||||
continue;
|
||||
@@ -273,7 +273,7 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
|
||||
|
||||
if (out.str().empty())
|
||||
continue;
|
||||
|
||||
|
||||
if (itr->first == 0)
|
||||
{
|
||||
LOG_ERROR("playerbots", "?! {}", itr->first);
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
#include "BroadcastHelper.h"
|
||||
|
||||
bool LootAction::Execute(Event /*event*/)
|
||||
{
|
||||
{
|
||||
if (!AI_VALUE(bool, "has available loot"))
|
||||
return false;
|
||||
|
||||
@@ -156,7 +156,7 @@ bool OpenLootAction::DoLoot(LootObject& lootObject)
|
||||
uint32 spellId = GetOpeningSpell(lootObject);
|
||||
if (!spellId)
|
||||
return false;
|
||||
|
||||
|
||||
return botAI->CastSpell(spellId, bot);
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ bool LootRollAction::Execute(Event event)
|
||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
|
||||
if (!proto)
|
||||
continue;
|
||||
|
||||
|
||||
std::string itemUsageParam;
|
||||
if (randomProperty != 0) {
|
||||
itemUsageParam = std::to_string(itemId) + "," + std::to_string(randomProperty);
|
||||
@@ -47,7 +47,7 @@ bool LootRollAction::Execute(Event event)
|
||||
itemUsageParam = std::to_string(itemId);
|
||||
}
|
||||
ItemUsage usage = AI_VALUE2(ItemUsage, "item usage", itemUsageParam);
|
||||
|
||||
|
||||
// Armor Tokens are classed as MISC JUNK (Class 15, Subclass 0), luckily no other items I found have class bits and epic quality.
|
||||
if (proto->Class == ITEM_CLASS_MISC && proto->SubClass == ITEM_SUBCLASS_JUNK && proto->Quality == ITEM_QUALITY_EPIC)
|
||||
{
|
||||
@@ -93,7 +93,7 @@ bool LootRollAction::Execute(Event event)
|
||||
{
|
||||
vote = PASS;
|
||||
}
|
||||
else
|
||||
else
|
||||
{
|
||||
vote = GREED;
|
||||
}
|
||||
@@ -228,7 +228,7 @@ bool RollUniqueCheck(ItemTemplate const* proto, Player* bot)
|
||||
bool RollAction::Execute(Event event)
|
||||
{
|
||||
std::string link = event.getParam();
|
||||
|
||||
|
||||
if (link.empty())
|
||||
{
|
||||
bot->DoRandomRoll(0,100);
|
||||
@@ -245,7 +245,7 @@ bool RollAction::Execute(Event event)
|
||||
}
|
||||
std::string itemUsageParam;
|
||||
itemUsageParam = std::to_string(itemId);
|
||||
|
||||
|
||||
ItemUsage usage = AI_VALUE2(ItemUsage, "item usage", itemUsageParam);
|
||||
switch (proto->Class)
|
||||
{
|
||||
|
||||
@@ -1040,7 +1040,7 @@ void MovementAction::UpdateMovementState()
|
||||
|
||||
// Save current state for the next check
|
||||
wasMovementRestricted = isCurrentlyRestricted;
|
||||
|
||||
|
||||
// Temporary speed increase in group
|
||||
// if (botAI->HasRealPlayerMaster()) {
|
||||
// bot->SetSpeedRate(MOVE_RUN, 1.1f);
|
||||
@@ -2412,7 +2412,7 @@ bool TankFaceAction::Execute(Event event)
|
||||
|
||||
if (!AI_VALUE2(bool, "has aggro", "current target"))
|
||||
return false;
|
||||
|
||||
|
||||
float averageAngle = AverageGroupAngle(target, true);
|
||||
|
||||
if (averageAngle == 0.0f)
|
||||
@@ -2435,7 +2435,7 @@ bool TankFaceAction::Execute(Event event)
|
||||
std::vector<Position> availablePos;
|
||||
float x, y, z;
|
||||
target->GetNearPoint(bot, x, y, z, 0.0f, dist, goodAngle1);
|
||||
if (bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(),
|
||||
if (bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(),
|
||||
x, y, z))
|
||||
{
|
||||
/// @todo: movement control now is a mess, prepare to rewrite
|
||||
@@ -2448,7 +2448,7 @@ bool TankFaceAction::Execute(Event event)
|
||||
}
|
||||
}
|
||||
target->GetNearPoint(bot, x, y, z, 0.0f, dist, goodAngle2);
|
||||
if (bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(),
|
||||
if (bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(),
|
||||
x, y, z))
|
||||
{
|
||||
std::list<FleeInfo>& infoList = AI_VALUE(std::list<FleeInfo>&, "recently flee info");
|
||||
@@ -2500,7 +2500,7 @@ bool RearFlankAction::Execute(Event event)
|
||||
{
|
||||
destination = &rightFlank;
|
||||
}
|
||||
|
||||
|
||||
return MoveTo(bot->GetMapId(), destination->GetPositionX(), destination->GetPositionY(), destination->GetPositionZ(),
|
||||
false, false, false, true, MovementPriority::MOVEMENT_COMBAT);
|
||||
}
|
||||
@@ -2666,7 +2666,7 @@ bool SetBehindTargetAction::Execute(Event event)
|
||||
std::vector<Position> availablePos;
|
||||
float x, y, z;
|
||||
target->GetNearPoint(bot, x, y, z, 0.0f, dist, goodAngle1);
|
||||
if (bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(),
|
||||
if (bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(),
|
||||
x, y, z))
|
||||
{
|
||||
/// @todo: movement control now is a mess, prepare to rewrite
|
||||
@@ -2679,7 +2679,7 @@ bool SetBehindTargetAction::Execute(Event event)
|
||||
}
|
||||
}
|
||||
target->GetNearPoint(bot, x, y, z, 0.0f, dist, goodAngle2);
|
||||
if (bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(),
|
||||
if (bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(),
|
||||
x, y, z))
|
||||
{
|
||||
std::list<FleeInfo>& infoList = AI_VALUE(std::list<FleeInfo>&, "recently flee info");
|
||||
|
||||
@@ -186,7 +186,7 @@ void OutfitAction::Update(std::string const name)
|
||||
{
|
||||
ListItemsVisitor visitor;
|
||||
IterateItems(&visitor, ITERATE_ITEMS_IN_EQUIP);
|
||||
|
||||
|
||||
ItemIds items;
|
||||
for (std::map<uint32, uint32>::iterator i = visitor.items.begin(); i != visitor.items.end(); ++i)
|
||||
items.insert(i->first);
|
||||
|
||||
@@ -23,7 +23,7 @@ public:
|
||||
|
||||
private:
|
||||
bool warningEnabled = true;
|
||||
std::string defaultCmd;
|
||||
std::string defaultCmd;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -113,7 +113,7 @@ bool QueryQuestAction::Execute(Event event)
|
||||
void QueryQuestAction::TellObjectives(uint32 questId)
|
||||
{
|
||||
Quest const* questTemplate = sObjectMgr->GetQuestTemplate(questId);
|
||||
|
||||
|
||||
// Checks if the questTemplate is valid
|
||||
if (!questTemplate)
|
||||
{
|
||||
|
||||
@@ -278,7 +278,7 @@ bool QuestUpdateCompleteAction::Execute(Event event)
|
||||
{
|
||||
// std::map<std::string, std::string> placeholders;
|
||||
// placeholders["%quest_link"] = format;
|
||||
|
||||
|
||||
// if (botAI->HasStrategy("debug quest", BotState::BOT_STATE_NON_COMBAT) || botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT))
|
||||
// {
|
||||
// LOG_INFO("playerbots", "{} => Quest [ {} ] completed", bot->GetName(), qInfo->GetTitle());
|
||||
@@ -338,7 +338,7 @@ bool QuestUpdateAddKillAction::Execute(Event event)
|
||||
botAI->TellMasterNoFacing(out.str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -373,7 +373,7 @@ bool QuestUpdateAddItemAction::Execute(Event event)
|
||||
|
||||
BroadcastHelper::BroadcastQuestUpdateAddItem(botAI, bot, pair.first, availableItemsCount, requiredItemsCount, itemPrototype);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -390,7 +390,7 @@ bool QuestItemPushResultAction::Execute(Event event)
|
||||
|
||||
if (guid != bot->GetGUID())
|
||||
return false;
|
||||
|
||||
|
||||
const ItemTemplate* proto = sObjectMgr->GetItemTemplate(itemEntry);
|
||||
if (!proto)
|
||||
return false;
|
||||
@@ -406,13 +406,13 @@ bool QuestItemPushResultAction::Execute(Event event)
|
||||
return false;
|
||||
|
||||
const QuestStatusData& q_status = bot->getQuestStatusMap().at(questId);
|
||||
|
||||
|
||||
for (int i = 0; i < QUEST_ITEM_OBJECTIVES_COUNT; i++)
|
||||
{
|
||||
uint32 itemId = quest->RequiredItemId[i];
|
||||
if (!itemId)
|
||||
continue;
|
||||
|
||||
|
||||
int32 previousCount = itemCount - count;
|
||||
if (itemId == itemEntry && previousCount < quest->RequiredItemCount[i])
|
||||
{
|
||||
|
||||
@@ -20,7 +20,7 @@ bool ReleaseSpiritAction::Execute(Event event)
|
||||
{
|
||||
if (bot->IsAlive())
|
||||
{
|
||||
if (!bot->InBattleground())
|
||||
if (!bot->InBattleground())
|
||||
{
|
||||
botAI->TellMasterNoFacing("I am not dead, will wait here");
|
||||
// -follow in bg is overwriten each tick with +follow
|
||||
@@ -38,8 +38,8 @@ bool ReleaseSpiritAction::Execute(Event event)
|
||||
}
|
||||
|
||||
const WorldPacket& packet = event.getPacket();
|
||||
const std::string message = !packet.empty() && packet.GetOpcode() == CMSG_REPOP_REQUEST
|
||||
? "Releasing..."
|
||||
const std::string message = !packet.empty() && packet.GetOpcode() == CMSG_REPOP_REQUEST
|
||||
? "Releasing..."
|
||||
: "Meet me at the graveyard";
|
||||
botAI->TellMasterNoFacing(message);
|
||||
|
||||
@@ -89,7 +89,7 @@ bool AutoReleaseSpiritAction::Execute(Event event)
|
||||
bot->GetSession()->HandleRepopRequestOpcode(packet);
|
||||
|
||||
LogRelease("releases spirit", true);
|
||||
|
||||
|
||||
if (bot->InBattleground())
|
||||
{
|
||||
return HandleBattlegroundSpiritHealer();
|
||||
@@ -117,8 +117,8 @@ bool AutoReleaseSpiritAction::HandleBattlegroundSpiritHealer()
|
||||
{
|
||||
constexpr uint32_t RESURRECT_DELAY = 15;
|
||||
const time_t now = time(nullptr);
|
||||
|
||||
if ((now - m_bgGossipTime < RESURRECT_DELAY) &&
|
||||
|
||||
if ((now - m_bgGossipTime < RESURRECT_DELAY) &&
|
||||
bot->HasAura(SPELL_WAITING_FOR_RESURRECT))
|
||||
{
|
||||
return false;
|
||||
@@ -174,9 +174,9 @@ bool AutoReleaseSpiritAction::ShouldAutoRelease() const
|
||||
if (!botAI->HasActivePlayerMaster())
|
||||
return true;
|
||||
|
||||
if (botAI->HasActivePlayerMaster() &&
|
||||
if (botAI->HasActivePlayerMaster() &&
|
||||
groupMaster->GetMapId() == bot->GetMapId() &&
|
||||
bot->GetMap() &&
|
||||
bot->GetMap() &&
|
||||
(bot->GetMap()->IsRaid() || bot->GetMap()->IsDungeon()))
|
||||
{
|
||||
return false;
|
||||
@@ -218,7 +218,7 @@ bool AutoReleaseSpiritAction::ShouldDelayBattlegroundRelease() const
|
||||
bool RepopAction::Execute(Event event)
|
||||
{
|
||||
const GraveyardStruct* graveyard = GetGrave(
|
||||
AI_VALUE(uint32, "death count") > 10 ||
|
||||
AI_VALUE(uint32, "death count") > 10 ||
|
||||
CalculateDeadTime() > 30 * MINUTE
|
||||
);
|
||||
|
||||
@@ -238,7 +238,7 @@ int64 RepopAction::CalculateDeadTime() const
|
||||
{
|
||||
if (Corpse* corpse = bot->GetCorpse())
|
||||
return time(nullptr) - corpse->GetGhostTime();
|
||||
|
||||
|
||||
return bot->isDead() ? 0 : 60 * MINUTE;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ protected:
|
||||
void toxicLinks();
|
||||
void somethingToxic();
|
||||
void thunderfury();
|
||||
|
||||
|
||||
std::vector<uint32> GetIncompletedQuests();
|
||||
|
||||
private:
|
||||
|
||||
@@ -102,7 +102,7 @@ void TalkToQuestGiverAction::RewardNoItem(Quest const* quest, Object* questGiver
|
||||
{
|
||||
std::map<std::string, std::string> args;
|
||||
args["%quest"] = chat->FormatQuest(quest);
|
||||
|
||||
|
||||
if (bot->CanRewardQuest(quest, false))
|
||||
{
|
||||
out << BOT_TEXT2("quest_status_completed", args);
|
||||
|
||||
@@ -13,13 +13,13 @@ bool TeleportAction::Execute(Event event)
|
||||
{
|
||||
/*
|
||||
// List of allowed portal entries (you can populate this dynamically)
|
||||
std::vector<uint32> allowedPortals = {
|
||||
187055, 195142, 195141, 201797, 202079, 194481, 195682, 191164, 176498, 182351,
|
||||
178404, 176497, 181146, 184605, 176499, 195140, 193948, 193427, 193052, 193206,
|
||||
192786, 184594, 183384, 182352, 184604, 189994, 193053, 193207, 193956, 195139,
|
||||
176296, 194011, 194012, 189993, 176500, 176501, 193955, 193425, 193772, 193604,
|
||||
191006, 191007, 191008, 191009, 191013, 191014, 191010, 190960, 191011, 191012,
|
||||
183317, 183321, 183322, 187056, 183323, 183324, 183325, 183326, 183327, 190203,
|
||||
std::vector<uint32> allowedPortals = {
|
||||
187055, 195142, 195141, 201797, 202079, 194481, 195682, 191164, 176498, 182351,
|
||||
178404, 176497, 181146, 184605, 176499, 195140, 193948, 193427, 193052, 193206,
|
||||
192786, 184594, 183384, 182352, 184604, 189994, 193053, 193207, 193956, 195139,
|
||||
176296, 194011, 194012, 189993, 176500, 176501, 193955, 193425, 193772, 193604,
|
||||
191006, 191007, 191008, 191009, 191013, 191014, 191010, 190960, 191011, 191012,
|
||||
183317, 183321, 183322, 187056, 183323, 183324, 183325, 183326, 183327, 190203,
|
||||
190204, 190205, 190206, 193908, 181575, 181576, 181577, 181578, 202277, 202278
|
||||
};
|
||||
|
||||
@@ -60,7 +60,7 @@ bool TeleportAction::Execute(Event event)
|
||||
return true;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
// If no portal was found, fallback to spellcaster-type game objects
|
||||
GuidVector gos = *context->GetValue<GuidVector>("nearest game objects");
|
||||
for (ObjectGuid const guid : gos)
|
||||
@@ -91,7 +91,7 @@ bool TeleportAction::Execute(Event event)
|
||||
spell->cast(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// If no game objects were found, try using the last area trigger
|
||||
LastMovement& movement = context->GetValue<LastMovement&>("last area trigger")->Get();
|
||||
if (movement.lastAreaTrigger)
|
||||
|
||||
@@ -24,7 +24,7 @@ bool TradeStatusAction::Execute(Event event)
|
||||
return false;
|
||||
|
||||
PlayerbotAI* traderBotAI = GET_PLAYERBOT_AI(trader);
|
||||
|
||||
|
||||
// Allow the master and group members to trade
|
||||
if (trader != master && !traderBotAI && (!bot->GetGroup() || !bot->GetGroup()->IsMember(trader->GetGUID())))
|
||||
{
|
||||
@@ -181,7 +181,7 @@ bool TradeStatusAction::CheckTrade()
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (isGettingItem)
|
||||
{
|
||||
if (bot->GetGroup() && bot->GetGroup()->IsMember(bot->GetTrader()->GetGUID()) &&
|
||||
|
||||
@@ -66,7 +66,7 @@ bool UnlockTradedItemAction::CanUnlockItem(Item* item)
|
||||
else
|
||||
{
|
||||
std::ostringstream out;
|
||||
out << "Lockpicking skill too low (" << botSkill << "/" << requiredSkill << ") to unlock: "
|
||||
out << "Lockpicking skill too low (" << botSkill << "/" << requiredSkill << ") to unlock: "
|
||||
<< item->GetTemplate()->Name1;
|
||||
botAI->TellMaster(out.str());
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ bool UseMeetingStoneAction::Execute(Event event)
|
||||
|
||||
GameObjectTemplate const* goInfo = gameObject->GetGOInfo();
|
||||
if (!goInfo || goInfo->entry != 179944)
|
||||
return false;
|
||||
return false;
|
||||
|
||||
return Teleport(master, bot);
|
||||
}
|
||||
@@ -169,7 +169,7 @@ bool SummonAction::Teleport(Player* summoner, Player* player)
|
||||
// Player* master = GetMaster();
|
||||
if (!summoner)
|
||||
return false;
|
||||
|
||||
|
||||
if (player->GetVehicle())
|
||||
{
|
||||
botAI->TellError("You cannot summon me while I'm on a vehicle");
|
||||
|
||||
@@ -23,7 +23,7 @@ bool EnterVehicleAction::Execute(Event event)
|
||||
// do not switch vehicles yet
|
||||
if (bot->GetVehicle())
|
||||
return false;
|
||||
|
||||
|
||||
Player* master = botAI->GetMaster();
|
||||
// Triggered by a chat command
|
||||
if (event.getOwner() && master && master->GetTarget())
|
||||
@@ -45,7 +45,7 @@ bool EnterVehicleAction::Execute(Event event)
|
||||
Unit* vehicleBase = botAI->GetUnit(*i);
|
||||
if (!vehicleBase)
|
||||
continue;
|
||||
|
||||
|
||||
if (vehicleBase->HasUnitFlag(UNIT_FLAG_NOT_SELECTABLE))
|
||||
continue;
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ public:
|
||||
creators["quest update complete"] = &WorldPacketActionContext::quest_update_complete;
|
||||
creators["turn in query quest"] = &WorldPacketActionContext::turn_in_query_quest;
|
||||
creators["quest item push result"] = &WorldPacketActionContext::quest_item_push_result;
|
||||
|
||||
|
||||
creators["party command"] = &WorldPacketActionContext::party_command;
|
||||
creators["tell cast failed"] = &WorldPacketActionContext::tell_cast_failed;
|
||||
creators["accept duel"] = &WorldPacketActionContext::accept_duel;
|
||||
|
||||
@@ -43,7 +43,7 @@ bool XpGainAction::Execute(Event event)
|
||||
// randomBotXPRate is now implemented in OnPlayerGiveXP script
|
||||
// if (!sRandomPlayerbotMgr->IsRandomBot(bot) || sPlayerbotAIConfig->randomBotXPRate == 1)
|
||||
// return true;
|
||||
|
||||
|
||||
// Unit* victim = nullptr;
|
||||
// if (guid)
|
||||
// victim = botAI->GetUnit(guid);
|
||||
|
||||
@@ -73,6 +73,6 @@ bool DeathAndDecayCooldownTrigger::IsActive()
|
||||
uint32 spellId = AI_VALUE2(uint32, "spell id", name);
|
||||
if (!spellId)
|
||||
return true;
|
||||
|
||||
|
||||
return bot->GetSpellCooldownDelay(spellId) >= 2000;
|
||||
}
|
||||
@@ -25,7 +25,7 @@ public:
|
||||
// creators["icy clutch"] = &icy_clutch;
|
||||
creators["horn of winter"] = &horn_of_winter;
|
||||
creators["killing machine"] = &killing_machine; // buff
|
||||
// creators["deathchill"] = &deathchill; //boost
|
||||
// creators["deathchill"] = &deathchill; //boost
|
||||
creators["icebound fortitude"] = &icebound_fortitude;
|
||||
// creators["mind freeze"] = &mind_freeze; interrupt
|
||||
// creators["empower rune weapon"] = &empower_rune_weapon; boost
|
||||
@@ -184,7 +184,7 @@ void GenericDKStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
"horn of winter", NextAction::array(0, new NextAction("horn of winter", ACTION_NORMAL + 1), nullptr)));
|
||||
triggers.push_back(new TriggerNode("critical health",
|
||||
NextAction::array(0, new NextAction("death pact", ACTION_HIGH + 5), nullptr)));
|
||||
|
||||
|
||||
triggers.push_back(
|
||||
new TriggerNode("low health", NextAction::array(0, new NextAction("icebound fortitude", ACTION_HIGH + 5),
|
||||
new NextAction("rune tap", ACTION_HIGH + 4), nullptr)));
|
||||
@@ -193,8 +193,8 @@ void GenericDKStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
new NextAction("pestilence", ACTION_NORMAL + 4),
|
||||
new NextAction("blood boil", ACTION_NORMAL + 3), nullptr)));
|
||||
// triggers.push_back(new TriggerNode("light aoe", NextAction::array(0,
|
||||
// new NextAction("pestilence", ACTION_NORMAL + 4),
|
||||
// nullptr)));
|
||||
// new NextAction("pestilence", ACTION_NORMAL + 4),
|
||||
// nullptr)));
|
||||
triggers.push_back(
|
||||
new TriggerNode("pestilence glyph", NextAction::array(0, new NextAction("pestilence", ACTION_HIGH + 9), NULL)));
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ void UnholyDKStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
{
|
||||
GenericDKStrategy::InitTriggers(triggers);
|
||||
triggers.push_back(new TriggerNode(
|
||||
"death and decay cooldown", NextAction::array(0,
|
||||
"death and decay cooldown", NextAction::array(0,
|
||||
new NextAction("ghoul frenzy", ACTION_DEFAULT + 0.9f),
|
||||
new NextAction("scourge strike", ACTION_DEFAULT + 0.8f),
|
||||
new NextAction("icy touch", ACTION_DEFAULT + 0.7f),
|
||||
@@ -107,7 +107,7 @@ void UnholyDKStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
// "plague strike", NextAction::array(0, new NextAction("plague strike", ACTION_HIGH + 1), nullptr)));
|
||||
|
||||
triggers.push_back(new TriggerNode(
|
||||
"high frost rune", NextAction::array(0,
|
||||
"high frost rune", NextAction::array(0,
|
||||
new NextAction("icy touch", ACTION_NORMAL + 3), nullptr)));
|
||||
|
||||
triggers.push_back(new TriggerNode(
|
||||
|
||||
@@ -133,7 +133,7 @@ void CasterDruidStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
NextAction::array(0, new NextAction("starfire", ACTION_DEFAULT + 0.2f), nullptr)));
|
||||
triggers.push_back(new TriggerNode("eclipse (solar) cooldown",
|
||||
NextAction::array(0, new NextAction("wrath", ACTION_DEFAULT + 0.2f), nullptr)));
|
||||
|
||||
|
||||
triggers.push_back(new TriggerNode(
|
||||
"insect swarm", NextAction::array(0, new NextAction("insect swarm", ACTION_NORMAL + 5), nullptr)));
|
||||
triggers.push_back(
|
||||
|
||||
@@ -14,7 +14,7 @@ class DruidAiObjectContext : public AiObjectContext
|
||||
{
|
||||
public:
|
||||
DruidAiObjectContext(PlayerbotAI* botAI);
|
||||
|
||||
|
||||
static void BuildSharedContexts();
|
||||
static void BuildSharedStrategyContexts(SharedNamedObjectContextList<Strategy>& strategyContexts);
|
||||
static void BuildSharedActionContexts(SharedNamedObjectContextList<Action>& actionContexts);
|
||||
|
||||
@@ -132,7 +132,7 @@ void GenericDruidNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& trig
|
||||
|
||||
triggers.push_back(
|
||||
new TriggerNode("party member low health",
|
||||
NextAction::array(0,
|
||||
NextAction::array(0,
|
||||
new NextAction("wild growth on party", ACTION_MEDIUM_HEAL + 5),
|
||||
new NextAction("regrowth on party", ACTION_MEDIUM_HEAL + 4),
|
||||
new NextAction("rejuvenation on party", ACTION_MEDIUM_HEAL + 3),
|
||||
@@ -160,7 +160,7 @@ void GenericDruidNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& trig
|
||||
new NextAction("regrowth on party", ACTION_MEDIUM_HEAL + 6),
|
||||
new NextAction("rejuvenation on party", ACTION_MEDIUM_HEAL + 5),
|
||||
nullptr)));
|
||||
triggers.push_back(new TriggerNode("party member low health", NextAction::array(0,
|
||||
triggers.push_back(new TriggerNode("party member low health", NextAction::array(0,
|
||||
new NextAction("wild growth on party", ACTION_MEDIUM_HEAL + 5),
|
||||
new NextAction("regrowth on party", ACTION_MEDIUM_HEAL + 4),
|
||||
new NextAction("rejuvenation on party", ACTION_MEDIUM_HEAL + 3),
|
||||
@@ -174,8 +174,8 @@ void GenericDruidNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& trig
|
||||
new NextAction("wild growth on party", ACTION_LIGHT_HEAL + 3),
|
||||
new NextAction("rejuvenation on party", ACTION_LIGHT_HEAL + 2),
|
||||
nullptr)));
|
||||
triggers.push_back(new TriggerNode("party member remove curse", NextAction::array(0,
|
||||
new NextAction("remove curse on party", ACTION_DISPEL + 7),
|
||||
triggers.push_back(new TriggerNode("party member remove curse", NextAction::array(0,
|
||||
new NextAction("remove curse on party", ACTION_DISPEL + 7),
|
||||
nullptr)));
|
||||
|
||||
int specTab = AiFactory::GetPlayerSpecTab(botAI->GetBot());
|
||||
@@ -196,12 +196,12 @@ void GenericDruidBuffStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
NonCombatStrategy::InitTriggers(triggers);
|
||||
|
||||
triggers.push_back(new TriggerNode("mark of the wild on party", NextAction::array(0,
|
||||
new NextAction("mark of the wild on party", 13.0f),
|
||||
new NextAction("mark of the wild on party", 13.0f),
|
||||
nullptr)));
|
||||
triggers.push_back(new TriggerNode("thorns on main tank", NextAction::array(0,
|
||||
new NextAction("thorns on main tank", 11.0f),
|
||||
triggers.push_back(new TriggerNode("thorns on main tank", NextAction::array(0,
|
||||
new NextAction("thorns on main tank", 11.0f),
|
||||
nullptr)));
|
||||
triggers.push_back(new TriggerNode("thorns", NextAction::array(0,
|
||||
new NextAction("thorns", 10.0f),
|
||||
triggers.push_back(new TriggerNode("thorns", NextAction::array(0,
|
||||
new NextAction("thorns", 10.0f),
|
||||
nullptr)));
|
||||
}
|
||||
|
||||
@@ -133,11 +133,11 @@ void DruidCureStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
triggers.push_back(
|
||||
new TriggerNode("party member cure poison",
|
||||
NextAction::array(0, new NextAction("abolish poison on party", ACTION_DISPEL + 1), nullptr)));
|
||||
|
||||
|
||||
triggers.push_back(
|
||||
new TriggerNode("party member remove curse",
|
||||
NextAction::array(0, new NextAction("remove curse on party", ACTION_DISPEL + 7), NULL)));
|
||||
|
||||
|
||||
}
|
||||
|
||||
void DruidBoostStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
|
||||
@@ -82,7 +82,7 @@ void HealDruidStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
|
||||
triggers.push_back(new TriggerNode(
|
||||
"group heal setting",
|
||||
NextAction::array(0,
|
||||
NextAction::array(0,
|
||||
new NextAction("tree form", ACTION_MEDIUM_HEAL + 2.3f),
|
||||
new NextAction("wild growth on party", ACTION_MEDIUM_HEAL + 2.2f),
|
||||
new NextAction("rejuvenation on not full", ACTION_MEDIUM_HEAL + 2.1f),
|
||||
@@ -90,7 +90,7 @@ void HealDruidStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
|
||||
triggers.push_back(
|
||||
new TriggerNode("medium group heal setting",
|
||||
NextAction::array(0,
|
||||
NextAction::array(0,
|
||||
new NextAction("tree form", ACTION_CRITICAL_HEAL + 0.6f),
|
||||
new NextAction("tranquility", ACTION_CRITICAL_HEAL + 0.5f), nullptr)));
|
||||
|
||||
@@ -107,7 +107,7 @@ void HealDruidStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||
// MEDIUM
|
||||
triggers.push_back(
|
||||
new TriggerNode("party member medium health",
|
||||
NextAction::array(0,
|
||||
NextAction::array(0,
|
||||
new NextAction("tree form", ACTION_MEDIUM_HEAL + 0.5f),
|
||||
new NextAction("wild growth on party", ACTION_MEDIUM_HEAL + 0.4f),
|
||||
new NextAction("rejuvenation on party", ACTION_MEDIUM_HEAL + 0.3f),
|
||||
|
||||
@@ -47,7 +47,7 @@ class DungeonStrategyContext : public NamedObjectContext<Strategy>
|
||||
|
||||
// Burning Crusade
|
||||
// ...
|
||||
|
||||
|
||||
// Wrath of the Lich King
|
||||
creators["wotlk-uk"] = &DungeonStrategyContext::wotlk_uk; // Utgarde Keep
|
||||
creators["wotlk-nex"] = &DungeonStrategyContext::wotlk_nex; // The Nexus
|
||||
@@ -84,8 +84,8 @@ class DungeonStrategyContext : public NamedObjectContext<Strategy>
|
||||
static Strategy* wotlk_toc(PlayerbotAI* botAI) { return new WotlkDungeonToCStrategy(botAI); }
|
||||
// NYI from here down
|
||||
static Strategy* wotlk_hor(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); }
|
||||
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -25,7 +25,7 @@ bool AttackWebWrapAction::Execute(Event event)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return Attack(webWrap);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
float KrikthirMultiplier::GetValue(Action* action)
|
||||
{
|
||||
if (!botAI->IsDps(bot)) { return 1.0f; }
|
||||
|
||||
|
||||
// Target is not findable from threat table using AI_VALUE2(),
|
||||
// therefore need to search manually for the unit name
|
||||
Unit* boss = nullptr;
|
||||
|
||||
@@ -11,7 +11,7 @@ void WotlkDungeonANStrategy::InitTriggers(std::vector<TriggerNode*> &triggers)
|
||||
NextAction::array(0, new NextAction("attack web wrap", ACTION_RAID + 5), nullptr)));
|
||||
triggers.push_back(new TriggerNode("krik'thir watchers",
|
||||
NextAction::array(0, new NextAction("krik'thir priority", ACTION_RAID + 4), nullptr)));
|
||||
|
||||
|
||||
// Hadronox
|
||||
// The core AC triggers are very buggy with this boss, but default strat seems to play correctly
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include "AiObjectContext.h"
|
||||
#include "AzjolNerubTriggers.h"
|
||||
|
||||
class WotlkDungeonANTriggerContext : public NamedObjectContext<Trigger>
|
||||
class WotlkDungeonANTriggerContext : public NamedObjectContext<Trigger>
|
||||
{
|
||||
public:
|
||||
WotlkDungeonANTriggerContext()
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
bool KrikthirWebWrapTrigger::IsActive()
|
||||
{
|
||||
if (!botAI->IsDps(bot)) { return false; }
|
||||
|
||||
|
||||
// Target is not findable from threat table using AI_VALUE2(),
|
||||
// therefore need to search manually for the unit name
|
||||
GuidVector targets = AI_VALUE(GuidVector, "possible targets no los");
|
||||
@@ -51,7 +51,7 @@ bool KrikthirWatchersTrigger::IsActive()
|
||||
// for (auto i = triggers.begin(); i != triggers.end(); i++)
|
||||
// {
|
||||
// Unit* unit = botAI->GetUnit(*i);
|
||||
|
||||
|
||||
// if (unit)
|
||||
// {
|
||||
// bot->Yell("TRIGGER="+unit->GetName(), LANG_UNIVERSAL);
|
||||
@@ -64,6 +64,6 @@ bool AnubarakPoundTrigger::IsActive()
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "anub'arak");
|
||||
if (!boss) { return false; }
|
||||
|
||||
|
||||
return boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(SPELL_POUND);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ float EpochMultiplier::GetValue(Action* action)
|
||||
if (!boss) { return 1.0f; }
|
||||
|
||||
if (bot->getClass() == CLASS_HUNTER) { return 1.0f; }
|
||||
|
||||
|
||||
if (dynamic_cast<FleeAction*>(action)) { return 0.0f; }
|
||||
|
||||
return 1.0f;
|
||||
|
||||
@@ -10,12 +10,12 @@ void WotlkDungeonCoSStrategy::InitTriggers(std::vector<TriggerNode*> &triggers)
|
||||
// Salramm the Fleshcrafter
|
||||
triggers.push_back(new TriggerNode("explode ghoul",
|
||||
NextAction::array(0, new NextAction("explode ghoul spread", ACTION_MOVE + 5), nullptr)));
|
||||
|
||||
|
||||
// Chrono-Lord Epoch
|
||||
// Not sure if this actually works, I think I've seen him charge melee characters..?
|
||||
triggers.push_back(new TriggerNode("epoch ranged",
|
||||
NextAction::array(0, new NextAction("epoch stack", ACTION_MOVE + 5), nullptr)));
|
||||
|
||||
|
||||
// Mal'Ganis
|
||||
|
||||
// Infinite Corruptor (Heroic only)
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include "AiObjectContext.h"
|
||||
#include "CullingOfStratholmeTriggers.h"
|
||||
|
||||
class WotlkDungeonCoSTriggerContext : public NamedObjectContext<Trigger>
|
||||
class WotlkDungeonCoSTriggerContext : public NamedObjectContext<Trigger>
|
||||
{
|
||||
public:
|
||||
WotlkDungeonCoSTriggerContext()
|
||||
|
||||
@@ -64,11 +64,11 @@ bool NovosTargetPriorityAction::Execute(Event event)
|
||||
{
|
||||
// TODO: This can be improved, some parts are still buggy.
|
||||
// But it works for now and this fight is very easy
|
||||
|
||||
|
||||
// Designate a dps char to handle the stairs adds.
|
||||
// This is probably better as a melee, so just pick the first
|
||||
// melee dps in the party. If none exist, pick the first ranged.
|
||||
|
||||
|
||||
// TODO: Switch to botAI->Index instead, cleaner
|
||||
Player* stairsDps = nullptr;
|
||||
GuidVector members = AI_VALUE(GuidVector, "group members");
|
||||
@@ -76,7 +76,7 @@ bool NovosTargetPriorityAction::Execute(Event event)
|
||||
{
|
||||
Player* groupMember = botAI->GetPlayer(member);
|
||||
if (!groupMember) { continue; }
|
||||
|
||||
|
||||
if (botAI->IsDps(groupMember))
|
||||
{
|
||||
if (botAI->IsMelee(groupMember))
|
||||
@@ -131,7 +131,7 @@ bool NovosTargetPriorityAction::Execute(Event event)
|
||||
{
|
||||
selectedTargets[0] = unit;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
else if (creatureId == NPC_FETID_TROLL_CORPSE)
|
||||
{
|
||||
|
||||
@@ -10,7 +10,7 @@ float NovosMultiplier::GetValue(Action* action)
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "novos the summoner");
|
||||
if (!boss) { return 1.0f; }
|
||||
|
||||
|
||||
if (boss->FindCurrentSpellBySpellId(SPELL_ARCANE_FIELD) && bot->GetTarget())
|
||||
{
|
||||
if (dynamic_cast<DpsAssistAction*>(action)
|
||||
@@ -25,7 +25,7 @@ float NovosMultiplier::GetValue(Action* action)
|
||||
float TharonjaMultiplier::GetValue(Action* action)
|
||||
{
|
||||
if (!bot->HasAura(SPELL_GIFT_OF_THARONJA)) { return 1.0f; }
|
||||
|
||||
|
||||
// Suppress all skills that are not enabled in skeleton form.
|
||||
// Still allow non-ability actions such as movement
|
||||
if (dynamic_cast<CastSpellAction*>(action)
|
||||
|
||||
@@ -7,7 +7,7 @@ void WotlkDungeonDTKStrategy::InitTriggers(std::vector<TriggerNode*> &triggers)
|
||||
// Trollgore
|
||||
triggers.push_back(new TriggerNode("corpse explode",
|
||||
NextAction::array(0, new NextAction("corpse explode spread", ACTION_MOVE + 5), nullptr)));
|
||||
|
||||
|
||||
// Novos the Summoner
|
||||
// TODO: Can be improved - it's a pretty easy fight but complex to program, revisit if needed
|
||||
triggers.push_back(new TriggerNode("arcane field",
|
||||
@@ -16,7 +16,7 @@ void WotlkDungeonDTKStrategy::InitTriggers(std::vector<TriggerNode*> &triggers)
|
||||
NextAction::array(0, new NextAction("novos positioning", ACTION_MOVE + 4), nullptr)));
|
||||
triggers.push_back(new TriggerNode("arcane field",
|
||||
NextAction::array(0, new NextAction("novos target priority", ACTION_NORMAL + 1), nullptr)));
|
||||
|
||||
|
||||
// King Dred
|
||||
// TODO: Fear ward / tremor totem, or general anti-fear strat development
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include "AiObjectContext.h"
|
||||
#include "DrakTharonKeepTriggers.h"
|
||||
|
||||
class WotlkDungeonDTKTriggerContext : public NamedObjectContext<Trigger>
|
||||
class WotlkDungeonDTKTriggerContext : public NamedObjectContext<Trigger>
|
||||
{
|
||||
public:
|
||||
WotlkDungeonDTKTriggerContext()
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include "SharedDefines.h"
|
||||
|
||||
bool MoveFromBronjahmAction::Execute(Event event)
|
||||
{
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "bronjahm");
|
||||
if (!boss)
|
||||
return false;
|
||||
@@ -46,10 +46,10 @@ bool AttackCorruptedSoulFragmentAction::Execute(Event event)
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ bool BronjahmGroupPositionAction::Execute(Event event)
|
||||
// If soul is near boss, flee from boss
|
||||
if (soulToBossDist < 10.0f)
|
||||
return FleePosition(unit->GetPosition(), 13.0f, 1000U);
|
||||
|
||||
|
||||
// If soul exists but none of the above conditions, don't move to tank position yet
|
||||
bot->SetFacingToObject(boss);
|
||||
return true;
|
||||
|
||||
@@ -30,7 +30,7 @@ bool SwitchToSoulFragment::IsActive()
|
||||
if (!activeSoulExists)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BronjahmPositionTrigger::IsActive()
|
||||
|
||||
@@ -15,11 +15,11 @@ void WotlkDungeonGDStrategy::InitTriggers(std::vector<TriggerNode*> &triggers)
|
||||
NextAction::array(0, new NextAction("avoid poison nova", ACTION_RAID + 5), nullptr)));
|
||||
triggers.push_back(new TriggerNode("snake wrap",
|
||||
NextAction::array(0, new NextAction("attack snake wrap", ACTION_RAID + 4), nullptr)));
|
||||
|
||||
|
||||
// Gal'darah
|
||||
triggers.push_back(new TriggerNode("whirling slash",
|
||||
NextAction::array(0, new NextAction("avoid whirling slash", ACTION_RAID + 5), nullptr)));
|
||||
|
||||
|
||||
// Eck the Ferocious (Heroic only)
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include "AiObjectContext.h"
|
||||
#include "GundrakTriggers.h"
|
||||
|
||||
class WotlkDungeonGDTriggerContext : public NamedObjectContext<Trigger>
|
||||
class WotlkDungeonGDTriggerContext : public NamedObjectContext<Trigger>
|
||||
{
|
||||
public:
|
||||
WotlkDungeonGDTriggerContext()
|
||||
|
||||
@@ -7,7 +7,7 @@ bool SladranPoisonNovaTrigger::IsActive()
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "slad'ran");
|
||||
if (!boss) { return false; }
|
||||
|
||||
|
||||
return bool(boss->FindCurrentSpellBySpellId(SPELL_POISON_NOVA));
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ enum GundrakIDs
|
||||
SPELL_POISON_NOVA_N = 55081,
|
||||
SPELL_POISON_NOVA_H = 59842,
|
||||
NPC_SNAKE_WRAP = 29742,
|
||||
|
||||
|
||||
// Gal'darah
|
||||
SPELL_WHIRLING_SLASH_N = 55250,
|
||||
SPELL_WHIRLING_SLASH_H = 59824,
|
||||
|
||||
@@ -59,7 +59,7 @@ bool VolkhanTargetAction::Execute(Event event)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return Attack(boss);
|
||||
}
|
||||
|
||||
@@ -148,7 +148,7 @@ bool LokenStackAction::Execute(Event event)
|
||||
// else
|
||||
return Move(bot->GetAngle(boss), fmin(bot->GetExactDist2d(boss), maxMovement));
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ float BjarngrimMultiplier::GetValue(Action* action)
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ float VolkhanMultiplier::GetValue(Action* action)
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ void WotlkDungeonHoLStrategy::InitTriggers(std::vector<TriggerNode*> &triggers)
|
||||
// Volkhan
|
||||
triggers.push_back(new TriggerNode("volkhan",
|
||||
NextAction::array(0, new NextAction("volkhan target", ACTION_RAID + 5), nullptr)));
|
||||
|
||||
|
||||
// Ionar
|
||||
triggers.push_back(new TriggerNode("ionar disperse",
|
||||
NextAction::array(0, new NextAction("disperse position", ACTION_MOVE + 5), nullptr)));
|
||||
@@ -24,7 +24,7 @@ void WotlkDungeonHoLStrategy::InitTriggers(std::vector<TriggerNode*> &triggers)
|
||||
// TODO: Targeted player can dodge the ball, but a single player soaking it isn't too bad to heal
|
||||
triggers.push_back(new TriggerNode("ball lightning",
|
||||
NextAction::array(0, new NextAction("ball lightning spread", ACTION_MOVE + 2), nullptr)));
|
||||
|
||||
|
||||
// Loken
|
||||
triggers.push_back(new TriggerNode("lightning nova",
|
||||
NextAction::array(0, new NextAction("avoid lightning nova", ACTION_MOVE + 5), nullptr)));
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include "AiObjectContext.h"
|
||||
#include "HallsOfLightningTriggers.h"
|
||||
|
||||
class WotlkDungeonHoLTriggerContext : public NamedObjectContext<Trigger>
|
||||
class WotlkDungeonHoLTriggerContext : public NamedObjectContext<Trigger>
|
||||
{
|
||||
public:
|
||||
WotlkDungeonHoLTriggerContext()
|
||||
|
||||
@@ -27,7 +27,7 @@ bool BjarngrimWhirlwindTrigger::IsActive()
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "general bjarngrim");
|
||||
if (!boss) { return false; }
|
||||
|
||||
|
||||
return boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(SPELL_WHIRLWIND_BJARNGRIM);
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ bool IonarBallLightningTrigger::IsActive()
|
||||
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "ionar");
|
||||
if (!boss) { return false; }
|
||||
|
||||
|
||||
return boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(SPELL_BALL_LIGHTNING);
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ bool IonarTankAggroTrigger::IsActive()
|
||||
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "ionar");
|
||||
if (!boss) { return false; }
|
||||
|
||||
|
||||
return AI_VALUE2(bool, "has aggro", "current target");
|
||||
}
|
||||
|
||||
@@ -88,6 +88,6 @@ bool LokenLightningNovaTrigger::IsActive()
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "loken");
|
||||
if (!boss) { return false; }
|
||||
|
||||
|
||||
return boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(SPELL_LIGHTNING_NOVA);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ bool ShatterSpreadAction::Execute(Event event)
|
||||
|
||||
float radius = 40.0f;
|
||||
Unit* closestMember = nullptr;
|
||||
|
||||
|
||||
GuidVector members = AI_VALUE(GuidVector, "group members");
|
||||
for (auto& member : members)
|
||||
{
|
||||
@@ -31,7 +31,7 @@ bool ShatterSpreadAction::Execute(Event event)
|
||||
// return MoveAway(closestMember, radius - bot->GetExactDist2d(closestMember));
|
||||
return MoveAway(closestMember, 5.0f);
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ float KrystallusMultiplier::GetValue(Action* action)
|
||||
{
|
||||
Unit* boss = AI_VALUE2(Unit*, "find target", "krystallus");
|
||||
if (!boss) { return 1.0f; }
|
||||
|
||||
|
||||
// Check both of these... the spell is applied first, debuff later.
|
||||
// Neither is active for the full duration so we need to trigger off both
|
||||
if (bot->HasAura(SPELL_GROUND_SLAM) || bot->HasAura(DEBUFF_GROUND_SLAM))
|
||||
|
||||
@@ -11,7 +11,7 @@ void WotlkDungeonHoSStrategy::InitTriggers(std::vector<TriggerNode*> &triggers)
|
||||
// TODO: I think bots need to dismiss pets on this, or they nuke players they are standing close to
|
||||
triggers.push_back(new TriggerNode("ground slam",
|
||||
NextAction::array(0, new NextAction("shatter spread", ACTION_RAID + 5), nullptr)));
|
||||
|
||||
|
||||
// Tribunal of Ages
|
||||
// Seems fine, maybe add focus targeting strat if needed on heroic.
|
||||
// Main issue is dps will immediately rambo in and sometimes die before tank gets aggro,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user