diff --git a/data/sql/updates/pending_db_world/rev_1774097862453121000.sql b/data/sql/updates/pending_db_world/rev_1774097862453121000.sql
new file mode 100644
index 0000000000..979663d734
--- /dev/null
+++ b/data/sql/updates/pending_db_world/rev_1774097862453121000.sql
@@ -0,0 +1,24 @@
+-- Add acore_string entries (30110-30125) and command table entry for .debug factionchange
+
+DELETE FROM `acore_string` WHERE `entry` BETWEEN 30110 AND 30125;
+INSERT INTO `acore_string` (`entry`, `content_default`, `locale_koKR`, `locale_frFR`, `locale_deDE`, `locale_zhCN`, `locale_zhTW`, `locale_esES`, `locale_esMX`, `locale_ruRU`) VALUES
+(30110, '=== Faction/Race Change Debug for {} (GUID: {}) ===', '=== {} 진영/종족 변경 디버그 (GUID: {}) ===', '=== Débogage changement de faction/race pour {} (GUID : {}) ===', '=== Fraktions-/Rassenwechsel-Debug für {} (GUID: {}) ===', '=== 阵营/种族变更调试:{} (GUID: {}) ===', '=== 陣營/種族變更調試:{} (GUID: {}) ===', '=== Depuración de cambio de facción/raza para {} (GUID: {}) ===', '=== Depuración de cambio de facción/raza para {} (GUID: {}) ===', '=== Отладка смены фракции/расы для {} (GUID: {}) ==='),
+(30111, ' Faction change flag: SET [OK]', ' 진영 변경 플래그: 설정됨 [OK]', ' Flag changement de faction : ACTIVÉ [OK]', ' Fraktionswechsel-Flag: GESETZT [OK]', ' 阵营变更标记:已设置 [OK]', ' 陣營變更標記:已設置 [OK]', ' Flag de cambio de facción: ACTIVO [OK]', ' Flag de cambio de facción: ACTIVO [OK]', ' Флаг смены фракции: УСТАНОВЛЕН [OK]'),
+(30112, ' Race change flag: SET [OK]', ' 종족 변경 플래그: 설정됨 [OK]', ' Flag changement de race : ACTIVÉ [OK]', ' Rassenwechsel-Flag: GESETZT [OK]', ' 种族变更标记:已设置 [OK]', ' 種族變更標記:已設置 [OK]', ' Flag de cambio de raza: ACTIVO [OK]', ' Flag de cambio de raza: ACTIVO [OK]', ' Флаг смены расы: УСТАНОВЛЕН [OK]'),
+(30113, ' Change flag: NEITHER faction nor race change flag is set [FAIL]', ' 변경 플래그: 진영 및 종족 변경 플래그가 설정되지 않음 [실패]', ' Flag de changement : NI faction NI race n''est activé [ÉCHEC]', ' Wechsel-Flag: WEDER Fraktions- noch Rassenwechsel-Flag gesetzt [FEHLER]', ' 变更标记:阵营和种族变更标记均未设置 [失败]', ' 變更標記:陣營和種族變更標記均未設置 [失敗]', ' Flag de cambio: NI facción NI raza están activos [FALLO]', ' Flag de cambio: NI facción NI raza están activos [FALLO]', ' Флаг смены: НЕ установлен ни флаг фракции, ни расы [ОШИБКА]'),
+(30114, ' Guild: member of \"{}\" - must leave before faction change [FAIL]', ' 길드: \"{}\"의 멤버 - 진영 변경 전 탈퇴 필요 [실패]', ' Guilde : membre de \"{}\" - doit quitter avant le changement de faction [ÉCHEC]', ' Gilde: Mitglied von \"{}\" - muss vor dem Fraktionswechsel austreten [FEHLER]', ' 公会:是\"{}\"的成员 - 必须在阵营变更前退出 [失败]', ' 公會:是\"{}\"的成員 - 必須在陣營變更前退出 [失敗]', ' Hermandad: miembro de \"{}\" - debe abandonar antes de cambiar de facción [FALLO]', ' Hermandad: miembro de \"{}\" - debe abandonar antes de cambiar de facción [FALLO]', ' Гильдия: состоит в \"{}\" - необходимо покинуть перед сменой фракции [ОШИБКА]'),
+(30115, ' Guild: not in a guild or cross-faction guilds enabled [OK]', ' 길드: 길드 미가입 또는 교차 진영 길드 허용됨 [OK]', ' Guilde : pas dans une guilde ou guildes cross-faction activées [OK]', ' Gilde: nicht in einer Gilde oder fraktionsübergreifende Gilden aktiviert [OK]', ' 公会:未加入公会或已启用跨阵营公会 [OK]', ' 公會:未加入公會或已啟用跨陣營公會 [OK]', ' Hermandad: sin hermandad o hermandades cross-facción habilitadas [OK]', ' Hermandad: sin hermandad o hermandades cross-facción habilitadas [OK]', ' Гильдия: не в гильдии или межфракционные гильдии включены [OK]'),
+(30116, ' Arena captain: is captain of an arena team - must transfer or disband [FAIL]', ' 아레나 대장: 아레나 팀의 대장 - 양도 또는 해체 필요 [실패]', ' Capitaine d''arène : est capitaine d''une équipe d''arène - doit transférer ou dissoudre [ÉCHEC]', ' Arena-Kapitän: ist Kapitän eines Arena-Teams - muss übergeben oder auflösen [FEHLER]', ' 竞技场队长:是竞技场队伍的队长 - 必须转让或解散 [失败]', ' 競技場隊長:是競技場隊伍的隊長 - 必須轉讓或解散 [失敗]', ' Capitán de arena: es capitán de un equipo de arena - debe transferir o disolver [FALLO]', ' Capitán de arena: es capitán de un equipo de arena - debe transferir o disolver [FALLO]', ' Капитан арены: является капитаном команды арены - необходимо передать или распустить [ОШИБКА]'),
+(30117, ' Arena captain: not a captain [OK]', ' 아레나 대장: 대장 아님 [OK]', ' Capitaine d''arène : n''est pas capitaine [OK]', ' Arena-Kapitän: kein Kapitän [OK]', ' 竞技场队长:不是队长 [OK]', ' 競技場隊長:不是隊長 [OK]', ' Capitán de arena: no es capitán [OK]', ' Capitán de arena: no es capitán [OK]', ' Капитан арены: не является капитаном [OK]'),
+(30118, ' Mail: {} message(s) in mailbox - must clear mail [FAIL]', ' 우편: 우편함에 {}개의 메시지 - 우편 정리 필요 [실패]', ' Courrier : {} message(s) dans la boîte - doit vider le courrier [ÉCHEC]', ' Post: {} Nachricht(en) im Postfach - Post muss gelöscht werden [FEHLER]', ' 邮件:邮箱中有{}封邮件 - 必须清空邮箱 [失败]', ' 郵件:信箱中有{}封郵件 - 必須清空信箱 [失敗]', ' Correo: {} mensaje(s) en el buzón - debe vaciar el correo [FALLO]', ' Correo: {} mensaje(s) en el buzón - debe vaciar el correo [FALLO]', ' Почта: {} сообщений в почтовом ящике - необходимо очистить почту [ОШИБКА]'),
+(30119, ' Mail: mailbox is empty [OK]', ' 우편: 우편함 비어 있음 [OK]', ' Courrier : boîte aux lettres vide [OK]', ' Post: Postfach ist leer [OK]', ' 邮件:邮箱为空 [OK]', ' 郵件:信箱為空 [OK]', ' Correo: buzón vacío [OK]', ' Correo: buzón vacío [OK]', ' Почта: почтовый ящик пуст [OK]'),
+(30120, ' Auctions: has active auctions or bids - must clear auctions [FAIL]', ' 경매: 활성 경매 또는 입찰 있음 - 경매 정리 필요 [실패]', ' Enchères : a des enchères ou offres actives - doit supprimer les enchères [ÉCHEC]', ' Auktionen: hat aktive Auktionen oder Gebote - Auktionen müssen gelöscht werden [FEHLER]', ' 拍卖:有活跃的拍卖或出价 - 必须清除拍卖 [失败]', ' 拍賣:有活躍的拍賣或出價 - 必須清除拍賣 [失敗]', ' Subastas: tiene subastas o pujas activas - debe eliminar las subastas [FALLO]', ' Subastas: tiene subastas o pujas activas - debe eliminar las subastas [FALLO]', ' Аукционы: есть активные аукционы или ставки - необходимо очистить аукционы [ОШИБКА]'),
+(30121, ' Auctions: no active auctions or bids [OK]', ' 경매: 활성 경매 또는 입찰 없음 [OK]', ' Enchères : aucune enchère ni offre active [OK]', ' Auktionen: keine aktiven Auktionen oder Gebote [OK]', ' 拍卖:没有活跃的拍卖或出价 [OK]', ' 拍賣:沒有活躍的拍賣或出價 [OK]', ' Subastas: sin subastas ni pujas activas [OK]', ' Subastas: sin subastas ni pujas activas [OK]', ' Аукционы: нет активных аукционов или ставок [OK]'),
+(30122, ' Gold: {} copper, exceeds limit of {} copper [FAIL]', ' 골드: {} 코퍼, 제한 {} 코퍼 초과 [실패]', ' Or : {} cuivres, dépasse la limite de {} cuivres [ÉCHEC]', ' Gold: {} Kupfer, überschreitet Limit von {} Kupfer [FEHLER]', ' 金币:{}铜币,超过限额{}铜币 [失败]', ' 金幣:{}銅幣,超過限額{}銅幣 [失敗]', ' Oro: {} cobres, excede el límite de {} cobres [FALLO]', ' Oro: {} cobres, excede el límite de {} cobres [FALLO]', ' Золото: {} медных, превышает лимит в {} медных [ОШИБКА]'),
+(30123, ' Gold: {} copper (limit: {} copper) [OK]', ' 골드: {} 코퍼 (제한: {} 코퍼) [OK]', ' Or : {} cuivres (limite : {} cuivres) [OK]', ' Gold: {} Kupfer (Limit: {} Kupfer) [OK]', ' 金币:{}铜币(限额:{}铜币)[OK]', ' 金幣:{}銅幣(限額:{}銅幣)[OK]', ' Oro: {} cobres (límite: {} cobres) [OK]', ' Oro: {} cobres (límite: {} cobres) [OK]', ' Золото: {} медных (лимит: {} медных) [OK]'),
+(30124, ' Gold: {} copper (no limit configured) [OK]', ' 골드: {} 코퍼 (제한 없음) [OK]', ' Or : {} cuivres (aucune limite configurée) [OK]', ' Gold: {} Kupfer (kein Limit konfiguriert) [OK]', ' 金币:{}铜币(未配置限额)[OK]', ' 金幣:{}銅幣(未配置限額)[OK]', ' Oro: {} cobres (sin límite configurado) [OK]', ' Oro: {} cobres (sin límite configurado) [OK]', ' Золото: {} медных (лимит не настроен) [OK]'),
+(30125, ' Guild/Arena/Mail/Auction checks: N/A (faction change not requested, only applies to faction change)', ' 길드/아레나/우편/경매 검사: 해당 없음 (진영 변경이 요청되지 않음, 진영 변경에만 적용)', ' Vérifications guilde/arène/courrier/enchères : N/A (changement de faction non demandé, applicable uniquement au changement de faction)', ' Gilden-/Arena-/Post-/Auktionsprüfungen: Nicht zutreffend (kein Fraktionswechsel angefordert, gilt nur für Fraktionswechsel)', ' 公会/竞技场/邮件/拍卖检查:不适用(未请求阵营变更,仅适用于阵营变更)', ' 公會/競技場/郵件/拍賣檢查:不適用(未請求陣營變更,僅適用於陣營變更)', ' Comprobaciones de hermandad/arena/correo/subastas: N/A (no se solicitó cambio de facción, solo aplica a cambio de facción)', ' Comprobaciones de hermandad/arena/correo/subastas: N/A (no se solicitó cambio de facción, solo aplica a cambio de facción)', ' Проверки гильдии/арены/почты/аукционов: Н/Д (смена фракции не запрошена, применимо только к смене фракции)');
+
+DELETE FROM `command` WHERE `name` = 'debug factionchange';
+INSERT INTO `command` (`name`, `security`, `help`) VALUES
+('debug factionchange', 3, 'Syntax: .debug factionchange [$playerName]\nChecks all faction/race change requirements for the target player and reports pass/fail for each condition (AT_LOGIN flags, guild, arena captain, mail, auctions, gold limit).');
diff --git a/src/server/game/Miscellaneous/Language.h b/src/server/game/Miscellaneous/Language.h
index 4ee2eb3e7b..61977af9e2 100644
--- a/src/server/game/Miscellaneous/Language.h
+++ b/src/server/game/Miscellaneous/Language.h
@@ -1404,6 +1404,24 @@ enum AcoreStrings
LANG_DEBUG_LOOT_HEADER_MULTI = 30106,
LANG_DEBUG_LOOT_ITEM_MULTI = 30107,
LANG_DEBUG_LOOT_ITEM_QUEST_MULTI = 30108,
- LANG_DEBUG_LOOT_GOLD_MULTI = 30109
+ LANG_DEBUG_LOOT_GOLD_MULTI = 30109,
+
+ // debug factionchange command (30110-30125)
+ LANG_DEBUG_FACTIONCHANGE_HEADER = 30110,
+ LANG_DEBUG_FACTIONCHANGE_FLAG_FACTION = 30111,
+ LANG_DEBUG_FACTIONCHANGE_FLAG_RACE = 30112,
+ LANG_DEBUG_FACTIONCHANGE_FLAG_NONE = 30113,
+ LANG_DEBUG_FACTIONCHANGE_GUILD_FAIL = 30114,
+ LANG_DEBUG_FACTIONCHANGE_GUILD_OK = 30115,
+ LANG_DEBUG_FACTIONCHANGE_ARENA_CAPTAIN_FAIL = 30116,
+ LANG_DEBUG_FACTIONCHANGE_ARENA_CAPTAIN_OK = 30117,
+ LANG_DEBUG_FACTIONCHANGE_MAIL_FAIL = 30118,
+ LANG_DEBUG_FACTIONCHANGE_MAIL_OK = 30119,
+ LANG_DEBUG_FACTIONCHANGE_AUCTION_FAIL = 30120,
+ LANG_DEBUG_FACTIONCHANGE_AUCTION_OK = 30121,
+ LANG_DEBUG_FACTIONCHANGE_GOLD_FAIL = 30122,
+ LANG_DEBUG_FACTIONCHANGE_GOLD_OK = 30123,
+ LANG_DEBUG_FACTIONCHANGE_GOLD_NOLIMIT = 30124,
+ LANG_DEBUG_FACTIONCHANGE_NA = 30125
};
#endif
diff --git a/src/server/scripts/Commands/cs_debug.cpp b/src/server/scripts/Commands/cs_debug.cpp
index f4531e9ed8..cda1b6244d 100644
--- a/src/server/scripts/Commands/cs_debug.cpp
+++ b/src/server/scripts/Commands/cs_debug.cpp
@@ -15,13 +15,18 @@
* with this program. If not, see .
*/
+#include "ArenaTeamMgr.h"
+#include "AuctionHouseMgr.h"
#include "Bag.h"
#include "BattlegroundMgr.h"
#include "CellImpl.h"
#include "Channel.h"
+#include "CharacterCache.h"
+#include "DatabaseEnv.h"
#include "Chat.h"
#include "CommandScript.h"
#include "GridNotifiersImpl.h"
+#include "GuildMgr.h"
#include "ItemTemplate.h"
#include "LFGMgr.h"
#include "Language.h"
@@ -32,6 +37,7 @@
#include "ObjectAccessor.h"
#include "ObjectMgr.h"
#include "PoolMgr.h"
+#include "RaceMgr.h"
#include "ScriptMgr.h"
#include "Transport.h"
#include "Warden.h"
@@ -109,6 +115,7 @@ public:
{ "mapdata", HandleDebugMapDataCommand, SEC_ADMINISTRATOR, Console::No },
{ "boundary", HandleDebugBoundaryCommand, SEC_ADMINISTRATOR, Console::No },
{ "visibilitydata", HandleDebugVisibilityDataCommand, SEC_ADMINISTRATOR, Console::No },
+ { "factionchange", HandleDebugFactionChangeCommand, SEC_ADMINISTRATOR, Console::Yes},
{ "zonestats", HandleDebugZoneStatsCommand, SEC_MODERATOR, Console::Yes}
};
static ChatCommandTable commandTable =
@@ -1841,6 +1848,123 @@ public:
return true;
}
+
+ static bool HandleDebugFactionChangeCommand(ChatHandler* handler, Optional playerTarget)
+ {
+ if (!playerTarget)
+ playerTarget = PlayerIdentifier::FromTarget(handler);
+
+ if (!playerTarget)
+ {
+ handler->SendErrorMessage(LANG_PLAYER_NOT_FOUND);
+ return false;
+ }
+
+ ObjectGuid guid = playerTarget->GetGUID();
+ std::string name = playerTarget->GetName();
+
+ CharacterCacheEntry const* playerData = sCharacterCache->GetCharacterCacheByGuid(guid);
+ if (!playerData)
+ {
+ handler->SendErrorMessage(LANG_PLAYER_NOT_FOUND);
+ return false;
+ }
+
+ // Query at_login flags and money from the database (synchronous)
+ CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_AT_LOGIN_TITLES_MONEY);
+ stmt->SetData(0, guid.GetCounter());
+ PreparedQueryResult result = CharacterDatabase.Query(stmt);
+
+ if (!result)
+ {
+ handler->SendErrorMessage(LANG_PLAYER_NOT_FOUND);
+ return false;
+ }
+
+ Field* fields = result->Fetch();
+ uint32 atLoginFlags = fields[0].Get();
+ uint32 money = fields[2].Get();
+
+ // Header
+ handler->PSendSysMessage(LANG_DEBUG_FACTIONCHANGE_HEADER, name, guid.GetCounter());
+
+ // AT_LOGIN flags
+ bool hasFactionFlag = (atLoginFlags & AT_LOGIN_CHANGE_FACTION) != 0;
+ bool hasRaceFlag = (atLoginFlags & AT_LOGIN_CHANGE_RACE) != 0;
+
+ if (hasFactionFlag)
+ handler->PSendSysMessage(LANG_DEBUG_FACTIONCHANGE_FLAG_FACTION);
+ if (hasRaceFlag)
+ handler->PSendSysMessage(LANG_DEBUG_FACTIONCHANGE_FLAG_RACE);
+ if (!hasFactionFlag && !hasRaceFlag)
+ handler->PSendSysMessage(LANG_DEBUG_FACTIONCHANGE_FLAG_NONE);
+
+ // Faction-change-only checks (guild, arena captain, mail, auctions)
+ if (hasFactionFlag)
+ {
+ // Guild check
+ if (playerData->GuildId && !sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_GUILD))
+ {
+ std::string guildName = sGuildMgr->GetGuildNameById(playerData->GuildId);
+ handler->PSendSysMessage(LANG_DEBUG_FACTIONCHANGE_GUILD_FAIL, guildName);
+ }
+ else
+ handler->PSendSysMessage(LANG_DEBUG_FACTIONCHANGE_GUILD_OK);
+
+ // Arena team captain check
+ if (sArenaTeamMgr->GetArenaTeamByCaptain(guid))
+ handler->PSendSysMessage(LANG_DEBUG_FACTIONCHANGE_ARENA_CAPTAIN_FAIL);
+ else
+ handler->PSendSysMessage(LANG_DEBUG_FACTIONCHANGE_ARENA_CAPTAIN_OK);
+
+ // Mail check
+ if (playerData->MailCount)
+ handler->PSendSysMessage(LANG_DEBUG_FACTIONCHANGE_MAIL_FAIL, playerData->MailCount);
+ else
+ handler->PSendSysMessage(LANG_DEBUG_FACTIONCHANGE_MAIL_OK);
+
+ // Auction check - mirrors CharacterHandler.cpp logic
+ // AH faction IDs: 0 = neutral, 12 = alliance, 29 = horde
+ bool hasAuctions = false;
+ for (uint8 i = 0; i < 2; ++i)
+ {
+ AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsMap(
+ i == 0 ? 0 : (((1 << (playerData->Race - 1)) & sRaceMgr->GetAllianceRaceMask()) ? 12 : 29));
+
+ for (auto const& [auID, Aentry] : auctionHouse->GetAuctions())
+ {
+ if (Aentry && (Aentry->owner == guid || Aentry->bidder == guid))
+ {
+ hasAuctions = true;
+ break;
+ }
+ }
+
+ if (hasAuctions)
+ break;
+ }
+
+ if (hasAuctions)
+ handler->PSendSysMessage(LANG_DEBUG_FACTIONCHANGE_AUCTION_FAIL);
+ else
+ handler->PSendSysMessage(LANG_DEBUG_FACTIONCHANGE_AUCTION_OK);
+ }
+ else
+ {
+ handler->PSendSysMessage(LANG_DEBUG_FACTIONCHANGE_NA);
+ }
+
+ // Gold limit check
+ uint32 maxMoney = sWorld->getIntConfig(CONFIG_CHANGE_FACTION_MAX_MONEY);
+ if (maxMoney && money > maxMoney)
+ handler->PSendSysMessage(LANG_DEBUG_FACTIONCHANGE_GOLD_FAIL, money, maxMoney);
+ else if (maxMoney)
+ handler->PSendSysMessage(LANG_DEBUG_FACTIONCHANGE_GOLD_OK, money, maxMoney);
+ else
+ handler->PSendSysMessage(LANG_DEBUG_FACTIONCHANGE_GOLD_NOLIMIT, money);
+
+ return true;
+ }
};
void AddSC_debug_commandscript()