From b71ff978755956238f9f4646640b2c835622e68b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=A9=E9=B9=AD?= <18535853+PkllonG@users.noreply.github.com> Date: Sat, 9 May 2026 08:22:39 +0800 Subject: [PATCH 01/26] fix(Scripts/HoR): The correct method to fix Quel'Delar freeze (#25756) --- .../FrozenHalls/HallsOfReflection/halls_of_reflection.h | 2 +- .../HallsOfReflection/instance_halls_of_reflection.cpp | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) 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()) From e72d8e94db1a4a8ff8e8bb989dcce1b413cbca10 Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Fri, 8 May 2026 21:51:04 -0300 Subject: [PATCH 02/26] fix(Core/RBAC): use filter perm to gate .whispers command (#25771) Co-authored-by: Claude Sonnet 4.6 --- data/sql/updates/pending_db_auth/rev_63913873569817690000.sql | 2 ++ src/server/game/Accounts/RBAC.h | 1 - src/server/scripts/Commands/cs_message.cpp | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 data/sql/updates/pending_db_auth/rev_63913873569817690000.sql diff --git a/data/sql/updates/pending_db_auth/rev_63913873569817690000.sql b/data/sql/updates/pending_db_auth/rev_63913873569817690000.sql new file mode 100644 index 000000000..98e975a35 --- /dev/null +++ b/data/sql/updates/pending_db_auth/rev_63913873569817690000.sql @@ -0,0 +1,2 @@ +DELETE FROM `rbac_linked_permissions` WHERE `id` = 198 AND `linkedId` = 471; +DELETE FROM `rbac_permissions` WHERE `id` = 471; diff --git a/src/server/game/Accounts/RBAC.h b/src/server/game/Accounts/RBAC.h index de2419a2f..1ea012c04 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, 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; } From 94a096d77871d2fc0462ce33211df78e86ea3503 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 9 May 2026 00:52:28 +0000 Subject: [PATCH 03/26] chore(DB): import pending files Referenced commit(s): e72d8e94db1a4a8ff8e8bb989dcce1b413cbca10 --- .../rev_63913873569817690000.sql => db_auth/2026_05_09_00.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_auth/rev_63913873569817690000.sql => db_auth/2026_05_09_00.sql} (74%) diff --git a/data/sql/updates/pending_db_auth/rev_63913873569817690000.sql b/data/sql/updates/db_auth/2026_05_09_00.sql similarity index 74% rename from data/sql/updates/pending_db_auth/rev_63913873569817690000.sql rename to data/sql/updates/db_auth/2026_05_09_00.sql index 98e975a35..228d620df 100644 --- a/data/sql/updates/pending_db_auth/rev_63913873569817690000.sql +++ b/data/sql/updates/db_auth/2026_05_09_00.sql @@ -1,2 +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; From 1f24863e586a60bd5f958d2f0ef77560de9e1aef Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Fri, 8 May 2026 22:35:37 -0300 Subject: [PATCH 04/26] fix(Scripts/UP): fix "My Girl Loves to Skadi All the Time" achievement (#25770) Co-authored-by: Claude Sonnet 4.6 --- .../UtgardePinnacle/boss_skadi.cpp | 36 ++++--------------- 1 file changed, 7 insertions(+), 29 deletions(-) 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; }; }; From 6c4f96de8b060ad66fe4ba265c6a030f13297bf6 Mon Sep 17 00:00:00 2001 From: sogladev Date: Sat, 9 May 2026 17:53:26 +0200 Subject: [PATCH 05/26] fix(DB/SmartAI): fix excessive amount of Flamebringers spawns (#25780) --- data/sql/updates/pending_db_world/rev_1778334994886874112.sql | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 data/sql/updates/pending_db_world/rev_1778334994886874112.sql diff --git a/data/sql/updates/pending_db_world/rev_1778334994886874112.sql b/data/sql/updates/pending_db_world/rev_1778334994886874112.sql new file mode 100644 index 000000000..a44c5deb1 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1778334994886874112.sql @@ -0,0 +1,4 @@ +-- +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\''); From 64d0b993a34d0434db5e6b2c8b47fd2070c469e6 Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Sat, 9 May 2026 12:53:47 -0300 Subject: [PATCH 06/26] fix(Scripts/RFD): make Belnistrasz's Brazier persist after his despawn (#25782) --- .../scripts/Kalimdor/RazorfenDowns/razorfen_downs.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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: From c03fdf101adccf9b556e2af2187a731956dda5ad Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 9 May 2026 15:54:33 +0000 Subject: [PATCH 07/26] chore(DB): import pending files Referenced commit(s): 6c4f96de8b060ad66fe4ba265c6a030f13297bf6 --- .../rev_1778334994886874112.sql => db_world/2026_05_09_00.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1778334994886874112.sql => db_world/2026_05_09_00.sql} (94%) diff --git a/data/sql/updates/pending_db_world/rev_1778334994886874112.sql b/data/sql/updates/db_world/2026_05_09_00.sql similarity index 94% rename from data/sql/updates/pending_db_world/rev_1778334994886874112.sql rename to data/sql/updates/db_world/2026_05_09_00.sql index a44c5deb1..427f3a246 100644 --- a/data/sql/updates/pending_db_world/rev_1778334994886874112.sql +++ b/data/sql/updates/db_world/2026_05_09_00.sql @@ -1,3 +1,4 @@ +-- 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 From 5df0282bae6988db5c962e9092bfd2d73d57e0e6 Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Sat, 9 May 2026 15:49:21 -0300 Subject: [PATCH 08/26] fix(Core/Commands): limit .character deleted list results to 50 (#25779) Co-authored-by: Claude Sonnet 4.6 Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../rev_1746741600000000000.sql | 4 ++++ .../Implementation/CharacterDatabase.cpp | 3 ++- .../Implementation/CharacterDatabase.h | 1 + src/server/game/Miscellaneous/Language.h | 4 +++- src/server/scripts/Commands/cs_character.cpp | 19 +++++++++++++++---- 5 files changed, 25 insertions(+), 6 deletions(-) create mode 100644 data/sql/updates/pending_db_world/rev_1746741600000000000.sql diff --git a/data/sql/updates/pending_db_world/rev_1746741600000000000.sql b/data/sql/updates/pending_db_world/rev_1746741600000000000.sql new file mode 100644 index 000000000..419935f1f --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1746741600000000000.sql @@ -0,0 +1,4 @@ +-- +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/src/server/database/Database/Implementation/CharacterDatabase.cpp b/src/server/database/Database/Implementation/CharacterDatabase.cpp index 31664d06b..167a0db13 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.cpp +++ b/src/server/database/Database/Implementation/CharacterDatabase.cpp @@ -404,7 +404,8 @@ void CharacterDatabaseConnection::DoPrepareStatements() 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_CHAR_DEL_INFO_BY_NAME_LIMIT, "SELECT guid, deleteInfos_Name, deleteInfos_Account, deleteDate FROM characters WHERE deleteDate IS NOT NULL AND deleteInfos_Name LIKE CONCAT('%%', ?, '%%') LIMIT 51", CONNECTION_SYNCH); + PrepareStatement(CHAR_SEL_CHAR_DEL_INFO, "SELECT guid, deleteInfos_Name, deleteInfos_Account, deleteDate FROM characters WHERE deleteDate IS NOT NULL 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); diff --git a/src/server/database/Database/Implementation/CharacterDatabase.h b/src/server/database/Database/Implementation/CharacterDatabase.h index 968bafa69..313a87dd5 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.h +++ b/src/server/database/Database/Implementation/CharacterDatabase.h @@ -332,6 +332,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/Miscellaneous/Language.h b/src/server/game/Miscellaneous/Language.h index 9ab22993a..a0d198901 100644 --- a/src/server/game/Miscellaneous/Language.h +++ b/src/server/game/Miscellaneous/Language.h @@ -1487,6 +1487,8 @@ 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 }; #endif diff --git a/src/server/scripts/Commands/cs_character.cpp b/src/server/scripts/Commands/cs_character.cpp index 464b70512..19e708394 100644 --- a/src/server/scripts/Commands/cs_character.cpp +++ b/src/server/scripts/Commands/cs_character.cpp @@ -99,14 +99,17 @@ public: 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 +128,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); } @@ -590,7 +594,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 +604,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; } From bf7b54c899a796cd256453f745eeb2af4b2efb91 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 9 May 2026 18:50:31 +0000 Subject: [PATCH 09/26] chore(DB): import pending files Referenced commit(s): 5df0282bae6988db5c962e9092bfd2d73d57e0e6 --- .../rev_1746741600000000000.sql => db_world/2026_05_09_01.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1746741600000000000.sql => db_world/2026_05_09_01.sql} (96%) diff --git a/data/sql/updates/pending_db_world/rev_1746741600000000000.sql b/data/sql/updates/db_world/2026_05_09_01.sql similarity index 96% rename from data/sql/updates/pending_db_world/rev_1746741600000000000.sql rename to data/sql/updates/db_world/2026_05_09_01.sql index 419935f1f..23fd53ee8 100644 --- a/data/sql/updates/pending_db_world/rev_1746741600000000000.sql +++ b/data/sql/updates/db_world/2026_05_09_01.sql @@ -1,3 +1,4 @@ +-- 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 From f4be7b1e2b0a15d472c34f344aa8f7a3cba9314f Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Sun, 10 May 2026 11:33:24 -0300 Subject: [PATCH 10/26] fix(DB/Quest): On Ruby Wings - prevent Antiok idle despawn pre-engage (#25785) Co-authored-by: Claude Opus 4.7 --- .../updates/pending_db_world/rev_1778350030492406400.sql | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 data/sql/updates/pending_db_world/rev_1778350030492406400.sql diff --git a/data/sql/updates/pending_db_world/rev_1778350030492406400.sql b/data/sql/updates/pending_db_world/rev_1778350030492406400.sql new file mode 100644 index 000000000..7dc4159c2 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1778350030492406400.sql @@ -0,0 +1,6 @@ +-- 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; From 7e13d17cc1fb4a7958f409a7b291ec8bc9cfadec Mon Sep 17 00:00:00 2001 From: Ijostrom Date: Sun, 10 May 2026 07:34:15 -0700 Subject: [PATCH 11/26] feat(Core/Config): Resurrection durability loss percentage (#25715) Co-authored-by: Ijostrom --- src/server/apps/worldserver/worldserver.conf.dist | 8 ++++++++ src/server/game/Handlers/NPCHandler.cpp | 4 +++- src/server/game/World/WorldConfig.cpp | 2 ++ src/server/game/World/WorldConfig.h | 1 + 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/server/apps/worldserver/worldserver.conf.dist b/src/server/apps/worldserver/worldserver.conf.dist index f0d4ff84b..9fdd35910 100644 --- a/src/server/apps/worldserver/worldserver.conf.dist +++ b/src/server/apps/worldserver/worldserver.conf.dist @@ -2606,6 +2606,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/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/World/WorldConfig.cpp b/src/server/game/World/WorldConfig.cpp index b9a9fb473..243e13499 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"); diff --git a/src/server/game/World/WorldConfig.h b/src/server/game/World/WorldConfig.h index 20c5fa6e8..8d95ed5cc 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, From 925912246d05520a3e7c683bc468922cdf45b716 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 10 May 2026 14:34:32 +0000 Subject: [PATCH 12/26] chore(DB): import pending files Referenced commit(s): f4be7b1e2b0a15d472c34f344aa8f7a3cba9314f --- .../rev_1778350030492406400.sql => db_world/2026_05_10_00.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1778350030492406400.sql => db_world/2026_05_10_00.sql} (90%) diff --git a/data/sql/updates/pending_db_world/rev_1778350030492406400.sql b/data/sql/updates/db_world/2026_05_10_00.sql similarity index 90% rename from data/sql/updates/pending_db_world/rev_1778350030492406400.sql rename to data/sql/updates/db_world/2026_05_10_00.sql index 7dc4159c2..fedd062d2 100644 --- a/data/sql/updates/pending_db_world/rev_1778350030492406400.sql +++ b/data/sql/updates/db_world/2026_05_10_00.sql @@ -1,3 +1,4 @@ +-- 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 From 40a52c885e684137ab110eba7685726e6a5b54da Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Sun, 10 May 2026 11:46:36 -0300 Subject: [PATCH 13/26] feat(Core/Scripts): Add ChatLog.Enable config to chat_log.cpp (#25795) Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> --- .../apps/worldserver/worldserver.conf.dist | 9 +++++++++ src/server/game/World/WorldConfig.cpp | 2 ++ src/server/game/World/WorldConfig.h | 1 + src/server/scripts/World/chat_log.cpp | 16 ++++++++++++++++ 4 files changed, 28 insertions(+) diff --git a/src/server/apps/worldserver/worldserver.conf.dist b/src/server/apps/worldserver/worldserver.conf.dist index 9fdd35910..982d73c27 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 diff --git a/src/server/game/World/WorldConfig.cpp b/src/server/game/World/WorldConfig.cpp index 243e13499..d96915e15 100644 --- a/src/server/game/World/WorldConfig.cpp +++ b/src/server/game/World/WorldConfig.cpp @@ -604,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 8d95ed5cc..c573fc287 100644 --- a/src/server/game/World/WorldConfig.h +++ b/src/server/game/World/WorldConfig.h @@ -492,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/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) || From a9571bcc56ea043f7fedf2e913d7b7f60f5134e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefano=20Borz=C3=AC?= Date: Sun, 10 May 2026 16:47:21 +0200 Subject: [PATCH 14/26] feat(Scripts/Commands): show level on character deleted list, show characters from most recent (#25791) --- .../updates/pending_db_world/rev_1778409675794843140.sql | 9 +++++++++ .../Database/Implementation/CharacterDatabase.cpp | 8 ++++---- src/server/scripts/Commands/cs_character.cpp | 8 ++++++-- 3 files changed, 19 insertions(+), 6 deletions(-) create mode 100644 data/sql/updates/pending_db_world/rev_1778409675794843140.sql diff --git a/data/sql/updates/pending_db_world/rev_1778409675794843140.sql b/data/sql/updates/pending_db_world/rev_1778409675794843140.sql new file mode 100644 index 000000000..6000150c6 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1778409675794843140.sql @@ -0,0 +1,9 @@ +-- +-- 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/src/server/database/Database/Implementation/CharacterDatabase.cpp b/src/server/database/Database/Implementation/CharacterDatabase.cpp index 167a0db13..de3cdeb37 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.cpp +++ b/src/server/database/Database/Implementation/CharacterDatabase.cpp @@ -402,10 +402,10 @@ 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_BY_NAME_LIMIT, "SELECT guid, deleteInfos_Name, deleteInfos_Account, deleteDate FROM characters WHERE deleteDate IS NOT NULL AND deleteInfos_Name LIKE CONCAT('%%', ?, '%%') LIMIT 51", CONNECTION_SYNCH); - PrepareStatement(CHAR_SEL_CHAR_DEL_INFO, "SELECT guid, deleteInfos_Name, deleteInfos_Account, deleteDate FROM characters WHERE deleteDate IS NOT NULL LIMIT 51", 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); diff --git a/src/server/scripts/Commands/cs_character.cpp b/src/server/scripts/Commands/cs_character.cpp index 19e708394..df169b7d1 100644 --- a/src/server/scripts/Commands/cs_character.cpp +++ b/src/server/scripts/Commands/cs_character.cpp @@ -95,6 +95,7 @@ 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; @@ -155,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()); } @@ -187,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); } From 1a951f5e73ce51fbeab683d0ab6bd67c90aefee4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 10 May 2026 14:47:33 +0000 Subject: [PATCH 15/26] chore(DB): import pending files Referenced commit(s): 40a52c885e684137ab110eba7685726e6a5b54da --- .../rev_1778409675794843140.sql => db_world/2026_05_10_01.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1778409675794843140.sql => db_world/2026_05_10_01.sql} (96%) diff --git a/data/sql/updates/pending_db_world/rev_1778409675794843140.sql b/data/sql/updates/db_world/2026_05_10_01.sql similarity index 96% rename from data/sql/updates/pending_db_world/rev_1778409675794843140.sql rename to data/sql/updates/db_world/2026_05_10_01.sql index 6000150c6..9765bf229 100644 --- a/data/sql/updates/pending_db_world/rev_1778409675794843140.sql +++ b/data/sql/updates/db_world/2026_05_10_01.sql @@ -1,3 +1,4 @@ +-- DB update 2026_05_10_00 -> 2026_05_10_01 -- -- Show character level in `.character deleted list` output -- From 3720bc585c931c8b9bcdbce29452a2612791a131 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francesco=20Borz=C3=AC?= Date: Mon, 11 May 2026 13:25:14 +0200 Subject: [PATCH 16/26] chore(gitignore): optimize Claude ignores (#25796) --- .gitignore | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 6db545d09..d41a6021f 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 From 3678096ef779b62235b09349c9092f465cfe3129 Mon Sep 17 00:00:00 2001 From: 5Buttons Date: Tue, 12 May 2026 00:34:56 +0200 Subject: [PATCH 17/26] fix(Scripts/Ulduar): kologarn eyebeam emote being visible to all players (#25807) --- src/server/scripts/Northrend/Ulduar/Ulduar/boss_kologarn.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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); } } } From cb999cf889543330a074bd22874ce1c202446234 Mon Sep 17 00:00:00 2001 From: mpfans Date: Tue, 12 May 2026 19:10:09 +0800 Subject: [PATCH 18/26] fix(ICC/Putricide): add Mutated Plague cast in P3 (#25802) --- .../Northrend/IcecrownCitadel/boss_professor_putricide.cpp | 7 +++++++ 1 file changed, 7 insertions(+) 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; From 1dd2236a0693e202eef8510c15a3a678afad0e5c Mon Sep 17 00:00:00 2001 From: sigman Date: Thu, 14 May 2026 02:49:29 -0700 Subject: [PATCH 19/26] fix(Scripts/Spells): Fix 'still in combat' status after hunter tames a beast (in quest) (#25811) --- src/server/scripts/Spells/spell_hunter.cpp | 1 + 1 file changed, 1 insertion(+) 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(); } } } From 485769354c052ad11867fc2e404f054b1747f145 Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Thu, 14 May 2026 06:50:43 -0300 Subject: [PATCH 20/26] =?UTF-8?q?feat(Scripts/Commands):=20add=20`respawn?= =?UTF-8?q?=20guid/entry'=20command=20and=20expand=20`list=20resp=E2=80=A6?= =?UTF-8?q?=20(#25787)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> --- .../pending_db_auth/respawn_id_rbac.sql | 17 ++ .../respawn_id_and_list_respawns_strings.sql | 36 +++ src/server/game/Accounts/RBAC.h | 4 + src/server/game/Miscellaneous/Language.h | 21 +- src/server/scripts/Commands/cs_list.cpp | 38 ++- src/server/scripts/Commands/cs_misc.cpp | 264 ++++++++++++++++++ 6 files changed, 371 insertions(+), 9 deletions(-) create mode 100644 data/sql/updates/pending_db_auth/respawn_id_rbac.sql create mode 100644 data/sql/updates/pending_db_world/respawn_id_and_list_respawns_strings.sql diff --git a/data/sql/updates/pending_db_auth/respawn_id_rbac.sql b/data/sql/updates/pending_db_auth/respawn_id_rbac.sql new file mode 100644 index 000000000..d77b26f8c --- /dev/null +++ b/data/sql/updates/pending_db_auth/respawn_id_rbac.sql @@ -0,0 +1,17 @@ +-- +-- 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/pending_db_world/respawn_id_and_list_respawns_strings.sql b/data/sql/updates/pending_db_world/respawn_id_and_list_respawns_strings.sql new file mode 100644 index 000000000..7dccec609 --- /dev/null +++ b/data/sql/updates/pending_db_world/respawn_id_and_list_respawns_strings.sql @@ -0,0 +1,36 @@ +-- +-- 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/src/server/game/Accounts/RBAC.h b/src/server/game/Accounts/RBAC.h index 1ea012c04..d926ea791 100644 --- a/src/server/game/Accounts/RBAC.h +++ b/src/server/game/Accounts/RBAC.h @@ -677,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/Miscellaneous/Language.h b/src/server/game/Miscellaneous/Language.h index a0d198901..c4ac6dbdb 100644 --- a/src/server/game/Miscellaneous/Language.h +++ b/src/server/game/Miscellaneous/Language.h @@ -1489,6 +1489,25 @@ enum AcoreStrings LANG_PET_DELETE_NOT_FOUND = 35438, LANG_PET_DELETE_SUCCESS = 35439, - LANG_CHARACTER_DELETED_LIST_LIMIT = 35440 + 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/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_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(); From b1e226db966b14beda5073b3a1152ce64d6cb238 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 14 May 2026 09:50:45 +0000 Subject: [PATCH 21/26] chore(DB): import pending files Referenced commit(s): 1dd2236a0693e202eef8510c15a3a678afad0e5c --- .../respawn_id_rbac.sql => db_auth/2026_05_14_00.sql} | 1 + .../2026_05_14_00.sql} | 1 + 2 files changed, 2 insertions(+) rename data/sql/updates/{pending_db_auth/respawn_id_rbac.sql => db_auth/2026_05_14_00.sql} (93%) rename data/sql/updates/{pending_db_world/respawn_id_and_list_respawns_strings.sql => db_world/2026_05_14_00.sql} (99%) diff --git a/data/sql/updates/pending_db_auth/respawn_id_rbac.sql b/data/sql/updates/db_auth/2026_05_14_00.sql similarity index 93% rename from data/sql/updates/pending_db_auth/respawn_id_rbac.sql rename to data/sql/updates/db_auth/2026_05_14_00.sql index d77b26f8c..f0a5d87a3 100644 --- a/data/sql/updates/pending_db_auth/respawn_id_rbac.sql +++ b/data/sql/updates/db_auth/2026_05_14_00.sql @@ -1,3 +1,4 @@ +-- 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) diff --git a/data/sql/updates/pending_db_world/respawn_id_and_list_respawns_strings.sql b/data/sql/updates/db_world/2026_05_14_00.sql similarity index 99% rename from data/sql/updates/pending_db_world/respawn_id_and_list_respawns_strings.sql rename to data/sql/updates/db_world/2026_05_14_00.sql index 7dccec609..3165d9e1c 100644 --- a/data/sql/updates/pending_db_world/respawn_id_and_list_respawns_strings.sql +++ b/data/sql/updates/db_world/2026_05_14_00.sql @@ -1,3 +1,4 @@ +-- 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 From 61e99f9c444ef84daff059ed566e07ae4f211d6d Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Thu, 14 May 2026 09:58:16 -0300 Subject: [PATCH 22/26] fix(Core/Maps): fix pooled creature/GO respawn in instanced maps (#25826) Co-authored-by: Claude Sonnet 4.6 --- src/server/game/Maps/Map.cpp | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/server/game/Maps/Map.cpp b/src/server/game/Maps/Map.cpp index 324d19f13..7de49ba77 100644 --- a/src/server/game/Maps/Map.cpp +++ b/src/server/game/Maps/Map.cpp @@ -2717,12 +2717,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); @@ -2778,12 +2784,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); From 0b30b3f99f8f874746940dc410233bc400a5e9ef Mon Sep 17 00:00:00 2001 From: Marchy <89603088+M4rchy-S@users.noreply.github.com> Date: Thu, 14 May 2026 21:25:09 +0200 Subject: [PATCH 23/26] fix(Scripts/Gundrak): Dispell puncture aura from players after killing Gal'darah (#25830) --- src/server/scripts/Northrend/Gundrak/boss_gal_darah.cpp | 1 + 1 file changed, 1 insertion(+) 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 From de9461976dffee91326bd43a2492da533175fd68 Mon Sep 17 00:00:00 2001 From: armedprophet Date: Sat, 16 May 2026 03:38:12 +1000 Subject: [PATCH 24/26] fix(DB/SAI): Rampage! chain bunnies not arming on spawn (#25808) Co-authored-by: sogla --- .../rev_1778860073831551057.sql | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 data/sql/updates/pending_db_world/rev_1778860073831551057.sql diff --git a/data/sql/updates/pending_db_world/rev_1778860073831551057.sql b/data/sql/updates/pending_db_world/rev_1778860073831551057.sql new file mode 100644 index 000000000..0ee174d56 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1778860073831551057.sql @@ -0,0 +1,69 @@ +-- 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); From 46a649c6f6aba3a9a587bb835df2b4dd4ea25fae Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 15 May 2026 17:39:23 +0000 Subject: [PATCH 25/26] chore(DB): import pending files Referenced commit(s): de9461976dffee91326bd43a2492da533175fd68 --- .../rev_1778860073831551057.sql => db_world/2026_05_15_00.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1778860073831551057.sql => db_world/2026_05_15_00.sql} (99%) diff --git a/data/sql/updates/pending_db_world/rev_1778860073831551057.sql b/data/sql/updates/db_world/2026_05_15_00.sql similarity index 99% rename from data/sql/updates/pending_db_world/rev_1778860073831551057.sql rename to data/sql/updates/db_world/2026_05_15_00.sql index 0ee174d56..bdc947f43 100644 --- a/data/sql/updates/pending_db_world/rev_1778860073831551057.sql +++ b/data/sql/updates/db_world/2026_05_15_00.sql @@ -1,3 +1,4 @@ +-- 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)); From 04dff5141870a202393ee09753cb128efd4f175e Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Fri, 15 May 2026 22:40:22 -0300 Subject: [PATCH 26/26] fix(Scripts/Karazhan): fix Moroes guests not respawning on evade (#25851) Co-authored-by: Claude Sonnet 4.5 --- .../EasternKingdoms/Karazhan/boss_moroes.cpp | 35 +++++++------------ 1 file changed, 13 insertions(+), 22 deletions(-) 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; }