Compare commits

...

6 Commits

Author SHA1 Message Date
kadeshar
4f69b7bb48
Merge pull request #1601 from brighton-chi/more-chat-filters
Add chat filters for aura, aggro, and spec and make all filters case insensitive
2025-09-17 22:46:45 +02:00
Crow
0362db94ad
Exclude Log.h
Not needed
2025-09-17 11:09:59 -05:00
crow
3717c6133e clean-ups and fixes
simplified code
fixed bug where neutral creatures were not captured by the aggroby filter
trim white spaces so space between filter and message is permitted but not required
2025-09-05 12:27:58 -05:00
crow
876455baca Merge remote-tracking branch 'upstream/master' into more-chat-filters 2025-09-02 17:10:42 -05:00
crow
f5aa484e8d Merge remote-tracking branch 'upstream/master' into more-chat-filters 2025-08-28 13:54:22 -05:00
crow
fbf8ed9256 Add chat filters for aura, aggro, and spec and make all filters case insensitive 2025-08-28 13:53:49 -05:00

View File

@ -8,6 +8,18 @@
#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>
#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) std::string const ChatFilter::Filter(std::string& message)
{ {
@ -25,32 +37,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 +82,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 +113,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 +173,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 +218,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 +233,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 +265,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 +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) CompositeChatFilter::CompositeChatFilter(PlayerbotAI* botAI) : ChatFilter(botAI)
{ {
filters.push_back(new StrategyChatFilter(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 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 +601,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;
} }