Skip to content

Commit ba83525

Browse files
New whisper command "pvp stats" that allows players to ask a bot to report its current Arena Points, Honor Points, and Arena Teams (mod-playerbots#2071)
# Pull Request This PR adds a new whisper command "pvp stats" that allows players to ask a bot to report its current Arena Points, Honor Points, and Arena Teams (name and team rating). Reason: Due to a client limitation in WoW 3.3.5a, the inspection window does not display another player's Arena or Honor points , only team data. This command provides an easy in-game way to check a bot’s PvP currencies without modifying the client or core packets. --- ## Design Philosophy Uses existing core getters (GetArenaPoints, GetHonorPoints, GetArenaTeamId, etc.). Fully integrated into the chat command system (ChatTriggerContext, ChatActionContext). Safe, no gameplay changes, purely informational. No harcoded texts, use database local instead --- ## How to Test the Changes /w BotName pvp stats Bot reply: [PVP] Arena Points: 302 | Honor Points: 11855 [PVP] 2v2: <The Fighters> (rating 2000) [PVP] 3v3: <The Trio> (rating 573) ## Complexity & Impact - Does this change add new decision branches? - [x] No - [ ] Yes (**explain below**) - Does this change increase per-bot or per-tick processing? - [x] No - [ ] Yes (**describe and justify impact**) - Could this logic scale poorly under load? - [x] No - [ ] Yes (**explain why**) --- ## Defaults & Configuration - Does this change modify default bot behavior? - [x] No - [ ] Yes (**explain why**) If this introduces more advanced or AI-heavy logic: - [x] Lightweight mode remains the default - [ ] More complex behavior is optional and thereby configurable --- ## AI Assistance - Was AI assistance (e.g. ChatGPT or similar tools) used while working on this change? - [x] No - [ ] Yes (**explain below**) --- ## Final Checklist - [x] Stability is not compromised - [x] Performance impact is understood, tested, and acceptable - [x] Added logic complexity is justified and explained - [x] Documentation updated if needed --- Multibot already ready Here is a sample of multibot when merged: <img width="706" height="737" alt="image" src="https://github.com/user-attachments/assets/5bcdd9f8-e2fc-4c29-a497-9fffba5dfd4e" /> --- ## Notes for Reviewers Anything that significantly improves realism at the cost of stability or performance should be carefully discussed before merging. --------- Co-authored-by: bashermens <31279994+hermensbas@users.noreply.github.com>
1 parent 8c2a27b commit ba83525

7 files changed

Lines changed: 229 additions & 0 deletions

File tree

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
-- #########################################################
2+
-- Playerbots - Add PVP / Arena texts for TellPvpAction
3+
-- Localized for all WotLK locales (koKR, frFR, deDE, zhCN,
4+
-- zhTW, esES, esMX, ruRU)
5+
-- #########################################################
6+
7+
-- ---------------------------------------------------------
8+
-- pvp_currency
9+
-- [PVP] Arena points: %arena_points | Honor Points: %honor_points
10+
-- ---------------------------------------------------------
11+
INSERT INTO `ai_playerbot_texts`
12+
(`name`, `text`, `say_type`, `reply_type`,
13+
`text_loc1`, `text_loc2`, `text_loc3`, `text_loc4`,
14+
`text_loc5`, `text_loc6`, `text_loc7`, `text_loc8`)
15+
SELECT
16+
'pvp_currency',
17+
'[PVP] Arena points: %arena_points | Honor Points: %honor_points',
18+
0, 0,
19+
-- koKR
20+
'[PVP] 투기장 점수: %arena_points | 명예 점수: %honor_points',
21+
-- frFR
22+
'[PVP] Points d''arène : %arena_points | Points d''honneur : %honor_points',
23+
-- deDE
24+
'[PVP] Arenapunkte: %arena_points | Ehrenpunkte: %honor_points',
25+
-- zhCN
26+
'[PVP] 竞技场点数:%arena_points | 荣誉点数:%honor_points',
27+
-- zhTW
28+
'[PVP] 競技場點數:%arena_points | 榮譽點數:%honor_points',
29+
-- esES
30+
'[PVP] Puntos de arena: %arena_points | Puntos de honor: %honor_points',
31+
-- esMX
32+
'[PVP] Puntos de arena: %arena_points | Puntos de honor: %honor_points',
33+
-- ruRU
34+
'[PVP] Очки арены: %arena_points | Очки чести: %honor_points'
35+
WHERE NOT EXISTS (
36+
SELECT 1 FROM `ai_playerbot_texts` WHERE `name` = 'pvp_currency'
37+
);
38+
39+
-- ---------------------------------------------------------
40+
-- pvp_arena_team
41+
-- [PVP] %bracket: <%team_name> (rating %team_rating)
42+
-- ---------------------------------------------------------
43+
INSERT INTO `ai_playerbot_texts`
44+
(`name`, `text`, `say_type`, `reply_type`,
45+
`text_loc1`, `text_loc2`, `text_loc3`, `text_loc4`,
46+
`text_loc5`, `text_loc6`, `text_loc7`, `text_loc8`)
47+
SELECT
48+
'pvp_arena_team',
49+
'[PVP] %bracket: <%team_name> (rating %team_rating)',
50+
0, 0,
51+
-- koKR
52+
'[PVP] %bracket: <%team_name> (평점 %team_rating)',
53+
-- frFR
54+
'[PVP] %bracket : <%team_name> (cote %team_rating)',
55+
-- deDE
56+
'[PVP] %bracket: <%team_name> (Wertung %team_rating)',
57+
-- zhCN
58+
'[PVP] %bracket: <%team_name> (评分 %team_rating)',
59+
-- zhTW
60+
'[PVP] %bracket: <%team_name> (評分 %team_rating)',
61+
-- esES
62+
'[PVP] %bracket: <%team_name> (índice %team_rating)',
63+
-- esMX
64+
'[PVP] %bracket: <%team_name> (índice %team_rating)',
65+
-- ruRU
66+
'[PVP] %bracket: <%team_name> (рейтинг %team_rating)'
67+
WHERE NOT EXISTS (
68+
SELECT 1 FROM `ai_playerbot_texts` WHERE `name` = 'pvp_arena_team'
69+
);
70+
71+
-- ---------------------------------------------------------
72+
-- pvp_no_arena_team
73+
-- [PVP] I have no Arena Team.
74+
-- ---------------------------------------------------------
75+
INSERT INTO `ai_playerbot_texts`
76+
(`name`, `text`, `say_type`, `reply_type`,
77+
`text_loc1`, `text_loc2`, `text_loc3`, `text_loc4`,
78+
`text_loc5`, `text_loc6`, `text_loc7`, `text_loc8`)
79+
SELECT
80+
'pvp_no_arena_team',
81+
'[PVP] I have no Arena Team.',
82+
0, 0,
83+
-- koKR
84+
'[PVP] 투기장 팀이 없습니다.',
85+
-- frFR
86+
'[PVP] Je n''ai aucune équipe d''arène.',
87+
-- deDE
88+
'[PVP] Ich habe kein Arenateam.',
89+
-- zhCN
90+
'[PVP] 我没有竞技场战队。',
91+
-- zhTW
92+
'[PVP] 我沒有競技場隊伍。',
93+
-- esES
94+
'[PVP] No tengo equipo de arena.',
95+
-- esMX
96+
'[PVP] No tengo equipo de arena.',
97+
-- ruRU
98+
'[PVP] У меня нет команды арены.'
99+
WHERE NOT EXISTS (
100+
SELECT 1 FROM `ai_playerbot_texts` WHERE `name` = 'pvp_no_arena_team'
101+
);
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/*
2+
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
3+
* and/or modify it under version 3 of the License, or (at your option), any later version.
4+
*/
5+
6+
#include "TellPvpStatsAction.h"
7+
8+
#include <map>
9+
10+
#include "ArenaTeam.h"
11+
#include "ArenaTeamMgr.h"
12+
#include "Event.h"
13+
#include "Player.h"
14+
#include "PlayerbotAI.h"
15+
#include "PlayerbotTextMgr.h"
16+
#include "Playerbots.h"
17+
#include "SharedDefines.h"
18+
#include "Language.h"
19+
20+
namespace
21+
{
22+
inline char const* BracketName(uint8 slot)
23+
{
24+
switch (slot)
25+
{
26+
case ARENA_SLOT_2v2: return "2v2";
27+
case ARENA_SLOT_3v3: return "3v3";
28+
default: return "5v5"; // ARENA_SLOT_5v5
29+
}
30+
}
31+
}
32+
33+
bool TellPvpStatsAction::Execute(Event event)
34+
{
35+
if (!bot)
36+
return false;
37+
38+
// Prefer the actual chat sender (whisper / say / etc.) if available.
39+
Player* requester = nullptr;
40+
41+
if (Unit* owner = event.getOwner())
42+
requester = owner->ToPlayer();
43+
44+
// Fallback to master if event owner is not available.
45+
if (!requester)
46+
requester = GetMaster();
47+
48+
// If we still do not have a valid player to answer to, bail out.
49+
if (!requester)
50+
return false;
51+
52+
// PVP currencies
53+
std::map<std::string, std::string> currencyPlaceholders;
54+
currencyPlaceholders["%arena_points"] = std::to_string(bot->GetArenaPoints());
55+
currencyPlaceholders["%honor_points"] = std::to_string(bot->GetHonorPoints());
56+
57+
std::string const currencyText = PlayerbotTextMgr::instance().GetBotTextOrDefault(
58+
"pvp_currency",
59+
"[PVP] Arena points: %arena_points | Honor Points: %honor_points",
60+
currencyPlaceholders);
61+
62+
bot->Whisper(currencyText, LANG_UNIVERSAL, requester);
63+
64+
// Arena Teams by slot
65+
bool anyTeam = false;
66+
for (uint8 slot = 0; slot < MAX_ARENA_SLOT; ++slot)
67+
{
68+
uint32 const teamId = bot->GetArenaTeamId(slot);
69+
if (!teamId)
70+
continue;
71+
72+
if (ArenaTeam* team = sArenaTeamMgr->GetArenaTeamById(teamId))
73+
{
74+
anyTeam = true;
75+
std::map<std::string, std::string> placeholders;
76+
placeholders["%bracket"] = BracketName(slot);
77+
placeholders["%team_name"] = team->GetName();
78+
placeholders["%team_rating"] = std::to_string(team->GetRating());
79+
80+
std::string const teamText = PlayerbotTextMgr::instance().GetBotTextOrDefault(
81+
"pvp_arena_team",
82+
"[PVP] %bracket: <%team_name> (rating %team_rating)",
83+
placeholders);
84+
85+
bot->Whisper(teamText, LANG_UNIVERSAL, requester);
86+
}
87+
}
88+
89+
if (!anyTeam)
90+
{
91+
std::string const noTeamText = PlayerbotTextMgr::instance().GetBotTextOrDefault(
92+
"pvp_no_arena_team",
93+
"[PVP] I have no Arena Team.",
94+
std::map<std::string, std::string>());
95+
96+
bot->Whisper(noTeamText, LANG_UNIVERSAL, requester);
97+
}
98+
99+
return true;
100+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
3+
* and/or modify it under version 3 of the License, or (at your option), any later version.
4+
*/
5+
6+
#ifndef _PLAYERBOT_TELLPVPSTATSACTION_H
7+
#define _PLAYERBOT_TELLPVPSTATSACTION_H
8+
9+
#include "Action.h"
10+
11+
class PlayerbotAI;
12+
13+
class TellPvpStatsAction : public Action
14+
{
15+
public:
16+
TellPvpStatsAction(PlayerbotAI* botAI) : Action(botAI, "tell pvp stats") {}
17+
18+
bool Execute(Event event) override;
19+
};
20+
#endif

src/Ai/Base/ChatActionContext.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
#include "TellItemCountAction.h"
6868
#include "TellLosAction.h"
6969
#include "TellReputationAction.h"
70+
#include "TellPvpStatsAction.h"
7071
#include "TellTargetAction.h"
7172
#include "TradeAction.h"
7273
#include "TrainerAction.h"
@@ -97,6 +98,7 @@ class ChatActionContext : public NamedObjectContext<Action>
9798
creators["quests"] = &ChatActionContext::quests;
9899
creators["leave"] = &ChatActionContext::leave;
99100
creators["reputation"] = &ChatActionContext::reputation;
101+
creators["tell pvp stats"] = &ChatActionContext::tell_pvp_stats;
100102
creators["log"] = &ChatActionContext::log;
101103
creators["los"] = &ChatActionContext::los;
102104
creators["rpg status"] = &ChatActionContext::rpg_status;
@@ -279,6 +281,7 @@ class ChatActionContext : public NamedObjectContext<Action>
279281
static Action* quests(PlayerbotAI* botAI) { return new ListQuestsAction(botAI); }
280282
static Action* leave(PlayerbotAI* botAI) { return new LeaveGroupAction(botAI); }
281283
static Action* reputation(PlayerbotAI* botAI) { return new TellReputationAction(botAI); }
284+
static Action* tell_pvp_stats(PlayerbotAI* botAI) { return new TellPvpStatsAction(botAI); }
282285
static Action* log(PlayerbotAI* botAI) { return new LogLevelAction(botAI); }
283286
static Action* los(PlayerbotAI* botAI) { return new TellLosAction(botAI); }
284287
static Action* rpg_status(PlayerbotAI* botAI) { return new TellRpgStatusAction(botAI); }

src/Ai/Base/ChatTriggerContext.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ class ChatTriggerContext : public NamedObjectContext<Trigger>
2424
creators["leave"] = &ChatTriggerContext::leave;
2525
creators["rep"] = &ChatTriggerContext::reputation;
2626
creators["reputation"] = &ChatTriggerContext::reputation;
27+
creators["pvp stats"] = &ChatTriggerContext::pvp_stats;
2728
creators["log"] = &ChatTriggerContext::log;
2829
creators["los"] = &ChatTriggerContext::los;
2930
creators["rpg status"] = &ChatTriggerContext::rpg_status;
@@ -224,6 +225,7 @@ class ChatTriggerContext : public NamedObjectContext<Trigger>
224225
static Trigger* stats(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "stats"); }
225226
static Trigger* leave(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "leave"); }
226227
static Trigger* reputation(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "reputation"); }
228+
static Trigger* pvp_stats(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "pvp stats"); }
227229
static Trigger* log(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "log"); }
228230
static Trigger* los(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "los"); }
229231
static Trigger* rpg_status(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "rpg status"); }

