diff --git a/data/sql/updates/db_characters/2026_06_24_00.sql b/data/sql/updates/db_characters/2026_06_24_00.sql new file mode 100644 index 000000000..64cf2d7be --- /dev/null +++ b/data/sql/updates/db_characters/2026_06_24_00.sql @@ -0,0 +1,3 @@ +-- DB update 2026_06_01_00 -> 2026_06_24_00 +ALTER TABLE `mail_server_template` + ADD COLUMN `senderEntry` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT 'Entry from creature_template. 0 for default "Customer Support" sender.' AFTER `id`; diff --git a/data/sql/updates/db_world/2026_06_20_00.sql b/data/sql/updates/db_world/2026_06_20_00.sql new file mode 100644 index 000000000..7da14f8d1 --- /dev/null +++ b/data/sql/updates/db_world/2026_06_20_00.sql @@ -0,0 +1,4 @@ +-- DB update 2026_06_18_03 -> 2026_06_20_00 +-- +DELETE FROM `spell_script_names` WHERE `spell_id` = 64132; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES (64132, 'spell_yogg_saron_constrictor_tentacle_aura'); diff --git a/data/sql/updates/db_world/2026_06_21_00.sql b/data/sql/updates/db_world/2026_06_21_00.sql new file mode 100644 index 000000000..38f3e7c2c --- /dev/null +++ b/data/sql/updates/db_world/2026_06_21_00.sql @@ -0,0 +1,57 @@ +-- DB update 2026_06_20_00 -> 2026_06_21_00 + +-- Set Extra Flag NO_MOVE_FLAGS_UPDATE. +UPDATE `creature_template` SET `flags_extra` = `flags_extra` |512 WHERE (`entry` = 28465); + +-- Remove Movement Flags. +DELETE FROM `creature_template_movement` WHERE (`CreatureId` = 28465); + +-- Remove MT and waypoint ids. +UPDATE `creature` SET `MovementType` = 0 WHERE (`id` = 28465) AND (`guid` IN (100536, 100540)); +UPDATE `creature_addon` SET `path_id` = 0 WHERE (`guid` IN (100536, 100540)); + +-- Move Heb Drakkar Striker paths on waypoints table. +DELETE FROM `waypoint_data` WHERE (`id` IN (1005360, 1005400)); +DELETE FROM `waypoints` WHERE `entry` IN (10053600, 10054000); +INSERT INTO `waypoints` (`entry`, `pointid`, `position_x`, `position_y`, `position_z`, `orientation`, `point_comment`) VALUES +(10053600, 1, 5997.71, -3803.25, 384.444, NULL, 'Heb Drakkar Striker 1'), +(10053600, 2, 5971.09, -3866.25, 390.999, NULL, 'Heb Drakkar Striker 1'), +(10053600, 3, 5896.79, -3922, 387, NULL, 'Heb Drakkar Striker 1'), +(10053600, 4, 5846.83, -3912.56, 378.944, NULL, 'Heb Drakkar Striker 1'), +(10053600, 5, 5803.36, -3868.98, 376.389, NULL, 'Heb Drakkar Striker 1'), +(10053600, 6, 5803.01, -3817.95, 376.055, NULL, 'Heb Drakkar Striker 1'), +(10053600, 7, 5839.45, -3767.71, 375.888, NULL, 'Heb Drakkar Striker 1'), +(10053600, 8, 5879.84, -3723.38, 375.222, NULL, 'Heb Drakkar Striker 1'), +(10053600, 9, 5906.52, -3689.99, 375.222, NULL, 'Heb Drakkar Striker 1'), +(10053600, 10, 5948.16, -3694.75, 375.222, NULL, 'Heb Drakkar Striker 1'), +(10053600, 11, 5986.21, -3743.16, 378.722, NULL, 'Heb Drakkar Striker 1'), +(10054000, 1, 5921.5, -3765.99, 374.932, NULL, 'Heb Drakkar Striker 2'), +(10054000, 2, 5892.01, -3795.87, 373.182, NULL, 'Heb Drakkar Striker 2'), +(10054000, 3, 5894.13, -3870.95, 381.932, NULL, 'Heb Drakkar Striker 2'), +(10054000, 4, 5936.05, -3933.15, 399.932, NULL, 'Heb Drakkar Striker 2'), +(10054000, 5, 5992.46, -3987.92, 399.932, NULL, 'Heb Drakkar Striker 2'), +(10054000, 6, 6035.39, -3982.35, 382.682, NULL, 'Heb Drakkar Striker 2'), +(10054000, 7, 6084.95, -3923.61, 382.682, NULL, 'Heb Drakkar Striker 2'), +(10054000, 8, 6090.31, -3870.81, 401.877, NULL, 'Heb Drakkar Striker 2'), +(10054000, 9, 6015.04, -3792.97, 401.877, NULL, 'Heb Drakkar Striker 2'), +(10054000, 10, 5975.41, -3758.67, 379.627, NULL, 'Heb Drakkar Striker 2'); + +-- Add Heb Drakkar Striker Specific GUID. +DELETE FROM `smart_scripts` WHERE (`source_type` = 0) AND (`entryorguid` IN (-100536, -100540)); +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 +(-100536, 0, 0, 1, 11, 0, 100, 0, 0, 0, 0, 0, 0, 0, 60, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Heb\'Drakkar Striker - On Respawn - Set Disable Gravity On'), +(-100536, 0, 1, 0, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 53, 0, 10053600, 1, 0, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Heb\'Drakkar Striker - On Respawn - Start Patrol Path 10053600'), +(-100536, 0, 2, 3, 4, 0, 100, 0, 0, 0, 0, 0, 0, 0, 60, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Heb\'Drakkar Striker - On Aggro - Set Disable Gravity Off'), +(-100536, 0, 3, 0, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 43, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Heb\'Drakkar Striker - On Aggro - Dismount'), +(-100536, 0, 4, 5, 7, 0, 100, 0, 0, 0, 0, 0, 0, 0, 60, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Heb\'Drakkar Striker - On Evade - Set Disable Gravity On'), +(-100536, 0, 5, 0, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 43, 0, 9074, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Heb\'Drakkar Striker - On Evade - Mount To Model 9074'), +(-100536, 0, 6, 0, 0, 0, 100, 0, 5000, 5000, 16000, 16000, 0, 0, 11, 11976, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Heb\'Drakkar Striker - In Combat - Cast \'Strike\''), +(-100536, 0, 7, 0, 0, 0, 100, 0, 4000, 14000, 50000, 60000, 0, 0, 11, 51951, 32, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Heb\'Drakkar Striker - In Combat - Cast \'Rabies\''), +(-100540, 0, 0, 1, 11, 0, 100, 0, 0, 0, 0, 0, 0, 0, 60, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Heb\'Drakkar Striker - On Respawn - Set Disable Gravity On'), +(-100540, 0, 1, 0, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 53, 0, 10054000, 1, 0, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Heb\'Drakkar Striker - On Respawn - Start Patrol Path 10054000'), +(-100540, 0, 2, 3, 4, 0, 100, 0, 0, 0, 0, 0, 0, 0, 60, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Heb\'Drakkar Striker - On Aggro - Set Disable Gravity Off'), +(-100540, 0, 3, 0, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 43, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Heb\'Drakkar Striker - On Aggro - Dismount'), +(-100540, 0, 4, 5, 7, 0, 100, 0, 0, 0, 0, 0, 0, 0, 60, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Heb\'Drakkar Striker - On Evade - Set Disable Gravity On'), +(-100540, 0, 5, 0, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 43, 0, 9074, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Heb\'Drakkar Striker - On Evade - Mount To Model 9074'), +(-100540, 0, 6, 0, 0, 0, 100, 0, 5000, 5000, 16000, 16000, 0, 0, 11, 11976, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Heb\'Drakkar Striker - In Combat - Cast \'Strike\''), +(-100540, 0, 7, 0, 0, 0, 100, 0, 4000, 14000, 50000, 60000, 0, 0, 11, 51951, 32, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Heb\'Drakkar Striker - In Combat - Cast \'Rabies\''); diff --git a/data/sql/updates/db_world/2026_06_22_00.sql b/data/sql/updates/db_world/2026_06_22_00.sql new file mode 100644 index 000000000..74f806b8f --- /dev/null +++ b/data/sql/updates/db_world/2026_06_22_00.sql @@ -0,0 +1,105 @@ +-- DB update 2026_06_21_00 -> 2026_06_22_00 + +-- Add Spell Script Name. +DELETE FROM `spell_script_names` WHERE `spell_id` = 51036; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(51036, 'spell_venture_pilot_summon'); + +-- Add new Sniffed Waypoints +DELETE FROM `waypoints` WHERE `entry` IN (28192, 2819200, 2822900); +INSERT INTO `waypoints` (`entry`, `pointid`, `position_x`, `position_y`, `position_z`, `orientation`, `point_comment`) VALUES +(2819200, 1, 5142.8774, 4676.631, 73.46826, NULL, 'Archmage Pentarus Flying Machine'), +(2819200, 2, 5199.205, 4727.144, 72.46811, NULL, 'Archmage Pentarus Flying Machine'), +(2819200, 3, 5264.4897, 4794.4497, 69.96814, NULL, 'Archmage Pentarus Flying Machine'), +(2819200, 4, 5309.7544, 4842.0576, 59.273388, NULL, 'Archmage Pentarus Flying Machine'), +(2819200, 5, 5339.674, 4877.8887, 46.18986, NULL, 'Archmage Pentarus Flying Machine'), +(2819200, 6, 5403.4263, 4944.932, 15.161798, NULL, 'Archmage Pentarus Flying Machine'), +(2819200, 7, 5455.6265, 5002.442, -3.115965, NULL, 'Archmage Pentarus Flying Machine'), +(2819200, 8, 5529.6377, 5085.854, -31.67146, NULL, 'Archmage Pentarus Flying Machine'), +(2819200, 9, 5599.5723, 5174.9565, -62.560642, NULL, 'Archmage Pentarus Flying Machine'), +(2819200, 10, 5622.023, 5202.6616, -68.72743, NULL, 'Archmage Pentarus Flying Machine'), +(2819200, 11, 5635.8755, 5225.5605, -75.11644, NULL, 'Archmage Pentarus Flying Machine'), +(2822900, 1, 5508.8813, 4993.8525, -26.99996, NULL, 'Venture Co. Pilot'), +(2822900, 2, 5527.892, 5024.44, -33.166454, NULL, 'Venture Co. Pilot'), +(2822900, 3, 5555.9478, 5055.0166, -40.555496, NULL, 'Venture Co. Pilot'), +(2822900, 4, 5573.23, 5081.3677, -46.416637, NULL, 'Venture Co. Pilot'), +(2822900, 5, 5596.7915, 5120.5415, -60.861324, NULL, 'Venture Co. Pilot'), +(2822900, 6, 5616.4214, 5169.3354, -73.94473, NULL, 'Venture Co. Pilot'), +(2822900, 7, 5639.541, 5246.319, -82.02806, NULL, 'Venture Co. Pilot'), +(2822900, 8, 5649.434, 5336.935, -80.611534, NULL, 'Venture Co. Pilot'), +(2822900, 9, 5649.434, 5336.935, -80.611534, NULL, 'Venture Co. Pilot'); + +-- Set Sniffed walk and run speed (Archmage Pentarus' Flying Machine). +UPDATE `creature_template` SET `speed_run` = 3.14, `speed_walk` = 6 WHERE `entry` = 28192; + +-- Set Default Aura (Archmage Pentarus' Flying Machine). +UPDATE `creature_template_addon` SET `auras` = '48602' WHERE (`entry` = 28192); + +-- Edit SAI & Action List (Archmage Pentarus) +UPDATE `creature_template` SET `AIName` = 'SmartAI' WHERE `entry` = 28160; + +DELETE FROM `smart_scripts` WHERE (`source_type` = 0 AND `entryorguid` = 28160); +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 +(28160, 0, 0, 0, 62, 0, 100, 512, 10024, 0, 0, 0, 0, 0, 80, 2816000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Archmage Pentarus - On Gossip Option 0 Selected - Run Script'); + +DELETE FROM `smart_scripts` WHERE (`source_type` = 9 AND `entryorguid` = 2816000); +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 +(2816000, 9, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 72, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 'Archmage Pentarus - Actionlist - Close Gossip'), +(2816000, 9, 1, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 134, 50860, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Archmage Pentarus - Actionlist - Invoker Cast \'Flight to Sholazar\''), +(2816000, 9, 2, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Archmage Pentarus - Actionlist - Say Line 0'), +(2816000, 9, 3, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 50, 190488, 19, 0, 0, 0, 0, 8, 0, 0, 0, 0, 5828.64, 427.118, 670.096, 0.820303, 'Archmage Pentarus - Actionlist - Summon Gameobject \'Pentarus\' Portal to Sholazar Basin\''); + +-- Set SAI & Action Lists (Archmage Pentarus Flying Machine). +UPDATE `creature_template` SET `AIName` = 'SmartAI' WHERE `entry` = 28192; + +DELETE FROM `smart_scripts` WHERE (`source_type` = 0 AND `entryorguid` = 28192); +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 +(28192, 0, 0, 0, 54, 0, 100, 0, 0, 0, 0, 0, 0, 0, 80, 2819200, 2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Archmage Pentarus\' Flying Machine - On Just Summoned - Run Script'), +(28192, 0, 1, 0, 34, 0, 100, 0, 8, 22, 0, 0, 0, 0, 80, 2819201, 2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Archmage Pentarus\' Flying Machine - On Reached Point 22 - Run Script'), +(28192, 0, 2, 0, 58, 0, 100, 0, 0, 2819200, 0, 0, 0, 0, 80, 2819202, 2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Archmage Pentarus\' Flying Machine - On Path 2819200 Finished - Run Script'), +(28192, 0, 3, 4, 34, 0, 100, 0, 8, 23, 0, 0, 0, 0, 223, 24, 0, 0, 0, 0, 0, 10, 103289, 27987, 0, 0, 0, 0, 0, 0, 'Archmage Pentarus\' Flying Machine - On Reached Point 23 - Do Action ID 24'), +(28192, 0, 4, 5, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 11, 53119, 10, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Archmage Pentarus\' Flying Machine - On Reached Point 23 - Cast \'Cosmetic Flame Dart\''), +(28192, 0, 5, 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, 'Archmage Pentarus\' Flying Machine - On Reached Point 23 - Despawn Instant'); + +DELETE FROM `smart_scripts` WHERE (`source_type` = 9) AND (`entryorguid` IN (2819200, 2819201, 2819202)); +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 +(2819200, 9, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 11, 51076, 2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Archmage Pentarus\' Flying Machine - Actionlist - Cast \'Flight to Sholazar (Trigger Warning)\''), +(2819200, 9, 1, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 134, 46598, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Archmage Pentarus\' Flying Machine - Actionlist - Invoker Cast \'Ride Vehicle Hardcoded\''), +(2819200, 9, 2, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 60, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Archmage Pentarus\' Flying Machine - Actionlist - Set Fly On'), +(2819200, 9, 3, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 75, 51038, 0, 0, 0, 0, 0, 23, 0, 0, 0, 0, 0, 0, 0, 0, 'Archmage Pentarus\' Flying Machine - Actionlist - Add Aura \'Flight to Sholazar Periodic\''), +(2819200, 9, 4, 0, 0, 0, 100, 0, 2000, 2000, 0, 0, 0, 0, 69, 22, 0, 1, 0, 0, 0, 8, 0, 0, 0, 0, 5828.73, 428.093, 669.899, 0, 'Archmage Pentarus\' Flying Machine - Actionlist - Move To Position'), +(2819201, 9, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 11, 50987, 10, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Archmage Pentarus\' Flying Machine - Actionlist - Cast \'Teleport to Sholazar\''), +(2819201, 9, 1, 0, 0, 0, 100, 0, 2000, 2000, 0, 0, 0, 0, 53, 0, 2819200, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Archmage Pentarus\' Flying Machine - Actionlist - Start Waypoint Path 2819200'), +(2819202, 9, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 11, 53119, 10, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Archmage Pentarus\' Flying Machine - Actionlist - Cast \'Cosmetic Flame Dart\''), +(2819202, 9, 1, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 75, 61360, 0, 0, 0, 0, 0, 23, 0, 0, 0, 0, 0, 0, 0, 0, 'Archmage Pentarus\' Flying Machine - Actionlist - Add Aura \'Parachute\''), +(2819202, 9, 2, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 11, 50630, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Archmage Pentarus\' Flying Machine - Actionlist - Cast \'Eject All Passengers\''), +(2819202, 9, 3, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 69, 23, 0, 1, 0, 0, 0, 8, 0, 0, 0, 0, 5637.41, 5301.54, -109.587, 0, 'Archmage Pentarus\' Flying Machine - Actionlist - Move To Position'); + +-- Set SAI & Action List Venture Co. Pilot +UPDATE `creature_template` SET `AIName` = 'SmartAI' WHERE `entry` = 28229; + +DELETE FROM `smart_scripts` WHERE (`source_type` = 0 AND `entryorguid` = 28229); +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 +(28229, 0, 0, 0, 54, 0, 100, 0, 0, 0, 0, 0, 0, 0, 80, 2822900, 2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Venture Co. Pilot - On Just Summoned - Run Script'); + +DELETE FROM `smart_scripts` WHERE (`source_type` = 9 AND `entryorguid` = 2822900); +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 +(2822900, 9, 0, 0, 0, 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, 'Venture Co. Pilot - Actionlist - Set Reactstate Passive'), +(2822900, 9, 1, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 60, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Venture Co. Pilot - Actionlist - Set Fly On'), +(2822900, 9, 2, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 11, 48602, 10, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Venture Co. Pilot - Actionlist - Cast \'Flight\''), +(2822900, 9, 3, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 11, 51044, 2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Venture Co. Pilot - Actionlist - Cast \'Venture Co. Air Patrol Rockets\''), +(2822900, 9, 4, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 11, 51043, 2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Venture Co. Pilot - Actionlist - Cast \'Venture Co. Air Patrol Periodic (Kill Shot)\''), +(2822900, 9, 5, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 53, 0, 2822900, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Venture Co. Pilot - Actionlist - Start Waypoint Path 2822900'), +(2822900, 9, 6, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Venture Co. Pilot - Actionlist - Say Line 0'), +(2822900, 9, 7, 0, 0, 0, 100, 0, 2000, 2000, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 22, 0, 0, 0, 0, 0, 0, 0, 0, 'Venture Co. Pilot - Actionlist - Say Line 0'); + +-- Edit SAI (Monte Muzzleshot). +UPDATE `creature_template` SET `AIName` = 'SmartAI' WHERE `entry` = 27987; + +DELETE FROM `smart_scripts` WHERE (`source_type` = 0 AND `entryorguid` = 27987); +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 +(27987, 0, 0, 0, 25, 0, 100, 512, 0, 0, 0, 0, 0, 0, 60, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Monte Muzzleshot - On Reset - Set Fly On'), +(27987, 0, 1, 0, 72, 0, 100, 0, 24, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Monte Muzzleshot - On Action 24 Done - Say Line 0'); + +-- Delete unecessary Action List (Monte Muzzleshot). +DELETE FROM `smart_scripts` WHERE (`entryorguid` = 2798700) AND (`source_type` = 9); diff --git a/data/sql/updates/db_world/2026_06_25_00.sql b/data/sql/updates/db_world/2026_06_25_00.sql new file mode 100644 index 000000000..ee6e9967f --- /dev/null +++ b/data/sql/updates/db_world/2026_06_25_00.sql @@ -0,0 +1,9 @@ +-- DB update 2026_06_22_00 -> 2026_06_25_00 +-- Riding Jokkum cast is now handled by spell_q13010_jokkum_summon +DELETE FROM `smart_scripts` WHERE (`entryorguid` = 30331) AND (`source_type` = 0) AND (`id` IN (5)); + +DELETE FROM `spell_script_names` WHERE `ScriptName`='spell_q13010_jokkum_summon'; +DELETE FROM `spell_script_names` WHERE `ScriptName`='spell_riding_jokkum'; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(56541, 'spell_q13010_jokkum_summon'), +(56606, 'spell_riding_jokkum'); diff --git a/data/sql/updates/db_world/2026_06_26_00.sql b/data/sql/updates/db_world/2026_06_26_00.sql new file mode 100644 index 000000000..70ab2be2e --- /dev/null +++ b/data/sql/updates/db_world/2026_06_26_00.sql @@ -0,0 +1,24 @@ +-- DB update 2026_06_25_00 -> 2026_06_26_00 +-- PrevQuestID 11239 to 11231, make Of Keys and Cages the prerequisite, not In Service to the Light +UPDATE `quest_template_addon` SET `PrevQuestID` = 11231 WHERE (`ID` = 11432); + +-- Add Mage-Lieutenant Malister's text +DELETE FROM `creature_text` WHERE (`CreatureID` = 23888) AND (`GroupID` = 0) AND (`ID` = 0); +INSERT INTO `creature_text` (`CreatureID`, `GroupID`, `ID`, `Text`, `Type`, `Language`, `Probability`, `Emote`, `Duration`, `Sound`, `BroadcastTextId`, `TextRange`, `comment`) VALUES +(23888, 0, 0, '$n, I would have words with you.', 15, 0, 100, 0, 0, 0, 23634, 0, 'Mage-Lieutenant Malister - Quest 11231'); + +-- Father Levariol - Store player who completes Of Keys and Cages and send to Mage-Lieutenant Malister +UPDATE `creature_template` SET `AIName` = 'SmartAI' WHERE `entry` = 24038; + +DELETE FROM `smart_scripts` WHERE (`source_type` = 0 AND `entryorguid` = 24038); +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 +(24038, 0, 1, 2, 20, 0, 100, 0, 11231, 0, 0, 0, 0, 0, 64, 1, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 'Father Levariol - Store player who completes Of Keys and Cages - Quest 11231'), +(24038, 0, 2, 3, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 100, 1, 0, 0, 0, 0, 0, 19, 23888, 0, 0, 0, 0, 0, 0, 0, 'Father Levariol - Send stored target to Mage-Lieutenant Malister - Quest 11231'), +(24038, 0, 3, 0, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 223, 1, 0, 0, 0, 0, 0, 19, 23888, 0, 0, 0, 0, 0, 0, 0, 'Father Levariol - DO_ACTION to Mage-Lieutenant Malister - Quest 11231'); + +-- Mage-Lieutenant Malister - On SET_DATA, whisper to stored target +UPDATE `creature_template` SET `AIName` = 'SmartAI' WHERE `entry` = 23888; + +DELETE FROM `smart_scripts` WHERE (`entryorguid` = 23888) 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 +(23888, 0, 0, 0, 72, 0, 100, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 12, 1, 0, 0, 0, 0, 0, 0, 0, 'Mage-Lieutenant Malister - On ACTION_DONE whisper to stored target - Quest 11231'); diff --git a/src/common/Common.h b/src/common/Common.h index f816a2ded..a14647b1f 100644 --- a/src/common/Common.h +++ b/src/common/Common.h @@ -65,7 +65,7 @@ enum AccountTypes enum AccountFlag { ACCOUNT_FLAG_GM = 0x1, // Account is GM - ACCOUNT_FLAG_NOKICK = 0x2, // NYI UNK + ACCOUNT_FLAG_NOKICK = 0x2, // Will not be logged out while AFK ACCOUNT_FLAG_COLLECTOR = 0x4, // NYI Collector's Edition ACCOUNT_FLAG_TRIAL = 0x8, // Trial account ACCOUNT_FLAG_CANCELLED = 0x10, // NYI UNK diff --git a/src/server/apps/worldserver/ACSoap/ACSoap.cpp b/src/server/apps/worldserver/ACSoap/ACSoap.cpp index 247f6bb95..4b76d2b58 100644 --- a/src/server/apps/worldserver/ACSoap/ACSoap.cpp +++ b/src/server/apps/worldserver/ACSoap/ACSoap.cpp @@ -20,6 +20,8 @@ #include "Log.h" #include "World.h" #include "soapStub.h" +#include +#include void ACSoapThread(const std::string& host, uint16 port) { @@ -33,10 +35,15 @@ void ACSoapThread(const std::string& host, uint16 port) soap.recv_timeout = 5; soap.send_timeout = 5; + // allow rebinding while the previous socket is still in TIME_WAIT (e.g. on a quick restart) + soap.bind_flags = SO_REUSEADDR; + if (!soap_valid_socket(soap_bind(&soap, host.c_str(), port, 100))) { LOG_ERROR("network.soap", "ACSoap: couldn't bind to {}:{}", host, port); - exit(-1); + // graceful shutdown: exit() here would destroy SocketMgr before StopNetwork() and assert + World::StopNow(ERROR_EXIT_CODE); + return; } LOG_INFO("network.soap", "ACSoap: bound to http://{}:{}", host, port); @@ -103,21 +110,28 @@ int ns1__executeCommand(soap* soap, char* command, char** result) return soap_sender_fault(soap, "Command can not be empty", "The supplied command was an empty string"); LOG_DEBUG("network.soap", "ACSoap: got command '{}'", command); - SOAPCommand connection; + + // Shared so the object survives if we stop waiting below: the queued command keeps a raw + // pointer to it and the world thread may still run it after that. The extra reference is + // released by commandFinished() once the world side is done with it. + auto connection = std::make_shared(); + connection->m_self = connection; // commands are executed in the world thread. We have to wait for them to be completed + sWorld->QueueCliCommand(new CliCommandHolder(connection.get(), command, &SOAPCommand::print, &SOAPCommand::commandFinished)); + + // Wait for the command to finish, but bail on shutdown: ProcessCliCommands() (which fulfils + // the promise) stops once the world loop exits, so an unbounded wait here would deadlock. + std::future finished = connection->finishedPromise.get_future(); + while (finished.wait_for(std::chrono::seconds(1)) != std::future_status::ready) { - // CliCommandHolder will be deleted from world, accessing after queueing is NOT save - CliCommandHolder* cmd = new CliCommandHolder(&connection, command, &SOAPCommand::print, &SOAPCommand::commandFinished); - sWorld->QueueCliCommand(cmd); + if (World::IsStopped()) + return soap_receiver_fault(soap, "Server is shutting down", "Command aborted: the server is shutting down"); } - // Wait until the command has finished executing - connection.finishedPromise.get_future().wait(); - // The command has finished executing already - char* printBuffer = soap_strdup(soap, connection.m_printBuffer.c_str()); - if (connection.hasCommandSucceeded()) + char* printBuffer = soap_strdup(soap, connection->m_printBuffer.c_str()); + if (connection->hasCommandSucceeded()) { *result = printBuffer; return SOAP_OK; @@ -130,6 +144,8 @@ void SOAPCommand::commandFinished(void* soapconnection, bool success) { SOAPCommand* con = (SOAPCommand*)soapconnection; con->setCommandSuccess(success); + // world side is done with us; drop the keep-alive (may free the object) + con->m_self.reset(); } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/server/apps/worldserver/ACSoap/ACSoap.h b/src/server/apps/worldserver/ACSoap/ACSoap.h index bcdccc07b..36162a33c 100644 --- a/src/server/apps/worldserver/ACSoap/ACSoap.h +++ b/src/server/apps/worldserver/ACSoap/ACSoap.h @@ -20,6 +20,7 @@ #include "Define.h" #include +#include void process_message(struct soap* soap_message); void ACSoapThread(const std::string& host, uint16 port); @@ -58,6 +59,8 @@ public: bool m_success; std::string m_printBuffer; std::promise finishedPromise; + // keep-alive while a queued command still references this object; released in commandFinished() + std::shared_ptr m_self; }; #endif diff --git a/src/server/game/AI/SmartScripts/SmartScript.cpp b/src/server/game/AI/SmartScripts/SmartScript.cpp index ee060c635..a5cf7b90c 100644 --- a/src/server/game/AI/SmartScripts/SmartScript.cpp +++ b/src/server/game/AI/SmartScripts/SmartScript.cpp @@ -4528,7 +4528,6 @@ void SmartScript::ProcessEvent(SmartScriptHolder& e, Unit* unit, uint32 var0, ui break; } case SMART_EVENT_RECEIVE_HEAL: - case SMART_EVENT_DAMAGED: case SMART_EVENT_DAMAGED_TARGET: { if (var0 > e.event.minMaxRepeat.max || var0 < e.event.minMaxRepeat.min) @@ -4537,6 +4536,25 @@ void SmartScript::ProcessEvent(SmartScriptHolder& e, Unit* unit, uint32 var0, ui ProcessAction(e, unit); break; } + case SMART_EVENT_DAMAGED: + { + if (e.event.minMaxRepeat.rangeMin) // health check mode + { + if (!me || !me->IsEngaged() || !me->GetMaxHealth()) + return; + if (!me->HealthBelowPctDamaged(e.event.minMaxRepeat.rangeMin, var0)) + return; + ProcessAction(e, unit); + } + else + { + if (var0 > e.event.minMaxRepeat.max || var0 < e.event.minMaxRepeat.min) + return; + RecalcTimer(e, e.event.minMaxRepeat.repeatMin, e.event.minMaxRepeat.repeatMax); + ProcessAction(e, unit); + } + break; + } case SMART_EVENT_MOVEMENTINFORM: { if ((e.event.movementInform.type && var0 != e.event.movementInform.type) || (e.event.movementInform.id && var1 != e.event.movementInform.id)) diff --git a/src/server/game/AI/SmartScripts/SmartScriptMgr.cpp b/src/server/game/AI/SmartScripts/SmartScriptMgr.cpp index acc23295b..ac0216405 100644 --- a/src/server/game/AI/SmartScripts/SmartScriptMgr.cpp +++ b/src/server/game/AI/SmartScripts/SmartScriptMgr.cpp @@ -282,6 +282,12 @@ void SmartAIMgr::LoadSmartAIFromDB() if (temp.event.minMaxRepeat.repeatMin == 0 && temp.event.minMaxRepeat.repeatMax == 0) temp.event.event_flags |= SMART_EVENT_FLAG_NOT_REPEATABLE; break; + case SMART_EVENT_DAMAGED: + // health check mode stays below the threshold on every following hit, so force one-shot; + // normal mode keeps repeatMin/repeatMax as a cooldown (0 = fire on every hit), so leave it repeatable. + if (temp.event.minMaxRepeat.rangeMin) + temp.event.event_flags |= SMART_EVENT_FLAG_NOT_REPEATABLE; + break; case SMART_EVENT_VICTIM_CASTING: case SMART_EVENT_FRIENDLY_IS_CC: if (temp.event.friendlyCC.repeatMin == 0 && temp.event.friendlyCC.repeatMax == 0) @@ -1075,7 +1081,6 @@ bool SmartAIMgr::IsEventValid(SmartScriptHolder& e) case SMART_EVENT_MANA_PCT: case SMART_EVENT_TARGET_HEALTH_PCT: case SMART_EVENT_TARGET_MANA_PCT: - case SMART_EVENT_DAMAGED: case SMART_EVENT_DAMAGED_TARGET: case SMART_EVENT_RECEIVE_HEAL: if (!IsMinMaxValid(e, e.event.minMaxRepeat.min, e.event.minMaxRepeat.max)) @@ -1084,6 +1089,25 @@ bool SmartAIMgr::IsEventValid(SmartScriptHolder& e) if (!IsMinMaxValid(e, e.event.minMaxRepeat.repeatMin, e.event.minMaxRepeat.repeatMax)) return false; break; + case SMART_EVENT_DAMAGED: + if (e.event.minMaxRepeat.rangeMin) // health check mode + { + if (e.event.minMaxRepeat.rangeMin > 100) + { + LOG_ERROR("sql.sql", "SmartAIMgr: Entry {} SourceType {} Event {} Action {} has invalid health pct value ({}), must be between 1 and 100, skipped.", + e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.event.minMaxRepeat.rangeMin); + return false; + } + } + else // normal mode + { + if (!IsMinMaxValid(e, e.event.minMaxRepeat.min, e.event.minMaxRepeat.max)) + return false; + + if (!IsMinMaxValid(e, e.event.minMaxRepeat.repeatMin, e.event.minMaxRepeat.repeatMax)) + return false; + } + break; case SMART_EVENT_AREA_RANGE: case SMART_EVENT_AREA_CASTING: case SMART_EVENT_IS_BEHIND_TARGET: diff --git a/src/server/game/AI/SmartScripts/SmartScriptMgr.h b/src/server/game/AI/SmartScripts/SmartScriptMgr.h index 1105fe559..a8ae1cc9f 100644 --- a/src/server/game/AI/SmartScripts/SmartScriptMgr.h +++ b/src/server/game/AI/SmartScripts/SmartScriptMgr.h @@ -128,7 +128,7 @@ enum SMART_EVENT SMART_EVENT_CHARMED = 29, // onRemove (0 - on apply, 1 - on remove) SMART_EVENT_CHARMED_TARGET = 30, // NONE SMART_EVENT_SPELLHIT_TARGET = 31, // SpellID, School, CooldownMin, CooldownMax - SMART_EVENT_DAMAGED = 32, // MinDmg, MaxDmg, CooldownMin, CooldownMax + SMART_EVENT_DAMAGED = 32, // MinDmg, MaxDmg, CooldownMin, CooldownMax, rangeMin (hpPct, if set: health check mode, one-shot) SMART_EVENT_DAMAGED_TARGET = 33, // MinDmg, MaxDmg, CooldownMin, CooldownMax SMART_EVENT_MOVEMENTINFORM = 34, // MovementType(any), PointID, PathId(0 - any) SMART_EVENT_SUMMON_DESPAWNED = 35, // Entry, CooldownMin, CooldownMax diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index 36c95bbd9..0c1e8b75f 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -264,7 +264,7 @@ Creature::Creature(): Unit(), MovableMapObject(), m_groupLootTimer(0), lootingGr m_spawnId(0), m_equipmentId(0), m_originalEquipmentId(0), m_alreadyCallForHelp(false), 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), _gossipMenuId(0), m_moveInLineOfSightDisabled(false), m_moveInLineOfSightStrictlyDisabled(false), 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), - _isMissingSwimmingFlagOutOfCombat(false), m_assistanceTimer(0), _playerDamageReq(0), _damagedByPlayer(false), _isCombatMovementAllowed(true) + _isMissingSwimmingFlagOutOfCombat(false), m_assistanceTimer(0), _playerDamageReq(0), _damagedByPlayer(false), _highestPlayerAttackerLevel(0), _isCombatMovementAllowed(true) { m_regenTimer = CREATURE_REGEN_INTERVAL; m_valuesCount = UNIT_END; @@ -750,7 +750,7 @@ void Creature::Update(uint32 diff) { Group* group = sGroupMgr->GetGroupByGUID(lootingGroupLowGUID); if (group) - group->EndRoll(&loot, GetMap()); + group->EndRoll(&loot); m_groupLootTimer = 0; lootingGroupLowGUID = 0; } @@ -2874,6 +2874,13 @@ void Creature::AtEngage(Unit* target) UpdateSpeed(MOVE_FLIGHT, true); } + // Notify controlled creatures (guardians/pets) so they assist when + // the owner enters combat via assist/aggro chain, not only via damage. + for (Unit* controlled : m_Controlled) + if (Creature* cControlled = controlled->ToCreature()) + if (CreatureAI* controlledAI = cControlled->AI()) + controlledAI->OwnerAttackedBy(target); + MovementGeneratorType const movetype = GetMotionMaster()->GetCurrentMovementGeneratorType(); if (movetype == WAYPOINT_MOTION_TYPE || movetype == ESCORT_MOTION_TYPE || (IsAIEnabled && AI()->IsEscorted())) { @@ -3836,7 +3843,7 @@ bool Creature::IsDamageEnoughForLootingAndReward() const return m_creatureInfo->HasFlagsExtra(CREATURE_FLAG_EXTRA_NO_PLAYER_DAMAGE_REQ) || (_playerDamageReq == 0 && _damagedByPlayer); } -void Creature::LowerPlayerDamageReq(uint32 unDamage, bool damagedByPlayer /*= true*/) +void Creature::LowerPlayerDamageReq(uint32 unDamage, bool damagedByPlayer /*= true*/, uint8 attackerLevel /*= 0*/) { if (_playerDamageReq) _playerDamageReq > unDamage ? _playerDamageReq -= unDamage : _playerDamageReq = 0; @@ -3845,12 +3852,16 @@ void Creature::LowerPlayerDamageReq(uint32 unDamage, bool damagedByPlayer /*= tr { _damagedByPlayer = damagedByPlayer; } + + if (attackerLevel > _highestPlayerAttackerLevel) + _highestPlayerAttackerLevel = attackerLevel; } void Creature::ResetPlayerDamageReq() { _playerDamageReq = GetHealth() / 2; _damagedByPlayer = false; + _highestPlayerAttackerLevel = 0; } uint32 Creature::GetPlayerDamageReq() const diff --git a/src/server/game/Entities/Creature/Creature.h b/src/server/game/Entities/Creature/Creature.h index c0eb2a753..b492bd6c8 100644 --- a/src/server/game/Entities/Creature/Creature.h +++ b/src/server/game/Entities/Creature/Creature.h @@ -380,9 +380,10 @@ public: void SetLootRewardDisabled(bool disable) { DisableLootReward = disable; } [[nodiscard]] bool IsLootRewardDisabled() const { return DisableLootReward; } [[nodiscard]] bool IsDamageEnoughForLootingAndReward() const; - void LowerPlayerDamageReq(uint32 unDamage, bool damagedByPlayer = true); + void LowerPlayerDamageReq(uint32 unDamage, bool damagedByPlayer = true, uint8 attackerLevel = 0); void ResetPlayerDamageReq(); [[nodiscard]] uint32 GetPlayerDamageReq() const; + [[nodiscard]] uint8 GetHighestPlayerAttackerLevel() const { return _highestPlayerAttackerLevel; } [[nodiscard]] uint32 GetOriginalEntry() const { return m_originalEntry; } void SetOriginalEntry(uint32 entry) { m_originalEntry = entry; } @@ -554,6 +555,7 @@ private: uint32 _playerDamageReq; bool _damagedByPlayer; + uint8 _highestPlayerAttackerLevel; bool _isCombatMovementAllowed; }; diff --git a/src/server/game/Entities/GameObject/GameObject.cpp b/src/server/game/Entities/GameObject/GameObject.cpp index bb5dabd98..523144f2c 100644 --- a/src/server/game/Entities/GameObject/GameObject.cpp +++ b/src/server/game/Entities/GameObject/GameObject.cpp @@ -766,7 +766,7 @@ void GameObject::Update(uint32 diff) { Group* group = sGroupMgr->GetGroupByGUID(lootingGroupLowGUID); if (group) - group->EndRoll(&loot, GetMap()); + group->EndRoll(&loot); m_groupLootTimer = 0; lootingGroupLowGUID = 0; } diff --git a/src/server/game/Entities/Item/Container/Bag.cpp b/src/server/game/Entities/Item/Container/Bag.cpp index 4dfa6208a..4502722e2 100644 --- a/src/server/game/Entities/Item/Container/Bag.cpp +++ b/src/server/game/Entities/Item/Container/Bag.cpp @@ -201,7 +201,7 @@ uint32 Bag::GetItemCount(uint32 item, Item* eItem) const for (uint32 i = 0; i < GetBagSize(); ++i) { pItem = m_bagslot[i]; - if (pItem && pItem != eItem && pItem->GetTemplate()->Socket[0].Color) + if (pItem && pItem != eItem && pItem->HasSocket()) count += pItem->GetGemCountWithID(item); } } diff --git a/src/server/game/Entities/Item/Item.cpp b/src/server/game/Entities/Item/Item.cpp index 9c0c27fb7..3e8ac0b38 100644 --- a/src/server/game/Entities/Item/Item.cpp +++ b/src/server/game/Entities/Item/Item.cpp @@ -1014,8 +1014,11 @@ bool Item::HasSocket() const uint8 Item::GetGemCountWithID(uint32 GemID) const { uint8 count = 0; - for (uint32 enchant_slot = SOCK_ENCHANTMENT_SLOT; enchant_slot < SOCK_ENCHANTMENT_SLOT + MAX_GEM_SOCKETS; ++enchant_slot) + for (uint32 enchant_slot = SOCK_ENCHANTMENT_SLOT; enchant_slot <= PRISMATIC_ENCHANTMENT_SLOT; ++enchant_slot) { + if (enchant_slot == BONUS_ENCHANTMENT_SLOT) + continue; + uint32 enchant_id = GetEnchantmentId(EnchantmentSlot(enchant_slot)); if (!enchant_id) continue; @@ -1033,8 +1036,11 @@ uint8 Item::GetGemCountWithID(uint32 GemID) const uint8 Item::GetGemCountWithLimitCategory(uint32 limitCategory) const { uint8 count = 0; - for (uint32 enchant_slot = SOCK_ENCHANTMENT_SLOT; enchant_slot < SOCK_ENCHANTMENT_SLOT + MAX_GEM_SOCKETS; ++enchant_slot) + for (uint32 enchant_slot = SOCK_ENCHANTMENT_SLOT; enchant_slot <= PRISMATIC_ENCHANTMENT_SLOT; ++enchant_slot) { + if (enchant_slot == BONUS_ENCHANTMENT_SLOT) + continue; + uint32 enchant_id = GetEnchantmentId(EnchantmentSlot(enchant_slot)); if (!enchant_id) continue; diff --git a/src/server/game/Entities/Item/ItemTemplate.h b/src/server/game/Entities/Item/ItemTemplate.h index dca51af79..2ce1f3195 100644 --- a/src/server/game/Entities/Item/ItemTemplate.h +++ b/src/server/game/Entities/Item/ItemTemplate.h @@ -180,38 +180,38 @@ enum ItemFlags : uint32 enum ItemFlags2 : uint32 { - ITEM_FLAG2_FACTION_HORDE = 0x00000001, - ITEM_FLAG2_FACTION_ALLIANCE = 0x00000002, + ITEM_FLAG2_FACTION_HORDE = 0x00000001, // Only Horde can equip, loot, or buy back this item + ITEM_FLAG2_FACTION_ALLIANCE = 0x00000002, // Only Alliance can equip, loot, or buy back this item ITEM_FLAG2_DONT_IGNORE_BUY_PRICE = 0x00000004, // when item uses extended cost, gold is also required - ITEM_FLAG2_CLASSIFY_AS_CASTER = 0x00000008, // NYI + ITEM_FLAG2_CLASSIFY_AS_CASTER = 0x00000008, // NYI - unused ITEM_FLAG2_CLASSIFY_AS_PHYSICAL = 0x00000010, // NYI - ITEM_FLAG2_EVERYONE_CAN_ROLL_NEED = 0x00000020, // NYI - ITEM_FLAG2_NO_TRADE_BIND_ON_ACQUIRE = 0x00000040, // NYI - ITEM_FLAG2_CAN_TRADE_BIND_ON_ACQUIRE = 0x00000080, // NYI - ITEM_FLAG2_CAN_ONLY_ROLL_GREED = 0x00000100, + ITEM_FLAG2_EVERYONE_CAN_ROLL_NEED = 0x00000020, // Anyone can roll need + ITEM_FLAG2_NO_TRADE_BIND_ON_ACQUIRE = 0x00000040, // NYI - unused + ITEM_FLAG2_CAN_TRADE_BIND_ON_ACQUIRE = 0x00000080, // NYI - unused + ITEM_FLAG2_CAN_ONLY_ROLL_GREED = 0x00000100, // Need rolls disallowed, only Greed / Disenchant / Pass ITEM_FLAG2_CASTER_WEAPON = 0x00000200, // NYI - ITEM_FLAG2_DELETE_ON_LOGIN = 0x00000400, // NYI - ITEM_FLAG2_INTERNAL_ITEM = 0x00000800, // NYI - ITEM_FLAG2_NO_VENDOR_VALUE = 0x00001000, // NYI + ITEM_FLAG2_DELETE_ON_LOGIN = 0x00000400, // NYI - unused + ITEM_FLAG2_INTERNAL_ITEM = 0x00000800, // NYI - unused + ITEM_FLAG2_NO_VENDOR_VALUE = 0x00001000, // NYI - unused ITEM_FLAG2_SHOW_BEFORE_DISCOVERED = 0x00002000, // NYI - ITEM_FLAG2_OVERRIDE_GOLD_COST = 0x00004000, // NYI + ITEM_FLAG2_OVERRIDE_GOLD_COST = 0x00004000, // NYI - unused ITEM_FLAG2_IGNORE_DEFAULT_RATED_BG_RESTRICTIONS = 0x00008000, // NYI ITEM_FLAG2_NOT_USABLE_IN_RATED_BG = 0x00010000, // NYI ITEM_FLAG2_BNET_ACCOUNT_TRADE_OK = 0x00020000, // NYI - ITEM_FLAG2_CONFIRM_BEFORE_USE = 0x00040000, // NYI - ITEM_FLAG2_REEVALUATE_BONDING_ON_TRANSFORM = 0x00080000, // NYI - ITEM_FLAG2_NO_TRANSFORM_ON_CHARGE_DEPLETION = 0x00100000, // NYI + ITEM_FLAG2_CONFIRM_BEFORE_USE = 0x00040000, // NYI - unused + ITEM_FLAG2_REEVALUATE_BONDING_ON_TRANSFORM = 0x00080000, // NYI - unused + ITEM_FLAG2_NO_TRANSFORM_ON_CHARGE_DEPLETION = 0x00100000, // NYI - unused ITEM_FLAG2_NO_ALTER_ITEM_VISUAL = 0x00200000, // NYI ITEM_FLAG2_NO_SOURCE_FOR_ITEM_VISUAL = 0x00400000, // NYI - ITEM_FLAG2_IGNORE_QUALITY_FOR_ITEM_VISUAL_SOURCE = 0x00800000, // NYI - ITEM_FLAG2_NO_DURABILITY = 0x01000000, // NYI - ITEM_FLAG2_ROLE_TANK = 0x02000000, // NYI - ITEM_FLAG2_ROLE_HEALER = 0x04000000, // NYI - ITEM_FLAG2_ROLE_DAMAGE = 0x08000000, // NYI - ITEM_FLAG2_CAN_DROP_IN_CHALLENGE_MODE = 0x10000000, // NYI - ITEM_FLAG2_NEVER_STACK_IN_LOOT_UI = 0x20000000, // NYI - ITEM_FLAG2_DISENCHANT_TO_LOOT_TABLE = 0x40000000, // NYI - ITEM_FLAG2_USED_IN_A_TRADESKILL = 0x80000000 // NYI + ITEM_FLAG2_IGNORE_QUALITY_FOR_ITEM_VISUAL_SOURCE = 0x00800000, // NYI - unused + ITEM_FLAG2_NO_DURABILITY = 0x01000000, // NYI - unused + ITEM_FLAG2_ROLE_TANK = 0x02000000, // NYI - unused + ITEM_FLAG2_ROLE_HEALER = 0x04000000, // NYI - unused + ITEM_FLAG2_ROLE_DAMAGE = 0x08000000, // NYI - unused + ITEM_FLAG2_CAN_DROP_IN_CHALLENGE_MODE = 0x10000000, // NYI - unused + ITEM_FLAG2_NEVER_STACK_IN_LOOT_UI = 0x20000000, // NYI - unused + ITEM_FLAG2_DISENCHANT_TO_LOOT_TABLE = 0x40000000, // NYI - unused + ITEM_FLAG2_USED_IN_A_TRADESKILL = 0x80000000 // NYI - unused }; enum ItemFlagsCustom : uint32 diff --git a/src/server/game/Entities/Object/Object.cpp b/src/server/game/Entities/Object/Object.cpp index 793cb8ed2..3e2cb36dd 100644 --- a/src/server/game/Entities/Object/Object.cpp +++ b/src/server/game/Entities/Object/Object.cpp @@ -2123,7 +2123,7 @@ void WorldObject::SendMessageToSetInRange(WorldPacket const* data, float dist, b void WorldObject::SendMessageToSet(WorldPacket const* data, Player const* skipped_rcvr) const { - Acore::MessageDistDeliverer notifier(this, data, 0.0f, false, skipped_rcvr); + Acore::MessageDistDeliverer notifier(this, data, 0.0f, Acore::TeamFilter::All, skipped_rcvr); notifier.Visit(GetObjectVisibilityContainer().GetVisiblePlayersMap()); } diff --git a/src/server/game/Entities/Player/KillRewarder.cpp b/src/server/game/Entities/Player/KillRewarder.cpp index 0bbb4c541..5a32ec4b0 100644 --- a/src/server/game/Entities/Player/KillRewarder.cpp +++ b/src/server/game/Entities/Player/KillRewarder.cpp @@ -16,6 +16,7 @@ */ #include "KillRewarder.h" +#include "Creature.h" #include "Formulas.h" #include "Group.h" #include "Pet.h" @@ -161,6 +162,19 @@ void KillRewarder::_RewardXP(Player* player, float rate) else xp = 0; } + + // An ungrouped helper who out-levels everyone eligible for the kill and for + // whom the victim is gray halves the tagger's XP, matching the group + // gray-member penalty. Closes a power-leveling exploit. + if (xp) + if (Creature* creature = _victim->ToCreature()) + { + uint8 const referenceLevel = _group ? _maxLevel : player->GetLevel(); + uint8 const highestLevel = creature->GetHighestPlayerAttackerLevel(); + if (highestLevel > referenceLevel && creature->GetLevel() <= Acore::XP::GetGrayLevel(highestLevel)) + xp = xp / 2 + 1; + } + if (xp) { // 4.2.2. Apply auras modifying rewarded XP (SPELL_AURA_MOD_XP_PCT). diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index f2b3207c7..d8acdfd03 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -3891,10 +3891,10 @@ bool Player::HasSpell(uint32 spell) const return (itr != m_spells.end() && itr->second->State != PLAYERSPELL_REMOVED && itr->second->IsInSpec(m_activeSpec)); } -bool Player::HasTalent(uint32 spell, uint8 /*spec*/) const +bool Player::HasTalent(uint32 spell, uint8 spec) const { PlayerTalentMap::const_iterator itr = m_talents.find(spell); - return (itr != m_talents.end() && itr->second->State != PLAYERSPELL_REMOVED && itr->second->IsInSpec(m_activeSpec)); + return (itr != m_talents.end() && itr->second->State != PLAYERSPELL_REMOVED && itr->second->IsInSpec(spec)); } bool Player::HasActiveSpell(uint32 spell) const @@ -5703,7 +5703,7 @@ void Player::SendMessageToSet(WorldPacket const* data, Player const* skipped_rcv if (skipped_rcvr != this) SendDirectMessage(data); - Acore::MessageDistDeliverer notifier(this, data, 0.0f, false, skipped_rcvr); + Acore::MessageDistDeliverer notifier(this, data, 0.0f, Acore::TeamFilter::All, skipped_rcvr); notifier.Visit(GetObjectVisibilityContainer().GetVisiblePlayersMap()); } @@ -9418,7 +9418,7 @@ void Player::Say(std::string_view text, Language language, WorldObject const* /* SendDirectMessage(&data); // Special handling for messages, do not use visibility map for stealthed units - Acore::MessageDistDeliverer notifier(this, &data, sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_SAY), false, nullptr, true); + Acore::MessageDistDeliverer notifier(this, &data, sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_SAY), Acore::TeamFilter::All, nullptr, true); Cell::VisitObjects(this, notifier, sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_SAY)); } @@ -9446,7 +9446,7 @@ void Player::Yell(std::string_view text, Language language, WorldObject const* / SendDirectMessage(&data); // Special handling for messages, do not use visibility map for stealthed units - Acore::MessageDistDeliverer notifier(this, &data, sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_YELL), false, nullptr, true); + Acore::MessageDistDeliverer notifier(this, &data, sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_YELL), Acore::TeamFilter::All, nullptr, true); Cell::VisitObjects(this, notifier, sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_YELL)); } @@ -9474,8 +9474,27 @@ void Player::TextEmote(std::string_view text, WorldObject const* /*= nullptr*/, SendDirectMessage(&data); // Special handling for messages, do not use visibility map for stealthed units - Acore::MessageDistDeliverer notifier(this, &data, sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_TEXTEMOTE), !GetSession()->HasPermission(rbac::RBAC_PERM_TWO_SIDE_INTERACTION_CHAT), nullptr, true); - Cell::VisitObjects(this, notifier, sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_TEXTEMOTE)); + if (GetSession()->HasPermission(rbac::RBAC_PERM_TWO_SIDE_INTERACTION_CHAT)) + { + Acore::MessageDistDeliverer notifier(this, &data, sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_TEXTEMOTE), Acore::TeamFilter::All, nullptr, true); + Cell::VisitObjects(this, notifier, sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_TEXTEMOTE)); + } + else + { + // Same faction + { + Acore::MessageDistDeliverer notifier(this, &data, sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_TEXTEMOTE), Acore::TeamFilter::OwnTeam, nullptr, true); + Cell::VisitObjects(this, notifier, sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_TEXTEMOTE)); + } + // Opposite faction + { + WorldPacket data; + ChatHandler::BuildChatPacket(data, CHAT_MSG_EMOTE, LANG_UNIVERSAL, this, this, ""); + + Acore::MessageDistDeliverer notifier(this, &data, sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_TEXTEMOTE), Acore::TeamFilter::OtherTeam, nullptr, true); + Cell::VisitObjects(this, notifier, sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_TEXTEMOTE)); + } + } } void Player::TextEmote(uint32 textId, WorldObject const* target /*= nullptr*/, bool /*isBossEmote = false*/) @@ -13922,8 +13941,11 @@ InventoryResult Player::CanEquipUniqueItem(Item* pItem, uint8 eslot, uint32 limi return res; // check unique-equipped on gems - for (uint32 enchant_slot = SOCK_ENCHANTMENT_SLOT; enchant_slot < SOCK_ENCHANTMENT_SLOT + 3; ++enchant_slot) + for (uint32 enchant_slot = SOCK_ENCHANTMENT_SLOT; enchant_slot <= PRISMATIC_ENCHANTMENT_SLOT; ++enchant_slot) { + if (enchant_slot == BONUS_ENCHANTMENT_SLOT) + continue; + uint32 enchant_id = pItem->GetEnchantmentId(EnchantmentSlot(enchant_slot)); if (!enchant_id) continue; diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 951701082..484b352da 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -655,7 +655,7 @@ enum PlayerSlots #define INVENTORY_SLOT_BAG_0 255 -enum EquipmentSlots : uint32 // 19 slots +enum EquipmentSlots // 19 slots { EQUIPMENT_SLOT_START = 0, EQUIPMENT_SLOT_HEAD = 0, diff --git a/src/server/game/Entities/Player/PlayerStorage.cpp b/src/server/game/Entities/Player/PlayerStorage.cpp index 04010c812..bc9f918bd 100644 --- a/src/server/game/Entities/Player/PlayerStorage.cpp +++ b/src/server/game/Entities/Player/PlayerStorage.cpp @@ -347,7 +347,7 @@ uint32 Player::GetItemCount(uint32 item, bool inBankAlso, Item* skipItem) const if (skipItem && skipItem->GetTemplate()->GemProperties) for (uint8 i = EQUIPMENT_SLOT_START; i < INVENTORY_SLOT_ITEM_END; ++i) if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i)) - if (pItem != skipItem && pItem->GetTemplate()->Socket[0].Color) + if (pItem != skipItem && pItem->HasSocket()) count += pItem->GetGemCountWithID(item); if (inBankAlso) @@ -365,7 +365,7 @@ uint32 Player::GetItemCount(uint32 item, bool inBankAlso, Item* skipItem) const if (skipItem && skipItem->GetTemplate()->GemProperties) for (uint8 i = BANK_SLOT_ITEM_START; i < BANK_SLOT_ITEM_END; ++i) if (Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i)) - if (pItem != skipItem && pItem->GetTemplate()->Socket[0].Color) + if (pItem != skipItem && pItem->HasSocket()) count += pItem->GetGemCountWithID(item); } @@ -754,7 +754,7 @@ bool Player::HasItemOrGemWithIdEquipped(uint32 item, uint32 count, uint8 except_ continue; Item* pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i); - if (pItem && pItem->GetTemplate()->Socket[0].Color) + if (pItem && pItem->HasSocket()) { tempcount += pItem->GetGemCountWithID(item); if (tempcount >= count) @@ -2455,6 +2455,10 @@ InventoryResult Player::CanRollForItemInLFG(ItemTemplate const* proto, WorldObje SKILL_FISHING }; //Copy from function Item::GetSkill() + // Anyone can roll need on this item + if (proto->HasFlag2(ITEM_FLAG2_EVERYONE_CAN_ROLL_NEED)) + return EQUIP_ERR_OK; + if ((proto->AllowableClass & getClassMask()) == 0 || (proto->AllowableRace & getRaceMask()) == 0) return EQUIP_ERR_YOU_CAN_NEVER_USE_THAT_ITEM; diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index 5912e36eb..cad9773f1 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -1233,7 +1233,18 @@ uint32 Unit::DealDamage(Unit* attacker, Unit* victim, uint32 damage, CleanDamage uint32 unDamage = health < damage ? health : damage; bool damagedByPlayer = unDamage && attacker && (attacker->IsPlayer() || attacker->m_movedByPlayer != nullptr || attacker->GetCharmerGUID().IsPlayer()); - victim->ToCreature()->LowerPlayerDamageReq(unDamage, damagedByPlayer); + + uint8 attackerLevel = 0; + if (damagedByPlayer) + { + Player* attackerPlayer = attacker->GetCharmerOrOwnerPlayerOrPlayerItself(); + if (!attackerPlayer && attacker->m_movedByPlayer) + attackerPlayer = attacker->m_movedByPlayer->ToPlayer(); + if (attackerPlayer) + attackerLevel = attackerPlayer->GetLevel(); + } + + victim->ToCreature()->LowerPlayerDamageReq(unDamage, damagedByPlayer, attackerLevel); } } @@ -11335,22 +11346,7 @@ void Unit::SetSpeed(UnitMoveType mtype, float rate, bool forced) if (forced && IsClientControlled()) { - Player* player = const_cast(GetClientControlling()); - uint32 const counter = player->GetSession()->GetOrderCounter(); - - // register forced speed changes for WorldSession::HandleForceSpeedChangeAck - // and do it only for real sent packets and use run for run/mounted as client expected - ++player->m_forced_speed_changes[mtype]; - - WorldPacket data(speedOpcodes[static_cast(SpeedOpcodeIndex::PC)], 18); - data << GetPackGUID(); - data << counter; - if (mtype == MOVE_RUN) - data << uint8(0); // new 2.1.0 - - data << GetSpeed(mtype); - player->GetSession()->SendPacket(&data); - player->GetSession()->IncrementOrderCounter(); + SendSpeedToController(mtype, const_cast(GetClientControlling())); } else if (forced) { @@ -11383,6 +11379,24 @@ void Unit::SetSpeed(UnitMoveType mtype, float rate, bool forced) } } +void Unit::SendSpeedToController(UnitMoveType mtype, Player* target) const +{ + SpeedOpcodePair const& speedOpcodes = SetSpeed2Opc_table[mtype]; + uint32 const counter = target->GetSession()->GetOrderCounter(); + + ++target->m_forced_speed_changes[mtype]; + + WorldPacket data(speedOpcodes[static_cast(SpeedOpcodeIndex::PC)], 18); + data << GetPackGUID(); + data << counter; + if (mtype == MOVE_RUN) + data << uint8(0); + + data << GetSpeed(mtype); + target->GetSession()->SendPacket(&data); + target->GetSession()->IncrementOrderCounter(); +} + void Unit::setDeathState(DeathState s, bool despawn) { // death state needs to be updated before RemoveAllAurasOnDeath() calls HandleChannelDeathItem(..) so that @@ -14711,6 +14725,10 @@ bool Unit::SetCharmedBy(Unit* charmer, CharmType type, AuraApplication const* au _oldFactionId = GetFaction(); SetFaction(charmer->GetFaction()); + // snapshot current speed rates so we can detect speed changes + for (uint8 i = MOVE_WALK; i < MAX_MOVE_TYPE; ++i) + _charmStartSpeedRate[i] = m_speed_rate[i]; + // Set charmed charmer->SetCharm(this, true); @@ -14862,9 +14880,6 @@ void Unit::RemoveCharmedBy(Unit* charmer) CastStop(); AttackStop(); - // xinef: update speed after charming - UpdateSpeed(MOVE_RUN, false); - // xinef: do not break any controlled motion slot if (GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_CONTROLLED) == NULL_MOTION_TYPE) { @@ -14967,7 +14982,15 @@ void Unit::RemoveCharmedBy(Unit* charmer) RemoveUnitMovementFlag(MOVEMENTFLAG_FLYING); } else - ToPlayer()->SetClientControl(this, true); // verified + { + Player* targetPlayer = ToPlayer(); + targetPlayer->SetClientControl(this, true); // verified + + // Re-sync only the speed types that changed during the charm. + for (uint8 i = MOVE_WALK; i < MAX_MOVE_TYPE; ++i) + if (m_speed_rate[i] != _charmStartSpeedRate[i]) + SendSpeedToController(UnitMoveType(i), targetPlayer); + } // a guardian should always have charminfo if (playerCharmer && this != charmer->GetFirstControlled()) @@ -15863,7 +15886,7 @@ void Unit::_ExitVehicle(Position const* exitPosition) if ((!player || !(player->GetDelayedOperations() & DELAYED_VEHICLE_TELEPORT)) && vehicle->GetBase()->HasUnitTypeMask(UNIT_MASK_MINION)) if (((Minion*)vehicleBase)->GetOwner() == this) { - if (!vehicleInfo || vehicleInfo->m_ID != 349) + if (!vehicleInfo || (vehicleInfo->m_ID != 349 && vehicleBase->GetEntry() != 28192)) { vehicle->Dismiss(); } diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h index a6d53e117..070426eac 100644 --- a/src/server/game/Entities/Unit/Unit.h +++ b/src/server/game/Entities/Unit/Unit.h @@ -1744,6 +1744,7 @@ public: [[nodiscard]] float GetSpeedRate(UnitMoveType mtype) const { return m_speed_rate[mtype]; } void SetSpeed(UnitMoveType mtype, float rate, bool forced = false); void SetSpeedRate(UnitMoveType mtype, float rate) { m_speed_rate[mtype] = rate; } + void SendSpeedToController(UnitMoveType mtype, Player* target) const; void propagateSpeedChange() { GetMotionMaster()->propagateSpeedChange(); } @@ -2188,6 +2189,9 @@ protected: float m_speed_rate[MAX_MOVE_TYPE]; + // snapshot of speed rates taken when SetCharmedBy() is called + float _charmStartSpeedRate[MAX_MOVE_TYPE]{}; + CharmInfo* m_charmInfo; SharedVisionList m_sharedVision; diff --git a/src/server/game/Grids/Notifiers/GridNotifiers.h b/src/server/game/Grids/Notifiers/GridNotifiers.h index 838ed4048..8484eb3b5 100644 --- a/src/server/game/Grids/Notifiers/GridNotifiers.h +++ b/src/server/game/Grids/Notifiers/GridNotifiers.h @@ -96,33 +96,63 @@ namespace Acore void Visit(CreatureMapType&); }; + enum class TeamFilter + { + All, + OwnTeam, + OtherTeam, + }; + struct MessageDistDeliverer { WorldObject const* i_source; WorldPacket const* i_message; uint32 i_phaseMask; float i_distSq; + TeamFilter teamFilter; TeamId teamId; Player const* skipped_receiver; bool required3dDist; - MessageDistDeliverer(WorldObject const* src, WorldPacket const* msg, float dist, bool own_team_only = false, Player const* skipped = nullptr, bool req3dDist = false) - : i_source(src), i_message(msg), i_phaseMask(src->GetPhaseMask()), i_distSq(dist * dist) - , teamId((own_team_only && src->IsPlayer()) ? src->ToPlayer()->GetTeamId() : TEAM_NEUTRAL) - , skipped_receiver(skipped), required3dDist(req3dDist) - { - } + + MessageDistDeliverer(WorldObject const* src, WorldPacket const* msg, float dist, TeamFilter teamFilter = TeamFilter::All, Player const* skipped = nullptr, bool req3dDist = false) : + i_source(src), + i_message(msg), + i_phaseMask(src->GetPhaseMask()), + i_distSq(dist * dist), + teamFilter(src->IsPlayer() ? teamFilter : TeamFilter::All), + teamId(src->IsPlayer() ? src->ToPlayer()->GetTeamId() : TEAM_NEUTRAL), + skipped_receiver(skipped), + required3dDist(req3dDist) + { } + void Visit(VisiblePlayersMap const& m); void Visit(PlayerMapType& m); void Visit(CreatureMapType& m); void Visit(DynamicObjectMapType& m); - template void Visit(GridRefMgr&) {} - void SendPacket(Player* player) + template void Visit(GridRefMgr&) { } + + void SendPacket(Player* player) const { // never send packet to self - if (player == i_source || (teamId != TEAM_NEUTRAL && player->GetTeamId() != teamId) || skipped_receiver == player) + if (player == i_source || skipped_receiver == player) return; + switch (teamFilter) + { + default: + case TeamFilter::All: + break; + case TeamFilter::OwnTeam: + if (player->GetTeamId() != teamId) + return; + break; + case TeamFilter::OtherTeam: + if (player->GetTeamId() == teamId) + return; + break; + } + if (!player->HaveAtClient(i_source)) return; diff --git a/src/server/game/Groups/Group.cpp b/src/server/game/Groups/Group.cpp index e6799fec5..a6b6bc390 100644 --- a/src/server/game/Groups/Group.cpp +++ b/src/server/game/Groups/Group.cpp @@ -1414,20 +1414,20 @@ bool Group::CountRollVote(ObjectGuid playerGUID, ObjectGuid Guid, uint8 Choice) if (roll->totalPass + roll->totalNeed + roll->totalGreed >= roll->totalPlayersRolling) { - CountTheRoll(rollI, nullptr); + CountTheRoll(rollI); return true; } return false; } //called when roll timer expires -void Group::EndRoll(Loot* pLoot, Map* allowedMap) +void Group::EndRoll(Loot* pLoot) { for (Rolls::iterator itr = RollId.begin(); itr != RollId.end();) { if ((*itr)->getLoot() == pLoot) { - CountTheRoll(itr, allowedMap); //i don't have to edit player votes, who didn't vote ... he will pass + CountTheRoll(itr); //i don't have to edit player votes, who didn't vote ... he will pass itr = RollId.begin(); } else @@ -1466,7 +1466,7 @@ void Group::RemovePlayerFromRolls(ObjectGuid guid) } } -void Group::CountTheRoll(Rolls::iterator rollI, Map* allowedMap) +void Group::CountTheRoll(Rolls::iterator rollI) { Roll* roll = *rollI; if (!roll->isValid()) // is loot already deleted ? @@ -1491,7 +1491,7 @@ void Group::CountTheRoll(Rolls::iterator rollI, Map* allowedMap) continue; player = ObjectAccessor::FindPlayer(itr->first); - if (!player || (allowedMap != nullptr && player->FindMap() != allowedMap)) + if (!player) { --roll->totalNeed; continue; @@ -1555,7 +1555,7 @@ void Group::CountTheRoll(Rolls::iterator rollI, Map* allowedMap) roll->totalNeed = 0; } } - if (roll->totalNeed == 0 && roll->totalGreed > 0) // pussywizard: if (roll->totalNeed == 0 && ...), not else if, because numbers can be modified above if player is on a different map + if (roll->totalNeed == 0 && roll->totalGreed > 0) // if (roll->totalNeed == 0 && ...), not else if, because totalNeed can be decremented above when a needing player is offline { if (!roll->playerVote.empty()) { @@ -1571,7 +1571,7 @@ void Group::CountTheRoll(Rolls::iterator rollI, Map* allowedMap) continue; player = ObjectAccessor::FindPlayer(itr->first); - if (!player || (allowedMap != nullptr && player->FindMap() != allowedMap)) + if (!player) { --roll->totalGreed; continue; @@ -1669,7 +1669,7 @@ void Group::CountTheRoll(Rolls::iterator rollI, Map* allowedMap) roll->totalGreed = 0; } } - if (roll->totalNeed == 0 && roll->totalGreed == 0) // pussywizard: if, not else, because numbers can be modified above if player is on a different map + if (roll->totalNeed == 0 && roll->totalGreed == 0) // if, not else, because the totals can be decremented above when a rolling player is offline { SendLootAllPassed(*roll); diff --git a/src/server/game/Groups/Group.h b/src/server/game/Groups/Group.h index ebcc5a6bb..be19930a1 100644 --- a/src/server/game/Groups/Group.h +++ b/src/server/game/Groups/Group.h @@ -306,9 +306,9 @@ public: void NeedBeforeGreed(Loot* loot, WorldObject* pLootedObject); void MasterLoot(Loot* loot, WorldObject* pLootedObject); Rolls::iterator GetRoll(ObjectGuid Guid); - void CountTheRoll(Rolls::iterator roll, Map* allowedMap); + void CountTheRoll(Rolls::iterator roll); bool CountRollVote(ObjectGuid playerGUID, ObjectGuid Guid, uint8 Choise); - void EndRoll(Loot* loot, Map* allowedMap); + void EndRoll(Loot* loot); void RemovePlayerFromRolls(ObjectGuid guid); Rolls GetRolls() const { return RollId; } diff --git a/src/server/game/Handlers/CharacterHandler.cpp b/src/server/game/Handlers/CharacterHandler.cpp index 719bb4be1..2f34b1c55 100644 --- a/src/server/game/Handlers/CharacterHandler.cpp +++ b/src/server/game/Handlers/CharacterHandler.cpp @@ -945,16 +945,6 @@ void WorldSession::HandlePlayerLoginFromDB(LoginQueryHolder const& holder) // Place character in world (and load zone) before some object loading pCurrChar->LoadCorpse(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_CORPSE_LOCATION)); - // setting Ghost+speed if dead - if (pCurrChar->m_deathState != DeathState::Alive) - { - // not blizz like, we must correctly save and load player instead... - if (pCurrChar->getRace() == RACE_NIGHTELF) - pCurrChar->CastSpell(pCurrChar, 20584, true, 0); // auras SPELL_AURA_INCREASE_SPEED(+speed in wisp form), SPELL_AURA_INCREASE_SWIM_SPEED(+swim speed in wisp form), SPELL_AURA_TRANSFORM (to wisp form) - - pCurrChar->CastSpell(pCurrChar, 8326, true, 0); // auras SPELL_AURA_GHOST, SPELL_AURA_INCREASE_SPEED(why?), SPELL_AURA_INCREASE_SWIM_SPEED(why?) - } - // Set FFA PvP for non GM in non-rest mode if (sWorld->IsFFAPvPRealm() && !pCurrChar->IsGameMaster() && !pCurrChar->HasPlayerFlag(PLAYER_FLAGS_RESTING)) if (!pCurrChar->HasByteFlag(UNIT_FIELD_BYTES_2, 1, UNIT_BYTE2_FLAG_FFA_PVP)) diff --git a/src/server/game/Handlers/MiscHandler.cpp b/src/server/game/Handlers/MiscHandler.cpp index da9480a6b..97c40e936 100644 --- a/src/server/game/Handlers/MiscHandler.cpp +++ b/src/server/game/Handlers/MiscHandler.cpp @@ -426,7 +426,7 @@ void WorldSession::HandleLogoutRequestOpcode(WorldPackets::Character::LogoutRequ bool preventAfkSanctuaryLogout = sWorld->getIntConfig(CONFIG_AFK_PREVENT_LOGOUT) == 1 && GetPlayer()->isAFK() && sAreaTableStore.LookupEntry(GetPlayer()->GetAreaId())->IsSanctuary(); - bool preventAfkLogout = sWorld->getIntConfig(CONFIG_AFK_PREVENT_LOGOUT) == 2 + bool preventAfkLogout = ((sWorld->getIntConfig(CONFIG_AFK_PREVENT_LOGOUT) == 2) || HasAccountFlag(ACCOUNT_FLAG_NOKICK)) && GetPlayer()->isAFK(); /// @todo: Possibly add RBAC permission to log out in combat diff --git a/src/server/game/Mails/ServerMailMgr.cpp b/src/server/game/Mails/ServerMailMgr.cpp index 571e1ab44..32c8c425a 100644 --- a/src/server/game/Mails/ServerMailMgr.cpp +++ b/src/server/game/Mails/ServerMailMgr.cpp @@ -41,8 +41,8 @@ void ServerMailMgr::LoadMailServerTemplates() _serverMailStore.clear(); // for reload case - // 0 1 2 3 4 5 - QueryResult result = CharacterDatabase.Query("SELECT `id`, `moneyA`, `moneyH`, `subject`, `body`, `active` FROM `mail_server_template`"); + // 0 1 2 3 4 5 6 + QueryResult result = CharacterDatabase.Query("SELECT `id`, `senderEntry`, `moneyA`, `moneyH`, `subject`, `body`, `active` FROM `mail_server_template`"); if (!result) { LOG_INFO("server.loading", ">> Loaded 0 server mail rewards. DB table `mail_server_template` is empty."); @@ -60,11 +60,12 @@ void ServerMailMgr::LoadMailServerTemplates() ServerMail& servMail = _serverMailStore[id]; servMail.id = id; - servMail.moneyA = fields[1].Get(); - servMail.moneyH = fields[2].Get(); - servMail.subject = fields[3].Get(); - servMail.body = fields[4].Get(); - servMail.active = fields[5].Get(); + servMail.senderEntry = fields[1].Get(); + servMail.moneyA = fields[2].Get(); + servMail.moneyH = fields[3].Get(); + servMail.subject = fields[4].Get(); + servMail.body = fields[5].Get(); + servMail.active = fields[6].Get(); // Skip non-activated entries if (!servMail.active) @@ -75,6 +76,12 @@ void ServerMailMgr::LoadMailServerTemplates() LOG_ERROR("sql.sql", "Table `mail_server_template` has moneyA {} or moneyH {} larger than MAX_MONEY_AMOUNT {} for id {}, skipped.", servMail.moneyA, servMail.moneyH, MAX_MONEY_AMOUNT, servMail.id); continue; } + + if (servMail.senderEntry && !sObjectMgr->GetCreatureTemplate(servMail.senderEntry)) + { + LOG_ERROR("sql.sql", "Table `mail_server_template` has invalid senderEntry {} (no matching creature_template) for id {}, falling back to default sender.", servMail.senderEntry, servMail.id); + servMail.senderEntry = 0; + } } while (result->NextRow()); LoadMailServerTemplatesItems(); @@ -281,7 +288,7 @@ void ServerMailMgr::LoadMailServerTemplatesConditions() } while (result->NextRow()); } -void ServerMailMgr::SendServerMail(Player* player, uint32 id, uint32 money, +void ServerMailMgr::SendServerMail(Player* player, uint32 id, uint32 senderEntry, uint32 money, std::vector const& items, std::vector const& conditions, std::string const& subject, std::string const& body) const @@ -292,7 +299,9 @@ void ServerMailMgr::SendServerMail(Player* player, uint32 id, uint32 money, CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); - MailSender sender(MAIL_NORMAL, player->GetGUID().GetCounter(), MAIL_STATIONERY_GM); + MailSender sender = senderEntry + ? MailSender(MAIL_CREATURE, senderEntry, MAIL_STATIONERY_DEFAULT) + : MailSender(MAIL_NORMAL, player->GetGUID().GetCounter(), MAIL_STATIONERY_GM); MailDraft draft(subject, body); draft.AddMoney(money); diff --git a/src/server/game/Mails/ServerMailMgr.h b/src/server/game/Mails/ServerMailMgr.h index 73092d546..892dfd4a0 100644 --- a/src/server/game/Mails/ServerMailMgr.h +++ b/src/server/game/Mails/ServerMailMgr.h @@ -128,6 +128,7 @@ struct ServerMail { ServerMail() = default; uint32 id{ 0 }; + uint32 senderEntry{ 0 }; ///< Entry from creature_template. 0 for default "Customer Support" sender. uint32 moneyA{ 0 }; uint32 moneyH{ 0 }; std::string subject; @@ -205,6 +206,7 @@ public: * * @param player The recipient player. * @param id The template ID. + * @param senderEntry Entry from creature_template. 0 for default "Customer Support" sender. * @param money Money reward. * @param items List of items to include in the mail. * @param conditions List of the conditions for the mail. @@ -212,7 +214,7 @@ public: * @param body Mail body. * @param active Whether the mail template is active. */ - void SendServerMail(Player* player, uint32 id, uint32 money, std::vector const& items, std::vector const& conditions, std::string const& subject, std::string const& body) const; + void SendServerMail(Player* player, uint32 id, uint32 senderEntry, uint32 money, std::vector const& items, std::vector const& conditions, std::string const& subject, std::string const& body) const; /** * @brief Retrieves the entire server mail store. diff --git a/src/server/game/Spells/SpellInfoCorrections.cpp b/src/server/game/Spells/SpellInfoCorrections.cpp index 0f71208b0..ea20e8202 100644 --- a/src/server/game/Spells/SpellInfoCorrections.cpp +++ b/src/server/game/Spells/SpellInfoCorrections.cpp @@ -5209,6 +5209,12 @@ void SpellMgr::LoadSpellInfoCorrections() spellInfo->Effects[EFFECT_0].BasePoints = 1; }); + // 51036 Summon Venture Co. Air Patrol + ApplySpellFix({ 51036 }, [](SpellInfo* spellInfo) + { + spellInfo->Effects[EFFECT_0].TargetA = SpellImplicitTargetInfo(TARGET_DEST_CASTER); + }); + for (uint32 i = 0; i < GetSpellInfoStoreSize(); ++i) { SpellInfo* spellInfo = mSpellInfoMap[i]; diff --git a/src/server/scripts/Events/midsummer.cpp b/src/server/scripts/Events/midsummer.cpp index 5e5b96321..cff91c99a 100644 --- a/src/server/scripts/Events/midsummer.cpp +++ b/src/server/scripts/Events/midsummer.cpp @@ -428,6 +428,13 @@ class spell_bonfires_blessing : public AuraScript return ValidateSpellInfo({ SPELL_BONFIRES_BLESSING }); } + bool CheckProc(ProcEventInfo& eventInfo) + { + Unit* actor = eventInfo.GetActor(); + Unit* actionTarget = eventInfo.GetActionTarget(); + return actor && actionTarget && actor->IsValidAttackTarget(actionTarget); + } + void OnApply(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) { if (!IsHolidayActive(HOLIDAY_FIRE_FESTIVAL)) @@ -439,6 +446,7 @@ class spell_bonfires_blessing : public AuraScript void Register() override { + DoCheckProc += AuraCheckProcFn(spell_bonfires_blessing::CheckProc); AfterEffectApply += AuraEffectApplyFn(spell_bonfires_blessing::OnApply, EFFECT_0, SPELL_AURA_PROC_TRIGGER_SPELL, AURA_EFFECT_HANDLE_REAL_OR_REAPPLY_MASK); } }; diff --git a/src/server/scripts/Northrend/IcecrownCitadel/boss_blood_queen_lana_thel.cpp b/src/server/scripts/Northrend/IcecrownCitadel/boss_blood_queen_lana_thel.cpp index e5d0f9bef..c392f324f 100644 --- a/src/server/scripts/Northrend/IcecrownCitadel/boss_blood_queen_lana_thel.cpp +++ b/src/server/scripts/Northrend/IcecrownCitadel/boss_blood_queen_lana_thel.cpp @@ -83,9 +83,14 @@ enum Shadowmourne bool IsVampire(Unit const* unit) { - if (unit->HasAnyAuras(SPELL_ESSENCE_OF_BLOOD_QUEEN, SPELL_ESSENCE_OF_THE_BLOOD_QUEEN_PLR, SPELL_FRENZIED_BLOODTHIRST)) - return true; - return false; + if (!unit) + return false; + + return unit->HasAnyAuras( + sSpellMgr->GetSpellIdForDifficulty(SPELL_ESSENCE_OF_BLOOD_QUEEN, unit), + sSpellMgr->GetSpellIdForDifficulty(SPELL_ESSENCE_OF_THE_BLOOD_QUEEN_PLR, unit), + sSpellMgr->GetSpellIdForDifficulty(SPELL_FRENZIED_BLOODTHIRST, unit) + ); } enum Events diff --git a/src/server/scripts/Northrend/Naxxramas/boss_razuvious.cpp b/src/server/scripts/Northrend/Naxxramas/boss_razuvious.cpp index 4aae788f5..d6fbd906a 100644 --- a/src/server/scripts/Northrend/Naxxramas/boss_razuvious.cpp +++ b/src/server/scripts/Northrend/Naxxramas/boss_razuvious.cpp @@ -33,7 +33,7 @@ enum Says enum Spells { - SPELL_UNBALANCING_STRIKE = 26613, + SPELL_UNBALANCING_STRIKE = 55470, SPELL_DISRUPTING_SHOUT = 55543, SPELL_JAGGED_KNIFE = 55550, SPELL_HOPELESS = 29125, diff --git a/src/server/scripts/Northrend/Naxxramas/instance_naxxramas.cpp b/src/server/scripts/Northrend/Naxxramas/instance_naxxramas.cpp index bfb88be6e..3a032ade7 100644 --- a/src/server/scripts/Northrend/Naxxramas/instance_naxxramas.cpp +++ b/src/server/scripts/Northrend/Naxxramas/instance_naxxramas.cpp @@ -595,6 +595,7 @@ public: case NOT_STARTED: { _horsemanAchievement = true; + _events.CancelEvent(EVENT_AND_THEY_WOULD_ALL_GO_DOWN_TOGETHER); if (!horsemanKilled) break; diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_yoggsaron.cpp b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_yoggsaron.cpp index 3fd86f9de..91f6f7abf 100644 --- a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_yoggsaron.cpp +++ b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_yoggsaron.cpp @@ -102,6 +102,8 @@ enum YoggSpells SPELL_FOCUSED_ANGER = 57688, // CONSTRICTOR TENTACLE + SPELL_CONSTRICTOR_TENTACLE = 64132, + SPELL_CONSTRICTOR_TENTACLE_SUMMON = 64133, SPELL_LUNGE = 64123, SPELL_SQUEEZE = 64125, @@ -853,7 +855,7 @@ struct boss_yoggsaron_sara : public ScriptedAI events.Repeat(Milliseconds(uint32((50000 + urand(0, 10000)) * _summonSpeed))); break; case EVENT_SARA_P2_SUMMON_T2: // CONSTRICTOR - SpawnTentacle(NPC_CONSTRICTOR_TENTACLE); + me->CastCustomSpell(SPELL_CONSTRICTOR_TENTACLE, SPELLVALUE_MAX_TARGETS, 1, me, false); events.Repeat(Milliseconds(uint32((15000 + urand(0, 5000))* _summonSpeed))); break; case EVENT_SARA_P2_SUMMON_T3: // CORRUPTOR @@ -886,7 +888,7 @@ struct boss_yoggsaron_sara : public ScriptedAI me->SetPosition(me->GetPositionX(), me->GetPositionY(), 355, me->GetOrientation()); SpawnTentacle(NPC_CRUSHER_TENTACLE); - SpawnTentacle(NPC_CONSTRICTOR_TENTACLE); + me->CastCustomSpell(SPELL_CONSTRICTOR_TENTACLE, SPELLVALUE_MAX_TARGETS, 1, me, false); SpawnTentacle(NPC_CORRUPTOR_TENTACLE); SpawnTentacle(NPC_CORRUPTOR_TENTACLE); @@ -1569,13 +1571,46 @@ struct boss_yoggsaron_constrictor_tentacle : public ScriptedAI boss_yoggsaron_constrictor_tentacle(Creature* creature) : ScriptedAI(creature) { me->SetCombatMovement(false); - _checkTimer = 1; _playerGUID.Clear(); } - uint32 _checkTimer; ObjectGuid _playerGUID; + void GrabPlayer(Unit* target) + { + target->CastSpell(me, SPELL_LUNGE, true); + target->CastSpell(target, SPELL_SQUEEZE, true); + _playerGUID = target->GetGUID(); + } + + void IsSummonedBy(WorldObject* summoner) override + { + me->CastSpell(me, SPELL_TENTACLE_ERUPT, true); + me->CastSpell(me, SPELL_VOID_ZONE_SMALL, true); + me->HandleEmoteCommand(EMOTE_ONESHOT_EMERGE); + me->SetInCombatWithZone(); + + // The summoner is the marked player (64133 is self-cast); 64132 already + // picked a valid enemy, so only re-check that they can still be squeezed. + // Fall back to the nearest valid player otherwise. + Unit* target = nullptr; + if (Player* player = summoner ? summoner->ToPlayer() : nullptr) + if (player->IsAlive() && !player->HasAura(sSpellMgr->GetSpellIdForDifficulty(SPELL_SQUEEZE, me))) + target = player; + + if (!target) + target = SelectConstrictTarget(); + + if (target) + GrabPlayer(target); + + // Summoned from a player, so register with Sara to keep the encounter's + // despawn handling working. + if (InstanceScript* instance = me->GetInstanceScript()) + if (Creature* sara = instance->GetCreature(DATA_SARA)) + sara->AI()->JustSummoned(me); + } + Unit* SelectConstrictTarget() { Player* target = nullptr; @@ -1598,27 +1633,6 @@ struct boss_yoggsaron_constrictor_tentacle : public ScriptedAI return target; } - void UpdateAI(uint32 diff) override - { - if (_checkTimer) - { - _checkTimer += diff; - if (_checkTimer >= 1000 && !me->HasUnitState(UNIT_STATE_STUNNED)) - { - if (Unit* target = SelectConstrictTarget()) - { - target->CastSpell(me, SPELL_LUNGE, true); - target->CastSpell(target, SPELL_SQUEEZE, true); - _playerGUID = target->GetGUID(); - _checkTimer = 0; - return; - } - - _checkTimer = 1; - } - } - } - void DoAction(int32 param) override { if (param == ACTION_REMOVE_STUN) @@ -1629,6 +1643,8 @@ struct boss_yoggsaron_constrictor_tentacle : public ScriptedAI { if (Unit* player = ObjectAccessor::GetUnit(*me, _playerGUID)) player->RemoveAura(sSpellMgr->GetSpellIdForDifficulty(SPELL_SQUEEZE, me)); + + me->DespawnOrUnsummon(5s); } }; @@ -2677,6 +2693,28 @@ class spell_yogg_saron_target_selectors : public SpellScript } }; +// 64132 - Constrictor Tentacle +class spell_yogg_saron_constrictor_tentacle_aura : public AuraScript +{ + PrepareAuraScript(spell_yogg_saron_constrictor_tentacle_aura); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_CONSTRICTOR_TENTACLE_SUMMON }); + } + + void OnApply(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) + { + // Spawns the tentacle on the marked player so it grabs them on the spot. + GetTarget()->CastSpell(GetTarget(), SPELL_CONSTRICTOR_TENTACLE_SUMMON, true); + } + + void Register() override + { + AfterEffectApply += AuraEffectApplyFn(spell_yogg_saron_constrictor_tentacle_aura::OnApply, EFFECT_0, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL); + } +}; + // 63305 - Grim Reprisal class spell_yogg_saron_grim_reprisal_aura : public AuraScript { @@ -2807,6 +2845,7 @@ void AddSC_boss_yoggsaron() RegisterSpellScript(spell_yogg_saron_empowering_shadows); RegisterSpellScript(spell_yogg_saron_in_the_maws_of_the_old_god); RegisterSpellScript(spell_yogg_saron_target_selectors); + RegisterSpellScript(spell_yogg_saron_constrictor_tentacle_aura); RegisterSpellScript(spell_yogg_saron_grim_reprisal_aura); // ACHIEVEMENTS diff --git a/src/server/scripts/Northrend/VioletHold/violet_hold.cpp b/src/server/scripts/Northrend/VioletHold/violet_hold.cpp index d2dbf17af..b202de260 100644 --- a/src/server/scripts/Northrend/VioletHold/violet_hold.cpp +++ b/src/server/scripts/Northrend/VioletHold/violet_hold.cpp @@ -166,7 +166,6 @@ struct violet_hold_trashAI : public npc_escortAI { Instance = c->GetInstanceScript(); PortalLoc = Instance->GetData(DATA_PORTAL_LOCATION); - AddedWaypoints = false; UseAlternate = false; } @@ -209,58 +208,59 @@ struct violet_hold_trashAI : public npc_escortAI ScriptedAI::MoveInLineOfSight(who); } + void InitializeAI() override + { + npc_escortAI::InitializeAI(); + + switch (PortalLoc) + { + case 0: + for (int i = 0; i < 6; i++) + AddWaypoint(i, FirstPortalTrashWPs[i][0] + irand(-1, 1), FirstPortalTrashWPs[i][1] + irand(-1, 1), FirstPortalTrashWPs[i][2] + irand(-1, 1), 0); + me->SetHomePosition(FirstPortalTrashWPs[5][0], FirstPortalTrashWPs[5][1], FirstPortalTrashWPs[5][2], 3.149439f); + break; + case 1: + UseAlternate = (bool)urand(0, 1); + if (!UseAlternate) + { + for (int i = 0; i < 9; i++) + AddWaypoint(i, SecondPortalTrashWPs1[i][0] + irand(-1, 1), SecondPortalTrashWPs1[i][1] + irand(-1, 1), SecondPortalTrashWPs1[i][2], 0); + me->SetHomePosition(SecondPortalTrashWPs1[8][0] + irand(-1, 1), SecondPortalTrashWPs1[8][1] + irand(-1, 1), SecondPortalTrashWPs1[8][2] + irand(-1, 1), 3.149439f); + } + else + { + for (int i = 0; i < 8; i++) + AddWaypoint(i, SecondPortalTrashWPs2[i][0] + irand(-1, 1), SecondPortalTrashWPs2[i][1] + irand(-1, 1), SecondPortalTrashWPs2[i][2], 0); + me->SetHomePosition(SecondPortalTrashWPs2[7][0], SecondPortalTrashWPs2[7][1], SecondPortalTrashWPs2[7][2], 3.149439f); + } + break; + case 2: + for (int i = 0; i < 8; i++) + AddWaypoint(i, ThirdPortalTrashWPs[i][0] + irand(-1, 1), ThirdPortalTrashWPs[i][1] + irand(-1, 1), ThirdPortalTrashWPs[i][2], 0); + me->SetHomePosition(ThirdPortalTrashWPs[7][0], ThirdPortalTrashWPs[7][1], ThirdPortalTrashWPs[7][2], 3.149439f); + break; + case 3: + for (int i = 0; i < 9; i++) + AddWaypoint(i, FourthPortalTrashWPs[i][0] + irand(-1, 1), FourthPortalTrashWPs[i][1] + irand(-1, 1), FourthPortalTrashWPs[i][2], 0); + me->SetHomePosition(FourthPortalTrashWPs[8][0], FourthPortalTrashWPs[8][1], FourthPortalTrashWPs[8][2], 3.149439f); + break; + case 4: + for (int i = 0; i < 6; i++) + AddWaypoint(i, FifthPortalTrashWPs[i][0] + irand(-1, 1), FifthPortalTrashWPs[i][1] + irand(-1, 1), FifthPortalTrashWPs[i][2], 0); + me->SetHomePosition(FifthPortalTrashWPs[5][0], FifthPortalTrashWPs[5][1], FifthPortalTrashWPs[5][2], 3.149439f); + break; + case 5: + for (int i = 0; i < 4; i++) + AddWaypoint(i, SixthPoralTrashWPs[i][0] + irand(-1, 1), SixthPoralTrashWPs[i][1] + irand(-1, 1), SixthPoralTrashWPs[i][2], 0); + me->SetHomePosition(SixthPoralTrashWPs[3][0], SixthPoralTrashWPs[3][1], SixthPoralTrashWPs[3][2], 3.149439f); + break; + } + SetDespawnAtEnd(false); + Start(true); + } + void UpdateAI(uint32 diff) override { - if (!AddedWaypoints) - { - AddedWaypoints = true; - switch (PortalLoc) - { - case 0: - for (int i = 0; i < 6; i++) - AddWaypoint(i, FirstPortalTrashWPs[i][0] + irand(-1, 1), FirstPortalTrashWPs[i][1] + irand(-1, 1), FirstPortalTrashWPs[i][2] + irand(-1, 1), 0); - me->SetHomePosition(FirstPortalTrashWPs[5][0], FirstPortalTrashWPs[5][1], FirstPortalTrashWPs[5][2], 3.149439f); - break; - case 1: - UseAlternate = (bool)urand(0, 1); - if (!UseAlternate) - { - for (int i = 0; i < 9; i++) - AddWaypoint(i, SecondPortalTrashWPs1[i][0] + irand(-1, 1), SecondPortalTrashWPs1[i][1] + irand(-1, 1), SecondPortalTrashWPs1[i][2], 0); - me->SetHomePosition(SecondPortalTrashWPs1[8][0] + irand(-1, 1), SecondPortalTrashWPs1[8][1] + irand(-1, 1), SecondPortalTrashWPs1[8][2] + irand(-1, 1), 3.149439f); - } - else - { - for (int i = 0; i < 8; i++) - AddWaypoint(i, SecondPortalTrashWPs2[i][0] + irand(-1, 1), SecondPortalTrashWPs2[i][1] + irand(-1, 1), SecondPortalTrashWPs2[i][2], 0); - me->SetHomePosition(SecondPortalTrashWPs2[7][0], SecondPortalTrashWPs2[7][1], SecondPortalTrashWPs2[7][2], 3.149439f); - } - break; - case 2: - for (int i = 0; i < 8; i++) - AddWaypoint(i, ThirdPortalTrashWPs[i][0] + irand(-1, 1), ThirdPortalTrashWPs[i][1] + irand(-1, 1), ThirdPortalTrashWPs[i][2], 0); - me->SetHomePosition(ThirdPortalTrashWPs[7][0], ThirdPortalTrashWPs[7][1], ThirdPortalTrashWPs[7][2], 3.149439f); - break; - case 3: - for (int i = 0; i < 9; i++) - AddWaypoint(i, FourthPortalTrashWPs[i][0] + irand(-1, 1), FourthPortalTrashWPs[i][1] + irand(-1, 1), FourthPortalTrashWPs[i][2], 0); - me->SetHomePosition(FourthPortalTrashWPs[8][0], FourthPortalTrashWPs[8][1], FourthPortalTrashWPs[8][2], 3.149439f); - break; - case 4: - for (int i = 0; i < 6; i++) - AddWaypoint(i, FifthPortalTrashWPs[i][0] + irand(-1, 1), FifthPortalTrashWPs[i][1] + irand(-1, 1), FifthPortalTrashWPs[i][2], 0); - me->SetHomePosition(FifthPortalTrashWPs[5][0], FifthPortalTrashWPs[5][1], FifthPortalTrashWPs[5][2], 3.149439f); - break; - case 5: - for (int i = 0; i < 4; i++) - AddWaypoint(i, SixthPoralTrashWPs[i][0] + irand(-1, 1), SixthPoralTrashWPs[i][1] + irand(-1, 1), SixthPoralTrashWPs[i][2], 0); - me->SetHomePosition(SixthPoralTrashWPs[3][0], SixthPoralTrashWPs[3][1], SixthPoralTrashWPs[3][2], 3.149439f); - break; - } - SetDespawnAtEnd(false); - Start(true); - } - npc_escortAI::UpdateAI(diff); } @@ -302,7 +302,6 @@ struct violet_hold_trashAI : public npc_escortAI protected: InstanceScript* Instance; - bool AddedWaypoints; uint32 PortalLoc; bool UseAlternate; }; @@ -834,7 +833,6 @@ struct npc_azure_saboteur : public npc_escortAI _boss = _instance->GetData(DATA_WAVE_COUNT) == 6 ? _instance->GetPersistentData(PERSISTENT_DATA_FIRST_BOSS) : _instance->GetPersistentData(PERSISTENT_DATA_SECOND_BOSS); - _addedWaypoints = false; _isOpening = false; } @@ -870,48 +868,49 @@ struct npc_azure_saboteur : public npc_escortAI } } + void InitializeAI() override + { + npc_escortAI::InitializeAI(); + + switch (_boss) + { + case BOSS_MORAGG: + for (int i = 0; i < 3; i++) + AddWaypoint(i, SaboteurFinalPos1[i][0], SaboteurFinalPos1[i][1], SaboteurFinalPos1[i][2], 0); + me->SetHomePosition(SaboteurFinalPos1[2][0], SaboteurFinalPos1[2][1], SaboteurFinalPos1[2][2], 4.762346f); + break; + case BOSS_EREKEM: + for (int i = 0; i < 3; i++) + AddWaypoint(i, SaboteurFinalPos2[i][0], SaboteurFinalPos2[i][1], SaboteurFinalPos2[i][2], 0); + me->SetHomePosition(SaboteurFinalPos2[2][0], SaboteurFinalPos2[2][1], SaboteurFinalPos2[2][2], 1.862674f); + break; + case BOSS_ICHORON: + for (int i = 0; i < 2; i++) + AddWaypoint(i, SaboteurFinalPos3[i][0], SaboteurFinalPos3[i][1], SaboteurFinalPos3[i][2], 0); + me->SetHomePosition(SaboteurFinalPos3[1][0], SaboteurFinalPos3[1][1], SaboteurFinalPos3[1][2], 5.500638f); + break; + case BOSS_LAVANTHOR: + AddWaypoint(0, SaboteurFinalPos4[0], SaboteurFinalPos4[1], SaboteurFinalPos4[2], 0); + me->SetHomePosition(SaboteurFinalPos4[0], SaboteurFinalPos4[1], SaboteurFinalPos4[2], 3.991108f); + break; + case BOSS_XEVOZZ: + AddWaypoint(0, SaboteurFinalPos5[0], SaboteurFinalPos5[1], SaboteurFinalPos5[2], 0); + me->SetHomePosition(SaboteurFinalPos5[0], SaboteurFinalPos5[1], SaboteurFinalPos5[2], 1.100841f); + break; + case BOSS_ZURAMAT: + for (int i = 0; i < 5; i++) + AddWaypoint(i, SaboteurFinalPos6[i][0], SaboteurFinalPos6[i][1], SaboteurFinalPos6[i][2], 0); + me->SetHomePosition(SaboteurFinalPos6[4][0], SaboteurFinalPos6[4][1], SaboteurFinalPos6[4][2], 0.983031f); + break; + } + SetDespawnAtEnd(false); + Start(true); + } + void UpdateAI(uint32 diff) override { npc_escortAI::UpdateAI(diff); - if (!_addedWaypoints) - { - _addedWaypoints = true; - switch (_boss) - { - case BOSS_MORAGG: - for (int i = 0; i < 3; i++) - AddWaypoint(i, SaboteurFinalPos1[i][0], SaboteurFinalPos1[i][1], SaboteurFinalPos1[i][2], 0); - me->SetHomePosition(SaboteurFinalPos1[2][0], SaboteurFinalPos1[2][1], SaboteurFinalPos1[2][2], 4.762346f); - break; - case BOSS_EREKEM: - for (int i = 0; i < 3; i++) - AddWaypoint(i, SaboteurFinalPos2[i][0], SaboteurFinalPos2[i][1], SaboteurFinalPos2[i][2], 0); - me->SetHomePosition(SaboteurFinalPos2[2][0], SaboteurFinalPos2[2][1], SaboteurFinalPos2[2][2], 1.862674f); - break; - case BOSS_ICHORON: - for (int i = 0; i < 2; i++) - AddWaypoint(i, SaboteurFinalPos3[i][0], SaboteurFinalPos3[i][1], SaboteurFinalPos3[i][2], 0); - me->SetHomePosition(SaboteurFinalPos3[1][0], SaboteurFinalPos3[1][1], SaboteurFinalPos3[1][2], 5.500638f); - break; - case BOSS_LAVANTHOR: - AddWaypoint(0, SaboteurFinalPos4[0], SaboteurFinalPos4[1], SaboteurFinalPos4[2], 0); - me->SetHomePosition(SaboteurFinalPos4[0], SaboteurFinalPos4[1], SaboteurFinalPos4[2], 3.991108f); - break; - case BOSS_XEVOZZ: - AddWaypoint(0, SaboteurFinalPos5[0], SaboteurFinalPos5[1], SaboteurFinalPos5[2], 0); - me->SetHomePosition(SaboteurFinalPos5[0], SaboteurFinalPos5[1], SaboteurFinalPos5[2], 1.100841f); - break; - case BOSS_ZURAMAT: - for (int i = 0; i < 5; i++) - AddWaypoint(i, SaboteurFinalPos6[i][0], SaboteurFinalPos6[i][1], SaboteurFinalPos6[i][2], 0); - me->SetHomePosition(SaboteurFinalPos6[4][0], SaboteurFinalPos6[4][1], SaboteurFinalPos6[4][2], 0.983031f); - break; - } - SetDespawnAtEnd(false); - Start(true); - } - if (_isOpening) { _events.Update(diff); @@ -951,7 +950,6 @@ struct npc_azure_saboteur : public npc_escortAI private: InstanceScript* _instance; - bool _addedWaypoints; uint8 _boss; bool _isOpening; EventMap _events; diff --git a/src/server/scripts/Northrend/zone_sholazar_basin.cpp b/src/server/scripts/Northrend/zone_sholazar_basin.cpp index c97f094e7..9a289c098 100644 --- a/src/server/scripts/Northrend/zone_sholazar_basin.cpp +++ b/src/server/scripts/Northrend/zone_sholazar_basin.cpp @@ -1089,6 +1089,24 @@ class spell_shango_tracks : public SpellScript } }; +// 51036 - Summon Venture Co. Pilot +class spell_venture_pilot_summon : public SpellScript +{ + PrepareSpellScript(spell_venture_pilot_summon); + + void SetDest(SpellDestination& dest) + { + // Spawn to the right of the caster at flight altitude (negative Y = right in local coords) + Position const offset = { 0.0f, -10.0f, 0.0f, 0.0f }; + dest.RelocateOffset(offset); + } + + void Register() override + { + OnDestinationTargetSelect += SpellDestinationTargetSelectFn(spell_venture_pilot_summon::SetDest, EFFECT_0, TARGET_DEST_CASTER); + } +}; + enum ReturnedSevenfold { SPELL_FREYAS_WARD = 51845, @@ -1138,4 +1156,5 @@ void AddSC_sholazar_basin() RegisterSpellScript(spell_shango_tracks); RegisterSpellScript(spell_q12611_deathbolt); + RegisterSpellScript(spell_venture_pilot_summon); } diff --git a/src/server/scripts/Northrend/zone_storm_peaks.cpp b/src/server/scripts/Northrend/zone_storm_peaks.cpp index 7b5637693..1df406b29 100644 --- a/src/server/scripts/Northrend/zone_storm_peaks.cpp +++ b/src/server/scripts/Northrend/zone_storm_peaks.cpp @@ -1290,6 +1290,73 @@ struct npc_oathbound_warder : public ScriptedAI } }; +class spell_q13010_jokkum_summon : public SpellScript +{ + PrepareSpellScript(spell_q13010_jokkum_summon); + + void HandleSummon(SpellEffIndex effIndex) + { + PreventHitDefaultEffect(effIndex); + + Unit* caster = GetCaster(); + Player* player = caster->ToPlayer(); + if (!player) + return; + + if (player->IsInDisallowedMountForm()) + player->RemoveAurasByType(SPELL_AURA_MOD_SHAPESHIFT); + + uint32 entry = uint32(GetSpellInfo()->Effects[effIndex].MiscValue); + SummonPropertiesEntry const* properties = sSummonPropertiesStore.LookupEntry(uint32(GetSpellInfo()->Effects[effIndex].MiscValueB)); + uint32 duration = uint32(GetSpellInfo()->GetDuration()); + + Position pos = caster->GetPosition(); + if (Creature* summon = caster->GetMap()->SummonCreature(entry, pos, properties, duration, caster, GetSpellInfo()->Id)) + { + uint32 spellId = GetSpellInfo()->Effects[EFFECT_0].CalcValue(); + caster->CastSpell(summon, spellId, true); + + if (summon->IsImmuneToNPC()) + summon->SetDisableGravity(false); + } + } + + void Register() override + { + OnEffectHit += SpellEffectFn(spell_q13010_jokkum_summon::HandleSummon, EFFECT_0, SPELL_EFFECT_SUMMON); + } +}; + +enum KingJokkum +{ + NPC_KING_JOKKUM = 30331 +}; + +class spell_riding_jokkum : public AuraScript +{ + PrepareAuraScript(spell_riding_jokkum); + + void HandleEffectRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/) + { + Unit* charm = GetUnitOwner()->GetCharm(); + if (!charm || charm->GetEntry() != NPC_KING_JOKKUM) + return; + + Creature* summoned = charm->ToCreature(); + if (!summoned) + return; + + AuraRemoveMode removeMode = GetTargetApplication()->GetRemoveMode(); + if (removeMode == AURA_REMOVE_BY_CANCEL) + summoned->DespawnOrUnsummon(); + } + + void Register() override + { + OnEffectRemove += AuraEffectRemoveFn(spell_riding_jokkum::HandleEffectRemove, EFFECT_1, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL); + } +}; + void AddSC_storm_peaks() { RegisterCreatureAI(npc_frosthound); @@ -1316,4 +1383,6 @@ void AddSC_storm_peaks() RegisterSpellScript(spell_fatal_strike); RegisterSpellScript(spell_player_mount_wyrm); RegisterSpellScript(spell_eject_passenger_wild_wyrm); + RegisterSpellScript(spell_q13010_jokkum_summon); + RegisterSpellScript(spell_riding_jokkum); } diff --git a/src/server/scripts/Outland/CoilfangReservoir/SlavePens/boss_ahune.cpp b/src/server/scripts/Outland/CoilfangReservoir/SlavePens/boss_ahune.cpp index f34f0176d..53f0d1c55 100644 --- a/src/server/scripts/Outland/CoilfangReservoir/SlavePens/boss_ahune.cpp +++ b/src/server/scripts/Outland/CoilfangReservoir/SlavePens/boss_ahune.cpp @@ -304,11 +304,11 @@ struct npc_frozen_core : public ScriptedAI void JustDied(Unit* /*killer*/) override { - if (Creature* ahune = _instance->GetCreature(DATA_AHUNE)) - Unit::Kill(me, ahune); - DoCastSelf(SPELL_SUMMON_LOOT_MISSILE, true); DoCastSelf(SPELL_MINION_DESPAWNER, true); + + if (Creature* ahune = _instance->GetCreature(DATA_AHUNE)) + Unit::Kill(me, ahune); } void DoAction(int32 action) override diff --git a/src/server/scripts/World/server_mail.cpp b/src/server/scripts/World/server_mail.cpp index 3a40f7492..59d40372d 100644 --- a/src/server/scripts/World/server_mail.cpp +++ b/src/server/scripts/World/server_mail.cpp @@ -58,6 +58,7 @@ public: sServerMailMgr->SendServerMail( session->GetPlayer(), servMail.id, + servMail.senderEntry, money, items, conditions,