From 3c8f31ed477da1068c88be88a29499c300c3b7f3 Mon Sep 17 00:00:00 2001 From: Skjalf <47818697+Nyeriah@users.noreply.github.com> Date: Fri, 22 May 2026 19:17:23 -0300 Subject: [PATCH] feat(Core): Add .transmog check diagnostic command Adds a GM/console command that audits whether a given destination/source item pair can be transmogged for a target player, working for both online and offline characters via CharacterCache and direct character_skills / character_spell lookups. Output is split into Pair, per-Item, and Collection sections with one summary line each. Co-Authored-By: Claude Opus 4.7 (1M context) --- ...05_09_migrate_strings_to_module_string.sql | 340 +++++++++++++++++- src/Transmogrification.h | 42 +++ src/cs_transmog.cpp | 285 ++++++++++++++- 3 files changed, 658 insertions(+), 9 deletions(-) diff --git a/data/sql/db-world/updates/2026_05_09_migrate_strings_to_module_string.sql b/data/sql/db-world/updates/2026_05_09_migrate_strings_to_module_string.sql index ec2e05c9..e9b69811 100644 --- a/data/sql/db-world/updates/2026_05_09_migrate_strings_to_module_string.sql +++ b/data/sql/db-world/updates/2026_05_09_migrate_strings_to_module_string.sql @@ -53,7 +53,49 @@ INSERT INTO `module_string` (`module`, `id`, `string`) VALUES -- Disclaimer strings ('mod-transmog', 43, '|cFF4DB3FFSet bonuses won''t appear in the item tooltip while transmogrified, but they are still fully active.\nTo stop seeing this notice, type |cFFFFFFFF.transmog disclaimer off|cFF4DB3FF.|r'), ('mod-transmog', 44, 'Set bonus disclaimer enabled.'), -('mod-transmog', 45, 'Set bonus disclaimer disabled.'); +('mod-transmog', 45, 'Set bonus disclaimer disabled.'), +-- Check command: header / info +('mod-transmog', 46, '=== Transmog Diagnostic ==='), +('mod-transmog', 47, 'Destination : {}'), +('mod-transmog', 48, 'Source : {}'), +('mod-transmog', 49, 'Player : {}'), +('mod-transmog', 50, 'Player ''{}'' not found'), +-- Check command: section labels +('mod-transmog', 51, '--- Pair Checks ---'), +('mod-transmog', 52, '--- Item: {} ---'), +('mod-transmog', 53, '--- Collection ---'), +-- Check command: verdict +('mod-transmog', 54, '=== RESULT: CAN transmog ==='), +('mod-transmog', 55, '=== RESULT: CANNOT transmog ==='), +-- Check command: pair fail messages +('mod-transmog', 56, 'Same item ID — cannot transmog an item onto itself'), +('mod-transmog', 57, 'Identical display ID — appearance already matches'), +('mod-transmog', 58, 'Item class mismatch — both must be armor or both must be weapon'), +('mod-transmog', 59, 'Destination inventory type is forbidden (bag/relic/ring/trinket/ammo/quiver)'), +('mod-transmog', 60, 'Source inventory type is forbidden (bag/relic/ring/trinket/ammo/quiver)'), +('mod-transmog', 61, 'Ranged weapon type mismatch — cannot mix ranged and melee'), +('mod-transmog', 62, 'Subclass mismatch — not allowed'), +('mod-transmog', 63, 'Inventory type mismatch — not allowed'), +-- Check command: per-item fail messages +('mod-transmog', 64, 'Whitelisted — all requirement checks are bypassed'), +('mod-transmog', 65, 'Item class must be armor or weapon'), +('mod-transmog', 66, 'Blacklisted — cannot be used for transmogrification'), +('mod-transmog', 67, 'Quality not allowed by server config'), +('mod-transmog', 68, 'Fishing pole — not allowed (AllowFishingPoles disabled)'), +('mod-transmog', 69, 'Required event/holiday is not active'), +('mod-transmog', 70, 'All item stats are zero — cannot be used for transmogrification'), +('mod-transmog', 71, 'Missing proficiency (skill: {}) — not allowed by config'), +('mod-transmog', 72, 'Faction restriction not met'), +('mod-transmog', 73, 'Class requirement not met'), +('mod-transmog', 74, 'Race requirement not met'), +('mod-transmog', 75, 'Required skill {} at rank {} — not met (have: {})'), +('mod-transmog', 76, 'Level requirement not met (need: {}, have: {})'), +('mod-transmog', 77, 'Required spell {} is not known'), +-- Check command: collection fail messages +('mod-transmog', 78, 'Collection system disabled'), +('mod-transmog', 79, 'Appearance is NOT in player''s collection'), +-- Check command: section pass message +('mod-transmog', 80, 'All checks passed'); INSERT INTO `module_string_locale` (`module`, `id`, `locale`, `string`) VALUES -- ID 1: OK @@ -442,4 +484,298 @@ INSERT INTO `module_string_locale` (`module`, `id`, `locale`, `string`) VALUES ('mod-transmog', 45, 'zhTW', '套裝加成提示已禁用。'), ('mod-transmog', 45, 'esES', 'Aviso de bonificación de conjunto desactivado.'), ('mod-transmog', 45, 'esMX', 'Aviso de bonificación de conjunto desactivado.'), -('mod-transmog', 45, 'ruRU', 'Уведомление о бонусах набора отключено.'); +('mod-transmog', 45, 'ruRU', 'Уведомление о бонусах набора отключено.'), +-- ===== Check command strings (46-80) ===== +-- ===== koKR ===== +('mod-transmog', 46, 'koKR', '=== 형상변환 진단 ==='), +('mod-transmog', 47, 'koKR', '목표 아이템 : {}'), +('mod-transmog', 48, 'koKR', '원본 아이템 : {}'), +('mod-transmog', 49, 'koKR', '플레이어 : {}'), +('mod-transmog', 50, 'koKR', '플레이어 ''{}''을(를) 찾을 수 없습니다'), +('mod-transmog', 51, 'koKR', '--- 아이템 쌍 검사 ---'), +('mod-transmog', 52, 'koKR', '--- 아이템: {} ---'), +('mod-transmog', 53, 'koKR', '--- 컬렉션 ---'), +('mod-transmog', 54, 'koKR', '=== 결과: 형상변환 가능 ==='), +('mod-transmog', 55, 'koKR', '=== 결과: 형상변환 불가 ==='), +('mod-transmog', 56, 'koKR', '동일한 아이템 ID — 같은 아이템으로 형상변환 불가'), +('mod-transmog', 57, 'koKR', '동일한 외형 ID — 이미 외형이 일치함'), +('mod-transmog', 58, 'koKR', '아이템 유형 불일치 — 양쪽 모두 방어구 또는 무기여야 함'), +('mod-transmog', 59, 'koKR', '목표 아이템 슬롯 유형 금지됨'), +('mod-transmog', 60, 'koKR', '원본 아이템 슬롯 유형 금지됨'), +('mod-transmog', 61, 'koKR', '원거리 무기 유형 불일치 — 원거리와 근접을 혼용할 수 없음'), +('mod-transmog', 62, 'koKR', '세부 유형 불일치 — 허용되지 않음'), +('mod-transmog', 63, 'koKR', '인벤토리 유형 불일치 — 허용되지 않음'), +('mod-transmog', 64, 'koKR', '허용 목록에 포함됨 — 모든 조건 검사 건너뜀'), +('mod-transmog', 65, 'koKR', '아이템 유형은 방어구 또는 무기여야 함'), +('mod-transmog', 66, 'koKR', '블랙리스트에 포함됨 — 형상변환에 사용할 수 없음'), +('mod-transmog', 67, 'koKR', '아이템 품질 서버 설정에 의해 불허됨'), +('mod-transmog', 68, 'koKR', '낚싯대 — 서버 설정에 의해 허용되지 않음 (AllowFishingPoles 비활성화)'), +('mod-transmog', 69, 'koKR', '필요한 이벤트/공휴일이 활성화되지 않음'), +('mod-transmog', 70, 'koKR', '모든 아이템 스탯이 0 — 형상변환에 사용할 수 없음'), +('mod-transmog', 71, 'koKR', '능숙도 없음 (스킬: {}) — 설정에 의해 허용되지 않음'), +('mod-transmog', 72, 'koKR', '진영 제한 미충족'), +('mod-transmog', 73, 'koKR', '클래스 요구사항 미충족'), +('mod-transmog', 74, 'koKR', '종족 요구사항 미충족'), +('mod-transmog', 75, 'koKR', '필요 스킬 {} 등급 {} — 미충족 (보유: {})'), +('mod-transmog', 76, 'koKR', '레벨 요구사항 미충족 (필요: {}, 보유: {})'), +('mod-transmog', 77, 'koKR', '필요 주문 {} 미습득'), +('mod-transmog', 78, 'koKR', '컬렉션 시스템 비활성화'), +('mod-transmog', 79, 'koKR', '외형이 플레이어 컬렉션에 없음'), +('mod-transmog', 80, 'koKR', '모든 검사 통과'), +-- ===== frFR ===== +('mod-transmog', 46, 'frFR', '=== Diagnostic de transmogrification ==='), +('mod-transmog', 47, 'frFR', 'Destination : {}'), +('mod-transmog', 48, 'frFR', 'Source : {}'), +('mod-transmog', 49, 'frFR', 'Joueur : {}'), +('mod-transmog', 50, 'frFR', 'Joueur ''{}'' introuvable'), +('mod-transmog', 51, 'frFR', '--- Vérifications de paire ---'), +('mod-transmog', 52, 'frFR', '--- Objet : {} ---'), +('mod-transmog', 53, 'frFR', '--- Collection ---'), +('mod-transmog', 54, 'frFR', '=== RÉSULTAT : PEUT transmogrifier ==='), +('mod-transmog', 55, 'frFR', '=== RÉSULTAT : NE PEUT PAS transmogrifier ==='), +('mod-transmog', 56, 'frFR', 'ID d''objet identique — impossible de transmogrifier un objet sur lui-même'), +('mod-transmog', 57, 'frFR', 'ID d''apparence identique — l''apparence correspond déjà'), +('mod-transmog', 58, 'frFR', 'Type d''objet différent — les deux doivent être armure ou arme'), +('mod-transmog', 59, 'frFR', 'Type d''emplacement de la destination interdit'), +('mod-transmog', 60, 'frFR', 'Type d''emplacement de la source interdit'), +('mod-transmog', 61, 'frFR', 'Type d''arme à distance différent — impossible de mélanger armes à distance et de mêlée'), +('mod-transmog', 62, 'frFR', 'Sous-classe différente — non autorisée'), +('mod-transmog', 63, 'frFR', 'Type d''emplacement différent — non autorisé'), +('mod-transmog', 64, 'frFR', 'En liste blanche — toutes les vérifications sont ignorées'), +('mod-transmog', 65, 'frFR', 'La classe d''objet doit être armure ou arme'), +('mod-transmog', 66, 'frFR', 'En liste noire — ne peut pas être utilisé pour la transmogrification'), +('mod-transmog', 67, 'frFR', 'Qualité non autorisée par la configuration du serveur'), +('mod-transmog', 68, 'frFR', 'Canne à pêche — non autorisée (AllowFishingPoles désactivé)'), +('mod-transmog', 69, 'frFR', 'L''événement ou la fête requis n''est pas actif'), +('mod-transmog', 70, 'frFR', 'Toutes les statistiques de l''objet sont nulles — ne peut pas être utilisé pour la transmogrification'), +('mod-transmog', 71, 'frFR', 'Maîtrise manquante (compétence : {}) — non autorisée par la config'), +('mod-transmog', 72, 'frFR', 'Restriction de faction non respectée'), +('mod-transmog', 73, 'frFR', 'Exigence de classe non remplie'), +('mod-transmog', 74, 'frFR', 'Exigence de race non remplie'), +('mod-transmog', 75, 'frFR', 'Compétence requise {} au rang {} — non remplie (actuel : {})'), +('mod-transmog', 76, 'frFR', 'Niveau insuffisant (requis : {}, actuel : {})'), +('mod-transmog', 77, 'frFR', 'Sort requis {} inconnu'), +('mod-transmog', 78, 'frFR', 'Système de collection désactivé'), +('mod-transmog', 79, 'frFR', 'L''apparence N''EST PAS dans la collection du joueur'), +('mod-transmog', 80, 'frFR', 'Tous les contrôles sont passés'), +-- ===== deDE ===== +('mod-transmog', 46, 'deDE', '=== Transmog-Diagnose ==='), +('mod-transmog', 47, 'deDE', 'Ziel : {}'), +('mod-transmog', 48, 'deDE', 'Quelle : {}'), +('mod-transmog', 49, 'deDE', 'Spieler : {}'), +('mod-transmog', 50, 'deDE', 'Spieler ''{}'' nicht gefunden'), +('mod-transmog', 51, 'deDE', '--- Paar-Prüfungen ---'), +('mod-transmog', 52, 'deDE', '--- Gegenstand: {} ---'), +('mod-transmog', 53, 'deDE', '--- Kollektion ---'), +('mod-transmog', 54, 'deDE', '=== ERGEBNIS: KANN transmogrifiziert werden ==='), +('mod-transmog', 55, 'deDE', '=== ERGEBNIS: KANN NICHT transmogrifiziert werden ==='), +('mod-transmog', 56, 'deDE', 'Gleiche Gegenstands-ID — kein Transmog auf sich selbst'), +('mod-transmog', 57, 'deDE', 'Gleiche Darstellungs-ID — Aussehen stimmt bereits überein'), +('mod-transmog', 58, 'deDE', 'Gegenstandsklasse stimmt nicht überein — beide müssen Rüstung oder Waffe sein'), +('mod-transmog', 59, 'deDE', 'Inventartyp des Ziels ist verboten'), +('mod-transmog', 60, 'deDE', 'Inventartyp der Quelle ist verboten'), +('mod-transmog', 61, 'deDE', 'Fernkampfwaffentyp stimmt nicht überein — Fern- und Nahkampfwaffen können nicht gemischt werden'), +('mod-transmog', 62, 'deDE', 'Unterklasse stimmt nicht überein — nicht erlaubt'), +('mod-transmog', 63, 'deDE', 'Inventartyp stimmt nicht überein — nicht erlaubt'), +('mod-transmog', 64, 'deDE', 'Auf der Whitelist — alle Anforderungsprüfungen werden übersprungen'), +('mod-transmog', 65, 'deDE', 'Gegenstandsklasse muss Rüstung oder Waffe sein'), +('mod-transmog', 66, 'deDE', 'Auf der Blacklist — kann nicht für Transmog verwendet werden'), +('mod-transmog', 67, 'deDE', 'Qualität nicht erlaubt laut Serverkonfiguration'), +('mod-transmog', 68, 'deDE', 'Angelrute — nicht erlaubt (AllowFishingPoles deaktiviert)'), +('mod-transmog', 69, 'deDE', 'Erforderliches Ereignis/Feiertag ist nicht aktiv'), +('mod-transmog', 70, 'deDE', 'Alle Gegenstandswerte sind null — kann nicht für Transmog verwendet werden'), +('mod-transmog', 71, 'deDE', 'Fehlende Beherrschung (Fertigkeit: {}) — nicht erlaubt laut Konfiguration'), +('mod-transmog', 72, 'deDE', 'Fraktionsanforderung nicht erfüllt'), +('mod-transmog', 73, 'deDE', 'Klassenanforderung nicht erfüllt'), +('mod-transmog', 74, 'deDE', 'Rassenanforderung nicht erfüllt'), +('mod-transmog', 75, 'deDE', 'Erforderliche Fertigkeit {} Rang {} — nicht erfüllt (vorhanden: {})'), +('mod-transmog', 76, 'deDE', 'Stufenanforderung nicht erfüllt (benötigt: {}, vorhanden: {})'), +('mod-transmog', 77, 'deDE', 'Erforderlicher Zauber {} nicht bekannt'), +('mod-transmog', 78, 'deDE', 'Sammlungssystem deaktiviert'), +('mod-transmog', 79, 'deDE', 'Aussehen ist NICHT in der Sammlung des Spielers'), +('mod-transmog', 80, 'deDE', 'Alle Prüfungen bestanden'), +-- ===== zhCN ===== +('mod-transmog', 46, 'zhCN', '=== 幻化诊断 ==='), +('mod-transmog', 47, 'zhCN', '目标物品 : {}'), +('mod-transmog', 48, 'zhCN', '来源物品 : {}'), +('mod-transmog', 49, 'zhCN', '玩家 : {}'), +('mod-transmog', 50, 'zhCN', '未找到玩家''{}'''), +('mod-transmog', 51, 'zhCN', '--- 物品对检查 ---'), +('mod-transmog', 52, 'zhCN', '--- 物品:{} ---'), +('mod-transmog', 53, 'zhCN', '--- 收藏 ---'), +('mod-transmog', 54, 'zhCN', '=== 结果:可以幻化 ==='), +('mod-transmog', 55, 'zhCN', '=== 结果:无法幻化 ==='), +('mod-transmog', 56, 'zhCN', '相同物品ID — 不能将物品幻化为自身'), +('mod-transmog', 57, 'zhCN', '相同外观ID — 外观已匹配'), +('mod-transmog', 58, 'zhCN', '物品类别不匹配 — 两件物品必须同为护甲或武器'), +('mod-transmog', 59, 'zhCN', '目标物品槽位类型被禁止'), +('mod-transmog', 60, 'zhCN', '来源物品槽位类型被禁止'), +('mod-transmog', 61, 'zhCN', '远程武器类型不匹配 — 不能混用远程和近战武器'), +('mod-transmog', 62, 'zhCN', '子类别不匹配 — 不允许'), +('mod-transmog', 63, 'zhCN', '背包类型不匹配 — 不允许'), +('mod-transmog', 64, 'zhCN', '已列入白名单 — 跳过所有要求检查'), +('mod-transmog', 65, 'zhCN', '物品类别必须为护甲或武器'), +('mod-transmog', 66, 'zhCN', '已列入黑名单 — 无法用于幻化'), +('mod-transmog', 67, 'zhCN', '物品品质不符合服务器配置要求'), +('mod-transmog', 68, 'zhCN', '钓鱼竿 — 不允许(AllowFishingPoles已禁用)'), +('mod-transmog', 69, 'zhCN', '所需活动/节日未激活'), +('mod-transmog', 70, 'zhCN', '所有物品属性为零 — 无法用于幻化'), +('mod-transmog', 71, 'zhCN', '缺少熟练度(技能:{}) — 配置不允许'), +('mod-transmog', 72, 'zhCN', '阵营限制未达成'), +('mod-transmog', 73, 'zhCN', '职业要求未达成'), +('mod-transmog', 74, 'zhCN', '种族要求未达成'), +('mod-transmog', 75, 'zhCN', '所需技能 {} 等级 {} — 未达成(当前:{})'), +('mod-transmog', 76, 'zhCN', '等级要求未达成(需要:{},当前:{})'), +('mod-transmog', 77, 'zhCN', '所需法术 {} 未习得'), +('mod-transmog', 78, 'zhCN', '收藏系统已禁用'), +('mod-transmog', 79, 'zhCN', '外观不在玩家收藏中'), +('mod-transmog', 80, 'zhCN', '所有检查通过'), +-- ===== zhTW ===== +('mod-transmog', 46, 'zhTW', '=== 幻化診斷 ==='), +('mod-transmog', 47, 'zhTW', '目標物品 : {}'), +('mod-transmog', 48, 'zhTW', '來源物品 : {}'), +('mod-transmog', 49, 'zhTW', '玩家 : {}'), +('mod-transmog', 50, 'zhTW', '未找到玩家''{}'''), +('mod-transmog', 51, 'zhTW', '--- 物品對檢查 ---'), +('mod-transmog', 52, 'zhTW', '--- 物品:{} ---'), +('mod-transmog', 53, 'zhTW', '--- 收藏 ---'), +('mod-transmog', 54, 'zhTW', '=== 結果:可以幻化 ==='), +('mod-transmog', 55, 'zhTW', '=== 結果:無法幻化 ==='), +('mod-transmog', 56, 'zhTW', '相同物品ID — 不能將物品幻化為自身'), +('mod-transmog', 57, 'zhTW', '相同外觀ID — 外觀已匹配'), +('mod-transmog', 58, 'zhTW', '物品類別不匹配 — 兩件物品必須同為護甲或武器'), +('mod-transmog', 59, 'zhTW', '目標物品槽位類型被禁止'), +('mod-transmog', 60, 'zhTW', '來源物品槽位類型被禁止'), +('mod-transmog', 61, 'zhTW', '遠程武器類型不匹配 — 不能混用遠程和近戰武器'), +('mod-transmog', 62, 'zhTW', '子類別不匹配 — 不允許'), +('mod-transmog', 63, 'zhTW', '背包類型不匹配 — 不允許'), +('mod-transmog', 64, 'zhTW', '已列入白名單 — 跳過所有要求檢查'), +('mod-transmog', 65, 'zhTW', '物品類別必須為護甲或武器'), +('mod-transmog', 66, 'zhTW', '已列入黑名單 — 無法用於幻化'), +('mod-transmog', 67, 'zhTW', '物品品質不符合伺服器配置要求'), +('mod-transmog', 68, 'zhTW', '釣魚竿 — 不允許(AllowFishingPoles已禁用)'), +('mod-transmog', 69, 'zhTW', '所需活動/節日未啟動'), +('mod-transmog', 70, 'zhTW', '所有物品屬性為零 — 無法用於幻化'), +('mod-transmog', 71, 'zhTW', '缺少熟練度(技能:{}) — 配置不允許'), +('mod-transmog', 72, 'zhTW', '陣營限制未達成'), +('mod-transmog', 73, 'zhTW', '職業要求未達成'), +('mod-transmog', 74, 'zhTW', '種族要求未達成'), +('mod-transmog', 75, 'zhTW', '所需技能 {} 等級 {} — 未達成(當前:{})'), +('mod-transmog', 76, 'zhTW', '等級要求未達成(需要:{},當前:{})'), +('mod-transmog', 77, 'zhTW', '所需法術 {} 未習得'), +('mod-transmog', 78, 'zhTW', '收藏系統已禁用'), +('mod-transmog', 79, 'zhTW', '外觀不在玩家收藏中'), +('mod-transmog', 80, 'zhTW', '所有檢查通過'), +-- ===== esES ===== +('mod-transmog', 46, 'esES', '=== Diagnóstico de transmogrificación ==='), +('mod-transmog', 47, 'esES', 'Destino : {}'), +('mod-transmog', 48, 'esES', 'Fuente : {}'), +('mod-transmog', 49, 'esES', 'Jugador : {}'), +('mod-transmog', 50, 'esES', 'Jugador ''{}'' no encontrado'), +('mod-transmog', 51, 'esES', '--- Comprobaciones de par ---'), +('mod-transmog', 52, 'esES', '--- Objeto: {} ---'), +('mod-transmog', 53, 'esES', '--- Colección ---'), +('mod-transmog', 54, 'esES', '=== RESULTADO: PUEDE transmogrificar ==='), +('mod-transmog', 55, 'esES', '=== RESULTADO: NO PUEDE transmogrificar ==='), +('mod-transmog', 56, 'esES', 'ID de objeto igual — no se puede transmogrificar un objeto sobre sí mismo'), +('mod-transmog', 57, 'esES', 'ID de apariencia idéntico — la apariencia ya coincide'), +('mod-transmog', 58, 'esES', 'Clase de objeto diferente — ambos deben ser armadura o arma'), +('mod-transmog', 59, 'esES', 'Tipo de ranura de destino prohibida'), +('mod-transmog', 60, 'esES', 'Tipo de ranura de fuente prohibida'), +('mod-transmog', 61, 'esES', 'Tipo de arma a distancia diferente — no se pueden mezclar armas a distancia y cuerpo a cuerpo'), +('mod-transmog', 62, 'esES', 'Subclase diferente — no permitida'), +('mod-transmog', 63, 'esES', 'Tipo de ranura diferente — no permitido'), +('mod-transmog', 64, 'esES', 'En lista blanca — todas las comprobaciones de requisitos son ignoradas'), +('mod-transmog', 65, 'esES', 'La clase de objeto debe ser armadura o arma'), +('mod-transmog', 66, 'esES', 'En lista negra — no se puede usar para transmogrificación'), +('mod-transmog', 67, 'esES', 'Calidad no permitida por la configuración del servidor'), +('mod-transmog', 68, 'esES', 'Caña de pescar — no permitida (AllowFishingPoles desactivado)'), +('mod-transmog', 69, 'esES', 'El evento/festivo requerido no está activo'), +('mod-transmog', 70, 'esES', 'Todas las estadísticas del objeto son cero — no se puede usar para transmogrificación'), +('mod-transmog', 71, 'esES', 'Habilidad insuficiente (habilidad: {}) — no permitida por la config'), +('mod-transmog', 72, 'esES', 'Restricción de facción no cumplida'), +('mod-transmog', 73, 'esES', 'Requisito de clase no cumplido'), +('mod-transmog', 74, 'esES', 'Requisito de raza no cumplido'), +('mod-transmog', 75, 'esES', 'Habilidad requerida {} en rango {} — no cumplida (actual: {})'), +('mod-transmog', 76, 'esES', 'Nivel insuficiente (requerido: {}, actual: {})'), +('mod-transmog', 77, 'esES', 'Hechizo requerido {} desconocido'), +('mod-transmog', 78, 'esES', 'Sistema de colección desactivado'), +('mod-transmog', 79, 'esES', 'La apariencia NO está en la colección del jugador'), +('mod-transmog', 80, 'esES', 'Todas las comprobaciones superadas'), +-- ===== esMX ===== +('mod-transmog', 46, 'esMX', '=== Diagnóstico de transmogrificación ==='), +('mod-transmog', 47, 'esMX', 'Destino : {}'), +('mod-transmog', 48, 'esMX', 'Fuente : {}'), +('mod-transmog', 49, 'esMX', 'Jugador : {}'), +('mod-transmog', 50, 'esMX', 'Jugador ''{}'' no encontrado'), +('mod-transmog', 51, 'esMX', '--- Comprobaciones de par ---'), +('mod-transmog', 52, 'esMX', '--- Objeto: {} ---'), +('mod-transmog', 53, 'esMX', '--- Colección ---'), +('mod-transmog', 54, 'esMX', '=== RESULTADO: PUEDE transmogrificar ==='), +('mod-transmog', 55, 'esMX', '=== RESULTADO: NO PUEDE transmogrificar ==='), +('mod-transmog', 56, 'esMX', 'ID de objeto igual — no se puede transmogrificar un objeto sobre sí mismo'), +('mod-transmog', 57, 'esMX', 'ID de apariencia idéntico — la apariencia ya coincide'), +('mod-transmog', 58, 'esMX', 'Clase de objeto diferente — ambos deben ser armadura o arma'), +('mod-transmog', 59, 'esMX', 'Tipo de ranura de destino prohibida'), +('mod-transmog', 60, 'esMX', 'Tipo de ranura de fuente prohibida'), +('mod-transmog', 61, 'esMX', 'Tipo de arma a distancia diferente — no se pueden mezclar armas a distancia y cuerpo a cuerpo'), +('mod-transmog', 62, 'esMX', 'Subclase diferente — no permitida'), +('mod-transmog', 63, 'esMX', 'Tipo de ranura diferente — no permitido'), +('mod-transmog', 64, 'esMX', 'En lista blanca — todas las comprobaciones de requisitos son ignoradas'), +('mod-transmog', 65, 'esMX', 'La clase de objeto debe ser armadura o arma'), +('mod-transmog', 66, 'esMX', 'En lista negra — no se puede usar para transmogrificación'), +('mod-transmog', 67, 'esMX', 'Calidad no permitida por la configuración del servidor'), +('mod-transmog', 68, 'esMX', 'Caña de pescar — no permitida (AllowFishingPoles desactivado)'), +('mod-transmog', 69, 'esMX', 'El evento/festivo requerido no está activo'), +('mod-transmog', 70, 'esMX', 'Todas las estadísticas del objeto son cero — no se puede usar para transmogrificación'), +('mod-transmog', 71, 'esMX', 'Habilidad insuficiente (habilidad: {}) — no permitida por la config'), +('mod-transmog', 72, 'esMX', 'Restricción de facción no cumplida'), +('mod-transmog', 73, 'esMX', 'Requisito de clase no cumplido'), +('mod-transmog', 74, 'esMX', 'Requisito de raza no cumplido'), +('mod-transmog', 75, 'esMX', 'Habilidad requerida {} en rango {} — no cumplida (actual: {})'), +('mod-transmog', 76, 'esMX', 'Nivel insuficiente (requerido: {}, actual: {})'), +('mod-transmog', 77, 'esMX', 'Hechizo requerido {} desconocido'), +('mod-transmog', 78, 'esMX', 'Sistema de colección desactivado'), +('mod-transmog', 79, 'esMX', 'La apariencia NO está en la colección del jugador'), +('mod-transmog', 80, 'esMX', 'Todas las comprobaciones superadas'), +-- ===== ruRU ===== +('mod-transmog', 46, 'ruRU', '=== Диагностика трансмогрификации ==='), +('mod-transmog', 47, 'ruRU', 'Цель : {}'), +('mod-transmog', 48, 'ruRU', 'Источник : {}'), +('mod-transmog', 49, 'ruRU', 'Игрок : {}'), +('mod-transmog', 50, 'ruRU', 'Игрок ''{}'' не найден'), +('mod-transmog', 51, 'ruRU', '--- Проверки пары ---'), +('mod-transmog', 52, 'ruRU', '--- Предмет: {} ---'), +('mod-transmog', 53, 'ruRU', '--- Коллекция ---'), +('mod-transmog', 54, 'ruRU', '=== РЕЗУЛЬТАТ: МОЖНО трансмогрифицировать ==='), +('mod-transmog', 55, 'ruRU', '=== РЕЗУЛЬТАТ: НЕЛЬЗЯ трансмогрифицировать ==='), +('mod-transmog', 56, 'ruRU', 'Одинаковый ID предмета — нельзя трансмогрифицировать предмет на себя'), +('mod-transmog', 57, 'ruRU', 'Одинаковый ID внешности — внешность уже совпадает'), +('mod-transmog', 58, 'ruRU', 'Несоответствие класса предмета — оба должны быть бронёй или оружием'), +('mod-transmog', 59, 'ruRU', 'Тип слота цели запрещён'), +('mod-transmog', 60, 'ruRU', 'Тип слота источника запрещён'), +('mod-transmog', 61, 'ruRU', 'Несоответствие типа дальнобойного оружия — нельзя смешивать дальнобойное и ближнее'), +('mod-transmog', 62, 'ruRU', 'Несоответствие подкласса — не разрешено'), +('mod-transmog', 63, 'ruRU', 'Несоответствие типа инвентаря — не разрешено'), +('mod-transmog', 64, 'ruRU', 'В белом списке — все проверки требований пропущены'), +('mod-transmog', 65, 'ruRU', 'Класс предмета должен быть бронёй или оружием'), +('mod-transmog', 66, 'ruRU', 'В чёрном списке — нельзя использовать для трансмогрификации'), +('mod-transmog', 67, 'ruRU', 'Качество не разрешено конфигурацией сервера'), +('mod-transmog', 68, 'ruRU', 'Удочка — не разрешена (AllowFishingPoles отключено)'), +('mod-transmog', 69, 'ruRU', 'Требуемое событие/праздник не активно'), +('mod-transmog', 70, 'ruRU', 'Все характеристики предмета равны нулю — нельзя использовать для трансмогрификации'), +('mod-transmog', 71, 'ruRU', 'Нет владения (навык: {}) — не разрешено конфигурацией'), +('mod-transmog', 72, 'ruRU', 'Требование фракции не выполнено'), +('mod-transmog', 73, 'ruRU', 'Требование класса не выполнено'), +('mod-transmog', 74, 'ruRU', 'Требование расы не выполнено'), +('mod-transmog', 75, 'ruRU', 'Необходимый навык {} ранга {} — не выполнен (есть: {})'), +('mod-transmog', 76, 'ruRU', 'Требование уровня не выполнено (требуется: {}, есть: {})'), +('mod-transmog', 77, 'ruRU', 'Требуемое заклинание {} неизвестно'), +('mod-transmog', 78, 'ruRU', 'Система коллекций отключена'), +('mod-transmog', 79, 'ruRU', 'Внешний вид ОТСУТСТВУЕТ в коллекции игрока'), +('mod-transmog', 80, 'ruRU', 'Все проверки пройдены'); + +-- Check command table entry +DELETE FROM `command` WHERE `name` = 'transmog check'; +INSERT INTO `command` (`name`, `security`, `help`) VALUES +('transmog check', 2, 'Syntax: .transmog check $player $destItem $srcItem\nRuns a full diagnostic on whether $srcItem can be applied as appearance on $destItem for $player.\nWorks for offline players and from console.\nOutputs one summary line per section showing failures only.'); diff --git a/src/Transmogrification.h b/src/Transmogrification.h index 48ccd0a8..c1193dec 100644 --- a/src/Transmogrification.h +++ b/src/Transmogrification.h @@ -95,6 +95,48 @@ enum TransmogStrings : uint32 LANG_TRANSMOG_SET_DISCLAIMER = 43, LANG_TRANSMOG_CMD_DISCLAIMER_ON = 44, LANG_TRANSMOG_CMD_DISCLAIMER_OFF = 45, + // Check command: header / info + LANG_TRANSMOG_CHECK_HEADER = 46, + LANG_TRANSMOG_CHECK_DEST = 47, + LANG_TRANSMOG_CHECK_SRC = 48, + LANG_TRANSMOG_CHECK_PLAYER = 49, + LANG_TRANSMOG_CHECK_PLAYER_NOT_FOUND = 50, + // Check command: section labels + LANG_TRANSMOG_CHECK_SECTION_PAIR = 51, + LANG_TRANSMOG_CHECK_SECTION_ITEM = 52, + LANG_TRANSMOG_CHECK_SECTION_COLL = 53, + // Check command: verdict + LANG_TRANSMOG_CHECK_RESULT_OK = 54, + LANG_TRANSMOG_CHECK_RESULT_FAIL = 55, + // Check command: pair fail messages + LANG_TRANSMOG_CHECK_PAIR_IDS_SAME = 56, + LANG_TRANSMOG_CHECK_PAIR_DISP_SAME = 57, + LANG_TRANSMOG_CHECK_PAIR_CLASS_FAIL = 58, + LANG_TRANSMOG_CHECK_PAIR_DEST_TYPE_FAIL = 59, + LANG_TRANSMOG_CHECK_PAIR_SRC_TYPE_FAIL = 60, + LANG_TRANSMOG_CHECK_PAIR_RANGED_FAIL = 61, + LANG_TRANSMOG_CHECK_PAIR_SUB_DENIED = 62, + LANG_TRANSMOG_CHECK_PAIR_INV_DENIED = 63, + // Check command: per-item fail messages + LANG_TRANSMOG_CHECK_ITEM_WHITELISTED = 64, + LANG_TRANSMOG_CHECK_ITEM_CLASS_FAIL = 65, + LANG_TRANSMOG_CHECK_ITEM_BLACKLISTED = 66, + LANG_TRANSMOG_CHECK_ITEM_QUALITY_FAIL = 67, + LANG_TRANSMOG_CHECK_ITEM_POLE_FAIL = 68, + LANG_TRANSMOG_CHECK_ITEM_EVENT_FAIL = 69, + LANG_TRANSMOG_CHECK_ITEM_STAT_FAIL = 70, + LANG_TRANSMOG_CHECK_ITEM_PROF_FAIL = 71, + LANG_TRANSMOG_CHECK_ITEM_FACTION_FAIL = 72, + LANG_TRANSMOG_CHECK_ITEM_CLASS_REQ_FAIL = 73, + LANG_TRANSMOG_CHECK_ITEM_RACE_REQ_FAIL = 74, + LANG_TRANSMOG_CHECK_ITEM_SKILL_FAIL = 75, + LANG_TRANSMOG_CHECK_ITEM_LEVEL_FAIL = 76, + LANG_TRANSMOG_CHECK_ITEM_SPELL_FAIL = 77, + // Check command: collection fail messages + LANG_TRANSMOG_CHECK_COLL_DISABLED = 78, + LANG_TRANSMOG_CHECK_COLL_NOT_FOUND = 79, + // Check command: section pass message (shared by all sections) + LANG_TRANSMOG_CHECK_SECTION_OK = 80, }; enum ArmorClassSpellIDs diff --git a/src/cs_transmog.cpp b/src/cs_transmog.cpp index 4cfcb338..a7b19bc0 100644 --- a/src/cs_transmog.cpp +++ b/src/cs_transmog.cpp @@ -41,13 +41,14 @@ class transmog_commandscript : public CommandScript static ChatCommandTable transmogTable = { - { "add", addCollectionTable }, - { "", HandleDisableTransMogVisual, SEC_PLAYER, Console::No }, - { "sync", HandleSyncTransMogCommand, SEC_PLAYER, Console::No }, - { "portable", HandleTransmogPortableCommand, SEC_PLAYER, Console::No }, - { "interface", HandleInterfaceOption, SEC_PLAYER, Console::No }, - { "disclaimer", HandleDisclaimerOption, SEC_PLAYER, Console::No }, - { "reload", HandleReloadTransmogConfig, SEC_ADMINISTRATOR, Console::Yes} + { "add", addCollectionTable }, + { "check", HandleCheckTransmog, SEC_GAMEMASTER, Console::Yes }, + { "", HandleDisableTransMogVisual, SEC_PLAYER, Console::No }, + { "sync", HandleSyncTransMogCommand, SEC_PLAYER, Console::No }, + { "portable", HandleTransmogPortableCommand, SEC_PLAYER, Console::No }, + { "interface", HandleInterfaceOption, SEC_PLAYER, Console::No }, + { "disclaimer", HandleDisclaimerOption, SEC_PLAYER, Console::No }, + { "reload", HandleReloadTransmogConfig, SEC_ADMINISTRATOR, Console::Yes } }; static ChatCommandTable commandTable = @@ -334,6 +335,276 @@ class transmog_commandscript : public CommandScript return true; } + static bool HandleCheckTransmog(ChatHandler* handler, PlayerIdentifier playerIdent, ItemTemplate const* destItem, ItemTemplate const* srcItem) + { + static constexpr char const* MOD = "mod-transmog"; + + WorldSession* gSession = handler->GetSession(); + auto itemDisplay = [&](uint32 itemId) -> std::string + { + if (gSession) + return sTransmogrification->GetItemLink(itemId, gSession); + ItemTemplate const* tpl = sObjectMgr->GetItemTemplate(itemId); + return tpl ? Acore::StringFormat("{} (ID: {})", tpl->Name1, itemId) + : Acore::StringFormat("ID: {}", itemId); + }; + + // Header + handler->PSendModuleSysMessage(MOD, LANG_TRANSMOG_CHECK_HEADER); + handler->PSendModuleSysMessage(MOD, LANG_TRANSMOG_CHECK_DEST, itemDisplay(destItem->ItemId)); + handler->PSendModuleSysMessage(MOD, LANG_TRANSMOG_CHECK_SRC, itemDisplay(srcItem->ItemId)); + handler->PSendModuleSysMessage(MOD, LANG_TRANSMOG_CHECK_PLAYER, playerIdent.GetName()); + + // Resolve player + ObjectGuid playerGuid = playerIdent.GetGUID(); + Player* player = playerIdent.GetConnectedPlayer(); + + CharacterCacheEntry const* cache = sCharacterCache->GetCharacterCacheByGuid(playerGuid); + if (!cache) + { + handler->PSendModuleSysMessage(MOD, LANG_TRANSMOG_CHECK_PLAYER_NOT_FOUND, playerIdent.GetName()); + return true; + } + + uint32 rawGuid = static_cast(playerGuid.GetCounter()); + uint8 playerRace = cache->Race; + uint32 playerLevel = cache->Level; + uint32 raceMask = 1u << (playerRace - 1); + uint32 classMask = 1u << (cache->Class - 1); + TeamId teamId = Player::TeamIdForRace(playerRace); + + std::unordered_map offlineSkills; + if (!player) + { + if (QueryResult res = CharacterDatabase.Query( + "SELECT `skill`, `value` FROM `character_skills` WHERE `guid` = {}", rawGuid)) + { + do { + Field* f = res->Fetch(); + offlineSkills[f[0].Get()] = f[1].Get(); + } while (res->NextRow()); + } + } + + auto skillValue = [&](uint32 skillId) -> uint32 + { + if (player) return player->GetSkillValue(skillId); + auto it = offlineSkills.find(skillId); + return it != offlineSkills.end() ? it->second : 0u; + }; + + auto hasSpellFn = [&](uint32 spellId) -> bool + { + if (player) return player->HasSpell(spellId); + return static_cast(CharacterDatabase.Query( + "SELECT `spell` FROM `character_spell` WHERE `guid` = {} AND `spell` = {}", + rawGuid, spellId)); + }; + + // Section: collects failure messages; skips are silently ignored + struct Section { + bool ok = true; + bool whitelisted = false; + std::vector fails; + + void check(bool passed, std::string failMsg) + { + if (!passed) { ok = false; fails.push_back(std::move(failMsg)); } + } + }; + + // Render one section line: "label [+] All checks passed" or "label [-] fail1; fail2" + std::string okMsg = handler->PGetParseModuleString(MOD, LANG_TRANSMOG_CHECK_SECTION_OK); + auto printSection = [&](std::string const& label, Section const& sec, std::string const& whitelistMsg = "") + { + std::string line = label + " "; + if (sec.whitelisted) + { + line += "[~] " + whitelistMsg; + } + else if (sec.ok) + { + line += "[+] " + okMsg; + } + else + { + line += "[-] "; + for (size_t i = 0; i < sec.fails.size(); ++i) + { + if (i) line += "; "; + line += sec.fails[i]; + } + } + handler->SendSysMessage(line); + }; + + // ---------------------------------------------------------------- + // Pair checks + // ---------------------------------------------------------------- + Section pair; + + pair.check(srcItem->ItemId != destItem->ItemId, + handler->PGetParseModuleString(MOD, LANG_TRANSMOG_CHECK_PAIR_IDS_SAME)); + pair.check(srcItem->DisplayInfoID != destItem->DisplayInfoID, + handler->PGetParseModuleString(MOD, LANG_TRANSMOG_CHECK_PAIR_DISP_SAME)); + pair.check(srcItem->Class == destItem->Class, + handler->PGetParseModuleString(MOD, LANG_TRANSMOG_CHECK_PAIR_CLASS_FAIL)); + + auto isForbiddenType = [](uint32 t) -> bool + { + return t == INVTYPE_BAG || t == INVTYPE_RELIC || t == INVTYPE_FINGER || + t == INVTYPE_TRINKET || t == INVTYPE_AMMO || t == INVTYPE_QUIVER; + }; + + pair.check(!isForbiddenType(destItem->InventoryType), + handler->PGetParseModuleString(MOD, LANG_TRANSMOG_CHECK_PAIR_DEST_TYPE_FAIL)); + pair.check(!isForbiddenType(srcItem->InventoryType), + handler->PGetParseModuleString(MOD, LANG_TRANSMOG_CHECK_PAIR_SRC_TYPE_FAIL)); + + { + bool srcRanged = sTransmogrification->IsRangedWeapon(srcItem->Class, srcItem->SubClass); + bool destRanged = sTransmogrification->IsRangedWeapon(destItem->Class, destItem->SubClass); + pair.check(srcRanged == destRanged, + handler->PGetParseModuleString(MOD, LANG_TRANSMOG_CHECK_PAIR_RANGED_FAIL)); + } + + if (srcItem->SubClass != destItem->SubClass) + pair.check(sTransmogrification->IsSubclassMismatchAllowed(player, srcItem, destItem), + handler->PGetParseModuleString(MOD, LANG_TRANSMOG_CHECK_PAIR_SUB_DENIED)); + + if (srcItem->InventoryType != destItem->InventoryType) + pair.check(sTransmogrification->IsInvTypeMismatchAllowed(srcItem, destItem), + handler->PGetParseModuleString(MOD, LANG_TRANSMOG_CHECK_PAIR_INV_DENIED)); + + // ---------------------------------------------------------------- + // Per-item checks (collect into a section, print later) + // ---------------------------------------------------------------- + auto gatherItemChecks = [&](ItemTemplate const* proto, Section& sec) + { + if (sTransmogrification->IsAllowed(proto->ItemId)) + { + sec.whitelisted = true; + return; + } + + { + bool ok = proto->Class == ITEM_CLASS_ARMOR || proto->Class == ITEM_CLASS_WEAPON; + sec.check(ok, handler->PGetParseModuleString(MOD, LANG_TRANSMOG_CHECK_ITEM_CLASS_FAIL)); + if (!ok) + return; + } + + sec.check(!sTransmogrification->IsNotAllowed(proto->ItemId), + handler->PGetParseModuleString(MOD, LANG_TRANSMOG_CHECK_ITEM_BLACKLISTED)); + + sec.check(sTransmogrification->IsAllowedQuality(proto->Quality, playerGuid), + handler->PGetParseModuleString(MOD, LANG_TRANSMOG_CHECK_ITEM_QUALITY_FAIL)); + + if (proto->Class == ITEM_CLASS_WEAPON && proto->SubClass == ITEM_SUBCLASS_WEAPON_FISHING_POLE) + sec.check(sTransmogrification->AllowFishingPoles, + handler->PGetParseModuleString(MOD, LANG_TRANSMOG_CHECK_ITEM_POLE_FAIL)); + + if (proto->HolidayId) + sec.check(sTransmogrification->IgnoreReqEvent || IsHolidayActive((HolidayIds)proto->HolidayId), + handler->PGetParseModuleString(MOD, LANG_TRANSMOG_CHECK_ITEM_EVENT_FAIL)); + + if (!sTransmogrification->IgnoreReqStats && !proto->RandomProperty && !proto->RandomSuffix && proto->StatsCount > 0) + { + bool hasStats = false; + for (uint8 i = 0; i < proto->StatsCount; ++i) + if (proto->ItemStat[i].ItemStatValue != 0) { hasStats = true; break; } + sec.check(hasStats, handler->PGetParseModuleString(MOD, LANG_TRANSMOG_CHECK_ITEM_STAT_FAIL)); + } + + { + uint32 subclassSkill = proto->GetSkill(); + if (proto->SubClass > 0 && subclassSkill) + { + uint32 sv = skillValue(subclassSkill); + bool ok; + if (proto->Class == ITEM_CLASS_ARMOR) + ok = sv > 0 || sTransmogrification->AllowMixedArmorTypes; + else + ok = sv > 0 || sTransmogrification->AllowMixedWeaponTypes == MIXED_WEAPONS_LOOSE; + sec.check(ok, handler->PGetParseModuleString(MOD, + LANG_TRANSMOG_CHECK_ITEM_PROF_FAIL, subclassSkill)); + } + } + + if (proto->HasFlag2(ITEM_FLAG2_FACTION_HORDE)) + sec.check(teamId == TEAM_HORDE, + handler->PGetParseModuleString(MOD, LANG_TRANSMOG_CHECK_ITEM_FACTION_FAIL)); + else if (proto->HasFlag2(ITEM_FLAG2_FACTION_ALLIANCE)) + sec.check(teamId == TEAM_ALLIANCE, + handler->PGetParseModuleString(MOD, LANG_TRANSMOG_CHECK_ITEM_FACTION_FAIL)); + + if (!sTransmogrification->IgnoreReqClass) + sec.check((proto->AllowableClass & classMask) != 0, + handler->PGetParseModuleString(MOD, LANG_TRANSMOG_CHECK_ITEM_CLASS_REQ_FAIL)); + + if (!sTransmogrification->IgnoreReqRace) + sec.check((proto->AllowableRace & raceMask) != 0, + handler->PGetParseModuleString(MOD, LANG_TRANSMOG_CHECK_ITEM_RACE_REQ_FAIL)); + + if (!sTransmogrification->IgnoreReqSkill && proto->RequiredSkill != 0) + { + uint32 sv = skillValue(proto->RequiredSkill); + sec.check(sv >= proto->RequiredSkillRank, + handler->PGetParseModuleString(MOD, LANG_TRANSMOG_CHECK_ITEM_SKILL_FAIL, + proto->RequiredSkill, proto->RequiredSkillRank, sv)); + } + + if (!sTransmogrification->IgnoreLevelRequirement(playerGuid)) + sec.check(playerLevel >= proto->RequiredLevel, + handler->PGetParseModuleString(MOD, LANG_TRANSMOG_CHECK_ITEM_LEVEL_FAIL, + proto->RequiredLevel, playerLevel)); + + bool skipSpell = sTransmogrification->AllowLowerTiers && + sTransmogrification->TierAvailable(player, rawGuid, proto->SubClass); + if (!sTransmogrification->IgnoreReqSpell && proto->RequiredSpell != 0 && !skipSpell) + sec.check(hasSpellFn(proto->RequiredSpell), + handler->PGetParseModuleString(MOD, LANG_TRANSMOG_CHECK_ITEM_SPELL_FAIL, proto->RequiredSpell)); + }; + + Section destSec, srcSec; + gatherItemChecks(destItem, destSec); + gatherItemChecks(srcItem, srcSec); + + // ---------------------------------------------------------------- + // Collection check + // ---------------------------------------------------------------- + bool collOk = true; + std::string collLine = handler->PGetParseModuleString(MOD, LANG_TRANSMOG_CHECK_SECTION_COLL) + " "; + if (!sTransmogrification->GetUseCollectionSystem()) + { + collLine += "[~] " + handler->PGetParseModuleString(MOD, LANG_TRANSMOG_CHECK_COLL_DISABLED); + } + else + { + uint32 accountId = sCharacterCache->GetCharacterAccountIdByGuid(playerGuid); + auto const& collCache = sTransmogrification->collectionCache[accountId]; + bool inCollection = collCache.find(srcItem->ItemId) != collCache.end(); + collOk = inCollection; + collLine += inCollection + ? "[+] " + okMsg + : "[-] " + handler->PGetParseModuleString(MOD, LANG_TRANSMOG_CHECK_COLL_NOT_FOUND); + } + + // ---------------------------------------------------------------- + // Print all section results, then final verdict + // ---------------------------------------------------------------- + std::string whitelistMsg = handler->PGetParseModuleString(MOD, LANG_TRANSMOG_CHECK_ITEM_WHITELISTED); + printSection(handler->PGetParseModuleString(MOD, LANG_TRANSMOG_CHECK_SECTION_PAIR), pair); + printSection(handler->PGetParseModuleString(MOD, LANG_TRANSMOG_CHECK_SECTION_ITEM, itemDisplay(destItem->ItemId)), destSec, whitelistMsg); + printSection(handler->PGetParseModuleString(MOD, LANG_TRANSMOG_CHECK_SECTION_ITEM, itemDisplay(srcItem->ItemId)), srcSec, whitelistMsg); + handler->SendSysMessage(collLine); + + bool overallOk = pair.ok && destSec.ok && srcSec.ok && collOk; + handler->PSendModuleSysMessage(MOD, overallOk ? LANG_TRANSMOG_CHECK_RESULT_OK : LANG_TRANSMOG_CHECK_RESULT_FAIL); + + return true; + } + static bool HandleReloadTransmogConfig(ChatHandler* handler) { sTransmogrification->LoadConfig(true);