mirror of
https://github.com/mod-playerbots/mod-playerbots
synced 2025-11-29 15:58:20 +08:00
Merge pull request #1601 from brighton-chi/more-chat-filters
Add chat filters for aura, aggro, and spec and make all filters case insensitive
This commit is contained in:
@@ -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,15 +601,12 @@ 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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user