34 Commits
Author SHA1 Message Date
luis 1221562029 Ruination 2026-09-15 23:28:14 -03:00
luis 18ed96f0b5 Hellbent Commander 2026-09-15 22:59:58 -03:00
luis de6731c35d To Hell and Back 2026-09-15 22:35:09 -03:00
luis 452a087df4 lfg list group finder update to check thordekk when get time 2026-09-15 22:29:38 -03:00
luis 167a0f0685 Lfg list update to 12.1 2026-09-15 21:43:58 -03:00
luis a6fa569873 Dominion of Argus 2026-09-15 21:16:25 -03:00
SistemasCPU 9f103d8db3 Core/Conditions: Make CONDITION_SOURCE_GAMEOBJECT_INTERACTION the gate for GO interaction 2026-09-15 20:26:42 +02:00
SistemasCPU c395cf8095 Merge branch 'main' of https://github.com/Thordekk/WowCommunityProject 2026-09-15 20:05:06 +02:00
SistemasCPU ee37d9e24b Core/Conditions: Evaluate CONDITION_SOURCE_GAMEOBJECT_INTERACTION in MeetsInteractCondition 2026-09-15 20:04:56 +02:00
luis 2543c0a138 spell sql 2026-09-15 15:04:10 -03:00
luis 1cc3cc295c Grimoire: Imp Lord 2026-09-15 15:03:31 -03:00
luis 24faed09e9 blighted maw 2026-09-15 14:51:44 -03:00
luis 98b6221faf walock i will upload sql later 2026-09-15 14:33:27 -03:00
SistemasCPU bae6615dfc Merge branch 'main' of https://github.com/Thordekk/WowCommunityProject 2026-09-15 18:53:48 +02:00
SistemasCPU 419e747a1a Core/Conditions: Port CONDITION_SOURCE_GAMEOBJECT_INTERACTION to all gameobject types 2026-09-15 18:53:42 +02:00
luis e679e059c7 Fel Firebolt 2026-09-15 13:48:18 -03:00
luis a69fc40271 Power Siphon 2026-09-15 13:17:19 -03:00
luis c4fcc30587 ++ 2026-09-15 12:50:53 -03:00
luis 8d9799b1c6 Implosion 2026-09-15 12:50:53 -03:00
Céfiro_Caecias 5eb1019716 DB/World: Update Build 69814 2026-09-15 12:18:56 -03:00
Céfiro_Caecias a89a654653 DB/Hotfixes: Update Build 69814 2026-09-15 12:16:18 -03:00
luis c1cf1dd828 typo 2026-09-15 11:45:28 -03:00
luis 2c187ea79f warlock 2026-09-15 11:38:05 -03:00
luis 170331c6d6 Inner Demons 2026-09-15 10:49:16 -03:00
luis 06ec5d3b57 neltharus and sniff 2026-09-15 10:24:50 -03:00
luis 8b0a639883 neltharus 2026-09-15 10:23:30 -03:00
luis 31774f51ba typo 2026-09-15 08:50:47 -03:00
luis cb62057856 SMSG_SET_SHIPMENT_READY_RESPONSE 2026-09-15 08:36:42 -03:00
luis 04cbe672f9 Roll back 2026-09-14 14:58:59 -03:00
Céfiro_Caecias 337b50eb18 Update 2026_09_14_00_world_dberrors.sql 2026-09-14 09:16:29 -03:00
Céfiro_Caecias 07f3f93060 DB/World: DBErrors Fixes 2026-09-14 09:11:13 -03:00
luis eb8e2ebd53 Core/BattlePets: queue status carries the ride ticket before the slot results 2026-09-14 08:42:06 -03:00
luis 5f6adfb702 Delves 2026-09-14 08:37:52 -03:00
luis a81041c96a Scripts/Delves: the companion casts Npc Join Player Party 2026-09-14 08:19:25 -03:00
41 changed files with 5686 additions and 935 deletions
@@ -0,0 +1,206 @@
-- Update Build 69814
UPDATE `achievement` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `achievement_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `adventure_journal` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `adventure_journal_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `anim_kit` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `area_group_member` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `area_table` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `area_table_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `area_trigger_action_set` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `bank_tab` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `battle_pet_species` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `battle_pet_species_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `battle_pet_species_state` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `battlemaster_list` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `battlemaster_list_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `broadcast_text` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `broadcast_text_duration` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `broadcast_text_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `campaign` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `campaign_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `cfg_regions` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `char_titles` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `char_titles_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `chr_customization_choice` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `chr_customization_choice_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `chr_customization_element` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `chr_customization_option` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `chr_customization_option_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `conditional_content_tuning` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `content_tuning` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `content_tuning_x_expected` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `conversation_line` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `creature_display_info` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `creature_display_info_extra` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `creature_label` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `creature_model_data` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `criteria` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `criteria_tree` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `criteria_tree_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `currency_types` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `currency_types_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `curve` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `curve_point` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `dungeon_encounter` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `dungeon_encounter_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `emotes` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `expected_stat_mod` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `faction` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `faction_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `gameobject_display_info` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `gameobject_label` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `gameobjects` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `gameobjects_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `global_curve` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `gossip_npc_option` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `holidays` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `hotfix_blob` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `hotfix_data` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
DELETE FROM `hotfix_data` WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `initiative_milestone` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `initiative_reward` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `initiative_reward_x_milestone` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `initiative_task` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `initiative_x_task` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `item` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `item_appearance` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `item_bonus` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `item_bonus_tree_node` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `item_creation_context` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `item_currency_cost` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `item_effect` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `item_extended_cost` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `item_modified_appearance` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `item_name_description` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `item_name_description_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `item_offset_curve` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `item_scaling_config` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `item_search_name` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `item_search_name_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `item_sparse` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `item_sparse_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `item_spec` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `item_spec_override` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `item_x_bonus_tree` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `item_x_item_effect` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `journal_encounter` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `journal_encounter_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `journal_encounter_section` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `journal_encounter_section_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `lfg_dungeons` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `lfg_dungeons_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `liquid_type` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `location` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `lock` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `mail_template` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `mail_template_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `map` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `map_challenge_mode` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `map_challenge_mode_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `map_difficulty` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `map_difficulty_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `map_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `modified_crafting_item` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `modifier_tree` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `mount` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `mount_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `mount_x_display` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `neighborhood_initiative` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `neighborhood_map` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `neighborhood_plot` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `override_spell_data` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `path` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `path_node` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `path_property` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `perks_activity` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `perks_activity_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `phase` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `phase_x_phase_group` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `player_condition` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `player_condition_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `player_data_element_account` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `player_data_element_character` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `player_data_flag_account` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `pvp_difficulty` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `quest_info` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `quest_info_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `quest_line_x_quest` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `quest_v2` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `room_component_option` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `scenario` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `scenario_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `scenario_step` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `scenario_step_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `scene_script` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `scene_script_text` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `skill_line_ability` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `skill_line_ability_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `sound_kit` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `spell` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `spell_aura_options` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `spell_aura_restrictions` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `spell_cast_times` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `spell_casting_requirements` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `spell_categories` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `spell_category` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `spell_category_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `spell_class_options` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `spell_cooldowns` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `spell_duration` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `spell_effect` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `spell_interrupts` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `spell_item_enchantment` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `spell_item_enchantment_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `spell_label` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `spell_levels` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `spell_misc` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `spell_name` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `spell_name_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `spell_power` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `spell_radius` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `spell_reagents` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `spell_scaling` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `spell_shapeshift` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `spell_target_restrictions` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `spell_visual` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `spell_visual_effect_name` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `spell_visual_kit` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `spell_visual_missile` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `spell_x_spell_visual` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `summon_properties` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `tact_key` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `taxi_nodes` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `taxi_nodes_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `taxi_path` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `trait_cond` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `trait_currency_source` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `trait_currency_source_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `trait_definition` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `trait_definition_effect_points` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `trait_definition_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `trait_node_group` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `trait_node_group_x_trait_cond` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `trait_node_group_x_trait_node` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `trait_tree` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `trait_tree_loadout` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `trait_tree_loadout_entry` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `trait_tree_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `transmog_illusion` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `transmog_set` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `transmog_set_item` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `transmog_set_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `ui_splash_screen` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `ui_splash_screen_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `unit_condition` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `vehicle` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `vehicle_seat` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `vignette` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `vignette_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `wmo_area_table` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `wmo_area_table_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `world_effect` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `world_state_expression` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
@@ -0,0 +1,17 @@
-- ==============
-- DBErrors Fixes
-- ==============
UPDATE `instance_template` SET `script`='instance_the_dread_pit' WHERE `map`=2684;
UPDATE `creature_template` SET `ScriptName`='npc_housing_steward' WHERE `entry` IN (233063,233708);
UPDATE `creature_template` SET `ScriptName`='npc_under_lord_viktis' WHERE `entry`=220158;
DELETE FROM `spell_script_names` WHERE `spell_id`=426756 AND `ScriptName`='spell_dungeon_assistance';
INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES
(426756, 'spell_dungeon_assistance');
DELETE FROM `areatrigger` WHERE `SpawnId`=1029;
INSERT INTO `areatrigger` (`SpawnId`, `AreaTriggerCreatePropertiesId`, `IsCustom`, `MapId`, `SpawnDifficulties`, `PosX`, `PosY`, `PosZ`, `Orientation`, `PhaseUseFlags`, `PhaseId`, `PhaseGroup`, `ScriptName`, `Comment`, `VerifiedBuild`) VALUES
(1029, 1029, 1, 2927, '0', -1101.67, -3554.37, 48.92, 6.2, 1, 0, 0, 'at_catchup_arathi_entry', 'Arathi Entry', 69814);
DELETE FROM `areatrigger_scripts` WHERE `entry`=1612;
@@ -0,0 +1,201 @@
UPDATE `creature_template` SET `npcflag` = 137438953473 WHERE `entry` = 94399; -- Fleet Command Table: GOSSIP | GARRISON_MISSION_NPC (match retail sibling 94398)
DELETE FROM `world_quest_template` WHERE `QuestID` IN (54562 /*Singer*/, 45922 /*Agmozuul*/, 51333 /*Anchors Aweigh!*/, 57588 /*Spirit Drinker*/, 58367 /*Empowered Demolisher*/, 52865 /*Blockade Runner*/, 54363 /*Darkshore Donations: War Resources*/, 51908 /*Nevermore*/, 51683 /*Slash and Burn Tactics*/, 50863 /*Avatar of Xolotal*/, 57335 /*Murloc Mayhem*/, 52325 /*Captured Evil*/, 51120 /*Stef "Marrow" Quin*/, 50490 /*Uroku the Bound*/, 57299 /*Mysterious Sarcophagus*/, 58479 /*Lightning Crashes*/, 50975 /*Azerite Mining*/, 54382 /*Darkshore Donations: Deep Sea Satin*/, 57721 /*Spoils of War*/, 49888 /*Atal'Dazar: A Little Mojo*/, 45927 /*Zagmothar*/, 52794 /*Lizards and Ledgers*/, 57171 /*Goldbough Guardian*/, 58144 /*Battered and Bruised*/, 54392 /*Darkshore Donations: Sand Shifter*/, 57592 /*Executor of N'Zoth*/, 55303 /*The Cycle of Life*/, 59658 /*Express Dominance*/, 58235 /*Anchors of the Black Empire*/, 59680 /*Vesiphone's Vicious Vesper*/, 51100 /*Jumbo Sandsnapper*/, 58476 /*Bringing the Thunder*/, 55793 /*Requisition: More Flotsam*/);
INSERT INTO `world_quest_template` (`QuestID`, `Duration`, `VariableID`, `Value`) VALUES
(54562, 86400, 16876, 1), -- Singer
(45922, 21600, 13014, 1), -- Agmozuul
(51333, 86400, 15267, 2), -- Anchors Aweigh!
(57588, 86400, 18490, 1), -- Spirit Drinker
(58367, 86400, 18527, 1), -- Empowered Demolisher
(52865, 86400, 15940, 1), -- Blockade Runner
(54363, 86400, 16717, 1), -- Darkshore Donations: War Resources
(51908, 86400, 15576, 1), -- Nevermore
(51683, 86400, 15449, 2), -- Slash and Burn Tactics
(50863, 86400, 14952, 1), -- Avatar of Xolotal
(57335, 43200, 18138, 1), -- Murloc Mayhem
(52325, 86400, 15747, 1), -- Captured Evil
(51120, 86400, 15093, 1), -- Stef "Marrow" Quin
(50490, 86400, 14791, 1), -- Uroku the Bound
(57299, 86400, 18125, 1), -- Mysterious Sarcophagus
(58479, 86400, 18542, 1), -- Lightning Crashes
(50975, 86400, 15756, 1), -- Azerite Mining
(54382, 86400, 16782, 1), -- Darkshore Donations: Deep Sea Satin
(57721, 86400, 18540, 4), -- Spoils of War
(49888, 345600, 14496, 3), -- Atal'Dazar: A Little Mojo
(45927, 21600, 13022, 1), -- Zagmothar
(52794, 86400, 15895, 1), -- Lizards and Ledgers
(57171, 86400, 18103, 1), -- Goldbough Guardian
(58144, 86400, 19065, 1), -- Battered and Bruised
(54392, 86400, 16764, 1), -- Darkshore Donations: Sand Shifter
(57592, 86400, 18494, 1), -- Executor of N'Zoth
(55303, 86400, 17261, 1), -- The Cycle of Life
(59658, 21600, 19107, 1), -- Express Dominance
(58235, 86400, 18450, 2), -- Anchors of the Black Empire
(59680, 86400, 19002, 1), -- Vesiphone's Vicious Vesper
(51100, 86400, 15077, 1), -- Jumbo Sandsnapper
(58476, 86400, 18541, 1), -- Bringing the Thunder
(55793, 86400, 17473, 1); -- Requisition: More Flotsam
UPDATE `world_quest_template` SET `VariableID`=14245 WHERE `QuestID`=49091; -- Invasion Point: Val
REPLACE INTO `spell_target_position` (`ID`, `EffectIndex`, `OrderIndex`, `MapID`, `PositionX`, `PositionY`, `PositionZ`, `VerifiedBuild`) VALUES
(323017, 0, 0, 2222, -1604.861083984375, 8116.181640625, 4048.4150390625, 69814), -- Spell: 323017 (Holy Scorch) Effect 0: 32 (SPELL_EFFECT_TRIGGER_MISSILE)
(323017, 0, 1, 2222, -1602.4097900390625, 8102.40380859375, 4043.009033203125, 69814), -- Spell: 323017 (Holy Scorch) Effect 0: 32 (SPELL_EFFECT_TRIGGER_MISSILE)
(323017, 0, 2, 2222, -1749.9410400390625, 8077.9140625, 4046.225830078125, 69814), -- Spell: 323017 (Holy Scorch) Effect 0: 32 (SPELL_EFFECT_TRIGGER_MISSILE)
(323017, 0, 3, 2222, -1688.2083740234375, 8189.533203125, 4080.239501953125, 69814), -- Spell: 323017 (Holy Scorch) Effect 0: 32 (SPELL_EFFECT_TRIGGER_MISSILE)
(323017, 0, 4, 2222, -1749.79345703125, 8064.8662109375, 4044.66064453125, 69814), -- Spell: 323017 (Holy Scorch) Effect 0: 32 (SPELL_EFFECT_TRIGGER_MISSILE)
(323017, 0, 5, 2222, -1734.484375, 8082.4140625, 4047.822998046875, 69814), -- Spell: 323017 (Holy Scorch) Effect 0: 32 (SPELL_EFFECT_TRIGGER_MISSILE)
(323017, 0, 6, 2222, -1741.0833740234375, 8090.73876953125, 4048.34375, 69814), -- Spell: 323017 (Holy Scorch) Effect 0: 32 (SPELL_EFFECT_TRIGGER_MISSILE)
(323017, 0, 7, 2222, -1591.671875, 8110.619140625, 4042.899658203125, 69814), -- Spell: 323017 (Holy Scorch) Effect 0: 32 (SPELL_EFFECT_TRIGGER_MISSILE)
(323017, 0, 8, 2222, -1695.907958984375, 8186.7646484375, 4080.24267578125, 69814); -- Spell: 323017 (Holy Scorch) Effect 0: 32 (SPELL_EFFECT_TRIGGER_MISSILE)
UPDATE `creature_model_info` SET `VerifiedBuild`=69814 WHERE `DisplayID` IN (109460, 102503, 102085, 108693, 112785, 108996, 107189, 104578, 109595, 107029, 108666, 106306, 111190, 105131, 108248, 104026, 108348, 104023, 69532, 97175, 93505, 97067, 94002, 93683, 93068, 93675, 90426, 96090, 96095, 96094, 97439, 96992, 94410, 93814, 97438, 92703, 97432, 97436, 97437, 98037, 97434, 98026, 69223, 92018, 70162, 93854, 96594, 96595, 93364, 93813, 97173, 96711, 96694, 95500, 93441, 95499, 95348, 99976, 100716, 100715, 94498, 95347, 99942, 93306, 97945, 95349, 99627, 99681, 96006, 99999, 98210, 94434, 94960, 97952, 95720, 97955, 95622, 97958, 99670, 99990, 95535, 93223, 92399, 97954, 97947, 94447, 97949, 97696, 93210, 95536, 94445, 94446, 94444, 97691, 97693, 97695, 94765, 97692, 98189, 93768, 97946, 97948, 96159, 99993, 93305, 95733, 99676, 99991, 94500, 98014, 99056, 95732, 99680, 93815, 99162, 96830, 94910, 95719, 100332, 99988, 97956, 92385, 93511, 65130, 93170, 97148, 96117, 79406, 92704, 93169, 93606, 93605, 96672, 94409, 42349, 96121, 51414, 93671, 96611, 93260, 94516, 93524, 93250, 93254, 93249, 93253, 93526, 93251);
UPDATE `creature_model_info` SET `BoundingRadius`=2.429767131805419921, `CombatReach`=2, `VerifiedBuild`=69814 WHERE `DisplayID`=92702;
UPDATE `creature_model_info` SET `BoundingRadius`=0.150000005960464477, `VerifiedBuild`=69814 WHERE `DisplayID`=98038;
UPDATE `creature_model_info` SET `BoundingRadius`=0.5, `CombatReach`=1, `VerifiedBuild`=69814 WHERE `DisplayID`=96593;
UPDATE `creature_model_info` SET `BoundingRadius`=1.152193188667297363, `CombatReach`=2.444444656372070312, `VerifiedBuild`=69814 WHERE `DisplayID`=93162;
UPDATE `creature_model_info` SET `BoundingRadius`=1.771305322647094726, `CombatReach`=2.222222328186035156, `VerifiedBuild`=69814 WHERE `DisplayID`=98203;
DELETE FROM `creature_template_gossip` WHERE (`CreatureID`=159000 AND `MenuID`=24732) OR (`CreatureID`=164738 AND `MenuID`=25658);
INSERT INTO `creature_template_gossip` (`CreatureID`, `MenuID`, `VerifiedBuild`) VALUES
(159000, 24732, 69814), -- Vasilica
(164738, 25658, 69814); -- Tenaval
UPDATE `creature_template_gossip` SET `VerifiedBuild`=69814 WHERE (`CreatureID`=174714 AND `MenuID`=26552) OR (`CreatureID`=175772 AND `MenuID`=26737) OR (`CreatureID`=172605 AND `MenuID`=26197);
DELETE FROM `npc_vendor` WHERE (`entry`=174710 AND `item`=245501 AND `ExtendedCost`=10519 AND `type`=1) OR (`entry`=174710 AND `item`=182204 AND `ExtendedCost`=6955 AND `type`=1) OR (`entry`=174710 AND `item`=183715 AND `ExtendedCost`=6956 AND `type`=1) OR (`entry`=174710 AND `item`=183716 AND `ExtendedCost`=6954 AND `type`=1) OR (`entry`=174710 AND `item`=183712 AND `ExtendedCost`=7043 AND `type`=1);
INSERT INTO `npc_vendor` (`entry`, `slot`, `item`, `maxcount`, `ExtendedCost`, `type`, `PlayerConditionID`, `IgnoreFiltering`, `VerifiedBuild`) VALUES
(174710, 65, 245501, 0, 10519, 1, 91049, 0, 69814), -- Venthyr Tome of Unforgiven Sins
(174710, 11, 182204, 0, 6955, 1, 82888, 0, 69814), -- Illusion: Sinwrath
(174710, 8, 183715, 0, 6956, 1, 82875, 0, 69814), -- Sinfall Gargon
(174710, 6, 183716, 0, 6954, 1, 82863, 0, 69814), -- Venthyr Sinstone
(174710, 5, 183712, 0, 7043, 1, 82857, 0, 69814); -- Gleaming Crypt Keeper's Mantle
UPDATE `npc_vendor` SET `slot`=67, `VerifiedBuild`=69814 WHERE (`entry`=160322 AND `item`=184719 AND `ExtendedCost`=7005 AND `type`=1); -- Enchanted Map of Infused Ruby Network
UPDATE `npc_vendor` SET `slot`=66, `maxcount`=3, `VerifiedBuild`=69814 WHERE (`entry`=160322 AND `item`=180696 AND `ExtendedCost`=6780 AND `type`=1); -- Legion Wing Insignia
UPDATE `npc_vendor` SET `slot`=65, `VerifiedBuild`=69814 WHERE (`entry`=160322 AND `item`=180683 AND `ExtendedCost`=6780 AND `type`=1); -- Guardian Stonewing
UPDATE `npc_vendor` SET `slot`=64, `VerifiedBuild`=69814 WHERE (`entry`=160322 AND `item`=184718 AND `ExtendedCost`=7004 AND `type`=1); -- Stolen Map of Infused Ruby Network
UPDATE `npc_vendor` SET `slot`=63, `VerifiedBuild`=69814 WHERE (`entry`=160322 AND `item`=184716 AND `ExtendedCost`=7006 AND `type`=1); -- Sentry Neutralizer
UPDATE `npc_vendor` SET `slot`=17, `VerifiedBuild`=69814 WHERE (`entry`=160322 AND `item`=64670 AND `ExtendedCost`=0 AND `type`=1); -- Vanishing Powder
UPDATE `npc_vendor` SET `slot`=16, `VerifiedBuild`=69814 WHERE (`entry`=160322 AND `item`=39505 AND `ExtendedCost`=0 AND `type`=1); -- Virtuoso Inking Set
UPDATE `npc_vendor` SET `slot`=15, `VerifiedBuild`=69814 WHERE (`entry`=160322 AND `item`=20815 AND `ExtendedCost`=0 AND `type`=1); -- Jeweler's Toolset
UPDATE `npc_vendor` SET `slot`=14, `VerifiedBuild`=69814 WHERE (`entry`=160322 AND `item`=5956 AND `ExtendedCost`=0 AND `type`=1); -- Blacksmith Hammer
UPDATE `npc_vendor` SET `slot`=13, `VerifiedBuild`=69814 WHERE (`entry`=160322 AND `item`=6217 AND `ExtendedCost`=0 AND `type`=1); -- Copper Rod
UPDATE `npc_vendor` SET `slot`=12, `VerifiedBuild`=69814 WHERE (`entry`=160322 AND `item`=6256 AND `ExtendedCost`=0 AND `type`=1); -- Fishing Pole
UPDATE `npc_vendor` SET `VerifiedBuild`=69814 WHERE (`entry`=160322 AND `item`=85663 AND `ExtendedCost`=0 AND `type`=1);
UPDATE `npc_vendor` SET `slot`=10, `VerifiedBuild`=69814 WHERE (`entry`=160322 AND `item`=7005 AND `ExtendedCost`=0 AND `type`=1); -- Skinning Knife
UPDATE `npc_vendor` SET `slot`=9, `VerifiedBuild`=69814 WHERE (`entry`=160322 AND `item`=2901 AND `ExtendedCost`=0 AND `type`=1); -- Mining Pick
UPDATE `npc_vendor` SET `slot`=8, `VerifiedBuild`=69814 WHERE (`entry`=160322 AND `item`=138293 AND `ExtendedCost`=0 AND `type`=1); -- Explorer's Pack
UPDATE `npc_vendor` SET `slot`=7, `VerifiedBuild`=69814 WHERE (`entry`=160322 AND `item`=180733 AND `ExtendedCost`=0 AND `type`=1); -- Luminous Flux
UPDATE `npc_vendor` SET `slot`=6, `VerifiedBuild`=69814 WHERE (`entry`=160322 AND `item`=173168 AND `ExtendedCost`=0 AND `type`=1); -- Laestrite Setting
UPDATE `npc_vendor` SET `slot`=5, `VerifiedBuild`=69814 WHERE (`entry`=160322 AND `item`=177062 AND `ExtendedCost`=0 AND `type`=1); -- Penumbra Thread
UPDATE `npc_vendor` SET `slot`=4, `VerifiedBuild`=69814 WHERE (`entry`=160322 AND `item`=4470 AND `ExtendedCost`=0 AND `type`=1); -- Simple Wood
UPDATE `npc_vendor` SET `slot`=3, `VerifiedBuild`=69814 WHERE (`entry`=160322 AND `item`=38682 AND `ExtendedCost`=0 AND `type`=1); -- Enchanting Vellum
UPDATE `npc_vendor` SET `slot`=2, `VerifiedBuild`=69814 WHERE (`entry`=160322 AND `item`=180732 AND `ExtendedCost`=0 AND `type`=1); -- Rune Etched Vial
UPDATE `npc_vendor` SET `slot`=1, `VerifiedBuild`=69814 WHERE (`entry`=160322 AND `item`=175886 AND `ExtendedCost`=0 AND `type`=1); -- Dark Parchment
UPDATE `npc_vendor` SET `slot`=68, `PlayerConditionID`=91346, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=186509 AND `ExtendedCost`=7114 AND `type`=1); -- Ensemble: Sinful Venthyr Attire
UPDATE `npc_vendor` SET `slot`=67, `PlayerConditionID`=92126, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=186508 AND `ExtendedCost`=7114 AND `type`=1); -- Ensemble: Court Inquisitor's Vestments
UPDATE `npc_vendor` SET `slot`=66, `PlayerConditionID`=91911, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=186478 AND `ExtendedCost`=7115 AND `type`=1); -- Obsidian Gravewing
UPDATE `npc_vendor` SET `slot`=64, `PlayerConditionID`=92125, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=186510 AND `ExtendedCost`=7114 AND `type`=1); -- Ensemble: Sinful Inquisitor's Vestments
UPDATE `npc_vendor` SET `slot`=63, `PlayerConditionID`=82891, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=182332 AND `ExtendedCost`=6948 AND `type`=1); -- Gravestone Battle Armor
UPDATE `npc_vendor` SET `slot`=62, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=180461 AND `ExtendedCost`=7031 AND `type`=1); -- Horrid Dredwing
UPDATE `npc_vendor` SET `slot`=61, `PlayerConditionID`=82889, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=182438 AND `ExtendedCost`=6950 AND `type`=1); -- Sinfall Gilded Bulwark
UPDATE `npc_vendor` SET `slot`=60, `PlayerConditionID`=82889, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=182437 AND `ExtendedCost`=6950 AND `type`=1); -- Screecher's Crimson Spinethrower
UPDATE `npc_vendor` SET `slot`=59, `PlayerConditionID`=82889, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=182436 AND `ExtendedCost`=6950 AND `type`=1); -- Crimson Dredwing Glaive
UPDATE `npc_vendor` SET `slot`=58, `PlayerConditionID`=82889, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=182432 AND `ExtendedCost`=6950 AND `type`=1); -- Gilded Sentry Stave
UPDATE `npc_vendor` SET `slot`=57, `PlayerConditionID`=82889, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=182429 AND `ExtendedCost`=6950 AND `type`=1); -- Claw of the Gilded Stoneborn
UPDATE `npc_vendor` SET `slot`=56, `PlayerConditionID`=82889, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=182427 AND `ExtendedCost`=6950 AND `type`=1); -- Sinfall Heartpiercer
UPDATE `npc_vendor` SET `slot`=55, `PlayerConditionID`=82889, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=183975 AND `ExtendedCost`=6950 AND `type`=1); -- Sinfall Ceremonial Blade
UPDATE `npc_vendor` SET `slot`=54, `PlayerConditionID`=82889, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=182428 AND `ExtendedCost`=6950 AND `type`=1); -- Gilded Sinfall Bastard Sword
UPDATE `npc_vendor` SET `slot`=53, `PlayerConditionID`=82889, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=182434 AND `ExtendedCost`=6950 AND `type`=1); -- Crimson-Edged Wingblade
UPDATE `npc_vendor` SET `slot`=52, `PlayerConditionID`=82889, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=182433 AND `ExtendedCost`=6950 AND `type`=1); -- Barbedged Dredblade
UPDATE `npc_vendor` SET `slot`=51, `PlayerConditionID`=82889, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=182431 AND `ExtendedCost`=6950 AND `type`=1); -- Gilded Sineater's Staff
UPDATE `npc_vendor` SET `slot`=50, `PlayerConditionID`=82889, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=182439 AND `ExtendedCost`=6950 AND `type`=1); -- Sinheart Spell Foci
UPDATE `npc_vendor` SET `slot`=49, `PlayerConditionID`=82889, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=182435 AND `ExtendedCost`=6950 AND `type`=1); -- Crimsonbarbed Sinbreaker
UPDATE `npc_vendor` SET `slot`=48, `PlayerConditionID`=82889, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=182430 AND `ExtendedCost`=6950 AND `type`=1); -- Sinseeker's Dredwing Gavel
UPDATE `npc_vendor` SET `slot`=47, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=181009 AND `ExtendedCost`=6953 AND `type`=1); -- Dread Sentinel's Crimson Spaulders
UPDATE `npc_vendor` SET `slot`=46, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=181072 AND `ExtendedCost`=6953 AND `type`=1); -- Fearstalker's Crimson Monnion
UPDATE `npc_vendor` SET `slot`=45, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=181045 AND `ExtendedCost`=6953 AND `type`=1); -- Crimson Death Shroud Spaulders
UPDATE `npc_vendor` SET `slot`=44, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=181099 AND `ExtendedCost`=6953 AND `type`=1); -- Soulbreaker's Crimson Mantle
UPDATE `npc_vendor` SET `slot`=43, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=181004 AND `ExtendedCost`=6953 AND `type`=1); -- Dread Sentinel's Crimson Headgear
UPDATE `npc_vendor` SET `slot`=42, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=181070 AND `ExtendedCost`=6953 AND `type`=1); -- Fearstalker's Crimson Helm
UPDATE `npc_vendor` SET `slot`=41, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=181043 AND `ExtendedCost`=6953 AND `type`=1); -- Crimson Death Shroud Hood
UPDATE `npc_vendor` SET `slot`=40, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=181097 AND `ExtendedCost`=6953 AND `type`=1); -- Soulbreaker's Crimson Hood
UPDATE `npc_vendor` SET `slot`=39, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=181005 AND `ExtendedCost`=6953 AND `type`=1); -- Dread Sentinel's Crimson Chestplate
UPDATE `npc_vendor` SET `slot`=38, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=181067 AND `ExtendedCost`=6953 AND `type`=1); -- Fearstalker's Crimson Hauberk
UPDATE `npc_vendor` SET `slot`=37, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=181040 AND `ExtendedCost`=6953 AND `type`=1); -- Crimson Death Shroud Vest
UPDATE `npc_vendor` SET `slot`=36, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=181094 AND `ExtendedCost`=6953 AND `type`=1); -- Soulbreaker's Crimson Vestments
UPDATE `npc_vendor` SET `slot`=35, `PlayerConditionID`=82878, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=181008 AND `ExtendedCost`=6952 AND `type`=1); -- Dread Sentinel's Crimson Legguards
UPDATE `npc_vendor` SET `slot`=34, `PlayerConditionID`=82878, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=181071 AND `ExtendedCost`=6952 AND `type`=1); -- Fearstalker's Crimson Leggings
UPDATE `npc_vendor` SET `slot`=33, `PlayerConditionID`=82878, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=181044 AND `ExtendedCost`=6952 AND `type`=1); -- Crimson Death Shroud Breeches
UPDATE `npc_vendor` SET `slot`=32, `PlayerConditionID`=82878, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=181098 AND `ExtendedCost`=6952 AND `type`=1); -- Soulbreaker's Crimson Leggings
UPDATE `npc_vendor` SET `slot`=31, `PlayerConditionID`=82878, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=181006 AND `ExtendedCost`=6951 AND `type`=1); -- Dread Sentinel's Crimson Greatboots
UPDATE `npc_vendor` SET `slot`=30, `PlayerConditionID`=82878, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=181068 AND `ExtendedCost`=6951 AND `type`=1); -- Fearstalker's Crimson Sabatons
UPDATE `npc_vendor` SET `slot`=29, `PlayerConditionID`=82878, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=181041 AND `ExtendedCost`=6951 AND `type`=1); -- Crimson Death Shroud Boots
UPDATE `npc_vendor` SET `slot`=28, `PlayerConditionID`=82878, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=181095 AND `ExtendedCost`=6951 AND `type`=1); -- Soulbreaker's Crimson Slippers
UPDATE `npc_vendor` SET `slot`=27, `PlayerConditionID`=82878, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=181012 AND `ExtendedCost`=7030 AND `type`=1); -- Dread Sentinel's Crimson Cloak
UPDATE `npc_vendor` SET `slot`=26, `PlayerConditionID`=82878, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=181075 AND `ExtendedCost`=7030 AND `type`=1); -- Fearstalker's Crimson Cloak
UPDATE `npc_vendor` SET `slot`=25, `PlayerConditionID`=82878, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=181048 AND `ExtendedCost`=7030 AND `type`=1); -- Crimson Death Shroud Wrap
UPDATE `npc_vendor` SET `slot`=24, `PlayerConditionID`=82878, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=181102 AND `ExtendedCost`=7030 AND `type`=1); -- Soulbreaker's Crimson Drape
UPDATE `npc_vendor` SET `slot`=23, `PlayerConditionID`=82868, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=181011 AND `ExtendedCost`=6951 AND `type`=1); -- Dread Sentinel's Crimson Vambraces
UPDATE `npc_vendor` SET `slot`=22, `PlayerConditionID`=82868, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=181074 AND `ExtendedCost`=6951 AND `type`=1); -- Fearstalker's Crimson Bracers
UPDATE `npc_vendor` SET `slot`=21, `PlayerConditionID`=82868, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=181047 AND `ExtendedCost`=6951 AND `type`=1); -- Crimson Death Shroud Bindings
UPDATE `npc_vendor` SET `slot`=20, `PlayerConditionID`=82868, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=181101 AND `ExtendedCost`=6951 AND `type`=1); -- Soulbreaker's Crimson Wraps
UPDATE `npc_vendor` SET `slot`=19, `PlayerConditionID`=82868, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=181010 AND `ExtendedCost`=6952 AND `type`=1); -- Dread Sentinel's Crimson Girdle
UPDATE `npc_vendor` SET `slot`=18, `PlayerConditionID`=82868, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=181073 AND `ExtendedCost`=6952 AND `type`=1); -- Fearstalker's Crimson Belt
UPDATE `npc_vendor` SET `slot`=17, `PlayerConditionID`=82868, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=181046 AND `ExtendedCost`=6952 AND `type`=1); -- Crimson Death Shroud Belt
UPDATE `npc_vendor` SET `slot`=16, `PlayerConditionID`=82868, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=181100 AND `ExtendedCost`=6952 AND `type`=1); -- Soulbreaker's Crimson Sash
UPDATE `npc_vendor` SET `slot`=15, `PlayerConditionID`=82868, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=181007 AND `ExtendedCost`=6951 AND `type`=1); -- Dread Sentinel's Crimson Grips
UPDATE `npc_vendor` SET `slot`=14, `PlayerConditionID`=82868, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=181069 AND `ExtendedCost`=6951 AND `type`=1); -- Fearstalker's Crimson Gauntlets
UPDATE `npc_vendor` SET `slot`=13, `PlayerConditionID`=82868, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=181042 AND `ExtendedCost`=6951 AND `type`=1); -- Crimson Death Shroud Gloves
UPDATE `npc_vendor` SET `slot`=12, `PlayerConditionID`=82868, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=181096 AND `ExtendedCost`=6951 AND `type`=1); -- Soulbreaker's Crimson Handwraps
UPDATE `npc_vendor` SET `slot`=10, `PlayerConditionID`=82886, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=183705 AND `ExtendedCost`=6949 AND `type`=1); -- Mantle of Crimson Blades
UPDATE `npc_vendor` SET `slot`=9, `PlayerConditionID`=82879, `VerifiedBuild`=69814 WHERE (`entry`=174710 AND `item`=181555 AND `ExtendedCost`=6947 AND `type`=1); -- Sinheart
UPDATE `gossip_menu` SET `VerifiedBuild`=69814 WHERE (`TextID`=42161 AND `MenuID`=26552) OR (`TextID`=38900 AND `MenuID`=24732) OR (`TextID`=40493 AND `MenuID` IN (25658,26197)) OR (`TextID`=42523 AND `MenuID`=26737);
UPDATE `gossip_menu_option` SET `GossipOptionID`=54123, `GossipNpcOptionID`=31392, `VerifiedBuild`=69814 WHERE (`MenuID`=26197 AND `OptionID`=0);
UPDATE `gossip_menu_option` SET `GossipOptionID`=49479, `BoxMoney`=1000, `BoxText`='A small fee for supplies is required.', `BoxBroadcastTextID`=66369, `VerifiedBuild`=69814 WHERE (`MenuID`=24732 AND `OptionID`=2);
UPDATE `gossip_menu_option` SET `VerifiedBuild`=69814 WHERE (`MenuID`=26737 AND `OptionID`=0);
UPDATE `creature_template_model` SET `VerifiedBuild`=69814 WHERE (`Idx`=0 AND `CreatureID` IN (192464,194389,120271,190085,194816,189669,189786,189219,189478,190630,193291,189471,189901,193944,191498,189466,194883,189340,106232,160450,169999,160456,160642,160640,160641,157637,165687,160400,160345,160375,158533,160317,160322,160323,159265,159114,159577,158976,158988,158985,159106,159495,155654,160604,160570,160425,160582,160537,173303,161015,160535,178336,178338,167822,157195,164524,163861,158038,175533,168152,170004,168174,168165,157194,164575,164576,158165,175624,175625,175623,175622,175580,175770,55455,175408,175406,174038,175593,175762,175775,174045,175620,174994,175922,55626,172649,164931,173942,175452,164738,175451,175453,161977,161979,164741,175462,175450,175455,175449,175466,175459,175441,164900,158653,175458,175456,175769,175448,166160,174710,175772,172605,162704,162702,159000,166137,166048,174714,175590,175470,156296,165767,175469,175572,168703,174116,174032,175850,173323,173439,164533,166089,169841,173390,158416,166047,158668,158667,169840,173389,168120,166092,162146,166076,167813,166084,166078,158700,158663,160701,157785,163507,157727,157731,157726,157784,157729)) OR (`Idx`=1 AND `CreatureID` IN (191498,160641,160375,160317,159265,159114,159577,158976,158988,158985,159106,159495,155654,160535,167822,157195,164524,168152,168174,175580,174038,175593,174045,175620,174994,175466,175459,175441,175458,175448,175590,175470,175469,175572,173323,164533,167813,158663,160701,157785,157727,157731,157726,157784,157729)) OR (`Idx`=3 AND `CreatureID` IN (160641,160317,159114,159495,175580,175593,175620,175466,175459,175441,175458,175448,175470,175469,175572,164533,157785,157727,157731,157726,157784,157729)) OR (`Idx`=2 AND `CreatureID` IN (160641,160375,160317,159265,159114,159106,159495,160535,167822,164524,168174,175580,174038,175593,175620,175466,175459,175441,175458,175448,175470,175469,175572,164533,158663,160701,157785,157727,157731,157726,157784,157729)) OR (`Idx`=5 AND `CreatureID` IN (175580,175620,175466,175459,175441,175470,175469,175572,164533,157785,157727,157731,157726,157784,157729)) OR (`Idx`=4 AND `CreatureID` IN (175580,175620,175466,175459,175441,175458,175470,175469,175572,164533,157785,157727,157731,157726,157784,157729)) OR (`Idx`=7 AND `CreatureID` IN (175620,175466,175459,175441,175470,175469,175572,164533,157785,157727,157731,157726,157784,157729)) OR (`Idx`=6 AND `CreatureID` IN (175620,175466,175459,175441,175470,175469,175572,164533,157785,157727,157731,157726,157784,157729)) OR (`Idx`=15 AND `CreatureID`=175469) OR (`Idx`=14 AND `CreatureID`=175469) OR (`Idx`=13 AND `CreatureID`=175469) OR (`Idx`=12 AND `CreatureID`=175469) OR (`Idx`=11 AND `CreatureID`=175469) OR (`Idx`=10 AND `CreatureID`=175469) OR (`Idx`=9 AND `CreatureID`=175469) OR (`Idx`=8 AND `CreatureID`=175469);
UPDATE `creature_template_model` SET `CreatureDisplayID`=11686, `VerifiedBuild`=69814 WHERE (`CreatureID`=120271 AND `Idx`=1); -- Invisible Bunny (IGC)
UPDATE `creature_template_model` SET `CreatureDisplayID`=11686, `VerifiedBuild`=69814 WHERE (`CreatureID`=178336 AND `Idx`=1); -- [DNT] Kill Credit: The Power of a Crown Final
UPDATE `creature_template_model` SET `CreatureDisplayID`=11686, `VerifiedBuild`=69814 WHERE (`CreatureID`=178338 AND `Idx`=1); -- [DNT] Kill Credit: The Power of a Crown Gate
UPDATE `creature_template_model` SET `CreatureDisplayID`=11686, `VerifiedBuild`=69814 WHERE (`Idx`=1 AND `CreatureID` IN (55455,55626)); -- General Purpose Bunny (DLA)
UPDATE `creature_template_model` SET `CreatureDisplayID`=11686, `VerifiedBuild`=69814 WHERE (`CreatureID`=173439 AND `Idx`=1); -- Bonebind Trap
UPDATE `creature_template_model` SET `CreatureDisplayID`=11686, `VerifiedBuild`=69814 WHERE (`CreatureID`=162146 AND `Idx`=1); -- [DNT] Goodbye RP Bunny
UPDATE `creature_template_model` SET `CreatureDisplayID`=27823, `VerifiedBuild`=69814 WHERE (`CreatureID`=158700 AND `Idx`=1); -- (Bunny) Area Trigger - Light Beam
UPDATE `creature_template` SET `ScriptName`='npc_nl_qalashi_trainee_buff' WHERE `entry`=189266;
UPDATE `creature_template` SET `ScriptName`='npc_nl_qalashi_hunter' WHERE `entry`=189227;
UPDATE `creature_template` SET `ScriptName`='npc_nl_qalashi_warcaster' WHERE `entry`=189235;
UPDATE `creature_template` SET `ScriptName`='npc_nl_qalashi_lavacaller' WHERE `entry`=189265;
UPDATE `creature_template` SET `ScriptName`='npc_nl_qalashi_scorcher' WHERE `entry`=189464;
UPDATE `creature_template` SET `ScriptName`='npc_nl_irontorch_commander' WHERE `entry`=189466;
UPDATE `creature_template` SET `ScriptName`='npc_nl_qalashi_bonebreaker' WHERE `entry`=189467;
UPDATE `creature_template` SET `ScriptName`='npc_nl_qalashi_blacksmith' WHERE `entry`=189471;
UPDATE `creature_template` SET `ScriptName`='npc_nl_qalashi_lavaslinger' WHERE `entry`=189472;
UPDATE `creature_template` SET `ScriptName`='npc_nl_forge_golem' WHERE `entry`=189474;
UPDATE `creature_template` SET `ScriptName`='npc_nl_qalashi_spearman' WHERE `entry`=189669;
UPDATE `creature_template` SET `ScriptName`='npc_nl_qalashi_apothecary' WHERE `entry`=189905;
UPDATE `creature_template` SET `ScriptName`='npc_nl_magma_globule' WHERE `entry`=190023;
UPDATE `creature_template` SET `ScriptName`='npc_nl_magma_globule' WHERE `entry`=190630;
UPDATE `creature_template` SET `ScriptName`='npc_nl_qalashi_chain' WHERE `entry`=191498;
UPDATE `creature_template` SET `ScriptName`='npc_nl_qalashi_earthwarden' WHERE `entry`=192781;
UPDATE `creature_template` SET `ScriptName`='npc_nl_explosive_alchemist' WHERE `entry`=192786;
UPDATE `creature_template` SET `ScriptName`='npc_nl_qalashi_brute' WHERE `entry`=192787;
UPDATE `creature_template` SET `ScriptName`='npc_nl_qalashi_thaumaturge' WHERE `entry`=192788;
UPDATE `creature_template` SET `ScriptName`='npc_nl_candescent_tempest' WHERE `entry`=193291;
UPDATE `creature_template` SET `ScriptName`='npc_nl_volcanic_warden' WHERE `entry`=193293;
UPDATE `creature_template` SET `ScriptName`='npc_nl_qalashi_lavamancer' WHERE `entry`=193944;
UPDATE `creature_template` SET `ScriptName`='npc_nl_forgewrought_monstrosity' WHERE `entry`=194816;
UPDATE `creature_template` SET `ScriptName`='npc_nl_burning_book' WHERE `entry`=197333;
UPDATE `creature_template` SET `ScriptName`='npc_nl_burning_books' WHERE `entry`=197337;
@@ -0,0 +1,86 @@
-- Update Build 69814
UPDATE `areatrigger` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `areatrigger_create_properties` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `areatrigger_create_properties_orbit` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `areatrigger_create_properties_polygon_vertex` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `areatrigger_template` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `chromie_time_expansion_quest` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `conversation_actors` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `conversation_line_template` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `conversation_template` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `creature` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `creature_equip_template` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `creature_model_info` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `creature_quest_currency` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `creature_questender` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `creature_questitem` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `creature_queststarter` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `creature_template` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `creature_template_difficulty` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `creature_template_gossip` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `creature_template_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `creature_template_model` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `creature_template_resistance` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `creature_template_spell` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `gameobject` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `gameobject_questender` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `gameobject_questitem` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `gameobject_queststarter` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `gameobject_template` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `gameobject_template_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `gossip_menu` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `gossip_menu_addon` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `gossip_menu_option` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `lfg_dungeon_template` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `npc_text` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `npc_vendor` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `page_text` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `page_text_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `player_classlevelstats` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `playerchoice` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `playerchoice_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `playerchoice_response` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `playerchoice_response_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `playerchoice_response_maw_power` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `playerchoice_response_reward` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `playerchoice_response_reward_currency` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `playerchoice_response_reward_faction` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `playerchoice_response_reward_item` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `playerchoice_response_reward_item_choice` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `points_of_interest` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `points_of_interest_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `quest_completion_log_conditional` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `quest_description_conditional` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `quest_details` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `quest_greeting` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `quest_greeting_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `quest_objectives` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `quest_objectives_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `quest_offer_reward` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `quest_offer_reward_conditional` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `quest_offer_reward_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `quest_poi` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `quest_poi_points` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `quest_request_items` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `quest_request_items_conditional` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `quest_request_items_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `quest_reward_choice_items` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `quest_reward_display_spell` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `quest_reward_house_decor` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `quest_reward_house_room` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `quest_template` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `quest_template_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `quest_visual_effect` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `scenario_poi` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `scenario_poi_points` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `spawn_tracking_quest_objective` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `spawn_tracking_template` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `spell_summon_position` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `spell_target_position` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `trainer` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `trainer_locale` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `trainer_spell` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `ui_map_quest` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `ui_map_quest_line` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
UPDATE `world_quest_update` SET `VerifiedBuild`=69814 WHERE `VerifiedBuild` NOT IN (69814);
@@ -0,0 +1,32 @@
DELETE FROM `spell_script_names` WHERE `spell_id`=267216 AND `ScriptName`='spell_warl_inner_demons';
INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES (267216, 'spell_warl_inner_demons');
DELETE FROM `spell_script_names` WHERE `spell_id`=265187 AND `ScriptName`='spell_warl_summon_demonic_tyrant';
INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES (265187, 'spell_warl_summon_demonic_tyrant');
DELETE FROM `spell_script_names` WHERE `spell_id`=374763 AND `ScriptName`='spell_dragonriding_lift_off';
INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES (374763, 'spell_dragonriding_lift_off');
DELETE FROM `spell_script_names` WHERE `spell_id`=196277 AND `ScriptName`='spell_warl_implosion';
INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES (196277, 'spell_warl_implosion');
DELETE FROM `spell_script_names` WHERE `spell_id`=264130 AND `ScriptName`='spell_warl_power_siphon';
INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES (264130, 'spell_warl_power_siphon');
DELETE FROM `spell_script_names` WHERE `spell_id`=104318 AND `ScriptName`='spell_imp_fel_firebolt';
INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES (104318, 'spell_imp_fel_firebolt');
DELETE FROM `spell_script_names` WHERE `ScriptName`='spell_warl_carnivorous_stalkers';
INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES
(205196, 'spell_warl_carnivorous_stalkers'),
(271971, 'spell_warl_carnivorous_stalkers');
DELETE FROM `spell_script_names` WHERE `ScriptName`='spell_warl_blighted_maw';
INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES
(205196, 'spell_warl_blighted_maw'),
(271971, 'spell_warl_blighted_maw');
DELETE FROM `spell_script_names` WHERE `ScriptName`='spell_warl_grimoire_imp_lord';
INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES
(1276452, 'spell_warl_grimoire_imp_lord');
+1 -1
View File
@@ -2462,7 +2462,7 @@ namespace PetBattles
{
if (Player* p = GetPlayerForTeam(PET_BATTLE_TEAM_1))
if (Creature* trainer = ObjectAccessor::GetCreature(*p, _npcTrainerGUID))
finalRound.NpcCreatureID = trainer->GetEntry();
finalRound.NpcCreatureID[t] = trainer->GetEntry();
}
for (uint8 i = 0; i < team.PetCount; ++i)
@@ -59,6 +59,7 @@
#include "BattlePetMgr.h"
#include "ChallengeMode.h"
#include "ChallengeModeMgr.h"
#include "GroupMgr.h"
#include "Map.h"
//WowCommunity
@@ -408,6 +409,17 @@ void Creature::RemoveFromWorld()
if (m_formation)
FormationMgr::RemoveCreatureFromGroup(m_formation, this);
//WowCommunity
// a party member that leaves the world leaves the party frame with it
if (!m_partyGroupGuid.IsEmpty())
{
if (Group* group = sGroupMgr->GetGroupByGUID(m_partyGroupGuid))
group->RemoveNpcMember(GetGUID());
m_partyGroupGuid.Clear();
}
//WowCommunity
Unit::RemoveFromWorld();
if (m_spawnId)
+8 -1
View File
@@ -269,6 +269,10 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, public Ma
void SetBaseAttackPower(uint32 attackPower) { m_baseAttackPower = attackPower; }
void SetBaseRangedAttackPower(uint32 attackPower) { m_baseRangedAttackPower = attackPower; }
void SetDamageModifier(float mod) { m_overrideDamageModifier = mod; }
// Party frame membership granted by SPELL_EFFECT_CHANGE_PARTY_MEMBERS (empty when the
// creature is not a party member)
ObjectGuid GetPartyGroupGUID() const { return m_partyGroupGuid; }
void SetPartyGroupGUID(ObjectGuid const& guid) { m_partyGroupGuid = guid; }
//WowCommunity
void SetCanDualWield(bool value) override;
@@ -577,7 +581,10 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, public Ma
SpellSchoolMask m_meleeDamageSchoolMask;
uint32 m_baseAttackPower;
uint32 m_baseRangedAttackPower;
float m_overrideDamageModifier = -1.0f;// WowCommunity
// WowCommunity
float m_overrideDamageModifier = -1.0f;
ObjectGuid m_partyGroupGuid; // group whose party frame this creature is shown in
// WowCommunity
uint32 m_originalEntry;
Position m_homePosition;
@@ -4760,6 +4760,9 @@ GuidUnorderedSet const* GameObject::GetInsidePlayers() const
bool GameObject::MeetsInteractCondition(Player const* user) const
{
if (sConditionMgr->HasConditionsForNotGroupedEntry(CONDITION_SOURCE_GAMEOBJECT_INTERACTION, GetEntry()))
return sConditionMgr->IsObjectMeetingNotGroupedConditions(CONDITION_SOURCE_GAMEOBJECT_INTERACTION, GetEntry(), user);
return ConditionMgr::IsPlayerMeetingCondition(user, m_goInfo->GetConditionID1());
}
@@ -20683,6 +20683,13 @@ bool Player::CheckInstanceValidity(bool /*isLogin*/)
return true;
Group* group = GetGroup();
//WowCommunity
// a party that exists only to hold NPC companions never owns an instance and must not invalidate one
if (group && group->IsNpcParty())
group = nullptr;
//WowCommunity
// raid instances require the player to be in a raid group to be valid
if (map->IsRaid() && !sWorld->getBoolConfig(CONFIG_INSTANCE_IGNORE_RAID) && (map->GetEntry()->Expansion() >= sWorld->getIntConfig(CONFIG_EXPANSION)))
if (!group || !group->isRaidGroup())
+36 -2
View File
@@ -6939,6 +6939,35 @@ void Unit::EnergizeBySpell(Unit* victim, SpellInfo const* spellInfo, int32 damag
SendEnergizeSpellLog(victim, spellInfo->Id, gain, overEnergize, powerType);
}
namespace
{
uint8 GetControlledDemonCount(Unit const* owner)
{
uint8 count = 0;
for (Unit const* controlled : owner->m_Controlled)
if (controlled->IsAlive() && controlled->IsCreature() && controlled->GetCreatureType() == CREATURE_TYPE_DEMON)
++count;
return count;
}
// Hellbent Commander (1250897) - each demon the warlock controls grants 1% (spell aura amount)
// increased damage to every demon the player controls.
void ApplyHellbentCommanderDamageMod(Unit const* damageDealer, float& pctMod)
{
if (!damageDealer->IsCreature() || damageDealer->GetCreatureType() != CREATURE_TYPE_DEMON)
return;
Unit* owner = damageDealer->GetOwner();
Player* player = owner ? owner->ToPlayer() : nullptr;
if (!player)
return;
if (AuraEffect const* hellbent = player->GetAuraEffect(1250897, EFFECT_0))
AddPct(pctMod, hellbent->GetAmount() * GetControlledDemonCount(player));
}
}
int32 Unit::SpellDamageBonusDone(Unit* victim, SpellInfo const* spellProto, int32 pdamage, DamageEffectType damagetype, SpellEffectInfo const& spellEffectInfo, uint32 stack /*= 1*/, Spell* spell /*= nullptr*/, AuraEffect const* aurEff /*= nullptr*/) const
{
if (!spellProto || !victim)
@@ -6959,6 +6988,7 @@ int32 Unit::SpellDamageBonusDone(Unit* victim, SpellInfo const* spellProto, int3
if (damagetype == DIRECT_DAMAGE || spellProto->HasAttribute(SPELL_ATTR3_IGNORE_CASTER_MODIFIERS))
{
callDamageScript(pdamage, DoneTotal, DoneTotalMod);
ApplyHellbentCommanderDamageMod(this, DoneTotalMod);
return int32(std::max(float(pdamage + DoneTotal) * DoneTotalMod, 0.0f));
}
@@ -7152,6 +7182,8 @@ float Unit::SpellDamagePctDone(Unit* victim, SpellInfo const* spellProto, Damage
break;
}
ApplyHellbentCommanderDamageMod(this, DoneTotalMod);
return DoneTotalMod;
}
@@ -8295,6 +8327,8 @@ int32 Unit::MeleeDamageBonusDone(Unit* pVictim, int32 damage, WeaponAttackType a
else if (aurEff)
aurEff->GetBase()->CallScriptCalcDamageAndHealingHandlers(aurEff, aurEff->GetBase()->GetApplicationOfTarget(pVictim->GetGUID()), pVictim, damage, DoneFlatBenefit, DoneTotalMod);
ApplyHellbentCommanderDamageMod(this, DoneTotalMod);
float damageF = float(damage + DoneFlatBenefit) * DoneTotalMod;
// apply spellmod to Done damage
@@ -8654,10 +8688,10 @@ void Unit::UpdateMountCapability()
aurEff->GetBase()->Remove();
else if (MountCapabilityEntry const* capability = sMountCapabilityStore.LookupEntry(aurEff->GetAmountAsInt())) // aura may get removed by interrupt flag, reapply
{
SetFlightCapabilityID(capability->FlightCapabilityID, true);
if (!HasAura(capability->ModSpellAuraID))
CastSpell(this, capability->ModSpellAuraID, aurEff);
SetFlightCapabilityID(capability->FlightCapabilityID, true);
}
}
}
+54
View File
@@ -918,6 +918,12 @@ void Garrison::Update(uint32 diff)
// Keep each work-order crate's "filled with goods" display in sync with the orders on its plot.
UpdateWorkOrderCrates();
// Push SMSG_SET_SHIPMENT_READY_RESPONSE the moment a work order matures, same rationale as the
// finishedMissions re-send below: IsReady() was otherwise only evaluated lazily on interaction (opening
// the NPC / crate), so a shipment that finished while the player was elsewhere stayed shown as "in
// progress" until the next interaction.
SendReadyShipmentNotifications();
// Class-hall / order-hall work orders (plotless) are NOT auto-completed. Retail leaves each finished order
// waiting at its container's "standard" GameObject (GAMEOBJECT_TYPE_GARRISON_SHIPMENT, e.g. "Training Troops"):
// the player walks up and clicks it to pick up the recruited troop / produced good
@@ -1219,6 +1225,22 @@ void Garrison::ActivateBuilding(uint32 garrPlotInstanceId)
_owner->UpdateCriteria(CriteriaType::ActivateAnyGarrisonBuilding, plot->BuildingInfo.PacketInfo->GarrBuildingID);
// CriteriaType::ActivateGarrisonBuilding (169, Asset = GarrBuildingID).
_owner->UpdateCriteria(CriteriaType::ActivateGarrisonBuilding, plot->BuildingInfo.PacketInfo->GarrBuildingID);
// Force the client to re-render the plot as a finished building.
//
// The client draws each plot's building shell/WMO from the plot-building landmarks in
// GarrisonMapDataResponse (SendMapData) combined with the building's Active/TimeBuilt in
// GetGarrisonInfoResult (SendInfo) - NOT from the server GameObject's model. This is the same
// render pipeline the render-on-entry fix drives (see garrison_generic.cpp GarrisonRenderEvent:
// on a seamless garrison entry the plots render empty until exactly these two packets are pushed).
//
// The lone GarrisonBuildingActivated above updates the Architect/report UI but does not make the
// client rebuild the plot's world WMO, so the under-construction shell stays drawn and the finished
// building appears merged over it (two building objects at once). Re-pushing the map data + info -
// the same snapshot the client would receive on entry - makes it rebuild the plot cleanly from the
// now-Active state, so only the completed building remains. Mirrors GarrisonRenderEvent's order.
SendInfo();
SendMapData(_owner);
}
}
}
@@ -4871,6 +4893,7 @@ void Garrison::CompleteShipment(uint64 dbId)
response.Result = GARRISON_SUCCESS;
_owner->SendDirectMessage(response.Write());
_notifiedReadyShipments.erase(dbId);
_shipments.erase(itr);
// Remove the persisted row immediately so a crash can't resurrect an already-collected order.
@@ -5318,6 +5341,37 @@ void Garrison::SendLandingPageShipments()
_owner->SendDirectMessage(response.Write());
}
void Garrison::SendReadyShipmentNotifications()
{
// PLAN_B6.md #9 (SMSG_SET_SHIPMENT_READY_RESPONSE, case 5177402 in the family-0x4F switch): no CMSG
// requests this, so it can only be a server-initiated push. Field0/"Result" is UNVERIFIED by RE (see
// GarrisonPackets.h); the CharacterShipment payload and the ready-transition trigger are anchored --
// same live-refresh gap the finishedMissions counter above this closes for missions.
for (auto const& [dbId, shipment] : _shipments)
{
if (!shipment.IsReady())
continue;
if (!_notifiedReadyShipments.insert(dbId).second)
continue; // already notified for this maturation; cleared again on CompleteShipment
WorldPackets::Garrison::SetShipmentReadyResponse response;
response.Result = GARRISON_SUCCESS; // UNVERIFIED: see GarrisonPackets.h
WorldPackets::Garrison::CharacterShipment& packetShipment = response.Shipment;
packetShipment.ShipmentRecID = shipment.ShipmentRecID;
packetShipment.ShipmentID = shipment.DbID;
packetShipment.AssignedFollowerDBID = shipment.AssignedFollowerDBID;
packetShipment.CreationTime = shipment.CreationTime;
packetShipment.ShipmentDuration = shipment.Duration;
uint8 buildingType = GetBuildingTypeForPlot(shipment.PlotInstanceID);
packetShipment.BuildingTypeID = buildingType;
if (CharShipmentContainerEntry const* container = sGarrisonMgr.GetShipmentContainerForBuilding(buildingType, uint8(GetFaction())))
packetShipment.ContainerID = container->ID;
packetShipment.GarrTypeID = static_cast<uint8>(GetType());
_owner->SendDirectMessage(response.Write());
}
}
// ============================================================
// Talent helpers
// ============================================================
+6
View File
@@ -548,6 +548,11 @@ public:
std::vector<Shipment const*> GetAllShipments() const;
void SendShipmentInfo(ObjectGuid npcGUID);
void SendLandingPageShipments();
// #9 (SMSG_SET_SHIPMENT_READY_RESPONSE): mirrors the finishedMissions live-refresh pattern in Update() --
// IsReady() is otherwise only evaluated lazily on interaction, so a shipment that matures while the
// player is elsewhere never tells the open UI. Pushes exactly once per not-ready -> ready transition.
void SendReadyShipmentNotifications();
uint32 GetBuildingTypeForPlot(uint32 plotInstanceId) const;
uint32 FindPlotInstanceForNpc(ObjectGuid npcGUID) const;
@@ -707,6 +712,7 @@ private:
// Shipments
std::unordered_map<uint64 /*dbId*/, Shipment> _shipments;
std::unordered_set<uint64 /*dbId*/> _notifiedReadyShipments; // SMSG_SET_SHIPMENT_READY_RESPONSE dedupe, see SendReadyShipmentNotifications()
std::unordered_map<uint32 /*containerId*/, uint8> _shownStandardContainers; // standards we've lit up, so they reset to base after collection
std::unordered_map<uint32 /*containerId*/, ObjectGuid> _privateStandards; // per-player private "standard" GOs showing THIS owner's order state
std::unordered_map<uint32 /*garrTalentID*/, Talent> _talents;
+120 -6
View File
@@ -171,7 +171,7 @@ bool Group::Create(Player* leader)
m_raidDifficulty = DIFFICULTY_NORMAL_RAID;
m_legacyRaidDifficulty = DIFFICULTY_10_N;
if (!isBGGroup() && !isBFGroup())
if (!isBGGroup() && !isBFGroup()&& !IsNpcParty())
{
m_dungeonDifficulty = leader->GetDungeonDifficultyID();
m_raidDifficulty = leader->GetRaidDifficultyID();
@@ -484,7 +484,7 @@ bool Group::AddMember(Player* player)
}
// insert into the table if we're not a battleground group
if (!isBGGroup() && !isBFGroup())
if (!isBGGroup() && !isBFGroup() && !IsNpcParty())
{
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_GROUP_MEMBER);
@@ -628,7 +628,7 @@ bool Group::RemoveMember(ObjectGuid guid, RemoveMethod method /*= GROUP_REMOVEME
}
// Remove player from group in DB
if (!isBGGroup() && !isBFGroup())
if (!isBGGroup() && !isBFGroup() && !IsNpcParty())
{
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GROUP_MEMBER);
stmt->setUInt64(0, guid.GetCounter());
@@ -772,10 +772,13 @@ void Group::Disband(bool hideDestroy /* = false */)
}
m_memberSlots.clear();
//WowCommunity
m_npcMemberSlots.clear();
//WowCommunity
RemoveAllInvites();
if (!isBGGroup() && !isBFGroup())
if (!isBGGroup() && !isBFGroup() && !IsNpcParty())
{
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
@@ -833,6 +836,99 @@ void Group::SendTargetIconList(WorldSession* session) const
session->SendPacket(updateAll.Write());
}
//WowCommunity
// A player without a group casting/receiving "Npc Join Player Party" still has to see a party frame, so a
// real group is created for them. It is flagged as an NPC party: nothing about it is written to the
// database and it disbands as soon as its last creature is gone.
Group* Group::CreateNpcParty(Player* player)
{
Group* group = new Group();
group->SetNpcParty(true);
// 12.1 captures (gulf 102157): the companion party of a delve runs on PartyIndex 1, the instance
// category, while a companion picked up in the open world belongs to the home party.
if (Map const* map = player->FindMap(); map && map->IsDungeon())
{
group->m_groupCategory = GROUP_CATEGORY_INSTANCE;
group->m_dungeonDifficulty = map->GetDifficultyID(); // gulf 102157: 208, the delve difficulty
}
if (!group->Create(player))
{
delete group;
return nullptr;
}
sGroupMgr->AddGroup(group);
return group;
}
bool Group::AddNpcMember(Creature* creature, uint8 roles /*= 0*/)
{
if (!creature || IsNpcMember(creature->GetGUID()))
return false;
// a creature can only sit in one party frame at a time
if (Group* previous = sGroupMgr->GetGroupByGUID(creature->GetPartyGroupGUID()))
if (previous != this)
previous->RemoveNpcMember(creature->GetGUID());
NpcMemberSlot& slot = m_npcMemberSlots.emplace_back();
slot.guid = creature->GetGUID();
slot.name = creature->GetName();
slot._class = creature->GetClass();
slot.factionGroup = 0; // 0 for every creature member in the captures
slot.subGroup = 0;
slot.flags = MEMBER_FLAG_COMPANION;
// the captures disagree on the role, so it belongs to the companion rather than to this code:
// damage in the two Gulf/Deatholme runs, tank in the Darkway one, 4/8/8/8 for the four NPCs of
// a follower dungeon, and 0 in the first frame of the Shadow Enclave run
slot.roles = roles;
creature->SetPartyGroupGUID(m_guid);
// retail sends no SMSG_PARTY_MEMBER_FULL_STATE for a creature member (none in any of the four
// captures): the client fills the frame from the unit's own object update.
SendUpdate();
return true;
}
bool Group::RemoveNpcMember(ObjectGuid guid)
{
auto slot = std::ranges::find(m_npcMemberSlots, guid, &NpcMemberSlot::guid);
if (slot == m_npcMemberSlots.end())
return true;
m_npcMemberSlots.erase(slot);
// an NPC party only exists for its creatures - the last one leaving takes the party frame with it
if (IsNpcParty() && m_npcMemberSlots.empty())
{
// gulf 1129673: the client is told with SMSG_GROUP_UNINVITE and an empty party update, not
// with the SMSG_GROUP_DESTROYED that ending a player group sends
for (MemberSlot const& memberSlot : m_memberSlots)
if (Player* player = ObjectAccessor::FindConnectedPlayer(memberSlot.guid))
{
WorldPackets::Party::GroupUninvite uninvite;
uninvite.Reason = WorldPackets::Party::GROUP_UNINVITE_REASON_COMPANION_PARTY_ENDED;
player->SendDirectMessage(uninvite.Write());
}
Disband(true);
return false;
}
SendUpdate();
return true;
}
bool Group::IsNpcMember(ObjectGuid guid) const
{
return std::ranges::find(m_npcMemberSlots, guid, &NpcMemberSlot::guid) != m_npcMemberSlots.end();
}
//WowCommunity
void Group::SendUpdate() const
{
@@ -866,7 +962,7 @@ void Group::SendUpdateToPlayer(Player* player, MemberSlot const* slot /*= nullpt
partyUpdate.PartyFlags = m_groupFlags;
partyUpdate.PartyIndex = m_groupCategory;
partyUpdate.PartyType = IsCreated() ? GROUP_TYPE_NORMAL : GROUP_TYPE_NONE;
partyUpdate.PartyType = IsCreated() ? (IsNpcParty() ? GROUP_TYPE_COMPANION : GROUP_TYPE_NORMAL) : GROUP_TYPE_NONE;
partyUpdate.PartyGUID = m_guid;
partyUpdate.LeaderGUID = m_leaderGuid;
@@ -903,7 +999,21 @@ void Group::SendUpdateToPlayer(Player* player, MemberSlot const* slot /*= nullpt
playerInfos.RolesAssigned = citr->roles; // Lfg Roles
}
if (GetMembersCount() > 1)
for (NpcMemberSlot const& npcSlot : m_npcMemberSlots)
{
WorldPackets::Party::PartyPlayerInfo& npcInfos = partyUpdate.PlayerList.emplace_back();
npcInfos.GUID = npcSlot.guid;
npcInfos.Name = npcSlot.name;
npcInfos.Class = npcSlot._class;
npcInfos.FactionGroup = npcSlot.factionGroup;
npcInfos.Connected = true;
npcInfos.Subgroup = npcSlot.subGroup;
npcInfos.Flags = npcSlot.flags;
npcInfos.RolesAssigned = npcSlot.roles;
}
if (partyUpdate.PlayerList.size() > 1)
{
// LootSettings
partyUpdate.LootSettings.emplace();
@@ -1395,6 +1505,10 @@ void Group::LinkOwnedInstance(GroupInstanceReference* ref)
void Group::_homebindIfInstance(Player* player)
{
// leaving a party of NPC companions is not leaving the group that owns the instance
if (IsNpcParty())
return;
if (player && !player->IsGameMaster() && sMapStore.LookupEntry(player->GetMapId())->IsDungeon())
player->m_InstanceValid = false;
}
+34 -2
View File
@@ -76,7 +76,8 @@ enum GroupMemberFlags
{
MEMBER_FLAG_ASSISTANT = 0x01,
MEMBER_FLAG_MAINTANK = 0x02,
MEMBER_FLAG_MAINASSIST = 0x04
MEMBER_FLAG_MAINASSIST = 0x04,
MEMBER_FLAG_COMPANION = 0x10 // NPC party member (12.1 captures: every creature member carries it)
};
enum GroupMemberAssignment
@@ -89,7 +90,8 @@ enum GroupType
{
GROUP_TYPE_NONE = 0,
GROUP_TYPE_NORMAL = 1,
GROUP_TYPE_WORLD_PVP = 4
GROUP_TYPE_WORLD_PVP = 4,
GROUP_TYPE_COMPANION = 5 // party holding NPC companions (all four delve captures; LFG groups send 3)
};
enum GroupFlags : uint16
@@ -220,6 +222,22 @@ class TC_GAME_API Group
uint8 roles;
bool readyChecked;
};
//WowCommunity
// A creature sharing the party frame with the group's players (SPELL_EFFECT_CHANGE_PARTY_MEMBERS,
// "Npc Join Player Party" and friends). These members exist only in memory: they are never written
// to `group_member`, never counted towards the group size and never become leader or looter.
struct NpcMemberSlot
{
ObjectGuid guid;
std::string name;
uint8 _class;
uint8 factionGroup;
uint8 subGroup;
uint8 flags;
uint8 roles;
};
//WowCommunity
typedef std::list<MemberSlot> MemberSlotList;
typedef MemberSlotList::const_iterator member_citerator;
@@ -426,12 +444,26 @@ class TC_GAME_API Group
bool AddFollowerMemberTank(Creature* creature);
bool AddFollowerMemberDPS(Creature* creature);
bool AddFollowerMemberHealer(Creature* creature);
// Non-player party members. A group created by CreateNpcParty exists only to give a player a party
// frame for its companions; it holds a single player and disbands with its last creature.
static Group* CreateNpcParty(Player* player);
bool AddNpcMember(Creature* creature, uint8 roles = 0);
bool RemoveNpcMember(ObjectGuid guid); // false when the group disbanded itself
bool IsNpcMember(ObjectGuid guid) const;
std::vector<NpcMemberSlot> const& GetNpcMemberSlots() const { return m_npcMemberSlots; }
uint32 GetNpcMembersCount() const { return uint32(m_npcMemberSlots.size()); }
bool IsNpcParty() const { return m_npcParty; }
void SetNpcParty(bool npcParty) { m_npcParty = npcParty; }
private:
std::vector<ObjectGuid> m_followerBots;
bool AddFollowerMember(Creature* creature, uint8 lfgRoles);
void RemoveFollowerBotSlot(ObjectGuid guid);
bool IsFollowerDungeon(Player const* player) const;
protected:
std::vector<NpcMemberSlot> m_npcMemberSlots;
bool m_npcParty = false;
//WowCommunity
protected:
+15
View File
@@ -605,3 +605,18 @@ void WorldSession::SendLfgSlotInvalid(lfg::LfgSlotInvalidReason reason, int32 su
slotInvalid.SubReason2 = subReason2;
SendPacket(slotInvalid.Write());
}
void WorldSession::HandleDFReadyCheckResponse(WorldPackets::LFG::DFReadyCheckResponse& dfReadyCheckResponse)
{
TC_LOG_DEBUG("lfg", "CMSG_DF_READY_CHECK_RESPONSE {} isReady: {}", GetPlayerInfo(), dfReadyCheckResponse.IsReady);
Player* player = GetPlayer();
if (!player)
return;
Group const* group = player->GetGroup();
if (!group)
return;
//sLFGMgr->UpdateReadyCheck(group->GetGUID(), player->GetGUID(), dfReadyCheckResponse.IsReady);
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+320 -11
View File
@@ -21,9 +21,14 @@
#include "Define.h"
#include "ObjectGuid.h"
#include "LFGListPackets.h"
#include "SharedDefines.h"
#include <array>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
class Group;
class Player;
// Premade Group Finder (the "Premade Groups" tab). Server-side registry of player-published group listings + the
@@ -46,7 +51,12 @@ namespace LFGList
{
uint32 Id = 0;
ObjectGuid ApplicantGuid; // the applying player (or group leader)
uint8 RoleMask = 0;
uint8 RoleMask = 0; // the role the applicant asked for
// The role the LEADER assigned when inviting. CMSG_LFG_LIST_INVITE_APPLICANT carries an Invitees[]
// list of { PackedGuid, u8 RoleMask } (client writer RVA 0x6A4A30) precisely so the leader can grant
// a role other than the one applied for - a healer applicant slotted as damage, say. Zero until an
// invite names it; SMSG_LFG_LIST_APPLICATION_STATUS_UPDATE.RoleGranted then carries this, not RoleMask.
uint8 GrantedRoleMask = 0;
uint32 SpecID = 0;
uint32 ItemLevel = 0;
std::string Comment;
@@ -54,18 +64,130 @@ namespace LFGList
uint32 AppliedTime = 0; // drives the retail 300s application timeout (sniff-verified)
};
// How the player has dealt with a flagged (censored) listing. Mirrors the tri-state the client keeps
// at LFG-list manager offset +0x13E0: 1 = flagged and undecided, 2 = the player confirmed it as is,
// 0 = not flagged / cleared. C_LFGList.IsCensoredActiveEntryUnresolved tests for exactly the 1.
enum class CensorState : uint8
{
None = 0,
Unresolved = 1,
Confirmed = 2,
};
// AdvancedFilterOptions as the client packs it into CMSG_LFG_LIST_SEARCH.AdvancedFilterMask (client RVA 0x24E1030,
// LSB first in the order Blizzard_APIDocumentationGenerated/LFGListInfoDocumentation.lua declares the fields).
enum AdvancedFilterFlag : uint32
{
ADVANCED_FILTER_NEEDS_TANK = 0x0001,
ADVANCED_FILTER_NEEDS_HEALER = 0x0002,
ADVANCED_FILTER_NEEDS_DAMAGE = 0x0004,
ADVANCED_FILTER_NEEDS_MY_CLASS = 0x0008,
ADVANCED_FILTER_HAS_TANK = 0x0010,
ADVANCED_FILTER_HAS_HEALER = 0x0020,
ADVANCED_FILTER_DIFFICULTY_NORMAL = 0x0040,
ADVANCED_FILTER_DIFFICULTY_HEROIC = 0x0080,
ADVANCED_FILTER_DIFFICULTY_MYTHIC = 0x0100,
ADVANCED_FILTER_DIFFICULTY_MYTHIC_PLUS = 0x0200,
ADVANCED_FILTER_GENERAL_PLAYSTYLE_1 = 0x0400, // Learning
ADVANCED_FILTER_GENERAL_PLAYSTYLE_2 = 0x0800, // FunRelaxed
ADVANCED_FILTER_GENERAL_PLAYSTYLE_3 = 0x1000, // FunSerious
ADVANCED_FILTER_GENERAL_PLAYSTYLE_4 = 0x2000, // Expert
ADVANCED_FILTER_DIFFICULTY_ANY = ADVANCED_FILTER_DIFFICULTY_NORMAL | ADVANCED_FILTER_DIFFICULTY_HEROIC
| ADVANCED_FILTER_DIFFICULTY_MYTHIC | ADVANCED_FILTER_DIFFICULTY_MYTHIC_PLUS,
ADVANCED_FILTER_GENERAL_PLAYSTYLE_ANY = ADVANCED_FILTER_GENERAL_PLAYSTYLE_1 | ADVANCED_FILTER_GENERAL_PLAYSTYLE_2
| ADVANCED_FILTER_GENERAL_PLAYSTYLE_3 | ADVANCED_FILTER_GENERAL_PLAYSTYLE_4,
};
// What a listing's party looks like to a browser: C_LFGList.GetSearchResultMemberCounts' TANK / HEALER / DAMAGER
// and per-class counts, taken from the very role bytes the search row carries so the two cannot disagree.
struct MemberComposition
{
uint8 Tanks = 0;
uint8 Healers = 0;
uint8 Damagers = 0;
std::array<uint8, MAX_CLASSES> Classes = { };
};
// The search terms of one CMSG_LFG_LIST_SEARCH, as the packet hands them over: one inner vector per
// term block, holding that block's non-empty values. Blocks are ANDed, values inside a block ORed -
// see LFGListSearch::GetKeywords, which owns that decision and marks it.
using SearchKeywords = std::vector<std::vector<std::string>>;
// Everything one CMSG_LFG_LIST_SEARCH asks for, in one object, so the search reply and the live push
// cannot drift apart: both run it through LFGListMgr::Matches and there is no second copy of the rule.
// Field meanings and their sources are documented on WorldPackets::LFGList::LFGListSearch; in short,
// ResolvedActivityIds is the set the CLIENT derived from category + filter + search box, while
// ActivityIds and ActivityGroupIds are the explicit narrowings from the advanced filter. Every member
// left empty / 0 is a wildcard.
struct SearchFilter
{
uint32 CategoryId = 0;
std::vector<uint32> ResolvedActivityIds; // GroupFinderActivity ids, client-resolved
std::vector<uint32> ActivityIds; // GroupFinderActivity ids, C_LFGList.Search arg 7
std::vector<uint32> ActivityGroupIds; // GroupFinderActivityGrp ids, advancedFilter.activities
SearchKeywords Keywords;
uint32 AdvancedFilterMask = 0; // AdvancedFilterFlag
uint32 MinimumRating = 0; // advancedFilter.minimumRating, against the leader's dungeon score
uint32 LanguageMask = 0; // one bit per LocaleConstant; 0 = no restriction
uint8 SearcherClass = 0; // needsMyClass is relative to the player who searches
};
// One published group listing.
struct Listing
{
uint32 Id = 0; // server-issued listing id (RideTicket.Id)
ObjectGuid LeaderGuid;
ObjectGuid GroupGuid; // the leader's group (empty = solo listing)
ObjectGuid GroupGuid; // the leader's group (empty = solo listing), LIVE - see TicketGuid
// The listing's identity on the wire, frozen at CreateListing and never written again. It is the
// group guid if the leader already had a group when publishing, otherwise the leader's own guid.
// It exists because GroupGuid is LIVE: a solo leader who publishes and then accepts an applicant
// gets a group created for him (LFGListHandler, HandleLFGListInviteResponse), and deriving the
// ticket from GroupGuid made the ticket of an already-published listing change underneath the
// client. The consumer of SMSG_LFG_LIST_UPDATE_STATUS (RVA 0x24DE410) compares the incoming ticket
// against its stored active entry field by field and drops anything that differs, so after that
// flip the leader's own delist, the expiry and every application status update were discarded in
// silence and the entry stayed on his screen. The row header of a search result is the same ticket
// in disguise, so an open browser saw a NEW row instead of an update of the one it had.
// GroupGuid stays live on purpose - it is what enumerates the current members.
ObjectGuid TicketGuid;
WorldPackets::LFGList::ListingDescriptor Descriptor;
// SMSG_LFG_LIST_SEARCH_RESULTS(+_UPDATE) row revision. The client applies an update record only when its revision
// is not older than the row it holds (0x7FF7CF2AF730), so it has to grow with every change. Every fresh 12.1
// retail row carries 3.
uint32 Revision = 3;
// Who last changed the listing, and its name / comment / voice-chat text (row guids +1840..+1888).
ObjectGuid LastEditorGuid;
ObjectGuid NameEditorGuid;
ObjectGuid CommentEditorGuid;
ObjectGuid VoiceChatEditorGuid;
uint32 CreatedTime = 0;
uint32 ExpireTime = 0;
std::vector<Application> Applications;
// Every client that has been handed this listing with Listed = true, and therefore holds an active
// entry for it. The delist has to reach ALL of them, not just the leader: the consumer of
// SMSG_LFG_LIST_UPDATE_STATUS (RVA 0x24DE410) creates the entry from any Listed payload whose ticket
// it does not know yet, and an applicant who accepts an invite is sent exactly that (status 0x19).
// Notifying only the leader left the joined member's group finder showing the entry until relog -
// the very damage DelistAndNotify exists to prevent, one flank short. Maintained by
// WorldSession::SendLFGListUpdateStatus, which is the only producer of a Listed payload.
std::unordered_set<ObjectGuid> StatusRecipients;
CensorState Censor = CensorState::None;
uint8 CensorCode = 0;
uint8 CensorFieldFlags = 0; // search row +2161: 1 = the name is flagged, 2 = the comment // non-zero is what makes the client treat the entry as flagged
uint32 GetCategoryID() const { return Descriptor.CategoryID; }
// Flagged at all - this is what the wire carries, and what decides whether
// SMSG_LFG_LIST_CENSORED_ACTIVE_ENTRY_UPDATE goes out with a code.
bool IsCensored() const { return Censor != CensorState::None && CensorCode != 0; }
// Flagged AND still undecided - this, and only this, is what withholds the listing's text.
// Once the player has confirmed the listing (CensorState::Confirmed, set by
// CMSG_LFG_LIST_CONFIRM_CENSORED_ACTIVE_ENTRY) the text goes out again. It has to: this wire can
// only ever push the client's censor state to 0 or 1, so a confirmed listing is never re-announced,
// and a client that lost its local 2 (any UI reload) would otherwise render an EMPTY title with no
// dialog and no explanation left to account for it. Withholding past the confirmation buys nothing
// and costs the owner their listing's name. See GetPublicDescriptor.
bool IsTextWithheld() const { return Censor == CensorState::Unresolved && CensorCode != 0; }
};
}
@@ -80,31 +202,183 @@ public:
uint32 CreateListing(Player* leader, WorldPackets::LFGList::ListingDescriptor const& descriptor);
bool UpdateListing(uint32 listingId, ObjectGuid leader, WorldPackets::LFGList::ListingDescriptor const& descriptor);
void RemoveListing(uint32 listingId, ObjectGuid leader);
void RemoveListingsBy(ObjectGuid leader); // logout cleanup
// Delist whatever this leader has listed - logout cleanup, and the replace step of a re-publish.
// Goes through DelistAndNotify like every other server-initiated delist; see its definition for why
// erasing the maps in silence was wrong on both paths.
void RemoveListingsBy(ObjectGuid leader);
// Delist AND tell everyone holding an active entry for the listing, in that order, from one place. The
// notification has to be built while the listing still exists: the client only accepts a delist whose
// ticket matches its stored active entry field for field (see FillListingTicket below), and a ticket
// cannot be reconstructed once the listing is gone. Every server-initiated delist goes through here; a
// hand-built "not listed" message is silently dropped by the client and leaves the entry standing in the
// recipient's group finder.
// "Everyone" is the leader plus Listing::StatusRecipients - an applicant who accepted an invite was sent
// the listing with Listed = true (status 0x19) and holds an entry of its own. Addressing the leader alone
// fixed the create/edit flank and left that one standing until relog.
// `status` selects the client's popup (consumer RVA 0x24DE410): 0x2C too many players, 0x3B timeout,
// 0x4B GameError 0x291, anything else no popup at all.
void DelistAndNotify(uint32 listingId, ObjectGuid leader, uint8 status);
LFGList::Listing* GetListing(uint32 listingId);
LFGList::Listing const* GetListing(uint32 listingId) const;
LFGList::Listing* GetListingByLeader(ObjectGuid leader);
// The listing published for THIS group, if any. There is no index for it because there cannot be more
// than a handful of listings at a time and because an index keyed on Listing::GroupGuid would have to be
// maintained across the one place that writes it after CreateListing (a solo leader whose first
// applicant forms the party, WorldSession::HandleLFGListInviteResponse). The three group hooks below are
// its only callers.
LFGList::Listing* GetListingByGroup(ObjectGuid groupGuid);
// Search the registry. Any argument left 0 acts as a wildcard. Results are capped by config.
std::vector<LFGList::Listing const*> Search(uint32 category, uint32 activityGroup, uint32 activityId, std::string const& keyword = std::string()) const;
// ---- the group's own lifecycle, as it reaches the listing (LFGGroupScript) ----
// Without these three the listing outlives the group it advertises. Logout of the leader and the expiry
// sweep were the only two events that could ever end a listing, so a disbanded group kept a phantom row
// in the browser for the rest of the 30-minute window, a promoted leader could not touch the listing of
// his own party, and a member who left kept an active entry until relog.
// The party has a new leader. The listing follows him: Listing::LeaderGuid is what UpdateListing,
// RemoveListing and DelistAndNotify all check the sender against, so leaving it on the previous leader
// locks the listing - the new leader can neither edit nor delist it while it goes on being advertised.
// He is also sent the active entry, because he needs one for C_LFGList.HasActiveEntryInfo to be true.
//
// Retail additionally WARNS him and delists on a timer, and that half does not live here: the warning
// is SMSG_PARTY_NOTIFY_LFG_LEADER_CHANGE (0x45030A, family 0x45, still STATUS_UNHANDLED and NOT part of
// this unit), which feeds LFG_GROUP_DELISTED_LEADERSHIP_CHANGE(listingName, automaticDelistTimeRemaining)
// (LFGListInfoDocumentation.lua:684-691) and through it the popup PREMADE_GROUP_LEADER_CHANGE_DELIST_WARNING
// (LFGList.lua:346-348). The delist itself is then the CLIENT's: GameDialogDefs.lua:3286-3317 shows the
// popup calling C_LFGList.RemoveListing() from its OnHide unless the new leader picks "List My Group" -
// i.e. the server must NOT delist here, it must keep the listing alive and owned so that the client's
// choice has something to act on. See dod_luecken/D2 in the status file for the handover.
void TransferListingLeadership(ObjectGuid groupGuid, ObjectGuid newLeader);
// The party is gone. So is anything it advertised.
void RemoveListingsByGroup(ObjectGuid groupGuid);
// One member joined a listed party. Nothing to decide, only something to say: the row a browser holds
// enumerates the LIVE group, so it is out of date until this pushes it.
void NotifyGroupMemberJoined(ObjectGuid groupGuid);
// One member left a listed party - the listing stays, that member's active entry must not. Also
// refreshes the row in every open browser, without the departing member.
void NotifyGroupMemberLeft(ObjectGuid groupGuid, ObjectGuid member);
// Search the registry. Every empty / zero member of the filter is a wildcard. Results are capped by
// config. The per-listing test is Matches(), which the live push uses too.
std::vector<LFGList::Listing const*> Search(LFGList::SearchFilter const& filter) const;
// Does this listing satisfy a search filter? THE one place that answers it - the search reply and the
// live SMSG_LFG_LIST_SEARCH_RESULTS_UPDATE push both call it, so a pushed row can never reach a browser
// whose filters would have excluded it from the reply. That agreement used to be a promise kept by two
// hand-maintained copies of the same conditions.
static bool Matches(LFGList::Listing const& listing, LFGList::SearchFilter const& filter);
// Does this listing satisfy the keyword part of a search? The ONE place that answers it, because the
// search reply and the live push have to agree - and because it has to agree with what
// GetPublicDescriptor actually delivers. A listing whose text is withheld is matched against an EMPTY
// name, i.e. it is never a keyword hit: matching the STORED name would have let a searcher confirm a
// word inside the withheld text from a result row that renders as CENSORED_LFG_GROUP_NAME, which is the
// one thing withholding the text is supposed to prevent. Such a listing still shows up in an unfiltered
// browse, exactly as in retail.
static bool MatchesKeywords(LFGList::Listing const& listing, LFGList::SearchKeywords const& keywords);
// The role a member advertises in a search row (0 tank, 1 healer, 2 damage - ChrSpecialization.Role): the role the
// party assigned (Group member slot, set when an applicant is invited with a granted role), else the member's
// specialization role. Used for the row's role byte and for the advanced filter, so both see the same party.
static uint8 GetMemberRole(Player const* player, Group const* group);
static LFGList::MemberComposition GetMemberComposition(LFGList::Listing const& listing, ObjectGuid excludeMember = ObjectGuid::Empty);
// The leader's overall Mythic+ rating (PlayerData.DungeonScore), what advancedFilter.minimumRating is compared to.
static float GetLeaderDungeonScore(LFGList::Listing const& listing);
// advancedFilter's difficulty bands for the listing's first activity (LFGList.lua uses activityIDs[1]). True when the
// activity is in no band the filter leaves out.
static bool MatchesDifficultyBand(LFGList::Listing const& listing, uint32 advancedFilterMask);
// Fills one search-result row for a listing. Shared by the search reply, the apply-result snapshot and the
// live update push so all three serialize a listing identically.
void FillSearchRow(WorldPackets::LFGList::SearchResultListing& row, LFGList::Listing const& listing) const;
// `excludeMember` leaves one guid out of the roster. It exists for exactly one caller and for a reason
// that is not negotiable: LFGGroupScript::OnRemoveMember fires from the FIRST statement of
// Group::RemoveMember (Group.cpp:551), long before the member slot is erased, so a row built from the
// live group at that moment still advertises the player who is leaving.
// `viewer` is the player the row is written for (the friend / guild-mate lists and hasSelf are relative to them);
// null for an update record, which carries none of those.
void FillSearchRow(WorldPackets::LFGList::SearchResultListing& row, LFGList::Listing const& listing,
Player const* viewer, ObjectGuid excludeMember = ObjectGuid::Empty) const;
// The two RideTickets of the listing system. These MUST be built in exactly one place: the client keys
// its stored active entry on the whole 32-byte ticket and compares it field by field before it accepts a
// delist (SMSG_LFG_LIST_UPDATE_STATUS consumer @ RVA 0x24DE410, six comparisons against LFG-list manager
// +0..+24). A ticket assembled differently on one path than on another is silently ignored by the client.
static void FillListingTicket(WorldPackets::LFG::RideTicket& ticket, LFGList::Listing const& listing);
static void FillApplicationTicket(WorldPackets::LFG::RideTicket& ticket, LFGList::Application const& app);
// The absolute deadline of an application, as BOTH messages of the application strand carry it:
// SMSG_LFG_LIST_APPLY_TO_GROUP_RESULT.ApplicationExpiration and
// SMSG_LFG_LIST_APPLICATION_STATUS_UPDATE.ApplicationExpiration. The two are not two fields that happen
// to look alike - they land in the SAME client slot. Both messages are decoded into the state setter
// @ RVA 0x24DD190 as (record, state, time, code, byte), and the consumer subtracts the client's time
// base qword_7FF7877C9640 before storing it, i.e. the wire value is an absolute server timestamp.
// Shared because it MUST be: the apply reply used to compute it inline while every status update sent a
// hard 0, so the reply set the applicant's deadline and the status update one line later overwrote it
// with 0 - timebase. Same defect class, same fix, as the drifted ticket builders above.
static uint64 GetApplicationExpiration(LFGList::Application const& app);
// The wire state bits of an application, and one whole applicant record of SMSG_LFG_LIST_APPLICANT_LIST_UPDATE.
// Shared for the same reason as the tickets: the message has two producers (the handler's push and the
// application-timeout sweep) and every field one of them forgets is a field the leader's applicant list loses.
static uint8 ApplicationStateToBits(LFGList::ApplicationState state);
static void FillApplicantInfo(WorldPackets::LFGList::ApplicantInfo& info, LFGList::Application const& app);
// Push the full applicant list of a listing to every connected member of the listed group (sniff: the
// packet goes to all members, not only the leader; solo listings notify just the leader). THE one
// producer of SMSG_LFG_LIST_APPLICANT_LIST_UPDATE - it lived in the handler's anonymous namespace while
// the timeout sweep in Update() had a hand-built copy that addressed the LEADER ALONE, so after every
// application timeout the non-leader members of a listed group kept the expired applicant in their list
// until relog. Same defect class as the four drifted copies of the delist message, and the same fix.
static void SendApplicantList(LFGList::Listing const& listing);
// SMSG_LFG_LIST_APPLICATION_STATUS_UPDATE with the wire state given DIRECTLY instead of derived from
// Application::State. It exists because an application can end in states the server-side enum has no
// member for - "declined because the party filled up", "declined because the listing is gone" - which
// the CLIENT does have (nibbles 6 and 7 of the state->string mapper @ RVA 0x24DADA0). Without it those
// endings were bare returns and the applicant was told nothing at all.
//
// failureReason has NO default on purpose. It is the message's reason field, and the client reads it on
// exactly one state: the setter @ RVA 0x24DD190 forwards it to the error presenter @ RVA 0x24E25E0 only
// when the state resolves to 3 = failed. There it must be one of the presenter table's values or the
// lookup falls off the end and the applicant sees nothing - the state changes, no text appears. On every
// other state pass ApplicationFailureReason::NotAFailure, which is the value the capture carries and
// says in its name that no reason is being claimed. A default would let a Failed caller forget it and
// reinstate exactly the silence this parameter exists to end.
static void SendApplicationStatusBits(LFGList::Listing const& listing, LFGList::Application const& app,
uint8 stateBits, uint8 failureReason);
// The descriptor as it must go out to a client. Identical to the stored one, except that a listing
// whose text was flagged and not yet resolved goes out WITHOUT its name and comment: retail withholds
// them (that is why the edit dialog has to ask the server via C_LFGList.DoesCensoredTextMatch instead
// of comparing locally), and the client renders CENSORED_LFG_GROUP_NAME in their place.
WorldPackets::LFGList::ListingDescriptor GetPublicDescriptor(LFGList::Listing const& listing) const;
// Runs the listing's text through the server-side check and updates its censor state. Returns true if
// the state changed, i.e. if SMSG_LFG_LIST_CENSORED_ACTIVE_ENTRY_UPDATE has to go out.
bool EvaluateCensorship(LFGList::Listing& listing);
// CMSG_LFG_LIST_CONFIRM_CENSORED_ACTIVE_ENTRY: the player keeps the flagged listing as it is.
// Returns true when the state actually moved to Confirmed, and a true return OBLIGES the caller to push
// the descriptor: this is the third mutation of the publicly visible descriptor (IsTextWithheld goes
// false, so GetPublicDescriptor stops clearing Name/Comment and MatchesKeywords stops rejecting), and
// unlike CreateListing and UpdateListing it does not notify by itself - the handler owns the two sends
// because only it has the owner's session. See WorldSession::HandleLFGListConfirmCensoredActiveEntry.
bool ConfirmCensoredListing(uint32 listingId, ObjectGuid leader);
// Live search updates. While a player has the Premade Groups browser open, retail keeps pushing
// SMSG_LFG_LIST_SEARCH_RESULTS_UPDATE as listings appear/change. A search registers the player's filters
// here; listing mutations then push the affected row to every subscriber whose filters still match.
void RegisterSearch(ObjectGuid player, uint32 category, uint32 activityGroup, std::string const& keyword);
void RegisterSearch(ObjectGuid player, LFGList::SearchFilter filter);
void UnregisterSearch(ObjectGuid player);
void NotifyListingChanged(uint32 listingId);
// `changes` (WorldPackets::LFGList::SearchResultChange) names the listing fields the update record carries.
void NotifyListingChanged(uint32 listingId, ObjectGuid excludeMember = ObjectGuid::Empty, uint32 changes = 0);
// Applications. An application gets a globally-unique id the client keys on via a RideTicket.
LFGList::Application* AddApplication(uint32 listingId, ObjectGuid applicant, uint8 roleMask, uint32 specId, uint32 itemLevel, std::string const& comment);
LFGList::Listing* GetListingByApplication(uint32 applicationId);
LFGList::Application* GetApplication(uint32 applicationId);
bool SetApplicationState(uint32 applicationId, LFGList::ApplicationState state);
// Records the role the leader granted in CMSG_LFG_LIST_INVITE_APPLICANT.Invitees[].
bool SetApplicationGrantedRole(uint32 applicationId, uint8 roleMask);
void RemoveApplication(uint32 applicationId);
// Drops every application this player has outstanding (logout cleanup).
void RemoveApplicationsBy(ObjectGuid applicant);
@@ -114,17 +388,52 @@ public:
private:
LFGListMgr() = default;
// Push ONE row of one listing to every open browser whose filters still match it, and reap the
// subscriptions that have lapsed while doing so. The one place that serializes
// SMSG_LFG_LIST_SEARCH_RESULTS_UPDATE, which is what keeps its two occasions in step: a listing that
// changed (delisted = false) and a listing that is about to cease to exist (delisted = true, the sticky
// isDelisted bit - see LFGListSearchResultsUpdate in LFGListPackets.h). The second occasion is why this
// takes the listing by reference rather than an id: it is called from inside DelistAndNotify, where the
// row has to be built while the listing is still there.
// Every push is a change of the listing, so it advances the listing's revision.
void PushSearchRow(LFGList::Listing& listing, bool delisted, ObjectGuid excludeMember, uint32 changes);
// The delist form of SMSG_LFG_LIST_UPDATE_STATUS - Listed = 0, no expiry, no descriptor - built from the
// LIVE listing. THE one builder, for the same reason the two ticket builders are: the client compares
// the incoming ticket against its stored active entry field by field and drops a mismatch in silence
// (consumer RVA 0x24DE410), and four hand-built copies of this message have already drifted apart once.
// Its two callers differ only in who they send it to - DelistAndNotify to everyone holding an entry,
// NotifyGroupMemberLeft to the single player who left.
static void BuildDelistPacket(WorldPackets::LFGList::LFGListUpdateStatus& packet,
LFGList::Listing const& listing, uint8 status);
// An open Premade Groups browser: the filters the player last searched with. 0 / empty = wildcard, matching
// Search(). Refreshed by every search; expires so a client that closed the browser (there is no "stopped
// searching" opcode) stops receiving pushes.
struct SearchSubscription
{
uint32 CategoryId = 0;
uint32 ActivityGroupId = 0;
std::string Keyword;
LFGList::SearchFilter Filter;
uint32 ExpireTime = 0;
};
// Parsed LFGList.CensorWords, lower-cased. Re-parsed whenever the config string changes, which is what
// makes `reload config` take effect without a hook of its own.
std::vector<std::string> _censorWords;
std::string _censorWordsConfig;
bool _censorWordsLoaded = false;
std::vector<std::string> const& GetCensorWords();
// Does this listing text hit the configured word list? The list is LFGList.CensorWords and nothing
// else. It used to be ObjectMgr::IsReservedName, i.e. the `reserved_name` table, and that was wrong in
// both directions: the table ships EMPTY (sql/base/characters_database.sql has not one INSERT for it),
// so on any normal realm the check could never fire and the whole censor pair was dead wire; and where
// an admin does fill it, he fills it with CHARACTER names he wants kept off the realm - class names,
// "gm", "admin" - which would then flag ordinary listing titles, withhold their name and comment from
// every searcher and drop them out of keyword search entirely (MatchesKeywords returns false for a
// withheld listing). Two unrelated policies on one table. Empty list = censorship off, which is the
// shipped default and is an honest off, not an accident.
bool ContainsCensoredWord(std::string const& text);
uint32 _nextListingId = 1;
uint32 _nextApplicationId = 1;
uint32 _expireTimer = 0;
@@ -589,8 +589,10 @@ namespace WorldPackets::BattlePet
_worldPacket << Bits<1>(Winners[1]); // bit4 ? team1 won
_worldPacket.FlushBits();
_worldPacket << uint32(0); // flat field #1: 0 in every captured battle; role unknown, NOT winners
_worldPacket << uint32(NpcCreatureID); // 0 for wild battles (no trainer); trainer entry for NPC battles
// 69587 client reader (0x7FF7CD50FC10) stores these two into one array indexed by team: team0 is the
// player (always 0), team1 the trainer entry, or 0 when the opponent is a wild pet.
for (uint32 creatureId : NpcCreatureID)
_worldPacket << uint32(creatureId);
_worldPacket << uint32(Pets.size());
@@ -617,13 +619,17 @@ namespace WorldPackets::BattlePet
WorldPacket const* PetBattleQueueStatus::Write()
{
// 12.1.0.69587 client reader (Handler_SMSG_PET_BATTLE_QUEUE_STATUS): status, the slot-result
// count, the ride ticket, and only then the slot results themselves. Both captured frames had an
// empty slot-result list, which is why the wrong order still decoded byte for byte.
_worldPacket << uint32(Status);
_worldPacket << Size<uint32>(SlotResult);
for (uint32 result : SlotResult)
_worldPacket << uint32(result);
_worldPacket << Ticket;
for (uint32 result : SlotResult)
_worldPacket << uint32(result);
_worldPacket << OptionalInit(ClientWaitTime);
_worldPacket << OptionalInit(AvgWaitTime);
_worldPacket.FlushBits();
@@ -638,10 +638,11 @@ namespace WorldPackets
bool PvpBattle = false;
// 12.0.7 (68275) JamPetBattleFinalRound (sniff-verified vs b_pets, 5 battles): the winner is the
// per-team flag pair in the flag byte ? bit5=Winners[0] (team0), bit4=Winners[1] (team1). The two
// flat uint32s after the flush are field#1 (0 in every capture, role unknown, NOT winners) and
// NpcCreatureID (0 for wild battles, the trainer entry for NPC battles).
// flat uint32s after the flush are one creature id PER TEAM (69587 client reader 0x7FF7CD50FC10
// stores them into a two-element array); team0 is the player, so its entry is always 0 and team1
// carries the trainer entry, or 0 for a wild battle.
std::array<bool, 2> Winners = {};
uint32 NpcCreatureID = 0;
std::array<uint32, 2> NpcCreatureID = {};
std::vector<PetBattleFinalPet> Pets;
};
@@ -60,13 +60,6 @@ namespace WorldPackets::Garrison
return data;
}
// Sniff-verified for 12.0.1.66102 (148-byte and 156-byte FOLLOWER_CHANGED_QUALITY bodies
// match this writer byte-for-byte ? see SNIFF_AUDIT_12.0.1.66102.md §3.1).
// Note: AGENT_BRIEF_GARRISON.md's Deserialize_JamGarrisonFollower @ 0x7FF75C1752A0 decodes
// the JAM-mirror wire format (account-data field-mask packet, 16 VarUInt32 reads ending in
// HealCost/HealStartTime/HealDuration). That is a SEPARATE code path from the dedicated
// SMSGs ? do NOT transplant the brief's layout here. Audit §5.1 verified that doing so
// produces nonsense values (e.g. DbID=49148642 vs the actual 397927906).
ByteBuffer& operator<<(ByteBuffer& data, GarrisonFollower const& follower)
{
data << uint64(follower.DbID);
@@ -113,7 +106,7 @@ namespace WorldPackets::Garrison
}
// Sniff-verified for 12.0.1.66102 ? observed in the 8316-byte SMSG_GET_GARRISON_INFO_RESULT
// body (SNIFF_AUDIT_12.0.1.66102.md §5.3). int32 ItemFileDataID is always present
// body (SNIFF_AUDIT_12.0.1.66102.md -5.3). int32 ItemFileDataID is always present
// (sometimes zero, sometimes a real DBD ID like 1599042); the trailing OptionalInit bit
// gates an optional ItemInstance blob.
// The brief's Deserialize_JamGarrisonMissionReward @ 0x7FF75C1754C0 (Byte-gated VarUInt32)
@@ -140,11 +133,11 @@ namespace WorldPackets::Garrison
return data;
}
// Sniff-verified for 12.0.1.66102 (SNIFF_AUDIT_12.0.1.66102.md §3.15, embedded in
// Sniff-verified for 12.0.1.66102 (SNIFF_AUDIT_12.0.1.66102.md -3.15, embedded in
// the 188-byte SMSG_GET_GARRISON_INFO_RESULT). MissionScalar(float) sits between
// Flags and ContentTuningID followed by 3 size fields. Note: SMSG_GARRISON_START_MISSION_RESULT
// embeds a Mission whose exact byte layout has a residual ~2-byte misalignment vs this
// writer (audit §4.1) ? needs IDA pseudocode of Build_GarrisonStartMissionResult to fully
// writer (audit -4.1) ? needs IDA pseudocode of Build_GarrisonStartMissionResult to fully
// resolve. The brief's Deserialize_JamGarrisonMission @ 0x7FF75C1755E0 with Currency/
// BonusActions/AutoMissionData tail is the JAM-mirror path, NOT this dedicated SMSG.
ByteBuffer& operator<<(ByteBuffer& data, GarrisonMission const& mission)
@@ -597,7 +590,7 @@ namespace WorldPackets::Garrison
return &_worldPacket;
}
// IDA case 4980791 (§8.45): PackedGuid + sub-call + varU32 size + varU32[size].
// IDA case 4980791 (-8.45): PackedGuid + sub-call + varU32 size + varU32[size].
WorldPacket const* GarrisonOpenCrafter::Write()
{
_worldPacket << NpcGUID;
@@ -609,7 +602,7 @@ namespace WorldPackets::Garrison
return &_worldPacket;
}
// IDA case 4980817 (§8.51): generic byte-block helper. Conservative: u32 NewMinLevel.
// IDA case 4980817 (-8.51): generic byte-block helper. Conservative: u32 NewMinLevel.
// 8 bytes, not 4 - the client's handler (0x22A0BA0) reads two u32s out of the opaque tail. See the header.
WorldPacket const* GarrisonAutoTroopMinLevelUpdateResult::Write()
{
@@ -619,7 +612,7 @@ namespace WorldPackets::Garrison
return &_worldPacket;
}
// IDA case 4980772 (§8.30): u8 GarrTypeID + GarrisonSmallStruct.
// IDA case 4980772 (-8.30): u8 GarrTypeID + GarrisonSmallStruct.
// Conservative inner shape: {u32 MissionRecID, u32 BonusAbilityID}.
WorldPacket const* GarrisonActivateMissionBonusAbility::Write()
{
@@ -641,22 +634,13 @@ namespace WorldPackets::Garrison
_worldPacket >> followerCount;
_worldPacket >> MissionRecID;
// Cap before resize(): a client-controlled count can't legitimately exceed the packet's own byte size (each
// element is >= 1 byte), so this bounds the allocation to a few KB. resize() throws std::bad_alloc, which the
// opcode dispatcher does NOT catch (only ByteBufferException) -> an uncapped count crashes the world thread.
followerCount = std::min<uint32>(followerCount, _worldPacket.size());
FollowerDBIDs.resize(followerCount);
FollowerBoardIndexes.resize(followerCount);
for (uint32 i = 0; i < followerCount; ++i)
{
_worldPacket >> FollowerDBIDs[i];
// BoardIndex: the ally board slot the player dropped this companion into. This used to be
// skipped as "unused, always -1" ? true for the boardless WoD/Legion mission UIs, but the
// Shadowlands Adventures UI sends a real GarrAutoBoardIndex here (optional third argument of
// C_Garrison.AddFollowerToMission) and then expects it echoed back in
// SMSG_GARRISON_START_MISSION_RESULT to fill its own follower record. Discarding it is what
// left FollowerMissionCompleteInfo.boardIndex at -1 and made the complete screen resolve a
// nil follower frame.
_worldPacket >> FollowerBoardIndexes[i];
_worldPacket.read_skip<int32>(); // Health (client sends 0; the server owns follower health)
_worldPacket.read_skip<uint8>(); // HasFollowerEntry (unused, always false)
@@ -678,9 +662,6 @@ namespace WorldPackets::Garrison
void OpenMissionNpc::Read()
{
_worldPacket >> NpcGUID;
// Trailing uint8 (GarrFollowerTypeID). Consumed but deliberately not stored ? see GarrisonPackets.h for
// why this class must not grow a member. Skipping it keeps the object layout byte-identical and stops the
// "read stop at 14 from 15" tail warning the client's extra byte produced on every open.
_worldPacket.read_skip<uint8>();
}
@@ -694,15 +675,6 @@ namespace WorldPackets::Garrison
// Mission SMSG Write implementations
// ============================================================
// Wire layout: IDA dispatcher case 4980763 (sub_7FF75C1449A0) + sniff-verified
// against 12.0.1.66102 captures (115 / 149 / 149 byte bodies, all decoding cleanly):
// u32 Result, u16 NumOfferedToday,
// u32 FollowerInfoCount, u32 FollowersCount,
// GarrisonMission Mission,
// FollowerInfo[FollowerInfoCount] (each: u64 DbID, i32 BoardIndex, i32 Health,
// u8 HasFollowerEntry; if HasFollowerEntry then u32 FollowerEntry),
// GarrisonFollower[FollowersCount]
// See SNIFF_AUDIT_12.0.1.66102.md §8.1.
WorldPacket const* GarrisonStartMissionResult::Write()
{
_worldPacket << uint32(Result);
@@ -725,9 +697,6 @@ namespace WorldPackets::Garrison
return &_worldPacket;
}
// IDA-confirmed (12.0.5.67186) layout ? see SNIFF_AUDIT §10.1.
// 7 CONFIRMED + 3 HIGH + 2 LOW fields (the LOW are field NAMES inside the 32-byte
// FollowerInfo sub-struct; the wire SHAPE is fully locked in).
WorldPacket const* GarrisonCompleteMissionResult::Write()
{
_worldPacket << uint32(Result);
@@ -746,9 +715,7 @@ namespace WorldPackets::Garrison
_worldPacket << Mission;
// Single byte where the client reads bit7 as Succeeded and bit6 as OvermaxSucceeded.
// Written directly because TC's Bits<1> writer is LSB-first while the client reads
// MSB-first (see SNIFF_AUDIT §10.1.8 bit-pack note).
_worldPacket << uint8((Succeeded ? 0x80u : 0u) | (OvermaxSucceeded ? 0x40u : 0u));
for (GarrisonAutoMissionRound const& round : Rounds)
@@ -769,8 +736,7 @@ namespace WorldPackets::Garrison
_worldPacket << uint32(tgt.OldHealth);
_worldPacket << uint32(tgt.NewHealth);
_worldPacket << uint32(tgt.MaxHealth);
// bit7 set when Points is present; the client reads (n & 0x80) >> 7 then
// gates the optional u32 read on that bit.
_worldPacket << uint8(tgt.Points.has_value() ? 0x80u : 0u);
if (tgt.Points)
_worldPacket << uint32(*tgt.Points);
@@ -781,8 +747,8 @@ namespace WorldPackets::Garrison
return &_worldPacket;
}
// IDA-confirmed (12.0.5.67186) layout ? see SNIFF_AUDIT §10.2. 3 CONFIRMED + 2 HIGH
// fields. FollowerInfo struct shape matches COMPLETE_MISSION_RESULT (§10.1.3).
// IDA-confirmed (12.0.5.67186) layout ? see SNIFF_AUDIT -10.2. 3 CONFIRMED + 2 HIGH
// fields. FollowerInfo struct shape matches COMPLETE_MISSION_RESULT (-10.1.3).
WorldPacket const* GarrisonMissionBonusRollResult::Write()
{
_worldPacket << Mission;
@@ -807,7 +773,7 @@ namespace WorldPackets::Garrison
// IDA case 4980762: u8 GarrTypeID, u32 Result, u8 State, Bits<1>+Flush, GarrisonMission.
// The Mission struct already contains its own Rewards/OvermaxRewards arrays ? duplicating
// them at the outer level (as TC previously did) wrote bytes the client never reads.
// See SNIFF_AUDIT §8.23.
// See SNIFF_AUDIT -8.23.
WorldPacket const* GarrisonAddMissionResult::Write()
{
_worldPacket << uint8(GarrTypeID);
@@ -820,7 +786,7 @@ namespace WorldPackets::Garrison
return &_worldPacket;
}
// IDA case 4980771: u8 GarrTypeID, u32 Result, u32 MissionRecID. See SNIFF_AUDIT §8.29.
// IDA case 4980771: u8 GarrTypeID, u32 Result, u32 MissionRecID. See SNIFF_AUDIT -8.29.
WorldPacket const* GarrisonDeleteMissionResult::Write()
{
_worldPacket << uint8(GarrTypeID);
@@ -855,7 +821,7 @@ namespace WorldPackets::Garrison
return &_worldPacket;
}
// IDA case 4980765 (§8.25): u32 Result, u32 MissionRecID, GarrisonMission.
// IDA case 4980765 (-8.25): u32 Result, u32 MissionRecID, GarrisonMission.
WorldPacket const* GarrisonChangeMissionStartTimeResult::Write()
{
_worldPacket << uint32(Result);
@@ -865,7 +831,7 @@ namespace WorldPackets::Garrison
return &_worldPacket;
}
// IDA case 4980766 (§8.26): u32 Result, u64 LastUsedTimestamp.
// IDA case 4980766 (-8.26): u32 Result, u64 LastUsedTimestamp.
WorldPacket const* GarrisonGetRecallPortalLastUsedTimeResult::Write()
{
_worldPacket << uint32(Result);
@@ -874,7 +840,7 @@ namespace WorldPackets::Garrison
return &_worldPacket;
}
// IDA case 4980767 (§8.27): u32 Result, u32 MissionRecID, u64 RecallTimestamp, GarrisonMission.
// IDA case 4980767 (-8.27): u32 Result, u32 MissionRecID, u64 RecallTimestamp, GarrisonMission.
WorldPacket const* GarrisonUseRecallPortalResult::Write()
{
_worldPacket << uint32(Result);
@@ -885,7 +851,7 @@ namespace WorldPackets::Garrison
return &_worldPacket;
}
// IDA case 4980803 (§8.53): u32 Result, u64 ItemDbID, u32 size, u32 size,
// IDA case 4980803 (-8.53): u32 Result, u64 ItemDbID, u32 size, u32 size,
// GarrisonMissionReward[size], GarrisonMissionReward[size].
WorldPacket const* GarrisonMissionRequestRewardInfoResponse::Write()
{
@@ -993,7 +959,7 @@ namespace WorldPackets::Garrison
// Follower SMSG Write implementations
// ============================================================
// IDA case 4980780: u32 Result, u64 FollowerDBID, u32 PlotInstanceID. See SNIFF_AUDIT §8.37.
// IDA case 4980780: u32 Result, u64 FollowerDBID, u32 PlotInstanceID. See SNIFF_AUDIT -8.37.
WorldPacket const* GarrisonAssignFollowerToBuildingResult::Write()
{
_worldPacket << uint32(Result);
@@ -1003,7 +969,7 @@ namespace WorldPackets::Garrison
return &_worldPacket;
}
// IDA case 4980781: u32 Result, u64 FollowerDBID. See SNIFF_AUDIT §8.38.
// IDA case 4980781: u32 Result, u64 FollowerDBID. See SNIFF_AUDIT -8.38.
WorldPacket const* GarrisonRemoveFollowerFromBuildingResult::Write()
{
_worldPacket << uint32(Result);
@@ -1012,7 +978,7 @@ namespace WorldPackets::Garrison
return &_worldPacket;
}
// IDA case 4980782: u32 Result, GarrisonFollower. See SNIFF_AUDIT §8.39.
// IDA case 4980782: u32 Result, GarrisonFollower. See SNIFF_AUDIT -8.39.
WorldPacket const* GarrisonRenameFollowerResult::Write()
{
_worldPacket << uint32(Result);
@@ -1056,7 +1022,7 @@ namespace WorldPackets::Garrison
}
// IDA case 4980788: u32 Result, GarrisonFollower (single follower).
// See SNIFF_AUDIT §8.43.
// See SNIFF_AUDIT -8.43.
WorldPacket const* GarrisonRecruitFollowerResult::Write()
{
_worldPacket << uint32(Result);
@@ -1065,7 +1031,7 @@ namespace WorldPackets::Garrison
return &_worldPacket;
}
// IDA-confirmed (12.0.5.67186) ? see SNIFF_AUDIT §10.3.
// IDA-confirmed (12.0.5.67186) ? see SNIFF_AUDIT -10.3.
// All 6 fields have two-source evidence (IDA pseudocode + C_Garrison Lua accessors:
// CanGenerateRecruits, CanSetRecruitmentPreference, GetAvailableRecruits,
// SetRecruitmentPreferences). The previous {AbilityCounters, AbilityTraits, GarrTypeID,
@@ -1088,7 +1054,7 @@ namespace WorldPackets::Garrison
return &_worldPacket;
}
// IDA case 4980778 (§8.35): u8 GarrTypeID, u32 Result.
// IDA case 4980778 (-8.35): u8 GarrTypeID, u32 Result.
WorldPacket const* GarrisonFollowerFatigueCleared::Write()
{
_worldPacket << uint8(GarrTypeID);
@@ -1097,7 +1063,7 @@ namespace WorldPackets::Garrison
return &_worldPacket;
}
// IDA case 4980783 (§8.40): full GarrisonFollower.
// IDA case 4980783 (-8.40): full GarrisonFollower.
WorldPacket const* GarrisonRemoveFollowerAbilityResult::Write()
{
_worldPacket << Follower;
@@ -1105,7 +1071,7 @@ namespace WorldPackets::Garrison
return &_worldPacket;
}
// IDA case 4980787 (§8.42): exactly 3 inline GarrisonFollowers (no count prefix).
// IDA case 4980787 (-8.42): exactly 3 inline GarrisonFollowers (no count prefix).
WorldPacket const* GarrisonGenerateFollowersResult::Write()
{
for (GarrisonFollower const& follower : Followers)
@@ -1114,7 +1080,7 @@ namespace WorldPackets::Garrison
return &_worldPacket;
}
// IDA case 4980761 (§8.22): u32 size, GarrisonFollower[size].
// IDA case 4980761 (-8.22): u32 size, GarrisonFollower[size].
WorldPacket const* GarrisonListFollowersCheatResult::Write()
{
_worldPacket << uint32(Followers.size());
@@ -1124,7 +1090,7 @@ namespace WorldPackets::Garrison
return &_worldPacket;
}
// IDA case 4980800 (§8.50): u32 size, u32[size].
// IDA case 4980800 (-8.50): u32 size, u32[size].
WorldPacket const* GarrisonListCompletedMissionsCheatResult::Write()
{
_worldPacket << uint32(MissionRecIDs.size());
@@ -1134,7 +1100,7 @@ namespace WorldPackets::Garrison
return &_worldPacket;
}
// IDA case 4980816 (§8.62-66): u32 Result, u32 MissionRecID, u32 NewState, GarrisonMission.
// IDA case 4980816 (-8.62-66): u32 Result, u32 MissionRecID, u32 NewState, GarrisonMission.
WorldPacket const* GarrisonUpdateMissionCheatResult::Write()
{
_worldPacket << uint32(Result);
@@ -1146,7 +1112,7 @@ namespace WorldPackets::Garrison
}
// ============================================================
// Collection / event-list / spec-group SMSG implementations (§8.54-8.61)
// Collection / event-list / spec-group SMSG implementations (-8.54-8.61)
// ============================================================
WorldPacket const* GarrisonCollectionUpdateEntry::Write()
@@ -1270,7 +1236,7 @@ namespace WorldPackets::Garrison
_worldPacket >> PlotInstanceID2;
}
// IDA case 4980796: 3 × u32. See SNIFF_AUDIT §8.46.
// IDA case 4980796: 3 × u32. See SNIFF_AUDIT -8.46.
WorldPacket const* GarrisonSwapBuildingsResponse::Write()
{
_worldPacket << uint32(Result);
@@ -1299,7 +1265,7 @@ namespace WorldPackets::Garrison
}
// IDA case 4980750: u32 Result, u8 GarrTypeID, Bits<1>+Flush, GarrisonTalent.
// See SNIFF_AUDIT §8.11.
// See SNIFF_AUDIT -8.11.
WorldPacket const* GarrisonResearchTalentResult::Write()
{
_worldPacket << uint32(Result);
@@ -1311,7 +1277,7 @@ namespace WorldPackets::Garrison
return &_worldPacket;
}
// IDA case 4980751: u8, u32, u32, u32. See SNIFF_AUDIT §8.12.
// IDA case 4980751: u8, u32, u32, u32. See SNIFF_AUDIT -8.12.
WorldPacket const* GarrisonTalentCompleted::Write()
{
_worldPacket << uint8(GarrTypeID);
@@ -1322,7 +1288,7 @@ namespace WorldPackets::Garrison
return &_worldPacket;
}
// IDA case 4980752: u8, u32. See SNIFF_AUDIT §8.13.
// IDA case 4980752: u8, u32. See SNIFF_AUDIT -8.13.
WorldPacket const* GarrisonTalentRemoved::Write()
{
_worldPacket << uint8(GarrTypeID);
@@ -1331,7 +1297,7 @@ namespace WorldPackets::Garrison
return &_worldPacket;
}
// IDA case 4980753: u8, u32, optional<TalentSocket> (top-bit-gated). See SNIFF_AUDIT §8.14.
// IDA case 4980753: u8, u32, optional<TalentSocket> (top-bit-gated). See SNIFF_AUDIT -8.14.
WorldPacket const* GarrisonTalentUpdateSocketData::Write()
{
_worldPacket << uint8(GarrTypeID);
@@ -1344,7 +1310,7 @@ namespace WorldPackets::Garrison
return &_worldPacket;
}
// IDA case 4980754: u8, u32. See SNIFF_AUDIT §8.15.
// IDA case 4980754: u8, u32. See SNIFF_AUDIT -8.15.
WorldPacket const* GarrisonTalentRemoveSocketData::Write()
{
_worldPacket << uint8(GarrTypeID);
@@ -1353,7 +1319,7 @@ namespace WorldPackets::Garrison
return &_worldPacket;
}
// IDA case 4980755: u8, u32. See SNIFF_AUDIT §8.16.
// IDA case 4980755: u8, u32. See SNIFF_AUDIT -8.16.
WorldPacket const* GarrisonResetTalentTree::Write()
{
_worldPacket << uint8(GarrTypeID);
@@ -1362,7 +1328,7 @@ namespace WorldPackets::Garrison
return &_worldPacket;
}
// IDA case 4980756: u8, u32. See SNIFF_AUDIT §8.17.
// IDA case 4980756: u8, u32. See SNIFF_AUDIT -8.17.
WorldPacket const* GarrisonResetTalentTreeSocketData::Write()
{
_worldPacket << uint8(GarrTypeID);
@@ -1371,7 +1337,7 @@ namespace WorldPackets::Garrison
return &_worldPacket;
}
// IDA case 4980813: u8 GarrTypeID, u32 size, GarrisonTalent[size]. See SNIFF_AUDIT §8.62.
// IDA case 4980813: u8 GarrTypeID, u32 size, GarrisonTalent[size]. See SNIFF_AUDIT -8.62.
WorldPacket const* GarrisonSwitchTalentTreeBranch::Write()
{
_worldPacket << uint8(GarrTypeID);
@@ -1383,7 +1349,7 @@ namespace WorldPackets::Garrison
}
// IDA case 4980814: opaque (helper). Conservative shape: u8 GarrTypeID + size-prefixed
// list of unlocked talent tree IDs. See SNIFF_AUDIT §8.63.
// list of unlocked talent tree IDs. See SNIFF_AUDIT -8.63.
WorldPacket const* GarrisonTalentWorldQuestUnlocksResponse::Write()
{
_worldPacket << uint8(GarrTypeID);
@@ -1395,7 +1361,7 @@ namespace WorldPackets::Garrison
}
// IDA case 4980815: opaque (helper). Conservative shape: u8 GarrTypeID + size-prefixed
// {GarrTalentID, Socket} pairs. See SNIFF_AUDIT §8.64.
// {GarrTalentID, Socket} pairs. See SNIFF_AUDIT -8.64.
WorldPacket const* GarrisonApplyTalentSocketDataChanges::Write()
{
_worldPacket << uint8(GarrTypeID);
@@ -1458,6 +1424,19 @@ namespace WorldPackets::Garrison
return &_worldPacket;
}
WorldPacket const* SetShipmentReadyResponse::Write()
{
_worldPacket << uint32(Result);
_worldPacket << uint32(Shipment.ShipmentRecID);
_worldPacket << uint64(Shipment.ShipmentID);
_worldPacket << uint64(Shipment.AssignedFollowerDBID);
_worldPacket << Shipment.CreationTime;
_worldPacket << uint64(uint32(Shipment.BuildingTypeID));
_worldPacket << uint8(Shipment.GarrTypeID);
return &_worldPacket;
}
void OpenShipmentNpc::Read()
{
_worldPacket >> NpcGUID;
@@ -1534,7 +1513,7 @@ namespace WorldPackets::Garrison
_worldPacket >> NpcGUID;
}
// IDA case 4980801 (§8.51 pet name): {ObjectGuid NpcGUID, 7-bit-prefixed string PetName}.
// IDA case 4980801 (-8.51 pet name): {ObjectGuid NpcGUID, 7-bit-prefixed string PetName}.
WorldPacket const* QueryGarrisonPetNameResponse::Write()
{
_worldPacket << NpcGUID;
@@ -534,7 +534,7 @@ namespace WorldPackets
uint32 Result = 0;
};
// IDA case 4980791 (§8.45): PackedGuid NpcGUID, sub-call (likely a small descriptor),
// IDA case 4980791 (-8.45): PackedGuid NpcGUID, sub-call (likely a small descriptor),
// varU32 size, varU32[size]. Conservative interpretation: {NpcGUID, u32 GarrTypeID,
// u32[] CraftableItemIDs}.
class GarrisonOpenCrafter final : public ServerPacket
@@ -581,7 +581,7 @@ namespace WorldPackets
uint32 NewMinLevel = 0;
};
// IDA case 4980772 (§8.30): u8 GarrTypeID + GarrisonSmallStruct (likely
// IDA case 4980772 (-8.30): u8 GarrTypeID + GarrisonSmallStruct (likely
// {u32 MissionRecID, u32 BonusAbilityID}). Sent when a mission bonus ability activates.
class GarrisonActivateMissionBonusAbility final : public ServerPacket
{
@@ -668,7 +668,7 @@ namespace WorldPackets
// Per-follower entry ? IDA-confirmed 17-byte (or 21-byte) tuple. The first 17 bytes
// mirror the CMSG_GARRISON_START_MISSION shape; if HasFollowerEntry is set the wire
// appends an additional u32 FollowerEntry. See SNIFF_AUDIT_12.0.1.66102.md §8.1.
// appends an additional u32 FollowerEntry. See SNIFF_AUDIT_12.0.1.66102.md -8.1.
struct GarrisonMissionFollowerEntry
{
uint64 DbID = 0;
@@ -688,7 +688,7 @@ namespace WorldPackets
// IDA-confirmed (12.0.5.67186) + sniff-verified (12.0.1.66102) layout:
// u32 Result, u16 NumOfferedToday, u32 FollowerInfoCount, u32 FollowersCount,
// GarrisonMission Mission, FollowerInfo[FollowerInfoCount], GarrisonFollower[FollowersCount].
// See SNIFF_AUDIT_12.0.1.66102.md §8.1 for the byte-by-byte trace.
// See SNIFF_AUDIT_12.0.1.66102.md -8.1 for the byte-by-byte trace.
uint32 Result = 0;
uint16 NumOfferedToday = 0;
GarrisonMission Mission;
@@ -698,7 +698,7 @@ namespace WorldPackets
// 32-byte per-follower complete-mission record ? IDA-confirmed
// (12.0.5.67186) via Deserialize_CompleteMissionFollowerInfo @ 0x7FF75C175900.
// See SNIFF_AUDIT §10.1.3 + §11 (deepest-pass field-naming).
// See SNIFF_AUDIT -10.1.3 + -11 (deepest-pass field-naming).
//
// Wire format:
// varint64 DbID CONFIRMED ? follower DbID
@@ -721,7 +721,7 @@ namespace WorldPackets
// 24-byte per-target record produced by one auto-combat event. IDA-confirmed
// via the inner combatant loop in sub_7FF75C1750F0 + Lua C-binding accessor
// sub_7FF75CB37FF0 ? see SNIFF_AUDIT §10.1.6. All 6 fields CONFIRMED via
// sub_7FF75CB37FF0 ? see SNIFF_AUDIT -10.1.6. All 6 fields CONFIRMED via
// matching Lua table keys ("boardIndex", "oldHealth", "newHealth", "maxHealth",
// "points").
struct GarrisonAutoMissionTargetInfo
@@ -735,7 +735,7 @@ namespace WorldPackets
// 48-byte per-event auto-combat record (one spell cast / aura tick / ...).
// IDA-confirmed via Deserialize @ 0x7FF75C1750F0 + Lua C-binding accessor
// sub_7FF75CB38250 ? see SNIFF_AUDIT §10.1.5. All 7 fields CONFIRMED via
// sub_7FF75CB38250 ? see SNIFF_AUDIT -10.1.5. All 7 fields CONFIRMED via
// matching Lua table keys ("type", "spellID", "schoolMask", "effectIndex",
// "casterBoardIndex", "auraType", "targetInfo").
struct GarrisonAutoMissionEvent
@@ -750,7 +750,7 @@ namespace WorldPackets
};
// 24-byte pure-container round struct ? IDA-confirmed via the cleanup symbol
// string "struct JamGarrisonAutoMissionRoundInfo" ? see SNIFF_AUDIT §10.1.4.
// string "struct JamGarrisonAutoMissionRoundInfo" ? see SNIFF_AUDIT -10.1.4.
// The Lua API only exposes "events"; no scalar fields. The round index is
// implicit by array position.
struct GarrisonAutoMissionRound
@@ -758,7 +758,7 @@ namespace WorldPackets
std::vector<GarrisonAutoMissionEvent> Events;
};
// IDA-confirmed (12.0.5.67186) layout ? see SNIFF_AUDIT §10.1.
// IDA-confirmed (12.0.5.67186) layout ? see SNIFF_AUDIT -10.1.
// u32 Result, u32 MissionRecID, u8 GarrTypeID,
// u32 FollowerInfoCount, u32 RoundsCount,
// FollowerInfo[FollowerInfoCount] (32 bytes each),
@@ -782,11 +782,11 @@ namespace WorldPackets
std::vector<GarrisonAutoMissionRound> Rounds;
};
// IDA-confirmed (12.0.5.67186) layout ? see SNIFF_AUDIT §10.2.
// IDA-confirmed (12.0.5.67186) layout ? see SNIFF_AUDIT -10.2.
// GarrisonMission Mission, u32 MissionRecID, u32 Result,
// u32 FollowerInfoCount, FollowerInfo[FollowerInfoCount] (32 bytes each),
// single byte (bit7=Succeeded, 7 padding bits).
// Same FollowerInfo struct as COMPLETE_MISSION_RESULT (§10.1.3).
// Same FollowerInfo struct as COMPLETE_MISSION_RESULT (-10.1.3).
class GarrisonMissionBonusRollResult final : public ServerPacket
{
public:
@@ -803,7 +803,7 @@ namespace WorldPackets
// IDA case 4980762: u8 GarrTypeID, u32 Result, u8 State, Bits<1>, GarrisonMission.
// The Mission carries its own Rewards/OvermaxRewards inline, so the wire has no
// outer reward arrays. See SNIFF_AUDIT §8.23.
// outer reward arrays. See SNIFF_AUDIT -8.23.
class GarrisonAddMissionResult final : public ServerPacket
{
public:
@@ -865,7 +865,7 @@ namespace WorldPackets
uint32 Result = 0;
};
// IDA case 4980765 (§8.25): u32 Result, u32 MissionRecID, GarrisonMission.
// IDA case 4980765 (-8.25): u32 Result, u32 MissionRecID, GarrisonMission.
// Sent when a mission's start time is moved (e.g., Master Plan reduces wait).
class GarrisonChangeMissionStartTimeResult final : public ServerPacket
{
@@ -879,7 +879,7 @@ namespace WorldPackets
GarrisonMission Mission;
};
// IDA case 4980766 (§8.26): u32 Result, u64 LastUsedTimestamp.
// IDA case 4980766 (-8.26): u32 Result, u64 LastUsedTimestamp.
class GarrisonGetRecallPortalLastUsedTimeResult final : public ServerPacket
{
public:
@@ -891,7 +891,7 @@ namespace WorldPackets
uint64 LastUsedTimestamp = 0;
};
// IDA case 4980767 (§8.27): u32 Result, u32 ?, u64 ?, GarrisonMission.
// IDA case 4980767 (-8.27): u32 Result, u32 ?, u64 ?, GarrisonMission.
// Conservative interpretation: {Result, MissionRecID, RecallTimestamp, Mission}.
class GarrisonUseRecallPortalResult final : public ServerPacket
{
@@ -906,7 +906,7 @@ namespace WorldPackets
GarrisonMission Mission;
};
// IDA case 4980803 (§8.53): u32 Result, u64 ItemDbID, u32 size, u32 size,
// IDA case 4980803 (-8.53): u32 Result, u64 ItemDbID, u32 size, u32 size,
// GarrisonMissionReward[size], GarrisonMissionReward[size].
// Conservative naming: {Result, ItemDbID, Rewards, OvermaxRewards}.
class GarrisonMissionRequestRewardInfoResponse final : public ServerPacket
@@ -1106,7 +1106,7 @@ namespace WorldPackets
};
// IDA case 4980782: u32 Result, GarrisonFollower (the renamed CustomName lives in
// the trailing SizedString INSIDE the follower struct). See SNIFF_AUDIT §8.39.
// the trailing SizedString INSIDE the follower struct). See SNIFF_AUDIT -8.39.
class GarrisonRenameFollowerResult final : public ServerPacket
{
public:
@@ -1177,7 +1177,7 @@ namespace WorldPackets
};
// IDA case 4980788: u32 Result, GarrisonFollower (single follower, no array prefix).
// See SNIFF_AUDIT §8.43.
// See SNIFF_AUDIT -8.43.
class GarrisonRecruitFollowerResult final : public ServerPacket
{
public:
@@ -1189,7 +1189,7 @@ namespace WorldPackets
GarrisonFollower Follower;
};
// IDA-confirmed (12.0.5.67186) layout ? see SNIFF_AUDIT §10.3, all 6 fields
// IDA-confirmed (12.0.5.67186) layout ? see SNIFF_AUDIT -10.3, all 6 fields
// CONFIRMED with two-source evidence (IDA + Lua C-binding accessor names).
// PackedGuid NpcGUID, u32 MechanicTypeID, u32 TraitID,
// GarrisonFollower Followers[3] (FIXED 3, no count prefix),
@@ -1209,7 +1209,7 @@ namespace WorldPackets
bool CanSetRecruitmentPreference = false;
};
// IDA case 4980778 (§8.35): u8, u32. Conservative: u8 GarrTypeID, u32 Result.
// IDA case 4980778 (-8.35): u8, u32. Conservative: u8 GarrTypeID, u32 Result.
class GarrisonFollowerFatigueCleared final : public ServerPacket
{
public:
@@ -1221,7 +1221,7 @@ namespace WorldPackets
uint32 Result = 0;
};
// IDA case 4980783 (§8.40): single full GarrisonFollower.
// IDA case 4980783 (-8.40): single full GarrisonFollower.
class GarrisonRemoveFollowerAbilityResult final : public ServerPacket
{
public:
@@ -1232,7 +1232,7 @@ namespace WorldPackets
GarrisonFollower Follower;
};
// IDA case 4980787 (§8.42): exactly 3 inline GarrisonFollowers (no count prefix).
// IDA case 4980787 (-8.42): exactly 3 inline GarrisonFollowers (no count prefix).
// Sent in response to CMSG_GARRISON_GENERATE_RECRUITS once new offers are rolled.
class GarrisonGenerateFollowersResult final : public ServerPacket
{
@@ -1244,7 +1244,7 @@ namespace WorldPackets
std::array<GarrisonFollower, 3> Followers;
};
// IDA case 4980761 (§8.22): u32 size, GarrisonFollower[size].
// IDA case 4980761 (-8.22): u32 size, GarrisonFollower[size].
// Cheat opcode ? invoked by .garrison list-followers GM command.
class GarrisonListFollowersCheatResult final : public ServerPacket
{
@@ -1256,7 +1256,7 @@ namespace WorldPackets
std::vector<GarrisonFollower> Followers;
};
// IDA case 4980800 (§8.50): u32 size, u32[size].
// IDA case 4980800 (-8.50): u32 size, u32[size].
// Cheat opcode ? list of completed mission rec IDs.
class GarrisonListCompletedMissionsCheatResult final : public ServerPacket
{
@@ -1268,7 +1268,7 @@ namespace WorldPackets
std::vector<uint32> MissionRecIDs;
};
// IDA case 4980816 (§8.62-8.66 group): u32 Result, u32 MissionRecID, u32 NewState, GarrisonMission.
// IDA case 4980816 (-8.62-8.66 group): u32 Result, u32 MissionRecID, u32 NewState, GarrisonMission.
// Cheat opcode ? used by GM commands to force a mission state change.
class GarrisonUpdateMissionCheatResult final : public ServerPacket
{
@@ -1283,40 +1283,6 @@ namespace WorldPackets
GarrisonMission Mission;
};
// ============================================================
// Collection / event-list / spec-group SMSG packets (opcodes 0x4C0045 - 0x4C004C)
//
// SEMANTICS, recovered from the client's handlers rather than guessed. The event names below were
// PROVEN by recovering the client's Lua-event-name -> 64-bit-hash table and matching the immediates,
// not inferred from spelling:
//
// * A "collection" is the SOULBIND CONDUIT collection. All three collection handlers gate their Lua
// notify on `CollectionType == 1`, firing SOULBIND_CONDUIT_COLLECTION_UPDATED / _REMOVED /
// _CLEARED; consumers are C_Soulbinds.GetConduitCollection / GetConduitCollectionData /
// GetConduitCollectionCount / GetConduitRank. An entry is {conduitID, rank}.
// CollectionType 1 is THE ONLY VALUE WITH A CLIENT CONSUMER - every other value is stored into a
// generic map and is inert. The rest of the CollectionType integer set could not be enumerated
// (no such enum exists in the client's data or as a binary string). DO NOT INVENT ONE.
// The server side needed to populate type 1 already exists: Player::m_soulbindConduits
// (conduitId -> rankIndex, persisted). What does NOT exist is any removal or clear path, since
// conduits are never taken away - so REMOVE/CLEAR have a live consumer but no game event to fire
// on, and GarrisonInfo.Collections is currently never populated at all.
//
// * An "event list" is the garrison/class-hall TALENT event list (research-completion timestamps).
// ADD_EVENT fires GARRISON_TALENT_EVENT_UPDATE; REMOVE_EVENT and CLEAR_EVENT_LIST fire no Lua
// event at all. Entries are {timestamp, entryID} and EventValue is very likely GarrTalentID.
// The notify is NOT gated on a specific type, so these are not hard-blocked the way collections
// are - but no enum of EventListID values could be found, and the server has no event store.
//
// * A "spec group" is the per-ChrSpecialization soulbind memory - see GarrisonAddSpecGroups.
// TC models the active soulbind per COVENANT (Player::m_covenantSoulbinds), not per spec, so
// there is no state to publish here.
// ============================================================
// SUPERSEDED: this used to be described as "Conservative: {u8 GarrTypeID, u8 CollectionEntryFlags,
// u32 GarrTalentID, GarrisonTalentSocketData}" - an admitted guess, and a wrong one (14 bytes
// written where the client reads 13). The real shape was read off the client and is documented on
// the fields below.
class GarrisonCollectionUpdateEntry final : public ServerPacket
{
public:
@@ -1324,19 +1290,12 @@ namespace WorldPackets
WorldPacket const* Write() override;
// 13 bytes: u8 GarrTypeID, u32 CollectionType, u32 EntryID, u32 Rank.
// The reader at RVA 0x71ABB8 does read8, read32, then a two-u32 helper. There is no
// CollectionEntryFlags field - TC's own header called its shape "Conservative", i.e. a guess,
// and it was wrong (14 bytes written vs 13 read).
uint8 GarrTypeID = 0;
uint32 CollectionType = 0;
uint32 EntryID = 0;
uint32 Rank = 0;
};
// u8 GarrTypeID, u32 CollectionType, u32 EntryID (9 bytes, CONFIRMED).
// The third field was named GarrTalentID, which is misleading: it is the same wire field as
// GarrisonCollectionUpdateEntry::EntryID, i.e. a SoulbindConduitID. Renamed.
class GarrisonCollectionRemoveEntry final : public ServerPacket
{
public:
@@ -1349,7 +1308,6 @@ namespace WorldPackets
uint32 EntryID = 0;
};
// IDA case 4980807 (§8.56): u8 GarrTypeID, u32 CollectionType.
class GarrisonClearCollection final : public ServerPacket
{
public:
@@ -1361,7 +1319,6 @@ namespace WorldPackets
uint32 CollectionType = 0;
};
// IDA case 4980808 (§8.57): u8 GarrTypeID, u32 EventListID, u64 Timestamp, u32 EventValue.
class GarrisonAddEvent final : public ServerPacket
{
public:
@@ -1375,7 +1332,6 @@ namespace WorldPackets
uint32 EventValue = 0;
};
// IDA case 4980809 (§8.58): u8 GarrTypeID, u32 EventListID, u32 EventValue.
class GarrisonRemoveEvent final : public ServerPacket
{
public:
@@ -1388,7 +1344,6 @@ namespace WorldPackets
uint32 EventValue = 0;
};
// IDA case 4980810 (§8.59): u8 GarrTypeID, u32 EventListID.
class GarrisonClearEventList final : public ServerPacket
{
public:
@@ -1423,7 +1378,6 @@ namespace WorldPackets
std::vector<GarrisonSpecGroup> SpecGroups;
};
// IDA case 4980812 (§8.61): single byte GarrTypeID.
class GarrisonClearSpecGroups final : public ServerPacket
{
public:
@@ -1512,10 +1466,6 @@ namespace WorldPackets
uint32 PlotInstanceID2 = 0;
};
// IDA case 4980796: 3 × u32. Likely shape is {Result, PlotInstanceID1, PlotInstanceID2}
// mirroring the CMSG. Field meaning of the trailing two u32 is not byte-confirmed
// (no sniff sample); echoing the CMSG plot pair is the conservative choice.
// See SNIFF_AUDIT §8.46.
class GarrisonSwapBuildingsResponse final : public ServerPacket
{
public:
@@ -1563,7 +1513,7 @@ namespace WorldPackets
};
// IDA case 4980750: u32 Result, u8 GarrTypeID, Bits<1> (purpose unknown ? observed
// value 0 in the static analysis), GarrisonTalent. See SNIFF_AUDIT §8.11.
// value 0 in the static analysis), GarrisonTalent. See SNIFF_AUDIT -8.11.
class GarrisonResearchTalentResult final : public ServerPacket
{
public:
@@ -1578,7 +1528,7 @@ namespace WorldPackets
};
// IDA case 4980751: u8 GarrTypeID, u32 GarrTalentID, u32 Rank, u32 ResearchStartTime.
// Sent when a talent's research timer finishes. See SNIFF_AUDIT §8.12.
// Sent when a talent's research timer finishes. See SNIFF_AUDIT -8.12.
class GarrisonTalentCompleted final : public ServerPacket
{
public:
@@ -1592,7 +1542,7 @@ namespace WorldPackets
uint32 ResearchStartTime = 0;
};
// IDA case 4980752: u8 GarrTypeID, u32 GarrTalentID. See SNIFF_AUDIT §8.13.
// IDA case 4980752: u8 GarrTypeID, u32 GarrTalentID. See SNIFF_AUDIT -8.13.
class GarrisonTalentRemoved final : public ServerPacket
{
public:
@@ -1605,7 +1555,7 @@ namespace WorldPackets
};
// IDA case 4980753: u8 GarrTypeID, u32 GarrTalentID, optional<GarrisonTalentSocketData>
// (top-bit-gated by Bits<1>). See SNIFF_AUDIT §8.14.
// (top-bit-gated by Bits<1>). See SNIFF_AUDIT -8.14.
class GarrisonTalentUpdateSocketData final : public ServerPacket
{
public:
@@ -1618,7 +1568,7 @@ namespace WorldPackets
Optional<GarrisonTalentSocketData> Socket;
};
// IDA case 4980754: u8 GarrTypeID, u32 GarrTalentID. See SNIFF_AUDIT §8.15.
// IDA case 4980754: u8 GarrTypeID, u32 GarrTalentID. See SNIFF_AUDIT -8.15.
class GarrisonTalentRemoveSocketData final : public ServerPacket
{
public:
@@ -1630,7 +1580,7 @@ namespace WorldPackets
uint32 GarrTalentID = 0;
};
// IDA case 4980755: u8 GarrTypeID, u32 GarrTalentTreeID. See SNIFF_AUDIT §8.16.
// IDA case 4980755: u8 GarrTypeID, u32 GarrTalentTreeID. See SNIFF_AUDIT -8.16.
class GarrisonResetTalentTree final : public ServerPacket
{
public:
@@ -1642,7 +1592,7 @@ namespace WorldPackets
uint32 GarrTalentTreeID = 0;
};
// IDA case 4980756: u8 GarrTypeID, u32 GarrTalentTreeID. See SNIFF_AUDIT §8.17.
// IDA case 4980756: u8 GarrTypeID, u32 GarrTalentTreeID. See SNIFF_AUDIT -8.17.
class GarrisonResetTalentTreeSocketData final : public ServerPacket
{
public:
@@ -1654,7 +1604,7 @@ namespace WorldPackets
uint32 GarrTalentTreeID = 0;
};
// IDA case 4980813: u8 GarrTypeID, u32 size, GarrisonTalent[size]. See SNIFF_AUDIT §8.62.
// IDA case 4980813: u8 GarrTypeID, u32 size, GarrisonTalent[size]. See SNIFF_AUDIT -8.62.
class GarrisonSwitchTalentTreeBranch final : public ServerPacket
{
public:
@@ -1668,7 +1618,7 @@ namespace WorldPackets
// IDA case 4980814: opaque generic-byte-block helper. Field shape unknown without sniff.
// TC sends a single u8 GarrTypeID + size-prefixed list of unlocked talent tree IDs as
// the conservative interpretation. See SNIFF_AUDIT §8.63.
// the conservative interpretation. See SNIFF_AUDIT -8.63.
class GarrisonTalentWorldQuestUnlocksResponse final : public ServerPacket
{
public:
@@ -1754,6 +1704,17 @@ namespace WorldPackets
std::vector<CharacterShipment> Shipments;
};
class SetShipmentReadyResponse final : public ServerPacket
{
public:
explicit SetShipmentReadyResponse() : ServerPacket(SMSG_SET_SHIPMENT_READY_RESPONSE, 4 + 37) {}
WorldPacket const* Write() override;
uint32 Result = 0; // UNVERIFIED: see class comment
CharacterShipment Shipment;
};
class OpenShipmentNpc final : public ClientPacket
{
public:
@@ -1858,7 +1819,7 @@ namespace WorldPackets
ObjectGuid NpcGUID;
};
// IDA case 4980801 (§8.51 in main audit table ? pet name): u64 NpcGUID-as-DBID + sub-call
// IDA case 4980801 (-8.51 in main audit table ? pet name): u64 NpcGUID-as-DBID + sub-call
// (likely PackedGuid serializer) + 1-byte top-bit-prefixed string. Conservative shape:
// {ObjectGuid NpcGUID, SizedString PetName}.
class QueryGarrisonPetNameResponse final : public ServerPacket
+440 -255
View File
@@ -20,94 +20,153 @@
namespace WorldPackets::LFGList
{
// The published-listing parameters. RESOLVED from the 12.0.7.68275 premade-groups sniff + the client JOIN
// serializer (sub_7FF72914ABE0) + the generated Lua API doc (LfgListingCreateData). The descriptor is BIT-PACKED:
// a bit-packed header (5-bit trailing-vector count; three bit-packed string lengths of 10/11/8 bits; four boolean
// flags; and presence bits for the nilable numeric fields), then FlushBits, then the member-requirement block, the
// fixed activity fields, the trailing uint32 vector, the three strings, and the present optional fields. Only
// CategoryID + the activity vector + item-level drive server filtering; the rest are pass-through echo. Reads are guarded against
// over-run (the descriptor is variable-length and pass-through, so a malformed tail is tolerated, never fatal).
// Full layout + bit-widths: c:\dumps\LFG_LIST_WIRE_68275.md.
namespace
{
// Client buffer capacities of the three descriptor strings; they decide the bit width of each length
// prefix (N = ceil(log2(capacity))) and, because the client appends its own NUL at buf[len], the
// largest string that still fits.
constexpr std::size_t DESCRIPTOR_NAME_CAPACITY = 513; // bits<10>
constexpr std::size_t DESCRIPTOR_COMMENT_CAPACITY = 1025; // bits<11>
constexpr std::size_t DESCRIPTOR_VOICECHAT_CAPACITY = 129; // bits<8>
// The trailing uint32 vector's count is a 5-bit field, so it can never legitimately exceed 31.
constexpr uint32 DESCRIPTOR_MAX_ACTIVITY_IDS = 31;
}
// The embedded DungeonScoreSummary. MythicPlusPacketsCommon already provides operator<< for both this and
// its element type and both are byte-identical to the client's reader 0x6EC830 / writer 0x6EC980
// ({float, float, u32 count} then count x {i32, float, i32, i32, u8, one bit + flush}); only the read direction
// is missing there, so it lives here.
static ByteBuffer& operator>>(ByteBuffer& data, MythicPlus::DungeonScoreMapSummary& run)
{
data >> run.ChallengeModeID;
data >> run.MapScore;
data >> run.BestRunLevel;
data >> run.BestRunDurationMS;
data >> run.Unknown1110;
data >> Bits<1>(run.FinishedSuccess);
data.ResetBitPos();
return data;
}
static ByteBuffer& operator>>(ByteBuffer& data, MythicPlus::DungeonScoreSummary& summary)
{
data >> summary.OverallScoreCurrentSeason;
data >> summary.LadderScoreCurrentSeason;
uint32 runCount = 0;
data >> runCount;
// 18 wire bytes per run; refuse a count the packet cannot possibly contain rather than trusting it.
if (runCount <= (data.size() - data.rpos()) / 18)
{
summary.Runs.resize(runCount);
for (MythicPlus::DungeonScoreMapSummary& run : summary.Runs)
data >> run;
}
return data;
}
// ---------------------------------------------------------------------------------------------------
// ListingDescriptor. See the header for the full layout and for what is measured vs. inferred.
// Reads are size-guarded throughout: the descriptor is variable-length and largely pass-through, so a
// malformed tail must be tolerated, never fatal.
static ByteBuffer& operator>>(ByteBuffer& data, ListingDescriptor& d)
{
auto remaining = [&]() -> std::size_t { return data.size() - data.rpos(); };
// --- bit-packed header (client bit-writer, MSB-first) ---
uint32 vectorCount = data.ReadBits(5); // sub_7FF729064C20: count of the trailing uint32 vector
uint32 str0Len = data.ReadBits(10); // string @0x40 length
uint32 str1Len = data.ReadBits(11); // string @0x241 length
uint32 str2Len = data.ReadBits(8); // string @0x642 length ("crate" in the sniff)
d.IsAutoAccept = data.ReadBits(1) != 0; // presence/flag bits (client offsets 0x6c3..0x703)
d.IsCrossFactionListing = data.ReadBits(1) != 0;
d.IsPrivateGroup = data.ReadBits(1) != 0;
d.NewPlayerFriendly = data.ReadBits(1) != 0;
bool hasQuestId = data.ReadBits(1) != 0; // 0x6cc -> uint32 @0x6c8
bool hasOpt1 = data.ReadBits(1) != 0; // 0x6f4 -> uint32 @0x6f0
bool hasOpt2 = data.ReadBits(1) != 0; // 0x6fc -> uint32 @0x6f8
bool hasOpt3 = data.ReadBits(1) != 0; // 0x701 -> uint8 @0x700
data.ReadBits(1); // 0x703 standalone flag (unused server-side)
data.ResetBitPos(); // FlushBits (sub_7FF729064E60)
// --- bit header, MSB-first, 43 bits (client reader 0x7572C0 / writer 0x757660) ---
uint32 const activityCount = data.ReadBits(5);
uint32 const nameLength = data.ReadBits(10);
uint32 const commentLength = data.ReadBits(11);
uint32 const voiceChatLength = data.ReadBits(8);
d.IsAutoAccept = data.ReadBits(1) != 0; // +1699
d.IsPrivateGroup = data.ReadBits(1) != 0; // +1700
d.IsWarMode = data.ReadBits(1) != 0; // +1701
d.IsCrossFactionListing = data.ReadBits(1) != 0; // +1702
bool const hasQuestId = data.ReadBits(1) != 0;
bool const hasRequiredDungeonScore = data.ReadBits(1) != 0;
bool const hasRequiredPvpRating = data.ReadBits(1) != 0;
bool const hasPlaystyle = data.ReadBits(1) != 0;
d.NewPlayerFriendly = data.ReadBits(1) != 0; // +1763
data.ResetBitPos(); // 5 spare bits; the client never reads them
// --- member-requirement block (nested sub_7FF729167840) ---
data >> d.HeaderFloat0 >> d.HeaderFloat1;
uint32 memberCount = 0;
data >> memberCount;
if (memberCount <= remaining() / 0x11) // 0x11 = min bytes per entry; guard against a bad count
// --- byte-aligned body ---
data >> d.CategoryID;
data >> d.RequiredItemLevel;
// The client zeroes this block on every CreateListing / UpdateListing, so an inbound descriptor always
// carries an empty summary. Read it anyway - it is 12 bytes of wire either way.
data >> d.LeaderScore;
data >> d.GeneralPlaystyle;
// --- deferred: the uint32 vector, then the three strings ---
if (activityCount <= DESCRIPTOR_MAX_ACTIVITY_IDS && activityCount <= remaining() / 4)
{
d.MemberRequirements.resize(memberCount);
for (ListingMemberRequirement& m : d.MemberRequirements)
{
data >> m.Field0 >> m.Field1 >> m.Field2 >> m.Field3 >> m.Field4;
m.Flag = data.ReadBits(1) != 0;
data.ResetBitPos();
}
d.ActivityIDs.resize(activityCount);
for (uint32& activityId : d.ActivityIDs)
data >> activityId;
}
// --- fixed activity fields ---
data >> d.CategoryID; // uint32 @0x38: GroupFinderCategory id (68974: 1; the activities ride in the vector below)
data >> d.RequiredDungeonScore; // float @0x3c
data >> d.TrailingByte; // uint8 @0x702
if (nameLength <= remaining() && nameLength < DESCRIPTOR_NAME_CAPACITY)
d.Name.assign(data.ReadString(nameLength));
if (commentLength <= remaining() && commentLength < DESCRIPTOR_COMMENT_CAPACITY)
d.Comment.assign(data.ReadString(commentLength));
if (voiceChatLength <= remaining() && voiceChatLength < DESCRIPTOR_VOICECHAT_CAPACITY)
d.VoiceChat.assign(data.ReadString(voiceChatLength));
// --- trailing uint32 vector ---
if (vectorCount <= remaining() / 4)
{
d.ActivityIDs.resize(vectorCount);
for (uint32& v : d.ActivityIDs)
data >> v;
}
// --- string data (order matches the serializer: @0x40, @0x241, @0x642) ---
if (str0Len <= remaining()) d.Name.assign(data.ReadString(str0Len));
if (str1Len <= remaining()) d.VoiceChat.assign(data.ReadString(str1Len));
if (str2Len <= remaining()) d.Comment.assign(data.ReadString(str2Len));
// --- present optional (nilable) numeric fields ---
// --- deferred: the present optionals ---
if (hasQuestId && remaining() >= 4) { uint32 v; data >> v; d.QuestID = v; }
if (hasOpt1 && remaining() >= 4) { uint32 v; data >> v; d.OptionalValue1 = v; }
if (hasOpt2 && remaining() >= 4) { uint32 v; data >> v; d.OptionalValue2 = v; }
if (hasOpt3 && remaining() >= 1) { uint8 v; data >> v; d.OptionalValue3 = v; }
if (hasRequiredDungeonScore && remaining() >= 4) { uint32 v; data >> v; d.RequiredDungeonScore = v; }
if (hasRequiredPvpRating && remaining() >= 4) { uint32 v; data >> v; d.RequiredPvpRating = v; }
if (hasPlaystyle && remaining() >= 1) { uint8 v; data >> v; d.Playstyle = v; }
return data;
}
ByteBuffer& operator<<(ByteBuffer& data, ListingInfo const& listing)
static ByteBuffer& operator<<(ByteBuffer& data, ListingDescriptor const& d)
{
for (uint8 param : listing.Params)
data << param;
data << uint32(listing.ActivityID);
data << uint32(listing.Field1);
data << uint8(listing.Field2);
data << uint32(listing.RequiredItemLevel);
data << SizedString::BitsSize<10>(listing.Comment);
data << SizedString::BitsSize<7>(listing.LeaderName);
data << SizedString::BitsSize<7>(listing.VoiceChat);
// The client copies each string into a fixed-size buffer and writes its own NUL at buf[len], so
// capacity-1 is the largest length it can survive. ReadBytes on that side bounds-checks against the
// remaining packet only, never against the target buffer - an over-long string is a client-side
// memory overrun, so clamp here.
std::size_t const nameLength = std::min<std::size_t>(d.Name.length(), DESCRIPTOR_NAME_CAPACITY - 1);
std::size_t const commentLength = std::min<std::size_t>(d.Comment.length(), DESCRIPTOR_COMMENT_CAPACITY - 1);
std::size_t const voiceChatLength = std::min<std::size_t>(d.VoiceChat.length(), DESCRIPTOR_VOICECHAT_CAPACITY - 1);
std::size_t const activityCount = std::min<std::size_t>(d.ActivityIDs.size(), DESCRIPTOR_MAX_ACTIVITY_IDS);
data.WriteBits(activityCount, 5);
data.WriteBits(nameLength, 10);
data.WriteBits(commentLength, 11);
data.WriteBits(voiceChatLength, 8);
data << Bits<1>(d.IsAutoAccept); // +1699
data << Bits<1>(d.IsPrivateGroup); // +1700
data << Bits<1>(d.IsWarMode); // +1701
data << Bits<1>(d.IsCrossFactionListing); // +1702
data << OptionalInit(d.QuestID);
data << OptionalInit(d.RequiredDungeonScore);
data << OptionalInit(d.RequiredPvpRating);
data << OptionalInit(d.Playstyle);
data << Bits<1>(d.NewPlayerFriendly); // +1763
data.FlushBits();
data << SizedString::Data(listing.Comment);
data << SizedString::Data(listing.LeaderName);
data << SizedString::Data(listing.VoiceChat);
data << uint32(listing.Field3);
data << uint32(listing.Field4);
data << uint32(listing.Field5);
data << uint8(listing.Field6);
data << uint32(d.CategoryID);
data << uint32(d.RequiredItemLevel);
data << d.LeaderScore;
data << uint8(d.GeneralPlaystyle);
for (std::size_t i = 0; i < activityCount; ++i)
data << uint32(d.ActivityIDs[i]);
data.WriteString(d.Name.c_str(), nameLength);
data.WriteString(d.Comment.c_str(), commentLength);
data.WriteString(d.VoiceChat.c_str(), voiceChatLength);
if (d.QuestID)
data << uint32(*d.QuestID);
if (d.RequiredDungeonScore)
data << uint32(*d.RequiredDungeonScore);
if (d.RequiredPvpRating)
data << uint32(*d.RequiredPvpRating);
if (d.Playstyle)
data << uint8(*d.Playstyle);
return data;
}
@@ -115,17 +174,13 @@ namespace WorldPackets::LFGList
void LFGListJoin::Read()
{
std::size_t const start = _worldPacket.rpos();
_worldPacket >> Listing;
Listing.RawBytes.assign(_worldPacket.data() + start, _worldPacket.data() + _worldPacket.rpos());
}
void LFGListUpdateRequest::Read()
{
_worldPacket >> Ticket;
std::size_t const start = _worldPacket.rpos();
_worldPacket >> Listing;
Listing.RawBytes.assign(_worldPacket.data() + start, _worldPacket.data() + _worldPacket.rpos());
}
void LFGListLeave::Read()
@@ -133,35 +188,88 @@ namespace WorldPackets::LFGList
_worldPacket >> Ticket;
}
std::vector<std::vector<std::string>> LFGListSearch::GetKeywords() const
{
std::vector<std::vector<std::string>> keywords;
keywords.reserve(Terms.size());
for (LFGListSearchTerm const& term : Terms)
{
std::vector<std::string> alternatives;
for (std::string const& value : term.Values)
if (!value.empty())
alternatives.push_back(value);
// A block with nothing in it constrains nothing. Keeping it would turn every search that carries a
// trailing empty block into a search no listing can satisfy.
if (!alternatives.empty())
keywords.push_back(std::move(alternatives));
}
return keywords;
}
void LFGListSearch::Read()
{
// Sniff-exact (43B no keyword / 56B with one): see header comment. All reads size-guarded.
// Client writer 0x757EC0; term blocks 0x757D00. See the header for the three defects this replaces.
uint32 const termCount = _worldPacket.ReadBits(5);
_worldPacket.ReadBit(); // presence/flag bit (semantics approximate)
_worldPacket >> Bits<1>(CrossFaction);
_worldPacket.ResetBitPos();
if (termCount)
{
std::array<uint32, 10> lengths = { };
for (uint32& len : lengths)
len = _worldPacket.ReadBits(5); // ten bits(5) lengths packed into the 8-byte block
_worldPacket.ReadBits(64 - 10 * 5); // padding to the full 8 bytes
_worldPacket.ResetBitPos();
SearchTerms.resize(std::min<uint32>(termCount, 10));
for (std::size_t i = 0; i < SearchTerms.size(); ++i)
if (lengths[i] && _worldPacket.rpos() + lengths[i] <= _worldPacket.size())
SearchTerms[i] = _worldPacket.ReadString(lengths[i]);
}
for (uint32& f : Filters)
_worldPacket >> f;
_worldPacket >> FilterByte1; // observed 0xFF
_worldPacket >> FilterByte2; // observed 0x05
_worldPacket >> CategoryID;
_worldPacket >> Filter;
_worldPacket >> PreferredFilters;
_worldPacket >> LanguageMask;
uint32 resolvedActivityCount = 0;
_worldPacket >> resolvedActivityCount;
_worldPacket >> AdvancedFilterMask;
uint32 activityGroupCount = 0;
_worldPacket >> activityGroupCount;
uint32 activityCount = 0;
_worldPacket >> activityCount;
_worldPacket >> MinimumRating;
_worldPacket >> FilterByte1;
_worldPacket >> FilterByte2;
uint32 guidCount = 0;
_worldPacket >> guidCount;
if (guidCount <= 50)
// A term block is at least 8 bytes (the interleaved 60-bit header) - refuse counts the packet cannot
// hold before allocating.
auto remaining = [&]() -> std::size_t { return _worldPacket.size() - _worldPacket.rpos(); };
if (termCount && termCount <= remaining() / 8)
{
Terms.resize(termCount);
for (LFGListSearchTerm& term : Terms)
{
std::array<uint32, LFGListSearchTerm::MAX_VALUES> lengths = { };
for (std::size_t i = 0; i < LFGListSearchTerm::MAX_VALUES; ++i)
{
lengths[i] = _worldPacket.ReadBits(5); // client buffer 32 -> ceil(log2(32)) = 5
term.Flags[i] = _worldPacket.ReadBit(); // one presence bit per slot, interleaved
}
_worldPacket.ResetBitPos(); // 60 bits used, 4 padding -> 8 bytes
for (std::size_t i = 0; i < LFGListSearchTerm::MAX_VALUES; ++i)
if (lengths[i] && lengths[i] < LFGListSearchTerm::MAX_VALUE_LENGTH && lengths[i] <= remaining())
term.Values[i] = _worldPacket.ReadString(lengths[i]);
}
}
auto readValues = [&](std::vector<uint32>& out, uint32 count)
{
if (!count || count > remaining() / 4)
return;
out.resize(count);
for (uint32& value : out)
_worldPacket >> value;
};
// Order on the wire is fixed by the client writer and is NOT the order the counts appear in: the
// count of the first list sits at struct +48, i.e. between LanguageMask and AdvancedFilterMask,
// while the list itself follows the term blocks. See the header for what each one carries.
readValues(ResolvedActivityIDs, resolvedActivityCount);
readValues(ActivityGroupIDs, activityGroupCount);
readValues(ActivityIDs, activityCount);
if (guidCount && guidCount <= remaining() / 2) // a PackedGuid is at least its 2-byte mask
{
Guids.resize(guidCount);
for (ObjectGuid& guid : Guids)
@@ -174,7 +282,9 @@ namespace WorldPackets::LFGList
_worldPacket >> Ticket;
_worldPacket >> ActivityID;
_worldPacket >> RoleMask;
_worldPacket >> Field2;
_worldPacket >> SizedString::BitsSize<8>(Comment);
_worldPacket.ResetBitPos();
_worldPacket >> SizedString::Data(Comment);
}
void LFGListCancelApplication::Read()
@@ -191,10 +301,21 @@ namespace WorldPackets::LFGList
void LFGListInviteApplicant::Read()
{
_worldPacket >> Ticket;
_worldPacket >> ListingId;
_worldPacket >> ApplicantGuid;
_worldPacket >> RoleMask;
_worldPacket >> ApplicantTicket;
uint32 inviteeCount = 0;
_worldPacket >> inviteeCount;
// Each invitee is at least 3 bytes (2-byte guid mask + role); a group cannot exceed MAX_RAID_SIZE
// anyway, so a large count is malformed either way.
if (inviteeCount && inviteeCount <= (_worldPacket.size() - _worldPacket.rpos()) / 3)
{
Invitees.resize(inviteeCount);
for (LFGListInvitee& invitee : Invitees)
{
_worldPacket >> invitee.Guid;
_worldPacket >> invitee.RoleMask;
}
}
}
void LFGListInviteResponse::Read()
@@ -204,262 +325,326 @@ namespace WorldPackets::LFGList
_worldPacket.ResetBitPos();
}
void LFGListConfirmCensoredActiveEntry::Read()
{
_worldPacket >> Ticket;
}
// ---- SMSG Write ----
WorldPacket const* LFGListJoinResult::Write()
{
_worldPacket << Ticket;
_worldPacket << uint32(Status);
_worldPacket << uint8(Result);
_worldPacket << uint8(ResultDetail);
return &_worldPacket;
}
WorldPacket const* LFGListUpdateStatus::Write()
{
// Byte order re-derived from the 12.0.7.69587 create sniff (feed 0x5A000A, idx 44791/44792/44912).
// The previous writer (Ticket, ExpirationTime, Status, Descriptor, one Listed bit) is WRONG for this
// build: the descriptor comes straight after the ticket, and the tail is a per-status flag byte, the
// owner's PackedGuid and an optional trailing 0x00. Byte-exact for the statuses we emit:
// 0x06 listed (create) : ticket 27B | desc 39B | u64 expiry | 06 | C0 | owner guid (85B)
// 0x38 listed (steady) : ticket 27B | desc 39B | u64 expiry | 38 | E0 | owner guid | 00 (86B)
// 0x08 delisted : ticket 27B | desc 27x00 | u64 0 | 08 | 60 | owner guid | 00 (74B)
static constexpr std::size_t EMPTY_DESCRIPTOR_SIZE = 27;
_worldPacket << Ticket;
if (Listed && !RawDescriptor.empty())
_worldPacket.append(RawDescriptor.data(), RawDescriptor.size());
else
_worldPacket.append(std::vector<uint8>(EMPTY_DESCRIPTOR_SIZE, 0).data(), EMPTY_DESCRIPTOR_SIZE);
_worldPacket << Listing; // 12.1: directly behind the ticket, not at the end
_worldPacket << uint64(Listed ? ExpirationTime : 0);
_worldPacket << uint8(Status);
_worldPacket << Bits<1>(Listed);
_worldPacket << OptionalInit(LeaderGuid);
_worldPacket << OptionalInit(UnkByte);
_worldPacket.FlushBits();
if (LeaderGuid)
_worldPacket << *LeaderGuid;
if (UnkByte)
_worldPacket << uint8(*UnkByte);
// Tail flag byte + trailing 0x00 are status-dependent (sniff MSB-first bit-flush remnants, reproduced
// verbatim: 0x06 -> 0xC0 no trailing, 0x38 -> 0xE0 + 0x00, 0x08 -> 0x60 + 0x00).
uint8 tailFlags = 0xE0;
bool tail00 = true;
switch (Status)
{
case 0x06: tailFlags = 0xC0; tail00 = false; break;
case 0x08: tailFlags = 0x60; break;
default: tailFlags = 0xE0; break; // 0x38 and unknown statuses
}
_worldPacket << uint8(tailFlags);
_worldPacket << PlayerGuid; // PackedGuid, the listing owner
if (tail00)
_worldPacket << uint8(0);
return &_worldPacket;
}
WorldPacket const* LFGListUpdateExpiration::Write()
{
_worldPacket << Ticket;
_worldPacket << uint64(ExpirationTime);
_worldPacket << uint8(Reason);
return &_worldPacket;
}
WorldPacket const* LFGListCensoredActiveEntryUpdate::Write()
{
// 69587 create sniff (idx 44793): 41B = the 39-byte listing descriptor echoed verbatim + 0x80 + 0x00.
// The 0x80 byte mirrors the "listed" marker; semantics otherwise unknown, bytes cited verbatim.
if (!RawDescriptor.empty())
_worldPacket.append(RawDescriptor.data(), RawDescriptor.size());
_worldPacket << uint8(0x80);
_worldPacket << uint8(0x00);
return &_worldPacket;
}
WorldPacket const* LFGListSearchStatus::Write()
{
_worldPacket << Ticket;
_worldPacket << uint8(Status);
_worldPacket << Bits<1>(Complete);
_worldPacket.FlushBits();
return &_worldPacket;
}
// One MemberDetail record (head sub_7FF7291DBF80 + tail sub_7FF729162CC0), shared between the full
// search-result row and the compact SEARCH_RESULTS_UPDATE row (68974: byte-identical in both).
// Head decoded from the 68974 capture: guid, level, class, role (0 tank/1 healer/2 dps), spec; the head
// flag bit was set on both retail members (each was the listing's leader).
// One member record, reader 0x7FF7CD528140 (see SearchResultMember).
static void WriteSearchResultMember(ByteBuffer& data, SearchResultMember const& member)
{
data << member.Guid; // PackedGuid MemberGuid
data << member.Guid;
data << uint8(member.Level);
data << uint8(member.ClassID);
data << uint8(member.Role);
data << uint32(member.SpecID);
data << uint8(0); // Unk24
data << uint8(member.IsLeader ? 0x80 : 0x00); // head flag (bit-as-byte; 68974: 1 for the leader)
// tail (sub_7FF729162CC0)
data << member.Guid; // PackedGuid MemberGuid2
data << uint32(0); // T20
data << uint32(0); // T24 (68974 live values 17; semantics unknown, zero-filled)
data << uint32(0); // T28
data << uint32(0); // T32 (68974 live values 18)
data << uint32(0); // T36 (68974 live values 18)
data << uint64(0); // T40
data << uint64(0); // T48
data << uint32(0); // T56
data << uint8(0); // T_flag (bit-as-byte)
data << uint8(member.LfgRoles);
// tail block (RVA 0x6E7EA0): leaver bookkeeping keyed by the Battle.net account
data << member.BnetAccountGuid;
data << uint32(0);
data << uint32(0);
data << uint32(0);
data << uint32(0);
data << uint32(0);
data << uint64(0);
data << uint64(0);
data << uint32(0);
data << Bits<1>(member.IsLeaver);
data.FlushBits();
data << Bits<1>(member.IsLeader);
data.FlushBits();
}
// Emit one SMSG_LFG_LIST_SEARCH_RESULTS row per c:\dumps\lfg_search_results_layout.md, re-verified byte-exact
// against the 12.0.7.68974 capture (both bodies consume to exact end with the same layout ? no structural
// drift 68275 -> 68974). Row-level "bit" fields are full wire bytes with the boolean in bit 7 (client reads
// x >> 7); PackedGuid uses the standard TrinityCore ObjectGuid operator<< (u16 mask + data bytes). Unknown
// scalars are zero-filled (the client parses them fine); observed retail constants are mirrored
// (Unk_b=4, Unk1816/Unk2160=3 at 68974 ? they were 5 at 68275).
// The row body from the age counter onward ? shared verbatim between SEARCH_RESULTS rows and the row
// snapshot embedded in SMSG_LFG_LIST_APPLY_TO_GROUP_RESULT (sniff: identical bytes in both containers).
static void WriteSearchResultRowBody(ByteBuffer& data, SearchResultListing const& row)
// The descriptor as a search row carries it: the leader's score rides in the row (+2056), the embedded copy is empty in
// every retail row.
static ListingDescriptor RowDescriptor(SearchResultListing const& row)
{
data << uint32(row.Age); // Unk40 (age counter; 68974 rows: 3)
data << uint8(3); // Unk1816 (68974: 3; was 5 at 68275)
data << row.LeaderGuid; // Guid_A
data << row.LeaderGuid; // Guid_B
data << row.LeaderGuid; // Guid_C
data << row.LeaderGuid; // Guid_D
data << row.LeaderGuid; // Guid_E
data << uint32(0); // Unk1904
data << uint32(0); // Unk1908
data << uint32(0); // Unk1912
data << uint32(0); // Count1 (GuidList1 length)
data << uint32(0); // Count2 (GuidList2 length)
data << uint32(0); // Count3 (GuidList3 length)
data << uint32(uint32(row.Members.size())); // MemberCount
data << uint32(0); // Unk2016
data << uint64(row.PostTime); // PostTime2 (== PostTime)
data << uint8(0); // Unk2032
data << row.GroupGuid; // LeaderGuidEcho (== GroupGuid)
for (uint32 i = 0; i < 9; ++i) // fixed 9-entry {u32,u8} table (sub_7FF729195220)
{
// 68974 capture: every entry is {u32 0, u8 index} ? the previous writer emitted {u32 index, u8 0},
// which put the index into the wrong client field (same byte count, wrong values).
data << uint32(0);
data << uint8(i);
}
data << uint8(3); // Unk2160 (68974: 3; was 5 at 68275)
data << uint8(0); // Unk2161
// === three PackedGuid lists (all empty, Count1/2/3 == 0) -> emit nothing ===
// === embedded ListingDescriptor (echo verbatim) ===
if (!row.RawDescriptor.empty())
data.append(row.RawDescriptor.data(), row.RawDescriptor.size());
else
data.append(std::vector<uint8>(27, 0).data(), 27); // minimal all-zero descriptor fallback
// === trailing bit ===
data << uint8(0); // Unk1916 (bit-as-byte, observed 0)
// === block sub_7FF7291676F0 @2056 (empty) ===
data << uint32(0); // Blk_f0
data << uint32(0); // Blk_f1
data << uint32(0); // Blk_count == 0
// === member detail list x MemberCount (sub_7FF7291DBF80 + tail sub_7FF729162CC0) ===
for (SearchResultMember const& member : row.Members)
WriteSearchResultMember(data, member);
ListingDescriptor descriptor = row.Listing;
descriptor.LeaderScore = { };
return descriptor;
}
// Emit one full row: header block (sub_7FF7291CCDB0) + body.
// One SMSG_LFG_LIST_SEARCH_RESULTS row, reader 0x7FF7CD528370 (see SearchResultListing).
static ByteBuffer& operator<<(ByteBuffer& data, SearchResultListing const& row)
{
data << row.GroupGuid; // PackedGuid GroupGuid
data << uint32(row.ListingId); // ListingId (APPLY_TO_GROUP key)
data << uint32(4); // Unk_b (observed constant 4)
data << uint64(row.PostTime); // PostTime
data << uint8(0); // Unk_hdrbit (bit-as-byte, observed 0)
WriteSearchResultRowBody(data, row);
data << row.GroupGuid;
data << uint32(row.ListingId);
data << uint32(4); // RideType::LfgListListing
data << uint64(row.PostTime);
data << Bits<1>(false); // IsCrossFaction
data.FlushBits();
data << uint32(row.Revision);
data << RowDescriptor(row);
// Not read by any 12.1 consumer of the row; 7 in all 105 retail 12.1 rows.
data << uint8(7);
data << row.LeaderGuid;
data << row.LastEditorGuid;
data << row.NameEditorGuid;
data << row.CommentEditorGuid;
data << row.VoiceChatEditorGuid;
data << uint32(row.LeaderVirtualRealmAddress);
data << uint32(row.LeaderAreaID);
data << uint32(0); // +1912: only ever set by an update record's flag bit
data << uint32(row.BNetFriendGuids.size());
data << uint32(row.CharacterFriendGuids.size());
data << uint32(row.GuildMateGuids.size());
data << uint32(row.Members.size());
data << uint32(0); // +2016: only ever set by an update record
data << uint64(row.PostTime);
data << uint8(0); // +2032: no 12.1 consumer; 0 in 93 of 105 retail rows
data << row.GroupGuid;
data << row.LeaderScore;
for (uint32 bracket = 0; bracket < row.LeaderPvpRatings.size(); ++bracket)
{
data << uint32(row.LeaderPvpRatings[bracket]);
data << uint8(bracket);
}
data << uint8(row.LeaderFactionMask);
data << uint8(row.CensorFlags);
for (ObjectGuid const& guid : row.BNetFriendGuids)
data << guid;
for (ObjectGuid const& guid : row.CharacterFriendGuids)
data << guid;
for (ObjectGuid const& guid : row.GuildMateGuids)
data << guid;
for (SearchResultMember const& member : row.Members)
WriteSearchResultMember(data, member);
data << Bits<1>(row.HasSelf);
data.FlushBits();
return data;
}
WorldPacket const* LFGListSearchResults::Write()
{
_worldPacket << uint16(Listings.size()); // Unk32 (duplicate row-count hint; == RowCount in sniffs)
_worldPacket << uint32(Listings.size()); // RowCount (array length)
_worldPacket << uint16(Listings.size()); // duplicate row-count hint (== RowCount in every sniff)
_worldPacket << uint32(Listings.size());
for (SearchResultListing const& row : Listings)
_worldPacket << row;
return &_worldPacket;
}
// One update record, reader 0x7FF7CD528690 (see LFGListSearchResultsUpdate).
WorldPacket const* LFGListSearchResultsUpdate::Write()
{
// 68974 capture (bodies idx 16193 len=69 / idx 18313 len=136): the UPDATE row is NOT the full
// search-result row (the previous writer emitted the full ~285B row ? wrong wire). Observed compact row:
// PackedGuid GroupGuid, u32 ListingId, u32 4, u64 PostTime, bit(0),
// u32 Age (3 / 4), u32 MemberCount (0 / 1),
// u8 0, u32 8 (constant in both bodies), u8[26] zero,
// MemberDetail x MemberCount (identical 66B record as SEARCH_RESULTS).
_worldPacket << uint32(Listings.size());
for (SearchResultListing const& row : Listings)
{
_worldPacket << row.GroupGuid;
_worldPacket << uint32(row.ListingId);
_worldPacket << uint32(4); // header constant (== full-row Unk_b)
_worldPacket << uint32(4); // RideType::LfgListListing
_worldPacket << uint64(row.PostTime);
_worldPacket << uint8(0); // header bit (bit-as-byte, observed 0)
_worldPacket << uint32(row.Age); // refresh/age counter (matches the row's Unk40)
_worldPacket << Bits<1>(false);
_worldPacket.FlushBits();
_worldPacket << uint32(row.Revision);
_worldPacket << uint32(row.Members.size());
_worldPacket << RowDescriptor(row);
_worldPacket << uint8(0);
_worldPacket << uint32(8); // observed constant 8 in both 68974 bodies
for (uint32 i = 0; i < 26; ++i)
_worldPacket << uint8(0); // zero block (semantics unknown, all-zero in both bodies)
for (SearchResultMember const& member : row.Members)
WriteSearchResultMember(_worldPacket, member);
uint32 const changes = row.Changes;
bool const leader = (changes & SEARCH_RESULT_CHANGE_LEADER) != 0;
bool const name = (changes & SEARCH_RESULT_CHANGE_NAME) != 0;
bool const comment = (changes & SEARCH_RESULT_CHANGE_COMMENT) != 0;
bool const voiceChat = (changes & SEARCH_RESULT_CHANGE_VOICE_CHAT) != 0;
_worldPacket << Bits<1>(leader);
_worldPacket << Bits<1>(leader);
_worldPacket << Bits<1>(false); // flag +1912 present
_worldPacket << Bits<1>(false); // u32 +2016 present
_worldPacket << Bits<1>(row.Delisted);
_worldPacket << Bits<1>(row.Delisted);
_worldPacket << Bits<1>(false); // guid the applier does not read
_worldPacket << Bits<1>(name);
_worldPacket << Bits<1>(comment);
_worldPacket << Bits<1>(voiceChat);
_worldPacket << Bits<1>((changes & SEARCH_RESULT_CHANGE_REQUIRED_ITEM_LEVEL) != 0);
_worldPacket << Bits<1>((changes & SEARCH_RESULT_CHANGE_AUTO_ACCEPT) != 0);
_worldPacket << Bits<1>((changes & SEARCH_RESULT_CHANGE_PRIVATE) != 0);
_worldPacket << Bits<1>((changes & SEARCH_RESULT_CHANGE_REQUIRED_DUNGEON_SCORE) != 0);
_worldPacket << Bits<1>((changes & SEARCH_RESULT_CHANGE_REQUIRED_PVP_RATING) != 0);
_worldPacket << Bits<1>((changes & SEARCH_RESULT_CHANGE_PLAYSTYLE) != 0);
_worldPacket << Bits<1>(false); // not read by the applier
_worldPacket << Bits<1>((changes & SEARCH_RESULT_CHANGE_CROSS_FACTION) != 0);
_worldPacket << Bits<1>((changes & SEARCH_RESULT_CHANGE_ACTIVITIES) != 0);
_worldPacket << Bits<1>((changes & SEARCH_RESULT_CHANGE_NEW_PLAYER_FRIENDLY) != 0);
_worldPacket << Bits<1>(false); // flag +1912 value
_worldPacket.FlushBits();
if (leader)
{
_worldPacket << row.LeaderGuid;
_worldPacket << uint32(row.LeaderVirtualRealmAddress);
}
if (name)
_worldPacket << row.NameEditorGuid;
if (comment)
_worldPacket << row.CommentEditorGuid;
if (voiceChat)
_worldPacket << row.VoiceChatEditorGuid;
}
return &_worldPacket;
}
WorldPacket const* LFGListCensoredActiveEntryUpdate::Write()
{
_worldPacket << Listing;
_worldPacket << OptionalInit(CensorCode);
_worldPacket.FlushBits();
if (CensorCode)
_worldPacket << uint8(*CensorCode);
return &_worldPacket;
}
WorldPacket const* LFGListApplicantListUpdate::Write()
{
// Sniff-exact: Ticket(listing) + u32 count + u32 unk + entries. Entry (status-only form, HasInfo=0):
// Ticket(application) + PackedGuid player + u32 HasInfo(0) + u8 StateBits + u8 pad.
_worldPacket << ListingTicket;
_worldPacket << uint32(Applicants.size());
_worldPacket << uint32(Unknown);
for (ApplicantInfo const& a : Applicants)
_worldPacket << Size<uint32>(Applicants);
_worldPacket << uint32(Unknown); // UNVERIFIED: see the field's note in LFGListPackets.h
for (ApplicantInfo const& applicant : Applicants)
{
_worldPacket << a.Ticket;
_worldPacket << a.PlayerGuid;
_worldPacket << uint32(0); // HasInfo: 0 = status-only entry (full snapshot form documented, unresolved scalars)
_worldPacket << uint8(a.StateBits);
_worldPacket << uint8(0); // pad
_worldPacket << applicant.Ticket;
_worldPacket << applicant.PlayerGuid;
_worldPacket << uint32(applicant.Members.size());
for (ApplicantMember const& member : applicant.Members)
{
_worldPacket << member.Guid;
_worldPacket << uint32(member.VirtualRealmAddress);
_worldPacket << uint32(member.Level);
_worldPacket << uint32(member.HonorLevel);
_worldPacket << uint8(member.RoleMask);
_worldPacket << uint8(member.AssignedRole);
_worldPacket << uint32(0); // {u32, u32} pairs: no consumer among the applicant getters
_worldPacket << member.DungeonScore;
for (uint32 bracket = 0; bracket < member.PvpRatings.size(); ++bracket)
{
_worldPacket << uint32(member.PvpRatings[bracket]);
_worldPacket << uint8(bracket);
}
_worldPacket << uint8(member.RaceID);
_worldPacket << uint8(member.FactionMask);
_worldPacket << member.BnetAccountGuid;
_worldPacket << uint32(0);
_worldPacket << uint32(0);
_worldPacket << uint32(0);
_worldPacket << uint32(0);
_worldPacket << uint32(0);
_worldPacket << uint64(0);
_worldPacket << uint64(0);
_worldPacket << uint32(0);
_worldPacket << Bits<1>(member.IsLeaver);
_worldPacket.FlushBits();
_worldPacket << float(member.ItemLevel);
_worldPacket << float(member.PvpItemLevel);
_worldPacket << uint32(member.SpecID);
}
// 12.1: the 13-bit block sits behind the member array. Written out bit by bit rather than as two
// hand-packed bytes so it stays correct if the member list is ever filled.
_worldPacket.WriteBits(applicant.StateBits >> 4, 4);
_worldPacket << Bits<1>(applicant.CommentUpdated);
_worldPacket << SizedString::BitsSize<8>(applicant.Comment);
_worldPacket.FlushBits();
_worldPacket << SizedString::Data(applicant.Comment);
}
return &_worldPacket;
}
WorldPacket const* LFGListApplicationStatusUpdate::Write()
{
_worldPacket << Ticket;
_worldPacket << uint64(0);
_worldPacket << ListingTicket; // 12.1: pulled forward, adjacent to the first ticket
_worldPacket << uint64(ApplicationExpiration);
_worldPacket << uint32(UnkResult);
_worldPacket << uint8(RoleGranted);
_worldPacket << ListingTicket;
_worldPacket << uint8(StateBits);
_worldPacket << uint8(StateBits); // client keeps bits 7..4 only
return &_worldPacket;
}
WorldPacket const* LFGListApplyToGroupResult::Write()
{
_worldPacket << Ticket;
_worldPacket << ListingTicket;
_worldPacket << Row; // 12.1: the row moved in front of the scalar tail
_worldPacket << uint64(ApplicationExpiration);
_worldPacket << uint8(Status);
_worldPacket << uint8(0);
_worldPacket << ListingTicket;
_worldPacket << uint8(0x10); // observed constant
_worldPacket << ListingTicket;
WriteSearchResultRowBody(_worldPacket, Row);
_worldPacket << uint8(RoleGranted);
_worldPacket << uint8(StateBits); // client keeps bits 7..4 only
return &_worldPacket;
}
WorldPacket const* LFGListUpdateBlacklist::Write()
{
_worldPacket << uint32(Entries.size());
_worldPacket << Size<uint32>(Entries);
for (LFGListBlacklistEntry const& entry : Entries)
{
_worldPacket << uint32(entry.ActivityID);
_worldPacket << uint32(entry.Reason);
}
return &_worldPacket;
}
}
File diff suppressed because it is too large Load Diff
@@ -460,4 +460,16 @@ WorldPacket const* RequestPvpRewardsResponse::Write()
return &_worldPacket;
}
void DFReadyCheckResponse::Read()
{
// Client 12.1.0.69382 serializer 0x6A5240: presence bit, then IsReady, then FlushBits, then the
// conditional uint8. Calibrated against CMSG_SET_EVERYONE_IS_ASSISTANT (0x430046, serializer 0x6A5080),
// whose byte-for-byte identical body TrinityCore already reads in this order.
_worldPacket >> OptionalInit(PartyIndex);
_worldPacket >> Bits<1>(IsReady);
_worldPacket.ResetBitPos();
if (PartyIndex)
_worldPacket >> *PartyIndex;
}
}
@@ -537,6 +537,17 @@ namespace WorldPackets
uint8 BrawlFlags = 0x00;
uint8 ExtraFlags = 0x80;
};
class DFReadyCheckResponse final : public ClientPacket
{
public:
explicit DFReadyCheckResponse(WorldPacket&& packet) : ClientPacket(CMSG_DF_READY_CHECK_RESPONSE, std::move(packet)) {}
void Read() override;
Optional<uint8> PartyIndex;
bool IsReady = false;
};
}
}
@@ -129,6 +129,15 @@ namespace WorldPackets
std::string Name;
};
//WowCommunity
// Reason 5 is what the delve captures carry when a companion party ends (gulf 1129673,
// eversong 2651548, deatholme 816681); the other values are unobserved.
enum GroupUninviteReason : uint8
{
GROUP_UNINVITE_REASON_COMPANION_PARTY_ENDED = 5
};
//WowCommunity
class GroupUninvite final : public ServerPacket
{
public:
+3 -3
View File
@@ -436,7 +436,7 @@ void OpcodeTable::InitializeClientOpcodes()
DEFINE_HANDLER(CMSG_DF_JOIN, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleLfgJoinOpcode);
DEFINE_HANDLER(CMSG_DF_LEAVE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleLfgLeaveOpcode);
DEFINE_HANDLER(CMSG_DF_PROPOSAL_RESPONSE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleLfgProposalResultOpcode);
DEFINE_HANDLER(CMSG_DF_READY_CHECK_RESPONSE, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL);
DEFINE_HANDLER(CMSG_DF_READY_CHECK_RESPONSE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleDFReadyCheckResponse);
DEFINE_HANDLER(CMSG_DF_SET_ROLES, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleLfgSetRolesOpcode);
DEFINE_HANDLER(CMSG_DF_TELEPORT, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleLfgTeleportOpcode);
DEFINE_HANDLER(CMSG_DISCARDED_TIME_SYNC_ACKS, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleDiscardedTimeSyncAcks);
@@ -649,7 +649,7 @@ void OpcodeTable::InitializeClientOpcodes()
DEFINE_HANDLER(CMSG_LEAVE_PET_BATTLE_QUEUE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleLeavePetBattleQueue);
DEFINE_HANDLER(CMSG_LFG_LIST_APPLY_TO_GROUP, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleLFGListApplyToGroup);
DEFINE_HANDLER(CMSG_LFG_LIST_CANCEL_APPLICATION, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleLFGListCancelApplication);
DEFINE_HANDLER(CMSG_LFG_LIST_CONFIRM_CENSORED_ACTIVE_ENTRY, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL);
DEFINE_HANDLER(CMSG_LFG_LIST_CONFIRM_CENSORED_ACTIVE_ENTRY, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleLFGListConfirmCensoredActiveEntry);
DEFINE_HANDLER(CMSG_LFG_LIST_DECLINE_APPLICANT, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleLFGListDeclineApplicant);
DEFINE_HANDLER(CMSG_LFG_LIST_GET_STATUS, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleLFGListGetStatus);
DEFINE_HANDLER(CMSG_LFG_LIST_INVITE_APPLICANT, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleLFGListInviteApplicant);
@@ -2406,7 +2406,7 @@ void OpcodeTable::InitializeServerOpcodes()
DEFINE_SERVER_OPCODE_HANDLER(SMSG_SET_PLAY_HOVER_ANIM, STATUS_NEVER, CONNECTION_TYPE_INSTANCE);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_SET_PROFICIENCY, STATUS_NEVER, CONNECTION_TYPE_INSTANCE);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_SET_QUEST_REPLAY_COOLDOWN_OVERRIDE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_SET_SHIPMENT_READY_RESPONSE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_SET_SHIPMENT_READY_RESPONSE, STATUS_NEVER, CONNECTION_TYPE_REALM);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_SET_SPELL_CHARGES, STATUS_NEVER, CONNECTION_TYPE_INSTANCE);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_SET_TIME_ZONE_INFORMATION, STATUS_NEVER, CONNECTION_TYPE_REALM);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_SET_VEHICLE_REC_ID, STATUS_NEVER, CONNECTION_TYPE_INSTANCE);
+5
View File
@@ -760,6 +760,7 @@ namespace WorldPackets
class DFGetJoinStatus;
class DFConfirmExpandSearch;
struct RideTicket;
class DFReadyCheckResponse;
}
namespace LFGList
@@ -774,6 +775,7 @@ namespace WorldPackets
class LFGListDeclineApplicant;
class LFGListInviteApplicant;
class LFGListInviteResponse;
class LFGListConfirmCensoredActiveEntry;
class RequestLFGListBlacklist;
}
@@ -2322,8 +2324,10 @@ public:
void HandleLFGListDeclineApplicant(WorldPackets::LFGList::LFGListDeclineApplicant& packet);
void HandleLFGListInviteApplicant(WorldPackets::LFGList::LFGListInviteApplicant& packet);
void HandleLFGListInviteResponse(WorldPackets::LFGList::LFGListInviteResponse& packet);
void HandleLFGListConfirmCensoredActiveEntry(WorldPackets::LFGList::LFGListConfirmCensoredActiveEntry& packet);
void HandleRequestLFGListBlacklist(WorldPackets::LFGList::RequestLFGListBlacklist& packet);
void SendLFGListUpdateStatus(uint32 listingId, uint8 status = 0x38);
void SendLFGListCensoredActiveEntryUpdate(uint32 listingId);
void SendLfgUpdateStatus(lfg::LfgUpdateData const& updateData, bool party);
void SendLfgRoleChosen(ObjectGuid guid, uint8 roles);
@@ -2338,6 +2342,7 @@ public:
void SendLfgTeleportError(lfg::LfgTeleportResult err);
void SendLfgExpandSearchPrompt(WorldPackets::LFG::RideTicket const& ticket);
void SendLfgSlotInvalid(lfg::LfgSlotInvalidReason reason, int32 subReason1, int32 subReason2);
void HandleDFReadyCheckResponse(WorldPackets::LFG::DFReadyCheckResponse& dfReadyCheckResponse);
void HandleSelfResOpcode(WorldPackets::Spells::SelfRes& selfRes);
void HandleRequestPetInfo(WorldPackets::Pet::RequestPetInfo& requestPetInfo);
@@ -2945,6 +2945,7 @@ void AuraEffect::HandleAuraMounted(AuraApplication const* aurApp, uint8 mode, bo
if (MountCapabilityEntry const* mountCapability = sMountCapabilityStore.LookupEntry(GetAmountAsInt()))
target->RemoveAurasDueToSpell(mountCapability->ModSpellAuraID, target->GetGUID());
target->SetFlightCapabilityID(0, false);
// Clear all flight flags on dismount
target->SetCanFly(false);
target->SetCanAdvFly(false);
+40 -16
View File
@@ -94,7 +94,9 @@
//WowCommunity
#include "Crafting.h"
#include "ItemUpgradeMgr.h"
#include <GarrisonMgr.h>
#include "GarrisonMgr.h"
#include "GroupMgr.h"
#include "LFG.h"
//WowCommunity
NonDefaultConstructible<SpellEffectHandlerFn> SpellEffectHandlers[TOTAL_SPELL_EFFECTS] =
@@ -7636,26 +7638,48 @@ void Spell::EffectChangePartyMembers()
if (effectHandleMode != SPELL_EFFECT_HANDLE_HIT_TARGET)
return;
if (!unitTarget || !unitTarget->IsPlayer())
Creature* companion = m_caster->ToCreature();
if (!companion && unitTarget)
companion = unitTarget->ToCreature();
Player* player = unitTarget ? unitTarget->ToPlayer() : nullptr;
if (!player)
player = m_caster->ToPlayer();
if (!player)
return;
Player* targetPlayer = unitTarget->ToPlayer();
if (effectInfo->MiscValue == 1)
if (!effectInfo->MiscValue)
{
if (targetPlayer->GetGroup())
return;
// leave: the casting creature, or every companion of the player when a player casts it
if (companion)
{
if (Group* group = sGroupMgr->GetGroupByGUID(companion->GetPartyGroupGUID()))
group->RemoveNpcMember(companion->GetGUID());
}
else if (Group* group = player->GetGroup())
{
std::vector<ObjectGuid> npcMembers;
for (Group::NpcMemberSlot const& npcSlot : group->GetNpcMemberSlots())
npcMembers.push_back(npcSlot.guid);
Group* group = new Group();
if (!group->Create(targetPlayer))
delete group;
}
else if (effectInfo->MiscValue == 0)
{
if (Group* group = targetPlayer->GetGroup())
if (group->IsMember(targetPlayer->GetGUID()))
group->RemoveMember(targetPlayer->GetGUID());
for (ObjectGuid const& npcMember : npcMembers)
if (!group->RemoveNpcMember(npcMember))
break; // the group disbanded itself with its last creature
}
return;
}
if (!companion)
return;
Group* group = player->GetGroup();
if (!group)
group = Group::CreateNpcParty(player);
if (group)
group->AddNpcMember(companion, lfg::PLAYER_ROLE_DAMAGE); // the role a lone companion joins with
}
void Spell::EffectDespawnSummon()
+6
View File
@@ -5515,6 +5515,12 @@ void SpellMgr::LoadSpellInfoCorrections()
spellInfo->MaxAffectedTargets = 1;
}
// Wild Imp (Warlock) - triggered-only summon spell, never castable directly
ApplySpellFix({ 279910 }, [](SpellInfo* spellInfo)
{
spellInfo->AttributesEx4 |= SPELL_ATTR4_NOT_IN_SPELLBOOK;
});
DB2HotfixGenerator summonProperties(sSummonPropertiesStore);
summonProperties.ApplyHotfix(121, [](SummonPropertiesEntry* properties)
{
@@ -90,7 +90,7 @@ struct npc_rira_hackclaw : public BossAI
events.ScheduleEvent(EVENT_CLEAVE, 15s);
}
void EnterEvadeMode(EvadeReason why) override
void EnterEvadeMode(EvadeReason /*why*/) override
{
summons.DespawnAll();
instance->SendEncounterUnit(ENCOUNTER_FRAME_DISENGAGE, me);
@@ -285,4 +285,4 @@ void AddSC_npc_rira_hackclaw()
RegisterBrackenhideHollowCreatureAI(npc_rira_hackclaw);
RegisterBrackenhideHollowCreatureAI(npc_gasthooth_186124);
RegisterBrackenhideHollowCreatureAI(npc_gasTricktotem_186125);
}
}
@@ -5,23 +5,544 @@
* and read our Terms
*/
#include "AreaTrigger.h"
#include "AreaTriggerAI.h"
#include "InstanceScript.h"
#include "Map.h"
#include "SpellAuras.h"
#include "SpellScript.h"
#include "ScriptMgr.h"
#include "Unit.h"
#include "Neltharus.h"
#include "InstanceScript.h"
#include "ScriptMgr.h"
#include "ScriptedCreature.h"
#include "SpellScript.h"
#include "SpellAuraEffects.h"
#include "SpellAuras.h"
#include "SpellHistory.h"
#include "Spell.h"
#include "Player.h"
#include "MotionMaster.h"
#include "TemporarySummon.h"
//here to script trash and gobs
// Neltharus trash (retail 12.1 sniff 69814)
enum NpcsSpells
enum NeltharusTrashSpells
{
//Qalashi Trainee 189266
SPELL_FIRED_UP = 371875,
SPELL_MAGMA_FIST = 372372,
//Qalashi Hunter 189227
SPELL_BOLD_AMBUSH = 372570,
SPELL_BINDING_SPEAR = 372561,
//Qalashi caster 189235
SPELL_IMBUED_MAGMA = 372461,
SPELL_ERUPTIVE_CRUSH = 372135,
SPELL_BURNING_ROAR = 395427,
//Qalashi lavacaller 189265
SPELL_CHANNEL_LAVA = 364588,
SPELL_MENDING_CLAY = 372223,
SPELL_BONE_TOSS = 372226,
//Qalashi scorcher 189464
SPELL_SCORCHING_BREATH = 372201,
SPELL_MOTE_OF_COMBUSTION = 384161,
//Irontorch Commander 189466
SPELL_SCORCHING_FUSILLADE = 372543,
SPELL_CONFLAGRANT_BATTERY = 372295,
//Qalashi bonebreaker 189467
SPELL_DRAGONBONE_AXE = 372224,
SPELL_PIERCE_MARROW = 372262,
//Qalashi blacksmith 189471
SPELL_REVERBERATING_SLAM = 372971,
SPELL_FORGESTOMP = 384623,
//Qalashi lavaslinger 189472
SPELL_THROW_LAVA = 379404,
SPELL_LAVA_SPILL = 372212,
//Forge golem 189474
SPELL_HEATED_SWINGS_TRASH = 374355,
SPELL_MIGHT_OF_THE_FORGE = 374723,
SPELL_FORGESTORM_TRASH = 375209,
//Qalashi spearman 189669
SPELL_BINDING_SPEAR_2 = 373540,
//Qalashi apothecary 189905
SPELL_THROW_CONCOCTION = 376169,
SPELL_EXPERIMENTAL_CONCOC = 376193,
//Magmatusk room globule 190023 / 190630
SPELL_MAGMA_LOB = 375068,
SPELL_LIQUID_HOT_MAGMA = 375204,
//Qalashi chain 191498
SPELL_CHAINS = 376032,
//Qalashi earthwarden 192781
SPELL_GRANITE_SHELL = 378149,
//Qalashi alchemist 192786
SPELL_EXPLOSIVE_CONCOCTION = 378824,
//Qalashi brute 192787
SPELL_BRUTAL_STRIKE = 378847,
//Qalashi Thaumaturge 192788
SPELL_MAGMA_CONFLAGRATION = 378818,
//Candescent Tempest 193291
SPELL_CANDESCENT_TEMPEST = 381662,
//Volcanic Warden 193293
SPELL_BLAZING_SLASH = 384597,
SPELL_VOLCANIC_GUARD = 382708,
//Qalashi Lavamancer 193944
SPELL_LAVA_BOLT = 383231,
SPELL_MOLTEN_BARRIER = 382791,
SPELL_MOLTEN_ARMY = 383651,
//Forgewrought Monstrosity 194816
SPELL_FORGEWROUGHT_FURY = 384662,
//Burning Book 197333
SPELL_BURNING_BOOK = 382248,
//Burning Books 197337
SPELL_BURNING_BOOKS = 390521,
};
enum NeltharusTrashEvents
{
EVENT_NL_A = 1,
EVENT_NL_B,
EVENT_NL_C,
};
// ---------- generic one-spell trash ----------
template<uint32 SpellA, uint32 CooldownA>
struct npc_nl_one_spell : public ScriptedAI
{
npc_nl_one_spell(Creature* creature) : ScriptedAI(creature) { }
void JustEngagedWith(Unit* /*who*/) override
{
_events.ScheduleEvent(EVENT_NL_A, 2s);
}
void UpdateAI(uint32 diff) override
{
if (!UpdateVictim())
return;
_events.Update(diff);
if (me->HasUnitState(UNIT_STATE_CASTING))
return;
while (uint32 eventId = _events.ExecuteEvent())
{
if (eventId == EVENT_NL_A)
{
DoCastVictim(SpellA);
_events.ScheduleEvent(EVENT_NL_A, Milliseconds(CooldownA));
}
}
}
private:
EventMap _events;
};
// ---------- generic two-spell caster trash ----------
template<uint32 SpellA, uint32 CooldownA, uint32 SpellB, uint32 CooldownB>
struct npc_nl_two_caster : public ScriptedAI
{
npc_nl_two_caster(Creature* creature) : ScriptedAI(creature) { }
void JustEngagedWith(Unit* /*who*/) override
{
_events.ScheduleEvent(EVENT_NL_A, 2s);
_events.ScheduleEvent(EVENT_NL_B, 7s);
}
void UpdateAI(uint32 diff) override
{
if (!UpdateVictim())
return;
_events.Update(diff);
if (me->HasUnitState(UNIT_STATE_CASTING))
return;
while (uint32 eventId = _events.ExecuteEvent())
{
switch (eventId)
{
case EVENT_NL_A:
DoCastVictim(SpellA);
_events.ScheduleEvent(EVENT_NL_A, Milliseconds(CooldownA));
break;
case EVENT_NL_B:
DoCastVictim(SpellB);
_events.ScheduleEvent(EVENT_NL_B, Milliseconds(CooldownB));
break;
default:
break;
}
}
}
private:
EventMap _events;
};
// ---------- generic buff + attack trash ----------
template<uint32 SpellBuff, uint32 CooldownBuff, uint32 SpellAttack, uint32 CooldownAttack>
struct npc_nl_buffer : public ScriptedAI
{
npc_nl_buffer(Creature* creature) : ScriptedAI(creature) { }
void JustEngagedWith(Unit* /*who*/) override
{
_events.ScheduleEvent(EVENT_NL_A, 1s);
_events.ScheduleEvent(EVENT_NL_B, 6s);
}
void UpdateAI(uint32 diff) override
{
if (!UpdateVictim())
return;
_events.Update(diff);
if (me->HasUnitState(UNIT_STATE_CASTING))
return;
while (uint32 eventId = _events.ExecuteEvent())
{
switch (eventId)
{
case EVENT_NL_A:
DoCastSelf(SpellBuff);
_events.ScheduleEvent(EVENT_NL_A, Milliseconds(CooldownBuff));
break;
case EVENT_NL_B:
DoCastVictim(SpellAttack);
_events.ScheduleEvent(EVENT_NL_B, Milliseconds(CooldownAttack));
break;
default:
break;
}
}
}
private:
EventMap _events;
};
// ---------- named trash AI ----------
//Qalashi Hunter 189227 - binds then ambushes
struct npc_nl_qalashi_hunter : public ScriptedAI
{
npc_nl_qalashi_hunter(Creature* creature) : ScriptedAI(creature) { }
void JustEngagedWith(Unit* /*who*/) override
{
_events.ScheduleEvent(EVENT_NL_A, 5s);
_events.ScheduleEvent(EVENT_NL_B, 11s);
}
void UpdateAI(uint32 diff) override
{
if (!UpdateVictim())
return;
_events.Update(diff);
if (me->HasUnitState(UNIT_STATE_CASTING))
return;
while (uint32 eventId = _events.ExecuteEvent())
{
switch (eventId)
{
case EVENT_NL_A:
DoCastVictim(SPELL_BINDING_SPEAR);
_events.ScheduleEvent(EVENT_NL_A, 14s);
break;
case EVENT_NL_B:
DoCastVictim(SPELL_BOLD_AMBUSH);
_events.ScheduleEvent(EVENT_NL_B, 9s);
break;
default:
break;
}
}
}
private:
EventMap _events;
};
//Irontorch Commander 189466 - fusillade + battery
struct npc_nl_irontorch_commander : public ScriptedAI
{
npc_nl_irontorch_commander(Creature* creature) : ScriptedAI(creature) { }
void JustEngagedWith(Unit* /*who*/) override
{
_events.ScheduleEvent(EVENT_NL_A, 4s);
_events.ScheduleEvent(EVENT_NL_B, 10s);
}
void UpdateAI(uint32 diff) override
{
if (!UpdateVictim())
return;
_events.Update(diff);
if (me->HasUnitState(UNIT_STATE_CASTING))
return;
while (uint32 eventId = _events.ExecuteEvent())
{
switch (eventId)
{
case EVENT_NL_A:
DoCastVictim(SPELL_SCORCHING_FUSILLADE);
_events.ScheduleEvent(EVENT_NL_A, 12s);
break;
case EVENT_NL_B:
DoCastSelf(SPELL_CONFLAGRANT_BATTERY);
_events.ScheduleEvent(EVENT_NL_B, 22s);
break;
default:
break;
}
}
}
private:
EventMap _events;
};
//Qalashi Apothecary 189905 - experimental concoctions
struct npc_nl_qalashi_apothecary : public ScriptedAI
{
npc_nl_qalashi_apothecary(Creature* creature) : ScriptedAI(creature) { }
void JustEngagedWith(Unit* /*who*/) override
{
_events.ScheduleEvent(EVENT_NL_A, 3s);
_events.ScheduleEvent(EVENT_NL_B, 9s);
}
void UpdateAI(uint32 diff) override
{
if (!UpdateVictim())
return;
_events.Update(diff);
if (me->HasUnitState(UNIT_STATE_CASTING))
return;
while (uint32 eventId = _events.ExecuteEvent())
{
switch (eventId)
{
case EVENT_NL_A:
DoCastVictim(SPELL_THROW_CONCOCTION);
_events.ScheduleEvent(EVENT_NL_A, 9s);
break;
case EVENT_NL_B:
DoCastVictim(SPELL_EXPERIMENTAL_CONCOC);
_events.ScheduleEvent(EVENT_NL_B, 18s);
break;
default:
break;
}
}
}
private:
EventMap _events;
};
//Forge golem 189474 - spins and empowers
struct npc_nl_forge_golem : public ScriptedAI
{
npc_nl_forge_golem(Creature* creature) : ScriptedAI(creature) { }
void JustEngagedWith(Unit* /*who*/) override
{
_events.ScheduleEvent(EVENT_NL_A, 4s);
_events.ScheduleEvent(EVENT_NL_B, 9s);
_events.ScheduleEvent(EVENT_NL_C, 18s);
}
void UpdateAI(uint32 diff) override
{
if (!UpdateVictim())
return;
_events.Update(diff);
if (me->HasUnitState(UNIT_STATE_CASTING))
return;
while (uint32 eventId = _events.ExecuteEvent())
{
switch (eventId)
{
case EVENT_NL_A:
DoCastVictim(SPELL_HEATED_SWINGS_TRASH);
_events.ScheduleEvent(EVENT_NL_A, 8s);
break;
case EVENT_NL_B:
DoCastSelf(SPELL_MIGHT_OF_THE_FORGE);
_events.ScheduleEvent(EVENT_NL_B, 18s);
break;
case EVENT_NL_C:
DoCastSelf(SPELL_FORGESTORM_TRASH);
_events.ScheduleEvent(EVENT_NL_C, 26s);
break;
default:
break;
}
}
}
private:
EventMap _events;
};
//Qalashi Lavamancer 193944 - barrier + army + bolt
struct npc_nl_qalashi_lavamancer : public ScriptedAI
{
npc_nl_qalashi_lavamancer(Creature* creature) : ScriptedAI(creature) { }
void JustEngagedWith(Unit* /*who*/) override
{
_events.ScheduleEvent(EVENT_NL_A, 2s);
_events.ScheduleEvent(EVENT_NL_B, 6s);
_events.ScheduleEvent(EVENT_NL_C, 16s);
}
void UpdateAI(uint32 diff) override
{
if (!UpdateVictim())
return;
_events.Update(diff);
if (me->HasUnitState(UNIT_STATE_CASTING))
return;
while (uint32 eventId = _events.ExecuteEvent())
{
switch (eventId)
{
case EVENT_NL_A:
DoCastVictim(SPELL_LAVA_BOLT);
_events.ScheduleEvent(EVENT_NL_A, 6s);
break;
case EVENT_NL_B:
DoCastSelf(SPELL_MOLTEN_BARRIER);
_events.ScheduleEvent(EVENT_NL_B, 20s);
break;
case EVENT_NL_C:
DoCast(SPELL_MOLTEN_ARMY);
_events.ScheduleEvent(EVENT_NL_C, 32s);
break;
default:
break;
}
}
}
private:
EventMap _events;
};
//Magmatusk room globule 190023 / 190630
struct npc_nl_magma_globule : public ScriptedAI
{
npc_nl_magma_globule(Creature* creature) : ScriptedAI(creature) { }
void JustEngagedWith(Unit* /*who*/) override
{
_events.ScheduleEvent(EVENT_NL_A, 2s);
_events.ScheduleEvent(EVENT_NL_B, 10s);
}
void UpdateAI(uint32 diff) override
{
if (!UpdateVictim())
return;
_events.Update(diff);
if (me->HasUnitState(UNIT_STATE_CASTING))
return;
while (uint32 eventId = _events.ExecuteEvent())
{
switch (eventId)
{
case EVENT_NL_A:
DoCastVictim(SPELL_MAGMA_LOB);
_events.ScheduleEvent(EVENT_NL_A, 6s);
break;
case EVENT_NL_B:
DoCast(SPELL_LIQUID_HOT_MAGMA);
_events.ScheduleEvent(EVENT_NL_B, 22s);
break;
default:
break;
}
}
}
private:
EventMap _events;
};
// ---------- bindings (AI name = ScriptName) ----------
typedef npc_nl_one_spell<SPELL_GRANITE_SHELL, 12000> npc_nl_qalashi_earthwarden;
typedef npc_nl_one_spell<SPELL_BRUTAL_STRIKE, 6000> npc_nl_qalashi_brute;
typedef npc_nl_one_spell<SPELL_MAGMA_CONFLAGRATION, 7000> npc_nl_qalashi_thaumaturge;
typedef npc_nl_one_spell<SPELL_BURNING_BOOK, 5000> npc_nl_burning_book;
typedef npc_nl_one_spell<SPELL_BURNING_BOOKS, 6000> npc_nl_burning_books;
typedef npc_nl_one_spell<SPELL_FORGEWROUGHT_FURY, 7000> npc_nl_forgewrought_monstrosity;
typedef npc_nl_one_spell<SPELL_CANDESCENT_TEMPEST, 8000> npc_nl_candescent_tempest;
typedef npc_nl_one_spell<SPELL_BINDING_SPEAR_2, 9000> npc_nl_qalashi_spearman;
typedef npc_nl_two_caster<SPELL_DRAGONBONE_AXE, 5000, SPELL_PIERCE_MARROW, 12000> npc_nl_qalashi_bonebreaker;
typedef npc_nl_two_caster<SPELL_THROW_LAVA, 5000, SPELL_LAVA_SPILL, 15000> npc_nl_qalashi_lavaslinger;
typedef npc_nl_two_caster<SPELL_THROW_CONCOCTION, 8000, SPELL_EXPLOSIVE_CONCOCTION, 16000> npc_nl_explosive_alchemist;
typedef npc_nl_two_caster<SPELL_SCORCHING_BREATH, 8000, SPELL_MOTE_OF_COMBUSTION, 20000> npc_nl_qalashi_scorcher;
typedef npc_nl_two_caster<SPELL_CHAINS, 9000, SPELL_BINDING_SPEAR_2, 18000> npc_nl_qalashi_chain;
typedef npc_nl_buffer<SPELL_IMBUED_MAGMA, 25000, SPELL_ERUPTIVE_CRUSH, 8000> npc_nl_qalashi_warcaster;
typedef npc_nl_buffer<SPELL_CHANNEL_LAVA, 15000, SPELL_BONE_TOSS, 8000> npc_nl_qalashi_lavacaller;
typedef npc_nl_buffer<SPELL_FIRED_UP, 14000, SPELL_MAGMA_FIST, 4000> npc_nl_qalashi_trainee_buff;
typedef npc_nl_buffer<SPELL_VOLCANIC_GUARD, 22000, SPELL_BLAZING_SLASH, 7000> npc_nl_volcanic_warden;
typedef npc_nl_buffer<SPELL_REVERBERATING_SLAM, 12000, SPELL_FORGESTOMP, 20000> npc_nl_qalashi_blacksmith;
void AddSC_Neltharus()
{
}
RegisterCreatureAI(npc_nl_qalashi_hunter);
RegisterCreatureAI(npc_nl_irontorch_commander);
RegisterCreatureAI(npc_nl_qalashi_apothecary);
RegisterCreatureAI(npc_nl_forge_golem);
RegisterCreatureAI(npc_nl_qalashi_lavamancer);
RegisterCreatureAI(npc_nl_magma_globule);
RegisterCreatureAI(npc_nl_qalashi_trainee_buff);
RegisterCreatureAI(npc_nl_qalashi_earthwarden);
RegisterCreatureAI(npc_nl_qalashi_brute);
RegisterCreatureAI(npc_nl_qalashi_thaumaturge);
RegisterCreatureAI(npc_nl_burning_book);
RegisterCreatureAI(npc_nl_burning_books);
RegisterCreatureAI(npc_nl_forgewrought_monstrosity);
RegisterCreatureAI(npc_nl_candescent_tempest);
RegisterCreatureAI(npc_nl_qalashi_spearman);
RegisterCreatureAI(npc_nl_qalashi_bonebreaker);
RegisterCreatureAI(npc_nl_qalashi_lavaslinger);
RegisterCreatureAI(npc_nl_explosive_alchemist);
RegisterCreatureAI(npc_nl_qalashi_scorcher);
RegisterCreatureAI(npc_nl_qalashi_chain);
RegisterCreatureAI(npc_nl_qalashi_warcaster);
RegisterCreatureAI(npc_nl_qalashi_lavacaller);
RegisterCreatureAI(npc_nl_volcanic_warden);
RegisterCreatureAI(npc_nl_qalashi_blacksmith);
}
@@ -32,10 +32,10 @@ enum ChargatSpells
SPELL_FETTER_2 = 374655, // if chargat get trips on ground spell casted to him dmg + stun 2 sec
SPELL_FIERE_FOCUS = 375056, // if reach 100 energy cast on primary target visual casting
SPELL_FIERE_FOCUS_2 = 384019, // DMG
SPELL_LAVA_SPLASH = 392217, // AOE DMG + visual
SPELL_LAVA_SPLASH = 375397, // AOE DMG + visual
SPELL_DRAGON_STRIKE = 373733, //random one time dmg
SPELL_DRAGON_STRIKE_2 = 373735, // Victim periodic dmg
SPELL_MAGMA_WAVE = 373756, //aoe dmg ,
SPELL_MAGMA_WAVE = 373767, //aoe dmg
SPELL_MAGMA_WAVE_VISUAL = 373762, //cast orb in front
SPELL_MAGMA_WAVE_2 = 373742, //aoe visual
PERIODIC_ENERGY_GAIN = 299956, // dont find so we reuse from other boss
@@ -98,6 +98,7 @@ void AddSC_boss_primal_tsunami();
// Neltharus
void AddSC_instance_Neltharus_df();
void AddSC_Neltharus();
void AddSC_boss_Chargat_Bane_of_Scales();
void AddSC_boss_Forgemaster_Gorek();
void AddSC_boss_Magmatusk();
@@ -208,6 +209,7 @@ void AddDragonIslesScripts()
// Neltharus
AddSC_instance_Neltharus_df();
AddSC_Neltharus();
AddSC_boss_Chargat_Bane_of_Scales();
AddSC_boss_Forgemaster_Gorek();
AddSC_boss_Magmatusk();
@@ -34,9 +34,6 @@
* - 249057 "Valeera Sanguinar" (same display) is created 44 times in gulf: transient combat copies. Not implemented.
*
* Not implemented, on purpose:
* - 1249690 "Npc Join Player Party" is SpellEffect 190 SPELL_EFFECT_CHANGE_PARTY_MEMBERS, which the core maps to
* EffectNULL (SpellEffects.cpp), so casting it would do nothing; Group members are players only. The party
* membership seen in SMSG_PARTY_UPDATE therefore has no server-side equivalent yet.
* - The exact rotation. Only the observed spell ids are used; the cadence below is derived from the per-run cast
* counts over the ~16 minutes of combat in gulf, not from a decoded priority list.
*
@@ -63,6 +60,7 @@ namespace
enum CompanionSpells : uint32
{
SPELL_JOIN_PLAYER_PARTY = 1249690, // SPELL_EFFECT_CHANGE_PARTY_MEMBERS: puts her in the party frame
SPELL_STEALTH = 1252003, // aura 16 MOD_STEALTH, cast at spawn and after every fight
SPELL_BLOODCRYPT_TOXIN = 1251111, // self, at spawn (gulf 103996) and periodically
SPELL_AFRAID_OF_THE_DARK = 1266682, // self; cast by nearly every creature in the delve
@@ -115,6 +113,10 @@ struct npc_delve_companion : public ScriptedAI
_ownerGuid = owner->GetGUID();
me->SetFaction(owner->GetFaction());
// gulf 103996: she joins the party frame, and the party update arrives with the same tick as her
// first self-buff
CastIfKnown(owner, SPELL_JOIN_PLAYER_PARTY);
// gulf 103987: Stealth is the first thing she casts; 103996: Bloodcrypt Toxin on herself
CastIfKnown(me, SPELL_STEALTH);
CastIfKnown(me, SPELL_BLOODCRYPT_TOXIN);
+21 -11
View File
@@ -15,10 +15,10 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* Ordered alphabetically using scriptname.
* Scriptnames of files in this file should be prefixed with "npc_pet_mag_".
*/
/*
* Ordered alphabetically using scriptname.
* Scriptnames of files in this file should be prefixed with "npc_pet_mag_".
*/
#include "ScriptMgr.h"
#include "CellImpl.h"
@@ -30,24 +30,34 @@
enum MageSpells
{
SPELL_MAGE_FROST_BOLT = 59638,
SPELL_MAGE_FIRE_BLAST = 59637
SPELL_MAGE_CLONE_ME = 45204,
SPELL_MAGE_MASTERS_THREAT_LIST = 58838,
SPELL_MAGE_FROST_BOLT = 59638,
SPELL_MAGE_FIRE_BLAST = 59637
};
enum MirrorImageTimers
{
TIMER_MIRROR_IMAGE_FIRE_BLAST = 6500
TIMER_MIRROR_IMAGE_FIRE_BLAST = 6500
};
struct npc_pet_mage_mirror_image : ScriptedAI
{
const float CHASE_DISTANCE = 35.0f;
npc_pet_mage_mirror_image(Creature* creature) : ScriptedAI(creature) { }
npc_pet_mage_mirror_image(Creature* creature) : ScriptedAI(creature) {}
// Clone Me! (45204), Copy Weapon (41055), Copy Off-hand Weapon (45206) and
// Inherit Master's Threat List (58838) are applied to each image by the
// summoning spell script (spell_mage_mirror_image).
void InitializeAI() override
{
Unit* owner = me->GetOwner();
if (!owner)
return;
// here mirror image casts on summoner spell (not present in client dbc) 49866
// here should be auras (not present in client dbc): 35657, 35658, 35659, 35660 selfcast by mirror images (stats related?)
// Clone Me!
owner->CastSpell(me, SPELL_MAGE_CLONE_ME, true);
}
// custom UpdateVictim implementation to handle special target selection
// we prioritize between things that are in combat with owner based on the owner's threat to them
@@ -36,11 +36,12 @@ enum DragonridingSpells
SPELL_SKYRIDING_BASICS = 376777,
SPELL_SWITCH_FLIGHT_STYLE = 436854,
SPELL_VIGOR = 372773,
SPELL_LIFT_OFF_1 = 374763,
SPELL_LIFT_OFF_1 = 374763, //383363, 404191 //12.1 sniff to verify
SPELL_LIFT_OFF_2 = 372610,
SPELL_LIFT_OFF_3 = 386451,
SPELL_SURGE_FORWARD = 372608,
SPELL_WHIRLING_SURGE = 361584,
SPELL_DRAGONRIDING_LAUNCH_BOOST = 392752,
};
// 436854 - Switch Flight Style
@@ -201,10 +202,31 @@ public:
}
};
//374763
class spell_dragonriding_lift_off : public SpellScript
{
bool Validate(SpellInfo const* /*spellInfo*/) override
{
return ValidateSpellInfo({ SPELL_DRAGONRIDING_LAUNCH_BOOST });
}
void HandleHit(SpellEffIndex /*effIndex*/)
{
if (Unit* caster = GetCaster())
caster->CastSpell(caster, SPELL_DRAGONRIDING_LAUNCH_BOOST, true);
}
void Register() override
{
OnEffectHitTarget += SpellEffectFn(spell_dragonriding_lift_off::HandleHit, EFFECT_0, SPELL_EFFECT_DUMMY);
}
};
void AddSC_dragonriding_spell_scripts()
{
RegisterSpellScript(spell_switch_flight);
RegisterSpellScript(spell_af_surge_forward);
RegisterSpellScript(spell_af_whirling_surge);
RegisterSpellScript(spell_dragonriding_lift_off);
new adv_flying_check();
}
+702 -10
View File
@@ -71,9 +71,13 @@ enum WarlockSpells
SPELL_WARLOCK_IMPROVED_HEALTH_FUNNEL_BUFF_R2 = 60956,
SPELL_WARLOCK_IMPROVED_HEALTH_FUNNEL_R1 = 18703,
SPELL_WARLOCK_IMPROVED_HEALTH_FUNNEL_R2 = 18704,
SPELL_WARLOCK_IMP_FEL_FIREBOLT = 104318,
SPELL_WARLOCK_INCUBUS_PACT = 365355,
SPELL_WARLOCK_INFERNAL_RAPIDITY = 1263941,
SPELL_WARLOCK_PERPETUAL_UNSTABILITY_DAMAGE = 459461,
SPELL_WARLOCK_PERPETUAL_UNSTABILITY_TALENT = 459376,
SPELL_WARLOCK_POWER_SIPHON = 264130,
SPELL_WARLOCK_POWER_SIPHON_DEMONIC_CORE = 334581,
SPELL_WARLOCK_PYROGENICS_DEBUFF = 387096,
SPELL_WARLOCK_PYROGENICS_TALENT = 387095,
SPELL_WARLOCK_RAIN_OF_FIRE = 5740,
@@ -105,13 +109,27 @@ enum WarlockSpells
SPELL_WARLOCK_WITHER_TALENT = 445465,
//WowCommunity
SPELL_WARLOCK_HAVOC = 80240,
SPELL_WARLOCK_BLIGHTED_MAW = 1276956,
SPELL_WARLOCK_CALL_DREADSTALKERS_LEFT = 193331,
SPELL_WARLOCK_CALL_DREADSTALKERS_RIGHT = 193332,
SPELL_WARLOCK_CARNIVOROUS_STALKERS = 386194,
SPELL_WARLOCK_DREADBITE = 205196,
SPELL_WARLOCK_DREADSTALKER_CHARGE = 194247,
SPELL_WARLOCK_GRIMOIRE_IMP_LORD = 1276452,
SPELL_WARLOCK_GRIMOIRE_IMP_LORD_OVERRIDE = 1276623,
SPELL_WARLOCK_DOMINION_OF_ARGUS = 1276163,
SPELL_WARLOCK_DOMINION_OF_ARGUS_PORTAL = 1276166,
SPELL_WARLOCK_DOMINION_OF_ARGUS_SUMMON_ANTORAN_JAILER = 1276182,
SPELL_WARLOCK_DOMINION_OF_ARGUS_SUMMON_ANTORAN_INQUISITOR = 1276283,
SPELL_WARLOCK_DOMINION_OF_ARGUS_SUMMON_LADY_SACROLASH = 1282501,
SPELL_WARLOCK_DOMINION_OF_ARGUS_SUMMON_GRAND_WARLOCK_ALYTHESS = 1282502,
SPELL_WARLOCK_TO_HELL_AND_BACK = 1281511,
SPELL_WARLOCK_TO_HELL_AND_BACK_UNSTABLE_SOUL = 1281512,
SPELL_WARLOCK_HAND_OF_GULDAN_DAMAGE = 86040,
SPELL_WARLOCK_WILD_IMP_SUMMON = 104317,
SPELL_WARLOCK_WILD_IMP_SUMMON = 279910,
SPELL_WARLOCK_UMBRAL_BRAZE = 405802,
SPELL_WARLOCK_IMPLOSION = 196277,
SPELL_WARLOCK_IMPLOSION_DAMAGE = 196278,
SPELL_WARLOCK_IMPLOSION_TRIGGER = 296553,
SPELL_WARLOCK_HAND_OF_DOOM = 196283,
SPELL_WARLOCK_DOOM = 603,
@@ -128,6 +146,9 @@ enum WarlockSpells
SPELL_WARLOCK_SHARD_INSTABILITY = 1260269,
SPELL_WARLOCK_DARK_HARVEST = 1257052,
SPELL_WARLOCK_SHADOWBOLT_VOLLEY_AREA = 453176,
//WowCommunity
SPELL_WARLOCK_RUINATION = 428522,
SPELL_WARLOCK_RUINATION_AOE = 434635,
};
enum MiscSpells
@@ -141,6 +162,84 @@ enum WarlockSpellVisuals
SPELL_VISUAL_WARLOCK_BILESCOURGE_BOMBERS_CRASH = 75806
};
//WowCommunity
// 428522 - Ruination (passive)
// 434635 - Ruination
// Summoning a Pit Lord (228574) makes the warlock's next Chaos Bolt (Destruction) or
// Hand of Gul'dan (Demonology) become Ruination, replacing the cast with the meteor
// 434635. "Ruination ready" is derived from the presence of a freshly summoned, still
// alive Pit Lord among the caster's controlled units; each Pit Lord grants exactly one
// empowered cast before it is marked as consumed.
namespace
{
constexpr uint32 NPC_WARLOCK_PIT_LORD = 228574;
std::unordered_map<ObjectGuid, GuidUnorderedSet> sRuinationConsumedPitLords;
bool IsUnconsumedPitLord(Unit* unit, GuidUnorderedSet const& consumed)
{
return unit && unit->GetEntry() == NPC_WARLOCK_PIT_LORD && unit->IsAlive()
&& !consumed.contains(unit->GetGUID());
}
Unit* GetPendingPitLord(Unit* caster)
{
if (!caster || caster->GetTypeId() != TYPEID_PLAYER)
return nullptr;
Player* player = caster->ToPlayer();
GuidUnorderedSet const& consumed = sRuinationConsumedPitLords[player->GetGUID()];
for (Unit* controlled : caster->m_Controlled)
if (IsUnconsumedPitLord(controlled, consumed))
return controlled;
Unit* pet = ObjectAccessor::GetCreatureOrPetOrVehicle(*player, player->GetPetGUID());
return IsUnconsumedPitLord(pet, consumed) ? pet : nullptr;
}
bool IsRuinationReady(Unit* caster)
{
return caster && caster->HasAura(SPELL_WARLOCK_RUINATION) && GetPendingPitLord(caster);
}
void ConsumeRuination(Unit* caster)
{
if (!caster || caster->GetTypeId() != TYPEID_PLAYER)
return;
Player* player = caster->ToPlayer();
GuidUnorderedSet& consumed = sRuinationConsumedPitLords[player->GetGUID()];
auto consume = [&consumed](Unit* unit)
{
if (unit && unit->GetEntry() == NPC_WARLOCK_PIT_LORD && unit->IsAlive() && !consumed.contains(unit->GetGUID()))
{
consumed.insert(unit->GetGUID());
return true;
}
return false;
};
for (Unit* controlled : caster->m_Controlled)
if (consume(controlled))
return;
consume(ObjectAccessor::GetCreatureOrPetOrVehicle(*player, player->GetPetGUID()));
}
}
class player_script_warl_ruination : public PlayerScript
{
public:
player_script_warl_ruination() : PlayerScript("player_script_warl_ruination") { }
void OnLogout(Player* player) override
{
sRuinationConsumedPitLords.erase(player->GetGUID());
}
};
// 146739 - Corruption
// 445474 - Wither
class spell_warl_absolute_corruption : public SpellScript
@@ -460,9 +559,25 @@ class spell_warl_chaos_bolt : public SpellScript
return GetCaster()->GetTypeId() == TYPEID_PLAYER;
}
void HandleDummy(SpellEffIndex /*effIndex*/)
void HandleDummy(SpellEffIndex effIndex)
{
SetHitDamage(GetHitDamage() + CalculatePct(GetHitDamage(), GetCaster()->ToPlayer()->m_activePlayerData->SpellCritPercentage));
Unit* caster = GetCaster();
Unit* target = GetHitUnit();
if (target && IsRuinationReady(caster))
{
ConsumeRuination(caster);
PreventHitEffect(effIndex);
if (SpellInfo const* ruination = sSpellMgr->GetSpellInfo(SPELL_WARLOCK_RUINATION_AOE, DIFFICULTY_NONE))
caster->CastSpell(target, SPELL_WARLOCK_RUINATION_AOE, CastSpellExtraArgsInit{
.TriggerFlags = TRIGGERED_IGNORE_GCD | TRIGGERED_IGNORE_CAST_TIME | TRIGGERED_IGNORE_CAST_IN_PROGRESS | TRIGGERED_DONT_REPORT_CAST_ERROR,
.TriggeringSpell = GetSpell()
});
return;
}
SetHitDamage(GetHitDamage() + CalculatePct(GetHitDamage(), caster->ToPlayer()->m_activePlayerData->SpellCritPercentage));
}
void CalcCritChance(Unit const* /*victim*/, float& critChance)
@@ -634,12 +749,28 @@ class spell_warl_demonbolt : public SpellScript
{
bool Validate(SpellInfo const* /*spellInfo*/) override
{
return ValidateSpellInfo({ SPELL_WARLOCK_DEMONBOLT_ENERGIZE });
return ValidateSpellInfo({ SPELL_WARLOCK_DEMONBOLT_ENERGIZE, SPELL_WARLOCK_POWER_SIPHON_DEMONIC_CORE });
}
void HandleCalcDamage(SpellEffectInfo const& /*spellEffectInfo*/, Unit* /*victim*/, int32 const& /*damage*/, int32 const& /*flatMod*/, float& pctMod) const
{
if (Unit* caster = GetCaster())
{
if (caster->GetAuraCount(SPELL_WARLOCK_POWER_SIPHON_DEMONIC_CORE) > 0)
AddPct(pctMod, 30);
}
}
void HandleAfterCast() const
{
GetCaster()->CastSpell(GetCaster(), SPELL_WARLOCK_DEMONBOLT_ENERGIZE, CastSpellExtraArgsInit{
Unit* caster = GetCaster();
if (!caster)
return;
if (Aura* aura = caster->GetAura(SPELL_WARLOCK_POWER_SIPHON_DEMONIC_CORE))
aura->ModStackAmount(-1, AURA_REMOVE_BY_DEFAULT);
caster->CastSpell(caster, SPELL_WARLOCK_DEMONBOLT_ENERGIZE, CastSpellExtraArgsInit{
.TriggerFlags = TRIGGERED_IGNORE_CAST_IN_PROGRESS | TRIGGERED_DONT_REPORT_CAST_ERROR,
.TriggeringSpell = GetSpell()
});
@@ -647,6 +778,7 @@ class spell_warl_demonbolt : public SpellScript
void Register() override
{
CalcDamage += SpellCalcDamageFn(spell_warl_demonbolt::HandleCalcDamage);
AfterCast += SpellCastFn(spell_warl_demonbolt::HandleAfterCast);
}
};
@@ -1617,6 +1749,116 @@ class spell_warl_dreadstalker_spawn : public SpellScript
}
};
// 205196/271971 - Dreadbite
class spell_warl_carnivorous_stalkers : public SpellScript
{
bool Validate(SpellInfo const* /*spellInfo*/) override
{
return ValidateSpellInfo({ SPELL_WARLOCK_CARNIVOROUS_STALKERS });
}
void HandleAfterCast() const
{
Spell const* spell = GetSpell();
Unit* dreadstalker = GetCaster();
Unit* victim = GetExplTargetUnit();
if (!spell || !dreadstalker || !victim)
return;
if (!spell->m_originalCastId.IsEmpty())
return;
Unit* owner = dreadstalker->IsSummon() ? dreadstalker->ToTempSummon()->GetSummonerUnit() : nullptr;
if (!owner)
return;
AuraEffect const* chance = owner->GetAuraEffect(SPELL_WARLOCK_CARNIVOROUS_STALKERS, EFFECT_0);
if (!chance || !roll_chance(chance->GetAmount()))
return;
dreadstalker->CastSpell(victim, spell->m_spellInfo->Id, CastSpellExtraArgs(TRIGGERED_IGNORE_CAST_IN_PROGRESS | TRIGGERED_DONT_REPORT_CAST_ERROR)
.SetOriginalCastId(spell->m_castId));
}
void Register() override
{
AfterCast += SpellCastFn(spell_warl_carnivorous_stalkers::HandleAfterCast);
}
};
// 205196/271971 - Dreadbite
class spell_warl_blighted_maw : public SpellScript
{
bool Validate(SpellInfo const* /*spellInfo*/) override
{
return ValidateSpellInfo({ SPELL_WARLOCK_BLIGHTED_MAW });
}
void HandleHit() const
{
Unit* dreadstalker = GetCaster();
Unit* victim = GetHitUnit();
if (!dreadstalker || !victim)
return;
int32 hitDamage = GetHitDamage();
if (hitDamage <= 0)
return;
Unit* owner = dreadstalker->IsSummon() ? dreadstalker->ToTempSummon()->GetSummonerUnit() : nullptr;
if (!owner)
return;
AuraEffect const* maw = owner->GetAuraEffect(SPELL_WARLOCK_BLIGHTED_MAW, EFFECT_0);
if (!maw)
return;
int32 plagueDamage = CalculatePct(hitDamage, maw->GetAmount());
if (plagueDamage <= 0)
return;
Unit::DealDamage(dreadstalker, victim, plagueDamage, nullptr, DIRECT_DAMAGE, SPELL_SCHOOL_MASK_NATURE, GetSpellInfo(), false);
}
void Register() override
{
OnHit += SpellHitFn(spell_warl_blighted_maw::HandleHit);
}
};
// 1276452 - Grimoire: Imp Lord
class spell_warl_grimoire_imp_lord : public SpellScript
{
void HandleAfterCast() const
{
Unit* caster = GetCaster();
if (!caster)
return;
// Remove 1 harmful Magic effect from the caster when summoned
for (auto const& itr : caster->GetAppliedAuras())
{
Aura* aura = itr.second->GetBase();
SpellInfo const* spellInfo = aura->GetSpellInfo();
if (spellInfo->Dispel != DISPEL_MAGIC || spellInfo->IsPassive() || spellInfo->IsPositive())
continue;
if (aura->IsArea())
continue;
caster->RemoveAura(aura);
break;
}
// Transform the button into Singe Magic while on cooldown
if (sSpellMgr->GetSpellInfo(SPELL_WARLOCK_GRIMOIRE_IMP_LORD_OVERRIDE, DIFFICULTY_NONE))
caster->CastSpell(caster, SPELL_WARLOCK_GRIMOIRE_IMP_LORD_OVERRIDE, CastSpellExtraArgs(TRIGGERED_IGNORE_CAST_IN_PROGRESS | TRIGGERED_DONT_REPORT_CAST_ERROR));
}
void Register() override
{
AfterCast += SpellCastFn(spell_warl_grimoire_imp_lord::HandleAfterCast);
}
};
// 105174 - Hand of Gul'Dan
class spell_warl_hand_of_guldan : public SpellScript
{
@@ -1628,12 +1870,20 @@ class spell_warl_hand_of_guldan : public SpellScript
if (!caster || !target)
return;
std::list<Creature*> oldImps;
caster->GetCreatureListWithEntryInGrid(oldImps, 55659, 100.0f);
for (Creature* imp : oldImps)
if (IsRuinationReady(caster))
{
if (imp->IsAlive() && imp->GetOwnerGUID() == caster->GetGUID())
imp->DespawnOrUnsummon();
ConsumeRuination(caster);
// Suppress every database driven effect of Hand of Gul'dan (damage/summoning)
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
PreventHitEffect(SpellEffIndex(i));
if (SpellInfo const* ruination = sSpellMgr->GetSpellInfo(SPELL_WARLOCK_RUINATION_AOE, DIFFICULTY_NONE))
caster->CastSpell(target, SPELL_WARLOCK_RUINATION_AOE, CastSpellExtraArgsInit{
.TriggerFlags = TRIGGERED_IGNORE_GCD | TRIGGERED_IGNORE_CAST_TIME | TRIGGERED_IGNORE_CAST_IN_PROGRESS | TRIGGERED_DONT_REPORT_CAST_ERROR,
.TriggeringSpell = GetSpell()
});
return;
}
SpellInfo const* summonSpellInfo = sSpellMgr->GetSpellInfo(SPELL_WARLOCK_WILD_IMP_SUMMON, DIFFICULTY_NONE);
@@ -1716,6 +1966,279 @@ class spell_warl_hand_of_guldan_damage : public SpellScript
}
};
// 1281511 - To Hell and Back
// While talented, Power Siphon and Implosion track how many Wild Imps they sacrifice on
// this aura's stack amount. Every $s2 (EFFECT_1 dummy, default 2) sacrificed Wild Imps,
// the caster summons $s1 (EFFECT_0 dummy, default 1) Wild Imp Gang Boss(es) - Wild Imps
// that instantly gain "Unstable Soul" (1281512): +$w1 damage and Fel Firebolt now jumps
// to 4 additional targets. A fresh talent application (relog / re-talent) resets the
// sacrificed counter to zero.
class spell_warl_to_hell_and_back : public AuraScript
{
public:
// Called by spell_warl_implosion and spell_warl_power_siphon for every Wild Imp they
// sacrifice. Counts on the talent aura's stack; when the threshold (`interval`, from
// EFFECT_1 dummy) is reached, summons `bossCount` Wild Imp(s) (EFFECT_0 dummy) and
// applies "Unstable Soul" (1281512) to the freshly summoned imp.
static void CountSacrifice(Unit* caster)
{
if (!caster)
return;
Aura* talent = caster->GetAura(SPELL_WARLOCK_TO_HELL_AND_BACK);
if (!talent)
return;
int32 interval = 2; // EFFECT_1 dummy default
int32 bossCount = 1; // EFFECT_0 dummy default
if (AuraEffect const* eff = talent->GetEffect(EFFECT_1))
interval = std::max<int32>(1, eff->GetAmount());
if (AuraEffect const* eff = talent->GetEffect(EFFECT_0))
bossCount = std::max<int32>(1, eff->GetAmount());
if (uint32(talent->GetStackAmount()) + 1 < uint32(interval))
{
talent->SetStackAmount(uint8(talent->GetStackAmount() + 1));
return;
}
talent->SetStackAmount(0);
SpellInfo const* summonSpellInfo = sSpellMgr->GetSpellInfo(SPELL_WARLOCK_WILD_IMP_SUMMON, DIFFICULTY_NONE);
if (!summonSpellInfo)
return;
uint32 impEntry = uint32(summonSpellInfo->GetEffect(EFFECT_0).MiscValue);
for (int32 i = 0; i < bossCount; ++i)
{
caster->CastSpell(caster, SPELL_WARLOCK_WILD_IMP_SUMMON, CastSpellExtraArgs()
.SetTriggerFlags(TRIGGERED_IGNORE_CAST_IN_PROGRESS | TRIGGERED_DONT_REPORT_CAST_ERROR));
// The freshly summoned Wild Imp spawns right next to the caster (the imps
// that were just sacrificed were despawned moments earlier). The closest
// owned, still-alive Wild Imp that does not already carry Unstable Soul is
// therefore the Imp Gang Boss we just brought in.
std::list<Creature*> imps;
caster->GetCreatureListWithEntryInGrid(imps, impEntry, 100.0f);
Creature* bossImp = nullptr;
float bestDist = std::numeric_limits<float>::max();
for (Creature* imp : imps)
{
if (!imp || !imp->IsAlive() || imp->GetOwnerGUID() != caster->GetGUID())
continue;
if (imp->HasAura(SPELL_WARLOCK_TO_HELL_AND_BACK_UNSTABLE_SOUL))
continue;
float dist = caster->GetDistance(imp);
if (dist < bestDist)
{
bestDist = dist;
bossImp = imp;
}
}
if (bossImp)
bossImp->CastSpell(bossImp, SPELL_WARLOCK_TO_HELL_AND_BACK_UNSTABLE_SOUL, CastSpellExtraArgs()
.SetTriggerFlags(TRIGGERED_IGNORE_CAST_IN_PROGRESS | TRIGGERED_DONT_REPORT_CAST_ERROR));
}
}
bool Validate(SpellInfo const* /*spellInfo*/) override
{
return ValidateSpellInfo({ SPELL_WARLOCK_TO_HELL_AND_BACK, SPELL_WARLOCK_TO_HELL_AND_BACK_UNSTABLE_SOUL });
}
// A fresh application (relog / re-talent) resets the sacrificed counter to zero.
void OnApply(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
{
GetAura()->SetStackAmount(0);
}
void Register() override
{
OnEffectApply += AuraEffectApplyFn(spell_warl_to_hell_and_back::OnApply, EFFECT_0, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL);
}
};
// 196277 - Implosion
// Pulls up to 6 Wild Imps toward the target and causes each to explode for Shadowflame AOE damage.
class spell_warl_implosion : public SpellScript
{
bool Validate(SpellInfo const* /*spellInfo*/) override
{
return ValidateSpellInfo({ SPELL_WARLOCK_WILD_IMP_SUMMON, SPELL_WARLOCK_IMPLOSION_DAMAGE });
}
void HandleHitTarget(SpellEffIndex /*effIndex*/)
{
Unit* caster = GetCaster();
Unit* target = GetHitUnit();
if (!caster || !target)
return;
SpellInfo const* summonSpellInfo = sSpellMgr->GetSpellInfo(SPELL_WARLOCK_WILD_IMP_SUMMON, DIFFICULTY_NONE);
if (!summonSpellInfo)
return;
uint32 creatureEntry = summonSpellInfo->GetEffect(EFFECT_0).MiscValue;
int32 maxImps = GetSpellInfo()->GetEffect(EFFECT_0).BasePoints;
if (maxImps <= 0)
return;
std::list<Creature*> wildImps;
caster->GetCreatureListWithEntryInGrid(wildImps, creatureEntry, 100.0f);
int32 pulled = 0;
for (Creature* imp : wildImps)
{
if (pulled >= maxImps)
break;
if (!imp->IsAlive() || imp->GetOwnerGUID() != caster->GetGUID())
continue;
float angle = rand_norm() * 2.0f * float(M_PI);
float radius = rand_norm() * 2.0f;
imp->GetMotionMaster()->MovePoint(0, target->GetPositionX() + radius * std::cos(angle),
target->GetPositionY() + radius * std::sin(angle), target->GetPositionZ(), false);
uint32 travelMs = std::clamp<uint32>(uint32(imp->GetDistance(target) * 20.0f), 300, 1500);
ObjectGuid targetGuid = target->GetGUID();
imp->GetScheduler().Schedule(Milliseconds(travelMs), [imp, targetGuid](TaskContext /*context*/)
{
if (Unit* explodeTarget = ObjectAccessor::GetUnit(*imp, targetGuid))
imp->CastSpell(explodeTarget, SPELL_WARLOCK_IMPLOSION_DAMAGE, CastSpellExtraArgs()
.SetTriggerFlags(TRIGGERED_IGNORE_CAST_IN_PROGRESS | TRIGGERED_DONT_REPORT_CAST_ERROR));
imp->DespawnOrUnsummon();
// To Hell and Back (1281511): every Wild Imp exploded here counts as a
// sacrifice toward the talent's Imp Gang Boss proc.
if (Unit* owner = imp->GetOwner())
spell_warl_to_hell_and_back::CountSacrifice(owner);
});
++pulled;
}
}
void Register() override
{
OnEffectHitTarget += SpellEffectFn(spell_warl_implosion::HandleHitTarget, EFFECT_0, SPELL_EFFECT_DUMMY);
}
};
// 264130 - Power Siphon
class spell_warl_power_siphon : public SpellScript
{
bool Validate(SpellInfo const* /*spellInfo*/) override
{
return ValidateSpellInfo({ SPELL_WARLOCK_WILD_IMP_SUMMON, SPELL_WARLOCK_POWER_SIPHON_DEMONIC_CORE });
}
void HandleHitTarget(SpellEffIndex effIndex)
{
Unit* caster = GetCaster();
Unit* target = GetHitUnit();
if (!caster || !target)
return;
Creature* imp = target->ToCreature();
if (!imp || !imp->IsAlive())
return;
SpellInfo const* summonSpellInfo = sSpellMgr->GetSpellInfo(SPELL_WARLOCK_WILD_IMP_SUMMON, DIFFICULTY_NONE);
if (!summonSpellInfo)
return;
uint32 impEntry = summonSpellInfo->GetEffect(EFFECT_0).MiscValue;
if (imp->GetEntry() != impEntry || imp->GetOwnerGUID() != caster->GetGUID())
return;
if (_sacrificed >= uint32(GetSpellInfo()->GetEffect(effIndex).BasePoints))
{
PreventHitEffect(effIndex);
return;
}
imp->DespawnOrUnsummon();
// To Hell and Back (1281511): every Wild Imp sacrificed by Power Siphon counts
// toward the talent's Imp Gang Boss proc.
spell_warl_to_hell_and_back::CountSacrifice(caster);
caster->CastSpell(caster, SPELL_WARLOCK_POWER_SIPHON_DEMONIC_CORE,
CastSpellExtraArgs(TRIGGERED_IGNORE_CAST_IN_PROGRESS | TRIGGERED_DONT_REPORT_CAST_ERROR));
++_sacrificed;
}
void Register() override
{
OnEffectHitTarget += SpellEffectFn(spell_warl_power_siphon::HandleHitTarget, EFFECT_0, SPELL_EFFECT_DUMMY);
}
private:
uint32 _sacrificed = 0;
};
// 104318 - Fel Firebolt
class spell_imp_fel_firebolt : public SpellScript
{
bool Validate(SpellInfo const* /*spellInfo*/) override
{
return ValidateSpellInfo({ SPELL_WARLOCK_INFERNAL_RAPIDITY });
}
void HandleCalcDamage(SpellEffectInfo const& /*spellEffectInfo*/, Unit* /*victim*/, int32 const& /*damage*/, int32 const& /*flatMod*/, float& pctMod) const
{
Spell const* spell = GetSpell();
if (!spell || spell->m_originalCastId.IsEmpty())
return;
Unit* imp = GetCaster();
if (!imp)
return;
Unit* owner = imp->IsSummon() ? imp->ToTempSummon()->GetSummonerUnit() : nullptr;
if (!owner)
return;
if (AuraEffect const* effectiveness = owner->GetAuraEffect(SPELL_WARLOCK_INFERNAL_RAPIDITY, EFFECT_1))
AddPct(pctMod, effectiveness->GetAmount() - 100);
}
void HandleAfterCast() const
{
Spell const* spell = GetSpell();
Unit* imp = GetCaster();
Unit* victim = GetExplTargetUnit();
if (!spell || !imp || !victim)
return;
if (!spell->m_originalCastId.IsEmpty())
return;
Unit* owner = imp->IsSummon() ? imp->ToTempSummon()->GetSummonerUnit() : nullptr;
if (!owner)
return;
AuraEffect const* chance = owner->GetAuraEffect(SPELL_WARLOCK_INFERNAL_RAPIDITY, EFFECT_0);
if (!chance || !roll_chance(chance->GetAmount()))
return;
imp->CastSpell(victim, SPELL_WARLOCK_IMP_FEL_FIREBOLT, CastSpellExtraArgs(TRIGGERED_IGNORE_CAST_IN_PROGRESS | TRIGGERED_DONT_REPORT_CAST_ERROR)
.SetOriginalCastId(spell->m_castId));
}
void Register() override
{
CalcDamage += SpellCalcDamageFn(spell_imp_fel_firebolt::HandleCalcDamage);
AfterCast += SpellCastFn(spell_imp_fel_firebolt::HandleAfterCast);
}
};
enum FreakzWarlockNPCs
{
NPC_WARLOCK_DEMONIC_GATEWAY_PURPLE = 59271,
@@ -1941,6 +2464,163 @@ class spell_warl_cunning_cruelty : public AuraScript
}
};
// 265187 - Summon Demonic Tyrant
// Spell has two SPELL_EFFECT_SUMMON effects (EFFECT_0 entry 135002, EFFECT_3 entry 250289).
// Suppress EFFECT_3 to avoid summoning two tyrants simultaneously.
// When talented with Dominion of Argus (1276163), summoning the tyrant opens
// the portal window to Argus (1276166) on the caster.
class spell_warl_summon_demonic_tyrant : public SpellScript
{
void PreventDuplicateSummon(SpellEffIndex effIndex)
{
PreventHitEffect(effIndex);
}
void OpenArgusPortal()
{
if (Unit* caster = GetCaster())
if (caster->HasAura(SPELL_WARLOCK_DOMINION_OF_ARGUS))
caster->CastSpell(caster, SPELL_WARLOCK_DOMINION_OF_ARGUS_PORTAL, CastSpellExtraArgs(TRIGGERED_IGNORE_CAST_IN_PROGRESS | TRIGGERED_DONT_REPORT_CAST_ERROR));
}
void Register() override
{
OnEffectLaunch += SpellEffectFn(spell_warl_summon_demonic_tyrant::PreventDuplicateSummon, EFFECT_3, SPELL_EFFECT_SUMMON);
AfterCast += SpellCastFn(spell_warl_summon_demonic_tyrant::OpenArgusPortal);
}
};
// 1276166 - Dominion of Argus (portal window)
// Window buff applied on the caster when the Demonic Tyrant is summoned while
// talented with Dominion of Argus (1276163). Its aura stack carries a running
// count of Hand of Gul'dan casts inside the 15 sec window; a fresh application
// (new tyrant) resets the counter back to zero.
class spell_warl_dominion_of_argus_portal : public AuraScript
{
bool Validate(SpellInfo const* /*spellInfo*/) override
{
return ValidateSpellInfo({ SPELL_WARLOCK_DOMINION_OF_ARGUS, SPELL_WARLOCK_DOMINION_OF_ARGUS_PORTAL,
SPELL_WARLOCK_DOMINION_OF_ARGUS_SUMMON_ANTORAN_JAILER, SPELL_WARLOCK_DOMINION_OF_ARGUS_SUMMON_ANTORAN_INQUISITOR,
SPELL_WARLOCK_DOMINION_OF_ARGUS_SUMMON_LADY_SACROLASH, SPELL_WARLOCK_DOMINION_OF_ARGUS_SUMMON_GRAND_WARLOCK_ALYTHESS });
}
void OnApply(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
{
GetAura()->SetStackAmount(0);
}
void Register() override
{
OnEffectApply += AuraEffectApplyFn(spell_warl_dominion_of_argus_portal::OnApply, EFFECT_0, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL);
}
};
// 105174 - Hand of Gul'dan (with Dominion of Argus)
// While the portal window 1276166 is open on the caster, counts Hand of Gul'dan
// casts. Every `interval` cast (dummy amount of the 1276163 talent, default 2)
// the caster summons a random subjugated demon that serves for 10 sec, then the
// counter resets. The counter lives in the portal aura's stack amount.
class spell_warl_dominion_of_argus : public SpellScript
{
bool Validate(SpellInfo const* /*spellInfo*/) override
{
return ValidateSpellInfo({ SPELL_WARLOCK_DOMINION_OF_ARGUS, SPELL_WARLOCK_DOMINION_OF_ARGUS_PORTAL });
}
void HandleAfterCast()
{
Unit* caster = GetCaster();
if (!caster)
return;
Aura* portal = caster->GetAura(SPELL_WARLOCK_DOMINION_OF_ARGUS_PORTAL);
if (!portal)
return;
uint32 interval = 2;
if (AuraEffect const* talentEff = caster->GetAuraEffect(SPELL_WARLOCK_DOMINION_OF_ARGUS, EFFECT_0, caster->GetGUID()))
interval = std::max<int32>(1, talentEff->GetAmount());
uint32 casts = uint32(portal->GetStackAmount()) + 1;
if (casts < interval)
{
portal->SetStackAmount(uint8(casts));
return;
}
std::array<uint32, 4> const Summons =
{
SPELL_WARLOCK_DOMINION_OF_ARGUS_SUMMON_ANTORAN_JAILER,
SPELL_WARLOCK_DOMINION_OF_ARGUS_SUMMON_ANTORAN_INQUISITOR,
SPELL_WARLOCK_DOMINION_OF_ARGUS_SUMMON_LADY_SACROLASH,
SPELL_WARLOCK_DOMINION_OF_ARGUS_SUMMON_GRAND_WARLOCK_ALYTHESS
};
std::vector<uint32> available;
available.reserve(Summons.size());
for (uint32 summonSpell : Summons)
if (sSpellMgr->GetSpellInfo(summonSpell, DIFFICULTY_NONE))
available.push_back(summonSpell);
if (!available.empty())
caster->CastSpell(caster, Trinity::Containers::SelectRandomContainerElement(available),
CastSpellExtraArgs(TRIGGERED_IGNORE_CAST_IN_PROGRESS | TRIGGERED_DONT_REPORT_CAST_ERROR));
portal->SetStackAmount(0);
}
void Register() override
{
AfterCast += SpellCastFn(spell_warl_dominion_of_argus::HandleAfterCast);
}
};
// 267216 - Inner Demons
// Periodically summons a Wild Imp every 12 seconds.
class spell_warl_inner_demons : public AuraScript
{
bool Validate(SpellInfo const* /*spellInfo*/) override
{
return ValidateSpellInfo({ SPELL_WARLOCK_WILD_IMP_SUMMON });
}
void HandlePeriodicTick(AuraEffect const* /*aurEff*/) const
{
Unit* caster = GetCaster();
if (!caster)
return;
SpellInfo const* summonSpellInfo = sSpellMgr->GetSpellInfo(SPELL_WARLOCK_WILD_IMP_SUMMON, DIFFICULTY_NONE);
if (!summonSpellInfo)
return;
SpellEffectInfo const& effect = summonSpellInfo->GetEffect(EFFECT_0);
uint32 creatureEntry = effect.MiscValue;
uint32 propertiesId = effect.MiscValueB;
Milliseconds duration = Milliseconds(summonSpellInfo->GetDuration());
float x = caster->GetPositionX();
float y = caster->GetPositionY();
float z = caster->GetPositionZ();
float angle = rand_norm() * 2 * M_PI;
float radius = 2.0f + rand_norm() * 3.0f;
float destX = x + radius * std::cos(angle);
float destY = y + radius * std::sin(angle);
float destZ = z;
if (SummonPropertiesEntry const* properties = sSummonPropertiesStore.LookupEntry(propertiesId))
caster->GetMap()->SummonCreature(creatureEntry, Position(destX, destY, destZ, 0.0f), properties, duration, caster);
}
void Register() override
{
OnEffectPeriodic += AuraEffectPeriodicFn(spell_warl_inner_demons::HandlePeriodicTick, EFFECT_0, SPELL_AURA_PERIODIC_DUMMY);
}
};
// 387506 - Mayhem
// On harmful single-target spells, 35% chance to apply Havoc to a nearby enemy.
class spell_warl_mayhem : public AuraScript
@@ -1987,6 +2667,7 @@ class spell_warl_mayhem : public AuraScript
void AddSC_warlock_spell_scripts()
{
new player_script_warl_ruination();
RegisterSpellScript(spell_warl_absolute_corruption);
RegisterSpellScript(spell_warl_backdraft);
RegisterSpellScript(spell_warl_banish);
@@ -2041,6 +2722,9 @@ void AddSC_warlock_spell_scripts()
//WowCommunity
RegisterSpellScript(spell_warl_call_dreadstalkers);
RegisterSpellScript(spell_warl_dreadstalker_spawn);
RegisterSpellScript(spell_warl_carnivorous_stalkers);
RegisterSpellScript(spell_warl_blighted_maw);
RegisterSpellScript(spell_warl_grimoire_imp_lord);
RegisterSpellScript(spell_warl_hand_of_guldan);
RegisterSpellScript(spell_warl_hand_of_guldan_damage);
RegisterSpellScript(spell_warl_demonic_gateway);
@@ -2048,4 +2732,12 @@ void AddSC_warlock_spell_scripts()
RegisterSpellScript(spell_warl_shard_instability);
RegisterSpellScript(spell_warl_cull_the_weak);
RegisterSpellScript(spell_warl_cunning_cruelty);
RegisterSpellScript(spell_warl_inner_demons);
RegisterSpellScript(spell_warl_summon_demonic_tyrant);
RegisterSpellScript(spell_warl_dominion_of_argus_portal);
RegisterSpellScript(spell_warl_dominion_of_argus);
RegisterSpellScript(spell_warl_implosion);
RegisterSpellScript(spell_warl_power_siphon);
RegisterSpellScript(spell_warl_to_hell_and_back);
RegisterSpellScript(spell_imp_fel_firebolt);
}