diff --git a/data/sql/playerbots/updates/2026_01_31_00_ai_playerbot_multi_pvp_text_variables.sql b/data/sql/playerbots/updates/2026_01_31_00_ai_playerbot_multi_pvp_text_variables.sql new file mode 100644 index 00000000..9889147f --- /dev/null +++ b/data/sql/playerbots/updates/2026_01_31_00_ai_playerbot_multi_pvp_text_variables.sql @@ -0,0 +1,101 @@ +-- ######################################################### +-- Playerbots - Add PVP / Arena texts for TellPvpAction +-- Localized for all WotLK locales (koKR, frFR, deDE, zhCN, +-- zhTW, esES, esMX, ruRU) +-- ######################################################### + +-- --------------------------------------------------------- +-- pvp_currency +-- [PVP] Arena points: %arena_points | Honor Points: %honor_points +-- --------------------------------------------------------- +INSERT INTO `ai_playerbot_texts` + (`name`, `text`, `say_type`, `reply_type`, + `text_loc1`, `text_loc2`, `text_loc3`, `text_loc4`, + `text_loc5`, `text_loc6`, `text_loc7`, `text_loc8`) +SELECT + 'pvp_currency', + '[PVP] Arena points: %arena_points | Honor Points: %honor_points', + 0, 0, + -- koKR + '[PVP] 투기장 점수: %arena_points | 명예 점수: %honor_points', + -- frFR + '[PVP] Points d''arène : %arena_points | Points d''honneur : %honor_points', + -- deDE + '[PVP] Arenapunkte: %arena_points | Ehrenpunkte: %honor_points', + -- zhCN + '[PVP] 竞技场点数:%arena_points | 荣誉点数:%honor_points', + -- zhTW + '[PVP] 競技場點數:%arena_points | 榮譽點數:%honor_points', + -- esES + '[PVP] Puntos de arena: %arena_points | Puntos de honor: %honor_points', + -- esMX + '[PVP] Puntos de arena: %arena_points | Puntos de honor: %honor_points', + -- ruRU + '[PVP] Очки арены: %arena_points | Очки чести: %honor_points' +WHERE NOT EXISTS ( + SELECT 1 FROM `ai_playerbot_texts` WHERE `name` = 'pvp_currency' +); + +-- --------------------------------------------------------- +-- pvp_arena_team +-- [PVP] %bracket: <%team_name> (rating %team_rating) +-- --------------------------------------------------------- +INSERT INTO `ai_playerbot_texts` + (`name`, `text`, `say_type`, `reply_type`, + `text_loc1`, `text_loc2`, `text_loc3`, `text_loc4`, + `text_loc5`, `text_loc6`, `text_loc7`, `text_loc8`) +SELECT + 'pvp_arena_team', + '[PVP] %bracket: <%team_name> (rating %team_rating)', + 0, 0, + -- koKR + '[PVP] %bracket: <%team_name> (평점 %team_rating)', + -- frFR + '[PVP] %bracket : <%team_name> (cote %team_rating)', + -- deDE + '[PVP] %bracket: <%team_name> (Wertung %team_rating)', + -- zhCN + '[PVP] %bracket: <%team_name> (评分 %team_rating)', + -- zhTW + '[PVP] %bracket: <%team_name> (評分 %team_rating)', + -- esES + '[PVP] %bracket: <%team_name> (índice %team_rating)', + -- esMX + '[PVP] %bracket: <%team_name> (índice %team_rating)', + -- ruRU + '[PVP] %bracket: <%team_name> (рейтинг %team_rating)' +WHERE NOT EXISTS ( + SELECT 1 FROM `ai_playerbot_texts` WHERE `name` = 'pvp_arena_team' +); + +-- --------------------------------------------------------- +-- pvp_no_arena_team +-- [PVP] I have no Arena Team. +-- --------------------------------------------------------- +INSERT INTO `ai_playerbot_texts` + (`name`, `text`, `say_type`, `reply_type`, + `text_loc1`, `text_loc2`, `text_loc3`, `text_loc4`, + `text_loc5`, `text_loc6`, `text_loc7`, `text_loc8`) +SELECT + 'pvp_no_arena_team', + '[PVP] I have no Arena Team.', + 0, 0, + -- koKR + '[PVP] 투기장 팀이 없습니다.', + -- frFR + '[PVP] Je n''ai aucune équipe d''arène.', + -- deDE + '[PVP] Ich habe kein Arenateam.', + -- zhCN + '[PVP] 我没有竞技场战队。', + -- zhTW + '[PVP] 我沒有競技場隊伍。', + -- esES + '[PVP] No tengo equipo de arena.', + -- esMX + '[PVP] No tengo equipo de arena.', + -- ruRU + '[PVP] У меня нет команды арены.' +WHERE NOT EXISTS ( + SELECT 1 FROM `ai_playerbot_texts` WHERE `name` = 'pvp_no_arena_team' +); diff --git a/src/Ai/Base/Actions/TellPvpStatsAction.cpp b/src/Ai/Base/Actions/TellPvpStatsAction.cpp new file mode 100644 index 00000000..943cf415 --- /dev/null +++ b/src/Ai/Base/Actions/TellPvpStatsAction.cpp @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it + * and/or modify it under version 3 of the License, or (at your option), any later version. + */ + +#include "TellPvpStatsAction.h" + +#include + +#include "ArenaTeam.h" +#include "ArenaTeamMgr.h" +#include "Event.h" +#include "Player.h" +#include "PlayerbotAI.h" +#include "PlayerbotTextMgr.h" +#include "Playerbots.h" +#include "SharedDefines.h" +#include "Language.h" + +namespace +{ + inline char const* BracketName(uint8 slot) + { + switch (slot) + { + case ARENA_SLOT_2v2: return "2v2"; + case ARENA_SLOT_3v3: return "3v3"; + default: return "5v5"; // ARENA_SLOT_5v5 + } + } +} + +bool TellPvpStatsAction::Execute(Event event) +{ + if (!bot) + return false; + + // Prefer the actual chat sender (whisper / say / etc.) if available. + Player* requester = nullptr; + + if (Unit* owner = event.getOwner()) + requester = owner->ToPlayer(); + + // Fallback to master if event owner is not available. + if (!requester) + requester = GetMaster(); + + // If we still do not have a valid player to answer to, bail out. + if (!requester) + return false; + + // PVP currencies + std::map currencyPlaceholders; + currencyPlaceholders["%arena_points"] = std::to_string(bot->GetArenaPoints()); + currencyPlaceholders["%honor_points"] = std::to_string(bot->GetHonorPoints()); + + std::string const currencyText = PlayerbotTextMgr::instance().GetBotTextOrDefault( + "pvp_currency", + "[PVP] Arena points: %arena_points | Honor Points: %honor_points", + currencyPlaceholders); + + bot->Whisper(currencyText, LANG_UNIVERSAL, requester); + + // Arena Teams by slot + bool anyTeam = false; + for (uint8 slot = 0; slot < MAX_ARENA_SLOT; ++slot) + { + uint32 const teamId = bot->GetArenaTeamId(slot); + if (!teamId) + continue; + + if (ArenaTeam* team = sArenaTeamMgr->GetArenaTeamById(teamId)) + { + anyTeam = true; + std::map placeholders; + placeholders["%bracket"] = BracketName(slot); + placeholders["%team_name"] = team->GetName(); + placeholders["%team_rating"] = std::to_string(team->GetRating()); + + std::string const teamText = PlayerbotTextMgr::instance().GetBotTextOrDefault( + "pvp_arena_team", + "[PVP] %bracket: <%team_name> (rating %team_rating)", + placeholders); + + bot->Whisper(teamText, LANG_UNIVERSAL, requester); + } + } + + if (!anyTeam) + { + std::string const noTeamText = PlayerbotTextMgr::instance().GetBotTextOrDefault( + "pvp_no_arena_team", + "[PVP] I have no Arena Team.", + std::map()); + + bot->Whisper(noTeamText, LANG_UNIVERSAL, requester); + } + + return true; +} diff --git a/src/Ai/Base/Actions/TellPvpStatsAction.h b/src/Ai/Base/Actions/TellPvpStatsAction.h new file mode 100644 index 00000000..025cbd0d --- /dev/null +++ b/src/Ai/Base/Actions/TellPvpStatsAction.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it + * and/or modify it under version 3 of the License, or (at your option), any later version. + */ + +#ifndef _PLAYERBOT_TELLPVPSTATSACTION_H +#define _PLAYERBOT_TELLPVPSTATSACTION_H + +#include "Action.h" + +class PlayerbotAI; + +class TellPvpStatsAction : public Action +{ +public: + TellPvpStatsAction(PlayerbotAI* botAI) : Action(botAI, "tell pvp stats") {} + + bool Execute(Event event) override; +}; +#endif \ No newline at end of file diff --git a/src/Ai/Base/ChatActionContext.h b/src/Ai/Base/ChatActionContext.h index 4873f520..5e215e12 100644 --- a/src/Ai/Base/ChatActionContext.h +++ b/src/Ai/Base/ChatActionContext.h @@ -67,6 +67,7 @@ #include "TellItemCountAction.h" #include "TellLosAction.h" #include "TellReputationAction.h" +#include "TellPvpStatsAction.h" #include "TellTargetAction.h" #include "TradeAction.h" #include "TrainerAction.h" @@ -97,6 +98,7 @@ public: creators["quests"] = &ChatActionContext::quests; creators["leave"] = &ChatActionContext::leave; creators["reputation"] = &ChatActionContext::reputation; + creators["tell pvp stats"] = &ChatActionContext::tell_pvp_stats; creators["log"] = &ChatActionContext::log; creators["los"] = &ChatActionContext::los; creators["rpg status"] = &ChatActionContext::rpg_status; @@ -279,6 +281,7 @@ private: static Action* quests(PlayerbotAI* botAI) { return new ListQuestsAction(botAI); } static Action* leave(PlayerbotAI* botAI) { return new LeaveGroupAction(botAI); } static Action* reputation(PlayerbotAI* botAI) { return new TellReputationAction(botAI); } + static Action* tell_pvp_stats(PlayerbotAI* botAI) { return new TellPvpStatsAction(botAI); } static Action* log(PlayerbotAI* botAI) { return new LogLevelAction(botAI); } static Action* los(PlayerbotAI* botAI) { return new TellLosAction(botAI); } static Action* rpg_status(PlayerbotAI* botAI) { return new TellRpgStatusAction(botAI); } diff --git a/src/Ai/Base/ChatTriggerContext.h b/src/Ai/Base/ChatTriggerContext.h index b3498ce3..3e71839b 100644 --- a/src/Ai/Base/ChatTriggerContext.h +++ b/src/Ai/Base/ChatTriggerContext.h @@ -24,6 +24,7 @@ public: creators["leave"] = &ChatTriggerContext::leave; creators["rep"] = &ChatTriggerContext::reputation; creators["reputation"] = &ChatTriggerContext::reputation; + creators["pvp stats"] = &ChatTriggerContext::pvp_stats; creators["log"] = &ChatTriggerContext::log; creators["los"] = &ChatTriggerContext::los; creators["rpg status"] = &ChatTriggerContext::rpg_status; @@ -224,6 +225,7 @@ private: static Trigger* stats(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "stats"); } static Trigger* leave(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "leave"); } static Trigger* reputation(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "reputation"); } + static Trigger* pvp_stats(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "pvp stats"); } static Trigger* log(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "log"); } static Trigger* los(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "los"); } static Trigger* rpg_status(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "rpg status"); } diff --git a/src/Ai/Base/Strategy/ChatCommandHandlerStrategy.cpp b/src/Ai/Base/Strategy/ChatCommandHandlerStrategy.cpp index 0a81686a..04f79790 100644 --- a/src/Ai/Base/Strategy/ChatCommandHandlerStrategy.cpp +++ b/src/Ai/Base/Strategy/ChatCommandHandlerStrategy.cpp @@ -27,6 +27,7 @@ void ChatCommandHandlerStrategy::InitTriggers(std::vector& trigger PassTroughStrategy::InitTriggers(triggers); triggers.push_back(new TriggerNode("rep", { NextAction("reputation", relevance) })); + triggers.push_back(new TriggerNode("pvp stats", { NextAction("tell pvp stats", relevance) })); triggers.push_back(new TriggerNode("q", { NextAction("query quest", relevance), NextAction("query item usage", relevance) })); triggers.push_back(new TriggerNode("add all loot", { NextAction("add all loot", relevance), @@ -116,6 +117,7 @@ ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : Pas supported.push_back("stats"); supported.push_back("leave"); supported.push_back("reputation"); + supported.push_back("tell pvp stats"); supported.push_back("log"); supported.push_back("los"); supported.push_back("rpg status"); diff --git a/src/Bot/PlayerbotAI.cpp b/src/Bot/PlayerbotAI.cpp index 34fc1336..437e62fc 100644 --- a/src/Bot/PlayerbotAI.cpp +++ b/src/Bot/PlayerbotAI.cpp @@ -892,6 +892,7 @@ bool PlayerbotAI::IsAllowedCommand(std::string const text) unsecuredCommands.insert("invite"); unsecuredCommands.insert("leave"); unsecuredCommands.insert("lfg"); + unsecuredCommands.insert("pvp stats"); unsecuredCommands.insert("rpg status"); }