diff --git a/.gitignore b/.gitignore index 01e0a7232..27794c483 100644 --- a/.gitignore +++ b/.gitignore @@ -54,9 +54,11 @@ CMakeLists.txt.user *.LOCAL.* # -# AI tools +# Claude # -.claude +.claude/worktrees +.claude/settings.local.json +.claude/plans/** # # IDE & other software diff --git a/data/sql/updates/db_auth/2026_05_09_00.sql b/data/sql/updates/db_auth/2026_05_09_00.sql new file mode 100644 index 000000000..228d620df --- /dev/null +++ b/data/sql/updates/db_auth/2026_05_09_00.sql @@ -0,0 +1,3 @@ +-- DB update 2026_05_03_00 -> 2026_05_09_00 +DELETE FROM `rbac_linked_permissions` WHERE `id` = 198 AND `linkedId` = 471; +DELETE FROM `rbac_permissions` WHERE `id` = 471; diff --git a/data/sql/updates/db_auth/2026_05_14_00.sql b/data/sql/updates/db_auth/2026_05_14_00.sql new file mode 100644 index 000000000..f0a5d87a3 --- /dev/null +++ b/data/sql/updates/db_auth/2026_05_14_00.sql @@ -0,0 +1,18 @@ +-- DB update 2026_05_09_00 -> 2026_05_14_00 +-- +-- RBAC permissions for `respawn creature guid`, `respawn gameobject guid`, +-- `respawn creature entry`, and `respawn gameobject entry` commands (Console::Yes/No, security level 3 - GM) +-- +DELETE FROM `rbac_permissions` WHERE `id` IN (916, 917, 918, 919); +INSERT INTO `rbac_permissions` (`id`, `name`) VALUES +(916, 'Command: respawn creature guid'), +(917, 'Command: respawn gameobject guid'), +(918, 'Command: respawn creature entry'), +(919, 'Command: respawn gameobject entry'); + +DELETE FROM `rbac_linked_permissions` WHERE `linkedId` IN (916, 917, 918, 919); +INSERT INTO `rbac_linked_permissions` (`id`, `linkedId`) VALUES +(197, 916), +(197, 917), +(197, 918), +(197, 919); diff --git a/data/sql/updates/db_world/2026_05_09_00.sql b/data/sql/updates/db_world/2026_05_09_00.sql new file mode 100644 index 000000000..427f3a246 --- /dev/null +++ b/data/sql/updates/db_world/2026_05_09_00.sql @@ -0,0 +1,5 @@ +-- DB update 2026_05_08_00 -> 2026_05_09_00 +-- +DELETE FROM `smart_scripts` WHERE (`entryorguid` = 27292) AND (`source_type` = 0) AND (`id` IN (5)); +INSERT INTO `smart_scripts` (`entryorguid`, `source_type`, `id`, `link`, `event_type`, `event_phase_mask`, `event_chance`, `event_flags`, `event_param1`, `event_param2`, `event_param3`, `event_param4`, `event_param5`, `event_param6`, `action_type`, `action_param1`, `action_param2`, `action_param3`, `action_param4`, `action_param5`, `action_param6`, `target_type`, `target_param1`, `target_param2`, `target_param3`, `target_param4`, `target_x`, `target_y`, `target_z`, `target_o`, `comment`) VALUES +(27292, 0, 5, 0, 61, 0, 100, 512, 0, 0, 0, 0, 0, 0, 134, 48600, 3, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Flamebringer - On Just Summoned - Invoker Cast \'Ride Flamebringer\''); diff --git a/data/sql/updates/db_world/2026_05_09_01.sql b/data/sql/updates/db_world/2026_05_09_01.sql new file mode 100644 index 000000000..23fd53ee8 --- /dev/null +++ b/data/sql/updates/db_world/2026_05_09_01.sql @@ -0,0 +1,5 @@ +-- DB update 2026_05_09_00 -> 2026_05_09_01 +-- +DELETE FROM `acore_string` WHERE `entry` = 35440; +INSERT INTO `acore_string` (`entry`, `content_default`, `locale_koKR`, `locale_frFR`, `locale_deDE`, `locale_zhCN`, `locale_zhTW`, `locale_esES`, `locale_esMX`, `locale_ruRU`) VALUES +(35440, 'Showing only first 50 results. Please use a more specific search string.', '처음 50개의 결과만 표시됩니다. 더 구체적인 검색어를 사용해 주세요.', 'Affichage des 50 premiers résultats uniquement. Veuillez utiliser une chaîne de recherche plus spécifique.', 'Es werden nur die ersten 50 Ergebnisse angezeigt. Bitte verwende eine genauere Suchzeichenkette.', '仅显示前50个结果,请使用更具体的搜索字符串。', '僅顯示前50個結果,請使用更具體的搜尋字串。', 'Solo se muestran los primeros 50 resultados. Por favor, utilice una cadena de búsqueda más específica.', 'Solo se muestran los primeros 50 resultados. Por favor, utilice una cadena de búsqueda más específica.', 'Отображаются только первые 50 результатов. Пожалуйста, используйте более конкретную строку поиска.'); diff --git a/data/sql/updates/db_world/2026_05_10_00.sql b/data/sql/updates/db_world/2026_05_10_00.sql new file mode 100644 index 000000000..fedd062d2 --- /dev/null +++ b/data/sql/updates/db_world/2026_05_10_00.sql @@ -0,0 +1,7 @@ +-- DB update 2026_05_09_01 -> 2026_05_10_00 +-- Restore TEMPSUMMON_MANUAL_DESPAWN (8) with no timer for Thiassi -> Antiok +-- accessory. With timer 0 this is rewritten to TEMPSUMMON_DEAD_DESPAWN at +-- runtime, so Antiok only despawns when he dies and no longer ticks down +-- while seated on Thiassi pre-engage. +UPDATE `vehicle_template_accessory` SET `summontype` = 8, `summontimer` = 0 + WHERE `entry` = 28018 AND `accessory_entry` = 28006; diff --git a/data/sql/updates/db_world/2026_05_10_01.sql b/data/sql/updates/db_world/2026_05_10_01.sql new file mode 100644 index 000000000..9765bf229 --- /dev/null +++ b/data/sql/updates/db_world/2026_05_10_01.sql @@ -0,0 +1,10 @@ +-- DB update 2026_05_10_00 -> 2026_05_10_01 +-- +-- Show character level in `.character deleted list` output +-- +DELETE FROM `acore_string` WHERE `entry` IN (1016, 1017, 1018, 1026); +INSERT INTO `acore_string` (`entry`, `content_default`, `locale_koKR`, `locale_frFR`, `locale_deDE`, `locale_zhCN`, `locale_zhTW`, `locale_esES`, `locale_esMX`, `locale_ruRU`) VALUES +(1016, '| GUID | Name | Level | Account | Delete Date |', NULL, NULL, '| GUID | Name | Level | Account | gelöscht am |', '| 唯一标识符 | 名称 | 等级 | 账号 | 删除日期 |', NULL, NULL, NULL, NULL), +(1017, '| {} | {} | {} | {} ({}) | {} |', NULL, NULL, '| {} | {} | {} | {} ({}) | {} |', '| {} | {} | {} | {} ({}) | {} |', NULL, NULL, NULL, NULL), +(1018, '==================================================================================================', NULL, NULL, '==================================================================================================', '错误:510', NULL, NULL, NULL, NULL), +(1026, 'GUID: {} Name: {} Level: {} Account: {} ({}) Date: {}', NULL, NULL, 'GUID: {} Name: {} Level: {} Account: {} ({}) Datum: {}', '唯一标识符: {} 名称: {} 等级: {} 账号: {} ({}) 时间: {}', NULL, NULL, NULL, NULL); diff --git a/data/sql/updates/db_world/2026_05_14_00.sql b/data/sql/updates/db_world/2026_05_14_00.sql new file mode 100644 index 000000000..3165d9e1c --- /dev/null +++ b/data/sql/updates/db_world/2026_05_14_00.sql @@ -0,0 +1,37 @@ +-- DB update 2026_05_10_01 -> 2026_05_14_00 +-- +-- Strings for `respawn creature guid` / `respawn gameobject guid` / +-- `respawn creature entry` / `respawn gameobject entry` commands and updated `list respawns` command +-- +DELETE FROM `acore_string` WHERE `entry` IN (35441, 35442, 35443, 35444, 35445, 35446, 35447, 35448, 35449, 35450, 35451, 35452); +INSERT INTO `acore_string` (`entry`, `content_default`, `locale_koKR`, `locale_frFR`, `locale_deDE`, `locale_zhCN`, `locale_zhTW`, `locale_esES`, `locale_esMX`, `locale_ruRU`) VALUES +(35441, 'No creature with spawn GUID {} found.', '스폰 GUID {}를 가진 생물을 찾을 수 없습니다.', 'Aucune créature avec le GUID d''apparition {} trouvée.', 'Keine Kreatur mit Spawn-GUID {} gefunden.', '未找到生成 GUID 为 {} 的生物。', '未找到生成 GUID 為 {} 的生物。', 'No se encontró ninguna criatura con el GUID de generación {}.', 'No se encontró ninguna criatura con el GUID de generación {}.', 'Существо с GUID спавна {} не найдено.'), +(35442, 'Map {} is not currently loaded.', '맵 {}이(가) 현재 로드되어 있지 않습니다.', 'La carte {} n''est pas actuellement chargée.', 'Karte {} ist derzeit nicht geladen.', '地图 {} 当前未加载。', '地圖 {} 目前未載入。', 'El mapa {} no está cargado actualmente.', 'El mapa {} no está cargado actualmente.', 'Карта {} в данный момент не загружена.'), +(35443, 'Creature (spawn GUID {}, entry {}) is already alive.', '생물 (스폰 GUID {}, 항목 {})이(가) 이미 살아 있습니다.', 'La créature (GUID d''apparition {}, entrée {}) est déjà en vie.', 'Kreatur (Spawn-GUID {}, Eintrag {}) ist bereits am Leben.', '生物(生成 GUID {},条目 {})已经存活。', '生物(生成 GUID {},條目 {})已經存活。', 'La criatura (GUID de generación {}, entrada {}) ya está viva.', 'La criatura (GUID de generación {}, entrada {}) ya está viva.', 'Существо (GUID спавна {}, запись {}) уже живо.'), +(35444, 'Creature (spawn GUID {}, entry {}) queued for respawn.', '생물 (스폰 GUID {}, 항목 {})이(가) 리스폰 대기열에 추가되었습니다.', 'La créature (GUID d''apparition {}, entrée {}) est mise en file d''attente pour réapparition.', 'Kreatur (Spawn-GUID {}, Eintrag {}) für Respawn in Warteschlange.', '生物(生成 GUID {},条目 {})已加入重生队列。', '生物(生成 GUID {},條目 {})已加入重生佇列。', 'La criatura (GUID de generación {}, entrada {}) está en cola para reaparecer.', 'La criatura (GUID de generación {}, entrada {}) está en cola para reaparecer.', 'Существо (GUID спавна {}, запись {}) добавлено в очередь на респаун.'), +(35445, 'Gameobject (spawn GUID {}, entry {}) is already active.', '게임오브젝트 (스폰 GUID {}, 항목 {})이(가) 이미 활성화되어 있습니다.', 'L''objet de jeu (GUID d''apparition {}, entrée {}) est déjà actif.', 'Spielobjekt (Spawn-GUID {}, Eintrag {}) ist bereits aktiv.', '游戏对象(生成 GUID {},条目 {})已经激活。', '遊戲物件(生成 GUID {},條目 {})已經激活。', 'El objeto de juego (GUID de generación {}, entrada {}) ya está activo.', 'El objeto de juego (GUID de generación {}, entrada {}) ya está activo.', 'Игровой объект (GUID спавна {}, запись {}) уже активен.'), +(35446, 'Gameobject (spawn GUID {}, entry {}) queued for respawn.', '게임오브젝트 (스폰 GUID {}, 항목 {})이(가) 리스폰 대기열에 추가되었습니다.', 'L''objet de jeu (GUID d''apparition {}, entrée {}) est mis en file d''attente pour réapparition.', 'Spielobjekt (Spawn-GUID {}, Eintrag {}) für Respawn in Warteschlange.', '游戏对象(生成 GUID {},条目 {})已加入重生队列。', '遊戲物件(生成 GUID {},條目 {})已加入重生佇列。', 'El objeto de juego (GUID de generación {}, entrada {}) está en cola para reaparecer.', 'El objeto de juego (GUID de generación {}, entrada {}) está en cola para reaparecer.', 'Игровой объект (GUID спавна {}, запись {}) добавлен в очередь на респаун.'), +(35447, 'A map ID is required when using this command from the console.', '콘솔에서 이 명령을 사용하려면 맵 ID가 필요합니다.', 'Un ID de carte est requis lors de l''utilisation de cette commande depuis la console.', 'Eine Karten-ID ist erforderlich, wenn dieser Befehl über die Konsole verwendet wird.', '从控制台使用此命令时需要提供地图 ID。', '從控制台使用此命令時需要提供地圖 ID。', 'Se requiere un ID de mapa al usar este comando desde la consola.', 'Se requiere un ID de mapa al usar este comando desde la consola.', 'При использовании этой команды из консоли требуется ID карты.'), +(35448, 'No gameobject with spawn GUID {} found.', '스폰 GUID {}를 가진 게임오브젝트를 찾을 수 없습니다.', 'Aucun objet de jeu avec le GUID d''apparition {} trouvé.', 'Kein Spielobjekt mit Spawn-GUID {} gefunden.', '未找到生成 GUID 为 {} 的游戏对象。', '未找到生成 GUID 為 {} 的遊戲物件。', 'No se encontró ningún objeto de juego con el GUID de generación {}.', 'No se encontró ningún objeto de juego con el GUID de generación {}.', 'Игровой объект с GUID спавна {} не найден.'), +(35449, 'No creature template with entry {} found.', '항목 {}을(를) 가진 생물 템플릿을 찾을 수 없습니다.', 'Aucun modèle de créature avec l''entrée {} trouvé.', 'Kein Kreaturvorlage mit Eintrag {} gefunden.', '未找到条目为 {} 的生物模板。', '未找到條目為 {} 的生物模板。', 'No se encontró ninguna plantilla de criatura con la entrada {}.', 'No se encontró ninguna plantilla de criatura con la entrada {}.', 'Шаблон существа с записью {} не найден.'), +(35450, 'No gameobject template with entry {} found.', '항목 {}을(를) 가진 게임오브젝트 템플릿을 찾을 수 없습니다.', 'Aucun modèle d''objet de jeu avec l''entrée {} trouvé.', 'Kein Spielobjektvorlage mit Eintrag {} gefunden.', '未找到条目为 {} 的游戏对象模板。', '未找到條目為 {} 的遊戲物件模板。', 'No se encontró ninguna plantilla de objeto de juego con la entrada {}.', 'No se encontró ninguna plantilla de objeto de juego con la entrada {}.', 'Шаблон игрового объекта с записью {} не найден.'), +(35451, 'Queued {} creature spawn(s) with entry {} for respawn on this map.', '이 맵에서 항목 {}을(를) 가진 생물 스폰 {}개가 리스폰 대기열에 추가되었습니다.', '{} apparition(s) de créature avec l''entrée {} mise(s) en file d''attente pour réapparition sur cette carte.', '{} Kreaturspawn(s) mit Eintrag {} für Respawn auf dieser Karte in Warteschlange.', '在此地图上,已将 {} 个条目为 {} 的生物生成加入重生队列。', '在此地圖上,已將 {} 個條目為 {} 的生物生成加入重生佇列。', 'Se pusieron en cola {} aparición(es) de criatura con entrada {} para reaparecer en este mapa.', 'Se pusieron en cola {} aparición(es) de criatura con entrada {} para reaparecer en este mapa.', 'В очередь на респаун на этой карте добавлено {} спавн(ов) существа с записью {}.'), +(35452, 'Queued {} gameobject spawn(s) with entry {} for respawn on this map.', '이 맵에서 항목 {}을(를) 가진 게임오브젝트 스폰 {}개가 리스폰 대기열에 추가되었습니다.', '{} apparition(s) d''objet de jeu avec l''entrée {} mise(s) en file d''attente pour réapparition sur cette carte.', '{} Spielobjektspawn(s) mit Eintrag {} für Respawn auf dieser Karte in Warteschlange.', '在此地图上,已将 {} 个条目为 {} 的游戏对象生成加入重生队列。', '在此地圖上,已將 {} 個條目為 {} 的遊戲物件生成加入重生佇列。', 'Se pusieron en cola {} aparición(es) de objeto de juego con entrada {} para reaparecer en este mapa.', 'Se pusieron en cola {} aparición(es) de objeto de juego con entrada {} para reaparecer en este mapa.', 'В очередь на респаун на этой карте добавлено {} спавн(ов) игрового объекта с записью {}.'); + +-- +-- Update `list respawns` command help text +-- +DELETE FROM `command` WHERE `name` = 'list respawns'; +INSERT INTO `command` (`name`, `security`, `help`) VALUES +('list respawns', 2, 'Syntax: .list respawns [entryId]\r\nIn-game: shows all pending creature and gameobject respawns on the current map, filtered by entryId if provided.\r\nConsole: .list respawns #mapId [#instanceId [#entryId]] - specify map and optional instance ID and entry filter.'); + +-- +-- Register `respawn creature guid`, `respawn gameobject guid`, +-- `respawn creature entry`, and `respawn gameobject entry` commands +-- +DELETE FROM `command` WHERE `name` IN ('respawn id', 'respawn guid', 'respawn creature guid', 'respawn gameobject guid', 'respawn creature entry', 'respawn gameobject entry'); +INSERT INTO `command` (`name`, `security`, `help`) VALUES +('respawn creature guid', 3, 'Syntax: .respawn creature guid #spawnGuid\r\nForces the creature with the given spawn GUID (database GUID) to respawn. Usable from console.'), +('respawn gameobject guid', 3, 'Syntax: .respawn gameobject guid #spawnGuid\r\nForces the gameobject with the given spawn GUID (database GUID) to respawn. Usable from console.'), +('respawn creature entry', 3, 'Syntax: .respawn creature entry #entry [#mapId [#instanceId]]\r\nIn-game: forces all creatures with the given #entry to respawn on the current map.\r\nConsole: specify #mapId (required) and optionally #instanceId. Pooled creatures are skipped in the queue phase to prevent duplicate spawns.'), +('respawn gameobject entry', 3, 'Syntax: .respawn gameobject entry #entry [#mapId [#instanceId]]\r\nIn-game: forces all gameobjects with the given #entry to respawn on the current map.\r\nConsole: specify #mapId (required) and optionally #instanceId. Pooled gameobjects are skipped in the queue phase to prevent duplicate spawns.'); diff --git a/data/sql/updates/db_world/2026_05_15_00.sql b/data/sql/updates/db_world/2026_05_15_00.sql new file mode 100644 index 000000000..bdc947f43 --- /dev/null +++ b/data/sql/updates/db_world/2026_05_15_00.sql @@ -0,0 +1,70 @@ +-- DB update 2026_05_14_00 -> 2026_05_15_00 +-- Remove trigger bunny SAI scripts that interfere with the chain mechanic +DELETE FROM `smart_scripts` WHERE (`source_type` = 0 AND `entryorguid` IN (-113478, -113479, -113550, -113551)); + +-- === CHAIN BUNNIES: add On Spawn event (id=0) so chains arm immediately on spawn === +-- Phase 1 = chain intact and ready to be broken; Phase 0 = chain broken +-- id=0: SPAWN -> CAST chain spell, link=1 +-- id=1: LINK -> SET PHASE 1 +-- id=2: ACTION(10) -> CAST chain spell, link=1 (Akali reset: re-arm) +-- id=3: SPELLHIT(52816), phase=1, flags=512 -> REMOVE_AURA from trigger bunny, link=4 +-- id=4: LINK, phase=1 -> DO_ACTION(11) on Akali, link=5 +-- id=5: LINK -> SET PHASE 0 + +DELETE FROM `smart_scripts` WHERE (`source_type` = 0 AND `entryorguid` IN (-113549, -113548, -61994, -61995)); +INSERT INTO `smart_scripts` (`entryorguid`, `source_type`, `id`, `link`, `event_type`, `event_phase_mask`, `event_chance`, `event_flags`, `event_param1`, `event_param2`, `event_param3`, `event_param4`, `event_param5`, `event_param6`, `action_type`, `action_param1`, `action_param2`, `action_param3`, `action_param4`, `action_param5`, `action_param6`, `target_type`, `target_param1`, `target_param2`, `target_param3`, `target_param4`, `target_x`, `target_y`, `target_z`, `target_o`, `comment`) VALUES +-- Right Front Paw GUID -113549 / chain spell 52834 / trigger bunny 113551 +(-113549, 0, 0, 1, 11, 0, 100, 0, 0, 0, 0, 0, 0, 0, 11, 52834, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'ELM General Purpose Bunny (scale x0.01) Large - On Spawn: Cast \'Rampage: Akali`s Chains - Right Front Paw\''), +(-113549, 0, 1, 0, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 22, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'ELM General Purpose Bunny (scale x0.01) Large - On Spawn/Reset: Set Event Phase 1'), +(-113549, 0, 2, 1, 72, 0, 100, 0, 10, 0, 0, 0, 0, 0, 11, 52834, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'ELM General Purpose Bunny (scale x0.01) Large - On Action 10 (Reset): Cast \'Rampage: Akali`s Chains - Right Front Paw\''), +(-113549, 0, 3, 4, 8, 1, 100, 512, 52816, 0, 0, 0, 0, 0, 28, 52834, 0, 0, 0, 0, 0, 10, 113551, 26298, 0, 0, 0, 0, 0, 0, 'ELM General Purpose Bunny (scale x0.01) Large - On Spellhit \'Rampage: Akali Chain Anchor On Disturb\': Remove Aura \'Rampage: Akali`s Chains - Right Front Paw\''), +(-113549, 0, 4, 5, 61, 1, 100, 0, 0, 0, 0, 0, 0, 0, 223, 11, 0, 0, 0, 0, 0, 10, 98159, 28952, 0, 0, 0, 0, 0, 0, 'ELM General Purpose Bunny (scale x0.01) Large - On Spellhit \'Rampage: Akali Chain Anchor On Disturb\': Do Action 11 On Akali - Right Front Paw Freed'), +(-113549, 0, 5, 0, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 22, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'ELM General Purpose Bunny (scale x0.01) Large - On Spellhit \'Rampage: Akali Chain Anchor On Disturb\': Set Event Phase 0'), +-- Left Front Paw GUID -113548 / chain spell 52833 / trigger bunny 113550 +(-113548, 0, 0, 1, 11, 0, 100, 0, 0, 0, 0, 0, 0, 0, 11, 52833, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'ELM General Purpose Bunny (scale x0.01) Large - On Spawn: Cast \'Rampage: Akali`s Chains - Left Front Paw\''), +(-113548, 0, 1, 0, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 22, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'ELM General Purpose Bunny (scale x0.01) Large - On Spawn/Reset: Set Event Phase 1'), +(-113548, 0, 2, 1, 72, 0, 100, 0, 10, 0, 0, 0, 0, 0, 11, 52833, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'ELM General Purpose Bunny (scale x0.01) Large - On Action 10 (Reset): Cast \'Rampage: Akali`s Chains - Left Front Paw\''), +(-113548, 0, 3, 4, 8, 1, 100, 512, 52816, 0, 0, 0, 0, 0, 28, 52833, 0, 0, 0, 0, 0, 10, 113550, 26298, 0, 0, 0, 0, 0, 0, 'ELM General Purpose Bunny (scale x0.01) Large - On Spellhit \'Rampage: Akali Chain Anchor On Disturb\': Remove Aura \'Rampage: Akali`s Chains - Left Front Paw\''), +(-113548, 0, 4, 5, 61, 1, 100, 0, 0, 0, 0, 0, 0, 0, 223, 11, 0, 0, 0, 0, 0, 10, 98159, 28952, 0, 0, 0, 0, 0, 0, 'ELM General Purpose Bunny (scale x0.01) Large - On Spellhit \'Rampage: Akali Chain Anchor On Disturb\': Do Action 11 On Akali - Left Front Paw Freed'), +(-113548, 0, 5, 0, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 22, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'ELM General Purpose Bunny (scale x0.01) Large - On Spellhit \'Rampage: Akali Chain Anchor On Disturb\': Set Event Phase 0'), +-- Right Rear Paw GUID -61994 / chain spell 52837 / trigger bunny 113478 +(-61994, 0, 0, 1, 11, 0, 100, 0, 0, 0, 0, 0, 0, 0, 11, 52837, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'ELM General Purpose Bunny (scale x0.01) Large - On Spawn: Cast \'Rampage: Akali`s Chains - Right Rear Paw\''), +(-61994, 0, 1, 0, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 22, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'ELM General Purpose Bunny (scale x0.01) Large - On Spawn/Reset: Set Event Phase 1'), +(-61994, 0, 2, 1, 72, 0, 100, 0, 10, 0, 0, 0, 0, 0, 11, 52837, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'ELM General Purpose Bunny (scale x0.01) Large - On Action 10 (Reset): Cast \'Rampage: Akali`s Chains - Right Rear Paw\''), +(-61994, 0, 3, 4, 8, 1, 100, 512, 52816, 0, 0, 0, 0, 0, 28, 52837, 0, 0, 0, 0, 0, 10, 113478, 26298, 0, 0, 0, 0, 0, 0, 'ELM General Purpose Bunny (scale x0.01) Large - On Spellhit \'Rampage: Akali Chain Anchor On Disturb\': Remove Aura \'Rampage: Akali`s Chains - Right Rear Paw\''), +(-61994, 0, 4, 5, 61, 1, 100, 0, 0, 0, 0, 0, 0, 0, 223, 11, 0, 0, 0, 0, 0, 10, 98159, 28952, 0, 0, 0, 0, 0, 0, 'ELM General Purpose Bunny (scale x0.01) Large - On Spellhit \'Rampage: Akali Chain Anchor On Disturb\': Do Action 11 On Akali - Right Rear Paw Freed'), +(-61994, 0, 5, 0, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 22, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'ELM General Purpose Bunny (scale x0.01) Large - On Spellhit \'Rampage: Akali Chain Anchor On Disturb\': Set Event Phase 0'), +-- Left Rear Paw GUID -61995 / chain spell 52838 / trigger bunny 113479 +(-61995, 0, 0, 1, 11, 0, 100, 0, 0, 0, 0, 0, 0, 0, 11, 52838, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'ELM General Purpose Bunny (scale x0.01) Large - On Spawn: Cast \'Rampage: Akali`s Chains - Left Rear Paw\''), +(-61995, 0, 1, 0, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 22, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'ELM General Purpose Bunny (scale x0.01) Large - On Spawn/Reset: Set Event Phase 1'), +(-61995, 0, 2, 1, 72, 0, 100, 0, 10, 0, 0, 0, 0, 0, 11, 52838, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'ELM General Purpose Bunny (scale x0.01) Large - On Action 10 (Reset): Cast \'Rampage: Akali`s Chains - Left Rear Paw\''), +(-61995, 0, 3, 4, 8, 1, 100, 512, 52816, 0, 0, 0, 0, 0, 28, 52838, 0, 0, 0, 0, 0, 10, 113479, 26298, 0, 0, 0, 0, 0, 0, 'ELM General Purpose Bunny (scale x0.01) Large - On Spellhit \'Rampage: Akali Chain Anchor On Disturb\': Remove Aura \'Rampage: Akali`s Chains - Left Rear Paw\''), +(-61995, 0, 4, 5, 61, 1, 100, 0, 0, 0, 0, 0, 0, 0, 223, 11, 0, 0, 0, 0, 0, 10, 98159, 28952, 0, 0, 0, 0, 0, 0, 'ELM General Purpose Bunny (scale x0.01) Large - On Spellhit \'Rampage: Akali Chain Anchor On Disturb\': Do Action 11 On Akali - Left Rear Paw Freed'), +(-61995, 0, 5, 0, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 22, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'ELM General Purpose Bunny (scale x0.01) Large - On Spellhit \'Rampage: Akali Chain Anchor On Disturb\': Set Event Phase 0'); + +-- === FIRE BUNNIES: add On Spawn cast so fire visual appears immediately on spawn === +-- id=0: SPAWN -> CAST fire aura on self +-- id=1: ACTION(10) -> CAST fire aura on self (Akali reset relay) +-- id=2: SPELLHIT(52816), flags=512 -> REMOVE_AURA fire aura from self + +DELETE FROM `smart_scripts` WHERE (`source_type` = 0 AND `entryorguid` IN (-100336, -100333, -100335, -100334)); +INSERT INTO `smart_scripts` (`entryorguid`, `source_type`, `id`, `link`, `event_type`, `event_phase_mask`, `event_chance`, `event_flags`, `event_param1`, `event_param2`, `event_param3`, `event_param4`, `event_param5`, `event_param6`, `action_type`, `action_param1`, `action_param2`, `action_param3`, `action_param4`, `action_param5`, `action_param6`, `target_type`, `target_param1`, `target_param2`, `target_param3`, `target_param4`, `target_x`, `target_y`, `target_z`, `target_o`, `comment`) VALUES +-- Right Front Fire GUID -100336 +(-100336, 0, 0, 0, 11, 0, 100, 0, 0, 0, 0, 0, 0, 0, 11, 52855, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'ELM General Purpose Bunny Large - On Spawn: Cast \'Cosmetic - Low Poly Fire (with Sound)\''), +(-100336, 0, 1, 0, 72, 0, 100, 0, 10, 0, 0, 0, 0, 0, 11, 52855, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'ELM General Purpose Bunny Large - On Action 10 (Reset): Cast \'Cosmetic - Low Poly Fire (with Sound)\''), +(-100336, 0, 2, 0, 8, 0, 100, 512, 52816, 0, 0, 0, 0, 0, 28, 52855, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'ELM General Purpose Bunny Large - On Spellhit \'Rampage: Akali Chain Anchor On Disturb\': Remove Aura \'Cosmetic - Low Poly Fire (with Sound)\''), +-- Left Front Fire GUID -100333 +(-100333, 0, 0, 0, 11, 0, 100, 0, 0, 0, 0, 0, 0, 0, 11, 52855, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'ELM General Purpose Bunny Large - On Spawn: Cast \'Cosmetic - Low Poly Fire (with Sound)\''), +(-100333, 0, 1, 0, 72, 0, 100, 0, 10, 0, 0, 0, 0, 0, 11, 52855, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'ELM General Purpose Bunny Large - On Action 10 (Reset): Cast \'Cosmetic - Low Poly Fire (with Sound)\''), +(-100333, 0, 2, 0, 8, 0, 100, 512, 52816, 0, 0, 0, 0, 0, 28, 52855, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'ELM General Purpose Bunny Large - On Spellhit \'Rampage: Akali Chain Anchor On Disturb\': Remove Aura \'Cosmetic - Low Poly Fire (with Sound)\''), +-- Right Rear Fire GUID -100335 +(-100335, 0, 0, 0, 11, 0, 100, 0, 0, 0, 0, 0, 0, 0, 11, 52855, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'ELM General Purpose Bunny Large - On Spawn: Cast \'Cosmetic - Low Poly Fire (with Sound)\''), +(-100335, 0, 1, 0, 72, 0, 100, 0, 10, 0, 0, 0, 0, 0, 11, 52855, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'ELM General Purpose Bunny Large - On Action 10 (Reset): Cast \'Cosmetic - Low Poly Fire (with Sound)\''), +(-100335, 0, 2, 0, 8, 0, 100, 512, 52816, 0, 0, 0, 0, 0, 28, 52855, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'ELM General Purpose Bunny Large - On Spellhit \'Rampage: Akali Chain Anchor On Disturb\': Remove Aura \'Cosmetic - Low Poly Fire (with Sound)\''), +-- Left Rear Fire GUID -100334 +(-100334, 0, 0, 0, 11, 0, 100, 0, 0, 0, 0, 0, 0, 0, 11, 52855, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'ELM General Purpose Bunny Large - On Spawn: Cast \'Cosmetic - Low Poly Fire (with Sound)\''), +(-100334, 0, 1, 0, 72, 0, 100, 0, 10, 0, 0, 0, 0, 0, 11, 52855, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'ELM General Purpose Bunny Large - On Action 10 (Reset): Cast \'Cosmetic - Low Poly Fire (with Sound)\''), +(-100334, 0, 2, 0, 8, 0, 100, 512, 52816, 0, 0, 0, 0, 0, 28, 52855, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'ELM General Purpose Bunny Large - On Spellhit \'Rampage: Akali Chain Anchor On Disturb\': Remove Aura \'Cosmetic - Low Poly Fire (with Sound)\''); + +-- Fix comment on Akali Reset Script relay action +UPDATE `smart_scripts` SET `comment` = 'Akali - Reset Script - Do Action ID 10: Relay Reset to All Creatures in 100yd' WHERE (`source_type` = 9 AND `entryorguid` = 2895202 AND `id` = 2); diff --git a/src/server/apps/worldserver/worldserver.conf.dist b/src/server/apps/worldserver/worldserver.conf.dist index 8be788fe1..29d6253d6 100644 --- a/src/server/apps/worldserver/worldserver.conf.dist +++ b/src/server/apps/worldserver/worldserver.conf.dist @@ -621,6 +621,15 @@ Allow.IP.Based.Action.Logging = 0 LogSpamReports = 1 +# +# ChatLog.Enable +# Description: Enable or disable chat logging (chat_log.cpp). +# Default: 0 - (Disabled) +# 1 - (Enabled) +# + +ChatLog.Enable = 0 + # # Appender config values: Given an appender "name" # Appender.name @@ -2608,6 +2617,14 @@ DurabilityLoss.InPvP = 0 DurabilityLoss.OnDeath = 10 +# +# DurabilityLoss.OnSpiritResurrect +# Description: Durability loss percentage on spirit healer resurrection. Does not effect PvP +# spirit healers. +# Default: 25 + +DurabilityLoss.OnSpiritResurrect = 25 + # # DurabilityLossChance.Damage # Description: Chance to lose durability on one equipped item from damage. diff --git a/src/server/database/Database/Implementation/CharacterDatabase.cpp b/src/server/database/Database/Implementation/CharacterDatabase.cpp index 84f0a775d..f376e1ee3 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.cpp +++ b/src/server/database/Database/Implementation/CharacterDatabase.cpp @@ -403,10 +403,11 @@ void CharacterDatabaseConnection::DoPrepareStatements() PrepareStatement(CHAR_UPD_CHARACTER_POSITION, "UPDATE characters SET position_x = ?, position_y = ?, position_z = ?, orientation = ?, map = ?, zone = ?, trans_x = 0, trans_y = 0, trans_z = 0, transguid = 0, taxi_path = '', cinematic = 1 WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_CHARACTER_AURA_FROZEN, "SELECT characters.name FROM characters LEFT JOIN character_aura ON (characters.guid = character_aura.guid) WHERE character_aura.spell = 9454", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_CHARACTER_ONLINE, "SELECT name, account, map, zone FROM characters WHERE online > 0", CONNECTION_SYNCH); - PrepareStatement(CHAR_SEL_CHAR_DEL_INFO_BY_GUID, "SELECT guid, deleteInfos_Name, deleteInfos_Account, deleteDate FROM characters WHERE deleteDate IS NOT NULL AND guid = ?", CONNECTION_SYNCH); - PrepareStatement(CHAR_SEL_CHAR_DEL_INFO_BY_NAME, "SELECT guid, deleteInfos_Name, deleteInfos_Account, deleteDate FROM characters WHERE deleteDate IS NOT NULL AND deleteInfos_Name LIKE CONCAT('%%', ?, '%%')", CONNECTION_SYNCH); - PrepareStatement(CHAR_SEL_CHAR_DEL_INFO, "SELECT guid, deleteInfos_Name, deleteInfos_Account, deleteDate FROM characters WHERE deleteDate IS NOT NULL", CONNECTION_SYNCH); - PrepareStatement(CHAR_SEL_CHARS_BY_ACCOUNT_ID, "SELECT guid, class, race FROM characters WHERE account = ?", CONNECTION_SYNCH); + PrepareStatement(CHAR_SEL_CHAR_DEL_INFO_BY_GUID, "SELECT guid, deleteInfos_Name, deleteInfos_Account, deleteDate, level FROM characters WHERE deleteDate IS NOT NULL AND guid = ?", CONNECTION_SYNCH); + PrepareStatement(CHAR_SEL_CHAR_DEL_INFO_BY_NAME, "SELECT guid, deleteInfos_Name, deleteInfos_Account, deleteDate, level FROM characters WHERE deleteDate IS NOT NULL AND deleteInfos_Name LIKE CONCAT('%%', ?, '%%') ORDER BY deleteDate DESC", CONNECTION_SYNCH); + PrepareStatement(CHAR_SEL_CHAR_DEL_INFO_BY_NAME_LIMIT, "SELECT guid, deleteInfos_Name, deleteInfos_Account, deleteDate, level FROM characters WHERE deleteDate IS NOT NULL AND deleteInfos_Name LIKE CONCAT('%%', ?, '%%') ORDER BY deleteDate DESC LIMIT 51", CONNECTION_SYNCH); + PrepareStatement(CHAR_SEL_CHAR_DEL_INFO, "SELECT guid, deleteInfos_Name, deleteInfos_Account, deleteDate, level FROM characters WHERE deleteDate IS NOT NULL ORDER BY deleteDate DESC LIMIT 51", CONNECTION_SYNCH); + PrepareStatement(CHAR_SEL_CHARS_BY_ACCOUNT_ID, "SELECT guid FROM characters WHERE account = ?", CONNECTION_SYNCH); PrepareStatement(CHAR_SEL_CHAR_PINFO, "SELECT totaltime, level, money, account, race, class, map, zone, gender, health, playerFlags FROM characters WHERE guid = ?", CONNECTION_SYNCH); PrepareStatement(CHAR_SEL_PINFO_BANS, "SELECT unbandate, bandate = unbandate, bannedby, banreason FROM character_banned WHERE guid = ? AND active ORDER BY bandate ASC LIMIT 1", CONNECTION_SYNCH); PrepareStatement(CHAR_SEL_PINFO_MAILS, "SELECT SUM(CASE WHEN (checked & 1) THEN 1 ELSE 0 END) AS 'readmail', COUNT(*) AS 'totalmail' FROM mail WHERE `receiver` = ?", CONNECTION_SYNCH); diff --git a/src/server/database/Database/Implementation/CharacterDatabase.h b/src/server/database/Database/Implementation/CharacterDatabase.h index f2472299f..db8ed0bed 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.h +++ b/src/server/database/Database/Implementation/CharacterDatabase.h @@ -333,6 +333,7 @@ enum CharacterDatabaseStatements : uint32 CHAR_SEL_CHAR_DEL_INFO_BY_GUID, CHAR_SEL_CHAR_DEL_INFO_BY_NAME, + CHAR_SEL_CHAR_DEL_INFO_BY_NAME_LIMIT, CHAR_SEL_CHAR_DEL_INFO, CHAR_SEL_CHARS_BY_ACCOUNT_ID, diff --git a/src/server/game/Accounts/RBAC.h b/src/server/game/Accounts/RBAC.h index de2419a2f..d926ea791 100644 --- a/src/server/game/Accounts/RBAC.h +++ b/src/server/game/Accounts/RBAC.h @@ -300,7 +300,6 @@ enum RBACPermissions RBAC_PERM_COMMAND_GMNOTIFY = 468, RBAC_PERM_COMMAND_NAMEANNOUNCE = 469, RBAC_PERM_COMMAND_NOTIFY = 470, - RBAC_PERM_COMMAND_WHISPERS = 471, RBAC_PERM_COMMAND_GROUP = 472, RBAC_PERM_COMMAND_GROUP_LEADER = 473, RBAC_PERM_COMMAND_GROUP_DISBAND = 474, @@ -678,6 +677,10 @@ enum RBACPermissions RBAC_PERM_COMMAND_BF_QUEUE = 913, RBAC_PERM_COMMAND_PET_LIST = 914, RBAC_PERM_COMMAND_PET_DELETE = 915, + RBAC_PERM_COMMAND_RESPAWN_CREATURE_GUID = 916, + RBAC_PERM_COMMAND_RESPAWN_GAMEOBJECT_GUID = 917, + RBAC_PERM_COMMAND_RESPAWN_CREATURE_ENTRY = 918, + RBAC_PERM_COMMAND_RESPAWN_GAMEOBJECT_ENTRY = 919, // custom permissions 1000+ RBAC_PERM_MAX }; diff --git a/src/server/game/Handlers/NPCHandler.cpp b/src/server/game/Handlers/NPCHandler.cpp index 57d560807..d2c0383cc 100644 --- a/src/server/game/Handlers/NPCHandler.cpp +++ b/src/server/game/Handlers/NPCHandler.cpp @@ -262,7 +262,9 @@ void WorldSession::SendSpiritResurrect() { _player->ResurrectPlayer(0.5f, true); - _player->DurabilityLossAll(0.25f, true); + float durabilityLossOnSpiritResurrect = sWorld->getRate(RATE_DURABILITY_LOSS_ON_SPIRIT_RESURRECT) / 100.0f; + if (durabilityLossOnSpiritResurrect) + _player->DurabilityLossAll(durabilityLossOnSpiritResurrect, true); // get corpse nearest graveyard GraveyardStruct const* corpseGrave = nullptr; diff --git a/src/server/game/Maps/Map.cpp b/src/server/game/Maps/Map.cpp index b4ff04a36..e18b0d099 100644 --- a/src/server/game/Maps/Map.cpp +++ b/src/server/game/Maps/Map.cpp @@ -2724,12 +2724,18 @@ void Map::ProcessRespawns() void Map::ProcessCreatureRespawn(ObjectGuid::LowType spawnId) { - // Pool members are handled entirely by PoolMgr - if (uint32 poolId = sPoolMgr->IsPartOfAPool(spawnId)) + // Pool members in non-instanced maps are handled entirely by PoolMgr. + // In instanced maps the pool system operates globally and Spawn1Object is + // a no-op for instanceable maps, so fall through to the normal per-instance + // respawn logic instead. + if (!Instanceable()) { - sPoolMgr->UpdatePool(poolId, spawnId); - RemoveCreatureRespawnTime(spawnId); - return; + if (uint32 poolId = sPoolMgr->IsPartOfAPool(spawnId)) + { + sPoolMgr->UpdatePool(poolId, spawnId); + RemoveCreatureRespawnTime(spawnId); + return; + } } CreatureData const* data = sObjectMgr->GetCreatureData(spawnId); @@ -2785,12 +2791,16 @@ void Map::ProcessCreatureRespawn(ObjectGuid::LowType spawnId) void Map::ProcessGameObjectRespawn(ObjectGuid::LowType spawnId) { - // Pool members are handled entirely by PoolMgr - if (uint32 poolId = sPoolMgr->IsPartOfAPool(spawnId)) + // Same rationale as ProcessCreatureRespawn: pool management via PoolMgr is + // only meaningful for non-instanced maps where Spawn1Object actually spawns. + if (!Instanceable()) { - sPoolMgr->UpdatePool(poolId, spawnId); - RemoveGORespawnTime(spawnId); - return; + if (uint32 poolId = sPoolMgr->IsPartOfAPool(spawnId)) + { + sPoolMgr->UpdatePool(poolId, spawnId); + RemoveGORespawnTime(spawnId); + return; + } } GameObjectData const* data = sObjectMgr->GetGameObjectData(spawnId); diff --git a/src/server/game/Miscellaneous/Language.h b/src/server/game/Miscellaneous/Language.h index 9ab22993a..c4ac6dbdb 100644 --- a/src/server/game/Miscellaneous/Language.h +++ b/src/server/game/Miscellaneous/Language.h @@ -1487,6 +1487,27 @@ enum AcoreStrings // Pet delete command LANG_PET_DELETE_NOT_FOUND = 35438, - LANG_PET_DELETE_SUCCESS = 35439 + LANG_PET_DELETE_SUCCESS = 35439, + + LANG_CHARACTER_DELETED_LIST_LIMIT = 35440, + + // Respawn creature/gameobject by spawn GUID commands + LANG_RESPAWN_GUID_CREATURE_NOT_FOUND = 35441, + LANG_RESPAWN_GUID_MAP_NOT_LOADED = 35442, + LANG_RESPAWN_GUID_CREATURE_ALIVE = 35443, + LANG_RESPAWN_GUID_CREATURE_QUEUED = 35444, + LANG_RESPAWN_GUID_GAMEOBJECT_ACTIVE = 35445, + LANG_RESPAWN_GUID_GAMEOBJECT_QUEUED = 35446, + // 35447 is reserved for LANG_LIST_RESPAWNS_NO_MAP (list respawns command, defined at the end of this enum) + LANG_RESPAWN_GUID_GAMEOBJECT_NOT_FOUND = 35448, + + // Respawn creature/gameobject by entry commands + LANG_RESPAWN_ENTRY_CREATURE_NOT_FOUND = 35449, + LANG_RESPAWN_ENTRY_GAMEOBJECT_NOT_FOUND = 35450, + LANG_RESPAWN_ENTRY_CREATURE_QUEUED = 35451, + LANG_RESPAWN_ENTRY_GAMEOBJECT_QUEUED = 35452, + + // List respawns console support + LANG_LIST_RESPAWNS_NO_MAP = 35447 }; #endif diff --git a/src/server/game/World/WorldConfig.cpp b/src/server/game/World/WorldConfig.cpp index b9a9fb473..d96915e15 100644 --- a/src/server/game/World/WorldConfig.cpp +++ b/src/server/game/World/WorldConfig.cpp @@ -138,6 +138,8 @@ void WorldConfig::BuildConfigCache() SetConfigValue(RATE_DURABILITY_LOSS_ON_DEATH, "DurabilityLoss.OnDeath", 10.0f, ConfigValueCache::Reloadable::Yes, [](float const& value) { return value >= 0.0f && value <= 100.0f; }, ">= 0 && <= 100"); + SetConfigValue(RATE_DURABILITY_LOSS_ON_SPIRIT_RESURRECT, "DurabilityLoss.OnSpiritResurrect", 25.0f, ConfigValueCache::Reloadable::Yes, [](float const& value) { return value >= 0.0f && value <= 100.0f; }, ">= 0 && <= 100"); + SetConfigValue(RATE_DURABILITY_LOSS_DAMAGE, "DurabilityLossChance.Damage", 0.5f, ConfigValueCache::Reloadable::Yes, [](float const& value) { return value >= 0.0f; }, ">= 0"); SetConfigValue(RATE_DURABILITY_LOSS_ABSORB, "DurabilityLossChance.Absorb", 0.5f, ConfigValueCache::Reloadable::Yes, [](float const& value) { return value >= 0.0f; }, ">= 0"); SetConfigValue(RATE_DURABILITY_LOSS_PARRY, "DurabilityLossChance.Parry", 0.05f, ConfigValueCache::Reloadable::Yes, [](float const& value) { return value >= 0.0f; }, ">= 0"); @@ -602,6 +604,8 @@ void WorldConfig::BuildConfigCache() SetConfigValue(CONFIG_LOGSPAMREPORTS, "LogSpamReports", true); + SetConfigValue(CONFIG_CHATLOG_ENABLED, "ChatLog.Enable", false); + // Whether to use LoS from game objects SetConfigValue(CONFIG_CHECK_GOBJECT_LOS, "CheckGameObjectLoS", true); diff --git a/src/server/game/World/WorldConfig.h b/src/server/game/World/WorldConfig.h index 20c5fa6e8..c573fc287 100644 --- a/src/server/game/World/WorldConfig.h +++ b/src/server/game/World/WorldConfig.h @@ -480,6 +480,7 @@ enum ServerConfigs RATE_CORPSE_DECAY_LOOTED, RATE_INSTANCE_RESET_TIME, RATE_DURABILITY_LOSS_ON_DEATH, + RATE_DURABILITY_LOSS_ON_SPIRIT_RESURRECT, RATE_DURABILITY_LOSS_DAMAGE, RATE_DURABILITY_LOSS_PARRY, RATE_DURABILITY_LOSS_ABSORB, @@ -491,6 +492,7 @@ enum ServerConfigs CONFIG_NEW_CHAR_STRING, CONFIG_VALIDATE_SKILL_LEARNED_BY_SPELLS, CONFIG_ACHIEVEMENT_REALM_FIRST_KILL_WINDOW, + CONFIG_CHATLOG_ENABLED, MAX_NUM_SERVER_CONFIGS }; diff --git a/src/server/scripts/Commands/cs_character.cpp b/src/server/scripts/Commands/cs_character.cpp index 464b70512..df169b7d1 100644 --- a/src/server/scripts/Commands/cs_character.cpp +++ b/src/server/scripts/Commands/cs_character.cpp @@ -95,18 +95,22 @@ public: uint32 accountId; ///< the account id std::string accountName; ///< the account name time_t deleteDate; ///< the date at which the character has been deleted + uint8 level; ///< the character level at the time of deletion }; typedef std::list DeletedInfoList; + static constexpr std::size_t MAX_DELETED_CHAR_RESULTS = 50; + /** * Collects all GUIDs (and related info) from deleted characters which are still in the database. * * @param foundList a reference to an std::list which will be filled with info data - * @param searchString the search string which either contains a player GUID or a part fo the character-name + * @param searchString the search string which either contains a player GUID or a part of the character-name + * @param limitResults if true, caps results at MAX_DELETED_CHAR_RESULTS + 1 using a DB-level LIMIT * @return returns false if there was a problem while selecting the characters (e.g. player name not normalizeable) */ - static bool GetDeletedCharacterInfoList(DeletedInfoList& foundList, std::string searchString) + static bool GetDeletedCharacterInfoList(DeletedInfoList& foundList, std::string searchString, bool limitResults = false) { PreparedQueryResult result; CharacterDatabasePreparedStatement* stmt = nullptr; @@ -125,7 +129,8 @@ public: if (!normalizePlayerName(searchString)) return false; - stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_DEL_INFO_BY_NAME); + CharacterDatabaseStatements nameStmt = limitResults ? CHAR_SEL_CHAR_DEL_INFO_BY_NAME_LIMIT : CHAR_SEL_CHAR_DEL_INFO_BY_NAME; + stmt = CharacterDatabase.GetPreparedStatement(nameStmt); stmt->SetData(0, searchString); result = CharacterDatabase.Query(stmt); } @@ -151,6 +156,7 @@ public: // account name will be empty for nonexisting account AccountMgr::GetName(info.accountId, info.accountName); info.deleteDate = time_t(fields[3].Get()); + info.level = fields[4].Get(); foundList.push_back(info); } while (result->NextRow()); } @@ -183,11 +189,13 @@ public: if (!handler->GetSession()) handler->PSendSysMessage(LANG_CHARACTER_DELETED_LIST_LINE_CONSOLE, - itr->lowGuid, itr->name, itr->accountName.empty() ? "" : itr->accountName, + itr->lowGuid, itr->name, uint32(itr->level), + itr->accountName.empty() ? "" : itr->accountName, itr->accountId, dateStr); else handler->PSendSysMessage(LANG_CHARACTER_DELETED_LIST_LINE_CHAT, - itr->lowGuid, itr->name, itr->accountName.empty() ? "" : itr->accountName, + itr->lowGuid, itr->name, uint32(itr->level), + itr->accountName.empty() ? "" : itr->accountName, itr->accountId, dateStr); } @@ -590,7 +598,7 @@ public: if (needleStr) needle.assign(*needleStr); DeletedInfoList foundList; - if (!GetDeletedCharacterInfoList(foundList, needle)) + if (!GetDeletedCharacterInfoList(foundList, needle, true)) return false; // if no characters have been found, output a warning @@ -600,8 +608,15 @@ public: return false; } + bool truncated = foundList.size() > MAX_DELETED_CHAR_RESULTS; + if (truncated) + foundList.resize(MAX_DELETED_CHAR_RESULTS); + HandleCharacterDeletedListHelper(foundList, handler); + if (truncated) + handler->SendSysMessage(LANG_CHARACTER_DELETED_LIST_LIMIT); + return true; } diff --git a/src/server/scripts/Commands/cs_list.cpp b/src/server/scripts/Commands/cs_list.cpp index c098a7e30..ecf82319c 100644 --- a/src/server/scripts/Commands/cs_list.cpp +++ b/src/server/scripts/Commands/cs_list.cpp @@ -51,7 +51,7 @@ public: { "item", HandleListItemCommand, rbac::RBAC_PERM_COMMAND_LIST_ITEM, Console::Yes }, { "object", HandleListObjectCommand, rbac::RBAC_PERM_COMMAND_LIST_OBJECT, Console::Yes }, { "auras", listAurasCommandTable }, - { "respawns", HandleListRespawnsCommand, rbac::RBAC_PERM_COMMAND_LIST_CREATURE, Console::No }, + { "respawns", HandleListRespawnsCommand, rbac::RBAC_PERM_COMMAND_LIST_RESPAWNS, Console::Yes }, }; static ChatCommandTable commandTable = { @@ -521,13 +521,35 @@ public: return true; } - static bool HandleListRespawnsCommand(ChatHandler* handler) + static bool HandleListRespawnsCommand(ChatHandler* handler, Optional firstArg, Optional secondArg, Optional thirdArg) { - Player* player = handler->GetSession()->GetPlayer(); - if (!player) - return false; + Map* map = nullptr; + Optional entryFilter; + + if (handler->GetSession()) + { + // In-game: first arg = entryId (optional), use player's current map + map = handler->GetSession()->GetPlayer()->GetMap(); + entryFilter = firstArg; + } + else + { + // Console: first arg = mapId (required), second = instanceId, third = entryId + if (!firstArg) + { + handler->SendSysMessage(LANG_LIST_RESPAWNS_NO_MAP); + return false; + } + map = sMapMgr->FindMap(*firstArg, secondArg.value_or(0)); + entryFilter = thirdArg; + } + + if (!map) + { + handler->PSendSysMessage(LANG_RESPAWN_GUID_MAP_NOT_LOADED, firstArg.value_or(0)); + return false; + } - Map* map = player->GetMap(); uint32 count = 0; time_t now = GameTime::GetGameTime().count(); @@ -535,7 +557,7 @@ public: for (auto const& pair : map->GetCreatureRespawnTimes()) { CreatureData const* data = sObjectMgr->GetCreatureData(pair.first); - if (!data) + if (!data || (entryFilter && data->id1 != *entryFilter)) continue; CreatureTemplate const* cTemplate = sObjectMgr->GetCreatureTemplate(data->id1); @@ -555,7 +577,7 @@ public: for (auto const& pair : map->GetGORespawnTimes()) { GameObjectData const* data = sObjectMgr->GetGameObjectData(pair.first); - if (!data) + if (!data || (entryFilter && data->id != *entryFilter)) continue; GameObjectTemplate const* goTemplate = sObjectMgr->GetGameObjectTemplate(data->id); diff --git a/src/server/scripts/Commands/cs_message.cpp b/src/server/scripts/Commands/cs_message.cpp index d03d72e20..fd7da3310 100644 --- a/src/server/scripts/Commands/cs_message.cpp +++ b/src/server/scripts/Commands/cs_message.cpp @@ -45,7 +45,7 @@ public: { "gmannounce", HandleGMAnnounceCommand, rbac::RBAC_PERM_COMMAND_GMANNOUNCE, Console::Yes }, { "notify", HandleNotifyCommand, rbac::RBAC_PERM_COMMAND_NOTIFY, Console::Yes }, { "gmnotify", HandleGMNotifyCommand, rbac::RBAC_PERM_COMMAND_GMNOTIFY, Console::Yes }, - { "whispers", HandleWhispersCommand, rbac::RBAC_PERM_COMMAND_WHISPERS, Console::No }, + { "whispers", HandleWhispersCommand, rbac::RBAC_PERM_CAN_FILTER_WHISPERS, Console::No }, }; return commandTable; } diff --git a/src/server/scripts/Commands/cs_misc.cpp b/src/server/scripts/Commands/cs_misc.cpp index 961e9a5b9..22e457506 100644 --- a/src/server/scripts/Commands/cs_misc.cpp +++ b/src/server/scripts/Commands/cs_misc.cpp @@ -24,6 +24,7 @@ #include "CommandScript.h" #include "Common.h" #include "GameGraveyard.h" +#include "GameObject.h" #include "GameTime.h" #include "GridNotifiers.h" #include "GridTerrainLoader.h" @@ -177,6 +178,10 @@ public: { "pinfo", HandlePInfoCommand, rbac::RBAC_PERM_COMMAND_PINFO, Console::Yes }, { "respawn", HandleRespawnCommand, rbac::RBAC_PERM_COMMAND_RESPAWN, Console::No }, { "respawn all", HandleRespawnAllCommand, rbac::RBAC_PERM_COMMAND_RESPAWN_ALL, Console::No }, + { "respawn creature guid", HandleRespawnCreatureByGuidCommand, rbac::RBAC_PERM_COMMAND_RESPAWN_CREATURE_GUID, Console::Yes }, + { "respawn gameobject guid", HandleRespawnGameObjectByGuidCommand, rbac::RBAC_PERM_COMMAND_RESPAWN_GAMEOBJECT_GUID, Console::Yes }, + { "respawn creature entry", HandleRespawnCreatureByEntryCommand, rbac::RBAC_PERM_COMMAND_RESPAWN_CREATURE_ENTRY, Console::Yes }, + { "respawn gameobject entry", HandleRespawnGameObjectByEntryCommand, rbac::RBAC_PERM_COMMAND_RESPAWN_GAMEOBJECT_ENTRY, Console::Yes }, { "mute", HandleMuteCommand, rbac::RBAC_PERM_COMMAND_MUTE, Console::Yes }, { "mutehistory", HandleMuteInfoCommand, rbac::RBAC_PERM_COMMAND_MUTEHISTORY, Console::Yes }, { "unmute", HandleUnmuteCommand, rbac::RBAC_PERM_COMMAND_UNMUTE, Console::Yes }, @@ -2448,6 +2453,265 @@ public: return true; } + static bool HandleRespawnCreatureByGuidCommand(ChatHandler* handler, ObjectGuid::LowType spawnId) + { + CreatureData const* creData = sObjectMgr->GetCreatureData(spawnId); + if (!creData) + { + handler->SendErrorMessage(LANG_RESPAWN_GUID_CREATURE_NOT_FOUND, spawnId); + return false; + } + + Map* map = nullptr; + if (handler->GetSession()) + { + Player* player = handler->GetSession()->GetPlayer(); + if (player->GetMapId() == creData->mapid) + map = player->GetMap(); + } + if (!map) + map = sMapMgr->FindMap(creData->mapid, 0); + + if (!map) + { + handler->PSendSysMessage(LANG_RESPAWN_GUID_MAP_NOT_LOADED, creData->mapid); + return true; + } + + // First pass: check if any instance is alive + bool isAlive = false; + auto const creBounds = map->GetCreatureBySpawnIdStore().equal_range(spawnId); + for (auto itr = creBounds.first; itr != creBounds.second; ++itr) + { + if (itr->second->IsAlive()) + { + isAlive = true; + break; + } + } + + if (isAlive) + { + handler->PSendSysMessage(LANG_RESPAWN_GUID_CREATURE_ALIVE, spawnId, creData->id1); + return true; + } + + // Second pass: respawn any dead corpses in the world + for (auto itr = creBounds.first; itr != creBounds.second; ++itr) + { + if (itr->second->isDead()) + itr->second->Respawn(true); + } + // Also trigger via respawn time queue for fully-removed spawns + if (map->GetCreatureRespawnTime(spawnId) > 0) + { + time_t now = GameTime::GetGameTime().count(); + map->SaveCreatureRespawnTime(spawnId, now); + } + handler->PSendSysMessage(LANG_RESPAWN_GUID_CREATURE_QUEUED, spawnId, creData->id1); + return true; + } + + static bool HandleRespawnGameObjectByGuidCommand(ChatHandler* handler, ObjectGuid::LowType spawnId) + { + GameObjectData const* goData = sObjectMgr->GetGameObjectData(spawnId); + if (!goData) + { + handler->SendErrorMessage(LANG_RESPAWN_GUID_GAMEOBJECT_NOT_FOUND, spawnId); + return false; + } + + Map* map = nullptr; + if (handler->GetSession()) + { + Player* player = handler->GetSession()->GetPlayer(); + if (player->GetMapId() == goData->mapid) + map = player->GetMap(); + } + if (!map) + map = sMapMgr->FindMap(goData->mapid, 0); + + if (!map) + { + handler->PSendSysMessage(LANG_RESPAWN_GUID_MAP_NOT_LOADED, goData->mapid); + return true; + } + + // First pass: check if any instance is already active + bool isActive = false; + auto const goBounds = map->GetGameObjectBySpawnIdStore().equal_range(spawnId); + for (auto itr = goBounds.first; itr != goBounds.second; ++itr) + { + if (itr->second->isSpawned()) + { + isActive = true; + break; + } + } + + if (isActive) + { + handler->PSendSysMessage(LANG_RESPAWN_GUID_GAMEOBJECT_ACTIVE, spawnId, goData->id); + return true; + } + + // Second pass: respawn inactive objects in the world + for (auto itr = goBounds.first; itr != goBounds.second; ++itr) + itr->second->Respawn(); + // Also trigger via respawn time queue for fully-removed spawns + if (map->GetGORespawnTime(spawnId) > 0) + { + time_t now = GameTime::GetGameTime().count(); + map->SaveGORespawnTime(spawnId, now); + } + handler->PSendSysMessage(LANG_RESPAWN_GUID_GAMEOBJECT_QUEUED, spawnId, goData->id); + return true; + } + + static bool HandleRespawnCreatureByEntryCommand(ChatHandler* handler, uint32 entry, Optional mapIdArg, Optional instanceIdArg) + { + if (!sObjectMgr->GetCreatureTemplate(entry)) + { + handler->SendErrorMessage(LANG_RESPAWN_ENTRY_CREATURE_NOT_FOUND, entry); + return false; + } + + Map* map = nullptr; + if (handler->GetSession()) + { + // In-game: always use the player's current map + map = handler->GetSession()->GetPlayer()->GetMap(); + } + else + { + // Console: mapId required, instanceId optional + if (!mapIdArg) + { + handler->SendSysMessage(LANG_LIST_RESPAWNS_NO_MAP); + return false; + } + map = sMapMgr->FindMap(*mapIdArg, instanceIdArg.value_or(0)); + } + + if (!map) + { + handler->PSendSysMessage(LANG_RESPAWN_GUID_MAP_NOT_LOADED, mapIdArg.value_or(0)); + return false; + } + + time_t now = GameTime::GetGameTime().count(); + uint32 count = 0; + + // Phase 1: respawn dead corpses that are still tracked in the spawn-id store. + // Collect first to avoid iterator invalidation caused by Respawn(). + std::vector deadCreatures; + for (auto const& [spawnId, creature] : map->GetCreatureBySpawnIdStore()) + { + CreatureData const* data = sObjectMgr->GetCreatureData(spawnId); + if (!data || data->id1 != entry) + continue; + if (creature->isDead()) + deadCreatures.push_back(creature); + } + for (Creature* creature : deadCreatures) + { + creature->Respawn(true); + ++count; + } + + // Phase 2: set respawn time to now for fully-removed spawns, skipping pools + std::vector toRespawn; + for (auto const& [spawnId, respawnTime] : map->GetCreatureRespawnTimes()) + { + CreatureData const* data = sObjectMgr->GetCreatureData(spawnId); + if (!data || data->id1 != entry) + continue; + if (sPoolMgr->IsPartOfAPool(spawnId)) + continue; + toRespawn.push_back(spawnId); + } + for (ObjectGuid::LowType spawnId : toRespawn) + { + map->SaveCreatureRespawnTime(spawnId, now); + ++count; + } + + handler->PSendSysMessage(LANG_RESPAWN_ENTRY_CREATURE_QUEUED, count, entry); + return true; + } + + static bool HandleRespawnGameObjectByEntryCommand(ChatHandler* handler, uint32 entry, Optional mapIdArg, Optional instanceIdArg) + { + if (!sObjectMgr->GetGameObjectTemplate(entry)) + { + handler->SendErrorMessage(LANG_RESPAWN_ENTRY_GAMEOBJECT_NOT_FOUND, entry); + return false; + } + + Map* map = nullptr; + if (handler->GetSession()) + { + // In-game: always use the player's current map + map = handler->GetSession()->GetPlayer()->GetMap(); + } + else + { + // Console: mapId required, instanceId optional + if (!mapIdArg) + { + handler->SendSysMessage(LANG_LIST_RESPAWNS_NO_MAP); + return false; + } + map = sMapMgr->FindMap(*mapIdArg, instanceIdArg.value_or(0)); + } + + if (!map) + { + handler->PSendSysMessage(LANG_RESPAWN_GUID_MAP_NOT_LOADED, mapIdArg.value_or(0)); + return false; + } + + time_t now = GameTime::GetGameTime().count(); + uint32 count = 0; + + // Phase 1: respawn inactive objects that are still tracked in the spawn-id store. + // Collect first to avoid iterator invalidation caused by Respawn(). + std::vector inactiveGOs; + for (auto const& [spawnId, go] : map->GetGameObjectBySpawnIdStore()) + { + GameObjectData const* data = sObjectMgr->GetGameObjectData(spawnId); + if (!data || data->id != entry) + continue; + if (!go->isSpawned()) + inactiveGOs.push_back(go); + } + for (GameObject* go : inactiveGOs) + { + go->Respawn(); + ++count; + } + + // Phase 2: set respawn time to now for fully-removed spawns, skipping pools + std::vector toRespawn; + for (auto const& [spawnId, respawnTime] : map->GetGORespawnTimes()) + { + GameObjectData const* data = sObjectMgr->GetGameObjectData(spawnId); + if (!data || data->id != entry) + continue; + if (sPoolMgr->IsPartOfAPool(spawnId)) + continue; + toRespawn.push_back(spawnId); + } + for (ObjectGuid::LowType spawnId : toRespawn) + { + map->SaveGORespawnTime(spawnId, now); + ++count; + } + + handler->PSendSysMessage(LANG_RESPAWN_ENTRY_GAMEOBJECT_QUEUED, count, entry); + return true; + } + static bool HandleRespawnCommand(ChatHandler* handler) { Player* player = handler->GetSession()->GetPlayer(); diff --git a/src/server/scripts/EasternKingdoms/Karazhan/boss_moroes.cpp b/src/server/scripts/EasternKingdoms/Karazhan/boss_moroes.cpp index dc300de98..93c6bcab7 100644 --- a/src/server/scripts/EasternKingdoms/Karazhan/boss_moroes.cpp +++ b/src/server/scripts/EasternKingdoms/Karazhan/boss_moroes.cpp @@ -77,18 +77,6 @@ struct boss_moroes : public BossAI _activeGuests = 0; } - void InitializeAI() override - { - BossAI::InitializeAI(); - InitializeGuests(); - } - - void JustReachedHome() override - { - BossAI::JustReachedHome(); - InitializeGuests(); - } - void InitializeGuests() { if (!me->IsAlive()) @@ -96,16 +84,18 @@ struct boss_moroes : public BossAI if (_activeGuests == 0) { - _activeGuests |= 0x3F; + _activeGuests = 0x3F; uint8 rand1 = RAND(0x01, 0x02, 0x04); uint8 rand2 = RAND(0x08, 0x10, 0x20); _activeGuests &= ~(rand1 | rand2); } - for (uint8 i = 0; i < MAX_GUEST_COUNT; ++i) + + uint8 positionIndex = 0; + for (uint8 i = 0; i < MAX_GUEST_COUNT && positionIndex < ACTIVE_GUEST_COUNT; ++i) { if ((1 << i) & _activeGuests) { - me->SummonCreature(GuestEntries[i], GuestsPosition[summons.size()], TEMPSUMMON_MANUAL_DESPAWN); + me->SummonCreature(GuestEntries[i], GuestsPosition[positionIndex++], TEMPSUMMON_MANUAL_DESPAWN); } } @@ -130,6 +120,8 @@ struct boss_moroes : public BossAI _recentlySpoken = false; _vanished = false; + InitializeGuests(); + ScheduleHealthCheckEvent(30, [&] { DoCastSelf(SPELL_FRENZY, true); }); @@ -203,6 +195,9 @@ struct boss_moroes : public BossAI } } + if (guestList.empty()) + return nullptr; + return Acore::Containers::SelectRandomContainerElement(guestList); } @@ -211,12 +206,12 @@ struct boss_moroes : public BossAI bool guestsInRoom = true; summons.DoForAllSummons([&guestsInRoom](WorldObject* summon) { - if ((summon->ToCreature()->GetPositionX()) < -11028.f || (summon->ToCreature()->GetPositionY()) < -1955.f) //boundaries of the two doors + Creature* creature = summon->ToCreature(); + if (creature->IsAlive() && + ((creature->GetPositionX() < -11028.f) || (creature->GetPositionY() < -1955.f))) // boundaries of the two doors { guestsInRoom = false; - return false; } - return true; }); return guestsInRoom; @@ -229,10 +224,6 @@ struct boss_moroes : public BossAI if (!CheckGuestsInRoom()) { EnterEvadeMode(); - summons.DoForAllSummons([](WorldObject* summon) - { - summon->ToCreature()->DespawnOnEvade(5s); - }); return; } diff --git a/src/server/scripts/Kalimdor/RazorfenDowns/razorfen_downs.cpp b/src/server/scripts/Kalimdor/RazorfenDowns/razorfen_downs.cpp index 3199eea09..d8de809a4 100644 --- a/src/server/scripts/Kalimdor/RazorfenDowns/razorfen_downs.cpp +++ b/src/server/scripts/Kalimdor/RazorfenDowns/razorfen_downs.cpp @@ -204,7 +204,9 @@ public: case EVENT_COMPLETE: { DoCast(me, SPELL_IDOM_ROOM_CAMERA_SHAKE); - me->SummonGameObject(GO_BELNISTRASZS_BRAZIER, 2577.196f, 947.0781f, 53.16757f, 2.356195f, 0, 0, 0.9238796f, 0.3826832f, 3600); + // Summon via the map so the brazier is not tied to Belnistrasz's + // m_gameObj list and survives his despawn below. + me->GetMap()->SummonGameObject(GO_BELNISTRASZS_BRAZIER, 2577.196f, 947.0781f, 53.16757f, 2.356195f, 0, 0, 0.9238796f, 0.3826832f, 3600); std::list ClusterList; Acore::AllWorldObjectsInRange objects(me, 50.0f); Acore::WorldObjectListSearcher searcher(me, ClusterList, objects); @@ -223,7 +225,7 @@ public: } } instance->SetData(GO_BELNISTRASZS_BRAZIER, DONE); - me->DespawnOrUnsummon(); + me->DespawnOrUnsummon(5s); break; } case EVENT_FIREBALL: diff --git a/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/halls_of_reflection.h b/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/halls_of_reflection.h index 363d1d17e..dc6270961 100644 --- a/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/halls_of_reflection.h +++ b/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/halls_of_reflection.h @@ -36,8 +36,8 @@ enum Data // Non-boss data (SetData/GetData only) DATA_INTRO, DATA_FROSTSWORN_GENERAL, + DATA_BATTERED_HILT = 6, //For SAI DB DATA_LK_INTRO, - DATA_BATTERED_HILT, DATA_WAVE_NUMBER, DATA_LK_BATTLE, DATA_SHIP_CAPTAIN, diff --git a/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/instance_halls_of_reflection.cpp b/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/instance_halls_of_reflection.cpp index c04e21ee3..9d235c7b9 100644 --- a/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/instance_halls_of_reflection.cpp +++ b/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/instance_halls_of_reflection.cpp @@ -60,7 +60,6 @@ public: case 5: _owner.SetFacingTo(2.82f); _owner.SetStandState(UNIT_STAND_STATE_KNEEL); - _owner.m_Events.AddEventAtOffset(new UtherBatteredHiltEvent(_owner, 6), 3s); break; case 6: if (InstanceScript* inst = _owner.GetInstanceScript()) diff --git a/src/server/scripts/Northrend/Gundrak/boss_gal_darah.cpp b/src/server/scripts/Northrend/Gundrak/boss_gal_darah.cpp index 611fb8aa9..f44da0ccb 100644 --- a/src/server/scripts/Northrend/Gundrak/boss_gal_darah.cpp +++ b/src/server/scripts/Northrend/Gundrak/boss_gal_darah.cpp @@ -155,6 +155,7 @@ struct boss_gal_darah : public BossAI { Talk(SAY_DEATH); BossAI::JustDied(killer); + instance->DoRemoveAurasDueToSpellOnPlayers(SPELL_PUNCTURE); } void KilledUnit(Unit*) override diff --git a/src/server/scripts/Northrend/IcecrownCitadel/boss_professor_putricide.cpp b/src/server/scripts/Northrend/IcecrownCitadel/boss_professor_putricide.cpp index 5c5354775..736bf6c2e 100644 --- a/src/server/scripts/Northrend/IcecrownCitadel/boss_professor_putricide.cpp +++ b/src/server/scripts/Northrend/IcecrownCitadel/boss_professor_putricide.cpp @@ -133,6 +133,7 @@ enum Events EVENT_UNBOUND_PLAGUE, EVENT_MALLEABLE_GOO, EVENT_CHOKING_GAS_BOMB, + EVENT_MUTATED_PLAGUE, }; #define EVENT_GROUP_ABILITIES 1 @@ -653,6 +654,11 @@ public: me->CastSpell(me, SPELL_CHOKING_GAS_BOMB, false); events.ScheduleEvent(EVENT_CHOKING_GAS_BOMB, 35s, 40s, EVENT_GROUP_ABILITIES); break; + case EVENT_MUTATED_PLAGUE: + if (Unit* target = me->GetVictim()) + me->CastSpell(target, SPELL_MUTATED_PLAGUE, false); + events.ScheduleEvent(EVENT_MUTATED_PLAGUE, 10s, EVENT_GROUP_ABILITIES); + break; default: break; } @@ -721,6 +727,7 @@ public: case 2: _phase = 3; events.CancelEvent(EVENT_UNSTABLE_EXPERIMENT); + events.ScheduleEvent(EVENT_MUTATED_PLAGUE, 10s, EVENT_GROUP_ABILITIES); break; default: break; diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_kologarn.cpp b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_kologarn.cpp index 804584415..6a31deef6 100644 --- a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_kologarn.cpp +++ b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_kologarn.cpp @@ -441,7 +441,6 @@ struct boss_kologarn : public BossAI { events.ScheduleEvent(EVENT_FOCUSED_EYEBEAM, 20s); me->CastSpell(me, SPELL_FOCUSED_EYEBEAM_SUMMON, false); - Talk(EMOTE_EYES); return; } case EVENT_RESTORE_ARM_LEFT: @@ -605,6 +604,7 @@ struct boss_kologarn_eyebeam : public ScriptedAI if (Creature* cr = _instance->GetCreature(BOSS_KOLOGARN)) { me->CastSpell(cr, me->GetEntry() == NPC_EYE_LEFT ? SPELL_FOCUSED_EYEBEAM_LEFT : SPELL_FOCUSED_EYEBEAM_RIGHT, true); + cr->AI()->Talk(EMOTE_EYES, player); } } } diff --git a/src/server/scripts/Northrend/UtgardeKeep/UtgardePinnacle/boss_skadi.cpp b/src/server/scripts/Northrend/UtgardeKeep/UtgardePinnacle/boss_skadi.cpp index eab8ec6d7..5ab1d6647 100644 --- a/src/server/scripts/Northrend/UtgardeKeep/UtgardePinnacle/boss_skadi.cpp +++ b/src/server/scripts/Northrend/UtgardeKeep/UtgardePinnacle/boss_skadi.cpp @@ -71,7 +71,6 @@ enum Actions ACTION_START_ENCOUNTER = 1, ACTION_DRAKE_BREATH = 2, ACTION_PHASE2 = 3, - ACTION_HARPOON_HIT = 4, }; enum CombatPhase @@ -171,8 +170,6 @@ public: _events.Reset(); _summons.DespawnAll(); _phase = PHASE_GROUND; - _harpoonHit = 0; - _loveSkadi = 0; _firstWaveSummoned = false; _encounterStarted = false; @@ -266,8 +263,6 @@ public: _events.ScheduleEvent(EVENT_SKADI_RESET_CHECK, 6s); break; case ACTION_DRAKE_BREATH: - if (_loveSkadi == 1) - ++_loveSkadi; Talk(SAY_DRAKE_BREATH); break; case ACTION_PHASE2: @@ -283,21 +278,9 @@ public: _events.ScheduleEvent(EVENT_SKADI_SPEAR, 11s); _events.ScheduleEvent(EVENT_SKADI_WHIRLWIND, 23s); break; - case ACTION_HARPOON_HIT: - ++_harpoonHit; - if (_harpoonHit == 1) - _loveSkadi = 1; - break; } } - uint32 GetData(uint32 id) const override - { - if (id == DATA_LOVE_TO_SKADI) - return _loveSkadi; - return 0; - } - void UpdateAI(uint32 diff) override { _events.Update(diff); @@ -384,8 +367,6 @@ public: EventMap _events; SummonList _summons; CombatPhase _phase; - uint8 _harpoonHit; - uint8 _loveSkadi; bool _firstWaveSummoned; bool _encounterStarted; }; @@ -415,6 +396,7 @@ public: me->SetReactState(REACT_PASSIVE); me->SetSpeedRate(MOVE_RUN, 2.5f); _flyingToSide = false; + _passFreshStart = false; } void DoAction(int32 param) override @@ -430,13 +412,6 @@ public: } } - void SpellHit(Unit* /*caster*/, SpellInfo const* spellInfo) override - { - if (spellInfo->Id == SPELL_LAUNCH_HARPOON) - if (Creature* skadi = _instance->GetCreature(DATA_SKADI_THE_RUTHLESS)) - skadi->AI()->DoAction(ACTION_HARPOON_HIT); - } - void MovementInform(uint32 type, uint32 /*id*/) override { if (type == ESCORT_MOTION_TYPE && me->movespline->Finalized()) @@ -470,6 +445,7 @@ public: FORCED_MOVEMENT_RUN); break; case EVENT_GRAUF_BREATH_START: + _passFreshStart = me->IsFullHealth(); me->GetMotionMaster()->MovePath( _lastSide == POINT_LEFT ? PATH_LEFT : PATH_RIGHT, FORCED_MOVEMENT_RUN); @@ -493,10 +469,11 @@ public: { skadi->ExitVehicle(); skadi->AI()->DoAction(ACTION_PHASE2); - - if (skadi->AI()->GetData(DATA_LOVE_TO_SKADI) == 1) - _instance->SetData(DATA_SKADI_ACHIEVEMENT, true); } + + if (_passFreshStart) + _instance->SetData(DATA_SKADI_ACHIEVEMENT, true); + me->DespawnOrUnsummon(6s); } @@ -506,6 +483,7 @@ public: SummonList _summons; uint8 _lastSide = POINT_LEFT; bool _flyingToSide = false; + bool _passFreshStart = false; }; }; diff --git a/src/server/scripts/Spells/spell_hunter.cpp b/src/server/scripts/Spells/spell_hunter.cpp index a7147d328..ece192038 100644 --- a/src/server/scripts/Spells/spell_hunter.cpp +++ b/src/server/scripts/Spells/spell_hunter.cpp @@ -313,6 +313,7 @@ class spell_hun_taming_the_beast : public AuraScript if (Creature* creature = target->ToCreature()) { creature->GetThreatMgr().ClearAllThreat(); + creature->GetCombatManager().EndAllCombat(); } } } diff --git a/src/server/scripts/World/chat_log.cpp b/src/server/scripts/World/chat_log.cpp index 3feb0c528..bc89576f0 100644 --- a/src/server/scripts/World/chat_log.cpp +++ b/src/server/scripts/World/chat_log.cpp @@ -20,6 +20,7 @@ #include "Guild.h" #include "Log.h" #include "PlayerScript.h" +#include "World.h" class ChatLogScript : public PlayerScript { @@ -38,6 +39,9 @@ public: bool OnPlayerCanUseChat(Player* player, uint32 type, uint32 lang, std::string& msg) override { + if (!sWorld->getBoolConfig(CONFIG_CHATLOG_ENABLED)) + return true; + std::string logType = ""; std::string chatType = ""; @@ -67,6 +71,9 @@ public: bool OnPlayerCanUseChat(Player* player, uint32 /*type*/, uint32 lang, std::string& msg, Player* receiver) override { + if (!sWorld->getBoolConfig(CONFIG_CHATLOG_ENABLED)) + return true; + //! NOTE: //! LANG_ADDON can only be sent by client in "PARTY", "RAID", "GUILD", "BATTLEGROUND", "WHISPER" std::string logType = (lang != LANG_ADDON) ? "chat." : "chat.addon."; @@ -80,6 +87,9 @@ public: bool OnPlayerCanUseChat(Player* player, uint32 type, uint32 lang, std::string& msg, Group* group) override { + if (!sWorld->getBoolConfig(CONFIG_CHATLOG_ENABLED)) + return true; + //! NOTE: //! LANG_ADDON can only be sent by client in "PARTY", "RAID", "GUILD", "BATTLEGROUND", "WHISPER" std::string logType = (lang != LANG_ADDON) ? "chat." : "chat.addon."; @@ -116,6 +126,9 @@ public: bool OnPlayerCanUseChat(Player* player, uint32 type, uint32 lang, std::string& msg, Guild* guild) override { + if (!sWorld->getBoolConfig(CONFIG_CHATLOG_ENABLED)) + return true; + //! NOTE: //! LANG_ADDON can only be sent by client in "PARTY", "RAID", "GUILD", "BATTLEGROUND", "WHISPER" std::string logType = (lang != LANG_ADDON) ? "chat." : "chat.addon."; @@ -141,6 +154,9 @@ public: bool OnPlayerCanUseChat(Player* player, uint32 /*type*/, uint32 /*lang*/, std::string& msg, Channel* channel) override { + if (!sWorld->getBoolConfig(CONFIG_CHATLOG_ENABLED)) + return true; + bool isSystem = channel && (channel->HasFlag(CHANNEL_FLAG_TRADE) || channel->HasFlag(CHANNEL_FLAG_GENERAL) ||