Add chat filters for aura, aggro, and spec and make all filters case insensitive

This commit is contained in:
crow
2025-08-26 21:21:22 -05:00
parent bc737ecc68
commit fbf8ed9256

View File

@@ -8,6 +8,17 @@
#include "Group.h" #include "Group.h"
#include "Playerbots.h" #include "Playerbots.h"
#include "RtiTargetValue.h" #include "RtiTargetValue.h"
#include "AiFactory.h"
#include <algorithm>
#include <cctype>
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) std::string const ChatFilter::Filter(std::string& message)
{ {
@@ -25,32 +36,33 @@ public:
std::string const Filter(std::string& message) override std::string const Filter(std::string& message) override
{ {
Player* bot = botAI->GetBot(); 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)) if (tank && !botAI->IsTank(bot))
return ""; return "";
bool dps = message.find("@dps") == 0; bool dps = msgLower.find("@dps") == 0;
if (dps && (botAI->IsTank(bot) || botAI->IsHeal(bot))) if (dps && (botAI->IsTank(bot) || botAI->IsHeal(bot)))
return ""; return "";
bool heal = message.find("@heal") == 0; bool heal = msgLower.find("@heal") == 0;
if (heal && !botAI->IsHeal(bot)) if (heal && !botAI->IsHeal(bot))
return ""; return "";
bool ranged = message.find("@ranged") == 0; bool ranged = msgLower.find("@ranged") == 0;
if (ranged && !botAI->IsRanged(bot)) if (ranged && !botAI->IsRanged(bot))
return ""; return "";
bool melee = message.find("@melee") == 0; bool melee = msgLower.find("@melee") == 0;
if (melee && botAI->IsRanged(bot)) if (melee && botAI->IsRanged(bot))
return ""; return "";
bool rangeddps = message.find("@rangeddps") == 0; bool rangeddps = msgLower.find("@rangeddps") == 0;
if (rangeddps && (!botAI->IsRanged(bot) || botAI->IsTank(bot) || botAI->IsHeal(bot))) if (rangeddps && (!botAI->IsRanged(bot) || botAI->IsTank(bot) || botAI->IsHeal(bot)))
return ""; return "";
bool meleedps = message.find("@meleedps") == 0; bool meleedps = msgLower.find("@meleedps") == 0;
if (meleedps && (!botAI->IsMelee(bot) || botAI->IsTank(bot) || botAI->IsHeal(bot))) if (meleedps && (!botAI->IsMelee(bot) || botAI->IsTank(bot) || botAI->IsHeal(bot)))
return ""; return "";
@@ -69,11 +81,11 @@ public:
std::string const Filter(std::string& message) override std::string const Filter(std::string& message) override
{ {
Player* bot = botAI->GetBot(); Player* bot = botAI->GetBot();
std::string msgLower = ToLower(message);
if (message[0] != '@') if (msgLower[0] != '@')
return message; 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 fromLevel = atoi(message.substr(message.find("@") + 1, message.find("-")).c_str());
uint32 toLevel = atoi(message.substr(message.find("-") + 1, message.find(" ")).c_str()); uint32 toLevel = atoi(message.substr(message.find("-") + 1, message.find(" ")).c_str());
@@ -100,9 +112,10 @@ public:
std::string const Filter(std::string& message) override std::string const Filter(std::string& message) override
{ {
Player* bot = botAI->GetBot(); Player* bot = botAI->GetBot();
std::string msgLower = ToLower(message);
bool melee = message.find("@melee") == 0; bool melee = msgLower.find("@melee") == 0;
bool ranged = message.find("@ranged") == 0; bool ranged = msgLower.find("@ranged") == 0;
if (!melee && !ranged) if (!melee && !ranged)
return message; return message;
@@ -159,17 +172,17 @@ public:
std::string const Filter(std::string& message) override std::string const Filter(std::string& message) override
{ {
Player* bot = botAI->GetBot(); Player* bot = botAI->GetBot();
std::string msgLower = ToLower(message);
Group* group = bot->GetGroup(); Group* group = bot->GetGroup();
if (!group) if (!group)
return message; return message;
bool found = false; 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++) for (std::vector<std::string>::iterator i = rtis.begin(); i != rtis.end(); i++)
{ {
std::string const rti = *i; std::string const rti = *i;
std::string rtiLower = ToLower(rti);
bool isRti = message.find(rti) == 0; bool isRti = msgLower.find(rtiLower) == 0;
if (!isRti) if (!isRti)
continue; continue;
@@ -204,7 +217,7 @@ class ClassChatFilter : public ChatFilter
public: public:
ClassChatFilter(PlayerbotAI* botAI) : ChatFilter(botAI) ClassChatFilter(PlayerbotAI* botAI) : ChatFilter(botAI)
{ {
classNames["@death_knight"] = CLASS_DEATH_KNIGHT; classNames["@dk"] = CLASS_DEATH_KNIGHT;
classNames["@druid"] = CLASS_DRUID; classNames["@druid"] = CLASS_DRUID;
classNames["@hunter"] = CLASS_HUNTER; classNames["@hunter"] = CLASS_HUNTER;
classNames["@mage"] = CLASS_MAGE; classNames["@mage"] = CLASS_MAGE;
@@ -219,12 +232,12 @@ public:
std::string const Filter(std::string& message) override std::string const Filter(std::string& message) override
{ {
Player* bot = botAI->GetBot(); Player* bot = botAI->GetBot();
std::string msgLower = ToLower(message);
bool found = false; 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++) 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) if (isClass && bot->getClass() != i->second)
return ""; return "";
@@ -251,8 +264,8 @@ public:
std::string const Filter(std::string& message) override std::string const Filter(std::string& message) override
{ {
Player* bot = botAI->GetBot(); Player* bot = botAI->GetBot();
std::string msgLower = ToLower(message);
if (message.find("@group") == 0) if (msgLower.find("@group") == 0)
{ {
size_t spacePos = message.find(" "); size_t spacePos = message.find(" ");
if (spacePos == std::string::npos) if (spacePos == std::string::npos)
@@ -295,6 +308,290 @@ 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;
}
std::string auraIdOrName = message.substr(prefixLen);
if (auraIdOrName.empty())
{
return message;
}
std::string rest = "";
uint32 auraId = 0;
size_t spacePos = auraIdOrName.find(' ');
std::string idStr = (spacePos != std::string::npos) ? auraIdOrName.substr(0, spacePos) : auraIdOrName;
if (spacePos != std::string::npos)
{
rest = auraIdOrName.substr(spacePos + 1);
}
else
{
rest = "";
}
if (!idStr.empty())
{
bool isNumeric = std::all_of(idStr.begin(), idStr.end(), ::isdigit);
if (isNumeric)
{
auraId = atoi(idStr.c_str());
}
}
if (auraId == 0)
{
// Only allow numeric spell IDs
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;
}
std::string enemyStr = message.substr(prefixLen);
if (enemyStr.empty())
{
return message;
}
std::string rest = "";
std::string enemyName = "";
std::string entryIdStr = "";
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 spacePos = enemyStr.find(' ');
std::string idOrName = (spacePos != std::string::npos) ? enemyStr.substr(0, spacePos) : enemyStr;
if (spacePos != std::string::npos)
{
rest = enemyStr.substr(spacePos + 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;
}
}
}
// Use nearest hostile npcs value and filter by entryID or name within 100 yards
const float radius = 100.0f;
GuidVector npcs = botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest hostile npcs")->Get();
bool match = false;
for (const auto& guid : npcs)
{
Unit* unit = botAI->GetUnit(guid);
if (!unit)
{
continue;
}
if (!unit->IsCreature())
{
continue;
}
Creature* c = static_cast<Creature*>(unit);
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) CompositeChatFilter::CompositeChatFilter(PlayerbotAI* botAI) : ChatFilter(botAI)
{ {
filters.push_back(new StrategyChatFilter(botAI)); filters.push_back(new StrategyChatFilter(botAI));
@@ -303,6 +600,9 @@ CompositeChatFilter::CompositeChatFilter(PlayerbotAI* botAI) : ChatFilter(botAI)
filters.push_back(new CombatTypeChatFilter(botAI)); filters.push_back(new CombatTypeChatFilter(botAI));
filters.push_back(new LevelChatFilter(botAI)); filters.push_back(new LevelChatFilter(botAI));
filters.push_back(new SubGroupChatFilter(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() CompositeChatFilter::~CompositeChatFilter()
@@ -313,15 +613,12 @@ CompositeChatFilter::~CompositeChatFilter()
std::string const CompositeChatFilter::Filter(std::string& message) 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 = filter->Filter(message);
{ if (message.empty())
message = (*i)->Filter(message); break;
if (message.empty())
break;
}
} }
return message; return message;
} }