Compare commits
34
Commits
84522036eb
...
1221562029
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1221562029 | ||
|
|
18ed96f0b5 | ||
|
|
de6731c35d | ||
|
|
452a087df4 | ||
|
|
167a0f0685 | ||
|
|
a6fa569873 | ||
|
|
9f103d8db3 | ||
|
|
c395cf8095 | ||
|
|
ee37d9e24b | ||
|
|
2543c0a138 | ||
|
|
1cc3cc295c | ||
|
|
24faed09e9 | ||
|
|
98b6221faf | ||
|
|
bae6615dfc | ||
|
|
419e747a1a | ||
|
|
e679e059c7 | ||
|
|
a69fc40271 | ||
|
|
c4fcc30587 | ||
|
|
8d9799b1c6 | ||
|
|
5eb1019716 | ||
|
|
a89a654653 | ||
|
|
c1cf1dd828 | ||
|
|
2c187ea79f | ||
|
|
170331c6d6 | ||
|
|
06ec5d3b57 | ||
|
|
8b0a639883 | ||
|
|
31774f51ba | ||
|
|
cb62057856 | ||
|
|
04cbe672f9 | ||
|
|
337b50eb18 | ||
|
|
07f3f93060 | ||
|
|
eb8e2ebd53 | ||
|
|
5f6adfb702 | ||
|
|
a81041c96a |
@@ -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');
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
// ============================================================
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
+1033
-118
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user