src/Ai/Base/Strategy/ChatCommandHandlerStrategy.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ void ChatCommandHandlerStrategy::InitTriggers(std::vector<TriggerNode*>& trigger
2727
PassTroughStrategy::InitTriggers(triggers);
2828

2929
triggers.push_back(new TriggerNode("rep", { NextAction("reputation", relevance) }));
30+
triggers.push_back(new TriggerNode("pvp stats", { NextAction("tell pvp stats", relevance) }));
3031
triggers.push_back(new TriggerNode("q", { NextAction("query quest", relevance),
3132
NextAction("query item usage", relevance) }));
3233
triggers.push_back(new TriggerNode("add all loot", { NextAction("add all loot", relevance),
@@ -116,6 +117,7 @@ ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : Pas
116117
supported.push_back("stats");
117118
supported.push_back("leave");
118119
supported.push_back("reputation");
120+
supported.push_back("tell pvp stats");
119121
supported.push_back("log");
120122
supported.push_back("los");
121123
supported.push_back("rpg status");

src/Bot/PlayerbotAI.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -892,6 +892,7 @@ bool PlayerbotAI::IsAllowedCommand(std::string const text)
892892
unsecuredCommands.insert("invite");
893893
unsecuredCommands.insert("leave");
894894
unsecuredCommands.insert("lfg");
895+
unsecuredCommands.insert("pvp stats");
895896
unsecuredCommands.insert("rpg status");
896897
}
897898

0 commit comments

Comments
 (0)