Compare commits

...

2 Commits

Author SHA1 Message Date
Alex Dcnh
0c1700c117 CORE - Improved language detection for bots (#1784)
I've had this problem for a long time, my bots only speak English even
though I'm playing on a French client.
I suppose this must be the case for some other people who do not have a
large number of players with the same local client.
If we use French DBCs, the bots bug because they only recognize US DBCs.
From what I understand, the language is chosen as follows:
On load, the module reads the entire `ai_playerbot_texts` table and
stores each text variant in a dictionary indexed by the locale ID: the
`text` column remains the default value (English), and the `text_loc1`
to `text_loc8` columns fill slots 1 through 8.

Whenever a real player connects, the module increments a counter for
that player's DBC locale using
`AddLocalePriority(player->GetSession()->GetSessionDbcLocale())`.

When a bot needs a text, `GetLocalePriority()` returns the most
frequently used locale index among currently connected players. The
corresponding string is then retrieved. if the box is empty, we fall
back to the English version (text[0]).

### This PR improve language detection.
**Summary**

- log both the client DBC locale and the account database locale when a
player logs in
- fall back to the account locale when the client reports enUS but the
account is configured for another locale
- keep the existing vote-based selection so bots always speak the
majority language among connected players

**Therefore, the original behavior is maintained. Bots still choose the
most represented language among connected players (the counter is simply
more efficient by prioritizing the account's locale when it differs from
the client's English). For example, if more English-speaking players are
connected, the language will revert to English, as the bots always share
the majority locale.**
2025-11-21 15:56:03 +01:00
Alex Dcnh
0b1b0eaecc Core - Fix RTSC SeeSpellAction crash on malformed WorldPacket (#1841)
## Summary

This PR fixes the Crash 1 Source from Issue
[#1840](https://github.com/mod-playerbots/mod-playerbots/issues/1840)
posted in a @Regrad posted logs, in `SeeSpellAction::Execute` when an
RTSC "see spell" event arrives with an empty or malformed `WorldPacket`.
In that case the code used to read from the packet without any
validation, causing a `ByteBufferException` and a crash in the map
thread.

## Fix

- Reset the packet read position and check that the RTSC header
(castCount + spellId + castFlags) fits into the packet before reading.
- Wrap `SpellCastTargets::Read` in a `try { } catch (ByteBufferException
const&) { }` block so truncated RTSC payloads are handled gracefully.
- Check that `targets.GetDst()` is not `nullptr` before accessing its
position.

For valid RTSC packets the behavior is unchanged; malformed packets are
now safely ignored instead of crashing the server.

## Testing

- Sent bots to multiple locations using RTSC and verified they still
move as before.
- Reproduced the previous crash scenario with malformed RTSC packets:
the worldserver no longer crashes and the event is simply ignored.

---------

Co-authored-by: bash <hermensb@gmail.com>
Co-authored-by: bashermens <31279994+hermensbas@users.noreply.github.com>
2025-11-21 13:18:14 +01:00
3 changed files with 63 additions and 16 deletions

View File

@@ -1602,8 +1602,26 @@ void PlayerbotMgr::OnBotLoginInternal(Player* const bot)
void PlayerbotMgr::OnPlayerLogin(Player* player)
{
if (!player)
return;
WorldSession* session = player->GetSession();
if (!session)
{
LOG_WARN("playerbots", "Unable to register locale priority for player {} because the session is missing", player->GetName());
return;
}
// DB locale (source of bot text translation)
LocaleConstant const databaseLocale = session->GetSessionDbLocaleIndex();
// For bot texts (DB-driven), prefer the database locale with a safe fallback.
LocaleConstant usedLocale = databaseLocale;
if (usedLocale >= MAX_LOCALES)
usedLocale = LOCALE_enUS; // fallback
// set locale priority for bot texts
sPlayerbotTextMgr->AddLocalePriority(player->GetSession()->GetSessionDbcLocale());
sPlayerbotTextMgr->AddLocalePriority(usedLocale);
if (sPlayerbotAIConfig->selfBotLevel > 2)
HandlePlayerbotCommand("self", player);
@@ -1611,7 +1629,7 @@ void PlayerbotMgr::OnPlayerLogin(Player* player)
if (!sPlayerbotAIConfig->botAutologin)
return;
uint32 accountId = player->GetSession()->GetAccountId();
uint32 accountId = session->GetAccountId();
QueryResult results = CharacterDatabase.Query("SELECT name FROM characters WHERE account = {}", accountId);
if (results)
{

View File

@@ -190,26 +190,29 @@ bool PlayerbotTextMgr::GetBotText(std::string name, std::string& text, std::map<
void PlayerbotTextMgr::AddLocalePriority(uint32 locale)
{
if (!locale)
if (locale >= MAX_LOCALES)
{
LOG_WARN("playerbots", "Ignoring locale {} for bot texts because it exceeds MAX_LOCALES ({})", locale, MAX_LOCALES - 1);
return;
}
botTextLocalePriority[locale]++;
}
uint32 PlayerbotTextMgr::GetLocalePriority()
{
uint32 topLocale = 0;
// if no real players online, reset top locale
if (!sWorldSessionMgr->GetActiveSessionCount())
uint32 const activeSessions = sWorldSessionMgr->GetActiveSessionCount();
if (!activeSessions)
{
ResetLocalePriority();
return 0;
}
uint32 topLocale = 0;
for (uint8 i = 0; i < MAX_LOCALES; ++i)
{
if (botTextLocalePriority[i] > topLocale)
if (botTextLocalePriority[i] > botTextLocalePriority[topLocale])
topLocale = i;
}

View File

@@ -12,6 +12,7 @@
#include "RTSCValues.h"
#include "RtscAction.h"
#include "PositionValue.h"
#include "ByteBuffer.h"
Creature* SeeSpellAction::CreateWps(Player* wpOwner, float x, float y, float z, float o, uint32 entry, Creature* lastWp,
bool important)
@@ -31,27 +32,52 @@ Creature* SeeSpellAction::CreateWps(Player* wpOwner, float x, float y, float z,
bool SeeSpellAction::Execute(Event event)
{
WorldPacket p(event.getPacket()); //
// RTSC packet data
WorldPacket p(event.getPacket());
uint8 castCount;
uint32 spellId;
uint8 castCount, castFlags;
uint8 castFlags;
// check RTSC header size = castCount (uint8) + spellId (uint32) + castFlags (uint8)
uint32 const rtscHeaderSize = sizeof(uint8) + sizeof(uint32) + sizeof(uint8);
if (p.size() < rtscHeaderSize)
{
LOG_WARN("playerbots", "SeeSpellAction: Corrupt RTSC packet size={}, expected>={}", p.size(), rtscHeaderSize);
return false;
}
Player* master = botAI->GetMaster();
p.rpos(0);
p >> castCount >> spellId >> castFlags;
if (!master)
return false;
// read RTSC packet data
p.rpos(0); // set read position to start
p >> castCount >> spellId >> castFlags;
// if (!botAI->HasStrategy("RTSC", botAI->GetState()))
// return false;
if (spellId != RTSC_MOVE_SPELL)
return false;
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
// should not throw exception,just defensive measure to prevent any crashes when core function breaks.
SpellCastTargets targets;
targets.Read(p, botAI->GetMaster());
try
{
targets.Read(p, master);
if (!targets.GetDst())
{
// do not dereference a null destination; ignore malformed RTSC packets instead of crashing
LOG_WARN("playerbots", "SeeSpellAction: (malformed) RTSC payload does not contain full targets data");
return false;
}
}
catch (ByteBufferException const&)
{
// ignore malformed RTSC packets instead of crashing
LOG_WARN("playerbots", "SeeSpellAction: Failed deserialization (malformed) RTSC payload");
return false;
}
WorldPosition spellPosition(master->GetMapId(), targets.GetDst()->_position);
SET_AI_VALUE(WorldPosition, "see spell location", spellPosition);