From 15f84f6154d5e9fc5f7fb0623a4a96c54f4ad5c6 Mon Sep 17 00:00:00 2001 From: Billy Jones Date: Sat, 13 Jun 2026 05:47:30 +0100 Subject: [PATCH 01/80] fix(DB/SAI): Engineer Trinket NPCs (#24695) Co-authored-by: Ludwig Co-authored-by: Claude Opus 4.8 (1M context) --- .../rev_1770804493564634772.sql | 54 ++++++++ .../rev_1770829879217658167.sql | 59 +++++++++ .../rev_1770846981140352968.sql | 46 +++++++ .../rev_1770946272358222202.sql | 49 ++++++++ src/server/scripts/World/npc_professions.cpp | 115 ------------------ 5 files changed, 208 insertions(+), 115 deletions(-) create mode 100644 data/sql/updates/pending_db_world/rev_1770804493564634772.sql create mode 100644 data/sql/updates/pending_db_world/rev_1770829879217658167.sql create mode 100644 data/sql/updates/pending_db_world/rev_1770846981140352968.sql create mode 100644 data/sql/updates/pending_db_world/rev_1770946272358222202.sql diff --git a/data/sql/updates/pending_db_world/rev_1770804493564634772.sql b/data/sql/updates/pending_db_world/rev_1770804493564634772.sql new file mode 100644 index 000000000..f29bdd037 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1770804493564634772.sql @@ -0,0 +1,54 @@ +-- SMILES O'BYRON (Engineering(350 / Gnomish Engineer) - Ultrasafe Transporter: Toshley's Station) + +-- Add NPC Text for learn Ultrasafe Transporter gossip concurrent to already learned schematic gossip +DELETE FROM `npc_text` WHERE (`ID` = 10369); +INSERT INTO `npc_text` (`ID`, `text0_0`, `text0_1`, `BroadcastTextID0`, `lang0`, `Probability0`, `em0_0`, `em0_1`, `em0_2`, `em0_3`, `em0_4`, `em0_5`, `text1_0`, `text1_1`, `BroadcastTextID1`, `lang1`, `Probability1`, `em1_0`, `em1_1`, `em1_2`, `em1_3`, `em1_4`, `em1_5`, `text2_0`, `text2_1`, `BroadcastTextID2`, `lang2`, `Probability2`, `em2_0`, `em2_1`, `em2_2`, `em2_3`, `em2_4`, `em2_5`, `text3_0`, `text3_1`, `BroadcastTextID3`, `lang3`, `Probability3`, `em3_0`, `em3_1`, `em3_2`, `em3_3`, `em3_4`, `em3_5`, `text4_0`, `text4_1`, `BroadcastTextID4`, `lang4`, `Probability4`, `em4_0`, `em4_1`, `em4_2`, `em4_3`, `em4_4`, `em4_5`, `text5_0`, `text5_1`, `BroadcastTextID5`, `lang5`, `Probability5`, `em5_0`, `em5_1`, `em5_2`, `em5_3`, `em5_4`, `em5_5`, `text6_0`, `text6_1`, `BroadcastTextID6`, `lang6`, `Probability6`, `em6_0`, `em6_1`, `em6_2`, `em6_3`, `em6_4`, `em6_5`, `text7_0`, `text7_1`, `BroadcastTextID7`, `lang7`, `Probability7`, `em7_0`, `em7_1`, `em7_2`, `em7_3`, `em7_4`, `em7_5`, `VerifiedBuild`) VALUES +(10369, 'Once you have built the device, you simply activate the device to be transported to Toshley''s Station! A few people have reported being transformed into chickens, but I am sure they were exaggerating!$B$BIf possible I would try and not use the device on a day when someone else has, but it will probably work out alright even if you do!', '', 19133, 0, 0, 0, 0, 0, 0, 0, 0, '', '', 0, 0, 0, 0, 0, 0, 0, 0, 0, '', '', 0, 0, 0, 0, 0, 0, 0, 0, 0, '', '', 0, 0, 0, 0, 0, 0, 0, 0, 0, '', '', 0, 0, 0, 0, 0, 0, 0, 0, 0, '', '', 0, 0, 0, 0, 0, 0, 0, 0, 0, '', '', 0, 0, 0, 0, 0, 0, 0, 0, 0, '', '', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + +-- Add learn Ultrasafe Transporter: Toshley's Station gossip concurrent to Smiles' base gossip menu (8306) with correct text +DELETE FROM `gossip_menu` WHERE (`MenuID` = 8307) AND (`TextID` IN (10369)); +INSERT INTO `gossip_menu` (`MenuID`, `TextID`) VALUES +(8307, 10369); + +-- Add gossip menu option (0) +DELETE FROM `gossip_menu_option` WHERE (`MenuID` = 8306) AND (`OptionID` IN (0)); +INSERT INTO `gossip_menu_option` (`MenuID`, `OptionID`, `OptionIcon`, `OptionText`, `OptionBroadcastTextID`, `OptionType`, `OptionNpcFlag`, `ActionMenuID`, `ActionPoiID`, `BoxCoded`, `BoxMoney`, `BoxText`, `BoxBroadcastTextID`, `VerifiedBuild`) VALUES +(8306, 0, 0, 'I must build a beacon for this marvelous device!', 9997, 1, 1, 8307, 0, 0, 0, '', 0, 0); + +-- Add secondary gossip text for players that have already learned Ultrasafe Transporter +DELETE FROM `gossip_menu` WHERE (`MenuID` = 8306) AND (`TextID` IN (10368)); +INSERT INTO `gossip_menu` (`MenuID`, `TextID`) VALUES +(8306, 10368); + +-- Add condition (Engineering >= 350) to show option 0 +DELETE FROM `conditions` WHERE (`SourceTypeOrReferenceId` = 15) AND (`SourceGroup` = 8306) AND (`SourceEntry` = 0) AND (`SourceId` = 0) AND (`ElseGroup` = 0) AND (`ConditionTypeOrReference` = 7) AND (`ConditionTarget` = 0) AND (`ConditionValue1` = 202) AND (`ConditionValue2` = 350) AND (`ConditionValue3` = 0); +INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES +(15, 8306, 0, 0, 0, 7, 0, 202, 350, 0, 0, 0, 0, '', 'Show Ultrasafe Transporter gossip option if Engineering skill is >= 350'); + +-- Add condition (Has Gnomish Engineer) to show option 0 +DELETE FROM `conditions` WHERE (`SourceTypeOrReferenceId` = 15) AND (`SourceGroup` = 8306) AND (`SourceEntry` = 0) AND (`SourceId` = 0) AND (`ElseGroup` = 0) AND (`ConditionTypeOrReference` = 25) AND (`ConditionTarget` = 0) AND (`ConditionValue1` = 20219) AND (`ConditionValue2` = 0) AND (`ConditionValue3` = 0); +INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES +(15, 8306, 0, 0, 0, 25, 0, 20219, 0, 0, 0, 0, 0, '', 'Show Ultrasafe Transporter gossip option if player has Gnomish Engineer'); + +-- Add condition (NOT Learned Ultrasafe Transporter) to show option 0 +DELETE FROM `conditions` WHERE (`SourceTypeOrReferenceId` = 15) AND (`SourceGroup` = 8306) AND (`SourceEntry` = 0) AND (`SourceId` = 0) AND (`ElseGroup` = 0) AND (`ConditionTypeOrReference` = 25) AND (`ConditionTarget` = 0) AND (`ConditionValue1` = 36955) AND (`ConditionValue2` = 0) AND (`ConditionValue3` = 0); +INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES +(15, 8306, 0, 0, 0, 25, 0, 36955, 0, 0, 1, 0, 0, '', 'Show Ultrasafe Transporter gossip option if not already learned'); + +-- Add condition to show base gossip text when Ultrasafe Transporter has NOT been learned +DELETE FROM `conditions` WHERE (`SourceTypeOrReferenceId` = 14) AND (`SourceGroup` = 8306) AND (`SourceEntry` = 10410) AND (`SourceId` = 0) AND (`ElseGroup` = 0) AND (`ConditionTypeOrReference` = 25) AND (`ConditionTarget` = 0) AND (`ConditionValue1` = 36955) AND (`ConditionValue2` = 0) AND (`ConditionValue3` = 0); +INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES +(14, 8306, 10410, 0, 0, 25, 0, 36955, 0, 0, 1, 0, 0, '', 'Show gossip text regarding ''Ultrasafe Transporter: Toshley''s Station'' if it has NOT been learned'); + +-- Add condition to show gossip text when Ultrasafe Transporter has already been learned +DELETE FROM `conditions` WHERE (`SourceTypeOrReferenceId` = 14) AND (`SourceGroup` = 8306) AND (`SourceEntry` = 10368) AND (`SourceId` = 0) AND (`ElseGroup` = 0) AND (`ConditionTypeOrReference` = 25) AND (`ConditionTarget` = 0) AND (`ConditionValue1` = 36955) AND (`ConditionValue2` = 0) AND (`ConditionValue3` = 0); +INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES +(14, 8306, 10368, 0, 0, 25, 0, 36955, 0, 0, 0, 0, 0, '', 'Show gossip text regarding ''Ultrasafe Transporter: Toshley''s Station'' if it has already been learned'); + +-- Update Smiles to use SmartAI +UPDATE `creature_template` SET `AIName` = 'SmartAI', `ScriptName` = '' WHERE (`entry` = 21494) AND (`name` = 'Smiles O''Byron'); + +-- Add SmartAI to learn spell Ultrasafe Transporter: Toshley's Station to invoker(player) using triggered flag for spell (36957) +DELETE FROM `smart_scripts` WHERE (`entryorguid` = 21494) 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 +(21494, 0, 0, 0, 62, 0, 100, 0, 8306, 0, 0, 0, 0, 0, 134, 36957, 2, 0, 1, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 'Smiles O''Byron - On Gossip Option 0 Selected - Invoker Cast ''Ultrasafe Transporter - Toshley`s Station'''); diff --git a/data/sql/updates/pending_db_world/rev_1770829879217658167.sql b/data/sql/updates/pending_db_world/rev_1770829879217658167.sql new file mode 100644 index 000000000..0451ac799 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1770829879217658167.sql @@ -0,0 +1,59 @@ +-- KABLAMM FARFLINGER (Engineering(350 / Goblin Engineer)) - Dimensional Ripper: Area 52 + +-- Add NPC Text for learn schematic gossip +DELETE FROM `npc_text` WHERE (`ID` = 10367); +INSERT INTO `npc_text` (`ID`, `text0_0`, `text0_1`, `BroadcastTextID0`, `lang0`, `Probability0`, `em0_0`, `em0_1`, `em0_2`, `em0_3`, `em0_4`, `em0_5`, `text1_0`, `text1_1`, `BroadcastTextID1`, `lang1`, `Probability1`, `em1_0`, `em1_1`, `em1_2`, `em1_3`, `em1_4`, `em1_5`, `text2_0`, `text2_1`, `BroadcastTextID2`, `lang2`, `Probability2`, `em2_0`, `em2_1`, `em2_2`, `em2_3`, `em2_4`, `em2_5`, `text3_0`, `text3_1`, `BroadcastTextID3`, `lang3`, `Probability3`, `em3_0`, `em3_1`, `em3_2`, `em3_3`, `em3_4`, `em3_5`, `text4_0`, `text4_1`, `BroadcastTextID4`, `lang4`, `Probability4`, `em4_0`, `em4_1`, `em4_2`, `em4_3`, `em4_4`, `em4_5`, `text5_0`, `text5_1`, `BroadcastTextID5`, `lang5`, `Probability5`, `em5_0`, `em5_1`, `em5_2`, `em5_3`, `em5_4`, `em5_5`, `text6_0`, `text6_1`, `BroadcastTextID6`, `lang6`, `Probability6`, `em6_0`, `em6_1`, `em6_2`, `em6_3`, `em6_4`, `em6_5`, `text7_0`, `text7_1`, `BroadcastTextID7`, `lang7`, `Probability7`, `em7_0`, `em7_1`, `em7_2`, `em7_3`, `em7_4`, `em7_5`, `VerifiedBuild`) VALUES +(10367, 'The theory behind it is that we completely destroy you with a massive explosion wherever you are and send those particles through a dimensional rip and then re-implode you at the machine here. Instant Transport! It might not work ALL the time, but what kind of goblin engineer are you! If survival was your first priority you could never be a real Goblin Engineer!$B$BHere is the recipe you will need to make the Dimensional Ripper and try it out!', '', 9995, 0, 0, 0, 0, 0, 0, 0, 0, '', '', 0, 0, 0, 0, 0, 0, 0, 0, 0, '', '', 0, 0, 0, 0, 0, 0, 0, 0, 0, '', '', 0, 0, 0, 0, 0, 0, 0, 0, 0, '', '', 0, 0, 0, 0, 0, 0, 0, 0, 0, '', '', 0, 0, 0, 0, 0, 0, 0, 0, 0, '', '', 0, 0, 0, 0, 0, 0, 0, 0, 0, '', '', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + +-- Add NPC Text for already learned schematic gossip text +DELETE FROM `npc_text` WHERE (`ID` = 10366); +INSERT INTO `npc_text` (`ID`, `text0_0`, `text0_1`, `BroadcastTextID0`, `lang0`, `Probability0`, `em0_0`, `em0_1`, `em0_2`, `em0_3`, `em0_4`, `em0_5`, `text1_0`, `text1_1`, `BroadcastTextID1`, `lang1`, `Probability1`, `em1_0`, `em1_1`, `em1_2`, `em1_3`, `em1_4`, `em1_5`, `text2_0`, `text2_1`, `BroadcastTextID2`, `lang2`, `Probability2`, `em2_0`, `em2_1`, `em2_2`, `em2_3`, `em2_4`, `em2_5`, `text3_0`, `text3_1`, `BroadcastTextID3`, `lang3`, `Probability3`, `em3_0`, `em3_1`, `em3_2`, `em3_3`, `em3_4`, `em3_5`, `text4_0`, `text4_1`, `BroadcastTextID4`, `lang4`, `Probability4`, `em4_0`, `em4_1`, `em4_2`, `em4_3`, `em4_4`, `em4_5`, `text5_0`, `text5_1`, `BroadcastTextID5`, `lang5`, `Probability5`, `em5_0`, `em5_1`, `em5_2`, `em5_3`, `em5_4`, `em5_5`, `text6_0`, `text6_1`, `BroadcastTextID6`, `lang6`, `Probability6`, `em6_0`, `em6_1`, `em6_2`, `em6_3`, `em6_4`, `em6_5`, `text7_0`, `text7_1`, `BroadcastTextID7`, `lang7`, `Probability7`, `em7_0`, `em7_1`, `em7_2`, `em7_3`, `em7_4`, `em7_5`, `VerifiedBuild`) VALUES +(10366, 'It''s good to see an engineer brave enough to make the device. We goblin engineers laugh at danger!', '', 10000, 0, 0, 0, 0, 0, 0, 0, 0, '', '', 0, 0, 0, 0, 0, 0, 0, 0, 0, '', '', 0, 0, 0, 0, 0, 0, 0, 0, 0, '', '', 0, 0, 0, 0, 0, 0, 0, 0, 0, '', '', 0, 0, 0, 0, 0, 0, 0, 0, 0, '', '', 0, 0, 0, 0, 0, 0, 0, 0, 0, '', '', 0, 0, 0, 0, 0, 0, 0, 0, 0, '', '', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + +-- Update gossip option (0) to be correct (This NPC shouldn't be a trainer) +DELETE FROM `gossip_menu_option` WHERE (`MenuID` = 8308) AND (`OptionID` IN (0)); +INSERT INTO `gossip_menu_option` (`MenuID`, `OptionID`, `OptionIcon`, `OptionText`, `OptionBroadcastTextID`, `OptionType`, `OptionNpcFlag`, `ActionMenuID`, `ActionPoiID`, `BoxCoded`, `BoxMoney`, `BoxText`, `BoxBroadcastTextID`, `VerifiedBuild`) VALUES +(8308, 0, 0, 'This Dimensional Imploder sounds dangerous! How can I make one?', 9994, 1, 1, 8309, 0, 0, 0, '', 0, 0); + +-- Add gossip menu for learn schematic +DELETE FROM `gossip_menu` WHERE (`MenuID` = 8309) AND (`TextID` IN (10367)); +INSERT INTO `gossip_menu` (`MenuID`, `TextID`) VALUES +(8309, 10367); + +-- Add gossip text for already learned schematic +DELETE FROM `gossip_menu` WHERE (`MenuID` = 8308) AND (`TextID` IN (10366)); +INSERT INTO `gossip_menu` (`MenuID`, `TextID`) VALUES +(8308, 10366); + +-- Add condition to show gossip option if engineering >= 350 +DELETE FROM `conditions` WHERE (`SourceTypeOrReferenceId` = 15) AND (`SourceGroup` = 8308) AND (`SourceEntry` = 0) AND (`SourceId` = 0) AND (`ElseGroup` = 0) AND (`ConditionTypeOrReference` = 7) AND (`ConditionTarget` = 0) AND (`ConditionValue1` = 202) AND (`ConditionValue2` = 350) AND (`ConditionValue3` = 0); +INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES +(15, 8308, 0, 0, 0, 7, 0, 202, 350, 0, 0, 0, 0, '', 'Show option to learn Dimensional Ripper: Area 52 if engineering >= 350'); + +-- Add condition to show gossip option if player has Goblin Engineer +DELETE FROM `conditions` WHERE (`SourceTypeOrReferenceId` = 15) AND (`SourceGroup` = 8308) AND (`SourceEntry` = 0) AND (`SourceId` = 0) AND (`ElseGroup` = 0) AND (`ConditionTypeOrReference` = 25) AND (`ConditionTarget` = 0) AND (`ConditionValue1` = 20222) AND (`ConditionValue2` = 0) AND (`ConditionValue3` = 0); +INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES +(15, 8308, 0, 0, 0, 25, 0, 20222, 0, 0, 0, 0, 0, '', 'Show option to learn Dimensional Ripper: Area 52 if player has Goblin Engineer'); + +-- Add condition to show gossip option if player does NOT have schematic +DELETE FROM `conditions` WHERE (`SourceTypeOrReferenceId` = 15) AND (`SourceGroup` = 8308) AND (`SourceEntry` = 0) AND (`SourceId` = 0) AND (`ElseGroup` = 0) AND (`ConditionTypeOrReference` = 25) AND (`ConditionTarget` = 0) AND (`ConditionValue1` = 36954) AND (`ConditionValue2` = 0) AND (`ConditionValue3` = 0); +INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES +(15, 8308, 0, 0, 0, 25, 0, 36954, 0, 0, 1, 0, 0, '', 'Show option to learn Dimensional Ripper: Area 52 if player does NOT already have schematic'); + +-- Add condition to show alt gossip text if player has schematic +DELETE FROM `conditions` WHERE (`SourceTypeOrReferenceId` = 14) AND (`SourceGroup` = 8308) AND (`SourceEntry` = 10366) AND (`SourceId` = 0) AND (`ElseGroup` = 0) AND (`ConditionTypeOrReference` = 25) AND (`ConditionTarget` = 0) AND (`ConditionValue1` = 36954) AND (`ConditionValue2` = 0) AND (`ConditionValue3` = 0); +INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES +(14, 8308, 10366, 0, 0, 25, 0, 36954, 0, 0, 0, 0, 0, '', 'Show learned schematic gossip text if player already has Dimensional Ripper: Area 52 schematic'); + +-- Add condition to show base gossip text if player does NOT have schematic +DELETE FROM `conditions` WHERE (`SourceTypeOrReferenceId` = 14) AND (`SourceGroup` = 8308) AND (`SourceEntry` = 10365) AND (`SourceId` = 0) AND (`ElseGroup` = 0) AND (`ConditionTypeOrReference` = 25) AND (`ConditionTarget` = 0) AND (`ConditionValue1` = 36954) AND (`ConditionValue2` = 0) AND (`ConditionValue3` = 0); +INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES +(14, 8308, 10365, 0, 0, 25, 0, 36954, 0, 0, 1, 0, 0, '', 'Show base gossip text if player does not have Dimensional Ripper: Area 52 schematic'); + +-- Update Kablamm to use SMART-AI and FORCE-GOSSIP +UPDATE `creature_template` SET `AIName` = 'SmartAI', `ScriptName` = '', `type_flags` = 134217728 WHERE (`entry` = 21493) AND (`name` = 'Kablamm Farflinger'); + +-- Add Smart-AI to learn spell Dimensional Ripper: Area 52 to invoker(player) +DELETE FROM `smart_scripts` WHERE (`entryorguid` = 21493) 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 +(21493, 0, 0, 0, 62, 0, 100, 0, 8308, 0, 0, 0, 0, 0, 134, 36956, 2, 0, 1, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 'Kablamm Farflinger - On Gossip Option 0 Selected - Invoker Cast ''Dimensional Ripper: Area 52'''); diff --git a/data/sql/updates/pending_db_world/rev_1770846981140352968.sql b/data/sql/updates/pending_db_world/rev_1770846981140352968.sql new file mode 100644 index 000000000..af1bf971f --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1770846981140352968.sql @@ -0,0 +1,46 @@ +-- ZAP FARFLINGER (Engineering(260 / Goblin Engineer)) - Dimensional Ripper: Everlook + +-- Dimensional Ripper gossip option already updated in Kablamm Farflinger update / Both brothers share post learn gossip texts + +-- Add gossip option (0) to learn schematic +DELETE FROM `gossip_menu_option` WHERE (`MenuID` = 6092) AND (`OptionID` IN (0)); +INSERT INTO `gossip_menu_option` (`MenuID`, `OptionID`, `OptionIcon`, `OptionText`, `OptionBroadcastTextID`, `OptionType`, `OptionNpcFlag`, `ActionMenuID`, `ActionPoiID`, `BoxCoded`, `BoxMoney`, `BoxText`, `BoxBroadcastTextID`, `VerifiedBuild`) VALUES +(6092, 0, 0, 'This Dimensional Imploder sounds dangerous! How can I make one?', 9994, 1, 1, 8309, 0, 0, 0, '', 0, 0); + +-- Add gossip text for already learned schematic +DELETE FROM `gossip_menu` WHERE (`MenuID` = 6092) AND (`TextID` IN (10366)); +INSERT INTO `gossip_menu` (`MenuID`, `TextID`) VALUES +(6092, 10366); + +-- Add condition to show gossip option if engineering >= 260 +DELETE FROM `conditions` WHERE (`SourceTypeOrReferenceId` = 15) AND (`SourceGroup` = 6092) AND (`SourceEntry` = 0) AND (`SourceId` = 0) AND (`ElseGroup` = 0) AND (`ConditionTypeOrReference` = 7) AND (`ConditionTarget` = 0) AND (`ConditionValue1` = 202) AND (`ConditionValue2` = 260) AND (`ConditionValue3` = 0); +INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES +(15, 6092, 0, 0, 0, 7, 0, 202, 260, 0, 0, 0, 0, '', 'Show option to learn Dimensional Ripper: Everlook if engineering >= 260'); + +-- Add condition to show gossip option if player has Goblin Engineer +DELETE FROM `conditions` WHERE (`SourceTypeOrReferenceId` = 15) AND (`SourceGroup` = 6092) AND (`SourceEntry` = 0) AND (`SourceId` = 0) AND (`ElseGroup` = 0) AND (`ConditionTypeOrReference` = 25) AND (`ConditionTarget` = 0) AND (`ConditionValue1` = 20222) AND (`ConditionValue2` = 0) AND (`ConditionValue3` = 0); +INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES +(15, 6092, 0, 0, 0, 25, 0, 20222, 0, 0, 0, 0, 0, '', 'Show option to learn Dimensional Ripper: Everlook if player has Goblin Engineer'); + +-- Add condition to show gossip option if player does NOT have schematic +DELETE FROM `conditions` WHERE (`SourceTypeOrReferenceId` = 15) AND (`SourceGroup` = 6092) AND (`SourceEntry` = 0) AND (`SourceId` = 0) AND (`ElseGroup` = 0) AND (`ConditionTypeOrReference` = 25) AND (`ConditionTarget` = 0) AND (`ConditionValue1` = 23486) AND (`ConditionValue2` = 0) AND (`ConditionValue3` = 0); +INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES +(15, 6092, 0, 0, 0, 25, 0, 23486, 0, 0, 1, 0, 0, '', 'Show option to learn Dimensional Ripper: Everlook if player does NOT already have schematic'); + +-- Add condition to show alt gossip text if player has schematic +DELETE FROM `conditions` WHERE (`SourceTypeOrReferenceId` = 14) AND (`SourceGroup` = 6092) AND (`SourceEntry` = 10366) AND (`SourceId` = 0) AND (`ElseGroup` = 0) AND (`ConditionTypeOrReference` = 25) AND (`ConditionTarget` = 0) AND (`ConditionValue1` = 23486) AND (`ConditionValue2` = 0) AND (`ConditionValue3` = 0); +INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES +(14, 6092, 10366, 0, 0, 25, 0, 23486, 0, 0, 0, 0, 0, '', 'Show learned schematic gossip text if player already has Dimensional Ripper: Everlook schematic'); + +-- Add condition to show base gossip text if player does NOT have schematic +DELETE FROM `conditions` WHERE (`SourceTypeOrReferenceId` = 14) AND (`SourceGroup` = 6092) AND (`SourceEntry` = 7249) AND (`SourceId` = 0) AND (`ElseGroup` = 0) AND (`ConditionTypeOrReference` = 25) AND (`ConditionTarget` = 0) AND (`ConditionValue1` = 23486) AND (`ConditionValue2` = 0) AND (`ConditionValue3` = 0); +INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES +(14, 6092, 7249, 0, 0, 25, 0, 23486, 0, 0, 1, 0, 0, '', 'Show base gossip text if player does not have Dimensional Ripper: Everlook schematic'); + +-- Update Zap to use SMART-AI and FORCE-GOSSIP +UPDATE `creature_template` SET `type_flags` = 134217728, `AIName` = 'SmartAI', `ScriptName` = '' WHERE (`entry` = 14742) AND (`name` = 'Zap Farflinger'); + +-- Add Smart-AI to learn spell Dimensional Ripper: Everlook to invoker(player) +DELETE FROM `smart_scripts` WHERE (`entryorguid` = 14742) 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 +(14742, 0, 0, 0, 62, 0, 100, 0, 6092, 0, 0, 0, 0, 0, 134, 23490, 2, 0, 1, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 'Zap Farflinger - On Gossip Option 0 Selected - Invoker Cast \'Dimension Ripper - Everlook\''); diff --git a/data/sql/updates/pending_db_world/rev_1770946272358222202.sql b/data/sql/updates/pending_db_world/rev_1770946272358222202.sql new file mode 100644 index 000000000..060bb5181 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1770946272358222202.sql @@ -0,0 +1,49 @@ +-- JHORDY LAPFORGE (Engineering(260 / Gnomish Engineer)) - Ultrasafe Transporter: Gadgetzan + +-- Add gossip option (0) to learn schematic +DELETE FROM `gossip_menu_option` WHERE (`MenuID` = 6094) AND (`OptionID` IN (0)); +INSERT INTO `gossip_menu_option` (`MenuID`, `OptionID`, `OptionIcon`, `OptionText`, `OptionBroadcastTextID`, `OptionType`, `OptionNpcFlag`, `ActionMenuID`, `ActionPoiID`, `BoxCoded`, `BoxMoney`, `BoxText`, `BoxBroadcastTextID`, `VerifiedBuild`) VALUES +(6094, 0, 0, 'I must build a beacon for this marvelous device!', 9997, 1, 1, 6095, 0, 0, 0, '', 0, 0); + +-- Add gossip menu for learn schematic +DELETE FROM `gossip_menu` WHERE (`MenuID` = 6095) AND (`TextID` IN (7252)); +INSERT INTO `gossip_menu` (`MenuID`, `TextID`) VALUES +(6095, 7252); + +-- Add gossip text for already learned schematic +DELETE FROM `gossip_menu` WHERE (`MenuID` = 6094) AND (`TextID` IN (7253)); +INSERT INTO `gossip_menu` (`MenuID`, `TextID`) VALUES +(6094, 7253); + +-- Add condition to show gossip option if engineering >= 260 +DELETE FROM `conditions` WHERE (`SourceTypeOrReferenceId` = 15) AND (`SourceGroup` = 6094) AND (`SourceEntry` = 0) AND (`SourceId` = 0) AND (`ElseGroup` = 0) AND (`ConditionTypeOrReference` = 7) AND (`ConditionTarget` = 0) AND (`ConditionValue1` = 202) AND (`ConditionValue2` = 260) AND (`ConditionValue3` = 0); +INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES +(15, 6094, 0, 0, 0, 7, 0, 202, 260, 0, 0, 0, 0, '', 'Show option to learn Ultrasafe Transporter: Gadgetzan if engineering >= 260'); + +-- Add condition to show gossip option if player has Gnomish Engineer +DELETE FROM `conditions` WHERE (`SourceTypeOrReferenceId` = 15) AND (`SourceGroup` = 6094) AND (`SourceEntry` = 0) AND (`SourceId` = 0) AND (`ElseGroup` = 0) AND (`ConditionTypeOrReference` = 25) AND (`ConditionTarget` = 0) AND (`ConditionValue1` = 20219) AND (`ConditionValue2` = 0) AND (`ConditionValue3` = 0); +INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES +(15, 6094, 0, 0, 0, 25, 0, 20219, 0, 0, 0, 0, 0, '', 'Show option to learn Ultrasafe Transporter: Gadgetzan if player has Gnomish Engineer'); + +-- Add condition to show gossip option if player does NOT have schematic +DELETE FROM `conditions` WHERE (`SourceTypeOrReferenceId` = 15) AND (`SourceGroup` = 6094) AND (`SourceEntry` = 0) AND (`SourceId` = 0) AND (`ElseGroup` = 0) AND (`ConditionTypeOrReference` = 25) AND (`ConditionTarget` = 0) AND (`ConditionValue1` = 23489) AND (`ConditionValue2` = 0) AND (`ConditionValue3` = 0); +INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES +(15, 6094, 0, 0, 0, 25, 0, 23489, 0, 0, 1, 0, 0, '', 'Show option to learn Ultrasafe Transporter: Gadgetzan if player does NOT already have schematic'); + +-- Add condition to show alt gossip text if player has schematic +DELETE FROM `conditions` WHERE (`SourceTypeOrReferenceId` = 14) AND (`SourceGroup` = 6094) AND (`SourceEntry` = 7253) AND (`SourceId` = 0) AND (`ElseGroup` = 0) AND (`ConditionTypeOrReference` = 25) AND (`ConditionTarget` = 0) AND (`ConditionValue1` = 23489) AND (`ConditionValue2` = 0) AND (`ConditionValue3` = 0); +INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES +(14, 6094, 7253, 0, 0, 25, 0, 23489, 0, 0, 0, 0, 0, '', 'Show learned schematic gossip text if player already has Ultrasafe Transporter: Gadgetzan schematic'); + +-- Add condition to show base gossip text if player does NOT have schematic +DELETE FROM `conditions` WHERE (`SourceTypeOrReferenceId` = 14) AND (`SourceGroup` = 6094) AND (`SourceEntry` = 7251) AND (`SourceId` = 0) AND (`ElseGroup` = 0) AND (`ConditionTypeOrReference` = 25) AND (`ConditionTarget` = 0) AND (`ConditionValue1` = 23489) AND (`ConditionValue2` = 0) AND (`ConditionValue3` = 0); +INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES +(14, 6094, 7251, 0, 0, 25, 0, 23489, 0, 0, 1, 0, 0, '', 'Show base gossip text if player does not have Ultrasafe Transporter: Gadgetzan schematic'); + +-- Update Jhordy to use SMART-AI and FORCE-GOSSIP +UPDATE `creature_template` SET `type_flags` = 134217728, `AIName` = 'SmartAI', `ScriptName` = '' WHERE (`entry` = 14743) AND (`name` = 'Jhordy Lapforge'); + +-- Add Smart-AI to learn spell Ultrasafe Transporter: Gadgetzan to invoker(player) +DELETE FROM `smart_scripts` WHERE (`entryorguid` = 14743) 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 +(14743, 0, 0, 0, 62, 0, 100, 0, 6094, 0, 0, 0, 0, 0, 134, 23491, 2, 0, 1, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 'Jhordy Lapforge - On Gossip Option 0 Selected - Invoker Cast \'Ultrasafe Transporter: Gadgetzan\''); diff --git a/src/server/scripts/World/npc_professions.cpp b/src/server/scripts/World/npc_professions.cpp index a4c203ef8..c4bf87629 100644 --- a/src/server/scripts/World/npc_professions.cpp +++ b/src/server/scripts/World/npc_professions.cpp @@ -833,120 +833,6 @@ public: } }; -/*### -# engineering trinkets -###*/ - -enum EngineeringTrinkets -{ - NPC_ZAP = 14742, - NPC_JHORDY = 14743, - NPC_KABLAM = 21493, - NPC_SMILES = 21494, - - SPELL_LEARN_TO_EVERLOOK = 23490, - SPELL_LEARN_TO_GADGET = 23491, - SPELL_LEARN_TO_AREA52 = 36956, - SPELL_LEARN_TO_TOSHLEY = 36957, - - SPELL_TO_EVERLOOK = 23486, - SPELL_TO_GADGET = 23489, - SPELL_TO_AREA52 = 36954, - SPELL_TO_TOSHLEY = 36955, -}; - -#define GOSSIP_ITEM_ZAP "This Dimensional Imploder sounds dangerous! How can I make one?" -#define GOSSIP_ITEM_JHORDY "I must build a beacon for this marvelous device!" -#define GOSSIP_ITEM_KABLAM "[PH] Unknown" - -class npc_engineering_tele_trinket : public CreatureScript -{ -public: - npc_engineering_tele_trinket() : CreatureScript("npc_engineering_tele_trinket") { } - - bool CanLearn(Player* player, uint32 textId, uint32 altTextId, uint32 skillValue, uint32 reqSpellId, uint32 spellId, uint32& npcTextId) - { - bool res = false; - npcTextId = textId; - if (player->GetBaseSkillValue(SKILL_ENGINEERING) >= skillValue && player->HasSpell(reqSpellId)) - { - if (!player->HasSpell(spellId)) - res = true; - else - npcTextId = altTextId; - } - return res; - } - - bool OnGossipHello(Player* player, Creature* creature) override - { - uint32 npcTextId = 0; - std::string gossipItem; - bool canLearn = false; - - if (player->HasSkill(SKILL_ENGINEERING)) - { - switch (creature->GetEntry()) - { - case NPC_ZAP: - canLearn = CanLearn(player, 6092, 0, 260, S_GOBLIN, SPELL_TO_EVERLOOK, npcTextId); - if (canLearn) - gossipItem = GOSSIP_ITEM_ZAP; - break; - case NPC_JHORDY: - canLearn = CanLearn(player, 7251, 7252, 260, S_GNOMISH, SPELL_TO_GADGET, npcTextId); - if (canLearn) - gossipItem = GOSSIP_ITEM_JHORDY; - break; - case NPC_KABLAM: - canLearn = CanLearn(player, 10365, 0, 350, S_GOBLIN, SPELL_TO_AREA52, npcTextId); - if (canLearn) - gossipItem = GOSSIP_ITEM_KABLAM; - break; - case NPC_SMILES: - canLearn = CanLearn(player, 10363, 0, 350, S_GNOMISH, SPELL_TO_TOSHLEY, npcTextId); - if (canLearn) - gossipItem = GOSSIP_ITEM_KABLAM; - break; - } - } - - if (canLearn) - AddGossipItemFor(player, GOSSIP_ICON_CHAT, gossipItem, creature->GetEntry(), GOSSIP_ACTION_INFO_DEF + 1); - - SendGossipMenuFor(player, npcTextId ? npcTextId : player->GetGossipTextId(creature), creature->GetGUID()); - return true; - } - - bool OnGossipSelect(Player* player, Creature* creature, uint32 sender, uint32 action) override - { - ClearGossipMenuFor(player); - if (action == GOSSIP_ACTION_INFO_DEF + 1) - CloseGossipMenuFor(player); - - if (sender != creature->GetEntry()) - return true; - - switch (sender) - { - case NPC_ZAP: - player->CastSpell(player, SPELL_LEARN_TO_EVERLOOK, false); - break; - case NPC_JHORDY: - player->CastSpell(player, SPELL_LEARN_TO_GADGET, false); - break; - case NPC_KABLAM: - player->CastSpell(player, SPELL_LEARN_TO_AREA52, false); - break; - case NPC_SMILES: - player->CastSpell(player, SPELL_LEARN_TO_TOSHLEY, false); - break; - } - - return true; - } -}; - /*### # start menues leatherworking ###*/ @@ -1358,7 +1244,6 @@ void AddSC_npc_professions() { new npc_prof_alchemy(); new npc_prof_blacksmith(); - new npc_engineering_tele_trinket(); new npc_prof_leather(); new npc_prof_tailor(); new go_evil_book_for_dummies(); From 6ef2da35654362f257d2eed30b580f52e1545c8d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 13 Jun 2026 04:48:40 +0000 Subject: [PATCH 02/80] chore(DB): import pending files Referenced commit(s): 15f84f6154d5e9fc5f7fb0623a4a96c54f4ad5c6 --- .../rev_1770804493564634772.sql => db_world/2026_06_13_00.sql} | 1 + .../rev_1770829879217658167.sql => db_world/2026_06_13_01.sql} | 1 + .../rev_1770846981140352968.sql => db_world/2026_06_13_02.sql} | 1 + .../rev_1770946272358222202.sql => db_world/2026_06_13_03.sql} | 1 + 4 files changed, 4 insertions(+) rename data/sql/updates/{pending_db_world/rev_1770804493564634772.sql => db_world/2026_06_13_00.sql} (99%) rename data/sql/updates/{pending_db_world/rev_1770829879217658167.sql => db_world/2026_06_13_01.sql} (99%) rename data/sql/updates/{pending_db_world/rev_1770846981140352968.sql => db_world/2026_06_13_02.sql} (99%) rename data/sql/updates/{pending_db_world/rev_1770946272358222202.sql => db_world/2026_06_13_03.sql} (99%) diff --git a/data/sql/updates/pending_db_world/rev_1770804493564634772.sql b/data/sql/updates/db_world/2026_06_13_00.sql similarity index 99% rename from data/sql/updates/pending_db_world/rev_1770804493564634772.sql rename to data/sql/updates/db_world/2026_06_13_00.sql index f29bdd037..de5f16888 100644 --- a/data/sql/updates/pending_db_world/rev_1770804493564634772.sql +++ b/data/sql/updates/db_world/2026_06_13_00.sql @@ -1,3 +1,4 @@ +-- DB update 2026_06_12_02 -> 2026_06_13_00 -- SMILES O'BYRON (Engineering(350 / Gnomish Engineer) - Ultrasafe Transporter: Toshley's Station) -- Add NPC Text for learn Ultrasafe Transporter gossip concurrent to already learned schematic gossip diff --git a/data/sql/updates/pending_db_world/rev_1770829879217658167.sql b/data/sql/updates/db_world/2026_06_13_01.sql similarity index 99% rename from data/sql/updates/pending_db_world/rev_1770829879217658167.sql rename to data/sql/updates/db_world/2026_06_13_01.sql index 0451ac799..281719790 100644 --- a/data/sql/updates/pending_db_world/rev_1770829879217658167.sql +++ b/data/sql/updates/db_world/2026_06_13_01.sql @@ -1,3 +1,4 @@ +-- DB update 2026_06_13_00 -> 2026_06_13_01 -- KABLAMM FARFLINGER (Engineering(350 / Goblin Engineer)) - Dimensional Ripper: Area 52 -- Add NPC Text for learn schematic gossip diff --git a/data/sql/updates/pending_db_world/rev_1770846981140352968.sql b/data/sql/updates/db_world/2026_06_13_02.sql similarity index 99% rename from data/sql/updates/pending_db_world/rev_1770846981140352968.sql rename to data/sql/updates/db_world/2026_06_13_02.sql index af1bf971f..17a4e99ee 100644 --- a/data/sql/updates/pending_db_world/rev_1770846981140352968.sql +++ b/data/sql/updates/db_world/2026_06_13_02.sql @@ -1,3 +1,4 @@ +-- DB update 2026_06_13_01 -> 2026_06_13_02 -- ZAP FARFLINGER (Engineering(260 / Goblin Engineer)) - Dimensional Ripper: Everlook -- Dimensional Ripper gossip option already updated in Kablamm Farflinger update / Both brothers share post learn gossip texts diff --git a/data/sql/updates/pending_db_world/rev_1770946272358222202.sql b/data/sql/updates/db_world/2026_06_13_03.sql similarity index 99% rename from data/sql/updates/pending_db_world/rev_1770946272358222202.sql rename to data/sql/updates/db_world/2026_06_13_03.sql index 060bb5181..c8d8973bb 100644 --- a/data/sql/updates/pending_db_world/rev_1770946272358222202.sql +++ b/data/sql/updates/db_world/2026_06_13_03.sql @@ -1,3 +1,4 @@ +-- DB update 2026_06_13_02 -> 2026_06_13_03 -- JHORDY LAPFORGE (Engineering(260 / Gnomish Engineer)) - Ultrasafe Transporter: Gadgetzan -- Add gossip option (0) to learn schematic From 83207c677918710ceb226c36b9b690de93233995 Mon Sep 17 00:00:00 2001 From: ONEMOARSOUL <103105424+ONEMOARSOUL@users.noreply.github.com> Date: Sat, 13 Jun 2026 07:50:31 +0300 Subject: [PATCH 03/80] fix(DB/SAI): Fix Surveyor Candress Fireball timers (#24401) Co-authored-by: Ludwig Co-authored-by: Claude Opus 4.8 (1M context) --- .../updates/pending_db_world/rev_1768211667223489297.sql | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 data/sql/updates/pending_db_world/rev_1768211667223489297.sql diff --git a/data/sql/updates/pending_db_world/rev_1768211667223489297.sql b/data/sql/updates/pending_db_world/rev_1768211667223489297.sql new file mode 100644 index 000000000..3145c79e6 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1768211667223489297.sql @@ -0,0 +1,7 @@ +UPDATE `smart_scripts` SET + `event_param1` = 10000, -- InitialMin: 10s + `event_param2` = 20000, -- InitialMax: 20s + `event_param3` = 10000, -- RepeatMin: 10s + `event_param4` = 45000, -- RepeatMax: 45s + `comment` = 'Surveyor Candress - Combat - Cast Fireball (Range 0-40, 10-45s cd)' +WHERE `entryorguid` = 16522 AND `id` = 5 AND `source_type` = 0; From f86d5a019a4fc7031165ec1c2bb053c9952e34ba Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 13 Jun 2026 04:51:49 +0000 Subject: [PATCH 04/80] chore(DB): import pending files Referenced commit(s): 83207c677918710ceb226c36b9b690de93233995 --- .../rev_1768211667223489297.sql => db_world/2026_06_13_04.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1768211667223489297.sql => db_world/2026_06_13_04.sql} (89%) diff --git a/data/sql/updates/pending_db_world/rev_1768211667223489297.sql b/data/sql/updates/db_world/2026_06_13_04.sql similarity index 89% rename from data/sql/updates/pending_db_world/rev_1768211667223489297.sql rename to data/sql/updates/db_world/2026_06_13_04.sql index 3145c79e6..0a1e1bb1c 100644 --- a/data/sql/updates/pending_db_world/rev_1768211667223489297.sql +++ b/data/sql/updates/db_world/2026_06_13_04.sql @@ -1,3 +1,4 @@ +-- DB update 2026_06_13_03 -> 2026_06_13_04 UPDATE `smart_scripts` SET `event_param1` = 10000, -- InitialMin: 10s `event_param2` = 20000, -- InitialMax: 20s From 674c45537b97e5a03b26437646b605d2d3cd77a5 Mon Sep 17 00:00:00 2001 From: sogladev Date: Sat, 13 Jun 2026 10:46:50 +0200 Subject: [PATCH 05/80] fix(DB/SpellCone): update Sapphiron and Keristrasza tail swipe (#26165) --- data/sql/updates/pending_db_world/rev_1781286411241098962.sql | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 data/sql/updates/pending_db_world/rev_1781286411241098962.sql diff --git a/data/sql/updates/pending_db_world/rev_1781286411241098962.sql b/data/sql/updates/pending_db_world/rev_1781286411241098962.sql new file mode 100644 index 000000000..807d4d9df --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1781286411241098962.sql @@ -0,0 +1,2 @@ +-- +UPDATE `spell_cone` SET `ConeDegrees`=82 WHERE `ID` IN (55696, 55697, 50155, 15847, 23364, 25653, 50155); From a9db3544e2cc50d4d63031beed931bd9bd078934 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 13 Jun 2026 08:48:00 +0000 Subject: [PATCH 06/80] chore(DB): import pending files Referenced commit(s): 674c45537b97e5a03b26437646b605d2d3cd77a5 --- .../rev_1781286411241098962.sql => db_world/2026_06_13_05.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1781286411241098962.sql => db_world/2026_06_13_05.sql} (71%) diff --git a/data/sql/updates/pending_db_world/rev_1781286411241098962.sql b/data/sql/updates/db_world/2026_06_13_05.sql similarity index 71% rename from data/sql/updates/pending_db_world/rev_1781286411241098962.sql rename to data/sql/updates/db_world/2026_06_13_05.sql index 807d4d9df..01743a9a2 100644 --- a/data/sql/updates/pending_db_world/rev_1781286411241098962.sql +++ b/data/sql/updates/db_world/2026_06_13_05.sql @@ -1,2 +1,3 @@ +-- DB update 2026_06_13_04 -> 2026_06_13_05 -- UPDATE `spell_cone` SET `ConeDegrees`=82 WHERE `ID` IN (55696, 55697, 50155, 15847, 23364, 25653, 50155); From 009ea3890b569f4bdef7d5cb2e87574c176ce2a9 Mon Sep 17 00:00:00 2001 From: EricksOliveira Date: Sat, 13 Jun 2026 09:38:06 +0000 Subject: [PATCH 07/80] =?UTF-8?q?fix(Core/Battleground):=20Re-enqueue=20BG?= =?UTF-8?q?=20when=20invitation=20expires=20to=20prev=E2=80=A6=20(#25001)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ludwig Co-authored-by: Claude Opus 4.8 (1M context) --- .../game/Battlegrounds/BattlegroundQueue.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/server/game/Battlegrounds/BattlegroundQueue.cpp b/src/server/game/Battlegrounds/BattlegroundQueue.cpp index 295b8859f..169f642ec 100644 --- a/src/server/game/Battlegrounds/BattlegroundQueue.cpp +++ b/src/server/game/Battlegrounds/BattlegroundQueue.cpp @@ -310,9 +310,24 @@ void BattlegroundQueue::RemovePlayer(ObjectGuid guid, bool decreaseInvitedCount) // if invited to bg, and should decrease invited count, then do it if (decreaseInvitedCount && groupInfo->IsInvitedToBGInstanceGUID) + { if (Battleground* bg = sBattlegroundMgr->GetBattleground(groupInfo->IsInvitedToBGInstanceGUID, groupInfo->BgTypeId)) + { bg->DecreaseInvitedCount(groupInfo->teamId); + // re-enqueue BG if free slots reopened due to invite expiration + if (bg->HasFreeSlots()) + { + bg->AddToBGFreeSlotQueue(); + + BattlegroundQueueTypeId queueTypeId = + BattlegroundMgr::BGQueueTypeId(bg->GetBgTypeID(), bg->GetArenaType()); + + sBattlegroundMgr->ScheduleQueueUpdate(0, 0, queueTypeId, bg->GetBgTypeID(), bg->GetBracketId()); + } + } + } + // remove player queue info m_QueuedPlayers.erase(itr); From 230da2a87812537a1fd9b6471cc64f4385cfcc8e Mon Sep 17 00:00:00 2001 From: EricksOliveira Date: Sat, 13 Jun 2026 09:38:31 +0000 Subject: [PATCH 08/80] fix(Core/Maps): prevent newly spawned summons from aggroing nearby pets/totems on spawn (#24109) Co-authored-by: Ludwig Co-authored-by: Claude Opus 4.8 (1M context) --- src/server/game/Maps/Map.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/server/game/Maps/Map.cpp b/src/server/game/Maps/Map.cpp index 506b72234..15265050a 100644 --- a/src/server/game/Maps/Map.cpp +++ b/src/server/game/Maps/Map.cpp @@ -307,7 +307,7 @@ bool Map::AddToMap(T* obj, bool checkTransport) if (obj->IsInWorld()) { ASSERT(obj->IsInGrid()); - obj->UpdateObjectVisibilityOnCreate(); + obj->UpdateObjectVisibility(true); return true; } @@ -344,7 +344,7 @@ bool Map::AddToMap(T* obj, bool checkTransport) //something, such as vehicle, needs to be update immediately //also, trigger needs to cast spell, if not update, cannot see visual - obj->UpdateObjectVisibility(true); + obj->UpdateObjectVisibilityOnCreate(); // Post-visibility so accessories seat after the vehicle's create packet reaches clients. if (obj->IsCreature()) From c5b96f65bac40afbc92c08972012c12ace7cb69c Mon Sep 17 00:00:00 2001 From: v-mstrs <104088833+v-mstrs@users.noreply.github.com> Date: Sat, 13 Jun 2026 12:47:23 +0200 Subject: [PATCH 09/80] fix(Scripts/GruulsLair): Olm moves freely and Kiggler's chase range is 40y (#23334) Co-authored-by: sudlud --- .../Outland/GruulsLair/boss_high_king_maulgar.cpp | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/server/scripts/Outland/GruulsLair/boss_high_king_maulgar.cpp b/src/server/scripts/Outland/GruulsLair/boss_high_king_maulgar.cpp index 4a81ca2c1..ac24a919d 100644 --- a/src/server/scripts/Outland/GruulsLair/boss_high_king_maulgar.cpp +++ b/src/server/scripts/Outland/GruulsLair/boss_high_king_maulgar.cpp @@ -183,15 +183,6 @@ struct boss_olm_the_summoner : public ScriptedAI instance->SetBossState(DATA_MAULGAR, NOT_STARTED); } - void AttackStart(Unit* who) override - { - if (!who) - return; - - if (me->Attack(who, true)) - me->GetMotionMaster()->MoveChase(who, 25.0f); - } - void JustEngagedWith(Unit* /*who*/) override { me->SetInCombatWithZone(); @@ -263,7 +254,7 @@ struct boss_kiggler_the_crazed : public ScriptedAI return; if (me->Attack(who, true)) - me->GetMotionMaster()->MoveChase(who, 25.0f); + me->GetMotionMaster()->MoveChase(who, 40.0f); } void JustEngagedWith(Unit* /*who*/) override From c04698c5c1522ed2e3d2298dadaab29986ae63f9 Mon Sep 17 00:00:00 2001 From: Fosoul Date: Sat, 13 Jun 2026 13:46:38 +0200 Subject: [PATCH 10/80] fix(Core/Skills): Remove wrong skill increase for Inscription (#24173) Co-authored-by: sudlud --- src/server/game/Skills/SkillDiscovery.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/server/game/Skills/SkillDiscovery.cpp b/src/server/game/Skills/SkillDiscovery.cpp index 89b5da238..0517c287b 100644 --- a/src/server/game/Skills/SkillDiscovery.cpp +++ b/src/server/game/Skills/SkillDiscovery.cpp @@ -184,12 +184,7 @@ uint32 GetExplicitDiscoverySpell(uint32 spellId, Player* player) continue; if (item_iter->chance > roll) - { - // Update skill, not Book of Glyph Mastery - if (spellId != 64323) - player->UpdateGatherSkill(SKILL_INSCRIPTION, player->GetPureSkillValue(SKILL_INSCRIPTION), item_iter->reqSkillValue); return item_iter->spellId; - } roll -= item_iter->chance; } From 8d0a1aaf6f9134a572c1c1214e18fbbc80789e4e Mon Sep 17 00:00:00 2001 From: silviu20092 Date: Sat, 13 Jun 2026 15:13:19 +0300 Subject: [PATCH 11/80] fix(Core/SmartAI): Call unit evade mode hook for SmartAI aswell (#23958) Co-authored-by: sudlud --- src/server/game/AI/SmartScripts/SmartAI.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/server/game/AI/SmartScripts/SmartAI.cpp b/src/server/game/AI/SmartScripts/SmartAI.cpp index 1eb4233d9..eec86a422 100644 --- a/src/server/game/AI/SmartScripts/SmartAI.cpp +++ b/src/server/game/AI/SmartScripts/SmartAI.cpp @@ -24,6 +24,7 @@ #include "ObjectDefines.h" #include "ObjectMgr.h" #include "ScriptedCreature.h" +#include "ScriptMgr.h" #include "SpellMgr.h" #include "Vehicle.h" @@ -711,7 +712,7 @@ void SmartAI::JustExitedCombat() CreatureAI::JustExitedCombat(); } -void SmartAI::EnterEvadeMode(EvadeReason /*why*/) +void SmartAI::EnterEvadeMode(EvadeReason why) { if (mSuppressEvade) return; @@ -763,6 +764,8 @@ void SmartAI::EnterEvadeMode(EvadeReason /*why*/) if (!me->HasUnitState(UNIT_STATE_EVADE)) GetScript()->OnReset(); } + + sScriptMgr->OnUnitEnterEvadeMode(me, why); } void SmartAI::MoveInLineOfSight(Unit* who) From 12f1834d416caafc878324baa52abbd47493f1b1 Mon Sep 17 00:00:00 2001 From: "@cgrahamseven" Date: Sat, 13 Jun 2026 07:27:21 -0500 Subject: [PATCH 12/80] fix(Scripts/Ahnkahet): Fix Taldaram's Flame Orb despawn effect (#22763) Co-authored-by: cgrahamseven Co-authored-by: Andrew <47818697+Nyeriah@users.noreply.github.com> Co-authored-by: sogladev Co-authored-by: Ludwig Co-authored-by: Claude Opus 4.8 (1M context) --- .../AzjolNerub/ahnkahet/boss_prince_taldaram.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/server/scripts/Northrend/AzjolNerub/ahnkahet/boss_prince_taldaram.cpp b/src/server/scripts/Northrend/AzjolNerub/ahnkahet/boss_prince_taldaram.cpp index 041d68360..551f7deb0 100644 --- a/src/server/scripts/Northrend/AzjolNerub/ahnkahet/boss_prince_taldaram.cpp +++ b/src/server/scripts/Northrend/AzjolNerub/ahnkahet/boss_prince_taldaram.cpp @@ -103,6 +103,15 @@ struct npc_taldaram_flamesphere : public NullCreatureAI } } + void MovementInform(uint32 type, uint32 id) override + { + if (type == POINT_MOTION_TYPE && id == POINT_ORB) + { + me->DespawnOrUnsummon(1s); + DoCastSelf(SPELL_FLAME_SPHERE_DEATH_EFFECT, true); + } + } + void IsSummonedBy(WorldObject* /*summoner*/) override { // Replace sphere instantly if sphere is summoned after prince death @@ -116,11 +125,6 @@ struct npc_taldaram_flamesphere : public NullCreatureAI DoCastSelf(SPELL_FLAME_SPHERE_VISUAL); } - void JustDied(Unit* /*who*/) override - { - DoCastSelf(SPELL_FLAME_SPHERE_DEATH_EFFECT); - } - void UpdateAI(uint32 diff) override { if (moveTimer) @@ -147,7 +151,7 @@ struct npc_taldaram_flamesphere : public NullCreatureAI float angle = me->GetAngle(&victimPos) + angleOffset; float x = me->GetPositionX() + DATA_SPHERE_DISTANCE * cos(angle); float y = me->GetPositionY() + DATA_SPHERE_DISTANCE * std::sin(angle); - me->GetMotionMaster()->MovePoint(POINT_ORB, x, y, me->GetPositionZ()); + me->GetMotionMaster()->MovePoint(POINT_ORB, x, y, me->GetPositionZ(), FORCED_MOVEMENT_WALK); moveTimer = 0; } From 81457d95f2e326d90d7e00f4f3860cf8b2822ac2 Mon Sep 17 00:00:00 2001 From: Taras Date: Sat, 13 Jun 2026 15:30:34 +0200 Subject: [PATCH 13/80] fix(Core/Commands): Learn profession rank spells in .learn all recipes (#25114) Co-authored-by: Ludwig Co-authored-by: Claude Opus 4.8 (1M context) --- src/server/game/Spells/SpellMgr.cpp | 20 ++++++++++++++++++++ src/server/game/Spells/SpellMgr.h | 1 + src/server/scripts/Commands/cs_learn.cpp | 16 +++++++++++++++- 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/server/game/Spells/SpellMgr.cpp b/src/server/game/Spells/SpellMgr.cpp index 43c15c686..69f16604a 100644 --- a/src/server/game/Spells/SpellMgr.cpp +++ b/src/server/game/Spells/SpellMgr.cpp @@ -33,6 +33,8 @@ #include "Tokenize.h" #include "World.h" +#include + bool IsPrimaryProfessionSkill(uint32 skill) { SkillLineEntry const* pSkill = sSkillLineStore.LookupEntry(skill); @@ -687,6 +689,24 @@ SpellLearnSkillNode const* SpellMgr::GetSpellLearnSkill(uint32 spell_id) const return nullptr; } +std::vector SpellMgr::GetSkillRankSpells(uint32 skillId) const +{ + // Returns every spell that grants this skill via SPELL_EFFECT_SKILL, + // i.e. the profession rank/proficiency spells (Apprentice -> Grand Master), + // ordered by step. Not strictly limited to the six ranks: any skill-granting + // spell for the line is included. + std::vector result; + for (auto const& [spellId, node] : mSpellLearnSkills) + if (node.skill == skillId) + result.push_back(spellId); + + std::ranges::sort(result, [this](uint32 a, uint32 b) + { + return mSpellLearnSkills.at(a).step < mSpellLearnSkills.at(b).step; + }); + return result; +} + SpellTargetPosition const* SpellMgr::GetSpellTargetPosition(uint32 spell_id, SpellEffIndex effIndex) const { SpellTargetPositionMap::const_iterator itr = mSpellTargetPositions.find(std::make_pair(spell_id, effIndex)); diff --git a/src/server/game/Spells/SpellMgr.h b/src/server/game/Spells/SpellMgr.h index f4004c1a9..27c049497 100644 --- a/src/server/game/Spells/SpellMgr.h +++ b/src/server/game/Spells/SpellMgr.h @@ -687,6 +687,7 @@ public: // Spell learning [[nodiscard]] SpellLearnSkillNode const* GetSpellLearnSkill(uint32 spell_id) const; + [[nodiscard]] std::vector GetSkillRankSpells(uint32 skillId) const; // Spell target coordinates [[nodiscard]] SpellTargetPosition const* GetSpellTargetPosition(uint32 spell_id, SpellEffIndex effIndex) const; diff --git a/src/server/scripts/Commands/cs_learn.cpp b/src/server/scripts/Commands/cs_learn.cpp index b86e56662..874729b63 100644 --- a/src/server/scripts/Commands/cs_learn.cpp +++ b/src/server/scripts/Commands/cs_learn.cpp @@ -309,6 +309,8 @@ public: static bool HandleLearnAllCraftsCommand(ChatHandler* handler) { + Player* target = handler->GetSession()->GetPlayer(); + for (uint32 i = 0; i < sSkillLineStore.GetNumRows(); ++i) { SkillLineEntry const* skillInfo = sSkillLineStore.LookupEntry(i); @@ -318,7 +320,10 @@ public: if ((skillInfo->categoryId == SKILL_CATEGORY_PROFESSION || skillInfo->categoryId == SKILL_CATEGORY_SECONDARY) && skillInfo->canLink) // only prof. with recipes have { - HandleLearnSkillRecipesHelper(handler->GetSession()->GetPlayer(), skillInfo->id); + HandleLearnSkillRecipesHelper(target, skillInfo->id); + + uint16 const maxLevel = target->GetPureMaxSkillValue(skillInfo->id); + target->SetSkill(skillInfo->id, target->GetSkillStep(skillInfo->id), maxLevel, maxLevel); } } @@ -388,6 +393,15 @@ public: static void HandleLearnSkillRecipesHelper(Player* player, uint32 skillId) { + // Rank spells (Apprentice -> Grand Master) must be learned so that the + // skill-cleanup loop in Player::SetSkill (which calls removeSpell on the + // first spell in each chain) can walk forward and strip every rank on + // profession unlearn. Without the first rank in the spellbook that loop + // bails out and the leftover rank spells re-grant the skill after relog + // (issue #2330). + for (uint32 rankSpell : sSpellMgr->GetSkillRankSpells(skillId)) + player->learnSpell(rankSpell); + uint32 classmask = player->getClassMask(); for (SkillLineAbilityEntry const* skillLine : GetSkillLineAbilitiesBySkillLine(skillId)) From a623dd23831b4d9be78d3070c40be4b3eec1b2a5 Mon Sep 17 00:00:00 2001 From: Benjamin Jackson <38561765+heyitsbench@users.noreply.github.com> Date: Sat, 13 Jun 2026 15:29:45 -0400 Subject: [PATCH 14/80] refactor(Core/Packets): Rewrite various quest packets to modern class. (#22835) Co-authored-by: sudlud Co-authored-by: Claude Opus 4.8 (1M context) --- src/server/game/Entities/Player/Player.h | 4 +- .../game/Entities/Player/PlayerQuest.cpp | 141 ++++++------ src/server/game/Handlers/QuestHandler.cpp | 66 ++---- src/server/game/Server/Packets/AllPackets.h | 1 + .../game/Server/Packets/QuestPackets.cpp | 126 +++++++++++ src/server/game/Server/Packets/QuestPackets.h | 214 ++++++++++++++++++ src/server/game/Server/WorldSession.h | 22 +- src/server/scripts/World/player_scripts.cpp | 13 +- 8 files changed, 458 insertions(+), 129 deletions(-) create mode 100644 src/server/game/Server/Packets/QuestPackets.cpp create mode 100644 src/server/game/Server/Packets/QuestPackets.h diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 43482d4ed..a8e744a1b 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -1559,9 +1559,9 @@ public: void SendQuestReward(Quest const* quest, uint32 XP); void SendQuestFailed(uint32 questId, InventoryResult reason = EQUIP_ERR_OK); void SendQuestTimerFailed(uint32 quest_id); - void SendCanTakeQuestResponse(uint32 msg) const; + void SendCanTakeQuestResponse(QuestFailedReason msg) const; void SendQuestConfirmAccept(Quest const* quest, Player* pReceiver); - void SendPushToPartyResponse(Player const* player, uint8 msg) const; + void SendPushToPartyResponse(Player const* player, QuestShareMessages msg) const; void SendQuestUpdateAddItem(Quest const* quest, uint32 item_idx, uint16 count); void SendQuestUpdateAddCreatureOrGo(Quest const* quest, ObjectGuid guid, uint32 creatureOrGO_idx, uint16 old_count, uint16 add_count); void SendQuestUpdateAddPlayer(Quest const* quest, uint16 old_count, uint16 add_count); diff --git a/src/server/game/Entities/Player/PlayerQuest.cpp b/src/server/game/Entities/Player/PlayerQuest.cpp index 94ac419b4..57735685e 100644 --- a/src/server/game/Entities/Player/PlayerQuest.cpp +++ b/src/server/game/Entities/Player/PlayerQuest.cpp @@ -26,6 +26,7 @@ #include "MapMgr.h" #include "Player.h" #include "PoolMgr.h" +#include "QuestPackets.h" #include "ReputationMgr.h" #include "ScriptMgr.h" #include "SpellAuraEffects.h" @@ -991,8 +992,7 @@ bool Player::SatisfyQuestLog(bool msg) if (msg) { - WorldPacket data(SMSG_QUESTLOG_FULL, 0); - SendDirectMessage(&data); + SendDirectMessage(WorldPackets::Quest::QuestLogFull().Write()); LOG_DEBUG("network", "WORLD: Sent SMSG_QUESTLOG_FULL"); } return false; @@ -2381,13 +2381,13 @@ bool Player::HasQuestForItem(uint32 itemid, uint32 excludeQuestId /* 0 */, bool void Player::SendQuestComplete(uint32 quest_id) { - if (quest_id) - { - WorldPacket data(SMSG_QUESTUPDATE_COMPLETE, 4); - data << uint32(quest_id); - SendDirectMessage(&data); - LOG_DEBUG("network", "WORLD: Sent SMSG_QUESTUPDATE_COMPLETE quest = {}", quest_id); - } + if (!quest_id) + return; + + WorldPackets::Quest::QuestUpdateComplete questUpdateComplete; + questUpdateComplete.QuestId = quest_id; + SendDirectMessage(questUpdateComplete.Write()); + LOG_DEBUG("network", "WORLD: Sent SMSG_QUESTUPDATE_COMPLETE quest = {}", quest_id); } void Player::SendQuestReward(Quest const* quest, uint32 XP) @@ -2395,54 +2395,50 @@ void Player::SendQuestReward(Quest const* quest, uint32 XP) uint32 questid = quest->GetQuestId(); LOG_DEBUG("network", "WORLD: Sent SMSG_QUESTGIVER_QUEST_COMPLETE quest = {}", questid); sGameEventMgr->HandleQuestComplete(questid); - WorldPacket data(SMSG_QUESTGIVER_QUEST_COMPLETE, (4 + 4 + 4 + 4 + 4)); - data << uint32(questid); + WorldPackets::Quest::QuestGiverQuestComplete questGiverQuestComplete; + questGiverQuestComplete.QuestId = questid; + uint32 rewardMoney = quest->GetRewOrReqMoney(GetLevel()); if (!IsMaxLevel()) - { - data << uint32(XP); - data << uint32(quest->GetRewOrReqMoney(GetLevel())); - } + questGiverQuestComplete.Experience = XP; else - { - data << uint32(0); - data << uint32(quest->GetRewOrReqMoney(GetLevel()) + quest->GetRewMoneyMaxLevel()); - } + rewardMoney += quest->GetRewMoneyMaxLevel(); - data << uint32(10 * quest->CalculateHonorGain(GetQuestLevel(quest))); - data << uint32(quest->GetBonusTalents()); // bonus talents - data << uint32(quest->GetRewArenaPoints()); - SendDirectMessage(&data); + questGiverQuestComplete.RewardMoney = rewardMoney; + questGiverQuestComplete.RewardHonor = 10 * quest->CalculateHonorGain(GetQuestLevel(quest)); + questGiverQuestComplete.RewardTalents = quest->GetBonusTalents(); + questGiverQuestComplete.RewardArena = quest->GetRewArenaPoints(); + SendDirectMessage(questGiverQuestComplete.Write()); } void Player::SendQuestFailed(uint32 questId, InventoryResult reason) { - if (questId) - { - WorldPacket data(SMSG_QUESTGIVER_QUEST_FAILED, 4 + 4); - data << uint32(questId); - data << uint32(reason); // failed reason (valid reasons: 4, 16, 50, 17, 74, other values show default message) - SendDirectMessage(&data); - LOG_DEBUG("network", "WORLD: Sent SMSG_QUESTGIVER_QUEST_FAILED"); - } + if (!questId) + return; + + WorldPackets::Quest::QuestGiverQuestFailed questGiverQuestFailed; + questGiverQuestFailed.QuestId = questId; + questGiverQuestFailed.FailureReason = reason; // failed reason (valid reasons: 4, 16, 50, 17, 74, other values show default message) + SendDirectMessage(questGiverQuestFailed.Write()); + LOG_DEBUG("network", "WORLD: Sent SMSG_QUESTGIVER_QUEST_FAILED"); } void Player::SendQuestTimerFailed(uint32 quest_id) { - if (quest_id) - { - WorldPacket data(SMSG_QUESTUPDATE_FAILEDTIMER, 4); - data << uint32(quest_id); - SendDirectMessage(&data); - LOG_DEBUG("network", "WORLD: Sent SMSG_QUESTUPDATE_FAILEDTIMER"); - } + if (!quest_id) + return; + + WorldPackets::Quest::QuestUpdateFailedTimer questUpdateFailedTimer; + questUpdateFailedTimer.QuestId = quest_id; + SendDirectMessage(questUpdateFailedTimer.Write()); + LOG_DEBUG("network", "WORLD: Sent SMSG_QUESTUPDATE_FAILEDTIMER"); } -void Player::SendCanTakeQuestResponse(uint32 msg) const +void Player::SendCanTakeQuestResponse(QuestFailedReason msg) const { - WorldPacket data(SMSG_QUESTGIVER_QUEST_INVALID, 4); - data << uint32(msg); - SendDirectMessage(&data); + WorldPackets::Quest::QuestGiverQuestInvalid questGiverQuestInvalid; + questGiverQuestInvalid.FailureReason = msg; + SendDirectMessage(questGiverQuestInvalid.Write()); LOG_DEBUG("network", "WORLD: Sent SMSG_QUESTGIVER_QUEST_INVALID"); } @@ -2458,35 +2454,36 @@ void Player::SendQuestConfirmAccept(const Quest* quest, Player* pReceiver) if (const QuestLocale* pLocale = sObjectMgr->GetQuestLocale(quest->GetQuestId())) ObjectMgr::GetLocaleString(pLocale->Title, loc_idx, strTitle); - WorldPacket data(SMSG_QUEST_CONFIRM_ACCEPT, (4 + quest->GetTitle().size() + 8)); - data << uint32(quest->GetQuestId()); - data << quest->GetTitle(); - data << GetGUID(); - pReceiver->SendDirectMessage(&data); + WorldPackets::Quest::QuestConfirmAccept questConfirmAccept; + questConfirmAccept.QuestId = quest->GetQuestId(); + questConfirmAccept.QuestTitle = quest->GetTitle(); + questConfirmAccept.PlayerGuid = GetGUID(); + pReceiver->SendDirectMessage(questConfirmAccept.Write()); LOG_DEBUG("network", "WORLD: Sent SMSG_QUEST_CONFIRM_ACCEPT"); } } -void Player::SendPushToPartyResponse(Player const* player, uint8 msg) const +void Player::SendPushToPartyResponse(Player const* player, QuestShareMessages msg) const { - if (player) - { - WorldPacket data(MSG_QUEST_PUSH_RESULT, (8 + 1)); - data << player->GetGUID(); - data << uint8(msg); // valid values: 0-8 - SendDirectMessage(&data); - LOG_DEBUG("network", "WORLD: Sent MSG_QUEST_PUSH_RESULT"); - } + if (!player) + return; + + WorldPackets::Quest::QuestPushResult questPushResult; + questPushResult.PlayerGuid = player->GetGUID(); + questPushResult.QuestShareMessage = msg; + SendDirectMessage(questPushResult.Write()); + LOG_DEBUG("network", "WORLD: Sent MSG_QUEST_PUSH_RESULT"); } void Player::SendQuestUpdateAddItem(Quest const* /*quest*/, uint32 /*item_idx*/, uint16 /*count*/) { - WorldPacket data(SMSG_QUESTUPDATE_ADD_ITEM, 0); + // Packet is intentionally sent empty; the optional payload (item id and + // count) is not required by the 3.3.5 client: + //questUpdateAddItem.ItemId = quest->RequiredItemId[item_idx]; + //questUpdateAddItem.Count = count; + SendDirectMessage(WorldPackets::Quest::QuestUpdateAddItem().Write()); LOG_DEBUG("network", "WORLD: Sent SMSG_QUESTUPDATE_ADD_ITEM"); - //data << quest->RequiredItemId[item_idx]; - //data << count; - SendDirectMessage(&data); } void Player::SendQuestUpdateAddCreatureOrGo(Quest const* quest, ObjectGuid guid, uint32 creatureOrGO_idx, uint16 old_count, uint16 add_count) @@ -2498,14 +2495,14 @@ void Player::SendQuestUpdateAddCreatureOrGo(Quest const* quest, ObjectGuid guid, // client expected gameobject template id in form (id|0x80000000) entry = (-entry) | 0x80000000; - WorldPacket data(SMSG_QUESTUPDATE_ADD_KILL, (4 * 4 + 8)); + WorldPackets::Quest::QuestUpdateAddKill questUpdateAddKill; + questUpdateAddKill.QuestId = quest->GetQuestId(); + questUpdateAddKill.CreatureEntry = entry; + questUpdateAddKill.CurrentCount = old_count + add_count; + questUpdateAddKill.RequiredCount = quest->RequiredNpcOrGoCount[creatureOrGO_idx]; + questUpdateAddKill.ObjectiveGuid = guid; + SendDirectMessage(questUpdateAddKill.Write()); LOG_DEBUG("network", "WORLD: Sent SMSG_QUESTUPDATE_ADD_KILL"); - data << uint32(quest->GetQuestId()); - data << uint32(entry); - data << uint32(old_count + add_count); - data << uint32(quest->RequiredNpcOrGoCount[ creatureOrGO_idx ]); - data << guid; - SendDirectMessage(&data); uint16 log_slot = FindQuestSlot(quest->GetQuestId()); if (log_slot < MAX_QUEST_LOG_SIZE) @@ -2516,12 +2513,12 @@ void Player::SendQuestUpdateAddPlayer(Quest const* quest, uint16 old_count, uint { ASSERT(old_count + add_count < 65536 && "player count store in 16 bits"); - WorldPacket data(SMSG_QUESTUPDATE_ADD_PVP_KILL, (3 * 4)); + WorldPackets::Quest::QuestUpdateAddPvPKill questUpdateAddPvPKill; + questUpdateAddPvPKill.QuestId = quest->GetQuestId(); + questUpdateAddPvPKill.CurrentCount = old_count + add_count; + questUpdateAddPvPKill.RequiredCount = quest->GetPlayersSlain(); + SendDirectMessage(questUpdateAddPvPKill.Write()); LOG_DEBUG("network", "WORLD: Sent SMSG_QUESTUPDATE_ADD_PVP_KILL"); - data << uint32(quest->GetQuestId()); - data << uint32(old_count + add_count); - data << uint32(quest->GetPlayersSlain()); - SendDirectMessage(&data); uint16 log_slot = FindQuestSlot(quest->GetQuestId()); if (log_slot < MAX_QUEST_LOG_SIZE) diff --git a/src/server/game/Handlers/QuestHandler.cpp b/src/server/game/Handlers/QuestHandler.cpp index a2c775328..89a0a84af 100644 --- a/src/server/game/Handlers/QuestHandler.cpp +++ b/src/server/game/Handlers/QuestHandler.cpp @@ -27,6 +27,7 @@ #include "Opcodes.h" #include "Player.h" #include "QuestDef.h" +#include "QuestPackets.h" #include "ScriptMgr.h" #include "World.h" #include "WorldPacket.h" @@ -382,29 +383,23 @@ void WorldSession::HandleQuestgiverCancel(WorldPacket& /*recvData*/) _player->PlayerTalkClass->SendCloseGossip(); } -void WorldSession::HandleQuestLogSwapQuest(WorldPacket& recvData) +void WorldSession::HandleQuestLogSwapQuest(WorldPackets::Quest::QuestLogSwapQuest& packet) { - uint8 slot1, slot2; - recvData >> slot1 >> slot2; - - if (slot1 == slot2 || slot1 >= MAX_QUEST_LOG_SIZE || slot2 >= MAX_QUEST_LOG_SIZE) + if (packet.Slot1 == packet.Slot2 || packet.Slot1 >= MAX_QUEST_LOG_SIZE || packet.Slot2 >= MAX_QUEST_LOG_SIZE) return; - LOG_DEBUG("network", "WORLD: Received CMSG_QUESTLOG_SWAP_QUEST slot 1 = {}, slot 2 = {}", slot1, slot2); + LOG_DEBUG("network", "WORLD: Received CMSG_QUESTLOG_SWAP_QUEST slot 1 = {}, slot 2 = {}", packet.Slot1, packet.Slot2); - GetPlayer()->SwapQuestSlot(slot1, slot2); + GetPlayer()->SwapQuestSlot(packet.Slot1, packet.Slot2); } -void WorldSession::HandleQuestLogRemoveQuest(WorldPacket& recvData) +void WorldSession::HandleQuestLogRemoveQuest(WorldPackets::Quest::QuestLogRemoveQuest& packet) { - uint8 slot; - recvData >> slot; + LOG_DEBUG("network", "WORLD: Received CMSG_QUESTLOG_REMOVE_QUEST slot = {}", packet.Slot); - LOG_DEBUG("network", "WORLD: Received CMSG_QUESTLOG_REMOVE_QUEST slot = {}", slot); - - if (slot < MAX_QUEST_LOG_SIZE) + if (packet.Slot < MAX_QUEST_LOG_SIZE) { - if (uint32 questId = _player->GetQuestSlotQuestId(slot)) + if (uint32 questId = _player->GetQuestSlotQuestId(packet.Slot)) { if (!_player->TakeQuestSourceItem(questId, true)) return; // can't un-equip some items, reject quest cancel @@ -442,20 +437,17 @@ void WorldSession::HandleQuestLogRemoveQuest(WorldPacket& recvData) } } - _player->SetQuestSlot(slot, 0); + _player->SetQuestSlot(packet.Slot, 0); _player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_QUEST_ABANDONED, 1); } } -void WorldSession::HandleQuestConfirmAccept(WorldPacket& recvData) +void WorldSession::HandleQuestConfirmAccept(WorldPackets::Quest::QuestConfirmAcceptClient& packet) { - uint32 questId; - recvData >> questId; + LOG_DEBUG("network", "WORLD: Received CMSG_QUEST_CONFIRM_ACCEPT quest = {}", packet.QuestId); - LOG_DEBUG("network", "WORLD: Received CMSG_QUEST_CONFIRM_ACCEPT quest = {}", questId); - - if (Quest const* quest = sObjectMgr->GetQuestTemplate(questId)) + if (Quest const* quest = sObjectMgr->GetQuestTemplate(packet.QuestId)) { if (!quest->HasFlag(QUEST_FLAGS_PARTY_ACCEPT)) return; @@ -530,21 +522,18 @@ void WorldSession::HandleQuestgiverCompleteQuest(WorldPacket& recvData) } } -void WorldSession::HandleQuestgiverQuestAutoLaunch(WorldPacket& /*recvPacket*/) +void WorldSession::HandleQuestgiverQuestAutoLaunch(WorldPackets::Quest::QuestGiverQuestAutoLaunch& /*packet*/) { } -void WorldSession::HandlePushQuestToParty(WorldPacket& recvPacket) +void WorldSession::HandlePushQuestToParty(WorldPackets::Quest::PushQuestToParty& packet) { - uint32 questId; - recvPacket >> questId; - - if (!_player->CanShareQuest(questId)) + if (!_player->CanShareQuest(packet.QuestId)) return; - LOG_DEBUG("network", "WORLD: Received CMSG_PUSHQUESTTOPARTY quest = {}", questId); + LOG_DEBUG("network", "WORLD: Received CMSG_PUSHQUESTTOPARTY quest = {}", packet.QuestId); - if (Quest const* quest = sObjectMgr->GetQuestTemplate(questId)) + if (Quest const* quest = sObjectMgr->GetQuestTemplate(packet.QuestId)) { if (Group* group = _player->GetGroup()) { @@ -561,7 +550,7 @@ void WorldSession::HandlePushQuestToParty(WorldPacket& recvPacket) continue; } - if (player->GetQuestStatus(questId) == QUEST_STATUS_COMPLETE) + if (player->GetQuestStatus(packet.QuestId) == QUEST_STATUS_COMPLETE) { _player->SendPushToPartyResponse(player, QUEST_PARTY_MSG_FINISH_QUEST); continue; @@ -613,21 +602,16 @@ void WorldSession::HandlePushQuestToParty(WorldPacket& recvPacket) } } -void WorldSession::HandleQuestPushResult(WorldPacket& recvPacket) +void WorldSession::HandleQuestPushResult(WorldPackets::Quest::QuestPushResultClient& packet) { - ObjectGuid guid; - uint32 questId; - uint8 msg; - recvPacket >> guid >> questId >> msg; - - if (_player->GetDivider() && _player->GetDivider() == guid) + if (_player->GetDivider() && _player->GetDivider() == packet.PlayerGuid) { if (Player* player = ObjectAccessor::GetPlayer(*_player, _player->GetDivider())) { - WorldPacket data(MSG_QUEST_PUSH_RESULT, 8 + 4 + 1); - data << _player->GetGUID(); - data << uint8(msg); // valid values: 0-8 - player->SendDirectMessage(&data); + WorldPackets::Quest::QuestPushResult questPushResult; + questPushResult.PlayerGuid = _player->GetGUID(); + questPushResult.QuestShareMessage = packet.QuestShareMessage; + player->SendDirectMessage(questPushResult.Write()); _player->SetDivider(); } } diff --git a/src/server/game/Server/Packets/AllPackets.h b/src/server/game/Server/Packets/AllPackets.h index 8cdbc7cda..85d247e7e 100644 --- a/src/server/game/Server/Packets/AllPackets.h +++ b/src/server/game/Server/Packets/AllPackets.h @@ -32,6 +32,7 @@ #include "NPCPackets.h" #include "PetPackets.h" #include "QueryPackets.h" +#include "QuestPackets.h" #include "TotemPackets.h" #include "WorldStatePackets.h" diff --git a/src/server/game/Server/Packets/QuestPackets.cpp b/src/server/game/Server/Packets/QuestPackets.cpp new file mode 100644 index 000000000..e840602f3 --- /dev/null +++ b/src/server/game/Server/Packets/QuestPackets.cpp @@ -0,0 +1,126 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include "QuestPackets.h" + +WorldPacket const* WorldPackets::Quest::QuestUpdateComplete::Write() +{ + _worldPacket << QuestId; + + return &_worldPacket; +} + +WorldPacket const* WorldPackets::Quest::QuestGiverQuestComplete::Write() +{ + _worldPacket << QuestId; + _worldPacket << Experience; + _worldPacket << RewardMoney; + _worldPacket << RewardHonor; + _worldPacket << RewardTalents; + _worldPacket << RewardArena; + + return &_worldPacket; +} + +WorldPacket const* WorldPackets::Quest::QuestGiverQuestFailed::Write() +{ + _worldPacket << QuestId; + _worldPacket << FailureReason; + + return &_worldPacket; +} + +WorldPacket const* WorldPackets::Quest::QuestUpdateFailedTimer::Write() +{ + _worldPacket << QuestId; + + return &_worldPacket; +} + +WorldPacket const* WorldPackets::Quest::QuestGiverQuestInvalid::Write() +{ + _worldPacket << FailureReason; + + return &_worldPacket; +} + +WorldPacket const* WorldPackets::Quest::QuestConfirmAccept::Write() +{ + _worldPacket << QuestId; + _worldPacket << QuestTitle; + _worldPacket << PlayerGuid; + + return &_worldPacket; +} + +WorldPacket const* WorldPackets::Quest::QuestPushResult::Write() +{ + _worldPacket << PlayerGuid; + _worldPacket << QuestShareMessage; + + return &_worldPacket; +} + +WorldPacket const* WorldPackets::Quest::QuestUpdateAddKill::Write() +{ + _worldPacket << QuestId; + _worldPacket << CreatureEntry; + _worldPacket << CurrentCount; + _worldPacket << RequiredCount; + _worldPacket << ObjectiveGuid; + + return &_worldPacket; +} + +WorldPacket const* WorldPackets::Quest::QuestUpdateAddPvPKill::Write() +{ + _worldPacket << QuestId; + _worldPacket << CurrentCount; + _worldPacket << RequiredCount; + + return &_worldPacket; +} + +void WorldPackets::Quest::QuestPushResultClient::Read() +{ + _worldPacket >> PlayerGuid; + _worldPacket >> QuestId; + uint8 Message; + _worldPacket >> Message; + QuestShareMessage = static_cast(Message); +} + +void WorldPackets::Quest::QuestLogSwapQuest::Read() +{ + _worldPacket >> Slot1; + _worldPacket >> Slot2; +} + +void WorldPackets::Quest::QuestLogRemoveQuest::Read() +{ + _worldPacket >> Slot; +} + +void WorldPackets::Quest::QuestConfirmAcceptClient::Read() +{ + _worldPacket >> QuestId; +} + +void WorldPackets::Quest::PushQuestToParty::Read() +{ + _worldPacket >> QuestId; +} diff --git a/src/server/game/Server/Packets/QuestPackets.h b/src/server/game/Server/Packets/QuestPackets.h new file mode 100644 index 000000000..40079d437 --- /dev/null +++ b/src/server/game/Server/Packets/QuestPackets.h @@ -0,0 +1,214 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef QuestPackets_h__ +#define QuestPackets_h__ + +#include "ObjectGuid.h" +#include "Packet.h" +#include "QuestDef.h" + +namespace WorldPackets +{ + namespace Quest + { + class QuestLogFull final : public ServerPacket + { + public: + QuestLogFull() : ServerPacket(SMSG_QUESTLOG_FULL, 0) {} + + WorldPacket const* Write() override { return &_worldPacket; } + }; + + class QuestUpdateComplete final : public ServerPacket + { + public: + QuestUpdateComplete() : ServerPacket(SMSG_QUESTUPDATE_COMPLETE, 4) {} + + WorldPacket const* Write() override; + + uint32 QuestId = 0; + }; + + class QuestGiverQuestComplete final : public ServerPacket + { + public: + QuestGiverQuestComplete() : ServerPacket(SMSG_QUESTGIVER_QUEST_COMPLETE, 4 + 4 + 4 + 4 + 4 + 4) {} + + WorldPacket const* Write() override; + + uint32 QuestId = 0; + uint32 Experience = 0; + uint32 RewardMoney = 0; + uint32 RewardHonor = 0; + uint32 RewardTalents = 0; + uint32 RewardArena = 0; + }; + + class QuestGiverQuestFailed final : public ServerPacket + { + public: + QuestGiverQuestFailed() : ServerPacket(SMSG_QUESTGIVER_QUEST_FAILED, 4 + 4) {} + + WorldPacket const* Write() override; + + uint32 QuestId = 0; + uint32 FailureReason = 0; + }; + + class QuestUpdateFailedTimer final : public ServerPacket + { + public: + QuestUpdateFailedTimer() : ServerPacket(SMSG_QUESTUPDATE_FAILEDTIMER, 4) {} + + WorldPacket const* Write() override; + + uint32 QuestId = 0; + }; + + class QuestGiverQuestInvalid final : public ServerPacket + { + public: + QuestGiverQuestInvalid() : ServerPacket(SMSG_QUESTGIVER_QUEST_INVALID, 4) {} + + WorldPacket const* Write() override; + + QuestFailedReason FailureReason = INVALIDREASON_DONT_HAVE_REQ; + }; + + class QuestConfirmAccept final : public ServerPacket + { + public: + // 4 (QuestId) + title (variable, 16 reserved estimate) + 8 (GUID) + QuestConfirmAccept() : ServerPacket(SMSG_QUEST_CONFIRM_ACCEPT, 4 + 16 + 8) {} + + WorldPacket const* Write() override; + + uint32 QuestId = 0; + std::string_view QuestTitle = ""; + ObjectGuid PlayerGuid; + }; + + class QuestPushResult final : public ServerPacket + { + public: + QuestPushResult() : ServerPacket(MSG_QUEST_PUSH_RESULT, 8 + 1) {} + + WorldPacket const* Write() override; + + ObjectGuid PlayerGuid; + QuestShareMessages QuestShareMessage = QUEST_PARTY_MSG_SHARING_QUEST; // valid values: 0-10 + }; + + class QuestUpdateAddItem final : public ServerPacket + { + public: + QuestUpdateAddItem() : ServerPacket(SMSG_QUESTUPDATE_ADD_ITEM, 0) {} + + WorldPacket const* Write() override { return &_worldPacket; } + }; + + class QuestUpdateAddKill final : public ServerPacket + { + public: + QuestUpdateAddKill() : ServerPacket(SMSG_QUESTUPDATE_ADD_KILL, 4 * 4 + 8) {} + + WorldPacket const* Write() override; + + uint32 QuestId = 0; + uint32 CreatureEntry = 0; + uint32 CurrentCount = 0; + uint32 RequiredCount = 0; + ObjectGuid ObjectiveGuid; + }; + + class QuestUpdateAddPvPKill final : public ServerPacket + { + public: + QuestUpdateAddPvPKill() : ServerPacket(SMSG_QUESTUPDATE_ADD_PVP_KILL, 3 * 4) {} + + WorldPacket const* Write() override; + + uint32 QuestId = 0; + uint32 CurrentCount = 0; + uint32 RequiredCount = 0; + }; + + class QuestPushResultClient final : public ClientPacket + { + public: + QuestPushResultClient(WorldPacket&& packet) : ClientPacket(MSG_QUEST_PUSH_RESULT, std::move(packet)) {} + + void Read() override; + + ObjectGuid PlayerGuid; + uint32 QuestId = 0; + QuestShareMessages QuestShareMessage = QUEST_PARTY_MSG_SHARING_QUEST; + }; + + class QuestGiverQuestAutoLaunch final : public ClientPacket + { + public: + QuestGiverQuestAutoLaunch(WorldPacket&& packet) : ClientPacket(CMSG_QUESTGIVER_QUEST_AUTOLAUNCH, std::move(packet)) {} + + void Read() override {} + }; + + class QuestLogSwapQuest final : public ClientPacket + { + public: + QuestLogSwapQuest(WorldPacket&& packet) : ClientPacket(CMSG_QUESTLOG_SWAP_QUEST, std::move(packet)) {} + + void Read() override; + + uint8 Slot1 = 0; + uint8 Slot2 = 0; + }; + + class QuestLogRemoveQuest final : public ClientPacket + { + public: + QuestLogRemoveQuest(WorldPacket&& packet) : ClientPacket(CMSG_QUESTLOG_REMOVE_QUEST, std::move(packet)) {} + + void Read() override; + + uint8 Slot = 0; + }; + + class QuestConfirmAcceptClient final : public ClientPacket + { + public: + QuestConfirmAcceptClient(WorldPacket&& packet) : ClientPacket(CMSG_QUEST_CONFIRM_ACCEPT, std::move(packet)) {} + + void Read() override; + + uint32 QuestId = 0; + }; + + class PushQuestToParty final : public ClientPacket + { + public: + PushQuestToParty(WorldPacket&& packet) : ClientPacket(CMSG_PUSHQUESTTOPARTY, std::move(packet)) {} + + void Read() override; + + uint32 QuestId = 0; + }; + } +} + +#endif // QuestPackets_h__ diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h index 0904312c9..b588ad8f1 100644 --- a/src/server/game/Server/WorldSession.h +++ b/src/server/game/Server/WorldSession.h @@ -192,6 +192,16 @@ namespace WorldPackets class ItemRefund; } + namespace Quest + { + class QuestPushResultClient; + class QuestGiverQuestAutoLaunch; + class QuestLogSwapQuest; + class QuestLogRemoveQuest; + class QuestConfirmAcceptClient; + class PushQuestToParty; + } + namespace Calendar { class GetEvent; @@ -915,13 +925,13 @@ public: // opcodes handlers void HandleQuestgiverRequestRewardOpcode(WorldPacket& recvPacket); void HandleQuestQueryOpcode(WorldPacket& recvPacket); void HandleQuestgiverCancel(WorldPacket& recvData); - void HandleQuestLogSwapQuest(WorldPacket& recvData); - void HandleQuestLogRemoveQuest(WorldPacket& recvData); - void HandleQuestConfirmAccept(WorldPacket& recvData); + void HandleQuestLogSwapQuest(WorldPackets::Quest::QuestLogSwapQuest& packet); + void HandleQuestLogRemoveQuest(WorldPackets::Quest::QuestLogRemoveQuest& packet); + void HandleQuestConfirmAccept(WorldPackets::Quest::QuestConfirmAcceptClient& packet); void HandleQuestgiverCompleteQuest(WorldPacket& recvData); - void HandleQuestgiverQuestAutoLaunch(WorldPacket& recvPacket); - void HandlePushQuestToParty(WorldPacket& recvPacket); - void HandleQuestPushResult(WorldPacket& recvPacket); + void HandleQuestgiverQuestAutoLaunch(WorldPackets::Quest::QuestGiverQuestAutoLaunch& packet); + void HandlePushQuestToParty(WorldPackets::Quest::PushQuestToParty& packet); + void HandleQuestPushResult(WorldPackets::Quest::QuestPushResultClient& packet); void HandleMessagechatOpcode(WorldPacket& recvPacket); void SendPlayerNotFoundNotice(std::string const& name); diff --git a/src/server/scripts/World/player_scripts.cpp b/src/server/scripts/World/player_scripts.cpp index 22e0bd85a..3cdfaa2d2 100644 --- a/src/server/scripts/World/player_scripts.cpp +++ b/src/server/scripts/World/player_scripts.cpp @@ -17,6 +17,7 @@ #include "Player.h" #include "PlayerScript.h" +#include "QuestPackets.h" enum ApprenticeAnglerQuestEnum { @@ -54,14 +55,10 @@ public: player->SaveToDB(false, false); // Send packet with money - WorldPacket data(SMSG_QUESTGIVER_QUEST_COMPLETE, (4 + 4 + 4 + 4 + 4)); - data << uint32(quest->GetQuestId()); - data << uint32(0); - data << uint32(moneyRew); - data << uint32(0); - data << uint32(0); - data << uint32(0); - player->SendDirectMessage(&data); + WorldPackets::Quest::QuestGiverQuestComplete questGiverQuestComplete; + questGiverQuestComplete.QuestId = quest->GetQuestId(); + questGiverQuestComplete.RewardMoney = moneyRew; + player->SendDirectMessage(questGiverQuestComplete.Write()); } } }; From 13b7a8064f3589306456f0e75f56b0683d966480 Mon Sep 17 00:00:00 2001 From: Vox Date: Sat, 13 Jun 2026 20:27:50 -0400 Subject: [PATCH 15/80] fix(DB/SAI): High General Abbendis talk sequence in Scarlet Enclave (#26093) --- .../rev_1780793919312143900.sql | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 data/sql/updates/pending_db_world/rev_1780793919312143900.sql diff --git a/data/sql/updates/pending_db_world/rev_1780793919312143900.sql b/data/sql/updates/pending_db_world/rev_1780793919312143900.sql new file mode 100644 index 000000000..a61c90369 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1780793919312143900.sql @@ -0,0 +1,58 @@ + +-- Add SAI (High General Abbendis) +UPDATE `creature_template` SET `AIName` = 'SmartAI' WHERE `entry` = 28548; + +DELETE FROM `smart_scripts` WHERE (`source_type` = 0 AND `entryorguid` = 28548); +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 +(28548, 0, 0, 0, 25, 0, 100, 0, 0, 0, 0, 0, 0, 0, 22, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'High General Abbendis - On Reset - Set Event Phase 1'), +(28548, 0, 1, 0, 1, 1, 100, 0, 15000, 35000, 15000, 35000, 0, 0, 88, 2854800, 2854803, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'High General Abbendis - OOC - Call Random Actionlist (Phase 1)'); + +-- Set Action Lists +DELETE FROM `smart_scripts` WHERE (`source_type` = 9) AND (`entryorguid` IN (2854800, 2854801, 2854802, 2854803)); +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 +(2854800, 9, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 22, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'High General Abbendis - On Script - Set Event Phase 2'), +(2854800, 9, 1, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 10, 129476, 28660, 0, 0, 0, 0, 0, 0, 'High General Abbendis - On Script - Say Line 0 (Citizen of Havenshire)'), +(2854800, 9, 2, 0, 0, 0, 100, 0, 6000, 6000, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'High General Abbendis - On Script - Say Line 0'), +(2854800, 9, 3, 0, 0, 0, 100, 0, 3000, 3000, 0, 0, 0, 0, 5, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'High General Abbendis - On Script - Play Emote \'Talk\''), +(2854800, 9, 4, 0, 0, 0, 100, 0, 5000, 5000, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 10, 129476, 28660, 0, 0, 0, 0, 0, 0, 'High General Abbendis - On Script - Say Line 1 (Citizen of Havenshire)'), +(2854800, 9, 5, 0, 0, 0, 100, 0, 3000, 3000, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 19, 28558, 20, 0, 0, 0, 0, 0, 0, 'High General Abbendis - On Script - Say Line 0 (High Abbot Landgren)'), +(2854800, 9, 6, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 22, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'High General Abbendis - On Script - Set Event Phase 1'), +(2854800, 9, 7, 0, 0, 0, 100, 0, 2000, 2000, 0, 0, 0, 0, 5, 6, 0, 0, 0, 0, 0, 19, 28558, 20, 0, 0, 0, 0, 0, 0, 'High General Abbendis - On Script - Play Emote \'Question\' (High Abbot Landgren)'), +(2854801, 9, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 22, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'High General Abbendis - On Script - Set Event Phase 2'), +(2854801, 9, 1, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0, 0, 10, 129478, 28660, 0, 0, 0, 0, 0, 0, 'High General Abbendis - On Script - Say Line 2 (Citizen of Havenshire)'), +(2854801, 9, 2, 0, 0, 0, 100, 0, 5000, 5000, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 10, 129489, 28662, 0, 0, 0, 0, 0, 0, 'High General Abbendis - On Script - Say Line 0 (Citizen of Havenshire)'), +(2854801, 9, 3, 0, 0, 0, 100, 0, 6000, 6000, 0, 0, 0, 0, 1, 3, 0, 0, 0, 0, 0, 10, 129474, 28660, 0, 0, 0, 0, 0, 0, 'High General Abbendis - On Script - Say Line 3 (Citizen of Havenshire)'), +(2854801, 9, 4, 0, 0, 0, 100, 0, 5000, 5000, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 10, 129490, 28662, 0, 0, 0, 0, 0, 0, 'High General Abbendis - On Script - Say Line 1 (Citizen of Havenshire)'), +(2854801, 9, 5, 0, 0, 0, 100, 0, 3000, 3000, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'High General Abbendis - On Script - Say Line 1'), +(2854801, 9, 6, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 22, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'High General Abbendis - On Script - Set Event Phase 1'), +(2854801, 9, 7, 0, 0, 0, 100, 0, 3000, 3000, 0, 0, 0, 0, 5, 6, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'High General Abbendis - On Script - Play Emote \'Question\''), +(2854801, 9, 8, 0, 0, 0, 100, 0, 2000, 2000, 0, 0, 0, 0, 5, 5, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'High General Abbendis - On Script - Play Emote \'Point\''), +(2854802, 9, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 22, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'High General Abbendis - On Script - Set Event Phase 2'), +(2854802, 9, 1, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 1, 4, 0, 0, 0, 0, 0, 10, 129475, 28660, 0, 0, 0, 0, 0, 0, 'High General Abbendis - On Script - Say Line 4 (Citizen of Havenshire)'), +(2854802, 9, 2, 0, 0, 0, 100, 0, 6000, 6000, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'High General Abbendis - On Script - Say Line 2'), +(2854802, 9, 3, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 22, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'High General Abbendis - On Script - Set Event Phase 1'), +(2854802, 9, 4, 0, 0, 0, 100, 0, 3000, 3000, 0, 0, 0, 0, 5, 6, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'High General Abbendis - On Script - Play Emote \'Question\''), +(2854803, 9, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 22, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'High General Abbendis - On Script - Set Event Phase 2'), +(2854803, 9, 1, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0, 0, 10, 129487, 28662, 0, 0, 0, 0, 0, 0, 'High General Abbendis - On Script - Say Line 2 (Citizen of Havenshire)'), +(2854803, 9, 2, 0, 0, 0, 100, 0, 5000, 5000, 0, 0, 0, 0, 1, 3, 0, 0, 0, 0, 0, 10, 129483, 28662, 0, 0, 0, 0, 0, 0, 'High General Abbendis - On Script - Say Line 3 (Citizen of Havenshire)'), +(2854803, 9, 3, 0, 0, 0, 100, 0, 6000, 6000, 0, 0, 0, 0, 1, 3, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'High General Abbendis - On Script - Say Line 3'), +(2854803, 9, 4, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 22, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'High General Abbendis - On Script - Set Event Phase 1'), +(2854803, 9, 5, 0, 0, 0, 100, 0, 2000, 2000, 0, 0, 0, 0, 5, 15, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'High General Abbendis - On Script - Play Emote \'Roar\''); + +-- Add Creature Texts +DELETE FROM `creature_text` WHERE `CreatureID` IN (28548, 28558, 28660, 28662); +INSERT INTO `creature_text` (`CreatureID`, `GroupID`, `ID`, `Text`, `Type`, `Language`, `Probability`, `Emote`, `Duration`, `Sound`, `BroadcastTextId`, `TextRange`, `comment`) VALUES +(28548, 0, 0, "The pure of heart will be allowed to remain in New Avalon. Those that the inquisitors find bereft of the holy Light will be turned away.", 12, 0, 100, 1, 0, 0, 28580, 0, "High General Abbendis"), +(28548, 1, 0, "SILENCE! The Light has not abandoned us! The Light is stronger than ever among the pure!", 12, 0, 100, 5, 0, 0, 28575, 0, "High General Abbendis"), +(28548, 2, 0, "We fight! We push them back, just as we have always done!", 12, 0, 100, 5, 0, 0, 28568, 0, "High General Abbendis"), +(28548, 3, 0, "We are sending crusaders to reclaim what is rightfully ours! Any Scourge that stand in our way will be turned to ashes.", 12, 0, 100, 1, 0, 0, 28578, 0, "High General Abbendis"), +(28558, 0, 0, "But none of you could possibly have anything to worry about, right?", 12, 0, 100, 25, 0, 0, 28582, 0, "High Abbot Landgren"), +(28660, 0, 0, "Where do you expect us to stay? New Avalon cannot hold all of us!", 12, 0, 100, 1, 0, 0, 28579, 0, "Citizen of Havenshire"), +(28660, 1, 0, "The crowd gasps.", 16, 0, 100, 0, 0, 0, 28581, 0, "Citizen of Havenshire"), +(28660, 2, 0, "What does the Light say to you now, Abbendis!", 12, 0, 100, 25, 0, 0, 28570, 0, "Citizen of Havenshire"), +(28660, 3, 0, "Nor my husband and brothers!", 12, 0, 100, 1, 0, 0, 28572, 0, "Citizen of Havenshire"), +(28660, 4, 0, "What do we do? We've lost everything!", 12, 0, 100, 5, 0, 0, 0, 0, "Citizen of Havenshire"), +(28662, 0, 0, "I didn't see the Light step in to save my wife and children!", 12, 0, 100, 1, 0, 0, 28571, 0, "Citizen of Havenshire"), +(28662, 1, 0, "The Light has abandoned us!", 12, 0, 100, 1, 0, 0, 28574, 0, "Citizen of Havenshire"), +(28662, 2, 0, "Havenshire is lost!", 12, 0, 100, 1, 0, 0, 28576, 0, "Citizen of Havenshire"), +(28662, 3, 0, "The stables and mill are left abandoned! What will happen to our horses?", 12, 0, 100, 1, 0, 0, 28577, 0, "Citizen of Havenshire"); From 80fd8c057a5e175eb622d2cf53aba105b69792d8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 14 Jun 2026 00:29:07 +0000 Subject: [PATCH 16/80] chore(DB): import pending files Referenced commit(s): 13b7a8064f3589306456f0e75f56b0683d966480 --- .../rev_1780793919312143900.sql => db_world/2026_06_14_00.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1780793919312143900.sql => db_world/2026_06_14_00.sql} (99%) diff --git a/data/sql/updates/pending_db_world/rev_1780793919312143900.sql b/data/sql/updates/db_world/2026_06_14_00.sql similarity index 99% rename from data/sql/updates/pending_db_world/rev_1780793919312143900.sql rename to data/sql/updates/db_world/2026_06_14_00.sql index a61c90369..4e5854a95 100644 --- a/data/sql/updates/pending_db_world/rev_1780793919312143900.sql +++ b/data/sql/updates/db_world/2026_06_14_00.sql @@ -1,3 +1,4 @@ +-- DB update 2026_06_13_05 -> 2026_06_14_00 -- Add SAI (High General Abbendis) UPDATE `creature_template` SET `AIName` = 'SmartAI' WHERE `entry` = 28548; From 6c4c1e8869557f19a91a8725fcec1304cd6f5ffa Mon Sep 17 00:00:00 2001 From: sogladev Date: Sun, 14 Jun 2026 07:35:22 +0200 Subject: [PATCH 17/80] fix(Scripts/Spells): Gnomish Mind Control Cap, Mind Amplification Dish (#24555) Co-authored-by: tkrokli --- src/server/scripts/Spells/spell_item.cpp | 77 ++++++++++++++++-------- 1 file changed, 53 insertions(+), 24 deletions(-) diff --git a/src/server/scripts/Spells/spell_item.cpp b/src/server/scripts/Spells/spell_item.cpp index a5d1f5d5d..3f2a0babc 100644 --- a/src/server/scripts/Spells/spell_item.cpp +++ b/src/server/scripts/Spells/spell_item.cpp @@ -309,44 +309,73 @@ class spell_item_titanium_seal_of_dalaran : public SpellScript } }; -enum AmplifyDish +// 13180 - Gnomish Mind Control Cap +// 67799 - Mind Amplification Dish +enum AmplificationDish { - SPELL_AMPLIFY_30S = 13180, - SPELL_AMPLIFY_10S = 67799, - SPELL_MENTAL_BATTLE = 67810, - SPELL_AMPLIFY_CHARM_30S = 13181, - SPELL_AMPLIFY_CHARM_10S = 26740, + SPELL_MIND_CONTROL_CAP = 13180, // Gnomish Mind Control cap + SPELL_AMPLIFICATION_DISH = 67799, // Mind Amplification Dish + SPELL_MENTAL_BATTLE = 67810, + SPELL_MIND_CONTROL_CAP_CHARM_30S = 13181, + SPELL_MIND_CONTROL_CAP_CHARM_10S = 26740, + SPELL_DULLARD = 67809, }; class spell_item_mind_amplify_dish : public SpellScript { PrepareSpellScript(spell_item_mind_amplify_dish) + bool Load() override + { + return GetCastItem() != nullptr; + } + + bool Validate(SpellInfo const* /*spell*/) override + { + return ValidateSpellInfo({ SPELL_MIND_CONTROL_CAP_CHARM_10S, SPELL_MIND_CONTROL_CAP_CHARM_30S, SPELL_DULLARD, SPELL_MENTAL_BATTLE }); + } + void OnDummyEffect(SpellEffIndex effIndex) { PreventHitDefaultEffect(effIndex); Unit* caster = GetCaster(); - if (Player* player = caster->ToPlayer()) + Unit* target = GetHitUnit(); + if (!caster || !target) + return; + + // little protection + if (target->ToCreature() && target->ToCreature()->GetCreatureTemplate()->rank > CREATURE_ELITE_NORMAL) + return; + + uint32 charmSpell = GetSpellInfo()->Id == SPELL_AMPLIFICATION_DISH ? SPELL_MIND_CONTROL_CAP_CHARM_10S : SPELL_MIND_CONTROL_CAP_CHARM_30S; + + // 5% of the time - Backfire + // 65% of the time - Successful Mind Control. + // 30% of the time - Unsuccessful Mind Control. + int32 backfire = 5; + int32 failure = 30; + + // Increased chance of failure when used against targets over level 60. + bool isIncreasedChanceOfFailure = GetSpellInfo()->Id == SPELL_MIND_CONTROL_CAP && target->GetLevel() > 60; + if (isIncreasedChanceOfFailure) { - if (Unit* target = GetHitUnit()) - { - // little protection - if (target->ToCreature()) - if (target->ToCreature()->GetCreatureTemplate()->rank > CREATURE_ELITE_NORMAL) - return; - - if (GetSpellInfo()->Id != SPELL_AMPLIFY_10S) - if (target->GetLevel() > 60) - return; - - uint8 pct = std::max(0, 20 + player->GetLevel() - target->GetLevel()); - if (roll_chance_i(pct)) - player->CastSpell(target, SPELL_MENTAL_BATTLE, true); - else if (roll_chance_i(pct)) - player->CastSpell(target, GetSpellInfo()->Id == SPELL_AMPLIFY_10S ? SPELL_AMPLIFY_CHARM_10S : SPELL_AMPLIFY_CHARM_30S, true); - } + backfire = 20; // not verified + failure = 45; // not verified } + + int32 roll = irand(0, 99); + if (roll < backfire) + target->CastSpell(caster, charmSpell, true, GetCastItem()); + else if (roll < backfire + failure) + { + if (roll_chance_i(50)) // not verified + caster->CastSpell(target, SPELL_MENTAL_BATTLE, true, GetCastItem()); + else + caster->CastSpell(caster, SPELL_DULLARD, true, GetCastItem()); + } + else // success + caster->CastSpell(target, charmSpell, true, GetCastItem()); } void Register() override From afa7000b612a1d6702bb2be54a632fc4ab5b5731 Mon Sep 17 00:00:00 2001 From: Crow Date: Sun, 14 Jun 2026 00:54:42 -0500 Subject: [PATCH 18/80] fix(Scripts/ZulAman): Correct boss DataType and hostage order (#25212) Co-authored-by: sudlud --- .../scripts/EasternKingdoms/ZulAman/instance_zulaman.cpp | 8 ++++---- src/server/scripts/EasternKingdoms/ZulAman/zulaman.cpp | 4 ++-- src/server/scripts/EasternKingdoms/ZulAman/zulaman.h | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/server/scripts/EasternKingdoms/ZulAman/instance_zulaman.cpp b/src/server/scripts/EasternKingdoms/ZulAman/instance_zulaman.cpp index 8eae8a7e7..721d1ba5b 100644 --- a/src/server/scripts/EasternKingdoms/ZulAman/instance_zulaman.cpp +++ b/src/server/scripts/EasternKingdoms/ZulAman/instance_zulaman.cpp @@ -38,10 +38,10 @@ struct SHostageInfo static SHostageInfo HostageInfo[] = { - {23790, 186648, { -57.0f, 1343.0f, 40.77f, 3.2f } }, // bear - {23999, 187021, { 400.0f, 1414.0f, 74.36f, 3.3f } }, // eagle - {24001, 186672, { -35.0f, 1134.0f, 18.71f, 1.9f } }, // dragonhawk - {24024, 186667, { 413.0f, 1117.0f, 6.32f, 3.1f } } // lynx + {23999, 187021, { 400.0f, 1414.0f, 74.36f, 3.3f } }, // Harkor (Akil'zon) + {23790, 186648, { -57.0f, 1343.0f, 40.77f, 3.2f } }, // Tanzar (Nalorakk) + {24024, 186667, { -35.0f, 1134.0f, 18.71f, 1.9f } }, // Kraz (Jan'alai) + {24001, 186672, { 413.0f, 1117.0f, 6.32f, 3.1f } } // Ashli (Halazzi) }; Position const HarrisonJonesLoc = {120.687f, 1674.0f, 42.0217f, 1.59044f}; diff --git a/src/server/scripts/EasternKingdoms/ZulAman/zulaman.cpp b/src/server/scripts/EasternKingdoms/ZulAman/zulaman.cpp index 47e6100d1..23f3a5825 100644 --- a/src/server/scripts/EasternKingdoms/ZulAman/zulaman.cpp +++ b/src/server/scripts/EasternKingdoms/ZulAman/zulaman.cpp @@ -242,8 +242,8 @@ struct npc_forest_frog : public ScriptedAI #define GOSSIP_HOSTAGE1 "I am glad to help you." -static uint32 HostageEntry[] = {23790, 23999, 24024, 24001}; -static uint32 ChestEntry[] = {186648, 187021, 186667, 186672}; +static uint32 HostageEntry[] = {23999, 23790, 24024, 24001}; +static uint32 ChestEntry[] = {187021, 186648, 186667, 186672}; class npc_zulaman_hostage : public CreatureScript { diff --git a/src/server/scripts/EasternKingdoms/ZulAman/zulaman.h b/src/server/scripts/EasternKingdoms/ZulAman/zulaman.h index d131a9905..7d3c1f401 100644 --- a/src/server/scripts/EasternKingdoms/ZulAman/zulaman.h +++ b/src/server/scripts/EasternKingdoms/ZulAman/zulaman.h @@ -25,8 +25,8 @@ enum DataTypes { - DATA_NALORAKK = 0, - DATA_AKILZON = 1, + DATA_AKILZON = 0, + DATA_NALORAKK = 1, DATA_JANALAI = 2, DATA_HALAZZI = 3, DATA_HEXLORD = 4, From df82d1be97ec25e909f581c1460990d893113ca1 Mon Sep 17 00:00:00 2001 From: jrad7 <106174524+jrad7@users.noreply.github.com> Date: Sun, 14 Jun 2026 00:55:41 -0500 Subject: [PATCH 19/80] fix(Scripts/ZulFarrak): correct Sezz'ziz add summon coords spawning ~1000yd away (#26183) Co-authored-by: Claude Opus 4.8 --- src/server/scripts/Kalimdor/ZulFarrak/zulfarrak.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/server/scripts/Kalimdor/ZulFarrak/zulfarrak.cpp b/src/server/scripts/Kalimdor/ZulFarrak/zulfarrak.cpp index f1c1d88ea..6b87f39b3 100644 --- a/src/server/scripts/Kalimdor/ZulFarrak/zulfarrak.cpp +++ b/src/server/scripts/Kalimdor/ZulFarrak/zulfarrak.cpp @@ -585,9 +585,9 @@ enum ShadowPriestSezzizEnum std::array>, 4> shadowpriestSezzizAdds = { { { { NPC_SANDFURY_ZEALOT, { 1874.12f, 1198.90f, 8.87f } }, { NPC_SANDFURY_ACOLYTE, { 1874.12f, 1198.90f, 8.87f } } }, - { { NPC_SANDFURY_ACOLYTE, { 895.26f, 1199.09f, 8.87f } }, { NPC_SANDFURY_ACOLYTE, { 895.26f, 1199.088f, 8.87f } } }, - { { NPC_SANDFURY_ZEALOT, { 1874.12f, 1198.90f, 8.87f } }, { NPC_SANDFURY_ACOLYTE, { 895.26f, 1199.09f, 8.87f } }, { NPC_SANDFURY_ACOLYTE, { 895.26f, 1199.09f, 8.87f } } }, - { { NPC_SANDFURY_ZEALOT, { 895.26f, 1199.09f, 8.87f } }, { NPC_SANDFURY_ZEALOT, { 1874.12f, 1198.90f, 8.87f } }, { NPC_SANDFURY_ACOLYTE, { 1874.12f, 1198.90f } }, { NPC_SANDFURY_ACOLYTE, { 895.26f, 1199.09f, 8.87f } } } + { { NPC_SANDFURY_ACOLYTE, { 1895.26f, 1199.09f, 8.87f } }, { NPC_SANDFURY_ACOLYTE, { 1895.26f, 1199.088f, 8.87f } } }, + { { NPC_SANDFURY_ZEALOT, { 1874.12f, 1198.90f, 8.87f } }, { NPC_SANDFURY_ACOLYTE, { 1895.26f, 1199.09f, 8.87f } }, { NPC_SANDFURY_ACOLYTE, { 1895.26f, 1199.09f, 8.87f } } }, + { { NPC_SANDFURY_ZEALOT, { 1895.26f, 1199.09f, 8.87f } }, { NPC_SANDFURY_ZEALOT, { 1874.12f, 1198.90f, 8.87f } }, { NPC_SANDFURY_ACOLYTE, { 1874.12f, 1198.90f } }, { NPC_SANDFURY_ACOLYTE, { 1895.26f, 1199.09f, 8.87f } } } } }; class npc_shadowpriest_sezziz : public CreatureScript From 04f35f7923c22c11124c1c015b29da194e3f7c9b Mon Sep 17 00:00:00 2001 From: sogladev Date: Sun, 14 Jun 2026 23:06:34 +0200 Subject: [PATCH 20/80] fix(Scripts/Spells): Deep Freeze Immunity State proc (#26168) Co-authored-by: Ovahlord --- .../rev_1781291919238142004.sql | 4 ++++ src/server/scripts/Spells/spell_mage.cpp | 20 +++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 data/sql/updates/pending_db_world/rev_1781291919238142004.sql diff --git a/data/sql/updates/pending_db_world/rev_1781291919238142004.sql b/data/sql/updates/pending_db_world/rev_1781291919238142004.sql new file mode 100644 index 000000000..078572e19 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1781291919238142004.sql @@ -0,0 +1,4 @@ +-- +-- Fix Deep Freeze: only proc trigger spell 71757 on permanently stun-immune creatures +DELETE FROM `spell_script_names` WHERE `spell_id`=71761; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES (71761, 'spell_mage_deep_freeze_immunity_state'); diff --git a/src/server/scripts/Spells/spell_mage.cpp b/src/server/scripts/Spells/spell_mage.cpp index be2d979e5..b6a5cf812 100644 --- a/src/server/scripts/Spells/spell_mage.cpp +++ b/src/server/scripts/Spells/spell_mage.cpp @@ -1588,6 +1588,25 @@ class spell_mage_missile_barrage_proc : public AuraScript } }; +// 71761 - Deep Freeze Immunity State +class spell_mage_deep_freeze_immunity_state : public AuraScript +{ + PrepareAuraScript(spell_mage_deep_freeze_immunity_state); + + bool CheckEffectProc(AuraEffect const* /*aurEff*/, ProcEventInfo& eventInfo) + { + if (!eventInfo.GetProcTarget() || !eventInfo.GetProcTarget()->IsCreature()) + return false; + + return eventInfo.GetProcTarget()->ToCreature()->HasMechanicTemplateImmunity(1ULL << MECHANIC_STUN); + } + + void Register() override + { + DoCheckEffectProc += AuraCheckEffectProcFn(spell_mage_deep_freeze_immunity_state::CheckEffectProc, EFFECT_0, SPELL_AURA_PROC_TRIGGER_SPELL); + } +}; + void AddSC_mage_spell_scripts() { RegisterSpellScript(spell_mage_arcane_blast); @@ -1632,4 +1651,5 @@ void AddSC_mage_spell_scripts() RegisterSpellScript(spell_mage_summon_water_elemental); RegisterSpellScript(spell_mage_fingers_of_frost); RegisterSpellScript(spell_mage_magic_absorption); + RegisterSpellScript(spell_mage_deep_freeze_immunity_state); } From d2cbba20bffcecf0265aeeea83114fec09a06b52 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 14 Jun 2026 21:07:48 +0000 Subject: [PATCH 21/80] chore(DB): import pending files Referenced commit(s): 04f35f7923c22c11124c1c015b29da194e3f7c9b --- .../rev_1781291919238142004.sql => db_world/2026_06_14_01.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1781291919238142004.sql => db_world/2026_06_14_01.sql} (85%) diff --git a/data/sql/updates/pending_db_world/rev_1781291919238142004.sql b/data/sql/updates/db_world/2026_06_14_01.sql similarity index 85% rename from data/sql/updates/pending_db_world/rev_1781291919238142004.sql rename to data/sql/updates/db_world/2026_06_14_01.sql index 078572e19..a7a0158d5 100644 --- a/data/sql/updates/pending_db_world/rev_1781291919238142004.sql +++ b/data/sql/updates/db_world/2026_06_14_01.sql @@ -1,3 +1,4 @@ +-- DB update 2026_06_14_00 -> 2026_06_14_01 -- -- Fix Deep Freeze: only proc trigger spell 71757 on permanently stun-immune creatures DELETE FROM `spell_script_names` WHERE `spell_id`=71761; From 133167b0ae27e68a5c3a2d6e47c8a8e133fde04a Mon Sep 17 00:00:00 2001 From: AlsoNotMehh Date: Sun, 14 Jun 2026 17:32:49 -0400 Subject: [PATCH 22/80] fix(Core/Spells): Fix Improved Barkskin armor (#26150) --- src/server/game/Spells/Auras/SpellAuraEffects.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/server/game/Spells/Auras/SpellAuraEffects.cpp b/src/server/game/Spells/Auras/SpellAuraEffects.cpp index a77315c0f..126afaf3b 100644 --- a/src/server/game/Spells/Auras/SpellAuraEffects.cpp +++ b/src/server/game/Spells/Auras/SpellAuraEffects.cpp @@ -847,7 +847,9 @@ void AuraEffect::ApplySpellMod(Unit* target, bool apply) Aura* aura = iter->second->GetBase(); // only passive and permament auras-active auras should have amount set on spellcast and not be affected // if aura is casted by others, it will not be affected - if ((aura->IsPassive() || aura->IsPermanent()) && aura->GetCasterGUID() == guid && aura->GetSpellInfo()->IsAffectedBySpellMod(m_spellmod)) + if ((aura->IsPassive() || aura->IsPermanent()) && aura->GetCasterGUID() == guid && + aura->GetSpellInfo()->CheckShapeshift(target->GetShapeshiftForm()) == SPELL_CAST_OK && + aura->GetSpellInfo()->IsAffectedBySpellMod(m_spellmod)) { if (GetMiscValue() == SPELLMOD_ALL_EFFECTS) { From 4a4de647cb9648573f3608ac59db2a39fc387e65 Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Sun, 14 Jun 2026 17:51:09 -0500 Subject: [PATCH 23/80] fix(Core/Combat): let asymmetric-faction aggressors engage friendly-back targets (#26194) Co-authored-by: blinkysc --- src/server/game/Combat/CombatManager.cpp | 3 +- .../server/game/Combat/CombatManagerTest.cpp | 43 +++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/src/server/game/Combat/CombatManager.cpp b/src/server/game/Combat/CombatManager.cpp index 60bcd77be..6883067a8 100644 --- a/src/server/game/Combat/CombatManager.cpp +++ b/src/server/game/Combat/CombatManager.cpp @@ -59,7 +59,8 @@ // ... both units must be allowed to enter combat if (a->IsCombatDisallowed() || b->IsCombatDisallowed()) return false; - if (a->IsFriendlyTo(b) || b->IsFriendlyTo(a)) + // ...not friendly, unless one side is hostile (asymmetric aggressor wins) + if ((a->IsFriendlyTo(b) || b->IsFriendlyTo(a)) && !a->IsHostileTo(b) && !b->IsHostileTo(a)) return false; Player const* playerA = a->GetCharmerOrOwnerPlayerOrPlayerItself(); Player const* playerB = b->GetCharmerOrOwnerPlayerOrPlayerItself(); diff --git a/src/test/server/game/Combat/CombatManagerTest.cpp b/src/test/server/game/Combat/CombatManagerTest.cpp index f060a83d6..5900ee5e8 100644 --- a/src/test/server/game/Combat/CombatManagerTest.cpp +++ b/src/test/server/game/Combat/CombatManagerTest.cpp @@ -1103,6 +1103,49 @@ TEST_F(CombatManagerIntegrationTest, CanBeginCombat_ValidUnits_Succeeds) EXPECT_TRUE(CombatManager::CanBeginCombat(_creatureA, _creatureB)); } +// Asymmetric factions: A is hostile to the target, but the target's faction still considers A +// friendly (e.g. Dragonblight Mage Hunters vs Moonrest Highborne). The aggressor's hostility must +// win so combat can begin. +TEST_F(CombatManagerIntegrationTest, CanBeginCombat_AsymmetricHostileVsFriendly_Succeeds) +{ + auto* factionC = new FactionTemplateEntry{}; + factionC->ID = 90003; + factionC->faction = 90003; + factionC->factionFlags = 0; + factionC->ourMask = 2; // A (hostileMask=2) is hostile to this group... + factionC->friendlyMask = 1; // ...but this faction considers A's group (ourMask=1) friendly + factionC->hostileMask = 0; + for (auto& e : factionC->enemyFaction) e = 0; + for (auto& f : factionC->friendFaction) f = 0; + sFactionTemplateStore.SetEntry(90003, factionC); + + _creatureB->SetFaction(90003); + ASSERT_TRUE(_creatureA->IsHostileTo(_creatureB)); + ASSERT_TRUE(_creatureB->IsFriendlyTo(_creatureA)); + EXPECT_TRUE(CombatManager::CanBeginCombat(_creatureA, _creatureB)); +} + +// One side friendly and neither side hostile: combat must still be blocked so neutral/friendly +// bystanders are not dragged into combat. +TEST_F(CombatManagerIntegrationTest, CanBeginCombat_FriendlyWithoutHostility_Fails) +{ + auto* factionD = new FactionTemplateEntry{}; + factionD->ID = 90004; + factionD->faction = 90004; + factionD->factionFlags = 0; + factionD->ourMask = 4; // A (hostileMask=2) is NOT hostile to this group... + factionD->friendlyMask = 1; // ...and this faction considers A's group friendly + factionD->hostileMask = 0; + for (auto& e : factionD->enemyFaction) e = 0; + for (auto& f : factionD->friendFaction) f = 0; + sFactionTemplateStore.SetEntry(90004, factionD); + + _creatureB->SetFaction(90004); + ASSERT_FALSE(_creatureA->IsHostileTo(_creatureB)); + ASSERT_FALSE(_creatureB->IsHostileTo(_creatureA)); + EXPECT_FALSE(CombatManager::CanBeginCombat(_creatureA, _creatureB)); +} + // ============================================================================ // GAP COVERAGE: CombatManager::IsInCombatWith (ObjectGuid variant) // ============================================================================ From e1262cd99aece5e47b3c76b9a77835fff77d1c1a Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Sun, 14 Jun 2026 20:05:15 -0300 Subject: [PATCH 24/80] fix(DB/Creature): Freya's Detonating Lasher should be susceptible to crowd control (#26197) Co-authored-by: Claude Opus 4.8 --- data/sql/updates/pending_db_world/rev_1781475932164983700.sql | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 data/sql/updates/pending_db_world/rev_1781475932164983700.sql diff --git a/data/sql/updates/pending_db_world/rev_1781475932164983700.sql b/data/sql/updates/pending_db_world/rev_1781475932164983700.sql new file mode 100644 index 000000000..128609c1a --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1781475932164983700.sql @@ -0,0 +1,2 @@ +-- Freya: Detonating Lasher (32918/33399) CC-able like TC/retail (was -273). +UPDATE `creature_template` SET `CreatureImmunitiesId` = 0 WHERE `entry` IN (32918, 33399); From fe7daf2d87c34e6a7a3d0a0623ed1104b8cc1ee1 Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Sun, 14 Jun 2026 20:13:02 -0300 Subject: [PATCH 25/80] fix(Scripts/Ulduar): Freya's Ancient Conservator should cast Nature's Fury on a random player (#26199) Co-authored-by: Lopin Co-authored-by: Claude Opus 4.8 --- src/server/game/AI/CoreAI/UnitAI.cpp | 8 ++++++-- src/server/game/AI/CoreAI/UnitAI.h | 3 ++- src/server/scripts/Northrend/Ulduar/Ulduar/boss_freya.cpp | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/server/game/AI/CoreAI/UnitAI.cpp b/src/server/game/AI/CoreAI/UnitAI.cpp index ee6ddcd03..dad8adebe 100644 --- a/src/server/game/AI/CoreAI/UnitAI.cpp +++ b/src/server/game/AI/CoreAI/UnitAI.cpp @@ -292,10 +292,14 @@ SpellCastResult UnitAI::DoCastAOE(uint32 spellId, bool triggered) /** * @brief Cast the spell on a random unit from the threat list + * + * @param aura Optional aura filter forwarded to SelectTarget: a positive value + * requires the aura on the target, a negative value excludes targets + * that already have it. */ -SpellCastResult UnitAI::DoCastRandomTarget(uint32 spellId, uint32 threatTablePosition, float dist, bool playerOnly, bool triggered, bool withTank) +SpellCastResult UnitAI::DoCastRandomTarget(uint32 spellId, uint32 threatTablePosition, float dist, bool playerOnly, bool triggered, bool withTank, int32 aura) { - if (Unit* target = SelectTarget(SelectTargetMethod::Random, threatTablePosition, dist, playerOnly, withTank)) + if (Unit* target = SelectTarget(SelectTargetMethod::Random, threatTablePosition, dist, playerOnly, withTank, aura)) { return DoCast(target, spellId, triggered); } diff --git a/src/server/game/AI/CoreAI/UnitAI.h b/src/server/game/AI/CoreAI/UnitAI.h index af3f4c907..a4c01dd47 100644 --- a/src/server/game/AI/CoreAI/UnitAI.h +++ b/src/server/game/AI/CoreAI/UnitAI.h @@ -395,7 +395,8 @@ public: SpellCastResult DoCastToAllHostilePlayers(uint32 spellid, bool triggered = false); SpellCastResult DoCastVictim(uint32 spellId, bool triggered = false); SpellCastResult DoCastAOE(uint32 spellId, bool triggered = false); - SpellCastResult DoCastRandomTarget(uint32 spellId, uint32 threatTablePosition = 0, float dist = 0.0f, bool playerOnly = true, bool triggered = false, bool withTank = true); + // aura: forwarded to SelectTarget - if 0: ignored, if > 0: the target shall have the aura, if < 0: the target shall NOT have the aura + SpellCastResult DoCastRandomTarget(uint32 spellId, uint32 threatTablePosition = 0, float dist = 0.0f, bool playerOnly = true, bool triggered = false, bool withTank = true, int32 aura = 0); /// @brief Cast spell on the top threat target, which may not be the current victim. SpellCastResult DoCastMaxThreat(uint32 spellId, uint32 threatTablePosition = 0, float dist = 0.0f, bool playerOnly = true, bool triggered = false); diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_freya.cpp b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_freya.cpp index 3be8247d1..27b076390 100644 --- a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_freya.cpp +++ b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_freya.cpp @@ -1058,7 +1058,7 @@ struct boss_freya_summons : public ScriptedAI switch (events.ExecuteEvent()) { case EVENT_ANCIENT_CONSERVATOR_NATURE_FURY: - me->CastSpell(me->GetVictim(), SPELL_NATURE_FURY, false); + DoCastRandomTarget(SPELL_NATURE_FURY, 0, 100.0f, true, false, true, -SPELL_NATURE_FURY); events.Repeat(14s); break; case EVENT_ANCIENT_CONSERVATOR_GRIP: From 5db7fea67c240c687ee4e2b77a7d3634009b3602 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 14 Jun 2026 23:42:47 +0000 Subject: [PATCH 26/80] chore(DB): import pending files Referenced commit(s): e1262cd99aece5e47b3c76b9a77835fff77d1c1a --- .../rev_1781475932164983700.sql => db_world/2026_06_14_02.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1781475932164983700.sql => db_world/2026_06_14_02.sql} (79%) diff --git a/data/sql/updates/pending_db_world/rev_1781475932164983700.sql b/data/sql/updates/db_world/2026_06_14_02.sql similarity index 79% rename from data/sql/updates/pending_db_world/rev_1781475932164983700.sql rename to data/sql/updates/db_world/2026_06_14_02.sql index 128609c1a..f35eeeb46 100644 --- a/data/sql/updates/pending_db_world/rev_1781475932164983700.sql +++ b/data/sql/updates/db_world/2026_06_14_02.sql @@ -1,2 +1,3 @@ +-- DB update 2026_06_14_01 -> 2026_06_14_02 -- Freya: Detonating Lasher (32918/33399) CC-able like TC/retail (was -273). UPDATE `creature_template` SET `CreatureImmunitiesId` = 0 WHERE `entry` IN (32918, 33399); From 7513ff498d7ca5e28aa426cde76a128184ee76d3 Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Sun, 14 Jun 2026 19:54:45 -0500 Subject: [PATCH 27/80] fix(Core/AI): restore missing EngagementOver in GuardAI::EnterEvadeMode (#26196) Co-authored-by: blinkysc Co-authored-by: Treeston --- src/server/game/AI/CoreAI/GuardAI.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/server/game/AI/CoreAI/GuardAI.cpp b/src/server/game/AI/CoreAI/GuardAI.cpp index d55dc741e..1508e6b52 100644 --- a/src/server/game/AI/CoreAI/GuardAI.cpp +++ b/src/server/game/AI/CoreAI/GuardAI.cpp @@ -42,6 +42,7 @@ void GuardAI::EnterEvadeMode(EvadeReason /*why*/) me->GetMotionMaster()->MoveIdle(); me->CombatStop(true); me->GetThreatMgr().ClearAllThreat(); + EngagementOver(); return; } @@ -51,6 +52,8 @@ void GuardAI::EnterEvadeMode(EvadeReason /*why*/) me->GetThreatMgr().ClearAllThreat(); me->CombatStop(true); + EngagementOver(); + // Remove ChaseMovementGenerator from MotionMaster stack list, and add HomeMovementGenerator instead if (me->GetMotionMaster()->GetCurrentMovementGeneratorType() == CHASE_MOTION_TYPE) me->GetMotionMaster()->MoveTargetedHome(); From 79bdca26efdaa279e6f58a31261749c4e1792861 Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Mon, 15 Jun 2026 01:03:02 -0300 Subject: [PATCH 28/80] fix(DB/Creature): Auriaya should reset to her patrol start on evade (#26203) Co-authored-by: Claude Opus 4.8 --- data/sql/updates/pending_db_world/rev_1781480954188803100.sql | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 data/sql/updates/pending_db_world/rev_1781480954188803100.sql diff --git a/data/sql/updates/pending_db_world/rev_1781480954188803100.sql b/data/sql/updates/pending_db_world/rev_1781480954188803100.sql new file mode 100644 index 000000000..9f07213de --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1781480954188803100.sql @@ -0,0 +1,2 @@ +-- Auriaya (33515/34175): HARD_RESET - despawn/respawn at spawn on evade. +UPDATE `creature_template` SET `flags_extra` = `flags_extra` | 0x80000000 WHERE `entry` IN (33515, 34175); From d157d4f60d5f94f21cad5a9ffb12b094a6dac9d4 Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Mon, 15 Jun 2026 01:03:46 -0300 Subject: [PATCH 29/80] fix(Scripts/Ulduar): Steelbreaker's Static Disruption should target a ranged player (#26200) Co-authored-by: Claude Opus 4.8 --- .../Ulduar/Ulduar/boss_assembly_of_iron.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_assembly_of_iron.cpp b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_assembly_of_iron.cpp index ff6b7f782..e956b3108 100644 --- a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_assembly_of_iron.cpp +++ b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_assembly_of_iron.cpp @@ -316,11 +316,20 @@ struct boss_steelbreaker : public ScriptedAI events.Repeat(15s, 20s); break; case EVENT_STATIC_DISRUPTION: - if (Unit* pTarget = SelectTarget(SelectTargetMethod::MinDistance, 0, 0, true)) - me->CastSpell(pTarget, SPELL_STATIC_DISRUPTION, false); + { + // Prefer a random player out of melee range so the nature damage debuff + // (and its 5y splash) stays off the melee/tank cluster; if everyone is in + // melee, fall back to a random player regardless of range. + Unit* target = SelectTarget(SelectTargetMethod::Random, 0, -10.0f, true); + if (!target) + target = SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true); + + if (target) + me->CastSpell(target, SPELL_STATIC_DISRUPTION, false); events.Repeat(20s, 40s); break; + } case EVENT_OVERWHELMING_POWER: Talk(SAY_STEELBREAKER_POWER); me->CastSpell(me->GetVictim(), SPELL_OVERWHELMING_POWER, true); From a6f5f98a3101bc47ad89070f562774a3aaccde13 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 15 Jun 2026 04:05:08 +0000 Subject: [PATCH 30/80] chore(DB): import pending files Referenced commit(s): 79bdca26efdaa279e6f58a31261749c4e1792861 --- .../rev_1781480954188803100.sql => db_world/2026_06_15_00.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1781480954188803100.sql => db_world/2026_06_15_00.sql} (80%) diff --git a/data/sql/updates/pending_db_world/rev_1781480954188803100.sql b/data/sql/updates/db_world/2026_06_15_00.sql similarity index 80% rename from data/sql/updates/pending_db_world/rev_1781480954188803100.sql rename to data/sql/updates/db_world/2026_06_15_00.sql index 9f07213de..f2e1fdd73 100644 --- a/data/sql/updates/pending_db_world/rev_1781480954188803100.sql +++ b/data/sql/updates/db_world/2026_06_15_00.sql @@ -1,2 +1,3 @@ +-- DB update 2026_06_14_02 -> 2026_06_15_00 -- Auriaya (33515/34175): HARD_RESET - despawn/respawn at spawn on evade. UPDATE `creature_template` SET `flags_extra` = `flags_extra` | 0x80000000 WHERE `entry` IN (33515, 34175); From a319c1120deb0cb9c3a7acfa93b4df78846efbc0 Mon Sep 17 00:00:00 2001 From: sogladev Date: Mon, 15 Jun 2026 19:58:41 +0200 Subject: [PATCH 31/80] fix(DB/SpellCone): Onyxia Tail Sweep (#26219) --- .../updates/pending_db_world/rev_1781530109728879618.sql | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 data/sql/updates/pending_db_world/rev_1781530109728879618.sql diff --git a/data/sql/updates/pending_db_world/rev_1781530109728879618.sql b/data/sql/updates/pending_db_world/rev_1781530109728879618.sql new file mode 100644 index 000000000..d23b1dfb2 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1781530109728879618.sql @@ -0,0 +1,6 @@ +-- +-- Ony 25man difficulty Tail Sweep +DELETE FROM `spell_cone` WHERE `ID` = 69286; +INSERT INTO `spell_cone` (`ID`, `ConeDegrees`) VALUES (69286, 82); +-- Ony 10man difficulty Tail Sweep +UPDATE `spell_cone` SET `ConeDegrees`=82 WHERE `ID`=68867; From eb3cdd1d1fbdc98a3a59be80e94b2eb592e1ed04 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 15 Jun 2026 18:00:01 +0000 Subject: [PATCH 32/80] chore(DB): import pending files Referenced commit(s): a319c1120deb0cb9c3a7acfa93b4df78846efbc0 --- .../rev_1781530109728879618.sql => db_world/2026_06_15_01.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1781530109728879618.sql => db_world/2026_06_15_01.sql} (84%) diff --git a/data/sql/updates/pending_db_world/rev_1781530109728879618.sql b/data/sql/updates/db_world/2026_06_15_01.sql similarity index 84% rename from data/sql/updates/pending_db_world/rev_1781530109728879618.sql rename to data/sql/updates/db_world/2026_06_15_01.sql index d23b1dfb2..8d1f4d3c9 100644 --- a/data/sql/updates/pending_db_world/rev_1781530109728879618.sql +++ b/data/sql/updates/db_world/2026_06_15_01.sql @@ -1,3 +1,4 @@ +-- DB update 2026_06_15_00 -> 2026_06_15_01 -- -- Ony 25man difficulty Tail Sweep DELETE FROM `spell_cone` WHERE `ID` = 69286; From cf0d86a9ff234430ff7bb82d515761638bc4fab8 Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Tue, 16 Jun 2026 01:17:38 -0300 Subject: [PATCH 33/80] refactor(Core/DB): normalize creature table by extracting multi-ID spawns (#25197) Co-authored-by: Claude Opus 4.6 (1M context) Co-authored-by: Ludwig --- .../pending_db_world/rev_8337814054007982.sql | 25 ++ .../Database/Implementation/WorldDatabase.cpp | 8 +- .../game/AI/SmartScripts/SmartScriptMgr.cpp | 4 +- src/server/game/Conditions/ConditionMgr.cpp | 4 +- .../game/Entities/Creature/Creature.cpp | 12 +- .../game/Entities/Creature/CreatureData.h | 6 +- src/server/game/Events/GameEventMgr.cpp | 12 +- src/server/game/Globals/ObjectMgr.cpp | 336 +++++++++++------- .../game/Handlers/AuctionHouseHandler.cpp | 2 +- src/server/game/Maps/ZoneScript.h | 2 +- src/server/game/OutdoorPvP/OutdoorPvP.cpp | 2 +- src/server/scripts/Commands/cs_go.cpp | 4 +- src/server/scripts/Commands/cs_list.cpp | 16 +- src/server/scripts/Commands/cs_misc.cpp | 8 +- src/server/scripts/Commands/cs_npc.cpp | 20 +- src/server/scripts/Commands/cs_pool.cpp | 2 +- src/server/scripts/Commands/cs_tele.cpp | 6 +- src/server/scripts/Commands/cs_wp.cpp | 2 +- .../TempleOfAhnQiraj/temple_of_ahnqiraj.cpp | 2 +- .../PitOfSaron/instance_pit_of_saron.cpp | 2 +- .../IcecrownCitadel/icecrown_citadel.cpp | 4 +- .../instance_icecrown_citadel.cpp | 2 +- 22 files changed, 281 insertions(+), 200 deletions(-) create mode 100644 data/sql/updates/pending_db_world/rev_8337814054007982.sql diff --git a/data/sql/updates/pending_db_world/rev_8337814054007982.sql b/data/sql/updates/pending_db_world/rev_8337814054007982.sql new file mode 100644 index 000000000..0ccc95924 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_8337814054007982.sql @@ -0,0 +1,25 @@ +-- Create creature_multispawn table for multi-ID spawning +CREATE TABLE IF NOT EXISTS `creature_multispawn` ( + `spawnId` int unsigned NOT NULL COMMENT 'creature.guid', + `entry` int unsigned NOT NULL COMMENT 'creature_template.entry', + PRIMARY KEY (`spawnId`, `entry`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Additional creature entries for multi-ID spawning'; + +-- Migrate id2 and id3 entries +DELETE FROM `creature_multispawn`; +INSERT IGNORE INTO `creature_multispawn` (`spawnId`, `entry`) +SELECT `guid`, `id2` FROM `creature` WHERE `id2` != 0 +UNION ALL +SELECT `guid`, `id3` FROM `creature` WHERE `id3` != 0; + +-- Drop old index before column rename +ALTER TABLE `creature` DROP INDEX `idx_id`; + +-- Rename id1 -> id +ALTER TABLE `creature` CHANGE COLUMN `id1` `id` int unsigned NOT NULL DEFAULT 0 COMMENT 'Creature Identifier'; + +-- Drop id2, id3 +ALTER TABLE `creature` DROP COLUMN `id2`, DROP COLUMN `id3`; + +-- Recreate index on renamed column +ALTER TABLE `creature` ADD INDEX `idx_id` (`id`); diff --git a/src/server/database/Database/Implementation/WorldDatabase.cpp b/src/server/database/Database/Implementation/WorldDatabase.cpp index 7d580068b..77ca6da16 100644 --- a/src/server/database/Database/Implementation/WorldDatabase.cpp +++ b/src/server/database/Database/Implementation/WorldDatabase.cpp @@ -80,15 +80,15 @@ void WorldDatabaseConnection::DoPrepareStatements() PrepareStatement(WORLD_SEL_CREATURE_TEMPLATE, "SELECT entry, difficulty_entry_1, difficulty_entry_2, difficulty_entry_3, KillCredit1, KillCredit2, name, subname, IconName, gossip_menu_id, minlevel, maxlevel, exp, faction, npcflag, speed_walk, speed_run, speed_swim, speed_flight, detection_range, `rank`, dmgschool, DamageModifier, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, unit_class, unit_flags, unit_flags2, dynamicflags, family, type, type_flags, lootid, pickpocketloot, skinloot, PetSpellDataId, VehicleId, mingold, maxgold, AIName, MovementType, ctm.Ground, ctm.Swim, ctm.Flight, ctm.Rooted, ctm.Chase, ctm.Random, ctm.InteractionPauseTimer, HoverHeight, HealthModifier, ManaModifier, ArmorModifier, ExperienceModifier, RacialLeader, movementId, RegenHealth, CreatureImmunitiesId, flags_extra, ScriptName FROM creature_template ct LEFT JOIN creature_template_movement ctm ON ct.entry = ctm.CreatureId WHERE entry = ?", CONNECTION_SYNCH); PrepareStatement(WORLD_SEL_WAYPOINT_SCRIPT_BY_ID, "SELECT guid, delay, command, datalong, datalong2, dataint, x, y, z, o FROM waypoint_scripts WHERE id = ?", CONNECTION_SYNCH); PrepareStatement(WORLD_SEL_ITEM_TEMPLATE_BY_NAME, "SELECT entry FROM item_template WHERE name = ?", CONNECTION_SYNCH); - PrepareStatement(WORLD_SEL_CREATURE_BY_ID, "SELECT guid FROM creature WHERE id1 = ? OR id2 = ? OR id3 = ?", CONNECTION_SYNCH); + PrepareStatement(WORLD_SEL_CREATURE_BY_ID, "SELECT guid FROM creature WHERE id = ? UNION SELECT spawnId AS guid FROM creature_multispawn WHERE entry = ?", CONNECTION_SYNCH); PrepareStatement(WORLD_SEL_GAMEOBJECT_NEAREST, "SELECT guid, id, position_x, position_y, position_z, map, (POW(position_x - ?, 2) + POW(position_y - ?, 2) + POW(position_z - ?, 2)) AS order_ FROM gameobject WHERE map = ? AND (POW(position_x - ?, 2) + POW(position_y - ?, 2) + POW(position_z - ?, 2)) <= ? AND (phaseMask & ?) <> 0 ORDER BY order_", CONNECTION_SYNCH); - PrepareStatement(WORLD_SEL_CREATURE_NEAREST, "SELECT guid, id1, id2, id3, position_x, position_y, position_z, map, (POW(position_x - ?, 2) + POW(position_y - ?, 2) + POW(position_z - ?, 2)) AS order_ FROM creature WHERE map = ? AND (POW(position_x - ?, 2) + POW(position_y - ?, 2) + POW(position_z - ?, 2)) <= ? AND (phaseMask & ?) <> 0 ORDER BY order_", CONNECTION_SYNCH); - PrepareStatement(WORLD_INS_CREATURE, "INSERT INTO creature (guid, id1, id2, id3, map, spawnMask, phaseMask, equipment_id, position_x, position_y, position_z, orientation, spawntimesecs, wander_distance, currentwaypoint, curhealth, curmana, MovementType, npcflag, unit_flags, dynamicflags) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC); + PrepareStatement(WORLD_SEL_CREATURE_NEAREST, "SELECT guid, id, position_x, position_y, position_z, map, (POW(position_x - ?, 2) + POW(position_y - ?, 2) + POW(position_z - ?, 2)) AS order_ FROM creature WHERE map = ? AND (POW(position_x - ?, 2) + POW(position_y - ?, 2) + POW(position_z - ?, 2)) <= ? AND (phaseMask & ?) <> 0 ORDER BY order_", CONNECTION_SYNCH); + PrepareStatement(WORLD_INS_CREATURE, "INSERT INTO creature (guid, id, map, spawnMask, phaseMask, equipment_id, position_x, position_y, position_z, orientation, spawntimesecs, wander_distance, currentwaypoint, curhealth, curmana, MovementType, npcflag, unit_flags, dynamicflags) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC); PrepareStatement(WORLD_SEL_GAME_EVENTS, "SELECT eventEntry, UNIX_TIMESTAMP(start_time), UNIX_TIMESTAMP(end_time), occurence, length, holiday, holidayStage, description, world_event, announce FROM game_event", CONNECTION_SYNCH); PrepareStatement(WORLD_SEL_GAME_EVENT_PREREQUISITE_DATA, "SELECT eventEntry, prerequisite_event FROM game_event_prerequisite", CONNECTION_SYNCH); PrepareStatement(WORLD_SEL_GAME_EVENT_CREATURE_DATA, "SELECT guid, eventEntry FROM game_event_creature", CONNECTION_SYNCH); PrepareStatement(WORLD_SEL_GAME_EVENT_GAMEOBJECT_DATA, "SELECT guid, eventEntry FROM game_event_gameobject", CONNECTION_SYNCH); - PrepareStatement(WORLD_SEL_GAME_EVENT_MODEL_EQUIPMENT_DATA, "SELECT creature.guid, creature.id1, creature.id2, creature.id3, game_event_model_equip.eventEntry, game_event_model_equip.modelid, game_event_model_equip.equipment_id FROM creature JOIN game_event_model_equip ON creature.guid=game_event_model_equip.guid", CONNECTION_SYNCH); + PrepareStatement(WORLD_SEL_GAME_EVENT_MODEL_EQUIPMENT_DATA, "SELECT creature.guid, creature.id, game_event_model_equip.eventEntry, game_event_model_equip.modelid, game_event_model_equip.equipment_id FROM creature JOIN game_event_model_equip ON creature.guid=game_event_model_equip.guid", CONNECTION_SYNCH); PrepareStatement(WORLD_SEL_GAME_EVENT_QUEST_DATA, "SELECT id, quest, eventEntry FROM game_event_creature_quest", CONNECTION_SYNCH); PrepareStatement(WORLD_SEL_GAME_EVENT_GAMEOBJECT_QUEST_DATA, "SELECT id, quest, eventEntry FROM game_event_gameobject_quest", CONNECTION_SYNCH); PrepareStatement(WORLD_SEL_GAME_EVENT_QUEST_CONDITION_DATA, "SELECT quest, eventEntry, condition_id, num FROM game_event_quest_condition", CONNECTION_SYNCH); diff --git a/src/server/game/AI/SmartScripts/SmartScriptMgr.cpp b/src/server/game/AI/SmartScripts/SmartScriptMgr.cpp index 2f96485c7..acc23295b 100644 --- a/src/server/game/AI/SmartScripts/SmartScriptMgr.cpp +++ b/src/server/game/AI/SmartScripts/SmartScriptMgr.cpp @@ -330,7 +330,7 @@ void SmartAIMgr::CheckIfSmartAIInDatabaseExists() // check GUID SAI for (auto const& pair : sObjectMgr->GetAllCreatureData()) { - if (pair.second.id1 != creatureTemplate.Entry) + if (pair.second.id != creatureTemplate.Entry) continue; if (mEventMap[uint32(SmartScriptType::SMART_SCRIPT_TYPE_CREATURE)].find((-1) * pair.first) != mEventMap[uint32(SmartScriptType::SMART_SCRIPT_TYPE_CREATURE)].end()) @@ -2100,7 +2100,7 @@ bool SmartAIMgr::IsTextValid(SmartScriptHolder const& e, uint32 id) return false; } else - entry = data->id1; + entry = data->id; } else entry = uint32(e.entryOrGuid); diff --git a/src/server/game/Conditions/ConditionMgr.cpp b/src/server/game/Conditions/ConditionMgr.cpp index 90319545d..32695a2e8 100644 --- a/src/server/game/Conditions/ConditionMgr.cpp +++ b/src/server/game/Conditions/ConditionMgr.cpp @@ -1958,7 +1958,7 @@ bool ConditionMgr::isSourceTypeValid(Condition* cond) return false; } - if (data->id1 != uint32(cond->SourceEntry)) + if (data->id != uint32(cond->SourceEntry)) { LOG_ERROR("sql.sql", "CONDITION_SOURCE_TYPE_OBJECT_VISIBILITY has creature guid {} that does not match SourceEntry {}, skipped.", cond->SourceId, cond->SourceEntry); return false; @@ -2322,7 +2322,7 @@ bool ConditionMgr::isConditionTypeValid(Condition* cond) { if (CreatureData const* creatureData = sObjectMgr->GetCreatureData(cond->ConditionValue3)) { - if (cond->ConditionValue2 && creatureData->id1 != cond->ConditionValue2) + if (cond->ConditionValue2 && creatureData->id != cond->ConditionValue2) { LOG_ERROR("sql.sql", "ObjectEntryGuid condition has guid {} set but does not match creature entry ({}), skipped", cond->ConditionValue3, cond->ConditionValue2); return false; diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index 304f8ef93..daa849503 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -1423,7 +1423,7 @@ void Creature::SaveToDB(uint32 mapid, uint8 spawnMask, uint32 phaseMask) dynamicflags = 0; } - data.id1 = GetEntry(); + data.id = GetEntry(); data.mapid = mapid; data.phaseMask = phaseMask; data.displayid = displayId; @@ -1469,8 +1469,6 @@ void Creature::SaveToDB(uint32 mapid, uint8 spawnMask, uint32 phaseMask) stmt = WorldDatabase.GetPreparedStatement(WORLD_INS_CREATURE); stmt->SetData(index++, m_spawnId); stmt->SetData(index++, GetEntry()); - stmt->SetData(index++, 0); - stmt->SetData(index++, 0); stmt->SetData(index++, uint16(mapid)); stmt->SetData(index++, spawnMask); stmt->SetData(index++, GetPhaseMask()); @@ -1725,7 +1723,7 @@ bool Creature::LoadCreatureFromDB(ObjectGuid::LowType spawnId, Map* map, bool ad || !groupData || (groupData->flags & SPAWNGROUP_FLAG_COMPATIBILITY_MODE); // Add to world - uint32 entry = GetRandomId(data->id1, data->id2, data->id3); + uint32 entry = GetRandomId(data->id, data->id2, data->id3); if (!Create(map->GenerateLowGuid(), map, data->phaseMask, entry, 0, data->posX, data->posY, data->posZ, data->orientation, data)) return false; @@ -2041,7 +2039,7 @@ void Creature::Respawn(bool force) if (!allowed && !force) // Will be rechecked on next Update call return; - ObjectGuid dbtableHighGuid = ObjectGuid::Create(m_creatureData ? m_creatureData->id1 : GetEntry(), m_spawnId); + ObjectGuid dbtableHighGuid = ObjectGuid::Create(m_creatureData ? m_creatureData->id : GetEntry(), m_spawnId); time_t linkedRespawntime = GetMap()->GetLinkedRespawnTime(dbtableHighGuid); CreatureTemplate const* cInfo = sObjectMgr->GetCreatureTemplate(GetEntry()); @@ -2061,7 +2059,7 @@ void Creature::Respawn(bool force) // Respawn check if spawn has 2 entries if (data->id2) { - uint32 entry = GetRandomId(data->id1, data->id2, data->id3); + uint32 entry = GetRandomId(data->id, data->id2, data->id3); UpdateEntry(entry, data, true); // Select Random Entry m_defaultMovementType = MovementGeneratorType(data->movementType); // Reload Movement Type LoadEquipment(data->equipmentId); // Reload Equipment @@ -3169,7 +3167,7 @@ uint32 Creature::GetScriptId() const if (CreatureData const* creatureData = GetCreatureData()) { uint32 scriptId = creatureData->ScriptId; - if (scriptId && GetEntry() == creatureData->id1) + if (scriptId && GetEntry() == creatureData->id) return scriptId; } diff --git a/src/server/game/Entities/Creature/CreatureData.h b/src/server/game/Entities/Creature/CreatureData.h index ec71df83a..38c8d3ef5 100644 --- a/src/server/game/Entities/Creature/CreatureData.h +++ b/src/server/game/Entities/Creature/CreatureData.h @@ -369,9 +369,9 @@ typedef std::unordered_map EquipmentInfo struct CreatureData : public SpawnData { CreatureData() : SpawnData(SPAWN_TYPE_CREATURE) {} - uint32 id1{0}; // entry in creature_template - uint32 id2{0}; // entry in creature_template - uint32 id3{0}; // entry in creature_template + uint32 id{0}; // entry in creature_template + uint32 id2{0}; // entry in creature_template (from creature_multispawn) + uint32 id3{0}; // entry in creature_template (from creature_multispawn) uint32 displayid{0}; int8 equipmentId{0}; uint32 spawntimesecs{0}; diff --git a/src/server/game/Events/GameEventMgr.cpp b/src/server/game/Events/GameEventMgr.cpp index bde9ba819..ee987592e 100644 --- a/src/server/game/Events/GameEventMgr.cpp +++ b/src/server/game/Events/GameEventMgr.cpp @@ -300,7 +300,7 @@ void GameEventMgr::LoadEventVendors() // Get creature entry newEntry.Entry = 0; if (CreatureData const* data = sObjectMgr->GetCreatureData(guid)) - newEntry.Entry = data->id1; + newEntry.Entry = data->id; // Validate vendor item if (!sObjectMgr->IsVendorItemValid(newEntry.Entry, newEntry.Item, newEntry.MaxCount, newEntry.Incrtime, newEntry.ExtendedCost, nullptr, nullptr, event_npc_flag)) @@ -614,9 +614,7 @@ void GameEventMgr::LoadEventModelEquipmentChangeData() ObjectGuid::LowType guid = fields[0].Get(); uint32 entry = fields[1].Get(); - uint32 entry2 = fields[2].Get(); - uint32 entry3 = fields[3].Get(); - uint16 eventId = fields[4].Get(); + uint16 eventId = fields[2].Get(); if (eventId >= _gameEventModelEquip.size()) { @@ -626,15 +624,15 @@ void GameEventMgr::LoadEventModelEquipmentChangeData() ModelEquipList& equiplist = _gameEventModelEquip[eventId]; ModelEquip newModelEquipSet; - newModelEquipSet.ModelId = fields[5].Get(); - newModelEquipSet.EquipmentId = fields[6].Get(); + newModelEquipSet.ModelId = fields[3].Get(); + newModelEquipSet.EquipmentId = fields[4].Get(); newModelEquipSet.EquipementIdPrev = 0; newModelEquipSet.ModelIdPrev = 0; if (newModelEquipSet.EquipmentId > 0) { int8 equipId = static_cast(newModelEquipSet.EquipmentId); - if ((!sObjectMgr->GetEquipmentInfo(entry, equipId)) || (entry2 && !sObjectMgr->GetEquipmentInfo(entry2, equipId)) || (entry3 && !sObjectMgr->GetEquipmentInfo(entry3, equipId))) + if (!sObjectMgr->GetEquipmentInfo(entry, equipId)) { LOG_ERROR("sql.sql", "Table `game_event_model_equip` have creature (Guid: {}) with equipment_id {} not found in table `creature_equip_template`, set to no equipment.", guid, newModelEquipSet.EquipmentId); diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp index b934ffa5c..849ee2627 100644 --- a/src/server/game/Globals/ObjectMgr.cpp +++ b/src/server/game/Globals/ObjectMgr.cpp @@ -1572,7 +1572,7 @@ void ObjectMgr::LoadCreatureMovementOverrides() "COALESCE(cmo.InteractionPauseTimer, ctm.InteractionPauseTimer) " "FROM creature_movement_override AS cmo " "LEFT JOIN creature AS c ON c.guid = cmo.SpawnId " - "LEFT JOIN creature_template_movement AS ctm ON ctm.CreatureId = c.id1"); + "LEFT JOIN creature_template_movement AS ctm ON ctm.CreatureId = c.id"); if (!result) { LOG_WARN("server.loading", ">> Loaded 0 creature movement overrides. DB table `creature_movement_override` is empty!"); @@ -1978,8 +1978,8 @@ void ObjectMgr::LoadLinkedRespawn() break; } - guid = ObjectGuid::Create(slave->id1, guidLow); - linkedGuid = ObjectGuid::Create(master->id1, linkedGuidLow); + guid = ObjectGuid::Create(slave->id, guidLow); + linkedGuid = ObjectGuid::Create(master->id, linkedGuidLow); break; } case CREATURE_TO_GO: @@ -2015,7 +2015,7 @@ void ObjectMgr::LoadLinkedRespawn() break; } - guid = ObjectGuid::Create(slave->id1, guidLow); + guid = ObjectGuid::Create(slave->id, guidLow); linkedGuid = ObjectGuid::Create(master->id, linkedGuidLow); break; } @@ -2090,7 +2090,7 @@ void ObjectMgr::LoadLinkedRespawn() } guid = ObjectGuid::Create(slave->id, guidLow); - linkedGuid = ObjectGuid::Create(master->id1, linkedGuidLow); + linkedGuid = ObjectGuid::Create(master->id, linkedGuidLow); break; } } @@ -2109,7 +2109,7 @@ bool ObjectMgr::SetCreatureLinkedRespawn(ObjectGuid::LowType guidLow, ObjectGuid return false; CreatureData const* master = GetCreatureData(guidLow); - ObjectGuid guid = ObjectGuid::Create(master->id1, guidLow); + ObjectGuid guid = ObjectGuid::Create(master->id, guidLow); if (!linkedGuidLow) // we're removing the linking { @@ -2140,7 +2140,7 @@ bool ObjectMgr::SetCreatureLinkedRespawn(ObjectGuid::LowType guidLow, ObjectGuid return false; } - ObjectGuid linkedGuid = ObjectGuid::Create(slave->id1, linkedGuidLow); + ObjectGuid linkedGuid = ObjectGuid::Create(slave->id, linkedGuidLow); _linkedRespawnStore[guid] = linkedGuid; WorldDatabasePreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_REP_CREATURE_LINKED_RESPAWN); @@ -2321,11 +2321,11 @@ void ObjectMgr::LoadCreatures() { uint32 oldMSTime = getMSTime(); - // 0 1 2 3 4 5 6 7 8 9 10 11 - QueryResult result = WorldDatabase.Query("SELECT creature.guid, id1, id2, id3, map, equipment_id, position_x, position_y, position_z, orientation, spawntimesecs, wander_distance, " - // 12 13 14 15 16 17 18 19 20 21 22 + // 0 1 2 3 4 5 6 7 8 9 + QueryResult result = WorldDatabase.Query("SELECT creature.guid, id, map, equipment_id, position_x, position_y, position_z, orientation, spawntimesecs, wander_distance, " + // 10 11 12 13 14 15 16 17 18 19 20 "currentwaypoint, curhealth, curmana, MovementType, spawnMask, phaseMask, eventEntry, pool_entry, creature.npcflag, creature.unit_flags, creature.dynamicflags, " - // 23 + // 21 "creature.ScriptName " "FROM creature " "LEFT OUTER JOIN game_event_creature ON creature.guid = game_event_creature.guid " @@ -2356,58 +2356,37 @@ void ObjectMgr::LoadCreatures() Field* fields = result->Fetch(); ObjectGuid::LowType spawnId = fields[0].Get(); - uint32 id1 = fields[1].Get(); - uint32 id2 = fields[2].Get(); - uint32 id3 = fields[3].Get(); + uint32 creatureId = fields[1].Get(); - CreatureTemplate const* cInfo = GetCreatureTemplate(id1); + CreatureTemplate const* cInfo = GetCreatureTemplate(creatureId); if (!cInfo) { - LOG_ERROR("sql.sql", "Table `creature` has creature (SpawnId: {}) with non existing creature entry {} in id1 field, skipped.", spawnId, id1); - continue; - } - CreatureTemplate const* cInfo2 = GetCreatureTemplate(id2); - if (!cInfo2 && id2) - { - LOG_ERROR("sql.sql", "Table `creature` has creature (SpawnId: {}) with non existing creature entry {} in id2 field, skipped.", spawnId, id2); - continue; - } - CreatureTemplate const* cInfo3 = GetCreatureTemplate(id3); - if (!cInfo3 && id3) - { - LOG_ERROR("sql.sql", "Table `creature` has creature (SpawnId: {}) with non existing creature entry {} in id3 field, skipped.", spawnId, id3); - continue; - } - if (!id2 && id3) - { - LOG_ERROR("sql.sql", "Table `creature` has creature (SpawnId: {}) with creature entry {} in id3 field but no entry in id2 field, skipped.", spawnId, id3); + LOG_ERROR("sql.sql", "Table `creature` has creature (SpawnId: {}) with non existing creature entry {} in `id` field, skipped.", spawnId, creatureId); continue; } CreatureData& data = _creatureDataStore[spawnId]; data.spawnId = spawnId; - data.id1 = id1; - data.id2 = id2; - data.id3 = id3; - data.mapid = fields[4].Get(); - data.equipmentId = fields[5].Get(); - data.posX = fields[6].Get(); - data.posY = fields[7].Get(); - data.posZ = fields[8].Get(); - data.orientation = fields[9].Get(); - data.spawntimesecs = fields[10].Get(); - data.wander_distance = fields[11].Get(); - data.currentwaypoint = fields[12].Get(); - data.curhealth = fields[13].Get(); - data.curmana = fields[14].Get(); - data.movementType = fields[15].Get(); - data.spawnMask = fields[16].Get(); - data.phaseMask = fields[17].Get(); - int16 gameEvent = fields[18].Get(); - uint32 PoolId = fields[19].Get(); - data.npcflag = fields[20].Get(); - data.unit_flags = fields[21].Get(); - data.dynamicflags = fields[22].Get(); - data.ScriptId = GetScriptId(fields[23].Get()); + data.id = creatureId; + data.mapid = fields[2].Get(); + data.equipmentId = fields[3].Get(); + data.posX = fields[4].Get(); + data.posY = fields[5].Get(); + data.posZ = fields[6].Get(); + data.orientation = fields[7].Get(); + data.spawntimesecs = fields[8].Get(); + data.wander_distance = fields[9].Get(); + data.currentwaypoint = fields[10].Get(); + data.curhealth = fields[11].Get(); + data.curmana = fields[12].Get(); + data.movementType = fields[13].Get(); + data.spawnMask = fields[14].Get(); + data.phaseMask = fields[15].Get(); + int16 gameEvent = fields[16].Get(); + uint32 PoolId = fields[17].Get(); + data.npcflag = fields[18].Get(); + data.unit_flags = fields[19].Get(); + data.dynamicflags = fields[20].Get(); + data.ScriptId = GetScriptId(fields[21].Get()); data.spawnGroupId = 0; if (!data.ScriptId) @@ -2437,10 +2416,10 @@ void ObjectMgr::LoadCreatures() bool ok = true; for (uint32 diff = 0; diff < MAX_DIFFICULTY - 1 && ok; ++diff) { - if ((_difficultyEntries[diff].find(data.id1) != _difficultyEntries[diff].end()) || (_difficultyEntries[diff].find(data.id2) != _difficultyEntries[diff].end()) || (_difficultyEntries[diff].find(data.id3) != _difficultyEntries[diff].end())) + if (_difficultyEntries[diff].find(data.id) != _difficultyEntries[diff].end()) { - LOG_ERROR("sql.sql", "Table `creature` have creature (SpawnId: {}) that listed as difficulty {} template (Entries: {}, {}, {}) in `creature_template`, skipped.", - spawnId, diff + 1, data.id1, data.id2, data.id3); + LOG_ERROR("sql.sql", "Table `creature` have creature (SpawnId: {}) that listed as difficulty {} template (Entry: {}) in `creature_template`, skipped.", + spawnId, diff + 1, data.id); ok = false; } } @@ -2450,35 +2429,35 @@ void ObjectMgr::LoadCreatures() // -1 random, 0 no equipment, if (data.equipmentId != 0) { - if ((!GetEquipmentInfo(data.id1, data.equipmentId)) || (data.id2 && !GetEquipmentInfo(data.id2, data.equipmentId)) || (data.id3 && !GetEquipmentInfo(data.id3, data.equipmentId))) + if (!GetEquipmentInfo(data.id, data.equipmentId)) { - LOG_ERROR("sql.sql", "Table `creature` have creature (Entries: {}, {}, {}) one or more with equipment_id {} not found in table `creature_equip_template`, set to no equipment.", - data.id1, data.id2, data.id3, data.equipmentId); + LOG_ERROR("sql.sql", "Table `creature` have creature (Entry: {}) with equipment_id {} not found in table `creature_equip_template`, set to no equipment.", + data.id, data.equipmentId); data.equipmentId = 0; } } - if (cInfo->HasFlagsExtra(CREATURE_FLAG_EXTRA_INSTANCE_BIND) || (data.id2 && cInfo2->HasFlagsExtra(CREATURE_FLAG_EXTRA_INSTANCE_BIND)) || (data.id3 && cInfo3->HasFlagsExtra(CREATURE_FLAG_EXTRA_INSTANCE_BIND))) + if (cInfo->HasFlagsExtra(CREATURE_FLAG_EXTRA_INSTANCE_BIND)) { if (!mapEntry->IsDungeon()) - LOG_ERROR("sql.sql", "Table `creature` have creature (SpawnId: {} Entries: {}, {}, {}) with a `creature_template`.`flags_extra` in one or more entries including CREATURE_FLAG_EXTRA_INSTANCE_BIND but creature are not in instance.", - spawnId, data.id1, data.id2, data.id3); + LOG_ERROR("sql.sql", "Table `creature` have creature (SpawnId: {} Entry: {}) with `creature_template`.`flags_extra` including CREATURE_FLAG_EXTRA_INSTANCE_BIND but creature are not in instance.", + spawnId, data.id); } if (data.movementType >= MAX_DB_MOTION_TYPE) { - LOG_ERROR("sql.sql", "Table `creature` has creature (SpawnId: {} Entries: {}, {}, {}) with wrong movement generator type ({}), ignored and set to IDLE.", spawnId, data.id1, data.id2, data.id3, data.movementType); + LOG_ERROR("sql.sql", "Table `creature` has creature (SpawnId: {} Entry: {}) with wrong movement generator type ({}), ignored and set to IDLE.", spawnId, data.id, data.movementType); data.movementType = IDLE_MOTION_TYPE; } if (data.wander_distance < 0.0f) { - LOG_ERROR("sql.sql", "Table `creature` have creature (SpawnId: {} Entries: {}, {}, {}) with `wander_distance`< 0, set to 0.", spawnId, data.id1, data.id2, data.id3); + LOG_ERROR("sql.sql", "Table `creature` have creature (SpawnId: {} Entry: {}) with `wander_distance`< 0, set to 0.", spawnId, data.id); data.wander_distance = 0.0f; } else if (data.movementType == RANDOM_MOTION_TYPE) { if (data.wander_distance == 0.0f) { - LOG_ERROR("sql.sql", "Table `creature` have creature (SpawnId: {} Entries: {}, {}, {}) with `MovementType`=1 (random movement) but with `wander_distance`=0, replace by idle movement type (0).", - spawnId, data.id1, data.id2, data.id3); + LOG_ERROR("sql.sql", "Table `creature` have creature (SpawnId: {} Entry: {}) with `MovementType`=1 (random movement) but with `wander_distance`=0, replace by idle movement type (0).", + spawnId, data.id); data.movementType = IDLE_MOTION_TYPE; } } @@ -2486,14 +2465,14 @@ void ObjectMgr::LoadCreatures() { if (data.wander_distance != 0.0f) { - LOG_ERROR("sql.sql", "Table `creature` have creature (SpawnId: {} Entries: {}, {}, {}) with `MovementType`=0 (idle) have `wander_distance`<>0, set to 0.", spawnId, data.id1, data.id2, data.id3); + LOG_ERROR("sql.sql", "Table `creature` have creature (SpawnId: {} Entry: {}) with `MovementType`=0 (idle) have `wander_distance`<>0, set to 0.", spawnId, data.id); data.wander_distance = 0.0f; } } if (data.phaseMask == 0) { - LOG_ERROR("sql.sql", "Table `creature` have creature (SpawnId: {} Entries: {}, {}, {}) with `phaseMask`=0 (not visible for anyone), set to 1.", spawnId, data.id1, data.id2, data.id3); + LOG_ERROR("sql.sql", "Table `creature` have creature (SpawnId: {} Entry: {}) with `phaseMask`=0 (not visible for anyone), set to 1.", spawnId, data.id); data.phaseMask = 1; } @@ -2518,6 +2497,72 @@ void ObjectMgr::LoadCreatures() ++count; } while (result->NextRow()); + // Load alternate entries from creature_multispawn + QueryResult variantResult = WorldDatabase.Query("SELECT spawnId, entry FROM creature_multispawn ORDER BY spawnId"); + if (variantResult) + { + uint32 variantCount = 0; + do + { + Field* fields = variantResult->Fetch(); + ObjectGuid::LowType spawnId = fields[0].Get(); + uint32 entry = fields[1].Get(); + + auto creatureDataIt = _creatureDataStore.find(spawnId); + if (creatureDataIt == _creatureDataStore.end()) + { + LOG_ERROR("sql.sql", "Table `creature_multispawn` has entry for non-existing creature spawn (SpawnId: {}), skipped.", spawnId); + continue; + } + + CreatureData* data = &creatureDataIt->second; + + CreatureTemplate const* variantInfo = GetCreatureTemplate(entry); + if (!variantInfo) + { + LOG_ERROR("sql.sql", "Table `creature_multispawn` has creature (SpawnId: {}) with non-existing creature entry {}, skipped.", spawnId, entry); + continue; + } + + // Check difficulty entries for variant + bool diffOk = true; + for (uint32 diff = 0; diff < MAX_DIFFICULTY - 1 && diffOk; ++diff) + { + if (_difficultyEntries[diff].find(entry) != _difficultyEntries[diff].end()) + { + LOG_ERROR("sql.sql", "Table `creature_multispawn` has creature (SpawnId: {}) with entry {} listed as difficulty template in `creature_template`, skipped.", + spawnId, entry); + diffOk = false; + } + } + if (!diffOk) + continue; + + // Validate equipment for variant entry + if (data->equipmentId != 0 && !GetEquipmentInfo(entry, data->equipmentId)) + { + LOG_ERROR("sql.sql", "Table `creature_multispawn` has creature (SpawnId: {}) with entry {} where equipment_id {} not found in `creature_equip_template`.", + spawnId, entry, data->equipmentId); + } + + // Populate id2/id3 fields + if (!data->id2) + { + data->id2 = entry; + ++variantCount; + } + else if (!data->id3) + { + data->id3 = entry; + ++variantCount; + } + else + LOG_ERROR("sql.sql", "Table `creature_multispawn` has more than 2 variant entries for creature (SpawnId: {}), extra entry {} skipped.", spawnId, entry); + } while (variantResult->NextRow()); + + LOG_INFO("server.loading", ">> Loaded {} creature spawn variants", variantCount); + } + LOG_INFO("server.loading", ">> Loaded {} Creatures in {} ms", count, GetMSTimeDiffToNow(oldMSTime)); LOG_INFO("server.loading", " "); } @@ -2531,7 +2576,7 @@ CreatureData const* ObjectMgr::LoadCreatureDataFromDB(ObjectGuid::LowType spawnI if (data) return data; - QueryResult result = WorldDatabase.Query("SELECT creature.guid, id1, id2, id3, map, equipment_id, " + QueryResult result = WorldDatabase.Query("SELECT creature.guid, id, map, equipment_id, " "position_x, position_y, position_z, orientation, spawntimesecs, wander_distance, " "currentwaypoint, curhealth, curmana, MovementType, spawnMask, phaseMask, " "creature.npcflag, creature.unit_flags, creature.dynamicflags, creature.ScriptName " @@ -2541,62 +2586,83 @@ CreatureData const* ObjectMgr::LoadCreatureDataFromDB(ObjectGuid::LowType spawnI return nullptr; Field* fields = result->Fetch(); - uint32 id1 = fields[1].Get(); - uint32 id2 = fields[2].Get(); - uint32 id3 = fields[3].Get(); + uint32 creatureId = fields[1].Get(); - CreatureTemplate const* cInfo = GetCreatureTemplate(id1); + CreatureTemplate const* cInfo = GetCreatureTemplate(creatureId); if (!cInfo) { - LOG_ERROR("sql.sql", "Table `creature` has creature (SpawnId: {}) with non-existing creature entry {} in id1 field, skipped.", spawnId, id1); - return nullptr; - } - - if (id2 && !GetCreatureTemplate(id2)) - { - LOG_ERROR("sql.sql", "Table `creature` has creature (SpawnId: {}) with non-existing creature entry {} in id2 field, skipped.", spawnId, id2); - return nullptr; - } - - if (id3 && !GetCreatureTemplate(id3)) - { - LOG_ERROR("sql.sql", "Table `creature` has creature (SpawnId: {}) with non-existing creature entry {} in id3 field, skipped.", spawnId, id3); - return nullptr; - } - - if (!id2 && id3) - { - LOG_ERROR("sql.sql", "Table `creature` has creature (SpawnId: {}) with creature entry {} in id3 field but no entry in id2 field, skipped.", spawnId, id3); + LOG_ERROR("sql.sql", "Table `creature` has creature (SpawnId: {}) with non-existing creature entry {} in `id` field, skipped.", spawnId, creatureId); return nullptr; } CreatureData& creatureData = _creatureDataStore[spawnId]; - creatureData.id1 = id1; - creatureData.id2 = id2; - creatureData.id3 = id3; - creatureData.mapid = fields[4].Get(); - creatureData.equipmentId = fields[5].Get(); - creatureData.posX = fields[6].Get(); - creatureData.posY = fields[7].Get(); - creatureData.posZ = fields[8].Get(); - creatureData.orientation = fields[9].Get(); - creatureData.spawntimesecs = fields[10].Get(); - creatureData.wander_distance = fields[11].Get(); - creatureData.currentwaypoint = fields[12].Get(); - creatureData.curhealth = fields[13].Get(); - creatureData.curmana = fields[14].Get(); - creatureData.movementType = fields[15].Get(); - creatureData.spawnMask = fields[16].Get(); - creatureData.phaseMask = fields[17].Get(); - creatureData.npcflag = fields[18].Get(); - creatureData.unit_flags = fields[19].Get(); - creatureData.dynamicflags = fields[20].Get(); - creatureData.ScriptId = GetScriptId(fields[21].Get()); + creatureData.id = creatureId; + creatureData.mapid = fields[2].Get(); + creatureData.equipmentId = fields[3].Get(); + creatureData.posX = fields[4].Get(); + creatureData.posY = fields[5].Get(); + creatureData.posZ = fields[6].Get(); + creatureData.orientation = fields[7].Get(); + creatureData.spawntimesecs = fields[8].Get(); + creatureData.wander_distance = fields[9].Get(); + creatureData.currentwaypoint = fields[10].Get(); + creatureData.curhealth = fields[11].Get(); + creatureData.curmana = fields[12].Get(); + creatureData.movementType = fields[13].Get(); + creatureData.spawnMask = fields[14].Get(); + creatureData.phaseMask = fields[15].Get(); + creatureData.npcflag = fields[16].Get(); + creatureData.unit_flags = fields[17].Get(); + creatureData.dynamicflags = fields[18].Get(); + creatureData.ScriptId = GetScriptId(fields[19].Get()); creatureData.spawnGroupId = 0; if (!creatureData.ScriptId) creatureData.ScriptId = cInfo->ScriptID; + // Load alternate entries from creature_multispawn + QueryResult variantResult = WorldDatabase.Query("SELECT entry FROM creature_multispawn WHERE spawnId = {} ORDER BY entry", spawnId); + if (variantResult) + { + do + { + uint32 variantEntry = variantResult->Fetch()[0].Get(); + if (!GetCreatureTemplate(variantEntry)) + { + LOG_ERROR("sql.sql", "Table `creature_multispawn` has creature (SpawnId: {}) with non-existing entry {}, skipped.", spawnId, variantEntry); + continue; + } + + // Check difficulty entries for variant + bool diffOk = true; + for (uint32 diff = 0; diff < MAX_DIFFICULTY - 1 && diffOk; ++diff) + { + if (_difficultyEntries[diff].find(variantEntry) != _difficultyEntries[diff].end()) + { + LOG_ERROR("sql.sql", "Table `creature_multispawn` has creature (SpawnId: {}) with entry {} listed as difficulty template in `creature_template`, skipped.", + spawnId, variantEntry); + diffOk = false; + } + } + if (!diffOk) + continue; + + // Validate equipment for variant entry + if (creatureData.equipmentId != 0 && !GetEquipmentInfo(variantEntry, creatureData.equipmentId)) + { + LOG_ERROR("sql.sql", "Table `creature_multispawn` has creature (SpawnId: {}) with entry {} where equipment_id {} not found in `creature_equip_template`.", + spawnId, variantEntry, creatureData.equipmentId); + } + + if (!creatureData.id2) + creatureData.id2 = variantEntry; + else if (!creatureData.id3) + creatureData.id3 = variantEntry; + else + LOG_ERROR("sql.sql", "Table `creature_multispawn` has more than 2 variant entries for creature (SpawnId: {}), extra entry {} skipped.", spawnId, variantEntry); + } while (variantResult->NextRow()); + } + MapEntry const* mapEntry = sMapStore.LookupEntry(creatureData.mapid); if (!mapEntry) { @@ -2611,12 +2677,10 @@ CreatureData const* ObjectMgr::LoadCreatureDataFromDB(ObjectGuid::LowType spawnI bool ok = true; for (uint32 diff = 0; diff < MAX_DIFFICULTY - 1 && ok; ++diff) { - if (_difficultyEntries[diff].find(id1) != _difficultyEntries[diff].end() || - _difficultyEntries[diff].find(id2) != _difficultyEntries[diff].end() || - _difficultyEntries[diff].find(id3) != _difficultyEntries[diff].end()) + if (_difficultyEntries[diff].find(creatureId) != _difficultyEntries[diff].end()) { - LOG_ERROR("sql.sql", "Table `creature` has creature (SpawnId: {}) that is listed as difficulty {} template (Entries: {}, {}, {}) in `creature_template`, skipped.", - spawnId, diff + 1, id1, id2, id3); + LOG_ERROR("sql.sql", "Table `creature` has creature (SpawnId: {}) that is listed as difficulty {} template (Entry: {}) in `creature_template`, skipped.", + spawnId, diff + 1, creatureId); ok = false; } } @@ -2629,35 +2693,33 @@ CreatureData const* ObjectMgr::LoadCreatureDataFromDB(ObjectGuid::LowType spawnI if (creatureData.equipmentId != 0) { - if (!GetEquipmentInfo(id1, creatureData.equipmentId) || - (id2 && !GetEquipmentInfo(id2, creatureData.equipmentId)) || - (id3 && !GetEquipmentInfo(id3, creatureData.equipmentId))) + if (!GetEquipmentInfo(creatureId, creatureData.equipmentId)) { - LOG_ERROR("sql.sql", "Table `creature` has creature (Entries: {}, {}, {}) with equipment_id {} not found in table `creature_equip_template`, set to no equipment.", - id1, id2, id3, creatureData.equipmentId); + LOG_ERROR("sql.sql", "Table `creature` has creature (Entry: {}) with equipment_id {} not found in table `creature_equip_template`, set to no equipment.", + creatureId, creatureData.equipmentId); creatureData.equipmentId = 0; } } if (creatureData.movementType >= MAX_DB_MOTION_TYPE) { - LOG_ERROR("sql.sql", "Table `creature` has creature (SpawnId: {} Entries: {}, {}, {}) with wrong movement generator type ({}), set to IDLE.", - spawnId, id1, id2, id3, creatureData.movementType); + LOG_ERROR("sql.sql", "Table `creature` has creature (SpawnId: {} Entry: {}) with wrong movement generator type ({}), set to IDLE.", + spawnId, creatureId, creatureData.movementType); creatureData.movementType = IDLE_MOTION_TYPE; } if (creatureData.wander_distance < 0.0f) { - LOG_ERROR("sql.sql", "Table `creature` has creature (SpawnId: {} Entries: {}, {}, {}) with `wander_distance`< 0, set to 0.", - spawnId, id1, id2, id3); + LOG_ERROR("sql.sql", "Table `creature` has creature (SpawnId: {} Entry: {}) with `wander_distance`< 0, set to 0.", + spawnId, creatureId); creatureData.wander_distance = 0.0f; } else if (creatureData.movementType == RANDOM_MOTION_TYPE) { if (creatureData.wander_distance == 0.0f) { - LOG_ERROR("sql.sql", "Table `creature` has creature (SpawnId: {} Entries: {}, {}, {}) with `MovementType`=1 (random movement) but with `wander_distance`=0, replace by idle movement type (0).", - spawnId, id1, id2, id3); + LOG_ERROR("sql.sql", "Table `creature` has creature (SpawnId: {} Entry: {}) with `MovementType`=1 (random movement) but with `wander_distance`=0, replace by idle movement type (0).", + spawnId, creatureId); creatureData.movementType = IDLE_MOTION_TYPE; } } @@ -2665,16 +2727,16 @@ CreatureData const* ObjectMgr::LoadCreatureDataFromDB(ObjectGuid::LowType spawnI { if (creatureData.wander_distance != 0.0f) { - LOG_ERROR("sql.sql", "Table `creature` has creature (SpawnId: {} Entries: {}, {}, {}) with `MovementType`=0 (idle) have `wander_distance`<>0, set to 0.", - spawnId, id1, id2, id3); + LOG_ERROR("sql.sql", "Table `creature` has creature (SpawnId: {} Entry: {}) with `MovementType`=0 (idle) have `wander_distance`<>0, set to 0.", + spawnId, creatureId); creatureData.wander_distance = 0.0f; } } if (creatureData.phaseMask == 0) { - LOG_ERROR("sql.sql", "Table `creature` has creature (SpawnId: {} Entries: {}, {}, {}) with `phaseMask`=0 (not visible for anyone), set to 1.", - spawnId, id1, id2, id3); + LOG_ERROR("sql.sql", "Table `creature` has creature (SpawnId: {} Entry: {}) with `phaseMask`=0 (not visible for anyone), set to 1.", + spawnId, creatureId); creatureData.phaseMask = 1; } @@ -2814,7 +2876,7 @@ ObjectGuid::LowType ObjectMgr::AddCreData(uint32 entry, uint32 mapId, float x, f CreatureData& data = NewOrExistCreatureData(spawnId); data.spawnId = spawnId; data.spawnMask = spawnId; - data.id1 = entry; + data.id = entry; data.id2 = 0; data.id3 = 0; data.mapid = mapId; diff --git a/src/server/game/Handlers/AuctionHouseHandler.cpp b/src/server/game/Handlers/AuctionHouseHandler.cpp index 78cf20cbd..c103ea352 100644 --- a/src/server/game/Handlers/AuctionHouseHandler.cpp +++ b/src/server/game/Handlers/AuctionHouseHandler.cpp @@ -279,7 +279,7 @@ void WorldSession::HandleAuctionSellItem(WorldPacket& recvData) return; } - CreatureTemplate const* auctioneerInfo = sObjectMgr->GetCreatureTemplate(auctioneerData->id1); + CreatureTemplate const* auctioneerInfo = sObjectMgr->GetCreatureTemplate(auctioneerData->id); if (!auctioneerInfo) { LOG_ERROR("network.opcode", "Non existing auctioneer ({})", auctioneer.ToString()); diff --git a/src/server/game/Maps/ZoneScript.h b/src/server/game/Maps/ZoneScript.h index 3dd66ce29..45487c451 100644 --- a/src/server/game/Maps/ZoneScript.h +++ b/src/server/game/Maps/ZoneScript.h @@ -28,7 +28,7 @@ public: ZoneScript() {} virtual ~ZoneScript() {} - virtual uint32 GetCreatureEntry(ObjectGuid::LowType /*guidlow*/, CreatureData const* data) { return data->id1; } + virtual uint32 GetCreatureEntry(ObjectGuid::LowType /*guidlow*/, CreatureData const* data) { return data->id; } virtual uint32 GetGameObjectEntry(ObjectGuid::LowType /*guidlow*/, uint32 entry) { return entry; } virtual void OnCreatureCreate(Creature*) { } diff --git a/src/server/game/OutdoorPvP/OutdoorPvP.cpp b/src/server/game/OutdoorPvP/OutdoorPvP.cpp index 6f5ee4d81..fcc5de454 100644 --- a/src/server/game/OutdoorPvP/OutdoorPvP.cpp +++ b/src/server/game/OutdoorPvP/OutdoorPvP.cpp @@ -96,7 +96,7 @@ void OPvPCapturePoint::AddCre(uint32 type, ObjectGuid::LowType guid, uint32 entr return; } - entry = data->id1; + entry = data->id; } _creatures[type] = guid; diff --git a/src/server/scripts/Commands/cs_go.cpp b/src/server/scripts/Commands/cs_go.cpp index 7f575ac17..03e657aa1 100644 --- a/src/server/scripts/Commands/cs_go.cpp +++ b/src/server/scripts/Commands/cs_go.cpp @@ -609,7 +609,7 @@ public: CreatureData const* spawnpoint = nullptr; for (auto const& pair : sObjectMgr->GetAllCreatureData()) { - if (pair.second.id1 != entry) + if (pair.second.id != entry) { continue; } @@ -633,7 +633,7 @@ public: std::vector spawnpoints; for (auto const& pair : sObjectMgr->GetAllCreatureData()) { - if (pair.second.id1 != entry) + if (pair.second.id != entry) { continue; } diff --git a/src/server/scripts/Commands/cs_list.cpp b/src/server/scripts/Commands/cs_list.cpp index ecf82319c..808c835ea 100644 --- a/src/server/scripts/Commands/cs_list.cpp +++ b/src/server/scripts/Commands/cs_list.cpp @@ -77,19 +77,19 @@ public: QueryResult result; uint32 creatureCount = 0; - result = WorldDatabase.Query("SELECT COUNT(guid) FROM creature WHERE id1='{}' OR id2='{}' OR id3='{}'", uint32(creatureId), uint32(creatureId), uint32(creatureId)); + result = WorldDatabase.Query("SELECT COUNT(guid) FROM creature WHERE id = '{}' OR guid IN (SELECT spawnId FROM creature_multispawn WHERE entry = '{}')", uint32(creatureId), uint32(creatureId)); if (result) creatureCount = (*result)[0].Get(); if (handler->GetSession()) { Player* player = handler->GetSession()->GetPlayer(); - result = WorldDatabase.Query("SELECT guid, position_x, position_y, position_z, map, (POW(position_x - '{}', 2) + POW(position_y - '{}', 2) + POW(position_z - '{}', 2)) AS order_ FROM creature WHERE id1='{}' OR id2='{}' OR id3='{}' ORDER BY order_ ASC LIMIT {}", - player->GetPositionX(), player->GetPositionY(), player->GetPositionZ(), uint32(creatureId), uint32(creatureId), uint32(creatureId), count); + result = WorldDatabase.Query("SELECT guid, position_x, position_y, position_z, map, (POW(position_x - '{}', 2) + POW(position_y - '{}', 2) + POW(position_z - '{}', 2)) AS order_ FROM creature WHERE id = '{}' OR guid IN (SELECT spawnId FROM creature_multispawn WHERE entry = '{}') ORDER BY order_ ASC LIMIT {}", + player->GetPositionX(), player->GetPositionY(), player->GetPositionZ(), uint32(creatureId), uint32(creatureId), count); } else - result = WorldDatabase.Query("SELECT guid, position_x, position_y, position_z, map FROM creature WHERE id1='{}' OR id2='{}' OR id3='{}' LIMIT {}", - uint32(creatureId), uint32(creatureId), uint32(creatureId), count); + result = WorldDatabase.Query("SELECT guid, position_x, position_y, position_z, map FROM creature WHERE id = '{}' OR guid IN (SELECT spawnId FROM creature_multispawn WHERE entry = '{}') LIMIT {}", + uint32(creatureId), uint32(creatureId), count); if (result) { @@ -557,13 +557,13 @@ public: for (auto const& pair : map->GetCreatureRespawnTimes()) { CreatureData const* data = sObjectMgr->GetCreatureData(pair.first); - if (!data || (entryFilter && data->id1 != *entryFilter)) + if (!data || (entryFilter && data->id != *entryFilter)) continue; - CreatureTemplate const* cTemplate = sObjectMgr->GetCreatureTemplate(data->id1); + CreatureTemplate const* cTemplate = sObjectMgr->GetCreatureTemplate(data->id); std::string name = cTemplate ? cTemplate->Name : "Unknown"; time_t remaining = pair.second > now ? pair.second - now : 0; - handler->PSendSysMessage(LANG_LIST_RESPAWNS_CREATURE_ENTRY, pair.first, name, data->id1, remaining); + handler->PSendSysMessage(LANG_LIST_RESPAWNS_CREATURE_ENTRY, pair.first, name, data->id, remaining); ++count; if (count >= 50) { diff --git a/src/server/scripts/Commands/cs_misc.cpp b/src/server/scripts/Commands/cs_misc.cpp index 22e457506..6b66f6f27 100644 --- a/src/server/scripts/Commands/cs_misc.cpp +++ b/src/server/scripts/Commands/cs_misc.cpp @@ -2492,7 +2492,7 @@ public: if (isAlive) { - handler->PSendSysMessage(LANG_RESPAWN_GUID_CREATURE_ALIVE, spawnId, creData->id1); + handler->PSendSysMessage(LANG_RESPAWN_GUID_CREATURE_ALIVE, spawnId, creData->id); return true; } @@ -2508,7 +2508,7 @@ public: time_t now = GameTime::GetGameTime().count(); map->SaveCreatureRespawnTime(spawnId, now); } - handler->PSendSysMessage(LANG_RESPAWN_GUID_CREATURE_QUEUED, spawnId, creData->id1); + handler->PSendSysMessage(LANG_RESPAWN_GUID_CREATURE_QUEUED, spawnId, creData->id); return true; } @@ -2608,7 +2608,7 @@ public: for (auto const& [spawnId, creature] : map->GetCreatureBySpawnIdStore()) { CreatureData const* data = sObjectMgr->GetCreatureData(spawnId); - if (!data || data->id1 != entry) + if (!data || data->id != entry) continue; if (creature->isDead()) deadCreatures.push_back(creature); @@ -2624,7 +2624,7 @@ public: for (auto const& [spawnId, respawnTime] : map->GetCreatureRespawnTimes()) { CreatureData const* data = sObjectMgr->GetCreatureData(spawnId); - if (!data || data->id1 != entry) + if (!data || data->id != entry) continue; if (sPoolMgr->IsPartOfAPool(spawnId)) continue; diff --git a/src/server/scripts/Commands/cs_npc.cpp b/src/server/scripts/Commands/cs_npc.cpp index 594c447c0..19421af28 100644 --- a/src/server/scripts/Commands/cs_npc.cpp +++ b/src/server/scripts/Commands/cs_npc.cpp @@ -229,7 +229,7 @@ public: { ObjectGuid::LowType guid = sObjectMgr->GenerateCreatureSpawnId(); CreatureData& data = sObjectMgr->NewOrExistCreatureData(guid); - data.id1 = id; + data.id = id; data.phaseMask = chr->GetPhaseMaskForSpawn(); data.posX = chr->GetTransOffsetX(); data.posY = chr->GetTransOffsetY(); @@ -677,7 +677,7 @@ public: uint32 id3 = 0; if (CreatureData const* cData = target->GetCreatureData()) { - id1 = cData->id1; + id1 = cData->id; id2 = cData->id2; id3 = cData->id3; } @@ -721,7 +721,7 @@ public: static bool HandleNpcInfoCommandShowFromDB(ChatHandler* handler, ObjectGuid::LowType lowGuid, CreatureData const* cData) { - CreatureTemplate const* cInfo = sObjectMgr->GetCreatureTemplate(cData->id1); + CreatureTemplate const* cInfo = sObjectMgr->GetCreatureTemplate(cData->id); if (!cInfo) { handler->SendErrorMessage(LANG_COMMAND_CREATGUIDNOTFOUND, lowGuid); @@ -730,7 +730,7 @@ public: handler->PSendSysMessage("(Not in world - showing DB data)"); uint32 scriptId = cData->ScriptId ? cData->ScriptId : cInfo->ScriptID; - handler->PSendSysMessage(LANG_NPCINFO_CHAR, lowGuid, ObjectGuid::Create(cData->id1, lowGuid).ToString(), cData->id1, + handler->PSendSysMessage(LANG_NPCINFO_CHAR, lowGuid, ObjectGuid::Create(cData->id, lowGuid).ToString(), cData->id, cData->id2, cData->id3, cData->displayid, cData->displayid, cInfo->faction, cData->npcflag); handler->PSendSysMessage(LANG_NPCINFO_PHASEMASK, cData->phaseMask); @@ -763,7 +763,7 @@ public: uint32 id3 = 0; if (CreatureData const* cData = target->GetCreatureData()) { - id1 = cData->id1; + id1 = cData->id; id2 = cData->id2; id3 = cData->id3; } @@ -835,12 +835,10 @@ public: continue; uint32 entry = fields[1].Get(); - //uint32 entry2 = fields[2].Get(); - //uint32 entry3 = fields[3].Get(); - float x = fields[4].Get(); - float y = fields[5].Get(); - float z = fields[6].Get(); - uint16 mapId = fields[7].Get(); + float x = fields[2].Get(); + float y = fields[3].Get(); + float z = fields[4].Get(); + uint16 mapId = fields[5].Get(); CreatureTemplate const* creatureTemplate = sObjectMgr->GetCreatureTemplate(entry); if (!creatureTemplate) diff --git a/src/server/scripts/Commands/cs_pool.cpp b/src/server/scripts/Commands/cs_pool.cpp index 56addf9f3..8f56e7063 100644 --- a/src/server/scripts/Commands/cs_pool.cpp +++ b/src/server/scripts/Commands/cs_pool.cpp @@ -78,7 +78,7 @@ private: { if (CreatureData const* data = sObjectMgr->GetCreatureData(obj.guid)) { - entry = data->id1; + entry = data->id; mapId = data->mapid; x = data->posX; y = data->posY; diff --git a/src/server/scripts/Commands/cs_tele.cpp b/src/server/scripts/Commands/cs_tele.cpp index ee8c9d796..5591738b3 100644 --- a/src/server/scripts/Commands/cs_tele.cpp +++ b/src/server/scripts/Commands/cs_tele.cpp @@ -309,7 +309,7 @@ public: CreatureData const* spawnpoint = nullptr; for (auto const& pair : sObjectMgr->GetAllCreatureData()) { - if (pair.second.id1 != *creatureId) + if (pair.second.id != *creatureId) continue; if (!spawnpoint) @@ -341,7 +341,7 @@ public: return false; } - CreatureTemplate const* creatureTemplate = ASSERT_NOTNULL(sObjectMgr->GetCreatureTemplate(spawnpoint->id1)); + CreatureTemplate const* creatureTemplate = ASSERT_NOTNULL(sObjectMgr->GetCreatureTemplate(spawnpoint->id)); return DoNameTeleport(handler, player, spawnpoint->mapid, { spawnpoint->posX, spawnpoint->posY, spawnpoint->posZ }, creatureTemplate->Name); } @@ -352,7 +352,7 @@ public: WorldDatabase.EscapeString(normalizedName); // May need work //PussyWizardEliteMalcrom - QueryResult result = WorldDatabase.Query("SELECT c.position_x, c.position_y, c.position_z, c.orientation, c.map, ct.name FROM creature c INNER JOIN creature_template ct ON c.id1 = ct.entry WHERE ct.name LIKE '{}'", normalizedName); + QueryResult result = WorldDatabase.Query("SELECT c.position_x, c.position_y, c.position_z, c.orientation, c.map, ct.name FROM creature c INNER JOIN creature_template ct ON c.id = ct.entry WHERE ct.name LIKE '{}'", normalizedName); if (!result) { handler->SendErrorMessage(LANG_COMMAND_GOCREATNOTFOUND); diff --git a/src/server/scripts/Commands/cs_wp.cpp b/src/server/scripts/Commands/cs_wp.cpp index bc1a12387..0c17fd633 100644 --- a/src/server/scripts/Commands/cs_wp.cpp +++ b/src/server/scripts/Commands/cs_wp.cpp @@ -1037,7 +1037,7 @@ public: if (show == "off") { WorldDatabasePreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_SEL_CREATURE_BY_ID); - stmt->SetArguments(1, 1, 1); + stmt->SetArguments(1, 1); PreparedQueryResult result = WorldDatabase.Query(stmt); if (!result) diff --git a/src/server/scripts/Kalimdor/TempleOfAhnQiraj/temple_of_ahnqiraj.cpp b/src/server/scripts/Kalimdor/TempleOfAhnQiraj/temple_of_ahnqiraj.cpp index 697c70093..866c89a81 100644 --- a/src/server/scripts/Kalimdor/TempleOfAhnQiraj/temple_of_ahnqiraj.cpp +++ b/src/server/scripts/Kalimdor/TempleOfAhnQiraj/temple_of_ahnqiraj.cpp @@ -428,7 +428,7 @@ struct npc_ahnqiraji_critter : public ScriptedAI // Don't attack nearby players randomly if they are the Twin's pet bugs. if (CreatureData const* crData = me->GetCreatureData()) { - ObjectGuid dbtableHighGuid = ObjectGuid::Create(crData->id1, me->GetSpawnId()); + ObjectGuid dbtableHighGuid = ObjectGuid::Create(crData->id, me->GetSpawnId()); ObjectGuid targetGuid = sObjectMgr->GetLinkedRespawnGuid(dbtableHighGuid); if (targetGuid.GetEntry() == NPC_VEKLOR) diff --git a/src/server/scripts/Northrend/FrozenHalls/PitOfSaron/instance_pit_of_saron.cpp b/src/server/scripts/Northrend/FrozenHalls/PitOfSaron/instance_pit_of_saron.cpp index 1cc7f2ca7..a458987be 100644 --- a/src/server/scripts/Northrend/FrozenHalls/PitOfSaron/instance_pit_of_saron.cpp +++ b/src/server/scripts/Northrend/FrozenHalls/PitOfSaron/instance_pit_of_saron.cpp @@ -84,7 +84,7 @@ public: uint32 GetCreatureEntry(ObjectGuid::LowType /*guidLow*/, CreatureData const* data) override { - uint32 entry = data->id1; + uint32 entry = data->id; switch (entry) { case NPC_RESCUED_ALLIANCE_SLAVE: diff --git a/src/server/scripts/Northrend/IcecrownCitadel/icecrown_citadel.cpp b/src/server/scripts/Northrend/IcecrownCitadel/icecrown_citadel.cpp index f471555c4..bdde946bd 100644 --- a/src/server/scripts/Northrend/IcecrownCitadel/icecrown_citadel.cpp +++ b/src/server/scripts/Northrend/IcecrownCitadel/icecrown_citadel.cpp @@ -1045,7 +1045,7 @@ public: if (Creature* crusader = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_CAPTAIN_ARNATH + i))) if (crusader->IsAlive()) { - if (crusader->GetEntry() == crusader->GetCreatureData()->id1) + if (crusader->GetEntry() == crusader->GetCreatureData()->id) { crusader->m_Events.AddEventAtOffset(new CaptainSurviveTalk(*crusader), delay); delay += 6s; @@ -1235,7 +1235,7 @@ public: void Reset() override { me->SetCorpseDelay(DAY); // leave corpse for a long time so svalna can resurrect - IsUndead = (me->GetCreatureData() && me->GetCreatureData()->id1 != me->GetEntry()); + IsUndead = (me->GetCreatureData() && me->GetCreatureData()->id != me->GetEntry()); } void JustDied(Unit* /*killer*/) override diff --git a/src/server/scripts/Northrend/IcecrownCitadel/instance_icecrown_citadel.cpp b/src/server/scripts/Northrend/IcecrownCitadel/instance_icecrown_citadel.cpp index 6b2077677..9d5d5105a 100644 --- a/src/server/scripts/Northrend/IcecrownCitadel/instance_icecrown_citadel.cpp +++ b/src/server/scripts/Northrend/IcecrownCitadel/instance_icecrown_citadel.cpp @@ -539,7 +539,7 @@ public: uint32 GetCreatureEntry(ObjectGuid::LowType /*guidLow*/, CreatureData const* data) override { - uint32 entry = data->id1; + uint32 entry = data->id; switch (entry) { case NPC_HORDE_GUNSHIP_CANNON: From 1e0c3c824bbc731c41195b2e3a5417ba72cbb905 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 16 Jun 2026 05:14:05 +0000 Subject: [PATCH 34/80] chore(DB): import pending files Referenced commit(s): cf0d86a9ff234430ff7bb82d515761638bc4fab8 --- .../rev_8337814054007982.sql => db_world/2026_06_16_00.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_8337814054007982.sql => db_world/2026_06_16_00.sql} (95%) diff --git a/data/sql/updates/pending_db_world/rev_8337814054007982.sql b/data/sql/updates/db_world/2026_06_16_00.sql similarity index 95% rename from data/sql/updates/pending_db_world/rev_8337814054007982.sql rename to data/sql/updates/db_world/2026_06_16_00.sql index 0ccc95924..7df594d57 100644 --- a/data/sql/updates/pending_db_world/rev_8337814054007982.sql +++ b/data/sql/updates/db_world/2026_06_16_00.sql @@ -1,3 +1,4 @@ +-- DB update 2026_06_15_01 -> 2026_06_16_00 -- Create creature_multispawn table for multi-ID spawning CREATE TABLE IF NOT EXISTS `creature_multispawn` ( `spawnId` int unsigned NOT NULL COMMENT 'creature.guid', From f5917881ca90787ce7e1ce0c951a03f2c43eea43 Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Tue, 16 Jun 2026 01:51:44 -0500 Subject: [PATCH 35/80] fix(DB/Quest): Troll Patrol now grants Congratulations! (#23970) Co-authored-by: blinkysc Co-authored-by: sudlud Co-authored-by: Claude Opus 4.8 (1M context) --- .../pending_db_world/rev_1764462653575285832.sql | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 data/sql/updates/pending_db_world/rev_1764462653575285832.sql diff --git a/data/sql/updates/pending_db_world/rev_1764462653575285832.sql b/data/sql/updates/pending_db_world/rev_1764462653575285832.sql new file mode 100644 index 000000000..93b91d0ad --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1764462653575285832.sql @@ -0,0 +1,11 @@ +-- Troll Patrol dailies 12563 and 12587 were missing the spells that gate the +-- "Congratulations!" (12604) follow-up. 12604 requires both On Patrol (51573) +-- and On Patrol Heartbeat Script (53707); the time limit is enforced by 51573's +-- 20-minute duration. 12501 already grants both and works, so match it: +-- 51573 on accept (SourceSpellID), 53707 on turn-in (RewardSpell). + +-- On Patrol (51573) on accept of Troll Patrol 12563 +UPDATE `quest_template_addon` SET `SourceSpellID` = 51573 WHERE `ID` = 12563; + +-- On Patrol Heartbeat Script (53707) on turn-in of Troll Patrol 12563 and 12587 +UPDATE `quest_template` SET `RewardSpell` = 53707 WHERE `ID` IN (12563, 12587); From faedf206bf7d808092f3c2e8647a20eea361fc40 Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Tue, 16 Jun 2026 01:53:08 -0500 Subject: [PATCH 36/80] fix(Core/Spells): Reorder melee/ranged spell crit to match SpellCriticalDamageBonus (#24979) Co-authored-by: blinkysc --- src/server/game/Entities/Unit/Unit.cpp | 33 ++-- .../Spells/SpellCritDamageBonusOrderTest.cpp | 160 ++++++++++++++++++ 2 files changed, 178 insertions(+), 15 deletions(-) create mode 100644 src/test/server/game/Spells/SpellCritDamageBonusOrderTest.cpp diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index 311514935..3cbd7c241 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -1555,28 +1555,32 @@ void Unit::CalculateSpellDamageTaken(SpellNonMeleeDamage* damageInfo, int32 dama { damageInfo->HitInfo |= SPELL_HIT_TYPE_CRIT; - // Calculate crit bonus - uint32 crit_bonus = damage; - // Apply crit_damage bonus for melee spells - if (Player* modOwner = GetSpellModOwner()) - modOwner->ApplySpellMod(spellInfo->Id, SPELLMOD_CRIT_DAMAGE_BONUS, crit_bonus); - damage += crit_bonus; + // Calculate crit bonus (100% for melee/ranged spells) + int32 crit_bonus = damage; + crit_bonus += damage; // Apply SPELL_AURA_MOD_ATTACKER_RANGED_CRIT_DAMAGE or SPELL_AURA_MOD_ATTACKER_MELEE_CRIT_DAMAGE - float critPctDamageMod = 0.0f; + float crit_mod = 0.0f; if (attackType == RANGED_ATTACK) - critPctDamageMod += victim->GetTotalAuraModifier(SPELL_AURA_MOD_ATTACKER_RANGED_CRIT_DAMAGE); + crit_mod += victim->GetTotalAuraModifier(SPELL_AURA_MOD_ATTACKER_RANGED_CRIT_DAMAGE); else - critPctDamageMod += victim->GetTotalAuraModifier(SPELL_AURA_MOD_ATTACKER_MELEE_CRIT_DAMAGE); + crit_mod += victim->GetTotalAuraModifier(SPELL_AURA_MOD_ATTACKER_MELEE_CRIT_DAMAGE); // Increase crit damage from SPELL_AURA_MOD_CRIT_DAMAGE_BONUS - critPctDamageMod += GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_CRIT_DAMAGE_BONUS, spellInfo->GetSchoolMask()); - + crit_mod += GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_CRIT_DAMAGE_BONUS, spellInfo->GetSchoolMask()); // Increase crit damage from SPELL_AURA_MOD_CRIT_PERCENT_VERSUS - critPctDamageMod += GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_CRIT_PERCENT_VERSUS, crTypeMask); + crit_mod += GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_CRIT_PERCENT_VERSUS, crTypeMask); - if (critPctDamageMod != 0) - AddPct(damage, critPctDamageMod); + if (crit_bonus != 0 && crit_mod != 0.0f) + AddPct(crit_bonus, crit_mod); + + crit_bonus -= damage; + + // adds additional damage to critBonus (from talents) + if (Player* modOwner = GetSpellModOwner()) + modOwner->ApplySpellMod(spellInfo->Id, SPELLMOD_CRIT_DAMAGE_BONUS, crit_bonus); + + damage = crit_bonus + damage; } // Spell weapon based damage CAN BE crit & blocked at same time @@ -9384,7 +9388,6 @@ uint32 Unit::SpellCriticalDamageBonus(Unit const* caster, SpellInfo const* spell { case SPELL_DAMAGE_CLASS_MELEE: // for melee based spells is 100% case SPELL_DAMAGE_CLASS_RANGED: - /// @todo: write here full calculation for melee/ranged spells crit_bonus += damage; break; default: diff --git a/src/test/server/game/Spells/SpellCritDamageBonusOrderTest.cpp b/src/test/server/game/Spells/SpellCritDamageBonusOrderTest.cpp new file mode 100644 index 000000000..98c7e4df9 --- /dev/null +++ b/src/test/server/game/Spells/SpellCritDamageBonusOrderTest.cpp @@ -0,0 +1,160 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include "Util.h" +#include "gtest/gtest.h" + +/** + * @brief Tests for melee/ranged spell crit damage ordering (issue #24976) + * + * The inline melee/ranged spell crit code in CalculateSpellDamageTaken applied + * SPELLMOD_CRIT_DAMAGE_BONUS BEFORE aura percentage modifiers, causing aura + * modifiers to amplify the already-talent-modified bonus. The correct order + * (matching SpellCriticalDamageBonus) is: + * + * 1. Double damage (100% crit bonus for melee/ranged) + * 2. Apply aura % modifiers to the full crit value + * 3. Extract the bonus portion + * 4. Apply SPELLMOD_CRIT_DAMAGE_BONUS to the bonus + * 5. Recombine + * + * From the issue's expected blizzlike calculation: + * 200% * 1.03 = 206% (aura % first) + * 206% - 100% = 106% (extract bonus) + * 106% * 140% = 148.4% (talent mod on bonus) + * 100% + 148.4% = 248.4% (recombine) + */ + +namespace +{ + +/** + * Replicates the FIXED inline crit code (correct ordering, + * matches SpellCriticalDamageBonus) for melee/ranged spells. + * + * @param damage Base spell damage before crit + * @param auraCritMod Aura crit % modifier (e.g. 3 for meta gem +3%) + * @param spellModPct SPELLMOD_CRIT_DAMAGE_BONUS % (e.g. 40 for Mortal Shots) + */ +int32 CorrectCritOrder(int32 damage, float auraCritMod, float spellModPct) +{ + // Step 1: crit_bonus = 2x damage (100% bonus for melee/ranged) + int32 crit_bonus = damage; + crit_bonus += damage; + + // Step 2: Apply aura % modifiers FIRST + if (crit_bonus != 0 && auraCritMod != 0.0f) + AddPct(crit_bonus, auraCritMod); + + // Step 3: Extract bonus + crit_bonus -= damage; + + // Step 4: Apply talent mod to the bonus + AddPct(crit_bonus, spellModPct); + + // Step 5: Recombine + return crit_bonus + damage; +} + +/** + * Replicates the OLD inline crit code (wrong ordering): + * SPELLMOD_CRIT_DAMAGE_BONUS applied BEFORE aura % modifiers. + */ +int32 OldWrongCritOrder(int32 damage, float auraCritMod, float spellModPct) +{ + // OLD Step 1: talent mod on base bonus FIRST (wrong) + uint32 crit_bonus = damage; + AddPct(crit_bonus, spellModPct); + int32 totalDamage = damage + static_cast(crit_bonus); + + // OLD Step 2: aura % on full total SECOND + if (auraCritMod != 0.0f) + AddPct(totalDamage, auraCritMod); + + return totalDamage; +} + +} // namespace + +class SpellCritDamageBonusOrderTest : public ::testing::Test {}; + +// No modifiers: both orderings produce 2x damage +TEST_F(SpellCritDamageBonusOrderTest, NoModifiers) +{ + EXPECT_EQ(CorrectCritOrder(1000, 0.0f, 0.0f), 2000); + EXPECT_EQ(OldWrongCritOrder(1000, 0.0f, 0.0f), 2000); +} + +// Only aura mod, no talent mod: both orderings match +TEST_F(SpellCritDamageBonusOrderTest, OnlyAuraMod) +{ + EXPECT_EQ(CorrectCritOrder(1000, 30.0f, 0.0f), 2600); + EXPECT_EQ(OldWrongCritOrder(1000, 30.0f, 0.0f), 2600); +} + +// Only talent mod, no aura mod: both orderings match +TEST_F(SpellCritDamageBonusOrderTest, OnlyTalentMod) +{ + // Correct: bonus = 2000-1000 = 1000, *1.03 = 1030, +1000 = 2030 + // Old: crit_bonus = 1000*1.03 = 1030, total = 2030 + EXPECT_EQ(CorrectCritOrder(1000, 0.0f, 3.0f), 2030); + EXPECT_EQ(OldWrongCritOrder(1000, 0.0f, 3.0f), 2030); +} + +// Issue #24976 exact scenario: Hunter Steady Shot with Fine Light Crossbow +// +// damage = 320, meta gem = 3% aura, Mortal Shots etc = 40% SPELLMOD +// +// Expected (blizzlike): +// 200% * 1.03 = 206% → 640 * 1.03 = 659 +// 206% - 100% = 106% → 659 - 320 = 339 +// 106% * 140% = 148.4% → 339 * 1.40 = 474 +// 100% + 148.4% → 320 + 474 = 794 +// (issue reports 795 from float math; integer truncation gives 794) +// +// Old (wrong): +// crit_bonus = 320 * 1.40 = 448 +// damage = 320 + 448 = 768 +// 768 * 1.03 = 791 +TEST_F(SpellCritDamageBonusOrderTest, Issue24976_HunterSteadyShot) +{ + int32 damage = 320; + float auraMod = 3.0f; // meta gem: +3% crit damage + float talentMod = 40.0f; // Mortal Shots etc: +40% SPELLMOD_CRIT_DAMAGE_BONUS + + EXPECT_EQ(OldWrongCritOrder(damage, auraMod, talentMod), 791); + EXPECT_EQ(CorrectCritOrder(damage, auraMod, talentMod), 794); + EXPECT_GT(CorrectCritOrder(damage, auraMod, talentMod), + OldWrongCritOrder(damage, auraMod, talentMod)); +} + +// Both modifiers with larger values: difference becomes more pronounced +TEST_F(SpellCritDamageBonusOrderTest, BothMods_LargerValues) +{ + int32 damage = 1000; + float auraMod = 3.0f; + float talentMod = 40.0f; + + // Correct: 2000*1.03=2060, bonus=1060, *1.40=1484, +1000=2484 + EXPECT_EQ(CorrectCritOrder(damage, auraMod, talentMod), 2484); + + // Wrong: 1000*1.40=1400, total=2400, *1.03=2472 + EXPECT_EQ(OldWrongCritOrder(damage, auraMod, talentMod), 2472); + + EXPECT_GT(CorrectCritOrder(damage, auraMod, talentMod), + OldWrongCritOrder(damage, auraMod, talentMod)); +} From b9100544a8e6895a5fc59746a9f471ce1b60c480 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 16 Jun 2026 06:58:31 +0000 Subject: [PATCH 37/80] chore(DB): import pending files Referenced commit(s): f5917881ca90787ce7e1ce0c951a03f2c43eea43 --- .../rev_1764462653575285832.sql => db_world/2026_06_16_01.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1764462653575285832.sql => db_world/2026_06_16_01.sql} (93%) diff --git a/data/sql/updates/pending_db_world/rev_1764462653575285832.sql b/data/sql/updates/db_world/2026_06_16_01.sql similarity index 93% rename from data/sql/updates/pending_db_world/rev_1764462653575285832.sql rename to data/sql/updates/db_world/2026_06_16_01.sql index 93b91d0ad..638535742 100644 --- a/data/sql/updates/pending_db_world/rev_1764462653575285832.sql +++ b/data/sql/updates/db_world/2026_06_16_01.sql @@ -1,3 +1,4 @@ +-- DB update 2026_06_16_00 -> 2026_06_16_01 -- Troll Patrol dailies 12563 and 12587 were missing the spells that gate the -- "Congratulations!" (12604) follow-up. 12604 requires both On Patrol (51573) -- and On Patrol Heartbeat Script (53707); the time limit is enforced by 51573's From 6966986c0475e91fb4155864c3b87fb51f9ef953 Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Tue, 16 Jun 2026 04:44:19 -0500 Subject: [PATCH 38/80] fix(DB/SAI): Rhydian spawns portal to Shattrath on quest 13081 accept (#23930) Co-authored-by: blinkysc Co-authored-by: Gultask <100873791+Gultask@users.noreply.github.com> --- .../rev_1764229187065117576.sql | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 data/sql/updates/pending_db_world/rev_1764229187065117576.sql diff --git a/data/sql/updates/pending_db_world/rev_1764229187065117576.sql b/data/sql/updates/pending_db_world/rev_1764229187065117576.sql new file mode 100644 index 000000000..6a5cdc04a --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1764229187065117576.sql @@ -0,0 +1,32 @@ +-- Fix for issue #23784: Rhydian should spawn portal to Shattrath when quest 13081 "The Will of the Naaru" is accepted + +-- Enable SmartAI for Rhydian and Tirion +UPDATE `creature_template` SET `AIName` = 'SmartAI' WHERE `entry` IN (30656, 31044); + +-- Add Rhydian's text +DELETE FROM `creature_text` WHERE `CreatureID` = 30656 AND `GroupID` = 0; +INSERT INTO `creature_text` (`CreatureID`, `GroupID`, `ID`, `Text`, `Type`, `Language`, `Probability`, `Emote`, `Duration`, `Sound`, `BroadcastTextId`, `TextRange`, `comment`) VALUES +(30656, 0, 0, 'Hail. I could not help but overhear your conversation. Please allow me to lend some assistance.', 12, 0, 100, 0, 0, 0, 31380, 0, 'Rhydian - Quest 13081'); + +-- Tirion: On quest 13081 accepted, SetData on Rhydian +DELETE FROM `smart_scripts` WHERE `entryorguid` = 31044 AND `source_type` = 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 +(31044, 0, 0, 0, 19, 0, 100, 0, 13081, 0, 0, 0, 0, 0, 223, 1, 0, 0, 0, 0, 0, 19, 30656, 50, 0, 0, 0, 0, 0, 0, 'Highlord Tirion Fordring - On Quest 13081 Accepted - Do Action 1 on Rhydian'); + +-- Rhydian: On DataSet, run first action list (walk to point) +-- On MovementInform PointID 1, run second action list (say line, cast portal, walk back) +DELETE FROM `smart_scripts` WHERE `entryorguid` = 30656 AND `source_type` = 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 +(30656, 0, 0, 0, 72, 0, 100, 0, 1, 0, 0, 0, 0, 0, 80, 3065600, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Rhydian - On Do Action 1 - Run Timed Action List'), +(30656, 0, 1, 0, 34, 0, 100, 0, 8, 1, 0, 0, 0, 0, 80, 3065601, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Rhydian - On MovementInform Point 1 - Run Timed Action List'); + +-- Rhydian action list 1: set walk and move to point +DELETE FROM `smart_scripts` WHERE `entryorguid` IN (3065600, 3065601) AND `source_type` = 9; +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 +(3065600, 9, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 59, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Rhydian - Timed - Set Walk'), +(3065600, 9, 1, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 69, 1, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 6417.89, 431.21, 511.33, 0, 'Rhydian - Timed - Move To Point'), +-- Rhydian action list 2: say line, cast portal, walk back +(3065601, 9, 0, 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, 'Rhydian - Timed - Say Text'), +(3065601, 9, 1, 0, 0, 0, 100, 0, 5000, 5000, 0, 0, 0, 0, 11, 57676, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Rhydian - Timed - Cast Portal to Shattrath'), +(3065601, 9, 2, 0, 0, 0, 100, 0, 18000, 18000, 0, 0, 0, 0, 69, 2, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 6409.12, 422.382, 511.348, 0.628319, 'Rhydian - Timed - Walk Back To Home'), +(3065601, 9, 3, 0, 0, 0, 100, 0, 5000, 5000, 0, 0, 0, 0, 59, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Rhydian - Timed - Set Run'); From ddff3ba3fac4c911f2c0de0e3316d96822c6c07a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 16 Jun 2026 09:45:37 +0000 Subject: [PATCH 39/80] chore(DB): import pending files Referenced commit(s): 6966986c0475e91fb4155864c3b87fb51f9ef953 --- .../rev_1764229187065117576.sql => db_world/2026_06_16_02.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1764229187065117576.sql => db_world/2026_06_16_02.sql} (98%) diff --git a/data/sql/updates/pending_db_world/rev_1764229187065117576.sql b/data/sql/updates/db_world/2026_06_16_02.sql similarity index 98% rename from data/sql/updates/pending_db_world/rev_1764229187065117576.sql rename to data/sql/updates/db_world/2026_06_16_02.sql index 6a5cdc04a..20a722c55 100644 --- a/data/sql/updates/pending_db_world/rev_1764229187065117576.sql +++ b/data/sql/updates/db_world/2026_06_16_02.sql @@ -1,3 +1,4 @@ +-- DB update 2026_06_16_01 -> 2026_06_16_02 -- Fix for issue #23784: Rhydian should spawn portal to Shattrath when quest 13081 "The Will of the Naaru" is accepted -- Enable SmartAI for Rhydian and Tirion From 94525ccc708d60bad02c9198286104007ce37349 Mon Sep 17 00:00:00 2001 From: sudlud Date: Tue, 16 Jun 2026 12:15:09 +0200 Subject: [PATCH 40/80] feat(coderabbit-config): add CodeRabbit review config mirroring CLAUDE.md (#26231) Co-authored-by: Claude Opus 4.8 (1M context) Co-authored-by: Francesco Borzi' --- .coderabbit.yml | 80 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 .coderabbit.yml diff --git a/.coderabbit.yml b/.coderabbit.yml new file mode 100644 index 000000000..562b3562d --- /dev/null +++ b/.coderabbit.yml @@ -0,0 +1,80 @@ +# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json +# CodeRabbit review config. Mirrors the hard rules in CLAUDE.md so the AI +# reviewer enforces project conventions on every PR. +language: en-US + +reviews: + profile: chill + request_changes_workflow: false + high_level_summary: true + poem: false + review_status: true + auto_review: + enabled: true + auto_incremental_review: true + drafts: false + + # Skip generated, vendored, and immutable content so reviews stay focused. + path_filters: + - "!deps/**" + - "!data/sql/updates/db_*/**" + + path_instructions: + - path: "**/*.{cpp,h,hpp}" + instructions: | + AzerothCore C++ conventions (CI enforces these with -Werror; flag violations): + - C++20. 4-space indent, tabs forbidden. UTF-8, LF, max 80 columns, trailing newline. + - Allman braces. No braces around single-line statements. `if (x)`, never `if(x)` or `if ( x )`. + - `auto const&` (not `const auto&`); `Type const*` (not `const Type*`). + - Use fmt-style `{}` format specifiers, never printf-style `%u`/`%s`. + - Logging: `LOG_INFO("category.sub", "msg {}", arg)` (also LOG_WARN/ERROR/DEBUG/TRACE). + No printf, no `sLog->`, no `TC_LOG_*`. + - Random: use Random.h helpers (urand, irand, frand, rand32, rand_chance, + roll_chance_f/i). Never `std::rand` or `` directly. + - Strings: `Acore::StringFormat(fmt, args...)`. + - Config: `sConfigMgr->GetOption("Name", default)`. + - Namespace is `Acore::` — flag any leftover `Trinity::` from upstream ports. + - Use typed helpers instead of raw flag access: IsPlayer()/IsCreature()/IsItem(); + GetNpcFlags()/HasNpcFlag()/SetNpcFlag()/RemoveNpcFlag()/ReplaceAllNpcFlags(); + IsRefundable()/IsBOPTradable()/IsWrapped(); HasFlag(ItemFlag)/HasFlag2()/HasFlagCu(); + ObjectGuid::ToString().c_str() instead of GetCounter(). + - Never store a raw Player*/Creature*/Unit* past the current call/tick — store the + ObjectGuid and resolve at use time (ObjectAccessor::FindPlayer, + ObjectAccessor::GetCreature(*from, guid), Map::GetCreature, …). + - DB access: use PreparedStatement, not raw query strings. Non-blocking reads go through + the async path (_queryProcessor.AddCallback(db.AsyncQuery(...))). Multi-statement + writes wrap in SQLTransaction. + - Timed AI actions: use EventMap or TaskScheduler, not hand-rolled tick counters. + - Prefer SmartAI (DB) for new creature behaviour; reach for CreatureScript only when the + SmartAI vocabulary isn't enough. New creature AI prefers RegisterCreatureAI(ClassName). + - Script registration: spell/aura scripts use RegisterSpellScript(ClassName) or + RegisterSpellAndAuraScriptPair(...) inside AddSC_(); creature AI uses + RegisterCreatureAI(ClassName) (preferred) or new ClassName() (legacy). Declare and + call AddSC_() from the regional loader (e.g. Spells/spells_script_loader.cpp, + EasternKingdoms/eastern_kingdoms_script_loader.cpp). Module hooks inherit from + PlayerScript/WorldScript/etc. and register with new MyClass() in AddSC_(). + - path: "data/sql/updates/pending_db_*/**/*.sql" + instructions: | + AzerothCore SQL update conventions (enforced by apps/codestyle/codestyle-sql.py): + - Every INSERT must be preceded by a matching DELETE for idempotency, and that DELETE must include a WHERE clause scoped precisely to the intended rows. A predicate that is too broad will remove unrelated data, so confirm it matches exactly what the INSERT will re-add. + - 4-space indent (no tabs), trailing newline, no double semicolons, no multiple blank lines. + - Tables must use the InnoDB engine. + - path: "data/sql/base/**" + instructions: | + This SQL directory is immutable. Changes here should not happen in a normal PR — + flag any modification. New SQL belongs in data/sql/updates/pending_db_*/. + - path: "data/sql/archive/**" + instructions: | + This SQL directory is immutable. Changes here should not happen in a normal PR — + flag any modification. New SQL belongs in data/sql/updates/pending_db_*/. + +knowledge_base: + code_guidelines: + enabled: true + # CLAUDE.md is in CodeRabbit's defaults, but list it explicitly so the project's + # full guideline doc is always pulled into review context. + filePatterns: + - "CLAUDE.md" + +chat: + auto_reply: true From 6b428cd92b5cb4226d460dad300a819cb49653af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francesco=20Borz=C3=AC?= Date: Tue, 16 Jun 2026 13:53:29 +0200 Subject: [PATCH 41/80] chore(editorconfig): relax max_line_length (#26233) --- .editorconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.editorconfig b/.editorconfig index b9d8a411b..860e63626 100644 --- a/.editorconfig +++ b/.editorconfig @@ -5,7 +5,7 @@ indent_size = 4 tab_width = 4 insert_final_newline = true trim_trailing_whitespace = true -max_line_length = 80 +max_line_length = 120 [*.{json,ts,js,yml,sh}] charset = utf-8 @@ -14,4 +14,4 @@ indent_size = 2 tab_width = 2 insert_final_newline = true trim_trailing_whitespace = true -max_line_length = 80 +max_line_length = 120 From 72385804faa0dccba8fff684d6adcb289e769802 Mon Sep 17 00:00:00 2001 From: sudlud Date: Tue, 16 Jun 2026 14:07:17 +0200 Subject: [PATCH 42/80] fix(Core/DB): clean up creature_multispawn on spawn delete and fix .npc info DB output (#26230) Co-authored-by: Claude Opus 4.8 (1M context) --- data/sql/updates/pending_db_world/rev_1781589522985731400.sql | 2 ++ src/server/database/Database/Implementation/WorldDatabase.cpp | 1 + src/server/database/Database/Implementation/WorldDatabase.h | 1 + src/server/game/Entities/Creature/Creature.cpp | 4 ++++ src/server/scripts/Commands/cs_npc.cpp | 2 +- 5 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 data/sql/updates/pending_db_world/rev_1781589522985731400.sql diff --git a/data/sql/updates/pending_db_world/rev_1781589522985731400.sql b/data/sql/updates/pending_db_world/rev_1781589522985731400.sql new file mode 100644 index 000000000..a6d814d14 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1781589522985731400.sql @@ -0,0 +1,2 @@ +-- Remove creature_multispawn rows orphaned by deleted creature spawns +DELETE FROM `creature_multispawn` WHERE `spawnId` NOT IN (SELECT `guid` FROM `creature`); diff --git a/src/server/database/Database/Implementation/WorldDatabase.cpp b/src/server/database/Database/Implementation/WorldDatabase.cpp index 77ca6da16..dfe7b047d 100644 --- a/src/server/database/Database/Implementation/WorldDatabase.cpp +++ b/src/server/database/Database/Implementation/WorldDatabase.cpp @@ -76,6 +76,7 @@ void WorldDatabaseConnection::DoPrepareStatements() PrepareStatement(WORLD_UPD_WAYPOINT_SCRIPT_O, "UPDATE waypoint_scripts SET o = ? WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(WORLD_SEL_WAYPOINT_SCRIPT_ID_BY_GUID, "SELECT id FROM waypoint_scripts WHERE guid = ?", CONNECTION_SYNCH); PrepareStatement(WORLD_DEL_CREATURE, "DELETE FROM creature WHERE guid = ?", CONNECTION_ASYNC); + PrepareStatement(WORLD_DEL_CREATURE_MULTISPAWN, "DELETE FROM creature_multispawn WHERE spawnId = ?", CONNECTION_ASYNC); PrepareStatement(WORLD_SEL_COMMANDS, "SELECT name, security, help FROM command", CONNECTION_SYNCH); PrepareStatement(WORLD_SEL_CREATURE_TEMPLATE, "SELECT entry, difficulty_entry_1, difficulty_entry_2, difficulty_entry_3, KillCredit1, KillCredit2, name, subname, IconName, gossip_menu_id, minlevel, maxlevel, exp, faction, npcflag, speed_walk, speed_run, speed_swim, speed_flight, detection_range, `rank`, dmgschool, DamageModifier, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, unit_class, unit_flags, unit_flags2, dynamicflags, family, type, type_flags, lootid, pickpocketloot, skinloot, PetSpellDataId, VehicleId, mingold, maxgold, AIName, MovementType, ctm.Ground, ctm.Swim, ctm.Flight, ctm.Rooted, ctm.Chase, ctm.Random, ctm.InteractionPauseTimer, HoverHeight, HealthModifier, ManaModifier, ArmorModifier, ExperienceModifier, RacialLeader, movementId, RegenHealth, CreatureImmunitiesId, flags_extra, ScriptName FROM creature_template ct LEFT JOIN creature_template_movement ctm ON ct.entry = ctm.CreatureId WHERE entry = ?", CONNECTION_SYNCH); PrepareStatement(WORLD_SEL_WAYPOINT_SCRIPT_BY_ID, "SELECT guid, delay, command, datalong, datalong2, dataint, x, y, z, o FROM waypoint_scripts WHERE id = ?", CONNECTION_SYNCH); diff --git a/src/server/database/Database/Implementation/WorldDatabase.h b/src/server/database/Database/Implementation/WorldDatabase.h index 3c0ad9c15..6874a81d4 100644 --- a/src/server/database/Database/Implementation/WorldDatabase.h +++ b/src/server/database/Database/Implementation/WorldDatabase.h @@ -81,6 +81,7 @@ enum WorldDatabaseStatements : uint32 WORLD_UPD_WAYPOINT_SCRIPT_O, WORLD_SEL_WAYPOINT_SCRIPT_ID_BY_GUID, WORLD_DEL_CREATURE, + WORLD_DEL_CREATURE_MULTISPAWN, WORLD_SEL_COMMANDS, WORLD_SEL_CREATURE_TEMPLATE, WORLD_SEL_WAYPOINT_SCRIPT_BY_ID, diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index daa849503..dac755c74 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -1850,6 +1850,10 @@ void Creature::DeleteFromDB() stmt->SetData(0, m_spawnId); trans->Append(stmt); + stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_CREATURE_MULTISPAWN); + stmt->SetData(0, m_spawnId); + trans->Append(stmt); + stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_GAME_EVENT_CREATURE); stmt->SetData(0, m_spawnId); trans->Append(stmt); diff --git a/src/server/scripts/Commands/cs_npc.cpp b/src/server/scripts/Commands/cs_npc.cpp index 19421af28..0616f12b7 100644 --- a/src/server/scripts/Commands/cs_npc.cpp +++ b/src/server/scripts/Commands/cs_npc.cpp @@ -731,7 +731,7 @@ public: handler->PSendSysMessage("(Not in world - showing DB data)"); uint32 scriptId = cData->ScriptId ? cData->ScriptId : cInfo->ScriptID; handler->PSendSysMessage(LANG_NPCINFO_CHAR, lowGuid, ObjectGuid::Create(cData->id, lowGuid).ToString(), cData->id, - cData->id2, cData->id3, cData->displayid, cData->displayid, cInfo->faction, + cData->id, cData->id2, cData->id3, cData->displayid, cData->displayid, cInfo->faction, cData->npcflag); handler->PSendSysMessage(LANG_NPCINFO_PHASEMASK, cData->phaseMask); handler->PSendSysMessage(LANG_NPCINFO_POSITION, cData->posX, cData->posY, cData->posZ); From 1237b4f55d7f0c71fdf8bc350509e82270b7f08b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 16 Jun 2026 12:08:34 +0000 Subject: [PATCH 43/80] chore(DB): import pending files Referenced commit(s): 72385804faa0dccba8fff684d6adcb289e769802 --- .../rev_1781589522985731400.sql => db_world/2026_06_16_03.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1781589522985731400.sql => db_world/2026_06_16_03.sql} (78%) diff --git a/data/sql/updates/pending_db_world/rev_1781589522985731400.sql b/data/sql/updates/db_world/2026_06_16_03.sql similarity index 78% rename from data/sql/updates/pending_db_world/rev_1781589522985731400.sql rename to data/sql/updates/db_world/2026_06_16_03.sql index a6d814d14..2678af40f 100644 --- a/data/sql/updates/pending_db_world/rev_1781589522985731400.sql +++ b/data/sql/updates/db_world/2026_06_16_03.sql @@ -1,2 +1,3 @@ +-- DB update 2026_06_16_02 -> 2026_06_16_03 -- Remove creature_multispawn rows orphaned by deleted creature spawns DELETE FROM `creature_multispawn` WHERE `spawnId` NOT IN (SELECT `guid` FROM `creature`); From 5ca52a6f48cf01a31ab36f3bf7e77f04354bd2ca Mon Sep 17 00:00:00 2001 From: Uros Spasojevic Date: Tue, 16 Jun 2026 14:17:00 +0200 Subject: [PATCH 44/80] fix(DB/Quest) Fix Soothing Turtle Bisque turn-in POI for horde (#24255) Co-authored-by: Ludwig --- data/sql/updates/pending_db_world/rev_1767038609699595470.sql | 1 + 1 file changed, 1 insertion(+) create mode 100644 data/sql/updates/pending_db_world/rev_1767038609699595470.sql diff --git a/data/sql/updates/pending_db_world/rev_1767038609699595470.sql b/data/sql/updates/pending_db_world/rev_1767038609699595470.sql new file mode 100644 index 000000000..363b77f18 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1767038609699595470.sql @@ -0,0 +1 @@ +UPDATE `quest_poi_points` SET `X` = -6, `Y` = -927 WHERE `QuestID` = 7321 AND `Idx1` = 4 AND `Idx2` = 0; From 1f6bdc4f34ea89f50dd1b824e89a17f88d1903cc Mon Sep 17 00:00:00 2001 From: olive-spore-734 <139710466+olive-spore-734@users.noreply.github.com> Date: Tue, 16 Jun 2026 14:18:13 +0200 Subject: [PATCH 45/80] refactor: Update worldserver.conf.dist - whitespaces and alignments (#26191) Co-authored-by: Ludwig Co-authored-by: Claude Opus 4.8 (1M context) --- .../apps/worldserver/worldserver.conf.dist | 906 ++++++++---------- 1 file changed, 417 insertions(+), 489 deletions(-) diff --git a/src/server/apps/worldserver/worldserver.conf.dist b/src/server/apps/worldserver/worldserver.conf.dist index 103450202..8fc7fd0de 100644 --- a/src/server/apps/worldserver/worldserver.conf.dist +++ b/src/server/apps/worldserver/worldserver.conf.dist @@ -109,11 +109,10 @@ BindIP = "0.0.0.0" # CharacterDatabaseInfo # Description: Database connection settings for the world server. # Example: "hostname;port;username;password;database" -# ".;somenumber;username;password;database" - (Use named pipes on Windows -# "enable-named-pipe" to [mysqld] -# section my.ini) -# ".;/path/to/unix_socket;username;password;database" - (use Unix sockets on -# Unix/Linux) +# ".;somenumber;username;password;database" +# (Use named pipes on Windows "enable-named-pipe" to [mysqld] section my.ini) +# ".;/path/to/unix_socket;username;password;database" +# (Use Unix sockets on Unix/Linux) # Default: "127.0.0.1;3306;acore;acore;acore_auth" - (LoginDatabaseInfo) # "127.0.0.1;3306;acore;acore;acore_world" - (WorldDatabaseInfo) # "127.0.0.1;3306;acore;acore;acore_characters" - (CharacterDatabaseInfo) @@ -160,7 +159,6 @@ MaxPingTime = 30 # # Database.Reconnect.Seconds # Database.Reconnect.Attempts -# # Description: How many seconds between every reconnection attempt # and how many attempts will be performed in total # Default: 20 attempts every 15 seconds @@ -287,15 +285,13 @@ FlashAtStart = 1 # # Updates.EnableDatabases # Description: A mask that describes which databases should be updated. -# -# Following flags are available -# DATABASE_LOGIN = 1, // Auth database -# DATABASE_CHARACTER = 2, // Character database -# DATABASE_WORLD = 4, // World database -# -# Default: 7 - (All enabled) -# 4 - (Enable world only) -# 0 - (All disabled) +# Note: Following flags are available +# DATABASE_LOGIN = 1, Auth database +# DATABASE_CHARACTER = 2, Character database +# DATABASE_WORLD = 4, World database +# Default: 7 - (All enabled) +# 4 - (Enable world only) +# 0 - (All disabled) Updates.EnableDatabases = 7 @@ -405,9 +401,7 @@ Network.EnableProxyProtocol = 0 # When enabled and the process is started by systemd socket activation, # the server will use the socket passed by systemd instead of # creating and binding its own listening socket. Disabled by default. -# # When enabled the realm is not automatically set as offline on shutdown. -# # Example: 1 - (Enabled) # Default: 0 - (Disabled) @@ -479,10 +473,8 @@ SOAP.Port = 7878 # Description: The key used by authserver to decrypt TOTP secrets from database storage. # You only need to set this here if you plan to use the in-game 2FA # management commands (.account 2fa), otherwise this can be left blank. -# # The server will auto-detect if this does not match your authserver setting, # in which case any commands reliant on the secret will be disabled. -# # Default: # @@ -497,11 +489,11 @@ TOTPMasterSecret = # ThreadPool # Description: Number of threads to be used for the global thread pool # The thread pool is currently used for: -# - Signal handling -# - Remote access -# - Database keep-alive ping -# - Core freeze check -# - World socket networking +# - Signal handling +# - Remote access +# - Database keep-alive ping +# - Core freeze check +# - World socket networking # Default: 2 ThreadPool = 2 @@ -509,11 +501,11 @@ ThreadPool = 2 # # UseProcessors # Description: Processors mask for Windows and Linux based multi-processor systems. -# Example: For a computer with 3 CPUs: -# 1 - 1st CPU only -# 2 - 2nd CPU only -# 4 - 3rd CPU only -# 6 - 2nd + 3rd CPUs, because "2 | 4" -> 6 +# Example: For a computer with 3 CPUs: +# 1 - 1st CPU only +# 2 - 2nd CPU only +# 4 - 3rd CPU only +# 6 - 2nd + 3rd CPUs, because "2 | 4" -> 6 # Default: 0 - (Selected by OS) # 1+ - (Bit mask value of selected processors) @@ -531,8 +523,8 @@ ProcessPriority = 1 # Compression # Description: Compression level for client update packages # Range: 1-9 -# Default: 1 - (Speed) -# 9 - (Best compression) +# Default: 1 - (Speed) +# 9 - (Best compression) Compression = 1 @@ -568,7 +560,7 @@ LogDB.Opt.ClearInterval = 10 LogDB.Opt.ClearTime = 1209600 # -# RecordUpdateTimeDiffInterval +# RecordUpdateTimeDiffInterval # Description: Time (in milliseconds) update time diff is written to the log file. # Update diff can be used as a performance indicator. Diff < 300: good # performance. Diff > 600 bad performance, may be caused by high CPU usage. @@ -589,8 +581,7 @@ MinRecordUpdateTimeDiff = 100 # Description: The path to your IP2Location database CSV file. # Example: "C:/acore/IP2LOCATION-LITE-DB1.CSV" # "/home/acore/IP2LOCATION-LITE-DB1.CSV" -# Default: "" - (Disabled) -# +# Default: "" - (Disabled) IPLocationFile = "" @@ -599,7 +590,6 @@ IPLocationFile = "" # Description: Specifies if IP addresses can be logged to the database # Default: 1 - (Enabled) # 0 - (Disabled) -# AllowLoggingIPAddressesInDatabase = 1 @@ -608,7 +598,6 @@ AllowLoggingIPAddressesInDatabase = 1 # Description: Logs actions, e.g. account login and logout to name a few, based on IP of current session. # Default: 0 - (Disabled) # 1 - (Enabled) -# Allow.IP.Based.Action.Logging = 0 @@ -617,7 +606,6 @@ Allow.IP.Based.Action.Logging = 0 # Description: Allow logging spam reports from players in the chat, mail or calendar into the database. # Default: 1 - (Enabled) # 0 - (Disabled) -# LogSpamReports = 1 @@ -626,74 +614,62 @@ LogSpamReports = 1 # Description: Enable or disable chat logging (chat_log.cpp). # Default: 0 - (Disabled) # 1 - (Enabled) -# ChatLog.Enable = 0 # -# Appender config values: Given an appender "name" +# Appender config values: Given an appender "name" # Appender.name # Description: Defines 'where to log'. # Format: Type,LogLevel,Flags,optional1,optional2,optional3 -# -# Type -# 0 - (None) -# 1 - (Console) -# 2 - (File) -# 3 - (DB) -# -# LogLevel -# 0 - (Disabled) -# 1 - (Fatal) -# 2 - (Error) -# 3 - (Warning) -# 4 - (Info) -# 5 - (Debug) -# 6 - (Trace) -# -# Flags: -# 0 - None -# 1 - Prefix Timestamp to the text -# 2 - Prefix Log Level to the text -# 4 - Prefix Log Filter type to the text -# 8 - Append timestamp to the log file name. Format: YYYY-MM-DD_HH-MM-SS -# (Only used with Type = 2) -# 16 - Make a backup of existing file before overwrite -# (Only used with Mode = w) -# -# Colors (read as optional1 if Type = Console) -# Format: "fatal error warn info debug trace" -# 0 - BLACK -# 1 - RED -# 2 - GREEN -# 3 - BROWN -# 4 - BLUE -# 5 - MAGENTA -# 6 - CYAN -# 7 - GREY -# 8 - YELLOW -# 9 - LRED -# 10 - LGREEN -# 11 - LBLUE -# 12 - LMAGENTA -# 13 - LCYAN -# 14 - WHITE -# Example: "1 9 3 6 5 8" -# -# File: Name of the file (read as optional1 if Type = File) -# Allows to use one "%s" to create dynamic files -# -# Mode: Mode to open the file (read as optional2 if Type = File) -# a - (Append) -# w - (Overwrite) -# -# MaxFileSize: Maximum file size of the log file before creating a new log file +# Type: 0 - (None) +# 1 - (Console) +# 2 - (File) +# 3 - (DB) +# LogLevel: 0 - (Disabled) +# 1 - (Fatal) +# 2 - (Error) +# 3 - (Warning) +# 4 - (Info) +# 5 - (Debug) +# 6 - (Trace) +# Flags: 0 - None +# 1 - Prefix Timestamp to the text +# 2 - Prefix Log Level to the text +# 4 - Prefix Log Filter type to the text +# 8 - Append timestamp to the log file name. Format: YYYY-MM-DD_HH-MM-SS +# (Only used with Type = 2) +# 16 - Make a backup of existing file before overwrite +# (Only used with Mode = w) +# Colors: (read as optional1 if Type = Console) +# Format: "fatal error warn info debug trace" +# 0 - BLACK +# 1 - RED +# 2 - GREEN +# 3 - BROWN +# 4 - BLUE +# 5 - MAGENTA +# 6 - CYAN +# 7 - GREY +# 8 - YELLOW +# 9 - LRED +# 10 - LGREEN +# 11 - LBLUE +# 12 - LMAGENTA +# 13 - LCYAN +# 14 - WHITE +# Example: "1 9 3 6 5 8" +# File: Name of the file (read as optional1 if Type = File) +# Allows to use one "%s" to create dynamic files +# Mode: Mode to open the file (read as optional2 if Type = File) +# a - (Append) +# w - (Overwrite) +# MaxFileSize: Maximum file size of the log file before creating a new log file # (read as optional3 if Type = File) -# Size is measured in bytes expressed in a 64-bit unsigned integer. -# Maximum value is 4294967295 (4 GB). Leave blank for no limit. -# NOTE: Does not work with dynamic filenames. -# Example: 536870912 (512 MB) -# +# Size is measured in bytes expressed in a 64-bit unsigned integer. +# Maximum value is 4294967295 (4 GB). Leave blank for no limit. +# NOTE: Does not work with dynamic filenames. +# Example: 536870912 (512 MB) Appender.Console=1,4,0,"1 9 3 6 5 8" Appender.Server=2,5,0,Server.log,w @@ -702,27 +678,24 @@ Appender.Errors=2,2,0,Errors.log,w # Appender.DB=3,5,0 # Appender.Dev=2,5,0,Dev.log,a -# Logger config values: Given a logger "name" +# Logger config values: Given a logger "name" # Logger.name -# Description: Defines 'What to log' -# Format: LogLevel,AppenderList -# -# LogLevel -# 0 - (Disabled) -# 1 - (Fatal) -# 2 - (Error) -# 3 - (Warning) -# 4 - (Info) -# 5 - (Debug) -# 6 - (Trace) -# -# AppenderList: List of appenders linked to logger -# (Using spaces as separator). +# Description: Defines 'What to log' +# Format: LogLevel,AppenderList +# LogLevel: 0 - (Disabled) +# 1 - (Fatal) +# 2 - (Error) +# 3 - (Warning) +# 4 - (Info) +# 5 - (Debug) +# 6 - (Trace) +# AppenderList: List of appenders linked to logger +# (Using spaces as separator). # Logger.root=2,Console Server -#Logger.metric=2,Console Server -#Logger.commands.gm=4,Console GM +# Logger.metric=2,Console Server +# Logger.commands.gm=4,Console GM Logger.diff=3,Console Server Logger.mmaps=4,Server Logger.scripts.hotswap=4,Console Server @@ -733,105 +706,105 @@ Logger.sql=4,Console Server Logger.time.update=4,Console Server Logger.module=4,Console Server Logger.spells.scripts=2,Console Errors -#Logger.achievement=4,Console Server -#Logger.addon=4,Console Server -#Logger.auctionHouse=4,Console Server -#Logger.autobroadcast=4, Console Server -#Logger.bg.arena=4,Console Server -#Logger.bg.battlefield=4,Console Server -#Logger.bg.battleground=4,Console Server -#Logger.bg.reportpvpafk=4,Console Server -#Logger.calendar=4,Console Server -#Logger.chat.say=4,Console Chat -#Logger.chat.emote=4,Console Chat -#Logger.chat.yell=4,Console Chat -#Logger.chat.whisper=4,Console Chat -#Logger.chat.party=4,Console Chat -#Logger.chat.raid=4,Console Chat -#Logger.chat.bg=4,Console Chat -#Logger.chat.guild=4,Console Chat -#Logger.chat.guild.officer=4,Console Chat -#Logger.chat.channel=4,Console Chat -#Logger.chat.addon.msg=4,Console Chat -#Logger.chat.addon.emote=4,Console Chat -#Logger.chat.addon.yell=4,Console Chat -#Logger.chat.addon.whisper=4,Console Chat -#Logger.chat.addon.party=4,Console Chat -#Logger.chat.addon.raid=4,Console Chat -#Logger.chat.addon.bg=4,Console Chat -#Logger.chat.addon.guild=4,Console Chat -#Logger.chat.addon.guild.officer=4,Console Chat -#Logger.chat.addon.channel=4,Console Chat -#Logger.chat.log=4,Console Server -#Logger.chat.log.addon=4,Console Server -#Logger.chat.system=4,Console Server -#Logger.cheat=4,Console Server -#Logger.commands.ra=4,Console Server -#Logger.condition=4,Console Server -#Logger.dbc=4,Console Server -#Logger.disable=4,Console Server -#Logger.entities.dyobject=4,Console Server -#Logger.entities.faction=4,Console Server -#Logger.entities.gameobject=4,Console Server -#Logger.entities.object=4,Console Server -#Logger.entities.pet=4,Console Server -#Logger.entities.player.auctionhouse=4,Console Server -#Logger.entities.player.character=4,Console Server -#Logger.entities.player.dump=4,Console Server -#Logger.entities.player.items=4,Console Server -#Logger.entities.player.loading=4,Console Server -#Logger.entities.player.mail=4,Console Server -#Logger.entities.player.skills=4,Console Server -#Logger.entities.player.trade=4,Console Server -#Logger.entities.player=4,Console Server -#Logger.entities.transport=4,Console Server -#Logger.entities.unit.ai=4,Console Server -#Logger.entities.unit=4,Console Server -#Logger.entities.vehicle=4,Console Server -#Logger.gameevent=4,Console Server -#Logger.group=4,Console Server -#Logger.guild=4,Console Server -#Logger.instance.save=4,Console Server -#Logger.instance.script=4,Console Server -#Logger.lfg=4,Console Server -#Logger.loot=4,Console Server -#Logger.mail=4,Console Server -#Logger.maps.script=4,Console Server -#Logger.maps=4,Console Server -#Logger.misc=4,Console Server -#Logger.mmaps.tiles=4,Console Server -#Logger.movement.flightpath=4,Console Server -#Logger.movement.motionmaster=4,Console Server -#Logger.movement.splinechain=4,Console Server -#Logger.movement=4,Console Server -#Logger.network.kick=4,Console Server -#Logger.network.opcode=4,Console Server -#Logger.network.soap=4,Console Server -#Logger.network=4,Console Server -#Logger.outdoorpvp=4,Console Server -#Logger.pool=4,Console Server -#Logger.rbac=4,Console Server -#Logger.reputation=4,Console Server -#Logger.scripts.ai.escortai=4,Console Server -#Logger.scripts.ai.followerai=4,Console Server -#Logger.scripts.ai.petai=4,Console Server -#Logger.scripts.ai.sai=4,Console Server -#Logger.scripts.ai=4,Console Server -#Logger.scripts.cos=4,Console Server -#Logger.scripts.midsummer=4,Console Server -#Logger.scripts=4,Console Server -#Logger.server.authserver=4,Console Server -#Logger.spells.aura.effect.nospell=4,Console Server -#Logger.spells.aura.effect=4,Console Server -#Logger.spells.effect.nospell=4,Console Server -#Logger.spells.effect=4,Console Server -#Logger.spells.scripts=4,Console Server -#Logger.spells=4,Console Server -#Logger.sql.dev=4,Console Server Dev -#Logger.sql.driver=4,Console Server -#Logger.vehicles=4,Console Server -#Logger.warden=4,Console Server -#Logger.weather=4,Console Server +# Logger.achievement=4,Console Server +# Logger.addon=4,Console Server +# Logger.auctionHouse=4,Console Server +# Logger.autobroadcast=4, Console Server +# Logger.bg.arena=4,Console Server +# Logger.bg.battlefield=4,Console Server +# Logger.bg.battleground=4,Console Server +# Logger.bg.reportpvpafk=4,Console Server +# Logger.calendar=4,Console Server +# Logger.chat.say=4,Console Chat +# Logger.chat.emote=4,Console Chat +# Logger.chat.yell=4,Console Chat +# Logger.chat.whisper=4,Console Chat +# Logger.chat.party=4,Console Chat +# Logger.chat.raid=4,Console Chat +# Logger.chat.bg=4,Console Chat +# Logger.chat.guild=4,Console Chat +# Logger.chat.guild.officer=4,Console Chat +# Logger.chat.channel=4,Console Chat +# Logger.chat.addon.msg=4,Console Chat +# Logger.chat.addon.emote=4,Console Chat +# Logger.chat.addon.yell=4,Console Chat +# Logger.chat.addon.whisper=4,Console Chat +# Logger.chat.addon.party=4,Console Chat +# Logger.chat.addon.raid=4,Console Chat +# Logger.chat.addon.bg=4,Console Chat +# Logger.chat.addon.guild=4,Console Chat +# Logger.chat.addon.guild.officer=4,Console Chat +# Logger.chat.addon.channel=4,Console Chat +# Logger.chat.log=4,Console Server +# Logger.chat.log.addon=4,Console Server +# Logger.chat.system=4,Console Server +# Logger.cheat=4,Console Server +# Logger.commands.ra=4,Console Server +# Logger.condition=4,Console Server +# Logger.dbc=4,Console Server +# Logger.disable=4,Console Server +# Logger.entities.dyobject=4,Console Server +# Logger.entities.faction=4,Console Server +# Logger.entities.gameobject=4,Console Server +# Logger.entities.object=4,Console Server +# Logger.entities.pet=4,Console Server +# Logger.entities.player.auctionhouse=4,Console Server +# Logger.entities.player.character=4,Console Server +# Logger.entities.player.dump=4,Console Server +# Logger.entities.player.items=4,Console Server +# Logger.entities.player.loading=4,Console Server +# Logger.entities.player.mail=4,Console Server +# Logger.entities.player.skills=4,Console Server +# Logger.entities.player.trade=4,Console Server +# Logger.entities.player=4,Console Server +# Logger.entities.transport=4,Console Server +# Logger.entities.unit.ai=4,Console Server +# Logger.entities.unit=4,Console Server +# Logger.entities.vehicle=4,Console Server +# Logger.gameevent=4,Console Server +# Logger.group=4,Console Server +# Logger.guild=4,Console Server +# Logger.instance.save=4,Console Server +# Logger.instance.script=4,Console Server +# Logger.lfg=4,Console Server +# Logger.loot=4,Console Server +# Logger.mail=4,Console Server +# Logger.maps.script=4,Console Server +# Logger.maps=4,Console Server +# Logger.misc=4,Console Server +# Logger.mmaps.tiles=4,Console Server +# Logger.movement.flightpath=4,Console Server +# Logger.movement.motionmaster=4,Console Server +# Logger.movement.splinechain=4,Console Server +# Logger.movement=4,Console Server +# Logger.network.kick=4,Console Server +# Logger.network.opcode=4,Console Server +# Logger.network.soap=4,Console Server +# Logger.network=4,Console Server +# Logger.outdoorpvp=4,Console Server +# Logger.pool=4,Console Server +# Logger.rbac=4,Console Server +# Logger.reputation=4,Console Server +# Logger.scripts.ai.escortai=4,Console Server +# Logger.scripts.ai.followerai=4,Console Server +# Logger.scripts.ai.petai=4,Console Server +# Logger.scripts.ai.sai=4,Console Server +# Logger.scripts.ai=4,Console Server +# Logger.scripts.cos=4,Console Server +# Logger.scripts.midsummer=4,Console Server +# Logger.scripts=4,Console Server +# Logger.server.authserver=4,Console Server +# Logger.spells.aura.effect.nospell=4,Console Server +# Logger.spells.aura.effect=4,Console Server +# Logger.spells.effect.nospell=4,Console Server +# Logger.spells.effect=4,Console Server +# Logger.spells.scripts=4,Console Server +# Logger.spells=4,Console Server +# Logger.sql.dev=4,Console Server Dev +# Logger.sql.driver=4,Console Server +# Logger.vehicles=4,Console Server +# Logger.warden=4,Console Server +# Logger.weather=4,Console Server # # Log.Async.Enable @@ -859,27 +832,18 @@ Metric.Enable = 0 # # Metric.InfluxDB -# Description: Connection settings for InfluxDB. -# -# For InfluxDB v1: -# Only fill in Metric.InfluxDB.Connection. -# -# Example: -# Metric.InfluxDB.Connection = "hostname;port;database" -# -# For InfluxDB v2: -# Fill in every field. -# -# NOTE: Currently, Grafana will not work with the provided json files to visualize -# data from InfluxDB v2. -# -# Example: -# Metric.InfluxDB.Connection = "hostname;port" -# Metric.InfluxDB.v2 = 0 - (Disabled) -# 1 - (Enabled) -# Metric.InfluxDB.Org = "my-org" -# Metric.InfluxDB.Bucket = "my-bucket" -# Metric.InfluxDB.Token = "my-token" +# Description: Connection settings for InfluxDB. +# For InfluxDB v1: Only fill in Metric.InfluxDB.Connection. +# Example: Metric.InfluxDB.Connection = "hostname;port;database" +# For InfluxDB v2: Fill in every field. +# NOTE: Currently, Grafana will not work with the provided json files to visualize +# data from InfluxDB v2. +# Example: Metric.InfluxDB.Connection = "hostname;port" +# Metric.InfluxDB.v2 = 0 - (Disabled) +# 1 - (Enabled) +# Metric.InfluxDB.Org = "my-org" +# Metric.InfluxDB.Bucket = "my-bucket" +# Metric.InfluxDB.Token = "my-token" # Metric.InfluxDB.Connection = "127.0.0.1;8086;worldserver" @@ -894,7 +858,6 @@ Metric.InfluxDB.Token = "" # Longer interval means larger batch of data. If the batch # is too big, it might get rejected. # Default: 1 second -# Metric.Interval = 1 @@ -902,12 +865,11 @@ Metric.Interval = 1 # Metric.OverallStatusInterval # Description: Interval between every gathering of overall worldserver status data in seconds # Default: 1 second -# Metric.OverallStatusInterval = 1 # -# Metric threshold values: Given a metric "name" +# Metric threshold values: Given a metric "name" # Metric.Threshold.name # Description: Skips sending statistics with a value lower than the config value. # If the threshold is commented out, the metric will be ignored. @@ -926,7 +888,7 @@ Metric.OverallStatusInterval = 1 ################################################################################################### # SERVER # -# BirthdayTime +# BirthdayTime # Description: Set to date of project's birth in UNIX time. By default Thu Oct 2, 2008 # Default: 1222964635 @@ -935,9 +897,9 @@ BirthdayTime = 1222964635 # # PlayerLimit # Description: Maximum number of players in the world. Excluding Mods, GMs and Admins. -# Important: If you want to block players and only allow Mods, GMs or Admins to join the +# Important: If you want to block players and only allow Mods, GMs or Admins to join the # server, use the DB field "auth.realmlist.allowedSecurityLevel". -# Default: 1000 - (Enabled) +# Default: 1000 - (Enabled) # 1+ - (Enabled) # 0 - (Disabled, No limit) @@ -968,7 +930,7 @@ GameType = 0 # # RealmZone # Description: Server realm zone. Set allowed alphabet in character, etc. names. -# Default 1 - (Development - any language) +# Default: 1 - (Development - any language) # 2 - (United States - extended-Latin) # 3 - (Oceanic - extended-Latin) # 4 - (Latin America - extended-Latin) @@ -1078,14 +1040,14 @@ SocketTimeOutTimeActive = 60000 MaxOverspeedPings = 2 # -# DisconnectToleranceInterval +# DisconnectToleranceInterval # Description: Allows to skip queue after being disconnected for a given number of seconds. # Default: 0 DisconnectToleranceInterval = 0 # -# EnableLoginAfterDC +# EnableLoginAfterDC # Description: After not logging out properly (clicking Logout and waiting 20 seconds), # characters stay in game world for a full minute, even if the client connection was closed. # Such behaviour prevents for example exploiting boss encounters by alt+f4 @@ -1093,7 +1055,7 @@ DisconnectToleranceInterval = 0 # This setting is used to allow/disallow players to log back into characters that are left in world. # Default: 1 - (by clicking "Enter World" player will log back into a character that is already in world) # 0 - (by clicking "Enter World" player will get an error message when trying to log into a character -# that is left in world, and has to wait a minute for the character to be removed from world) +# that is left in world, and has to wait a minute for the character to be removed from world) EnableLoginAfterDC = 1 @@ -1118,7 +1080,7 @@ UpdateUptimeInterval = 10 # FreezeDetector # Default: 0 - (Disabled) # 10+ - (Enabled, Recommended 30+) -# Note: If enabled and the setting is too low, it can cause unexpected crash. +# Note: If enabled and the setting is too low, it can cause unexpected crash. MaxCoreStuckTime = 0 @@ -1139,7 +1101,7 @@ SaveRespawnTimeImmediately = 1 Server.LoginInfo = 0 # -# ShowKickInWorld +# ShowKickInWorld # Description: Determines whether a message is broadcast to the entire server when a # player gets kicked # Default: 0 - (Disabled) @@ -1148,7 +1110,7 @@ Server.LoginInfo = 0 ShowKickInWorld = 0 # -# ShowMuteInWorld +# ShowMuteInWorld # Description: Determines whether a message is broadcast to the entire server when a # player gets muted. # Default: 0 - (Disabled) @@ -1157,7 +1119,7 @@ ShowKickInWorld = 0 ShowMuteInWorld = 0 # -# ShowBanInWorld +# ShowBanInWorld # Description: Determines whether a message is broadcast to the entire server when a # player gets banned. # Default: 0 - (Disabled) @@ -1175,9 +1137,9 @@ MaxWhoListReturns = 49 # # PreventAFKLogout # Description: Prevent players AFK from being logged out -# Default: 0 - (Disabled) -# 1 - (Enabled, prevent players AFK from being logged out in Sanctuary zones) -# 2 - (Enabled, prevent players AFK from being logged out in all zones) +# Default: 0 - (Disabled) +# 1 - (Enabled, prevent players AFK from being logged out in Sanctuary zones) +# 2 - (Enabled, prevent players AFK from being logged out in all zones) PreventAFKLogout = 0 @@ -1189,9 +1151,9 @@ PreventAFKLogout = 0 # # PacketSpoof.BanMode # Description: If PacketSpoof.Policy equals 2, this will determine the ban mode. -# Values: 0 - Ban Account -# 1 - Ban IP -# Note: Banning by character not supported for logical reasons. +# Values: 0 - Ban Account +# 1 - Ban IP +# Note: Banning by character not supported for logical reasons. # PacketSpoof.BanMode = 0 @@ -1340,7 +1302,7 @@ Visibility.GroupMode = 1 # For instances default ~170. # Max: 250 # Min limit is max aggro radius (45) * Rate.Creature.Aggro -# Default: 100 - (Visibility.Distance.Continents) +# Default: 100 - (Visibility.Distance.Continents) # 170 - (Visibility.Distance.Instances) # 250 - (Visibility.Distance.BGArenas) @@ -1420,9 +1382,8 @@ vmap.BlizzlikePvPLOS = 1 # # vmap.BlizzlikeLOSInOpenWorld # Description: Check line of sight to see game objects in the open world. -# Default: 1 (Enabled, Players will be able to cast spells through tree stumps and other objects in the open world). -# 0 (Disabled, Players will not be able to cast spells through tree stumps and other objects in the open world). -# +# Default: 1 (Enabled, Players will be able to cast spells through tree stumps and other objects in the open world). +# 0 (Disabled, Players will not be able to cast spells through tree stumps and other objects in the open world). vmap.BlizzlikeLOSInOpenWorld = 1 @@ -1672,11 +1633,11 @@ GM.TicketSystem.ChanceOfGMSurvey = 50 # # DisableWaterBreath # Description: Required security level for water breathing. -# Default: 4 - (Disabled) -# 0 - (Enabled, Everyone) -# 1 - (Enabled, Mods/GMs/Admins) -# 2 - (Enabled, GMs/Admins) -# 3 - (Enabled, Admins) +# Default: 4 - (Disabled) +# 0 - (Enabled, Everyone) +# 1 - (Enabled, Mods/GMs/Admins) +# 2 - (Enabled, GMs/Admins) +# 3 - (Enabled, Admins) DisableWaterBreath = 4 @@ -1800,12 +1761,10 @@ CleanCharacterDB = 0 # CLEANING_FLAG_SPELLS = 0x4 # CLEANING_FLAG_TALENTS = 0x8 # CLEANING_FLAG_QUESTSTATUS = 0x10 -# -# Before use this feature, make a backup of your database. -# +# Note: Before use this feature, make a backup of your database. # Example: 14 - (CLEANING_FLAG_SKILLS + CLEANING_FLAG_SPELLS + CLEANING_FLAG_TALENTS -# 2+4+8 => 14. This will clean up skills, talents and spells will -# remain enabled after the next cleanup) +# 2+4+8 => 14. This will clean up skills, talents and spells will +# remain enabled after the next cleanup) # Default: 0 - (All cleanup methods will be disabled after the next cleanup) PersistentCharacterCleanFlags = 0 @@ -1815,8 +1774,8 @@ PersistentCharacterCleanFlags = 0 # Description: If enabled, players will lose spells that are invalid for their race/class. # Default: 1 - (Enabled, enforce valid spells) # 0 - (Disabled, allow invalid spells) -# Disabling this and then having your character learn spells which require DBC edits can result in the character not being saved in the database -# Disable AT YOUR OWN RISK +# Note: Disabling this and then having your character learn spells which require DBC edits can result in the character not being saved in the database +# Disable AT YOUR OWN RISK ValidateSkillLearnedBySpells = 1 @@ -1880,20 +1839,20 @@ MinPetName = 2 DeclinedNames = 0 # -# StrictNames.Reserved -# Description: Use the Reserved Filter from DBC. -# Prevents Player, Pet & Charter names from containing reserved names. -# Default: 1 - Enabled -# 0 - Disabled +# StrictNames.Reserved +# Description: Use the Reserved Filter from DBC. +# Prevents Player, Pet & Charter names from containing reserved names. +# Default: 1 - Enabled +# 0 - Disabled StrictNames.Reserved = 1 # -# StrictNames.Profanity -# Description: Use the Profanity Filter from DBC. -# Prevents Player, Pet & Charter names from containing profanity. -# Default: 1 - Enabled -# 0 - Disabled +# StrictNames.Profanity +# Description: Use the Profanity Filter from DBC. +# Prevents Player, Pet & Charter names from containing profanity. +# Default: 1 - Enabled +# 0 - Disabled StrictNames.Profanity = 1 @@ -1904,9 +1863,9 @@ StrictNames.Profanity = 1 # Default: 0 - (Disable, Limited server timezone dependent client check) # 1 - (Enabled, Strictly basic Latin characters) # 2 - (Enabled, Strictly realm zone specific, See RealmZone setting, -# Note: Client needs to have the appropriate fonts installed which support -# the charset. For non-official localization, custom fonts need to be -# placed in clientdir/Fonts.) +# Note: Client needs to have the appropriate fonts installed which support +# the charset. For non-official localization, custom fonts need to be +# placed in clientdir/Fonts.) # 3 - (Enabled, Basic Latin characters + server timezone specific) StrictPlayerNames = 0 @@ -1918,9 +1877,9 @@ StrictPlayerNames = 0 # Default: 0 - (Disable, Limited server timezone dependent client check) # 1 - (Enabled, Strictly basic Latin characters) # 2 - (Enabled, Strictly realm zone specific, See RealmZone setting, -# Note: Client needs to have the appropriate fonts installed which support -# the charset. For non-official localization, custom fonts need to be -# placed in clientdir/Fonts.) +# Note: Client needs to have the appropriate fonts installed which support +# the charset. For non-official localization, custom fonts need to be +# placed in clientdir/Fonts.) # 3 - (Enabled, Basic Latin characters + server timezone specific) StrictPetNames = 0 @@ -2048,9 +2007,9 @@ StartPlayerMoney = 0 StartHeroicPlayerMoney = 2000 # -# PlayerStart.String +# PlayerStart.String # Description: String to be displayed at first login of newly created characters. -# Default: "" - (Disabled) +# Default: "" - (Disabled) PlayerStart.String = "" @@ -2060,7 +2019,7 @@ PlayerStart.String = "" ################################################################################################### # CHARACTER # -# EnablePlayerSettings +# EnablePlayerSettings # Description: Enables the usage of character specific settings. # Default: 0 - Disabled # 1 - Enabled @@ -2171,9 +2130,9 @@ Rate.Loyalty = 1 # Rate.Rest.Offline.InWilderness # Rate.Rest.MaxBonus # Description: Resting points grow rates. -# Default: 1 - (Rate.Rest.InGame) -# 1 - (Rate.Rest.Offline.InTavernOrCity) -# 1 - (Rate.Rest.Offline.InWilderness) +# Default: 1 - (Rate.Rest.InGame) +# 1 - (Rate.Rest.Offline.InTavernOrCity) +# 1 - (Rate.Rest.Offline.InWilderness) # 1.5 - (Rate.Rest.MaxBonus) Rate.Rest.InGame = 1 @@ -2185,29 +2144,24 @@ Rate.Rest.MaxBonus = 1.5 # Rate.MissChanceMultiplier.Creature # Rate.MissChanceMultiplier.Player # Rate.MissChanceMultiplier.OnlyAffectsPlayer -# # Description: When the target is 3 or more level higher than the player, # the chance to hit is determined by the formula: 94 - (levelDiff - 2) * Rate.MissChanceMultiplier # The higher the Rate.MissChanceMultiplier constant, the higher is the chance to miss. -# -# Note: this does not affect when the player is less than 3 levels different than the target, +# Note: this does not affect when the player is less than 3 levels different than the target, # where this (linear) formula is used instead to calculate the hit chance: 96 - levelDiff. # You can set Rate.MissChanceMultiplier.OnlyAffectsPlayer to 1 if you only want to affect the MissChance # for player casters only. This way you won't be affecting creature missing chance. -# -# Example: if you want the chance to keep growing linearly, use 1. -# +# Example: if you want the chance to keep growing linearly, use 1. # Default: Rate.MissChanceMultiplier.TargetCreature = 11 # Rate.MissChanceMultiplier.TargetPlayer = 7 # Rate.MissChanceMultiplier.OnlyAffectsPlayer = 0 -# Rate.MissChanceMultiplier.TargetCreature = 11 Rate.MissChanceMultiplier.TargetPlayer = 7 Rate.MissChanceMultiplier.OnlyAffectsPlayer = 0 # -# LevelReq.Trade +# LevelReq.Trade # Description: Level requirement for characters to be able to initiate a trade. # Default: 1 @@ -2478,9 +2432,8 @@ Rate.XP.BattlegroundBonus = 1 # # Rate.Pet.LevelXP # Description: Modifies the amount of experience required to level up a pet. -# The lower the rate the less experience is required. +# The lower the rate the less experience is required. # Default: 0.05 -# Rate.Pet.LevelXP = 0.05 @@ -2500,7 +2453,7 @@ MaxHonorPoints = 75000 # MaxHonorPointsMoneyPerPoint # Description: Convert excess honor points into money if players got more points than allowed after changing the honor cap. # Honor points will be converted into copper according to the value set in this config. -# Default: 0 - Disabled +# Default: 0 - Disabled MaxHonorPointsMoneyPerPoint = 0 @@ -2672,9 +2625,9 @@ DurabilityLossChance.Block = 0.05 # Death.SicknessLevel # Description: Starting level for resurrection sickness. # Example: 11 - (Level 1-10 characters will not be affected, -# Level 11-19 characters will be affected for 1 minute, -# Level 20-MaxPlayerLevel characters will be affected for 10 minutes) -# Default: 11 - (Enabled, See Example) +# Level 11-19 characters will be affected for 1 minute, +# Level 20-MaxPlayerLevel characters will be affected for 10 minutes) +# Default: 11 - (Enabled, see Example) # MaxPlayerLevel+1 - (Disabled) # -10 - (Enabled, Level 1+ characters have 10 minute duration) @@ -2733,26 +2686,22 @@ ItemDelete.Method = 0 # Description: Saving items into database when the player sells items to vendor # Default: 0 (disabled) # 1 (enabled) -# ItemDelete.Vendor = 0 # # ItemDelete.Quality # Description: Saving items into database that have quality greater or equal to ItemDelete.Quality -# -# ID | Color | Quality -# 0 | Grey | Poor -# 1 | White | Common -# 2 | Green | Uncommon -# 3 | Blue | Rare -# 4 | Purple| Epic -# 5 | Orange| Legendary -# 6 | Red | Artifact -# 7 | Gold | Bind to Account -# +# | ID | Color | Quality | +# | 0 | Grey | Poor | +# | 1 | White | Common | +# | 2 | Green | Uncommon | +# | 3 | Blue | Rare | +# | 4 | Purple | Epic | +# | 5 | Orange | Legendary | +# | 6 | Red | Artifact | +# | 7 | Gold | Bind to Account | # Default: 3 -# ItemDelete.Quality = 3 @@ -2760,7 +2709,6 @@ ItemDelete.Quality = 3 # ItemDelete.ItemLevel # Description: Saving items into database that are Item Levels greater or equal to ItemDelete.ItemLevel # Default: 80 -# ItemDelete.ItemLevel = 80 @@ -2778,10 +2726,10 @@ ItemDelete.KeepDays = 0 ################################################################################################### # ITEM # -# DBC.EnforceItemAttributes -# Disallow overriding item attributes stored in DBC files with values from the database -# Default: 0 - Off, Use DB values -# 1 - On, Enforce DBC Values (default) +# DBC.EnforceItemAttributes +# Description: Disallow overriding item attributes stored in DBC files with values from the database +# Default: 0 - Off, Use DB values +# 1 - On, Enforce DBC Values (default) DBC.EnforceItemAttributes = 1 @@ -2816,6 +2764,7 @@ Rate.Drop.Item.Artifact = 1 Rate.Drop.Item.Referenced = 1 Rate.Drop.Money = 1 +# # Rate.Drop.Item.ReferencedAmount # Description: Multiplier for referenced loot amount. Makes many raid bosses (and others) drop additional loot. # Default: 1 @@ -2830,18 +2779,18 @@ Rate.Drop.Item.ReferencedAmount = 1 Rate.Drop.Item.GroupAmount = 1 # -# LootNeedBeforeGreedILvlRestriction +# LootNeedBeforeGreedILvlRestriction # Description: Specify level restriction for items below player's subclass in Need Before Greed loot mode in DF groups # Default: 70 -# 0 - Disabled +# 0 - Disabled LootNeedBeforeGreedILvlRestriction = 70 # -# Item.SetItemTradeable +# Item.SetItemTradeable # Description: Enabled/Disabled trading BoP items among raid members. -# Default: 1 - (Set BoP items tradeable timer to 2 hours) -# 0 - (Disable trading BoP items among raid members) +# Default: 1 - (Set BoP items tradeable timer to 2 hours) +# 0 - (Disable trading BoP items among raid members) Item.SetItemTradeable = 1 @@ -2853,13 +2802,13 @@ Item.SetItemTradeable = 1 # # Quests.EnableQuestTracker # Description: Store data in the database about quest completion and abandonment to help finding bugged quests. -# Default: 0 - (Disabled) -# 1 - (Enabled) +# Default: 0 - (Disabled) +# 1 - (Enabled) Quests.EnableQuestTracker = 0 # -# QuestPOI.Enabled +# QuestPOI.Enabled # Description: Show points of interest on the map # Default: 1 - Enabled # 0 - Disabled @@ -2929,7 +2878,7 @@ Rate.RewardBonusMoney = 1 ################################################################################################### # CREATURE # -# MonsterSight +# MonsterSight # Description: The maximum distance in yards that a "monster" creature can see # regardless of level difference (through CreatureAI::IsVisible). # Increases CONFIG_SIGHT_MONSTER to 50 yards. Used to be 20 yards. @@ -3122,7 +3071,7 @@ Creature.RepositionAgainstNpcs = 1 # Creature.MovingStopTimeForPlayer # Description: Time (in milliseconds) during which creature will not move after # interaction with player. -# Default: 180000 +# Default: 180000 Creature.MovingStopTimeForPlayer = 180000 @@ -3241,7 +3190,7 @@ Rate.RepairCost = 1 ################################################################################################### # GROUP # -# LeaveGroupOnLogout.Enabled +# LeaveGroupOnLogout.Enabled # Description: Should the player leave their group when they log out? # (It does not affect raids or dungeon finder groups) # @@ -3252,9 +3201,9 @@ LeaveGroupOnLogout.Enabled = 0 # # Group.Raid.LevelRestriction # -# The Group members need to the same, or higher level than the specified value. -# Minimum level is 10. -# Default: 10 +# Description: The Group members need to the same, or higher level than the specified value. +# Minimum level is 10. +# Default: 10 # Group.Raid.LevelRestriction = 10 @@ -3262,9 +3211,9 @@ Group.Raid.LevelRestriction = 10 # # Group.RandomRollMaximum # -# The maximum value for use with the client '/roll' command. -# Blizzlike and maximum value is 1000000. (Based on Classic and 3.3.5a client testing respectively) -# Default: 1000000 +# Description: The maximum value for use with the client '/roll' command. +# Blizzlike and maximum value is 1000000. (Based on Classic and 3.3.5a client testing respectively) +# Default: 1000000 # Group.RandomRollMaximum = 1000000 @@ -3307,12 +3256,12 @@ Instance.IgnoreRaid = 0 Instance.ResetTimeHour = 4 # -# Instance.ResetTimeRelativeTimestamp +# Instance.ResetTimeRelativeTimestamp # Description: Needed for displaying valid instance reset times in ingame calendar. # This timestamp should be set to a date in the past (midnight) on which # both 3-day and 7-day raids were reset. # Default: 1135814400 - (Thu, 29 Dec 2005 00:00:00 GMT - meaning that 7-day raid reset falls on Thursdays, -# while 3-day reset falls on "Thu 29 Dec 2005", "Sun 01 Jan 2006", "Wed 04 Jan 2006", and so on) +# while 3-day reset falls on "Thu 29 Dec 2005", "Sun 01 Jan 2006", "Wed 04 Jan 2006", and so on) Instance.ResetTimeRelativeTimestamp = 1135814400 @@ -3344,11 +3293,10 @@ Instance.UnloadDelay = 1800000 AccountInstancesPerHour = 5 # -# Instance.SharedNormalHeroicId -# Description: Forces ICC and RS Normal and Heroic to share lockouts. ToC is uneffected and Normal and Heroic will be separate. -# Default: 1 - Enable -# 0 - Disable -# +# Instance.SharedNormalHeroicId +# Description: Forces ICC and RS Normal and Heroic to share lockouts. ToC is uneffected and Normal and Heroic will be separate. +# Default: 1 - Enable +# 0 - Disable Instance.SharedNormalHeroicId = 1 @@ -3359,7 +3307,6 @@ Instance.SharedNormalHeroicId = 1 # Default: 1 - (Display only one requirement at a time (BlizzLike, like in the LFG interface)) # 0 - (Display no extra information, only "Requirements not met") # 2 - (Display detailed requirements, all at once, with clickable links) -# DungeonAccessRequirements.PrintMode = 1 @@ -3377,7 +3324,7 @@ DungeonAccessRequirements.PortalAvgIlevelCheck = 0 # # Description: Display an extra message from acore_strings in the chat after printing the dungeon access requirements. # To enable it set the ID of your desired string from the table acore_strings -# Default: 0 - (Disabled) +# Default: 0 - (Disabled) # 1+ - (Enabled) DungeonAccessRequirements.OptionalStringID = 0 @@ -3388,7 +3335,7 @@ DungeonAccessRequirements.OptionalStringID = 0 ################################################################################################### # DUNGEON AND BATTLEGROUND FINDER # -# JoinBGAndLFG.Enable +# JoinBGAndLFG.Enable # Description: Allow queueing for BG and LFG at the same time. # Default: 0 - Disabled # 1 - Enabled @@ -3396,7 +3343,7 @@ DungeonAccessRequirements.OptionalStringID = 0 JoinBGAndLFG.Enable = 0 # -# LFG.MailItemOnFullInventory +# LFG.MailItemOnFullInventory # Description: When a player wins a group roll but their inventory is full, mail # the item to them instead of leaving it on the corpse. # Default: 0 - Disabled @@ -3406,12 +3353,12 @@ JoinBGAndLFG.Enable = 0 LFG.MailItemOnFullInventory = 0 # -# DungeonFinder.OptionsMask +# DungeonFinder.OptionsMask # Description: Dungeon and raid finder system. -# Value is a bitmask consisting of: -# LFG_OPTION_ENABLE_DUNGEON_FINDER = 1, Enable the dungeon finder browser -# LFG_OPTION_ENABLE_RAID_BROWSER = 2, Enable the raid browser -# LFG_OPTION_ENABLE_SEASONAL_BOSSES = 4, Enable seasonal bosses +# Value is a bitmask consisting of: +# LFG_OPTION_ENABLE_DUNGEON_FINDER = 1, Enable the dungeon finder browser +# LFG_OPTION_ENABLE_RAID_BROWSER = 2, Enable the raid browser +# LFG_OPTION_ENABLE_SEASONAL_BOSSES = 4, Enable seasonal bosses # Default: 5 DungeonFinder.OptionsMask = 5 @@ -3419,15 +3366,14 @@ DungeonFinder.OptionsMask = 5 # # LFG.Location.All # -# Includes satellite to search for work elsewhere LFG -# Default: 0 - Disable -# 1 - Enable -# +# Description: Includes satellite to search for work elsewhere LFG +# Default: 0 - Disable +# 1 - Enable LFG.Location.All = 0 # -# LFG.MaxKickCount +# LFG.MaxKickCount # Description: Specify the maximum number of kicks allowed in LFG groups (max 3 kicks) # Default: 2 # 0 - Disabled (kicks are never allowed) @@ -3435,7 +3381,7 @@ LFG.Location.All = 0 LFG.MaxKickCount = 2 # -# LFG.KickPreventionTimer +# LFG.KickPreventionTimer # Description: Specify for how long players are prevented from being kicked after just joining LFG groups # Default: 900 secs (15 minutes) # 0 - Disabled @@ -3499,9 +3445,9 @@ MinCharterName = 2 # Default: 0 - (Disable, Limited server timezone dependent client check) # 1 - (Enabled, Strictly basic Latin characters) # 2 - (Enabled, Strictly realm zone specific, See RealmZone setting, -# Note: Client needs to have the appropriate fonts installed which support -# the charset. For non-official localization, custom fonts need to be -# placed in clientdir/Fonts. +# Note: Client needs to have the appropriate fonts installed which support +# the charset. For non-official localization, custom fonts need to be +# placed in clientdir/Fonts. # 3 - (Enabled, Basic Latin characters + server timezone specific) StrictCharterNames = 0 @@ -3554,7 +3500,7 @@ MinPetitionSigns = 9 Guild.CharterCost = 1000 # -# Guild.AllowMultipleGuildMaster +# Guild.AllowMultipleGuildMaster # Description: Allow more than one guild master. Additional Guild Masters must be set using # the ".guild rank" command. # Default: 0 - (Disabled) @@ -3563,7 +3509,7 @@ Guild.CharterCost = 1000 Guild.AllowMultipleGuildMaster = 0 # -# Guild.BankInitialTabs +# Guild.BankInitialTabs # Description: Changes the amounts of available tabs of the guild bank on guild creation # Default: 0 (no tabs given for free) # 1-6 (amount of tabs of the guild bank at guild creation) @@ -3571,7 +3517,7 @@ Guild.AllowMultipleGuildMaster = 0 Guild.BankInitialTabs = 0 # -# Guild.BankTabCost0-5 +# Guild.BankTabCost0-5 # Description: Changes the price of the guild tabs. Note that the client will still show the default values. # Default: 1000000 - (100 Gold) # 2500000 - (250 Gold) @@ -3588,7 +3534,7 @@ Guild.BankTabCost4 = 25000000 Guild.BankTabCost5 = 50000000 # -# Guild.MemberLimit +# Guild.MemberLimit # Description: Do not allow inviting new players to the guild if the member limit is met or exceeded. # Default: 0 - Disabled @@ -3600,7 +3546,7 @@ Guild.MemberLimit = 0 ################################################################################################### # FFAPVP # -# FFAPvPTimer +# FFAPvPTimer # Description: Specify time offset when player unset FFAPvP flag when leaving FFAPvP area. (e.g. Gurubashi Arena) # Default: 30 sec @@ -3612,7 +3558,7 @@ FFAPvPTimer = 30 ################################################################################################### # OUTDOORPVP # -# OutdoorPvPCaptureRate +# OutdoorPvPCaptureRate # Description: Specify rate multiplier for outdoor PvP capture points. (e.g. Eastern Plaguelands, Hellfire Peninsula) # Default: 1.0 @@ -3624,54 +3570,54 @@ OutdoorPvPCaptureRate = 1.0 ################################################################################################### # WINTERGRASP # -# Wintergrasp.Enable -# Description: Enable the Wintergrasp battlefield. -# Default: 1 - (Enabled, Experimental as of still being in development) -# 0 - (Battleground disabled, Wintergrasp world processing still occurs) -# 2 - (Disable all Wintergrasp processing) +# Wintergrasp.Enable +# Description: Enable the Wintergrasp battlefield. +# Default: 1 - (Enabled, Experimental as of still being in development) +# 0 - (Battleground disabled, Wintergrasp world processing still occurs) +# 2 - (Disable all Wintergrasp processing) Wintergrasp.Enable = 1 # -# Wintergrasp.PlayerMax -# Description: Maximum number of players allowed in Wintergrasp per team. -# Default: 120 +# Wintergrasp.PlayerMax +# Description: Maximum number of players allowed in Wintergrasp per team. +# Default: 120 Wintergrasp.PlayerMax = 120 # -# Wintergrasp.PlayerMin -# Description: Minimum number of players required for Wintergrasp per team. -# Default: 0 +# Wintergrasp.PlayerMin +# Description: Minimum number of players required for Wintergrasp per team. +# Default: 0 Wintergrasp.PlayerMin = 0 # -# Wintergrasp.PlayerMinLvl -# Description: Required character level for the Wintergrasp battle. -# Default: 75 +# Wintergrasp.PlayerMinLvl +# Description: Required character level for the Wintergrasp battle. +# Default: 75 Wintergrasp.PlayerMinLvl = 75 # -# Wintergrasp.BattleTimer -# Description: Time (in minutes) for the Wintergrasp battle to last. -# Default: 30 +# Wintergrasp.BattleTimer +# Description: Time (in minutes) for the Wintergrasp battle to last. +# Default: 30 Wintergrasp.BattleTimer = 30 # -# Wintergrasp.NoBattleTimer -# Description: Time (in minutes) between Wintergrasp battles. -# Default: 150 +# Wintergrasp.NoBattleTimer +# Description: Time (in minutes) between Wintergrasp battles. +# Default: 150 Wintergrasp.NoBattleTimer = 150 # -# Wintergrasp.CrashRestartTimer -# Description: Time (in minutes) to delay the restart of Wintergrasp if the world server -# crashed during a running battle. -# Default: 10 +# Wintergrasp.CrashRestartTimer +# Description: Time (in minutes) to delay the restart of Wintergrasp if the world server +# crashed during a running battle. +# Default: 10 Wintergrasp.CrashRestartTimer = 10 @@ -3691,9 +3637,9 @@ Wintergrasp.SkipBattleSessionCount = 3500 # block new entries during wartime / the 10-minute warmup. # Requires a server restart to take effect (not reloadable). # Default: 1 - (Enabled, players are warned then teleported to their -# homebind before the battle starts) +# homebind before the battle starts) # 0 - (Disabled, players stay inside and bosses behave -# normally during Wintergrasp) +# normally during Wintergrasp) Wintergrasp.KickVoAPlayers = 1 @@ -3705,7 +3651,7 @@ Wintergrasp.KickVoAPlayers = 1 # who receives the buff once the battle is over. # Default: 0 - (Disabled, only the defending faction gets the buff) # 1 - (Enabled, attackers and defenders both get the buff, -# so both factions can access Vault of Archavon) +# so both factions can access Vault of Archavon) Wintergrasp.EssenceBothFactions = 0 @@ -3717,7 +3663,7 @@ Wintergrasp.EssenceBothFactions = 0 # # Battleground.PrepTime # Description: Time (in seconds) for battleground preparation phase. Strand of the Ancients will be -# the exception and will always use the default 120 seconds timer, due to its boat timing mechanic. +# the exception and will always use the default 120 seconds timer, due to its boat timing mechanic. # Default: 120 Battleground.PrepTime = 120 @@ -3743,7 +3689,7 @@ Battleground.QueueAnnouncer.Enable = 0 # Description: Limit the QueueAnnouncer starting from a certain level. # When limited, it announces only if there are at least MinPlayers queued (see below) # At 80 it only limits RBG, at lower level only limits Warsong Gulch. -# Default: 0 - (Disabled, no limits) +# Default: 0 - (Disabled, no limits) # 10 - (Enabled for all, because BGs start at 10) # 20 - (Enabled for 20 and higher) # 80 - (Enabled only for 80) @@ -3762,7 +3708,6 @@ Battleground.QueueAnnouncer.Limit.MinPlayers = 3 # Battleground.QueueAnnouncer.SpamProtection.Delay # Description: Show announce if player rejoined in queue after sec # Default: 30 -# Battleground.QueueAnnouncer.SpamProtection.Delay = 30 @@ -3779,7 +3724,6 @@ Battleground.QueueAnnouncer.PlayerOnly = 0 # Description: Enabled battleground queue announcements based on timer # Default: 0 - (Disabled) # 1 - (Enabled - Set Arena.QueueAnnouncer.Timer) -# Battleground.QueueAnnouncer.Timed = 0 @@ -3787,7 +3731,6 @@ Battleground.QueueAnnouncer.Timed = 0 # Battleground.QueueAnnouncer.Timer # Description: Set timer for queue announcements # Default: 30000 (30 sec) -# Battleground.QueueAnnouncer.Timer = 30000 @@ -3843,17 +3786,17 @@ Battleground.TrackDeserters.Enable = 0 # Battleground.InvitationType # Description: Set Battleground invitation type. # Default: 0 - (Normal, Invite as much players to battlegrounds as queued, -# Don't bother with balance) +# Don't bother with balance) # 1 - (Experimental, Don't allow to invite much more players -# of one faction) +# of one faction) # 2 - (Experimental, Try to have even teams) Battleground.InvitationType = 0 # # Battleground.ReportAFK.Timer -# Description: After a few minutes that battle started you can report the player. -# Default: 4 +# Description: After a few minutes that battle started you can report the player. +# Default: 4 Battleground.ReportAFK.Timer = 4 @@ -3881,12 +3824,12 @@ Battleground.DisableQuestShareInBG = 0 Battleground.DisableReadyCheckInBG = 0 # -# Battleground.RewardWinnerHonorFirst -# Battleground.RewardWinnerArenaFirst -# Battleground.RewardWinnerHonorLast -# Battleground.RewardWinnerArenaLast -# Battleground.RewardLoserHonorFirst -# Battleground.RewardLoserHonorLast +# Battleground.RewardWinnerHonorFirst +# Battleground.RewardWinnerArenaFirst +# Battleground.RewardWinnerHonorLast +# Battleground.RewardWinnerArenaLast +# Battleground.RewardLoserHonorFirst +# Battleground.RewardLoserHonorLast # Description: Random Battlegrounds / call to the arms rewards # Default: 30 - Battleground.RewardWinnerHonorFirst # 25 - Battleground.RewardWinnerArenaFirst @@ -3894,7 +3837,6 @@ Battleground.DisableReadyCheckInBG = 0 # 0 - Battleground.RewardWinnerArenaLast # 5 - Battleground.RewardLoserHonorFirst # 5 - Battleground.RewardLoserHonorLast -# Battleground.RewardWinnerHonorFirst = 30 Battleground.RewardWinnerArenaFirst = 25 @@ -4055,7 +3997,6 @@ Arena.QueueAnnouncer.Enable = 0 # Description: Arena queue announcement type. # Default: 0 - (System message, Anyone can see it) # 1 - (Private, Only queued players can see it) -# Arena.QueueAnnouncer.PlayerOnly = 0 @@ -4066,7 +4007,6 @@ Arena.QueueAnnouncer.PlayerOnly = 0 # 2 - (Announce only the team's name) # 1 - (Announce only the team's rating) # 0 - (Do not announce any information about the teams) -# Arena.QueueAnnouncer.Detail = 3 @@ -4135,7 +4075,7 @@ Arena.ArenaMatchmakerRatingModifier = 24 # ArenaTeam.CharterCost.3v3 # ArenaTeam.CharterCost.5v5 # Description: Amount of money (in Copper) the petitions costs. -# Default: 800000 - (80 Gold) +# Default: 800000 - (80 Gold) # 1200000 - (120 Gold) # 2000000 - (200 Gold) @@ -4144,7 +4084,7 @@ ArenaTeam.CharterCost.3v3 = 1200000 ArenaTeam.CharterCost.5v5 = 2000000 # -# MaxAllowedMMRDrop +# MaxAllowedMMRDrop # Description: Some players continuously lose arena matches to lower their MMR and then fight with weaker opponents. # This setting prevents lowering MMR too much from max achieved MMR. # Eg. if max achieved MMR for a character was 2400, with default setting (MaxAllowedMMRDrop = 500) the character can't get below 1900 MMR no matter what. @@ -4177,23 +4117,19 @@ LevelReq.Mail = 1 ################################################################################################### # TRANSPORT # -# IsContinentTransport.Enabled +# IsContinentTransport.Enabled # Description: Controls the continent transport (ships, zeppelins etc..) # Default: 1 - (Enabled) -# -# IsContinentTransport.Enabled = 1 # -# IsPreloadedContinentTransport.Enabled +# IsPreloadedContinentTransport.Enabled # Description: Should we preload the transport? -# (Not recommended on low-end servers as it consumes 100% more ram) -# and it's not really necessary to be enabled. -# +# (Not recommended on low-end servers as it consumes 100% more ram) +# and it's not really necessary to be enabled. # Default: 0 - (Disabled) # -# IsPreloadedContinentTransport.Enabled = 0 @@ -4209,9 +4145,9 @@ IsPreloadedContinentTransport.Enabled = 0 # Default: 0 - (Disable, Limited server timezone dependent client check) # 1 - (Enabled, Strictly basic Latin characters) # 2 - (Enabled, Strictly realm zone specific, See RealmZone setting, -# Note: Client needs to have the appropriate fonts installed which support -# the charset. For non-official localization, custom fonts need to be -# placed in clientdir/Fonts. +# Note: Client needs to have the appropriate fonts installed which support +# the charset. For non-official localization, custom fonts need to be +# placed in clientdir/Fonts. # 3 - (Enabled, Basic Latin characters + server timezone specific) StrictChannelNames = 0 @@ -4233,7 +4169,6 @@ AddonChannel = 1 # "normal" chat messages for sending data to other clients. # Default: 1 - (Enabled, Blizzlike) # 0 - (Disabled) -# ChatFakeMessagePreventing = 1 @@ -4243,12 +4178,10 @@ ChatFakeMessagePreventing = 1 # -1 - (Only verify validity of link data, but permit use of custom colors) # Default: 0 - (Only verify that link data and color are valid without checking text) # 1 - (Additionally verifies that the link text matches the provided data) -# # Note: If this is set to '1', you must additionally provide .dbc files for all # client locales that are in use on your server. # If any files are missing, messages with links from clients using those # locales will likely be blocked by the server. -# ChatStrictLinkChecking.Severity = 0 @@ -4258,7 +4191,6 @@ ChatStrictLinkChecking.Severity = 0 # is received. # Default: 0 - (Silently ignore message) # 1 - (Ignore message and kick player) -# ChatStrictLinkChecking.Kick = 0 @@ -4281,7 +4213,7 @@ ChatFlood.MessageDelay = 1 # ChatFlood.AddonMessageCount # Description: Chat flood protection, number of addon messages before player gets muted. # Default: 100 - (Enabled) -# 0 - (Disabled) +# 0 - (Disabled) ChatFlood.AddonMessageCount = 100 @@ -4332,10 +4264,10 @@ Channel.RestrictedLfg = 1 Channel.SilentlyGMJoin = 0 # Channel.ModerationGMLevel -# Min GM account security level required for executing moderator in-game commands in the channels -# This also bypasses password prompts on joining channels which require password -# 0 (in-game channel moderator privileges only) -# Default: 1 (enabled for moderators and above) +# Description: Min GM account security level required for executing moderator in-game commands in the channels +# This also bypasses password prompts on joining channels which require password +# 0 (in-game channel moderator privileges only) +# Default: 1 (enabled for moderators and above) Channel.ModerationGMLevel = 1 @@ -4488,10 +4420,10 @@ AllowTwoSide.Interaction.Auction = 0 TalentsInspecting = 1 # -# ChangeFaction.MaxMoney +# ChangeFaction.MaxMoney # Description: Maximum amount of gold allowed on the character to perform a faction change. -# Default: 0 - Disabled -# > 0 - Enabled (money in copper) +# Default: 0 - Disabled +# >0 - Enabled (money in copper) # Example: If set to 10000, the maximum amount of money allowed on the character would be 1 gold. ChangeFaction.MaxMoney = 0 @@ -4583,14 +4515,14 @@ ScourgeInvasion.CounterThird = 150 ################################################################################################### # AUCTION HOUSE # -# AuctionHouse.WorkerThreads +# AuctionHouse.WorkerThreads # Description: Count of auctionhouse searcher worker threads to spawn # Default: 1 AuctionHouse.WorkerThreads = 1 # -# LevelReq.Auction +# LevelReq.Auction # Description: Level requirement for characters to be able to use the auction house. # Default: 1 @@ -4616,14 +4548,14 @@ Rate.Auction.Cut = 1 ################################################################################################### # PLAYER DUMP # -# PlayerDump.DisallowPaths +# PlayerDump.DisallowPaths # Description: Disallow using paths in PlayerDump output files # Default: 1 PlayerDump.DisallowPaths = 1 # -# PlayerDump.DisallowOverwrite +# PlayerDump.DisallowOverwrite # Description: Disallow overwriting existing files with PlayerDump # Default: 1 @@ -4635,28 +4567,28 @@ PlayerDump.DisallowOverwrite = 1 ################################################################################################### # CUSTOM # -# ICC Buff -# Description: Specify ICC buff -# (It is necessary to restart the server after changing the values!) -# Default: ICC.Buff.Horde = 73822 -# ICC.Buff.Alliance = 73828 -# -# Spell IDs for the auras: -# 73816 - 5% buff Horde -# 73818 - 10% buff Horde -# 73819 - 15% buff Horde -# 73820 - 20% buff Horde -# 73821 - 25% buff Horde -# 73822 - 30% buff Horde -# 73762 - 5% buff Alliance -# 73824 - 10% buff Alliance -# 73825 - 15% buff Alliance -# 73826 - 20% buff Alliance -# 73827 - 25% buff Alliance -# 73828 - 30% buff Alliance +# ICC.Buff.Alliance +# ICC.Buff.Horde +# Description: Specify ICC buff +# Note: It is necessary to restart the server after changing the values! +# Example: | Spell IDs | Amount | Faction | +# | 73762 | 5% | Alliance | +# | 73824 | 10% | Alliance | +# | 73825 | 15% | Alliance | +# | 73826 | 20% | Alliance | +# | 73827 | 25% | Alliance | +# | 73828 | 30% | Alliance | +# | 73816 | 5% | Horde | +# | 73818 | 10% | Horde | +# | 73819 | 15% | Horde | +# | 73820 | 20% | Horde | +# | 73821 | 25% | Horde | +# | 73822 | 30% | Horde | +# Default: ICC.Buff.Alliance = 73828 +# ICC.Buff.Horde = 73822 -ICC.Buff.Horde = 73822 ICC.Buff.Alliance = 73828 +ICC.Buff.Horde = 73822 # # WipeGunshipBlizzlike.Enable @@ -4666,55 +4598,53 @@ ICC.Buff.Alliance = 73828 WipeGunshipBlizzlike.Enable = 1 # -# Minigob.Manabonk.Enable +# Minigob.Manabonk.Enable # Description: Enable/ Disable Minigob Manabonk # Default: 1 Minigob.Manabonk.Enable = 1 # -# Calculate.Creature.Zone.Area.Data +# Calculate.Creature.Zone.Area.Data # Description: Calculate at loading creature zoneId / areaId and save in creature table # WARNING: SLOW WORLD SERVER STARTUP. Should only be used for debugging. -# Default: 0 - (Do not show) -# +# Default: 0 - (Do not show) Calculate.Creature.Zone.Area.Data = 0 # -# Calculate.Gameoject.Zone.Area.Data +# Calculate.Gameoject.Zone.Area.Data # Description: Calculate at loading gameobject zoneId / areaId and save in gameobject table # WARNING: SLOW WORLD SERVER STARTUP. Should only be used for debugging. -# Default: 0 - (Do not show) -# +# Default: 0 - (Do not show) Calculate.Gameoject.Zone.Area.Data = 0 # # DailyRBGArenaPoints.MinLevel # Description: Allows gaining arena points on the first RBG win at level 70. -# Default: 71 - (Blizzlike) +# Default: 71 - (Blizzlike) DailyRBGArenaPoints.MinLevel = 71 # # MunchingBlizzlike.Enabled # Description: Enable the Blizzlike implementation of munching with e.g. Warrior's Rend or Mage's Ignite -# Default: 1 - (Blizzlike) +# Default: 1 - (Blizzlike) MunchingBlizzlike.Enabled = 1 # # Daze.Enabled # Description: Enable or disable the chance for mob melee attacks to daze the victim. -# Default: 1 - (Blizzlike) +# Default: 1 - (Blizzlike) Daze.Enabled = 1 # # InfiniteAmmo.Enabled # Description: Enable or disable ammo consumption for ranged attacks and thrown weapons. -# Default: 0 - (Blizzlike) +# Default: 0 - (Blizzlike) InfiniteAmmo.Enabled = 0 @@ -4726,24 +4656,24 @@ InfiniteAmmo.Enabled = 0 # # Debug.Battleground # Description: Enable or disable Battleground 1v0 mode. (If enabled, the in-game command is disabled.) -# Default: 0 - (Disabled) -# 1 - (Enabled) +# Default: 0 - (Disabled) +# 1 - (Enabled) Debug.Battleground = 0 # # Debug.Arena # Description: Enable or disable Arena 1v1 mode. (If enabled, the in-game command is disabled.) -# Default: 0 - (Disabled) -# 1 - (Enabled) +# Default: 0 - (Disabled) +# 1 - (Enabled) Debug.Arena = 0 # # Debug.LFG # Description: Enable or disable LFG 1 player queue mode. (If enabled, the in-game command is disabled.) -# Default: 0 - (Disabled) -# 1 - (Enabled) +# Default: 0 - (Disabled) +# 1 - (Enabled) Debug.LFG = 0 @@ -4760,8 +4690,8 @@ Debug.LFG = 0 # As player count exceeds this value, respawn times decrease proportionally # (e.g., at double the player count, respawn times are halved; at triple the player count, respawns happen three times as fast). # Does not affect instanced creatures, bosses, or quest givers. -# Formula: adjustFactor = rate / playerCount -# RespawnTime = RespawnTime * adjustFactor +# Formula: adjustFactor = rate / playerCount +# RespawnTime = RespawnTime * adjustFactor # Default: 1 (Disabled) Respawn.DynamicRateCreature = 1 @@ -4780,8 +4710,8 @@ Respawn.DynamicMinimumCreature = 10 # As player count exceeds this value, respawn times decrease proportionally # (e.g., at double the player count, respawn times are halved; at triple the player count, respawns happen three times as fast). # Does not affect instanced objects or quest givers. -# Formula: adjustFactor = rate / playerCount -# RespawnTime = RespawnTime * adjustFactor +# Formula: adjustFactor = rate / playerCount +# RespawnTime = RespawnTime * adjustFactor # Default: 1 (Disabled) Respawn.DynamicRateGameObject = 1 @@ -4799,7 +4729,6 @@ Respawn.DynamicMinimumGameObject = 10 # When enabled, escort NPCs in spawn groups flagged as ESCORTQUESTNPC # will use special respawn handling. # Default: 0 - (Disabled) -# Respawn.DynamicEscortNPC = 0 @@ -4810,7 +4739,6 @@ Respawn.DynamicEscortNPC = 0 # gameobjects respawn in-place as they always have in AzerothCore. # Set to 1 to force legacy behavior for all spawns. # Default: 0 - (Disabled, spawn groups control respawn mode) -# Respawn.ForceCompatibilityMode = 0 From 0b359eab0f833c004fc7df16d4f27296f639f705 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 16 Jun 2026 12:19:25 +0000 Subject: [PATCH 46/80] chore(DB): import pending files Referenced commit(s): 5ca52a6f48cf01a31ab36f3bf7e77f04354bd2ca --- .../rev_1767038609699595470.sql => db_world/2026_06_16_04.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1767038609699595470.sql => db_world/2026_06_16_04.sql} (70%) diff --git a/data/sql/updates/pending_db_world/rev_1767038609699595470.sql b/data/sql/updates/db_world/2026_06_16_04.sql similarity index 70% rename from data/sql/updates/pending_db_world/rev_1767038609699595470.sql rename to data/sql/updates/db_world/2026_06_16_04.sql index 363b77f18..a218a2c25 100644 --- a/data/sql/updates/pending_db_world/rev_1767038609699595470.sql +++ b/data/sql/updates/db_world/2026_06_16_04.sql @@ -1 +1,2 @@ +-- DB update 2026_06_16_03 -> 2026_06_16_04 UPDATE `quest_poi_points` SET `X` = -6, `Y` = -927 WHERE `QuestID` = 7321 AND `Idx1` = 4 AND `Idx2` = 0; From 5dfe06614e0f2a4ac7960e059da3c70658ca4c5f Mon Sep 17 00:00:00 2001 From: NoxMax <50133316+NoxMax@users.noreply.github.com> Date: Tue, 16 Jun 2026 06:47:14 -0600 Subject: [PATCH 47/80] refactor(Core/Battlefield): Clean some dead code and follow convention in BattlefieldWG.h (#26224) --- .../game/Battlefield/Zones/BattlefieldWG.h | 63 +++++++++---------- 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/src/server/game/Battlefield/Zones/BattlefieldWG.h b/src/server/game/Battlefield/Zones/BattlefieldWG.h index b5bc7ecf2..6f3ccc6f7 100644 --- a/src/server/game/Battlefield/Zones/BattlefieldWG.h +++ b/src/server/game/Battlefield/Zones/BattlefieldWG.h @@ -36,7 +36,6 @@ using GameObjectSet = std::set; using GameObjectBuilding = std::set; using Workshop = std::set; using GroupSet = std::set; -//using CapturePointSet = std::set; unused ? uint32 const VehNumWorldState[2] = { WORLD_STATE_BATTLEFIELD_WG_VEHICLE_A, WORLD_STATE_BATTLEFIELD_WG_VEHICLE_H }; uint32 const MaxVehNumWorldState[2] = { WORLD_STATE_BATTLEFIELD_WG_MAX_VEHICLE_A, WORLD_STATE_BATTLEFIELD_WG_MAX_VEHICLE_H }; @@ -495,8 +494,8 @@ enum WintergraspWorkshopIds BATTLEFIELD_WG_WORKSHOP_NW, BATTLEFIELD_WG_WORKSHOP_SE, BATTLEFIELD_WG_WORKSHOP_SW, - BATTLEFIELD_WG_WORKSHOP_KEEP_WEST, BATTLEFIELD_WG_WORKSHOP_KEEP_EAST, + BATTLEFIELD_WG_WORKSHOP_KEEP_WEST, }; /// @todo: Handle this with creature_text ? @@ -1022,9 +1021,11 @@ WintergraspTowerCannonData const TowerCannon[WG_MAX_TOWER_CANNON] = // Workshop data and elements uint8 const WG_MAX_WORKSHOP = 6; +// IDs below this are the capturable workshops (NE/NW/SE/SW) with graveyards. +uint8 const WG_CAPTURE_WORKSHOP_COUNT = 4; + struct WGWorkshopData { - uint8 id; uint32 worldstate; uint8 attackText; uint8 takenText; @@ -1033,17 +1034,17 @@ struct WGWorkshopData WGWorkshopData const WorkshopsData[WG_MAX_WORKSHOP] = { // NE - {BATTLEFIELD_WG_WORKSHOP_NE, WORLD_STATE_BATTLEFIELD_WG_WORKSHOP_NE, BATTLEFIELD_WG_TEXT_WORKSHOP_NE_ATTACK, BATTLEFIELD_WG_TEXT_WORKSHOP_NE_TAKEN}, + {WORLD_STATE_BATTLEFIELD_WG_WORKSHOP_NE, BATTLEFIELD_WG_TEXT_WORKSHOP_NE_ATTACK, BATTLEFIELD_WG_TEXT_WORKSHOP_NE_TAKEN}, // NW - {BATTLEFIELD_WG_WORKSHOP_NW, WORLD_STATE_BATTLEFIELD_WG_WORKSHOP_NW, BATTLEFIELD_WG_TEXT_WORKSHOP_NW_ATTACK, BATTLEFIELD_WG_TEXT_WORKSHOP_NW_TAKEN}, + {WORLD_STATE_BATTLEFIELD_WG_WORKSHOP_NW, BATTLEFIELD_WG_TEXT_WORKSHOP_NW_ATTACK, BATTLEFIELD_WG_TEXT_WORKSHOP_NW_TAKEN}, // SE - {BATTLEFIELD_WG_WORKSHOP_SE, WORLD_STATE_BATTLEFIELD_WG_WORKSHOP_SE, BATTLEFIELD_WG_TEXT_WORKSHOP_SE_ATTACK, BATTLEFIELD_WG_TEXT_WORKSHOP_SE_TAKEN}, + {WORLD_STATE_BATTLEFIELD_WG_WORKSHOP_SE, BATTLEFIELD_WG_TEXT_WORKSHOP_SE_ATTACK, BATTLEFIELD_WG_TEXT_WORKSHOP_SE_TAKEN}, // SW - {BATTLEFIELD_WG_WORKSHOP_SW, WORLD_STATE_BATTLEFIELD_WG_WORKSHOP_SW, BATTLEFIELD_WG_TEXT_WORKSHOP_SW_ATTACK, BATTLEFIELD_WG_TEXT_WORKSHOP_SW_TAKEN}, - // KEEP WEST - It can't be taken - {BATTLEFIELD_WG_WORKSHOP_KEEP_WEST, WORLD_STATE_BATTLEFIELD_WG_WORKSHOP_K_W, 0, BATTLEFIELD_WG_TEXT_WORKSHOP_NE_TAKEN}, + {WORLD_STATE_BATTLEFIELD_WG_WORKSHOP_SW, BATTLEFIELD_WG_TEXT_WORKSHOP_SW_ATTACK, BATTLEFIELD_WG_TEXT_WORKSHOP_SW_TAKEN}, // KEEP EAST - It can't be taken - {BATTLEFIELD_WG_WORKSHOP_KEEP_EAST, WORLD_STATE_BATTLEFIELD_WG_WORKSHOP_K_E, 0, BATTLEFIELD_WG_TEXT_WORKSHOP_NE_TAKEN} + {WORLD_STATE_BATTLEFIELD_WG_WORKSHOP_K_E, 0, BATTLEFIELD_WG_TEXT_WORKSHOP_NE_TAKEN}, + // KEEP WEST - It can't be taken + {WORLD_STATE_BATTLEFIELD_WG_WORKSHOP_K_W, 0, BATTLEFIELD_WG_TEXT_WORKSHOP_NE_TAKEN} }; // Structs for Building, Graveyard, and Workshop runtime objects @@ -1185,9 +1186,7 @@ struct BfWGGameObjectBuilding void Init(GameObject* gobj, uint32 type, uint32 worldstate, uint8 damageText, uint8 destroyText) { if (!gobj) - { return; - } // GameObject associated to object m_Build = gobj->GetGUID(); @@ -1219,23 +1218,20 @@ struct BfWGGameObjectBuilding } m_State = sWorldState->getWorldState(m_WorldState); - if (gobj) + switch (m_State) { - switch (m_State) - { - case BATTLEFIELD_WG_OBJECTSTATE_ALLIANCE_INTACT: - case BATTLEFIELD_WG_OBJECTSTATE_HORDE_INTACT: - gobj->SetDestructibleState(GO_DESTRUCTIBLE_REBUILDING, nullptr, true); - break; - case BATTLEFIELD_WG_OBJECTSTATE_ALLIANCE_DESTROY: - case BATTLEFIELD_WG_OBJECTSTATE_HORDE_DESTROY: - gobj->SetDestructibleState(GO_DESTRUCTIBLE_DESTROYED); - break; - case BATTLEFIELD_WG_OBJECTSTATE_ALLIANCE_DAMAGE: - case BATTLEFIELD_WG_OBJECTSTATE_HORDE_DAMAGE: - gobj->SetDestructibleState(GO_DESTRUCTIBLE_DAMAGED); - break; - } + case BATTLEFIELD_WG_OBJECTSTATE_ALLIANCE_INTACT: + case BATTLEFIELD_WG_OBJECTSTATE_HORDE_INTACT: + gobj->SetDestructibleState(GO_DESTRUCTIBLE_REBUILDING, nullptr, true); + break; + case BATTLEFIELD_WG_OBJECTSTATE_ALLIANCE_DESTROY: + case BATTLEFIELD_WG_OBJECTSTATE_HORDE_DESTROY: + gobj->SetDestructibleState(GO_DESTRUCTIBLE_DESTROYED); + break; + case BATTLEFIELD_WG_OBJECTSTATE_ALLIANCE_DAMAGE: + case BATTLEFIELD_WG_OBJECTSTATE_HORDE_DAMAGE: + gobj->SetDestructibleState(GO_DESTRUCTIBLE_DAMAGED); + break; } int32 towerid = -1; @@ -1415,7 +1411,7 @@ struct WGWorkshop WGWorkshop(BattlefieldWG* _bf, uint8 _workshopId) { - ASSERT(_bf || _workshopId < WG_MAX_WORKSHOP); + ASSERT(_bf && _workshopId < WG_MAX_WORKSHOP); bf = _bf; workshopId = _workshopId; @@ -1423,6 +1419,9 @@ struct WGWorkshop state = BATTLEFIELD_WG_OBJECTSTATE_NONE; } + // True for the four capturable workshops. + bool IsCapturable() const { return workshopId < WG_CAPTURE_WORKSHOP_COUNT; } + void GiveControlTo(TeamId team, bool init /* for first call in setup*/) { switch (team) @@ -1438,7 +1437,7 @@ struct WGWorkshop bf->SendUpdateWorldState(WorkshopsData[workshopId].worldstate, state); // Found associate graveyard and update it - if (workshopId < BATTLEFIELD_WG_WORKSHOP_KEEP_WEST) + if (IsCapturable()) if (bf->GetGraveyardById(workshopId)) bf->GetGraveyardById(workshopId)->GiveControlTo(team); @@ -1457,7 +1456,7 @@ struct WGWorkshop bf->SendWarning(team == TEAM_ALLIANCE ? WorkshopsData[workshopId].takenText : (WorkshopsData[workshopId].takenText + 2)); // Found associate graveyard and update it - if (workshopId < BATTLEFIELD_WG_WORKSHOP_KEEP_WEST) + if (IsCapturable()) if (bf->GetGraveyardById(workshopId)) bf->GetGraveyardById(workshopId)->GiveControlTo(team); @@ -1475,7 +1474,7 @@ struct WGWorkshop void UpdateGraveyardAndWorkshop() { - if (workshopId < BATTLEFIELD_WG_WORKSHOP_KEEP_WEST) + if (IsCapturable()) bf->GetGraveyardById(workshopId)->GiveControlTo(TeamId(teamControl)); else GiveControlTo(bf->GetDefenderTeam(), true); From 1e39a1bc1f3d56fe9683dae26f687a43c0ab3f2e Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Tue, 16 Jun 2026 08:03:55 -0500 Subject: [PATCH 48/80] fix(Scripts/Spells): detonate Seed of Corruption when the target dies (#26201) Co-authored-by: blinkysc --- src/server/scripts/Spells/spell_warlock.cpp | 28 ++++++++++++++++----- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/src/server/scripts/Spells/spell_warlock.cpp b/src/server/scripts/Spells/spell_warlock.cpp index 49d698a26..784d41952 100644 --- a/src/server/scripts/Spells/spell_warlock.cpp +++ b/src/server/scripts/Spells/spell_warlock.cpp @@ -64,6 +64,7 @@ enum WarlockSpells SPELL_WARLOCK_LIFE_TAP_ENERGIZE_2 = 32553, SPELL_WARLOCK_SEED_OF_CORRUPTION_DAMAGE_R1 = 27285, SPELL_WARLOCK_SEED_OF_CORRUPTION_DAMAGE_GENERIC = 32865, + SPELL_WARLOCK_SEED_OF_CORRUPTION_VISUAL = 37826, SPELL_WARLOCK_SOULSHATTER = 32835, SPELL_WARLOCK_SIPHON_LIFE_HEAL = 63106, SPELL_WARLOCK_UNSTABLE_AFFLICTION_DISPEL = 31117, @@ -1530,6 +1531,18 @@ class spell_warl_seed_of_corruption_dummy : public AuraScript amount = caster->SpellDamageBonusDone(GetUnitOwner(), GetSpellInfo(), amount, SPELL_DIRECT_DAMAGE, aurEff->GetEffIndex()); } + void Detonate(AuraEffect const* aurEff) + { + Unit* caster = GetCaster(); + if (!caster) + return; + + GetUnitOwner()->CastSpell(GetUnitOwner(), SPELL_WARLOCK_SEED_OF_CORRUPTION_VISUAL, true, nullptr, aurEff); + + uint32 spellId = sSpellMgr->GetSpellWithRank(SPELL_WARLOCK_SEED_OF_CORRUPTION_DAMAGE_R1, GetSpellInfo()->GetRank()); + caster->CastSpell(GetUnitOwner(), spellId, true, nullptr, aurEff); + } + void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) { PreventDefaultAction(); @@ -1546,18 +1559,21 @@ class spell_warl_seed_of_corruption_dummy : public AuraScript } Remove(); + Detonate(aurEff); + } - Unit* caster = GetCaster(); - if (!caster) - return; - - uint32 spellId = sSpellMgr->GetSpellWithRank(SPELL_WARLOCK_SEED_OF_CORRUPTION_DAMAGE_R1, GetSpellInfo()->GetRank()); - caster->CastSpell(eventInfo.GetActionTarget(), spellId, true, nullptr, aurEff); + // Seed also detonates when the seeded target dies, not only when the damage + // buffer threshold is reached. + void OnRemove(AuraEffect const* aurEff, AuraEffectHandleModes /*mode*/) + { + if (GetTargetApplication()->GetRemoveMode() == AURA_REMOVE_BY_DEATH) + Detonate(aurEff); } void Register() override { DoEffectCalcAmount += AuraEffectCalcAmountFn(spell_warl_seed_of_corruption_dummy::CalculateBuffer, EFFECT_1, SPELL_AURA_DUMMY); + AfterEffectRemove += AuraEffectRemoveFn(spell_warl_seed_of_corruption_dummy::OnRemove, EFFECT_0, SPELL_AURA_PERIODIC_DAMAGE, AURA_EFFECT_HANDLE_REAL); OnEffectProc += AuraEffectProcFn(spell_warl_seed_of_corruption_dummy::HandleProc, EFFECT_1, SPELL_AURA_DUMMY); } }; From edf34ed6d92d4c051dac28c99014d5c591b05a05 Mon Sep 17 00:00:00 2001 From: Kitzunu <24550914+Kitzunu@users.noreply.github.com> Date: Tue, 16 Jun 2026 16:08:01 +0200 Subject: [PATCH 49/80] feat(Core/Player): Implement ACCOUNT_FLAG_TRIAL (#26064) Co-authored-by: Ludwig Co-authored-by: Claude Opus 4.8 --- src/common/Common.h | 2 +- .../apps/worldserver/worldserver.conf.dist | 93 +++++++++++++++++++ src/server/game/Entities/Player/Player.cpp | 24 ++++- .../game/Entities/Player/PlayerUpdates.cpp | 5 + .../game/Handlers/AuctionHouseHandler.cpp | 19 ++++ src/server/game/Handlers/ChatHandler.cpp | 28 ++++++ src/server/game/Handlers/GroupHandler.cpp | 42 ++++++++- src/server/game/Handlers/GuildHandler.cpp | 9 ++ src/server/game/Handlers/MailHandler.cpp | 6 ++ src/server/game/Handlers/PetitionsHandler.cpp | 18 ++++ src/server/game/Handlers/TradeHandler.cpp | 14 +++ src/server/game/Server/WorldSessionMgr.cpp | 7 +- src/server/game/World/WorldConfig.cpp | 11 +++ src/server/game/World/WorldConfig.h | 10 ++ 14 files changed, 281 insertions(+), 7 deletions(-) diff --git a/src/common/Common.h b/src/common/Common.h index 1a2c0a390..f816a2ded 100644 --- a/src/common/Common.h +++ b/src/common/Common.h @@ -67,7 +67,7 @@ enum AccountFlag ACCOUNT_FLAG_GM = 0x1, // Account is GM ACCOUNT_FLAG_NOKICK = 0x2, // NYI UNK ACCOUNT_FLAG_COLLECTOR = 0x4, // NYI Collector's Edition - ACCOUNT_FLAG_TRIAL = 0x8, // NYI Trial account + ACCOUNT_FLAG_TRIAL = 0x8, // Trial account ACCOUNT_FLAG_CANCELLED = 0x10, // NYI UNK ACCOUNT_FLAG_IGR = 0x20, // NYI Internet Game Room (Internet cafe?) ACCOUNT_FLAG_WHOLESALER = 0x40, // NYI UNK diff --git a/src/server/apps/worldserver/worldserver.conf.dist b/src/server/apps/worldserver/worldserver.conf.dist index 8fc7fd0de..956972d3c 100644 --- a/src/server/apps/worldserver/worldserver.conf.dist +++ b/src/server/apps/worldserver/worldserver.conf.dist @@ -30,6 +30,7 @@ # GAME SETTINGS # GAME MASTER # CHEAT +# ACCOUNT # CHARACTER DATABASE # CHARACTER DELETE # CHARACTER CREATION @@ -1718,6 +1719,98 @@ InstantLogout = 1 # ################################################################################################### +################################################################################################### +# ACCOUNT +# +# Trial.LevelCap +# Description: Maximum level a trial account can reach. XP gain stops once this level is reached. +# Default: 20 +# 0 - (Disabled, no cap) + +Trial.LevelCap = 20 + +# +# Trial.MoneyCap +# Description: Maximum money a trial account can hold, in copper. +# Default: 100000 - (10 gold) +# 0 - (Disabled, no cap) + +Trial.MoneyCap = 100000 + +# +# Trial.TradeSkillCap +# Description: Maximum value any profession skill can reach on a trial account. +# Default: 100 +# 0 - (Disabled, no cap) + +Trial.TradeSkillCap = 100 + +# +# Trial.Restriction.Chat +# Description: Restrict trial accounts from using whisper/channel/guild chats. +# Whispers are only allowed when the trial player is on the recipients friendslist +# or when the trial account was messaged first. +# Default: 1 - (Enabled) +# 0 - (Disabled) + +Trial.Restriction.Chat = 1 + +# +# Trial.Restriction.Mail +# Description: Restrict trial accounts from sending mail. +# Default: 1 - (Enabled) +# 0 - (Disabled) + +Trial.Restriction.Mail = 1 + +# +# Trial.Restriction.Trade +# Description: Restrict trial accounts from initiating or being the target of in-game trade. +# Default: 1 - (Enabled) +# 0 - (Disabled) + +Trial.Restriction.Trade = 1 + +# +# Trial.Restriction.Auction +# Description: Restrict trial accounts from selling, bidding, or cancelling auctions at the auction house. +# Default: 1 - (Enabled) +# 0 - (Disabled) + +Trial.Restriction.Auction = 1 + +# +# Trial.Restriction.Party +# Description: Restrict trial accounts from inviting players to a party, and from accepting +# an invite to a party whose existing members are above Trial.LevelCap. +# The join check is skipped when Trial.LevelCap is 0. +# Default: 1 - (Enabled) +# 0 - (Disabled) + +Trial.Restriction.Party = 1 + +# +# Trial.Restriction.Guild +# Description: Restrict trial accounts from joining a guild (accepting an invite) and from +# creating one (buying, signing, or turning in a guild charter). +# Default: 1 - (Enabled) +# 0 - (Disabled) + +Trial.Restriction.Guild = 1 + +# +# Trial.Restriction.Queue +# Description: Force trial accounts through the world session queue even if their account +# flags would otherwise grant skip-queue priority. The RBAC_PERM_SKIP_QUEUE +# permission and recent-disconnect grace are still honored. +# Default: 1 - (Enabled) +# 0 - (Disabled) + +Trial.Restriction.Queue = 1 + +# +################################################################################################### + ################################################################################################### # CHARACTER DATABASE # diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 90d0e0442..6dc714337 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -2388,7 +2388,14 @@ void Player::GiveXP(uint32 xp, Unit* victim, float group_rate, bool isLFGReward) // Favored experience increase END // XP to money conversion processed in Player::RewardQuest - if (level >= sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL)) + uint32 maxLevel = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL); + + // Trial account level cap (0 disables the cap) + if (uint32 trialLevelCap = sWorld->getIntConfig(CONFIG_TRIAL_LEVEL_CAP)) + if (GetSession()->IsTrialAccount()) + maxLevel = std::min(maxLevel, trialLevelCap); + + if (level >= maxLevel) return; uint32 bonus_xp = 0; @@ -2413,11 +2420,11 @@ void Player::GiveXP(uint32 xp, Unit* victim, float group_rate, bool isLFGReward) uint32 nextLvlXP = GetUInt32Value(PLAYER_NEXT_LEVEL_XP); uint32 newXP = curXP + xp + bonus_xp; - while (newXP >= nextLvlXP && level < sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL)) + while (newXP >= nextLvlXP && level < maxLevel) { newXP -= nextLvlXP; - if (level < sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL)) + if (level < maxLevel) GiveLevel(level + 1); level = GetLevel(); @@ -11500,6 +11507,17 @@ bool Player::ModifyMoney(int32 amount, bool sendError /*= true*/) SetMoney (GetMoney() > uint32(-amount) ? GetMoney() + amount : 0); else { + // Trial account money cap (0 disables the cap) + if (uint32 trialMoneyCap = sWorld->getIntConfig(CONFIG_TRIAL_MONEY_CAP)) + { + if (GetSession()->IsTrialAccount() && GetMoney() + uint32(amount) > trialMoneyCap) + { + if (sendError) + SendEquipError(EQUIP_ERR_TOO_MUCH_GOLD, nullptr, nullptr); + return false; + } + } + if (GetMoney() < uint32(MAX_MONEY_AMOUNT - amount)) SetMoney(GetMoney() + amount); else diff --git a/src/server/game/Entities/Player/PlayerUpdates.cpp b/src/server/game/Entities/Player/PlayerUpdates.cpp index fec393e3e..c9265d3a4 100644 --- a/src/server/game/Entities/Player/PlayerUpdates.cpp +++ b/src/server/game/Entities/Player/PlayerUpdates.cpp @@ -939,6 +939,11 @@ bool Player::UpdateSkillPro(uint16 SkillId, int32 Chance, uint32 step) if (!MaxValue || !SkillValue || SkillValue >= MaxValue) return false; + // Trial account trade-skill cap (0 disables the cap) + if (uint32 trialSkillCap = sWorld->getIntConfig(CONFIG_TRIAL_TRADE_SKILL_CAP)) + if (GetSession()->IsTrialAccount() && SkillValue >= trialSkillCap) + return false; + int32 Roll = irand(1, 1000); if (Roll <= Chance) diff --git a/src/server/game/Handlers/AuctionHouseHandler.cpp b/src/server/game/Handlers/AuctionHouseHandler.cpp index c103ea352..731a0955d 100644 --- a/src/server/game/Handlers/AuctionHouseHandler.cpp +++ b/src/server/game/Handlers/AuctionHouseHandler.cpp @@ -132,6 +132,13 @@ void WorldSession::HandleAuctionSellItem(WorldPacket& recvData) return; } + if (sWorld->getBoolConfig(CONFIG_TRIAL_RESTRICTION_AUCTION) && IsTrialAccount()) + { + SendAuctionCommandResult(0, AUCTION_SELL_ITEM, ERR_AUCTION_RESTRICTED_ACCOUNT); + recvData.rfinish(); + return; + } + for (uint32 i = 0; i < itemsCount; ++i) { recvData >> itemGUIDs[i]; @@ -426,6 +433,12 @@ void WorldSession::HandleAuctionPlaceBid(WorldPacket& recvData) if (!auctionId || !price) return; //check for cheaters + if (sWorld->getBoolConfig(CONFIG_TRIAL_RESTRICTION_AUCTION) && IsTrialAccount()) + { + SendAuctionCommandResult(0, AUCTION_PLACE_BID, ERR_AUCTION_RESTRICTED_ACCOUNT); + return; + } + Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(auctioneer, UNIT_NPC_FLAG_AUCTIONEER); if (!creature) { @@ -578,6 +591,12 @@ void WorldSession::HandleAuctionRemoveItem(WorldPacket& recvData) recvData >> auctioneer; recvData >> auctionId; + if (sWorld->getBoolConfig(CONFIG_TRIAL_RESTRICTION_AUCTION) && IsTrialAccount()) + { + SendAuctionCommandResult(0, AUCTION_CANCEL, ERR_AUCTION_RESTRICTED_ACCOUNT); + return; + } + Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(auctioneer, UNIT_NPC_FLAG_AUCTIONEER); if (!creature) { diff --git a/src/server/game/Handlers/ChatHandler.cpp b/src/server/game/Handlers/ChatHandler.cpp index 6b120f925..a9c9acb71 100644 --- a/src/server/game/Handlers/ChatHandler.cpp +++ b/src/server/game/Handlers/ChatHandler.cpp @@ -34,6 +34,7 @@ #include "Opcodes.h" #include "Player.h" #include "ScriptMgr.h" +#include "SocialMgr.h" #include "SpellAuraEffects.h" #include "SpellAuras.h" #include "Util.h" @@ -112,6 +113,18 @@ void WorldSession::HandleMessagechatOpcode(WorldPacket& recvData) } } + // Trial accounts cannot speak in chat channels or guild/officer chat. Whisper is handled later. + // Addon traffic (LANG_ADDON) is excluded; it carries the Warden Lua check response over guild chat. + if (sWorld->getBoolConfig(CONFIG_TRIAL_RESTRICTION_CHAT) && IsTrialAccount() && lang != LANG_ADDON + && (type == CHAT_MSG_CHANNEL || type == CHAT_MSG_GUILD || type == CHAT_MSG_OFFICER)) + { + WorldPacket data; + ChatHandler::BuildChatPacket(data, CHAT_MSG_RESTRICTED, LANG_UNIVERSAL, sender, sender, ""); + SendPacket(&data); + recvData.rfinish(); + return; + } + // pussywizard: chatting on most chat types requires 2 hours played to prevent spam/abuse if (!HasPermission(rbac::RBAC_PERM_SKIP_CHECK_CHAT_CHANNEL_REQ)) { @@ -398,6 +411,17 @@ void WorldSession::HandleMessagechatOpcode(WorldPacket& recvData) return; } + // Trial accounts can only whisper players who have them on their friend list, or players who whispered them first. + if (sWorld->getBoolConfig(CONFIG_TRIAL_RESTRICTION_CHAT) && IsTrialAccount() && receiver != sender + && !receiver->GetSocial()->HasFriend(sender->GetGUID()) + && !sender->IsInWhisperWhiteList(receiver->GetGUID())) + { + WorldPacket data; + ChatHandler::BuildChatPacket(data, CHAT_MSG_RESTRICTED, LANG_UNIVERSAL, sender, sender, ""); + SendPacket(&data); + return; + } + if (!sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_CHAT) && senderIsPlayer && receiverIsPlayer) if (GetPlayer()->GetTeamId() != receiver->GetTeamId()) { @@ -417,6 +441,10 @@ void WorldSession::HandleMessagechatOpcode(WorldPacket& recvData) (HasPermission(rbac::RBAC_PERM_CAN_FILTER_WHISPERS) && !sender->isAcceptWhispers() && !sender->IsInWhisperWhiteList(receiver->GetGUID()))) sender->AddWhisperWhiteList(receiver->GetGUID()); + // Allow a trial-account recipient to whisper back without being on the sender's friend list. + if (receiver->GetSession()->IsTrialAccount() && !receiver->IsInWhisperWhiteList(sender->GetGUID())) + receiver->AddWhisperWhiteList(sender->GetGUID()); + GetPlayer()->Whisper(msg, Language(lang), receiver); } break; diff --git a/src/server/game/Handlers/GroupHandler.cpp b/src/server/game/Handlers/GroupHandler.cpp index 13eadd687..3f0884ebe 100644 --- a/src/server/game/Handlers/GroupHandler.cpp +++ b/src/server/game/Handlers/GroupHandler.cpp @@ -89,6 +89,12 @@ void WorldSession::HandleGroupInviteOpcode(WorldPacket& recvData) if (!sScriptMgr->OnPlayerCanGroupInvite(invitingPlayer, membername)) return; + if (sWorld->getBoolConfig(CONFIG_TRIAL_RESTRICTION_PARTY) && IsTrialAccount()) + { + SendPartyResult(PARTY_OP_INVITE, membername, ERR_INVITE_RESTRICTED); + return; + } + if (invitingPlayer->IsSpectator() || invitedPlayer->IsSpectator()) { SendPartyResult(PARTY_OP_INVITE, membername, ERR_INVITE_RESTRICTED); @@ -244,6 +250,40 @@ void WorldSession::HandleGroupAcceptOpcode(WorldPacket& recvData) if (!sScriptMgr->OnPlayerCanGroupAccept(GetPlayer(), group)) return; + Player* leader = ObjectAccessor::FindConnectedPlayer(group->GetLeaderGUID()); + + // Trial accounts cannot join a group whose existing members are above the trial level cap. + if (sWorld->getBoolConfig(CONFIG_TRIAL_RESTRICTION_PARTY) && IsTrialAccount()) + { + if (uint32 trialLevelCap = sWorld->getIntConfig(CONFIG_TRIAL_LEVEL_CAP)) + { + uint8 leaderLevel = leader ? leader->GetLevel() : sCharacterCache->GetCharacterLevelByGuid(group->GetLeaderGUID()); + if (leaderLevel > trialLevelCap) + { + SendPartyResult(PARTY_OP_INVITE, "", ERR_INVITE_RESTRICTED); + return; + } + + for (auto const& slot : group->GetMemberSlots()) + { + if (slot.guid == GetPlayer()->GetGUID()) + continue; + + uint8 memberLevel = 0; + if (Player* member = ObjectAccessor::FindConnectedPlayer(slot.guid)) + memberLevel = member->GetLevel(); + else + memberLevel = sCharacterCache->GetCharacterLevelByGuid(slot.guid); + + if (memberLevel > trialLevelCap) + { + SendPartyResult(PARTY_OP_INVITE, "", ERR_INVITE_RESTRICTED); + return; + } + } + } + } + if (group->GetLeaderGUID() == GetPlayer()->GetGUID()) { LOG_ERROR("network.opcode", "HandleGroupAcceptOpcode: player {} ({}) tried to accept an invite to his own group", @@ -258,8 +298,6 @@ void WorldSession::HandleGroupAcceptOpcode(WorldPacket& recvData) return; } - Player* leader = ObjectAccessor::FindConnectedPlayer(group->GetLeaderGUID()); - // Forming a new group, create it if (!group->IsCreated()) { diff --git a/src/server/game/Handlers/GuildHandler.cpp b/src/server/game/Handlers/GuildHandler.cpp index 35134009d..8cb95e603 100644 --- a/src/server/game/Handlers/GuildHandler.cpp +++ b/src/server/game/Handlers/GuildHandler.cpp @@ -21,6 +21,7 @@ #include "Log.h" #include "ObjectMgr.h" #include "SocialMgr.h" +#include "World.h" #include "WorldSession.h" void WorldSession::HandleGuildQueryOpcode(WorldPackets::Guild::QueryGuildInfo& query) @@ -59,6 +60,14 @@ void WorldSession::HandleGuildAcceptOpcode(WorldPackets::Guild::AcceptGuildInvit { LOG_DEBUG("guild", "CMSG_GUILD_ACCEPT [{}]", GetPlayer()->GetName()); + if (sWorld->getBoolConfig(CONFIG_TRIAL_RESTRICTION_GUILD) && IsTrialAccount()) + { + Guild::SendCommandResult(this, GUILD_COMMAND_INVITE, ERR_GUILD_PERMISSIONS); + GetPlayer()->SetGuildIdInvited(0); + GetPlayer()->SetInGuild(0); + return; + } + if (!GetPlayer()->GetGuildId()) if (Guild* guild = sGuildMgr->GetGuildById(GetPlayer()->GetGuildIdInvited())) guild->HandleAcceptMember(this); diff --git a/src/server/game/Handlers/MailHandler.cpp b/src/server/game/Handlers/MailHandler.cpp index 90e04a0ea..e44001d36 100644 --- a/src/server/game/Handlers/MailHandler.cpp +++ b/src/server/game/Handlers/MailHandler.cpp @@ -117,6 +117,12 @@ void WorldSession::HandleSendMail(WorldPacket& recvData) Player* player = _player; + if (sWorld->getBoolConfig(CONFIG_TRIAL_RESTRICTION_MAIL) && IsTrialAccount()) + { + player->SendMailResult(0, MAIL_SEND, MAIL_ERR_DISABLED_FOR_TRIAL_ACC); + return; + } + if (player->GetLevel() < sWorld->getIntConfig(CONFIG_MAIL_LEVEL_REQ)) { ChatHandler(this).SendNotification(LANG_MAIL_SENDER_REQ, sWorld->getIntConfig(CONFIG_MAIL_LEVEL_REQ)); diff --git a/src/server/game/Handlers/PetitionsHandler.cpp b/src/server/game/Handlers/PetitionsHandler.cpp index 7ce587015..a75809b20 100644 --- a/src/server/game/Handlers/PetitionsHandler.cpp +++ b/src/server/game/Handlers/PetitionsHandler.cpp @@ -84,6 +84,12 @@ void WorldSession::HandlePetitionBuyOpcode(WorldPacket& recvData) if (_player->GetGuildId()) return; + if (sWorld->getBoolConfig(CONFIG_TRIAL_RESTRICTION_GUILD) && IsTrialAccount()) + { + Guild::SendCommandResult(this, GUILD_COMMAND_CREATE, ERR_GUILD_PERMISSIONS); + return; + } + charterid = GUILD_CHARTER; cost = sWorld->getIntConfig(CONFIG_CHARTER_COST_GUILD); type = GUILD_CHARTER_TYPE; @@ -455,6 +461,12 @@ void WorldSession::HandlePetitionSignOpcode(WorldPacket& recvData) return; } + if (sWorld->getBoolConfig(CONFIG_TRIAL_RESTRICTION_GUILD) && IsTrialAccount()) + { + Guild::SendCommandResult(this, GUILD_COMMAND_INVITE, ERR_GUILD_PERMISSIONS); + return; + } + if (_player->GetGuildId()) { Guild::SendCommandResult(this, GUILD_COMMAND_INVITE, ERR_ALREADY_IN_GUILD_S, _player->GetName()); @@ -678,6 +690,12 @@ void WorldSession::HandleTurnInPetitionOpcode(WorldPacket& recvData) // Petition type (guild/arena) specific checks if (type == GUILD_CHARTER_TYPE) { + if (sWorld->getBoolConfig(CONFIG_TRIAL_RESTRICTION_GUILD) && IsTrialAccount()) + { + Guild::SendCommandResult(this, GUILD_COMMAND_CREATE, ERR_GUILD_PERMISSIONS); + return; + } + // Check if player is already in a guild if (_player->GetGuildId()) { diff --git a/src/server/game/Handlers/TradeHandler.cpp b/src/server/game/Handlers/TradeHandler.cpp index a6951df67..ef7590f65 100644 --- a/src/server/game/Handlers/TradeHandler.cpp +++ b/src/server/game/Handlers/TradeHandler.cpp @@ -675,6 +675,13 @@ void WorldSession::HandleInitiateTradeOpcode(WorldPacket& recvPacket) return; } + if (sWorld->getBoolConfig(CONFIG_TRIAL_RESTRICTION_TRADE) && IsTrialAccount()) + { + info.Status = TRADE_STATUS_TRIAL_ACCOUNT; + SendTradeStatus(info); + return; + } + if (GetPlayer()->IsSpectator()) return; @@ -722,6 +729,13 @@ void WorldSession::HandleInitiateTradeOpcode(WorldPacket& recvPacket) return; } + if (sWorld->getBoolConfig(CONFIG_TRIAL_RESTRICTION_TRADE) && pOther->GetSession()->IsTrialAccount()) + { + info.Status = TRADE_STATUS_TRIAL_ACCOUNT; + SendTradeStatus(info); + return; + } + if (pOther->GetTeamId() != _player->GetTeamId() && !GetPlayer()->GetSession()->HasPermission(rbac::RBAC_PERM_ALLOW_TWO_SIDE_TRADE)) { diff --git a/src/server/game/Server/WorldSessionMgr.cpp b/src/server/game/Server/WorldSessionMgr.cpp index 8f5d40325..b22726d5a 100644 --- a/src/server/game/Server/WorldSessionMgr.cpp +++ b/src/server/game/Server/WorldSessionMgr.cpp @@ -324,7 +324,12 @@ void WorldSessionMgr::AddSession_(WorldSession* session) // don't count this session when checking player limit --Sessions; - if (pLimit > 0 && Sessions >= pLimit && !session->HasPermission(rbac::RBAC_PERM_SKIP_QUEUE) && !session->CanSkipQueue() && !HasRecentlyDisconnected(session)) + // Trial accounts do not get account-flag queue priority. RBAC_PERM_SKIP_QUEUE is still honored + // since it can be granted intentionally to specific accounts. + bool trialQueueRestricted = sWorld->getBoolConfig(CONFIG_TRIAL_RESTRICTION_QUEUE) && session->IsTrialAccount(); + bool canSkipQueue = session->HasPermission(rbac::RBAC_PERM_SKIP_QUEUE) || (!trialQueueRestricted && session->CanSkipQueue()); + + if (pLimit > 0 && Sessions >= pLimit && !canSkipQueue && !HasRecentlyDisconnected(session)) { AddQueuedPlayer(session); UpdateMaxSessionCounters(); diff --git a/src/server/game/World/WorldConfig.cpp b/src/server/game/World/WorldConfig.cpp index fce621980..7f6b51e98 100644 --- a/src/server/game/World/WorldConfig.cpp +++ b/src/server/game/World/WorldConfig.cpp @@ -348,6 +348,17 @@ void WorldConfig::BuildConfigCache() SetConfigValue(CONFIG_CHAT_MUTE_FIRST_LOGIN, "Chat.MuteFirstLogin", false); SetConfigValue(CONFIG_CHAT_TIME_MUTE_FIRST_LOGIN, "Chat.MuteTimeFirstLogin", 120); + SetConfigValue(CONFIG_TRIAL_RESTRICTION_CHAT, "Trial.Restriction.Chat", true); + SetConfigValue(CONFIG_TRIAL_RESTRICTION_MAIL, "Trial.Restriction.Mail", true); + SetConfigValue(CONFIG_TRIAL_RESTRICTION_TRADE, "Trial.Restriction.Trade", true); + SetConfigValue(CONFIG_TRIAL_RESTRICTION_AUCTION, "Trial.Restriction.Auction", true); + SetConfigValue(CONFIG_TRIAL_RESTRICTION_PARTY, "Trial.Restriction.Party", true); + SetConfigValue(CONFIG_TRIAL_RESTRICTION_GUILD, "Trial.Restriction.Guild", true); + SetConfigValue(CONFIG_TRIAL_RESTRICTION_QUEUE, "Trial.Restriction.Queue", true); + SetConfigValue(CONFIG_TRIAL_LEVEL_CAP, "Trial.LevelCap", 20); + SetConfigValue(CONFIG_TRIAL_MONEY_CAP, "Trial.MoneyCap", 100000); // copper, 10 gold + SetConfigValue(CONFIG_TRIAL_TRADE_SKILL_CAP, "Trial.TradeSkillCap", 100); + SetConfigValue(CONFIG_EVENT_ANNOUNCE, "Event.Announce", 0); SetConfigValue(CONFIG_CREATURE_LEASH_RADIUS, "CreatureLeashRadius", 30.0f); diff --git a/src/server/game/World/WorldConfig.h b/src/server/game/World/WorldConfig.h index dda89b786..bdeddd3a1 100644 --- a/src/server/game/World/WorldConfig.h +++ b/src/server/game/World/WorldConfig.h @@ -501,6 +501,16 @@ enum ServerConfigs CONFIG_ACHIEVEMENT_REALM_FIRST_KILL_WINDOW, CONFIG_ACHIEVEMENT_REALM_FIRST_RACE_LIMIT_ONE_PER_CHARACTER, CONFIG_CHATLOG_ENABLED, + CONFIG_TRIAL_RESTRICTION_CHAT, + CONFIG_TRIAL_RESTRICTION_MAIL, + CONFIG_TRIAL_RESTRICTION_TRADE, + CONFIG_TRIAL_RESTRICTION_AUCTION, + CONFIG_TRIAL_RESTRICTION_PARTY, + CONFIG_TRIAL_RESTRICTION_GUILD, + CONFIG_TRIAL_RESTRICTION_QUEUE, + CONFIG_TRIAL_LEVEL_CAP, + CONFIG_TRIAL_MONEY_CAP, + CONFIG_TRIAL_TRADE_SKILL_CAP, MAX_NUM_SERVER_CONFIGS }; From 778bf18d1d80fbc93fa0da15481b77672f4292ac Mon Sep 17 00:00:00 2001 From: Lilliandris <144048412+lilliandris@users.noreply.github.com> Date: Tue, 16 Jun 2026 16:50:07 +0200 Subject: [PATCH 50/80] fix(DB/Creature): Image of Loken non-attackable during quest Loken's Orders (#26202) Co-authored-by: Rorschach91 --- data/sql/updates/pending_db_world/rev_1781479317016009882.sql | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 data/sql/updates/pending_db_world/rev_1781479317016009882.sql diff --git a/data/sql/updates/pending_db_world/rev_1781479317016009882.sql b/data/sql/updates/pending_db_world/rev_1781479317016009882.sql new file mode 100644 index 000000000..7c2fcda30 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1781479317016009882.sql @@ -0,0 +1,2 @@ +-- Image of Loken +UPDATE `creature_template` SET `unit_flags` = `unit_flags` | 256 | 512 | 33554432 WHERE (`entry` = 27212); From f0addd172d09b993cc81e3ef7ee6fee2f48ef488 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 16 Jun 2026 14:51:28 +0000 Subject: [PATCH 51/80] chore(DB): import pending files Referenced commit(s): 778bf18d1d80fbc93fa0da15481b77672f4292ac --- .../rev_1781479317016009882.sql => db_world/2026_06_16_05.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1781479317016009882.sql => db_world/2026_06_16_05.sql} (73%) diff --git a/data/sql/updates/pending_db_world/rev_1781479317016009882.sql b/data/sql/updates/db_world/2026_06_16_05.sql similarity index 73% rename from data/sql/updates/pending_db_world/rev_1781479317016009882.sql rename to data/sql/updates/db_world/2026_06_16_05.sql index 7c2fcda30..5328d3449 100644 --- a/data/sql/updates/pending_db_world/rev_1781479317016009882.sql +++ b/data/sql/updates/db_world/2026_06_16_05.sql @@ -1,2 +1,3 @@ +-- DB update 2026_06_16_04 -> 2026_06_16_05 -- Image of Loken UPDATE `creature_template` SET `unit_flags` = `unit_flags` | 256 | 512 | 33554432 WHERE (`entry` = 27212); From 583d6e8da0426e84dee60baaff12a88869fcc024 Mon Sep 17 00:00:00 2001 From: sudlud Date: Tue, 16 Jun 2026 16:57:37 +0200 Subject: [PATCH 52/80] chore(coderabbit-config): reduce review noise (#26235) Co-authored-by: Claude Opus 4.8 --- .coderabbit.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.coderabbit.yml b/.coderabbit.yml index 562b3562d..2a9a50844 100644 --- a/.coderabbit.yml +++ b/.coderabbit.yml @@ -6,12 +6,15 @@ language: en-US reviews: profile: chill request_changes_workflow: false - high_level_summary: true + high_level_summary: false poem: false - review_status: true + review_status: false + collapse_walkthrough: true + sequence_diagrams: false auto_review: enabled: true - auto_incremental_review: true + # Review on PR open only; don't re-post a walkthrough on every push. + auto_incremental_review: false drafts: false # Skip generated, vendored, and immutable content so reviews stay focused. From 35a561849b1d315b53c52d8f71ebc0340c7ca597 Mon Sep 17 00:00:00 2001 From: warblups <51778260+warblups@users.noreply.github.com> Date: Tue, 16 Jun 2026 17:14:44 +0200 Subject: [PATCH 53/80] fix(Tools/VMap4Extractor): guard Windows mkdir block with _WIN32 (#25492) --- src/tools/vmap4_extractor/vmapexport.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/vmap4_extractor/vmapexport.cpp b/src/tools/vmap4_extractor/vmapexport.cpp index 52f4983f8..2b00673a3 100644 --- a/src/tools/vmap4_extractor/vmapexport.cpp +++ b/src/tools/vmap4_extractor/vmapexport.cpp @@ -23,7 +23,7 @@ #include #include -#ifdef WIN32 +#if defined(WIN32) || defined(_WIN32) #include #include #define mkdir _mkdir From 67f2fbdf91050ead3de01b6e5bd8c089bc6bffd1 Mon Sep 17 00:00:00 2001 From: Xepic90 Date: Tue, 16 Jun 2026 11:48:03 -0400 Subject: [PATCH 54/80] fix(Spells): Living Bomb explosion should not be reflectable by Spell Reflection (#26209) Co-authored-by: Claude Sonnet 4.6 --- src/server/game/Spells/SpellInfoCorrections.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/server/game/Spells/SpellInfoCorrections.cpp b/src/server/game/Spells/SpellInfoCorrections.cpp index cd00baf9f..0f71208b0 100644 --- a/src/server/game/Spells/SpellInfoCorrections.cpp +++ b/src/server/game/Spells/SpellInfoCorrections.cpp @@ -1068,6 +1068,8 @@ void SpellMgr::LoadSpellInfoCorrections() { spellInfo->AttributesEx3 |= SPELL_ATTR3_SUPPRESS_TARGET_PROCS; spellInfo->AttributesEx4 |= SPELL_ATTR4_DAMAGE_DOESNT_BREAK_AURAS; + // Explosion is AoE triggered on aura expiry - cannot be reflected (retail: Spell Reflection only works on single-target spells) + spellInfo->AttributesEx |= SPELL_ATTR1_NO_REFLECTION; }); // Evocation From 7b26de4e5863d308d229a7dd53b248bdac07cd15 Mon Sep 17 00:00:00 2001 From: Marchy <89603088+M4rchy-S@users.noreply.github.com> Date: Tue, 16 Jun 2026 19:39:06 +0200 Subject: [PATCH 55/80] fix(Scripts/UtgardeKeep): cast Dreadful Roar at start of Ingvar phase 2 (#25843) --- .../UtgardeKeep/UtgardeKeep/boss_ingvar_the_plunderer.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/boss_ingvar_the_plunderer.cpp b/src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/boss_ingvar_the_plunderer.cpp index 4e92ba786..7ae0c1fe8 100644 --- a/src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/boss_ingvar_the_plunderer.cpp +++ b/src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/boss_ingvar_the_plunderer.cpp @@ -278,6 +278,7 @@ struct boss_ingvar_the_plunderer : public ScriptedAI me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE); AttackStart(me->GetVictim()); me->GetMotionMaster()->MoveChase(me->GetVictim()); + me->CastSpell((Unit*)nullptr, SPELL_DREADFUL_ROAR, false); Talk(YELL_AGGRO_2); // schedule Phase 2 abilities From 80b6016528b7ccaf36bb56aa45bb51dc899293d8 Mon Sep 17 00:00:00 2001 From: Lilliandris <144048412+lilliandris@users.noreply.github.com> Date: Tue, 16 Jun 2026 20:03:01 +0200 Subject: [PATCH 56/80] fix(DB/Hunter): Dust Cloud debuff not removed after first miss (#26225) Co-authored-by: blinkysc --- data/sql/updates/pending_db_world/rev_1781559665924959595.sql | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 data/sql/updates/pending_db_world/rev_1781559665924959595.sql diff --git a/data/sql/updates/pending_db_world/rev_1781559665924959595.sql b/data/sql/updates/pending_db_world/rev_1781559665924959595.sql new file mode 100644 index 000000000..5705684e9 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1781559665924959595.sql @@ -0,0 +1,4 @@ +-- Dust Cloud (54404) - remove on the affected unit's first missed melee swing +DELETE FROM `spell_proc` WHERE `SpellId` = 54404; +INSERT INTO `spell_proc` (`SpellId`, `ProcFlags`, `HitMask`, `Chance`, `Charges`) + VALUES (54404, 0x00000004, 0x00000004, 100, 1); From 7e9dcf2421cb27134b42781c25898a76f6740e3a Mon Sep 17 00:00:00 2001 From: Lilliandris <144048412+lilliandris@users.noreply.github.com> Date: Tue, 16 Jun 2026 20:16:53 +0200 Subject: [PATCH 57/80] fix(Core/Shaman): Rockbiter Weapon flat damage persists after removal (#26220) --- src/server/game/Entities/Player/PlayerStorage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/game/Entities/Player/PlayerStorage.cpp b/src/server/game/Entities/Player/PlayerStorage.cpp index c0e596e9c..55925e66c 100644 --- a/src/server/game/Entities/Player/PlayerStorage.cpp +++ b/src/server/game/Entities/Player/PlayerStorage.cpp @@ -4701,7 +4701,7 @@ void Player::ApplyEnchantment(Item* item, EnchantmentSlot slot, bool apply, bool { WeaponAttackType const attackType = Player::GetAttackBySlot(item->GetSlot()); if (attackType != MAX_ATTACK) - UpdateDamageDoneMods(attackType); + UpdateDamageDoneMods(attackType, apply ? -1 : slot); break; } case ITEM_ENCHANTMENT_TYPE_USE_SPELL: From 42ea2271ed3aa6a4a09af6dda8d599e50d3cbaf1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 16 Jun 2026 18:18:36 +0000 Subject: [PATCH 58/80] chore(DB): import pending files Referenced commit(s): 7e9dcf2421cb27134b42781c25898a76f6740e3a --- .../rev_1781559665924959595.sql => db_world/2026_06_16_06.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1781559665924959595.sql => db_world/2026_06_16_06.sql} (85%) diff --git a/data/sql/updates/pending_db_world/rev_1781559665924959595.sql b/data/sql/updates/db_world/2026_06_16_06.sql similarity index 85% rename from data/sql/updates/pending_db_world/rev_1781559665924959595.sql rename to data/sql/updates/db_world/2026_06_16_06.sql index 5705684e9..64f5d08b9 100644 --- a/data/sql/updates/pending_db_world/rev_1781559665924959595.sql +++ b/data/sql/updates/db_world/2026_06_16_06.sql @@ -1,3 +1,4 @@ +-- DB update 2026_06_16_05 -> 2026_06_16_06 -- Dust Cloud (54404) - remove on the affected unit's first missed melee swing DELETE FROM `spell_proc` WHERE `SpellId` = 54404; INSERT INTO `spell_proc` (`SpellId`, `ProcFlags`, `HitMask`, `Chance`, `Charges`) From 0d07c33611e2fd71e6a77ab4ec2248d68846e19d Mon Sep 17 00:00:00 2001 From: sogladev Date: Tue, 16 Jun 2026 21:27:44 +0200 Subject: [PATCH 59/80] fix(Core/Vehicle): no longer eject and overwrite an existing passenger (#25591) Reject boarding a specific occupied seat instead of force-ejecting the current passenger. Followup to #25587 (Hyldnir Harpoon seat overwrite). --- src/server/game/Entities/Vehicle/Vehicle.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/server/game/Entities/Vehicle/Vehicle.cpp b/src/server/game/Entities/Vehicle/Vehicle.cpp index 2a64aab3c..ca4b21f6d 100644 --- a/src/server/game/Entities/Vehicle/Vehicle.cpp +++ b/src/server/game/Entities/Vehicle/Vehicle.cpp @@ -359,12 +359,7 @@ bool Vehicle::AddPassenger(Unit* unit, int8 seatId) return false; if (!seat->second.IsEmpty()) - { - if (Unit* passenger = ObjectAccessor::GetUnit(*GetBase(), seat->second.Passenger.Guid)) - passenger->ExitVehicle(); - - seat->second.Passenger.Guid.Clear(); - } + return false; ASSERT(seat->second.IsEmpty()); } From aed42e7b2474c7e826c0ae7fdd8793e8bdaa0eff Mon Sep 17 00:00:00 2001 From: EricksOliveira Date: Tue, 16 Jun 2026 19:50:12 +0000 Subject: [PATCH 60/80] fix(DB): aggro nearby Ancient Drakkari when looting Canopic Jar (quest It Takes Guts...) (#25840) Co-authored-by: Ludwig Co-authored-by: Claude Opus 4.8 (1M context) --- .../rev_1778784565223196100.sql | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 data/sql/updates/pending_db_world/rev_1778784565223196100.sql diff --git a/data/sql/updates/pending_db_world/rev_1778784565223196100.sql b/data/sql/updates/pending_db_world/rev_1778784565223196100.sql new file mode 100644 index 000000000..7272cc693 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1778784565223196100.sql @@ -0,0 +1,28 @@ +-- +UPDATE `spell_dbc` SET `RangeIndex`= 12, `ProcChance` = 101, `Effect_1` = 140, `ImplicitTargetA_1` = 25, `EffectTriggerSpell_1` = 47681 WHERE `Id` = 47680; + +DELETE FROM `conditions` WHERE `SourceTypeOrReferenceId` = 13 AND `SourceEntry` = 47681; +INSERT INTO `conditions` (`SourceTypeOrReferenceId`,`SourceGroup`,`SourceEntry`,`SourceId`,`ElseGroup`,`ConditionTypeOrReference`,`ConditionTarget`,`ConditionValue1`,`ConditionValue2`,`ConditionValue3`,`NegativeCondition`,`ErrorType`,`ErrorTextId`,`ScriptName`,`Comment`) VALUES +(13,1,47681,0,0,31,0,3,26811,0,0,0,0,'','Group 0: Spell \'Aggro Ancient Drakkari\' (Effect 0) targets creature \'Ancient Drakkari Warmonger\''), +(13,1,47681,0,1,31,0,3,26812,0,0,0,0,'','Group 1: Spell \'Aggro Ancient Drakkari\' (Effect 0) targets creature \'Ancient Drakkari Soothsayer\''); + +DELETE FROM `creature_text` WHERE `CreatureID` IN (26811,26812) AND `GroupID` = 1; +INSERT INTO `creature_text` (`CreatureID`, `GroupID`, `ID`, `Text`, `Type`, `Language`, `Probability`, `Emote`, `Duration`, `Sound`, `BroadcastTextId`, `TextRange`, `comment`) VALUES +(26811,1,0,'You take my heart, now I take yours!',12,0,100,0,0,0,26085,0,'Ancient Drakkari Warmonger'), +(26811,1,1,'Why ya wanna mess wit me innards, mon?',12,0,100,0,0,0,26086,0,'Ancient Drakkari Warmonger'), +(26811,1,2,'Dat me liver you be squeezin, mon!',12,0,100,0,0,0,26087,0,'Ancient Drakkari Warmonger'), +(26811,1,3,'Come back here with me guts, $r!',12,0,100,0,0,0,26088,0,'Ancient Drakkari Warmonger'), + +(26812,1,0,'You take my heart, now I take yours!',12,0,100,0,0,0,26085,0,'Ancient Drakkari Soothsayer'), +(26812,1,1,'Why ya wanna mess wit me innards, mon?',12,0,100,0,0,0,26086,0,'Ancient Drakkari Soothsayer'), +(26812,1,2,'Dat me liver you be squeezin, mon!',12,0,100,0,0,0,26087,0,'Ancient Drakkari Soothsayer'), +(26812,1,3,'Come back here with me guts, $r!',12,0,100,0,0,0,26088,0,'Ancient Drakkari Soothsayer'); + +DELETE FROM `smart_scripts` WHERE `entryorguid` = 26811 AND `source_type` = 0 AND `id` IN (12,13); +DELETE FROM `smart_scripts` WHERE `entryorguid` = 26812 AND `source_type` = 0 AND `id` IN (21,22); +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`,`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 +(26811,0,12,0,8,0,100,0,47681,0,0,0,0,1,1,0,0,0,0,0,7,0,0,0,0,0,0,0,0,'Ancient Drakkari Warmonger - On Spellhit \'Aggro Ancient Drakkari\' - Say Line 1'), +(26811,0,13,0,8,0,100,0,47681,0,0,0,0,49,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,'Ancient Drakkari Warmonger - On Spellhit \'Aggro Ancient Drakkari\' - Start Attacking'), + +(26812,0,21,0,8,0,100,0,47681,0,0,0,0,1,1,0,0,0,0,0,7,0,0,0,0,0,0,0,0,'Ancient Drakkari Soothsayer - On Spellhit \'Aggro Ancient Drakkari\' - Say Line 1'), +(26812,0,22,0,8,0,100,0,47681,0,0,0,0,49,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,'Ancient Drakkari Soothsayer - On Spellhit \'Aggro Ancient Drakkari\' - Start Attacking'); From b3787bd3b7688febfbf771209cf7ff04a218fdb8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 16 Jun 2026 19:52:44 +0000 Subject: [PATCH 61/80] chore(DB): import pending files Referenced commit(s): aed42e7b2474c7e826c0ae7fdd8793e8bdaa0eff --- .../rev_1778784565223196100.sql => db_world/2026_06_16_07.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1778784565223196100.sql => db_world/2026_06_16_07.sql} (98%) diff --git a/data/sql/updates/pending_db_world/rev_1778784565223196100.sql b/data/sql/updates/db_world/2026_06_16_07.sql similarity index 98% rename from data/sql/updates/pending_db_world/rev_1778784565223196100.sql rename to data/sql/updates/db_world/2026_06_16_07.sql index 7272cc693..fad09efd6 100644 --- a/data/sql/updates/pending_db_world/rev_1778784565223196100.sql +++ b/data/sql/updates/db_world/2026_06_16_07.sql @@ -1,3 +1,4 @@ +-- DB update 2026_06_16_06 -> 2026_06_16_07 -- UPDATE `spell_dbc` SET `RangeIndex`= 12, `ProcChance` = 101, `Effect_1` = 140, `ImplicitTargetA_1` = 25, `EffectTriggerSpell_1` = 47681 WHERE `Id` = 47680; From 72fbcd35d7b507f48a1ce38d79cb18a5a9262550 Mon Sep 17 00:00:00 2001 From: sogladev Date: Tue, 16 Jun 2026 21:58:09 +0200 Subject: [PATCH 62/80] fix(Scripts/Ulduar): XT002 Gravity Bomb no longer spawns mobs (#26237) --- src/server/scripts/Northrend/Ulduar/Ulduar/boss_xt002.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_xt002.cpp b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_xt002.cpp index 80690a7f6..d6260a114 100644 --- a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_xt002.cpp +++ b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_xt002.cpp @@ -50,7 +50,7 @@ enum XT002Spells // VOID ZONE SPELL_VOID_ZONE_SUMMON = 64203, - SPELL_VOID_ZONE_DAMAGE = 46262, + SPELL_VOID_ZONE_DAMAGE = 64208, // SPARK SPELL_SPARK_SUMMON = 64210, From 98e7a05caa68df751cf16354eecbb1baa7e63bdf Mon Sep 17 00:00:00 2001 From: Christopher Mckerracher Date: Tue, 16 Jun 2026 16:06:56 -0400 Subject: [PATCH 63/80] fix(Core/Spells): Restrict percent stat aura target (#26198) Co-authored-by: QAston --- .../game/Spells/Auras/SpellAuraEffects.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/server/game/Spells/Auras/SpellAuraEffects.cpp b/src/server/game/Spells/Auras/SpellAuraEffects.cpp index 126afaf3b..9c4fa8fe8 100644 --- a/src/server/game/Spells/Auras/SpellAuraEffects.cpp +++ b/src/server/game/Spells/Auras/SpellAuraEffects.cpp @@ -4246,15 +4246,18 @@ void AuraEffect::HandleModPercentStat(AuraApplication const* aurApp, uint8 mode, for (int32 i = STAT_STRENGTH; i < MAX_STATS; ++i) { - if (apply) - target->ApplyStatPctModifier(UnitMods(UNIT_MOD_STAT_START + i), BASE_PCT, float(GetAmount())); - else + if (GetMiscValue() == i || GetMiscValue() == -1) { - float amount = target->GetTotalAuraMultiplier(SPELL_AURA_MOD_PERCENT_STAT, [i](AuraEffect const* aurEff) + if (apply) + target->ApplyStatPctModifier(UnitMods(UNIT_MOD_STAT_START + i), BASE_PCT, float(GetAmount())); + else { - return (aurEff->GetMiscValue() == i || aurEff->GetMiscValue() == -1); - }); - target->SetStatPctModifier(UnitMods(UNIT_MOD_STAT_START + i), BASE_PCT, amount); + float amount = target->GetTotalAuraMultiplier(SPELL_AURA_MOD_PERCENT_STAT, [i](AuraEffect const* aurEff) + { + return (aurEff->GetMiscValue() == i || aurEff->GetMiscValue() == -1); + }); + target->SetStatPctModifier(UnitMods(UNIT_MOD_STAT_START + i), BASE_PCT, amount); + } } } } From 8ba0c75e1b94e43cf18d97760d2cfa0dbc707bc2 Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Tue, 16 Jun 2026 22:17:24 -0300 Subject: [PATCH 64/80] fix(Scripts/Ulduar): Razorscale rebuilds turrets in correct left-to-right order (#26243) Co-authored-by: Claude Opus 4.8 --- src/server/scripts/Northrend/Ulduar/Ulduar/boss_razorscale.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_razorscale.cpp b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_razorscale.cpp index 632a1e88b..02e774ce5 100644 --- a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_razorscale.cpp +++ b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_razorscale.cpp @@ -819,6 +819,8 @@ struct npc_ulduar_expedition_engineer : public NullCreatureAI std::list hfsList; me->GetCreaturesWithEntryInRange(hfsList, 300.0f, NPC_HARPOON_FIRE_STATE); + // Rebuild the turrets left-to-right (1 -> 4) instead of in grid/spawn order + hfsList.sort([](Creature const* a, Creature const* b) { return a->GetPositionX() < b->GetPositionX(); }); for (Creature* fs : hfsList) if (!fs->AI()->GetData(2)) { From 158caddf9189f6c4bd0ec073d0100ee9d914c436 Mon Sep 17 00:00:00 2001 From: mpfans Date: Wed, 17 Jun 2026 12:06:33 +0800 Subject: [PATCH 65/80] fix(Core/AuctionHouse): Fix SendAuctionSuccessfulMail log format (#25484) Co-authored-by: polly Co-authored-by: Ludwig Co-authored-by: Claude Opus 4.8 --- src/server/game/AuctionHouse/AuctionHouseMgr.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/server/game/AuctionHouse/AuctionHouseMgr.cpp b/src/server/game/AuctionHouse/AuctionHouseMgr.cpp index 481847d9d..541b1fee2 100644 --- a/src/server/game/AuctionHouse/AuctionHouseMgr.cpp +++ b/src/server/game/AuctionHouse/AuctionHouseMgr.cpp @@ -216,9 +216,11 @@ void AuctionHouseMgr::SendAuctionSuccessfulMail(AuctionEntry* auction, Character .AddMoney(profit) .SendMailTo(trans, MailReceiver(owner, auction->owner.GetCounter()), auction, MAIL_CHECK_MASK_COPIED, sWorld->getIntConfig(CONFIG_MAIL_DELIVERY_DELAY)); - LOG_INFO("entities.player.auctionhouse", "AuctionHouse: Auction #{} sold: Seller {} (GUID: {}), Buyer: {} (GUID: {}), Item (Entry: {}) x{}, Sale Price: {} copper, Profit: {} copper (cut: {} copper)", - auction->Id, owner ? owner->GetName() : "offline", auction->owner.GetCounter(), - auction->bidder.GetCounter(), auction->item_template, auction->itemCount, + CharacterCacheEntry const* sellerCache = sCharacterCache->GetCharacterCacheByGuid(auction->owner); + CharacterCacheEntry const* bidderCache = sCharacterCache->GetCharacterCacheByGuid(auction->bidder); + LOG_INFO("entities.player.auctionhouse", "AuctionHouse: Auction #{} sold: Seller {} (AccountID: {}, GUID: {}), Buyer: {} (AccountID: {}, GUID: {}), Item (Entry: {}) x{}, Sale Price: {} copper, Profit: {} copper (cut: {} copper)", + auction->Id, sellerCache ? sellerCache->Name : (owner ? owner->GetName() : "offline"), sellerCache ? sellerCache->AccountId : owner_accId, auction->owner.GetCounter(), + bidderCache ? bidderCache->Name : "unknown", bidderCache ? bidderCache->AccountId : 0, auction->bidder.GetCounter(), auction->item_template, auction->itemCount, auction->bid, profit, auction->GetAuctionCut()); if (auction->bid >= 500 * GOLD) From 5efc00e0ef59b626a02f62676f283abe83a04ca9 Mon Sep 17 00:00:00 2001 From: Lilliandris <144048412+lilliandris@users.noreply.github.com> Date: Wed, 17 Jun 2026 23:51:07 +0200 Subject: [PATCH 66/80] fix(DB/Spells): Garwal correctly invisible until quest Alpha Worg is taken (#26246) --- .../updates/pending_db_world/rev_1781720099132250410.sql | 6 ++++++ src/server/game/Spells/SpellMgr.cpp | 1 + 2 files changed, 7 insertions(+) create mode 100644 data/sql/updates/pending_db_world/rev_1781720099132250410.sql diff --git a/data/sql/updates/pending_db_world/rev_1781720099132250410.sql b/data/sql/updates/pending_db_world/rev_1781720099132250410.sql new file mode 100644 index 000000000..03ab13ec2 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1781720099132250410.sql @@ -0,0 +1,6 @@ +-- Garwal +UPDATE `creature_template` SET `faction` = 1971 WHERE (`entry` = 24277); + +-- 43062 - Garwal's Invisibility +UPDATE `spell_dbc` SET `Effect_1` = 6, `EffectBasePoints_1` = 999, `EffectAura_1` = 18, `EffectMiscValue_1` = 8, `ImplicitTargetA_1` = 1 +WHERE (`ID` = 43062); diff --git a/src/server/game/Spells/SpellMgr.cpp b/src/server/game/Spells/SpellMgr.cpp index 69f16604a..987ffd1d0 100644 --- a/src/server/game/Spells/SpellMgr.cpp +++ b/src/server/game/Spells/SpellMgr.cpp @@ -3277,6 +3277,7 @@ void SpellMgr::LoadSpellInfoCustomAttributes() case 44801: // Spectral Invisibility (Kalecgos, SWP) case 46021: // Spectral Realm (SWP) case 52951: // Chapel Invisibility (DK starting zone) + case 43062: // Alpha Worg: Garwal's Invisibility break; default: spellInfo->AuraInterruptFlags |= AURA_INTERRUPT_FLAG_CAST; From 252ed0fecf9d07db02b09ef3cfe211ad16efa21e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 17 Jun 2026 21:54:56 +0000 Subject: [PATCH 67/80] chore(DB): import pending files Referenced commit(s): 5efc00e0ef59b626a02f62676f283abe83a04ca9 --- .../rev_1781720099132250410.sql => db_world/2026_06_17_00.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1781720099132250410.sql => db_world/2026_06_17_00.sql} (86%) diff --git a/data/sql/updates/pending_db_world/rev_1781720099132250410.sql b/data/sql/updates/db_world/2026_06_17_00.sql similarity index 86% rename from data/sql/updates/pending_db_world/rev_1781720099132250410.sql rename to data/sql/updates/db_world/2026_06_17_00.sql index 03ab13ec2..32ff13819 100644 --- a/data/sql/updates/pending_db_world/rev_1781720099132250410.sql +++ b/data/sql/updates/db_world/2026_06_17_00.sql @@ -1,3 +1,4 @@ +-- DB update 2026_06_16_07 -> 2026_06_17_00 -- Garwal UPDATE `creature_template` SET `faction` = 1971 WHERE (`entry` = 24277); From 3596fc11f4dc0660a4950a2f125ff7a8716ced16 Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Wed, 17 Jun 2026 21:26:05 -0300 Subject: [PATCH 68/80] fix(Scripts/Ulduar): correct Ignis Iron Construct Brittle shatter threshold (#26260) Co-authored-by: Claude Opus 4.8 --- .../rev_1781737630533813500.sql | 8 +++ .../Northrend/Ulduar/Ulduar/boss_ignis.cpp | 68 ++++++++++++++----- 2 files changed, 59 insertions(+), 17 deletions(-) create mode 100644 data/sql/updates/pending_db_world/rev_1781737630533813500.sql diff --git a/data/sql/updates/pending_db_world/rev_1781737630533813500.sql b/data/sql/updates/pending_db_world/rev_1781737630533813500.sql new file mode 100644 index 000000000..09d85aaae --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1781737630533813500.sql @@ -0,0 +1,8 @@ +-- +-- Ignis the Furnace Master: bind the Brittle aura proc script (spell_ignis_brittle_aura) +-- to both raid difficulty variants so Iron Constructs shatter at the correct, +-- tooltip-accurate damage threshold (62382 = 10m/5000, 67114 = 25m/3000). +DELETE FROM `spell_script_names` WHERE `spell_id` IN (62382, 67114); +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(62382, 'spell_ignis_brittle_aura'), +(67114, 'spell_ignis_brittle_aura'); diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_ignis.cpp b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_ignis.cpp index e0756cbd9..1ac32a60c 100644 --- a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_ignis.cpp +++ b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_ignis.cpp @@ -77,6 +77,16 @@ enum eEvents EVENT_GRAB, }; +enum IgnisActions +{ + ACTION_CONSTRUCT_SHATTERED = 0, +}; + +enum IgnisData +{ + DATA_SHATTERED = 0, +}; + struct npc_ulduar_iron_construct : public ScriptedAI { npc_ulduar_iron_construct(Creature* pCreature) : ScriptedAI(pCreature) @@ -130,19 +140,6 @@ struct npc_ulduar_iron_construct : public ScriptedAI } } - void DamageTaken(Unit* attacker, uint32& damage, DamageEffectType, SpellSchoolMask) override - { - if (damage >= RAID_MODE(3000U, 5000U) && me->GetAura(sSpellMgr->GetSpellIdForDifficulty(SPELL_BRITTLE, me))) - { - me->CastSpell(me, SPELL_SHATTER, true); - Unit::Kill(attacker, me); - - if (InstanceScript* instance = me->GetInstanceScript()) - if (Creature* ignis = instance->GetCreature(BOSS_IGNIS)) - ignis->AI()->SetData(1337, 0); - } - } - void JustDied(Unit* /*killer*/) override { if (InstanceScript* instance = me->GetInstanceScript()) @@ -233,9 +230,9 @@ struct boss_ignis : public BossAI } } - void SetData(uint32 id, uint32 /*value*/) override + void DoAction(int32 action) override { - if (id == 1337) + if (action == ACTION_CONSTRUCT_SHATTERED) { if (lastShatterMSTime) if (getMSTimeDiff(lastShatterMSTime, GameTime::GetGameTimeMS().count()) <= 5000) @@ -247,7 +244,7 @@ struct boss_ignis : public BossAI uint32 GetData(uint32 id) const override { - if (id == 1337) + if (id == DATA_SHATTERED) return (bShattered ? 1 : 0); return 0; } @@ -498,6 +495,42 @@ class spell_ignis_slag_pot_aura : public AuraScript } }; +// 62382, 67114 - Brittle +class spell_ignis_brittle_aura : public AuraScript +{ + PrepareAuraScript(spell_ignis_brittle_aura); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_SHATTER }); + } + + bool CheckProc(ProcEventInfo& eventInfo) + { + DamageInfo* damageInfo = eventInfo.GetDamageInfo(); + return damageInfo && damageInfo->GetDamage() >= (GetId() == SPELL_BRITTLE ? 5000u : 3000u); + } + + void HandleProc(ProcEventInfo& eventInfo) + { + Unit* construct = GetTarget(); + construct->CastSpell(construct, SPELL_SHATTER, true); + + Unit* attacker = eventInfo.GetActor(); + Unit::Kill(attacker ? attacker : construct, construct); + + if (InstanceScript* instance = construct->GetInstanceScript()) + if (Creature* ignis = instance->GetCreature(BOSS_IGNIS)) + ignis->AI()->DoAction(ACTION_CONSTRUCT_SHATTERED); + } + + void Register() override + { + DoCheckProc += AuraCheckProcFn(spell_ignis_brittle_aura::CheckProc); + OnProc += AuraProcFn(spell_ignis_brittle_aura::HandleProc); + } +}; + class achievement_ignis_shattered : public AchievementCriteriaScript { public: @@ -507,7 +540,7 @@ public: { if (!target || !target->IsCreature()) return false; - return !!target->ToCreature()->AI()->GetData(1337); + return !!target->ToCreature()->AI()->GetData(DATA_SHATTERED); } }; @@ -518,5 +551,6 @@ void AddSC_boss_ignis() RegisterSpellScript(spell_ignis_scorch_aura); RegisterSpellScript(spell_ignis_grab_initial); RegisterSpellScript(spell_ignis_slag_pot_aura); + RegisterSpellScript(spell_ignis_brittle_aura); new achievement_ignis_shattered(); } From 32bf232bf8219837d6c5b71e1d90fd5faea85f58 Mon Sep 17 00:00:00 2001 From: sogladev Date: Thu, 18 Jun 2026 02:26:37 +0200 Subject: [PATCH 69/80] fix(Core/Unit): Ignore vehicle exit position LoS checks for abs coords (#26258) --- src/server/game/Entities/Unit/Unit.cpp | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index 3cbd7c241..4b355cd7c 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -15777,15 +15777,23 @@ void Unit::_ExitVehicle(Position const* exitPosition) if (seatAddon) { if (seatAddon->ExitParameter == VehicleExitParameters::VehicleExitParamOffset) + { pos.RelocateOffset({ seatAddon->ExitParameterX, seatAddon->ExitParameterY, seatAddon->ExitParameterZ, seatAddon->ExitParameterO }); + + bool isInLoS = GetMap()->isInLineOfSight(GetPositionX(), GetPositionY(), GetPositionZ(), pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), GetPhaseMask(), LINEOFSIGHT_ALL_CHECKS, VMAP::ModelIgnoreFlags::Nothing); + float floorZ = GetMapHeight(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ()); + + if (!isInLoS || pos.GetPositionZ() < floorZ) + pos = vehicleBase->GetPosition(); + } else if (seatAddon->ExitParameter == VehicleExitParameters::VehicleExitParamDest) + { pos.Relocate({ seatAddon->ExitParameterX, seatAddon->ExitParameterY, seatAddon->ExitParameterZ, seatAddon->ExitParameterO }); - bool isInLoS = GetMap()->isInLineOfSight(GetPositionX(), GetPositionY(), GetPositionZ(), pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), GetPhaseMask(), LINEOFSIGHT_ALL_CHECKS, VMAP::ModelIgnoreFlags::Nothing); - float floorZ = GetMapHeight(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ()); - - if (!isInLoS || pos.GetPositionZ() < floorZ) - pos = vehicleBase->GetPosition(); + float floorZ = GetMapHeight(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ()); + if (pos.GetPositionZ() < floorZ) + pos.m_positionZ = floorZ + 0.1f; + } } } From 61c6cc2b6442612b533f8275a96483b2a20e6cb2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 18 Jun 2026 00:27:19 +0000 Subject: [PATCH 70/80] chore(DB): import pending files Referenced commit(s): 3596fc11f4dc0660a4950a2f125ff7a8716ced16 --- .../rev_1781737630533813500.sql => db_world/2026_06_18_00.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1781737630533813500.sql => db_world/2026_06_18_00.sql} (91%) diff --git a/data/sql/updates/pending_db_world/rev_1781737630533813500.sql b/data/sql/updates/db_world/2026_06_18_00.sql similarity index 91% rename from data/sql/updates/pending_db_world/rev_1781737630533813500.sql rename to data/sql/updates/db_world/2026_06_18_00.sql index 09d85aaae..413a1a717 100644 --- a/data/sql/updates/pending_db_world/rev_1781737630533813500.sql +++ b/data/sql/updates/db_world/2026_06_18_00.sql @@ -1,3 +1,4 @@ +-- DB update 2026_06_17_00 -> 2026_06_18_00 -- -- Ignis the Furnace Master: bind the Brittle aura proc script (spell_ignis_brittle_aura) -- to both raid difficulty variants so Iron Constructs shatter at the correct, From 5025594ad012a68a7cd1a825b32cdf581ae1944d Mon Sep 17 00:00:00 2001 From: sogladev Date: Thu, 18 Jun 2026 02:28:28 +0200 Subject: [PATCH 71/80] fix(Core/LFG): resolve multi-role masks in-place during rolecheck (#26125) --- src/server/game/DungeonFinding/LFGMgr.cpp | 14 +++++++------- src/server/game/Groups/Group.cpp | 4 ++-- src/server/game/Groups/Group.h | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/server/game/DungeonFinding/LFGMgr.cpp b/src/server/game/DungeonFinding/LFGMgr.cpp index a948c6bf5..647284322 100644 --- a/src/server/game/DungeonFinding/LFGMgr.cpp +++ b/src/server/game/DungeonFinding/LFGMgr.cpp @@ -1504,7 +1504,6 @@ namespace lfg if (!gguid) return; - LfgRolesMap check_roles; LfgRoleCheckContainer::iterator itRoleCheck = RoleChecksStore.find(gguid); if (itRoleCheck == RoleChecksStore.end()) return; @@ -1527,9 +1526,7 @@ namespace lfg if (itRoles == roleCheck.roles.end()) { - // use temporal var to check roles, CheckGroupRoles modifies the roles - check_roles = roleCheck.roles; - roleCheck.state = CheckGroupRoles(check_roles) ? LFG_ROLECHECK_FINISHED : LFG_ROLECHECK_WRONG_ROLES; + roleCheck.state = CheckGroupRoles(roleCheck.roles) ? LFG_ROLECHECK_FINISHED : LFG_ROLECHECK_WRONG_ROLES; } } @@ -1798,14 +1795,17 @@ namespace lfg ObjectGuid gguid = grp->GetGUID(); SetState(gguid, LFG_STATE_PROPOSAL); sGroupMgr->AddGroup(grp); + grp->SetLfgRoles(pguid, proposal.players.find(pguid)->second.role); } else if (group != grp) { if (!grp->IsFull()) - grp->AddMember(player); + grp->AddMember(player, proposal.players.find(pguid)->second.role); + } + else + { + grp->SetLfgRoles(pguid, proposal.players.find(pguid)->second.role); } - - grp->SetLfgRoles(pguid, proposal.players.find(pguid)->second.role); } // pussywizard: crashfix, group wasn't created when iterating players (no player found by guid), proposal is deleted by the calling function diff --git a/src/server/game/Groups/Group.cpp b/src/server/game/Groups/Group.cpp index ee1b94fe9..f1c3010c6 100644 --- a/src/server/game/Groups/Group.cpp +++ b/src/server/game/Groups/Group.cpp @@ -421,7 +421,7 @@ Player* Group::GetInvited(const std::string& name) const return nullptr; } -bool Group::AddMember(Player* player) +bool Group::AddMember(Player* player, uint8 roles /* = 0 */) { if (!player) return false; @@ -449,7 +449,7 @@ bool Group::AddMember(Player* player) member.name = player->GetName(); member.group = subGroup; member.flags = 0; - member.roles = 0; + member.roles = roles; m_memberSlots.push_back(member); if (!isBGGroup() && !isBFGroup()) diff --git a/src/server/game/Groups/Group.h b/src/server/game/Groups/Group.h index 3b1bec04b..23d6d6be8 100644 --- a/src/server/game/Groups/Group.h +++ b/src/server/game/Groups/Group.h @@ -204,7 +204,7 @@ public: void RemoveInvite(Player* player); void RemoveAllInvites(); bool AddLeaderInvite(Player* player); - bool AddMember(Player* player); + bool AddMember(Player* player, uint8 roles = 0); bool RemoveMember(ObjectGuid guid, const RemoveMethod& method = GROUP_REMOVEMETHOD_DEFAULT, ObjectGuid kicker = ObjectGuid::Empty, const char* reason = nullptr); void ChangeLeader(ObjectGuid guid); void SetLootMethod(LootMethod method); From 723b24ce9ade039d32e518cc94582fe0253ed306 Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Wed, 17 Jun 2026 21:28:56 -0300 Subject: [PATCH 72/80] fix(Scripts/Ulduar): Salvaged Chopper can grab pyrite during Flame Leviathan (#26244) Co-authored-by: Claude Opus 4.8 --- .../rev_1781653877222108900.sql | 12 ++++++ .../Ulduar/Ulduar/boss_flame_leviathan.cpp | 43 +++++++++++++------ 2 files changed, 42 insertions(+), 13 deletions(-) create mode 100644 data/sql/updates/pending_db_world/rev_1781653877222108900.sql diff --git a/data/sql/updates/pending_db_world/rev_1781653877222108900.sql b/data/sql/updates/pending_db_world/rev_1781653877222108900.sql new file mode 100644 index 000000000..802858190 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1781653877222108900.sql @@ -0,0 +1,12 @@ +-- Ulduar: Flame Leviathan - give the Salvaged Chopper its "Grab Pyrite" hook ability (spell 67372 -> 67387). +-- Mirrors the Salvaged Demolisher's "Grab Crate" (62479 -> 62482). + +-- Add "Grab Pyrite" (67372) to the Salvaged Chopper (33062) vehicle action bar. +DELETE FROM `creature_template_spell` WHERE `CreatureID`=33062 AND `Index`=4; +INSERT INTO `creature_template_spell` (`CreatureID`, `Index`, `Spell`, `VerifiedBuild`) VALUES +(33062, 4, 67372, 0); + +-- Bind the chopper's "Grab Crate" (67387, triggered by 67372) to the existing grab-pyrite script. +DELETE FROM `spell_script_names` WHERE `spell_id`=67387 AND `ScriptName`='spell_vehicle_grab_pyrite'; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(67387, 'spell_vehicle_grab_pyrite'); diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_flame_leviathan.cpp b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_flame_leviathan.cpp index 610dce8ba..f763c60a4 100644 --- a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_flame_leviathan.cpp +++ b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_flame_leviathan.cpp @@ -1445,22 +1445,39 @@ class spell_vehicle_grab_pyrite : public SpellScript void HandleScript(SpellEffIndex /*effIndex*/) { - if (Unit* target = GetHitUnit()) - if (Unit* seat = GetCaster()->GetVehicleBase()) + Unit* target = GetHitUnit(); + if (!target) + return; + + // The grabbing vehicle: the demolisher's mechanic seat, or the chopper itself. + Unit* seat = GetCaster()->GetVehicleBase(); + if (!seat) + return; + + if (Vehicle* vehicle = seat->GetVehicleKit()) + if (Unit* passenger = vehicle->GetPassenger(1)) { - if (Vehicle* vehicle = seat->GetVehicleKit()) - if (Unit* pyrite = vehicle->GetPassenger(1)) - pyrite->ExitVehicle(); + // On the chopper the rear seat may carry a player; never eject them to grab. + if (passenger->IsPlayer()) + return; - if (Unit* parent = seat->GetVehicleBase()) - { - GetCaster()->CastSpell(parent, SPELL_ADD_PYRITE, true); - target->CastSpell(seat, GetEffectValue()); - - if (target->IsCreature()) - target->ToCreature()->DespawnOrUnsummon(1300ms); - } + passenger->ExitVehicle(); } + + if (Unit* parent = seat->GetVehicleBase()) + { + // Demolisher: the seat is mounted on a parent vehicle that the pyrite fuels. + GetCaster()->CastSpell(parent, SPELL_ADD_PYRITE, true); + target->CastSpell(seat, GetEffectValue()); + + if (target->IsCreature()) + target->ToCreature()->DespawnOrUnsummon(1300ms); + } + else + { + // Chopper: load the crate into the rear seat so it can be ferried to other vehicles. + target->CastSpell(seat, GetEffectValue()); + } } void Register() override From 0a8612b28a4401976cd96c59e2443c1b16fd2dbd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 18 Jun 2026 00:29:42 +0000 Subject: [PATCH 73/80] chore(DB): import pending files Referenced commit(s): 5025594ad012a68a7cd1a825b32cdf581ae1944d --- .../rev_1781653877222108900.sql => db_world/2026_06_18_01.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1781653877222108900.sql => db_world/2026_06_18_01.sql} (94%) diff --git a/data/sql/updates/pending_db_world/rev_1781653877222108900.sql b/data/sql/updates/db_world/2026_06_18_01.sql similarity index 94% rename from data/sql/updates/pending_db_world/rev_1781653877222108900.sql rename to data/sql/updates/db_world/2026_06_18_01.sql index 802858190..07780b57d 100644 --- a/data/sql/updates/pending_db_world/rev_1781653877222108900.sql +++ b/data/sql/updates/db_world/2026_06_18_01.sql @@ -1,3 +1,4 @@ +-- DB update 2026_06_18_00 -> 2026_06_18_01 -- Ulduar: Flame Leviathan - give the Salvaged Chopper its "Grab Pyrite" hook ability (spell 67372 -> 67387). -- Mirrors the Salvaged Demolisher's "Grab Crate" (62479 -> 62482). From aa08e113d4c18454d67e909216acb964a0714407 Mon Sep 17 00:00:00 2001 From: Xepic90 Date: Wed, 17 Jun 2026 20:40:30 -0400 Subject: [PATCH 74/80] fix(DB/Conditions): Brann Bronzebeard Archivum event condition uses wrong INSTANCE_INFO type (#26212) --- .../2026_06_15_00_fix_brann_archivum_condition.sql | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 data/sql/updates/pending_db_world/2026_06_15_00_fix_brann_archivum_condition.sql diff --git a/data/sql/updates/pending_db_world/2026_06_15_00_fix_brann_archivum_condition.sql b/data/sql/updates/pending_db_world/2026_06_15_00_fix_brann_archivum_condition.sql new file mode 100644 index 000000000..4d8abe73d --- /dev/null +++ b/data/sql/updates/pending_db_world/2026_06_15_00_fix_brann_archivum_condition.sql @@ -0,0 +1,9 @@ +-- DB update 2026_06_11_02 -> 2026_06_15_00 +-- +-- Fix Brann Bronzebeard (33235) SAI condition for Assembly of Iron completion check. +-- ConditionValue3=0 (INSTANCE_INFO_DATA) calls GetData() which has no handler for +-- BOSS_ASSEMBLY_OF_IRON (4) in Ulduar, always returning 0. The correct type is +-- ConditionValue3=2 (INSTANCE_INFO_BOSS_STATE) which calls GetBossState(4) == DONE(3). +DELETE FROM `conditions` WHERE `SourceTypeOrReferenceId` = 22 AND `SourceGroup` = 13 AND `SourceEntry` = 33235; +INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES +(22, 13, 33235, 0, 0, 13, 1, 4, 3, 2, 0, 0, 0, '', 'Execute SAI only if Assembly of Iron Done'); From 8cb229172ac03447130016f046fa6be980d75717 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 18 Jun 2026 00:41:36 +0000 Subject: [PATCH 75/80] chore(DB): import pending files Referenced commit(s): aa08e113d4c18454d67e909216acb964a0714407 --- .../2026_06_18_02.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/2026_06_15_00_fix_brann_archivum_condition.sql => db_world/2026_06_18_02.sql} (95%) diff --git a/data/sql/updates/pending_db_world/2026_06_15_00_fix_brann_archivum_condition.sql b/data/sql/updates/db_world/2026_06_18_02.sql similarity index 95% rename from data/sql/updates/pending_db_world/2026_06_15_00_fix_brann_archivum_condition.sql rename to data/sql/updates/db_world/2026_06_18_02.sql index 4d8abe73d..0b6a30344 100644 --- a/data/sql/updates/pending_db_world/2026_06_15_00_fix_brann_archivum_condition.sql +++ b/data/sql/updates/db_world/2026_06_18_02.sql @@ -1,3 +1,4 @@ +-- DB update 2026_06_18_01 -> 2026_06_18_02 -- DB update 2026_06_11_02 -> 2026_06_15_00 -- -- Fix Brann Bronzebeard (33235) SAI condition for Assembly of Iron completion check. From e6f8d4e2f505c42e6c0f852b7b882c931c63bd34 Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Thu, 18 Jun 2026 04:46:32 -0300 Subject: [PATCH 76/80] fix(DB/Creature): Hodir Flash Freeze is no longer immune to Bleed (#26261) Co-authored-by: Claude Opus 4.8 --- data/sql/updates/pending_db_world/rev_1781744195259833100.sql | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 data/sql/updates/pending_db_world/rev_1781744195259833100.sql diff --git a/data/sql/updates/pending_db_world/rev_1781744195259833100.sql b/data/sql/updates/pending_db_world/rev_1781744195259833100.sql new file mode 100644 index 000000000..0ad0b8f65 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1781744195259833100.sql @@ -0,0 +1,4 @@ +-- +-- Hodir: Flash Freeze should not be Bleed-immune (let Warriors apply Rend). +-- Move to preset -361 (identical to -369 but without the BLEED mechanic). +UPDATE `creature_template` SET `CreatureImmunitiesId` = -361 WHERE `entry` IN (32926, 32938, 33352, 33353); From 164f3c2a564544dbf7e575452956414da8b24833 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 18 Jun 2026 07:47:45 +0000 Subject: [PATCH 77/80] chore(DB): import pending files Referenced commit(s): e6f8d4e2f505c42e6c0f852b7b882c931c63bd34 --- .../rev_1781744195259833100.sql => db_world/2026_06_18_03.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1781744195259833100.sql => db_world/2026_06_18_03.sql} (85%) diff --git a/data/sql/updates/pending_db_world/rev_1781744195259833100.sql b/data/sql/updates/db_world/2026_06_18_03.sql similarity index 85% rename from data/sql/updates/pending_db_world/rev_1781744195259833100.sql rename to data/sql/updates/db_world/2026_06_18_03.sql index 0ad0b8f65..3b3479540 100644 --- a/data/sql/updates/pending_db_world/rev_1781744195259833100.sql +++ b/data/sql/updates/db_world/2026_06_18_03.sql @@ -1,3 +1,4 @@ +-- DB update 2026_06_18_02 -> 2026_06_18_03 -- -- Hodir: Flash Freeze should not be Bleed-immune (let Warriors apply Rend). -- Move to preset -361 (identical to -369 but without the BLEED mechanic). From 3c0f02a21b30b6a25d3a4233479fd670ac9ce748 Mon Sep 17 00:00:00 2001 From: sogladev Date: Thu, 18 Jun 2026 15:01:18 +0200 Subject: [PATCH 78/80] fix(Scripts/Ulduar): Kologarn no longer grips the main tank (#26259) --- .../Northrend/Ulduar/Ulduar/boss_kologarn.cpp | 38 +++---------------- 1 file changed, 5 insertions(+), 33 deletions(-) diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_kologarn.cpp b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_kologarn.cpp index 6a31deef6..2965bc4d4 100644 --- a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_kologarn.cpp +++ b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_kologarn.cpp @@ -702,28 +702,6 @@ struct boss_kologarn_pit_kill_bunny : public NullCreatureAI } }; -// predicate function to select non main tank target -class StoneGripTargetSelector -{ -public: - StoneGripTargetSelector(Creature* me, Unit const* victim) : _me(me), _victim(victim) {} - - bool operator() (WorldObject* target) const - { - if (target == _victim && _me->GetThreatMgr().GetThreatListSize() > 1) - return true; - - if (!target->IsPlayer()) - return true; - - return false; - } - -private: - Creature* _me; - Unit const* _victim; -}; - class spell_ulduar_stone_grip_cast_target : public SpellScript { PrepareSpellScript(spell_ulduar_stone_grip_cast_target); @@ -735,18 +713,12 @@ class spell_ulduar_stone_grip_cast_target : public SpellScript void FilterTargetsInitial(std::list& targets) { - // Remove "main tank" and non-player targets - targets.remove_if(StoneGripTargetSelector(GetCaster()->ToCreature(), GetCaster()->GetVictim())); - // Maximum affected targets per difficulty mode - uint32 maxTargets = GetSpellInfo()->Id == SPELL_STONE_GRIP ? 1 : 3; + if (Unit* victim = GetCaster()->GetVictim()) + targets.remove_if(Acore::ObjectGUIDCheck(victim->GetGUID(), true)); - // Return a random amount of targets based on maxTargets - while (maxTargets < targets.size()) - { - std::list::iterator itr = targets.begin(); - advance(itr, urand(0, targets.size() - 1)); - targets.erase(itr); - } + targets.remove_if(Acore::ObjectTypeIdCheck(TYPEID_PLAYER, false)); + + Acore::Containers::RandomResize(targets, GetSpellInfo()->Id == SPELL_STONE_GRIP ? 1 : 3); } void Register() override From 0c4bb984ae1ab512b6899be0afa9b89232a43ef8 Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Thu, 18 Jun 2026 10:01:38 -0300 Subject: [PATCH 79/80] fix(Scripts/Ulduar): Assembly of Iron no longer revives dead council members on a partial evade (#26262) Co-authored-by: Claude Opus 4.8 --- .../Ulduar/Ulduar/boss_assembly_of_iron.cpp | 45 +++++++++++++++---- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_assembly_of_iron.cpp b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_assembly_of_iron.cpp index e956b3108..7e2eb3f7a 100644 --- a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_assembly_of_iron.cpp +++ b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_assembly_of_iron.cpp @@ -135,6 +135,22 @@ static uint8 CountAliveBosses(InstanceScript* instance) return count; } +// True while any council member other than `me` is still alive and engaged. +// IsEngaged() (not IsInCombat()) survives Brundir's ~16s CombatStop during +// Lightning Tendrils, so a lone survivor's reset cannot revive dead members. +static bool IsAnyAssemblyMemberEngaged(InstanceScript* pInstance, Creature* me) +{ + if (!pInstance || !me) + return false; + + for (uint8 i = 0; i < 3; ++i) + if (Creature* boss = pInstance->GetCreature(DATA_STEELBREAKER + i)) + if (boss != me && boss->IsAlive() && boss->IsEngaged()) + return true; + + return false; +} + bool IsEncounterComplete(InstanceScript* pInstance, Creature* me) { if (!pInstance || !me) @@ -186,12 +202,16 @@ struct boss_steelbreaker : public ScriptedAI void Reset() override { me->SetLootMode(0); - RespawnAssemblyOfIron(pInstance, me); _phase = 0; events.Reset(); - if (pInstance) - pInstance->SetBossState(BOSS_ASSEMBLY, NOT_STARTED); + + if (!IsAnyAssemblyMemberEngaged(pInstance, me)) + { + RespawnAssemblyOfIron(pInstance, me); + if (pInstance) + pInstance->SetBossState(BOSS_ASSEMBLY, NOT_STARTED); + } } void JustReachedHome() override @@ -376,14 +396,17 @@ struct boss_runemaster_molgeim : public ScriptedAI void Reset() override { me->SetLootMode(0); - RespawnAssemblyOfIron(pInstance, me); _phase = 0; events.Reset(); summons.DespawnAll(); - if (pInstance) - pInstance->SetBossState(BOSS_ASSEMBLY, NOT_STARTED); + if (!IsAnyAssemblyMemberEngaged(pInstance, me)) + { + RespawnAssemblyOfIron(pInstance, me); + if (pInstance) + pInstance->SetBossState(BOSS_ASSEMBLY, NOT_STARTED); + } me->m_Events.AddEventAtOffset(new CastRunesEvent(*me), 8s); } @@ -568,7 +591,6 @@ struct boss_stormcaller_brundir : public ScriptedAI { SetInvincibility(false); me->SetLootMode(0); - RespawnAssemblyOfIron(pInstance, me); _channelTimer = 0; _phase = 0; @@ -580,8 +602,13 @@ struct boss_stormcaller_brundir : public ScriptedAI me->SetDisableGravity(false); me->SetRegeneratingHealth(true); me->SetReactState(REACT_AGGRESSIVE); - if (pInstance) - pInstance->SetBossState(BOSS_ASSEMBLY, NOT_STARTED); + + if (!IsAnyAssemblyMemberEngaged(pInstance, me)) + { + RespawnAssemblyOfIron(pInstance, me); + if (pInstance) + pInstance->SetBossState(BOSS_ASSEMBLY, NOT_STARTED); + } } void JustReachedHome() override From 8037e4c719d288a28a411925b6270646101d89ba Mon Sep 17 00:00:00 2001 From: Lilliandris <144048412+lilliandris@users.noreply.github.com> Date: Thu, 18 Jun 2026 22:23:57 +0200 Subject: [PATCH 80/80] chore(Agents): Update max line length from 80 to 120 characters (#26271) --- .coderabbit.yml | 2 +- .github/agents/pr-reviewer.md | 2 +- CLAUDE.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.coderabbit.yml b/.coderabbit.yml index 2a9a50844..506590fb5 100644 --- a/.coderabbit.yml +++ b/.coderabbit.yml @@ -26,7 +26,7 @@ reviews: - path: "**/*.{cpp,h,hpp}" instructions: | AzerothCore C++ conventions (CI enforces these with -Werror; flag violations): - - C++20. 4-space indent, tabs forbidden. UTF-8, LF, max 80 columns, trailing newline. + - C++20. 4-space indent, tabs forbidden. UTF-8, LF, max 120 columns, trailing newline. - Allman braces. No braces around single-line statements. `if (x)`, never `if(x)` or `if ( x )`. - `auto const&` (not `const auto&`); `Type const*` (not `const Type*`). - Use fmt-style `{}` format specifiers, never printf-style `%u`/`%s`. diff --git a/.github/agents/pr-reviewer.md b/.github/agents/pr-reviewer.md index 830c3533f..c685744c7 100644 --- a/.github/agents/pr-reviewer.md +++ b/.github/agents/pr-reviewer.md @@ -24,7 +24,7 @@ Based on CLAUDE.md, always verify: - 4-space indentation for C++ (no tabs) - 2-space indentation for JSON, YAML, shell scripts - UTF-8 encoding, LF line endings -- Max 80 character line length +- Max 120 character line length - No braces around single-line statements - Format variables in output using {} placeholders instead of printf-style format specifiers like %u diff --git a/CLAUDE.md b/CLAUDE.md index 1b50e5561..7e6e9031d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -64,7 +64,7 @@ python apps/codestyle/codestyle-sql.py # SQL (compares to origin/master) Hard rules (also enforced by CI with `-Werror`): -- 4-space indent for C++ (tabs forbidden); 2-space for JSON/YAML/sh/ts/js. UTF-8, LF, max 80 cols, trailing newline. +- 4-space indent for C++ (tabs forbidden); 2-space for JSON/YAML/sh/ts/js. UTF-8, LF, max 120 cols, trailing newline. - Allman braces. No braces around single-line statements. `if (x)` — never `if(x)` or `if ( x )`. - `auto const&` (not `const auto&`); `Type const*` (not `const Type*`). - Use `{}` format specifiers (`fmt`-style), not `%u`/`%s`.