боты русифицированы
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
#include "QuestDef.h"
|
||||
#include "ScriptMgr.h"
|
||||
#include "SharedDefines.h"
|
||||
#include "WorldSession.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
@@ -31,6 +32,8 @@ namespace MoonWell::PlayerGuide
|
||||
return;
|
||||
if (!player || !player->GetSession() || player->IsBeingTeleported())
|
||||
return;
|
||||
if (player->GetSession()->IsBot())
|
||||
return;
|
||||
|
||||
Payload p = sPlayerGuideMgr->BuildFor(player);
|
||||
std::string json = sPlayerGuideMgr->SerializeJson(p);
|
||||
|
||||
@@ -25,6 +25,29 @@ namespace MoonWell::PlayerGuide
|
||||
// The mgr clamps its configured chunk size into a safe range.
|
||||
constexpr std::size_t HARD_LIMIT = 250;
|
||||
|
||||
std::size_t Utf8ChunkSize(std::string_view body, std::size_t offset,
|
||||
std::size_t budget)
|
||||
{
|
||||
std::size_t take = std::min(budget, body.size() - offset);
|
||||
if (offset + take == body.size())
|
||||
return take;
|
||||
|
||||
// A chunk must not end between a UTF-8 leading byte and one of
|
||||
// its continuation bytes. Chat packet validation rejects such
|
||||
// an individually invalid string before the addon can reassemble
|
||||
// the complete JSON payload.
|
||||
while (take > 0 &&
|
||||
(static_cast<unsigned char>(body[offset + take]) & 0xC0) == 0x80)
|
||||
{
|
||||
--take;
|
||||
}
|
||||
|
||||
// The configured budget is clamped to at least 64 bytes, so a
|
||||
// valid UTF-8 code point always fits. Keep a defensive fallback
|
||||
// for malformed input to guarantee forward progress.
|
||||
return take > 0 ? take : std::min(budget, body.size() - offset);
|
||||
}
|
||||
|
||||
void SendOneChunk(Player* player, std::string_view command,
|
||||
uint32 seq, uint32 total, std::string_view body)
|
||||
{
|
||||
@@ -71,16 +94,20 @@ namespace MoonWell::PlayerGuide
|
||||
return;
|
||||
}
|
||||
|
||||
// Multi-chunk: split blindly by byte budget. JSON tolerates
|
||||
// concatenation on the client.
|
||||
uint32 total = static_cast<uint32>(
|
||||
(body.size() + budget - 1) / budget);
|
||||
uint32 seq = 1;
|
||||
for (std::size_t off = 0; off < body.size(); off += budget, ++seq)
|
||||
// Calculate UTF-8-safe slices first because backing up from a
|
||||
// continuation byte can increase the number of chunks.
|
||||
std::vector<std::string_view> chunks;
|
||||
for (std::size_t off = 0; off < body.size();)
|
||||
{
|
||||
std::size_t take = std::min(budget, body.size() - off);
|
||||
SendOneChunk(player, command, seq, total,
|
||||
std::string_view(body.data() + off, take));
|
||||
std::size_t take = Utf8ChunkSize(body, off, budget);
|
||||
chunks.emplace_back(body.data() + off, take);
|
||||
off += take;
|
||||
}
|
||||
|
||||
uint32 total = static_cast<uint32>(chunks.size());
|
||||
for (uint32 i = 0; i < total; ++i)
|
||||
{
|
||||
SendOneChunk(player, command, i + 1, total, chunks[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -84,6 +84,10 @@
|
||||
# Enable or disable Playerbots module
|
||||
AiPlayerbot.Enabled = 1
|
||||
|
||||
# Use the MoonWell Cyrillic name and guild-name pools.
|
||||
# Existing RNDbot characters can be migrated with localize-playerbots-ru.sh.
|
||||
AiPlayerbot.UseRussianNames = 0
|
||||
|
||||
# Enable randombot system
|
||||
AiPlayerbot.RandomBotAutologin = 1
|
||||
|
||||
|
||||
+4884
File diff suppressed because it is too large
Load Diff
+1746
File diff suppressed because it is too large
Load Diff
@@ -12,6 +12,7 @@
|
||||
#include "Event.h"
|
||||
#include "PlayerbotTextMgr.h"
|
||||
#include "Playerbots.h"
|
||||
#include "Util.h"
|
||||
|
||||
static const std::unordered_set<std::string> noReplyMsgs = {
|
||||
"join",
|
||||
@@ -52,6 +53,18 @@ static const std::unordered_set<std::string> noReplyMsgParts = {
|
||||
"+", "-", "@", "follow target", "focus heal", "cast ", "accept [", "e [", "destroy [", "go zone"};
|
||||
static const std::unordered_set<std::string> noReplyMsgStarts = {"e ", "accept ", "cast ", "destroy "};
|
||||
|
||||
static std::string Utf8Lowercase(std::string const& value)
|
||||
{
|
||||
std::wstring wide;
|
||||
if (!Utf8toWStr(value, wide))
|
||||
return value;
|
||||
|
||||
wstrToLower(wide);
|
||||
std::string lowered;
|
||||
WStrToUtf8(wide, lowered);
|
||||
return lowered;
|
||||
}
|
||||
|
||||
SayAction::SayAction(PlayerbotAI* botAI) : Action(botAI, "say"), Qualified() {}
|
||||
|
||||
bool SayAction::Execute(Event /*event*/)
|
||||
@@ -578,6 +591,40 @@ std::string ChatReplyAction::GenerateReplyMessage(Player* bot, std::string& inco
|
||||
|
||||
std::string respondsText = "";
|
||||
|
||||
// The legacy free-chat parser below is English-specific and constructs
|
||||
// several responses directly in English. For ruRU, classify the small
|
||||
// static reply set locally and let PlayerbotTextMgr select text_loc8.
|
||||
if (PlayerbotTextMgr::instance().GetLocalePriority() == LOCALE_ruRU)
|
||||
{
|
||||
std::string const message = Utf8Lowercase(incomingMessage);
|
||||
std::string const botName = Utf8Lowercase(bot->GetName());
|
||||
|
||||
if (Player* player = ObjectAccessor::FindPlayer(ObjectGuid(HighGuid::Player, guid1));
|
||||
player && player->isGMChat())
|
||||
{
|
||||
replyType = REPLY_ADMIN_ABUSE;
|
||||
}
|
||||
else if (message.find("привет") != std::string::npos || message.find("здравств") != std::string::npos ||
|
||||
message.find("добрый день") != std::string::npos || message.find("добрый вечер") != std::string::npos)
|
||||
{
|
||||
replyType = REPLY_HELLO;
|
||||
}
|
||||
else if (message.find("нуб") != std::string::npos || message.find("дурак") != std::string::npos ||
|
||||
message.find("идиот") != std::string::npos || message.find("заткнись") != std::string::npos)
|
||||
{
|
||||
replyType = REPLY_GRUDGE;
|
||||
}
|
||||
else if (message.find(botName) != std::string::npos)
|
||||
{
|
||||
replyType = REPLY_NAME;
|
||||
}
|
||||
|
||||
respondsText = PlayerbotTextMgr::instance().GetBotText(replyType, name);
|
||||
if (respondsText.size() > 255)
|
||||
respondsText.resize(255);
|
||||
return respondsText;
|
||||
}
|
||||
|
||||
// Chat Logic
|
||||
int32 verb_pos = -1;
|
||||
int32 verb_type = -1;
|
||||
|
||||
@@ -175,16 +175,17 @@ Player* RandomPlayerbotFactory::CreateRandomBot(WorldSession* session, uint8 cls
|
||||
std::string const RandomPlayerbotFactory::CreateRandomBotName(NameRaceAndGender raceAndGender)
|
||||
{
|
||||
std::string botName = "";
|
||||
char const* namesTable = sPlayerbotAIConfig.useRussianNames ? "playerbots_names_ru" : "playerbots_names";
|
||||
int tries = 3;
|
||||
while (--tries)
|
||||
{
|
||||
QueryResult result = CharacterDatabase.Query(
|
||||
"SELECT n.name "
|
||||
"FROM playerbots_names n "
|
||||
"FROM {} n "
|
||||
"LEFT OUTER JOIN characters c ON c.name = n.name "
|
||||
"WHERE c.guid IS NULL and n.gender = '{}' "
|
||||
"ORDER BY RAND() LIMIT 1",
|
||||
static_cast<uint8>(raceAndGender));
|
||||
namesTable, static_cast<uint8>(raceAndGender));
|
||||
if (!result)
|
||||
{
|
||||
break;
|
||||
@@ -668,7 +669,8 @@ void RandomPlayerbotFactory::CreateRandomBots()
|
||||
{
|
||||
nameCached = true;
|
||||
LOG_INFO("playerbots", "Creating cache for names per gender and race...");
|
||||
QueryResult result = CharacterDatabase.Query("SELECT name, gender FROM playerbots_names");
|
||||
char const* namesTable = sPlayerbotAIConfig.useRussianNames ? "playerbots_names_ru" : "playerbots_names";
|
||||
QueryResult result = CharacterDatabase.Query("SELECT name, gender FROM {}", namesTable);
|
||||
if (!result)
|
||||
{
|
||||
LOG_ERROR("playerbots", "No more unused names left");
|
||||
@@ -753,8 +755,10 @@ void RandomPlayerbotFactory::CreateRandomBots()
|
||||
std::string const RandomPlayerbotFactory::CreateRandomGuildName()
|
||||
{
|
||||
std::string guildName = "";
|
||||
char const* guildNamesTable =
|
||||
sPlayerbotAIConfig.useRussianNames ? "playerbots_guild_names_ru" : "playerbots_guild_names";
|
||||
|
||||
QueryResult result = CharacterDatabase.Query("SELECT MAX(name_id) FROM playerbots_guild_names");
|
||||
QueryResult result = CharacterDatabase.Query("SELECT MAX(name_id) FROM {}", guildNamesTable);
|
||||
if (!result)
|
||||
{
|
||||
LOG_ERROR("playerbots", "No more names left for random guilds");
|
||||
@@ -766,9 +770,9 @@ std::string const RandomPlayerbotFactory::CreateRandomGuildName()
|
||||
|
||||
uint32 id = urand(0, maxId);
|
||||
result = CharacterDatabase.Query(
|
||||
"SELECT n.name FROM playerbots_guild_names n "
|
||||
"SELECT n.name FROM {} n "
|
||||
"LEFT OUTER JOIN guild e ON e.name = n.name WHERE e.guildid IS NULL AND n.name_id >= {} LIMIT 1",
|
||||
id);
|
||||
guildNamesTable, id);
|
||||
if (!result)
|
||||
{
|
||||
LOG_ERROR("playerbots", "No more names left for random guilds");
|
||||
|
||||
@@ -1652,7 +1652,7 @@ void PlayerbotMgr::OnPlayerLogin(Player* player)
|
||||
|
||||
// For bot texts (DB-driven), prefer the database locale with a safe fallback.
|
||||
LocaleConstant usedLocale = databaseLocale;
|
||||
if (usedLocale >= MAX_LOCALES)
|
||||
if (usedLocale >= TOTAL_LOCALES)
|
||||
usedLocale = LOCALE_enUS; // fallback
|
||||
|
||||
// set locale priority for bot texts
|
||||
|
||||
@@ -161,13 +161,15 @@ void PlayerbotGuildMgr::ResetGuildCache()
|
||||
|
||||
void PlayerbotGuildMgr::LoadGuildNames()
|
||||
{
|
||||
LOG_INFO("playerbots", "Loading guild names from playerbots_guild_names...");
|
||||
char const* guildNamesTable =
|
||||
sPlayerbotAIConfig.useRussianNames ? "playerbots_guild_names_ru" : "playerbots_guild_names";
|
||||
LOG_INFO("playerbots", "Loading guild names from {}...", guildNamesTable);
|
||||
|
||||
QueryResult result = CharacterDatabase.Query("SELECT name_id, name FROM playerbots_guild_names");
|
||||
QueryResult result = CharacterDatabase.Query("SELECT name_id, name FROM {}", guildNamesTable);
|
||||
|
||||
if (!result)
|
||||
{
|
||||
LOG_ERROR("playerbots", "No entries found in playerbots_guild_names. List is empty.");
|
||||
LOG_ERROR("playerbots", "No entries found in {}. List is empty.", guildNamesTable);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -184,7 +186,7 @@ void PlayerbotGuildMgr::LoadGuildNames()
|
||||
std::mt19937 g(rd());
|
||||
|
||||
std::shuffle(_shuffled_guild_keys.begin(), _shuffled_guild_keys.end(), g);
|
||||
LOG_INFO("playerbots", "Loaded {} guild entries from playerbots_guild_names table.", _guildNames.size());
|
||||
LOG_INFO("playerbots", "Loaded {} guild entries from {} table.", _guildNames.size(), guildNamesTable);
|
||||
}
|
||||
|
||||
void PlayerbotGuildMgr::ValidateGuildCache()
|
||||
|
||||
@@ -38,7 +38,7 @@ void PlayerbotTextMgr::LoadBotTexts()
|
||||
text[0] = fields[1].Get<std::string>();
|
||||
uint8 sayType = fields[2].Get<uint8>();
|
||||
uint8 replyType = fields[3].Get<uint8>();
|
||||
for (uint8 i = 1; i < MAX_LOCALES; ++i)
|
||||
for (uint8 i = 1; i < TOTAL_LOCALES; ++i)
|
||||
{
|
||||
text[i] = fields[i + 3].Get<std::string>();
|
||||
}
|
||||
@@ -192,9 +192,10 @@ bool PlayerbotTextMgr::GetBotText(std::string name, std::string& text, std::map<
|
||||
|
||||
void PlayerbotTextMgr::AddLocalePriority(uint32 locale)
|
||||
{
|
||||
if (locale >= MAX_LOCALES)
|
||||
if (locale >= TOTAL_LOCALES)
|
||||
{
|
||||
LOG_WARN("playerbots", "Ignoring locale {} for bot texts because it exceeds MAX_LOCALES ({})", locale, MAX_LOCALES - 1);
|
||||
LOG_WARN("playerbots", "Ignoring locale {} for bot texts because it exceeds TOTAL_LOCALES ({})", locale,
|
||||
TOTAL_LOCALES - 1);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -212,7 +213,7 @@ uint32 PlayerbotTextMgr::GetLocalePriority()
|
||||
}
|
||||
|
||||
uint32 topLocale = 0;
|
||||
for (uint8 i = 0; i < MAX_LOCALES; ++i)
|
||||
for (uint8 i = 0; i < TOTAL_LOCALES; ++i)
|
||||
{
|
||||
if (botTextLocalePriority[i] > botTextLocalePriority[topLocale])
|
||||
topLocale = i;
|
||||
@@ -223,7 +224,7 @@ uint32 PlayerbotTextMgr::GetLocalePriority()
|
||||
|
||||
void PlayerbotTextMgr::ResetLocalePriority()
|
||||
{
|
||||
for (uint8 i = 0; i < MAX_LOCALES; ++i)
|
||||
for (uint8 i = 0; i < TOTAL_LOCALES; ++i)
|
||||
{
|
||||
botTextLocalePriority[i] = 0;
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ public:
|
||||
private:
|
||||
PlayerbotTextMgr()
|
||||
{
|
||||
for (uint8 i = 0; i < MAX_LOCALES; ++i)
|
||||
for (uint8 i = 0; i < TOTAL_LOCALES; ++i)
|
||||
{
|
||||
botTextLocalePriority[i] = 0;
|
||||
}
|
||||
@@ -102,7 +102,7 @@ private:
|
||||
|
||||
std::map<std::string, std::vector<BotTextEntry>> botTexts;
|
||||
std::map<std::string, uint32> botTextChance;
|
||||
uint32 botTextLocalePriority[MAX_LOCALES];
|
||||
uint32 botTextLocalePriority[TOTAL_LOCALES];
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -67,6 +67,8 @@ bool PlayerbotAIConfig::Initialize()
|
||||
return false;
|
||||
}
|
||||
|
||||
useRussianNames = sConfigMgr->GetOption<bool>("AiPlayerbot.UseRussianNames", false);
|
||||
|
||||
globalCoolDown = sConfigMgr->GetOption<int32>("AiPlayerbot.GlobalCooldown", 500);
|
||||
maxWaitForMove = sConfigMgr->GetOption<int32>("AiPlayerbot.MaxWaitForMove", 5000);
|
||||
disableMoveSplinePath = sConfigMgr->GetOption<int32>("AiPlayerbot.DisableMoveSplinePath", 0);
|
||||
|
||||
@@ -79,6 +79,7 @@ public:
|
||||
bool IsInPvpProhibitedArea(uint32 id);
|
||||
|
||||
bool enabled;
|
||||
bool useRussianNames;
|
||||
bool disabledWithoutRealPlayer;
|
||||
bool EnableICCBuffs;
|
||||
bool allowAccountBots, allowGuildBots, allowTrustedAccountBots;
|
||||
|
||||
Reference in New Issue
Block a user