mirror of
https://github.com/mod-playerbots/mod-playerbots.git
synced 2026-02-03 10:53:48 +00:00
New whisper command "pvp stats" that allows players to ask a bot to report its current Arena Points, Honor Points, and Arena Teams (#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>
This commit is contained in:
@@ -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'
|
||||
);
|
||||
100
src/Ai/Base/Actions/TellPvpStatsAction.cpp
Normal file
100
src/Ai/Base/Actions/TellPvpStatsAction.cpp
Normal file
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, 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 <map>
|
||||
|
||||
#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<std::string, std::string> 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<std::string, std::string> 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<std::string, std::string>());
|
||||
|
||||
bot->Whisper(noTeamText, LANG_UNIVERSAL, requester);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
20
src/Ai/Base/Actions/TellPvpStatsAction.h
Normal file
20
src/Ai/Base/Actions/TellPvpStatsAction.h
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, 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
|
||||
@@ -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); }
|
||||
|
||||
@@ -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"); }
|
||||
|
||||
@@ -27,6 +27,7 @@ void ChatCommandHandlerStrategy::InitTriggers(std::vector<TriggerNode*>& 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");
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user