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()