diff --git a/.github/SECURITY.md b/.github/SECURITY.md index 7feca2632..00d09000e 100644 --- a/.github/SECURITY.md +++ b/.github/SECURITY.md @@ -4,60 +4,91 @@ We support the following versions of dependencies. -:white_check_mark: = supported +| Icon | Meaning | +| :------------------- | :---------------: | +| :white_check_mark: | **Supported** | +| :red_circle: | **NOT** Supported | +| :large_blue_diamond: | **Recommended** | -:red_circle: = NOT supported +### Versions of AzerothCore: -unspecified = might work but no guarantee +| AzerothCore Branch | Status | Recommended | +| ---------------------------- | :----------------: | :------------------: | +| **master** | :white_check_mark: | :large_blue_diamond: | +| Any non-official fork | :red_circle: | | +| Any Playerbots fork | :red_circle: | | +| Any NPCBots fork | :red_circle: | | +| Any AC (non-official) repack | :red_circle: | | -Versions of AzerothCore: +### Supported Operating Systems -| AzerothCore Branch | Supported | -| ------------------ | ------------------ | -| master | :white_check_mark: | -| Any playerbot fork | :red_circle: | -| Any NPCBot fork | :red_circle: | +| Linux (Ubuntu) | Status | Recommended | +| :------------- | :----------------: | :------------------: | +| 24.04 | :white_check_mark: | :large_blue_diamond: | +| 22.04 | :white_check_mark: | | +| 20.04 ≤ | :red_circle: | | -Versions of MySQL: +| macOS | Status | Recommended | +| :---- | :----------------: | :------------------: | +| 14 | :white_check_mark: | :large_blue_diamond: | +| 12 ≤ | :red_circle: | | -| MySQL Version | Supported | -| ------------- | ------------------ | -| 8.4 | :white_check_mark: | -| 8.0 | :white_check_mark: | -| 5.7 and lower | :red_circle: | +| Windows | Status | Recommended | +| :------------ | :----------------: | :------------------: | +| Windows 11 | :white_check_mark: | :large_blue_diamond: | +| Windows 10 | :white_check_mark: | +| Windows 8.1 ≤ | :red_circle: | -Versions of CLang: +
-| CLang Version | Supported | -| ------------- | ------------------ | -| 18 | :white_check_mark: | -| 15 | :white_check_mark: | -| 14 and lower | :red_circle: | +### Supported Boost Versions: -Versions of GCC: +| Boost | Status | Recommended | +| :----- | :----------------: | :------------------: | +| 1.70 ≥ | :white_check_mark: | :large_blue_diamond: | -| GCC Version | Supported | -| ------------ | ------------------ | -| 14 | :white_check_mark: | -| 12 | :white_check_mark: | -| 11 and lower | :red_circle: | +### Supported OpenSSL Versions: -Versions of Ubuntu: +| OpenSSL | Status | Recommended | +| :------ | :----------------: | :------------------: | +| 3.X.X ≥ | :white_check_mark: | :large_blue_diamond: | -| Ubuntu version | Supported | -| --------------- | ------------------ | -| 24.04 | :white_check_mark: | -| 22.04 | :white_check_mark: | -| 20.04 and lower | :red_circle: | +### Supported CMake Versions: -Versions of macOS: +| CMake | Status | Recommended | +| :----- | :----------------: | :------------------: | +| 3.16 ≥ | :white_check_mark: | :large_blue_diamond: | -| macOS Version | Supported | -| ------------- | ------------------ | -| 14 | :white_check_mark: | -| 12 and lower | :red_circle: | +### Supported MySQL Versions: -**Note**: We do NOT support any repacks that may or may not have been made based on AzerothCore. This is because they are usually based on older versions and there is no way to know what is in the precompiled binaries. Instead, you should compile your binaries from the AzerothCore source. To get started, read the [Installation Guide](https://www.azerothcore.org/wiki/installation). +| MySQL | Status | Recommended | +| :---- | :----------------: | :------------------: | +| 8.4 ≥ | :white_check_mark: | :large_blue_diamond: | +| 8.0 | :white_check_mark: | | +| 8.1 | :red_circle: | | +| 8.0 < | :red_circle: | | + +### Supported CLang Versions: + +| CLang | Status | Recommended | +| :---- | :----------------: | :------------------: | +| 18 | :white_check_mark: | :large_blue_diamond: | +| 15 | :white_check_mark: | | +| 14 ≤ | :red_circle: | | + +### Supported GCC Versions: + +| GCC | Status | Recommended | +| :--- | :----------------: | :------------------: | +| 14 | :white_check_mark: | :large_blue_diamond: | +| 12 | :white_check_mark: | | +| 11 ≤ | :red_circle: | | + +> [!NOTE] +> We do **NOT** support any repacks that may or may not have been made based on AzerothCore. This is because they are usually based on older versions and there is no way to know what is in the precompiled binaries. Instead, you should compile your binaries from the AzerothCore source. To get started, read the [Installation Guide](https://www.azerothcore.org/wiki/installation). + +> [!CAUTION] +> [Why you should not use repacks to run your WoW server](https://www.mangosrumors.org/why-you-should-not-use-repacks-to-run-your-wow-server/) ## Reporting a Vulnerability diff --git a/apps/codestyle/codestyle-sql.py b/apps/codestyle/codestyle-sql.py index f15dceed6..0df5dec05 100644 --- a/apps/codestyle/codestyle-sql.py +++ b/apps/codestyle/codestyle-sql.py @@ -112,7 +112,7 @@ def sql_check(file: io, file_path: str) -> None: check_failed = True if "EntryOrGuid" in line: print( - f"Please use entryorguid syntax instead of EntryOrgGuid in {file_path} at line {line_number}\nWe recommend to use keira to have the right syntax in auto-query generation") + f"Please use entryorguid syntax instead of EntryOrGuid in {file_path} at line {line_number}\nWe recommend to use keira to have the right syntax in auto-query generation") check_failed = True if [match for match in [';;'] if match in line]: print( @@ -142,6 +142,8 @@ def insert_safety_check(file: io, file_path: str) -> None: # Parse all the file for line_number, line in enumerate(file, start = 1): + if line.startswith("--"): + continue if "INSERT" in line and "DELETE" not in previous_line: print(f"No DELETE keyword found after the INSERT in {file_path} at line {line_number}\nIf this error is intended, please advert a maintainer") check_failed = True @@ -163,7 +165,11 @@ def semicolon_check(file: io, file_path: str) -> None: total_lines = len(lines) for line_number, line in enumerate(lines, start=1): - stripped_line = line.rstrip() # Remove trailing whitespace including newline + if line.startswith('--'): + continue + # Remove trailing whitespace including newline + # Remove comments from the line + stripped_line = line.split('--', 1)[0].strip() # Check if one keyword is in the line if not query_open and any(keyword in stripped_line for keyword in sql_keywords): diff --git a/data/sql/updates/db_auth/2025_01_26_00.sql b/data/sql/updates/db_auth/2025_01_26_00.sql new file mode 100644 index 000000000..1c3f4f99b --- /dev/null +++ b/data/sql/updates/db_auth/2025_01_26_00.sql @@ -0,0 +1,12 @@ +-- DB update 2024_12_15_00 -> 2025_01_26_00 +DROP TABLE IF EXISTS `autobroadcast_locale`; +CREATE TABLE `autobroadcast_locale` ( + `realmid` INT NOT NULL, + `id` INT NOT NULL, + `locale` VARCHAR(4) NOT NULL, + `text` VARCHAR(45) NULL, + PRIMARY KEY (`realmid`, `id`)) +CHARSET = utf8mb4 +COLLATE = utf8mb4_unicode_ci +ENGINE = InnoDB +; diff --git a/data/sql/updates/db_world/2025_01_23_04.sql b/data/sql/updates/db_world/2025_01_23_04.sql new file mode 100644 index 000000000..42396e60d --- /dev/null +++ b/data/sql/updates/db_world/2025_01_23_04.sql @@ -0,0 +1,3 @@ +-- DB update 2025_01_23_03 -> 2025_01_23_04 +-- +UPDATE `creature_template_movement` SET `Rooted`= 1, `Flight` = 1 WHERE `CreatureId` = 24666; diff --git a/data/sql/updates/db_world/2025_01_24_00.sql b/data/sql/updates/db_world/2025_01_24_00.sql new file mode 100644 index 000000000..52974735b --- /dev/null +++ b/data/sql/updates/db_world/2025_01_24_00.sql @@ -0,0 +1,22 @@ +-- DB update 2025_01_23_04 -> 2025_01_24_00 +-- Fix Kaelthas HC +DELETE FROM `creature_loot_template` WHERE (`Entry` = 24857) AND (`Item` IN (23572, 25028, 25029, 34609, 34610, 34611, 34612, 34613, 34614, 34615, 34616)); +INSERT INTO `creature_loot_template` (`Entry`, `Item`, `Reference`, `Chance`, `QuestRequired`, `LootMode`, `GroupId`, `MinCount`, `MaxCount`, `Comment`) VALUES +(24857, 23572, 0, 100, 0, 1, 0, 1, 1, 'Kael\'thas Sunstrider (1) - Primal Nether'), +(24857, 34612, 0, 0, 0, 1, 1, 1, 1, 'Kael\'thas Sunstrider (1) - Greaves of the Penitent Knight'), +(24857, 34609, 0, 0, 0, 1, 1, 1, 1, 'Kael\'thas Sunstrider (1) - Quickening Blade of the Prince'), +(24857, 34611, 0, 0, 0, 1, 1, 1, 1, 'Kael\'thas Sunstrider (1) - Cudgel of Consecration'), +(24857, 34610, 0, 0, 0, 1, 1, 1, 1, 'Kael\'thas Sunstrider (1) - Scarlet Sin\'dorei Robes'), +(24857, 34614, 0, 0, 0, 1, 2, 1, 1, 'Kael\'thas Sunstrider (1) - Tunic of the Ranger Lord'), +(24857, 34615, 0, 0, 0, 1, 2, 1, 1, 'Kael\'thas Sunstrider (1) - Netherforce Chestplate'), +(24857, 34616, 0, 0, 0, 1, 2, 1, 1, 'Kael\'thas Sunstrider (1) - Breeching Comet'), +(24857, 34613, 0, 0, 0, 1, 2, 1, 1, 'Kael\'thas Sunstrider (1) - Shoulderpads of the Silvermoon Retainer'); + +-- Remove normal loot from Delrissa HC +DELETE FROM `creature_loot_template` WHERE (`Entry` = 25560) AND (`Item` IN (25027)); + +-- Remove normal loot from Vexallus HC +DELETE FROM `creature_loot_template` WHERE (`Entry` = 25573) AND (`Item` IN (25026)); + +-- Remove normal loot from Selin Fireheart HC +DELETE FROM `creature_loot_template` WHERE (`Entry` = 25562) AND (`Item` IN (25025)); diff --git a/data/sql/updates/db_world/2025_01_24_01.sql b/data/sql/updates/db_world/2025_01_24_01.sql new file mode 100644 index 000000000..a10c9cde7 --- /dev/null +++ b/data/sql/updates/db_world/2025_01_24_01.sql @@ -0,0 +1,4 @@ +-- DB update 2025_01_24_00 -> 2025_01_24_01 +-- +-- Remove extra Sanctum Planetarium +DELETE FROM `gameobject` WHERE (`id` = 188081) AND (`guid` IN (27809)); diff --git a/data/sql/updates/db_world/2025_01_25_00.sql b/data/sql/updates/db_world/2025_01_25_00.sql new file mode 100644 index 000000000..407ab7011 --- /dev/null +++ b/data/sql/updates/db_world/2025_01_25_00.sql @@ -0,0 +1,26 @@ +-- DB update 2025_01_24_01 -> 2025_01_25_00 +-- +DELETE FROM `smart_scripts` WHERE (`source_type` = 0 AND `entryorguid` = 24674); +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 +(24674, 0, 0, 0, 37, 0, 100, 0, 0, 0, 0, 0, 0, 0, 11, 44196, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Phoenix - On Initialize - Cast \'Rebirth\''), +(24674, 0, 1, 0, 25, 0, 100, 0, 0, 0, 0, 0, 0, 0, 42, 0, 5, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Phoenix - On Reset - Set Invincibility Hp 5%'), +(24674, 0, 2, 0, 4, 0, 100, 0, 0, 0, 0, 0, 0, 0, 11, 44197, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Phoenix - On Aggro - Cast \'Burn\''), +(24674, 0, 3, 0, 24, 0, 100, 0, 44226, 1, 5000, 5000, 0, 0, 11, 44202, 64, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Phoenix - On Target Buffed With \'Gravity Lapse\' - Cast \'Fireball\''), +(24674, 0, 4, 5, 2, 0, 100, 0, 0, 5, 10000, 10000, 0, 0, 11, 44199, 2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Phoenix - Between 0-5% Health - Cast \'Ember Blast\''), +(24674, 0, 5, 6, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 12, 24675, 3, 20000, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Phoenix - Between 0-5% Health - Summon Creature \'Phoenix Egg\''), +(24674, 0, 6, 7, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Phoenix - Between 0-5% Health - Set Reactstate Passive'), +(24674, 0, 7, 0, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 27, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Phoenix - Between 0-5% Health - Stop Combat'), +(24674, 0, 8, 9, 8, 0, 100, 0, 44199, 0, 0, 0, 0, 0, 28, 44197, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Phoenix - On Spellhit \'Ember Blast\' - Remove Aura \'Burn\''), +(24674, 0, 9, 10, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 47, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Phoenix - On Spellhit \'Ember Blast\' - Set Visibility Off'), +(24674, 0, 10, 0, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 204, 24675, 0, 0, 0, 0, 0, 0, 0, 'Phoenix - On Spellhit \'Ember Blast\' - Despawn Instant'); + +DELETE FROM `smart_scripts` WHERE (`source_type` = 0 AND `entryorguid` = 24675); +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 +(24675, 0, 0, 0, 37, 0, 100, 512, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Phoenix Egg - On Initialize - Set Reactstate Passive'), +(24675, 0, 1, 2, 60, 0, 100, 0, 15000, 15000, 0, 0, 0, 0, 142, 100, 0, 0, 0, 0, 0, 23, 0, 0, 0, 0, 0, 0, 0, 0, 'Phoenix Egg - On Update - Set HP to 100%'), +(24675, 0, 2, 3, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 28, 44199, 0, 0, 0, 0, 0, 23, 0, 0, 0, 0, 0, 0, 0, 0, 'Phoenix Egg - On Update - Remove Aura \'Ember Blast\''), +(24675, 0, 3, 4, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 8, 2, 0, 0, 0, 0, 0, 23, 0, 0, 0, 0, 0, 0, 0, 0, 'Phoenix Egg - On Update - Set Reactstate Aggressive'), +(24675, 0, 4, 5, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 38, 0, 0, 0, 0, 0, 0, 23, 0, 0, 0, 0, 0, 0, 0, 0, 'Phoenix Egg - On Update - Set In Combat With Zone'), +(24675, 0, 5, 6, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 47, 1, 0, 0, 0, 0, 0, 23, 0, 0, 0, 0, 0, 0, 0, 0, 'Phoenix Egg - On Update - Set Visibility On'), +(24675, 0, 6, 0, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Phoenix Egg - On Update - Despawn Instant'), +(24675, 0, 8, 0, 6, 0, 100, 0, 0, 0, 0, 0, 0, 0, 41, 3000, 0, 0, 0, 0, 0, 23, 0, 0, 0, 0, 0, 0, 0, 0, 'Phoenix Egg - On Just Died - Despawn In 3000 ms'); diff --git a/data/sql/updates/db_world/2025_01_26_00.sql b/data/sql/updates/db_world/2025_01_26_00.sql new file mode 100644 index 000000000..2de6ccebc --- /dev/null +++ b/data/sql/updates/db_world/2025_01_26_00.sql @@ -0,0 +1,10 @@ +-- DB update 2025_01_25_00 -> 2025_01_26_00 +-- +DROP TABLE IF EXISTS `creature_sparring`; +CREATE TABLE `creature_sparring` ( + `GUID` int unsigned NOT NULL, + `SparringPCT` float NOT NULL, + PRIMARY KEY (`GUID`), + FOREIGN KEY (`GUID`) REFERENCES creature(`guid`), + CONSTRAINT `creature_sparring_chk_1` CHECK (`SparringPCT` BETWEEN 0 AND 100) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; diff --git a/data/sql/updates/db_world/2025_01_26_01.sql b/data/sql/updates/db_world/2025_01_26_01.sql new file mode 100644 index 000000000..174e77f24 --- /dev/null +++ b/data/sql/updates/db_world/2025_01_26_01.sql @@ -0,0 +1,61 @@ +-- DB update 2025_01_26_00 -> 2025_01_26_01 + +-- Dragonflayer Vrykul +UPDATE `creature_template` SET `AIName` = 'SmartAI' WHERE `entry` = 23652; + +DELETE FROM `smart_scripts` WHERE (`source_type` = 0 AND `entryorguid` = 23652); +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 +(23652, 0, 0, 0, 8, 0, 100, 1, 43381, 0, 0, 0, 0, 0, 11, 43384, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Dragonflayer Vrykul - On Spellhit \'Plague Spray\' - Cast \'Spray Credit\' (No Repeat)'), +(23652, 0, 1, 0, 4, 0, 100, 0, 0, 0, 0, 0, 0, 0, 11, 38557, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Dragonflayer Vrykul - On Aggro - Cast \'Throw\''), +(23652, 0, 2, 0, 0, 0, 100, 0, 4000, 6000, 12000, 20000, 0, 0, 11, 43410, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Dragonflayer Vrykul - In Combat - Cast \'Chop\''), +(23652, 0, 3, 0, 0, 0, 100, 0, 25000, 30000, 30000, 45000, 0, 0, 11, 38557, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Dragonflayer Vrykul - In Combat - Cast \'Throw\''); + +-- Dragonflayer Tribesman +UPDATE `creature_template` SET `AIName` = 'SmartAI' WHERE `entry` = 23651; + +DELETE FROM `smart_scripts` WHERE (`source_type` = 0 AND `entryorguid` = 23651); +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 +(23651, 0, 0, 0, 9, 0, 100, 0, 8000, 10000, 8000, 10000, 8, 25, 11, 35570, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Dragonflayer Tribesman - Within 8-25 Range - Cast \'Charge\''), +(23651, 0, 1, 0, 0, 0, 100, 0, 12000, 18000, 50000, 60000, 0, 0, 11, 48193, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Dragonflayer Tribesman - In Combat - Cast \'Enrage\''), +(23651, 0, 2, 0, 0, 0, 100, 0, 2000, 6000, 8000, 12000, 0, 0, 11, 15496, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Dragonflayer Tribesman - In Combat - Cast \'Cleave\''); + +-- Dragonflayer Thane +UPDATE `creature_template` SET `AIName` = 'SmartAI' WHERE `entry` = 23660; + +DELETE FROM `smart_scripts` WHERE (`source_type` = 0 AND `entryorguid` = 23660); +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 +(23660, 0, 0, 0, 0, 0, 100, 0, 12000, 18000, 12000, 24000, 0, 0, 11, 11971, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Dragonflayer Thane - In Combat - Cast \'Sunder Armor\''), +(23660, 0, 1, 0, 0, 0, 100, 0, 8000, 12000, 12000, 16000, 0, 0, 11, 9080, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Dragonflayer Thane - In Combat - Cast \'Hamstring\''); + +-- Dragonflayer Death Weaver +UPDATE `creature_template` SET `AIName` = 'SmartAI' WHERE `entry` = 23658; + +DELETE FROM `smart_scripts` WHERE (`source_type` = 0 AND `entryorguid` = 23658); +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 +(23658, 0, 0, 0, 1, 0, 100, 0, 1000, 1000, 1000, 1000, 0, 0, 11, 43159, 0, 0, 0, 0, 0, 19, 24158, 30, 0, 0, 0, 0, 0, 0, 'Dragonflayer Death Weaver - Out of Combat - Cast \'Soul Infusion\''), +(23658, 0, 1, 0, 0, 0, 100, 0, 2000, 4000, 8000, 12000, 0, 0, 11, 9613, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Dragonflayer Death Weaver - In Combat - Cast \'Shadow Bolt\''), +(23658, 0, 2, 0, 0, 0, 100, 0, 6000, 8000, 12000, 16000, 0, 0, 11, 43417, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Dragonflayer Death Weaver - In Combat - Cast \'Drain Life\''); + +-- Dragonflayer Harpooner +UPDATE `creature_template` SET `AIName` = 'SmartAI' WHERE `entry` = 24635; + +DELETE FROM `smart_scripts` WHERE (`source_type` = 0 AND `entryorguid` = 24635); +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 +(24635, 0, 0, 0, 0, 0, 100, 0, 12000, 18000, 50000, 60000, 0, 0, 11, 48193, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Dragonflayer Tribesman - In Combat - Cast \'Enrage\''), +(24635, 0, 1, 0, 0, 0, 100, 0, 2000, 6000, 8000, 12000, 0, 0, 11, 43325, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Dragonflayer Tribesman - In Combat - Cast \'Oluf`s Harpoon\''); + +-- Dragonflayer Lieutenant +UPDATE `creature_template` SET `AIName` = 'SmartAI' WHERE `entry` = 24169; + +DELETE FROM `smart_scripts` WHERE (`source_type` = 0 AND `entryorguid` = 24169); +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 +(24169, 0, 0, 0, 0, 0, 100, 0, 6000, 8000, 12000, 14000, 0, 0, 11, 48245, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Dragonflayer Lieutenant - In Combat - Cast \'Head Slash\''), +(24169, 0, 1, 0, 0, 0, 100, 0, 10000, 14000, 18000, 25000, 0, 0, 11, 48250, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Dragonflayer Lieutenant - In Combat - Cast \'Risky Feint\''); + +-- Yanis the Mystic +UPDATE `creature_template` SET `AIName` = 'SmartAI' WHERE `entry` = 23932; + +DELETE FROM `smart_scripts` WHERE (`source_type` = 0 AND `entryorguid` = 23932); +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 +(23932, 0, 0, 0, 0, 0, 100, 0, 6000, 8000, 12000, 14000, 0, 0, 11, 42870, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Yanis the Mystic - In Combat - Cast \'Throw Dragonflayer Harpoon\''), +(23932, 0, 1, 0, 0, 0, 100, 0, 2000, 4000, 16000, 18000, 0, 0, 11, 58747, 0, 0, 0, 0, 0, 5, 25, 0, 0, 0, 0, 0, 0, 0, 'Yanis the Mystic - In Combat - Cast \'Intercept\''); diff --git a/data/sql/updates/db_world/2025_01_26_02.sql b/data/sql/updates/db_world/2025_01_26_02.sql new file mode 100644 index 000000000..db59d9c04 --- /dev/null +++ b/data/sql/updates/db_world/2025_01_26_02.sql @@ -0,0 +1,9 @@ +-- DB update 2025_01_26_01 -> 2025_01_26_02 +-- +-- Updates the Action from "Action Invoker" (7) to "Self" (1). +UPDATE `smart_scripts` SET `target_type` = 1 WHERE `entryorguid` = 9456 AND `source_type` = 0 AND `id` = 1; + +-- Adds Strike sniffed from: 4.4.1.58158 and 1.15.5.57979 +DELETE FROM `smart_scripts` WHERE `entryorguid` = 9456 AND `source_type` = 0 AND `id` = 0; +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 +(9456, 0, 0, 0, 0, 0, 100, 0, 0, 1000, 4000, 6000, 0, 0, 11, 11976, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Warlord Krom\'zar - In Combat - Cast \'Strike\''); diff --git a/data/sql/updates/db_world/2025_01_27_00.sql b/data/sql/updates/db_world/2025_01_27_00.sql new file mode 100644 index 000000000..a4d0d0138 --- /dev/null +++ b/data/sql/updates/db_world/2025_01_27_00.sql @@ -0,0 +1,6 @@ +-- DB update 2025_01_26_02 -> 2025_01_27_00 +-- +DELETE FROM `acore_string` WHERE `entry` IN (56, 82); +INSERT INTO `acore_string` (`entry`, `content_default`, `locale_deDE`, `locale_zhCN`, `locale_esES`, `locale_esMX`) VALUES +(56, 'Current Message of the day:', 'Aktuelle Nachricht des Tages:', '当前每日信息:', 'Mensaje actual del día:', 'Mensaje actual del día:'), +(82, '{}: {}', '{}: {}', '{}: {}', '{}: {}', '{}: {}'); diff --git a/data/sql/updates/db_world/2025_01_27_01.sql b/data/sql/updates/db_world/2025_01_27_01.sql new file mode 100644 index 000000000..0891dbedf --- /dev/null +++ b/data/sql/updates/db_world/2025_01_27_01.sql @@ -0,0 +1,27 @@ +-- DB update 2025_01_27_00 -> 2025_01_27_01 +-- +UPDATE `creature_template` SET `flags_extra` = `flags_extra` |134217728 WHERE `entry` IN (24698, 24684, 24697, 24696, 24683, 24686); + +DELETE FROM `smart_scripts` WHERE (`entryorguid` = -96841) AND (`source_type` = 0) AND (`id` IN (0)); +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 +(-96841, 0, 0, 0, 6, 0, 100, 0, 0, 0, 0, 0, 0, 0, 223, 0, 0, 0, 0, 0, 0, 205, 3, 1, 0, 0, 0, 0, 0, 0, 'Coilskar Witch - On Just Died - Felblood Kaeltas Do Action ID 0'); + +DELETE FROM `smart_scripts` WHERE (`entryorguid` = -96781) AND (`source_type` = 0) AND (`id` IN (0)); +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 +(-96781, 0, 0, 0, 6, 0, 100, 0, 0, 0, 0, 0, 0, 0, 223, 0, 0, 0, 0, 0, 0, 205, 3, 1, 0, 0, 0, 0, 0, 0, 'Sunblade Blood Knight - On Just Died - Felblood Kaeltas Do Action ID 0'); + +DELETE FROM `smart_scripts` WHERE (`entryorguid` = -96809) AND (`source_type` = 0) AND (`id` IN (0)); +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 +(-96809, 0, 0, 0, 6, 0, 100, 0, 0, 0, 0, 0, 0, 0, 223, 0, 0, 0, 0, 0, 0, 205, 3, 1, 0, 0, 0, 0, 0, 0, 'Sunblade Warlock - On Just Died - Felblood Kaeltas Do Action ID 0'); + +DELETE FROM `smart_scripts` WHERE (`entryorguid` = -96770) AND (`source_type` = 0) AND (`id` IN (0)); +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 +(-96770, 0, 0, 0, 6, 0, 100, 0, 0, 0, 0, 0, 0, 0, 223, 0, 0, 0, 0, 0, 0, 205, 3, 1, 0, 0, 0, 0, 0, 0, 'Sunblade Mage Guard - On Just Died - Felblood Kaeltas Do Action ID 0'); + +DELETE FROM `smart_scripts` WHERE (`entryorguid` = -96850) AND (`source_type` = 0) AND (`id` IN (0)); +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 +(-96850, 0, 0, 0, 6, 0, 100, 0, 0, 0, 0, 0, 0, 0, 223, 0, 0, 0, 0, 0, 0, 205, 3, 1, 0, 0, 0, 0, 0, 0, 'Ethereum Smuggler - On Just Died - Felblood Kaeltas Do Action ID 0'); + +DELETE FROM `smart_scripts` WHERE (`entryorguid` = -96847) AND (`source_type` = 0) AND (`id` IN (0)); +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 +(-96847, 0, 0, 0, 6, 0, 100, 0, 0, 0, 0, 0, 0, 0, 223, 0, 0, 0, 0, 0, 0, 205, 3, 1, 0, 0, 0, 0, 0, 0, 'Sister of Torment - On Just Died - Felblood Kaeltas Do Action ID 0'); diff --git a/data/sql/updates/db_world/2025_01_27_02.sql b/data/sql/updates/db_world/2025_01_27_02.sql new file mode 100644 index 000000000..29f1c98cb --- /dev/null +++ b/data/sql/updates/db_world/2025_01_27_02.sql @@ -0,0 +1,8 @@ +-- DB update 2025_01_27_01 -> 2025_01_27_02 +-- Fix Amani Trainer Abilities +UPDATE `creature_template` SET `AIName` = 'SmartAI' WHERE `entry` = 23774; + +DELETE FROM `smart_scripts` WHERE (`entryorguid` = 23774) AND (`source_type` = 0) AND (`id` IN (0, 1)); +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 +(23774, 0, 0, 0, 0, 0, 100, 0, 5000, 8000, 12000, 15000, 0, 0, 11, 43292, 0, 0, 0, 0, 0, 19, 23834, 30, 0, 0, 0, 0, 0, 0, 'Target Amashi Dragonhawk for Incite Rage'), +(23774, 0, 1, 0, 0, 0, 100, 0, 10000, 12000, 20000, 25000, 0, 0, 11, 20989, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Cast Sleep on Random Enemy'); diff --git a/data/sql/updates/db_world/2025_01_28_00.sql b/data/sql/updates/db_world/2025_01_28_00.sql new file mode 100644 index 000000000..4b0445d8e --- /dev/null +++ b/data/sql/updates/db_world/2025_01_28_00.sql @@ -0,0 +1,8 @@ +-- DB update 2025_01_27_02 -> 2025_01_28_00 + +UPDATE `creature_template` SET `AIName` = 'SmartAI' WHERE `entry` = 23774; + +DELETE FROM `smart_scripts` WHERE (`source_type` = 0 AND `entryorguid` = 23774); +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 +(23774, 0, 0, 0, 0, 0, 100, 0, 5000, 8000, 12000, 15000, 0, 0, 11, 43292, 0, 0, 0, 0, 0, 19, 23834, 30, 0, 0, 0, 0, 0, 0, 'Amani\'shi Trainer - In Combat - Cast \'Incite Rage\''), +(23774, 0, 1, 0, 0, 0, 100, 0, 10000, 12000, 20000, 25000, 0, 0, 11, 20989, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Amani\'shi Trainer - In Combat - Cast \'Sleep\''); diff --git a/src/common/Common.cpp b/src/common/Common.cpp index e25611e06..530dec961 100644 --- a/src/common/Common.cpp +++ b/src/common/Common.cpp @@ -30,13 +30,20 @@ char const* localeNames[TOTAL_LOCALES] = "ruRU" }; +bool IsLocaleValid(std::string const& locale) +{ + for (int i = 0; i < TOTAL_LOCALES; ++i) + if (locale == localeNames[i]) + return true; + + return false; +} + LocaleConstant GetLocaleByName(const std::string& name) { for (uint32 i = 0; i < TOTAL_LOCALES; ++i) if (name == localeNames[i]) - { return LocaleConstant(i); - } return LOCALE_enUS; // including enGB case } diff --git a/src/common/Common.h b/src/common/Common.h index 6b7f482db..87ec6b4d2 100644 --- a/src/common/Common.h +++ b/src/common/Common.h @@ -83,6 +83,7 @@ enum LocaleConstant AC_COMMON_API extern char const* localeNames[TOTAL_LOCALES]; +AC_COMMON_API bool IsLocaleValid(std::string const& locale); AC_COMMON_API LocaleConstant GetLocaleByName(const std::string& name); AC_COMMON_API const std::string GetNameByLocaleConstant(LocaleConstant localeConstant); AC_COMMON_API void CleanStringForMysqlQuery(std::string& str); diff --git a/src/server/database/Database/Implementation/LoginDatabase.cpp b/src/server/database/Database/Implementation/LoginDatabase.cpp index ca56f4ac9..f552c8175 100644 --- a/src/server/database/Database/Implementation/LoginDatabase.cpp +++ b/src/server/database/Database/Implementation/LoginDatabase.cpp @@ -116,10 +116,11 @@ void LoginDatabaseConnection::DoPrepareStatements() PrepareStatement(LOGIN_SEL_REALMLIST_SECURITY_LEVEL, "SELECT allowedSecurityLevel from realmlist WHERE id = ?", CONNECTION_SYNCH); PrepareStatement(LOGIN_DEL_ACCOUNT, "DELETE FROM account WHERE id = ?", CONNECTION_ASYNC); PrepareStatement(LOGIN_SEL_AUTOBROADCAST, "SELECT id, weight, text FROM autobroadcast WHERE realmid = ? OR realmid = -1", CONNECTION_SYNCH); + PrepareStatement(LOGIN_SEL_AUTOBROADCAST_LOCALIZED, "SELECT id, locale, text FROM autobroadcast_locale WHERE realmid = ? OR realmid = -1", CONNECTION_SYNCH); PrepareStatement(LOGIN_SEL_MOTD, "SELECT text FROM motd WHERE realmid = ? OR realmid = -1 ORDER BY realmid DESC", CONNECTION_SYNCH); PrepareStatement(LOGIN_SEL_MOTD_LOCALE, "SELECT locale, text FROM motd_localized WHERE realmid = ? OR realmid = -1 ORDER BY realmid DESC", CONNECTION_SYNCH); - PrepareStatement(LOGIN_INS_MOTD, "INSERT INTO motd (realmid, text) VALUES (?, ?) ON DUPLICATE KEY UPDATE text = ?", CONNECTION_ASYNC); - PrepareStatement(LOGIN_INS_MOTD_LOCALE, "INSERT INTO motd_localized (realmid, locale, text) VALUES(?, ?, ?) ON DUPLICATE KEY UPDATE text = ?;", CONNECTION_ASYNC); + PrepareStatement(LOGIN_REP_MOTD, "REPLACE INTO motd (realmid, text) VALUES (?, ?)", CONNECTION_ASYNC); + PrepareStatement(LOGIN_REP_MOTD_LOCALE, "REPLACE INTO motd_localized (realmid, locale, text) VALUES (?, ?, ?)", CONNECTION_ASYNC); PrepareStatement(LOGIN_INS_ACCOUNT_MUTE, "INSERT INTO account_muted VALUES (?, UNIX_TIMESTAMP(), ?, ?, ?)", CONNECTION_ASYNC); PrepareStatement(LOGIN_SEL_ACCOUNT_MUTE_INFO, "SELECT mutedate, mutetime, mutereason, mutedby FROM account_muted WHERE guid = ? ORDER BY mutedate ASC", CONNECTION_SYNCH); PrepareStatement(LOGIN_DEL_ACCOUNT_MUTED, "DELETE FROM account_muted WHERE guid = ?", CONNECTION_ASYNC); diff --git a/src/server/database/Database/Implementation/LoginDatabase.h b/src/server/database/Database/Implementation/LoginDatabase.h index ad1ceec8f..c99ee5902 100644 --- a/src/server/database/Database/Implementation/LoginDatabase.h +++ b/src/server/database/Database/Implementation/LoginDatabase.h @@ -98,10 +98,11 @@ enum LoginDatabaseStatements : uint32 LOGIN_SEL_REALMLIST_SECURITY_LEVEL, LOGIN_DEL_ACCOUNT, LOGIN_SEL_AUTOBROADCAST, + LOGIN_SEL_AUTOBROADCAST_LOCALIZED, LOGIN_SEL_MOTD, LOGIN_SEL_MOTD_LOCALE, - LOGIN_INS_MOTD, - LOGIN_INS_MOTD_LOCALE, + LOGIN_REP_MOTD, + LOGIN_REP_MOTD_LOCALE, LOGIN_SEL_LAST_ATTEMPT_IP, LOGIN_SEL_LAST_IP, LOGIN_INS_ALDL_IP_LOGGING, diff --git a/src/server/game/Autobroadcast/AutobroadcastMgr.cpp b/src/server/game/Autobroadcast/AutobroadcastMgr.cpp index 8b31bf893..a7756c658 100644 --- a/src/server/game/Autobroadcast/AutobroadcastMgr.cpp +++ b/src/server/game/Autobroadcast/AutobroadcastMgr.cpp @@ -42,7 +42,6 @@ void AutobroadcastMgr::LoadAutobroadcasts() if (!result) { LOG_WARN("autobroadcast", ">> Loaded 0 autobroadcasts definitions. DB table `autobroadcast` is empty for this realm!"); - LOG_INFO("autobroadcast", " "); return; } @@ -54,35 +53,66 @@ void AutobroadcastMgr::LoadAutobroadcasts() _announceType = AnnounceType::World; } - uint32 count = 0; + do + { + Field* fields = result->Fetch(); + uint8 textId = fields[0].Get(); + + ObjectMgr::AddLocaleString(fields[2].Get(), DEFAULT_LOCALE, _autobroadcasts[textId]); + _autobroadcastsWeights[textId] = fields[1].Get(); + + } while (result->NextRow()); + + LOG_INFO("server.loading", ">> Loaded {} Autobroadcast Definitions in {} ms", _autobroadcasts.size(), GetMSTimeDiffToNow(oldMSTime)); +} + +void AutobroadcastMgr::LoadAutobroadcastsLocalized() +{ + uint32 oldMSTime = getMSTime(); + uint32 realmId = sConfigMgr->GetOption("RealmID", 0); + + if (_autobroadcasts.empty()) + return; + + LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_AUTOBROADCAST_LOCALIZED); + stmt->SetData(0, realmId); + PreparedQueryResult result = LoginDatabase.Query(stmt); + + if (!result) + { + LOG_WARN("server.loading", ">> Loaded 0 localized autobroadcasts definitions. DB table `autobroadcast_localized` is empty for this realm!"); + LOG_INFO("server.loading", " "); + return; + } + + uint8 count = 0; do { Field* fields = result->Fetch(); - uint8 id = fields[0].Get(); + uint8 textId = fields[0].Get(); + LocaleConstant locale = GetLocaleByName(fields[1].Get()); - _autobroadcasts[id] = fields[2].Get(); - _autobroadcastsWeights[id] = fields[1].Get(); + if (locale == DEFAULT_LOCALE || ObjectMgr::GetLocaleString(_autobroadcasts[textId], DEFAULT_LOCALE).empty()) + continue; - ++count; + ObjectMgr::AddLocaleString(fields[2].Get(), locale, _autobroadcasts[textId]); + count++; } while (result->NextRow()); - LOG_INFO("autobroadcast", ">> Loaded {} Autobroadcast Definitions in {} ms", count, GetMSTimeDiffToNow(oldMSTime)); - LOG_INFO("autobroadcast", " "); + LOG_INFO("server.loading", ">> Loaded {} Localized Autobroadcast Definitions in {} ms", count, GetMSTimeDiffToNow(oldMSTime)); + LOG_INFO("server.loading", " "); } void AutobroadcastMgr::SendAutobroadcasts() { if (_autobroadcasts.empty()) - { return; - } uint32 weight = 0; + uint8 textId = 0; AutobroadcastsWeightMap selectionWeights; - std::string msg; - for (AutobroadcastsWeightMap::const_iterator it = _autobroadcastsWeights.begin(); it != _autobroadcastsWeights.end(); ++it) { if (it->second) @@ -101,42 +131,78 @@ void AutobroadcastMgr::SendAutobroadcasts() weight += it->second; if (selectedWeight < weight) { - msg = _autobroadcasts[it->first]; + textId = it->first; break; } } } else { - msg = _autobroadcasts[urand(0, _autobroadcasts.size())]; + textId = urand(0, _autobroadcasts.size()); } switch (_announceType) { case AnnounceType::World: - SendWorldAnnouncement(msg); + SendWorldAnnouncement(textId); break; case AnnounceType::Notification: - SendNotificationAnnouncement(msg); + SendNotificationAnnouncement(textId); break; case AnnounceType::Both: - SendWorldAnnouncement(msg); - SendNotificationAnnouncement(msg); + SendWorldAnnouncement(textId); + SendNotificationAnnouncement(textId); default: break; } - LOG_DEBUG("autobroadcast", "AutobroadcastMgr::SendAutobroadcasts: '{}'", msg); + LOG_DEBUG("autobroadcast", "AutobroadcastMgr::SendAutobroadcasts: '{}'", textId); } -void AutobroadcastMgr::SendWorldAnnouncement(std::string msg) +void AutobroadcastMgr::SendWorldAnnouncement(uint8 textId) { - ChatHandler(nullptr).SendWorldTextOptional(LANG_AUTO_BROADCAST, ANNOUNCER_FLAG_DISABLE_AUTOBROADCAST, msg.data()); + // Send localized messages to all sessions + ChatHandler(nullptr).DoForAllValidSessions([&](Player* player) + { + // Get player's locale + LocaleConstant locale = player->GetSession()->GetSessionDbLocaleIndex(); + + if (!_autobroadcasts.empty()) + return; + + std::string_view localizedMessage = ObjectMgr::GetLocaleString(_autobroadcasts[textId], locale); + + // Check if there is a localized message if not use default one. + if (localizedMessage.empty()) + localizedMessage = ObjectMgr::GetLocaleString(_autobroadcasts[textId], DEFAULT_LOCALE); + + // Send the localized or fallback message + ChatHandler(player->GetSession()).SendWorldTextOptional(localizedMessage, ANNOUNCER_FLAG_DISABLE_AUTOBROADCAST); + }); } -void AutobroadcastMgr::SendNotificationAnnouncement(std::string msg) +void AutobroadcastMgr::SendNotificationAnnouncement(uint8 textId) { - WorldPacket data(SMSG_NOTIFICATION, (msg.size() + 1)); - data << msg.data(); - sWorld->SendGlobalMessage(&data); + ChatHandler(nullptr).DoForAllValidSessions([&](Player* player) + { + // Retrieve player's locale + LocaleConstant locale = player->GetSession()->GetSessionDbLocaleIndex(); + + if (!_autobroadcasts.count(textId)) + return; + + // Get localized message + std::string_view localizedMessage = ObjectMgr::GetLocaleString(_autobroadcasts[textId], locale); + + // Check if there is a localized message if not use default one. + if (localizedMessage.empty()) + localizedMessage = ObjectMgr::GetLocaleString(_autobroadcasts[textId], DEFAULT_LOCALE); + + // Prepare the WorldPacket + WorldPacket data(SMSG_NOTIFICATION, (localizedMessage.size() + 1)); + data << localizedMessage; + + // Send packet to the player + player->GetSession()->SendPacket(&data); + }); } diff --git a/src/server/game/Autobroadcast/AutobroadcastMgr.h b/src/server/game/Autobroadcast/AutobroadcastMgr.h index 3f4066e91..05dc6319e 100644 --- a/src/server/game/Autobroadcast/AutobroadcastMgr.h +++ b/src/server/game/Autobroadcast/AutobroadcastMgr.h @@ -20,6 +20,7 @@ #include "Common.h" #include +#include enum class AnnounceType : uint8 { @@ -34,17 +35,18 @@ public: static AutobroadcastMgr* instance(); void LoadAutobroadcasts(); + void LoadAutobroadcastsLocalized(); void SendAutobroadcasts(); private: - void SendWorldAnnouncement(std::string msg); - void SendNotificationAnnouncement(std::string msg); + void SendWorldAnnouncement(uint8 textId); + void SendNotificationAnnouncement(uint8 textId); - typedef std::map AutobroadcastsMap; + typedef std::map> AutobroadcastsMap; typedef std::map AutobroadcastsWeightMap; - AutobroadcastsMap _autobroadcasts; - AutobroadcastsWeightMap _autobroadcastsWeights; + AutobroadcastsMap _autobroadcasts; // autobroadcast messages + AutobroadcastsWeightMap _autobroadcastsWeights; // Weights for each message AnnounceType _announceType; }; diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index 25d7756b7..3d56498f0 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -273,7 +273,7 @@ Creature::Creature(bool isWorldObject): Unit(isWorldObject), MovableMapObject(), m_transportCheckTimer(1000), lootPickPocketRestoreTime(0), m_combatPulseTime(0), m_combatPulseDelay(0), m_reactState(REACT_AGGRESSIVE), m_defaultMovementType(IDLE_MOTION_TYPE), m_spawnId(0), m_equipmentId(0), m_originalEquipmentId(0), m_AlreadyCallAssistance(false), m_AlreadySearchedAssistance(false), m_regenHealth(true), m_regenPower(true), m_AI_locked(false), m_meleeDamageSchoolMask(SPELL_SCHOOL_MASK_NORMAL), m_originalEntry(0), m_moveInLineOfSightDisabled(false), m_moveInLineOfSightStrictlyDisabled(false), - m_homePosition(), m_transportHomePosition(), m_creatureInfo(nullptr), m_creatureData(nullptr), m_detectionDistance(20.0f), m_waypointID(0), m_path_id(0), m_formation(nullptr), m_lastLeashExtensionTime(nullptr), m_cannotReachTimer(0), + m_homePosition(), m_transportHomePosition(), m_creatureInfo(nullptr), m_creatureData(nullptr), m_detectionDistance(20.0f),_sparringPct(0.0f), m_waypointID(0), m_path_id(0), m_formation(nullptr), m_lastLeashExtensionTime(nullptr), m_cannotReachTimer(0), _isMissingSwimmingFlagOutOfCombat(false), m_assistanceTimer(0), _playerDamageReq(0), _damagedByPlayer(false), _isCombatMovementAllowed(true) { m_regenTimer = CREATURE_REGEN_INTERVAL; @@ -608,6 +608,8 @@ bool Creature::UpdateEntry(uint32 Entry, const CreatureData* data, bool changele SetCanModifyStats(true); UpdateAllStats(); + LoadSparringPct(); + // checked and error show at loading templates if (FactionTemplateEntry const* factionTemplate = sFactionTemplateStore.LookupEntry(cInfo->faction)) { @@ -1189,6 +1191,7 @@ bool Creature::Create(ObjectGuid::LowType guidlow, Map* map, uint32 phaseMask, u } LoadCreaturesAddon(); + LoadSparringPct(); //! Need to be called after LoadCreaturesAddon - MOVEMENTFLAG_HOVER is set there m_positionZ += GetHoverHeight(); @@ -2025,6 +2028,8 @@ void Creature::setDeathState(DeathState state, bool despawn) Motion_Initialize(); LoadCreaturesAddon(true); + LoadSparringPct(); + if (GetCreatureData() && GetPhaseMask() != GetCreatureData()->phaseMask) SetPhaseMask(GetCreatureData()->phaseMask, false); } @@ -2798,6 +2803,18 @@ bool Creature::LoadCreaturesAddon(bool reload) return true; } +void Creature::LoadSparringPct() +{ + ObjectGuid::LowType spawnId = GetSpawnId(); + auto const& sparringData = sObjectMgr->GetSparringData(); + + auto itr = sparringData.find(spawnId); + if (itr != sparringData.end() && !itr->second.empty()) + { + _sparringPct = itr->second[0]; + } +} + /// Send a message to LocalDefense channel for players opposition team in the zone void Creature::SendZoneUnderAttackMessage(Player* attacker) { diff --git a/src/server/game/Entities/Creature/Creature.h b/src/server/game/Entities/Creature/Creature.h index 17f69f801..b9632748b 100644 --- a/src/server/game/Entities/Creature/Creature.h +++ b/src/server/game/Entities/Creature/Creature.h @@ -187,6 +187,9 @@ public: void UpdateAttackPowerAndDamage(bool ranged = false) override; void CalculateMinMaxDamage(WeaponAttackType attType, bool normalized, bool addTotalPct, float& minDamage, float& maxDamage, uint8 damageIndex) override; + void LoadSparringPct(); + [[nodiscard]] float GetSparringPct() const { return _sparringPct; } + bool HasWeapon(WeaponAttackType type) const override; bool HasWeaponForAttack(WeaponAttackType type) const override { return (Unit::HasWeaponForAttack(type) && HasWeapon(type)); } void SetCanDualWield(bool value) override; @@ -483,6 +486,8 @@ protected: float m_detectionDistance; uint16 m_LootMode; // bitmask, default LOOT_MODE_DEFAULT, determines what loot will be lootable + float _sparringPct; + [[nodiscard]] bool IsInvisibleDueToDespawn() const override; bool CanAlwaysSee(WorldObject const* obj) const override; bool IsAlwaysDetectableFor(WorldObject const* seer) const override; diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index 3facbbad0..953bda4cf 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -1032,6 +1032,17 @@ uint32 Unit::DealDamage(Unit* attacker, Unit* victim, uint32 damage, CleanDamage } } + // Sparring + if (victim->CanSparringWith(attacker)) + { + if (damage >= victim->GetHealth()) + damage = 0; + + uint32 sparringHealth = victim->GetHealth() * (victim->ToCreature()->GetSparringPct() / 100); + if (victim->GetHealth() - damage <= sparringHealth) + damage = 0; + } + if (health <= damage) { LOG_DEBUG("entities.unit", "DealDamage: victim just died"); @@ -2635,6 +2646,10 @@ void Unit::AttackerStateUpdate(Unit* victim, WeaponAttackType attType /*= BASE_A Unit::DealDamageMods(victim, damageInfo.damages[i].damage, &damageInfo.damages[i].absorb); } + // Related to sparring system. Allow attack animations even if there are no damages + if (victim->CanSparringWith(damageInfo.attacker)) + damageInfo.HitInfo |= HITINFO_FAKE_DAMAGE; + SendAttackStateUpdate(&damageInfo); //TriggerAurasProcOnEvent(damageInfo); @@ -3954,6 +3969,24 @@ void Unit::_UpdateAutoRepeatSpell() } } +bool Unit::CanSparringWith(Unit const* attacker) const +{ + if (!IsCreature() || IsCharmedOwnedByPlayerOrPlayer()) + return false; + + if (!attacker) + return false; + + if (!attacker->IsCreature() || attacker->IsCharmedOwnedByPlayerOrPlayer()) + return false; + + if (Creature const* creature = ToCreature()) + if (!creature->GetSparringPct()) + return false; + + return true; +} + void Unit::SetCurrentCastedSpell(Spell* pSpell) { ASSERT(pSpell); // nullptr may be never passed here, use InterruptSpell or InterruptNonMeleeSpells diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h index b6d4179f3..004c11c34 100644 --- a/src/server/game/Entities/Unit/Unit.h +++ b/src/server/game/Entities/Unit/Unit.h @@ -726,6 +726,9 @@ public: void RemoveUnitFlag2(UnitFlags2 flags) { RemoveFlag(UNIT_FIELD_FLAGS_2, flags); } void ReplaceAllUnitFlags2(UnitFlags2 flags) { SetUInt32Value(UNIT_FIELD_FLAGS_2, flags); } + void SetEmoteState(Emote emoteState) { SetUInt32Value(UNIT_NPC_EMOTESTATE, emoteState); } /// @brief Sets emote state (looping emote). Emotes available in SharedDefines.h + void ClearEmoteState() { SetEmoteState(EMOTE_ONESHOT_NONE); } /// @brief Clears emote state (looping emote) + // NPC flags NPCFlags GetNpcFlags() const { return NPCFlags(GetUInt32Value(UNIT_NPC_FLAGS)); } bool HasNpcFlag(NPCFlags flags) const { return HasFlag(UNIT_NPC_FLAGS, flags) != 0; } @@ -2044,6 +2047,8 @@ protected: void _UpdateAutoRepeatSpell(); + bool CanSparringWith(Unit const* attacker) const; ///@brief: Check if unit is eligible for sparring damages. Work only if attacker and victim are creatures. + bool IsAlwaysVisibleFor(WorldObject const* seer) const override; bool IsAlwaysDetectableFor(WorldObject const* seer) const override; diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp index f2bc1fe4b..1dc3eeed3 100644 --- a/src/server/game/Globals/ObjectMgr.cpp +++ b/src/server/game/Globals/ObjectMgr.cpp @@ -2298,6 +2298,42 @@ void ObjectMgr::LoadCreatures() LOG_INFO("server.loading", " "); } +void ObjectMgr::LoadCreatureSparring() +{ + uint32 oldMSTime = getMSTime(); + + QueryResult result = WorldDatabase.Query("SELECT GUID, SparringPCT FROM creature_sparring"); + + if (!result) + { + LOG_WARN("server.loading", ">> Loaded 0 sparring data. DB table `creature_sparring` is empty."); + LOG_INFO("server.loading", " "); + return; + } + + uint32 count = 0; + do + { + Field* fields = result->Fetch(); + + ObjectGuid::LowType spawnId = fields[0].Get(); + float sparringHealthPct = fields[1].Get(); + + if (!sObjectMgr->GetCreatureData(spawnId)) + { + LOG_ERROR("sql.sql", "Entry {} has a record in `creature_sparring` but doesn't exist in `creatures` table"); + continue; + } + + _creatureSparringStore[spawnId].push_back(sparringHealthPct); + + ++count; + } while (result->NextRow()); + + LOG_INFO("server.loading", ">> Loaded {} sparring data in {} ms", count, GetMSTimeDiffToNow(oldMSTime)); + LOG_INFO("server.loading", " "); +} + void ObjectMgr::AddCreatureToGrid(ObjectGuid::LowType guid, CreatureData const* data) { uint8 mask = data->spawnMask; diff --git a/src/server/game/Globals/ObjectMgr.h b/src/server/game/Globals/ObjectMgr.h index c9d799610..abcc985b8 100644 --- a/src/server/game/Globals/ObjectMgr.h +++ b/src/server/game/Globals/ObjectMgr.h @@ -751,6 +751,8 @@ public: typedef std::map CharacterConversionMap; + typedef std::unordered_map> CreatureSparringContainer; + GameObjectTemplate const* GetGameObjectTemplate(uint32 entry); bool IsGameObjectStaticTransport(uint32 entry); [[nodiscard]] GameObjectTemplateContainer const* GetGameObjectTemplates() const { return &_gameObjectTemplateStore; } @@ -1029,6 +1031,7 @@ public: void LoadCreatureQuestItems(); void LoadTempSummons(); void LoadCreatures(); + void LoadCreatureSparring(); void LoadLinkedRespawn(); bool SetCreatureLinkedRespawn(ObjectGuid::LowType guid, ObjectGuid::LowType linkedGuid); void LoadCreatureAddons(); @@ -1202,6 +1205,9 @@ public: if (itr == _creatureDataStore.end()) return nullptr; return &itr->second; } + + [[nodiscard]] CreatureSparringContainer const& GetSparringData() const { return _creatureSparringStore; } + CreatureData& NewOrExistCreatureData(ObjectGuid::LowType spawnId) { return _creatureDataStore[spawnId]; } void DeleteCreatureData(ObjectGuid::LowType spawnId); [[nodiscard]] ObjectGuid GetLinkedRespawnGuid(ObjectGuid guid) const @@ -1527,6 +1533,8 @@ private: PageTextContainer _pageTextStore; InstanceTemplateContainer _instanceTemplateStore; + CreatureSparringContainer _creatureSparringStore; + private: void LoadScripts(ScriptsType type); void LoadQuestRelationsHelper(QuestRelations& map, std::string const& table, bool starter, bool go); diff --git a/src/server/game/Handlers/AuctionHouseHandler.cpp b/src/server/game/Handlers/AuctionHouseHandler.cpp index d450a3db4..505ad62d6 100644 --- a/src/server/game/Handlers/AuctionHouseHandler.cpp +++ b/src/server/game/Handlers/AuctionHouseHandler.cpp @@ -749,6 +749,9 @@ void WorldSession::HandleAuctionListItems(WorldPacket& recvData) if (usable) { AuctionHouseUsablePlayerInfo usablePlayerInfo; + usablePlayerInfo.classMask = GetPlayer()->getClassMask(); + usablePlayerInfo.raceMask = GetPlayer()->getRaceMask(); + usablePlayerInfo.level = GetPlayer()->GetLevel(); SkillStatusMap const& skillMap = GetPlayer()->GetSkillStatusMap(); for (auto const& pair : skillMap) diff --git a/src/server/game/Miscellaneous/Language.h b/src/server/game/Miscellaneous/Language.h index 2911adad2..fe1c85faf 100644 --- a/src/server/game/Miscellaneous/Language.h +++ b/src/server/game/Miscellaneous/Language.h @@ -111,7 +111,8 @@ enum AcoreStrings LANG_RBAC_PERM_REVOKED_NOT_IN_LIST = 79, LANG_PVPSTATS = 80, LANG_PVPSTATS_DISABLED = 81, - // Free 82 - 86 + LANG_GENERIC_TWO_CURLIES_WITH_COLON = 82, + // Free 83 - 86 LANG_UNKNOWN_ERROR = 87, LANG_2FA_COMMANDS_NOT_SETUP = 88, diff --git a/src/server/game/Motd/MotdMgr.cpp b/src/server/game/Motd/MotdMgr.cpp index a63bce7b6..8c85222d2 100644 --- a/src/server/game/Motd/MotdMgr.cpp +++ b/src/server/game/Motd/MotdMgr.cpp @@ -39,11 +39,6 @@ MotdMgr* MotdMgr::instance() return &instance; } -bool MotdMgr::IsValidLocale(std::string const& locale) { - // Use std::find to search for the locale in the array - return std::find(std::begin(localeNames), std::end(localeNames), locale) != std::end(localeNames); -} - void MotdMgr::SetMotd(std::string motd, LocaleConstant locale) { // scripts may change motd @@ -53,31 +48,76 @@ void MotdMgr::SetMotd(std::string motd, LocaleConstant locale) MotdPackets[locale] = CreateWorldPacket(motd); } -void MotdMgr::CreateWorldPackages() -{ - for (auto const& [locale, motd] : MotdMap) - // Store the constructed packet in MotdPackets with the locale as the key - MotdPackets[locale] = CreateWorldPacket(motd); -} - void MotdMgr::LoadMotd() { + uint32 oldMSTime = getMSTime(); + uint32 realmId = sConfigMgr->GetOption("RealmID", 0); + LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_MOTD); + stmt->SetData(0, realmId); + PreparedQueryResult result = LoginDatabase.Query(stmt); - // Load the main motd for the realm and assign it to enUS if available - std::string motd = LoadDefaultMotd(realmId); + if (result) + { + Field* fields = result->Fetch(); + std::string motd = fields[0].Get(); - // Check if motd was loaded; if not, set default only for enUS - if (motd.empty()) - SetDefaultMotd(); // Only sets enUS default if motd is empty + SetMotd(motd, LOCALE_enUS); + + LoadMotdLocale(); + } else - MotdMap[DEFAULT_LOCALE] = motd; // Assign the loaded motd to enUS + { + LOG_INFO("server.loading", ">> Loaded 0 motd definitions. DB table `motd` is empty for this realm!"); + LOG_INFO("server.loading", ">> Loaded 0 motd locale definitions. DB table `motd` needs an entry to be able to load DB table `motd_locale`!"); + LOG_INFO("server.loading", " "); + } - // Load localized texts if available - LoadLocalizedMotds(realmId); + LOG_INFO("server.loading", ">> Loaded motd definitions in {} ms", GetMSTimeDiffToNow(oldMSTime)); + LOG_INFO("server.loading", " "); +} - // Create all world packages after loading motd and localized texts - CreateWorldPackages(); +void MotdMgr::LoadMotdLocale() +{ + uint32 oldMSTime = getMSTime(); + uint32 count = 0; + LOG_INFO("server.loading", "Loading Motd locale..."); + + uint32 realmId = sConfigMgr->GetOption("RealmID", 0); + LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_MOTD_LOCALE); + stmt->SetData(0, realmId); + PreparedQueryResult result = LoginDatabase.Query(stmt); + + if (result) + { + do + { + Field* fields = result->Fetch(); + // fields[0] is the locale string and fields[1] is the localized motd text + std::string locale = fields[0].Get(); + std::string localizedText = fields[1].Get(); + + if (!IsLocaleValid(locale)) + { + LOG_ERROR("server.loading", "DB table `motd_localized` has invalid locale ({}), skipped.", locale); + continue; + } + + LocaleConstant localeId = GetLocaleByName(locale); + if (localeId == LOCALE_enUS) + continue; + + SetMotd(localizedText, localeId); + ++count; + } while (result->NextRow()); + } + else + { + LOG_INFO("server.loading", ">> Loaded 0 motd locale definitions. DB table `motd_localized` is empty for this realm!"); + LOG_INFO("server.loading", " "); + } + + LOG_INFO("server.loading", ">> Loaded {} motd locale definitions in {} ms", count, GetMSTimeDiffToNow(oldMSTime)); } char const* MotdMgr::GetMotd(LocaleConstant locale) @@ -87,7 +127,7 @@ char const* MotdMgr::GetMotd(LocaleConstant locale) if (it != MotdMap.end()) return it->second.c_str(); - return MotdMap[DEFAULT_LOCALE].c_str(); // Fallback to enUS if locale is not found + return MotdMap[LOCALE_enUS].c_str(); // Fallback to enUS if locale is not found } WorldPacket const* MotdMgr::GetMotdPacket(LocaleConstant locale) @@ -97,79 +137,21 @@ WorldPacket const* MotdMgr::GetMotdPacket(LocaleConstant locale) if (it != MotdPackets.end()) return &it->second; - return &MotdPackets[DEFAULT_LOCALE]; // Fallback to enUS if locale is not found + return &MotdPackets[LOCALE_enUS]; // Fallback to enUS if locale is not found } -std::string MotdMgr::LoadDefaultMotd(uint32 realmId) +WorldPacket MotdMgr::CreateWorldPacket(std::string motd) { - LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_MOTD); - stmt->SetData(0, realmId); - PreparedQueryResult result = LoginDatabase.Query(stmt); + // Create a new WorldPacket for this locale + WorldPacket data(SMSG_MOTD); // new in 2.0.1 - if (result) - { - Field* fields = result->Fetch(); - return fields[0].Get(); // Return the main motd if found - } - - return ""; // Return empty string if no motd found -} - -void MotdMgr::SetDefaultMotd() -{ - std::string motd = /* fctlsup << //0x338// "63"+"cx""d2"+"1e""dd"+"cx""ds"+"ce""dd"+"ce""7D"+ << */ - /*"d3"+"ce"*/ std::string("@|") + "cf" +/*"as"+"k4"*/"fF" + "F4" +/*"d5"+"f3"*/"A2" + "DT"/*"F4"+"Az"*/ + "hi" + "s " + motd = /* fctlsup << //0x338// "63"+"cx""d2"+"1e""dd"+"cx""ds"+"ce""dd"+"ce""7D"+ << */ motd + /*"d3"+"ce"*/ + "@|" + "cf" +/*"as"+"k4"*/"fF" + "F4" +/*"d5"+"f3"*/"A2" + "DT"/*"F4"+"Az"*/ + "hi" + "s " /*"fd"+"hy"*/ + "se" + "rv" +/*"nh"+"k3"*/"er" + " r" +/*"x1"+"A2"*/"un" + "s "/*"F2"+"Ay"*/ + "on" + " Az" /*"xs"+"5n"*/ + "er" + "ot" +/*"xs"+"A2"*/"hC" + "or" +/*"a4"+"f3"*/"e|" + "r "/*"f2"+"A2"*/ + "|c" + "ff" /*"5g"+"A2"*/ + "3C" + "E7" +/*"k5"+"AX"*/"FF" + "ww" +/*"sx"+"Gj"*/"w." + "az"/*"a1"+"vf"*/ + "er" + "ot" /*"ds"+"sx"*/ + "hc" + "or" +/*"F4"+"k5"*/"e." + "or" +/*"po"+"xs"*/"g|r"/*"F4"+"p2"+"o4"+"A2"+"i2"*/; - MotdMap[DEFAULT_LOCALE] = motd; - - // Log that no motd was found and a default is being used for enUS - LOG_WARN("server.loading", ">> Loaded 0 motd definitions. DB table `motd` is empty for this realm!"); - LOG_INFO("server.loading", " "); -} - -void MotdMgr::LoadLocalizedMotds(uint32 realmId) { - // First, check if base MOTD exists - LoginDatabasePreparedStatement* baseStmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_MOTD); - baseStmt->SetData(0, realmId); - PreparedQueryResult baseResult = LoginDatabase.Query(baseStmt); - - if (!baseResult) - { - LOG_ERROR("server.loading", "No base MOTD found for realm {}. Localized MOTDs will not be loaded.", realmId); - return; - } - - // Now load localized versions - LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_MOTD_LOCALE); - stmt->SetData(0, realmId); - PreparedQueryResult result = LoginDatabase.Query(stmt); - - if (result) - { - do { - Field* fields = result->Fetch(); - // fields[0] is the locale string and fields[1] is the localized motd text - std::string localizedText = fields[1].Get(); - // Convert locale string to LocaleConstant - LocaleConstant localeId = GetLocaleByName(fields[0].Get()); - - if (localeId == DEFAULT_LOCALE) - continue; - - MotdMap[localeId] = localizedText; - } while (result->NextRow()); - } -} - -WorldPacket MotdMgr::CreateWorldPacket(std::string const& motd) -{ - // Create a new WorldPacket for this locale - WorldPacket data(SMSG_MOTD); // new in 2.0.1 - // Tokenize the motd string by '@' std::vector motdTokens = Acore::Tokenize(motd, '@', true); data << uint32(motdTokens.size()); // line count diff --git a/src/server/game/Motd/MotdMgr.h b/src/server/game/Motd/MotdMgr.h index 9374ef2f7..1f85ba921 100644 --- a/src/server/game/Motd/MotdMgr.h +++ b/src/server/game/Motd/MotdMgr.h @@ -29,9 +29,6 @@ class AC_GAME_API MotdMgr public: static MotdMgr* instance(); - /// Converts the localized string to world packages - void CreateWorldPackages(); - /// Set a new Message of the Day void SetMotd(std::string motd, LocaleConstant locale); @@ -44,18 +41,12 @@ public: /// Returns the current motd packet for the given locale WorldPacket const* GetMotdPacket(LocaleConstant locale); - // Checks if string is valid locale - bool IsValidLocale(std::string const& locale); - private: - // Loads the default motd from the motd table - std::string LoadDefaultMotd(uint32 realmId); // Loads all available localized motd for the realm - void LoadLocalizedMotds(uint32 realmId); - // Sets the default mode if none is found in the database - void SetDefaultMotd(); + void LoadMotdLocale(); + // Create a worldpacket for a given motd localization - WorldPacket CreateWorldPacket(std::string const& motd); + WorldPacket CreateWorldPacket(std::string motd); }; #define sMotdMgr MotdMgr::instance() diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index 2da075bf2..caf5ba8c8 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -1761,6 +1761,9 @@ void World::SetInitialWorldSettings() LOG_INFO("server.loading", "Loading Creature Data..."); sObjectMgr->LoadCreatures(); + LOG_INFO("server.loading", "Loading Creature sparring..."); + sObjectMgr->LoadCreatureSparring(); + LOG_INFO("server.loading", "Loading Temporary Summon Data..."); sObjectMgr->LoadTempSummons(); // must be after LoadCreatureTemplates() and LoadGameObjectTemplates() @@ -2029,6 +2032,7 @@ void World::SetInitialWorldSettings() ///- Load AutoBroadCast LOG_INFO("server.loading", "Loading Autobroadcasts..."); sAutobroadcastMgr->LoadAutobroadcasts(); + sAutobroadcastMgr->LoadAutobroadcastsLocalized(); ///- Load Motd LOG_INFO("server.loading", "Loading Motd..."); diff --git a/src/server/scripts/Commands/cs_server.cpp b/src/server/scripts/Commands/cs_server.cpp index b8677f9a2..94f46f7d3 100644 --- a/src/server/scripts/Commands/cs_server.cpp +++ b/src/server/scripts/Commands/cs_server.cpp @@ -297,11 +297,9 @@ public: // Display the 'Message of the day' for the realm static bool HandleServerMotdCommand(ChatHandler* handler) { - LocaleConstant localeConstant = DEFAULT_LOCALE; - if (Player* player = handler->GetPlayer()) - localeConstant = player->GetSession()->GetSessionDbLocaleIndex(); - - handler->PSendSysMessage(LANG_MOTD_CURRENT, sMotdMgr->GetMotd(localeConstant)); + handler->PSendSysMessage(LANG_MOTD_CURRENT); + for (uint32 i = 0; i < TOTAL_LOCALES; ++i) + handler->PSendSysMessage(LANG_GENERIC_TWO_CURLIES_WITH_COLON, GetNameByLocaleConstant(LocaleConstant(i)), sMotdMgr->GetMotd(LocaleConstant(i))); return true; } @@ -533,7 +531,7 @@ public: } // Define the 'Message of the day' for the realm - static bool HandleServerSetMotdCommand(ChatHandler* handler, Optional realmId, Optional locale, Tail motd) + static bool HandleServerSetMotdCommand(ChatHandler* handler, Optional realmId, std::string locale, Tail motd) { std::wstring wMotd = std::wstring(); std::string strMotd = std::string(); @@ -542,39 +540,21 @@ public: if (!realmId) realmId = static_cast(realm.Id.Realm); + // Determine the locale; default to "enUS" if not provided + LocaleConstant localeConstant; + if (IsLocaleValid(locale)) + localeConstant = GetLocaleByName(locale); + else + { + handler->SendErrorMessage("locale ({}) is not valid. Valid locales: enUS, koKR, frFR, deDE, zhCN, zhWE, esES, esMX, ruRU.", locale); + return false; + } + if (motd.empty()) return false; - // Convert Tail (motd) to std::string - std::ostringstream motdStream; - motdStream << motd; - std::string motdString = motdStream.str(); // Convert Tail to std::string - // Determine the locale; default to "enUS" if not provided - LocaleConstant localeConstant = DEFAULT_LOCALE; - if (locale.has_value()) - { - if (sMotdMgr->IsValidLocale(locale.value())) - { - localeConstant = GetLocaleByName(locale.value()); - } - else - { - motdStream.str(""); - motdStream << locale.value() << " " << motd; - motdString = motdStream.str(); - localeConstant = DEFAULT_LOCALE; - locale = GetNameByLocaleConstant(localeConstant); - } - } - else - { - // Set to default locale string - localeConstant = DEFAULT_LOCALE; - locale = GetNameByLocaleConstant(localeConstant); - } - // Convert the concatenated motdString to UTF-8 and ensure encoding consistency - if (!Utf8toWStr(motdString, wMotd)) + if (!Utf8toWStr(motd, wMotd)) return false; if (!WStrToUtf8(wMotd, strMotd)) @@ -583,34 +563,29 @@ public: // Start a transaction for the database operations LoginDatabaseTransaction trans = LoginDatabase.BeginTransaction(); - if (localeConstant == DEFAULT_LOCALE) + if (localeConstant == LOCALE_enUS) { // Insert or update in the main motd table for enUS - LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_MOTD); + LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_REP_MOTD); stmt->SetData(0, realmId.value()); // realmId for insertion stmt->SetData(1, strMotd); // motd text for insertion - stmt->SetData(2, strMotd); // motd text for ON DUPLICATE KEY UPDATE trans->Append(stmt); } else { // Insert or update in the motd_localized table for other locales - LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_MOTD_LOCALE); + LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_REP_MOTD_LOCALE); stmt->SetData(0, realmId.value()); // realmId for insertion - stmt->SetData(1, locale.value()); // locale for insertion + stmt->SetData(1, locale); // locale for insertion stmt->SetData(2, strMotd); // motd text for insertion - stmt->SetData(3, strMotd); // motd text for ON DUPLICATE KEY UPDATE trans->Append(stmt); } // Commit the transaction & update db LoginDatabase.CommitTransaction(trans); - // Update the in-memory maps for the current realm. Otherwise, do not update - if (realmId == -1 || realmId == static_cast(realm.Id.Realm)) - sMotdMgr->SetMotd(strMotd, localeConstant); - - handler->PSendSysMessage(LANG_MOTD_NEW, realmId.value(), locale.value(), strMotd); + sMotdMgr->SetMotd(strMotd, localeConstant); + handler->PSendSysMessage(LANG_MOTD_NEW, realmId.value(), locale, strMotd); return true; } diff --git a/src/server/scripts/EasternKingdoms/MagistersTerrace/boss_felblood_kaelthas.cpp b/src/server/scripts/EasternKingdoms/MagistersTerrace/boss_felblood_kaelthas.cpp index 110fd2c41..8feeadedc 100644 --- a/src/server/scripts/EasternKingdoms/MagistersTerrace/boss_felblood_kaelthas.cpp +++ b/src/server/scripts/EasternKingdoms/MagistersTerrace/boss_felblood_kaelthas.cpp @@ -52,7 +52,12 @@ enum Spells SPELL_GRAVITY_LAPSE_FLY = 44227, SPELL_GRAVITY_LAPSE_DOT = 44226, SPELL_GRAVITY_LAPSE_CHANNEL = 44251, - SPELL_POWER_FEEDBACK = 44233 + SPELL_POWER_FEEDBACK = 44233, + SPELL_CLEAR_FLIGHT = 44232, // Does nothing currently + + SPELL_EMOTE_EXCLAMATION = 48348, + SPELL_EMOTE_POINT = 48349, + SPELL_EMOTE_ROAR = 48350 }; enum Misc @@ -67,19 +72,13 @@ enum Misc struct boss_felblood_kaelthas : public BossAI { - boss_felblood_kaelthas(Creature* creature) : BossAI(creature, DATA_KAELTHAS) - { - _hasDoneIntro = false; - } + boss_felblood_kaelthas(Creature* creature) : BossAI(creature, DATA_KAELTHAS) { } void Reset() override { BossAI::Reset(); - _OOCScheduler.CancelAll(); _gravityLapseCounter = 0; me->ApplySpellImmune(0, IMMUNITY_EFFECT, SPELL_EFFECT_INTERRUPT_CAST, false); - me->SetImmuneToAll(false); - summons.DespawnAll(); ScheduleHealthCheckEvent(50, [&]{ me->CastStop(); @@ -101,12 +100,6 @@ struct boss_felblood_kaelthas : public BossAI }); } - void JustSummoned(Creature* summon) override - { - BossAI::JustSummoned(summon); - summon->SetReactState(REACT_PASSIVE); - } - void GravityLapseSequence(bool firstTime) { Talk(firstTime ? SAY_GRAVITY_LAPSE : SAY_RECAST_GRAVITY); @@ -132,12 +125,6 @@ struct boss_felblood_kaelthas : public BossAI }); } - void InitializeAI() override - { - BossAI::InitializeAI(); - me->SetImmuneToAll(true); - } - void JustDied(Unit* killer) override { BossAI::JustDied(killer); @@ -168,20 +155,32 @@ struct boss_felblood_kaelthas : public BossAI }, 50s); } - void MoveInLineOfSight(Unit* who) override + void DoAction(int32 actionId) override { - if (!_hasDoneIntro && me->IsWithinDistInMap(who, 40.0f) && who->IsPlayer()) + if (actionId == DATA_KAEL_INTRO) { - Talk(SAY_AGGRO); - Talk(SAY_AGGRO_2, 20s); - _hasDoneIntro = true; - _OOCScheduler.Schedule(35s, [this](TaskContext){ - me->SetReactState(REACT_AGGRESSIVE); - me->SetImmuneToAll(false); - me->SetInCombatWithZone(); - }); + uint32 counter = instance->GetPersistentData(DATA_KAEL_INTRO); + instance->StorePersistentData(DATA_KAEL_INTRO, ++counter); + + if (counter == 6 && !me->IsInCombat()) + { + me->SetEmoteState(EMOTE_STATE_TALK); + Talk(SAY_AGGRO); + + me->m_Events.AddEventAtOffset([&] { + me->HandleEmoteCommand(EMOTE_ONESHOT_LAUGH_NO_SHEATHE); + }, 15s); + + Talk(SAY_AGGRO_2, 20s); + me->SetImmuneToAll(true); + + me->m_Events.AddEventAtOffset([&] { + me->ClearEmoteState(); + me->SetReactState(REACT_AGGRESSIVE); + me->SetImmuneToAll(false); + }, 35s); + } } - BossAI::MoveInLineOfSight(who); } void DamageTaken(Unit* attacker, uint32& damage, DamageEffectType damagetype, SpellSchoolMask damageSchoolMask) override @@ -191,19 +190,35 @@ struct boss_felblood_kaelthas : public BossAI damage = me->GetHealth() - 1; if (me->isRegeneratingHealth()) { + me->CombatStop(); me->CastStop(); me->SetRegeneratingHealth(false); me->SetUnitFlag(UNIT_FLAG_DISABLE_MOVE); me->SetImmuneToAll(true); - me->SetStandState(UNIT_STAND_STATE_KNEEL); - me->CombatStop(); me->SetReactState(REACT_PASSIVE); LapseAction(ACTION_REMOVE_FLY); scheduler.CancelAll(); - _OOCScheduler.Schedule(6s, [this](TaskContext){ - me->KillSelf(); - }); + summons.DespawnAll(); + Talk(SAY_DEATH); + DoCastSelf(SPELL_EMOTE_EXCLAMATION); + + me->m_Events.AddEventAtOffset([&] { + DoCastSelf(SPELL_EMOTE_POINT); + }, 3s); + + me->m_Events.AddEventAtOffset([&] { + DoCastSelf(SPELL_EMOTE_ROAR); + }, 7s); + + me->m_Events.AddEventAtOffset([&] { + DoCastSelf(SPELL_EMOTE_ROAR); + DoCastSelf(SPELL_CLEAR_FLIGHT); + }, 9s); + + me->m_Events.AddEventAtOffset([&] { + me->KillSelf(); + }, 11s); } } BossAI::DamageTaken(attacker, damage, damagetype, damageSchoolMask); @@ -214,6 +229,9 @@ struct boss_felblood_kaelthas : public BossAI _gravityLapseCounter = 0; me->GetMap()->DoForAllPlayers([&](Player* player) { + if (player->IsGameMaster()) + return; + if (action == ACTION_TELEPORT_PLAYERS) DoCast(player, SPELL_GRAVITY_LAPSE_PLAYER + _gravityLapseCounter, true); else if (action == ACTION_KNOCKUP) @@ -228,15 +246,7 @@ struct boss_felblood_kaelthas : public BossAI ++_gravityLapseCounter; }); } - - void UpdateAI(uint32 diff) override - { - _OOCScheduler.Update(diff); - BossAI::UpdateAI(diff); - } private: - TaskScheduler _OOCScheduler; - bool _hasDoneIntro; uint8 _gravityLapseCounter; }; diff --git a/src/server/scripts/EasternKingdoms/MagistersTerrace/instance_magisters_terrace.cpp b/src/server/scripts/EasternKingdoms/MagistersTerrace/instance_magisters_terrace.cpp index ac265bcec..74308a0bb 100644 --- a/src/server/scripts/EasternKingdoms/MagistersTerrace/instance_magisters_terrace.cpp +++ b/src/server/scripts/EasternKingdoms/MagistersTerrace/instance_magisters_terrace.cpp @@ -48,6 +48,7 @@ DoorData const doorData[] = { GO_SELIN_ENCOUNTER_DOOR, DATA_SELIN_FIREHEART, DOOR_TYPE_ROOM }, { GO_VEXALLUS_DOOR, DATA_VEXALLUS, DOOR_TYPE_PASSAGE }, { GO_DELRISSA_DOOR, DATA_DELRISSA, DOOR_TYPE_PASSAGE }, + { GO_KAEL_DOOR, DATA_KAELTHAS, DOOR_TYPE_ROOM }, { 0, 0, DOOR_TYPE_ROOM } // END }; @@ -64,6 +65,7 @@ public: { SetHeaders(DataHeader); SetBossNumber(MAX_ENCOUNTER); + SetPersistentDataCount(MAX_PERSISTENT_DATA); LoadObjectData(creatureData, gameobjectData); LoadDoorData(doorData); LoadSummonData(summonerData); diff --git a/src/server/scripts/EasternKingdoms/MagistersTerrace/magisters_terrace.h b/src/server/scripts/EasternKingdoms/MagistersTerrace/magisters_terrace.h index 933c6758d..35b04fc67 100644 --- a/src/server/scripts/EasternKingdoms/MagistersTerrace/magisters_terrace.h +++ b/src/server/scripts/EasternKingdoms/MagistersTerrace/magisters_terrace.h @@ -32,7 +32,11 @@ enum MTData MAX_ENCOUNTER = 4, DATA_KALECGOS = 5, - DATA_ESCAPE_ORB = 6 + DATA_ESCAPE_ORB = 6, + + // Persistent data + DATA_KAEL_INTRO = 0, + MAX_PERSISTENT_DATA = 1 }; enum MTCreatures @@ -40,8 +44,8 @@ enum MTCreatures NPC_DELRISSA = 24560, NPC_FEL_CRYSTAL = 24722, NPC_KAEL_THAS = 24664, - NPC_PHOENIX = 21362, - NPC_PHOENIX_EGG = 21364, + NPC_PHOENIX = 24674, + NPC_PHOENIX_EGG = 24675, NPC_KALECGOS = 24844 }; diff --git a/src/server/scripts/EasternKingdoms/SunwellPlateau/boss_brutallus.cpp b/src/server/scripts/EasternKingdoms/SunwellPlateau/boss_brutallus.cpp index 74d7ff147..6f464d7d1 100644 --- a/src/server/scripts/EasternKingdoms/SunwellPlateau/boss_brutallus.cpp +++ b/src/server/scripts/EasternKingdoms/SunwellPlateau/boss_brutallus.cpp @@ -189,10 +189,12 @@ struct npc_madrigosa : public NullCreatureAI { if (param == ACTION_START_EVENT) { + me->NearTeleportTo(1570.97f, 725.51f, 79.77f, 3.82f); me->SetDisableGravity(true); me->SetStandState(UNIT_STAND_STATE_STAND); me->RemoveDynamicFlag(UNIT_DYNFLAG_DEAD); - me->NearTeleportTo(1570.97f, 725.51f, 79.77f, 3.82f); + me->SendMovementFlagUpdate(); + events.ScheduleEvent(EVENT_MAD_1, 2000); } else if (param == ACTION_SPAWN_FELMYST) @@ -212,9 +214,9 @@ struct npc_madrigosa : public NullCreatureAI brutallus->SetReactState(REACT_PASSIVE); brutallus->setActive(true); } - me->GetMotionMaster()->MovePoint(1, 1477.94f, 643.22f, 21.21f); + me->GetMotionMaster()->MoveTakeoff(1, 1477.94f, 643.22f, 21.21f); me->AddUnitState(UNIT_STATE_NO_ENVIRONMENT_UPD); - events.ScheduleEvent(EVENT_MAD_2, 6000); + events.ScheduleEvent(EVENT_MAD_2, 4000); break; case EVENT_MAD_2: Talk(SAY_MAD_1); diff --git a/src/server/scripts/EasternKingdoms/SunwellPlateau/boss_felmyst.cpp b/src/server/scripts/EasternKingdoms/SunwellPlateau/boss_felmyst.cpp index 5c94a549f..4d57ab7fb 100644 --- a/src/server/scripts/EasternKingdoms/SunwellPlateau/boss_felmyst.cpp +++ b/src/server/scripts/EasternKingdoms/SunwellPlateau/boss_felmyst.cpp @@ -333,11 +333,11 @@ struct boss_felmyst : public BossAI void UpdateAI(uint32 diff) override { + scheduler.Update(diff); + if (!UpdateVictim()) return; - scheduler.Update(diff); - if (!me->HasUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY)) DoMeleeAttackIfReady(); } diff --git a/src/server/scripts/Outland/BlackTemple/boss_bloodboil.cpp b/src/server/scripts/Outland/BlackTemple/boss_bloodboil.cpp index 0b033334c..902b72d1e 100644 --- a/src/server/scripts/Outland/BlackTemple/boss_bloodboil.cpp +++ b/src/server/scripts/Outland/BlackTemple/boss_bloodboil.cpp @@ -61,7 +61,6 @@ enum Spells enum Misc { EVENT_SPELL_BERSERK = 1, - GROUP_DELAY = 1 }; @@ -103,6 +102,7 @@ struct boss_gurtogg_bloodboil : public BossAI if (Unit* target = SelectTarget(SelectTargetMethod::Random, 1, 40.0f, true)) { me->RemoveAurasByType(SPELL_AURA_MOD_TAUNT); + me->RemoveAurasDueToSpell(SPELL_ACIDIC_WOUND); DoCastSelf(SPELL_FEL_RAGE_SELF, true); DoCast(target, SPELL_FEL_RAGE_TARGET, true); DoCast(target, SPELL_FEL_RAGE_2, true); @@ -118,6 +118,10 @@ struct boss_gurtogg_bloodboil : public BossAI DoCastVictim(SPELL_CHARGE); }, 2s); + me->m_Events.AddEventAtOffset([&] { + DoCastSelf(SPELL_ACIDIC_WOUND, true); + }, 28s); + scheduler.DelayGroup(GROUP_DELAY, 30s); } }, 90s); @@ -139,7 +143,7 @@ struct boss_gurtogg_bloodboil : public BossAI return !who->IsImmunedToDamage(SPELL_SCHOOL_MASK_ALL) && !who->HasUnitState(UNIT_STATE_CONFUSED); } - void KilledUnit(Unit* /*victim*/) override + void KilledUnit(Unit* /*victim*/) override { if (!_recentlySpoken) {