diff --git a/src/ChatFilter.cpp b/src/ChatFilter.cpp index b07b0848..0b3e7f7a 100644 --- a/src/ChatFilter.cpp +++ b/src/ChatFilter.cpp @@ -8,6 +8,17 @@ #include "Group.h" #include "Playerbots.h" #include "RtiTargetValue.h" +#include "AiFactory.h" + +#include +#include + +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 +36,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 +81,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 +112,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 +172,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::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 +217,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 +232,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::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 +264,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 +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::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("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(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) { 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 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 +613,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::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; }