diff --git a/.gitignore b/.gitignore index 27794c483..05c48bbf1 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ # AzerothCore # +.cache /conf/* !/conf/dist /modules/* diff --git a/data/sql/updates/db_world/2026_06_08_00.sql b/data/sql/updates/db_world/2026_06_08_00.sql new file mode 100644 index 000000000..723f93f6c --- /dev/null +++ b/data/sql/updates/db_world/2026_06_08_00.sql @@ -0,0 +1,3 @@ +-- DB update 2026_06_05_00 -> 2026_06_08_00 +-- Remove the XP gains from "Risen Alliance Soldier" and "Icy Ghoul" +UPDATE `creature_template` SET `ExperienceModifier` = 0 WHERE `entry` IN (31205, 31142); diff --git a/data/sql/updates/db_world/2026_06_08_01.sql b/data/sql/updates/db_world/2026_06_08_01.sql new file mode 100644 index 000000000..81ed2ab73 --- /dev/null +++ b/data/sql/updates/db_world/2026_06_08_01.sql @@ -0,0 +1,4 @@ +-- DB update 2026_06_08_00 -> 2026_06_08_01 +-- Reverts changes from https://github.com/azerothcore/azerothcore-wotlk/pull/26112 +-- Adds back the ExperienceModifier and Adds the correct flag "CREATURE_FLAG_EXTRA_NO_XP" for "Risen Alliance Soldier" and "Icy Ghoul" +UPDATE `creature_template` SET `ExperienceModifier` = 1, `flags_extra` = `flags_extra` | 64 WHERE `entry` IN (31205, 31142); diff --git a/data/sql/updates/db_world/2026_06_10_00.sql b/data/sql/updates/db_world/2026_06_10_00.sql new file mode 100644 index 000000000..708cf296a --- /dev/null +++ b/data/sql/updates/db_world/2026_06_10_00.sql @@ -0,0 +1,14 @@ +-- DB update 2026_06_08_01 -> 2026_06_10_00 +-- Stoneclaw Totem pulses should not break stealth (Patch 3.2.2). +DELETE FROM `spell_custom_attr` WHERE `spell_id` IN (5729, 6393, 6394, 6395, 10423, 10424, 25512, 58586, 58587, 58588); +INSERT INTO `spell_custom_attr` (`spell_id`, `attributes`) VALUES +(5729, 64), +(6393, 64), +(6394, 64), +(6395, 64), +(10423, 64), +(10424, 64), +(25512, 64), +(58586, 64), +(58587, 64), +(58588, 64); diff --git a/data/sql/updates/db_world/2026_06_10_01.sql b/data/sql/updates/db_world/2026_06_10_01.sql new file mode 100644 index 000000000..611129253 --- /dev/null +++ b/data/sql/updates/db_world/2026_06_10_01.sql @@ -0,0 +1,3 @@ +-- DB update 2026_06_10_00 -> 2026_06_10_01 +-- Earthgrab is a hostile root and should reveal stealthed targets. +DELETE FROM `spell_custom_attr` WHERE `spell_id` = 64695; diff --git a/data/sql/updates/db_world/2026_06_10_02.sql b/data/sql/updates/db_world/2026_06_10_02.sql new file mode 100644 index 000000000..0f7f1a389 --- /dev/null +++ b/data/sql/updates/db_world/2026_06_10_02.sql @@ -0,0 +1,4 @@ +-- DB update 2026_06_10_01 -> 2026_06_10_02 +-- +DELETE FROM `spell_script_names` WHERE `spell_id` = 62976 AND `ScriptName` = 'spell_thorim_lightning_pillar_P2'; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES (62976, 'spell_thorim_lightning_pillar_P2'); diff --git a/data/sql/updates/db_world/2026_06_10_03.sql b/data/sql/updates/db_world/2026_06_10_03.sql new file mode 100644 index 000000000..87b233e62 --- /dev/null +++ b/data/sql/updates/db_world/2026_06_10_03.sql @@ -0,0 +1,15 @@ +-- DB update 2026_06_10_02 -> 2026_06_10_03 +DELETE FROM `waypoint_data` WHERE `id` = 253190; +INSERT INTO `waypoint_data` + (`id`, `point`, `position_x`, `position_y`, `position_z`, `orientation`, `velocity`, `delay`, `smoothTransition`, `move_type`, `action`, `action_chance`) +VALUES + (253190, 1, 1735.393, 593.6747, 140.5267, NULL, 0, 0, 1, 1, 0, 100), + (253190, 2, 1746.861, 621.0573, 141.2209, NULL, 0, 0, 1, 1, 0, 100), + (253190, 3, 1734.7, 663.6804, 139.3877, NULL, 0, 0, 1, 1, 0, 100), + (253190, 4, 1695.096, 677.7586, 139.9433, NULL, 0, 0, 1, 1, 0, 100), + (253190, 5, 1663.575, 664.397, 139.952, NULL, 0, 0, 1, 1, 0, 100), + (253190, 6, 1651.432, 634.5519, 140.1377, NULL, 0, 0, 1, 1, 0, 100), + (253190, 7, 1666.394, 594.8594, 139.1098, NULL, 0, 0, 1, 1, 0, 100), + (253190, 8, 1704.55, 579.4996, 140.3171, NULL, 0, 0, 1, 1, 0, 100); + +UPDATE `creature_template` SET `speed_run` = 1.71429, `flags_extra` = `flags_extra` | 512 WHERE `entry` = 25319; diff --git a/data/sql/updates/db_world/2026_06_11_00.sql b/data/sql/updates/db_world/2026_06_11_00.sql new file mode 100644 index 000000000..d85f27238 --- /dev/null +++ b/data/sql/updates/db_world/2026_06_11_00.sql @@ -0,0 +1,3 @@ +-- DB update 2026_06_10_03 -> 2026_06_11_00 +UPDATE `reference_loot_template` SET `Item` = 49667 WHERE `Item` = 48679 AND `entry` = 10016; +UPDATE `conditions` SET `SourceEntry` = 49667, `Comment` = 'Only drop "Waterlogged Recipe" (49667) from "Bag of Fishing Treasures" (46007) if the player has at least 300 cooking skill' WHERE (`SourceTypeOrReferenceId` = 10) AND (`SourceGroup` = 10016) AND (`SourceEntry` = 48679) AND (`SourceId` = 0) AND (`ElseGroup` = 0) AND (`ConditionTypeOrReference` = 7) AND (`ConditionTarget` = 0) AND (`ConditionValue1` = 185) AND (`ConditionValue2` = 300) AND (`ConditionValue3` = 0); diff --git a/data/sql/updates/db_world/2026_06_11_01.sql b/data/sql/updates/db_world/2026_06_11_01.sql new file mode 100644 index 000000000..84fe7bfc4 --- /dev/null +++ b/data/sql/updates/db_world/2026_06_11_01.sql @@ -0,0 +1,110 @@ +-- DB update 2026_06_11_00 -> 2026_06_11_01 +-- +-- https://www.wowhead.com/quest=24743/shadows-edge +DELETE FROM `quest_offer_reward_locale` WHERE `ID` = 24743 AND `locale` IN ('esMX', 'esES'); +INSERT INTO `quest_offer_reward_locale` (`ID`, `locale`, `RewardText`, `VerifiedBuild`) VALUES +(24743, 'esMX', 'Escucha bien, $c. Al aceptar esta arma, tu destino está sellado.$B$BVencer o sucumbir.$B$BHe puesto tus pies en este camino. Por lo tanto, eres mi responsabilidad personal. Si fallas, tengo el deber de liberarte de esta vida.$B$BRecuerda mis palabras, $r, y no falles.', 0), +(24743, 'esES', 'Escucha bien, $c. Al aceptar esta arma, tu destino está sellado.$B$BVencer o sucumbir.$B$BHe puesto tus pies en este camino. Por lo tanto, eres mi responsabilidad personal. Si fallas, tengo el deber de liberarte de esta vida.$B$BRecuerda mis palabras, $r, y no falles.', 0); + +-- https://www.wowhead.com/quest=24547/a-feast-of-souls +DELETE FROM `quest_offer_reward_locale` WHERE `ID` = 24547 AND `locale` IN ('esMX', 'esES'); +INSERT INTO `quest_offer_reward_locale` (`ID`, `locale`, `RewardText`, `VerifiedBuild`) VALUES +(24547, 'esMX', '$b$bParece que has soportado esta etapa de tu prueba admirablemente, $r.$b$bReza para que el destino siga sonriéndote en tu esfuerzo...', 0), +(24547, 'esES', '$b$bParece que has soportado esta etapa de tu prueba admirablemente, $r.$b$bReza para que el destino siga sonriéndote en tu esfuerzo...', 0); + +-- https://www.wowhead.com/quest=24749/unholy-infusion +DELETE FROM `quest_offer_reward_locale` WHERE `ID` = 24749 AND `locale` IN ('esMX', 'esES'); +INSERT INTO `quest_offer_reward_locale` (`ID`, `locale`, `RewardText`, `VerifiedBuild`) VALUES +(24749, 'esMX', 'Bien hecho, $c.$b$bLa finalización de nuestro trabajo se acerca.', 0), +(24749, 'esES', 'Bien hecho, $c.$b$bLa finalización de nuestro trabajo se acerca.', 0); + +-- https://www.wowhead.com/quest=24756/blood-infusion +DELETE FROM `quest_offer_reward_locale` WHERE `ID` = 24756 AND `locale` IN ('esMX', 'esES'); +INSERT INTO `quest_offer_reward_locale` (`ID`, `locale`, `RewardText`, `VerifiedBuild`) VALUES +(24756, 'esMX', 'Bien hecho, $c.$b$bAgonía de Sombras está casi a nuestro alcance.', 0), +(24756, 'esES', 'Bien hecho, $c.$b$bAgonía de Sombras está casi a nuestro alcance.', 0); + +-- https://www.wowhead.com/quest=24757/frost-infusion +DELETE FROM `quest_offer_reward_locale` WHERE `ID` = 24757 AND `locale` IN ('esMX', 'esES'); +INSERT INTO `quest_offer_reward_locale` (`ID`, `locale`, `RewardText`, `VerifiedBuild`) VALUES +(24757, 'esMX', 'El hacha ha bebido hasta saciarse, $r, y la forja está casi lista para la creación de Agonía de Sombras...', 0), +(24757, 'esES', 'El hacha ha bebido hasta saciarse, $r, y la forja está casi lista para la creación de Agonía de Sombras...', 0); + +-- https://www.wowhead.com/quest=24548/the-splintered-throne +DELETE FROM `quest_offer_reward_locale` WHERE `ID` = 24548 AND `locale` IN ('esMX', 'esES'); +INSERT INTO `quest_offer_reward_locale` (`ID`, `locale`, `RewardText`, `VerifiedBuild`) VALUES +(24548, 'esMX', 'Esto debería ser suficiente, $c.$b$bHáblame cuando Filo de la Sombra esté completamente potenciado e intentaré completarlo.', 0), +(24548, 'esES', 'Esto debería ser suficiente, $c.$b$bHáblame cuando Filo de la Sombra esté completamente potenciado e intentaré completarlo.', 0); + +DELETE FROM `quest_request_items_locale` WHERE `ID` = 24548 AND `locale` IN ('esMX', 'esES'); +INSERT INTO `quest_request_items_locale` (`ID`, `locale`, `CompletionText`, `VerifiedBuild`) VALUES +(24548, 'esMX', '¿Has conseguido los fragmentos del trono helado, $C?', 0), +(24548, 'esES', '¿Has conseguido los fragmentos del trono helado, $C?', 0); + +-- https://www.wowhead.com/quest=24912/empowerment +DELETE FROM `quest_offer_reward_locale` WHERE `ID` = 24912 AND `locale` IN ('esMX', 'esES'); +INSERT INTO `quest_offer_reward_locale` (`ID`, `locale`, `RewardText`, `VerifiedBuild`) VALUES +(24912, 'esMX', 'Manténgase alejado, $c, y manténgase estable.', 0), +(24912, 'esES', 'Manténgase alejado, $c, y manténgase estable.', 0); + +-- https://www.wowhead.com/quest=24549/shadowmourne +DELETE FROM `quest_offer_reward_locale` WHERE `ID` = 24549 AND `locale` IN ('esMX', 'esES'); +INSERT INTO `quest_offer_reward_locale` (`ID`, `locale`, `RewardText`, `VerifiedBuild`) VALUES +(24549, 'esMX', 'Enhorabuena, $c.$b$bA pesar de todo lo contrario, has superado la tormenta.$b$bConfío en que encontrarás una recompensa proporcional a tu convicción...', 0), +(24549, 'esES', 'Enhorabuena, $c.$b$bA pesar de todo lo contrario, has superado la tormenta.$b$bConfío en que encontrarás una recompensa proporcional a tu convicción...', 0); + +-- https://www.wowhead.com/quest=24748/the-lich-kings-last-stand +DELETE FROM `quest_offer_reward_locale` WHERE `ID` = 24748 AND `locale` IN ('esMX', 'esES'); +INSERT INTO `quest_offer_reward_locale` (`ID`, `locale`, `RewardText`, `VerifiedBuild`) VALUES +(24748, 'esMX', 'Así que, por fin, está hecho.$b$bQue la gente de Azeroth recuerde nuestras hazañas aquí para siempre. Que los sacrificios de tantos ardan en sus corazones eternamente. Que nunca dejen de arriesgarse al peligro mortal ante un gran mal.', 0), +(24748, 'esES', 'Así que, por fin, está hecho.$b$bQue la gente de Azeroth recuerde nuestras hazañas aquí para siempre. Que los sacrificios de tantos ardan en sus corazones eternamente. Que nunca dejen de arriesgarse al peligro mortal ante un gran mal.', 0); + +-- https://www.wowhead.com/quest=24914/personal-property +DELETE FROM `quest_offer_reward_locale` WHERE `ID` = 24914 AND `locale` IN ('esMX', 'esES'); +INSERT INTO `quest_offer_reward_locale` (`ID`, `locale`, `RewardText`, `VerifiedBuild`) VALUES +(24914, 'esMX', '$B$BAquí tienes, $R.', 0), +(24914, 'esES', '$B$BAquí tienes, $R.', 0); + +-- https://www.wowhead.com/quest=24915/mograines-reunion +DELETE FROM `quest_offer_reward_locale` WHERE `ID` = 24915 AND `locale` IN ('esMX', 'esES'); +INSERT INTO `quest_offer_reward_locale` (`ID`, `locale`, `RewardText`, `VerifiedBuild`) VALUES +(24915, 'esMX', 'Imposible...$B$B¡Padre, ¿eres tú?!$B$BMe has hecho un gran favor, héroe. Te ofrezco las riendas de mi fiel corcel. Haz lo que quieras con él, pero no olvides a quienes te ayudaron en esta hazaña monumental.', 0), +(24915, 'esES', 'Imposible...$B$B¡Padre, ¿eres tú?!$B$BMe has hecho un gran favor, héroe. Te ofrezco las riendas de mi fiel corcel. Haz lo que quieras con él, pero no olvides a quienes te ayudaron en esta hazaña monumental.', 0); + +DELETE FROM `quest_request_items_locale` WHERE `ID` = 24915 AND `locale` IN ('esMX', 'esES'); +INSERT INTO `quest_request_items_locale` (`ID`, `locale`, `CompletionText`, `VerifiedBuild`) VALUES +(24915, 'esMX', '¿Qué es eso que tienes, $c?$B$BMe llama, despertando sentimientos que creía muertos hace mucho tiempo.', 0), +(24915, 'esES', '¿Qué es eso que tienes, $c?$B$BMe llama, despertando sentimientos que creía muertos hace mucho tiempo.', 0); + +-- https://www.wowhead.com/quest=24916/jainas-locket +DELETE FROM `quest_offer_reward_locale` WHERE `ID` = 24916 AND `locale` IN ('esMX', 'esES'); +INSERT INTO `quest_offer_reward_locale` (`ID`, `locale`, `RewardText`, `VerifiedBuild`) VALUES +(24916, 'esMX', '¿Cómo... cómo conseguiste esto?$B$BMe has hecho un gran favor, héroe. No podría soportar conservar este relicario, pero le pondré un encantamiento que te será útil. Haz lo que quieras con él, pero no olvides a quienes te ayudaron en esta hazaña monumental.', 0), +(24916, 'esES', '¿Cómo... cómo conseguiste esto?$B$BMe has hecho un gran favor, héroe. No podría soportar conservar este relicario, pero le pondré un encantamiento que te será útil. Haz lo que quieras con él, pero no olvides a quienes te ayudaron en esta hazaña monumental.', 0); + +DELETE FROM `quest_request_items_locale` WHERE `ID` = 24916 AND `locale` IN ('esMX', 'esES'); +INSERT INTO `quest_request_items_locale` (`ID`, `locale`, `CompletionText`, `VerifiedBuild`) VALUES +(24916, 'esMX', 'Sí, $c. ¿Cómo puedo ayudarte?', 0), +(24916, 'esES', 'Sí, $c. ¿Cómo puedo ayudarte?', 0); + +-- https://www.wowhead.com/quest=24917/muradins-lament +DELETE FROM `quest_offer_reward_locale` WHERE `ID` = 24917 AND `locale` IN ('esMX', 'esES'); +INSERT INTO `quest_offer_reward_locale` (`ID`, `locale`, `RewardText`, `VerifiedBuild`) VALUES +(24917, 'esMX', 'Sí. Conozco esta espada... y siempre la atesoraré: un instante que se perderá para siempre.$B$BMe has hecho un gran favor, héroe. Te ofrezco un regalo de los enanos nacidos en la escarcha. Haz lo que quieras con él, pero no olvides a quienes te ayudaron en esta hazaña monumental.', 0), +(24917, 'esES', 'Sí. Conozco esta espada... y siempre la atesoraré: un instante que se perderá para siempre.$B$BMe has hecho un gran favor, héroe. Te ofrezco un regalo de los enanos nacidos en la escarcha. Haz lo que quieras con él, pero no olvides a quienes te ayudaron en esta hazaña monumental.', 0); + +DELETE FROM `quest_request_items_locale` WHERE `ID` = 24917 AND `locale` IN ('esMX', 'esES'); +INSERT INTO `quest_request_items_locale` (`ID`, `locale`, `CompletionText`, `VerifiedBuild`) VALUES +(24917, 'esMX', '¿Qué tienes ahí, $r?', 0), +(24917, 'esES', '¿Qué tienes ahí, $r?', 0); + +-- https://www.wowhead.com/quest=24919/the-lightbringers-redemption +DELETE FROM `quest_offer_reward_locale` WHERE `ID` = 24919 AND `locale` IN ('esMX', 'esES'); +INSERT INTO `quest_offer_reward_locale` (`ID`, `locale`, `RewardText`, `VerifiedBuild`) VALUES +(24919, 'esMX', '$B$BLa medalla de Arthas.$B$B Recuerdo bien el día que se la entregué...$B$BMe has hecho un gran favor, héroe. Mi alma puede descansar en paz. Te ofrezco un recuerdo perdido en el tiempo. Haz lo que quieras con él, pero no olvides a quienes te ayudaron en esta hazaña monumental.', 0), +(24919, 'esES', '$B$BLa medalla de Arthas.$B$B Recuerdo bien el día que se la entregué...$B$BMe has hecho un gran favor, héroe. Mi alma puede descansar en paz. Te ofrezco un recuerdo perdido en el tiempo. Haz lo que quieras con él, pero no olvides a quienes te ayudaron en esta hazaña monumental.', 0); + +DELETE FROM `quest_request_items_locale` WHERE `ID` = 24919 AND `locale` IN ('esMX', 'esES'); +INSERT INTO `quest_request_items_locale` (`ID`, `locale`, `CompletionText`, `VerifiedBuild`) VALUES +(24919, 'esMX', '¿En qué puedo serle útil, $r?', 0), +(24919, 'esES', '¿En qué puedo serle útil, $r?', 0); diff --git a/data/sql/updates/db_world/2026_06_11_02.sql b/data/sql/updates/db_world/2026_06_11_02.sql new file mode 100644 index 000000000..2662dde4b --- /dev/null +++ b/data/sql/updates/db_world/2026_06_11_02.sql @@ -0,0 +1,587 @@ +-- DB update 2026_06_11_01 -> 2026_06_11_02 +-- +DELETE FROM `spell_cone` WHERE `Id` > 0; +INSERT INTO `spell_cone` (`ID`, `ConeDegrees`) VALUES +(4520, 53), +(5010, 53), +(5106, 53), +(5708, 71), +(6749, 53), +(7342, 53), +(7769, 90), +(8374, 71), +(8435, 53), +(8873, 53), +(9573, 53), +(10733, 53), +(11019, 71), +(11082, 53), +(11828, 53), +(11837, 53), +(12252, 53), +(12278, 53), +(12533, 53), +(12549, 53), +(12557, 53), +(12611, 53), +(12882, 71), +(12884, 53), +(12887, 71), +(12891, 53), +(13184, 53), +(15244, 53), +(15847, -82), +(15851, 53), +(16054, 53), +(16094, 71), +(16099, 71), +(16104, 53), +(16169, 71), +(16340, 71), +(16350, 71), +(16359, 71), +(16390, 71), +(16396, 82), +(17283, 53), +(17294, 53), +(17446, 53), +(18144, 71), +(18435, 60), +(18500, 82), +(19630, 82), +(19641, 82), +(19813, 180), +(20299, 53), +(20667, 71), +(20712, 82), +(20716, 71), +(20717, 71), +(20828, 53), +(21071, 82), +(21099, 82), +(21189, 71), +(21333, 180), +(21559, 53), +(21910, 53), +(22479, 71), +(22539, 82), +(22558, 71), +(22559, 71), +(22560, 71), +(22561, 71), +(22642, 71), +(22741, 53), +(22746, 53), +(22833, 82), +(22945, 82), +(23011, 53), +(23023, 53), +(23339, 82), +(23364, -82), +(23461, 82), +(23919, 82), +(24011, 53), +(24192, 82), +(24334, 53), +(24682, 82), +(24818, 82), +(24821, 45), +(24822, 135), +(24823, 90), +(24835, 180), +(24836, 225), +(24837, 270), +(24838, 315), +(24839, 82), +(24933, 7), +(25029, 53), +(25030, 71), +(25034, 53), +(25145, 71), +(25149, 71), +(25150, 71), +(25322, 71), +(25653, -82), +(25814, 53), +(25993, 82), +(26029, 5), +(27604, 53), +(27807, 71), +(28127, 71), +(28157, 42), +(28168, 82), +(28405, 180), +(28438, 180), +(28459, 71), +(28741, 90), +(28783, 29), +(29221, 53), +(29477, 53), +(29717, 53), +(29905, 82), +(29935, 180), +(29939, 180), +(30043, 71), +(30056, 180), +(30095, 53), +(30210, 82), +(30213, 180), +(30418, 195), +(30469, 5), +(30527, 60), +(30732, 180), +(30761, 53), +(30815, -180), +(30913, 42), +(30926, 82), +(30930, 71), +(31306, 82), +(31409, 53), +(31473, 71), +(31478, 71), +(31485, 53), +(31661, 90), +(31914, 71), +(32227, 63), +(32228, 63), +(32914, 82), +(33128, 53), +(33551, 82), +(33678, 71), +(34072, 82), +(34154, 7), +(34240, 71), +(34261, 71), +(34267, 82), +(34268, 53), +(34325, 53), +(34359, 53), +(34583, 71), +(35236, 71), +(35250, 53), +(35311, 53), +(35351, 71), +(35953, 82), +(36039, 71), +(36249, 82), +(36252, 82), +(36307, 5), +(36486, 71), +(36489, 82), +(36617, 180), +(36619, 180), +(36693, 71), +(36694, 71), +(36695, 71), +(36696, 71), +(36697, 71), +(36698, 71), +(36699, 71), +(36700, 71), +(36820, 82), +(36876, 82), +(36921, 82), +(36951, 71), +(37012, 71), +(37142, 63), +(37143, 63), +(37147, 63), +(37149, 63), +(37150, 63), +(37220, 63), +(37265, 53), +(37289, 53), +(37317, 180), +(37319, 82), +(37337, 63), +(37339, 63), +(37345, 63), +(37348, 63), +(37406, 63), +(37413, 63), +(37433, 10), +(37453, 63), +(37454, 63), +(37459, 20), +(37461, 20), +(37488, 82), +(37498, 82), +(37502, 82), +(37624, 71), +(37638, 53), +(37672, 53), +(37730, 120), +(37734, 53), +(37779, 71), +(37918, 53), +(37919, 53), +(37993, 53), +(38052, 53), +(38110, 82), +(38145, 53), +(38163, 82), +(38309, 53), +(38354, 82), +(38367, 53), +(38369, 53), +(38384, 53), +(38458, 42), +(38468, 53), +(38491, 82), +(38524, 45), +(38576, 180), +(38644, 53), +(38737, 82), +(38761, 71), +(38783, 53), +(38810, 180), +(38811, 180), +(38814, 180), +(38973, 71), +(38974, 53), +(39033, 71), +(39034, 71), +(39036, 71), +(39037, 71), +(39049, 71), +(39128, 53), +(39144, 82), +(39222, 71), +(39263, 53), +(39385, 82), +(39583, 53), +(39631, 180), +(39686, 53), +(39692, 7), +(39942, 71), +(40077, 82), +(40083, 60), +(40088, 60), +(40100, 60), +(40457, 82), +(40508, 60), +(40562, 90), +(40595, 60), +(40599, 82), +(40631, 90), +(40840, 10), +(40904, 60), +(40939, 82), +(41063, 60), +(41068, 60), +(41168, 60), +(41172, 180), +(41438, 53), +(41444, 82), +(41445, 82), +(41545, 60), +(41572, 82), +(42143, 150), +(42584, 10), +(42638, 150), +(42669, 60), +(42723, 60), +(42835, 10), +(43050, 60), +(43140, 30), +(43207, 90), +(43210, 1), +(43215, 90), +(43294, 60), +(43582, 60), +(43954, 30), +(44037, 60), +(44273, 53), +(44799, 60), +(44804, 90), +(45065, 60), +(45122, -60), +(45150, 120), +(45385, 53), +(45467, 60), +(45761, 60), +(45856, 90), +(45859, 60), +(45860, 90), +(45968, 120), +(45969, 120), +(45970, 120), +(46150, 53), +(46158, 53), +(46186, 53), +(46236, 60), +(46293, 71), +(46294, 71), +(46295, 71), +(46296, 71), +(46297, 71), +(46298, 71), +(46299, 71), +(46300, 71), +(46442, 75), +(46802, 71), +(46968, 90), +(46984, 53), +(47250, 60), +(47251, 60), +(47563, 90), +(47593, 90), +(47921, 90), +(48096, 60), +(48133, 60), +(48260, 71), +(48623, 60), +(48642, 120), +(49106, 60), +(49197, 180), +(49398, 180), +(49475, 180), +(49668, 75), +(49692, 20), +(49701, 60), +(49703, 60), +(49725, 90), +(49728, 45), +(49761, 30), +(49860, 45), +(49862, 60), +(49867, 45), +(49870, 60), +(49884, 53), +(50155, -82), +(50269, 60), +(50283, 60), +(50302, 90), +(50337, 20), +(50348, 180), +(50350, 60), +(50409, 20), +(50505, 71), +(50652, 90), +(50653, 120), +(50676, 120), +(50677, 60), +(50990, 60), +(51144, 82), +(51218, 53), +(51219, 53), +(51337, 53), +(51338, 60), +(51415, 53), +(51640, 180), +(51678, 90), +(51768, 53), +(51785, 82), +(51817, 40), +(52201, 20), +(52316, 180), +(52331, 200), +(52531, 53), +(52640, 60), +(52720, 90), +(52835, 60), +(52870, 82), +(52973, 90), +(53167, 53), +(53363, 82), +(53399, 71), +(53472, 120), +(53643, 120), +(54107, 90), +(54109, 90), +(54122, 90), +(54282, 60), +(54326, 71), +(54364, 42), +(54383, 1), +(54400, 40), +(54682, 60), +(54819, 10), +(55087, 40), +(55138, 75), +(55429, 180), +(55626, 10), +(55636, 90), +(55696, -82), +(55697, -82), +(55765, 71), +(55814, 30), +(55856, 60), +(55909, 60), +(55916, 60), +(55918, 90), +(56032, 71), +(56090, 29), +(56272, 60), +(56524, 60), +(56748, 60), +(56861, 40), +(56908, 82), +(56910, 82), +(57051, 90), +(57076, 60), +(57091, 60), +(57385, 20), +(57412, 20), +(57477, 60), +(57495, 90), +(57570, 60), +(57652, 60), +(57728, 90), +(57741, 90), +(57801, 82), +(57828, 60), +(57962, 60), +(58112, 30), +(58446, 53), +(58463, 60), +(58688, 60), +(58690, 90), +(58852, 90), +(58947, 90), +(58956, 82), +(58957, 82), +(58977, 90), +(58993, 10), +(59004, 75), +(59015, 60), +(59018, 60), +(59126, 60), +(59141, 53), +(59152, 53), +(59197, 53), +(59258, 60), +(59271, 60), +(59281, 60), +(59283, 90), +(59433, 120), +(59434, 82), +(59469, 60), +(59514, 60), +(59622, 30), +(59692, 120), +(59706, 60), +(59709, 60), +(59792, 180), +(60072, 60), +(60206, 60), +(60287, 30), +(60502, 90), +(60903, 60), +(61090, 60), +(61125, 60), +(61146, 71), +(61391, 90), +(61407, 90), +(61673, 60), +(61757, 90), +(62308, 100), +(62315, 60), +(62339, 60), +(62345, 100), +(62355, 90), +(62414, 60), +(62415, 60), +(62466, 75), +(62522, 60), +(62553, 60), +(62625, 60), +(62645, 20), +(62654, 20), +(62882, 60), +(62883, 60), +(62888, 180), +(62936, 20), +(62973, 10), +(62974, 50), +(62991, 10), +(63226, 120), +(63247, 40), +(63293, 10), +(63317, 75), +(63387, 60), +(63473, 60), +(63541, 53), +(63551, 120), +(63568, 40), +(64019, 60), +(64021, 75), +(64147, 10), +(64319, 180), +(64348, 60), +(64352, 60), +(64422, 120), +(64531, 60), +(64532, 60), +(64536, 60), +(64537, 60), +(64619, 45), +(64645, 53), +(64649, 60), +(64655, 53), +(64688, 120), +(64716, 35), +(64720, 60), +(64721, 60), +(64825, 60), +(64847, 60), +(64871, 90), +(64944, 60), +(64988, 60), +(65016, 60), +(65112, 60), +(65242, 35), +(65248, 71), +(65260, 60), +(65919, 50), +(66184, 10), +(66513, 60), +(66514, 35), +(66689, 60), +(66819, 60), +(66820, 60), +(67028, 71), +(67038, 60), +(67441, 60), +(67575, 35), +(67576, 35), +(67577, 60), +(67796, 90), +(67798, 90), +(68607, 30), +(68778, 60), +(68867, -82), +(68873, 5), +(68948, 30), +(69242, 60), +(69507, 25), +(69527, 71), +(69649, 75), +(70169, 60), +(70176, 60), +(70367, 71), +(70461, 20), +(70512, 60), +(70513, 60), +(70633, 15), +(70670, 60), +(71029, 20), +(71250, 180), +(71369, -82), +(71386, 60), +(71807, 60), +(72133, 25), +(72149, 90), +(72848, 60), +(73061, 75), +(73740, 90), +(74117, 180), +(74403, 60), +(74441, 35), +(74525, 60), +(74531, 82), +(74806, 60), +(75126, 60), +(75343, 90), +(75417, 90), +(76560, 40); diff --git a/data/sql/updates/db_world/2026_06_12_00.sql b/data/sql/updates/db_world/2026_06_12_00.sql new file mode 100644 index 000000000..5ce92aa8b --- /dev/null +++ b/data/sql/updates/db_world/2026_06_12_00.sql @@ -0,0 +1,5 @@ +-- DB update 2026_06_11_02 -> 2026_06_12_00 +-- DB update 2026_06_11_02 -> 2026_06_11_03 +-- Spell (ID: 30213) was added to spell_cone in 2026_06_11_02 but does not have +-- a cone implicit target, causing a server warning on startup. +DELETE FROM `spell_cone` WHERE `ID` = 30213; diff --git a/data/sql/updates/db_world/2026_06_12_01.sql b/data/sql/updates/db_world/2026_06_12_01.sql new file mode 100644 index 000000000..cbb70324b --- /dev/null +++ b/data/sql/updates/db_world/2026_06_12_01.sql @@ -0,0 +1,6 @@ +-- DB update 2026_06_12_00 -> 2026_06_12_01 +-- +-- Remove "We made it! Thank you for getting me out of that hell hole. Tell Hemet to expect me!" +DELETE FROM `creature_text` WHERE (`CreatureID` = 28787) AND `GroupID` = 0 AND `ID` = 0; +-- Remove "Let's get the hell out of here" +DELETE FROM `creature_text` WHERE (`CreatureID` = 28787) AND `GroupID` = 6 AND `ID` = 0; diff --git a/data/sql/updates/db_world/2026_06_12_02.sql b/data/sql/updates/db_world/2026_06_12_02.sql new file mode 100644 index 000000000..b1e751a4d --- /dev/null +++ b/data/sql/updates/db_world/2026_06_12_02.sql @@ -0,0 +1,4 @@ +-- DB update 2026_06_12_01 -> 2026_06_12_02 +DELETE FROM `creature_loot_template` WHERE `Entry` = 31610 AND `Item` = 41796; +INSERT INTO `creature_loot_template` (`Entry`, `Item`, `Reference`, `Chance`, `QuestRequired`, `LootMode`, `GroupId`, `MinCount`, `MaxCount`, `Comment`) VALUES +(31610, 41796, 0, 100, 0, 1, 0, 1, 1, 'Anub\'arak (1) - Design: Infused Twilight Opal'); diff --git a/src/server/apps/worldserver/worldserver.conf.dist b/src/server/apps/worldserver/worldserver.conf.dist index 4493247a3..812e59e6a 100644 --- a/src/server/apps/worldserver/worldserver.conf.dist +++ b/src/server/apps/worldserver/worldserver.conf.dist @@ -4393,13 +4393,16 @@ PreserveCustomChannels = 0 PreserveCustomChannelDuration = 14 # -# ChatFilter -# Description: Blocks chat messages when they contain any entry from the `chat_filter` -# table (substring, case-insensitive). -# Manage entries with .chatfilter add / remove / list and .reload chat_filter. -# Blizzlike is only whispers are enabled. -# Default: 0 - (Disbaled) -# 1 - (Enabled) +# ChatFilter.Whisper / ChatFilter.Say / ChatFilter.Yell / ChatFilter.Emote +# Description: Block chat messages that contain any entry from the `chat_filter` table +# (substring match, case-insensitive). +# Manage entries with: .chatfilter add / .chatfilter remove / .chatfilter list +# and .reload chat_filter. +# Blizzlike: only Whisper filtering is enabled. +# Default: ChatFilter.Whisper = 1 - (Enabled) +# ChatFilter.Say = 0 - (Disabled) +# ChatFilter.Yell = 0 - (Disabled) +# ChatFilter.Emote = 0 - (Disabled) # ChatFilter.Whisper = 1 diff --git a/src/server/game/Battlefield/Battlefield.cpp b/src/server/game/Battlefield/Battlefield.cpp index df9ec8e48..fe312adfa 100644 --- a/src/server/game/Battlefield/Battlefield.cpp +++ b/src/server/game/Battlefield/Battlefield.cpp @@ -74,12 +74,13 @@ Battlefield::~Battlefield() CapturePoints.clear(); } -void Battlefield::RemovePlayerFromTracking(ObjectGuid playerGuid) +void Battlefield::RemovePlayerFromTracking(ObjectGuid playerGuid, bool removeFromQueue /*= true*/) { for (uint8 i = 0; i < PVP_TEAMS_COUNT; ++i) { InvitedPlayers[i].erase(playerGuid); - PlayersInQueue[i].erase(playerGuid); + if (removeFromQueue) + PlayersInQueue[i].erase(playerGuid); PlayersWillBeKick[i].erase(playerGuid); Players[i].erase(playerGuid); } @@ -96,7 +97,8 @@ void Battlefield::HandlePlayerEnterZone(Player* player, uint32 /*zone*/) TryRejoinAfterLogout(player); // relog: auto-rejoin, skip invite below // Xinef: do not invite players on taxi - if (!player->IsInFlight()) + // GMs are not invited to war (see InvitePlayerToWar), so skip queue and slot tracking too + if (!player->IsInFlight() && !player->IsGameMaster()) { // If battle is started, // If not full of players > invite player to join the war @@ -108,7 +110,7 @@ void Battlefield::HandlePlayerEnterZone(Player* player, uint32 /*zone*/) else { /// @todo: Send a packet to announce it to player - PlayersWillBeKick[player->GetTeamId()][player->GetGUID()] = GameTime::GetGameTime().count() + (player->IsGameMaster() ? 30 * MINUTE : 10); + PlayersWillBeKick[player->GetTeamId()][player->GetGUID()] = GameTime::GetGameTime().count() + 10; InvitePlayerToQueue(player); } } @@ -152,7 +154,7 @@ void Battlefield::HandlePlayerLeaveZone(Player* player, uint32 /*zone*/) for (BfCapturePoint* cp : CapturePoints) cp->HandlePlayerLeave(player); - RemovePlayerFromTracking(player->GetGUID()); + RemovePlayerFromTracking(player->GetGUID(), /*removeFromQueue=*/false); SendRemoveWorldStates(player); RemovePlayerFromResurrectQueue(player->GetGUID()); OnPlayerLeaveZone(player); @@ -193,7 +195,7 @@ bool Battlefield::Update(uint32 diff) SendUpdateWorldStates(); } - _scheduler.Update(diff); + _scheduler.Update(); bool objectiveChanged = false; if (IsWarTime()) @@ -213,6 +215,9 @@ void Battlefield::InvitePlayersInZoneToQueue() void Battlefield::InvitePlayerToQueue(Player* player) { + if (player->IsGameMaster()) // GMs are not invited to war, so don't queue them either + return; + if (PlayersInQueue[player->GetTeamId()].count(player->GetGUID())) return; @@ -237,6 +242,10 @@ void Battlefield::InvitePlayersInZoneToWar() { ForEachPlayerInZone([this](Player* player) { + // Skip GMs: they are never invited, so they would linger in PlayersWillBeKick forever + if (player->IsGameMaster()) + return; + if (IsPlayerInWarOrInvited(player)) return; @@ -298,6 +307,20 @@ bool Battlefield::IsPlayerInWarOrInvited(Player* player) const return PlayersInWar[teamId].count(player->GetGUID()) || InvitedPlayers[teamId].count(player->GetGUID()); } +bool Battlefield::IsPlayerInBattlefield(ObjectGuid guid) const +{ + if (!IsWarTime()) + return false; + + // PlayersInWar is split into per-team sets, but a GUID alone does not indicate the team, so check both sets. + // This also stays correct if an OnBattlefieldPlayerJoinWar handler reassigned the team GetTeamId() returns. + for (uint8 team = 0; team < PVP_TEAMS_COUNT; ++team) + if (PlayersInWar[team].count(guid)) + return true; + + return false; +} + void Battlefield::KickAfkPlayers() { ForEachPlayerInWar([this](Player* player) diff --git a/src/server/game/Battlefield/Battlefield.h b/src/server/game/Battlefield/Battlefield.h index 2513df1f5..aba66db48 100644 --- a/src/server/game/Battlefield/Battlefield.h +++ b/src/server/game/Battlefield/Battlefield.h @@ -365,6 +365,9 @@ public: /// Returns the set of players actively fighting in the war (per team, read-only). GuidUnorderedSet const& GetPlayersInWarSet(TeamId teamId) const { return PlayersInWar[teamId]; } + // Returns true if the player is enrolled as a participant in the currently active battle. + bool IsPlayerInBattlefield(ObjectGuid guid) const; + void DoPlaySoundToAll(uint32 soundId); void InvitePlayerToQueue(Player* player); @@ -441,7 +444,7 @@ protected: /// Returns true if the player is already tracked as actively in the war or invited to join it. bool IsPlayerInWarOrInvited(Player* player) const; - void RemovePlayerFromTracking(ObjectGuid playerGuid); + void RemovePlayerFromTracking(ObjectGuid playerGuid, bool removeFromQueue = true); // Player-iteration helpers: resolve each GUID to a live Player* and call fn(player). // Using templates avoids std::function overhead and works naturally with lambdas. diff --git a/src/server/game/Battlegrounds/Arena.cpp b/src/server/game/Battlegrounds/Arena.cpp index a3dc1432e..2f8b0e54e 100644 --- a/src/server/game/Battlegrounds/Arena.cpp +++ b/src/server/game/Battlegrounds/Arena.cpp @@ -246,9 +246,13 @@ void Arena::EndBattleground(TeamId winnerTeamId) trans->Append(stmt2); uint8 memberId = 0; - for (auto const& [playerGuid, arenaLogEntryData] : ArenaLogEntries) + for (auto& [playerGuid, arenaLogEntryData] : ArenaLogEntries) { auto const& score = PlayerScores.find(playerGuid.GetCounter()); + // Update stats from PlayerScores if player is still in arena + if (score != PlayerScores.end()) + arenaLogEntryData.SaveStats(score->second->GetDamageDone(), score->second->GetHealingDone(), score->second->GetKillingBlows()); + stmt2 = CharacterDatabase.GetPreparedStatement(CHAR_INS_ARENA_LOG_MEMBERSTATS); stmt2->SetData(0, fightId); stmt2->SetData(1, ++memberId); @@ -257,18 +261,9 @@ void Arena::EndBattleground(TeamId winnerTeamId) stmt2->SetData(4, arenaLogEntryData.ArenaTeamId); stmt2->SetData(5, arenaLogEntryData.Acc); stmt2->SetData(6, arenaLogEntryData.IP); - if (score != PlayerScores.end()) - { - stmt2->SetData(7, score->second->GetDamageDone()); - stmt2->SetData(8, score->second->GetHealingDone()); - stmt2->SetData(9, score->second->GetKillingBlows()); - } - else - { - stmt2->SetData(7, 0); - stmt2->SetData(8, 0); - stmt2->SetData(9, 0); - } + stmt2->SetData(7, arenaLogEntryData.DamageDone); + stmt2->SetData(8, arenaLogEntryData.HealingDone); + stmt2->SetData(9, arenaLogEntryData.KillingBlows); trans->Append(stmt2); } diff --git a/src/server/game/Battlegrounds/Battleground.cpp b/src/server/game/Battlegrounds/Battleground.cpp index 572f7dfe3..b10feb7b8 100644 --- a/src/server/game/Battlegrounds/Battleground.cpp +++ b/src/server/game/Battlegrounds/Battleground.cpp @@ -1061,6 +1061,11 @@ void Battleground::RemovePlayerAtLeave(Player* player) auto const& itr2 = PlayerScores.find(player->GetGUID().GetCounter()); if (itr2 != PlayerScores.end()) { + // Save stats to ArenaLogEntries before deleting score (for arena logging) + auto itr3 = ArenaLogEntries.find(player->GetGUID()); + if (itr3 != ArenaLogEntries.end()) + itr3->second.SaveStats(itr2->second->GetDamageDone(), itr2->second->GetHealingDone(), itr2->second->GetKillingBlows()); + delete itr2->second; PlayerScores.erase(itr2); } diff --git a/src/server/game/Battlegrounds/Battleground.h b/src/server/game/Battlegrounds/Battleground.h index 6178869f5..eb075a346 100644 --- a/src/server/game/Battlegrounds/Battleground.h +++ b/src/server/game/Battlegrounds/Battleground.h @@ -259,11 +259,21 @@ public: IP = ip; } + void SaveStats(uint32 damageDone, uint32 healingDone, uint32 killingBlows) + { + DamageDone = damageDone; + HealingDone = healingDone; + KillingBlows = killingBlows; + } + std::string Name{}; ObjectGuid::LowType Guid{0}; uint32 Acc{0}; uint32 ArenaTeamId{0}; std::string IP{}; + uint32 DamageDone{0}; + uint32 HealingDone{0}; + uint32 KillingBlows{0}; }; enum BGHonorMode diff --git a/src/server/game/Battlegrounds/BattlegroundQueue.cpp b/src/server/game/Battlegrounds/BattlegroundQueue.cpp index 97a529327..295b8859f 100644 --- a/src/server/game/Battlegrounds/BattlegroundQueue.cpp +++ b/src/server/game/Battlegrounds/BattlegroundQueue.cpp @@ -1039,42 +1039,43 @@ void BattlegroundQueue::BattlegroundQueueAnnouncerUpdate(uint32 diff, Battlegrou return; } - if (sWorld->getBoolConfig(CONFIG_BATTLEGROUND_QUEUE_ANNOUNCER_TIMED)) - { - uint32 qPlayers = 0; + // Armed per-bracket timer drives both Timed mode and the deferred immediate + // announcement; the spam-window/Limit throttle gates only immediate mode. + bool const isTimed = sWorld->getBoolConfig(CONFIG_BATTLEGROUND_QUEUE_ANNOUNCER_TIMED); - if (_queueAnnouncementCrossfactioned) + uint32 qPlayers = 0; + + if (_queueAnnouncementCrossfactioned) + qPlayers = GetPlayersCountInGroupsQueue(bracket_id, BG_QUEUE_CFBG); + else + qPlayers = GetPlayersCountInGroupsQueue(bracket_id, BG_QUEUE_NORMAL_HORDE) + GetPlayersCountInGroupsQueue(bracket_id, BG_QUEUE_NORMAL_ALLIANCE); + + if (!qPlayers) + { + _queueAnnouncementTimer[bracket_id] = -1; + return; + } + + if (_queueAnnouncementTimer[bracket_id] >= 0) + { + if (_queueAnnouncementTimer[bracket_id] <= static_cast(diff)) { - qPlayers = GetPlayersCountInGroupsQueue(bracket_id, BG_QUEUE_CFBG); + _queueAnnouncementTimer[bracket_id] = -1; + + uint32 q_min_level = std::min(bracketEntry->minLevel, (uint32) 80); + + if (!isTimed && !sBGSpam->CanAnnounce(bg_template, bracket_id, q_min_level, qPlayers)) + return; + + auto bgName = bg_template->GetName(); + uint32 MaxPlayers = GetMinPlayersPerTeam(bg_template, bracketEntry) * 2; + uint32 q_max_level = std::min(bracketEntry->maxLevel, (uint32) 80); + + ChatHandler(nullptr).SendWorldTextOptional(LANG_BG_QUEUE_ANNOUNCE_WORLD, ANNOUNCER_FLAG_DISABLE_BG_QUEUE, bgName.c_str(), q_min_level, q_max_level, qPlayers, MaxPlayers); } else { - qPlayers = GetPlayersCountInGroupsQueue(bracket_id, BG_QUEUE_NORMAL_HORDE) + GetPlayersCountInGroupsQueue(bracket_id, BG_QUEUE_NORMAL_ALLIANCE); - } - - if (!qPlayers) - { - _queueAnnouncementTimer[bracket_id] = -1; - return; - } - - if (_queueAnnouncementTimer[bracket_id] >= 0) - { - if (_queueAnnouncementTimer[bracket_id] <= static_cast(diff)) - { - _queueAnnouncementTimer[bracket_id] = -1; - - auto bgName = bg_template->GetName(); - uint32 MaxPlayers = GetMinPlayersPerTeam(bg_template, bracketEntry) * 2; - uint32 q_min_level = std::min(bracketEntry->minLevel, (uint32) 80); - uint32 q_max_level = std::min(bracketEntry->maxLevel, (uint32) 80); - - ChatHandler(nullptr).SendWorldTextOptional(LANG_BG_QUEUE_ANNOUNCE_WORLD, ANNOUNCER_FLAG_DISABLE_BG_QUEUE, bgName.c_str(), q_min_level, q_max_level, qPlayers, MaxPlayers); - } - else - { - _queueAnnouncementTimer[bracket_id] -= static_cast(diff); - } + _queueAnnouncementTimer[bracket_id] -= static_cast(diff); } } } @@ -1145,12 +1146,10 @@ void BattlegroundQueue::SendMessageBGQueue(Player* leader, Battleground* bg, PvP } else { - if (!sBGSpam->CanAnnounce(leader, bg, q_min_level, qTotal)) - { - return; - } - - ChatHandler(nullptr).SendWorldTextOptional(LANG_BG_QUEUE_ANNOUNCE_WORLD, ANNOUNCER_FLAG_DISABLE_BG_QUEUE, bgName.c_str(), q_min_level, q_max_level, qAlliance + qHorde, MaxPlayers); + // Arm the per-bracket debounce; first join arms, rest are no-ops. + // BattlegroundQueueAnnouncerUpdate emits the aggregated line later. + if (_queueAnnouncementTimer[bracketId] < 0) + SetQueueAnnouncementTimer(bracketId, BG_QUEUE_ANNOUNCER_IMMEDIATE_DEBOUNCE, false); } } } diff --git a/src/server/game/Battlegrounds/BattlegroundQueue.h b/src/server/game/Battlegrounds/BattlegroundQueue.h index 475fae962..34575e539 100644 --- a/src/server/game/Battlegrounds/BattlegroundQueue.h +++ b/src/server/game/Battlegrounds/BattlegroundQueue.h @@ -27,6 +27,10 @@ constexpr auto COUNT_OF_PLAYERS_TO_AVERAGE_WAIT_TIME = 10; +// Immediate-mode announcer debounce (ms): fires on the next periodic pass, by +// which point a same-tick queue burst has collapsed into one aggregated line. +constexpr int32 BG_QUEUE_ANNOUNCER_IMMEDIATE_DEBOUNCE = 1; + struct GroupQueueInfo // stores information about the group in queue (also used when joined as solo!) { GuidSet Players; // player guid set diff --git a/src/server/game/Battlegrounds/BattlegroundSpamProtect.cpp b/src/server/game/Battlegrounds/BattlegroundSpamProtect.cpp index f917c61bc..ef47d0d0b 100644 --- a/src/server/game/Battlegrounds/BattlegroundSpamProtect.cpp +++ b/src/server/game/Battlegrounds/BattlegroundSpamProtect.cpp @@ -25,6 +25,7 @@ namespace { std::unordered_map _players; + std::unordered_map _brackets; void AddTime(ObjectGuid guid) { @@ -47,6 +48,26 @@ namespace // Skip if spam time < 30 secs (default) return GameTime::GetGameTime().count() - GetTime(guid) >= sWorld->getIntConfig(CONFIG_BATTLEGROUND_QUEUE_ANNOUNCER_SPAM_DELAY); } + + void AddTime(uint32 key) + { + _brackets.insert_or_assign(key, GameTime::GetGameTime().count()); + } + + uint32 GetTime(uint32 key) + { + auto const& itr = _brackets.find(key); + if (itr != _brackets.end()) + return itr->second; + + return 0; + } + + bool IsCorrectDelay(uint32 key) + { + // Skip if spam time < 30 secs (default) + return GameTime::GetGameTime().count() - GetTime(key) >= sWorld->getIntConfig(CONFIG_BATTLEGROUND_QUEUE_ANNOUNCER_SPAM_DELAY); + } } BGSpamProtect* BGSpamProtect::instance() @@ -84,3 +105,29 @@ bool BGSpamProtect::CanAnnounce(Player* player, Battleground* bg, uint32 minLeve AddTime(guid); return true; } + +bool BGSpamProtect::CanAnnounce(Battleground* bg, BattlegroundBracketId bracketId, uint32 minLevel, uint32 queueTotal) +{ + if (!bg) + return false; + + uint32 key = uint32(bg->GetBgTypeID()) * MAX_BATTLEGROUND_BRACKETS + uint32(bracketId); + + // Check prev time + if (!IsCorrectDelay(key)) + return false; + + // When limited, it announces only if there are at least CONFIG_BATTLEGROUND_QUEUE_ANNOUNCER_LIMIT_MIN_PLAYERS in queue + auto limitQueueMinLevel = sWorld->getIntConfig(CONFIG_BATTLEGROUND_QUEUE_ANNOUNCER_LIMIT_MIN_LEVEL); + if (limitQueueMinLevel && minLevel >= limitQueueMinLevel) + { + // limit only RBG for 80, WSG for lower levels + auto bgTypeToLimit = minLevel == 80 ? BATTLEGROUND_RB : BATTLEGROUND_WS; + + if (bg->GetBgTypeID() == bgTypeToLimit && queueTotal < sWorld->getIntConfig(CONFIG_BATTLEGROUND_QUEUE_ANNOUNCER_LIMIT_MIN_PLAYERS)) + return false; + } + + AddTime(key); + return true; +} diff --git a/src/server/game/Battlegrounds/BattlegroundSpamProtect.h b/src/server/game/Battlegrounds/BattlegroundSpamProtect.h index 7990deb99..ecbdcb213 100644 --- a/src/server/game/Battlegrounds/BattlegroundSpamProtect.h +++ b/src/server/game/Battlegrounds/BattlegroundSpamProtect.h @@ -19,6 +19,7 @@ #define _BATTLEGROUND_SPAM_PROTECT_H_ #include "Define.h" +#include "DBCEnums.h" class Player; class Battleground; @@ -29,6 +30,7 @@ public: static BGSpamProtect* instance(); bool CanAnnounce(Player* player, Battleground* bg, uint32 minLevel, uint32 queueTotal); + bool CanAnnounce(Battleground* bg, BattlegroundBracketId bracketId, uint32 minLevel, uint32 queueTotal); }; #define sBGSpam BGSpamProtect::instance() diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 12689393b..83ac3598f 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -12236,6 +12236,12 @@ Battleground* Player::GetBattleground(bool create) const return (create || (bg && bg->FindBgMap()) ? bg : nullptr); } +bool Player::InBattlefield() const +{ + Battlefield* bf = sBattlefieldMgr->GetBattlefieldToZoneId(GetZoneId()); + return bf && bf->IsWarTime(); +} + bool Player::InBattlegroundQueue(bool ignoreArena) const { for (uint8 i = 0; i < PLAYER_MAX_BATTLEGROUND_QUEUES; ++i) diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index dd8220702..83bdb5770 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -2293,6 +2293,7 @@ public: /*********************************************************/ [[nodiscard]] bool InBattleground() const { return m_bgData.bgInstanceID != 0; } + [[nodiscard]] bool InBattlefield() const; // True if the player is in Wintergrasp and it's war time. [[nodiscard]] bool InArena() const; [[nodiscard]] uint32 GetBattlegroundId() const { return m_bgData.bgInstanceID; } [[nodiscard]] BattlegroundTypeId GetBattlegroundTypeId() const { return m_bgData.bgTypeID; } diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index 5489b2067..ac9ce5570 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -10023,7 +10023,12 @@ bool Unit::IsImmunedToAuraPeriodicTick(Unit const* caster, SpellInfo const* spel return false; } -bool Unit::IsImmunedToSpell(SpellInfo const* spellInfo, Spell const* spell) +bool Unit::IsImmunedToSpell(SpellInfo const* spellInfo, Unit const* caster) +{ + return IsImmunedToSpell(spellInfo, caster, spellInfo ? spellInfo->GetSchoolMask() : SPELL_SCHOOL_MASK_NONE); +} + +bool Unit::IsImmunedToSpell(SpellInfo const* spellInfo, Unit const* caster, SpellSchoolMask spellSchoolMask) { if (!spellInfo) return false; @@ -10069,7 +10074,7 @@ bool Unit::IsImmunedToSpell(SpellInfo const* spellInfo, Spell const* spell) continue; // Xinef: if target is immune to one effect, and the spell has transform aura - it is immune to whole spell - if (IsImmunedToSpellEffect(spellInfo, i)) + if (IsImmunedToSpellEffect(spellInfo, i, caster)) { if (spellInfo->HasAura(SPELL_AURA_TRANSFORM)) return true; @@ -10084,13 +10089,6 @@ bool Unit::IsImmunedToSpell(SpellInfo const* spellInfo, Spell const* spell) if (!spellInfo->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES)) { - SpellSchoolMask spellSchoolMask = spellInfo->GetSchoolMask(); - Unit const* spellCaster = spell ? spell->GetCaster() : nullptr; - if (spell) - { - spellSchoolMask = spell->GetSpellSchoolMask(); - } - if (spellSchoolMask != SPELL_SCHOOL_MASK_NONE) { SpellImmuneContainer const& schoolList = m_spellImmune[IMMUNITY_SCHOOL]; @@ -10103,7 +10101,7 @@ bool Unit::IsImmunedToSpell(SpellInfo const* spellInfo, Spell const* spell) if (!(itr->first & spellSchoolMask)) continue; - if (IgnoresSchoolImmunityFromFriendlyCaster(spellCaster, itr->second, immuneSpellInfo)) + if (IgnoresSchoolImmunityFromFriendlyCaster(caster, itr->second, immuneSpellInfo)) continue; if (spellInfo->CanPierceImmuneAura(immuneSpellInfo)) @@ -10117,6 +10115,16 @@ bool Unit::IsImmunedToSpell(SpellInfo const* spellInfo, Spell const* spell) return false; } +bool Unit::IsImmunedToSpell(SpellInfo const* spellInfo, Spell const* spell) +{ + if (!spellInfo) + return false; + + Unit const* spellCaster = spell ? spell->GetCaster() : nullptr; + SpellSchoolMask spellSchoolMask = spell ? spell->GetSpellSchoolMask() : spellInfo->GetSchoolMask(); + return IsImmunedToSpell(spellInfo, spellCaster, spellSchoolMask); +} + bool Unit::IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index, Unit const* caster /*= nullptr*/) const { if (!spellInfo || !spellInfo->Effects[index].IsEffect()) diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h index 8055d7417..a6d53e117 100644 --- a/src/server/game/Entities/Unit/Unit.h +++ b/src/server/game/Entities/Unit/Unit.h @@ -1637,6 +1637,8 @@ public: // Spells immunities void ApplySpellImmune(uint32 spellId, uint32 op, uint32 type, bool apply, SpellImmuneBlockType blockType = SPELL_BLOCK_TYPE_ALL); virtual bool IsImmunedToSpell(SpellInfo const* spellInfo, Spell const* spell = nullptr); + bool IsImmunedToSpell(SpellInfo const* spellInfo, Unit const* caster); + bool IsImmunedToSpell(SpellInfo const* spellInfo, Unit const* caster, SpellSchoolMask spellSchoolMask); bool IsImmunedToSpell(SpellInfo const* spellInfo, uint32 effectMask, Unit const* caster = nullptr); bool IgnoresSchoolImmunityFromFriendlyCaster(Unit const* caster, uint32 immunityAuraId, SpellInfo const* immunitySpellInfo) const; [[nodiscard]] bool IsImmunedToDamage(SpellSchoolMask schoolMask) const; diff --git a/src/server/game/Maps/Map.cpp b/src/server/game/Maps/Map.cpp index e18b0d099..5ca2de605 100644 --- a/src/server/game/Maps/Map.cpp +++ b/src/server/game/Maps/Map.cpp @@ -1739,12 +1739,25 @@ uint32 Map::ApplyDynamicModeRespawnScaling(WorldObject const* obj, uint32 respaw if (obj->GetMap()->Instanceable()) return respawnDelay; - // No quest givers or world bosses if (Creature const* creature = obj->ToCreature()) + { + // Temporary spawns (no DB spawn id, e.g. summons / battlefield-spawned + // creatures such as Wintergrasp turrets) are not part of the respawn system. + if (!creature->GetSpawnId()) + return respawnDelay; + + // No quest givers or world bosses if (creature->IsQuestGiver() || creature->isWorldBoss() || (creature->GetCreatureTemplate()->rank == CREATURE_ELITE_RARE) || (creature->GetCreatureTemplate()->rank == CREATURE_ELITE_RAREELITE)) return respawnDelay; + } + // Temporary gameobjects (no DB spawn id) are likewise excluded. + else if (GameObject const* go = obj->ToGameObject()) + { + if (!go->GetSpawnId()) + return respawnDelay; + } auto it = _zonePlayerCountMap.find(obj->GetZoneId()); if (it == _zonePlayerCountMap.end()) diff --git a/src/server/game/Spells/Auras/SpellAuras.cpp b/src/server/game/Spells/Auras/SpellAuras.cpp index 3ca928b47..5f2b887f0 100644 --- a/src/server/game/Spells/Auras/SpellAuras.cpp +++ b/src/server/game/Spells/Auras/SpellAuras.cpp @@ -597,7 +597,7 @@ void Aura::UpdateTargetMap(Unit* caster, bool apply) if ((itr->second & (1 << effIndex)) && itr->first->IsImmunedToSpellEffect(GetSpellInfo(), effIndex, GetCaster())) itr->second &= ~(1 << effIndex); } - if (!itr->second || itr->first->IsImmunedToSpell(GetSpellInfo()) || !CanBeAppliedOn(itr->first)) + if (!itr->second || itr->first->IsImmunedToSpell(GetSpellInfo(), GetCaster()) || !CanBeAppliedOn(itr->first)) addUnit = false; if (addUnit && !itr->first->IsHighestExclusiveAura(this, true)) diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp index a66672753..a30b6c16a 100644 --- a/src/server/game/Spells/SpellEffects.cpp +++ b/src/server/game/Spells/SpellEffects.cpp @@ -2629,10 +2629,11 @@ void Spell::EffectDispel(SpellEffIndex effIndex) bool alreadyListed = false; for (DispelChargesList::iterator successItr = success_list.begin(); successItr != success_list.end(); ++successItr) { - if (successItr->first->GetId() == itr->first->GetId()) + if (successItr->first == itr->first) { ++successItr->second; alreadyListed = true; + break; } } if (!alreadyListed) diff --git a/src/server/game/Spells/SpellInfoCorrections.cpp b/src/server/game/Spells/SpellInfoCorrections.cpp index ceeb0b96d..cd00baf9f 100644 --- a/src/server/game/Spells/SpellInfoCorrections.cpp +++ b/src/server/game/Spells/SpellInfoCorrections.cpp @@ -5207,13 +5207,6 @@ void SpellMgr::LoadSpellInfoCorrections() spellInfo->Effects[EFFECT_0].BasePoints = 1; }); - // 26025 Impale - ApplySpellFix({ 26025 }, [](SpellInfo* spellInfo) - { - spellInfo->Effects[EFFECT_1].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_CONE_ENEMY_54); - spellInfo->Effects[EFFECT_1].TargetB = SpellImplicitTargetInfo(0); - }); - for (uint32 i = 0; i < GetSpellInfoStoreSize(); ++i) { SpellInfo* spellInfo = mSpellInfoMap[i]; diff --git a/src/server/scripts/EasternKingdoms/SunwellPlateau/boss_kiljaeden.cpp b/src/server/scripts/EasternKingdoms/SunwellPlateau/boss_kiljaeden.cpp index 63e45764c..0707bd1ab 100644 --- a/src/server/scripts/EasternKingdoms/SunwellPlateau/boss_kiljaeden.cpp +++ b/src/server/scripts/EasternKingdoms/SunwellPlateau/boss_kiljaeden.cpp @@ -86,6 +86,10 @@ enum Spells // Misc SPELL_ANVEENA_ENERGY_DRAIN = 46410, + SPELL_ARCANE_BOLT = 45670, + // TODO + // 45670 is an aura that triggers 45666; 45666 needs to be scripted + // SPELL_ARCANE_BOLT_DAMAGE = 45666, SPELL_RING_OF_BLUE_FLAMES = 45825, SPELL_SUMMON_BLUE_DRAKE = 45836, SPELL_VENGEANCE_OF_THE_BLUE_FLIGHT = 45839, @@ -102,7 +106,8 @@ enum Misc PHASE_SACRIFICE = 5, ACTION_START_POST_EVENT = 1, - ACTION_NO_KILL_TALK = 2 + ACTION_NO_KILL_TALK = 2, + ACTION_START_AERIAL_SUPPORT = 3 }; class CastArmageddon : public BasicEvent @@ -173,6 +178,7 @@ struct npc_kiljaeden_controller : public NullCreatureAI void JustSummoned(Creature* summon) override { summons.Summon(summon); + if (summon->GetEntry() == NPC_SINISTER_REFLECTION) { summon->m_Events.AddEventAtOffset([summon] { @@ -181,7 +187,14 @@ struct npc_kiljaeden_controller : public NullCreatureAI }, 5s); } else if (summon->GetEntry() == NPC_KALECGOS_KJ) + { summon->setActive(true); + summon->SetCanFly(true); + summon->SetDisableGravity(true); + summon->SetAnimTier(AnimTier::Fly); + summon->GetMotionMaster()->MoveWaypoint(NPC_KALECGOS_KJ * 10, true); + summon->AI()->DoAction(ACTION_START_AERIAL_SUPPORT); + } } void SummonedCreatureDies(Creature* summon, Unit*) override @@ -209,7 +222,7 @@ struct npc_kiljaeden_controller : public NullCreatureAI { me->RemoveAurasDueToSpell(SPELL_ANVEENA_ENERGY_DRAIN); me->SummonCreature(NPC_KILJAEDEN, me->GetPositionX(), me->GetPositionY(), me->GetPositionZ() + 1.5f, 4.3f, TEMPSUMMON_MANUAL_DESPAWN); - me->SummonCreature(NPC_KALECGOS_KJ, 1726.80f, 661.43f, 138.65f, 3.95f, TEMPSUMMON_MANUAL_DESPAWN); + me->SummonCreature(NPC_KALECGOS_KJ, 1734.465f, 592.5678f, 142.3971f, 4.533074f, TEMPSUMMON_MANUAL_DESPAWN); } } } @@ -705,8 +718,15 @@ struct npc_kalecgos_kj : public NullCreatureAI void DoAction(int32 param) override { - if (param == ACTION_START_POST_EVENT) + if (param == ACTION_START_AERIAL_SUPPORT) { + DoCastSelf(SPELL_ARCANE_BOLT, true); + } + else if (param == ACTION_START_POST_EVENT) + { + me->GetMotionMaster()->Clear(); + me->GetMotionMaster()->MoveIdle(); + me->RemoveAurasDueToSpell(SPELL_ARCANE_BOLT); me->SetCanFly(false); me->SetDisableGravity(false); me->CastSpell(me, SPELL_TELEPORT_AND_TRANSFORM, true); diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_thorim.cpp b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_thorim.cpp index a73aafa68..157873af3 100644 --- a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_thorim.cpp +++ b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_thorim.cpp @@ -1592,6 +1592,7 @@ public: } }; +// 63238 Lightning Pillar class spell_thorim_lightning_pillar_P2_aura : public AuraScript { PrepareAuraScript(spell_thorim_lightning_pillar_P2_aura); @@ -1609,6 +1610,29 @@ class spell_thorim_lightning_pillar_P2_aura : public AuraScript } }; +// 62976 Lightning Pillar +class spell_thorim_lightning_pillar_P2 : public SpellScript +{ + PrepareSpellScript(spell_thorim_lightning_pillar_P2); + + void SelectPillar(WorldObject*& target) + { + if (Unit* caster = GetCaster()) + { + std::list pillars; + caster->GetCreatureListWithEntryInGrid(pillars, NPC_PILLAR, GetSpellInfo()->GetMaxRange()); + + if (!pillars.empty()) + target = Acore::Containers::SelectRandomContainerElement(pillars); + } + } + + void Register() override + { + OnObjectTargetSelect += SpellObjectTargetSelectFn(spell_thorim_lightning_pillar_P2::SelectPillar, EFFECT_0, TARGET_UNIT_NEARBY_ENTRY); + } +}; + class spell_thorim_trash_impale_aura : public AuraScript { PrepareAuraScript(spell_thorim_trash_impale_aura); @@ -1679,6 +1703,7 @@ void AddSC_boss_thorim() new go_thorim_lever(); // Spells + RegisterSpellScript(spell_thorim_lightning_pillar_P2); RegisterSpellScript(spell_thorim_lightning_pillar_P2_aura); RegisterSpellScript(spell_thorim_trash_impale_aura); diff --git a/src/server/scripts/Outland/Auchindoun/ShadowLabyrinth/boss_murmur.cpp b/src/server/scripts/Outland/Auchindoun/ShadowLabyrinth/boss_murmur.cpp index 30969ea8a..4958a34f5 100644 --- a/src/server/scripts/Outland/Auchindoun/ShadowLabyrinth/boss_murmur.cpp +++ b/src/server/scripts/Outland/Auchindoun/ShadowLabyrinth/boss_murmur.cpp @@ -116,13 +116,19 @@ struct boss_murmur : public BossAI } } + void DamageTaken(Unit* attacker, uint32& damage, DamageEffectType damagetype, SpellSchoolMask damageSchoolMask) override + { + BossAI::DamageTaken(attacker, damage, damagetype, damageSchoolMask); + + if (attacker && !me->GetVictim() && attacker->IsControlledByPlayer()) + AttackStart(attacker); + } + void JustEngagedWith(Unit* who) override { // Boss engages mobs during roleplay, this checks prevents it from setting the zone in combat before players engage it. - if (who->IsPlayer() || who->IsPet() || who->IsGuardian()) - { + if (who->IsControlledByPlayer()) _JustEngagedWith(); - } scheduler.Schedule(28s, [this](TaskContext context) {