diff --git a/.coderabbit.yml b/.coderabbit.yml new file mode 100644 index 000000000..506590fb5 --- /dev/null +++ b/.coderabbit.yml @@ -0,0 +1,83 @@ +# 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: false + poem: false + review_status: false + collapse_walkthrough: true + sequence_diagrams: false + auto_review: + enabled: 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. + 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 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`. + - 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 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 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`. diff --git a/data/sql/updates/db_world/2026_06_13_00.sql b/data/sql/updates/db_world/2026_06_13_00.sql new file mode 100644 index 000000000..de5f16888 --- /dev/null +++ b/data/sql/updates/db_world/2026_06_13_00.sql @@ -0,0 +1,55 @@ +-- 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 +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/db_world/2026_06_13_01.sql b/data/sql/updates/db_world/2026_06_13_01.sql new file mode 100644 index 000000000..281719790 --- /dev/null +++ b/data/sql/updates/db_world/2026_06_13_01.sql @@ -0,0 +1,60 @@ +-- 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 +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/db_world/2026_06_13_02.sql b/data/sql/updates/db_world/2026_06_13_02.sql new file mode 100644 index 000000000..17a4e99ee --- /dev/null +++ b/data/sql/updates/db_world/2026_06_13_02.sql @@ -0,0 +1,47 @@ +-- 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 + +-- 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/db_world/2026_06_13_03.sql b/data/sql/updates/db_world/2026_06_13_03.sql new file mode 100644 index 000000000..c8d8973bb --- /dev/null +++ b/data/sql/updates/db_world/2026_06_13_03.sql @@ -0,0 +1,50 @@ +-- 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 +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/data/sql/updates/db_world/2026_06_13_04.sql b/data/sql/updates/db_world/2026_06_13_04.sql new file mode 100644 index 000000000..0a1e1bb1c --- /dev/null +++ b/data/sql/updates/db_world/2026_06_13_04.sql @@ -0,0 +1,8 @@ +-- DB update 2026_06_13_03 -> 2026_06_13_04 +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; diff --git a/data/sql/updates/db_world/2026_06_13_05.sql b/data/sql/updates/db_world/2026_06_13_05.sql new file mode 100644 index 000000000..01743a9a2 --- /dev/null +++ b/data/sql/updates/db_world/2026_06_13_05.sql @@ -0,0 +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); diff --git a/data/sql/updates/db_world/2026_06_14_00.sql b/data/sql/updates/db_world/2026_06_14_00.sql new file mode 100644 index 000000000..4e5854a95 --- /dev/null +++ b/data/sql/updates/db_world/2026_06_14_00.sql @@ -0,0 +1,59 @@ +-- DB update 2026_06_13_05 -> 2026_06_14_00 + +-- 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"); diff --git a/data/sql/updates/db_world/2026_06_14_01.sql b/data/sql/updates/db_world/2026_06_14_01.sql new file mode 100644 index 000000000..a7a0158d5 --- /dev/null +++ b/data/sql/updates/db_world/2026_06_14_01.sql @@ -0,0 +1,5 @@ +-- 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; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES (71761, 'spell_mage_deep_freeze_immunity_state'); diff --git a/data/sql/updates/db_world/2026_06_14_02.sql b/data/sql/updates/db_world/2026_06_14_02.sql new file mode 100644 index 000000000..f35eeeb46 --- /dev/null +++ b/data/sql/updates/db_world/2026_06_14_02.sql @@ -0,0 +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); diff --git a/data/sql/updates/db_world/2026_06_15_00.sql b/data/sql/updates/db_world/2026_06_15_00.sql new file mode 100644 index 000000000..f2e1fdd73 --- /dev/null +++ b/data/sql/updates/db_world/2026_06_15_00.sql @@ -0,0 +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); diff --git a/data/sql/updates/db_world/2026_06_15_01.sql b/data/sql/updates/db_world/2026_06_15_01.sql new file mode 100644 index 000000000..8d1f4d3c9 --- /dev/null +++ b/data/sql/updates/db_world/2026_06_15_01.sql @@ -0,0 +1,7 @@ +-- DB update 2026_06_15_00 -> 2026_06_15_01 +-- +-- 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; diff --git a/data/sql/updates/db_world/2026_06_16_00.sql b/data/sql/updates/db_world/2026_06_16_00.sql new file mode 100644 index 000000000..7df594d57 --- /dev/null +++ b/data/sql/updates/db_world/2026_06_16_00.sql @@ -0,0 +1,26 @@ +-- 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', + `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/data/sql/updates/db_world/2026_06_16_01.sql b/data/sql/updates/db_world/2026_06_16_01.sql new file mode 100644 index 000000000..638535742 --- /dev/null +++ b/data/sql/updates/db_world/2026_06_16_01.sql @@ -0,0 +1,12 @@ +-- 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 +-- 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); diff --git a/data/sql/updates/db_world/2026_06_16_02.sql b/data/sql/updates/db_world/2026_06_16_02.sql new file mode 100644 index 000000000..20a722c55 --- /dev/null +++ b/data/sql/updates/db_world/2026_06_16_02.sql @@ -0,0 +1,33 @@ +-- 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 +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'); diff --git a/data/sql/updates/db_world/2026_06_16_03.sql b/data/sql/updates/db_world/2026_06_16_03.sql new file mode 100644 index 000000000..2678af40f --- /dev/null +++ b/data/sql/updates/db_world/2026_06_16_03.sql @@ -0,0 +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`); diff --git a/data/sql/updates/db_world/2026_06_16_04.sql b/data/sql/updates/db_world/2026_06_16_04.sql new file mode 100644 index 000000000..a218a2c25 --- /dev/null +++ b/data/sql/updates/db_world/2026_06_16_04.sql @@ -0,0 +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; diff --git a/data/sql/updates/db_world/2026_06_16_05.sql b/data/sql/updates/db_world/2026_06_16_05.sql new file mode 100644 index 000000000..5328d3449 --- /dev/null +++ b/data/sql/updates/db_world/2026_06_16_05.sql @@ -0,0 +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); diff --git a/data/sql/updates/db_world/2026_06_16_06.sql b/data/sql/updates/db_world/2026_06_16_06.sql new file mode 100644 index 000000000..64f5d08b9 --- /dev/null +++ b/data/sql/updates/db_world/2026_06_16_06.sql @@ -0,0 +1,5 @@ +-- 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`) + VALUES (54404, 0x00000004, 0x00000004, 100, 1); diff --git a/data/sql/updates/db_world/2026_06_16_07.sql b/data/sql/updates/db_world/2026_06_16_07.sql new file mode 100644 index 000000000..fad09efd6 --- /dev/null +++ b/data/sql/updates/db_world/2026_06_16_07.sql @@ -0,0 +1,29 @@ +-- 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; + +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'); diff --git a/data/sql/updates/db_world/2026_06_17_00.sql b/data/sql/updates/db_world/2026_06_17_00.sql new file mode 100644 index 000000000..32ff13819 --- /dev/null +++ b/data/sql/updates/db_world/2026_06_17_00.sql @@ -0,0 +1,7 @@ +-- DB update 2026_06_16_07 -> 2026_06_17_00 +-- 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/data/sql/updates/db_world/2026_06_18_00.sql b/data/sql/updates/db_world/2026_06_18_00.sql new file mode 100644 index 000000000..413a1a717 --- /dev/null +++ b/data/sql/updates/db_world/2026_06_18_00.sql @@ -0,0 +1,9 @@ +-- 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, +-- 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/data/sql/updates/db_world/2026_06_18_01.sql b/data/sql/updates/db_world/2026_06_18_01.sql new file mode 100644 index 000000000..07780b57d --- /dev/null +++ b/data/sql/updates/db_world/2026_06_18_01.sql @@ -0,0 +1,13 @@ +-- 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). + +-- 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/data/sql/updates/db_world/2026_06_18_02.sql b/data/sql/updates/db_world/2026_06_18_02.sql new file mode 100644 index 000000000..0b6a30344 --- /dev/null +++ b/data/sql/updates/db_world/2026_06_18_02.sql @@ -0,0 +1,10 @@ +-- 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. +-- 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'); diff --git a/data/sql/updates/db_world/2026_06_18_03.sql b/data/sql/updates/db_world/2026_06_18_03.sql new file mode 100644 index 000000000..3b3479540 --- /dev/null +++ b/data/sql/updates/db_world/2026_06_18_03.sql @@ -0,0 +1,5 @@ +-- 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). +UPDATE `creature_template` SET `CreatureImmunitiesId` = -361 WHERE `entry` IN (32926, 32938, 33352, 33353); 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 812e59e6a..6f0e0a9f8 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 @@ -109,11 +110,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 +160,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 +286,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 +402,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 +474,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 +490,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 +502,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 +524,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 +561,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 +582,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 +591,6 @@ IPLocationFile = "" # Description: Specifies if IP addresses can be logged to the database # Default: 1 - (Enabled) # 0 - (Disabled) -# AllowLoggingIPAddressesInDatabase = 1 @@ -608,7 +599,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 +607,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 +615,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 @@ -703,27 +680,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 @@ -735,105 +709,105 @@ Logger.time.update=4,Console Server Logger.module=4,Console Server Logger.spells.scripts=2,Console Errors Logger.playerbots=5,Console Playerbots -#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 @@ -861,27 +835,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" @@ -896,7 +861,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 @@ -904,12 +868,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. @@ -928,7 +891,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 @@ -937,9 +900,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) @@ -970,7 +933,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) @@ -1080,14 +1043,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 @@ -1095,7 +1058,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 @@ -1120,7 +1083,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 @@ -1141,7 +1104,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) @@ -1150,7 +1113,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) @@ -1159,7 +1122,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) @@ -1177,9 +1140,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 @@ -1191,9 +1154,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 @@ -1342,7 +1305,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) @@ -1422,9 +1385,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 @@ -1674,11 +1636,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 @@ -1759,6 +1721,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 # @@ -1802,12 +1856,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 @@ -1817,8 +1869,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 @@ -1882,20 +1934,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 @@ -1906,9 +1958,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 @@ -1920,9 +1972,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 @@ -2050,9 +2102,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 = "" @@ -2062,7 +2114,7 @@ PlayerStart.String = "" ################################################################################################### # CHARACTER # -# EnablePlayerSettings +# EnablePlayerSettings # Description: Enables the usage of character specific settings. # Default: 0 - Disabled # 1 - Enabled @@ -2173,9 +2225,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 @@ -2187,29 +2239,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 @@ -2480,9 +2527,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 @@ -2502,7 +2548,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 @@ -2674,9 +2720,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) @@ -2735,26 +2781,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 @@ -2762,7 +2804,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 @@ -2780,10 +2821,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 @@ -2818,6 +2859,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 @@ -2832,18 +2874,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 @@ -2855,13 +2897,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 @@ -2931,7 +2973,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. @@ -3124,7 +3166,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 @@ -3243,7 +3285,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) # @@ -3254,9 +3296,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 @@ -3264,9 +3306,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 @@ -3309,12 +3351,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 @@ -3346,11 +3388,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 @@ -3361,7 +3402,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 @@ -3379,7 +3419,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 @@ -3390,7 +3430,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 @@ -3398,7 +3438,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 @@ -3408,12 +3448,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 @@ -3421,15 +3461,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) @@ -3437,7 +3476,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 @@ -3501,9 +3540,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 @@ -3556,7 +3595,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) @@ -3565,7 +3604,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) @@ -3573,7 +3612,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) @@ -3590,7 +3629,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 @@ -3602,7 +3641,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 @@ -3614,7 +3653,7 @@ FFAPvPTimer = 30 ################################################################################################### # OUTDOORPVP # -# OutdoorPvPCaptureRate +# OutdoorPvPCaptureRate # Description: Specify rate multiplier for outdoor PvP capture points. (e.g. Eastern Plaguelands, Hellfire Peninsula) # Default: 1.0 @@ -3626,54 +3665,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 @@ -3693,9 +3732,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 @@ -3707,7 +3746,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 @@ -3719,7 +3758,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 @@ -3745,7 +3784,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) @@ -3764,7 +3803,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 @@ -3781,7 +3819,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 @@ -3789,7 +3826,6 @@ Battleground.QueueAnnouncer.Timed = 0 # Battleground.QueueAnnouncer.Timer # Description: Set timer for queue announcements # Default: 30000 (30 sec) -# Battleground.QueueAnnouncer.Timer = 30000 @@ -3845,17 +3881,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 @@ -3883,12 +3919,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 @@ -3896,7 +3932,6 @@ Battleground.DisableReadyCheckInBG = 0 # 0 - Battleground.RewardWinnerArenaLast # 5 - Battleground.RewardLoserHonorFirst # 5 - Battleground.RewardLoserHonorLast -# Battleground.RewardWinnerHonorFirst = 30 Battleground.RewardWinnerArenaFirst = 25 @@ -4057,7 +4092,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 @@ -4068,7 +4102,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 @@ -4137,7 +4170,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) @@ -4146,7 +4179,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. @@ -4179,23 +4212,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 @@ -4211,9 +4240,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 @@ -4235,7 +4264,6 @@ AddonChannel = 1 # "normal" chat messages for sending data to other clients. # Default: 1 - (Enabled, Blizzlike) # 0 - (Disabled) -# ChatFakeMessagePreventing = 1 @@ -4245,12 +4273,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 @@ -4260,7 +4286,6 @@ ChatStrictLinkChecking.Severity = 0 # is received. # Default: 0 - (Silently ignore message) # 1 - (Ignore message and kick player) -# ChatStrictLinkChecking.Kick = 0 @@ -4283,7 +4308,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 @@ -4334,10 +4359,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 @@ -4490,10 +4515,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 @@ -4585,14 +4610,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 @@ -4618,14 +4643,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 @@ -4637,28 +4662,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 @@ -4668,55 +4693,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 @@ -4728,24 +4751,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 @@ -4762,8 +4785,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 @@ -4782,8 +4805,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 @@ -4801,7 +4824,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 @@ -4812,7 +4834,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 diff --git a/src/server/database/Database/Implementation/WorldDatabase.cpp b/src/server/database/Database/Implementation/WorldDatabase.cpp index 7d580068b..dfe7b047d 100644 --- a/src/server/database/Database/Implementation/WorldDatabase.cpp +++ b/src/server/database/Database/Implementation/WorldDatabase.cpp @@ -76,19 +76,20 @@ 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); 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/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/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(); 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/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) 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/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) 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); 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); 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/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/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/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index c31d8f2db..36c95bbd9 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; @@ -1852,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); @@ -2041,7 +2043,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 +2063,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 +3171,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 890970734..75da92618 100644 --- a/src/server/game/Entities/Creature/CreatureData.h +++ b/src/server/game/Entities/Creature/CreatureData.h @@ -369,10 +369,9 @@ typedef std::unordered_map EquipmentInfo struct CreatureData : public SpawnData { CreatureData() : SpawnData(SPAWN_TYPE_CREATURE) {} - ObjectGuid::LowType spawnId{ 0 }; // mod_playerbots - 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/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 83ac3598f..f2b3207c7 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(); @@ -11509,6 +11516,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/Player.h b/src/server/game/Entities/Player/Player.h index 83bdb5770..951701082 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -1560,9 +1560,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/Entities/Player/PlayerStorage.cpp b/src/server/game/Entities/Player/PlayerStorage.cpp index 3b80b0e4f..04010c812 100644 --- a/src/server/game/Entities/Player/PlayerStorage.cpp +++ b/src/server/game/Entities/Player/PlayerStorage.cpp @@ -4726,7 +4726,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: diff --git a/src/server/game/Entities/Player/PlayerUpdates.cpp b/src/server/game/Entities/Player/PlayerUpdates.cpp index 4839307f7..35ac03d79 100644 --- a/src/server/game/Entities/Player/PlayerUpdates.cpp +++ b/src/server/game/Entities/Player/PlayerUpdates.cpp @@ -940,6 +940,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/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index ac9ce5570..5912e36eb 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 @@ -9390,7 +9394,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: @@ -15799,15 +15802,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; + } } } 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()); } 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/Groups/Group.cpp b/src/server/game/Groups/Group.cpp index b47c0411f..e6799fec5 100644 --- a/src/server/game/Groups/Group.cpp +++ b/src/server/game/Groups/Group.cpp @@ -428,7 +428,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; @@ -456,7 +456,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 12bddfc0c..ebcc5a6bb 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); diff --git a/src/server/game/Handlers/AuctionHouseHandler.cpp b/src/server/game/Handlers/AuctionHouseHandler.cpp index 78cf20cbd..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]; @@ -279,7 +286,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()); @@ -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 2cce9521b..ce90a1883 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)) { @@ -401,6 +414,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()) { @@ -420,6 +444,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 464a9e06c..ed55395d6 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()); @@ -680,6 +692,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/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/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/Maps/Map.cpp b/src/server/game/Maps/Map.cpp index 5ca2de605..33fa36b9d 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()) 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/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 65da35370..6b74e3705 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; @@ -934,13 +944,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/game/Server/WorldSessionMgr.cpp b/src/server/game/Server/WorldSessionMgr.cpp index a901914e3..00e4d7ac3 100644 --- a/src/server/game/Server/WorldSessionMgr.cpp +++ b/src/server/game/Server/WorldSessionMgr.cpp @@ -329,7 +329,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/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; } diff --git a/src/server/game/Spells/Auras/SpellAuraEffects.cpp b/src/server/game/Spells/Auras/SpellAuraEffects.cpp index a77315c0f..9c4fa8fe8 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) { @@ -4244,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); + } } } } 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 diff --git a/src/server/game/Spells/SpellMgr.cpp b/src/server/game/Spells/SpellMgr.cpp index 43c15c686..987ffd1d0 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)); @@ -3257,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; 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/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 }; 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_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)) 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..0616f12b7 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,8 +730,8 @@ 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, - cData->id2, cData->id3, cData->displayid, cData->displayid, cInfo->faction, + handler->PSendSysMessage(LANG_NPCINFO_CHAR, lowGuid, ObjectGuid::Create(cData->id, lowGuid).ToString(), cData->id, + 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); @@ -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/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, 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/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 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; } 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: 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..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 @@ -316,11 +336,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); @@ -367,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); } @@ -559,7 +591,6 @@ struct boss_stormcaller_brundir : public ScriptedAI { SetInvincibility(false); me->SetLootMode(0); - RespawnAssemblyOfIron(pInstance, me); _channelTimer = 0; _phase = 0; @@ -571,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 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 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: 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(); } 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 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)) { 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, 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 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 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 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); } 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); } }; 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(); 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()); } } }; 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) // ============================================================================ 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)); +} 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