mirror of
https://github.com/liyunfan1223/mod-playerbots.git
synced 2026-06-20 15:39:25 +02:00
Compare commits
10 Commits
d0ba99f381
...
6294199343
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6294199343 | ||
|
|
1443668694 | ||
|
|
55c5d29e2d | ||
|
|
3a7e3e2719 | ||
|
|
cd2fe2f9a1 | ||
|
|
92081c9f1a | ||
|
|
34f34ef13d | ||
|
|
ab196cb532 | ||
|
|
c7b4b9aa80 | ||
|
|
2973083dda |
@ -552,6 +552,17 @@ AiPlayerbot.AutoGearCommand = 1
|
|||||||
# Default: 1 (enabled)
|
# Default: 1 (enabled)
|
||||||
AiPlayerbot.AutoGearCommandAltBots = 1
|
AiPlayerbot.AutoGearCommandAltBots = 1
|
||||||
|
|
||||||
|
# If 1 (enabled) chat command: autogear bis, gives bots BiS gear from the playerbots_bis_gear table
|
||||||
|
# whose item-level floor matches AutoGearScoreLimit (e.g. 290 = ICC, 245 = ToC,
|
||||||
|
# 125 = Kara, 78 = MC). See the AutoGearScoreLimit comment below for the full list.
|
||||||
|
# chat command: autogear bis x, (x must be positive integer) will give items based on value of x.
|
||||||
|
# If x is bigger than AutoGearScoreLimit, bis wont be given, if lower it will match x when giving items.
|
||||||
|
# Commands falls back to autogear when no tier matches or the table is empty for the bot's class/spec.
|
||||||
|
# Requires AutoGearQualityLimit = 4 and AutoGearCommand = 1.
|
||||||
|
# If AutoGearCommandAltBots = 1 it will be anbled for alt bots.
|
||||||
|
# Default: 0 (disabled)
|
||||||
|
AiPlayerbot.AutoGearBisCommand = 0
|
||||||
|
|
||||||
# Equipment quality limitation for autogear command (1 = normal, 2 = uncommon, 3 = rare, 4 = epic, 5 = legendary)
|
# Equipment quality limitation for autogear command (1 = normal, 2 = uncommon, 3 = rare, 4 = epic, 5 = legendary)
|
||||||
# Default: 3 (rare)
|
# Default: 3 (rare)
|
||||||
AiPlayerbot.AutoGearQualityLimit = 3
|
AiPlayerbot.AutoGearQualityLimit = 3
|
||||||
@ -2151,6 +2162,27 @@ AiPlayerbot.RandomClassSpecIndex.11.6 = 6
|
|||||||
#
|
#
|
||||||
####################################################################################################
|
####################################################################################################
|
||||||
|
|
||||||
|
###################################
|
||||||
|
# #
|
||||||
|
# RAIDS #
|
||||||
|
# #
|
||||||
|
###################################
|
||||||
|
|
||||||
|
####################################################################################################
|
||||||
|
#
|
||||||
|
#
|
||||||
|
#
|
||||||
|
|
||||||
|
# Enable buffs in ICC to make Heroic easier and more casual. Default is 1.
|
||||||
|
# 30% more damage, 40% damage reduction (tank bots), increased all resistances, reduced threat for
|
||||||
|
# non tank bots, increased threat for tank bots.
|
||||||
|
# Buffs will be applied on LDW, PP, Sindragosa and Lich King.
|
||||||
|
AiPlayerbot.EnableICCBuffs = 1
|
||||||
|
|
||||||
|
#
|
||||||
|
#
|
||||||
|
#
|
||||||
|
####################################################################################################
|
||||||
|
|
||||||
###################################
|
###################################
|
||||||
# #
|
# #
|
||||||
@ -2434,9 +2466,3 @@ AiPlayerbot.TargetPosRecalcDistance = 0.1
|
|||||||
|
|
||||||
# Allow bots to be summoned near innkeepers
|
# Allow bots to be summoned near innkeepers
|
||||||
AiPlayerbot.SummonAtInnkeepersEnabled = 1
|
AiPlayerbot.SummonAtInnkeepersEnabled = 1
|
||||||
|
|
||||||
# Enable buffs in ICC to make Heroic easier and more casual.
|
|
||||||
# 30% more damage, 40% damage reduction (tank bots), increased all resistances, reduced threat for non tank bots, increased threat for tank bots.
|
|
||||||
# Buffs will be applied on PP, Sindragosa and Lich King
|
|
||||||
|
|
||||||
AiPlayerbot.EnableICCBuffs = 1
|
|
||||||
|
|||||||
7973
data/sql/playerbots/updates/2026_04_28_00_playerbots_bis_gear.sql
Normal file
7973
data/sql/playerbots/updates/2026_04_28_00_playerbots_bis_gear.sql
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,183 @@
|
|||||||
|
-- #########################################################
|
||||||
|
-- Playerbots - Add /p autogear bis command texts
|
||||||
|
-- Localized for all WotLK locales (koKR, frFR, deDE, zhCN,
|
||||||
|
-- zhTW, esES, esMX, ruRU)
|
||||||
|
-- #########################################################
|
||||||
|
|
||||||
|
DELETE FROM ai_playerbot_texts WHERE name IN (
|
||||||
|
'bis_autogear_unavailable_error',
|
||||||
|
'bis_no_rows_fallback',
|
||||||
|
'bis_command_unavailable_error',
|
||||||
|
'bis_altbot_refused_error',
|
||||||
|
'bis_quality_floor_error',
|
||||||
|
'bis_pvp_refused_error',
|
||||||
|
'bis_invalid_arg_error',
|
||||||
|
'bis_arg_above_limit_error',
|
||||||
|
'bis_no_rows_autogear_msg',
|
||||||
|
'bis_closest_match_msg',
|
||||||
|
'bis_applying_msg',
|
||||||
|
'bis_applied_msg'
|
||||||
|
);
|
||||||
|
DELETE FROM ai_playerbot_texts_chance WHERE name IN (
|
||||||
|
'bis_autogear_unavailable_error',
|
||||||
|
'bis_no_rows_fallback',
|
||||||
|
'bis_command_unavailable_error',
|
||||||
|
'bis_altbot_refused_error',
|
||||||
|
'bis_quality_floor_error',
|
||||||
|
'bis_pvp_refused_error',
|
||||||
|
'bis_invalid_arg_error',
|
||||||
|
'bis_arg_above_limit_error',
|
||||||
|
'bis_no_rows_autogear_msg',
|
||||||
|
'bis_closest_match_msg',
|
||||||
|
'bis_applying_msg',
|
||||||
|
'bis_applied_msg'
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO `ai_playerbot_texts`
|
||||||
|
(`id`, `name`, `text`, `say_type`, `reply_type`,
|
||||||
|
`text_loc1`, `text_loc2`, `text_loc3`, `text_loc4`,
|
||||||
|
`text_loc5`, `text_loc6`, `text_loc7`, `text_loc8`)
|
||||||
|
VALUES
|
||||||
|
(1767, 'bis_autogear_unavailable_error',
|
||||||
|
'autogear command is not allowed, please check the configuration.', 0, 0,
|
||||||
|
'자동 장비 명령이 허용되지 않습니다. 설정을 확인하세요.',
|
||||||
|
'La commande autogear n''est pas autorisée, veuillez vérifier la configuration.',
|
||||||
|
'Der autogear-Befehl ist nicht erlaubt, bitte überprüfe die Konfiguration.',
|
||||||
|
'自动装备命令未启用,请检查配置。',
|
||||||
|
'自動裝備指令未啟用,請檢查設定。',
|
||||||
|
'El comando autogear no está permitido, por favor revisa la configuración.',
|
||||||
|
'El comando autogear no está permitido, por favor revisa la configuración.',
|
||||||
|
'Команда autogear не разрешена, проверьте конфигурацию.'),
|
||||||
|
|
||||||
|
(1768, 'bis_no_rows_fallback',
|
||||||
|
'No BiS for your tier/spec/level, check cfg, running autogear instead', 0, 0,
|
||||||
|
'해당 등급/전문화/레벨에 BiS 목록이 없습니다. 설정을 확인하세요. 대신 자동 장비를 실행합니다.',
|
||||||
|
'Pas de BiS pour votre tier/spé/niveau, vérifiez la config, exécution d''autogear à la place.',
|
||||||
|
'Kein BiS für deinen Tier/Spec/Level, prüfe die Config, führe stattdessen autogear aus.',
|
||||||
|
'您的等级/天赋/级别没有BiS数据,请检查配置,改为运行autogear。',
|
||||||
|
'你的等級/天賦/級別沒有BiS資料,請檢查設定,改為執行autogear。',
|
||||||
|
'No hay BiS para tu tier/spec/nivel, revisa la configuración, ejecutando autogear en su lugar.',
|
||||||
|
'No hay BiS para tu tier/spec/nivel, revisa la configuración, ejecutando autogear en su lugar.',
|
||||||
|
'Нет BiS для вашего тира/спека/уровня, проверьте конфиг, запускаю autogear.'),
|
||||||
|
|
||||||
|
(1769, 'bis_command_unavailable_error',
|
||||||
|
'bis command is not allowed, please check the configuration.', 0, 0,
|
||||||
|
'bis 명령이 허용되지 않습니다. 설정을 확인하세요.',
|
||||||
|
'La commande bis n''est pas autorisée, veuillez vérifier la configuration.',
|
||||||
|
'Der bis-Befehl ist nicht erlaubt, bitte überprüfe die Konfiguration.',
|
||||||
|
'bis命令未启用,请检查配置。',
|
||||||
|
'bis指令未啟用,請檢查設定。',
|
||||||
|
'El comando bis no está permitido, por favor revisa la configuración.',
|
||||||
|
'El comando bis no está permitido, por favor revisa la configuración.',
|
||||||
|
'Команда bis не разрешена, проверьте конфигурацию.'),
|
||||||
|
|
||||||
|
(1770, 'bis_altbot_refused_error',
|
||||||
|
'You cannot use bis on alt bots.', 0, 0,
|
||||||
|
'부캐 봇에는 bis를 사용할 수 없습니다.',
|
||||||
|
'Vous ne pouvez pas utiliser bis sur des bots alternatifs.',
|
||||||
|
'Du kannst bis nicht auf Zweitbots verwenden.',
|
||||||
|
'你不能在副号机器人上使用bis。',
|
||||||
|
'你不能在副號機器人上使用bis。',
|
||||||
|
'No puedes usar bis en bots alternativos.',
|
||||||
|
'No puedes usar bis en bots alternativos.',
|
||||||
|
'Вы не можете использовать bis на дополнительных ботах.'),
|
||||||
|
|
||||||
|
(1771, 'bis_quality_floor_error',
|
||||||
|
'AutoGearQualityLimit must be 4 for BiS.', 0, 0,
|
||||||
|
'BiS를 사용하려면 AutoGearQualityLimit이 4여야 합니다.',
|
||||||
|
'AutoGearQualityLimit doit être à 4 pour utiliser BiS.',
|
||||||
|
'AutoGearQualityLimit muss für BiS auf 4 stehen.',
|
||||||
|
'BiS要求AutoGearQualityLimit设置为4。',
|
||||||
|
'BiS要求AutoGearQualityLimit設定為4。',
|
||||||
|
'AutoGearQualityLimit debe ser 4 para BiS.',
|
||||||
|
'AutoGearQualityLimit debe ser 4 para BiS.',
|
||||||
|
'AutoGearQualityLimit должен быть 4 для BiS.'),
|
||||||
|
|
||||||
|
(1772, 'bis_pvp_refused_error',
|
||||||
|
'bis is PvE only, bot is configured as PvP.', 0, 0,
|
||||||
|
'bis는 PvE 전용이며, 이 봇은 PvP로 설정되어 있습니다.',
|
||||||
|
'bis est uniquement pour le JcE, ce bot est configuré en JcJ.',
|
||||||
|
'bis ist nur für PvE, dieser Bot ist als PvP konfiguriert.',
|
||||||
|
'bis仅适用于PvE,此机器人配置为PvP。',
|
||||||
|
'bis僅適用於PvE,此機器人設定為PvP。',
|
||||||
|
'bis es solo para PvE, el bot está configurado como PvP.',
|
||||||
|
'bis es solo para PvE, el bot está configurado como PvP.',
|
||||||
|
'bis только для PvE, бот настроен на PvP.'),
|
||||||
|
|
||||||
|
(1773, 'bis_invalid_arg_error',
|
||||||
|
'Invalid BiS ilvl argument ''%param''. Use a positive integer.', 0, 0,
|
||||||
|
'잘못된 BiS 아이템 레벨 인수 ''%param''. 양의 정수를 사용하세요.',
|
||||||
|
'Argument iLvl BiS invalide ''%param''. Utilisez un entier positif.',
|
||||||
|
'Ungültiges BiS-iLvl-Argument ''%param''. Verwende eine positive ganze Zahl.',
|
||||||
|
'无效的BiS物品等级参数“%param”。请使用正整数。',
|
||||||
|
'無效的BiS物品等級參數「%param」。請使用正整數。',
|
||||||
|
'Argumento iLvl BiS inválido ''%param''. Usa un entero positivo.',
|
||||||
|
'Argumento iLvl BiS inválido ''%param''. Usa un entero positivo.',
|
||||||
|
'Неверный аргумент iLvl BiS ''%param''. Используйте положительное целое число.'),
|
||||||
|
|
||||||
|
(1774, 'bis_arg_above_limit_error',
|
||||||
|
'BiS ilvl %requested exceeds AutoGearScoreLimit %limit, refusing', 0, 0,
|
||||||
|
'BiS 아이템 레벨 %requested이(가) AutoGearScoreLimit %limit을(를) 초과합니다. 거부합니다.',
|
||||||
|
'iLvl BiS %requested dépasse AutoGearScoreLimit %limit, refusé.',
|
||||||
|
'BiS-iLvl %requested überschreitet AutoGearScoreLimit %limit, abgelehnt.',
|
||||||
|
'BiS物品等级%requested超过AutoGearScoreLimit %limit,已拒绝。',
|
||||||
|
'BiS物品等級%requested超過AutoGearScoreLimit %limit,已拒絕。',
|
||||||
|
'iLvl BiS %requested supera AutoGearScoreLimit %limit, rechazado.',
|
||||||
|
'iLvl BiS %requested supera AutoGearScoreLimit %limit, rechazado.',
|
||||||
|
'BiS iLvl %requested превышает AutoGearScoreLimit %limit, отказано.'),
|
||||||
|
|
||||||
|
(1775, 'bis_no_rows_autogear_msg',
|
||||||
|
'No BiS at ilvl %ilvl, using Autogear %ilvl instead', 0, 0,
|
||||||
|
'아이템 레벨 %ilvl의 BiS가 없어 대신 Autogear %ilvl을(를) 사용합니다.',
|
||||||
|
'Pas de BiS à l''iLvl %ilvl, utilisation d''Autogear %ilvl à la place.',
|
||||||
|
'Kein BiS auf iLvl %ilvl, verwende stattdessen Autogear %ilvl.',
|
||||||
|
'物品等级%ilvl没有BiS,改用Autogear %ilvl。',
|
||||||
|
'物品等級%ilvl沒有BiS,改用Autogear %ilvl。',
|
||||||
|
'No hay BiS en iLvl %ilvl, usando Autogear %ilvl en su lugar.',
|
||||||
|
'No hay BiS en iLvl %ilvl, usando Autogear %ilvl en su lugar.',
|
||||||
|
'Нет BiS на iLvl %ilvl, использую Autogear %ilvl.'),
|
||||||
|
|
||||||
|
(1776, 'bis_closest_match_msg',
|
||||||
|
'No BiS at ilvl %requested, using closest match at ilvl %resolved', 0, 0,
|
||||||
|
'아이템 레벨 %requested의 BiS가 없어 가장 가까운 아이템 레벨 %resolved을(를) 사용합니다.',
|
||||||
|
'Pas de BiS à l''iLvl %requested, utilisation de la correspondance la plus proche à l''iLvl %resolved.',
|
||||||
|
'Kein BiS auf iLvl %requested, verwende nächstliegende Übereinstimmung auf iLvl %resolved.',
|
||||||
|
'物品等级%requested没有BiS,使用最接近的物品等级%resolved。',
|
||||||
|
'物品等級%requested沒有BiS,使用最接近的物品等級%resolved。',
|
||||||
|
'No hay BiS en iLvl %requested, usando coincidencia más cercana en iLvl %resolved.',
|
||||||
|
'No hay BiS en iLvl %requested, usando coincidencia más cercana en iLvl %resolved.',
|
||||||
|
'Нет BiS на iLvl %requested, использую ближайшее совпадение на iLvl %resolved.'),
|
||||||
|
|
||||||
|
(1777, 'bis_applying_msg', 'Applying BiS gear', 0, 0,
|
||||||
|
'BiS 장비를 적용합니다.',
|
||||||
|
'Application de l''équipement BiS.',
|
||||||
|
'Wende BiS-Ausrüstung an.',
|
||||||
|
'正在装备BiS装备。',
|
||||||
|
'正在裝備BiS裝備。',
|
||||||
|
'Aplicando equipo BiS.',
|
||||||
|
'Aplicando equipo BiS.',
|
||||||
|
'Применяю BiS-снаряжение.'),
|
||||||
|
|
||||||
|
(1778, 'bis_applied_msg', 'BiS applied', 0, 0,
|
||||||
|
'BiS 장비가 적용되었습니다.',
|
||||||
|
'Équipement BiS appliqué.',
|
||||||
|
'BiS-Ausrüstung angewendet.',
|
||||||
|
'BiS装备已应用。',
|
||||||
|
'BiS裝備已套用。',
|
||||||
|
'Equipo BiS aplicado.',
|
||||||
|
'Equipo BiS aplicado.',
|
||||||
|
'BiS-снаряжение применено.');
|
||||||
|
|
||||||
|
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES
|
||||||
|
('bis_autogear_unavailable_error', 100),
|
||||||
|
('bis_no_rows_fallback', 100),
|
||||||
|
('bis_command_unavailable_error', 100),
|
||||||
|
('bis_altbot_refused_error', 100),
|
||||||
|
('bis_quality_floor_error', 100),
|
||||||
|
('bis_pvp_refused_error', 100),
|
||||||
|
('bis_invalid_arg_error', 100),
|
||||||
|
('bis_arg_above_limit_error', 100),
|
||||||
|
('bis_no_rows_autogear_msg', 100),
|
||||||
|
('bis_closest_match_msg', 100),
|
||||||
|
('bis_applying_msg', 100),
|
||||||
|
('bis_applied_msg', 100);
|
||||||
@ -0,0 +1,489 @@
|
|||||||
|
DELETE FROM ai_playerbot_texts WHERE name IN (
|
||||||
|
'quest_accept_debug',
|
||||||
|
'quest_already_have_error',
|
||||||
|
'quest_cant_take_error',
|
||||||
|
'arena_team_already_in_team',
|
||||||
|
'arena_team_thanks_for_invite',
|
||||||
|
'area_trigger_follow_too_far_error',
|
||||||
|
'area_trigger_wait_for_me',
|
||||||
|
'attack_no_target_error',
|
||||||
|
'attack_target_not_in_world_error',
|
||||||
|
'attack_in_flight_error',
|
||||||
|
'attack_pvp_prohibited_error',
|
||||||
|
'attack_target_friendly_error',
|
||||||
|
'attack_target_dead_error',
|
||||||
|
'attack_target_not_in_sight_error',
|
||||||
|
'attack_already_attacking_error',
|
||||||
|
'attack_invalid_target_error',
|
||||||
|
'bank_no_banker_nearby_error',
|
||||||
|
'move_from_group',
|
||||||
|
'running_away',
|
||||||
|
'clean_quest_log_started',
|
||||||
|
'quest_trivial_will_remove',
|
||||||
|
'quest_has_been_removed',
|
||||||
|
'quest_not_trivial_kept',
|
||||||
|
'quest_removed_debug',
|
||||||
|
'quest_removed_with_name',
|
||||||
|
'guild_accept_inviter_not_in_guild',
|
||||||
|
'guild_accept_already_in_guild',
|
||||||
|
'guild_accept_declined',
|
||||||
|
'outfit_usage_add',
|
||||||
|
'outfit_usage_remove',
|
||||||
|
'outfit_usage_equip',
|
||||||
|
'outfit_set_as',
|
||||||
|
'outfit_equipping',
|
||||||
|
'outfit_replace_current',
|
||||||
|
'outfit_resetting',
|
||||||
|
'outfit_updating_current',
|
||||||
|
'outfit_item_removed_from',
|
||||||
|
'outfit_item_added_to',
|
||||||
|
'release_spirit_not_dead_wait',
|
||||||
|
'release_spirit_already_spirit',
|
||||||
|
'release_spirit_releasing',
|
||||||
|
'release_spirit_meet_graveyard',
|
||||||
|
'send_mail_no_mailbox_nearby',
|
||||||
|
'send_mail_one_item_only',
|
||||||
|
'send_mail_cannot_send_money',
|
||||||
|
'send_mail_not_enough_money',
|
||||||
|
'send_mail_sending_to',
|
||||||
|
'send_mail_cannot_send_item',
|
||||||
|
'send_mail_item_not_for_sale',
|
||||||
|
'send_mail_sent_to',
|
||||||
|
'craft_reset',
|
||||||
|
'craft_usage',
|
||||||
|
'craft_cannot_craft',
|
||||||
|
'craft_summary',
|
||||||
|
'set_home_success',
|
||||||
|
'set_home_no_innkeeper_error',
|
||||||
|
'quest_shared',
|
||||||
|
'tame_invalid_id_error',
|
||||||
|
'tame_usage_error',
|
||||||
|
'tame_pet_changed',
|
||||||
|
'tame_pet_changed_initialized',
|
||||||
|
'tame_exotic_requires_beast_mastery',
|
||||||
|
'tame_no_pet_by_name',
|
||||||
|
'tame_no_pet_by_id',
|
||||||
|
'tame_no_pet_by_family',
|
||||||
|
'tame_no_pet_to_rename',
|
||||||
|
'tame_pet_name_length_error',
|
||||||
|
'tame_pet_name_alpha_error',
|
||||||
|
'tame_pet_name_forbidden_error',
|
||||||
|
'tame_pet_renamed',
|
||||||
|
'tame_pet_rename_refresh_hint',
|
||||||
|
'tame_only_hunters_level_10',
|
||||||
|
'tame_creature_template_not_found',
|
||||||
|
'tame_create_pet_failed',
|
||||||
|
'tame_pet_abandoned',
|
||||||
|
'tame_no_hunter_pet_to_abandon',
|
||||||
|
'taxi_ready_next_flight',
|
||||||
|
'taxi_cant_fly_with_you',
|
||||||
|
'taxi_no_flightmaster_nearby',
|
||||||
|
'trade_busy_now',
|
||||||
|
'trade_disabled',
|
||||||
|
'trade_thank_you_player',
|
||||||
|
'trade_selling_disabled',
|
||||||
|
'trade_buying_disabled',
|
||||||
|
'trade_item_not_for_sale',
|
||||||
|
'trade_item_not_needed',
|
||||||
|
'trade_no_items_error',
|
||||||
|
'trade_discount_buy_only',
|
||||||
|
'trade_success_pleasure',
|
||||||
|
'trade_success_fair_trade',
|
||||||
|
'trade_success_thanks',
|
||||||
|
'trade_success_off_with_you',
|
||||||
|
'trade_want_money_for_this',
|
||||||
|
'use_item_none_available',
|
||||||
|
'use_gameobject',
|
||||||
|
'socket_does_not_fit',
|
||||||
|
'use_item_on_target',
|
||||||
|
'use_item',
|
||||||
|
'socketing_item_with_gem',
|
||||||
|
'meeting_stone_in_combat',
|
||||||
|
'meeting_stone_welcome',
|
||||||
|
'meeting_stone_none_nearby',
|
||||||
|
'meeting_stone_none_near_you',
|
||||||
|
'meeting_stone_no_hearthstone_self',
|
||||||
|
'meeting_stone_no_hearthstone_you',
|
||||||
|
'meeting_stone_hearthstone_not_ready_self',
|
||||||
|
'meeting_stone_hearthstone_not_ready_you',
|
||||||
|
'meeting_stone_no_innkeepers_nearby',
|
||||||
|
'meeting_stone_no_innkeepers_near_you',
|
||||||
|
'meeting_stone_cannot_summon_vehicle',
|
||||||
|
'meeting_stone_cannot_summon_master_in_combat',
|
||||||
|
'meeting_stone_cannot_summon_master_dead',
|
||||||
|
'meeting_stone_cannot_summon_bot_dead',
|
||||||
|
'meeting_stone_revived',
|
||||||
|
'meeting_stone_not_enough_space',
|
||||||
|
'new_rpg_quest_accepted',
|
||||||
|
'new_rpg_quest_rewarded',
|
||||||
|
'new_rpg_quest_dropped',
|
||||||
|
'rpg_item_better_for_player',
|
||||||
|
'rpg_start_trade_with_player'
|
||||||
|
);
|
||||||
|
|
||||||
|
DELETE FROM ai_playerbot_texts_chance WHERE name IN (
|
||||||
|
'quest_accept_debug',
|
||||||
|
'quest_already_have_error',
|
||||||
|
'quest_cant_take_error',
|
||||||
|
'arena_team_already_in_team',
|
||||||
|
'arena_team_thanks_for_invite',
|
||||||
|
'area_trigger_follow_too_far_error',
|
||||||
|
'area_trigger_wait_for_me',
|
||||||
|
'attack_no_target_error',
|
||||||
|
'attack_target_not_in_world_error',
|
||||||
|
'attack_in_flight_error',
|
||||||
|
'attack_pvp_prohibited_error',
|
||||||
|
'attack_target_friendly_error',
|
||||||
|
'attack_target_dead_error',
|
||||||
|
'attack_target_not_in_sight_error',
|
||||||
|
'attack_already_attacking_error',
|
||||||
|
'attack_invalid_target_error',
|
||||||
|
'bank_no_banker_nearby_error',
|
||||||
|
'move_from_group',
|
||||||
|
'running_away',
|
||||||
|
'clean_quest_log_started',
|
||||||
|
'quest_trivial_will_remove',
|
||||||
|
'quest_has_been_removed',
|
||||||
|
'quest_not_trivial_kept',
|
||||||
|
'quest_removed_debug',
|
||||||
|
'quest_removed_with_name',
|
||||||
|
'guild_accept_inviter_not_in_guild',
|
||||||
|
'guild_accept_already_in_guild',
|
||||||
|
'guild_accept_declined',
|
||||||
|
'outfit_usage_add',
|
||||||
|
'outfit_usage_remove',
|
||||||
|
'outfit_usage_equip',
|
||||||
|
'outfit_set_as',
|
||||||
|
'outfit_equipping',
|
||||||
|
'outfit_replace_current',
|
||||||
|
'outfit_resetting',
|
||||||
|
'outfit_updating_current',
|
||||||
|
'outfit_item_removed_from',
|
||||||
|
'outfit_item_added_to',
|
||||||
|
'release_spirit_not_dead_wait',
|
||||||
|
'release_spirit_already_spirit',
|
||||||
|
'release_spirit_releasing',
|
||||||
|
'release_spirit_meet_graveyard',
|
||||||
|
'send_mail_no_mailbox_nearby',
|
||||||
|
'send_mail_one_item_only',
|
||||||
|
'send_mail_cannot_send_money',
|
||||||
|
'send_mail_not_enough_money',
|
||||||
|
'send_mail_sending_to',
|
||||||
|
'send_mail_cannot_send_item',
|
||||||
|
'send_mail_item_not_for_sale',
|
||||||
|
'send_mail_sent_to',
|
||||||
|
'craft_reset',
|
||||||
|
'craft_usage',
|
||||||
|
'craft_cannot_craft',
|
||||||
|
'craft_summary',
|
||||||
|
'set_home_success',
|
||||||
|
'set_home_no_innkeeper_error',
|
||||||
|
'quest_shared',
|
||||||
|
'tame_invalid_id_error',
|
||||||
|
'tame_usage_error',
|
||||||
|
'tame_pet_changed',
|
||||||
|
'tame_pet_changed_initialized',
|
||||||
|
'tame_exotic_requires_beast_mastery',
|
||||||
|
'tame_no_pet_by_name',
|
||||||
|
'tame_no_pet_by_id',
|
||||||
|
'tame_no_pet_by_family',
|
||||||
|
'tame_no_pet_to_rename',
|
||||||
|
'tame_pet_name_length_error',
|
||||||
|
'tame_pet_name_alpha_error',
|
||||||
|
'tame_pet_name_forbidden_error',
|
||||||
|
'tame_pet_renamed',
|
||||||
|
'tame_pet_rename_refresh_hint',
|
||||||
|
'tame_only_hunters_level_10',
|
||||||
|
'tame_creature_template_not_found',
|
||||||
|
'tame_create_pet_failed',
|
||||||
|
'tame_pet_abandoned',
|
||||||
|
'tame_no_hunter_pet_to_abandon',
|
||||||
|
'taxi_ready_next_flight',
|
||||||
|
'taxi_cant_fly_with_you',
|
||||||
|
'taxi_no_flightmaster_nearby',
|
||||||
|
'trade_busy_now',
|
||||||
|
'trade_disabled',
|
||||||
|
'trade_thank_you_player',
|
||||||
|
'trade_selling_disabled',
|
||||||
|
'trade_buying_disabled',
|
||||||
|
'trade_item_not_for_sale',
|
||||||
|
'trade_item_not_needed',
|
||||||
|
'trade_no_items_error',
|
||||||
|
'trade_discount_buy_only',
|
||||||
|
'trade_success_pleasure',
|
||||||
|
'trade_success_fair_trade',
|
||||||
|
'trade_success_thanks',
|
||||||
|
'trade_success_off_with_you',
|
||||||
|
'trade_want_money_for_this',
|
||||||
|
'use_item_none_available',
|
||||||
|
'use_gameobject',
|
||||||
|
'socket_does_not_fit',
|
||||||
|
'use_item_on_target',
|
||||||
|
'use_item',
|
||||||
|
'socketing_item_with_gem',
|
||||||
|
'meeting_stone_in_combat',
|
||||||
|
'meeting_stone_welcome',
|
||||||
|
'meeting_stone_none_nearby',
|
||||||
|
'meeting_stone_none_near_you',
|
||||||
|
'meeting_stone_no_hearthstone_self',
|
||||||
|
'meeting_stone_no_hearthstone_you',
|
||||||
|
'meeting_stone_hearthstone_not_ready_self',
|
||||||
|
'meeting_stone_hearthstone_not_ready_you',
|
||||||
|
'meeting_stone_no_innkeepers_nearby',
|
||||||
|
'meeting_stone_no_innkeepers_near_you',
|
||||||
|
'meeting_stone_cannot_summon_vehicle',
|
||||||
|
'meeting_stone_cannot_summon_master_in_combat',
|
||||||
|
'meeting_stone_cannot_summon_master_dead',
|
||||||
|
'meeting_stone_cannot_summon_bot_dead',
|
||||||
|
'meeting_stone_revived',
|
||||||
|
'meeting_stone_not_enough_space',
|
||||||
|
'new_rpg_quest_accepted',
|
||||||
|
'new_rpg_quest_rewarded',
|
||||||
|
'new_rpg_quest_dropped',
|
||||||
|
'rpg_item_better_for_player',
|
||||||
|
'rpg_start_trade_with_player'
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO ai_playerbot_texts (id, name, text, say_type, reply_type, text_loc1, text_loc2, text_loc3, text_loc4, text_loc5, text_loc6, text_loc7, text_loc8) VALUES
|
||||||
|
(1779, 'quest_accept_debug', 'Quest [%quest] accepted', 0, 0, '퀘스트 [%quest] 수락', 'Quête [%quest] acceptée', 'Quest [%quest] angenommen', '任务 [%quest] 已接受', '任務 [%quest] 已接受', 'Misión [%quest] aceptada', 'Misión [%quest] aceptada', 'Задание [%quest] принято'),
|
||||||
|
(1780, 'quest_already_have_error', 'I have this quest', 0, 0, '이미 이 퀘스트를 가지고 있습니다', 'J''ai déjà cette quête', 'Ich habe diese Quest bereits', '我已经有这个任务了', '我已經有這個任務了', 'Ya tengo esta misión', 'Ya tengo esta misión', 'У меня уже есть это задание'),
|
||||||
|
(1781, 'quest_cant_take_error', 'I can''t take this quest', 0, 0, '이 퀘스트를 받을 수 없습니다', 'Je ne peux pas prendre cette quête', 'Ich kann diese Quest nicht annehmen', '我无法接受这个任务', '我無法接受這個任務', 'No puedo aceptar esta misión', 'No puedo aceptar esta misión', 'Я не могу взять это задание'),
|
||||||
|
(1782, 'arena_team_already_in_team', 'Sorry, I am already in such team', 0, 0, '죄송하지만 이미 그런 팀에 속해 있습니다', 'Désolé, je suis déjà dans une telle équipe', 'Entschuldigung, ich bin bereits in so einem Team', '抱歉,我已经在这样的队伍里了', '抱歉,我已經在這樣的隊伍裡了', 'Lo siento, ya estoy en un equipo así', 'Lo siento, ya estoy en un equipo así', 'Извините, я уже состою в такой команде'),
|
||||||
|
(1783, 'arena_team_thanks_for_invite', 'Thanks for the invite!', 0, 0, '초대해 주셔서 감사합니다!', 'Merci pour l''invitation !', 'Danke für die Einladung!', '谢谢你的邀请!', '謝謝你的邀請!', 'Gracias por la invitación!', 'Gracias por la invitación!', 'Спасибо за приглашение!'),
|
||||||
|
(1784, 'area_trigger_follow_too_far_error', 'I won''t follow: too far away', 0, 0, '너무 멀어서 따라가지 않겠습니다', 'Je ne suivrai pas : c''est trop loin', 'Ich werde nicht folgen: zu weit entfernt', '我不会跟随:距离太远了', '我不會跟隨:距離太遠了', 'No te seguiré: estás demasiado lejos', 'No te seguiré: estás demasiado lejos', 'Я не пойду следом: слишком далеко'),
|
||||||
|
(1785, 'area_trigger_wait_for_me', 'Wait for me', 0, 0, '잠깐만 기다려 주세요', 'Attendez-moi', 'Warte auf mich', '等等我', '等等我', 'Espérame', 'Espérame', 'Подожди меня'),
|
||||||
|
(1786, 'attack_no_target_error', 'I have no target', 0, 0, '대상이 없습니다', 'Je n''ai pas de cible', 'Ich habe kein Ziel', '我没有目标', '我沒有目標', 'No tengo objetivo', 'No tengo objetivo', 'У меня нет цели'),
|
||||||
|
(1787, 'attack_target_not_in_world_error', '%target is no longer in the world.', 0, 0, '%target은(는) 더 이상 월드에 없습니다.', '%target n''est plus dans le monde.', '%target ist nicht mehr in der Welt.', '%target 已不在世界中。', '%target 已不在世界中。', '%target ya no está en el mundo.', '%target ya no está en el mundo.', '%target больше не находится в мире.'),
|
||||||
|
(1788, 'attack_in_flight_error', 'I cannot attack in flight', 0, 0, '비행 중에는 공격할 수 없습니다', 'Je ne peux pas attaquer en vol', 'Ich kann im Flug nicht angreifen', '飞行中无法攻击', '飛行中無法攻擊', 'No puedo atacar en vuelo', 'No puedo atacar en vuelo', 'Я не могу атаковать в полёте'),
|
||||||
|
(1789, 'attack_pvp_prohibited_error', 'I cannot attack other players in PvP prohibited areas.', 0, 0, 'PvP 금지 지역에서는 다른 플레이어를 공격할 수 없습니다.', 'Je ne peux pas attaquer d''autres joueurs dans les zones où le PvP est interdit.', 'Ich kann andere Spieler in PvP-verbotenen Gebieten nicht angreifen.', '我不能在禁止 PvP 的区域攻击其他玩家。', '我不能在禁止 PvP 的區域攻擊其他玩家。', 'No puedo atacar a otros jugadores en zonas donde el JcJ está prohibido.', 'No puedo atacar a otros jugadores en zonas donde el JcJ está prohibido.', 'Я не могу атаковать других игроков в зонах, где PvP запрещено.'),
|
||||||
|
(1790, 'attack_target_friendly_error', '%target is friendly to me.', 0, 0, '%target은(는) 우호적인 대상입니다.', '%target m''est amical.', '%target ist mir gegenüber freundlich.', '%target 对我是友方目标。', '%target 對我是友方目標。', '%target es amistoso conmigo.', '%target es amistoso conmigo.', '%target дружелюбен ко мне.'),
|
||||||
|
(1791, 'attack_target_dead_error', '%target is dead.', 0, 0, '%target은(는) 죽었습니다.', '%target est mort.', '%target ist tot.', '%target 已经死了。', '%target 已經死了。', '%target está muerto.', '%target está muerto.', '%target мёртв.'),
|
||||||
|
(1792, 'attack_target_not_in_sight_error', '%target is not in my sight.', 0, 0, '%target이(가) 시야에 없습니다.', '%target n''est pas dans mon champ de vision.', '%target ist nicht in Sichtweite.', '%target 不在我的视野中。', '%target 不在我的視野中。', '%target no está a mi vista.', '%target no está a mi vista.', '%target вне поля моего зрения.'),
|
||||||
|
(1793, 'attack_already_attacking_error', 'I am already attacking %target.', 0, 0, '이미 %target을(를) 공격하고 있습니다.', 'J''attaque déjà %target.', 'Ich greife %target bereits an.', '我已经在攻击 %target。', '我已經在攻擊 %target。', 'Ya estoy atacando a %target.', 'Ya estoy atacando a %target.', 'Я уже атакую %target.'),
|
||||||
|
(1794, 'attack_invalid_target_error', 'I cannot attack an invalid target.', 0, 0, '유효하지 않은 대상은 공격할 수 없습니다.', 'Je ne peux pas attaquer une cible invalide.', 'Ich kann kein ungültiges Ziel angreifen.', '我不能攻击无效目标。', '我不能攻擊無效目標。', 'No puedo atacar un objetivo no válido.', 'No puedo atacar un objetivo no válido.', 'Я не могу атаковать недопустимую цель.'),
|
||||||
|
(1795, 'bank_no_banker_nearby_error', 'Cannot find banker nearby', 0, 0, '근처에 은행원이 없습니다', 'Impossible de trouver un banquier à proximité', 'Kein Bankier in der Nähe gefunden', '附近找不到银行职员', '附近找不到銀行職員', 'No encuentro un banquero cerca', 'No encuentro un banquero cerca', 'Поблизости нет банкира'),
|
||||||
|
(1796, 'move_from_group', 'Moving away from group', 0, 0, '파티에서 멀어지는 중입니다', 'Je m''éloigne du groupe', 'Ich entferne mich von der Gruppe', '正在远离队伍', '正在遠離隊伍', 'Me estoy alejando del grupo', 'Me estoy alejando del grupo', 'Отхожу от группы'),
|
||||||
|
(1797, 'running_away', 'Running away', 0, 0, '도망치는 중입니다', 'Je m''enfuis', 'Ich laufe weg', '正在逃跑', '正在逃跑', 'Estoy huyendo', 'Estoy huyendo', 'Убегаю'),
|
||||||
|
(1798, 'clean_quest_log_started', 'Clean Quest Log command received, removing grey/trivial quests...', 0, 0, '퀘스트 로그 정리 명령을 받았습니다. 회색/사소한 퀘스트를 제거하는 중입니다...', 'Commande de nettoyage du journal des quêtes reçue, suppression des quêtes grises/triviales...', 'Befehl zum Bereinigen des Questlogs erhalten, graue/triviale Quests werden entfernt...', '已收到清理任务日志命令,正在移除灰色/琐碎任务...', '已收到清理任務日誌命令,正在移除灰色/瑣碎任務...', 'Comando para limpiar el registro de misiones recibido, eliminando misiones grises/triviales...', 'Comando para limpiar el registro de misiones recibido, eliminando misiones grises/triviales...', 'Получена команда очистки журнала заданий, удаляю серые/простые задания...'),
|
||||||
|
(1799, 'quest_trivial_will_remove', 'Quest [%title] will be removed because it is trivial (grey).', 0, 0, '퀘스트 [%title]은(는) 사소한(회색) 퀘스트이므로 제거됩니다.', 'La quête [%title] sera supprimée car elle est triviale (grise).', 'Quest [%title] wird entfernt, weil sie trivial (grau) ist.', '任务 [%title] 将被移除,因为它是琐碎的(灰色)任务。', '任務 [%title] 將被移除,因為它是瑣碎的(灰色)任務。', 'La misión [%title] se eliminará porque es trivial (gris).', 'La misión [%title] se eliminará porque es trivial (gris).', 'Задание [%title] будет удалено, так как оно простое (серое).'),
|
||||||
|
(1800, 'quest_has_been_removed', 'Quest [%title] has been removed.', 0, 0, '퀘스트 [%title]이(가) 제거되었습니다.', 'La quête [%title] a été supprimée.', 'Quest [%title] wurde entfernt.', '任务 [%title] 已被移除。', '任務 [%title] 已被移除。', 'La misión [%title] ha sido eliminada.', 'La misión [%title] ha sido eliminada.', 'Задание [%title] было удалено.'),
|
||||||
|
(1801, 'quest_not_trivial_kept', 'Quest [%title] is not trivial and will be kept.', 0, 0, '퀘스트 [%title]은(는) 사소하지 않으므로 유지됩니다.', 'La quête [%title] n''est pas triviale et sera conservée.', 'Quest [%title] ist nicht trivial und wird behalten.', '任务 [%title] 并不琐碎,将被保留。', '任務 [%title] 並不瑣碎,將被保留。', 'La misión [%title] no es trivial y se conservará.', 'La misión [%title] no es trivial y se conservará.', 'Задание [%title] не является простым и будет сохранено.'),
|
||||||
|
(1802, 'quest_removed_debug', 'Quest [%quest] removed', 0, 0, '퀘스트 [%quest] 제거됨', 'Quête [%quest] supprimée', 'Quest [%quest] entfernt', '任务 [%quest] 已移除', '任務 [%quest] 已移除', 'Misión [%quest] eliminada', 'Misión [%quest] eliminada', 'Задание [%quest] удалено'),
|
||||||
|
(1803, 'quest_removed_with_name', 'Quest removed %quest', 0, 0, '퀘스트 제거됨 %quest', 'Quête supprimée %quest', 'Quest entfernt %quest', '任务已移除 %quest', '任務已移除 %quest', 'Misión eliminada %quest', 'Misión eliminada %quest', 'Задание удалено %quest'),
|
||||||
|
(1804, 'guild_accept_inviter_not_in_guild', 'You are not in a guild!', 0, 0, '당신은 길드에 속해 있지 않습니다!', 'Vous n''êtes pas dans une guilde !', 'Du bist in keiner Gilde!', '你不在公会中!', '你不在公會中!', 'No estás en un gremio!', 'No estás en un gremio!', 'Вы не состоите в гильдии!'),
|
||||||
|
(1805, 'guild_accept_already_in_guild', 'Sorry, I am in a guild already', 0, 0, '죄송하지만 이미 길드에 속해 있습니다', 'Désolé, je suis déjà dans une guilde', 'Entschuldigung, ich bin bereits in einer Gilde', '抱歉,我已经在公会里了', '抱歉,我已經在公會裡了', 'Lo siento, ya estoy en un gremio', 'Lo siento, ya estoy en un gremio', 'Извините, я уже в гильдии'),
|
||||||
|
(1806, 'guild_accept_declined', 'Sorry, I don''t want to join your guild :(', 0, 0, '죄송하지만 당신의 길드에 가입하고 싶지 않습니다 :(', 'Désolé, je ne veux pas rejoindre votre guilde :(', 'Entschuldigung, ich möchte deiner Gilde nicht beitreten :(', '抱歉,我不想加入你的公会 :(', '抱歉,我不想加入你的公會 :(', 'Lo siento, no quiero unirme a tu gremio :(', 'Lo siento, no quiero unirme a tu gremio :(', 'Извините, я не хочу вступать в вашу гильдию :('),
|
||||||
|
(1807, 'outfit_usage_add', 'outfit <name> +[item] to add items', 0, 0, '아이템을 추가하려면 outfit <name> +[item]', 'outfit <name> +[item] pour ajouter des objets', 'outfit <name> +[item], um Gegenstände hinzuzufügen', '使用 outfit <name> +[item] 添加物品', '使用 outfit <name> +[item] 添加物品', 'outfit <name> +[item] para agregar objetos', 'outfit <name> +[item] para agregar objetos', 'outfit <name> +[item], чтобы добавить предметы'),
|
||||||
|
(1808, 'outfit_usage_remove', 'outfit <name> -[item] to remove items', 0, 0, '아이템을 제거하려면 outfit <name> -[item]', 'outfit <name> -[item] pour retirer des objets', 'outfit <name> -[item], um Gegenstände zu entfernen', '使用 outfit <name> -[item] 移除物品', '使用 outfit <name> -[item] 移除物品', 'outfit <name> -[item] para eliminar objetos', 'outfit <name> -[item] para eliminar objetos', 'outfit <name> -[item], чтобы убрать предметы'),
|
||||||
|
(1809, 'outfit_usage_equip', 'outfit <name> equip/replace to equip items', 0, 0, '아이템을 장착하려면 outfit <name> equip/replace', 'outfit <name> equip/replace pour équiper des objets', 'outfit <name> equip/replace, um Gegenstände anzulegen', '使用 outfit <name> equip/replace 装备物品', '使用 outfit <name> equip/replace 裝備物品', 'outfit <name> equip/replace para equipar objetos', 'outfit <name> equip/replace para equipar objetos', 'outfit <name> equip/replace, чтобы экипировать предметы'),
|
||||||
|
(1810, 'outfit_set_as', 'Setting outfit %name as %param', 0, 0, '복장 %name을(를) %param(으)로 설정하는 중입니다', 'Définition de la tenue %name comme %param', 'Setze Outfit %name als %param', '正在将装备方案 %name 设置为 %param', '正在將裝備方案 %name 設定為 %param', 'Estableciendo el atuendo %name como %param', 'Estableciendo el atuendo %name como %param', 'Устанавливаю комплект %name как %param'),
|
||||||
|
(1811, 'outfit_equipping', 'Equipping outfit %name', 0, 0, '복장 %name을(를) 장착하는 중입니다', 'Équipement de la tenue %name', 'Outfit %name wird angelegt', '正在装备方案 %name', '正在裝備方案 %name', 'Equipando el atuendo %name', 'Equipando el atuendo %name', 'Экипирую комплект %name'),
|
||||||
|
(1812, 'outfit_replace_current', 'Replacing current equip with outfit %name', 0, 0, '현재 장비를 복장 %name으로 교체하는 중입니다', 'Remplacement de l''équipement actuel par la tenue %name', 'Aktuelle Ausrüstung wird durch Outfit %name ersetzt', '正在用装备方案 %name 替换当前装备', '正在用裝備方案 %name 替換目前裝備', 'Reemplazando el equipo actual con el atuendo %name', 'Reemplazando el equipo actual con el atuendo %name', 'Заменяю текущую экипировку комплектом %name'),
|
||||||
|
(1813, 'outfit_resetting', 'Resetting outfit %name', 0, 0, '복장 %name을(를) 초기화하는 중입니다', 'Réinitialisation de la tenue %name', 'Outfit %name wird zurückgesetzt', '正在重置装备方案 %name', '正在重置裝備方案 %name', 'Restableciendo el atuendo %name', 'Restableciendo el atuendo %name', 'Сбрасываю комплект %name'),
|
||||||
|
(1814, 'outfit_updating_current', 'Updating with current items outfit %name', 0, 0, '현재 아이템으로 복장 %name을(를) 업데이트하는 중입니다', 'Mise à jour de la tenue %name avec les objets actuels', 'Outfit %name wird mit der aktuellen Ausrüstung aktualisiert', '正在用当前物品更新装备方案 %name', '正在用目前物品更新裝備方案 %name', 'Actualizando el atuendo %name con los objetos actuales', 'Actualizando el atuendo %name con los objetos actuales', 'Обновляю комплект %name текущими предметами'),
|
||||||
|
(1815, 'outfit_item_removed_from', '%item removed from %name', 0, 0, '%name에서 %item이(가) 제거되었습니다', '%item retiré de %name', '%item aus %name entfernt', '已从 %name 中移除 %item', '已從 %name 中移除 %item', '%item eliminado de %name', '%item eliminado de %name', '%item удалён из %name'),
|
||||||
|
(1816, 'outfit_item_added_to', '%item added to %name', 0, 0, '%item이(가) %name에 추가되었습니다', '%item ajouté à %name', '%item zu %name hinzugefügt', '已将 %item 添加到 %name', '已將 %item 添加到 %name', '%item agregado a %name', '%item agregado a %name', '%item добавлен в %name'),
|
||||||
|
(1817, 'release_spirit_not_dead_wait', 'I am not dead, will wait here', 0, 0, '저는 죽지 않았습니다. 여기서 기다리겠습니다', 'Je ne suis pas mort, j''attendrai ici', 'Ich bin nicht tot und werde hier warten', '我还没死,会在这里等', '我還沒死,會在這裡等', 'No estoy muerto, esperaré aquí', 'No estoy muerto, esperaré aquí', 'Я не мёртв, буду ждать здесь'),
|
||||||
|
(1818, 'release_spirit_already_spirit', 'I am already a spirit', 0, 0, '저는 이미 유령입니다', 'Je suis déjà un esprit', 'Ich bin bereits ein Geist', '我已经是灵魂状态了', '我已經是靈魂狀態了', 'Ya soy un espíritu', 'Ya soy un espíritu', 'Я уже дух'),
|
||||||
|
(1819, 'release_spirit_releasing', 'Releasing...', 0, 0, '해방 중...', 'Je libère mon esprit...', 'Geist wird freigesetzt...', '正在释放灵魂...', '正在釋放靈魂...', 'Liberando espíritu...', 'Liberando espíritu...', 'Освобождаю дух...'),
|
||||||
|
(1820, 'release_spirit_meet_graveyard', 'Meet me at the graveyard', 0, 0, '묘지에서 만나요', 'Retrouvez-moi au cimetière', 'Triff mich auf dem Friedhof', '墓地见', '墓地見', 'Encuéntrame en el cementerio', 'Encuéntrame en el cementerio', 'Встречаемся на кладбище'),
|
||||||
|
(1821, 'send_mail_no_mailbox_nearby', 'There is no mailbox nearby', 0, 0, '근처에 우체통이 없습니다', 'Il n''y a pas de boîte aux lettres à proximité', 'Kein Briefkasten in der Nähe', '附近没有邮箱', '附近沒有郵箱', 'No hay un buzón cerca', 'No hay un buzón cerca', 'Поблизости нет почтового ящика'),
|
||||||
|
(1822, 'send_mail_one_item_only', 'You can not request more than one item', 0, 0, '둘 이상의 아이템은 요청할 수 없습니다', 'Vous ne pouvez pas demander plus d''un objet', 'Du kannst nicht mehr als einen Gegenstand anfordern', '你不能请求多于一件物品', '你不能要求超過一件物品', 'No puedes solicitar más de un objeto', 'No puedes solicitar más de un objeto', 'Нельзя запрашивать больше одного предмета'),
|
||||||
|
(1823, 'send_mail_cannot_send_money', 'I cannot send money', 0, 0, '돈은 보낼 수 없습니다', 'Je ne peux pas envoyer d''argent', 'Ich kann kein Geld senden', '我不能寄送钱', '我不能寄送金錢', 'No puedo enviar dinero', 'No puedo enviar dinero', 'Я не могу отправить деньги'),
|
||||||
|
(1824, 'send_mail_not_enough_money', 'I don''t have enough money', 0, 0, '돈이 충분하지 않습니다', 'Je n''ai pas assez d''argent', 'Ich habe nicht genug Geld', '我没有足够的钱', '我沒有足夠的錢', 'No tengo suficiente dinero', 'No tengo suficiente dinero', 'У меня недостаточно денег'),
|
||||||
|
(1825, 'send_mail_sending_to', 'Sending mail to %receiver', 0, 0, '%receiver에게 우편을 보내는 중입니다', 'Envoi du courrier à %receiver', 'Sende Post an %receiver', '正在向 %receiver 发送邮件', '正在向 %receiver 發送郵件', 'Enviando correo a %receiver', 'Enviando correo a %receiver', 'Отправляю почту %receiver'),
|
||||||
|
(1826, 'send_mail_cannot_send_item', 'Cannot send %item', 0, 0, '%item을(를) 보낼 수 없습니다', 'Impossible d''envoyer %item', '%item kann nicht gesendet werden', '无法发送 %item', '無法發送 %item', 'No se puede enviar %item', 'No se puede enviar %item', 'Невозможно отправить %item'),
|
||||||
|
(1827, 'send_mail_item_not_for_sale', '%item: it is not for sale', 0, 0, '%item: 판매 대상이 아닙니다', '%item : ce n''est pas à vendre', '%item: Das steht nicht zum Verkauf', '%item:这不是出售物品', '%item:這不是出售物品', '%item: esto no está en venta', '%item: esto no está en venta', '%item: это не продаётся'),
|
||||||
|
(1828, 'send_mail_sent_to', 'Sent mail to %receiver', 0, 0, '%receiver에게 우편을 보냈습니다', 'Courrier envoyé à %receiver', 'Post an %receiver gesendet', '已向 %receiver 发送邮件', '已向 %receiver 發送郵件', 'Correo enviado a %receiver', 'Correo enviado a %receiver', 'Почта отправлена %receiver'),
|
||||||
|
(1829, 'craft_reset', 'I will not craft anything', 0, 0, '아무것도 제작하지 않겠습니다', 'Je ne fabriquerai rien', 'Ich werde nichts herstellen', '我不会制作任何东西', '我不會製作任何東西', 'No fabricaré nada', 'No fabricaré nada', 'Я ничего не буду создавать'),
|
||||||
|
(1830, 'craft_usage', 'Usage: ''craft [itemId]'' or ''craft reset''', 0, 0, '사용법: ''craft [itemId]'' 또는 ''craft reset''', 'Utilisation : ''craft [itemId]'' ou ''craft reset''', 'Verwendung: ''craft [itemId]'' oder ''craft reset''', '用法:''craft [itemId]'' 或 ''craft reset''', '用法:''craft [itemId]'' 或 ''craft reset''', 'Uso: ''craft [itemId]'' o ''craft reset''', 'Uso: ''craft [itemId]'' o ''craft reset''', 'Использование: ''craft [itemId]'' или ''craft reset'''),
|
||||||
|
(1831, 'craft_cannot_craft', 'I cannot craft this', 0, 0, '이것은 제작할 수 없습니다', 'Je ne peux pas fabriquer ceci', 'Ich kann das nicht herstellen', '我无法制作这个', '我無法製作這個', 'No puedo fabricar esto', 'No puedo fabricar esto', 'Я не могу это создать'),
|
||||||
|
(1832, 'craft_summary', 'I will craft %item using reagents: %reagents (craft fee: %money)', 0, 0, '재료 %reagents을(를) 사용해 %item을(를) 제작하겠습니다 (제작 수수료: %money)', 'Je fabriquerai %item en utilisant les composants : %reagents (frais de fabrication : %money)', 'Ich werde %item mit folgenden Reagenzien herstellen: %reagents (Herstellungsgebühr: %money)', '我将使用材料 %reagents 制作 %item(制作费用:%money)', '我將使用材料 %reagents 製作 %item(製作費用:%money)', 'Fabricaré %item usando los materiales: %reagents (tarifa de fabricación: %money)', 'Fabricaré %item usando los materiales: %reagents (tarifa de fabricación: %money)', 'Я изготовлю %item, используя реагенты: %reagents (плата за изготовление: %money)'),
|
||||||
|
(1833, 'set_home_success', 'This inn is my new home', 0, 0, '이 여관을 새로운 집으로 설정했습니다', 'Cette auberge est ma nouvelle maison', 'Dieses Gasthaus ist mein neues Zuhause', '这家旅店是我的新家', '這間旅店是我的新家', 'Esta posada es mi nuevo hogar', 'Esta posada es mi nuevo hogar', 'Этот трактир теперь мой новый дом'),
|
||||||
|
(1834, 'set_home_no_innkeeper_error', 'Can''t find any innkeeper around', 0, 0, '주변에서 여관주인을 찾을 수 없습니다', 'Impossible de trouver un aubergiste aux alentours', 'Kein Gastwirt in der Nähe gefunden', '附近找不到旅店老板', '附近找不到旅店老闆', 'No encuentro ningún posadero cerca', 'No encuentro ningún posadero cerca', 'Не удалось найти поблизости трактирщика'),
|
||||||
|
(1835, 'quest_shared', 'Quest shared', 0, 0, '퀘스트 공유됨', 'Quête partagée', 'Quest geteilt', '任务已共享', '任務已共享', 'Misión compartida', 'Misión compartida', 'Задание поделено'),
|
||||||
|
(1836, 'tame_invalid_id_error', 'Invalid tame id.', 0, 0, '잘못된 길들이기 ID입니다.', 'Identifiant de dressage invalide.', 'Ungültige Zähm-ID.', '无效的驯服 ID。', '無效的馴服 ID。', 'ID de domesticación no válido.', 'ID de domesticación no válido.', 'Недопустимый ID приручения.'),
|
||||||
|
(1837, 'tame_usage_error', 'Usage: tame name <name> | tame id <id> | tame family <family> | tame rename <new name> | tame abandon', 0, 0, '사용법: tame name <name> | tame id <id> | tame family <family> | tame rename <new name> | tame abandon', 'Utilisation : tame name <name> | tame id <id> | tame family <family> | tame rename <new name> | tame abandon', 'Verwendung: tame name <name> | tame id <id> | tame family <family> | tame rename <new name> | tame abandon', '用法:tame name <name> | tame id <id> | tame family <family> | tame rename <new name> | tame abandon', '用法:tame name <name> | tame id <id> | tame family <family> | tame rename <new name> | tame abandon', 'Uso: tame name <name> | tame id <id> | tame family <family> | tame rename <new name> | tame abandon', 'Uso: tame name <name> | tame id <id> | tame family <family> | tame rename <new name> | tame abandon', 'Использование: tame name <name> | tame id <id> | tame family <family> | tame rename <new name> | tame abandon'),
|
||||||
|
(1838, 'tame_pet_changed', 'Pet changed to %name, ID: %id.', 0, 0, '소환수가 %name, ID: %id(으)로 변경되었습니다.', 'Familier changé en %name, ID : %id.', 'Begleiter geändert zu %name, ID: %id.', '宠物已更改为 %name,ID:%id。', '寵物已更改為 %name,ID:%id。', 'Mascota cambiada a %name, ID: %id.', 'Mascota cambiada a %name, ID: %id.', 'Питомец изменён на %name, ID: %id.'),
|
||||||
|
(1839, 'tame_pet_changed_initialized', 'Pet changed and initialized!', 0, 0, '소환수가 변경되고 초기화되었습니다!', 'Familier changé et initialisé !', 'Begleiter geändert und initialisiert!', '宠物已更改并初始化!', '寵物已更改並初始化!', 'La mascota ha sido cambiada e inicializada!', 'La mascota ha sido cambiada e inicializada!', 'Питомец изменён и инициализирован!'),
|
||||||
|
(1840, 'tame_exotic_requires_beast_mastery', 'I cannot use exotic pets unless I have the Beast Mastery talent.', 0, 0, '야수 지배 특성이 없으면 특수 야수 소환수를 사용할 수 없습니다.', 'Je ne peux pas utiliser de familiers exotiques sans le talent Maîtrise des bêtes.', 'Ich kann ohne das Talent Tierherrschaft keine exotischen Begleiter benutzen.', '如果没有野兽掌握天赋,我无法使用异种宠物。', '如果沒有野獸控制天賦,我無法使用異種寵物。', 'No puedo usar mascotas exóticas a menos que tenga el talento Dominio de bestias.', 'No puedo usar mascotas exóticas a menos que tenga el talento Dominio de bestias.', 'Я не могу использовать экзотических питомцев без таланта Повелитель зверей.'),
|
||||||
|
(1841, 'tame_no_pet_by_name', 'No tameable pet found with name: %name', 0, 0, '이름이 %name인 길들일 수 있는 소환수를 찾을 수 없습니다', 'Aucun familier apprivoisable trouvé avec le nom : %name', 'Kein zähmbares Tier mit dem Namen %name gefunden', '未找到名为 %name 的可驯服宠物', '未找到名為 %name 的可馴服寵物', 'No se encontró ninguna mascota domesticable con el nombre: %name', 'No se encontró ninguna mascota domesticable con el nombre: %name', 'Не найден приручаемый питомец с именем: %name'),
|
||||||
|
(1842, 'tame_no_pet_by_id', 'No tameable pet found with id: %id', 0, 0, 'ID가 %id인 길들일 수 있는 소환수를 찾을 수 없습니다', 'Aucun familier apprivoisable trouvé avec l''id : %id', 'Kein zähmbares Tier mit der ID %id gefunden', '未找到 ID 为 %id 的可驯服宠物', '未找到 ID 為 %id 的可馴服寵物', 'No se encontró ninguna mascota domesticable con el id: %id', 'No se encontró ninguna mascota domesticable con el id: %id', 'Не найден приручаемый питомец с id: %id'),
|
||||||
|
(1843, 'tame_no_pet_by_family', 'No tameable pet found with family: %family', 0, 0, '계열이 %family인 길들일 수 있는 소환수를 찾을 수 없습니다', 'Aucun familier apprivoisable trouvé avec la famille : %family', 'Kein zähmbares Tier mit der Familie %family gefunden', '未找到家族为 %family 的可驯服宠物', '未找到家族為 %family 的可馴服寵物', 'No se encontró ninguna mascota domesticable con la familia: %family', 'No se encontró ninguna mascota domesticable con la familia: %family', 'Не найден приручаемый питомец семейства: %family'),
|
||||||
|
(1844, 'tame_no_pet_to_rename', 'You have no pet to rename.', 0, 0, '이름을 바꿀 소환수가 없습니다.', 'Vous n''avez pas de familier à renommer.', 'Du hast kein Tier zum Umbenennen.', '你没有可重命名的宠物。', '你沒有可重新命名的寵物。', 'No tienes ninguna mascota para renombrar.', 'No tienes ninguna mascota para renombrar.', 'У вас нет питомца для переименования.'),
|
||||||
|
(1845, 'tame_pet_name_length_error', 'Pet name must be between 1 and 12 alphabetic characters.', 0, 0, '소환수 이름은 1자에서 12자의 알파벳 문자여야 합니다.', 'Le nom du familier doit contenir entre 1 et 12 caractères alphabétiques.', 'Der Name des Begleiters muss zwischen 1 und 12 alphabetischen Zeichen lang sein.', '宠物名字必须为 1 到 12 个字母字符。', '寵物名字必須為 1 到 12 個字母字元。', 'El nombre de la mascota debe tener entre 1 y 12 caracteres alfabéticos.', 'El nombre de la mascota debe tener entre 1 y 12 caracteres alfabéticos.', 'Имя питомца должно содержать от 1 до 12 буквенных символов.'),
|
||||||
|
(1846, 'tame_pet_name_alpha_error', 'Pet name must only contain alphabetic characters (A-Z, a-z).', 0, 0, '소환수 이름에는 알파벳 문자(A-Z, a-z)만 포함될 수 있습니다.', 'Le nom du familier ne doit contenir que des caractères alphabétiques (A-Z, a-z).', 'Der Name des Begleiters darf nur alphabetische Zeichen (A-Z, a-z) enthalten.', '宠物名字只能包含字母字符(A-Z, a-z)。', '寵物名字只能包含字母字元(A-Z, a-z)。', 'El nombre de la mascota solo puede contener caracteres alfabéticos (A-Z, a-z).', 'El nombre de la mascota solo puede contener caracteres alfabéticos (A-Z, a-z).', 'Имя питомца должно содержать только буквенные символы (A-Z, a-z).'),
|
||||||
|
(1847, 'tame_pet_name_forbidden_error', 'That pet name is forbidden. Please choose another name.', 0, 0, '그 소환수 이름은 사용할 수 없습니다. 다른 이름을 선택해 주세요.', 'Ce nom de familier est interdit. Veuillez en choisir un autre.', 'Dieser Name für den Begleiter ist verboten. Bitte wähle einen anderen Namen.', '该宠物名称被禁止使用。请选择其他名字。', '該寵物名稱被禁止使用。請選擇其他名字。', 'Ese nombre de mascota está prohibido. Por favor, elige otro nombre.', 'Ese nombre de mascota está prohibido. Por favor, elige otro nombre.', 'Это имя для питомца запрещено. Пожалуйста, выберите другое имя.'),
|
||||||
|
(1848, 'tame_pet_renamed', 'Your pet has been renamed to %name!', 0, 0, '당신의 소환수 이름이 %name(으)로 변경되었습니다!', 'Votre familier a été renommé en %name !', 'Dein Begleiter wurde in %name umbenannt!', '你的宠物已重命名为 %name!', '你的寵物已重新命名為 %name!', 'Tu mascota ha sido renombrada a %name!', 'Tu mascota ha sido renombrada a %name!', 'Ваш питомец был переименован в %name!'),
|
||||||
|
(1849, 'tame_pet_rename_refresh_hint', 'If you do not see the new name, please dismiss and recall your pet.', 0, 0, '새 이름이 보이지 않으면 소환수를 해제했다가 다시 소환해 주세요.', 'Si vous ne voyez pas le nouveau nom, veuillez renvoyer puis rappeler votre familier.', 'Wenn du den neuen Namen nicht siehst, schicke deinen Begleiter weg und rufe ihn erneut.', '如果你看不到新名字,请先解散再召回你的宠物。', '如果你看不到新名字,請先解散再召回你的寵物。', 'Si no ves el nuevo nombre, por favor retira y vuelve a invocar a tu mascota.', 'Si no ves el nuevo nombre, por favor retira y vuelve a invocar a tu mascota.', 'Если вы не видите новое имя, отпустите и снова призовите питомца.'),
|
||||||
|
(1850, 'tame_only_hunters_level_10', 'Only level 10+ hunters can have pets.', 0, 0, '10레벨 이상의 사냥꾼만 소환수를 가질 수 있습니다.', 'Seuls les chasseurs de niveau 10+ peuvent avoir des familiers.', 'Nur Jäger ab Stufe 10 können Begleiter haben.', '只有 10 级以上的猎人才能拥有宠物。', '只有 10 級以上的獵人才能擁有寵物。', 'Solo los cazadores de nivel 10 o superior pueden tener mascotas.', 'Solo los cazadores de nivel 10 o superior pueden tener mascotas.', 'Только охотники 10 уровня и выше могут иметь питомцев.'),
|
||||||
|
(1851, 'tame_creature_template_not_found', 'Creature template not found.', 0, 0, '생물 템플릿을 찾을 수 없습니다.', 'Modèle de créature introuvable.', 'Kreaturvorlage nicht gefunden.', '未找到生物模板。', '未找到生物範本。', 'No se encontró la plantilla de criatura.', 'No se encontró la plantilla de criatura.', 'Шаблон существа не найден.'),
|
||||||
|
(1852, 'tame_create_pet_failed', 'Failed to create pet.', 0, 0, '소환수 생성에 실패했습니다.', 'Échec de la création du familier.', 'Erstellen des Begleiters fehlgeschlagen.', '创建宠物失败。', '建立寵物失敗。', 'No se pudo crear la mascota.', 'No se pudo crear la mascota.', 'Не удалось создать питомца.'),
|
||||||
|
(1853, 'tame_pet_abandoned', 'Your pet has been abandoned.', 0, 0, '당신의 소환수를 버렸습니다.', 'Votre familier a été abandonné.', 'Dein Begleiter wurde freigelassen.', '你的宠物已被放弃。', '你的寵物已被放棄。', 'Tu mascota ha sido abandonada.', 'Tu mascota ha sido abandonada.', 'Ваш питомец был брошен.'),
|
||||||
|
(1854, 'tame_no_hunter_pet_to_abandon', 'You have no hunter pet to abandon.', 0, 0, '버릴 사냥꾼 소환수가 없습니다.', 'Vous n''avez pas de familier de chasseur à abandonner.', 'Du hast kein Jägertier zum Freilassen.', '你没有可放弃的猎人宠物。', '你沒有可放棄的獵人寵物。', 'No tienes ninguna mascota de cazador que abandonar.', 'No tienes ninguna mascota de cazador que abandonar.', 'У вас нет питомца охотника, которого можно бросить.'),
|
||||||
|
(1855, 'taxi_ready_next_flight', 'I am ready for the next flight', 0, 0, '다음 비행을 탈 준비가 되었습니다', 'Je suis prêt pour le prochain vol', 'Ich bin bereit für den nächsten Flug', '我已准备好进行下一次飞行', '我已準備好進行下一次飛行', 'Estoy listo para el siguiente vuelo', 'Estoy listo para el siguiente vuelo', 'Я готов к следующему перелёту'),
|
||||||
|
(1856, 'taxi_cant_fly_with_you', 'I can''t fly with you', 0, 0, '당신과 함께 날 수 없습니다', 'Je ne peux pas voler avec vous', 'Ich kann nicht mit dir fliegen', '我不能和你一起飞', '我不能和你一起飛', 'No puedo volar contigo', 'No puedo volar contigo', 'Я не могу лететь с вами'),
|
||||||
|
(1857, 'taxi_no_flightmaster_nearby', 'Cannot find any flightmaster to talk', 0, 0, '대화할 비행 조련사를 찾을 수 없습니다', 'Impossible de trouver un maître de vol à qui parler', 'Keinen Flugmeister zum Ansprechen gefunden', '找不到可以交谈的飞行管理员', '找不到可以交談的飛行管理員', 'No encuentro ningún maestro de vuelo con quien hablar', 'No encuentro ningún maestro de vuelo con quien hablar', 'Не могу найти распорядителя полётов, с которым можно поговорить'),
|
||||||
|
(1858, 'trade_busy_now', 'I''m kind of busy now', 0, 0, '지금은 좀 바쁩니다', 'Je suis un peu occupé en ce moment', 'Ich bin gerade etwas beschäftigt', '我现在有点忙', '我現在有點忙', 'Ahora estoy un poco ocupado', 'Ahora estoy un poco ocupado', 'Сейчас я немного занят'),
|
||||||
|
(1859, 'trade_disabled', 'Trading is disabled', 0, 0, '거래가 비활성화되어 있습니다', 'L''échange est désactivé', 'Handel ist deaktiviert', '交易已禁用', '交易已停用', 'El comercio está deshabilitado', 'El comercio está deshabilitado', 'Торговля отключена'),
|
||||||
|
(1860, 'trade_thank_you_player', 'Thank you %player', 0, 0, '고마워요 %player', 'Merci %player', 'Danke %player', '谢谢你 %player', '謝謝你 %player', 'Gracias %player', 'Gracias %player', 'Спасибо, %player'),
|
||||||
|
(1861, 'trade_selling_disabled', 'Selling is disabled.', 0, 0, '판매가 비활성화되어 있습니다.', 'La vente est désactivée.', 'Verkaufen ist deaktiviert.', '出售已禁用。', '出售已停用。', 'La venta está deshabilitada.', 'La venta está deshabilitada.', 'Продажа отключена.'),
|
||||||
|
(1862, 'trade_buying_disabled', 'Buying is disabled.', 0, 0, '구매가 비활성화되어 있습니다.', 'L''achat est désactivé.', 'Kaufen ist deaktiviert.', '购买已禁用。', '購買已停用。', 'La compra está deshabilitada.', 'La compra está deshabilitada.', 'Покупка отключена.'),
|
||||||
|
(1863, 'trade_item_not_for_sale', '%item - This is not for sale', 0, 0, '%item - 이것은 판매용이 아닙니다', '%item - Ceci n''est pas à vendre', '%item - Das steht nicht zum Verkauf', '%item - 这不是出售物品', '%item - 這不是出售物品', '%item - Esto no está en venta', '%item - Esto no está en venta', '%item - это не продаётся'),
|
||||||
|
(1864, 'trade_item_not_needed', '%item - I don''t need this', 0, 0, '%item - 저는 이것이 필요 없습니다', '%item - Je n''en ai pas besoin', '%item - Das brauche ich nicht', '%item - 我不需要这个', '%item - 我不需要這個', '%item - No necesito esto', '%item - No necesito esto', '%item - мне это не нужно'),
|
||||||
|
(1865, 'trade_no_items_error', 'There are no items to trade', 0, 0, '거래할 아이템이 없습니다', 'Il n''y a aucun objet à échanger', 'Es gibt keine Gegenstände zum Handeln', '没有可交易的物品', '沒有可交易的物品', 'No hay objetos para comerciar', 'No hay objetos para comerciar', 'Нет предметов для обмена'),
|
||||||
|
(1866, 'trade_discount_buy_only', 'You can use discount to buy items only', 0, 0, '할인은 아이템 구매에만 사용할 수 있습니다', 'Vous ne pouvez utiliser la réduction que pour acheter des objets', 'Rabatte können nur zum Kauf von Gegenständen verwendet werden', '折扣只能用于购买物品', '折扣只能用於購買物品', 'Solo puedes usar el descuento para comprar objetos', 'Solo puedes usar el descuento para comprar objetos', 'Скидку можно использовать только для покупки предметов'),
|
||||||
|
(1867, 'trade_success_pleasure', 'A pleasure doing business with you', 0, 0, '당신과 거래해서 즐거웠습니다', 'Un plaisir de faire affaire avec vous', 'Es war mir ein Vergnügen, mit dir Geschäfte zu machen', '很高兴和你做生意', '很高興和你做生意', 'Un placer hacer negocios contigo', 'Un placer hacer negocios contigo', 'Приятно было иметь с вами дело'),
|
||||||
|
(1868, 'trade_success_fair_trade', 'Fair trade', 0, 0, '공정한 거래입니다', 'Échange équitable', 'Fairer Handel', '公平交易', '公平交易', 'Intercambio justo', 'Intercambio justo', 'Честная сделка'),
|
||||||
|
(1869, 'trade_success_thanks', 'Thanks', 0, 0, '감사합니다', 'Merci', 'Danke', '谢谢', '謝謝', 'Gracias', 'Gracias', 'Спасибо'),
|
||||||
|
(1870, 'trade_success_off_with_you', 'Off with you', 0, 0, '이제 가보세요', 'Allez-vous-en maintenant', 'Nun geh deiner Wege', '走吧你', '走吧你', 'Ahora vete', 'Ahora vete', 'Иди уже'),
|
||||||
|
(1871, 'trade_want_money_for_this', 'I want %money for this', 0, 0, '이것에 대해 %money을(를) 원합니다', 'Je veux %money pour ceci', 'Ich möchte %money dafür', '这个我想要 %money', '這個我想要 %money', 'Quiero %money por esto', 'Quiero %money por esto', 'Я хочу %money за это'),
|
||||||
|
(1872, 'use_item_none_available', 'No items (or game objects) available', 0, 0, '사용할 수 있는 아이템(또는 게임 오브젝트)이 없습니다', 'Aucun objet (ou objet interactif) disponible', 'Keine Gegenstände (oder Spielobjekte) verfügbar', '没有可用的物品(或游戏物体)', '沒有可用的物品(或遊戲物件)', 'No hay objetos (ni objetos del juego) disponibles', 'No hay objetos (ni objetos del juego) disponibles', 'Нет доступных предметов (или игровых объектов)'),
|
||||||
|
(1873, 'use_gameobject', 'Using %gameobject', 0, 0, '%gameobject 사용 중', 'Utilisation de %gameobject', 'Benutze %gameobject', '正在使用 %gameobject', '正在使用 %gameobject', 'Usando %gameobject', 'Usando %gameobject', 'Использую %gameobject'),
|
||||||
|
(1874, 'socket_does_not_fit', 'Socket does not fit', 0, 0, '소켓이 맞지 않습니다', 'La châsse ne correspond pas', 'Sockel passt nicht', '插槽不匹配', '插槽不匹配', 'La ranura no encaja', 'La ranura no encaja', 'Гнездо не подходит'),
|
||||||
|
(1875, 'use_item_on_target', 'Using %item on %target', 0, 0, '%target에게 %item을(를) 사용하는 중', 'Utilisation de %item sur %target', 'Benutze %item auf %target', '正在对 %target 使用 %item', '正在對 %target 使用 %item', 'Usando %item en %target', 'Usando %item en %target', 'Использую %item на %target'),
|
||||||
|
(1876, 'use_item', 'Using %item', 0, 0, '%item 사용 중', 'Utilisation de %item', 'Benutze %item', '正在使用 %item', '正在使用 %item', 'Usando %item', 'Usando %item', 'Использую %item'),
|
||||||
|
(1877, 'socketing_item_with_gem', 'Socketing %item with %gem', 0, 0, '%item에 %gem 보석을 장착하는 중', 'Sertissage de %item avec %gem', 'Sockele %item mit %gem', '正在将 %gem 镶嵌到 %item 上', '正在將 %gem 鑲嵌到 %item 上', 'Engarzando %item con %gem', 'Engarzando %item con %gem', 'Вставляю %gem в %item'),
|
||||||
|
(1878, 'meeting_stone_in_combat', 'I am in combat', 0, 0, '전투 중입니다', 'Je suis en combat', 'Ich bin im Kampf', '我在战斗中', '我在戰鬥中', 'Estoy en combate', 'Estoy en combate', 'Я в бою'),
|
||||||
|
(1879, 'meeting_stone_welcome', 'Welcome!', 0, 0, '환영합니다!', 'Bienvenue !', 'Willkommen!', '欢迎!', '歡迎!', 'Bienvenido!', 'Bienvenido!', 'Добро пожаловать!'),
|
||||||
|
(1880, 'meeting_stone_none_nearby', 'There is no meeting stone nearby', 0, 0, '근처에 집결의 돌이 없습니다', 'Il n''y a pas de pierre de rencontre à proximité', 'Es gibt keinen Beschwörungsstein in der Nähe', '附近没有集合石', '附近沒有集合石', 'No hay ninguna piedra de encuentro cerca', 'No hay ninguna piedra de encuentro cerca', 'Поблизости нет камня встреч'),
|
||||||
|
(1881, 'meeting_stone_none_near_you', 'There is no meeting stone near you', 0, 0, '당신 근처에 집결의 돌이 없습니다', 'Il n''y a pas de pierre de rencontre près de vous', 'Es gibt keinen Beschwörungsstein in deiner Nähe', '你附近没有集合石', '你附近沒有集合石', 'No hay ninguna piedra de encuentro cerca de ti', 'No hay ninguna piedra de encuentro cerca de ti', 'Рядом с вами нет камня встреч'),
|
||||||
|
(1882, 'meeting_stone_no_hearthstone_self', 'I have no hearthstone', 0, 0, '귀환석이 없습니다', 'Je n''ai pas de pierre de foyer', 'Ich habe keinen Ruhestein', '我没有炉石', '我沒有爐石', 'No tengo piedra de hogar', 'No tengo piedra de hogar', 'У меня нет камня возвращения'),
|
||||||
|
(1883, 'meeting_stone_no_hearthstone_you', 'You have no hearthstone', 0, 0, '당신에게 귀환석이 없습니다', 'Vous n''avez pas de pierre de foyer', 'Du hast keinen Ruhestein', '你没有炉石', '你沒有爐石', 'No tienes piedra de hogar', 'No tienes piedra de hogar', 'У вас нет камня возвращения'),
|
||||||
|
(1884, 'meeting_stone_hearthstone_not_ready_self', 'My hearthstone is not ready', 0, 0, '제 귀환석은 아직 준비되지 않았습니다', 'Ma pierre de foyer n''est pas prête', 'Mein Ruhestein ist nicht bereit', '我的炉石还没准备好', '我的爐石還沒準備好', 'Mi piedra de hogar no está lista', 'Mi piedra de hogar no está lista', 'Мой камень возвращения ещё не готов'),
|
||||||
|
(1885, 'meeting_stone_hearthstone_not_ready_you', 'Your hearthstone is not ready', 0, 0, '당신의 귀환석은 아직 준비되지 않았습니다', 'Votre pierre de foyer n''est pas prête', 'Dein Ruhestein ist nicht bereit', '你的炉石还没准备好', '你的爐石還沒準備好', 'Tu piedra de hogar no está lista', 'Tu piedra de hogar no está lista', 'Ваш камень возвращения ещё не готов'),
|
||||||
|
(1886, 'meeting_stone_no_innkeepers_nearby', 'There are no innkeepers nearby', 0, 0, '근처에 여관주인이 없습니다', 'Il n''y a pas d''aubergistes à proximité', 'Es gibt keine Gastwirte in der Nähe', '附近没有旅店老板', '附近沒有旅店老闆', 'No hay posaderos cerca', 'No hay posaderos cerca', 'Поблизости нет трактирщиков'),
|
||||||
|
(1887, 'meeting_stone_no_innkeepers_near_you', 'There are no innkeepers near you', 0, 0, '당신 근처에 여관주인이 없습니다', 'Il n''y a pas d''aubergistes près de vous', 'Es gibt keine Gastwirte in deiner Nähe', '你附近没有旅店老板', '你附近沒有旅店老闆', 'No hay posaderos cerca de ti', 'No hay posaderos cerca de ti', 'Рядом с вами нет трактирщиков'),
|
||||||
|
(1888, 'meeting_stone_cannot_summon_vehicle', 'You cannot summon me while I''m on a vehicle', 0, 0, '제가 탈것/차량에 타고 있는 동안에는 소환할 수 없습니다', 'Vous ne pouvez pas m''invoquer tant que je suis sur un véhicule', 'Du kannst mich nicht beschwören, solange ich auf einem Fahrzeug bin', '当我在载具上时,你不能召唤我', '當我在載具上時,你不能召喚我', 'No puedes invocarme mientras esté en un vehículo', 'No puedes invocarme mientras esté en un vehículo', 'Вы не можете призвать меня, пока я на транспорте'),
|
||||||
|
(1889, 'meeting_stone_cannot_summon_master_in_combat', 'You cannot summon me while you''re in combat', 0, 0, '당신이 전투 중일 때는 저를 소환할 수 없습니다', 'Vous ne pouvez pas m''invoquer pendant que vous êtes en combat', 'Du kannst mich nicht beschwören, solange du im Kampf bist', '当你在战斗中时,不能召唤我', '當你在戰鬥中時,不能召喚我', 'No puedes invocarme mientras estés en combate', 'No puedes invocarme mientras estés en combate', 'Вы не можете призвать меня, пока вы в бою'),
|
||||||
|
(1890, 'meeting_stone_cannot_summon_master_dead', 'You cannot summon me while you''re dead', 0, 0, '당신이 죽어 있는 동안에는 저를 소환할 수 없습니다', 'Vous ne pouvez pas m''invoquer pendant que vous êtes mort', 'Du kannst mich nicht beschwören, solange du tot bist', '当你死亡时,不能召唤我', '當你死亡時,不能召喚我', 'No puedes invocarme mientras estés muerto', 'No puedes invocarme mientras estés muerto', 'Вы не можете призвать меня, пока вы мертвы'),
|
||||||
|
(1891, 'meeting_stone_cannot_summon_bot_dead', 'You cannot summon me while I''m dead, you need to release my spirit first', 0, 0, '제가 죽어 있는 동안에는 저를 소환할 수 없습니다. 먼저 제 영혼을 해방해야 합니다', 'Vous ne pouvez pas m''invoquer tant que je suis mort, vous devez d''abord libérer mon esprit', 'Du kannst mich nicht beschwören, solange ich tot bin; du musst zuerst meinen Geist freisetzen', '当我死亡时,你不能召唤我,你需要先释放我的灵魂', '當我死亡時,你不能召喚我,你需要先釋放我的靈魂', 'No puedes invocarme mientras esté muerto, primero debes liberar mi espíritu', 'No puedes invocarme mientras esté muerto, primero debes liberar mi espíritu', 'Вы не можете призвать меня, пока я мёртв, сначала нужно освободить мой дух'),
|
||||||
|
(1892, 'meeting_stone_revived', 'I live, again!', 0, 0, '다시 살아났습니다!', 'Je vis à nouveau !', 'Ich lebe wieder!', '我又活了!', '我又活了!', 'Vivo de nuevo!', 'Vivo de nuevo!', 'Я снова жив!'),
|
||||||
|
(1893, 'meeting_stone_not_enough_space', 'Not enough place to summon', 0, 0, '소환할 공간이 부족합니다', 'Pas assez de place pour invoquer', 'Nicht genug Platz zum Beschwören', '没有足够的空间进行召唤', '沒有足夠的空間進行召喚', 'No hay suficiente espacio para invocar', 'No hay suficiente espacio para invocar', 'Недостаточно места для призыва'),
|
||||||
|
(1894, 'new_rpg_quest_accepted', 'Quest accepted %quest', 0, 0, '퀘스트 수락 %quest', 'Quête acceptée %quest', 'Quest angenommen %quest', '任务已接受 %quest', '任務已接受 %quest', 'Misión aceptada %quest', 'Misión aceptada %quest', 'Задание принято %quest'),
|
||||||
|
(1895, 'new_rpg_quest_rewarded', 'Quest rewarded %quest', 0, 0, '퀘스트 보상 받음 %quest', 'Quête récompensée %quest', 'Quest abgeschlossen %quest', '任务已奖励 %quest', '任務已獎勵 %quest', 'Misión completada %quest', 'Misión completada %quest', 'Награда за задание получена %quest'),
|
||||||
|
(1896, 'new_rpg_quest_dropped', 'Quest dropped %quest', 0, 0, '퀘스트 포기 %quest', 'Quête abandonnée %quest', 'Quest abgebrochen %quest', '任务已放弃 %quest', '任務已放棄 %quest', 'Misión abandonada %quest', 'Misión abandonada %quest', 'Задание отменено %quest'),
|
||||||
|
(1897, 'rpg_item_better_for_player', 'You can use this %item better than me, %player.', 0, 0, '%player님, 이 %item은(는) 저보다 당신에게 더 잘 맞습니다.', 'Vous pouvez mieux utiliser cet objet %item que moi, %player.', 'Du kannst diesen %item besser gebrauchen als ich, %player.', '%player,这个 %item 比我更适合你使用。', '%player,這個 %item 比我更適合你使用。', 'Tú puedes usar este %item mejor que yo, %player.', 'Tú puedes usar este %item mejor que yo, %player.', '%player, ты можешь использовать %item лучше, чем я.'),
|
||||||
|
(1898, 'rpg_start_trade_with_player', 'Start trade with %player', 0, 0, '%player와(과) 거래를 시작합니다', 'Début de l''échange avec %player', 'Beginne Handel mit %player', '开始与 %player 交易', '開始與 %player 交易', 'Iniciando intercambio con %player', 'Iniciando intercambio con %player', 'Начинаю обмен с %player');
|
||||||
|
|
||||||
|
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES
|
||||||
|
('quest_accept_debug', 100),
|
||||||
|
('quest_already_have_error', 100),
|
||||||
|
('quest_cant_take_error', 100),
|
||||||
|
('arena_team_already_in_team', 100),
|
||||||
|
('arena_team_thanks_for_invite', 100),
|
||||||
|
('area_trigger_follow_too_far_error', 100),
|
||||||
|
('area_trigger_wait_for_me', 100),
|
||||||
|
('attack_no_target_error', 100),
|
||||||
|
('attack_target_not_in_world_error', 100),
|
||||||
|
('attack_in_flight_error', 100),
|
||||||
|
('attack_pvp_prohibited_error', 100),
|
||||||
|
('attack_target_friendly_error', 100),
|
||||||
|
('attack_target_dead_error', 100),
|
||||||
|
('attack_target_not_in_sight_error', 100),
|
||||||
|
('attack_already_attacking_error', 100),
|
||||||
|
('attack_invalid_target_error', 100),
|
||||||
|
('bank_no_banker_nearby_error', 100),
|
||||||
|
('move_from_group', 100),
|
||||||
|
('running_away', 100),
|
||||||
|
('clean_quest_log_started', 100),
|
||||||
|
('quest_trivial_will_remove', 100),
|
||||||
|
('quest_has_been_removed', 100),
|
||||||
|
('quest_not_trivial_kept', 100),
|
||||||
|
('quest_removed_debug', 100),
|
||||||
|
('quest_removed_with_name', 100),
|
||||||
|
('guild_accept_inviter_not_in_guild', 100),
|
||||||
|
('guild_accept_already_in_guild', 100),
|
||||||
|
('guild_accept_declined', 100),
|
||||||
|
('outfit_usage_add', 100),
|
||||||
|
('outfit_usage_remove', 100),
|
||||||
|
('outfit_usage_equip', 100),
|
||||||
|
('outfit_set_as', 100),
|
||||||
|
('outfit_equipping', 100),
|
||||||
|
('outfit_replace_current', 100),
|
||||||
|
('outfit_resetting', 100),
|
||||||
|
('outfit_updating_current', 100),
|
||||||
|
('outfit_item_removed_from', 100),
|
||||||
|
('outfit_item_added_to', 100),
|
||||||
|
('release_spirit_not_dead_wait', 100),
|
||||||
|
('release_spirit_already_spirit', 100),
|
||||||
|
('release_spirit_releasing', 100),
|
||||||
|
('release_spirit_meet_graveyard', 100),
|
||||||
|
('send_mail_no_mailbox_nearby', 100),
|
||||||
|
('send_mail_one_item_only', 100),
|
||||||
|
('send_mail_cannot_send_money', 100),
|
||||||
|
('send_mail_not_enough_money', 100),
|
||||||
|
('send_mail_sending_to', 100),
|
||||||
|
('send_mail_cannot_send_item', 100),
|
||||||
|
('send_mail_item_not_for_sale', 100),
|
||||||
|
('send_mail_sent_to', 100),
|
||||||
|
('craft_reset', 100),
|
||||||
|
('craft_usage', 100),
|
||||||
|
('craft_cannot_craft', 100),
|
||||||
|
('craft_summary', 100),
|
||||||
|
('set_home_success', 100),
|
||||||
|
('set_home_no_innkeeper_error', 100),
|
||||||
|
('quest_shared', 100),
|
||||||
|
('tame_invalid_id_error', 100),
|
||||||
|
('tame_usage_error', 100),
|
||||||
|
('tame_pet_changed', 100),
|
||||||
|
('tame_pet_changed_initialized', 100),
|
||||||
|
('tame_exotic_requires_beast_mastery', 100),
|
||||||
|
('tame_no_pet_by_name', 100),
|
||||||
|
('tame_no_pet_by_id', 100),
|
||||||
|
('tame_no_pet_by_family', 100),
|
||||||
|
('tame_no_pet_to_rename', 100),
|
||||||
|
('tame_pet_name_length_error', 100),
|
||||||
|
('tame_pet_name_alpha_error', 100),
|
||||||
|
('tame_pet_name_forbidden_error', 100),
|
||||||
|
('tame_pet_renamed', 100),
|
||||||
|
('tame_pet_rename_refresh_hint', 100),
|
||||||
|
('tame_only_hunters_level_10', 100),
|
||||||
|
('tame_creature_template_not_found', 100),
|
||||||
|
('tame_create_pet_failed', 100),
|
||||||
|
('tame_pet_abandoned', 100),
|
||||||
|
('tame_no_hunter_pet_to_abandon', 100),
|
||||||
|
('taxi_ready_next_flight', 100),
|
||||||
|
('taxi_cant_fly_with_you', 100),
|
||||||
|
('taxi_no_flightmaster_nearby', 100),
|
||||||
|
('trade_busy_now', 100),
|
||||||
|
('trade_disabled', 100),
|
||||||
|
('trade_thank_you_player', 100),
|
||||||
|
('trade_selling_disabled', 100),
|
||||||
|
('trade_buying_disabled', 100),
|
||||||
|
('trade_item_not_for_sale', 100),
|
||||||
|
('trade_item_not_needed', 100),
|
||||||
|
('trade_no_items_error', 100),
|
||||||
|
('trade_discount_buy_only', 100),
|
||||||
|
('trade_success_pleasure', 100),
|
||||||
|
('trade_success_fair_trade', 100),
|
||||||
|
('trade_success_thanks', 100),
|
||||||
|
('trade_success_off_with_you', 100),
|
||||||
|
('trade_want_money_for_this', 100),
|
||||||
|
('use_item_none_available', 100),
|
||||||
|
('use_gameobject', 100),
|
||||||
|
('socket_does_not_fit', 100),
|
||||||
|
('use_item_on_target', 100),
|
||||||
|
('use_item', 100),
|
||||||
|
('socketing_item_with_gem', 100),
|
||||||
|
('meeting_stone_in_combat', 100),
|
||||||
|
('meeting_stone_welcome', 100),
|
||||||
|
('meeting_stone_none_nearby', 100),
|
||||||
|
('meeting_stone_none_near_you', 100),
|
||||||
|
('meeting_stone_no_hearthstone_self', 100),
|
||||||
|
('meeting_stone_no_hearthstone_you', 100),
|
||||||
|
('meeting_stone_hearthstone_not_ready_self', 100),
|
||||||
|
('meeting_stone_hearthstone_not_ready_you', 100),
|
||||||
|
('meeting_stone_no_innkeepers_nearby', 100),
|
||||||
|
('meeting_stone_no_innkeepers_near_you', 100),
|
||||||
|
('meeting_stone_cannot_summon_vehicle', 100),
|
||||||
|
('meeting_stone_cannot_summon_master_in_combat', 100),
|
||||||
|
('meeting_stone_cannot_summon_master_dead', 100),
|
||||||
|
('meeting_stone_cannot_summon_bot_dead', 100),
|
||||||
|
('meeting_stone_revived', 100),
|
||||||
|
('meeting_stone_not_enough_space', 100),
|
||||||
|
('new_rpg_quest_accepted', 100),
|
||||||
|
('new_rpg_quest_rewarded', 100),
|
||||||
|
('new_rpg_quest_dropped', 100),
|
||||||
|
('rpg_item_better_for_player', 100),
|
||||||
|
('rpg_start_trade_with_player', 100);
|
||||||
@ -9,6 +9,7 @@
|
|||||||
#include "ObjectAccessor.h"
|
#include "ObjectAccessor.h"
|
||||||
#include "PlayerbotAIConfig.h"
|
#include "PlayerbotAIConfig.h"
|
||||||
#include "PlayerbotSecurity.h"
|
#include "PlayerbotSecurity.h"
|
||||||
|
#include "PlayerbotTextMgr.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
#include "WorldPacket.h"
|
#include "WorldPacket.h"
|
||||||
|
|
||||||
@ -55,7 +56,7 @@ bool AcceptInvitationAction::Execute(Event event)
|
|||||||
botAI->ChangeStrategy("+follow,-lfg,-bg", BOT_STATE_NON_COMBAT);
|
botAI->ChangeStrategy("+follow,-lfg,-bg", BOT_STATE_NON_COMBAT);
|
||||||
botAI->Reset();
|
botAI->Reset();
|
||||||
|
|
||||||
botAI->TellMaster("Hello");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault("hello", "Hello", {}));
|
||||||
|
|
||||||
if (sPlayerbotAIConfig.summonWhenGroup && bot->GetDistance(inviter) > sPlayerbotAIConfig.sightDistance)
|
if (sPlayerbotAIConfig.summonWhenGroup && bot->GetDistance(inviter) > sPlayerbotAIConfig.sightDistance)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
#include "AcceptQuestAction.h"
|
#include "AcceptQuestAction.h"
|
||||||
|
|
||||||
#include "Event.h"
|
#include "Event.h"
|
||||||
|
#include "PlayerbotTextMgr.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
|
|
||||||
bool AcceptAllQuestsAction::ProcessQuest(Quest const* quest, Object* questGiver)
|
bool AcceptAllQuestsAction::ProcessQuest(Quest const* quest, Object* questGiver)
|
||||||
@ -18,7 +19,11 @@ bool AcceptAllQuestsAction::ProcessQuest(Quest const* quest, Object* questGiver)
|
|||||||
if (botAI->HasStrategy("debug quest", BotState::BOT_STATE_NON_COMBAT) || botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT))
|
if (botAI->HasStrategy("debug quest", BotState::BOT_STATE_NON_COMBAT) || botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT))
|
||||||
{
|
{
|
||||||
LOG_INFO("playerbots", "{} => Quest [{}] accepted", bot->GetName(), quest->GetTitle());
|
LOG_INFO("playerbots", "{} => Quest [{}] accepted", bot->GetName(), quest->GetTitle());
|
||||||
bot->Say("Quest [" + text_quest + "] accepted", LANG_UNIVERSAL);
|
std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"quest_accept_debug",
|
||||||
|
"Quest [%quest] accepted",
|
||||||
|
{{"%quest", text_quest}});
|
||||||
|
bot->Say(text, LANG_UNIVERSAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -113,7 +118,8 @@ bool AcceptQuestShareAction::Execute(Event event)
|
|||||||
if (bot->HasQuest(quest))
|
if (bot->HasQuest(quest))
|
||||||
{
|
{
|
||||||
bot->SetDivider(ObjectGuid::Empty);
|
bot->SetDivider(ObjectGuid::Empty);
|
||||||
botAI->TellError("I have this quest");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"quest_already_have_error", "I have this quest", {}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,7 +127,8 @@ bool AcceptQuestShareAction::Execute(Event event)
|
|||||||
{
|
{
|
||||||
// can't take quest
|
// can't take quest
|
||||||
bot->SetDivider(ObjectGuid::Empty);
|
bot->SetDivider(ObjectGuid::Empty);
|
||||||
botAI->TellError("I can't take this quest");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"quest_cant_take_error", "I can't take this quest", {}));
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -149,7 +156,8 @@ bool AcceptQuestShareAction::Execute(Event event)
|
|||||||
bot->CastSpell(bot, qInfo->GetSrcSpell(), true);
|
bot->CastSpell(bot, qInfo->GetSrcSpell(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
botAI->TellMaster("Quest accepted");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"quest_accept", "Quest accepted", {}));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include "Event.h"
|
#include "Event.h"
|
||||||
#include "LastMovementValue.h"
|
#include "LastMovementValue.h"
|
||||||
|
#include "PlayerbotTextMgr.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
#include "Transport.h"
|
#include "Transport.h"
|
||||||
|
|
||||||
@ -36,7 +37,8 @@ bool ReachAreaTriggerAction::Execute(Event event)
|
|||||||
|
|
||||||
if (bot->GetMapId() != at->map)
|
if (bot->GetMapId() != at->map)
|
||||||
{
|
{
|
||||||
botAI->TellError("I won't follow: too far away");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"area_trigger_follow_too_far_error", "I won't follow: too far away", {}));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,7 +53,8 @@ bool ReachAreaTriggerAction::Execute(Event event)
|
|||||||
|
|
||||||
float distance = bot->GetDistance(at->x, at->y, at->z);
|
float distance = bot->GetDistance(at->x, at->y, at->z);
|
||||||
float delay = 1000.0f * distance / bot->GetSpeed(MOVE_RUN) + sPlayerbotAIConfig.reactDelay;
|
float delay = 1000.0f * distance / bot->GetSpeed(MOVE_RUN) + sPlayerbotAIConfig.reactDelay;
|
||||||
botAI->TellError("Wait for me");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"area_trigger_wait_for_me", "Wait for me", {}));
|
||||||
botAI->SetNextCheckDelay(delay);
|
botAI->SetNextCheckDelay(delay);
|
||||||
context->GetValue<LastMovement&>("last area trigger")->Get().lastAreaTrigger = triggerId;
|
context->GetValue<LastMovement&>("last area trigger")->Get().lastAreaTrigger = triggerId;
|
||||||
|
|
||||||
@ -76,6 +79,6 @@ bool AreaTriggerAction::Execute(Event /*event*/)
|
|||||||
p.rpos(0);
|
p.rpos(0);
|
||||||
bot->GetSession()->HandleAreaTriggerOpcode(p);
|
bot->GetSession()->HandleAreaTriggerOpcode(p);
|
||||||
|
|
||||||
botAI->TellMaster("Hello");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault("hello", "Hello", {}));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
#include "ArenaTeamActions.h"
|
#include "ArenaTeamActions.h"
|
||||||
|
|
||||||
#include "ArenaTeamMgr.h"
|
#include "ArenaTeamMgr.h"
|
||||||
|
#include "PlayerbotTextMgr.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
|
|
||||||
bool ArenaTeamAcceptAction::Execute(Event event)
|
bool ArenaTeamAcceptAction::Execute(Event event)
|
||||||
@ -31,7 +32,9 @@ bool ArenaTeamAcceptAction::Execute(Event event)
|
|||||||
if (bot->GetArenaTeamId(at->GetSlot()))
|
if (bot->GetArenaTeamId(at->GetSlot()))
|
||||||
{
|
{
|
||||||
// bot is already in an arena team
|
// bot is already in an arena team
|
||||||
bot->Say("Sorry, I am already in such team", LANG_UNIVERSAL);
|
std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"arena_team_already_in_team", "Sorry, I am already in such team", {});
|
||||||
|
bot->Say(text, LANG_UNIVERSAL);
|
||||||
accept = false;
|
accept = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,7 +42,9 @@ bool ArenaTeamAcceptAction::Execute(Event event)
|
|||||||
{
|
{
|
||||||
WorldPacket data(CMSG_ARENA_TEAM_ACCEPT);
|
WorldPacket data(CMSG_ARENA_TEAM_ACCEPT);
|
||||||
bot->GetSession()->HandleArenaTeamAcceptOpcode(data);
|
bot->GetSession()->HandleArenaTeamAcceptOpcode(data);
|
||||||
bot->Say("Thanks for the invite!", LANG_UNIVERSAL);
|
std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"arena_team_thanks_for_invite", "Thanks for the invite!", {});
|
||||||
|
bot->Say(text, LANG_UNIVERSAL);
|
||||||
LOG_INFO("playerbots", "Bot {} <{}> accepts Arena Team invite", bot->GetGUID().ToString().c_str(),
|
LOG_INFO("playerbots", "Bot {} <{}> accepts Arena Team invite", bot->GetGUID().ToString().c_str(),
|
||||||
bot->GetName().c_str());
|
bot->GetName().c_str());
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@ -10,6 +10,7 @@
|
|||||||
#include "LastMovementValue.h"
|
#include "LastMovementValue.h"
|
||||||
#include "LootObjectStack.h"
|
#include "LootObjectStack.h"
|
||||||
#include "PlayerbotAI.h"
|
#include "PlayerbotAI.h"
|
||||||
|
#include "PlayerbotTextMgr.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
#include "ServerFacade.h"
|
#include "ServerFacade.h"
|
||||||
#include "SharedDefines.h"
|
#include "SharedDefines.h"
|
||||||
@ -38,7 +39,8 @@ bool AttackMyTargetAction::Execute(Event /*event*/)
|
|||||||
if (!guid)
|
if (!guid)
|
||||||
{
|
{
|
||||||
if (verbose)
|
if (verbose)
|
||||||
botAI->TellError("You have no target");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"pull_no_target_error", "You have no target", {}));
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -56,7 +58,8 @@ bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/)
|
|||||||
if (!target)
|
if (!target)
|
||||||
{
|
{
|
||||||
if (verbose)
|
if (verbose)
|
||||||
botAI->TellError("I have no target");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"attack_no_target_error", "I have no target", {}));
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -64,7 +67,10 @@ bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/)
|
|||||||
if (!target->IsInWorld())
|
if (!target->IsInWorld())
|
||||||
{
|
{
|
||||||
if (verbose)
|
if (verbose)
|
||||||
botAI->TellError(std::string(target->GetName()) + " is no longer in the world.");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"attack_target_not_in_world_error",
|
||||||
|
"%target is no longer in the world.",
|
||||||
|
{{"%target", target->GetName()}}));
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -73,7 +79,8 @@ bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/)
|
|||||||
bot->HasUnitState(UNIT_STATE_IN_FLIGHT))
|
bot->HasUnitState(UNIT_STATE_IN_FLIGHT))
|
||||||
{
|
{
|
||||||
if (verbose)
|
if (verbose)
|
||||||
botAI->TellError("I cannot attack in flight");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"attack_in_flight_error", "I cannot attack in flight", {}));
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -85,7 +92,10 @@ bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/)
|
|||||||
sPlayerbotAIConfig.IsPvpProhibited(target->GetZoneId(), target->GetAreaId())))
|
sPlayerbotAIConfig.IsPvpProhibited(target->GetZoneId(), target->GetAreaId())))
|
||||||
{
|
{
|
||||||
if (verbose)
|
if (verbose)
|
||||||
botAI->TellError("I cannot attack other players in PvP prohibited areas.");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"attack_pvp_prohibited_error",
|
||||||
|
"I cannot attack other players in PvP prohibited areas.",
|
||||||
|
{}));
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -93,7 +103,10 @@ bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/)
|
|||||||
if (bot->IsFriendlyTo(target))
|
if (bot->IsFriendlyTo(target))
|
||||||
{
|
{
|
||||||
if (verbose)
|
if (verbose)
|
||||||
botAI->TellError(std::string(target->GetName()) + " is friendly to me.");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"attack_target_friendly_error",
|
||||||
|
"%target is friendly to me.",
|
||||||
|
{{"%target", target->GetName()}}));
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -101,7 +114,10 @@ bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/)
|
|||||||
if (target->isDead())
|
if (target->isDead())
|
||||||
{
|
{
|
||||||
if (verbose)
|
if (verbose)
|
||||||
botAI->TellError(std::string(target->GetName()) + " is dead.");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"attack_target_dead_error",
|
||||||
|
"%target is dead.",
|
||||||
|
{{"%target", target->GetName()}}));
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -109,7 +125,10 @@ bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/)
|
|||||||
if (!bot->IsWithinLOSInMap(target))
|
if (!bot->IsWithinLOSInMap(target))
|
||||||
{
|
{
|
||||||
if (verbose)
|
if (verbose)
|
||||||
botAI->TellError(std::string(target->GetName()) + " is not in my sight.");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"attack_target_not_in_sight_error",
|
||||||
|
"%target is not in my sight.",
|
||||||
|
{{"%target", target->GetName()}}));
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -129,7 +148,10 @@ bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/)
|
|||||||
if (sameTarget && inCombat && sameAttackMode)
|
if (sameTarget && inCombat && sameAttackMode)
|
||||||
{
|
{
|
||||||
if (verbose)
|
if (verbose)
|
||||||
botAI->TellError("I am already attacking " + std::string(target->GetName()) + ".");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"attack_already_attacking_error",
|
||||||
|
"I am already attacking %target.",
|
||||||
|
{{"%target", target->GetName()}}));
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -137,7 +159,8 @@ bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/)
|
|||||||
if (!bot->IsValidAttackTarget(target))
|
if (!bot->IsValidAttackTarget(target))
|
||||||
{
|
{
|
||||||
if (verbose)
|
if (verbose)
|
||||||
botAI->TellError("I cannot attack an invalid target.");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"attack_invalid_target_error", "I cannot attack an invalid target.", {}));
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include "Event.h"
|
#include "Event.h"
|
||||||
#include "ItemCountValue.h"
|
#include "ItemCountValue.h"
|
||||||
|
#include "PlayerbotTextMgr.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
|
|
||||||
bool BankAction::Execute(Event event)
|
bool BankAction::Execute(Event event)
|
||||||
@ -23,7 +24,8 @@ bool BankAction::Execute(Event event)
|
|||||||
return ExecuteBank(text, npc);
|
return ExecuteBank(text, npc);
|
||||||
}
|
}
|
||||||
|
|
||||||
botAI->TellError("Cannot find banker nearby");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"bank_no_banker_nearby_error", "Cannot find banker nearby", {}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1299,7 +1299,7 @@ std::string const BGTactics::HandleConsoleCommandPrivate(WorldSession* session,
|
|||||||
Player* player = session->GetPlayer();
|
Player* player = session->GetPlayer();
|
||||||
if (!player)
|
if (!player)
|
||||||
return "Error - session player not found";
|
return "Error - session player not found";
|
||||||
if (player->GetSession()->GetSecurity() < SEC_GAMEMASTER)
|
if (!player->CanBeGameMaster())
|
||||||
return "Command can only be used by a GM";
|
return "Command can only be used by a GM";
|
||||||
Battleground* bg = player->GetBattleground();
|
Battleground* bg = player->GetBattleground();
|
||||||
if (!bg)
|
if (!bg)
|
||||||
|
|||||||
@ -49,7 +49,7 @@ bool ChangeNonCombatStrategyAction::Execute(Event event)
|
|||||||
|
|
||||||
uint32 account = bot->GetSession()->GetAccountId();
|
uint32 account = bot->GetSession()->GetAccountId();
|
||||||
if (sPlayerbotAIConfig.IsInRandomAccountList(account) && botAI->GetMaster() &&
|
if (sPlayerbotAIConfig.IsInRandomAccountList(account) && botAI->GetMaster() &&
|
||||||
botAI->GetMaster()->GetSession()->GetSecurity() < SEC_GAMEMASTER)
|
!botAI->GetMaster()->CanBeGameMaster())
|
||||||
{
|
{
|
||||||
if (text.find("loot") != std::string::npos || text.find("gather") != std::string::npos)
|
if (text.find("loot") != std::string::npos || text.find("gather") != std::string::npos)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include "Event.h"
|
#include "Event.h"
|
||||||
#include "Formations.h"
|
#include "Formations.h"
|
||||||
|
#include "PlayerbotTextMgr.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
#include "PositionValue.h"
|
#include "PositionValue.h"
|
||||||
|
|
||||||
@ -85,7 +86,8 @@ bool FollowChatShortcutAction::Execute(Event /*event*/)
|
|||||||
|
|
||||||
if (moved)
|
if (moved)
|
||||||
{
|
{
|
||||||
botAI->TellMaster("Following");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"following", "Following", {}));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -108,7 +110,8 @@ bool FollowChatShortcutAction::Execute(Event /*event*/)
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
botAI->TellMaster("Following");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"following", "Following", {}));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,7 +128,8 @@ bool StayChatShortcutAction::Execute(Event /*event*/)
|
|||||||
SetReturnPosition(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ());
|
SetReturnPosition(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ());
|
||||||
SetStayPosition(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ());
|
SetStayPosition(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ());
|
||||||
|
|
||||||
botAI->TellMaster("Staying");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"staying", "Staying", {}));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,7 +144,8 @@ bool MoveFromGroupChatShortcutAction::Execute(Event /*event*/)
|
|||||||
botAI->ChangeStrategy("+move from group", BOT_STATE_NON_COMBAT);
|
botAI->ChangeStrategy("+move from group", BOT_STATE_NON_COMBAT);
|
||||||
botAI->ChangeStrategy("+move from group", BOT_STATE_COMBAT);
|
botAI->ChangeStrategy("+move from group", BOT_STATE_COMBAT);
|
||||||
|
|
||||||
botAI->TellMaster("Moving away from group");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"move_from_group", "Moving away from group", {}));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,11 +164,13 @@ bool FleeChatShortcutAction::Execute(Event /*event*/)
|
|||||||
|
|
||||||
if (bot->GetMapId() != master->GetMapId() || bot->GetDistance(master) > sPlayerbotAIConfig.sightDistance)
|
if (bot->GetMapId() != master->GetMapId() || bot->GetDistance(master) > sPlayerbotAIConfig.sightDistance)
|
||||||
{
|
{
|
||||||
botAI->TellError("I will not flee with you - too far away");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"fleeing_far", "I will not flee with you - too far away", {}));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
botAI->TellMaster("Fleeing");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"fleeing", "Fleeing", {}));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,7 +187,8 @@ bool GoawayChatShortcutAction::Execute(Event /*event*/)
|
|||||||
ResetReturnPosition();
|
ResetReturnPosition();
|
||||||
ResetStayPosition();
|
ResetStayPosition();
|
||||||
|
|
||||||
botAI->TellMaster("Running away");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"running_away", "Running away", {}));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,7 +204,8 @@ bool GrindChatShortcutAction::Execute(Event /*event*/)
|
|||||||
ResetReturnPosition();
|
ResetReturnPosition();
|
||||||
ResetStayPosition();
|
ResetStayPosition();
|
||||||
|
|
||||||
botAI->TellMaster("Grinding");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"grinding", "Grinding", {}));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -216,7 +225,8 @@ bool TankAttackChatShortcutAction::Execute(Event /*event*/)
|
|||||||
ResetReturnPosition();
|
ResetReturnPosition();
|
||||||
ResetStayPosition();
|
ResetStayPosition();
|
||||||
|
|
||||||
botAI->TellMaster("Attacking");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"attacking", "Attacking", {}));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include "ChatHelper.h"
|
#include "ChatHelper.h"
|
||||||
#include "Event.h"
|
#include "Event.h"
|
||||||
|
#include "PlayerbotTextMgr.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
|
|
||||||
bool DropQuestAction::Execute(Event event)
|
bool DropQuestAction::Execute(Event event)
|
||||||
@ -51,10 +52,15 @@ bool DropQuestAction::Execute(Event event)
|
|||||||
const Quest* pQuest = sObjectMgr->GetQuestTemplate(entry);
|
const Quest* pQuest = sObjectMgr->GetQuestTemplate(entry);
|
||||||
const std::string text_quest = ChatHelper::FormatQuest(pQuest);
|
const std::string text_quest = ChatHelper::FormatQuest(pQuest);
|
||||||
LOG_INFO("playerbots", "{} => Quest [ {} ] removed", bot->GetName(), pQuest->GetTitle());
|
LOG_INFO("playerbots", "{} => Quest [ {} ] removed", bot->GetName(), pQuest->GetTitle());
|
||||||
bot->Say("Quest [ " + text_quest + " ] removed", LANG_UNIVERSAL);
|
std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"quest_removed_debug",
|
||||||
|
"Quest [%quest] removed",
|
||||||
|
{{"%quest", text_quest}});
|
||||||
|
bot->Say(text, LANG_UNIVERSAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
botAI->TellMaster("Quest removed");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"quest_remove", "Quest removed", {}));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,7 +75,10 @@ bool CleanQuestLogAction::Execute(Event event)
|
|||||||
|
|
||||||
// Only output this message if "debug rpg" strategy is enabled
|
// Only output this message if "debug rpg" strategy is enabled
|
||||||
if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT))
|
if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT))
|
||||||
botAI->TellMaster("Clean Quest Log command received, removing grey/trivial quests...");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"clean_quest_log_started",
|
||||||
|
"Clean Quest Log command received, removing grey/trivial quests...",
|
||||||
|
{}));
|
||||||
|
|
||||||
uint8 botLevel = bot->GetLevel(); // Get bot's level
|
uint8 botLevel = bot->GetLevel(); // Get bot's level
|
||||||
|
|
||||||
@ -103,7 +112,10 @@ bool CleanQuestLogAction::Execute(Event event)
|
|||||||
{
|
{
|
||||||
// Output only if "debug rpg" strategy is enabled
|
// Output only if "debug rpg" strategy is enabled
|
||||||
if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT))
|
if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT))
|
||||||
botAI->TellMaster("Quest [ " + quest->GetTitle() + " ] will be removed because it is trivial (grey).");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"quest_trivial_will_remove",
|
||||||
|
"Quest [%title] will be removed because it is trivial (grey).",
|
||||||
|
{{"%title", quest->GetTitle()}}));
|
||||||
|
|
||||||
// Remove quest
|
// Remove quest
|
||||||
botAI->rpgStatistic.questDropped++;
|
botAI->rpgStatistic.questDropped++;
|
||||||
@ -116,17 +128,27 @@ bool CleanQuestLogAction::Execute(Event event)
|
|||||||
{
|
{
|
||||||
const std::string text_quest = ChatHelper::FormatQuest(quest);
|
const std::string text_quest = ChatHelper::FormatQuest(quest);
|
||||||
LOG_INFO("playerbots", "{} => Quest [ {} ] removed", bot->GetName(), quest->GetTitle());
|
LOG_INFO("playerbots", "{} => Quest [ {} ] removed", bot->GetName(), quest->GetTitle());
|
||||||
bot->Say("Quest [ " + text_quest + " ] removed", LANG_UNIVERSAL);
|
std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"quest_removed_debug",
|
||||||
|
"Quest [%quest] removed",
|
||||||
|
{{"%quest", text_quest}});
|
||||||
|
bot->Say(text, LANG_UNIVERSAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT))
|
if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT))
|
||||||
botAI->TellMaster("Quest [ " + quest->GetTitle() + " ] has been removed.");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"quest_has_been_removed",
|
||||||
|
"Quest [%title] has been removed.",
|
||||||
|
{{"%title", quest->GetTitle()}}));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Only output if "debug rpg" strategy is enabled
|
// Only output if "debug rpg" strategy is enabled
|
||||||
if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT))
|
if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT))
|
||||||
botAI->TellMaster("Quest [ " + quest->GetTitle() + " ] is not trivial and will be kept.");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"quest_not_trivial_kept",
|
||||||
|
"Quest [%title] is not trivial and will be kept.",
|
||||||
|
{{"%title", quest->GetTitle()}}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -204,9 +226,16 @@ void CleanQuestLogAction::DropQuestType(uint8& numQuest, uint8 wantNum, bool isG
|
|||||||
{
|
{
|
||||||
const std::string text_quest = ChatHelper::FormatQuest(quest);
|
const std::string text_quest = ChatHelper::FormatQuest(quest);
|
||||||
LOG_INFO("playerbots", "{} => Quest [ {} ] removed", bot->GetName(), quest->GetTitle());
|
LOG_INFO("playerbots", "{} => Quest [ {} ] removed", bot->GetName(), quest->GetTitle());
|
||||||
bot->Say("Quest [ " + text_quest + " ] removed", LANG_UNIVERSAL);
|
std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"quest_removed_debug",
|
||||||
|
"Quest [%quest] removed",
|
||||||
|
{{"%quest", text_quest}});
|
||||||
|
bot->Say(text, LANG_UNIVERSAL);
|
||||||
}
|
}
|
||||||
botAI->TellMaster("Quest removed" + chat->FormatQuest(quest));
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"quest_removed_with_name",
|
||||||
|
"Quest removed %quest",
|
||||||
|
{{"%quest", chat->FormatQuest(quest)}}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
#include "Player.h"
|
#include "Player.h"
|
||||||
#include "Pet.h"
|
#include "Pet.h"
|
||||||
#include "PlayerbotAIConfig.h"
|
#include "PlayerbotAIConfig.h"
|
||||||
|
#include "PlayerbotTextMgr.h"
|
||||||
#include "CreatureAI.h"
|
#include "CreatureAI.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
#include "CharmInfo.h"
|
#include "CharmInfo.h"
|
||||||
@ -178,7 +179,8 @@ bool SetPetStanceAction::Execute(Event /*event*/)
|
|||||||
// If there are no controlled pets or guardians, notify the player and exit
|
// If there are no controlled pets or guardians, notify the player and exit
|
||||||
if (targets.empty())
|
if (targets.empty())
|
||||||
{
|
{
|
||||||
botAI->TellError("You have no pet or guardian pet.");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"pet_no_pet_error", "You have no pet or guardian pet.", {}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
#include "Event.h"
|
#include "Event.h"
|
||||||
#include "GuildPackets.h"
|
#include "GuildPackets.h"
|
||||||
#include "PlayerbotSecurity.h"
|
#include "PlayerbotSecurity.h"
|
||||||
|
#include "PlayerbotTextMgr.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
|
|
||||||
bool GuildAcceptAction::Execute(Event event)
|
bool GuildAcceptAction::Execute(Event event)
|
||||||
@ -28,17 +29,20 @@ bool GuildAcceptAction::Execute(Event event)
|
|||||||
uint32 guildId = inviter->GetGuildId();
|
uint32 guildId = inviter->GetGuildId();
|
||||||
if (!guildId)
|
if (!guildId)
|
||||||
{
|
{
|
||||||
botAI->TellError("You are not in a guild!");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"guild_accept_inviter_not_in_guild", "You are not in a guild!", {}));
|
||||||
accept = false;
|
accept = false;
|
||||||
}
|
}
|
||||||
else if (bot->GetGuildId())
|
else if (bot->GetGuildId())
|
||||||
{
|
{
|
||||||
botAI->TellError("Sorry, I am in a guild already");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"guild_accept_already_in_guild", "Sorry, I am in a guild already", {}));
|
||||||
accept = false;
|
accept = false;
|
||||||
}
|
}
|
||||||
else if (!botAI->GetSecurity()->CheckLevelFor(PLAYERBOT_SECURITY_INVITE, false, inviter, true))
|
else if (!botAI->GetSecurity()->CheckLevelFor(PLAYERBOT_SECURITY_INVITE, false, inviter, true))
|
||||||
{
|
{
|
||||||
botAI->TellError("Sorry, I don't want to join your guild :(");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"guild_accept_declined", "Sorry, I don't want to join your guild :(", {}));
|
||||||
accept = false;
|
accept = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include "Event.h"
|
#include "Event.h"
|
||||||
#include "PlayerbotAIConfig.h"
|
#include "PlayerbotAIConfig.h"
|
||||||
|
#include "PlayerbotTextMgr.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
|
|
||||||
bool LeaveGroupAction::Execute(Event event)
|
bool LeaveGroupAction::Execute(Event event)
|
||||||
@ -86,7 +87,9 @@ bool LeaveGroupAction::Leave()
|
|||||||
|
|
||||||
Player* master = botAI -> GetMaster();
|
Player* master = botAI -> GetMaster();
|
||||||
if (master)
|
if (master)
|
||||||
botAI->TellMaster("Goodbye!", PLAYERBOT_SECURITY_TALK);
|
botAI->TellMaster(
|
||||||
|
PlayerbotTextMgr::instance().GetBotTextOrDefault("goodbye", "Goodbye!", {}),
|
||||||
|
PLAYERBOT_SECURITY_TALK);
|
||||||
|
|
||||||
botAI->LeaveOrDisbandGroup();
|
botAI->LeaveOrDisbandGroup();
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include "Event.h"
|
#include "Event.h"
|
||||||
#include "ItemVisitors.h"
|
#include "ItemVisitors.h"
|
||||||
|
#include "PlayerbotTextMgr.h"
|
||||||
#include "PlayerbotRepository.h"
|
#include "PlayerbotRepository.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
#include "ItemPackets.h"
|
#include "ItemPackets.h"
|
||||||
@ -18,9 +19,12 @@ bool OutfitAction::Execute(Event event)
|
|||||||
if (param == "?")
|
if (param == "?")
|
||||||
{
|
{
|
||||||
List();
|
List();
|
||||||
botAI->TellMaster("outfit <name> +[item] to add items");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
botAI->TellMaster("outfit <name> -[item] to remove items");
|
"outfit_usage_add", "outfit <name> +[item] to add items", {}));
|
||||||
botAI->TellMaster("outfit <name> equip/replace to equip items");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"outfit_usage_remove", "outfit <name> -[item] to remove items", {}));
|
||||||
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"outfit_usage_equip", "outfit <name> equip/replace to equip items", {}));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -32,8 +36,10 @@ bool OutfitAction::Execute(Event event)
|
|||||||
PlayerbotRepository::instance().Save(botAI);
|
PlayerbotRepository::instance().Save(botAI);
|
||||||
|
|
||||||
std::ostringstream out;
|
std::ostringstream out;
|
||||||
out << "Setting outfit " << name << " as " << param;
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
botAI->TellMaster(out);
|
"outfit_set_as",
|
||||||
|
"Setting outfit %name as %param",
|
||||||
|
{{"%name", name}, {"%param", param}}));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,18 +55,20 @@ bool OutfitAction::Execute(Event event)
|
|||||||
std::string const command = param.substr(space + 1);
|
std::string const command = param.substr(space + 1);
|
||||||
if (command == "equip")
|
if (command == "equip")
|
||||||
{
|
{
|
||||||
std::ostringstream out;
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
out << "Equipping outfit " << name;
|
"outfit_equipping",
|
||||||
botAI->TellMaster(out);
|
"Equipping outfit %name",
|
||||||
|
{{"%name", name}}));
|
||||||
|
|
||||||
EquipItems(outfit);
|
EquipItems(outfit);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else if (command == "replace")
|
else if (command == "replace")
|
||||||
{
|
{
|
||||||
std::ostringstream out;
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
out << "Replacing current equip with outfit " << name;
|
"outfit_replace_current",
|
||||||
botAI->TellMaster(out);
|
"Replacing current equip with outfit %name",
|
||||||
|
{{"%name", name}}));
|
||||||
|
|
||||||
for (uint8 slot = EQUIPMENT_SLOT_START; slot < EQUIPMENT_SLOT_END; slot++)
|
for (uint8 slot = EQUIPMENT_SLOT_START; slot < EQUIPMENT_SLOT_END; slot++)
|
||||||
{
|
{
|
||||||
@ -83,9 +91,10 @@ bool OutfitAction::Execute(Event event)
|
|||||||
}
|
}
|
||||||
else if (command == "reset")
|
else if (command == "reset")
|
||||||
{
|
{
|
||||||
std::ostringstream out;
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
out << "Resetting outfit " << name;
|
"outfit_resetting",
|
||||||
botAI->TellMaster(out);
|
"Resetting outfit %name",
|
||||||
|
{{"%name", name}}));
|
||||||
|
|
||||||
Save(name, ItemIds());
|
Save(name, ItemIds());
|
||||||
PlayerbotRepository::instance().Save(botAI);
|
PlayerbotRepository::instance().Save(botAI);
|
||||||
@ -93,9 +102,10 @@ bool OutfitAction::Execute(Event event)
|
|||||||
}
|
}
|
||||||
else if (command == "update")
|
else if (command == "update")
|
||||||
{
|
{
|
||||||
std::ostringstream out;
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
out << "Updating with current items outfit " << name;
|
"outfit_updating_current",
|
||||||
botAI->TellMaster(out);
|
"Updating with current items outfit %name",
|
||||||
|
{{"%name", name}}));
|
||||||
|
|
||||||
Update(name);
|
Update(name);
|
||||||
PlayerbotRepository::instance().Save(botAI);
|
PlayerbotRepository::instance().Save(botAI);
|
||||||
@ -107,24 +117,25 @@ bool OutfitAction::Execute(Event event)
|
|||||||
{
|
{
|
||||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemid);
|
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemid);
|
||||||
|
|
||||||
std::ostringstream out;
|
|
||||||
out << chat->FormatItem(proto);
|
|
||||||
if (remove)
|
if (remove)
|
||||||
{
|
{
|
||||||
std::set<uint32>::iterator j = outfit.find(itemid);
|
std::set<uint32>::iterator j = outfit.find(itemid);
|
||||||
if (j != outfit.end())
|
if (j != outfit.end())
|
||||||
outfit.erase(j);
|
outfit.erase(j);
|
||||||
|
|
||||||
out << " removed from ";
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"outfit_item_removed_from",
|
||||||
|
"%item removed from %name",
|
||||||
|
{{"%item", chat->FormatItem(proto)}, {"%name", name}}));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
outfit.insert(itemid);
|
outfit.insert(itemid);
|
||||||
out << " added to ";
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"outfit_item_added_to",
|
||||||
|
"%item added to %name",
|
||||||
|
{{"%item", chat->FormatItem(proto)}, {"%name", name}}));
|
||||||
}
|
}
|
||||||
|
|
||||||
out << name;
|
|
||||||
botAI->TellMaster(out.str());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Save(name, outfit);
|
Save(name, outfit);
|
||||||
|
|||||||
@ -10,6 +10,7 @@
|
|||||||
#include "NearestNpcsValue.h"
|
#include "NearestNpcsValue.h"
|
||||||
#include "ObjectDefines.h"
|
#include "ObjectDefines.h"
|
||||||
#include "ObjectGuid.h"
|
#include "ObjectGuid.h"
|
||||||
|
#include "PlayerbotTextMgr.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
#include "ServerFacade.h"
|
#include "ServerFacade.h"
|
||||||
#include "Corpse.h"
|
#include "Corpse.h"
|
||||||
@ -22,7 +23,8 @@ bool ReleaseSpiritAction::Execute(Event event)
|
|||||||
{
|
{
|
||||||
if (!bot->InBattleground())
|
if (!bot->InBattleground())
|
||||||
{
|
{
|
||||||
botAI->TellMasterNoFacing("I am not dead, will wait here");
|
botAI->TellMasterNoFacing(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"release_spirit_not_dead_wait", "I am not dead, will wait here", {}));
|
||||||
// -follow in bg is overwriten each tick with +follow
|
// -follow in bg is overwriten each tick with +follow
|
||||||
// +stay in bg causes stuttering effect as bot is cycled between +stay and +follow each tick
|
// +stay in bg causes stuttering effect as bot is cycled between +stay and +follow each tick
|
||||||
botAI->ChangeStrategy("-follow,+stay", BOT_STATE_NON_COMBAT);
|
botAI->ChangeStrategy("-follow,+stay", BOT_STATE_NON_COMBAT);
|
||||||
@ -33,14 +35,15 @@ bool ReleaseSpiritAction::Execute(Event event)
|
|||||||
|
|
||||||
if (bot->GetCorpse() && bot->HasPlayerFlag(PLAYER_FLAGS_GHOST))
|
if (bot->GetCorpse() && bot->HasPlayerFlag(PLAYER_FLAGS_GHOST))
|
||||||
{
|
{
|
||||||
botAI->TellMasterNoFacing("I am already a spirit");
|
botAI->TellMasterNoFacing(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"release_spirit_already_spirit", "I am already a spirit", {}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const WorldPacket& packet = event.getPacket();
|
const WorldPacket& packet = event.getPacket();
|
||||||
const std::string message = !packet.empty() && packet.GetOpcode() == CMSG_REPOP_REQUEST
|
const std::string message = !packet.empty() && packet.GetOpcode() == CMSG_REPOP_REQUEST
|
||||||
? "Releasing..."
|
? PlayerbotTextMgr::instance().GetBotTextOrDefault("release_spirit_releasing", "Releasing...", {})
|
||||||
: "Meet me at the graveyard";
|
: PlayerbotTextMgr::instance().GetBotTextOrDefault("release_spirit_meet_graveyard", "Meet me at the graveyard", {});
|
||||||
botAI->TellMasterNoFacing(message);
|
botAI->TellMasterNoFacing(message);
|
||||||
|
|
||||||
IncrementDeathCount();
|
IncrementDeathCount();
|
||||||
|
|||||||
@ -9,6 +9,7 @@
|
|||||||
#include "FleeManager.h"
|
#include "FleeManager.h"
|
||||||
#include "GameGraveyard.h"
|
#include "GameGraveyard.h"
|
||||||
#include "MapMgr.h"
|
#include "MapMgr.h"
|
||||||
|
#include "PlayerbotTextMgr.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
#include "RandomPlayerbotMgr.h"
|
#include "RandomPlayerbotMgr.h"
|
||||||
#include "ServerFacade.h"
|
#include "ServerFacade.h"
|
||||||
@ -322,7 +323,7 @@ bool SpiritHealerAction::Execute(Event /*event*/)
|
|||||||
bot->SpawnCorpseBones();
|
bot->SpawnCorpseBones();
|
||||||
context->GetValue<Unit*>("current target")->Set(nullptr);
|
context->GetValue<Unit*>("current target")->Set(nullptr);
|
||||||
bot->SetTarget();
|
bot->SetTarget();
|
||||||
botAI->TellMaster("Hello");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault("hello", "Hello", {}));
|
||||||
|
|
||||||
if (dCount > 20)
|
if (dCount > 20)
|
||||||
context->GetValue<uint32>("death count")->Set(0);
|
context->GetValue<uint32>("death count")->Set(0);
|
||||||
|
|||||||
@ -12,7 +12,7 @@ bool SecurityCheckAction::isUseful()
|
|||||||
{
|
{
|
||||||
return RandomPlayerbotMgr::instance().IsRandomBot(bot)
|
return RandomPlayerbotMgr::instance().IsRandomBot(bot)
|
||||||
&& botAI->GetMaster()
|
&& botAI->GetMaster()
|
||||||
&& botAI->GetMaster()->GetSession()->GetSecurity() < SEC_GAMEMASTER
|
&& !botAI->GetMaster()->CanBeGameMaster()
|
||||||
&& !GET_PLAYERBOT_AI(botAI->GetMaster());
|
&& !GET_PLAYERBOT_AI(botAI->GetMaster());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -9,6 +9,7 @@
|
|||||||
#include "Event.h"
|
#include "Event.h"
|
||||||
#include "ItemVisitors.h"
|
#include "ItemVisitors.h"
|
||||||
#include "Mail.h"
|
#include "Mail.h"
|
||||||
|
#include "PlayerbotTextMgr.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
|
|
||||||
bool SendMailAction::Execute(Event event)
|
bool SendMailAction::Execute(Event event)
|
||||||
@ -53,14 +54,18 @@ bool SendMailAction::Execute(Event event)
|
|||||||
|
|
||||||
if (!mailboxFound && !randomBot)
|
if (!mailboxFound && !randomBot)
|
||||||
{
|
{
|
||||||
bot->Whisper("There is no mailbox nearby", LANG_UNIVERSAL, tellTo);
|
bot->Whisper(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"send_mail_no_mailbox_nearby", "There is no mailbox nearby", {}),
|
||||||
|
LANG_UNIVERSAL, tellTo);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemIds ids = chat->parseItems(text);
|
ItemIds ids = chat->parseItems(text);
|
||||||
if (ids.size() > 1)
|
if (ids.size() > 1)
|
||||||
{
|
{
|
||||||
bot->Whisper("You can not request more than one item", LANG_UNIVERSAL, tellTo);
|
bot->Whisper(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"send_mail_one_item_only", "You can not request more than one item", {}),
|
||||||
|
LANG_UNIVERSAL, tellTo);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,13 +77,16 @@ bool SendMailAction::Execute(Event event)
|
|||||||
|
|
||||||
if (randomBot)
|
if (randomBot)
|
||||||
{
|
{
|
||||||
bot->Whisper("I cannot send money", LANG_UNIVERSAL, tellTo);
|
bot->Whisper(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"send_mail_cannot_send_money", "I cannot send money", {}),
|
||||||
|
LANG_UNIVERSAL, tellTo);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bot->GetMoney() < money)
|
if (bot->GetMoney() < money)
|
||||||
{
|
{
|
||||||
botAI->TellError("I don't have enough money");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"send_mail_not_enough_money", "I don't have enough money", {}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,8 +108,10 @@ bool SendMailAction::Execute(Event event)
|
|||||||
CharacterDatabase.CommitTransaction(trans);
|
CharacterDatabase.CommitTransaction(trans);
|
||||||
|
|
||||||
std::ostringstream out;
|
std::ostringstream out;
|
||||||
out << "Sending mail to " << receiver->GetName();
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
botAI->TellMaster(out.str());
|
"send_mail_sending_to",
|
||||||
|
"Sending mail to %receiver",
|
||||||
|
{{"%receiver", receiver->GetName()}}));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,7 +135,10 @@ bool SendMailAction::Execute(Event event)
|
|||||||
if (item->IsSoulBound() || item->IsConjuredConsumable())
|
if (item->IsSoulBound() || item->IsConjuredConsumable())
|
||||||
{
|
{
|
||||||
std::ostringstream out;
|
std::ostringstream out;
|
||||||
out << "Cannot send " << ChatHelper::FormatItem(item->GetTemplate());
|
out << PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"send_mail_cannot_send_item",
|
||||||
|
"Cannot send %item",
|
||||||
|
{{"%item", ChatHelper::FormatItem(item->GetTemplate())}});
|
||||||
bot->Whisper(out.str(), LANG_UNIVERSAL, tellTo);
|
bot->Whisper(out.str(), LANG_UNIVERSAL, tellTo);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -140,7 +153,10 @@ bool SendMailAction::Execute(Event event)
|
|||||||
if (!price)
|
if (!price)
|
||||||
{
|
{
|
||||||
std::ostringstream out;
|
std::ostringstream out;
|
||||||
out << ChatHelper::FormatItem(item->GetTemplate()) << ": it is not for sale";
|
out << PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"send_mail_item_not_for_sale",
|
||||||
|
"%item: it is not for sale",
|
||||||
|
{{"%item", ChatHelper::FormatItem(item->GetTemplate())}});
|
||||||
bot->Whisper(out.str(), LANG_UNIVERSAL, tellTo);
|
bot->Whisper(out.str(), LANG_UNIVERSAL, tellTo);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -160,7 +176,10 @@ bool SendMailAction::Execute(Event event)
|
|||||||
CharacterDatabase.CommitTransaction(trans);
|
CharacterDatabase.CommitTransaction(trans);
|
||||||
|
|
||||||
std::ostringstream out;
|
std::ostringstream out;
|
||||||
out << "Sent mail to " << receiver->GetName();
|
out << PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"send_mail_sent_to",
|
||||||
|
"Sent mail to %receiver",
|
||||||
|
{{"%receiver", receiver->GetName()}});
|
||||||
bot->Whisper(out.str(), LANG_UNIVERSAL, tellTo);
|
bot->Whisper(out.str(), LANG_UNIVERSAL, tellTo);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
#include "ChatHelper.h"
|
#include "ChatHelper.h"
|
||||||
#include "CraftValue.h"
|
#include "CraftValue.h"
|
||||||
#include "Event.h"
|
#include "Event.h"
|
||||||
|
#include "PlayerbotTextMgr.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
|
|
||||||
std::map<uint32, SkillLineAbilityEntry const*> SetCraftAction::skillSpells;
|
std::map<uint32, SkillLineAbilityEntry const*> SetCraftAction::skillSpells;
|
||||||
@ -24,7 +25,8 @@ bool SetCraftAction::Execute(Event event)
|
|||||||
if (link == "reset")
|
if (link == "reset")
|
||||||
{
|
{
|
||||||
data.Reset();
|
data.Reset();
|
||||||
botAI->TellMaster("I will not craft anything");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"craft_reset", "I will not craft anything", {}));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,7 +39,8 @@ bool SetCraftAction::Execute(Event event)
|
|||||||
ItemIds itemIds = chat->parseItems(link);
|
ItemIds itemIds = chat->parseItems(link);
|
||||||
if (itemIds.empty())
|
if (itemIds.empty())
|
||||||
{
|
{
|
||||||
botAI->TellMaster("Usage: 'craft [itemId]' or 'craft reset'");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"craft_usage", "Usage: 'craft [itemId]' or 'craft reset'", {}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,7 +97,8 @@ bool SetCraftAction::Execute(Event event)
|
|||||||
|
|
||||||
if (data.required.empty())
|
if (data.required.empty())
|
||||||
{
|
{
|
||||||
botAI->TellMaster("I cannot craft this");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"craft_cannot_craft", "I cannot craft this", {}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,7 +113,8 @@ void SetCraftAction::TellCraft()
|
|||||||
CraftData& data = AI_VALUE(CraftData&, "craft");
|
CraftData& data = AI_VALUE(CraftData&, "craft");
|
||||||
if (data.IsEmpty())
|
if (data.IsEmpty())
|
||||||
{
|
{
|
||||||
botAI->TellMaster("I will not craft anything");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"craft_reset", "I will not craft anything", {}));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,8 +122,7 @@ void SetCraftAction::TellCraft()
|
|||||||
if (!proto)
|
if (!proto)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
std::ostringstream out;
|
std::ostringstream reagentsOut;
|
||||||
out << "I will craft " << chat->FormatItem(proto) << " using reagents: ";
|
|
||||||
|
|
||||||
bool first = true;
|
bool first = true;
|
||||||
for (std::map<uint32, uint32>::iterator i = data.required.begin(); i != data.required.end(); ++i)
|
for (std::map<uint32, uint32>::iterator i = data.required.begin(); i != data.required.end(); ++i)
|
||||||
@ -130,20 +134,23 @@ void SetCraftAction::TellCraft()
|
|||||||
{
|
{
|
||||||
if (first)
|
if (first)
|
||||||
first = false;
|
first = false;
|
||||||
|
|
||||||
else
|
else
|
||||||
out << ", ";
|
reagentsOut << ", ";
|
||||||
|
|
||||||
out << chat->FormatItem(reagent, required);
|
reagentsOut << chat->FormatItem(reagent, required);
|
||||||
|
|
||||||
uint32 given = data.obtained[item];
|
uint32 given = data.obtained[item];
|
||||||
if (given)
|
if (given)
|
||||||
out << "|cffffff00(x" << given << " given)|r ";
|
reagentsOut << "|cffffff00(x" << given << " given)|r ";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
out << " (craft fee: " << chat->formatMoney(GetCraftFee(data)) << ")";
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
botAI->TellMaster(out.str());
|
"craft_summary",
|
||||||
|
"I will craft %item using reagents: %reagents (craft fee: %money)",
|
||||||
|
{{"%item", chat->FormatItem(proto)},
|
||||||
|
{"%reagents", reagentsOut.str()},
|
||||||
|
{"%money", chat->formatMoney(GetCraftFee(data))}}));
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32 SetCraftAction::GetCraftFee(CraftData& data)
|
uint32 SetCraftAction::GetCraftFee(CraftData& data)
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
#include "SetHomeAction.h"
|
#include "SetHomeAction.h"
|
||||||
|
|
||||||
#include "Event.h"
|
#include "Event.h"
|
||||||
|
#include "PlayerbotTextMgr.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
|
|
||||||
bool SetHomeAction::Execute(Event /*event*/)
|
bool SetHomeAction::Execute(Event /*event*/)
|
||||||
@ -28,7 +29,8 @@ bool SetHomeAction::Execute(Event /*event*/)
|
|||||||
{
|
{
|
||||||
Creature* creature = botAI->GetCreature(selection);
|
Creature* creature = botAI->GetCreature(selection);
|
||||||
bot->GetSession()->SendBindPoint(creature);
|
bot->GetSession()->SendBindPoint(creature);
|
||||||
botAI->TellMaster("This inn is my new home");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"set_home_success", "This inn is my new home", {}));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,10 +42,12 @@ bool SetHomeAction::Execute(Event /*event*/)
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
bot->GetSession()->SendBindPoint(unit);
|
bot->GetSession()->SendBindPoint(unit);
|
||||||
botAI->TellMaster("This inn is my new home");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"set_home_success", "This inn is my new home", {}));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
botAI->TellError("Can't find any innkeeper around");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"set_home_no_innkeeper_error", "Can't find any innkeeper around", {}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
#include "ShareQuestAction.h"
|
#include "ShareQuestAction.h"
|
||||||
|
|
||||||
#include "Event.h"
|
#include "Event.h"
|
||||||
|
#include "PlayerbotTextMgr.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
|
|
||||||
bool ShareQuestAction::Execute(Event event)
|
bool ShareQuestAction::Execute(Event event)
|
||||||
@ -32,7 +33,8 @@ bool ShareQuestAction::Execute(Event event)
|
|||||||
WorldPacket p;
|
WorldPacket p;
|
||||||
p << entry;
|
p << entry;
|
||||||
bot->GetSession()->HandlePushQuestToParty(p);
|
bot->GetSession()->HandlePushQuestToParty(p);
|
||||||
botAI->TellMaster("Quest shared");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"quest_shared", "Quest shared", {}));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -98,7 +100,8 @@ bool AutoShareQuestAction::Execute(Event /*event*/)
|
|||||||
WorldPacket p;
|
WorldPacket p;
|
||||||
p << logQuest;
|
p << logQuest;
|
||||||
bot->GetSession()->HandlePushQuestToParty(p);
|
bot->GetSession()->HandlePushQuestToParty(p);
|
||||||
botAI->TellMaster("Quest shared");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"quest_shared", "Quest shared", {}));
|
||||||
shared = true;
|
shared = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -15,6 +15,7 @@
|
|||||||
#include "Player.h"
|
#include "Player.h"
|
||||||
#include "PlayerbotAI.h"
|
#include "PlayerbotAI.h"
|
||||||
#include "PlayerbotFactory.h"
|
#include "PlayerbotFactory.h"
|
||||||
|
#include "PlayerbotTextMgr.h"
|
||||||
#include "SpellMgr.h"
|
#include "SpellMgr.h"
|
||||||
#include "WorldSession.h"
|
#include "WorldSession.h"
|
||||||
|
|
||||||
@ -123,7 +124,8 @@ bool TameAction::Execute(Event event)
|
|||||||
}
|
}
|
||||||
catch (...)
|
catch (...)
|
||||||
{
|
{
|
||||||
botAI->TellError("Invalid tame id.");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"tame_invalid_id_error", "Invalid tame id.", {}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (mode == "family" && !value.empty())
|
else if (mode == "family" && !value.empty())
|
||||||
@ -137,8 +139,10 @@ bool TameAction::Execute(Event event)
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Unrecognized command or missing argument; show usage
|
// Unrecognized command or missing argument; show usage
|
||||||
botAI->TellError(
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
"Usage: tame name <name> | tame id <id> | tame family <family> | tame rename <new name> | tame abandon");
|
"tame_usage_error",
|
||||||
|
"Usage: tame name <name> | tame id <id> | tame family <family> | tame rename <new name> | tame abandon",
|
||||||
|
{}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,12 +161,15 @@ bool TameAction::Execute(Event event)
|
|||||||
if (!lastPetName.empty() && lastPetId != 0)
|
if (!lastPetName.empty() && lastPetId != 0)
|
||||||
{
|
{
|
||||||
std::ostringstream oss;
|
std::ostringstream oss;
|
||||||
oss << "Pet changed to " << lastPetName << ", ID: " << lastPetId << ".";
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
botAI->TellMaster(oss.str());
|
"tame_pet_changed",
|
||||||
|
"Pet changed to %name, ID: %id.",
|
||||||
|
{{"%name", lastPetName}, {"%id", std::to_string(lastPetId)}}));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
botAI->TellMaster("Pet changed and initialized!");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"tame_pet_changed_initialized", "Pet changed and initialized!", {}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,7 +204,10 @@ bool TameAction::SetPetByName(const std::string& name)
|
|||||||
// If the creature is exotic and the bot doesn't have Beast Mastery, show error and fail
|
// If the creature is exotic and the bot doesn't have Beast Mastery, show error and fail
|
||||||
if (IsExoticPet(&creature) && !HasBeastMastery(bot))
|
if (IsExoticPet(&creature) && !HasBeastMastery(bot))
|
||||||
{
|
{
|
||||||
botAI->TellError("I cannot use exotic pets unless I have the Beast Mastery talent.");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"tame_exotic_requires_beast_mastery",
|
||||||
|
"I cannot use exotic pets unless I have the Beast Mastery talent.",
|
||||||
|
{}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -214,7 +224,8 @@ bool TameAction::SetPetByName(const std::string& name)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If no suitable pet found, show an error and return failure
|
// If no suitable pet found, show an error and return failure
|
||||||
botAI->TellError("No tameable pet found with name: " + name);
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"tame_no_pet_by_name", "No tameable pet found with name: %name", {{"%name", name}}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -231,21 +242,26 @@ bool TameAction::SetPetById(uint32 id)
|
|||||||
if (!creature->IsTameable(true))
|
if (!creature->IsTameable(true))
|
||||||
{
|
{
|
||||||
// If not tameable at all, show an error and fail
|
// If not tameable at all, show an error and fail
|
||||||
botAI->TellError("No tameable pet found with id: " + std::to_string(id));
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"tame_no_pet_by_id", "No tameable pet found with id: %id", {{"%id", std::to_string(id)}}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it's an exotic pet, make sure the bot has the Beast Mastery talent
|
// If it's an exotic pet, make sure the bot has the Beast Mastery talent
|
||||||
if (IsExoticPet(creature) && !HasBeastMastery(bot))
|
if (IsExoticPet(creature) && !HasBeastMastery(bot))
|
||||||
{
|
{
|
||||||
botAI->TellError("I cannot use exotic pets unless I have the Beast Mastery talent.");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"tame_exotic_requires_beast_mastery",
|
||||||
|
"I cannot use exotic pets unless I have the Beast Mastery talent.",
|
||||||
|
{}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the bot is actually allowed to tame this pet (honoring exotic pet rules)
|
// Check if the bot is actually allowed to tame this pet (honoring exotic pet rules)
|
||||||
if (!creature->IsTameable(bot->CanTameExoticPets()))
|
if (!creature->IsTameable(bot->CanTameExoticPets()))
|
||||||
{
|
{
|
||||||
botAI->TellError("No tameable pet found with id: " + std::to_string(id));
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"tame_no_pet_by_id", "No tameable pet found with id: %id", {{"%id", std::to_string(id)}}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -257,7 +273,8 @@ bool TameAction::SetPetById(uint32 id)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If no valid creature was found by id, show an error
|
// If no valid creature was found by id, show an error
|
||||||
botAI->TellError("No tameable pet found with id: " + std::to_string(id));
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"tame_no_pet_by_id", "No tameable pet found with id: %id", {{"%id", std::to_string(id)}}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -315,9 +332,13 @@ bool TameAction::SetPetByFamily(const std::string& family)
|
|||||||
if (candidates.empty())
|
if (candidates.empty())
|
||||||
{
|
{
|
||||||
if (foundExotic && !HasBeastMastery(bot))
|
if (foundExotic && !HasBeastMastery(bot))
|
||||||
botAI->TellError("I cannot use exotic pets unless I have the Beast Mastery talent.");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"tame_exotic_requires_beast_mastery",
|
||||||
|
"I cannot use exotic pets unless I have the Beast Mastery talent.",
|
||||||
|
{}));
|
||||||
else
|
else
|
||||||
botAI->TellError("No tameable pet found with family: " + family);
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"tame_no_pet_by_family", "No tameable pet found with family: %family", {{"%family", family}}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -342,14 +363,18 @@ bool TameAction::RenamePet(const std::string& newName)
|
|||||||
// Check if the bot currently has a pet
|
// Check if the bot currently has a pet
|
||||||
if (!pet)
|
if (!pet)
|
||||||
{
|
{
|
||||||
botAI->TellError("You have no pet to rename.");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"tame_no_pet_to_rename", "You have no pet to rename.", {}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate the new name: must not be empty and max 12 characters
|
// Validate the new name: must not be empty and max 12 characters
|
||||||
if (newName.empty() || newName.length() > 12)
|
if (newName.empty() || newName.length() > 12)
|
||||||
{
|
{
|
||||||
botAI->TellError("Pet name must be between 1 and 12 alphabetic characters.");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"tame_pet_name_length_error",
|
||||||
|
"Pet name must be between 1 and 12 alphabetic characters.",
|
||||||
|
{}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -358,7 +383,10 @@ bool TameAction::RenamePet(const std::string& newName)
|
|||||||
{
|
{
|
||||||
if (!std::isalpha(static_cast<unsigned char>(c)))
|
if (!std::isalpha(static_cast<unsigned char>(c)))
|
||||||
{
|
{
|
||||||
botAI->TellError("Pet name must only contain alphabetic characters (A-Z, a-z).");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"tame_pet_name_alpha_error",
|
||||||
|
"Pet name must only contain alphabetic characters (A-Z, a-z).",
|
||||||
|
{}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -372,7 +400,10 @@ bool TameAction::RenamePet(const std::string& newName)
|
|||||||
// Check if the new name is reserved or forbidden
|
// Check if the new name is reserved or forbidden
|
||||||
if (sObjectMgr->IsReservedName(normalized))
|
if (sObjectMgr->IsReservedName(normalized))
|
||||||
{
|
{
|
||||||
botAI->TellError("That pet name is forbidden. Please choose another name.");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"tame_pet_name_forbidden_error",
|
||||||
|
"That pet name is forbidden. Please choose another name.",
|
||||||
|
{}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -382,8 +413,12 @@ bool TameAction::RenamePet(const std::string& newName)
|
|||||||
bot->GetSession()->SendPetNameQuery(pet->GetGUID(), pet->GetEntry());
|
bot->GetSession()->SendPetNameQuery(pet->GetGUID(), pet->GetEntry());
|
||||||
|
|
||||||
// Notify the master about the rename and give a tip to update the client name display
|
// Notify the master about the rename and give a tip to update the client name display
|
||||||
botAI->TellMaster("Your pet has been renamed to " + normalized + "!");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
botAI->TellMaster("If you do not see the new name, please dismiss and recall your pet.");
|
"tame_pet_renamed", "Your pet has been renamed to %name!", {{"%name", normalized}}));
|
||||||
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"tame_pet_rename_refresh_hint",
|
||||||
|
"If you do not see the new name, please dismiss and recall your pet.",
|
||||||
|
{}));
|
||||||
|
|
||||||
// Remove the current pet and (re-)cast Call Pet spell if the bot is a hunter
|
// Remove the current pet and (re-)cast Call Pet spell if the bot is a hunter
|
||||||
bot->RemovePet(nullptr, PET_SAVE_AS_CURRENT, true);
|
bot->RemovePet(nullptr, PET_SAVE_AS_CURRENT, true);
|
||||||
@ -401,7 +436,8 @@ bool TameAction::CreateAndSetPet(uint32 creatureEntry)
|
|||||||
// Ensure the player is a hunter and at least level 10 (required for pets)
|
// Ensure the player is a hunter and at least level 10 (required for pets)
|
||||||
if (bot->getClass() != CLASS_HUNTER || bot->GetLevel() < 10)
|
if (bot->getClass() != CLASS_HUNTER || bot->GetLevel() < 10)
|
||||||
{
|
{
|
||||||
botAI->TellError("Only level 10+ hunters can have pets.");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"tame_only_hunters_level_10", "Only level 10+ hunters can have pets.", {}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -409,7 +445,8 @@ bool TameAction::CreateAndSetPet(uint32 creatureEntry)
|
|||||||
CreatureTemplate const* creature = sObjectMgr->GetCreatureTemplate(creatureEntry);
|
CreatureTemplate const* creature = sObjectMgr->GetCreatureTemplate(creatureEntry);
|
||||||
if (!creature)
|
if (!creature)
|
||||||
{
|
{
|
||||||
botAI->TellError("Creature template not found.");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"tame_creature_template_not_found", "Creature template not found.", {}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -430,7 +467,8 @@ bool TameAction::CreateAndSetPet(uint32 creatureEntry)
|
|||||||
Pet* pet = bot->CreateTamedPetFrom(creatureEntry, 0);
|
Pet* pet = bot->CreateTamedPetFrom(creatureEntry, 0);
|
||||||
if (!pet)
|
if (!pet)
|
||||||
{
|
{
|
||||||
botAI->TellError("Failed to create pet.");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"tame_create_pet_failed", "Failed to create pet.", {}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -485,13 +523,15 @@ bool TameAction::AbandonPet()
|
|||||||
// Remove the pet from the bot and mark it as deleted in the database
|
// Remove the pet from the bot and mark it as deleted in the database
|
||||||
bot->RemovePet(pet, PET_SAVE_AS_DELETED);
|
bot->RemovePet(pet, PET_SAVE_AS_DELETED);
|
||||||
// Inform the bot's master/player that the pet was abandoned
|
// Inform the bot's master/player that the pet was abandoned
|
||||||
botAI->TellMaster("Your pet has been abandoned.");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"tame_pet_abandoned", "Your pet has been abandoned.", {}));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// If there is no hunter pet, show an error message
|
// If there is no hunter pet, show an error message
|
||||||
botAI->TellError("You have no hunter pet to abandon.");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"tame_no_hunter_pet_to_abandon", "You have no hunter pet to abandon.", {}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include "Event.h"
|
#include "Event.h"
|
||||||
#include "LastMovementValue.h"
|
#include "LastMovementValue.h"
|
||||||
|
#include "PlayerbotTextMgr.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
#include "PlayerbotAIConfig.h"
|
#include "PlayerbotAIConfig.h"
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
@ -24,7 +25,8 @@ bool TaxiAction::Execute(Event event)
|
|||||||
{
|
{
|
||||||
movement.taxiNodes.clear();
|
movement.taxiNodes.clear();
|
||||||
movement.Set(nullptr);
|
movement.Set(nullptr);
|
||||||
botAI->TellMaster("I am ready for the next flight");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"taxi_ready_next_flight", "I am ready for the next flight", {}));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,13 +122,15 @@ bool TaxiAction::Execute(Event event)
|
|||||||
{
|
{
|
||||||
movement.taxiNodes.clear();
|
movement.taxiNodes.clear();
|
||||||
movement.Set(nullptr);
|
movement.Set(nullptr);
|
||||||
botAI->TellError("I can't fly with you");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"taxi_cant_fly_with_you", "I can't fly with you", {}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
botAI->TellError("Cannot find any flightmaster to talk");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"taxi_no_flightmaster_nearby", "Cannot find any flightmaster to talk", {}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,6 +12,7 @@
|
|||||||
#include "ItemVisitors.h"
|
#include "ItemVisitors.h"
|
||||||
#include "PlayerbotMgr.h"
|
#include "PlayerbotMgr.h"
|
||||||
#include "PlayerbotSecurity.h"
|
#include "PlayerbotSecurity.h"
|
||||||
|
#include "PlayerbotTextMgr.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
#include "RandomPlayerbotMgr.h"
|
#include "RandomPlayerbotMgr.h"
|
||||||
#include "SetCraftAction.h"
|
#include "SetCraftAction.h"
|
||||||
@ -28,13 +29,17 @@ bool TradeStatusAction::Execute(Event event)
|
|||||||
// Allow the master and group members to trade
|
// Allow the master and group members to trade
|
||||||
if (trader != master && !traderBotAI && (!bot->GetGroup() || !bot->GetGroup()->IsMember(trader->GetGUID())))
|
if (trader != master && !traderBotAI && (!bot->GetGroup() || !bot->GetGroup()->IsMember(trader->GetGUID())))
|
||||||
{
|
{
|
||||||
bot->Whisper("I'm kind of busy now", LANG_UNIVERSAL, trader);
|
bot->Whisper(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"trade_busy_now", "I'm kind of busy now", {}),
|
||||||
|
LANG_UNIVERSAL, trader);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sPlayerbotAIConfig.enableRandomBotTrading == 0 && (sRandomPlayerbotMgr.IsRandomBot(bot)|| sRandomPlayerbotMgr.IsAddclassBot(bot)))
|
if (sPlayerbotAIConfig.enableRandomBotTrading == 0 && (sRandomPlayerbotMgr.IsRandomBot(bot)|| sRandomPlayerbotMgr.IsAddclassBot(bot)))
|
||||||
{
|
{
|
||||||
bot->Whisper("Trading is disabled", LANG_UNIVERSAL, trader);
|
bot->Whisper(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"trade_disabled", "Trading is disabled", {}),
|
||||||
|
LANG_UNIVERSAL, trader);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,9 +185,15 @@ bool TradeStatusAction::CheckTrade()
|
|||||||
{
|
{
|
||||||
if (bot->GetGroup() && bot->GetGroup()->IsMember(bot->GetTrader()->GetGUID()) &&
|
if (bot->GetGroup() && bot->GetGroup()->IsMember(bot->GetTrader()->GetGUID()) &&
|
||||||
botAI->HasRealPlayerMaster())
|
botAI->HasRealPlayerMaster())
|
||||||
botAI->TellMasterNoFacing("Thank you " + chat->FormatWorldobject(bot->GetTrader()));
|
botAI->TellMasterNoFacing(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"trade_thank_you_player",
|
||||||
|
"Thank you %player",
|
||||||
|
{{"%player", chat->FormatWorldobject(bot->GetTrader())}}));
|
||||||
else
|
else
|
||||||
bot->Say("Thank you " + chat->FormatWorldobject(bot->GetTrader()),
|
bot->Say(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"trade_thank_you_player",
|
||||||
|
"Thank you %player",
|
||||||
|
{{"%player", chat->FormatWorldobject(bot->GetTrader())}}),
|
||||||
(bot->GetTeamId() == TEAM_ALLIANCE ? LANG_COMMON : LANG_ORCISH));
|
(bot->GetTeamId() == TEAM_ALLIANCE ? LANG_COMMON : LANG_ORCISH));
|
||||||
}
|
}
|
||||||
return isGettingItem;
|
return isGettingItem;
|
||||||
@ -210,12 +221,16 @@ bool TradeStatusAction::CheckTrade()
|
|||||||
int32 playerMoney = trader->GetTradeData()->GetMoney() + playerItemsMoney;
|
int32 playerMoney = trader->GetTradeData()->GetMoney() + playerItemsMoney;
|
||||||
if (botItemsMoney > 0 && sPlayerbotAIConfig.enableRandomBotTrading == 2 && (sRandomPlayerbotMgr.IsRandomBot(bot)|| sRandomPlayerbotMgr.IsAddclassBot(bot)))
|
if (botItemsMoney > 0 && sPlayerbotAIConfig.enableRandomBotTrading == 2 && (sRandomPlayerbotMgr.IsRandomBot(bot)|| sRandomPlayerbotMgr.IsAddclassBot(bot)))
|
||||||
{
|
{
|
||||||
bot->Whisper("Selling is disabled.", LANG_UNIVERSAL, trader);
|
bot->Whisper(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"trade_selling_disabled", "Selling is disabled.", {}),
|
||||||
|
LANG_UNIVERSAL, trader);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (playerItemsMoney && sPlayerbotAIConfig.enableRandomBotTrading == 3 && (sRandomPlayerbotMgr.IsRandomBot(bot)|| sRandomPlayerbotMgr.IsAddclassBot(bot)))
|
if (playerItemsMoney && sPlayerbotAIConfig.enableRandomBotTrading == 3 && (sRandomPlayerbotMgr.IsRandomBot(bot)|| sRandomPlayerbotMgr.IsAddclassBot(bot)))
|
||||||
{
|
{
|
||||||
bot->Whisper("Buying is disabled.", LANG_UNIVERSAL, trader);
|
bot->Whisper(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"trade_buying_disabled", "Buying is disabled.", {}),
|
||||||
|
LANG_UNIVERSAL, trader);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for (uint32 slot = 0; slot < TRADE_SLOT_TRADED_COUNT; ++slot)
|
for (uint32 slot = 0; slot < TRADE_SLOT_TRADED_COUNT; ++slot)
|
||||||
@ -224,8 +239,10 @@ bool TradeStatusAction::CheckTrade()
|
|||||||
if (item && !item->GetTemplate()->SellPrice && !item->GetTemplate()->IsConjuredConsumable())
|
if (item && !item->GetTemplate()->SellPrice && !item->GetTemplate()->IsConjuredConsumable())
|
||||||
{
|
{
|
||||||
std::ostringstream out;
|
std::ostringstream out;
|
||||||
out << chat->FormatItem(item->GetTemplate()) << " - This is not for sale";
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
botAI->TellMaster(out);
|
"trade_item_not_for_sale",
|
||||||
|
"%item - This is not for sale",
|
||||||
|
{{"%item", chat->FormatItem(item->GetTemplate())}}));
|
||||||
botAI->PlaySound(TEXT_EMOTE_NO);
|
botAI->PlaySound(TEXT_EMOTE_NO);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -239,8 +256,10 @@ bool TradeStatusAction::CheckTrade()
|
|||||||
if ((botMoney && !item->GetTemplate()->BuyPrice) || usage == ITEM_USAGE_NONE)
|
if ((botMoney && !item->GetTemplate()->BuyPrice) || usage == ITEM_USAGE_NONE)
|
||||||
{
|
{
|
||||||
std::ostringstream out;
|
std::ostringstream out;
|
||||||
out << chat->FormatItem(item->GetTemplate()) << " - I don't need this";
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
botAI->TellMaster(out);
|
"trade_item_not_needed",
|
||||||
|
"%item - I don't need this",
|
||||||
|
{{"%item", chat->FormatItem(item->GetTemplate())}}));
|
||||||
botAI->PlaySound(TEXT_EMOTE_NO);
|
botAI->PlaySound(TEXT_EMOTE_NO);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -252,7 +271,8 @@ bool TradeStatusAction::CheckTrade()
|
|||||||
|
|
||||||
if (!botItemsMoney && !playerItemsMoney)
|
if (!botItemsMoney && !playerItemsMoney)
|
||||||
{
|
{
|
||||||
botAI->TellError("There are no items to trade");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"trade_no_items_error", "There are no items to trade", {}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -266,7 +286,8 @@ bool TradeStatusAction::CheckTrade()
|
|||||||
{
|
{
|
||||||
if (moneyDelta < 0)
|
if (moneyDelta < 0)
|
||||||
{
|
{
|
||||||
botAI->TellError("You can use discount to buy items only");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"trade_discount_buy_only", "You can use discount to buy items only", {}));
|
||||||
botAI->PlaySound(TEXT_EMOTE_NO);
|
botAI->PlaySound(TEXT_EMOTE_NO);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -282,16 +303,20 @@ bool TradeStatusAction::CheckTrade()
|
|||||||
switch (urand(0, 4))
|
switch (urand(0, 4))
|
||||||
{
|
{
|
||||||
case 0:
|
case 0:
|
||||||
botAI->TellMaster("A pleasure doing business with you");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"trade_success_pleasure", "A pleasure doing business with you", {}));
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
botAI->TellMaster("Fair trade");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"trade_success_fair_trade", "Fair trade", {}));
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
botAI->TellMaster("Thanks");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"trade_success_thanks", "Thanks", {}));
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
botAI->TellMaster("Off with you");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"trade_success_off_with_you", "Off with you", {}));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -300,8 +325,10 @@ bool TradeStatusAction::CheckTrade()
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::ostringstream out;
|
std::ostringstream out;
|
||||||
out << "I want " << chat->formatMoney(-(delta + discount)) << " for this";
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
botAI->TellMaster(out);
|
"trade_want_money_for_this",
|
||||||
|
"I want %money for this",
|
||||||
|
{{"%money", chat->formatMoney(-(delta + discount))}}));
|
||||||
botAI->PlaySound(TEXT_EMOTE_NO);
|
botAI->PlaySound(TEXT_EMOTE_NO);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,10 +5,14 @@
|
|||||||
|
|
||||||
#include "TrainerAction.h"
|
#include "TrainerAction.h"
|
||||||
|
|
||||||
|
#include "AiFactory.h"
|
||||||
|
#include "BisListMgr.h"
|
||||||
#include "BudgetValues.h"
|
#include "BudgetValues.h"
|
||||||
#include "Event.h"
|
#include "Event.h"
|
||||||
#include "PlayerbotFactory.h"
|
#include "PlayerbotFactory.h"
|
||||||
|
#include "PlayerbotTextMgr.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
|
#include "ReputationMgr.h"
|
||||||
#include "Trainer.h"
|
#include "Trainer.h"
|
||||||
|
|
||||||
bool TrainerAction::Execute(Event event)
|
bool TrainerAction::Execute(Event event)
|
||||||
@ -269,6 +273,282 @@ bool MaintenanceAction::Execute(Event /*event*/)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool BisGearAction::RunAutogearFallback(uint16 effectiveIlvl)
|
||||||
|
{
|
||||||
|
if (!sPlayerbotAIConfig.autoGearCommand)
|
||||||
|
{
|
||||||
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"bis_autogear_unavailable_error",
|
||||||
|
"autogear command is not allowed, please check the configuration.", {}));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"bis_no_rows_fallback",
|
||||||
|
"No BiS for your tier/spec/level, check cfg, running autogear instead", {}));
|
||||||
|
|
||||||
|
// Wipe all equipped slots so autogear gears from scratch at the requested ilvl
|
||||||
|
// (avoids old high-tier items surviving the incremental 1.2x threshold).
|
||||||
|
for (uint8 slot = EQUIPMENT_SLOT_START; slot < EQUIPMENT_SLOT_END; ++slot)
|
||||||
|
{
|
||||||
|
if (slot == EQUIPMENT_SLOT_TABARD || slot == EQUIPMENT_SLOT_BODY)
|
||||||
|
continue;
|
||||||
|
if (bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot))
|
||||||
|
bot->DestroyItem(INVENTORY_SLOT_BAG_0, slot, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32 gs = effectiveIlvl == 0
|
||||||
|
? 0
|
||||||
|
: PlayerbotFactory::CalcMixedGearScore(effectiveIlvl, sPlayerbotAIConfig.autoGearQualityLimit);
|
||||||
|
PlayerbotFactory factory(bot, bot->GetLevel(), sPlayerbotAIConfig.autoGearQualityLimit, gs);
|
||||||
|
factory.InitEquipment(false, sPlayerbotAIConfig.twoRoundsGearInit);
|
||||||
|
factory.InitAmmo();
|
||||||
|
if (bot->GetLevel() >= sPlayerbotAIConfig.minEnchantingBotLevel)
|
||||||
|
factory.ApplyEnchantAndGemsNew();
|
||||||
|
bot->DurabilityRepairAll(false, 1.0f, false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BisGearAction::Execute(Event event)
|
||||||
|
{
|
||||||
|
if (!sPlayerbotAIConfig.autoGearBisCommand)
|
||||||
|
{
|
||||||
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"bis_command_unavailable_error",
|
||||||
|
"bis command is not allowed, please check the configuration.", {}));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sPlayerbotAIConfig.autoGearCommandAltBots &&
|
||||||
|
!sPlayerbotAIConfig.IsInRandomAccountList(bot->GetSession()->GetAccountId()))
|
||||||
|
{
|
||||||
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"bis_altbot_refused_error", "You cannot use bis on alt bots.", {}));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sPlayerbotAIConfig.autoGearQualityLimit < 4)
|
||||||
|
{
|
||||||
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"bis_quality_floor_error", "AutoGearQualityLimit must be 4 for BiS.", {}));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sRandomPlayerbotMgr.IsSpecPvp(bot->GetGUID().GetCounter(), bot->getClass()))
|
||||||
|
{
|
||||||
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"bis_pvp_refused_error", "bis is PvE only, bot is configured as PvP.", {}));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16 ilvl = static_cast<uint16>(sPlayerbotAIConfig.autoGearScoreLimit);
|
||||||
|
|
||||||
|
// Optional explicit ilvl override: `/p autogear bis 55`.
|
||||||
|
// Garbage or out-of-range args are hard-rejected: no autogear fallback, no gear change.
|
||||||
|
std::string const param = event.getParam();
|
||||||
|
if (!param.empty())
|
||||||
|
{
|
||||||
|
unsigned long parsed = 0;
|
||||||
|
size_t pos = 0;
|
||||||
|
bool valid = false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
parsed = std::stoul(param, &pos);
|
||||||
|
valid = (parsed > 0 && pos == param.size() && parsed <= 0xFFFFu);
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!valid)
|
||||||
|
{
|
||||||
|
std::map<std::string, std::string> phs;
|
||||||
|
phs["%param"] = param;
|
||||||
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"bis_invalid_arg_error",
|
||||||
|
"Invalid BiS ilvl argument '%param'. Use a positive integer.", phs));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (parsed > static_cast<unsigned long>(sPlayerbotAIConfig.autoGearScoreLimit))
|
||||||
|
{
|
||||||
|
std::map<std::string, std::string> phs;
|
||||||
|
phs["%requested"] = std::to_string(parsed);
|
||||||
|
phs["%limit"] = std::to_string(sPlayerbotAIConfig.autoGearScoreLimit);
|
||||||
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"bis_arg_above_limit_error",
|
||||||
|
"BiS ilvl %requested exceeds AutoGearScoreLimit %limit, refusing", phs));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ilvl = static_cast<uint16>(parsed);
|
||||||
|
}
|
||||||
|
uint8 cls = bot->getClass();
|
||||||
|
uint8 tab = AiFactory::GetPlayerSpecTab(bot);
|
||||||
|
uint8 faction = bot->GetTeamId() == TEAM_ALLIANCE ? 1 : 2;
|
||||||
|
|
||||||
|
// Druid Bear (Feral Tank) shares tab 1 with Cat. Use sentinel tab 10 when tank strategy active.
|
||||||
|
constexpr uint8 BIS_TAB_DRUID_BEAR = 10;
|
||||||
|
constexpr uint16 BIS_ILVL_FALLBACK_WINDOW = 20;
|
||||||
|
uint16 resolvedIlvl = 0;
|
||||||
|
std::map<uint8, uint32> bisMap;
|
||||||
|
if (cls == CLASS_DRUID && tab == DRUID_TAB_FERAL && PlayerbotAI::IsTank(bot))
|
||||||
|
bisMap = sBisListMgr->GetBisForNearest(ilvl, BIS_ILVL_FALLBACK_WINDOW, cls, BIS_TAB_DRUID_BEAR, faction,
|
||||||
|
&resolvedIlvl);
|
||||||
|
if (bisMap.empty())
|
||||||
|
bisMap = sBisListMgr->GetBisForNearest(ilvl, BIS_ILVL_FALLBACK_WINDOW, cls, tab, faction, &resolvedIlvl);
|
||||||
|
|
||||||
|
// No rows within fallback window -> full autogear fallback at the effective ilvl.
|
||||||
|
if (bisMap.empty())
|
||||||
|
{
|
||||||
|
std::map<std::string, std::string> phs;
|
||||||
|
phs["%ilvl"] = std::to_string(ilvl);
|
||||||
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"bis_no_rows_autogear_msg",
|
||||||
|
"No BiS at ilvl %ilvl, using Autogear %ilvl instead", phs));
|
||||||
|
return RunAutogearFallback(ilvl);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resolvedIlvl != ilvl)
|
||||||
|
{
|
||||||
|
std::map<std::string, std::string> phs;
|
||||||
|
phs["%requested"] = std::to_string(ilvl);
|
||||||
|
phs["%resolved"] = std::to_string(resolvedIlvl);
|
||||||
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"bis_closest_match_msg",
|
||||||
|
"No BiS at ilvl %requested, using closest match at ilvl %resolved", phs));
|
||||||
|
}
|
||||||
|
|
||||||
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"bis_applying_msg", "Applying BiS gear", {}));
|
||||||
|
|
||||||
|
// 1. Wipe everything currently equipped so autogear starts from a clean slate.
|
||||||
|
// Old items linger in inventory otherwise and autogear leaves slots empty on bag conflicts.
|
||||||
|
for (uint8 slot = EQUIPMENT_SLOT_START; slot < EQUIPMENT_SLOT_END; ++slot)
|
||||||
|
{
|
||||||
|
if (slot == EQUIPMENT_SLOT_TABARD || slot == EQUIPMENT_SLOT_BODY)
|
||||||
|
continue;
|
||||||
|
if (bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot))
|
||||||
|
bot->DestroyItem(INVENTORY_SLOT_BAG_0, slot, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wipe equippable items from bags too. Autogear can shove old equipped items into bags
|
||||||
|
// (HandleAutoStoreBagItemOpcode), and a unique-equipped duplicate stuck in a bag blocks
|
||||||
|
// CanEquipNewItem on subsequent BiS runs. Spare consumables/reagents.
|
||||||
|
auto destroyIfEquippable = [&](uint8 bag, uint8 slot)
|
||||||
|
{
|
||||||
|
Item* item = bot->GetItemByPos(bag, slot);
|
||||||
|
if (!item)
|
||||||
|
return;
|
||||||
|
ItemTemplate const* tmpl = item->GetTemplate();
|
||||||
|
if (!tmpl)
|
||||||
|
return;
|
||||||
|
if (tmpl->Class == ITEM_CLASS_WEAPON || tmpl->Class == ITEM_CLASS_ARMOR)
|
||||||
|
bot->DestroyItem(bag, slot, true);
|
||||||
|
};
|
||||||
|
for (uint8 slot = INVENTORY_SLOT_ITEM_START; slot < INVENTORY_SLOT_ITEM_END; ++slot)
|
||||||
|
destroyIfEquippable(INVENTORY_SLOT_BAG_0, slot);
|
||||||
|
for (uint8 bag = INVENTORY_SLOT_BAG_START; bag < INVENTORY_SLOT_BAG_END; ++bag)
|
||||||
|
{
|
||||||
|
if (Bag* container = bot->GetBagByPos(bag))
|
||||||
|
for (uint32 slot = 0; slot < container->GetBagSize(); ++slot)
|
||||||
|
destroyIfEquippable(bag, slot);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Run full autogear on the empty bot so every slot gets a best-available pick.
|
||||||
|
// Uncovered slots will keep the autogear pick; BiS overwrites the rest below.
|
||||||
|
if (sPlayerbotAIConfig.autoGearCommand)
|
||||||
|
{
|
||||||
|
uint32 fillGs = ilvl == 0
|
||||||
|
? 0
|
||||||
|
: PlayerbotFactory::CalcMixedGearScore(ilvl, sPlayerbotAIConfig.autoGearQualityLimit);
|
||||||
|
PlayerbotFactory fillFactory(bot, bot->GetLevel(), sPlayerbotAIConfig.autoGearQualityLimit, fillGs);
|
||||||
|
fillFactory.InitEquipment(false, sPlayerbotAIConfig.twoRoundsGearInit);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2b. Pre-destroy autogear picks that would conflict with any BiS item by entry.
|
||||||
|
// Autogear may have placed the exact item BiS wants into trinket2/finger2 (or vice versa);
|
||||||
|
// unique-equipped enforcement would then make BiS's equip silently drop one copy.
|
||||||
|
std::set<uint32> bisEntries;
|
||||||
|
for (auto const& kv : bisMap)
|
||||||
|
bisEntries.insert(kv.second);
|
||||||
|
for (uint8 slot = EQUIPMENT_SLOT_START; slot < EQUIPMENT_SLOT_END; ++slot)
|
||||||
|
{
|
||||||
|
if (Item* item = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot))
|
||||||
|
if (bisEntries.count(item->GetEntry()))
|
||||||
|
bot->DestroyItem(INVENTORY_SLOT_BAG_0, slot, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Apply BiS: only touch slots where the bot can actually equip the BiS item.
|
||||||
|
// If item requires reputation, grant the required rank first. If CanUseItem still
|
||||||
|
// fails (class/race/skill/level), keep autogear's pick for that slot.
|
||||||
|
for (auto const& kv : bisMap)
|
||||||
|
{
|
||||||
|
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(kv.second);
|
||||||
|
if (!proto)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Grant required reputation rank if the item gates on it.
|
||||||
|
if (proto->RequiredReputationFaction && proto->RequiredReputationRank > 0)
|
||||||
|
{
|
||||||
|
if (FactionEntry const* fac = sFactionStore.LookupEntry(proto->RequiredReputationFaction))
|
||||||
|
{
|
||||||
|
ReputationRank requiredRank = static_cast<ReputationRank>(proto->RequiredReputationRank);
|
||||||
|
if (bot->GetReputationRank(proto->RequiredReputationFaction) < requiredRank)
|
||||||
|
{
|
||||||
|
int32 standing = ReputationMgr::ReputationRankToStanding(
|
||||||
|
static_cast<ReputationRank>(requiredRank - 1)) + 1;
|
||||||
|
bot->GetReputationMgr().SetReputation(fac, standing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bot->CanUseItem(proto) != EQUIP_ERR_OK)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
uint8 slot = kv.first;
|
||||||
|
if (bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot))
|
||||||
|
bot->DestroyItem(INVENTORY_SLOT_BAG_0, slot, true);
|
||||||
|
|
||||||
|
uint16 dest = 0;
|
||||||
|
InventoryResult eqResult = bot->CanEquipNewItem(slot, dest, kv.second, false);
|
||||||
|
|
||||||
|
// Paired slots (finger 10<->11, trinket 12<->13): destroy paired slot and retry once
|
||||||
|
// when unique-equipped or autogear residue blocks the first attempt.
|
||||||
|
if (eqResult != EQUIP_ERR_OK)
|
||||||
|
{
|
||||||
|
uint8 pairedSlot = 0xFF;
|
||||||
|
if (slot == EQUIPMENT_SLOT_FINGER1) pairedSlot = EQUIPMENT_SLOT_FINGER2;
|
||||||
|
else if (slot == EQUIPMENT_SLOT_FINGER2) pairedSlot = EQUIPMENT_SLOT_FINGER1;
|
||||||
|
else if (slot == EQUIPMENT_SLOT_TRINKET1) pairedSlot = EQUIPMENT_SLOT_TRINKET2;
|
||||||
|
else if (slot == EQUIPMENT_SLOT_TRINKET2) pairedSlot = EQUIPMENT_SLOT_TRINKET1;
|
||||||
|
|
||||||
|
if (pairedSlot != 0xFF)
|
||||||
|
{
|
||||||
|
if (bot->GetItemByPos(INVENTORY_SLOT_BAG_0, pairedSlot))
|
||||||
|
bot->DestroyItem(INVENTORY_SLOT_BAG_0, pairedSlot, true);
|
||||||
|
eqResult = bot->CanEquipNewItem(slot, dest, kv.second, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eqResult == EQUIP_ERR_OK)
|
||||||
|
{
|
||||||
|
bot->EquipNewItem(dest, kv.second, true);
|
||||||
|
bot->AutoUnequipOffhandIfNeed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerbotFactory factory(bot, bot->GetLevel(), ITEM_QUALITY_EPIC, 0);
|
||||||
|
factory.InitAmmo();
|
||||||
|
if (bot->GetLevel() >= sPlayerbotAIConfig.minEnchantingBotLevel)
|
||||||
|
factory.ApplyEnchantAndGemsNew();
|
||||||
|
|
||||||
|
bot->DurabilityRepairAll(false, 1.0f, false);
|
||||||
|
|
||||||
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"bis_applied_msg", "BiS applied", {}));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool RemoveGlyphAction::Execute(Event /*event*/)
|
bool RemoveGlyphAction::Execute(Event /*event*/)
|
||||||
{
|
{
|
||||||
for (uint32 slotIndex = 0; slotIndex < MAX_GLYPH_SLOT_INDEX; ++slotIndex)
|
for (uint32 slotIndex = 0; slotIndex < MAX_GLYPH_SLOT_INDEX; ++slotIndex)
|
||||||
|
|||||||
@ -53,4 +53,14 @@ public:
|
|||||||
bool Execute(Event event) override;
|
bool Execute(Event event) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class BisGearAction : public Action
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
BisGearAction(PlayerbotAI* botAI) : Action(botAI, "autogear bis") {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool RunAutogearFallback(uint16 effectiveIlvl);
|
||||||
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -9,6 +9,7 @@
|
|||||||
#include "Event.h"
|
#include "Event.h"
|
||||||
#include "ItemPackets.h"
|
#include "ItemPackets.h"
|
||||||
#include "ItemUsageValue.h"
|
#include "ItemUsageValue.h"
|
||||||
|
#include "PlayerbotTextMgr.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
|
|
||||||
bool UseItemAction::Execute(Event event)
|
bool UseItemAction::Execute(Event event)
|
||||||
@ -35,7 +36,8 @@ bool UseItemAction::Execute(Event event)
|
|||||||
return UseItemOnGameObject(*items.begin(), *gos.begin());
|
return UseItemOnGameObject(*items.begin(), *gos.begin());
|
||||||
}
|
}
|
||||||
|
|
||||||
botAI->TellError("No items (or game objects) available");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"use_item_none_available", "No items (or game objects) available", {}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,8 +50,10 @@ bool UseItemAction::UseGameObject(ObjectGuid guid)
|
|||||||
go->Use(bot);
|
go->Use(bot);
|
||||||
|
|
||||||
std::ostringstream out;
|
std::ostringstream out;
|
||||||
out << "Using " << chat->FormatGameobject(go);
|
botAI->TellMasterNoFacing(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
botAI->TellMasterNoFacing(out.str());
|
"use_gameobject",
|
||||||
|
"Using %gameobject",
|
||||||
|
{{"%gameobject", chat->FormatGameobject(go)}}));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,16 +96,16 @@ bool UseItemAction::UseItem(Item* item, ObjectGuid goGuid, Item* itemTarget, Uni
|
|||||||
|
|
||||||
bool targetSelected = false;
|
bool targetSelected = false;
|
||||||
|
|
||||||
std::ostringstream out;
|
std::string itemText = chat->FormatItem(item->GetTemplate());
|
||||||
out << "Using " << chat->FormatItem(item->GetTemplate());
|
std::string targetText;
|
||||||
|
|
||||||
if (item->GetTemplate()->Stackable > 1)
|
if (item->GetTemplate()->Stackable > 1)
|
||||||
{
|
{
|
||||||
uint32 count = item->GetCount();
|
uint32 count = item->GetCount();
|
||||||
if (count > 1)
|
if (count > 1)
|
||||||
out << " (" << count << " available) ";
|
itemText += " (" + std::to_string(count) + " available)";
|
||||||
else
|
else
|
||||||
out << " (the last one!)";
|
itemText += " (the last one!)";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (goGuid)
|
if (goGuid)
|
||||||
@ -114,7 +118,7 @@ bool UseItemAction::UseItem(Item* item, ObjectGuid goGuid, Item* itemTarget, Uni
|
|||||||
|
|
||||||
packet << targetFlag;
|
packet << targetFlag;
|
||||||
packet << goGuid.WriteAsPacked();
|
packet << goGuid.WriteAsPacked();
|
||||||
out << " on " << chat->FormatGameobject(go);
|
targetText = chat->FormatGameobject(go);
|
||||||
targetSelected = true;
|
targetSelected = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,7 +128,8 @@ bool UseItemAction::UseItem(Item* item, ObjectGuid goGuid, Item* itemTarget, Uni
|
|||||||
{
|
{
|
||||||
bool fit = SocketItem(itemTarget, item) || SocketItem(itemTarget, item, true);
|
bool fit = SocketItem(itemTarget, item) || SocketItem(itemTarget, item, true);
|
||||||
if (!fit)
|
if (!fit)
|
||||||
botAI->TellMaster("Socket does not fit");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"socket_does_not_fit", "Socket does not fit", {}));
|
||||||
|
|
||||||
return fit;
|
return fit;
|
||||||
}
|
}
|
||||||
@ -133,7 +138,7 @@ bool UseItemAction::UseItem(Item* item, ObjectGuid goGuid, Item* itemTarget, Uni
|
|||||||
targetFlag = TARGET_FLAG_ITEM;
|
targetFlag = TARGET_FLAG_ITEM;
|
||||||
packet << targetFlag;
|
packet << targetFlag;
|
||||||
packet << itemTarget->GetGUID().WriteAsPacked();
|
packet << itemTarget->GetGUID().WriteAsPacked();
|
||||||
out << " on " << chat->FormatItem(itemTarget->GetTemplate());
|
targetText = chat->FormatItem(itemTarget->GetTemplate());
|
||||||
targetSelected = true;
|
targetSelected = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -149,7 +154,7 @@ bool UseItemAction::UseItem(Item* item, ObjectGuid goGuid, Item* itemTarget, Uni
|
|||||||
{
|
{
|
||||||
targetFlag = TARGET_FLAG_UNIT;
|
targetFlag = TARGET_FLAG_UNIT;
|
||||||
packet << targetFlag << masterSelection.WriteAsPacked();
|
packet << targetFlag << masterSelection.WriteAsPacked();
|
||||||
out << " on " << unit->GetName();
|
targetText = unit->GetName();
|
||||||
targetSelected = true;
|
targetSelected = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -159,7 +164,7 @@ bool UseItemAction::UseItem(Item* item, ObjectGuid goGuid, Item* itemTarget, Uni
|
|||||||
{
|
{
|
||||||
targetFlag = TARGET_FLAG_UNIT;
|
targetFlag = TARGET_FLAG_UNIT;
|
||||||
packet << targetFlag << unitTarget->GetGUID().WriteAsPacked();
|
packet << targetFlag << unitTarget->GetGUID().WriteAsPacked();
|
||||||
out << " on " << unitTarget->GetName();
|
targetText = unitTarget->GetName();
|
||||||
targetSelected = true;
|
targetSelected = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,9 +178,7 @@ bool UseItemAction::UseItem(Item* item, ObjectGuid goGuid, Item* itemTarget, Uni
|
|||||||
packet << uint32(0);
|
packet << uint32(0);
|
||||||
bot->GetSession()->HandleQuestgiverAcceptQuestOpcode(packet);
|
bot->GetSession()->HandleQuestgiverAcceptQuestOpcode(packet);
|
||||||
|
|
||||||
std::ostringstream out;
|
botAI->TellMasterNoFacing("Got quest " + chat->FormatQuest(qInfo));
|
||||||
out << "Got quest " << chat->FormatQuest(qInfo);
|
|
||||||
botAI->TellMasterNoFacing(out.str());
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -217,7 +220,7 @@ bool UseItemAction::UseItem(Item* item, ObjectGuid goGuid, Item* itemTarget, Uni
|
|||||||
targetFlag = TARGET_FLAG_TRADE_ITEM;
|
targetFlag = TARGET_FLAG_TRADE_ITEM;
|
||||||
packet << targetFlag << (uint8)1 << ObjectGuid((uint64)TRADE_SLOT_NONTRADED).WriteAsPacked();
|
packet << targetFlag << (uint8)1 << ObjectGuid((uint64)TRADE_SLOT_NONTRADED).WriteAsPacked();
|
||||||
targetSelected = true;
|
targetSelected = true;
|
||||||
out << " on traded item";
|
targetText = "traded item";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -225,7 +228,7 @@ bool UseItemAction::UseItem(Item* item, ObjectGuid goGuid, Item* itemTarget, Uni
|
|||||||
packet << targetFlag;
|
packet << targetFlag;
|
||||||
packet << itemForSpell->GetGUID().WriteAsPacked();
|
packet << itemForSpell->GetGUID().WriteAsPacked();
|
||||||
targetSelected = true;
|
targetSelected = true;
|
||||||
out << " on " << chat->FormatItem(itemForSpell->GetTemplate());
|
targetText = chat->FormatItem(itemForSpell->GetTemplate());
|
||||||
}
|
}
|
||||||
uint32 castTime = spellInfo->CalcCastTime();
|
uint32 castTime = spellInfo->CalcCastTime();
|
||||||
botAI->SetNextCheckDelay(castTime + sPlayerbotAIConfig.reactDelay);
|
botAI->SetNextCheckDelay(castTime + sPlayerbotAIConfig.reactDelay);
|
||||||
@ -246,17 +249,17 @@ bool UseItemAction::UseItem(Item* item, ObjectGuid goGuid, Item* itemTarget, Uni
|
|||||||
targetSelected = true;
|
targetSelected = true;
|
||||||
|
|
||||||
if (unitTarget == bot || !unitTarget->IsInWorld() || unitTarget->IsDuringRemoveFromWorld())
|
if (unitTarget == bot || !unitTarget->IsInWorld() || unitTarget->IsDuringRemoveFromWorld())
|
||||||
out << " on self";
|
targetText = "self";
|
||||||
else if (unitTarget->IsHostileTo(bot))
|
else if (unitTarget->IsHostileTo(bot))
|
||||||
out << " on self";
|
targetText = "self";
|
||||||
else
|
else
|
||||||
out << " on " << unitTarget->GetName();
|
targetText = unitTarget->GetName();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
packet << bot->GetPackGUID();
|
packet << bot->GetPackGUID();
|
||||||
targetSelected = true;
|
targetSelected = true;
|
||||||
out << " on self";
|
targetText = "self";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -307,7 +310,12 @@ bool UseItemAction::UseItem(Item* item, ObjectGuid goGuid, Item* itemTarget, Uni
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
// botAI->SetNextCheckDelay(sPlayerbotAIConfig.globalCoolDown);
|
// botAI->SetNextCheckDelay(sPlayerbotAIConfig.globalCoolDown);
|
||||||
botAI->TellMasterNoFacing(out.str());
|
std::string useText = targetSelected
|
||||||
|
? PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"use_item_on_target", "Using %item on %target", {{"%item", itemText}, {"%target", targetText}})
|
||||||
|
: PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"use_item", "Using %item", {{"%item", itemText}});
|
||||||
|
botAI->TellMasterNoFacing(useText);
|
||||||
bot->GetSession()->HandleUseItemOpcode(packet);
|
bot->GetSession()->HandleUseItemOpcode(packet);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -372,10 +380,10 @@ bool UseItemAction::SocketItem(Item* item, Item* gem, bool replace)
|
|||||||
|
|
||||||
if (fits)
|
if (fits)
|
||||||
{
|
{
|
||||||
std::ostringstream out;
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
out << "Socketing " << chat->FormatItem(item->GetTemplate());
|
"socketing_item_with_gem",
|
||||||
out << " with " << chat->FormatItem(gem->GetTemplate());
|
"Socketing %item with %gem",
|
||||||
botAI->TellMaster(out);
|
{{"%item", chat->FormatItem(item->GetTemplate())}, {"%gem", chat->FormatItem(gem->GetTemplate())}}));
|
||||||
|
|
||||||
WorldPackets::Item::SocketGems nicePacket(std::move(packet));
|
WorldPackets::Item::SocketGems nicePacket(std::move(packet));
|
||||||
nicePacket.Read();
|
nicePacket.Read();
|
||||||
|
|||||||
@ -11,6 +11,7 @@
|
|||||||
#include "GridNotifiersImpl.h"
|
#include "GridNotifiersImpl.h"
|
||||||
#include "NearestGameObjects.h"
|
#include "NearestGameObjects.h"
|
||||||
#include "PlayerbotAIConfig.h"
|
#include "PlayerbotAIConfig.h"
|
||||||
|
#include "PlayerbotTextMgr.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
#include "PositionValue.h"
|
#include "PositionValue.h"
|
||||||
|
|
||||||
@ -36,7 +37,8 @@ bool UseMeetingStoneAction::Execute(Event event)
|
|||||||
|
|
||||||
if (bot->IsInCombat())
|
if (bot->IsInCombat())
|
||||||
{
|
{
|
||||||
botAI->TellError("I am in combat");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"meeting_stone_in_combat", "I am in combat", {}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,13 +75,15 @@ bool SummonAction::Execute(Event /*event*/)
|
|||||||
|
|
||||||
if (SummonUsingGos(master, bot, true) || SummonUsingNpcs(master, bot, true))
|
if (SummonUsingGos(master, bot, true) || SummonUsingNpcs(master, bot, true))
|
||||||
{
|
{
|
||||||
botAI->TellMasterNoFacing("Hello!");
|
botAI->TellMasterNoFacing(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"hello", "Hello!", {}));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SummonUsingGos(bot, master, true) || SummonUsingNpcs(bot, master, true))
|
if (SummonUsingGos(bot, master, true) || SummonUsingNpcs(bot, master, true))
|
||||||
{
|
{
|
||||||
botAI->TellMasterNoFacing("Welcome!");
|
botAI->TellMasterNoFacing(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"meeting_stone_welcome", "Welcome!", {}));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,7 +103,10 @@ bool SummonAction::SummonUsingGos(Player* summoner, Player* player, bool preserv
|
|||||||
return Teleport(summoner, player, preserveAuras);
|
return Teleport(summoner, player, preserveAuras);
|
||||||
}
|
}
|
||||||
|
|
||||||
botAI->TellError(summoner == bot ? "There is no meeting stone nearby" : "There is no meeting stone near you");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
summoner == bot ? "meeting_stone_none_nearby" : "meeting_stone_none_near_you",
|
||||||
|
summoner == bot ? "There is no meeting stone nearby" : "There is no meeting stone near you",
|
||||||
|
{}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,13 +126,19 @@ bool SummonAction::SummonUsingNpcs(Player* summoner, Player* player, bool preser
|
|||||||
{
|
{
|
||||||
if (!player->HasItemCount(6948, 1, false))
|
if (!player->HasItemCount(6948, 1, false))
|
||||||
{
|
{
|
||||||
botAI->TellError(player == bot ? "I have no hearthstone" : "You have no hearthstone");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
player == bot ? "meeting_stone_no_hearthstone_self" : "meeting_stone_no_hearthstone_you",
|
||||||
|
player == bot ? "I have no hearthstone" : "You have no hearthstone",
|
||||||
|
{}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (player->HasSpellCooldown(8690))
|
if (player->HasSpellCooldown(8690))
|
||||||
{
|
{
|
||||||
botAI->TellError(player == bot ? "My hearthstone is not ready" : "Your hearthstone is not ready");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
player == bot ? "meeting_stone_hearthstone_not_ready_self" : "meeting_stone_hearthstone_not_ready_you",
|
||||||
|
player == bot ? "My hearthstone is not ready" : "Your hearthstone is not ready",
|
||||||
|
{}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,7 +154,10 @@ bool SummonAction::SummonUsingNpcs(Player* summoner, Player* player, bool preser
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
botAI->TellError(summoner == bot ? "There are no innkeepers nearby" : "There are no innkeepers near you");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
summoner == bot ? "meeting_stone_no_innkeepers_nearby" : "meeting_stone_no_innkeepers_near_you",
|
||||||
|
summoner == bot ? "There are no innkeepers nearby" : "There are no innkeepers near you",
|
||||||
|
{}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,7 +169,8 @@ bool SummonAction::Teleport(Player* summoner, Player* player, bool preserveAuras
|
|||||||
|
|
||||||
if (player->GetVehicle())
|
if (player->GetVehicle())
|
||||||
{
|
{
|
||||||
botAI->TellError("You cannot summon me while I'm on a vehicle");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"meeting_stone_cannot_summon_vehicle", "You cannot summon me while I'm on a vehicle", {}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -174,20 +191,29 @@ bool SummonAction::Teleport(Player* summoner, Player* player, bool preserveAuras
|
|||||||
|
|
||||||
if (summoner->IsInCombat() && !sPlayerbotAIConfig.allowSummonInCombat)
|
if (summoner->IsInCombat() && !sPlayerbotAIConfig.allowSummonInCombat)
|
||||||
{
|
{
|
||||||
botAI->TellError("You cannot summon me while you're in combat");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"meeting_stone_cannot_summon_master_in_combat",
|
||||||
|
"You cannot summon me while you're in combat",
|
||||||
|
{}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!summoner->IsAlive() && !sPlayerbotAIConfig.allowSummonWhenMasterIsDead)
|
if (!summoner->IsAlive() && !sPlayerbotAIConfig.allowSummonWhenMasterIsDead)
|
||||||
{
|
{
|
||||||
botAI->TellError("You cannot summon me while you're dead");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"meeting_stone_cannot_summon_master_dead",
|
||||||
|
"You cannot summon me while you're dead",
|
||||||
|
{}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bot->isDead() && !bot->HasPlayerFlag(PLAYER_FLAGS_GHOST) &&
|
if (bot->isDead() && !bot->HasPlayerFlag(PLAYER_FLAGS_GHOST) &&
|
||||||
!sPlayerbotAIConfig.allowSummonWhenBotIsDead)
|
!sPlayerbotAIConfig.allowSummonWhenBotIsDead)
|
||||||
{
|
{
|
||||||
botAI->TellError("You cannot summon me while I'm dead, you need to release my spirit first");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"meeting_stone_cannot_summon_bot_dead",
|
||||||
|
"You cannot summon me while I'm dead, you need to release my spirit first",
|
||||||
|
{}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,7 +225,8 @@ bool SummonAction::Teleport(Player* summoner, Player* player, bool preserveAuras
|
|||||||
{
|
{
|
||||||
bot->ResurrectPlayer(1.0f, false);
|
bot->ResurrectPlayer(1.0f, false);
|
||||||
bot->SpawnCorpseBones();
|
bot->SpawnCorpseBones();
|
||||||
botAI->TellMasterNoFacing("I live, again!");
|
botAI->TellMasterNoFacing(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"meeting_stone_revived", "I live, again!", {}));
|
||||||
botAI->GetAiObjectContext()->GetValue<GuidVector>("prioritized targets")->Reset();
|
botAI->GetAiObjectContext()->GetValue<GuidVector>("prioritized targets")->Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -229,6 +256,7 @@ bool SummonAction::Teleport(Player* summoner, Player* player, bool preserveAuras
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (summoner != player)
|
if (summoner != player)
|
||||||
botAI->TellError("Not enough place to summon");
|
botAI->TellError(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"meeting_stone_not_enough_space", "Not enough place to summon", {}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -139,6 +139,7 @@ public:
|
|||||||
creators["maintenance"] = &ChatActionContext::maintenance;
|
creators["maintenance"] = &ChatActionContext::maintenance;
|
||||||
creators["remove glyph"] = &ChatActionContext::remove_glyph;
|
creators["remove glyph"] = &ChatActionContext::remove_glyph;
|
||||||
creators["autogear"] = &ChatActionContext::autogear;
|
creators["autogear"] = &ChatActionContext::autogear;
|
||||||
|
creators["autogear bis"] = &ChatActionContext::autogear_bis;
|
||||||
creators["equip upgrade"] = &ChatActionContext::equip_upgrade;
|
creators["equip upgrade"] = &ChatActionContext::equip_upgrade;
|
||||||
creators["attack my target"] = &ChatActionContext::attack_my_target;
|
creators["attack my target"] = &ChatActionContext::attack_my_target;
|
||||||
creators["pull my target"] = &ChatActionContext::pull_my_target;
|
creators["pull my target"] = &ChatActionContext::pull_my_target;
|
||||||
@ -261,6 +262,7 @@ private:
|
|||||||
static Action* maintenance(PlayerbotAI* botAI) { return new MaintenanceAction(botAI); }
|
static Action* maintenance(PlayerbotAI* botAI) { return new MaintenanceAction(botAI); }
|
||||||
static Action* remove_glyph(PlayerbotAI* botAI) { return new RemoveGlyphAction(botAI); }
|
static Action* remove_glyph(PlayerbotAI* botAI) { return new RemoveGlyphAction(botAI); }
|
||||||
static Action* autogear(PlayerbotAI* botAI) { return new AutoGearAction(botAI); }
|
static Action* autogear(PlayerbotAI* botAI) { return new AutoGearAction(botAI); }
|
||||||
|
static Action* autogear_bis(PlayerbotAI* botAI) { return new BisGearAction(botAI); }
|
||||||
static Action* equip_upgrade(PlayerbotAI* botAI) { return new EquipUpgradeAction(botAI); }
|
static Action* equip_upgrade(PlayerbotAI* botAI) { return new EquipUpgradeAction(botAI); }
|
||||||
static Action* co(PlayerbotAI* botAI) { return new ChangeCombatStrategyAction(botAI); }
|
static Action* co(PlayerbotAI* botAI) { return new ChangeCombatStrategyAction(botAI); }
|
||||||
static Action* nc(PlayerbotAI* botAI) { return new ChangeNonCombatStrategyAction(botAI); }
|
static Action* nc(PlayerbotAI* botAI) { return new ChangeNonCombatStrategyAction(botAI); }
|
||||||
|
|||||||
@ -65,6 +65,7 @@ public:
|
|||||||
creators["maintenance"] = &ChatTriggerContext::maintenance;
|
creators["maintenance"] = &ChatTriggerContext::maintenance;
|
||||||
creators["remove glyph"] = &ChatTriggerContext::remove_glyph;
|
creators["remove glyph"] = &ChatTriggerContext::remove_glyph;
|
||||||
creators["autogear"] = &ChatTriggerContext::autogear;
|
creators["autogear"] = &ChatTriggerContext::autogear;
|
||||||
|
creators["autogear bis"] = &ChatTriggerContext::autogear_bis;
|
||||||
creators["equip upgrade"] = &ChatTriggerContext::equip_upgrade;
|
creators["equip upgrade"] = &ChatTriggerContext::equip_upgrade;
|
||||||
creators["attack"] = &ChatTriggerContext::attack;
|
creators["attack"] = &ChatTriggerContext::attack;
|
||||||
creators["pull"] = &ChatTriggerContext::pull;
|
creators["pull"] = &ChatTriggerContext::pull;
|
||||||
@ -220,6 +221,7 @@ private:
|
|||||||
static Trigger* maintenance(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "maintenance"); }
|
static Trigger* maintenance(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "maintenance"); }
|
||||||
static Trigger* remove_glyph(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "remove glyph"); }
|
static Trigger* remove_glyph(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "remove glyph"); }
|
||||||
static Trigger* autogear(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "autogear"); }
|
static Trigger* autogear(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "autogear"); }
|
||||||
|
static Trigger* autogear_bis(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "autogear bis"); }
|
||||||
static Trigger* equip_upgrade(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "equip upgrade"); }
|
static Trigger* equip_upgrade(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "equip upgrade"); }
|
||||||
static Trigger* co(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "co"); }
|
static Trigger* co(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "co"); }
|
||||||
static Trigger* nc(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "nc"); }
|
static Trigger* nc(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "nc"); }
|
||||||
|
|||||||
@ -111,6 +111,7 @@ ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : Pas
|
|||||||
supported.push_back("maintenance");
|
supported.push_back("maintenance");
|
||||||
supported.push_back("remove glyph");
|
supported.push_back("remove glyph");
|
||||||
supported.push_back("autogear");
|
supported.push_back("autogear");
|
||||||
|
supported.push_back("autogear bis");
|
||||||
supported.push_back("equip upgrade");
|
supported.push_back("equip upgrade");
|
||||||
supported.push_back("chat");
|
supported.push_back("chat");
|
||||||
supported.push_back("home");
|
supported.push_back("home");
|
||||||
|
|||||||
@ -41,6 +41,7 @@ void GenericDKNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& trigger
|
|||||||
{
|
{
|
||||||
NonCombatStrategy::InitTriggers(triggers);
|
NonCombatStrategy::InitTriggers(triggers);
|
||||||
|
|
||||||
|
triggers.push_back(new TriggerNode("often", { NextAction("apply stone", 1.0f) }));
|
||||||
triggers.push_back(
|
triggers.push_back(
|
||||||
new TriggerNode("horn of winter", { NextAction("horn of winter", 21.0f) }));
|
new TriggerNode("horn of winter", { NextAction("horn of winter", 21.0f) }));
|
||||||
triggers.push_back(
|
triggers.push_back(
|
||||||
|
|||||||
@ -11,6 +11,22 @@
|
|||||||
#include "ServerFacade.h"
|
#include "ServerFacade.h"
|
||||||
#include "SharedDefines.h"
|
#include "SharedDefines.h"
|
||||||
|
|
||||||
|
std::vector<NextAction> CastMoltenArmorAction::getAlternatives()
|
||||||
|
{
|
||||||
|
if (!AI_VALUE2(uint32, "spell id", "molten armor"))
|
||||||
|
return NextAction::merge({ NextAction("mage armor") }, CastBuffSpellAction::getAlternatives());
|
||||||
|
|
||||||
|
return CastBuffSpellAction::getAlternatives();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<NextAction> CastMageArmorAction::getAlternatives()
|
||||||
|
{
|
||||||
|
if (!AI_VALUE2(uint32, "spell id", "mage armor"))
|
||||||
|
return NextAction::merge({ NextAction("ice armor") }, CastBuffSpellAction::getAlternatives());
|
||||||
|
|
||||||
|
return CastBuffSpellAction::getAlternatives();
|
||||||
|
}
|
||||||
|
|
||||||
bool UseManaSapphireAction::isUseful()
|
bool UseManaSapphireAction::isUseful()
|
||||||
{
|
{
|
||||||
Player* bot = botAI->GetBot();
|
Player* bot = botAI->GetBot();
|
||||||
@ -50,22 +66,23 @@ bool UseManaAgateAction::isUseful()
|
|||||||
bool CastFrostNovaAction::isUseful()
|
bool CastFrostNovaAction::isUseful()
|
||||||
{
|
{
|
||||||
Unit* target = AI_VALUE(Unit*, "current target");
|
Unit* target = AI_VALUE(Unit*, "current target");
|
||||||
if (!target || !target->IsInWorld())
|
if (!target || !target->IsInWorld() || target->isFrozen() ||
|
||||||
|
(target->ToCreature() &&
|
||||||
|
target->ToCreature()->HasMechanicTemplateImmunity(1 << (MECHANIC_FREEZE - 1))))
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (target->ToCreature() && target->ToCreature()->HasMechanicTemplateImmunity(1 << (MECHANIC_FREEZE - 1)))
|
return ServerFacade::instance().IsDistanceLessOrEqualThan(
|
||||||
return false;
|
AI_VALUE2(float, "distance", GetTargetName()), 10.f);
|
||||||
|
|
||||||
if (target->isFrozen())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return ServerFacade::instance().IsDistanceLessOrEqualThan(AI_VALUE2(float, "distance", GetTargetName()), 10.f);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CastConeOfColdAction::isUseful()
|
bool CastConeOfColdAction::isUseful()
|
||||||
{
|
{
|
||||||
bool facingTarget = AI_VALUE2(bool, "facing", "current target");
|
bool facingTarget = AI_VALUE2(bool, "facing", "current target");
|
||||||
bool targetClose = ServerFacade::instance().IsDistanceLessOrEqualThan(AI_VALUE2(float, "distance", GetTargetName()), 10.f);
|
bool targetClose = ServerFacade::instance().IsDistanceLessOrEqualThan(
|
||||||
|
AI_VALUE2(float, "distance", GetTargetName()), 10.f);
|
||||||
|
|
||||||
return facingTarget && targetClose;
|
return facingTarget && targetClose;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,6 +91,7 @@ bool CastDragonsBreathAction::isUseful()
|
|||||||
Unit* target = AI_VALUE(Unit*, "current target");
|
Unit* target = AI_VALUE(Unit*, "current target");
|
||||||
if (!target)
|
if (!target)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
bool facingTarget = AI_VALUE2(bool, "facing", "current target");
|
bool facingTarget = AI_VALUE2(bool, "facing", "current target");
|
||||||
bool targetClose = bot->IsWithinCombatRange(target, 10.0f);
|
bool targetClose = bot->IsWithinCombatRange(target, 10.0f);
|
||||||
return facingTarget && targetClose;
|
return facingTarget && targetClose;
|
||||||
@ -84,6 +102,7 @@ bool CastBlastWaveAction::isUseful()
|
|||||||
Unit* target = AI_VALUE(Unit*, "current target");
|
Unit* target = AI_VALUE(Unit*, "current target");
|
||||||
if (!target)
|
if (!target)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
bool targetClose = bot->IsWithinCombatRange(target, 10.0f);
|
bool targetClose = bot->IsWithinCombatRange(target, 10.0f);
|
||||||
return targetClose;
|
return targetClose;
|
||||||
}
|
}
|
||||||
@ -100,14 +119,11 @@ Unit* CastFocusMagicOnPartyAction::GetTarget()
|
|||||||
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
||||||
{
|
{
|
||||||
Player* member = ref->GetSource();
|
Player* member = ref->GetSource();
|
||||||
if (!member || member == bot || !member->IsAlive())
|
if (!member || member == bot || !member->IsAlive() || member->GetMap() != bot->GetMap() ||
|
||||||
continue;
|
bot->GetDistance(member) > sPlayerbotAIConfig.spellDistance || member->HasAura(54646)) // Focus Magic
|
||||||
|
{
|
||||||
if (member->GetMap() != bot->GetMap() || bot->GetDistance(member) > sPlayerbotAIConfig.spellDistance)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (member->HasAura(54646))
|
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (member->getClass() == CLASS_MAGE)
|
if (member->getClass() == CLASS_MAGE)
|
||||||
return member;
|
return member;
|
||||||
@ -136,7 +152,7 @@ bool CastBlinkBackAction::Execute(Event event)
|
|||||||
Unit* target = AI_VALUE(Unit*, "current target");
|
Unit* target = AI_VALUE(Unit*, "current target");
|
||||||
if (!target)
|
if (!target)
|
||||||
return false;
|
return false;
|
||||||
// can cast spell check passed in isUseful()
|
|
||||||
bot->SetOrientation(bot->GetAngle(target) + M_PI);
|
bot->SetOrientation(bot->GetAngle(target) + M_PI);
|
||||||
return CastSpellAction::Execute(event);
|
return CastSpellAction::Execute(event);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,12 +18,14 @@ class CastMoltenArmorAction : public CastBuffSpellAction
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CastMoltenArmorAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "molten armor") {}
|
CastMoltenArmorAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "molten armor") {}
|
||||||
|
std::vector<NextAction> getAlternatives() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
class CastMageArmorAction : public CastBuffSpellAction
|
class CastMageArmorAction : public CastBuffSpellAction
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CastMageArmorAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "mage armor") {}
|
CastMageArmorAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "mage armor") {}
|
||||||
|
std::vector<NextAction> getAlternatives() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
class CastIceArmorAction : public CastBuffSpellAction
|
class CastIceArmorAction : public CastBuffSpellAction
|
||||||
@ -60,7 +62,8 @@ public:
|
|||||||
class CastSummonWaterElementalAction : public CastBuffSpellAction
|
class CastSummonWaterElementalAction : public CastBuffSpellAction
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CastSummonWaterElementalAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "summon water elemental") {}
|
CastSummonWaterElementalAction(PlayerbotAI* botAI)
|
||||||
|
: CastBuffSpellAction(botAI, "summon water elemental") {}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Boost Actions
|
// Boost Actions
|
||||||
@ -236,7 +239,8 @@ public:
|
|||||||
class CastCounterspellOnEnemyHealerAction : public CastSpellOnEnemyHealerAction
|
class CastCounterspellOnEnemyHealerAction : public CastSpellOnEnemyHealerAction
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CastCounterspellOnEnemyHealerAction(PlayerbotAI* botAI) : CastSpellOnEnemyHealerAction(botAI, "counterspell") {}
|
CastCounterspellOnEnemyHealerAction(PlayerbotAI* botAI)
|
||||||
|
: CastSpellOnEnemyHealerAction(botAI, "counterspell") {}
|
||||||
};
|
};
|
||||||
|
|
||||||
class CastFrostNovaAction : public CastSpellAction
|
class CastFrostNovaAction : public CastSpellAction
|
||||||
@ -275,9 +279,7 @@ class CastRemoveLesserCurseOnPartyAction : public CurePartyMemberAction
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CastRemoveLesserCurseOnPartyAction(PlayerbotAI* botAI)
|
CastRemoveLesserCurseOnPartyAction(PlayerbotAI* botAI)
|
||||||
: CurePartyMemberAction(botAI, "remove lesser curse", DISPEL_CURSE)
|
: CurePartyMemberAction(botAI, "remove lesser curse", DISPEL_CURSE) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Damage and Debuff Actions
|
// Damage and Debuff Actions
|
||||||
@ -331,7 +333,6 @@ public:
|
|||||||
CastLivingBombAction(PlayerbotAI* botAI) : CastDebuffSpellAction(botAI, "living bomb", true) {}
|
CastLivingBombAction(PlayerbotAI* botAI) : CastDebuffSpellAction(botAI, "living bomb", true) {}
|
||||||
bool isUseful() override
|
bool isUseful() override
|
||||||
{
|
{
|
||||||
// Bypass TTL check
|
|
||||||
return CastAuraSpellAction::isUseful();
|
return CastAuraSpellAction::isUseful();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -342,7 +343,6 @@ public:
|
|||||||
CastLivingBombOnAttackersAction(PlayerbotAI* botAI) : CastDebuffSpellOnAttackerAction(botAI, "living bomb", true) {}
|
CastLivingBombOnAttackersAction(PlayerbotAI* botAI) : CastDebuffSpellOnAttackerAction(botAI, "living bomb", true) {}
|
||||||
bool isUseful() override
|
bool isUseful() override
|
||||||
{
|
{
|
||||||
// Bypass TTL check
|
|
||||||
return CastAuraSpellAction::isUseful();
|
return CastAuraSpellAction::isUseful();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -89,6 +89,7 @@ public:
|
|||||||
creators["arcane intellect"] = &MageTriggerFactoryInternal::arcane_intellect;
|
creators["arcane intellect"] = &MageTriggerFactoryInternal::arcane_intellect;
|
||||||
creators["arcane intellect on party"] = &MageTriggerFactoryInternal::arcane_intellect_on_party;
|
creators["arcane intellect on party"] = &MageTriggerFactoryInternal::arcane_intellect_on_party;
|
||||||
creators["mage armor"] = &MageTriggerFactoryInternal::mage_armor;
|
creators["mage armor"] = &MageTriggerFactoryInternal::mage_armor;
|
||||||
|
creators["molten armor"] = &MageTriggerFactoryInternal::molten_armor;
|
||||||
creators["remove curse"] = &MageTriggerFactoryInternal::remove_curse;
|
creators["remove curse"] = &MageTriggerFactoryInternal::remove_curse;
|
||||||
creators["remove curse on party"] = &MageTriggerFactoryInternal::remove_curse_on_party;
|
creators["remove curse on party"] = &MageTriggerFactoryInternal::remove_curse_on_party;
|
||||||
creators["counterspell"] = &MageTriggerFactoryInternal::counterspell;
|
creators["counterspell"] = &MageTriggerFactoryInternal::counterspell;
|
||||||
@ -143,6 +144,7 @@ private:
|
|||||||
static Trigger* arcane_intellect(PlayerbotAI* botAI) { return new ArcaneIntellectTrigger(botAI); }
|
static Trigger* arcane_intellect(PlayerbotAI* botAI) { return new ArcaneIntellectTrigger(botAI); }
|
||||||
static Trigger* arcane_intellect_on_party(PlayerbotAI* botAI) { return new ArcaneIntellectOnPartyTrigger(botAI); }
|
static Trigger* arcane_intellect_on_party(PlayerbotAI* botAI) { return new ArcaneIntellectOnPartyTrigger(botAI); }
|
||||||
static Trigger* mage_armor(PlayerbotAI* botAI) { return new MageArmorTrigger(botAI); }
|
static Trigger* mage_armor(PlayerbotAI* botAI) { return new MageArmorTrigger(botAI); }
|
||||||
|
static Trigger* molten_armor(PlayerbotAI* botAI) { return new MoltenArmorTrigger(botAI); }
|
||||||
static Trigger* remove_curse(PlayerbotAI* botAI) { return new RemoveCurseTrigger(botAI); }
|
static Trigger* remove_curse(PlayerbotAI* botAI) { return new RemoveCurseTrigger(botAI); }
|
||||||
static Trigger* remove_curse_on_party(PlayerbotAI* botAI) { return new PartyMemberRemoveCurseTrigger(botAI); }
|
static Trigger* remove_curse_on_party(PlayerbotAI* botAI) { return new PartyMemberRemoveCurseTrigger(botAI); }
|
||||||
static Trigger* counterspell(PlayerbotAI* botAI) { return new CounterspellInterruptSpellTrigger(botAI); }
|
static Trigger* counterspell(PlayerbotAI* botAI) { return new CounterspellInterruptSpellTrigger(botAI); }
|
||||||
|
|||||||
@ -13,22 +13,18 @@ public:
|
|||||||
ArcaneMageStrategyActionNodeFactory()
|
ArcaneMageStrategyActionNodeFactory()
|
||||||
{
|
{
|
||||||
creators["arcane blast"] = &arcane_blast;
|
creators["arcane blast"] = &arcane_blast;
|
||||||
creators["arcane barrage"] = &arcane_barrage;
|
|
||||||
creators["arcane missiles"] = &arcane_missiles;
|
|
||||||
creators["fire blast"] = &fire_blast;
|
|
||||||
creators["frostbolt"] = &frostbolt;
|
|
||||||
creators["arcane power"] = &arcane_power;
|
|
||||||
creators["icy veins"] = &icy_veins;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static ActionNode* arcane_blast(PlayerbotAI*) { return new ActionNode("arcane blast", {}, {}, {}); }
|
// Arcane Barrage is the alternate for Arcane Blast (cast while moving, or
|
||||||
static ActionNode* arcane_barrage(PlayerbotAI*) { return new ActionNode("arcane barrage", {}, {}, {}); }
|
// when Arcane Blast is unavailable - e.g. not yet learned at low levels).
|
||||||
static ActionNode* arcane_missiles(PlayerbotAI*) { return new ActionNode("arcane missiles", {}, {}, {}); }
|
static ActionNode* arcane_blast([[maybe_unused]] PlayerbotAI* botAI)
|
||||||
static ActionNode* fire_blast(PlayerbotAI*) { return new ActionNode("fire blast", {}, {}, {}); }
|
{
|
||||||
static ActionNode* frostbolt(PlayerbotAI*) { return new ActionNode("frostbolt", {}, {}, {}); }
|
return new ActionNode("arcane blast",
|
||||||
static ActionNode* arcane_power(PlayerbotAI*) { return new ActionNode("arcane power", {}, {}, {}); }
|
/*P*/ {},
|
||||||
static ActionNode* icy_veins(PlayerbotAI*) { return new ActionNode("icy veins", {}, {}, {}); }
|
/*A*/ { NextAction("arcane barrage") },
|
||||||
|
/*C*/ {});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// ===== Single Target Strategy =====
|
// ===== Single Target Strategy =====
|
||||||
|
|||||||
@ -7,35 +7,9 @@
|
|||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
#include "Strategy.h"
|
#include "Strategy.h"
|
||||||
|
|
||||||
// ===== Action Node Factory =====
|
|
||||||
class FireMageStrategyActionNodeFactory : public NamedObjectFactory<ActionNode>
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
FireMageStrategyActionNodeFactory()
|
|
||||||
{
|
|
||||||
creators["fireball"] = &fireball;
|
|
||||||
creators["frostbolt"] = &frostbolt;
|
|
||||||
creators["fire blast"] = &fire_blast;
|
|
||||||
creators["pyroblast"] = &pyroblast;
|
|
||||||
creators["scorch"] = &scorch;
|
|
||||||
creators["living bomb"] = &living_bomb;
|
|
||||||
creators["combustion"] = &combustion;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
static ActionNode* fireball(PlayerbotAI*) { return new ActionNode("fireball", {}, {}, {}); }
|
|
||||||
static ActionNode* frostbolt(PlayerbotAI*) { return new ActionNode("frostbolt", {}, {}, {}); }
|
|
||||||
static ActionNode* fire_blast(PlayerbotAI*) { return new ActionNode("fire blast", {}, {}, {}); }
|
|
||||||
static ActionNode* pyroblast(PlayerbotAI*) { return new ActionNode("pyroblast", {}, {}, {}); }
|
|
||||||
static ActionNode* scorch(PlayerbotAI*) { return new ActionNode("scorch", {}, {}, {}); }
|
|
||||||
static ActionNode* living_bomb(PlayerbotAI*) { return new ActionNode("living bomb", {}, {}, {}); }
|
|
||||||
static ActionNode* combustion(PlayerbotAI*) { return new ActionNode("combustion", {}, {}, {}); }
|
|
||||||
};
|
|
||||||
|
|
||||||
// ===== Single Target Strategy =====
|
|
||||||
FireMageStrategy::FireMageStrategy(PlayerbotAI* botAI) : GenericMageStrategy(botAI)
|
FireMageStrategy::FireMageStrategy(PlayerbotAI* botAI) : GenericMageStrategy(botAI)
|
||||||
{
|
{
|
||||||
actionNodeFactories.Add(new FireMageStrategyActionNodeFactory());
|
// No custom ActionNodeFactory needed
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== Default Actions =====
|
// ===== Default Actions =====
|
||||||
|
|||||||
@ -28,4 +28,5 @@ public:
|
|||||||
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
|
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
|
||||||
std::string const getName() override { return "firestarter"; }
|
std::string const getName() override { return "firestarter"; }
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -6,35 +6,9 @@
|
|||||||
#include "FrostFireMageStrategy.h"
|
#include "FrostFireMageStrategy.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
|
|
||||||
// ===== Action Node Factory =====
|
|
||||||
class FrostFireMageStrategyActionNodeFactory : public NamedObjectFactory<ActionNode>
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
FrostFireMageStrategyActionNodeFactory()
|
|
||||||
{
|
|
||||||
creators["frostfire bolt"] = &frostfire_bolt;
|
|
||||||
creators["fire blast"] = &fire_blast;
|
|
||||||
creators["pyroblast"] = &pyroblast;
|
|
||||||
creators["combustion"] = &combustion;
|
|
||||||
creators["icy veins"] = &icy_veins;
|
|
||||||
creators["scorch"] = &scorch;
|
|
||||||
creators["living bomb"] = &living_bomb;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
static ActionNode* frostfire_bolt(PlayerbotAI*) { return new ActionNode("frostfire bolt", {}, {}, {}); }
|
|
||||||
static ActionNode* fire_blast(PlayerbotAI*) { return new ActionNode("fire blast", {}, {}, {}); }
|
|
||||||
static ActionNode* pyroblast(PlayerbotAI*) { return new ActionNode("pyroblast", {}, {}, {}); }
|
|
||||||
static ActionNode* combustion(PlayerbotAI*) { return new ActionNode("combustion", {}, {}, {}); }
|
|
||||||
static ActionNode* icy_veins(PlayerbotAI*) { return new ActionNode("icy veins", {}, {}, {}); }
|
|
||||||
static ActionNode* scorch(PlayerbotAI*) { return new ActionNode("scorch", {}, {}, {}); }
|
|
||||||
static ActionNode* living_bomb(PlayerbotAI*) { return new ActionNode("living bomb", {}, {}, {}); }
|
|
||||||
};
|
|
||||||
|
|
||||||
// ===== Single Target Strategy =====
|
|
||||||
FrostFireMageStrategy::FrostFireMageStrategy(PlayerbotAI* botAI) : GenericMageStrategy(botAI)
|
FrostFireMageStrategy::FrostFireMageStrategy(PlayerbotAI* botAI) : GenericMageStrategy(botAI)
|
||||||
{
|
{
|
||||||
actionNodeFactories.Add(new FrostFireMageStrategyActionNodeFactory());
|
// No custom ActionNodeFactory needed
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== Default Actions =====
|
// ===== Default Actions =====
|
||||||
|
|||||||
@ -4,44 +4,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "FrostMageStrategy.h"
|
#include "FrostMageStrategy.h"
|
||||||
|
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
|
|
||||||
// ===== Action Node Factory =====
|
|
||||||
class FrostMageStrategyActionNodeFactory : public NamedObjectFactory<ActionNode>
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
FrostMageStrategyActionNodeFactory()
|
|
||||||
{
|
|
||||||
creators["cold snap"] = &cold_snap;
|
|
||||||
creators["ice barrier"] = &ice_barrier;
|
|
||||||
creators["summon water elemental"] = &summon_water_elemental;
|
|
||||||
creators["deep freeze"] = &deep_freeze;
|
|
||||||
creators["icy veins"] = &icy_veins;
|
|
||||||
creators["frostbolt"] = &frostbolt;
|
|
||||||
creators["ice lance"] = &ice_lance;
|
|
||||||
creators["fire blast"] = &fire_blast;
|
|
||||||
creators["fireball"] = &fireball;
|
|
||||||
creators["frostfire bolt"] = &frostfire_bolt;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
static ActionNode* cold_snap(PlayerbotAI*) { return new ActionNode("cold snap", {}, {}, {}); }
|
|
||||||
static ActionNode* ice_barrier(PlayerbotAI*) { return new ActionNode("ice barrier", {}, {}, {}); }
|
|
||||||
static ActionNode* summon_water_elemental(PlayerbotAI*) { return new ActionNode("summon water elemental", {}, {}, {}); }
|
|
||||||
static ActionNode* deep_freeze(PlayerbotAI*) { return new ActionNode("deep freeze", {}, {}, {}); }
|
|
||||||
static ActionNode* icy_veins(PlayerbotAI*) { return new ActionNode("icy veins", {}, {}, {}); }
|
|
||||||
static ActionNode* frostbolt(PlayerbotAI*) { return new ActionNode("frostbolt", {}, {}, {}); }
|
|
||||||
static ActionNode* ice_lance(PlayerbotAI*) { return new ActionNode("ice lance", {}, {}, {}); }
|
|
||||||
static ActionNode* fire_blast(PlayerbotAI*) { return new ActionNode("fire blast", {}, {}, {}); }
|
|
||||||
static ActionNode* fireball(PlayerbotAI*) { return new ActionNode("fireball", {}, {}, {}); }
|
|
||||||
static ActionNode* frostfire_bolt(PlayerbotAI*) { return new ActionNode("frostfire bolt", {}, { NextAction("fireball") }, {}); }
|
|
||||||
};
|
|
||||||
|
|
||||||
// ===== Single Target Strategy =====
|
|
||||||
FrostMageStrategy::FrostMageStrategy(PlayerbotAI* botAI) : GenericMageStrategy(botAI)
|
FrostMageStrategy::FrostMageStrategy(PlayerbotAI* botAI) : GenericMageStrategy(botAI)
|
||||||
{
|
{
|
||||||
actionNodeFactories.Add(new FrostMageStrategyActionNodeFactory());
|
// No custom ActionNodeFactory needed
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== Default Actions =====
|
// ===== Default Actions =====
|
||||||
|
|||||||
@ -12,28 +12,10 @@ class GenericMageNonCombatStrategyActionNodeFactory : public NamedObjectFactory<
|
|||||||
public:
|
public:
|
||||||
GenericMageNonCombatStrategyActionNodeFactory()
|
GenericMageNonCombatStrategyActionNodeFactory()
|
||||||
{
|
{
|
||||||
creators["molten armor"] = &molten_armor;
|
|
||||||
creators["mage armor"] = &mage_armor;
|
|
||||||
creators["ice armor"] = &ice_armor;
|
creators["ice armor"] = &ice_armor;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static ActionNode* molten_armor([[maybe_unused]] PlayerbotAI* botAI)
|
|
||||||
{
|
|
||||||
return new ActionNode("molten armor",
|
|
||||||
/*P*/ {},
|
|
||||||
/*A*/ { NextAction("mage armor") },
|
|
||||||
/*C*/ {});
|
|
||||||
}
|
|
||||||
|
|
||||||
static ActionNode* mage_armor([[maybe_unused]] PlayerbotAI* botAI)
|
|
||||||
{
|
|
||||||
return new ActionNode("mage armor",
|
|
||||||
/*P*/ {},
|
|
||||||
/*A*/ { NextAction("ice armor") },
|
|
||||||
/*C*/ {});
|
|
||||||
}
|
|
||||||
|
|
||||||
static ActionNode* ice_armor([[maybe_unused]] PlayerbotAI* botAI)
|
static ActionNode* ice_armor([[maybe_unused]] PlayerbotAI* botAI)
|
||||||
{
|
{
|
||||||
return new ActionNode("ice armor",
|
return new ActionNode("ice armor",
|
||||||
@ -65,7 +47,7 @@ void MageBuffManaStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
|||||||
|
|
||||||
void MageBuffDpsStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
void MageBuffDpsStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||||
{
|
{
|
||||||
triggers.push_back(new TriggerNode("mage armor", { NextAction("molten armor", 19.0f) }));
|
triggers.push_back(new TriggerNode("molten armor", { NextAction("molten armor", 19.0f) }));
|
||||||
}
|
}
|
||||||
|
|
||||||
void MageBuffStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
void MageBuffStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||||
|
|||||||
@ -15,16 +15,8 @@ public:
|
|||||||
{
|
{
|
||||||
creators["frostbolt"] = &frostbolt;
|
creators["frostbolt"] = &frostbolt;
|
||||||
creators["frostfire bolt"] = &frostfire_bolt;
|
creators["frostfire bolt"] = &frostfire_bolt;
|
||||||
creators["ice lance"] = &ice_lance;
|
|
||||||
creators["fire blast"] = &fire_blast;
|
|
||||||
creators["scorch"] = &scorch;
|
creators["scorch"] = &scorch;
|
||||||
creators["frost nova"] = &frost_nova;
|
|
||||||
creators["cone of cold"] = &cone_of_cold;
|
|
||||||
creators["icy veins"] = &icy_veins;
|
|
||||||
creators["combustion"] = &combustion;
|
|
||||||
creators["evocation"] = &evocation;
|
creators["evocation"] = &evocation;
|
||||||
creators["dragon's breath"] = &dragons_breath;
|
|
||||||
creators["blast wave"] = &blast_wave;
|
|
||||||
creators["remove curse"] = &remove_curse;
|
creators["remove curse"] = &remove_curse;
|
||||||
creators["remove curse on party"] = &remove_curse_on_party;
|
creators["remove curse on party"] = &remove_curse_on_party;
|
||||||
creators["fireball"] = &fireball;
|
creators["fireball"] = &fireball;
|
||||||
@ -47,22 +39,6 @@ private:
|
|||||||
/*C*/ {});
|
/*C*/ {});
|
||||||
}
|
}
|
||||||
|
|
||||||
static ActionNode* ice_lance([[maybe_unused]] PlayerbotAI* botAI)
|
|
||||||
{
|
|
||||||
return new ActionNode("ice lance",
|
|
||||||
/*P*/ {},
|
|
||||||
/*A*/ {},
|
|
||||||
/*C*/ {});
|
|
||||||
}
|
|
||||||
|
|
||||||
static ActionNode* fire_blast([[maybe_unused]] PlayerbotAI* botAI)
|
|
||||||
{
|
|
||||||
return new ActionNode("fire blast",
|
|
||||||
/*P*/ {},
|
|
||||||
/*A*/ {},
|
|
||||||
/*C*/ {});
|
|
||||||
}
|
|
||||||
|
|
||||||
static ActionNode* scorch([[maybe_unused]] PlayerbotAI* botAI)
|
static ActionNode* scorch([[maybe_unused]] PlayerbotAI* botAI)
|
||||||
{
|
{
|
||||||
return new ActionNode("scorch",
|
return new ActionNode("scorch",
|
||||||
@ -71,38 +47,6 @@ private:
|
|||||||
/*C*/ {});
|
/*C*/ {});
|
||||||
}
|
}
|
||||||
|
|
||||||
static ActionNode* frost_nova([[maybe_unused]] PlayerbotAI* botAI)
|
|
||||||
{
|
|
||||||
return new ActionNode("frost nova",
|
|
||||||
/*P*/ {},
|
|
||||||
/*A*/ {},
|
|
||||||
/*C*/ {});
|
|
||||||
}
|
|
||||||
|
|
||||||
static ActionNode* cone_of_cold([[maybe_unused]] PlayerbotAI* botAI)
|
|
||||||
{
|
|
||||||
return new ActionNode("cone of cold",
|
|
||||||
/*P*/ {},
|
|
||||||
/*A*/ {},
|
|
||||||
/*C*/ {});
|
|
||||||
}
|
|
||||||
|
|
||||||
static ActionNode* icy_veins([[maybe_unused]] PlayerbotAI* botAI)
|
|
||||||
{
|
|
||||||
return new ActionNode("icy veins",
|
|
||||||
/*P*/ {},
|
|
||||||
/*A*/ {},
|
|
||||||
/*C*/ {});
|
|
||||||
}
|
|
||||||
|
|
||||||
static ActionNode* combustion([[maybe_unused]] PlayerbotAI* botAI)
|
|
||||||
{
|
|
||||||
return new ActionNode("combustion",
|
|
||||||
/*P*/ {},
|
|
||||||
/*A*/ {},
|
|
||||||
/*C*/ {});
|
|
||||||
}
|
|
||||||
|
|
||||||
static ActionNode* evocation([[maybe_unused]] PlayerbotAI* botAI)
|
static ActionNode* evocation([[maybe_unused]] PlayerbotAI* botAI)
|
||||||
{
|
{
|
||||||
return new ActionNode("evocation",
|
return new ActionNode("evocation",
|
||||||
@ -111,22 +55,6 @@ private:
|
|||||||
/*C*/ {});
|
/*C*/ {});
|
||||||
}
|
}
|
||||||
|
|
||||||
static ActionNode* dragons_breath([[maybe_unused]] PlayerbotAI* botAI)
|
|
||||||
{
|
|
||||||
return new ActionNode("dragon's breath",
|
|
||||||
/*P*/ {},
|
|
||||||
/*A*/ {},
|
|
||||||
/*C*/ {});
|
|
||||||
}
|
|
||||||
|
|
||||||
static ActionNode* blast_wave([[maybe_unused]] PlayerbotAI* botAI)
|
|
||||||
{
|
|
||||||
return new ActionNode("blast wave",
|
|
||||||
/*P*/ {},
|
|
||||||
/*A*/ {},
|
|
||||||
/*C*/ {});
|
|
||||||
}
|
|
||||||
|
|
||||||
static ActionNode* remove_curse([[maybe_unused]] PlayerbotAI* botAI)
|
static ActionNode* remove_curse([[maybe_unused]] PlayerbotAI* botAI)
|
||||||
{
|
{
|
||||||
return new ActionNode("remove curse",
|
return new ActionNode("remove curse",
|
||||||
@ -206,13 +134,13 @@ void MageBoostStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
|||||||
Player* bot = botAI->GetBot();
|
Player* bot = botAI->GetBot();
|
||||||
int tab = AiFactory::GetPlayerSpecTab(bot);
|
int tab = AiFactory::GetPlayerSpecTab(bot);
|
||||||
|
|
||||||
if (tab == 0) // Arcane
|
if (tab == MAGE_TAB_ARCANE)
|
||||||
{
|
{
|
||||||
triggers.push_back(new TriggerNode("arcane power", { NextAction("arcane power", 29.0f) }));
|
triggers.push_back(new TriggerNode("arcane power", { NextAction("arcane power", 29.0f) }));
|
||||||
triggers.push_back(new TriggerNode("icy veins", { NextAction("icy veins", 28.5f) }));
|
triggers.push_back(new TriggerNode("icy veins", { NextAction("icy veins", 28.5f) }));
|
||||||
triggers.push_back(new TriggerNode("mirror image", { NextAction("mirror image", 28.0f) }));
|
triggers.push_back(new TriggerNode("mirror image", { NextAction("mirror image", 28.0f) }));
|
||||||
}
|
}
|
||||||
else if (tab == 1)
|
else if (tab == MAGE_TAB_FIRE)
|
||||||
{
|
{
|
||||||
if (bot->HasSpell(44614) /*Frostfire Bolt*/ && bot->HasAura(15047) /*Ice Shards*/)
|
if (bot->HasSpell(44614) /*Frostfire Bolt*/ && bot->HasAura(15047) /*Ice Shards*/)
|
||||||
{ // Frostfire
|
{ // Frostfire
|
||||||
@ -226,7 +154,7 @@ void MageBoostStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
|||||||
triggers.push_back(new TriggerNode("mirror image", { NextAction("mirror image", 17.5f) }));
|
triggers.push_back(new TriggerNode("mirror image", { NextAction("mirror image", 17.5f) }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (tab == 2) // Frost
|
else if (tab == MAGE_TAB_FROST) // Frost
|
||||||
{
|
{
|
||||||
triggers.push_back(new TriggerNode("cold snap", { NextAction("cold snap", 28.0f) }));
|
triggers.push_back(new TriggerNode("cold snap", { NextAction("cold snap", 28.0f) }));
|
||||||
triggers.push_back(new TriggerNode("icy veins", { NextAction("icy veins", 27.5f) }));
|
triggers.push_back(new TriggerNode("icy veins", { NextAction("icy veins", 27.5f) }));
|
||||||
@ -254,15 +182,14 @@ void MageAoeStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
|||||||
Player* bot = botAI->GetBot();
|
Player* bot = botAI->GetBot();
|
||||||
int tab = AiFactory::GetPlayerSpecTab(bot);
|
int tab = AiFactory::GetPlayerSpecTab(bot);
|
||||||
|
|
||||||
if (tab == 0) // Arcane
|
if (tab == MAGE_TAB_ARCANE)
|
||||||
{
|
{
|
||||||
triggers.push_back(new TriggerNode("flamestrike active and medium aoe", { NextAction("blizzard", 24.0f) }));
|
triggers.push_back(new TriggerNode("flamestrike active and medium aoe", { NextAction("blizzard", 24.0f) }));
|
||||||
triggers.push_back(new TriggerNode("medium aoe", {
|
triggers.push_back(new TriggerNode("medium aoe", {
|
||||||
NextAction("flamestrike", 23.0f),
|
NextAction("flamestrike", 23.0f),
|
||||||
NextAction("blizzard", 22.0f) }));
|
NextAction("blizzard", 22.0f) }));
|
||||||
triggers.push_back(new TriggerNode("light aoe", { NextAction("arcane explosion", 21.0f) }));
|
|
||||||
}
|
}
|
||||||
else if (tab == 1) // Fire and Frostfire
|
else if (tab == MAGE_TAB_FIRE)
|
||||||
{
|
{
|
||||||
triggers.push_back(
|
triggers.push_back(
|
||||||
new TriggerNode("medium aoe", {
|
new TriggerNode("medium aoe", {
|
||||||
@ -275,7 +202,7 @@ void MageAoeStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
|||||||
triggers.push_back(new TriggerNode("firestarter", { NextAction("flamestrike", 40.0f) }));
|
triggers.push_back(new TriggerNode("firestarter", { NextAction("flamestrike", 40.0f) }));
|
||||||
triggers.push_back(new TriggerNode("living bomb on attackers", { NextAction("living bomb on attackers", 21.0f) }));
|
triggers.push_back(new TriggerNode("living bomb on attackers", { NextAction("living bomb on attackers", 21.0f) }));
|
||||||
}
|
}
|
||||||
else if (tab == 2) // Frost
|
else if (tab == MAGE_TAB_FROST)
|
||||||
{
|
{
|
||||||
triggers.push_back(new TriggerNode("flamestrike active and medium aoe", { NextAction("blizzard", 24.0f) }));
|
triggers.push_back(new TriggerNode("flamestrike active and medium aoe", { NextAction("blizzard", 24.0f) }));
|
||||||
triggers.push_back(new TriggerNode("medium aoe", {
|
triggers.push_back(new TriggerNode("medium aoe", {
|
||||||
|
|||||||
@ -4,7 +4,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "MageTriggers.h"
|
#include "MageTriggers.h"
|
||||||
#include "MageActions.h"
|
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
#include "Player.h"
|
#include "Player.h"
|
||||||
#include "Spell.h"
|
#include "Spell.h"
|
||||||
@ -23,7 +22,7 @@ bool NoManaGemTrigger::IsActive()
|
|||||||
5513, // Mana Jade
|
5513, // Mana Jade
|
||||||
5514 // Mana Agate
|
5514 // Mana Agate
|
||||||
};
|
};
|
||||||
Player* bot = botAI->GetBot();
|
|
||||||
for (uint32 gemId : gemIds)
|
for (uint32 gemId : gemIds)
|
||||||
{
|
{
|
||||||
if (bot->GetItemCount(gemId, false) > 0) // false = only in bags
|
if (bot->GetItemCount(gemId, false) > 0) // false = only in bags
|
||||||
@ -45,17 +44,35 @@ bool ArcaneIntellectTrigger::IsActive()
|
|||||||
bool MageArmorTrigger::IsActive()
|
bool MageArmorTrigger::IsActive()
|
||||||
{
|
{
|
||||||
Unit* target = GetTarget();
|
Unit* target = GetTarget();
|
||||||
|
if (botAI->HasAura("mage armor", target))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (AI_VALUE2(uint32, "spell id", "mage armor"))
|
||||||
|
return true;
|
||||||
|
|
||||||
return !botAI->HasAura("ice armor", target) && !botAI->HasAura("frost armor", target) &&
|
return !botAI->HasAura("ice armor", target) && !botAI->HasAura("frost armor", target) &&
|
||||||
!botAI->HasAura("molten armor", target) && !botAI->HasAura("mage armor", target);
|
!botAI->HasAura("molten armor", target);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MoltenArmorTrigger::IsActive()
|
||||||
|
{
|
||||||
|
Unit* target = GetTarget();
|
||||||
|
if (botAI->HasAura("molten armor", target))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (AI_VALUE2(uint32, "spell id", "molten armor"))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return !botAI->HasAura("ice armor", target) && !botAI->HasAura("frost armor", target) &&
|
||||||
|
!botAI->HasAura("mage armor", target);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FrostNovaOnTargetTrigger::IsActive()
|
bool FrostNovaOnTargetTrigger::IsActive()
|
||||||
{
|
{
|
||||||
Unit* target = GetTarget();
|
Unit* target = GetTarget();
|
||||||
if (!target || !target->IsAlive() || !target->IsInWorld())
|
if (!target || !target->IsAlive() || !target->IsInWorld())
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
return botAI->HasAura(spell, target);
|
return botAI->HasAura(spell, target);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,15 +80,14 @@ bool FrostbiteOnTargetTrigger::IsActive()
|
|||||||
{
|
{
|
||||||
Unit* target = GetTarget();
|
Unit* target = GetTarget();
|
||||||
if (!target || !target->IsAlive() || !target->IsInWorld())
|
if (!target || !target->IsAlive() || !target->IsInWorld())
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
return botAI->HasAura(spell, target);
|
return botAI->HasAura(spell, target);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NoFocusMagicTrigger::IsActive()
|
bool NoFocusMagicTrigger::IsActive()
|
||||||
{
|
{
|
||||||
if (!bot->HasSpell(54646))
|
if (!bot->HasSpell(54646)) // Focus Magic
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
Group* group = bot->GetGroup();
|
Group* group = bot->GetGroup();
|
||||||
@ -92,24 +108,19 @@ bool NoFocusMagicTrigger::IsActive()
|
|||||||
|
|
||||||
bool DeepFreezeCooldownTrigger::IsActive()
|
bool DeepFreezeCooldownTrigger::IsActive()
|
||||||
{
|
{
|
||||||
Player* bot = botAI->GetBot();
|
|
||||||
static const uint32 DEEP_FREEZE_SPELL_ID = 44572;
|
|
||||||
|
|
||||||
// If the bot does NOT have Deep Freeze, treat as "on cooldown"
|
// If the bot does NOT have Deep Freeze, treat as "on cooldown"
|
||||||
if (!bot->HasSpell(DEEP_FREEZE_SPELL_ID))
|
if (!bot->HasSpell(44572)) // Deep Freeze
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
// Otherwise, use the default cooldown logic
|
|
||||||
return SpellCooldownTrigger::IsActive();
|
return SpellCooldownTrigger::IsActive();
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::set<uint32> FlamestrikeNearbyTrigger::FLAMESTRIKE_SPELL_IDS = {2120, 2121, 8422, 8423, 10215,
|
const std::unordered_set<uint32> FlamestrikeNearbyTrigger::FLAMESTRIKE_SPELL_IDS = {
|
||||||
10216, 27086, 42925, 42926};
|
2120, 2121, 8422, 8423, 10215, 10216, 27086, 42925, 42926
|
||||||
|
};
|
||||||
|
|
||||||
bool FlamestrikeNearbyTrigger::IsActive()
|
bool FlamestrikeNearbyTrigger::IsActive()
|
||||||
{
|
{
|
||||||
Player* bot = botAI->GetBot();
|
|
||||||
|
|
||||||
for (uint32 spellId : FLAMESTRIKE_SPELL_IDS)
|
for (uint32 spellId : FLAMESTRIKE_SPELL_IDS)
|
||||||
{
|
{
|
||||||
Aura* aura = bot->GetAura(spellId, bot->GetGUID());
|
Aura* aura = bot->GetAura(spellId, bot->GetGUID());
|
||||||
@ -133,7 +144,6 @@ bool ImprovedScorchTrigger::IsActive()
|
|||||||
if (!target || !target->IsAlive() || !target->IsInWorld())
|
if (!target || !target->IsAlive() || !target->IsInWorld())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// List of all spell IDs for Improved Scorch, Winter's Chill, and Shadow Mastery
|
|
||||||
static const uint32 ImprovedScorchExclusiveDebuffs[] = {// Shadow Mastery
|
static const uint32 ImprovedScorchExclusiveDebuffs[] = {// Shadow Mastery
|
||||||
17794, 17797, 17798, 17799, 17800,
|
17794, 17797, 17798, 17799, 17800,
|
||||||
// Winter's Chill
|
// Winter's Chill
|
||||||
@ -147,11 +157,10 @@ bool ImprovedScorchTrigger::IsActive()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use default DebuffTrigger logic for the rest (only trigger if debuff is missing or expiring)
|
|
||||||
return DebuffTrigger::IsActive();
|
return DebuffTrigger::IsActive();
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::set<uint32> BlizzardChannelCheckTrigger::BLIZZARD_SPELL_IDS = {
|
const std::unordered_set<uint32> BlizzardChannelCheckTrigger::BLIZZARD_SPELL_IDS = {
|
||||||
10, // Blizzard Rank 1
|
10, // Blizzard Rank 1
|
||||||
6141, // Blizzard Rank 2
|
6141, // Blizzard Rank 2
|
||||||
8427, // Blizzard Rank 3
|
8427, // Blizzard Rank 3
|
||||||
@ -165,19 +174,12 @@ const std::set<uint32> BlizzardChannelCheckTrigger::BLIZZARD_SPELL_IDS = {
|
|||||||
|
|
||||||
bool BlizzardChannelCheckTrigger::IsActive()
|
bool BlizzardChannelCheckTrigger::IsActive()
|
||||||
{
|
{
|
||||||
Player* bot = botAI->GetBot();
|
if (Spell* spell = bot->GetCurrentSpell(CURRENT_CHANNELED_SPELL);
|
||||||
|
spell && BLIZZARD_SPELL_IDS.count(spell->m_spellInfo->Id))
|
||||||
// Check if the bot is channeling a spell
|
|
||||||
if (Spell* spell = bot->GetCurrentSpell(CURRENT_CHANNELED_SPELL))
|
|
||||||
{
|
|
||||||
// Only trigger if the spell being channeled is Blizzard
|
|
||||||
if (BLIZZARD_SPELL_IDS.count(spell->m_spellInfo->Id))
|
|
||||||
{
|
{
|
||||||
uint8 attackerCount = AI_VALUE(uint8, "attacker count");
|
uint8 attackerCount = AI_VALUE(uint8, "attacker count");
|
||||||
return attackerCount < minEnemies;
|
return attackerCount < minEnemies;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Not channeling Blizzard
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,19 +11,15 @@
|
|||||||
#include "SharedDefines.h"
|
#include "SharedDefines.h"
|
||||||
#include "Trigger.h"
|
#include "Trigger.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
#include "PlayerbotAI.h"
|
|
||||||
#include <set>
|
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
|
||||||
class PlayerbotAI;
|
|
||||||
|
|
||||||
// Buff and Out of Combat Triggers
|
// Buff and Out of Combat Triggers
|
||||||
|
|
||||||
class ArcaneIntellectOnPartyTrigger : public BuffOnPartyTrigger
|
class ArcaneIntellectOnPartyTrigger : public BuffOnPartyTrigger
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
ArcaneIntellectOnPartyTrigger(PlayerbotAI* botAI) : BuffOnPartyTrigger(botAI, "arcane intellect", 2 * 2000) {}
|
ArcaneIntellectOnPartyTrigger(PlayerbotAI* botAI)
|
||||||
|
: BuffOnPartyTrigger(botAI, "arcane intellect", 2 * 2000) {}
|
||||||
bool IsActive() override;
|
bool IsActive() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -41,6 +37,13 @@ public:
|
|||||||
bool IsActive() override;
|
bool IsActive() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class MoltenArmorTrigger : public BuffTrigger
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MoltenArmorTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "molten armor", 5 * 2000) {}
|
||||||
|
bool IsActive() override;
|
||||||
|
};
|
||||||
|
|
||||||
class NoFocusMagicTrigger : public Trigger
|
class NoFocusMagicTrigger : public Trigger
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -58,7 +61,6 @@ class NoManaGemTrigger : public Trigger
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
NoManaGemTrigger(PlayerbotAI* botAI) : Trigger(botAI, "no mana gem") {}
|
NoManaGemTrigger(PlayerbotAI* botAI) : Trigger(botAI, "no mana gem") {}
|
||||||
|
|
||||||
bool IsActive() override;
|
bool IsActive() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -109,10 +111,8 @@ public:
|
|||||||
class ArcaneBlast4StacksAndMissileBarrageTrigger : public TwoTriggers
|
class ArcaneBlast4StacksAndMissileBarrageTrigger : public TwoTriggers
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
ArcaneBlast4StacksAndMissileBarrageTrigger(PlayerbotAI* ai)
|
ArcaneBlast4StacksAndMissileBarrageTrigger(PlayerbotAI* botAI)
|
||||||
: TwoTriggers(ai, "arcane blast stack", "missile barrage")
|
: TwoTriggers(botAI, "arcane blast stack", "missile barrage") {}
|
||||||
{
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class CombustionTrigger : public BoostTrigger
|
class CombustionTrigger : public BoostTrigger
|
||||||
@ -138,7 +138,7 @@ public:
|
|||||||
class ColdSnapTrigger : public TwoTriggers
|
class ColdSnapTrigger : public TwoTriggers
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
ColdSnapTrigger(PlayerbotAI* ai) : TwoTriggers(ai, "icy veins on cd", "deep freeze on cd") {}
|
ColdSnapTrigger(PlayerbotAI* botAI) : TwoTriggers(botAI, "icy veins on cd", "deep freeze on cd") {}
|
||||||
};
|
};
|
||||||
|
|
||||||
class MirrorImageTrigger : public BoostTrigger
|
class MirrorImageTrigger : public BoostTrigger
|
||||||
@ -181,9 +181,8 @@ public:
|
|||||||
class PartyMemberRemoveCurseTrigger : public PartyMemberNeedCureTrigger
|
class PartyMemberRemoveCurseTrigger : public PartyMemberNeedCureTrigger
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
PartyMemberRemoveCurseTrigger(PlayerbotAI* botAI) : PartyMemberNeedCureTrigger(botAI, "remove curse", DISPEL_CURSE)
|
PartyMemberRemoveCurseTrigger(PlayerbotAI* botAI)
|
||||||
{
|
: PartyMemberNeedCureTrigger(botAI, "remove curse", DISPEL_CURSE) {}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class SpellstealTrigger : public TargetAuraDispelTrigger
|
class SpellstealTrigger : public TargetAuraDispelTrigger
|
||||||
@ -216,7 +215,7 @@ public:
|
|||||||
class LivingBombOnAttackersTrigger : public DebuffOnAttackerTrigger
|
class LivingBombOnAttackersTrigger : public DebuffOnAttackerTrigger
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
LivingBombOnAttackersTrigger(PlayerbotAI* ai) : DebuffOnAttackerTrigger(ai, "living bomb", true) {}
|
LivingBombOnAttackersTrigger(PlayerbotAI* botAI) : DebuffOnAttackerTrigger(botAI, "living bomb", true) {}
|
||||||
bool IsActive() override { return BuffTrigger::IsActive(); }
|
bool IsActive() override { return BuffTrigger::IsActive(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -282,13 +281,13 @@ public:
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
float radius;
|
float radius;
|
||||||
static const std::set<uint32> FLAMESTRIKE_SPELL_IDS;
|
static const std::unordered_set<uint32> FLAMESTRIKE_SPELL_IDS;
|
||||||
};
|
};
|
||||||
|
|
||||||
class FlamestrikeBlizzardTrigger : public TwoTriggers
|
class FlamestrikeBlizzardTrigger : public TwoTriggers
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
FlamestrikeBlizzardTrigger(PlayerbotAI* ai) : TwoTriggers(ai, "flamestrike nearby", "medium aoe") {}
|
FlamestrikeBlizzardTrigger(PlayerbotAI* botAI) : TwoTriggers(botAI, "flamestrike nearby", "medium aoe") {}
|
||||||
};
|
};
|
||||||
|
|
||||||
class BlizzardChannelCheckTrigger : public Trigger
|
class BlizzardChannelCheckTrigger : public Trigger
|
||||||
@ -301,7 +300,7 @@ public:
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
uint32 minEnemies;
|
uint32 minEnemies;
|
||||||
static const std::set<uint32> BLIZZARD_SPELL_IDS;
|
static const std::unordered_set<uint32> BLIZZARD_SPELL_IDS;
|
||||||
};
|
};
|
||||||
|
|
||||||
class BlastWaveOffCdTrigger : public SpellNoCooldownTrigger
|
class BlastWaveOffCdTrigger : public SpellNoCooldownTrigger
|
||||||
@ -313,7 +312,8 @@ public:
|
|||||||
class BlastWaveOffCdTriggerAndMediumAoeTrigger : public TwoTriggers
|
class BlastWaveOffCdTriggerAndMediumAoeTrigger : public TwoTriggers
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
BlastWaveOffCdTriggerAndMediumAoeTrigger(PlayerbotAI* ai) : TwoTriggers(ai, "blast wave off cd", "medium aoe") {}
|
BlastWaveOffCdTriggerAndMediumAoeTrigger(PlayerbotAI* botAI)
|
||||||
|
: TwoTriggers(botAI, "blast wave off cd", "medium aoe") {}
|
||||||
};
|
};
|
||||||
|
|
||||||
class NoFirestarterStrategyTrigger : public Trigger
|
class NoFirestarterStrategyTrigger : public Trigger
|
||||||
|
|||||||
@ -26,8 +26,8 @@ void GenericPaladinNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& tr
|
|||||||
triggers.push_back(new TriggerNode("not sensing undead", { NextAction("sense undead", ACTION_IDLE + 1.0f) }));
|
triggers.push_back(new TriggerNode("not sensing undead", { NextAction("sense undead", ACTION_IDLE + 1.0f) }));
|
||||||
|
|
||||||
int specTab = AiFactory::GetPlayerSpecTab(botAI->GetBot());
|
int specTab = AiFactory::GetPlayerSpecTab(botAI->GetBot());
|
||||||
if (specTab == PALADIN_TAB_HOLY || specTab == PALADIN_TAB_PROTECTION)
|
if (specTab == PALADIN_TAB_HOLY)
|
||||||
triggers.push_back(new TriggerNode("often", { NextAction("apply oil", ACTION_IDLE + 1.0f) }));
|
triggers.push_back(new TriggerNode("often", { NextAction("apply oil", ACTION_IDLE + 1.0f) }));
|
||||||
if (specTab == PALADIN_TAB_RETRIBUTION)
|
if (specTab == PALADIN_TAB_PROTECTION || specTab == PALADIN_TAB_RETRIBUTION)
|
||||||
triggers.push_back(new TriggerNode("often", { NextAction("apply stone", ACTION_IDLE + 1.0f) }));
|
triggers.push_back(new TriggerNode("often", { NextAction("apply stone", ACTION_IDLE + 1.0f) }));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -86,6 +86,7 @@ void HealPriestStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
|||||||
new TriggerNode(
|
new TriggerNode(
|
||||||
"party member almost full health",
|
"party member almost full health",
|
||||||
{
|
{
|
||||||
|
NextAction("power word: shield on party", ACTION_LIGHT_HEAL + 3),
|
||||||
NextAction("prayer of mending on party", ACTION_LIGHT_HEAL + 2),
|
NextAction("prayer of mending on party", ACTION_LIGHT_HEAL + 2),
|
||||||
NextAction("renew on party", ACTION_LIGHT_HEAL + 1)
|
NextAction("renew on party", ACTION_LIGHT_HEAL + 1)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,35 +1,40 @@
|
|||||||
#include "RaidBwlActions.h"
|
#include "RaidBwlActions.h"
|
||||||
|
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
|
#include "RaidBwlHelpers.h"
|
||||||
|
|
||||||
|
using namespace BlackwingLairHelpers;
|
||||||
|
|
||||||
|
// General
|
||||||
|
|
||||||
bool BwlOnyxiaScaleCloakAuraCheckAction::Execute(Event /*event*/)
|
bool BwlOnyxiaScaleCloakAuraCheckAction::Execute(Event /*event*/)
|
||||||
{
|
{
|
||||||
bot->AddAura(22683, bot);
|
bot->AddAura(SPELL_ONYXIA_SCALE_CLOAK, bot);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BwlOnyxiaScaleCloakAuraCheckAction::isUseful() { return !bot->HasAura(22683); }
|
bool BwlOnyxiaScaleCloakAuraCheckAction::isUseful()
|
||||||
|
{
|
||||||
|
return !bot->HasAura(SPELL_ONYXIA_SCALE_CLOAK);
|
||||||
|
}
|
||||||
|
|
||||||
bool BwlTurnOffSuppressionDeviceAction::Execute(Event /*event*/)
|
bool BwlTurnOffSuppressionDeviceAction::Execute(Event /*event*/)
|
||||||
{
|
{
|
||||||
GuidVector gos = AI_VALUE(GuidVector, "nearest game objects");
|
GuidVector gos = AI_VALUE(GuidVector, "nearest game objects");
|
||||||
for (GuidVector::iterator i = gos.begin(); i != gos.end(); i++)
|
for (auto i = gos.begin(); i != gos.end(); ++i)
|
||||||
{
|
{
|
||||||
GameObject* go = botAI->GetGameObject(*i);
|
GameObject* go = botAI->GetGameObject(*i);
|
||||||
if (!go)
|
if (IsActiveSuppressionDeviceInRange(go, bot))
|
||||||
{
|
{
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (go->GetEntry() != 179784 || go->GetDistance(bot) >= 15.0f || go->GetGoState() != GO_STATE_READY)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
go->SetGoState(GO_STATE_ACTIVE);
|
go->SetGoState(GO_STATE_ACTIVE);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Chromaggus
|
||||||
|
|
||||||
bool BwlUseHourglassSandAction::Execute(Event /*event*/)
|
bool BwlUseHourglassSandAction::Execute(Event /*event*/)
|
||||||
{
|
{
|
||||||
return botAI->CastSpell(23645, bot);
|
return botAI->CastSpell(SPELL_HOURGLASS_SAND, bot);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,11 +2,8 @@
|
|||||||
#define _PLAYERBOT_RAIDBWLACTIONS_H
|
#define _PLAYERBOT_RAIDBWLACTIONS_H
|
||||||
|
|
||||||
#include "Action.h"
|
#include "Action.h"
|
||||||
#include "AttackAction.h"
|
|
||||||
#include "GenericActions.h"
|
// General
|
||||||
#include "MovementActions.h"
|
|
||||||
#include "PlayerbotAI.h"
|
|
||||||
#include "Playerbots.h"
|
|
||||||
|
|
||||||
class BwlOnyxiaScaleCloakAuraCheckAction : public Action
|
class BwlOnyxiaScaleCloakAuraCheckAction : public Action
|
||||||
{
|
{
|
||||||
@ -23,6 +20,8 @@ public:
|
|||||||
bool Execute(Event event) override;
|
bool Execute(Event event) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Chromaggus
|
||||||
|
|
||||||
class BwlUseHourglassSandAction : public Action
|
class BwlUseHourglassSandAction : public Action
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|||||||
@ -1,15 +1,13 @@
|
|||||||
#include "RaidBwlStrategy.h"
|
#include "RaidBwlStrategy.h"
|
||||||
|
|
||||||
#include "Strategy.h"
|
|
||||||
|
|
||||||
void RaidBwlStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
void RaidBwlStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||||
{
|
{
|
||||||
triggers.push_back(new TriggerNode("often",
|
triggers.push_back(new TriggerNode("often", {
|
||||||
{ NextAction("bwl check onyxia scale cloak", ACTION_RAID) }));
|
NextAction("bwl check onyxia scale cloak", ACTION_RAID) }));
|
||||||
|
|
||||||
triggers.push_back(new TriggerNode("bwl suppression device",
|
triggers.push_back(new TriggerNode("bwl suppression device", {
|
||||||
{ NextAction("bwl turn off suppression device", ACTION_RAID) }));
|
NextAction("bwl turn off suppression device", ACTION_RAID) }));
|
||||||
|
|
||||||
triggers.push_back(new TriggerNode("bwl affliction bronze",
|
triggers.push_back(new TriggerNode("bwl affliction bronze", {
|
||||||
{ NextAction("bwl use hourglass sand", ACTION_RAID) }));
|
NextAction("bwl use hourglass sand", ACTION_RAID) }));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,9 +8,9 @@ class RaidBwlStrategy : public Strategy
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
RaidBwlStrategy(PlayerbotAI* ai) : Strategy(ai) {}
|
RaidBwlStrategy(PlayerbotAI* ai) : Strategy(ai) {}
|
||||||
virtual std::string const getName() override { return "bwl"; }
|
std::string const getName() override { return "bwl"; }
|
||||||
virtual void InitTriggers(std::vector<TriggerNode*>& triggers) override;
|
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
|
||||||
// virtual void InitMultipliers(std::vector<Multiplier*> &multipliers) override;
|
// void InitMultipliers(std::vector<Multiplier*> &multipliers) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -1,24 +1,29 @@
|
|||||||
#include "RaidBwlTriggers.h"
|
#include "RaidBwlTriggers.h"
|
||||||
|
|
||||||
#include "SharedDefines.h"
|
#include "Playerbots.h"
|
||||||
|
#include "RaidBwlHelpers.h"
|
||||||
|
|
||||||
|
using namespace BlackwingLairHelpers;
|
||||||
|
|
||||||
|
// General
|
||||||
|
|
||||||
bool BwlSuppressionDeviceTrigger::IsActive()
|
bool BwlSuppressionDeviceTrigger::IsActive()
|
||||||
{
|
{
|
||||||
GuidVector gos = AI_VALUE(GuidVector, "nearest game objects");
|
GuidVector gos = AI_VALUE(GuidVector, "nearest game objects");
|
||||||
for (GuidVector::iterator i = gos.begin(); i != gos.end(); i++)
|
for (auto i = gos.begin(); i != gos.end(); ++i)
|
||||||
{
|
{
|
||||||
GameObject* go = botAI->GetGameObject(*i);
|
const GameObject* go = botAI->GetGameObject(*i);
|
||||||
if (!go)
|
if (IsActiveSuppressionDeviceInRange(go, bot))
|
||||||
{
|
{
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (go->GetEntry() != 179784 || go->GetDistance(bot) >= 15.0f || go->GetGoState() != GO_STATE_READY)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BwlAfflictionBronzeTrigger::IsActive() { return bot->HasAura(23170); }
|
// Chromaggus
|
||||||
|
|
||||||
|
bool BwlAfflictionBronzeTrigger::IsActive()
|
||||||
|
{
|
||||||
|
return bot->HasAura(SPELL_BROOD_AFFLICTION_BRONZE);
|
||||||
|
}
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
#ifndef _PLAYERBOT_RAIDBWLTRIGGERS_H
|
#ifndef _PLAYERBOT_RAIDBWLTRIGGERS_H
|
||||||
#define _PLAYERBOT_RAIDBWLTRIGGERS_H
|
#define _PLAYERBOT_RAIDBWLTRIGGERS_H
|
||||||
|
|
||||||
#include "PlayerbotAI.h"
|
|
||||||
#include "Playerbots.h"
|
|
||||||
#include "Trigger.h"
|
#include "Trigger.h"
|
||||||
|
|
||||||
|
// General
|
||||||
|
|
||||||
class BwlSuppressionDeviceTrigger : public Trigger
|
class BwlSuppressionDeviceTrigger : public Trigger
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -12,6 +12,8 @@ public:
|
|||||||
bool IsActive() override;
|
bool IsActive() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Chromaggus
|
||||||
|
|
||||||
class BwlAfflictionBronzeTrigger : public Trigger
|
class BwlAfflictionBronzeTrigger : public Trigger
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|||||||
12
src/Ai/Raid/BlackwingLair/Util/RaidBwlHelpers.cpp
Normal file
12
src/Ai/Raid/BlackwingLair/Util/RaidBwlHelpers.cpp
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
#include "RaidBwlHelpers.h"
|
||||||
|
|
||||||
|
namespace BlackwingLairHelpers
|
||||||
|
{
|
||||||
|
bool IsActiveSuppressionDeviceInRange(const GameObject* go, const Player* bot)
|
||||||
|
{
|
||||||
|
return go &&
|
||||||
|
go->GetEntry() == GO_SUPPRESSION_DEVICE &&
|
||||||
|
go->GetDistance(bot) < 15.0f &&
|
||||||
|
go->GetGoState() == GO_STATE_READY;
|
||||||
|
}
|
||||||
|
}
|
||||||
27
src/Ai/Raid/BlackwingLair/Util/RaidBwlHelpers.h
Normal file
27
src/Ai/Raid/BlackwingLair/Util/RaidBwlHelpers.h
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
#ifndef _PLAYERBOT_RAIDBWLHELPERS_H
|
||||||
|
#define _PLAYERBOT_RAIDBWLHELPERS_H
|
||||||
|
|
||||||
|
#include "Player.h"
|
||||||
|
|
||||||
|
namespace BlackwingLairHelpers
|
||||||
|
{
|
||||||
|
enum BlackwingLairSpells
|
||||||
|
{
|
||||||
|
// General
|
||||||
|
SPELL_ONYXIA_SCALE_CLOAK = 22683,
|
||||||
|
|
||||||
|
// Chromaggus
|
||||||
|
SPELL_BROOD_AFFLICTION_BRONZE = 23170,
|
||||||
|
SPELL_HOURGLASS_SAND = 23645
|
||||||
|
};
|
||||||
|
|
||||||
|
enum BlackwingLairGameObjects
|
||||||
|
{
|
||||||
|
// General
|
||||||
|
GO_SUPPRESSION_DEVICE = 179784
|
||||||
|
};
|
||||||
|
|
||||||
|
bool IsActiveSuppressionDeviceInRange(const GameObject* go, const Player* bot);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //_PLAYERBOT_RAIDBWLHELPERS_H
|
||||||
878
src/Ai/Raid/ICC/Action/ICCActions.h
Normal file
878
src/Ai/Raid/ICC/Action/ICCActions.h
Normal file
@ -0,0 +1,878 @@
|
|||||||
|
#ifndef _PLAYERBOT_ICCA_H
|
||||||
|
#define _PLAYERBOT_ICCA_H
|
||||||
|
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
#include "Action.h"
|
||||||
|
#include "MovementActions.h"
|
||||||
|
#include "PlayerbotAI.h"
|
||||||
|
#include "Playerbots.h"
|
||||||
|
#include "AttackAction.h"
|
||||||
|
#include "LastMovementValue.h"
|
||||||
|
#include "ObjectGuid.h"
|
||||||
|
#include "PlayerbotAIConfig.h"
|
||||||
|
#include "ICCStrategy.h"
|
||||||
|
#include "ScriptedCreature.h"
|
||||||
|
#include "SharedDefines.h"
|
||||||
|
#include "Trigger.h"
|
||||||
|
#include "CellImpl.h"
|
||||||
|
#include "GridNotifiers.h"
|
||||||
|
#include "GridNotifiersImpl.h"
|
||||||
|
#include "Vehicle.h"
|
||||||
|
#include "ICCTriggers.h"
|
||||||
|
|
||||||
|
inline const Position ICC_LM_TANK_POSITION = Position(-391.0f, 2259.0f, 42.0f);
|
||||||
|
inline const Position ICC_LM_BONE_STORM_AT_POSITION = Position(-390.02332f, 2179.3481f, 41.96729f);
|
||||||
|
inline const Position ICC_LM_MID_POSITION = Position(-393.61722f, 2216.335f, 41.99396f);
|
||||||
|
inline const Position ICC_DARK_RECKONING_SAFE_POSITION = Position(-523.33386f, 2211.2031f, 62.823116f);
|
||||||
|
inline const Position ICC_LDW_TANK_POSTION = Position(-593.7436f, 2211.298f, 49.476673f); //old closer to stairs -570.1f, 2211.2456f, 49.476616f
|
||||||
|
inline const Position ICC_LDW_RANGED_POSITION = Position(-653.11096f, 2211.9568f,51.551437f); //old in frint of the boss (-607.0631f, 2212.2488f, 49.47009f)
|
||||||
|
inline const Position ICC_ROTTING_FROST_GIANT_TANK_POSITION = Position(-328.5085f, 2225.5142f, 199.97298f);
|
||||||
|
inline const Position ICC_GUNSHIP_ROCKET_JUMP_ALLY = Position (-370.04645f, 1993.3536f, 466.65656f);
|
||||||
|
inline const Position ICC_GUNSHIP_ROCKET_JUMP_ALLY2 = Position (-392.66208f, 2064.893f, 466.5672f);
|
||||||
|
inline const Position ICC_GUNSHIP_ROCKET_JUMP_ALLY_MIDDLE_POINT = Position(-383.01166f, 2022.8046f, 467.38193f);
|
||||||
|
inline const Position ICC_GUNSHIP_ROCKET_JUMP_ALLY_FRIENDLY_POINT = Position(-389.26788f, 2045.9136f, 467.56168f);
|
||||||
|
inline const Position ICC_GUNSHIP_ROCKET_JUMP_HORDE = Position (-449.5343f, 2477.2024f, 471.31906f);
|
||||||
|
inline const Position ICC_GUNSHIP_ROCKET_JUMP_HORDE2 = Position (-429.81586f, 2400.6804f, 471.56537f);
|
||||||
|
inline const Position ICC_GUNSHIP_ROCKET_JUMP_HORDE_MIDDLE_POINT = Position(-449.17645f, 2449.2705f, 470.9257f);
|
||||||
|
inline const Position ICC_GUNSHIP_ROCKET_JUMP_HORDE_FRIENDLY_POINT = Position(-444.74533f, 2420.1208f, 470.78748f);
|
||||||
|
inline const Position ICC_DBS_TANK_POSITION = Position(-494.26517f, 2211.549f, 541.11414f);
|
||||||
|
inline const Position ICC_FESTERGUT_TANK_POSITION = Position(4269.1772f, 3144.7673f, 360.38577f);
|
||||||
|
inline const Position ICC_FESTERGUT_RANGED_SPORE = Position(4264.3623f, 3120.889f, 360.38565f); //old closer to gates 4261.143f, 3109.4146f, 360.38605f
|
||||||
|
inline const Position ICC_FESTERGUT_RANGED_SPORE_2 = Position(4288.7974f, 3115.6274f, 360.38577f); //new position
|
||||||
|
inline const Position ICC_FESTERGUT_MELEE_SPORE = Position(4269.1772f, 3144.7673f, 360.38577f);
|
||||||
|
inline const Position ICC_ROTFACE_RANGED_POSITION_HC_1 = Position(4453.2085f, 3108.7488f, 360.38626f);
|
||||||
|
inline const Position ICC_ROTFACE_RANGED_POSITION_HC_2 = Position(4459.4390f, 3118.0210f, 360.38626f);
|
||||||
|
inline const Position ICC_ROTFACE_RANGED_POSITION_HC_3 = Position(4473.4062f, 3126.2861f, 360.38626f);
|
||||||
|
inline const Position ICC_ROTFACE_RANGED_POSITION_HC_4 = Position(4469.9370f, 3137.0173f, 360.38626f);
|
||||||
|
inline const Position ICC_ROTFACE_RANGED_POSITION_HC_5 = Position(4476.7583f, 3145.7160f, 360.38626f);
|
||||||
|
inline const Position ICC_ROTFACE_RANGED_POSITION_HC_6 = Position(4466.2100f, 3150.0630f, 360.38626f);
|
||||||
|
inline const Position ICC_ROTFACE_RANGED_POSITION_HC_7 = Position(4464.4297f, 3161.2317f, 360.38626f);
|
||||||
|
inline const Position ICC_ROTFACE_RANGED_POSITION_HC_8 = Position(4447.2900f, 3163.0322f, 360.38626f);
|
||||||
|
inline const Position ICC_ROTFACE_RANGED_POSITION_HC_9 = Position(4437.1250f, 3168.4062f, 360.38626f);
|
||||||
|
inline const Position ICC_ROTFACE_RANGED_POSITION_HC_10 = Position(4417.2217f, 3148.8909f, 360.38626f);
|
||||||
|
inline const Position ICC_ROTFACE_RANGED_POSITION_HC_11 = Position(4420.9727f, 3137.7700f, 360.38626f);
|
||||||
|
inline const Position ICC_ROTFACE_RANGED_POSITION_HC_12 = Position(4414.8716f, 3127.9534f, 360.38626f);
|
||||||
|
inline const Position ICC_ROTFACE_RANGED_POSITION_HC_13 = Position(4425.1553f, 3123.4717f, 360.38626f);
|
||||||
|
inline const Position ICC_ROTFACE_RANGED_POSITION_HC_14 = Position(4426.3760f, 3113.4153f, 360.38626f);
|
||||||
|
inline const Position ICC_ROTFACE_RANGED_POSITION_HC_15 = Position(4443.8027f, 3113.9207f, 360.38626f);
|
||||||
|
inline const Position ICC_ROTFACE_RANGED_POSITION_HC_16 = Position(4432.477f, 3156.7651f, 360.38568f);
|
||||||
|
inline const Position ICC_ROTFACE_RANGED_POSITION_HC_17 = Position(4458.083f, 3132.5842f, 360.38565f);
|
||||||
|
inline const Position ICC_ROTFACE_RANGED_POSITION_HC_18 = Position(4457.4565f, 3144.8442f, 360.38565f);
|
||||||
|
inline const Position ICC_ROTFACE_RANGED_POSITION_HC_19 = Position(4422.5460f, 3158.0435f, 360.38565f);
|
||||||
|
inline const Position ICC_ROTFACE_RANGED_POSITION_HC_20 = Position(4432.1646f, 3142.3418f, 360.38565f);
|
||||||
|
inline const Position ICC_ROTFACE_RANGED_POSITION_HC_21 = Position(4436.649f, 3126.1245f, 360.38565f);
|
||||||
|
inline const Position ICC_ROTFACE_RANGED_POSITION_HC_22 = Position(4450.2363f, 3122.4033f, 360.38565f);
|
||||||
|
inline const Position ICC_ROTFACE_EXPLOSION_POSITION_1 = Position(4481.433f, 3137.0117f, 360.38522f);
|
||||||
|
inline const Position ICC_ROTFACE_EXPLOSION_POSITION_2 = Position(4474.4565f, 3156.9617f, 360.38522f);
|
||||||
|
inline const Position ICC_ROTFACE_EXPLOSION_POSITION_3 = Position(4452.0586f, 3170.2231f, 360.38522f);
|
||||||
|
inline const Position ICC_ROTFACE_EXPLOSION_POSITION_4 = Position(4421.2437f, 3161.2275f, 360.38522f);
|
||||||
|
inline const Position ICC_ROTFACE_EXPLOSION_POSITION_5 = Position(4410.904f, 3136.9976f, 360.38522f);
|
||||||
|
inline const Position ICC_ROTFACE_EXPLOSION_POSITION_6 = Position(4417.603f, 3119.4712f, 360.38522f);
|
||||||
|
inline const Position ICC_ROTFACE_EXPLOSION_POSITION_7 = Position(4442.514f, 3105.2234f, 360.38522f);
|
||||||
|
inline const Position ICC_ROTFACE_EXPLOSION_POSITION_8 = Position(4465.7173f, 3113.6284f, 360.38522f);
|
||||||
|
inline const Position ICC_ROTFACE_TANK_POSITION = Position(4447.061f, 3150.9758f, 360.38568f);
|
||||||
|
inline const Position ICC_ROTFACE_BIG_OOZE_POSITION = Position(4432.687f, 3142.3035f, 360.38568f);
|
||||||
|
inline const Position ICC_ROTFACE_SAFE_POSITION = Position(4446.557f, 3065.6594f, 360.38568f);
|
||||||
|
inline const Position ICC_ROTFACE_CENTER_POSITION = Position(4446.0547f, 3144.8677f, 360.38568f); //actual center 4.74089 4445.6616f, 3137.1526f, 360.38608
|
||||||
|
inline const Position ICC_ROTFACE_CENTER_POSITION_BOSS = Position(4445.656f, 3137.1663f, 360.38565f);
|
||||||
|
inline const Position ICC_PUTRICIDE_TANK_POSITION = Position(4373.227f, 3222.058f, 389.4029f);
|
||||||
|
inline const Position ICC_PUTRICIDE_GREEN_POSITION = Position(4423.4126f, 3194.2715f, 389.37683f);
|
||||||
|
inline const Position ICC_PUTRICIDE_GATE_POSITION = Position(4356.3345f, 3167.9407f, 389.39825f);
|
||||||
|
inline const Position ICC_BPC_OT_POSITION = Position(4649.2236f, 2796.0972f, 361.1815f);
|
||||||
|
inline const Position ICC_BPC_MT_POSITION = Position(4648.5674f, 2744.847f, 361.18222f);
|
||||||
|
inline const Position ICC_BPC_CENTER_POSITION = Position(4638.7056f, 2769.3713f, 361.17108f);
|
||||||
|
inline const Position ICC_BQL_CENTER_POSITION = Position(4595.0f, 2769.0f, 400.0f);
|
||||||
|
inline const Position ICC_BQL_LWALL1_POSITION = Position(4624.685f, 2789.4895f, 400.13834f);
|
||||||
|
inline const Position ICC_BQL_LWALL2_POSITION = Position(4600.749f, 2805.7568f, 400.1374f);
|
||||||
|
inline const Position ICC_BQL_LWALL3_POSITION = Position(4572.857f, 2797.3872f, 400.1374f);
|
||||||
|
inline const Position ICC_BQL_RWALL1_POSITION = Position(4625.724f, 2748.9917f, 400.13693f);
|
||||||
|
inline const Position ICC_BQL_RWALL2_POSITION = Position(4608.3774f, 2735.7466f, 400.13693f);
|
||||||
|
inline const Position ICC_BQL_RWALL3_POSITION = Position(4576.813f, 2739.6067f, 400.13693f);
|
||||||
|
inline const Position ICC_BQL_LRWALL4_POSITION = Position(4539.345f, 2769.3853f, 403.7267f);
|
||||||
|
inline const Position ICC_BQL_TANK_POSITION = Position(4633.7964f, 2769.2515f, 401.74777f); //old 4629.746f, 2769.6396f, 401.7479f
|
||||||
|
inline const Position ICC_VDW_HEAL_POSITION = Position(4203.752f, 2483.4343f, 364.87274f);
|
||||||
|
inline const Position ICC_VDW_GROUP1_POSITION = Position(4203.585f, 2464.422f, 364.87323f);
|
||||||
|
inline const Position ICC_VDW_GROUP2_POSITION = Position(4203.5806f, 2505.2383f, 364.87323f);
|
||||||
|
inline const Position ICC_VDW_PORTALSTART_POSITION = Position(4202.637f, 2488.171f, 375.00256f);
|
||||||
|
inline const Position ICC_SINDRAGOSA_TANK_POSITION = Position(4408.016f, 2508.0647f, 203.37955f); //X: 4183.136 Y: 2492.9358 Z: 364.87595 Orientation: 5.3975997
|
||||||
|
inline const Position ICC_SINDRAGOSA_FLYING_POSITION = Position(4525.6f, 2485.15f, 245.082f);
|
||||||
|
inline const Position ICC_SINDRAGOSA_RANGED_POSITION = Position(4441.572f, 2484.482f, 203.37836f);
|
||||||
|
inline const Position ICC_SINDRAGOSA_MELEE_POSITION = Position(4423.1646f, 2486.6792f, 203.3749f); //old 4423.4546f, 2491.7175f, 203.37686f
|
||||||
|
inline const Position ICC_SINDRAGOSA_BLISTERING_COLD_POSITION = Position(4473.6616f, 2484.8489f, 203.38258f);
|
||||||
|
inline const Position ICC_SINDRAGOSA_THOMB1_POSITION = Position(4433.6484f, 2469.4133f, 203.3806f);
|
||||||
|
inline const Position ICC_SINDRAGOSA_THOMB2_POSITION = Position(4434.143f, 2486.201f, 203.37473f);
|
||||||
|
inline const Position ICC_SINDRAGOSA_THOMB3_POSITION = Position(4436.1147f, 2501.464f, 203.38266f);
|
||||||
|
inline const Position ICC_SINDRAGOSA_UNCHAINEDMAGIC1_POSITION = Position(4444.9707f, 2455.7322f, 203.38701f);
|
||||||
|
inline const Position ICC_SINDRAGOSA_UNCHAINEDMAGIC2_POSITION = Position(4461.3945f, 2463.5513f, 203.38727f);
|
||||||
|
inline const Position ICC_SINDRAGOSA_UNCHAINEDMAGIC3_POSITION = Position(4473.6616f, 2484.8489f, 203.38258f);
|
||||||
|
inline const Position ICC_SINDRAGOSA_UNCHAINEDMAGIC4_POSITION = Position(4459.9336f, 2507.409f, 203.38606f);
|
||||||
|
inline const Position ICC_SINDRAGOSA_UNCHAINEDMAGIC5_POSITION = Position(4442.3096f, 2512.4688f, 203.38647f);
|
||||||
|
inline const Position ICC_SINDRAGOSA_CENTER_POSITION = Position(4408.0464f, 2484.478f, 203.37529f);
|
||||||
|
inline const Position ICC_SINDRAGOSA_THOMBMB2_POSITION = Position(4436.895f, 2498.1401f, 203.38133f);
|
||||||
|
inline const Position ICC_SINDRAGOSA_FBOMB_POSITION = Position(4449.3647f, 2486.4524f, 203.379f);
|
||||||
|
inline const Position ICC_SINDRAGOSA_FBOMB10_POSITION = Position(4449.3647f, 2486.4524f, 203.379f);
|
||||||
|
inline const Position ICC_SINDRAGOSA_LOS2_POSITION = Position(4441.8286f, 2501.946f, 203.38435f);
|
||||||
|
inline const Position ICC_LICH_KING_ADDS_POSITION = Position(476.7332f, -2095.3894f, 840.857f); // old 486.63647f, -2095.7915f, 840.857f
|
||||||
|
inline const Position ICC_LICH_KING_MELEE_POSITION = Position(503.5546f, -2106.8213f, 840.857f);
|
||||||
|
inline const Position ICC_LICH_KING_RANGED_POSITION = Position(501.3563f, -2085.1816f, 840.857f);
|
||||||
|
inline const Position ICC_LICH_KING_ASSISTHC_POSITION = Position(517.2145f, -2125.0674f, 840.857f);
|
||||||
|
inline const Position ICC_LICH_KING_CENTER_POSITION = Position(503.62036f, -2124.7336f, 840.857f);
|
||||||
|
inline const Position ICC_LK_FROST1_POSITION = Position(503.96548f, -2183.216f, 840.857f);
|
||||||
|
inline const Position ICC_LK_FROST2_POSITION = Position(563.07166f, -2125.7578f, 840.857f);
|
||||||
|
inline const Position ICC_LK_FROST3_POSITION = Position(503.40182f, -2067.3435f, 840.857f);
|
||||||
|
inline const Position ICC_LK_FROSTR1_POSITION = Position(481.168f, -2177.8723f, 840.857f);
|
||||||
|
inline const Position ICC_LK_FROSTR2_POSITION = Position(562.20807f, -2100.2393f, 840.857f);
|
||||||
|
inline const Position ICC_LK_FROSTR3_POSITION = Position(526.35297f, -2071.0317f, 840.857f);
|
||||||
|
inline const Position ICC_LK_VILE_SPIRIT1_POSITION = Position(505.24002f, -2086.7778f, 840.857f);
|
||||||
|
inline const Position ICC_LK_VILE_SPIRIT2_POSITION = Position(532.668f, -2122.603f, 840.857f);
|
||||||
|
inline const Position ICC_LK_VILE_SPIRIT3_POSITION = Position(502.8796f, -2159.7466f, 840.857f);
|
||||||
|
|
||||||
|
//Lord Marrogwar
|
||||||
|
class IccLmTankPositionAction : public AttackAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccLmTankPositionAction(PlayerbotAI* botAI, std::string const name = "icc lm tank position")
|
||||||
|
: AttackAction(botAI, name) {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
|
||||||
|
bool MoveTowardPosition(Position const& position, float incrementSize);
|
||||||
|
};
|
||||||
|
|
||||||
|
class IccSpikeAction : public AttackAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccSpikeAction(PlayerbotAI* botAI) : AttackAction(botAI, "icc spike") {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
|
||||||
|
std::vector<Unit*> FindAliveSpikes();
|
||||||
|
bool HandleSpikeMarking(std::vector<Unit*> const& spikes, Unit* boss);
|
||||||
|
bool HandleNoSpikesMarking(Unit* boss);
|
||||||
|
bool HandleSpikeAssignment(std::vector<Unit*> const& spikes, Unit* boss);
|
||||||
|
bool MoveTowardPosition(Position const& position, float incrementSize);
|
||||||
|
static std::vector<size_t> CalculateBalancedGroupSizes(size_t totalMembers, size_t numSpikes);
|
||||||
|
static size_t GetAssignedSpikeIndex(size_t memberIndex, std::vector<size_t> const& groupSizes);
|
||||||
|
static std::string GetRTIValueForSpike(size_t spikeIndex);
|
||||||
|
bool IsSpikeInColdFlame(Unit* spike);
|
||||||
|
static Player* GetSpikeVictim(Unit* spike);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Lady Deathwhisper
|
||||||
|
class IccDarkReckoningAction : public MovementAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccDarkReckoningAction(PlayerbotAI* botAI, std::string const name = "icc dark reckoning")
|
||||||
|
: MovementAction(botAI, name) {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class IccRangedPositionLadyDeathwhisperAction : public AttackAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccRangedPositionLadyDeathwhisperAction(PlayerbotAI* botAI, std::string const name = "icc ranged position lady deathwhisper")
|
||||||
|
: AttackAction(botAI, name) {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
|
||||||
|
bool MaintainRangedSpacing();
|
||||||
|
};
|
||||||
|
|
||||||
|
class IccAddsLadyDeathwhisperAction : public AttackAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccAddsLadyDeathwhisperAction(PlayerbotAI* botAI, std::string const name = "icc adds lady deathwhisper")
|
||||||
|
: AttackAction(botAI, name) {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
|
||||||
|
bool IsTargetedByShade(uint32 shadeEntry);
|
||||||
|
bool MoveTowardPosition(Position const& position, float incrementSize);
|
||||||
|
bool HandleAddTargeting(Unit* boss);
|
||||||
|
bool UpdateRaidTargetIcon(Unit* target);
|
||||||
|
bool HandleNonTankAddEvasion();
|
||||||
|
bool IsAdd(Unit* unit);
|
||||||
|
bool IsAssistTankAlive();
|
||||||
|
bool ApplyNearbyAddCC();
|
||||||
|
bool ApplyCCToAdd(Unit* add);
|
||||||
|
bool IsAddsAlive();
|
||||||
|
bool EngageBoss();
|
||||||
|
Unit* FindAndCollectAdd(Unit* boss);
|
||||||
|
Unit* FindAddNearBoss(Unit* boss, float maxDist);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class IccShadeLadyDeathwhisperAction : public MovementAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccShadeLadyDeathwhisperAction(PlayerbotAI* botAI, std::string const name = "icc shade lady deathwhisper")
|
||||||
|
: MovementAction(botAI, name) {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
//Gunship Battle
|
||||||
|
class IccRottingFrostGiantTankPositionAction : public AttackAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccRottingFrostGiantTankPositionAction(PlayerbotAI* botAI, std::string const name = "icc rotting frost giant tank position")
|
||||||
|
: AttackAction(botAI, name) {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Gunship Battle
|
||||||
|
class IccCannonFireAction : public Action
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccCannonFireAction(PlayerbotAI* botAI, std::string const name = "icc cannon fire") : Action(botAI, name) {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Unit* FindValidCannonTarget();
|
||||||
|
bool TryCastCannonSpell(uint32 spellId, Unit* target, Unit* vehicleBase);
|
||||||
|
};
|
||||||
|
|
||||||
|
class IccGunshipEnterCannonAction : public MovementAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccGunshipEnterCannonAction(PlayerbotAI* botAI, std::string const name = "icc gunship enter cannon")
|
||||||
|
: MovementAction(botAI, name) {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Unit* FindBestAvailableCannon();
|
||||||
|
bool IsValidCannon(Unit* vehicle);
|
||||||
|
bool EnterVehicle(Unit* vehicleBase, bool moveIfFar);
|
||||||
|
};
|
||||||
|
|
||||||
|
class IccGunshipRocketJumpAction : public AttackAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccGunshipRocketJumpAction(PlayerbotAI* botAI, std::string const name = "icc gunship rocket jump")
|
||||||
|
: AttackAction(botAI, name) {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum class GunshipSide
|
||||||
|
{
|
||||||
|
NONE,
|
||||||
|
ALLY,
|
||||||
|
HORDE
|
||||||
|
};
|
||||||
|
|
||||||
|
GunshipSide DetectShip() const;
|
||||||
|
Item* FindRocketPack() const;
|
||||||
|
bool UseRocketPack(Position const& destination, bool walkIfOutOfRange);
|
||||||
|
bool RocketPackJumpToward(Position const& target);
|
||||||
|
bool ExitCannonIfSeated();
|
||||||
|
bool CleanupSkullIcon(uint8 skullIconIndex);
|
||||||
|
bool UpdateBossSkullIcon(Unit* boss, uint8 skullIconIndex);
|
||||||
|
bool IsMainTankOnEnemyShip(GunshipSide side) const;
|
||||||
|
bool AnyNonTankAwayFromFriendly(GunshipSide side) const;
|
||||||
|
Unit* FindNearestFriendlyCannon(GunshipSide side) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
class IccGunshipRocketPackSetupAction : public MovementAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccGunshipRocketPackSetupAction(PlayerbotAI* botAI, std::string const name = "icc gunship rocket pack setup")
|
||||||
|
: MovementAction(botAI, name) {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Item* FindRocketPack() const;
|
||||||
|
bool AcquireRocketPack();
|
||||||
|
bool EquipRocketPack();
|
||||||
|
};
|
||||||
|
|
||||||
|
//DBS
|
||||||
|
class IccDbsTankPositionAction : public AttackAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccDbsTankPositionAction(PlayerbotAI* botAI, std::string const name = "icc dbs tank position")
|
||||||
|
: AttackAction(botAI, name) {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
|
||||||
|
bool CrowdControlBloodBeasts();
|
||||||
|
bool EvadeBloodBeasts();
|
||||||
|
bool PositionInRangedFormation();
|
||||||
|
};
|
||||||
|
|
||||||
|
class IccAddsDbsAction : public AttackAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccAddsDbsAction(PlayerbotAI* botAI, std::string const name = "icc adds dbs")
|
||||||
|
: AttackAction(botAI, name) {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
|
||||||
|
Unit* FindPriorityTarget(Unit* boss);
|
||||||
|
bool UpdateSkullMarker(Unit* priorityTarget);
|
||||||
|
};
|
||||||
|
|
||||||
|
class IccDogsTankPositionAction : public AttackAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccDogsTankPositionAction(PlayerbotAI* botAI, std::string const name = "icc dogs tank position")
|
||||||
|
: AttackAction(botAI, name) {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
//FESTERGUT
|
||||||
|
class IccFestergutGroupPositionAction : public AttackAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccFestergutGroupPositionAction(PlayerbotAI* botAI, std::string const name = "icc festergut group position")
|
||||||
|
: AttackAction(botAI, name) {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
|
||||||
|
bool HasSporesInGroup();
|
||||||
|
bool PositionNonTankMembers();
|
||||||
|
int CalculatePositionIndex(Group* group);
|
||||||
|
};
|
||||||
|
|
||||||
|
class IccFestergutSporeAction : public AttackAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccFestergutSporeAction(PlayerbotAI* botAI, std::string const name = "icc festergut spore")
|
||||||
|
: AttackAction(botAI, name) {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
|
||||||
|
Position CalculateSpreadPosition();
|
||||||
|
struct SporeInfo
|
||||||
|
{
|
||||||
|
std::vector<Unit*> sporedPlayers;
|
||||||
|
ObjectGuid lowestGuid;
|
||||||
|
bool hasLowestGuid = false;
|
||||||
|
};
|
||||||
|
SporeInfo FindSporedPlayers();
|
||||||
|
Position DetermineTargetPosition(bool hasSpore, const SporeInfo& sporeInfo, const Position& spreadRangedPos);
|
||||||
|
bool CheckMainTankSpore();
|
||||||
|
bool GooNear(Position const& pos);
|
||||||
|
};
|
||||||
|
|
||||||
|
class IccFestergutAvoidMalleableGooAction : public MovementAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccFestergutAvoidMalleableGooAction(PlayerbotAI* botAI,
|
||||||
|
std::string const name = "icc festergut avoid malleable goo")
|
||||||
|
: MovementAction(botAI, name) {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
//Rotface
|
||||||
|
class IccRotfaceTankPositionAction : public AttackAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccRotfaceTankPositionAction(PlayerbotAI* botAI, std::string const name = "icc rotface tank position")
|
||||||
|
: AttackAction(botAI, name) {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
|
||||||
|
bool MarkBossWithSkull(Unit* boss);
|
||||||
|
bool PositionMainTankAndMelee(Unit* boss, Unit* smallOoze = nullptr);
|
||||||
|
bool HandleAssistTankPositioning(Unit* boss);
|
||||||
|
Unit* FindAssignedBigOoze(Unit* boss, std::vector<Unit*>& bigOozes);
|
||||||
|
bool HandleBigOozeKiting(Unit* bigOoze);
|
||||||
|
};
|
||||||
|
|
||||||
|
class IccRotfaceGroupPositionAction : public AttackAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccRotfaceGroupPositionAction(PlayerbotAI* botAI, std::string const name = "icc rotface group position")
|
||||||
|
: AttackAction(botAI, name) {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
|
||||||
|
//bool MoveAwayFromBigOoze(Unit* bigOoze);
|
||||||
|
bool HandlePuddleAvoidance(Unit* boss);
|
||||||
|
bool MoveAwayFromPuddle(Unit* boss, Unit* puddle, float puddleDistance);
|
||||||
|
bool HandleOozeTargeting();
|
||||||
|
bool HandleOozeMemberPositioning(Unit* mySmallOoze);
|
||||||
|
bool PositionRangedAndHealers(Unit* boss,Unit* smallOoze);
|
||||||
|
bool PositionHeroicGrid(Unit* boss);
|
||||||
|
};
|
||||||
|
|
||||||
|
class IccRotfaceMoveAwayFromExplosionAction : public MovementAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccRotfaceMoveAwayFromExplosionAction(PlayerbotAI* botAI, std::string const name = "icc rotface move away from explosion")
|
||||||
|
: MovementAction(botAI, name),
|
||||||
|
_escapePosition(0.0f, 0.0f, 0.0f),
|
||||||
|
_hasEscape(false),
|
||||||
|
_holdUntil(0) {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Position _escapePosition;
|
||||||
|
bool _hasEscape;
|
||||||
|
uint32 _holdUntil;
|
||||||
|
};
|
||||||
|
|
||||||
|
class IccRotfaceAvoidVileGasAction : public MovementAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccRotfaceAvoidVileGasAction(PlayerbotAI* botAI, std::string const name = "icc rotface avoid vile gas")
|
||||||
|
: MovementAction(botAI, name),
|
||||||
|
_safeSpot(0.0f, 0.0f, 0.0f),
|
||||||
|
_hasSafeSpot(false) {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Position _safeSpot;
|
||||||
|
bool _hasSafeSpot;
|
||||||
|
};
|
||||||
|
|
||||||
|
//PP
|
||||||
|
class IccPutricideMutatedPlagueAction : public AttackAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccPutricideMutatedPlagueAction(PlayerbotAI* botAI, std::string const name = "icc putricide mutated plague")
|
||||||
|
: AttackAction(botAI, name) {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class IccPutricideGrowingOozePuddleAction : public AttackAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccPutricideGrowingOozePuddleAction(PlayerbotAI* botAI, std::string const name = "icc putricide growing ooze puddle")
|
||||||
|
: AttackAction(botAI, name) {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
|
||||||
|
Unit* FindClosestThreateningPuddle();
|
||||||
|
Position CalculateSafeMovePosition(Unit* closestPuddle);
|
||||||
|
bool IsPositionTooCloseToOtherPuddles(float x, float y, Unit* ignorePuddle);
|
||||||
|
bool PathCrossesAnyPuddle(float fromX, float fromY, float toX, float toY, Unit* ignorePuddle);
|
||||||
|
};
|
||||||
|
|
||||||
|
class IccPutricideVolatileOozeAction : public AttackAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccPutricideVolatileOozeAction(PlayerbotAI* botAI, std::string const name = "icc putricide volatile ooze")
|
||||||
|
: AttackAction(botAI, name) {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
|
||||||
|
bool MarkOozeWithSkull(Unit* ooze);
|
||||||
|
Unit* FindAuraTarget();
|
||||||
|
};
|
||||||
|
|
||||||
|
class IccPutricideGasCloudAction : public AttackAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccPutricideGasCloudAction(PlayerbotAI* botAI, std::string const name = "icc putricide gas cloud")
|
||||||
|
: AttackAction(botAI, name) {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
|
||||||
|
bool HandleGaseousBloatMovement(Unit* gasCloud);
|
||||||
|
bool HandleGroupAuraSituation(Unit* gasCloud);
|
||||||
|
bool GroupHasGaseousBloat(Group* group);
|
||||||
|
};
|
||||||
|
|
||||||
|
class IccPutricideAvoidMalleableGooAction : public MovementAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccPutricideAvoidMalleableGooAction(PlayerbotAI* botAI, std::string const name = "icc putricide avoid malleable goo")
|
||||||
|
: MovementAction(botAI, name) {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
|
||||||
|
bool HandleTankPositioning(Unit* boss);
|
||||||
|
bool HandleUnboundPlague(Unit* boss);
|
||||||
|
bool HandleBossPositioning(Unit* boss);
|
||||||
|
bool HasObstacleBetween(Position const& from, Position const& to);
|
||||||
|
bool IsOnPath(Position const& from, Position const& to, Position const& point, float threshold);
|
||||||
|
};
|
||||||
|
|
||||||
|
class IccPutricideAbominationAction : public AttackAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccPutricideAbominationAction(PlayerbotAI* botAI, std::string const name = "icc putricide abomination")
|
||||||
|
: AttackAction(botAI, name) {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool BecomeAbomination();
|
||||||
|
bool IsSomeoneAlreadyPiloting();
|
||||||
|
Unit* FindClosestPuddle(float maxRange);
|
||||||
|
Unit* PickSlashTarget(Unit* boss);
|
||||||
|
bool TryRegurgitate(Unit* abo, Unit* target);
|
||||||
|
bool TryEatOoze(Unit* abo, Unit* puddle);
|
||||||
|
};
|
||||||
|
|
||||||
|
//BPC
|
||||||
|
class IccBpcKelesethTankAction : public AttackAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccBpcKelesethTankAction(PlayerbotAI* botAI)
|
||||||
|
: AttackAction(botAI, "icc bpc keleseth tank") {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class IccBpcMainTankAction : public AttackAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccBpcMainTankAction(PlayerbotAI* botAI)
|
||||||
|
: AttackAction(botAI, "icc bpc main tank") {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
|
||||||
|
bool MarkEmpoweredPrince();
|
||||||
|
};
|
||||||
|
|
||||||
|
class IccBpcEmpoweredVortexAction : public MovementAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccBpcEmpoweredVortexAction(PlayerbotAI* botAI)
|
||||||
|
: MovementAction(botAI, "icc bpc empowered vortex") {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
|
||||||
|
bool MaintainRangedSpacing();
|
||||||
|
bool HandleEmpoweredVortexSpread();
|
||||||
|
};
|
||||||
|
|
||||||
|
class IccBpcKineticBombAction : public AttackAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccBpcKineticBombAction(PlayerbotAI* botAI)
|
||||||
|
: AttackAction(botAI, "icc bpc kinetic bomb") {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
|
||||||
|
Unit* FindNearestBomb();
|
||||||
|
};
|
||||||
|
|
||||||
|
class IccBpcBallOfFlameAction : public MovementAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccBpcBallOfFlameAction(PlayerbotAI* botAI)
|
||||||
|
: MovementAction(botAI, "icc bpc ball of flame") {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
//Blood Queen Lana'thel
|
||||||
|
class IccBqlGroupPositionAction : public AttackAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccBqlGroupPositionAction(PlayerbotAI* botAI)
|
||||||
|
: AttackAction(botAI, "icc bql group position") {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
|
||||||
|
bool HandleTankPosition(Unit* boss, Aura* frenzyAura, Aura* shadowAura);
|
||||||
|
bool HandleShadowsMovement();
|
||||||
|
Position AdjustControlPoint(const Position& wall, const Position& center, float factor);
|
||||||
|
Position CalculateBezierPoint(float t, const Position path[4]);
|
||||||
|
bool HandleGroupPosition(Unit* boss, Aura* frenzyAura, Aura* shadowAura);
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Evaluate curves
|
||||||
|
struct CurveInfo
|
||||||
|
{
|
||||||
|
Position moveTarget;
|
||||||
|
int curveIdx = 0;
|
||||||
|
bool foundSafe = false;
|
||||||
|
float minDist = 0.0f;
|
||||||
|
float score = 0.0f;
|
||||||
|
Position closestPoint;
|
||||||
|
float t_closest = 0.0f;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
class IccBqlPactOfDarkfallenAction : public MovementAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccBqlPactOfDarkfallenAction(PlayerbotAI* botAI)
|
||||||
|
: MovementAction(botAI, "icc bql pact of darkfallen") {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
|
||||||
|
bool CalculateCenterPosition(Position& targetPos, const std::vector<Player*>& playersWithAura);
|
||||||
|
bool MoveToTargetPosition(const Position& targetPos, int auraCount);
|
||||||
|
};
|
||||||
|
|
||||||
|
class IccBqlVampiricBiteAction : public AttackAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccBqlVampiricBiteAction(PlayerbotAI* botAI)
|
||||||
|
: AttackAction(botAI, "icc bql vampiric bite") {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
|
||||||
|
Player* FindBestBiteTarget(Group* group);
|
||||||
|
bool IsInvalidTarget(Player* player);
|
||||||
|
bool MoveTowardsTarget(Player* target);
|
||||||
|
bool CastVampiricBite(Player* target);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Sister Svalna
|
||||||
|
class IccValkyreSpearAction : public AttackAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccValkyreSpearAction(PlayerbotAI* botAI)
|
||||||
|
: AttackAction(botAI, "icc valkyre spear") {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class IccSisterSvalnaAction : public AttackAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccSisterSvalnaAction(PlayerbotAI* botAI)
|
||||||
|
: AttackAction(botAI, "icc sister svalna") {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Valithria Dreamwalker
|
||||||
|
|
||||||
|
class IccValithriaGroupAction : public AttackAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccValithriaGroupAction(PlayerbotAI* botAI)
|
||||||
|
: AttackAction(botAI, "icc valithria group") {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
|
||||||
|
bool Handle25ManGroupLogic();
|
||||||
|
bool HandleMarkingLogic(bool inGroup1, bool inGroup2, bool singleMarkMode);
|
||||||
|
bool Handle10ManGroupLogic();
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool ApplyCrowdControl(Unit* zombie);
|
||||||
|
};
|
||||||
|
|
||||||
|
class IccValithriaZombieKiteAction : public MovementAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccValithriaZombieKiteAction(PlayerbotAI* botAI)
|
||||||
|
: MovementAction(botAI, "icc valithria zombie kite") {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class IccValithriaPortalAction : public MovementAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccValithriaPortalAction(PlayerbotAI* botAI)
|
||||||
|
: MovementAction(botAI, "icc valithria portal") {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class IccValithriaHealAction : public AttackAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccValithriaHealAction(PlayerbotAI* botAI)
|
||||||
|
: AttackAction(botAI, "icc valithria heal") {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class IccValithriaDreamCloudAction : public MovementAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccValithriaDreamCloudAction(PlayerbotAI* botAI)
|
||||||
|
: MovementAction(botAI, "icc valithria dream cloud") {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<Creature*> CollectClouds(uint32 entry, Unit* reference);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Sindragosa
|
||||||
|
class IccSindragosaGroupPositionAction : public AttackAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccSindragosaGroupPositionAction(PlayerbotAI* botAI)
|
||||||
|
: AttackAction(botAI, "icc sindragosa group position") {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
|
||||||
|
bool HandleTankPositioning(Unit* boss);
|
||||||
|
bool HandleNonTankPositioning();
|
||||||
|
bool MoveIncrementallyToPosition(const Position& targetPos, float maxStep);
|
||||||
|
};
|
||||||
|
|
||||||
|
class IccSindragosaFrostBeaconAction : public MovementAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccSindragosaFrostBeaconAction(PlayerbotAI* botAI)
|
||||||
|
: MovementAction(botAI, "icc sindragosa frost beacon") {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
|
||||||
|
bool HandleSupportActions();
|
||||||
|
bool HandleBeaconedPlayer(const Unit* boss);
|
||||||
|
bool HandleNonBeaconedPlayer(const Unit* boss);
|
||||||
|
bool MoveToPositionIfNeeded(const Position& position, float tolerance);
|
||||||
|
bool MoveToPosition(const Position& position);
|
||||||
|
bool IsBossFlying(const Unit* boss);
|
||||||
|
bool TryDropTombFlares(Unit const* boss);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr float POSITION_TOLERANCE = 1.0f;
|
||||||
|
static constexpr float TOMB_POSITION_TOLERANCE = 0.5f;
|
||||||
|
static constexpr float MIN_SAFE_DISTANCE = 13.0f;
|
||||||
|
static constexpr float MOVE_TOLERANCE = 2.0f;
|
||||||
|
// Keyed per-instance to avoid cross-instance pollution when multiple ICCs run simultaneously
|
||||||
|
static std::map<uint32, std::set<int>> s_flaredRedThisPhase;
|
||||||
|
static std::map<uint32, bool> s_flaredBluePhase3;
|
||||||
|
static std::map<uint32, bool> s_lastPhase3;
|
||||||
|
static uint32 s_nextFlareMs;
|
||||||
|
static constexpr uint32 FLARE_ITEM_COOLDOWN_MS = 1000;
|
||||||
|
};
|
||||||
|
|
||||||
|
class IccSindragosaHotAction : public Action
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccSindragosaHotAction(PlayerbotAI* botAI) : Action(botAI, "icc sindragosa hot") {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class IccSindragosaBlisteringColdAction : public MovementAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccSindragosaBlisteringColdAction(PlayerbotAI* botAI)
|
||||||
|
: MovementAction(botAI, "icc sindragosa blistering cold") {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class IccSindragosaUnchainedMagicAction : public AttackAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccSindragosaUnchainedMagicAction(PlayerbotAI* botAI)
|
||||||
|
: AttackAction(botAI, "icc sindragosa unchained magic") {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class IccSindragosaChilledToTheBoneAction : public AttackAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccSindragosaChilledToTheBoneAction(PlayerbotAI* botAI)
|
||||||
|
: AttackAction(botAI, "icc sindragosa chilled to the bone") {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class IccSindragosaMysticBuffetAction : public MovementAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccSindragosaMysticBuffetAction(PlayerbotAI* botAI)
|
||||||
|
: MovementAction(botAI, "icc sindragosa mystic buffet") {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class IccSindragosaFrostBombAction : public MovementAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccSindragosaFrostBombAction(PlayerbotAI* botAI)
|
||||||
|
: MovementAction(botAI, "icc sindragosa frost bomb") {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct FrostBombContext
|
||||||
|
{
|
||||||
|
Unit* marker = nullptr;
|
||||||
|
std::vector<Unit*> tombs;
|
||||||
|
};
|
||||||
|
bool CollectContext(FrostBombContext& ctx) const;
|
||||||
|
int ResolveGroupIndex(Group* group) const;
|
||||||
|
void PinGroupToCurrentZone();
|
||||||
|
std::vector<Unit*> SelectTombs(std::vector<Unit*> const& tombs, int groupIndex, int groupCount) const;
|
||||||
|
Unit* ResolveStickyTomb(std::vector<Unit*> const& myTombs);
|
||||||
|
bool HandleRtiMarking(Group* group, int groupIndex, std::vector<Unit*> const& myTombs, Unit* losTomb);
|
||||||
|
// Keyed per-instance to avoid cross-instance pollution when multiple ICCs run simultaneously
|
||||||
|
static std::map<std::pair<uint32, ObjectGuid>, int> s_groupAssignments;
|
||||||
|
static std::map<std::pair<uint32, ObjectGuid>, ObjectGuid> s_tombAssignments;
|
||||||
|
static std::set<std::pair<uint32, ObjectGuid>> s_freedFallback;
|
||||||
|
|
||||||
|
// Per-bot last LOS move stamp. When the LOS tomb dies/loses mark mid-walk
|
||||||
|
// the bot would otherwise freeze in the open. Replaying the last move for
|
||||||
|
// up to 2 seconds keeps it on its path until a new LOS target is chosen.
|
||||||
|
struct LastLosMove
|
||||||
|
{
|
||||||
|
uint32 timestampMs = 0;
|
||||||
|
float x = 0.0f;
|
||||||
|
float y = 0.0f;
|
||||||
|
float z = 0.0f;
|
||||||
|
};
|
||||||
|
// Keyed per-instance to avoid cross-instance pollution
|
||||||
|
static std::map<std::pair<uint32, ObjectGuid>, LastLosMove> s_lastLosMove;
|
||||||
|
};
|
||||||
|
|
||||||
|
class IccSindragosaTankSwapPositionAction : public AttackAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccSindragosaTankSwapPositionAction(PlayerbotAI* botAI)
|
||||||
|
: AttackAction(botAI, "icc sindragosa tank swap position") {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
//LK
|
||||||
|
class IccLichKingShadowTrapAction : public MovementAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccLichKingShadowTrapAction(PlayerbotAI* botAI)
|
||||||
|
: MovementAction(botAI, "icc lich king shadow trap") {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class IccLichKingNecroticPlagueAction : public MovementAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccLichKingNecroticPlagueAction(PlayerbotAI* botAI)
|
||||||
|
: MovementAction(botAI, "icc lich king necrotic plague") {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class IccLichKingWinterAction : public AttackAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccLichKingWinterAction(PlayerbotAI* botAI)
|
||||||
|
: AttackAction(botAI, "icc lich king winter") {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
|
||||||
|
bool IsPositionSafeFromDefile(float x, float y, float z, float minSafeDistance) const;
|
||||||
|
bool IsPositionSafeFromShadowTraps(float x, float y) const;
|
||||||
|
bool IsValidCollectibleAdd(Unit* unit) const;
|
||||||
|
bool HandleTankPositioning();
|
||||||
|
bool HandleMeleePositioning();
|
||||||
|
bool HandleRangedPositioning();
|
||||||
|
bool HandleMainTankAddManagement(Unit* boss, Position const* tankPos);
|
||||||
|
bool HandleAssistTankAddManagement(Unit* boss, Position const* tankPos);
|
||||||
|
bool HandlePetManagement();
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr float PLATFORM_Z = 840.857f;
|
||||||
|
static constexpr float BEHIND_DISTANCE = 4.0f;
|
||||||
|
bool FixPlatformPosition();
|
||||||
|
bool ClearInvalidTarget();
|
||||||
|
Position const* GetMainTankPosition();
|
||||||
|
Position const* GetMainTankRangedPosition();
|
||||||
|
bool TryMoveToPosition(float x, float y, float z, bool forced = true);
|
||||||
|
};
|
||||||
|
|
||||||
|
class IccLichKingAddsAction : public AttackAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccLichKingAddsAction(PlayerbotAI* botAI)
|
||||||
|
: AttackAction(botAI, "icc lich king adds") {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
|
||||||
|
bool HandleTeleportationFixes(Difficulty diff, Unit* terenas);
|
||||||
|
bool HandleHeroicNonTankPositioning(Difficulty diff, Unit* terenas);
|
||||||
|
bool HandleSpiritMarkingAndTargeting(Difficulty diff, Unit* terenas);
|
||||||
|
bool HandleQuakeMechanics(Unit* boss);
|
||||||
|
bool HandleShamblingHorrors(Unit* boss, bool hasPlague);
|
||||||
|
bool HandleRagingSpiritFlanking(Unit* boss, bool hasPlague, Difficulty diff);
|
||||||
|
bool HandleAssistTankAddManagement(Unit* boss, Difficulty diff);
|
||||||
|
bool HandleMeleePositioning(Unit* boss, bool hasPlague, Difficulty diff);
|
||||||
|
bool HandleMainTankTargeting(Unit* boss, Difficulty diff);
|
||||||
|
bool HandleNonTankHeroicPositioning(Unit* boss, Difficulty diff, bool hasPlague);
|
||||||
|
bool HandleRangedPositioning(Unit* boss, bool hasPlague, Difficulty diff);
|
||||||
|
bool HandleDefileMechanics(Unit* boss, Difficulty diff);
|
||||||
|
bool HandleCenterStacking(Unit* boss, Difficulty diff);
|
||||||
|
bool HandleValkyrMechanics(Difficulty diff);
|
||||||
|
bool HandleValkyrMarking(std::vector<Unit*> const& valkyrs, Difficulty diff);
|
||||||
|
bool HandleValkyrAssignment(std::vector<Unit*> const& valkyrs);
|
||||||
|
bool HandleVileSpiritMechanics();
|
||||||
|
bool HandleIceSphereMechanics();
|
||||||
|
bool ApplyCCToValkyr(Unit* valkyr);
|
||||||
|
bool IsValkyr(Unit* unit);
|
||||||
|
std::vector<size_t> CalculateBalancedGroupSizes(size_t totalAssist, size_t numValkyrs);
|
||||||
|
size_t GetAssignedValkyrIndex(size_t assistIndex, std::vector<size_t> const& groupSizes);
|
||||||
|
std::string GetRTIValueForValkyr(size_t valkyrIndex);
|
||||||
|
std::pair<float, float> DefileAwareStep(float tx, float ty,
|
||||||
|
std::vector<Unit*> const& defiles,
|
||||||
|
Difficulty diff);
|
||||||
|
};
|
||||||
|
|
||||||
|
class IccLichKingSpiritBombAction : public MovementAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccLichKingSpiritBombAction(PlayerbotAI* botAI)
|
||||||
|
: MovementAction(botAI, "icc lich king spirit bomb") {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
|
||||||
|
static bool IsBombThreatActive(PlayerbotAI* botAI, Player* bot);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
821
src/Ai/Raid/ICC/Action/ICCActions_BPC.cpp
Normal file
821
src/Ai/Raid/ICC/Action/ICCActions_BPC.cpp
Normal file
@ -0,0 +1,821 @@
|
|||||||
|
#include "ICCActions.h"
|
||||||
|
#include "NearestNpcsValue.h"
|
||||||
|
#include "ObjectAccessor.h"
|
||||||
|
#include "Playerbots.h"
|
||||||
|
#include "Vehicle.h"
|
||||||
|
#include "RtiValue.h"
|
||||||
|
#include "GenericSpellActions.h"
|
||||||
|
#include "GenericActions.h"
|
||||||
|
#include "ICCTriggers.h"
|
||||||
|
#include "Multiplier.h"
|
||||||
|
|
||||||
|
static float const BPC_FLOOR_Z = 361.18222f;
|
||||||
|
|
||||||
|
static bool CastClassTaunt(Player* bot, PlayerbotAI* botAI, Unit* target)
|
||||||
|
{
|
||||||
|
if (!target || !target->IsAlive())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
switch (bot->getClass())
|
||||||
|
{
|
||||||
|
case CLASS_PALADIN:
|
||||||
|
{
|
||||||
|
bot->RemoveSpellCooldown(SPELL_TAUNT_PALADIN, true);
|
||||||
|
if (botAI->CastSpell("hand of reckoning", target))
|
||||||
|
return true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CLASS_DEATH_KNIGHT:
|
||||||
|
{
|
||||||
|
bot->RemoveSpellCooldown(SPELL_TAUNT_DK, true);
|
||||||
|
if (botAI->CastSpell("dark command", target))
|
||||||
|
return true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CLASS_DRUID:
|
||||||
|
{
|
||||||
|
bot->RemoveSpellCooldown(SPELL_TAUNT_DRUID, true);
|
||||||
|
if (botAI->CastSpell("growl", target))
|
||||||
|
return true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CLASS_WARRIOR:
|
||||||
|
{
|
||||||
|
bot->RemoveSpellCooldown(SPELL_TAUNT_WARRIOR, true);
|
||||||
|
if (botAI->CastSpell("taunt", target))
|
||||||
|
return true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (botAI->CastSpell("shoot", target) || botAI->CastSpell("throw", target))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IccBpcKelesethTankAction::Execute(Event /*event*/)
|
||||||
|
{
|
||||||
|
Unit* boss = AI_VALUE2(Unit*, "find target", "prince keleseth");
|
||||||
|
if (!boss)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
static float const Z_TELEPORT_THRESHOLD = 6.0f;
|
||||||
|
|
||||||
|
// Teleport Z-axis back to center-level if the bot is significantly off in Z
|
||||||
|
float centerZ = ICC_BPC_CENTER_POSITION.GetPositionZ();
|
||||||
|
if (std::abs(bot->GetPositionZ() - centerZ) > Z_TELEPORT_THRESHOLD)
|
||||||
|
{
|
||||||
|
bot->TeleportTo(bot->GetMapId(), bot->GetPositionX(), bot->GetPositionY(), centerZ, bot->GetOrientation());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!botAI->IsAssistTank(bot))
|
||||||
|
{
|
||||||
|
// Non-assist-tank: pull toward center if too far
|
||||||
|
static float const CENTER_MOVE_THRESHOLD = 30.0f;
|
||||||
|
static float const MOVE_INCREMENT = 15.0f;
|
||||||
|
float distToCenter = bot->GetExactDist2d(ICC_BPC_CENTER_POSITION);
|
||||||
|
if (distToCenter > CENTER_MOVE_THRESHOLD)
|
||||||
|
{
|
||||||
|
float dirX = ICC_BPC_CENTER_POSITION.GetPositionX() - bot->GetPositionX();
|
||||||
|
float dirY = ICC_BPC_CENTER_POSITION.GetPositionY() - bot->GetPositionY();
|
||||||
|
float length = std::sqrt(dirX * dirX + dirY * dirY);
|
||||||
|
if (length > 0.001f)
|
||||||
|
{
|
||||||
|
dirX /= length;
|
||||||
|
dirY /= length;
|
||||||
|
float needed = distToCenter - CENTER_MOVE_THRESHOLD;
|
||||||
|
float step = std::min(MOVE_INCREMENT, needed);
|
||||||
|
float moveX = bot->GetPositionX() + dirX * step;
|
||||||
|
float moveY = bot->GetPositionY() + dirY * step;
|
||||||
|
MoveTo(bot->GetMapId(), moveX, moveY, bot->GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!botAI->IsAssistTank(bot))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
bool const isBossVictim = boss->GetVictim() == bot;
|
||||||
|
|
||||||
|
if (!isBossVictim)
|
||||||
|
{
|
||||||
|
CastClassTaunt(bot, botAI, boss);
|
||||||
|
bot->SetTarget(boss->GetGUID());
|
||||||
|
bot->SetFacingToObject(boss);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Collect stray Dark Nuclei via taunt, move toward if out of range
|
||||||
|
static float const TAUNT_RANGE = 30.0f;
|
||||||
|
std::list<Creature*> nuclei;
|
||||||
|
bot->GetCreatureListWithEntryInGrid(nuclei, NPC_DARK_NUCLEUS, 100.0f);
|
||||||
|
|
||||||
|
Unit* strayNucleus = nullptr;
|
||||||
|
for (Creature* nucleus : nuclei)
|
||||||
|
{
|
||||||
|
if (nucleus && nucleus->IsAlive() && nucleus->GetVictim() != bot)
|
||||||
|
{
|
||||||
|
strayNucleus = nucleus;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strayNucleus)
|
||||||
|
{
|
||||||
|
float dist = bot->GetExactDist2d(strayNucleus);
|
||||||
|
if (dist <= TAUNT_RANGE)
|
||||||
|
CastClassTaunt(bot, botAI, strayNucleus);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
float dirX = strayNucleus->GetPositionX() - bot->GetPositionX();
|
||||||
|
float dirY = strayNucleus->GetPositionY() - bot->GetPositionY();
|
||||||
|
float length = std::sqrt(dirX * dirX + dirY * dirY);
|
||||||
|
if (length > 0.001f)
|
||||||
|
{
|
||||||
|
dirX /= length;
|
||||||
|
dirY /= length;
|
||||||
|
static float const NUCLEUS_MOVE_STEP = 10.0f;
|
||||||
|
static float const NUCLEUS_APPROACH_BUFFER = 5.0f;
|
||||||
|
float step = std::min(NUCLEUS_MOVE_STEP, dist - TAUNT_RANGE + NUCLEUS_APPROACH_BUFFER);
|
||||||
|
float moveX = bot->GetPositionX() + dirX * step;
|
||||||
|
float moveY = bot->GetPositionY() + dirY * step;
|
||||||
|
MoveTo(bot->GetMapId(), moveX, moveY, bot->GetPositionZ(),
|
||||||
|
false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bot->SetTarget(boss->GetGUID());
|
||||||
|
bot->SetFacingToObject(boss);
|
||||||
|
Attack(boss);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IccBpcMainTankAction::Execute(Event /*event*/)
|
||||||
|
{
|
||||||
|
if (!botAI->IsMainTank(bot))
|
||||||
|
{
|
||||||
|
if (botAI->IsTank(bot))
|
||||||
|
MarkEmpoweredPrince();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Unit* valanar = AI_VALUE2(Unit*, "find target", "prince valanar");
|
||||||
|
Unit* taldaram = AI_VALUE2(Unit*, "find target", "prince taldaram");
|
||||||
|
|
||||||
|
bool const isVictimOfValanar = valanar && valanar->GetVictim() == bot;
|
||||||
|
bool const isVictimOfTaldaram = taldaram && taldaram->GetVictim() == bot;
|
||||||
|
|
||||||
|
// Move to MT position if targeted by both princes and not already close
|
||||||
|
static float const MT_POSITION_THRESHOLD = 15.0f;
|
||||||
|
if (isVictimOfValanar && isVictimOfTaldaram && bot->GetExactDist2d(ICC_BPC_MT_POSITION) > MT_POSITION_THRESHOLD)
|
||||||
|
{
|
||||||
|
float dirX = ICC_BPC_MT_POSITION.GetPositionX() - bot->GetPositionX();
|
||||||
|
float dirY = ICC_BPC_MT_POSITION.GetPositionY() - bot->GetPositionY();
|
||||||
|
float length = std::sqrt(dirX * dirX + dirY * dirY);
|
||||||
|
if (length > 0.001f)
|
||||||
|
{
|
||||||
|
dirX /= length;
|
||||||
|
dirY /= length;
|
||||||
|
float moveDist = std::min(3.0f, length);
|
||||||
|
float moveX = bot->GetPositionX() + dirX * moveDist;
|
||||||
|
float moveY = bot->GetPositionY() + dirY * moveDist;
|
||||||
|
MoveTo(bot->GetMapId(), moveX, moveY, bot->GetPositionZ(), false, false, false, false,
|
||||||
|
MovementPriority::MOVEMENT_COMBAT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Taunt princes not targeting us
|
||||||
|
if (valanar && !isVictimOfValanar)
|
||||||
|
{
|
||||||
|
CastClassTaunt(bot, botAI, valanar);
|
||||||
|
bot->SetTarget(valanar->GetGUID());
|
||||||
|
bot->SetFacingToObject(valanar);
|
||||||
|
Attack(valanar);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (taldaram && !isVictimOfTaldaram)
|
||||||
|
{
|
||||||
|
CastClassTaunt(bot, botAI, taldaram);
|
||||||
|
bot->SetTarget(taldaram->GetGUID());
|
||||||
|
bot->SetFacingToObject(taldaram);
|
||||||
|
Attack(taldaram);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Taunt nearby hostile adds not targeting a tank
|
||||||
|
GuidVector const npcs = AI_VALUE(GuidVector, "nearest hostile npcs");
|
||||||
|
for (auto const& npc : npcs)
|
||||||
|
{
|
||||||
|
Unit* unit = botAI->GetUnit(npc);
|
||||||
|
if (!unit || !unit->IsAlive())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (unit->GetEntry() == NPC_PRINCE_KELESETH || unit->GetEntry() == NPC_PRINCE_VALANAR ||
|
||||||
|
unit->GetEntry() == NPC_PRINCE_TALDARAM || unit->GetEntry() == NPC_DARK_NUCLEUS)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
static float const ADD_TAUNT_RANGE = 20.0f;
|
||||||
|
if (bot->GetDistance2d(unit) > ADD_TAUNT_RANGE)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Unit* victim = unit->GetVictim();
|
||||||
|
Player* victimPlayer = victim ? victim->ToPlayer() : nullptr;
|
||||||
|
if (!victimPlayer || !botAI->IsTank(victimPlayer))
|
||||||
|
{
|
||||||
|
CastClassTaunt(bot, botAI, unit);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Target marking for all tanks, called after main tank priority actions
|
||||||
|
if (botAI->IsTank(bot))
|
||||||
|
MarkEmpoweredPrince();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IccBpcMainTankAction::MarkEmpoweredPrince()
|
||||||
|
{
|
||||||
|
static constexpr uint8 SKULL_RAID_ICON = 7;
|
||||||
|
|
||||||
|
// Find empowered prince (Invocation of Blood)
|
||||||
|
Unit* empoweredPrince = nullptr;
|
||||||
|
GuidVector const& targets = AI_VALUE(GuidVector, "possible targets");
|
||||||
|
|
||||||
|
for (auto const& targetGuid : targets)
|
||||||
|
{
|
||||||
|
Unit* unit = botAI->GetUnit(targetGuid);
|
||||||
|
if (!unit || !unit->IsAlive())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (botAI->HasAura("Invocation of Blood", unit))
|
||||||
|
{
|
||||||
|
uint32 const entry = unit->GetEntry();
|
||||||
|
if (entry == NPC_PRINCE_KELESETH || entry == NPC_PRINCE_VALANAR || entry == NPC_PRINCE_TALDARAM)
|
||||||
|
{
|
||||||
|
empoweredPrince = unit;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle marking if we found an empowered prince
|
||||||
|
if (empoweredPrince && empoweredPrince->IsAlive())
|
||||||
|
{
|
||||||
|
Group* group = bot->GetGroup();
|
||||||
|
if (group)
|
||||||
|
{
|
||||||
|
ObjectGuid const currentSkullGuid = group->GetTargetIcon(SKULL_RAID_ICON);
|
||||||
|
Unit* markedUnit = botAI->GetUnit(currentSkullGuid);
|
||||||
|
|
||||||
|
// Clear dead marks or marks that are not on empowered prince
|
||||||
|
if (markedUnit && (!markedUnit->IsAlive() || markedUnit != empoweredPrince))
|
||||||
|
group->SetTargetIcon(SKULL_RAID_ICON, bot->GetGUID(), ObjectGuid::Empty);
|
||||||
|
|
||||||
|
// Mark alive empowered prince if needed
|
||||||
|
if (!currentSkullGuid || !markedUnit)
|
||||||
|
group->SetTargetIcon(SKULL_RAID_ICON, bot->GetGUID(), empoweredPrince->GetGUID());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IccBpcEmpoweredVortexAction::Execute(Event /*event*/)
|
||||||
|
{
|
||||||
|
Unit* valanar = AI_VALUE2(Unit*, "find target", "prince valanar");
|
||||||
|
if (!valanar)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Check if boss is casting empowered vortex
|
||||||
|
bool const isCastingVortex = valanar->HasUnitState(UNIT_STATE_CASTING) &&
|
||||||
|
(valanar->FindCurrentSpellBySpellId(SPELL_EMPOWERED_SHOCK_VORTEX1) ||
|
||||||
|
valanar->FindCurrentSpellBySpellId(SPELL_EMPOWERED_SHOCK_VORTEX2) ||
|
||||||
|
valanar->FindCurrentSpellBySpellId(SPELL_EMPOWERED_SHOCK_VORTEX3) ||
|
||||||
|
valanar->FindCurrentSpellBySpellId(SPELL_EMPOWERED_SHOCK_VORTEX4));
|
||||||
|
|
||||||
|
// Use complex positioning system for empowered vortex
|
||||||
|
if (isCastingVortex)
|
||||||
|
return HandleEmpoweredVortexSpread();
|
||||||
|
|
||||||
|
// Use simple ranged spreading for non-vortex situations
|
||||||
|
return MaintainRangedSpacing();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IccBpcEmpoweredVortexAction::MaintainRangedSpacing()
|
||||||
|
{
|
||||||
|
static float const IDEAL_RADIUS = 25.0f;
|
||||||
|
static float const RADIUS_TOLERANCE = 3.0f;
|
||||||
|
static float const MOVE_INCREMENT = 3.0f;
|
||||||
|
static float const MIN_SPACING = 13.0f;
|
||||||
|
|
||||||
|
bool const isRanged = botAI->IsRanged(bot) || botAI->IsHeal(bot);
|
||||||
|
if (!isRanged)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Unit* valanar = AI_VALUE2(Unit*, "find target", "prince valanar");
|
||||||
|
if (!valanar)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Collect ranged/healer bots sorted by GUID for consistent slot assignment
|
||||||
|
Group* group = bot->GetGroup();
|
||||||
|
if (!group)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
std::vector<Player*> rangedBots;
|
||||||
|
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
|
||||||
|
{
|
||||||
|
Player* member = itr->GetSource();
|
||||||
|
if (!member || !member->IsAlive())
|
||||||
|
continue;
|
||||||
|
if (botAI->IsTank(member))
|
||||||
|
continue;
|
||||||
|
PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member);
|
||||||
|
if (!memberAI)
|
||||||
|
continue;
|
||||||
|
if (memberAI->IsRanged(member) || memberAI->IsHeal(member))
|
||||||
|
rangedBots.push_back(member);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rangedBots.empty())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
std::sort(rangedBots.begin(), rangedBots.end(),
|
||||||
|
[](Player* a, Player* b) { return a->GetGUID() < b->GetGUID(); });
|
||||||
|
|
||||||
|
// Find this bot's slot index
|
||||||
|
int slotIndex = -1;
|
||||||
|
for (size_t i = 0; i < rangedBots.size(); ++i)
|
||||||
|
{
|
||||||
|
if (rangedBots[i] == bot)
|
||||||
|
{
|
||||||
|
slotIndex = static_cast<int>(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (slotIndex < 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Calculate angular position around Valanar
|
||||||
|
float angleStep = 2.0f * static_cast<float>(M_PI) / static_cast<float>(rangedBots.size());
|
||||||
|
float targetAngle = angleStep * static_cast<float>(slotIndex);
|
||||||
|
|
||||||
|
// Collect shock vortex positions to avoid
|
||||||
|
static float const VORTEX_AVOID_DIST = 13.0f;
|
||||||
|
std::list<Creature*> vortexList;
|
||||||
|
bot->GetCreatureListWithEntryInGrid(vortexList, NPC_SHOCK_VORTEX, 100.0f);
|
||||||
|
|
||||||
|
// Find safe angle — try assigned slot first, then nudge away from vortices
|
||||||
|
static float const ANGLE_NUDGE = static_cast<float>(M_PI) / 18.0f;
|
||||||
|
static int const MAX_NUDGE_STEPS = 18;
|
||||||
|
float bestAngle = targetAngle;
|
||||||
|
|
||||||
|
auto isAngleSafe = [&](float angle) -> bool
|
||||||
|
{
|
||||||
|
float testX = valanar->GetPositionX() + IDEAL_RADIUS * std::cos(angle);
|
||||||
|
float testY = valanar->GetPositionY() + IDEAL_RADIUS * std::sin(angle);
|
||||||
|
for (Creature* vortex : vortexList)
|
||||||
|
{
|
||||||
|
if (!vortex || !vortex->IsAlive())
|
||||||
|
continue;
|
||||||
|
float vdx = testX - vortex->GetPositionX();
|
||||||
|
float vdy = testY - vortex->GetPositionY();
|
||||||
|
if (std::sqrt(vdx * vdx + vdy * vdy) < VORTEX_AVOID_DIST)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!isAngleSafe(bestAngle))
|
||||||
|
{
|
||||||
|
for (int i = 1; i <= MAX_NUDGE_STEPS; ++i)
|
||||||
|
{
|
||||||
|
float plusAngle = targetAngle + ANGLE_NUDGE * static_cast<float>(i);
|
||||||
|
if (isAngleSafe(plusAngle))
|
||||||
|
{
|
||||||
|
bestAngle = plusAngle;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
float minusAngle = targetAngle - ANGLE_NUDGE * static_cast<float>(i);
|
||||||
|
if (isAngleSafe(minusAngle))
|
||||||
|
{
|
||||||
|
bestAngle = minusAngle;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float targetX = valanar->GetPositionX() + IDEAL_RADIUS * std::cos(bestAngle);
|
||||||
|
float targetY = valanar->GetPositionY() + IDEAL_RADIUS * std::sin(bestAngle);
|
||||||
|
float targetZ = bot->GetPositionZ();
|
||||||
|
|
||||||
|
float distToSlot = bot->GetExactDist2d(targetX, targetY);
|
||||||
|
if (distToSlot <= RADIUS_TOLERANCE)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Move toward assigned slot
|
||||||
|
float dx = targetX - bot->GetPositionX();
|
||||||
|
float dy = targetY - bot->GetPositionY();
|
||||||
|
float len = std::sqrt(dx * dx + dy * dy);
|
||||||
|
if (len < 0.001f)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
dx /= len;
|
||||||
|
dy /= len;
|
||||||
|
float step = std::min(MOVE_INCREMENT, distToSlot);
|
||||||
|
float moveX = bot->GetPositionX() + dx * step;
|
||||||
|
float moveY = bot->GetPositionY() + dy * step;
|
||||||
|
|
||||||
|
if (bot->IsWithinLOS(moveX, moveY, targetZ))
|
||||||
|
{
|
||||||
|
MoveTo(bot->GetMapId(), moveX, moveY, targetZ, false, false, false, true,
|
||||||
|
MovementPriority::MOVEMENT_NORMAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IccBpcEmpoweredVortexAction::HandleEmpoweredVortexSpread()
|
||||||
|
{
|
||||||
|
static std::map<std::pair<uint32, ObjectGuid>, uint32> spreadLockTimers;
|
||||||
|
static uint32 const SPREAD_LOCK_DURATION_MS = 250;
|
||||||
|
static float const MOVE_INCREMENT = 4.0f;
|
||||||
|
static float const SLOT_TOLERANCE = 2.0f;
|
||||||
|
static float const MIN_SPACING = 13.0f;
|
||||||
|
|
||||||
|
if (botAI->IsTank(bot))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Unit* valanar = AI_VALUE2(Unit*, "find target", "prince valanar");
|
||||||
|
if (!valanar)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// If locked in position, don't move — allow combat/healing
|
||||||
|
uint32 now = getMSTime();
|
||||||
|
|
||||||
|
// Prune expired entries to prevent unbounded growth
|
||||||
|
for (auto it = spreadLockTimers.begin(); it != spreadLockTimers.end(); )
|
||||||
|
{
|
||||||
|
if (getMSTimeDiff(it->second, now) >= SPREAD_LOCK_DURATION_MS)
|
||||||
|
it = spreadLockTimers.erase(it);
|
||||||
|
else
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32 const instanceId = bot->GetInstanceId();
|
||||||
|
auto it = spreadLockTimers.find({instanceId, bot->GetGUID()});
|
||||||
|
if (it != spreadLockTimers.end())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Collect ALL non-tank bots for spreading (melee must scatter too)
|
||||||
|
Group* group = bot->GetGroup();
|
||||||
|
if (!group)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
std::vector<Player*> spreadBots;
|
||||||
|
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
|
||||||
|
{
|
||||||
|
Player* member = itr->GetSource();
|
||||||
|
if (!member || !member->IsAlive())
|
||||||
|
continue;
|
||||||
|
if (botAI->IsTank(member))
|
||||||
|
continue;
|
||||||
|
spreadBots.push_back(member);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (spreadBots.empty())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
std::sort(spreadBots.begin(), spreadBots.end(),
|
||||||
|
[](Player* a, Player* b) { return a->GetGUID() < b->GetGUID(); });
|
||||||
|
|
||||||
|
int slotIndex = -1;
|
||||||
|
for (size_t i = 0; i < spreadBots.size(); ++i)
|
||||||
|
{
|
||||||
|
if (spreadBots[i] == bot)
|
||||||
|
{
|
||||||
|
slotIndex = static_cast<int>(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (slotIndex < 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Calculate radius so angular spacing >= MIN_SPACING
|
||||||
|
// Arc length between slots = 2*pi*R / N, need >= MIN_SPACING
|
||||||
|
float const botCount = static_cast<float>(spreadBots.size());
|
||||||
|
float idealRadius = (botCount * MIN_SPACING) / (2.0f * static_cast<float>(M_PI));
|
||||||
|
// Clamp radius to room bounds (room ~40y radius from center)
|
||||||
|
if (idealRadius < 15.0f)
|
||||||
|
idealRadius = 15.0f;
|
||||||
|
if (idealRadius > 38.0f)
|
||||||
|
idealRadius = 38.0f;
|
||||||
|
|
||||||
|
float angleStep = 2.0f * static_cast<float>(M_PI) / botCount;
|
||||||
|
float targetAngle = angleStep * static_cast<float>(slotIndex);
|
||||||
|
|
||||||
|
// Collect shock vortex positions to avoid
|
||||||
|
static float const VORTEX_AVOID_DIST = 13.0f;
|
||||||
|
std::list<Creature*> vortexList;
|
||||||
|
bot->GetCreatureListWithEntryInGrid(vortexList, NPC_SHOCK_VORTEX, 100.0f);
|
||||||
|
|
||||||
|
// Find safe angle — try assigned slot first, then nudge away from vortices
|
||||||
|
static float const ANGLE_NUDGE = static_cast<float>(M_PI) / 18.0f;
|
||||||
|
static int const MAX_NUDGE_STEPS = 18;
|
||||||
|
float bestAngle = targetAngle;
|
||||||
|
|
||||||
|
auto isAngleSafe = [&](float angle) -> bool
|
||||||
|
{
|
||||||
|
float testX = valanar->GetPositionX() + idealRadius * std::cos(angle);
|
||||||
|
float testY = valanar->GetPositionY() + idealRadius * std::sin(angle);
|
||||||
|
for (Creature* vortex : vortexList)
|
||||||
|
{
|
||||||
|
if (!vortex || !vortex->IsAlive())
|
||||||
|
continue;
|
||||||
|
float vdx = testX - vortex->GetPositionX();
|
||||||
|
float vdy = testY - vortex->GetPositionY();
|
||||||
|
if (std::sqrt(vdx * vdx + vdy * vdy) < VORTEX_AVOID_DIST)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!isAngleSafe(bestAngle))
|
||||||
|
{
|
||||||
|
for (int i = 1; i <= MAX_NUDGE_STEPS; ++i)
|
||||||
|
{
|
||||||
|
float plusAngle = targetAngle + ANGLE_NUDGE * static_cast<float>(i);
|
||||||
|
if (isAngleSafe(plusAngle))
|
||||||
|
{
|
||||||
|
bestAngle = plusAngle;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
float minusAngle = targetAngle - ANGLE_NUDGE * static_cast<float>(i);
|
||||||
|
if (isAngleSafe(minusAngle))
|
||||||
|
{
|
||||||
|
bestAngle = minusAngle;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float targetX = valanar->GetPositionX() + idealRadius * std::cos(bestAngle);
|
||||||
|
float targetY = valanar->GetPositionY() + idealRadius * std::sin(bestAngle);
|
||||||
|
float targetZ = bot->GetPositionZ();
|
||||||
|
|
||||||
|
float distToSlot = bot->GetExactDist2d(targetX, targetY);
|
||||||
|
|
||||||
|
// Close enough to slot — lock position
|
||||||
|
if (distToSlot <= SLOT_TOLERANCE)
|
||||||
|
{
|
||||||
|
spreadLockTimers[{instanceId, bot->GetGUID()}] = now;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move toward assigned slot
|
||||||
|
float dx = targetX - bot->GetPositionX();
|
||||||
|
float dy = targetY - bot->GetPositionY();
|
||||||
|
float len = std::sqrt(dx * dx + dy * dy);
|
||||||
|
if (len < 0.001f)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
dx /= len;
|
||||||
|
dy /= len;
|
||||||
|
float step = std::min(MOVE_INCREMENT, distToSlot);
|
||||||
|
float moveX = bot->GetPositionX() + dx * step;
|
||||||
|
float moveY = bot->GetPositionY() + dy * step;
|
||||||
|
|
||||||
|
if (bot->IsWithinLOS(moveX, moveY, targetZ))
|
||||||
|
{
|
||||||
|
MoveTo(bot->GetMapId(), moveX, moveY, targetZ, false, false, false, true,
|
||||||
|
MovementPriority::MOVEMENT_COMBAT);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IccBpcKineticBombAction::Execute(Event /*event*/)
|
||||||
|
{
|
||||||
|
if (!botAI->IsRangedDps(bot))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
static float const MAX_HEIGHT_DIFF = 30.0f;
|
||||||
|
static float const SAFE_HEIGHT_Z = 371.16473f;
|
||||||
|
static float const TELEPORT_DOWN_Z = 367.16473f;
|
||||||
|
|
||||||
|
// Fix bot stuck above arena
|
||||||
|
if (bot->GetPositionZ() > SAFE_HEIGHT_Z)
|
||||||
|
{
|
||||||
|
bot->TeleportTo(bot->GetMapId(), bot->GetPositionX(), bot->GetPositionY(), TELEPORT_DOWN_Z,
|
||||||
|
bot->GetOrientation());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assign bombs 1-to-1 to nearest available ranged DPS
|
||||||
|
Unit* assignedBomb = FindNearestBomb();
|
||||||
|
if (!assignedBomb)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Already attacking a valid bomb that's still low enough, keep going
|
||||||
|
if (Unit* currentTarget = AI_VALUE(Unit*, "current target"))
|
||||||
|
{
|
||||||
|
if (currentTarget == assignedBomb && currentTarget->IsAlive())
|
||||||
|
{
|
||||||
|
float const heightDiff = currentTarget->GetPositionZ() - BPC_FLOOR_Z;
|
||||||
|
if (heightDiff < MAX_HEIGHT_DIFF)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bot->SetTarget(assignedBomb->GetGUID());
|
||||||
|
bot->SetFacingToObject(assignedBomb);
|
||||||
|
Attack(assignedBomb);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Unit* IccBpcKineticBombAction::FindNearestBomb()
|
||||||
|
{
|
||||||
|
static constexpr std::array<uint32, 4> BOMB_ENTRIES = {NPC_KINETIC_BOMB1, NPC_KINETIC_BOMB2, NPC_KINETIC_BOMB3,
|
||||||
|
NPC_KINETIC_BOMB4};
|
||||||
|
static float const MAX_HEIGHT_DIFF = 35.0f;
|
||||||
|
|
||||||
|
GuidVector const targets = AI_VALUE(GuidVector, "possible targets no los");
|
||||||
|
if (targets.empty())
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
// Gather all valid bombs, sorted by Z ascending (lowest = most urgent)
|
||||||
|
std::vector<Unit*> kineticBombs;
|
||||||
|
for (auto const& guid : targets)
|
||||||
|
{
|
||||||
|
Unit* unit = botAI->GetUnit(guid);
|
||||||
|
if (!unit || !unit->IsAlive())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (std::find(BOMB_ENTRIES.begin(), BOMB_ENTRIES.end(), unit->GetEntry()) == BOMB_ENTRIES.end())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (unit->GetPositionZ() - BPC_FLOOR_Z > MAX_HEIGHT_DIFF)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
kineticBombs.push_back(unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kineticBombs.empty())
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
std::sort(kineticBombs.begin(), kineticBombs.end(),
|
||||||
|
[](Unit* unitA, Unit* unitB) { return unitA->GetPositionZ() < unitB->GetPositionZ(); });
|
||||||
|
|
||||||
|
// Gather alive ranged DPS bots only (no real players)
|
||||||
|
std::vector<Player*> rangedDps;
|
||||||
|
Group* group = bot->GetGroup();
|
||||||
|
if (group)
|
||||||
|
{
|
||||||
|
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
|
||||||
|
{
|
||||||
|
Player* member = itr->GetSource();
|
||||||
|
if (member && member->IsAlive() && GET_PLAYERBOT_AI(member) && botAI->IsRangedDps(member))
|
||||||
|
rangedDps.push_back(member);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rangedDps.push_back(bot);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Greedy 1-to-1 assignment: hunters first, druids second, then any ranged DPS
|
||||||
|
static float const MAX_ASSIGN_RANGE = 80.0f;
|
||||||
|
std::set<Player*> assigned;
|
||||||
|
for (Unit* bomb : kineticBombs)
|
||||||
|
{
|
||||||
|
Player* nearest = nullptr;
|
||||||
|
float nearestDist = std::numeric_limits<float>::max();
|
||||||
|
|
||||||
|
// Priority classes: hunter > druid > any
|
||||||
|
static constexpr std::array<uint8, 3> classPriority = {CLASS_HUNTER, CLASS_DRUID, 0};
|
||||||
|
for (uint8 priorityClass : classPriority)
|
||||||
|
{
|
||||||
|
nearest = nullptr;
|
||||||
|
nearestDist = std::numeric_limits<float>::max();
|
||||||
|
|
||||||
|
for (Player* dps : rangedDps)
|
||||||
|
{
|
||||||
|
if (assigned.count(dps))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (priorityClass != 0 && dps->getClass() != priorityClass)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
float dist = dps->GetDistance(bomb);
|
||||||
|
if (dist < nearestDist && dist < MAX_ASSIGN_RANGE)
|
||||||
|
{
|
||||||
|
nearestDist = dist;
|
||||||
|
nearest = dps;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nearest)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nearest)
|
||||||
|
{
|
||||||
|
assigned.insert(nearest);
|
||||||
|
if (nearest == bot)
|
||||||
|
return bomb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IccBpcBallOfFlameAction::Execute(Event /*event*/)
|
||||||
|
{
|
||||||
|
Unit* boss = AI_VALUE2(Unit*, "find target", "prince taldaram");
|
||||||
|
if (!boss)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Unit* ballOfFlameUnit = bot->FindNearestCreature(NPC_BALL_OF_FLAME, 100.0f);
|
||||||
|
Unit* infernoFlameUnit = bot->FindNearestCreature(NPC_BALL_OF_INFERNO_FLAME, 100.0f);
|
||||||
|
|
||||||
|
bool const hasBallOfFlame = ballOfFlameUnit && (ballOfFlameUnit->GetVictim() == bot);
|
||||||
|
bool const hasInfernoFlame = infernoFlameUnit && (infernoFlameUnit->GetVictim() == bot);
|
||||||
|
|
||||||
|
float infernoDist = infernoFlameUnit ? infernoFlameUnit->GetDistance2d(boss) : 0.0f;
|
||||||
|
// Hunters excluded — they can DPS from range without needing to soak
|
||||||
|
if (infernoFlameUnit && infernoDist > 2.0f && infernoDist <= 10.0f && !hasInfernoFlame &&
|
||||||
|
bot->getClass() != CLASS_HUNTER)
|
||||||
|
{
|
||||||
|
if (!botAI->IsTank(bot) && infernoFlameUnit->GetVictim() != bot)
|
||||||
|
{
|
||||||
|
float flameX = infernoFlameUnit->GetPositionX();
|
||||||
|
float flameY = infernoFlameUnit->GetPositionY();
|
||||||
|
float botX = bot->GetPositionX();
|
||||||
|
float botY = bot->GetPositionY();
|
||||||
|
|
||||||
|
// Calculate direction vector
|
||||||
|
float dx = flameX - botX;
|
||||||
|
float dy = flameY - botY;
|
||||||
|
float distance = std::sqrt(dx * dx + dy * dy);
|
||||||
|
|
||||||
|
// Normalize and scale to 5 units (or remaining distance if less than 5)
|
||||||
|
float step = std::min(5.0f, distance);
|
||||||
|
if (distance > 0.001f)
|
||||||
|
{
|
||||||
|
dx = dx / distance * step;
|
||||||
|
dy = dy / distance * step;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate intermediate position
|
||||||
|
float newX = botX + dx;
|
||||||
|
float newY = botY + dy;
|
||||||
|
|
||||||
|
MoveTo(infernoFlameUnit->GetMapId(), newX, newY, bot->GetPositionZ(), false, false, false, true,
|
||||||
|
MovementPriority::MOVEMENT_COMBAT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If victim of ball of flame, keep at least 15f from other party members
|
||||||
|
if (hasBallOfFlame || hasInfernoFlame)
|
||||||
|
{
|
||||||
|
static float const SAFE_DIST = 15.0f;
|
||||||
|
GuidVector members = AI_VALUE(GuidVector, "group members");
|
||||||
|
for (auto const& memberGuid : members)
|
||||||
|
{
|
||||||
|
Unit* member = botAI->GetUnit(memberGuid);
|
||||||
|
if (!member || !member->IsAlive() || member == bot)
|
||||||
|
continue;
|
||||||
|
float dist = bot->GetExactDist2d(member);
|
||||||
|
if (dist < SAFE_DIST)
|
||||||
|
{
|
||||||
|
// Move away from this member
|
||||||
|
float dx = bot->GetPositionX() - member->GetPositionX();
|
||||||
|
float dy = bot->GetPositionY() - member->GetPositionY();
|
||||||
|
float len = std::sqrt(dx * dx + dy * dy);
|
||||||
|
if (len < 0.001f)
|
||||||
|
continue;
|
||||||
|
dx /= len;
|
||||||
|
dy /= len;
|
||||||
|
float moveX = bot->GetPositionX() + dx * (SAFE_DIST - dist + 1.0f);
|
||||||
|
float moveY = bot->GetPositionY() + dy * (SAFE_DIST - dist + 1.0f);
|
||||||
|
float moveZ = bot->GetPositionZ();
|
||||||
|
if (bot->IsWithinLOS(moveX, moveY, moveZ))
|
||||||
|
{
|
||||||
|
MoveTo(bot->GetMapId(), moveX, moveY, moveZ, false, false, false, true,
|
||||||
|
MovementPriority::MOVEMENT_FORCED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
1318
src/Ai/Raid/ICC/Action/ICCActions_BQL.cpp
Normal file
1318
src/Ai/Raid/ICC/Action/ICCActions_BQL.cpp
Normal file
File diff suppressed because it is too large
Load Diff
504
src/Ai/Raid/ICC/Action/ICCActions_DBS.cpp
Normal file
504
src/Ai/Raid/ICC/Action/ICCActions_DBS.cpp
Normal file
@ -0,0 +1,504 @@
|
|||||||
|
|
||||||
|
#include "GenericActions.h"
|
||||||
|
#include "GenericSpellActions.h"
|
||||||
|
#include "Multiplier.h"
|
||||||
|
#include "NearestNpcsValue.h"
|
||||||
|
#include "ObjectAccessor.h"
|
||||||
|
#include "Playerbots.h"
|
||||||
|
#include "ICCActions.h"
|
||||||
|
#include "ICCTriggers.h"
|
||||||
|
#include "RtiValue.h"
|
||||||
|
#include "Vehicle.h"
|
||||||
|
|
||||||
|
bool IccDbsTankPositionAction::Execute(Event /*event*/)
|
||||||
|
{
|
||||||
|
Unit* boss = AI_VALUE2(Unit*, "find target", "deathbringer saurfang");
|
||||||
|
if (!boss)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Class-specific taunt with forced cooldown reset
|
||||||
|
auto CastClassTaunt = [&](Unit* target) -> bool
|
||||||
|
{
|
||||||
|
if (!target || !target->IsAlive())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
switch (bot->getClass())
|
||||||
|
{
|
||||||
|
case CLASS_PALADIN:
|
||||||
|
{
|
||||||
|
bot->RemoveSpellCooldown(SPELL_TAUNT_PALADIN, true);
|
||||||
|
if (botAI->CastSpell("hand of reckoning", target))
|
||||||
|
return true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CLASS_DEATH_KNIGHT:
|
||||||
|
{
|
||||||
|
bot->RemoveSpellCooldown(SPELL_TAUNT_DK, true);
|
||||||
|
if (botAI->CastSpell("dark command", target))
|
||||||
|
return true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CLASS_DRUID:
|
||||||
|
{
|
||||||
|
bot->RemoveSpellCooldown(SPELL_TAUNT_DRUID, true);
|
||||||
|
if (botAI->CastSpell("growl", target))
|
||||||
|
return true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CLASS_WARRIOR:
|
||||||
|
{
|
||||||
|
bot->RemoveSpellCooldown(SPELL_TAUNT_WARRIOR, true);
|
||||||
|
if (botAI->CastSpell("taunt", target))
|
||||||
|
return true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (botAI->CastSpell("shoot", target) || botAI->CastSpell("throw", target))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (botAI->IsTank(bot))
|
||||||
|
{
|
||||||
|
bool const hasRuneOfBlood = botAI->GetAura("Rune of Blood", bot) != nullptr;
|
||||||
|
|
||||||
|
if (hasRuneOfBlood)
|
||||||
|
{
|
||||||
|
// Stop attacking boss but still taunt loose blood beasts
|
||||||
|
if (bot->GetVictim() == boss)
|
||||||
|
bot->AttackStop();
|
||||||
|
|
||||||
|
std::array<uint32, 4> const beastEntries = {NPC_BLOOD_BEAST1, NPC_BLOOD_BEAST2, NPC_BLOOD_BEAST3,
|
||||||
|
NPC_BLOOD_BEAST4};
|
||||||
|
GuidVector const npcs = AI_VALUE(GuidVector, "nearest hostile npcs");
|
||||||
|
for (auto const& npc : npcs)
|
||||||
|
{
|
||||||
|
Unit* unit = botAI->GetUnit(npc);
|
||||||
|
if (!unit || !unit->IsAlive())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
bool const isBloodBeast =
|
||||||
|
std::find(beastEntries.begin(), beastEntries.end(), unit->GetEntry()) != beastEntries.end();
|
||||||
|
if (!isBloodBeast)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Unit* victim = unit->GetVictim();
|
||||||
|
Player* victimPlayer = victim ? victim->ToPlayer() : nullptr;
|
||||||
|
if (!victimPlayer || !botAI->IsTank(victimPlayer))
|
||||||
|
{
|
||||||
|
CastClassTaunt(unit);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bot->GetExactDist2d(ICC_DBS_TANK_POSITION) > 5.0f)
|
||||||
|
return MoveTo(bot->GetMapId(), ICC_DBS_TANK_POSITION.GetPositionX(),
|
||||||
|
ICC_DBS_TANK_POSITION.GetPositionY(), ICC_DBS_TANK_POSITION.GetPositionZ(), false, false,
|
||||||
|
false, true, MovementPriority::MOVEMENT_NORMAL);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tank without Rune of Blood: taunt boss if current tank has the debuff
|
||||||
|
Unit* currentTarget = boss->GetVictim();
|
||||||
|
if (currentTarget && currentTarget != bot && botAI->GetAura("Rune of Blood", currentTarget))
|
||||||
|
CastClassTaunt(boss);
|
||||||
|
|
||||||
|
// Taunt any blood beasts not targeting a tank
|
||||||
|
std::array<uint32, 4> const bloodBeastEntries = {NPC_BLOOD_BEAST1, NPC_BLOOD_BEAST2, NPC_BLOOD_BEAST3,
|
||||||
|
NPC_BLOOD_BEAST4};
|
||||||
|
GuidVector const npcs = AI_VALUE(GuidVector, "nearest hostile npcs");
|
||||||
|
for (auto const& npc : npcs)
|
||||||
|
{
|
||||||
|
Unit* unit = botAI->GetUnit(npc);
|
||||||
|
if (!unit || !unit->IsAlive())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
bool const isBloodBeast = std::find(bloodBeastEntries.begin(), bloodBeastEntries.end(), unit->GetEntry()) !=
|
||||||
|
bloodBeastEntries.end();
|
||||||
|
if (!isBloodBeast)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Unit* victim = unit->GetVictim();
|
||||||
|
Player* victimPlayer = victim ? victim->ToPlayer() : nullptr;
|
||||||
|
if (!victimPlayer || !botAI->IsTank(victimPlayer))
|
||||||
|
{
|
||||||
|
CastClassTaunt(unit);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bot->GetExactDist2d(ICC_DBS_TANK_POSITION) > 5.0f)
|
||||||
|
return MoveTo(bot->GetMapId(), ICC_DBS_TANK_POSITION.GetPositionX(), ICC_DBS_TANK_POSITION.GetPositionY(),
|
||||||
|
ICC_DBS_TANK_POSITION.GetPositionZ(), false, false, false, true,
|
||||||
|
MovementPriority::MOVEMENT_NORMAL);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!botAI->IsTank(bot))
|
||||||
|
{
|
||||||
|
if (CrowdControlBloodBeasts())
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle ranged and healer positioning
|
||||||
|
if (botAI->IsRanged(bot) || botAI->IsHeal(bot))
|
||||||
|
{
|
||||||
|
// Handle evasion from blood beasts
|
||||||
|
if (EvadeBloodBeasts())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// Position in formation
|
||||||
|
return PositionInRangedFormation();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IccDbsTankPositionAction::CrowdControlBloodBeasts()
|
||||||
|
{
|
||||||
|
std::array<uint32, 4> const bloodBeastEntries = {NPC_BLOOD_BEAST1, NPC_BLOOD_BEAST2, NPC_BLOOD_BEAST3,
|
||||||
|
NPC_BLOOD_BEAST4};
|
||||||
|
GuidVector const npcs = AI_VALUE(GuidVector, "nearest hostile npcs");
|
||||||
|
|
||||||
|
for (auto const& npc : npcs)
|
||||||
|
{
|
||||||
|
Unit* unit = botAI->GetUnit(npc);
|
||||||
|
if (!unit || !unit->IsAlive())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Check if this is a blood beast
|
||||||
|
bool const isBloodBeast =
|
||||||
|
std::find(bloodBeastEntries.begin(), bloodBeastEntries.end(), unit->GetEntry()) != bloodBeastEntries.end();
|
||||||
|
|
||||||
|
if (!isBloodBeast)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
switch (bot->getClass())
|
||||||
|
{
|
||||||
|
case CLASS_MAGE:
|
||||||
|
{
|
||||||
|
if (!botAI->HasAura("Frost Nova", unit))
|
||||||
|
return botAI->CastSpell("Frost Nova", unit);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CLASS_DRUID:
|
||||||
|
{
|
||||||
|
if (!botAI->HasAura("Entangling Roots", unit))
|
||||||
|
return botAI->CastSpell("Entangling Roots", unit);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CLASS_PALADIN:
|
||||||
|
{
|
||||||
|
if (!botAI->HasAura("Hammer of Justice", unit))
|
||||||
|
return botAI->CastSpell("Hammer of Justice", unit);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CLASS_WARRIOR:
|
||||||
|
{
|
||||||
|
if (!botAI->HasAura("Hamstring", unit))
|
||||||
|
return botAI->CastSpell("Hamstring", unit);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CLASS_HUNTER:
|
||||||
|
{
|
||||||
|
if (!botAI->HasAura("Concussive Shot", unit))
|
||||||
|
return botAI->CastSpell("Concussive Shot", unit);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CLASS_ROGUE:
|
||||||
|
{
|
||||||
|
if (!botAI->HasAura("Kidney Shot", unit))
|
||||||
|
return botAI->CastSpell("Kidney Shot", unit);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CLASS_SHAMAN:
|
||||||
|
{
|
||||||
|
if (!botAI->HasAura("Frost Shock", unit))
|
||||||
|
return botAI->CastSpell("Frost Shock", unit);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CLASS_DEATH_KNIGHT:
|
||||||
|
{
|
||||||
|
if (!botAI->HasAura("Chains of Ice", unit))
|
||||||
|
return botAI->CastSpell("Chains of Ice", unit);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CLASS_PRIEST:
|
||||||
|
{
|
||||||
|
if (!botAI->HasAura("Psychic Scream", unit))
|
||||||
|
return botAI->CastSpell("Psychic Scream", unit);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CLASS_WARLOCK:
|
||||||
|
{
|
||||||
|
if (!botAI->HasAura("Fear", unit))
|
||||||
|
return botAI->CastSpell("Fear", unit);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IccDbsTankPositionAction::EvadeBloodBeasts()
|
||||||
|
{
|
||||||
|
float const evasionDistance = 12.0f;
|
||||||
|
std::array<uint32, 4> const bloodBeastEntries = {NPC_BLOOD_BEAST1, NPC_BLOOD_BEAST2, NPC_BLOOD_BEAST3,
|
||||||
|
NPC_BLOOD_BEAST4};
|
||||||
|
|
||||||
|
// Get the nearest hostile NPCs
|
||||||
|
GuidVector const npcs = AI_VALUE(GuidVector, "nearest hostile npcs");
|
||||||
|
|
||||||
|
for (auto const& npc : npcs)
|
||||||
|
{
|
||||||
|
Unit* unit = botAI->GetUnit(npc);
|
||||||
|
if (!unit)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Check if this is a blood beast
|
||||||
|
bool const isBloodBeast =
|
||||||
|
std::find(bloodBeastEntries.begin(), bloodBeastEntries.end(), unit->GetEntry()) != bloodBeastEntries.end();
|
||||||
|
|
||||||
|
// Only evade if it's a blood beast targeting us
|
||||||
|
if (isBloodBeast && unit->GetVictim() == bot)
|
||||||
|
{
|
||||||
|
float const currentDistance = bot->GetDistance2d(unit);
|
||||||
|
|
||||||
|
// Move away if too close
|
||||||
|
if (currentDistance < evasionDistance)
|
||||||
|
return MoveAway(unit, evasionDistance - currentDistance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IccDbsTankPositionAction::PositionInRangedFormation()
|
||||||
|
{
|
||||||
|
// Get group
|
||||||
|
Group* group = bot->GetGroup();
|
||||||
|
if (!group)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
int32 const totalSlots = 15; // 3 rows x 5 cols
|
||||||
|
uint32 const dbsInstanceId = bot->GetInstanceId();
|
||||||
|
|
||||||
|
// Persistent per-bot slot memory shared across all bots.
|
||||||
|
// Keyed per-instance to avoid cross-instance pollution.
|
||||||
|
static std::map<std::pair<uint32, ObjectGuid>, int> botSlotMemory;
|
||||||
|
auto myKey = std::make_pair(dbsInstanceId, bot->GetGUID());
|
||||||
|
|
||||||
|
// Single pass: collect natural index (alive ranged/healer non-tank order)
|
||||||
|
// and other bots' reserved slots.
|
||||||
|
int32 myIndex = -1;
|
||||||
|
int32 currentIndex = 0;
|
||||||
|
std::vector<int> reservedSlots;
|
||||||
|
|
||||||
|
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
|
||||||
|
{
|
||||||
|
Player* member = itr->GetSource();
|
||||||
|
if (!member)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (member != bot)
|
||||||
|
{
|
||||||
|
auto it = botSlotMemory.find(std::make_pair(dbsInstanceId, member->GetGUID()));
|
||||||
|
if (it != botSlotMemory.end() && it->second >= 0 && it->second < totalSlots)
|
||||||
|
reservedSlots.push_back(it->second);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!member->IsAlive())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if ((botAI->IsRanged(member) || botAI->IsHeal(member)) && !botAI->IsTank(member))
|
||||||
|
{
|
||||||
|
if (member == bot)
|
||||||
|
{
|
||||||
|
myIndex = currentIndex;
|
||||||
|
}
|
||||||
|
currentIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (myIndex == -1)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
auto IsReserved = [&](int s) -> bool
|
||||||
|
{
|
||||||
|
return std::find(reservedSlots.begin(), reservedSlots.end(), s) != reservedSlots.end();
|
||||||
|
};
|
||||||
|
|
||||||
|
int myAssignedSlot = -1;
|
||||||
|
|
||||||
|
// Step 1: keep my remembered slot if still in range and not reserved by someone else.
|
||||||
|
auto myMemIt = botSlotMemory.find(myKey);
|
||||||
|
if (myMemIt != botSlotMemory.end())
|
||||||
|
{
|
||||||
|
int prev = myMemIt->second;
|
||||||
|
if (prev >= 0 && prev < totalSlots && !IsReserved(prev))
|
||||||
|
myAssignedSlot = prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2: pick first unreserved slot, seeded from my natural index.
|
||||||
|
if (myAssignedSlot < 0)
|
||||||
|
{
|
||||||
|
for (int attempt = 0; attempt < totalSlots; attempt++)
|
||||||
|
{
|
||||||
|
int s = (myIndex + attempt) % totalSlots;
|
||||||
|
if (!IsReserved(s))
|
||||||
|
{
|
||||||
|
myAssignedSlot = s;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3: overflow (16th+ ranged) - stack with closest non-tank melee bot,
|
||||||
|
// lowest GUID on tie. Forget my slot so others can take it.
|
||||||
|
if (myAssignedSlot < 0)
|
||||||
|
{
|
||||||
|
botSlotMemory.erase(myKey);
|
||||||
|
|
||||||
|
Player* anchor = nullptr;
|
||||||
|
float anchorDist = 0.0f;
|
||||||
|
uint64 anchorGuidRaw = 0;
|
||||||
|
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
|
||||||
|
{
|
||||||
|
Player* member = itr->GetSource();
|
||||||
|
if (!member || member == bot || !member->IsAlive())
|
||||||
|
continue;
|
||||||
|
if (!botAI->IsMelee(member) || botAI->IsTank(member))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
float d = bot->GetExactDist2d(member);
|
||||||
|
uint64 g = member->GetGUID().GetRawValue();
|
||||||
|
if (!anchor || d < anchorDist || (d == anchorDist && g < anchorGuidRaw))
|
||||||
|
{
|
||||||
|
anchor = member;
|
||||||
|
anchorDist = d;
|
||||||
|
anchorGuidRaw = g;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!anchor)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
float ax = anchor->GetPositionX();
|
||||||
|
float ay = anchor->GetPositionY();
|
||||||
|
float az = anchor->GetPositionZ();
|
||||||
|
if (bot->GetExactDist2d(ax, ay) > 3.0f)
|
||||||
|
return MoveTo(bot->GetMapId(), ax, ay, az, false, false, false, true,
|
||||||
|
MovementPriority::MOVEMENT_COMBAT);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
botSlotMemory[myKey] = myAssignedSlot;
|
||||||
|
|
||||||
|
// Fixed positions calculation
|
||||||
|
float const tankToBossAngle = 3.14f;
|
||||||
|
float const minBossDistance = 11.0f;
|
||||||
|
float const spreadDistance = 10.0f;
|
||||||
|
int32 const columnsPerRow = 5;
|
||||||
|
|
||||||
|
// Calculate position in a fixed grid (3 rows x 5 columns)
|
||||||
|
int32 const row = myAssignedSlot / columnsPerRow;
|
||||||
|
int32 const col = myAssignedSlot % columnsPerRow;
|
||||||
|
|
||||||
|
// Calculate base position
|
||||||
|
float xOffset = (col - 2) * spreadDistance; // Center around tank position
|
||||||
|
float yOffset = minBossDistance + (row * spreadDistance); // Each row further back
|
||||||
|
|
||||||
|
// Add zigzag offset for odd rows
|
||||||
|
if (row % 2 == 1)
|
||||||
|
xOffset += spreadDistance / 2.0f;
|
||||||
|
|
||||||
|
// Rotate position based on tank-to-boss angle
|
||||||
|
float finalX =
|
||||||
|
ICC_DBS_TANK_POSITION.GetPositionX() + (cos(tankToBossAngle) * yOffset - sin(tankToBossAngle) * xOffset);
|
||||||
|
float finalY =
|
||||||
|
ICC_DBS_TANK_POSITION.GetPositionY() + (sin(tankToBossAngle) * yOffset + cos(tankToBossAngle) * xOffset);
|
||||||
|
float finalZ = ICC_DBS_TANK_POSITION.GetPositionZ();
|
||||||
|
|
||||||
|
// Update Z coordinate
|
||||||
|
bot->UpdateAllowedPositionZ(finalX, finalY, finalZ);
|
||||||
|
|
||||||
|
// Move if not in position
|
||||||
|
if (bot->GetExactDist2d(finalX, finalY) > 3.0f)
|
||||||
|
return MoveTo(bot->GetMapId(), finalX, finalY, finalZ, false, false, false, true,
|
||||||
|
MovementPriority::MOVEMENT_COMBAT);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IccAddsDbsAction::Execute(Event /*event*/)
|
||||||
|
{
|
||||||
|
Unit* boss = AI_VALUE2(Unit*, "find target", "deathbringer saurfang");
|
||||||
|
if (!boss)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// This action is only for melee
|
||||||
|
if (!botAI->IsMelee(bot))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Unit* priorityTarget = FindPriorityTarget(boss);
|
||||||
|
|
||||||
|
// Update raid target icons if needed
|
||||||
|
UpdateSkullMarker(priorityTarget);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Unit* IccAddsDbsAction::FindPriorityTarget(Unit* boss)
|
||||||
|
{
|
||||||
|
GuidVector const targets = AI_VALUE(GuidVector, "possible targets no los");
|
||||||
|
|
||||||
|
// Blood beast entry IDs
|
||||||
|
std::array<uint32, 4> const addEntries = {NPC_BLOOD_BEAST1, NPC_BLOOD_BEAST2, NPC_BLOOD_BEAST3, NPC_BLOOD_BEAST4};
|
||||||
|
|
||||||
|
// First check for alive adds
|
||||||
|
for (uint32 const entry : addEntries)
|
||||||
|
{
|
||||||
|
for (ObjectGuid const& guid : targets)
|
||||||
|
{
|
||||||
|
Unit* unit = botAI->GetUnit(guid);
|
||||||
|
if (unit && unit->IsAlive() && unit->GetEntry() == entry)
|
||||||
|
return unit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only fallback to boss if it's alive
|
||||||
|
return boss->IsAlive() ? const_cast<Unit*>(boss) : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IccAddsDbsAction::UpdateSkullMarker(Unit* priorityTarget)
|
||||||
|
{
|
||||||
|
if (!priorityTarget)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Group* group = bot->GetGroup();
|
||||||
|
if (!group)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
uint8 const skullIconId = 7;
|
||||||
|
|
||||||
|
// Get current skull target
|
||||||
|
ObjectGuid const currentSkull = group->GetTargetIcon(skullIconId);
|
||||||
|
Unit* currentSkullUnit = botAI->GetUnit(currentSkull);
|
||||||
|
|
||||||
|
// Determine if skull marker needs updating
|
||||||
|
bool const needsUpdate = !currentSkullUnit || !currentSkullUnit->IsAlive() || currentSkullUnit != priorityTarget;
|
||||||
|
|
||||||
|
// Update if needed
|
||||||
|
if (needsUpdate)
|
||||||
|
group->SetTargetIcon(skullIconId, bot->GetGUID(), priorityTarget->GetGUID());
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
110
src/Ai/Raid/ICC/Action/ICCActions_Dogs.cpp
Normal file
110
src/Ai/Raid/ICC/Action/ICCActions_Dogs.cpp
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
#include "GenericActions.h"
|
||||||
|
#include "GenericSpellActions.h"
|
||||||
|
#include "Multiplier.h"
|
||||||
|
#include "Playerbots.h"
|
||||||
|
#include "ICCActions.h"
|
||||||
|
#include "ICCTriggers.h"
|
||||||
|
|
||||||
|
bool IccDogsTankPositionAction::Execute(Event /*event*/)
|
||||||
|
{
|
||||||
|
Unit* boss = AI_VALUE2(Unit*, "find target", "stinky");
|
||||||
|
if (!boss)
|
||||||
|
boss = AI_VALUE2(Unit*, "find target", "precious");
|
||||||
|
if (!boss)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
auto CastClassTaunt = [&](Unit* target) -> bool
|
||||||
|
{
|
||||||
|
if (!target || !target->IsAlive())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
switch (bot->getClass())
|
||||||
|
{
|
||||||
|
case CLASS_PALADIN:
|
||||||
|
{
|
||||||
|
bot->RemoveSpellCooldown(SPELL_TAUNT_PALADIN, true);
|
||||||
|
if (botAI->CastSpell("hand of reckoning", target))
|
||||||
|
return true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CLASS_DEATH_KNIGHT:
|
||||||
|
{
|
||||||
|
bot->RemoveSpellCooldown(SPELL_TAUNT_DK, true);
|
||||||
|
if (botAI->CastSpell("dark command", target))
|
||||||
|
return true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CLASS_DRUID:
|
||||||
|
{
|
||||||
|
bot->RemoveSpellCooldown(SPELL_TAUNT_DRUID, true);
|
||||||
|
if (botAI->CastSpell("growl", target))
|
||||||
|
return true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CLASS_WARRIOR:
|
||||||
|
{
|
||||||
|
bot->RemoveSpellCooldown(SPELL_TAUNT_WARRIOR, true);
|
||||||
|
if (botAI->CastSpell("taunt", target))
|
||||||
|
return true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (botAI->CastSpell("shoot", target) || botAI->CastSpell("throw", target))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (botAI->IsTank(bot))
|
||||||
|
{
|
||||||
|
Aura* aura = botAI->GetAura("mortal wound", bot, false, true);
|
||||||
|
bool const hasMortalWound = aura && aura->GetStackAmount() >= 8;
|
||||||
|
|
||||||
|
if (hasMortalWound)
|
||||||
|
{
|
||||||
|
if (bot->GetVictim() == boss)
|
||||||
|
bot->AttackStop();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tank without high mortal wound stacks: taunt boss if current tank has the debuff
|
||||||
|
Unit* currentTarget = boss->GetVictim();
|
||||||
|
if (currentTarget && currentTarget != bot)
|
||||||
|
{
|
||||||
|
Aura* victimAura = botAI->GetAura("mortal wound", currentTarget, false, true);
|
||||||
|
if (victimAura && victimAura->GetStackAmount() >= 8)
|
||||||
|
CastClassTaunt(boss);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Taunt nearby hostile adds not targeting a tank
|
||||||
|
GuidVector const npcs = AI_VALUE(GuidVector, "nearest hostile npcs");
|
||||||
|
for (auto const& npc : npcs)
|
||||||
|
{
|
||||||
|
Unit* unit = botAI->GetUnit(npc);
|
||||||
|
if (!unit || !unit->IsAlive())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (unit == boss)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (bot->GetDistance2d(unit) > 20.0f)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Unit* victim = unit->GetVictim();
|
||||||
|
Player* victimPlayer = victim ? victim->ToPlayer() : nullptr;
|
||||||
|
if (!victimPlayer || !botAI->IsTank(victimPlayer))
|
||||||
|
{
|
||||||
|
CastClassTaunt(unit);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
614
src/Ai/Raid/ICC/Action/ICCActions_FG.cpp
Normal file
614
src/Ai/Raid/ICC/Action/ICCActions_FG.cpp
Normal file
@ -0,0 +1,614 @@
|
|||||||
|
#include "GenericActions.h"
|
||||||
|
#include "GenericSpellActions.h"
|
||||||
|
#include "Multiplier.h"
|
||||||
|
#include "NearestNpcsValue.h"
|
||||||
|
#include "ObjectAccessor.h"
|
||||||
|
#include "Playerbots.h"
|
||||||
|
#include "ICCActions.h"
|
||||||
|
#include "ICCTriggers.h"
|
||||||
|
#include "ICCScripts.h"
|
||||||
|
#include "RtiValue.h"
|
||||||
|
#include "Vehicle.h"
|
||||||
|
#include <limits>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
// Festergut
|
||||||
|
bool IccFestergutGroupPositionAction::Execute(Event /*event*/)
|
||||||
|
{
|
||||||
|
Unit* boss = AI_VALUE2(Unit*, "find target", "festergut");
|
||||||
|
if (!boss)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
bot->SetTarget(boss->GetGUID());
|
||||||
|
|
||||||
|
auto CastClassTaunt = [&](Unit* target) -> bool
|
||||||
|
{
|
||||||
|
if (!target || !target->IsAlive())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
switch (bot->getClass())
|
||||||
|
{
|
||||||
|
case CLASS_PALADIN:
|
||||||
|
{
|
||||||
|
bot->RemoveSpellCooldown(SPELL_TAUNT_PALADIN, true);
|
||||||
|
if (botAI->CastSpell("hand of reckoning", target))
|
||||||
|
return true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CLASS_DEATH_KNIGHT:
|
||||||
|
{
|
||||||
|
bot->RemoveSpellCooldown(SPELL_TAUNT_DK, true);
|
||||||
|
if (botAI->CastSpell("dark command", target))
|
||||||
|
return true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CLASS_DRUID:
|
||||||
|
{
|
||||||
|
bot->RemoveSpellCooldown(SPELL_TAUNT_DRUID, true);
|
||||||
|
if (botAI->CastSpell("growl", target))
|
||||||
|
return true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CLASS_WARRIOR:
|
||||||
|
{
|
||||||
|
bot->RemoveSpellCooldown(SPELL_TAUNT_WARRIOR, true);
|
||||||
|
if (botAI->CastSpell("taunt", target))
|
||||||
|
return true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (botAI->CastSpell("shoot", target) || botAI->CastSpell("throw", target))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (botAI->IsTank(bot))
|
||||||
|
{
|
||||||
|
Aura* aura = botAI->GetAura("gastric bloat", bot, false, true);
|
||||||
|
bool const hasGastricBloat = aura && aura->GetStackAmount() >= 6;
|
||||||
|
|
||||||
|
if (hasGastricBloat)
|
||||||
|
{
|
||||||
|
if (bot->GetVictim() == boss)
|
||||||
|
bot->AttackStop();
|
||||||
|
|
||||||
|
if (bot->GetExactDist2d(ICC_FESTERGUT_TANK_POSITION) > 5.0f)
|
||||||
|
return MoveTo(bot->GetMapId(), ICC_FESTERGUT_TANK_POSITION.GetPositionX(),
|
||||||
|
ICC_FESTERGUT_TANK_POSITION.GetPositionY(), ICC_FESTERGUT_TANK_POSITION.GetPositionZ(),
|
||||||
|
false, false, false, true, MovementPriority::MOVEMENT_NORMAL);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Unit* currentTarget = boss->GetVictim();
|
||||||
|
if (currentTarget && currentTarget != bot)
|
||||||
|
{
|
||||||
|
Aura* victimAura = botAI->GetAura("gastric bloat", currentTarget, false, true);
|
||||||
|
if (victimAura && victimAura->GetStackAmount() >= 6)
|
||||||
|
CastClassTaunt(boss);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bot->GetExactDist2d(ICC_FESTERGUT_TANK_POSITION) > 5.0f)
|
||||||
|
return MoveTo(bot->GetMapId(), ICC_FESTERGUT_TANK_POSITION.GetPositionX(),
|
||||||
|
ICC_FESTERGUT_TANK_POSITION.GetPositionY(), ICC_FESTERGUT_TANK_POSITION.GetPositionZ(), false,
|
||||||
|
false, false, true, MovementPriority::MOVEMENT_NORMAL);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for spores in the group
|
||||||
|
if (HasSporesInGroup())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// No spore, no goo dodge - melee stack on main tank.
|
||||||
|
if (botAI->IsMelee(bot))
|
||||||
|
{
|
||||||
|
Unit* mainTank = AI_VALUE(Unit*, "main tank");
|
||||||
|
if (mainTank && bot->GetExactDist2d(mainTank) > 3.0f)
|
||||||
|
return MoveTo(bot->GetMapId(), mainTank->GetPositionX(), mainTank->GetPositionY(),
|
||||||
|
mainTank->GetPositionZ(), false, false, false, true,
|
||||||
|
MovementPriority::MOVEMENT_NORMAL);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Position non-tank ranged and healers
|
||||||
|
return PositionNonTankMembers();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IccFestergutGroupPositionAction::HasSporesInGroup()
|
||||||
|
{
|
||||||
|
GuidVector const members = AI_VALUE(GuidVector, "group members");
|
||||||
|
|
||||||
|
for (auto const& memberGuid : members)
|
||||||
|
{
|
||||||
|
Unit* unit = botAI->GetUnit(memberGuid);
|
||||||
|
if (unit && unit->HasAura(SPELL_GAS_SPORE))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IccFestergutGroupPositionAction::PositionNonTankMembers()
|
||||||
|
{
|
||||||
|
// Only position ranged and healers without spores
|
||||||
|
if (!(botAI->IsRanged(bot) || botAI->IsHeal(bot)))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Group* group = bot->GetGroup();
|
||||||
|
if (!group)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
int32 positionIndex = CalculatePositionIndex(group);
|
||||||
|
if (positionIndex == -1)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Position calculation parameters
|
||||||
|
constexpr float tankToBossAngle = 4.58f;
|
||||||
|
constexpr float minBossDistance = 15.0f;
|
||||||
|
constexpr float spreadDistance = 10.0f;
|
||||||
|
constexpr int32 columnsPerRow = 6;
|
||||||
|
|
||||||
|
// Calculate grid position
|
||||||
|
int32 row = positionIndex / columnsPerRow;
|
||||||
|
int32 col = positionIndex % columnsPerRow;
|
||||||
|
|
||||||
|
// Calculate base position
|
||||||
|
float xOffset = (col - 2) * spreadDistance; // Center around tank position
|
||||||
|
float yOffset = minBossDistance + (row * spreadDistance); // Each row further back
|
||||||
|
|
||||||
|
// Add zigzag offset for odd rows
|
||||||
|
if (row % 2 == 1)
|
||||||
|
xOffset += spreadDistance / 2.0f;
|
||||||
|
|
||||||
|
// Rotate position based on tank-to-boss angle
|
||||||
|
float finalX =
|
||||||
|
ICC_FESTERGUT_TANK_POSITION.GetPositionX() + (cos(tankToBossAngle) * yOffset - sin(tankToBossAngle) * xOffset);
|
||||||
|
float finalY =
|
||||||
|
ICC_FESTERGUT_TANK_POSITION.GetPositionY() + (sin(tankToBossAngle) * yOffset + cos(tankToBossAngle) * xOffset);
|
||||||
|
float finalZ = ICC_FESTERGUT_TANK_POSITION.GetPositionZ();
|
||||||
|
|
||||||
|
// Update Z coordinate
|
||||||
|
bot->UpdateAllowedPositionZ(finalX, finalY, finalZ);
|
||||||
|
|
||||||
|
// Move if not in position
|
||||||
|
if (bot->GetExactDist2d(finalX, finalY) > 3.0f)
|
||||||
|
return MoveTo(bot->GetMapId(), finalX, finalY, finalZ, false, false, false, true,
|
||||||
|
MovementPriority::MOVEMENT_COMBAT);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32 IccFestergutGroupPositionAction::CalculatePositionIndex(Group* group)
|
||||||
|
{
|
||||||
|
std::vector<ObjectGuid> healerGuids;
|
||||||
|
std::vector<ObjectGuid> rangedDpsGuids;
|
||||||
|
std::vector<ObjectGuid> hunterGuids;
|
||||||
|
|
||||||
|
// Collect all eligible members with their GUIDs
|
||||||
|
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
|
||||||
|
{
|
||||||
|
Player* member = itr->GetSource();
|
||||||
|
if (!member || !member->IsAlive() || botAI->IsTank(member))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
ObjectGuid memberGuid = member->GetGUID();
|
||||||
|
|
||||||
|
if (botAI->IsHeal(member))
|
||||||
|
healerGuids.push_back(memberGuid);
|
||||||
|
else if (botAI->IsRanged(member))
|
||||||
|
{
|
||||||
|
if (member->getClass() == CLASS_HUNTER)
|
||||||
|
hunterGuids.push_back(memberGuid);
|
||||||
|
else
|
||||||
|
rangedDpsGuids.push_back(memberGuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort GUIDs for consistent ordering across all bots
|
||||||
|
std::sort(healerGuids.begin(), healerGuids.end());
|
||||||
|
std::sort(rangedDpsGuids.begin(), rangedDpsGuids.end());
|
||||||
|
std::sort(hunterGuids.begin(), hunterGuids.end());
|
||||||
|
|
||||||
|
ObjectGuid botGuid = bot->GetGUID();
|
||||||
|
|
||||||
|
// Find which group this bot belongs to
|
||||||
|
auto healerIt = std::find(healerGuids.begin(), healerGuids.end(), botGuid);
|
||||||
|
auto rangedIt = std::find(rangedDpsGuids.begin(), rangedDpsGuids.end(), botGuid);
|
||||||
|
auto hunterIt = std::find(hunterGuids.begin(), hunterGuids.end(), botGuid);
|
||||||
|
|
||||||
|
// Calculate global position index considering group constraints
|
||||||
|
int32 const healerRows = 2;
|
||||||
|
int32 const columnsPerRow = 6;
|
||||||
|
|
||||||
|
// Healers: rows 0-1 (first two rows)
|
||||||
|
if (healerIt != healerGuids.end())
|
||||||
|
{
|
||||||
|
int32 healerIndex = static_cast<int32>(std::distance(healerGuids.begin(), healerIt));
|
||||||
|
|
||||||
|
// Ensure healers only occupy first two rows
|
||||||
|
if (healerIndex < healerRows * columnsPerRow)
|
||||||
|
return healerIndex;
|
||||||
|
|
||||||
|
// If too many healers, overflow to later rows but keep them early
|
||||||
|
return healerIndex; // Will be in row = index / 6, col = index % 6
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non-hunter ranged DPS: can be any row (no strict restriction)
|
||||||
|
if (rangedIt != rangedDpsGuids.end())
|
||||||
|
{
|
||||||
|
int32 rangedIndex = static_cast<int32>(std::distance(rangedDpsGuids.begin(), rangedIt));
|
||||||
|
|
||||||
|
// Start after all healers, then fill remaining spots
|
||||||
|
return static_cast<int32>(healerGuids.size()) + rangedIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hunters: never in 1st row (row 0)
|
||||||
|
if (hunterIt != hunterGuids.end())
|
||||||
|
{
|
||||||
|
int32 hunterIndex = static_cast<int32>(std::distance(hunterGuids.begin(), hunterIt));
|
||||||
|
|
||||||
|
// Calculate how many non-healer positions are before this hunter position
|
||||||
|
int32 baseOffset = static_cast<int32>(healerGuids.size()) + static_cast<int32>(rangedDpsGuids.size());
|
||||||
|
|
||||||
|
// Each row of hunters starts at positions that are multiples of columnsPerRow
|
||||||
|
// To avoid row 0, skip first column slots reserved for healers/non-hunters
|
||||||
|
return baseOffset + hunterIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IccFestergutSporeAction::Execute(Event /*event*/)
|
||||||
|
{
|
||||||
|
constexpr float positionTolerance = 4.0f;
|
||||||
|
|
||||||
|
bool hasSpore = bot->HasAura(SPELL_GAS_SPORE);
|
||||||
|
|
||||||
|
Position spreadRangedPos = CalculateSpreadPosition();
|
||||||
|
SporeInfo sporeInfo = FindSporedPlayers();
|
||||||
|
Position targetPos = DetermineTargetPosition(hasSpore, sporeInfo, spreadRangedPos);
|
||||||
|
|
||||||
|
if (bot->GetExactDist2d(targetPos) > positionTolerance)
|
||||||
|
{
|
||||||
|
botAI->Reset();
|
||||||
|
return MoveTo(bot->GetMapId(), targetPos.GetPositionX(), targetPos.GetPositionY(), targetPos.GetPositionZ(),
|
||||||
|
true, false, false, true, MovementPriority::MOVEMENT_FORCED);
|
||||||
|
}
|
||||||
|
|
||||||
|
// In position — let combat rotation run (multiplier blocks movement so bot stays put)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Position IccFestergutSporeAction::CalculateSpreadPosition()
|
||||||
|
{
|
||||||
|
constexpr float spreadRadius = 2.0f;
|
||||||
|
constexpr float gooNearSporeRadius = 12.0f;
|
||||||
|
constexpr uint32 impactLifetimeMs = 8000;
|
||||||
|
constexpr uint32 cycleIdleResetMs = 2000;
|
||||||
|
|
||||||
|
// Group-wide sticky slot decision. Spore + malleable goo can overlap: if
|
||||||
|
// an active goo lands near the current spread spot we flip to the other
|
||||||
|
// spot, and stay there for the rest of this spore cycle so bots aren't
|
||||||
|
// pulled back into the danger zone when the goo expires. The action only
|
||||||
|
// runs while the spore trigger is active, so a gap >2s between calls
|
||||||
|
// means the cycle ended - reset to the primary slot for the next cycle.
|
||||||
|
// State is keyed by instance ID so concurrent ICC raids don't share slots.
|
||||||
|
struct SpreadSlotState { uint32 lastCallMs = 0; int currentSlot = 1; };
|
||||||
|
static std::unordered_map<uint32, SpreadSlotState> s_slotState;
|
||||||
|
SpreadSlotState& state = s_slotState[bot->GetMap()->GetInstanceId()];
|
||||||
|
|
||||||
|
uint32 now = getMSTime();
|
||||||
|
if (state.lastCallMs == 0 || now - state.lastCallMs > cycleIdleResetMs)
|
||||||
|
state.currentSlot = 1;
|
||||||
|
state.lastCallMs = now;
|
||||||
|
|
||||||
|
Position currentSpot = (state.currentSlot == 2) ? ICC_FESTERGUT_RANGED_SPORE_2
|
||||||
|
: ICC_FESTERGUT_RANGED_SPORE;
|
||||||
|
|
||||||
|
auto it = IcecrownHelpers::malleableGooImpacts.find(bot->GetMap()->GetInstanceId());
|
||||||
|
if (it != IcecrownHelpers::malleableGooImpacts.end())
|
||||||
|
{
|
||||||
|
for (auto const& impact : it->second)
|
||||||
|
{
|
||||||
|
if (getMSTimeDiff(impact.castTime, now) > impactLifetimeMs)
|
||||||
|
continue;
|
||||||
|
float dx = impact.position.GetPositionX() - currentSpot.GetPositionX();
|
||||||
|
float dy = impact.position.GetPositionY() - currentSpot.GetPositionY();
|
||||||
|
if (dx * dx + dy * dy < gooNearSporeRadius * gooNearSporeRadius)
|
||||||
|
{
|
||||||
|
state.currentSlot = (state.currentSlot == 1) ? 2 : 1;
|
||||||
|
currentSpot = (state.currentSlot == 2) ? ICC_FESTERGUT_RANGED_SPORE_2
|
||||||
|
: ICC_FESTERGUT_RANGED_SPORE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unique angle based on bot's GUID
|
||||||
|
float angle = (bot->GetGUID().GetCounter() % 16) * (M_PI / 8);
|
||||||
|
|
||||||
|
Position spreadRangedPos = currentSpot;
|
||||||
|
spreadRangedPos.Relocate(spreadRangedPos.GetPositionX() + cos(angle) * spreadRadius,
|
||||||
|
spreadRangedPos.GetPositionY() + sin(angle) * spreadRadius,
|
||||||
|
spreadRangedPos.GetPositionZ(), spreadRangedPos.GetOrientation());
|
||||||
|
|
||||||
|
return spreadRangedPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
IccFestergutSporeAction::SporeInfo IccFestergutSporeAction::FindSporedPlayers()
|
||||||
|
{
|
||||||
|
SporeInfo info;
|
||||||
|
GuidVector const members = AI_VALUE(GuidVector, "group members");
|
||||||
|
|
||||||
|
for (auto const& memberGuid : members)
|
||||||
|
{
|
||||||
|
Unit* unit = botAI->GetUnit(memberGuid);
|
||||||
|
if (!unit)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (unit->HasAura(SPELL_GAS_SPORE))
|
||||||
|
{
|
||||||
|
info.sporedPlayers.push_back(unit);
|
||||||
|
|
||||||
|
if (!info.hasLowestGuid || unit->GetGUID() < info.lowestGuid)
|
||||||
|
{
|
||||||
|
info.lowestGuid = unit->GetGUID();
|
||||||
|
info.hasLowestGuid = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IccFestergutSporeAction::GooNear(Position const& pos)
|
||||||
|
{
|
||||||
|
constexpr uint32 impactLifetimeMs = 8000;
|
||||||
|
constexpr float gooDangerRadius = 12.0f;
|
||||||
|
|
||||||
|
uint32 now = getMSTime();
|
||||||
|
auto it = IcecrownHelpers::malleableGooImpacts.find(bot->GetMap()->GetInstanceId());
|
||||||
|
if (it != IcecrownHelpers::malleableGooImpacts.end())
|
||||||
|
{
|
||||||
|
for (auto const& impact : it->second)
|
||||||
|
{
|
||||||
|
if (getMSTimeDiff(impact.castTime, now) > impactLifetimeMs)
|
||||||
|
continue;
|
||||||
|
float dx = pos.GetPositionX() - impact.position.GetPositionX();
|
||||||
|
float dy = pos.GetPositionY() - impact.position.GetPositionY();
|
||||||
|
if (dx * dx + dy * dy < gooDangerRadius * gooDangerRadius)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Position IccFestergutSporeAction::DetermineTargetPosition(bool hasSpore, SporeInfo const& sporeInfo,
|
||||||
|
Position const& spreadRangedPos)
|
||||||
|
{
|
||||||
|
// No spores at all
|
||||||
|
if (sporeInfo.sporedPlayers.empty())
|
||||||
|
return botAI->IsMelee(bot) ? ICC_FESTERGUT_MELEE_SPORE : spreadRangedPos;
|
||||||
|
|
||||||
|
bool mainTankHasSpore = CheckMainTankSpore();
|
||||||
|
|
||||||
|
// Goo overlap override — checked before hasSpore so non-spored bots also redirect.
|
||||||
|
bool gooAtMelee = GooNear(ICC_FESTERGUT_MELEE_SPORE);
|
||||||
|
bool gooAtRanged = GooNear(ICC_FESTERGUT_RANGED_SPORE) || GooNear(ICC_FESTERGUT_RANGED_SPORE_2);
|
||||||
|
|
||||||
|
if (gooAtMelee && !gooAtRanged)
|
||||||
|
{
|
||||||
|
// Goo at melee: tank + melee-spore bot hold, other melee flee to ranged slot 1.
|
||||||
|
bool isMeleeSporeBot = (hasSpore && bot->GetGUID() == sporeInfo.lowestGuid && !botAI->IsTank(bot) && !mainTankHasSpore);
|
||||||
|
if (botAI->IsMainTank(bot) || isMeleeSporeBot)
|
||||||
|
return ICC_FESTERGUT_MELEE_SPORE;
|
||||||
|
if (botAI->IsMelee(bot))
|
||||||
|
return ICC_FESTERGUT_RANGED_SPORE;
|
||||||
|
return spreadRangedPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gooAtRanged && !gooAtMelee)
|
||||||
|
{
|
||||||
|
// Goo at ranged: all ranged collapse to melee spot.
|
||||||
|
if (!botAI->IsMelee(bot))
|
||||||
|
return ICC_FESTERGUT_MELEE_SPORE;
|
||||||
|
return ICC_FESTERGUT_MELEE_SPORE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normal spore logic (no overlap or both spots hit).
|
||||||
|
if (!hasSpore)
|
||||||
|
return botAI->IsMelee(bot) ? ICC_FESTERGUT_MELEE_SPORE : spreadRangedPos;
|
||||||
|
|
||||||
|
if (botAI->IsMainTank(bot))
|
||||||
|
return ICC_FESTERGUT_MELEE_SPORE;
|
||||||
|
|
||||||
|
if (bot->GetGUID() == sporeInfo.lowestGuid && !botAI->IsTank(bot) && !mainTankHasSpore)
|
||||||
|
return ICC_FESTERGUT_MELEE_SPORE;
|
||||||
|
|
||||||
|
return spreadRangedPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IccFestergutSporeAction::CheckMainTankSpore()
|
||||||
|
{
|
||||||
|
GuidVector const members = AI_VALUE(GuidVector, "group members");
|
||||||
|
|
||||||
|
for (auto const& memberGuid : members)
|
||||||
|
{
|
||||||
|
Unit* unit = botAI->GetUnit(memberGuid);
|
||||||
|
if (!unit)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (botAI->IsMainTank(unit->ToPlayer()) && unit->HasAura(SPELL_GAS_SPORE))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IccFestergutAvoidMalleableGooAction::Execute(Event /*event*/)
|
||||||
|
{
|
||||||
|
Unit* boss = AI_VALUE2(Unit*, "find target", "festergut");
|
||||||
|
if (!boss)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Tanks hold aggro at the fixed tank spot - never dodge.
|
||||||
|
if (botAI->IsTank(bot))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Festergut heroic - Putricide throws Malleable Goo from the balcony at
|
||||||
|
// random non-tank players. The impact is a triggered cast with no cast bar
|
||||||
|
// and no DynamicObject, so the IccPutricideListenerScript stamps the target's
|
||||||
|
// position into IcecrownHelpers::malleableGooImpacts at OnSpellCast time.
|
||||||
|
// Any bot within 12yd of an active impact must flee; danger persists 8s.
|
||||||
|
// Once a bot dodges, we return true (blocking group-position) until the
|
||||||
|
// impact expires so the bot doesn't immediately re-enter the danger zone.
|
||||||
|
constexpr uint32 impactLifetimeMs = 8000;
|
||||||
|
constexpr float gooDangerRadius = 12.0f;
|
||||||
|
|
||||||
|
uint32 now = getMSTime();
|
||||||
|
float botX = bot->GetPositionX();
|
||||||
|
float botY = bot->GetPositionY();
|
||||||
|
float botZ = bot->GetPositionZ();
|
||||||
|
ObjectGuid botGuid = bot->GetGUID();
|
||||||
|
|
||||||
|
std::vector<Position> goos;
|
||||||
|
goos.reserve(4);
|
||||||
|
bool botInDanger = false;
|
||||||
|
auto impactIt = IcecrownHelpers::malleableGooImpacts.find(bot->GetMap()->GetInstanceId());
|
||||||
|
if (impactIt != IcecrownHelpers::malleableGooImpacts.end())
|
||||||
|
{
|
||||||
|
for (auto const& impact : impactIt->second)
|
||||||
|
{
|
||||||
|
if (getMSTimeDiff(impact.castTime, now) > impactLifetimeMs)
|
||||||
|
continue;
|
||||||
|
goos.push_back(impact.position);
|
||||||
|
|
||||||
|
float dx = botX - impact.position.GetPositionX();
|
||||||
|
float dy = botY - impact.position.GetPositionY();
|
||||||
|
if (dx * dx + dy * dy < gooDangerRadius * gooDangerRadius)
|
||||||
|
botInDanger = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!botInDanger)
|
||||||
|
{
|
||||||
|
// Already safe. Return false so DPS/heal rotations can still fire -
|
||||||
|
// the multiplier blocks repositioning actions during the wait window
|
||||||
|
// via festergutGooWaitUntil so the bot stays put without idling.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep 10yd between fleeing bots. During spore phase melee stack at the
|
||||||
|
// tank spot, so we ignore melee allies (the tank/melee pile would reject
|
||||||
|
// every nearby candidate). When no spore is active, melee also spread.
|
||||||
|
constexpr float botSpacing = 10.0f;
|
||||||
|
bool sporeActive = false;
|
||||||
|
if (Group* group = bot->GetGroup())
|
||||||
|
{
|
||||||
|
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
|
||||||
|
{
|
||||||
|
Player* member = itr->GetSource();
|
||||||
|
if (member && member->HasAura(SPELL_GAS_SPORE))
|
||||||
|
{
|
||||||
|
sporeActive = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Position> alliesToSpace;
|
||||||
|
if (Group* group = bot->GetGroup())
|
||||||
|
{
|
||||||
|
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
|
||||||
|
{
|
||||||
|
Player* member = itr->GetSource();
|
||||||
|
if (!member || !member->IsAlive() || member->GetGUID() == botGuid)
|
||||||
|
continue;
|
||||||
|
if (sporeActive && botAI->IsMelee(member))
|
||||||
|
continue;
|
||||||
|
alliesToSpace.push_back(member->GetPosition());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr int angleSteps = 24;
|
||||||
|
float const radii[] = {13.0f, 16.0f, 20.0f};
|
||||||
|
float bestScore = -1.0f;
|
||||||
|
float bestX = botX;
|
||||||
|
float bestY = botY;
|
||||||
|
bool found = false;
|
||||||
|
|
||||||
|
// Per-bot preferred flee angle - stable across ticks, distinct per GUID -
|
||||||
|
// so stacked bots fan out into different sectors instead of converging.
|
||||||
|
float preferredAngle = (botGuid.GetCounter() % angleSteps) * (2.0f * float(M_PI) / angleSteps);
|
||||||
|
constexpr float angleBias = 3.0f;
|
||||||
|
|
||||||
|
for (float r : radii)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < angleSteps; ++i)
|
||||||
|
{
|
||||||
|
float a = (2.0f * float(M_PI) * i) / angleSteps;
|
||||||
|
float cx = botX + std::cos(a) * r;
|
||||||
|
float cy = botY + std::sin(a) * r;
|
||||||
|
|
||||||
|
float minGooDistSq = std::numeric_limits<float>::max();
|
||||||
|
bool safe = true;
|
||||||
|
for (Position const& g : goos)
|
||||||
|
{
|
||||||
|
float gdx = cx - g.GetPositionX();
|
||||||
|
float gdy = cy - g.GetPositionY();
|
||||||
|
float d2 = gdx * gdx + gdy * gdy;
|
||||||
|
if (d2 < gooDangerRadius * gooDangerRadius)
|
||||||
|
{
|
||||||
|
safe = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (d2 < minGooDistSq)
|
||||||
|
minGooDistSq = d2;
|
||||||
|
}
|
||||||
|
if (!safe)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
bool tooCloseToAlly = false;
|
||||||
|
for (Position const& a2 : alliesToSpace)
|
||||||
|
{
|
||||||
|
float adx = cx - a2.GetPositionX();
|
||||||
|
float ady = cy - a2.GetPositionY();
|
||||||
|
if (adx * adx + ady * ady < botSpacing * botSpacing)
|
||||||
|
{
|
||||||
|
tooCloseToAlly = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (tooCloseToAlly)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!bot->IsWithinLOS(cx, cy, botZ))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
float travel = std::sqrt((cx - botX) * (cx - botX) + (cy - botY) * (cy - botY));
|
||||||
|
float score = std::sqrt(minGooDistSq) - travel * 0.1f + std::cos(a - preferredAngle) * angleBias;
|
||||||
|
|
||||||
|
if (score > bestScore)
|
||||||
|
{
|
||||||
|
bestScore = score;
|
||||||
|
bestX = cx;
|
||||||
|
bestY = cy;
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (found)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (found)
|
||||||
|
{
|
||||||
|
return MoveTo(bot->GetMapId(), bestX, bestY, botZ, false, false, false, false,
|
||||||
|
MovementPriority::MOVEMENT_COMBAT);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
1074
src/Ai/Raid/ICC/Action/ICCActions_GSB.cpp
Normal file
1074
src/Ai/Raid/ICC/Action/ICCActions_GSB.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1044
src/Ai/Raid/ICC/Action/ICCActions_LDW.cpp
Normal file
1044
src/Ai/Raid/ICC/Action/ICCActions_LDW.cpp
Normal file
File diff suppressed because it is too large
Load Diff
4568
src/Ai/Raid/ICC/Action/ICCActions_LK.cpp
Normal file
4568
src/Ai/Raid/ICC/Action/ICCActions_LK.cpp
Normal file
File diff suppressed because it is too large
Load Diff
601
src/Ai/Raid/ICC/Action/ICCActions_LM.cpp
Normal file
601
src/Ai/Raid/ICC/Action/ICCActions_LM.cpp
Normal file
@ -0,0 +1,601 @@
|
|||||||
|
#include "GenericActions.h"
|
||||||
|
#include "GenericSpellActions.h"
|
||||||
|
#include "Multiplier.h"
|
||||||
|
#include "NearestNpcsValue.h"
|
||||||
|
#include "ObjectAccessor.h"
|
||||||
|
#include "Playerbots.h"
|
||||||
|
#include "ICCActions.h"
|
||||||
|
#include "ICCTriggers.h"
|
||||||
|
#include "RtiValue.h"
|
||||||
|
#include "Vehicle.h"
|
||||||
|
|
||||||
|
// Lord Marrowgar
|
||||||
|
|
||||||
|
// Group iteration filter for same-instance, alive, in-world members.
|
||||||
|
static bool IsValidLmMember(Player* member, Player* bot)
|
||||||
|
{
|
||||||
|
if (!member || !member->IsInWorld() || !member->IsAlive())
|
||||||
|
return false;
|
||||||
|
if (member->GetMapId() != bot->GetMapId())
|
||||||
|
return false;
|
||||||
|
if (member->GetInstanceId() != bot->GetInstanceId())
|
||||||
|
return false;
|
||||||
|
if (member->HasAura(SPELL_LM_IMPALED))
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Up to two lowest-GUID ranged bots in same instance, hunter-priority.
|
||||||
|
static std::vector<Player*> PickBoneStormRangedTargets(Player* bot, PlayerbotAI* botAI)
|
||||||
|
{
|
||||||
|
std::vector<Player*> result;
|
||||||
|
|
||||||
|
Group* group = bot->GetGroup();
|
||||||
|
if (!group)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
std::vector<Player*> ranged;
|
||||||
|
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
||||||
|
{
|
||||||
|
Player* member = ref->GetSource();
|
||||||
|
if (!IsValidLmMember(member, bot))
|
||||||
|
continue;
|
||||||
|
if (botAI->IsTank(member))
|
||||||
|
continue;
|
||||||
|
if (!botAI->IsRanged(member))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
ranged.push_back(member);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ranged.empty())
|
||||||
|
return result;
|
||||||
|
|
||||||
|
std::sort(ranged.begin(), ranged.end(),
|
||||||
|
[](Player const* a, Player const* b) { return a->GetGUID() < b->GetGUID(); });
|
||||||
|
|
||||||
|
for (Player* p : ranged)
|
||||||
|
{
|
||||||
|
if (p->getClass() != CLASS_HUNTER)
|
||||||
|
continue;
|
||||||
|
result.push_back(p);
|
||||||
|
if (result.size() == 2)
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Player* p : ranged)
|
||||||
|
{
|
||||||
|
if (p->getClass() == CLASS_HUNTER)
|
||||||
|
continue;
|
||||||
|
result.push_back(p);
|
||||||
|
if (result.size() == 2)
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// True if any coldflame line sits within 10f of the anchor position.
|
||||||
|
// Used to widen the tank's "stay-put" tolerance so AvoidAoe can move them
|
||||||
|
// off the line without IccLmTankPositionAction dragging them back.
|
||||||
|
static bool ColdflameNearAnchor(Player* bot, Position const& anchor, float leash)
|
||||||
|
{
|
||||||
|
std::list<Creature*> coldflames;
|
||||||
|
bot->GetCreatureListWithEntryInGrid(coldflames, NPC_COLDFLAME, 200.0f);
|
||||||
|
for (Creature* c : coldflames)
|
||||||
|
if (c->GetExactDist2d(anchor.GetPositionX(), anchor.GetPositionY()) < leash)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IccLmTankPositionAction::Execute(Event /*event*/)
|
||||||
|
{
|
||||||
|
Unit* boss = AI_VALUE2(Unit*, "find target", "lord marrowgar");
|
||||||
|
if (!boss)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
bool const isBossInBoneStorm = botAI->GetAura("Bone Storm", boss) != nullptr;
|
||||||
|
float const maxDistanceThreshold = 3.0f;
|
||||||
|
|
||||||
|
if (isBossInBoneStorm)
|
||||||
|
{
|
||||||
|
std::vector<Player*> const rangedTargets = PickBoneStormRangedTargets(bot, botAI);
|
||||||
|
if (std::find(rangedTargets.begin(), rangedTargets.end(), bot) != rangedTargets.end())
|
||||||
|
{
|
||||||
|
float const anchorDist = bot->GetExactDist2d(ICC_LM_BONE_STORM_AT_POSITION.GetPositionX(),
|
||||||
|
ICC_LM_BONE_STORM_AT_POSITION.GetPositionY());
|
||||||
|
|
||||||
|
float const bossDist = bot->GetExactDist2d(boss);
|
||||||
|
float const proximityTrigger = 20.0f;
|
||||||
|
float const leash = 10.0f;
|
||||||
|
|
||||||
|
// Boss too close or standing in coldflame: reposition within leash from anchor
|
||||||
|
bool const bossNear = bossDist < proximityTrigger;
|
||||||
|
bool const inColdflame = [&]()
|
||||||
|
{
|
||||||
|
std::list<Creature*> coldflames;
|
||||||
|
bot->GetCreatureListWithEntryInGrid(coldflames, NPC_COLDFLAME, 4.0f);
|
||||||
|
return !coldflames.empty();
|
||||||
|
}();
|
||||||
|
|
||||||
|
if (bossNear || inColdflame)
|
||||||
|
{
|
||||||
|
// Try eight candidate offsets from the anchor at the leash radius;
|
||||||
|
// pick first one that is far from boss and clear of coldflames.
|
||||||
|
float bestX = bot->GetPositionX();
|
||||||
|
float bestY = bot->GetPositionY();
|
||||||
|
float bestScore = -1.0f;
|
||||||
|
bool found = false;
|
||||||
|
|
||||||
|
for (int i = 0; i < 8; ++i)
|
||||||
|
{
|
||||||
|
float const angle = (float)i * (float)M_PI / 4.0f;
|
||||||
|
float const cx = ICC_LM_BONE_STORM_AT_POSITION.GetPositionX() + std::cos(angle) * leash;
|
||||||
|
float const cy = ICC_LM_BONE_STORM_AT_POSITION.GetPositionY() + std::sin(angle) * leash;
|
||||||
|
|
||||||
|
float const dx = cx - boss->GetPositionX();
|
||||||
|
float const dy = cy - boss->GetPositionY();
|
||||||
|
float const distToBoss = std::sqrt(dx * dx + dy * dy);
|
||||||
|
|
||||||
|
std::list<Creature*> coldflames;
|
||||||
|
bot->GetCreatureListWithEntryInGrid(coldflames, NPC_COLDFLAME, 200.0f);
|
||||||
|
bool hitColdflame = false;
|
||||||
|
for (Creature* c : coldflames)
|
||||||
|
{
|
||||||
|
if (c->GetExactDist2d(cx, cy) < 4.0f)
|
||||||
|
{
|
||||||
|
hitColdflame = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hitColdflame)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (distToBoss > bestScore)
|
||||||
|
{
|
||||||
|
bestScore = distToBoss;
|
||||||
|
bestX = cx;
|
||||||
|
bestY = cy;
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (found)
|
||||||
|
return MoveTo(bot->GetMapId(), bestX, bestY, ICC_LM_BONE_STORM_AT_POSITION.GetPositionZ(), false,
|
||||||
|
false, false, false, MovementPriority::MOVEMENT_COMBAT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (anchorDist > maxDistanceThreshold)
|
||||||
|
return MoveTo(bot->GetMapId(), ICC_LM_BONE_STORM_AT_POSITION.GetPositionX(),
|
||||||
|
ICC_LM_BONE_STORM_AT_POSITION.GetPositionY(),
|
||||||
|
ICC_LM_BONE_STORM_AT_POSITION.GetPositionZ(), false, false, false, false,
|
||||||
|
MovementPriority::MOVEMENT_COMBAT);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
float const tankLeash =
|
||||||
|
ColdflameNearAnchor(bot, ICC_LM_TANK_POSITION, 10.0f) ? 10.0f : maxDistanceThreshold;
|
||||||
|
|
||||||
|
if (botAI->IsMainTank(bot))
|
||||||
|
{
|
||||||
|
float const distance =
|
||||||
|
bot->GetExactDist2d(ICC_LM_TANK_POSITION.GetPositionX(), ICC_LM_TANK_POSITION.GetPositionY());
|
||||||
|
if (distance > tankLeash)
|
||||||
|
return MoveTowardPosition(ICC_LM_TANK_POSITION, maxDistanceThreshold);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (botAI->IsAssistTank(bot))
|
||||||
|
{
|
||||||
|
float const distance =
|
||||||
|
bot->GetExactDist2d(ICC_LM_TANK_POSITION.GetPositionX(), ICC_LM_TANK_POSITION.GetPositionY());
|
||||||
|
if (distance > tankLeash)
|
||||||
|
return MoveTo(bot->GetMapId(), ICC_LM_TANK_POSITION.GetPositionX(),
|
||||||
|
ICC_LM_TANK_POSITION.GetPositionY(), ICC_LM_TANK_POSITION.GetPositionZ(), false, false,
|
||||||
|
false, false, MovementPriority::MOVEMENT_COMBAT);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non-tanks: if too far from mid position, move toward it
|
||||||
|
float const distance =
|
||||||
|
bot->GetExactDist2d(ICC_LM_MID_POSITION.GetPositionX(), ICC_LM_MID_POSITION.GetPositionY());
|
||||||
|
if (distance > 35.0f)
|
||||||
|
return MoveTowardPosition(ICC_LM_MID_POSITION, 15.0f);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
float const tankLeash =
|
||||||
|
ColdflameNearAnchor(bot, ICC_LM_TANK_POSITION, 10.0f) ? 10.0f : maxDistanceThreshold;
|
||||||
|
|
||||||
|
if (botAI->HasAggro(boss) && botAI->IsMainTank(bot) && boss->GetVictim() == bot)
|
||||||
|
{
|
||||||
|
float const distance =
|
||||||
|
bot->GetExactDist2d(ICC_LM_TANK_POSITION.GetPositionX(), ICC_LM_TANK_POSITION.GetPositionY());
|
||||||
|
|
||||||
|
if (distance > tankLeash)
|
||||||
|
return MoveTowardPosition(ICC_LM_TANK_POSITION, maxDistanceThreshold);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (botAI->IsAssistTank(bot))
|
||||||
|
{
|
||||||
|
float const distance =
|
||||||
|
bot->GetExactDist2d(ICC_LM_TANK_POSITION.GetPositionX(), ICC_LM_TANK_POSITION.GetPositionY());
|
||||||
|
|
||||||
|
if (distance > tankLeash)
|
||||||
|
return MoveTo(bot->GetMapId(), ICC_LM_TANK_POSITION.GetPositionX(), ICC_LM_TANK_POSITION.GetPositionY(),
|
||||||
|
ICC_LM_TANK_POSITION.GetPositionZ(), false, false, false, false,
|
||||||
|
MovementPriority::MOVEMENT_COMBAT);
|
||||||
|
|
||||||
|
if (distance < maxDistanceThreshold)
|
||||||
|
{
|
||||||
|
bot->SetFacingToObject(boss);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IccLmTankPositionAction::MoveTowardPosition(Position const& position, float incrementSize)
|
||||||
|
{
|
||||||
|
float const dirX = position.GetPositionX() - bot->GetPositionX();
|
||||||
|
float const dirY = position.GetPositionY() - bot->GetPositionY();
|
||||||
|
float const length = std::sqrt(dirX * dirX + dirY * dirY);
|
||||||
|
|
||||||
|
float const normalizedDirX = dirX / length;
|
||||||
|
float const normalizedDirY = dirY / length;
|
||||||
|
|
||||||
|
float const moveX = bot->GetPositionX() + normalizedDirX * incrementSize;
|
||||||
|
float const moveY = bot->GetPositionY() + normalizedDirY * incrementSize;
|
||||||
|
|
||||||
|
return MoveTo(bot->GetMapId(), moveX, moveY, bot->GetPositionZ(), false, false, false, false,
|
||||||
|
MovementPriority::MOVEMENT_COMBAT);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IccSpikeAction::Execute(Event /*event*/)
|
||||||
|
{
|
||||||
|
if (bot->HasAura(SPELL_LM_IMPALED))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Unit* boss = AI_VALUE2(Unit*, "find target", "lord marrowgar");
|
||||||
|
if (!boss)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
bool const isBossInBoneStorm = botAI->GetAura("Bone Storm", boss) != nullptr;
|
||||||
|
std::vector<Unit*> const spikes = FindAliveSpikes();
|
||||||
|
|
||||||
|
if (!spikes.empty())
|
||||||
|
{
|
||||||
|
HandleSpikeMarking(spikes, boss);
|
||||||
|
return HandleSpikeAssignment(spikes, boss);
|
||||||
|
}
|
||||||
|
|
||||||
|
// No spikes alive -- skull on boss, clear cross, all bots on skull
|
||||||
|
HandleNoSpikesMarking(boss);
|
||||||
|
|
||||||
|
// Melee non-tanks in front of boss should reposition
|
||||||
|
if (boss->isInFront(bot) && !botAI->IsTank(bot) && !isBossInBoneStorm)
|
||||||
|
{
|
||||||
|
Position const safePosition = {-390.6757f, 2230.5283f, 0.0f};
|
||||||
|
float const distance = bot->GetExactDist2d(safePosition.GetPositionX(), safePosition.GetPositionY());
|
||||||
|
if (distance > 3.0f)
|
||||||
|
return MoveTowardPosition(safePosition, 3.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Unit*> IccSpikeAction::FindAliveSpikes()
|
||||||
|
{
|
||||||
|
// All difficulty variants — AzerothCore spawns a different entry per difficulty mode.
|
||||||
|
// Bonespike NPCs have UNIT_FLAG_NOT_SELECTABLE | UNIT_FLAG_IMMUNE_TO_PC so they
|
||||||
|
// never appear in "possible targets no los". Use a direct grid search instead.
|
||||||
|
static uint32 const spikeEntries[] = {
|
||||||
|
NPC_SPIKE1, NPC_SPIKE1_10H, NPC_SPIKE1_25N, NPC_SPIKE1_25H,
|
||||||
|
NPC_SPIKE2, NPC_SPIKE2_10H, NPC_SPIKE2_25N, NPC_SPIKE2_25H,
|
||||||
|
NPC_SPIKE3, NPC_SPIKE3_10H, NPC_SPIKE3_25N, NPC_SPIKE3_25H
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<Unit*> spikes;
|
||||||
|
for (uint32 const entry : spikeEntries)
|
||||||
|
{
|
||||||
|
std::list<Creature*> found;
|
||||||
|
bot->GetCreatureListWithEntryInGrid(found, entry, 200.0f);
|
||||||
|
for (Creature* c : found)
|
||||||
|
{
|
||||||
|
if (c && c->IsAlive())
|
||||||
|
spikes.push_back(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(spikes.begin(), spikes.end(), [](Unit const* a, Unit const* b) { return a->GetGUID() < b->GetGUID(); });
|
||||||
|
return spikes;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IccSpikeAction::HandleSpikeMarking(std::vector<Unit*> const& spikes, Unit* boss)
|
||||||
|
{
|
||||||
|
Group* group = bot->GetGroup();
|
||||||
|
if (!group)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
static uint8 const Icons[] = {7, 6, 0}; // Skull, Cross, Star
|
||||||
|
|
||||||
|
std::vector<ObjectGuid> aliveSpikeGuids;
|
||||||
|
aliveSpikeGuids.reserve(spikes.size());
|
||||||
|
for (Unit* spike : spikes)
|
||||||
|
aliveSpikeGuids.push_back(spike->GetGUID());
|
||||||
|
|
||||||
|
for (uint8 const iconIdx : Icons)
|
||||||
|
{
|
||||||
|
ObjectGuid const iconGuid = group->GetTargetIcon(iconIdx);
|
||||||
|
if (iconGuid.IsEmpty())
|
||||||
|
continue;
|
||||||
|
if (std::find(aliveSpikeGuids.begin(), aliveSpikeGuids.end(), iconGuid) != aliveSpikeGuids.end())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Unit* marked = botAI->GetUnit(iconGuid);
|
||||||
|
if (marked && !marked->IsAlive())
|
||||||
|
group->SetTargetIcon(iconIdx, bot->GetGUID(), ObjectGuid::Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the only spike left is a tank spike
|
||||||
|
Player* firstSpikeVictim = spikes.size() == 1 ? GetSpikeVictim(spikes[0]) : nullptr;
|
||||||
|
bool const onlyTankSpike = firstSpikeVictim && botAI->IsTank(firstSpikeVictim);
|
||||||
|
|
||||||
|
if (onlyTankSpike)
|
||||||
|
{
|
||||||
|
// Skull on spike, Cross on boss
|
||||||
|
if (group->GetTargetIcon(7) != spikes[0]->GetGUID())
|
||||||
|
group->SetTargetIcon(7, bot->GetGUID(), spikes[0]->GetGUID());
|
||||||
|
|
||||||
|
if (group->GetTargetIcon(6) != boss->GetGUID())
|
||||||
|
group->SetTargetIcon(6, bot->GetGUID(), boss->GetGUID());
|
||||||
|
|
||||||
|
if (!group->GetTargetIcon(0).IsEmpty())
|
||||||
|
group->SetTargetIcon(0, bot->GetGUID(), ObjectGuid::Empty);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear icon slots beyond the current spike count
|
||||||
|
for (size_t i = spikes.size(); i < sizeof(Icons); ++i)
|
||||||
|
{
|
||||||
|
if (!group->GetTargetIcon(Icons[i]).IsEmpty())
|
||||||
|
group->SetTargetIcon(Icons[i], bot->GetGUID(), ObjectGuid::Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assign Skull/Cross/Star to each alive spike
|
||||||
|
for (size_t i = 0; i < spikes.size() && i < sizeof(Icons); ++i)
|
||||||
|
{
|
||||||
|
uint8 const iconIdx = Icons[i];
|
||||||
|
if (group->GetTargetIcon(iconIdx) != spikes[i]->GetGUID())
|
||||||
|
group->SetTargetIcon(iconIdx, bot->GetGUID(), spikes[i]->GetGUID());
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IccSpikeAction::HandleNoSpikesMarking(Unit* boss)
|
||||||
|
{
|
||||||
|
Group* group = bot->GetGroup();
|
||||||
|
if (!group)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Clear cross and star
|
||||||
|
for (uint8 const iconIdx : {uint8(6), uint8(0)})
|
||||||
|
{
|
||||||
|
if (!group->GetTargetIcon(iconIdx).IsEmpty())
|
||||||
|
group->SetTargetIcon(iconIdx, bot->GetGUID(), ObjectGuid::Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skull on boss
|
||||||
|
if (group->GetTargetIcon(7) != boss->GetGUID())
|
||||||
|
group->SetTargetIcon(7, bot->GetGUID(), boss->GetGUID());
|
||||||
|
|
||||||
|
// Per-bot context value -- every bot needs this for its own ChooseTarget.
|
||||||
|
context->GetValue<std::string>("rti")->Set("skull");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IccSpikeAction::HandleSpikeAssignment(std::vector<Unit*> const& spikes, Unit* boss)
|
||||||
|
{
|
||||||
|
Group* group = bot->GetGroup();
|
||||||
|
if (!group)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
bool const isMelee = botAI->IsMelee(bot) && !botAI->IsTank(bot);
|
||||||
|
bool const isAssistTank = botAI->IsAssistTank(bot);
|
||||||
|
|
||||||
|
auto isTankSpike = [&](Unit* spike) -> bool
|
||||||
|
{
|
||||||
|
Player* victim = GetSpikeVictim(spike);
|
||||||
|
return victim && botAI->IsTank(victim);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Assist tank: only attack tank spike, ignore all others
|
||||||
|
if (isAssistTank)
|
||||||
|
{
|
||||||
|
for (Unit* spike : spikes)
|
||||||
|
{
|
||||||
|
if (isTankSpike(spike))
|
||||||
|
{
|
||||||
|
Attack(spike);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only tank spike left -- ranged go skull (spike), melee go cross (boss)
|
||||||
|
bool const onlyTankSpike = spikes.size() == 1 && isTankSpike(spikes[0]);
|
||||||
|
if (onlyTankSpike)
|
||||||
|
{
|
||||||
|
if (isMelee)
|
||||||
|
{
|
||||||
|
context->GetValue<std::string>("rti")->Set("cross");
|
||||||
|
Attack(boss);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
context->GetValue<std::string>("rti")->Set("skull");
|
||||||
|
Attack(spikes[0]);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Melee DPS: pick closest safe spike within 20y, never tank spikes
|
||||||
|
if (isMelee)
|
||||||
|
{
|
||||||
|
Unit* bestSpike = nullptr;
|
||||||
|
float bestDist = 20.0f;
|
||||||
|
for (Unit* spike : spikes)
|
||||||
|
{
|
||||||
|
if (isTankSpike(spike))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (boss->isInFront(spike, 7.0f))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (IsSpikeInColdFlame(spike))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
float const dist = bot->GetExactDist2d(spike);
|
||||||
|
if (dist < bestDist)
|
||||||
|
{
|
||||||
|
bestDist = dist;
|
||||||
|
bestSpike = spike;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bestSpike)
|
||||||
|
Attack(bestSpike);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ranged / healers: balanced assignment across all spikes
|
||||||
|
std::vector<Player*> rangedMembers;
|
||||||
|
for (GroupReference* itr = group->GetFirstMember(); itr; itr = itr->next())
|
||||||
|
{
|
||||||
|
Player* member = itr->GetSource();
|
||||||
|
if (!member || !member->IsAlive() || member->HasAura(SPELL_LM_IMPALED))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (botAI->IsMainTank(member) || botAI->IsAssistTank(member))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (botAI->IsMelee(member) && !botAI->IsTank(member))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
rangedMembers.push_back(member);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rangedMembers.empty())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
std::sort(rangedMembers.begin(), rangedMembers.end(),
|
||||||
|
[](Player const* a, Player const* b) { return a->GetGUID() < b->GetGUID(); });
|
||||||
|
|
||||||
|
auto const it = std::find(rangedMembers.begin(), rangedMembers.end(), bot);
|
||||||
|
if (it == rangedMembers.end())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
size_t const myIndex = std::distance(rangedMembers.begin(), it);
|
||||||
|
std::vector<size_t> const groupSizes = CalculateBalancedGroupSizes(rangedMembers.size(), spikes.size());
|
||||||
|
size_t const spikeIndex = GetAssignedSpikeIndex(myIndex, groupSizes);
|
||||||
|
|
||||||
|
if (spikeIndex >= spikes.size())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Unit* mySpike = spikes[spikeIndex];
|
||||||
|
context->GetValue<std::string>("rti")->Set(GetRTIValueForSpike(spikeIndex));
|
||||||
|
|
||||||
|
Attack(mySpike);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IccSpikeAction::MoveTowardPosition(Position const& position, float incrementSize)
|
||||||
|
{
|
||||||
|
float const dirX = position.GetPositionX() - bot->GetPositionX();
|
||||||
|
float const dirY = position.GetPositionY() - bot->GetPositionY();
|
||||||
|
float const length = std::sqrt(dirX * dirX + dirY * dirY);
|
||||||
|
|
||||||
|
float const normalizedDirX = dirX / length;
|
||||||
|
float const normalizedDirY = dirY / length;
|
||||||
|
|
||||||
|
float const moveX = bot->GetPositionX() + normalizedDirX * incrementSize;
|
||||||
|
float const moveY = bot->GetPositionY() + normalizedDirY * incrementSize;
|
||||||
|
|
||||||
|
return MoveTo(bot->GetMapId(), moveX, moveY, bot->GetPositionZ(), false, false, false, false,
|
||||||
|
MovementPriority::MOVEMENT_COMBAT);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<size_t> IccSpikeAction::CalculateBalancedGroupSizes(size_t totalMembers, size_t numSpikes)
|
||||||
|
{
|
||||||
|
std::vector<size_t> groupSizes(numSpikes, 0);
|
||||||
|
if (numSpikes == 0)
|
||||||
|
return groupSizes;
|
||||||
|
|
||||||
|
size_t const baseSize = totalMembers / numSpikes;
|
||||||
|
size_t const remainder = totalMembers % numSpikes;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < numSpikes; ++i)
|
||||||
|
{
|
||||||
|
groupSizes[i] = baseSize;
|
||||||
|
if (i < remainder)
|
||||||
|
++groupSizes[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return groupSizes;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t IccSpikeAction::GetAssignedSpikeIndex(size_t memberIndex, std::vector<size_t> const& groupSizes)
|
||||||
|
{
|
||||||
|
size_t cursor = 0;
|
||||||
|
for (size_t spikeIndex = 0; spikeIndex < groupSizes.size(); ++spikeIndex)
|
||||||
|
{
|
||||||
|
if (memberIndex < cursor + groupSizes[spikeIndex])
|
||||||
|
return spikeIndex;
|
||||||
|
cursor += groupSizes[spikeIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string IccSpikeAction::GetRTIValueForSpike(size_t spikeIndex)
|
||||||
|
{
|
||||||
|
switch (spikeIndex)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
return "skull";
|
||||||
|
case 1:
|
||||||
|
return "cross";
|
||||||
|
case 2:
|
||||||
|
return "star";
|
||||||
|
default:
|
||||||
|
return "skull";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Player* IccSpikeAction::GetSpikeVictim(Unit* spike)
|
||||||
|
{
|
||||||
|
// Spike holds player via vehicle; GetVictim() is unreliable (NPC not in combat)
|
||||||
|
if (Vehicle* veh = spike->GetVehicleKit())
|
||||||
|
{
|
||||||
|
for (auto const& [seatId, seatInfo] : veh->Seats)
|
||||||
|
{
|
||||||
|
if (Unit* passenger = ObjectAccessor::GetUnit(*spike, seatInfo.Passenger.Guid))
|
||||||
|
return passenger->ToPlayer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IccSpikeAction::IsSpikeInColdFlame(Unit* spike)
|
||||||
|
{
|
||||||
|
float const checkRadius = 6.0f;
|
||||||
|
std::list<Creature*> coldflames;
|
||||||
|
spike->GetCreatureListWithEntryInGrid(coldflames, NPC_COLDFLAME, checkRadius);
|
||||||
|
return !coldflames.empty();
|
||||||
|
}
|
||||||
1733
src/Ai/Raid/ICC/Action/ICCActions_PP.cpp
Normal file
1733
src/Ai/Raid/ICC/Action/ICCActions_PP.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1091
src/Ai/Raid/ICC/Action/ICCActions_RF.cpp
Normal file
1091
src/Ai/Raid/ICC/Action/ICCActions_RF.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1530
src/Ai/Raid/ICC/Action/ICCActions_SG.cpp
Normal file
1530
src/Ai/Raid/ICC/Action/ICCActions_SG.cpp
Normal file
File diff suppressed because it is too large
Load Diff
61
src/Ai/Raid/ICC/Action/ICCActions_SS.cpp
Normal file
61
src/Ai/Raid/ICC/Action/ICCActions_SS.cpp
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
#include "ICCActions.h"
|
||||||
|
#include "NearestNpcsValue.h"
|
||||||
|
#include "ObjectAccessor.h"
|
||||||
|
#include "Playerbots.h"
|
||||||
|
#include "Vehicle.h"
|
||||||
|
#include "RtiValue.h"
|
||||||
|
#include "GenericSpellActions.h"
|
||||||
|
#include "GenericActions.h"
|
||||||
|
#include "ICCTriggers.h"
|
||||||
|
#include "Multiplier.h"
|
||||||
|
|
||||||
|
bool IccValkyreSpearAction::Execute(Event /*event*/)
|
||||||
|
{
|
||||||
|
// Find the nearest spear
|
||||||
|
Creature* spear = bot->FindNearestCreature(NPC_SPEAR, 100.0f);
|
||||||
|
if (!spear)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Move to the spear if not in range
|
||||||
|
if (!spear->IsWithinDistInMap(bot, INTERACTION_DISTANCE))
|
||||||
|
return MoveTo(spear, INTERACTION_DISTANCE);
|
||||||
|
|
||||||
|
// Remove shapeshift forms
|
||||||
|
botAI->RemoveShapeshift();
|
||||||
|
|
||||||
|
// Stop movement and click the spear
|
||||||
|
bot->GetMotionMaster()->Clear();
|
||||||
|
bot->StopMoving();
|
||||||
|
spear->HandleSpellClick(bot);
|
||||||
|
|
||||||
|
// Dismount if mounted
|
||||||
|
WorldPacket emptyPacket;
|
||||||
|
bot->GetSession()->HandleCancelMountAuraOpcode(emptyPacket);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IccSisterSvalnaAction::Execute(Event /*event*/)
|
||||||
|
{
|
||||||
|
Unit* svalna = AI_VALUE2(Unit*, "find target", "sister svalna");
|
||||||
|
if (!svalna || !svalna->HasAura(SPELL_AETHER_SHIELD))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Check if bot has the spear item
|
||||||
|
if (!botAI->HasItemInInventory(ITEM_SPEAR))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Get all items from inventory
|
||||||
|
std::vector<Item*> items = botAI->GetInventoryItems();
|
||||||
|
for (Item* item : items)
|
||||||
|
{
|
||||||
|
if (item->GetEntry() == ITEM_SPEAR)
|
||||||
|
{
|
||||||
|
// Use spear on Svalna
|
||||||
|
botAI->ImbueItem(item, svalna);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
1357
src/Ai/Raid/ICC/Action/ICCActions_VT.cpp
Normal file
1357
src/Ai/Raid/ICC/Action/ICCActions_VT.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,9 +1,9 @@
|
|||||||
#ifndef _PLAYERBOT_RAIDICCACTIONCONTEXT_H
|
#ifndef _PLAYERBOT_ICCACTIONCONTEXT_H
|
||||||
#define _PLAYERBOT_RAIDICCACTIONCONTEXT_H
|
#define _PLAYERBOT_ICCACTIONCONTEXT_H
|
||||||
|
|
||||||
#include "Action.h"
|
#include "Action.h"
|
||||||
#include "NamedObjectContext.h"
|
#include "NamedObjectContext.h"
|
||||||
#include "RaidIccActions.h"
|
#include "ICCActions.h"
|
||||||
|
|
||||||
class RaidIccActionContext : public NamedObjectContext<Action>
|
class RaidIccActionContext : public NamedObjectContext<Action>
|
||||||
{
|
{
|
||||||
@ -21,23 +21,29 @@ public:
|
|||||||
creators["icc rotting frost giant tank position"] = &RaidIccActionContext::icc_rotting_frost_giant_tank_position;
|
creators["icc rotting frost giant tank position"] = &RaidIccActionContext::icc_rotting_frost_giant_tank_position;
|
||||||
creators["icc cannon fire"] = &RaidIccActionContext::icc_cannon_fire;
|
creators["icc cannon fire"] = &RaidIccActionContext::icc_cannon_fire;
|
||||||
creators["icc gunship enter cannon"] = &RaidIccActionContext::icc_gunship_enter_cannon;
|
creators["icc gunship enter cannon"] = &RaidIccActionContext::icc_gunship_enter_cannon;
|
||||||
creators["icc gunship teleport ally"] = &RaidIccActionContext::icc_gunship_teleport_ally;
|
creators["icc gunship rocket jump"] = &RaidIccActionContext::icc_gunship_rocket_jump;
|
||||||
creators["icc gunship teleport horde"] = &RaidIccActionContext::icc_gunship_teleport_horde;
|
creators["icc gunship rocket pack setup"] = &RaidIccActionContext::icc_gunship_rocket_pack_setup;
|
||||||
|
|
||||||
creators["icc dbs tank position"] = &RaidIccActionContext::icc_dbs_tank_position;
|
creators["icc dbs tank position"] = &RaidIccActionContext::icc_dbs_tank_position;
|
||||||
creators["icc adds dbs"] = &RaidIccActionContext::icc_adds_dbs;
|
creators["icc adds dbs"] = &RaidIccActionContext::icc_adds_dbs;
|
||||||
|
|
||||||
|
creators["icc dogs tank position"] = &RaidIccActionContext::icc_dogs_tank_position;
|
||||||
|
|
||||||
creators["icc festergut group position"] = &RaidIccActionContext::icc_festergut_group_position;
|
creators["icc festergut group position"] = &RaidIccActionContext::icc_festergut_group_position;
|
||||||
creators["icc festergut spore"] = &RaidIccActionContext::icc_festergut_spore;
|
creators["icc festergut spore"] = &RaidIccActionContext::icc_festergut_spore;
|
||||||
|
creators["icc festergut avoid malleable goo"] = &RaidIccActionContext::icc_festergut_avoid_malleable_goo;
|
||||||
|
|
||||||
creators["icc rotface tank position"] = &RaidIccActionContext::icc_rotface_tank_position;
|
creators["icc rotface tank position"] = &RaidIccActionContext::icc_rotface_tank_position;
|
||||||
creators["icc rotface group position"] = &RaidIccActionContext::icc_rotface_group_position;
|
creators["icc rotface group position"] = &RaidIccActionContext::icc_rotface_group_position;
|
||||||
creators["icc rotface move away from explosion"] = &RaidIccActionContext::icc_rotface_move_away_from_explosion;
|
creators["icc rotface move away from explosion"] = &RaidIccActionContext::icc_rotface_move_away_from_explosion;
|
||||||
|
creators["icc rotface avoid vile gas"] = &RaidIccActionContext::icc_rotface_avoid_vile_gas;
|
||||||
|
|
||||||
|
creators["icc putricide mutated plague"] = &RaidIccActionContext::icc_putricide_mutated_plague;
|
||||||
creators["icc putricide volatile ooze"] = &RaidIccActionContext::icc_putricide_volatile_ooze;
|
creators["icc putricide volatile ooze"] = &RaidIccActionContext::icc_putricide_volatile_ooze;
|
||||||
creators["icc putricide gas cloud"] = &RaidIccActionContext::icc_putricide_gas_cloud;
|
creators["icc putricide gas cloud"] = &RaidIccActionContext::icc_putricide_gas_cloud;
|
||||||
creators["icc putricide growing ooze puddle"] = &RaidIccActionContext::icc_putricide_growing_ooze_puddle;
|
creators["icc putricide growing ooze puddle"] = &RaidIccActionContext::icc_putricide_growing_ooze_puddle;
|
||||||
creators["icc putricide avoid malleable goo"] = &RaidIccActionContext::icc_putricide_avoid_malleable_goo;
|
creators["icc putricide avoid malleable goo"] = &RaidIccActionContext::icc_putricide_avoid_malleable_goo;
|
||||||
|
creators["icc putricide abomination"] = &RaidIccActionContext::icc_putricide_abomination;
|
||||||
|
|
||||||
creators["icc bpc keleseth tank"] = &RaidIccActionContext::icc_bpc_keleseth_tank;
|
creators["icc bpc keleseth tank"] = &RaidIccActionContext::icc_bpc_keleseth_tank;
|
||||||
creators["icc bpc main tank"] = &RaidIccActionContext::icc_bpc_main_tank;
|
creators["icc bpc main tank"] = &RaidIccActionContext::icc_bpc_main_tank;
|
||||||
@ -56,9 +62,11 @@ public:
|
|||||||
creators["icc valithria portal"] = &RaidIccActionContext::icc_valithria_portal;
|
creators["icc valithria portal"] = &RaidIccActionContext::icc_valithria_portal;
|
||||||
creators["icc valithria heal"] = &RaidIccActionContext::icc_valithria_heal;
|
creators["icc valithria heal"] = &RaidIccActionContext::icc_valithria_heal;
|
||||||
creators["icc valithria dream cloud"] = &RaidIccActionContext::icc_valithria_dream_cloud;
|
creators["icc valithria dream cloud"] = &RaidIccActionContext::icc_valithria_dream_cloud;
|
||||||
|
creators["icc valithria zombie kite"] = &RaidIccActionContext::icc_valithria_zombie_kite;
|
||||||
|
|
||||||
creators["icc sindragosa group position"] = &RaidIccActionContext::icc_sindragosa_group_position;
|
creators["icc sindragosa group position"] = &RaidIccActionContext::icc_sindragosa_group_position;
|
||||||
creators["icc sindragosa frost beacon"] = &RaidIccActionContext::icc_sindragosa_frost_beacon;
|
creators["icc sindragosa frost beacon"] = &RaidIccActionContext::icc_sindragosa_frost_beacon;
|
||||||
|
creators["icc sindragosa hot"] = &RaidIccActionContext::icc_sindragosa_hot;
|
||||||
creators["icc sindragosa blistering cold"] = &RaidIccActionContext::icc_sindragosa_blistering_cold;
|
creators["icc sindragosa blistering cold"] = &RaidIccActionContext::icc_sindragosa_blistering_cold;
|
||||||
creators["icc sindragosa unchained magic"] = &RaidIccActionContext::icc_sindragosa_unchained_magic;
|
creators["icc sindragosa unchained magic"] = &RaidIccActionContext::icc_sindragosa_unchained_magic;
|
||||||
creators["icc sindragosa chilled to the bone"] = &RaidIccActionContext::icc_sindragosa_chilled_to_the_bone;
|
creators["icc sindragosa chilled to the bone"] = &RaidIccActionContext::icc_sindragosa_chilled_to_the_bone;
|
||||||
@ -70,6 +78,7 @@ public:
|
|||||||
creators["icc lich king necrotic plague"] = &RaidIccActionContext::icc_lich_king_necrotic_plague;
|
creators["icc lich king necrotic plague"] = &RaidIccActionContext::icc_lich_king_necrotic_plague;
|
||||||
creators["icc lich king winter"] = &RaidIccActionContext::icc_lich_king_winter;
|
creators["icc lich king winter"] = &RaidIccActionContext::icc_lich_king_winter;
|
||||||
creators["icc lich king adds"] = &RaidIccActionContext::icc_lich_king_adds;
|
creators["icc lich king adds"] = &RaidIccActionContext::icc_lich_king_adds;
|
||||||
|
creators["icc lich king spirit bomb"] = &RaidIccActionContext::icc_lich_king_spirit_bomb;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -84,23 +93,29 @@ private:
|
|||||||
static Action* icc_rotting_frost_giant_tank_position(PlayerbotAI* ai) { return new IccRottingFrostGiantTankPositionAction(ai); }
|
static Action* icc_rotting_frost_giant_tank_position(PlayerbotAI* ai) { return new IccRottingFrostGiantTankPositionAction(ai); }
|
||||||
static Action* icc_cannon_fire(PlayerbotAI* ai) { return new IccCannonFireAction(ai); }
|
static Action* icc_cannon_fire(PlayerbotAI* ai) { return new IccCannonFireAction(ai); }
|
||||||
static Action* icc_gunship_enter_cannon(PlayerbotAI* ai) { return new IccGunshipEnterCannonAction(ai); }
|
static Action* icc_gunship_enter_cannon(PlayerbotAI* ai) { return new IccGunshipEnterCannonAction(ai); }
|
||||||
static Action* icc_gunship_teleport_ally(PlayerbotAI* ai) { return new IccGunshipTeleportAllyAction(ai); }
|
static Action* icc_gunship_rocket_jump(PlayerbotAI* ai) { return new IccGunshipRocketJumpAction(ai); }
|
||||||
static Action* icc_gunship_teleport_horde(PlayerbotAI* ai) { return new IccGunshipTeleportHordeAction(ai); }
|
static Action* icc_gunship_rocket_pack_setup(PlayerbotAI* ai) { return new IccGunshipRocketPackSetupAction(ai); }
|
||||||
|
|
||||||
static Action* icc_dbs_tank_position(PlayerbotAI* ai) { return new IccDbsTankPositionAction(ai); }
|
static Action* icc_dbs_tank_position(PlayerbotAI* ai) { return new IccDbsTankPositionAction(ai); }
|
||||||
static Action* icc_adds_dbs(PlayerbotAI* ai) { return new IccAddsDbsAction(ai); }
|
static Action* icc_adds_dbs(PlayerbotAI* ai) { return new IccAddsDbsAction(ai); }
|
||||||
|
|
||||||
|
static Action* icc_dogs_tank_position(PlayerbotAI* ai) { return new IccDogsTankPositionAction(ai); }
|
||||||
|
|
||||||
static Action* icc_festergut_group_position(PlayerbotAI* ai) { return new IccFestergutGroupPositionAction(ai); }
|
static Action* icc_festergut_group_position(PlayerbotAI* ai) { return new IccFestergutGroupPositionAction(ai); }
|
||||||
static Action* icc_festergut_spore(PlayerbotAI* ai) { return new IccFestergutSporeAction(ai); }
|
static Action* icc_festergut_spore(PlayerbotAI* ai) { return new IccFestergutSporeAction(ai); }
|
||||||
|
static Action* icc_festergut_avoid_malleable_goo(PlayerbotAI* ai) { return new IccFestergutAvoidMalleableGooAction(ai); }
|
||||||
|
|
||||||
static Action* icc_rotface_tank_position(PlayerbotAI* ai) { return new IccRotfaceTankPositionAction(ai); }
|
static Action* icc_rotface_tank_position(PlayerbotAI* ai) { return new IccRotfaceTankPositionAction(ai); }
|
||||||
static Action* icc_rotface_group_position(PlayerbotAI* ai) { return new IccRotfaceGroupPositionAction(ai); }
|
static Action* icc_rotface_group_position(PlayerbotAI* ai) { return new IccRotfaceGroupPositionAction(ai); }
|
||||||
static Action* icc_rotface_move_away_from_explosion(PlayerbotAI* ai) { return new IccRotfaceMoveAwayFromExplosionAction(ai); }
|
static Action* icc_rotface_move_away_from_explosion(PlayerbotAI* ai) { return new IccRotfaceMoveAwayFromExplosionAction(ai); }
|
||||||
|
static Action* icc_rotface_avoid_vile_gas(PlayerbotAI* ai) { return new IccRotfaceAvoidVileGasAction(ai); }
|
||||||
|
|
||||||
|
static Action* icc_putricide_mutated_plague(PlayerbotAI* ai) { return new IccPutricideMutatedPlagueAction(ai); }
|
||||||
static Action* icc_putricide_volatile_ooze(PlayerbotAI* ai) { return new IccPutricideVolatileOozeAction(ai); }
|
static Action* icc_putricide_volatile_ooze(PlayerbotAI* ai) { return new IccPutricideVolatileOozeAction(ai); }
|
||||||
static Action* icc_putricide_gas_cloud(PlayerbotAI* ai) { return new IccPutricideGasCloudAction(ai); }
|
static Action* icc_putricide_gas_cloud(PlayerbotAI* ai) { return new IccPutricideGasCloudAction(ai); }
|
||||||
static Action* icc_putricide_growing_ooze_puddle(PlayerbotAI* ai) { return new IccPutricideGrowingOozePuddleAction(ai); }
|
static Action* icc_putricide_growing_ooze_puddle(PlayerbotAI* ai) { return new IccPutricideGrowingOozePuddleAction(ai); }
|
||||||
static Action* icc_putricide_avoid_malleable_goo(PlayerbotAI* ai) { return new IccPutricideAvoidMalleableGooAction(ai); }
|
static Action* icc_putricide_avoid_malleable_goo(PlayerbotAI* ai) { return new IccPutricideAvoidMalleableGooAction(ai); }
|
||||||
|
static Action* icc_putricide_abomination(PlayerbotAI* ai) { return new IccPutricideAbominationAction(ai); }
|
||||||
|
|
||||||
static Action* icc_bpc_keleseth_tank(PlayerbotAI* ai) { return new IccBpcKelesethTankAction(ai); }
|
static Action* icc_bpc_keleseth_tank(PlayerbotAI* ai) { return new IccBpcKelesethTankAction(ai); }
|
||||||
static Action* icc_bpc_main_tank(PlayerbotAI* ai) { return new IccBpcMainTankAction(ai); }
|
static Action* icc_bpc_main_tank(PlayerbotAI* ai) { return new IccBpcMainTankAction(ai); }
|
||||||
@ -119,9 +134,11 @@ private:
|
|||||||
static Action* icc_valithria_portal(PlayerbotAI* ai) { return new IccValithriaPortalAction(ai); }
|
static Action* icc_valithria_portal(PlayerbotAI* ai) { return new IccValithriaPortalAction(ai); }
|
||||||
static Action* icc_valithria_heal(PlayerbotAI* ai) { return new IccValithriaHealAction(ai); }
|
static Action* icc_valithria_heal(PlayerbotAI* ai) { return new IccValithriaHealAction(ai); }
|
||||||
static Action* icc_valithria_dream_cloud(PlayerbotAI* ai) { return new IccValithriaDreamCloudAction(ai); }
|
static Action* icc_valithria_dream_cloud(PlayerbotAI* ai) { return new IccValithriaDreamCloudAction(ai); }
|
||||||
|
static Action* icc_valithria_zombie_kite(PlayerbotAI* ai) { return new IccValithriaZombieKiteAction(ai); }
|
||||||
|
|
||||||
static Action* icc_sindragosa_group_position(PlayerbotAI* ai) { return new IccSindragosaGroupPositionAction(ai); }
|
static Action* icc_sindragosa_group_position(PlayerbotAI* ai) { return new IccSindragosaGroupPositionAction(ai); }
|
||||||
static Action* icc_sindragosa_frost_beacon(PlayerbotAI* ai) { return new IccSindragosaFrostBeaconAction(ai); }
|
static Action* icc_sindragosa_frost_beacon(PlayerbotAI* ai) { return new IccSindragosaFrostBeaconAction(ai); }
|
||||||
|
static Action* icc_sindragosa_hot(PlayerbotAI* ai) { return new IccSindragosaHotAction(ai); }
|
||||||
static Action* icc_sindragosa_blistering_cold(PlayerbotAI* ai) { return new IccSindragosaBlisteringColdAction(ai); }
|
static Action* icc_sindragosa_blistering_cold(PlayerbotAI* ai) { return new IccSindragosaBlisteringColdAction(ai); }
|
||||||
static Action* icc_sindragosa_unchained_magic(PlayerbotAI* ai) { return new IccSindragosaUnchainedMagicAction(ai); }
|
static Action* icc_sindragosa_unchained_magic(PlayerbotAI* ai) { return new IccSindragosaUnchainedMagicAction(ai); }
|
||||||
static Action* icc_sindragosa_chilled_to_the_bone(PlayerbotAI* ai) { return new IccSindragosaChilledToTheBoneAction(ai); }
|
static Action* icc_sindragosa_chilled_to_the_bone(PlayerbotAI* ai) { return new IccSindragosaChilledToTheBoneAction(ai); }
|
||||||
@ -133,6 +150,7 @@ private:
|
|||||||
static Action* icc_lich_king_necrotic_plague(PlayerbotAI* ai) { return new IccLichKingNecroticPlagueAction(ai); }
|
static Action* icc_lich_king_necrotic_plague(PlayerbotAI* ai) { return new IccLichKingNecroticPlagueAction(ai); }
|
||||||
static Action* icc_lich_king_winter(PlayerbotAI* ai) { return new IccLichKingWinterAction(ai); }
|
static Action* icc_lich_king_winter(PlayerbotAI* ai) { return new IccLichKingWinterAction(ai); }
|
||||||
static Action* icc_lich_king_adds(PlayerbotAI* ai) { return new IccLichKingAddsAction(ai); }
|
static Action* icc_lich_king_adds(PlayerbotAI* ai) { return new IccLichKingAddsAction(ai); }
|
||||||
|
static Action* icc_lich_king_spirit_bomb(PlayerbotAI* ai) { return new IccLichKingSpiritBombAction(ai); }
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
1208
src/Ai/Raid/ICC/ICCMultipliers.cpp
Normal file
1208
src/Ai/Raid/ICC/ICCMultipliers.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,5 @@
|
|||||||
#ifndef _PLAYERBOT_RAIDICCMULTIPLIERS_H
|
#ifndef _PLAYERBOT_ICCM_H
|
||||||
#define _PLAYERBOT_RAIDICCMULTIPLIERS_H
|
#define _PLAYERBOT_ICCM_H
|
||||||
|
|
||||||
#include "Multiplier.h"
|
#include "Multiplier.h"
|
||||||
|
|
||||||
@ -99,4 +99,19 @@ public:
|
|||||||
virtual float GetValue(Action* action);
|
virtual float GetValue(Action* action);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class IccLichKingSpiritBombMultiplier : public Multiplier
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccLichKingSpiritBombMultiplier(PlayerbotAI* ai) : Multiplier(ai, "icc lich king spirit bomb") {}
|
||||||
|
virtual float GetValue(Action* action);
|
||||||
|
};
|
||||||
|
|
||||||
|
//GUNSHIP
|
||||||
|
class IccGunshipMultiplier : public Multiplier
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccGunshipMultiplier(PlayerbotAI* ai) : Multiplier(ai, "icc gunship") {}
|
||||||
|
virtual float GetValue(Action* action);
|
||||||
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
123
src/Ai/Raid/ICC/ICCScripts.cpp
Normal file
123
src/Ai/Raid/ICC/ICCScripts.cpp
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
#include "ICCScripts.h"
|
||||||
|
#include "Player.h"
|
||||||
|
#include "ICCTriggers.h"
|
||||||
|
#include "ScriptMgr.h"
|
||||||
|
#include "Spell.h"
|
||||||
|
#include "SpellInfo.h"
|
||||||
|
#include "Timer.h"
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace IcecrownHelpers
|
||||||
|
{
|
||||||
|
std::unordered_map<uint32, std::vector<MalleableGooImpact>> malleableGooImpacts;
|
||||||
|
std::map<ObjectGuid, uint32> festergutGooWaitUntil;
|
||||||
|
std::unordered_map<uint32, DefileCastInfo> defileCast;
|
||||||
|
std::unordered_map<uint32, VileGasVictim> rotfaceVileGas;
|
||||||
|
std::map<ObjectGuid, uint32> rotfaceVileGasWaitUntil;
|
||||||
|
}
|
||||||
|
|
||||||
|
class IccPutricideListenerScript : public AllSpellScript
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccPutricideListenerScript() : AllSpellScript("IccPutricideListenerScript") { }
|
||||||
|
|
||||||
|
void OnSpellCast(Spell* spell, Unit* caster, SpellInfo const* spellInfo, bool /*skipCheck*/) override
|
||||||
|
{
|
||||||
|
if (!caster || !spellInfo)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (spellInfo->Id != SPELL_MALLEABLE_GOO_10N &&
|
||||||
|
spellInfo->Id != SPELL_MALLEABLE_GOO_25N &&
|
||||||
|
spellInfo->Id != SPELL_MALLEABLE_GOO_10H &&
|
||||||
|
spellInfo->Id != SPELL_MALLEABLE_GOO_25H &&
|
||||||
|
spellInfo->Id != SPELL_MALLEABLE_GOO_BALCONY)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Malleable Goo is cast triggered, so m_UniqueTargetInfo is not yet
|
||||||
|
// populated at this point; read the explicit unit target directly.
|
||||||
|
Unit* target = spell->m_targets.GetUnitTarget();
|
||||||
|
if (!target || !target->IsPlayer())
|
||||||
|
return;
|
||||||
|
|
||||||
|
uint32 now = getMSTime();
|
||||||
|
|
||||||
|
IcecrownHelpers::MalleableGooImpact impact;
|
||||||
|
impact.position = target->GetPosition();
|
||||||
|
impact.castTime = now;
|
||||||
|
|
||||||
|
auto& impacts = IcecrownHelpers::malleableGooImpacts[caster->GetMap()->GetInstanceId()];
|
||||||
|
impacts.push_back(impact);
|
||||||
|
|
||||||
|
// Evict stale entries to keep the list bounded. Retention covers the
|
||||||
|
// longest consumer window (Festergut avoid: 8s) + slack.
|
||||||
|
impacts.erase(
|
||||||
|
std::remove_if(impacts.begin(), impacts.end(),
|
||||||
|
[now](IcecrownHelpers::MalleableGooImpact const& i)
|
||||||
|
{ return getMSTimeDiff(i.castTime, now) > 9000; }),
|
||||||
|
impacts.end());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class IccRotfaceListenerScript : public AllSpellScript
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccRotfaceListenerScript() : AllSpellScript("IccRotfaceListenerScript") { }
|
||||||
|
|
||||||
|
void OnSpellCast(Spell* spell, Unit* caster, SpellInfo const* spellInfo, bool /*skipCheck*/) override
|
||||||
|
{
|
||||||
|
if (!caster || !spellInfo)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (spellInfo->Id != SPELL_VILE_GAS_H)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Professor Putricide casts vile gas from the balcony during Rotface
|
||||||
|
// heroic. Filtering on caster entry keeps this hook scoped to the
|
||||||
|
// Rotface encounter only (Festergut also uses 'vile gas' as the gas
|
||||||
|
// spore aura name but a different spell ID).
|
||||||
|
if (caster->GetEntry() != NPC_PROFESSOR_PUTRICIDE)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Unit* target = spell->m_targets.GetUnitTarget();
|
||||||
|
if (!target || !target->IsPlayer())
|
||||||
|
return;
|
||||||
|
|
||||||
|
IcecrownHelpers::VileGasVictim& entry = IcecrownHelpers::rotfaceVileGas[caster->GetMap()->GetInstanceId()];
|
||||||
|
entry.victimGuid = target->GetGUID();
|
||||||
|
entry.castTime = getMSTime();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class IccLichKingListenerScript : public AllSpellScript
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccLichKingListenerScript() : AllSpellScript("IccLichKingListenerScript") { }
|
||||||
|
|
||||||
|
// OnSpellPrepare fires at cast START (Spell::prepare). OnSpellCast fires
|
||||||
|
// at cast END, which for Defile (2s cast time) is too late - the puddle
|
||||||
|
// is already spawning and bots have no time to move out.
|
||||||
|
void OnSpellPrepare(Spell* spell, Unit* caster, SpellInfo const* spellInfo) override
|
||||||
|
{
|
||||||
|
if (!caster || !spellInfo)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (spellInfo->Id != DEFILE_CAST_ID)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Unit* target = spell->m_targets.GetUnitTarget();
|
||||||
|
if (!target || !target->IsPlayer())
|
||||||
|
return;
|
||||||
|
|
||||||
|
IcecrownHelpers::DefileCastInfo& entry =
|
||||||
|
IcecrownHelpers::defileCast[caster->GetMap()->GetInstanceId()];
|
||||||
|
entry.targetGuid = target->GetGUID();
|
||||||
|
entry.castTime = getMSTime();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void AddSC_IcecrownBotScripts()
|
||||||
|
{
|
||||||
|
new IccPutricideListenerScript();
|
||||||
|
new IccRotfaceListenerScript();
|
||||||
|
new IccLichKingListenerScript();
|
||||||
|
}
|
||||||
68
src/Ai/Raid/ICC/ICCScripts.h
Normal file
68
src/Ai/Raid/ICC/ICCScripts.h
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
#ifndef _PLAYERBOT_ICCSCRIPTS_H
|
||||||
|
#define _PLAYERBOT_ICCSCRIPTS_H
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
#include "ObjectGuid.h"
|
||||||
|
#include "Position.h"
|
||||||
|
|
||||||
|
namespace IcecrownHelpers
|
||||||
|
{
|
||||||
|
// Putricide - Malleable Goo
|
||||||
|
// Each entry records the impact position (target's location at cast time)
|
||||||
|
// and the ms timestamp of the cast. IccPutricideAvoidMalleableGooAction
|
||||||
|
// reads this list on every tick and makes every bot flee any active
|
||||||
|
// impact points, since the core casts the spell triggered (no cast bar)
|
||||||
|
// and it is neither a DynamicObject, trap, nor trigger NPC.
|
||||||
|
struct MalleableGooImpact
|
||||||
|
{
|
||||||
|
Position position;
|
||||||
|
uint32 castTime;
|
||||||
|
};
|
||||||
|
extern std::unordered_map<uint32, std::vector<MalleableGooImpact>> malleableGooImpacts;
|
||||||
|
|
||||||
|
// Festergut avoid-malleable-goo wait state. When a bot dodges goo we stamp
|
||||||
|
// a wait-until timestamp here so the trigger stays active and movement is
|
||||||
|
// held for the full 8s impact window - otherwise the group-position action
|
||||||
|
// pulls the bot back the very next tick, producing jitter.
|
||||||
|
extern std::map<ObjectGuid, uint32> festergutGooWaitUntil;
|
||||||
|
|
||||||
|
// Lich King - Defile (SPELL_DEFILE = 72762). Stamped at OnSpellCast time
|
||||||
|
// because the boss script casts via CastSpell(target, ...) and reading the
|
||||||
|
// target later via current-spell APIs is unreliable. Readers treat entries
|
||||||
|
// older than ~3s as expired (cast time is 2s).
|
||||||
|
struct DefileCastInfo
|
||||||
|
{
|
||||||
|
ObjectGuid targetGuid;
|
||||||
|
uint32 castTime;
|
||||||
|
};
|
||||||
|
extern std::unordered_map<uint32, DefileCastInfo> defileCast;
|
||||||
|
|
||||||
|
// Rotface - Vile Gas. Stamped at OnSpellCast time so the targeted bot can
|
||||||
|
// react before the aura applies. Readers treat entries older than ~5s as
|
||||||
|
// expired (covers the dodge window plus the 3s post-arrival hold).
|
||||||
|
struct VileGasVictim
|
||||||
|
{
|
||||||
|
ObjectGuid victimGuid;
|
||||||
|
uint32 castTime;
|
||||||
|
};
|
||||||
|
extern std::unordered_map<uint32, VileGasVictim> rotfaceVileGas;
|
||||||
|
|
||||||
|
// Rotface vile gas hold-at-safe-spot state. When the victim bot reaches
|
||||||
|
// its safe spot we stamp now+3000ms so the multiplier blocks any other
|
||||||
|
// movement action that would yank the bot back into the raid stack.
|
||||||
|
extern std::map<ObjectGuid, uint32> rotfaceVileGasWaitUntil;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Putricide - Mutated Abomination vehicle
|
||||||
|
constexpr uint32 GO_PUTRICIDE_DRINK_ME = 201584;
|
||||||
|
constexpr uint32 NPC_MUTATED_ABOMINATION_10 = 37672;
|
||||||
|
constexpr uint32 NPC_MUTATED_ABOMINATION_25 = 38285;
|
||||||
|
constexpr uint32 SPELL_MUTATED_TRANSFORMATION = 70311;
|
||||||
|
constexpr uint32 SPELL_ABO_EAT_OOZE = 70346;
|
||||||
|
constexpr uint32 SPELL_ABO_REGURGITATED_OOZE = 70539;
|
||||||
|
|
||||||
|
void AddSC_IcecrownBotScripts();
|
||||||
|
|
||||||
|
#endif
|
||||||
@ -1,6 +1,6 @@
|
|||||||
#include "RaidIccStrategy.h"
|
#include "ICCStrategy.h"
|
||||||
|
|
||||||
#include "RaidIccMultipliers.h"
|
#include "ICCMultipliers.h"
|
||||||
|
|
||||||
void RaidIccStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
void RaidIccStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||||
{
|
{
|
||||||
@ -28,34 +28,32 @@ void RaidIccStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
|||||||
triggers.push_back( new TriggerNode("icc in cannon",
|
triggers.push_back( new TriggerNode("icc in cannon",
|
||||||
{ NextAction("icc cannon fire", ACTION_RAID+5) }));
|
{ NextAction("icc cannon fire", ACTION_RAID+5) }));
|
||||||
|
|
||||||
triggers.push_back(new TriggerNode("icc gunship teleport ally",
|
triggers.push_back(new TriggerNode("icc gunship rocket jump",
|
||||||
{ NextAction("icc gunship teleport ally", ACTION_RAID + 4) }));
|
{ NextAction("icc gunship rocket jump", ACTION_RAID + 4)}));
|
||||||
|
|
||||||
triggers.push_back(new TriggerNode("icc gunship teleport horde",
|
triggers.push_back(new TriggerNode("icc gunship rocket pack setup",
|
||||||
{ NextAction("icc gunship teleport horde", ACTION_RAID + 4) }));
|
{ NextAction("icc gunship rocket pack setup", ACTION_RAID + 2)}));
|
||||||
|
|
||||||
//DBS
|
//DBS
|
||||||
triggers.push_back(new TriggerNode("icc dbs",
|
triggers.push_back(new TriggerNode("icc dbs",
|
||||||
{ NextAction("icc dbs tank position", ACTION_RAID + 3),
|
{ NextAction("icc dbs tank position", ACTION_RAID + 3),
|
||||||
NextAction("icc adds dbs", ACTION_RAID + 5) }));
|
NextAction("icc adds dbs", ACTION_RAID + 5) }));
|
||||||
|
|
||||||
triggers.push_back(new TriggerNode("icc dbs main tank rune of blood",
|
// Boss taunt on Rune of Blood is handled inside icc dbs tank position action
|
||||||
{ NextAction("taunt spell", ACTION_EMERGENCY + 4) }));
|
|
||||||
|
|
||||||
//DOGS
|
triggers.push_back(new TriggerNode("icc dogs",
|
||||||
triggers.push_back(new TriggerNode("icc stinky precious main tank mortal wound",
|
{ NextAction("icc dogs tank position", ACTION_RAID + 3) }));
|
||||||
{ NextAction("taunt spell", ACTION_EMERGENCY + 4) }));
|
|
||||||
|
|
||||||
//FESTERGUT
|
//FESTERGUT
|
||||||
triggers.push_back(new TriggerNode("icc festergut group position",
|
triggers.push_back(new TriggerNode("icc festergut group position",
|
||||||
{ NextAction("icc festergut group position", ACTION_MOVE + 4) }));
|
{ NextAction("icc festergut group position", ACTION_MOVE + 4) }));
|
||||||
|
|
||||||
triggers.push_back(new TriggerNode("icc festergut main tank gastric bloat",
|
|
||||||
{ NextAction("taunt spell", ACTION_EMERGENCY + 6) }));
|
|
||||||
|
|
||||||
triggers.push_back(new TriggerNode("icc festergut spore",
|
triggers.push_back(new TriggerNode("icc festergut spore",
|
||||||
{ NextAction("icc festergut spore", ACTION_MOVE + 5) }));
|
{ NextAction("icc festergut spore", ACTION_MOVE + 5) }));
|
||||||
|
|
||||||
|
triggers.push_back(new TriggerNode("icc festergut avoid malleable goo",
|
||||||
|
{ NextAction("icc festergut avoid malleable goo", ACTION_RAID + 7) }));
|
||||||
|
|
||||||
//ROTFACE
|
//ROTFACE
|
||||||
triggers.push_back(new TriggerNode("icc rotface tank position",
|
triggers.push_back(new TriggerNode("icc rotface tank position",
|
||||||
{ NextAction("icc rotface tank position", ACTION_RAID + 5) }));
|
{ NextAction("icc rotface tank position", ACTION_RAID + 5) }));
|
||||||
@ -66,6 +64,9 @@ void RaidIccStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
|||||||
triggers.push_back(new TriggerNode("icc rotface move away from explosion",
|
triggers.push_back(new TriggerNode("icc rotface move away from explosion",
|
||||||
{ NextAction("icc rotface move away from explosion", ACTION_RAID +7) }));
|
{ NextAction("icc rotface move away from explosion", ACTION_RAID +7) }));
|
||||||
|
|
||||||
|
triggers.push_back(new TriggerNode("icc rotface avoid vile gas",
|
||||||
|
{ NextAction("icc rotface avoid vile gas", ACTION_RAID + 8) }));
|
||||||
|
|
||||||
//PP
|
//PP
|
||||||
triggers.push_back(new TriggerNode("icc putricide volatile ooze",
|
triggers.push_back(new TriggerNode("icc putricide volatile ooze",
|
||||||
{ NextAction("icc putricide volatile ooze", ACTION_RAID + 4) }));
|
{ NextAction("icc putricide volatile ooze", ACTION_RAID + 4) }));
|
||||||
@ -76,11 +77,14 @@ void RaidIccStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
|||||||
triggers.push_back(new TriggerNode("icc putricide growing ooze puddle",
|
triggers.push_back(new TriggerNode("icc putricide growing ooze puddle",
|
||||||
{ NextAction("icc putricide growing ooze puddle", ACTION_RAID + 3) }));
|
{ NextAction("icc putricide growing ooze puddle", ACTION_RAID + 3) }));
|
||||||
|
|
||||||
triggers.push_back(new TriggerNode("icc putricide main tank mutated plague",
|
triggers.push_back(new TriggerNode("icc putricide mutated plague",
|
||||||
{ NextAction("taunt spell", ACTION_RAID + 10) }));
|
{ NextAction("icc putricide mutated plague", ACTION_RAID + 3) }));
|
||||||
|
|
||||||
triggers.push_back(new TriggerNode("icc putricide malleable goo",
|
triggers.push_back(new TriggerNode("icc putricide malleable goo",
|
||||||
{ NextAction("icc putricide avoid malleable goo", ACTION_RAID + 2) }));
|
{ NextAction("icc putricide avoid malleable goo", ACTION_RAID + 6) }));
|
||||||
|
|
||||||
|
triggers.push_back(new TriggerNode("icc putricide abomination",
|
||||||
|
{ NextAction("icc putricide abomination", ACTION_RAID + 7) }));
|
||||||
|
|
||||||
//BPC
|
//BPC
|
||||||
triggers.push_back(new TriggerNode("icc bpc keleseth tank",
|
triggers.push_back(new TriggerNode("icc bpc keleseth tank",
|
||||||
@ -119,6 +123,9 @@ void RaidIccStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
|||||||
triggers.push_back(new TriggerNode("icc valithria group",
|
triggers.push_back(new TriggerNode("icc valithria group",
|
||||||
{ NextAction("icc valithria group", ACTION_RAID + 1) }));
|
{ NextAction("icc valithria group", ACTION_RAID + 1) }));
|
||||||
|
|
||||||
|
triggers.push_back(new TriggerNode("icc valithria zombie kite",
|
||||||
|
{ NextAction("icc valithria zombie kite", ACTION_EMERGENCY + 9) }));
|
||||||
|
|
||||||
triggers.push_back(new TriggerNode("icc valithria portal",
|
triggers.push_back(new TriggerNode("icc valithria portal",
|
||||||
{ NextAction("icc valithria portal", ACTION_RAID + 5) }));
|
{ NextAction("icc valithria portal", ACTION_RAID + 5) }));
|
||||||
|
|
||||||
@ -135,6 +142,9 @@ void RaidIccStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
|||||||
triggers.push_back(new TriggerNode("icc sindragosa frost beacon",
|
triggers.push_back(new TriggerNode("icc sindragosa frost beacon",
|
||||||
{ NextAction("icc sindragosa frost beacon", ACTION_RAID + 5) }));
|
{ NextAction("icc sindragosa frost beacon", ACTION_RAID + 5) }));
|
||||||
|
|
||||||
|
triggers.push_back(new TriggerNode("icc sindragosa hot",
|
||||||
|
{ NextAction("icc sindragosa hot", ACTION_RAID + 6) }));
|
||||||
|
|
||||||
triggers.push_back(new TriggerNode("icc sindragosa blistering cold",
|
triggers.push_back(new TriggerNode("icc sindragosa blistering cold",
|
||||||
{ NextAction("icc sindragosa blistering cold", ACTION_EMERGENCY + 4) }));
|
{ NextAction("icc sindragosa blistering cold", ACTION_EMERGENCY + 4) }));
|
||||||
|
|
||||||
@ -168,6 +178,9 @@ void RaidIccStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
|||||||
|
|
||||||
triggers.push_back(new TriggerNode("icc lich king adds",
|
triggers.push_back(new TriggerNode("icc lich king adds",
|
||||||
{ NextAction("icc lich king adds", ACTION_RAID +2) }));
|
{ NextAction("icc lich king adds", ACTION_RAID +2) }));
|
||||||
|
|
||||||
|
triggers.push_back(new TriggerNode("icc lich king spirit bomb",
|
||||||
|
{ NextAction("icc lich king spirit bomb", ACTION_RAID +7) }));
|
||||||
}
|
}
|
||||||
|
|
||||||
void RaidIccStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers)
|
void RaidIccStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers)
|
||||||
@ -183,4 +196,6 @@ void RaidIccStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers)
|
|||||||
multipliers.push_back(new IccValithriaDreamCloudMultiplier(botAI));
|
multipliers.push_back(new IccValithriaDreamCloudMultiplier(botAI));
|
||||||
multipliers.push_back(new IccSindragosaMultiplier(botAI));
|
multipliers.push_back(new IccSindragosaMultiplier(botAI));
|
||||||
multipliers.push_back(new IccLichKingAddsMultiplier(botAI));
|
multipliers.push_back(new IccLichKingAddsMultiplier(botAI));
|
||||||
|
multipliers.push_back(new IccLichKingSpiritBombMultiplier(botAI));
|
||||||
|
multipliers.push_back(new IccGunshipMultiplier(botAI));
|
||||||
}
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
#ifndef _PLAYERBOT_RAIDICCSTRATEGY_H
|
#ifndef _PLAYERBOT_ICCS_H
|
||||||
#define _PLAYERBOT_RAIDICCSTRATEGY_H
|
#define _PLAYERBOT_ICCS_H
|
||||||
|
|
||||||
#include "Strategy.h"
|
#include "Strategy.h"
|
||||||
|
|
||||||
@ -1,8 +1,8 @@
|
|||||||
#ifndef _PLAYERBOT_RAIDICCTRIGGERCONTEXT_H
|
#ifndef _PLAYERBOT_ICCTRIGGERCONTEXT_H
|
||||||
#define _PLAYERBOT_RAIDICCTRIGGERCONTEXT_H
|
#define _PLAYERBOT_ICCTRIGGERCONTEXT_H
|
||||||
|
|
||||||
#include "NamedObjectContext.h"
|
#include "NamedObjectContext.h"
|
||||||
#include "RaidIccTriggers.h"
|
#include "ICCTriggers.h"
|
||||||
|
|
||||||
class RaidIccTriggerContext : public NamedObjectContext<Trigger>
|
class RaidIccTriggerContext : public NamedObjectContext<Trigger>
|
||||||
{
|
{
|
||||||
@ -17,27 +17,29 @@ public:
|
|||||||
creators["icc rotting frost giant tank position"] = &RaidIccTriggerContext::icc_rotting_frost_giant_tank_position;
|
creators["icc rotting frost giant tank position"] = &RaidIccTriggerContext::icc_rotting_frost_giant_tank_position;
|
||||||
creators["icc in cannon"] = &RaidIccTriggerContext::icc_in_cannon;
|
creators["icc in cannon"] = &RaidIccTriggerContext::icc_in_cannon;
|
||||||
creators["icc gunship cannon near"] = &RaidIccTriggerContext::icc_gunship_cannon_near;
|
creators["icc gunship cannon near"] = &RaidIccTriggerContext::icc_gunship_cannon_near;
|
||||||
creators["icc gunship teleport ally"] = &RaidIccTriggerContext::icc_gunship_teleport_ally;
|
creators["icc gunship rocket jump"] = &RaidIccTriggerContext::icc_gunship_rocket_jump;
|
||||||
creators["icc gunship teleport horde"] = &RaidIccTriggerContext::icc_gunship_teleport_horde;
|
creators["icc gunship rocket pack setup"] = &RaidIccTriggerContext::icc_gunship_rocket_pack_setup;
|
||||||
|
|
||||||
creators["icc dbs"] = &RaidIccTriggerContext::icc_dbs;
|
creators["icc dbs"] = &RaidIccTriggerContext::icc_dbs;
|
||||||
creators["icc dbs main tank rune of blood"] = &RaidIccTriggerContext::icc_dbs_main_tank_rune_of_blood;
|
creators["icc dbs main tank rune of blood"] = &RaidIccTriggerContext::icc_dbs_main_tank_rune_of_blood;
|
||||||
|
|
||||||
creators["icc stinky precious main tank mortal wound"] = &RaidIccTriggerContext::icc_stinky_precious_main_tank_mortal_wound;
|
creators["icc dogs"] = &RaidIccTriggerContext::icc_dogs;
|
||||||
|
|
||||||
creators["icc festergut group position"] = &RaidIccTriggerContext::icc_festergut_group_position;
|
creators["icc festergut group position"] = &RaidIccTriggerContext::icc_festergut_group_position;
|
||||||
creators["icc festergut main tank gastric bloat"] = &RaidIccTriggerContext::icc_festergut_main_tank_gastric_bloat;
|
|
||||||
creators["icc festergut spore"] = &RaidIccTriggerContext::icc_festergut_spore;
|
creators["icc festergut spore"] = &RaidIccTriggerContext::icc_festergut_spore;
|
||||||
|
creators["icc festergut avoid malleable goo"] = &RaidIccTriggerContext::icc_festergut_avoid_malleable_goo;
|
||||||
|
|
||||||
creators["icc rotface tank position"] = &RaidIccTriggerContext::icc_rotface_tank_position;
|
creators["icc rotface tank position"] = &RaidIccTriggerContext::icc_rotface_tank_position;
|
||||||
creators["icc rotface group position"] = &RaidIccTriggerContext::icc_rotface_group_position;
|
creators["icc rotface group position"] = &RaidIccTriggerContext::icc_rotface_group_position;
|
||||||
creators["icc rotface move away from explosion"] = &RaidIccTriggerContext::icc_rotface_move_away_from_explosion;
|
creators["icc rotface move away from explosion"] = &RaidIccTriggerContext::icc_rotface_move_away_from_explosion;
|
||||||
|
creators["icc rotface avoid vile gas"] = &RaidIccTriggerContext::icc_rotface_avoid_vile_gas;
|
||||||
|
|
||||||
creators["icc putricide volatile ooze"] = &RaidIccTriggerContext::icc_putricide_volatile_ooze;
|
creators["icc putricide volatile ooze"] = &RaidIccTriggerContext::icc_putricide_volatile_ooze;
|
||||||
creators["icc putricide gas cloud"] = &RaidIccTriggerContext::icc_putricide_gas_cloud;
|
creators["icc putricide gas cloud"] = &RaidIccTriggerContext::icc_putricide_gas_cloud;
|
||||||
creators["icc putricide growing ooze puddle"] = &RaidIccTriggerContext::icc_putricide_growing_ooze_puddle;
|
creators["icc putricide growing ooze puddle"] = &RaidIccTriggerContext::icc_putricide_growing_ooze_puddle;
|
||||||
creators["icc putricide main tank mutated plague"] = &RaidIccTriggerContext::icc_putricide_main_tank_mutated_plague;
|
creators["icc putricide mutated plague"] = &RaidIccTriggerContext::icc_putricide_mutated_plague;
|
||||||
creators["icc putricide malleable goo"] = &RaidIccTriggerContext::icc_putricide_malleable_goo;
|
creators["icc putricide malleable goo"] = &RaidIccTriggerContext::icc_putricide_malleable_goo;
|
||||||
|
creators["icc putricide abomination"] = &RaidIccTriggerContext::icc_putricide_abomination;
|
||||||
|
|
||||||
creators["icc bpc keleseth tank"] = &RaidIccTriggerContext::icc_bpc_keleseth_tank;
|
creators["icc bpc keleseth tank"] = &RaidIccTriggerContext::icc_bpc_keleseth_tank;
|
||||||
creators["icc bpc main tank"] = &RaidIccTriggerContext::icc_bpc_main_tank;
|
creators["icc bpc main tank"] = &RaidIccTriggerContext::icc_bpc_main_tank;
|
||||||
@ -56,9 +58,11 @@ public:
|
|||||||
creators["icc valithria portal"] = &RaidIccTriggerContext::icc_valithria_portal;
|
creators["icc valithria portal"] = &RaidIccTriggerContext::icc_valithria_portal;
|
||||||
creators["icc valithria heal"] = &RaidIccTriggerContext::icc_valithria_heal;
|
creators["icc valithria heal"] = &RaidIccTriggerContext::icc_valithria_heal;
|
||||||
creators["icc valithria dream cloud"] = &RaidIccTriggerContext::icc_valithria_dream_cloud;
|
creators["icc valithria dream cloud"] = &RaidIccTriggerContext::icc_valithria_dream_cloud;
|
||||||
|
creators["icc valithria zombie kite"] = &RaidIccTriggerContext::icc_valithria_zombie_kite;
|
||||||
|
|
||||||
creators["icc sindragosa group position"] = &RaidIccTriggerContext::icc_sindragosa_group_position;
|
creators["icc sindragosa group position"] = &RaidIccTriggerContext::icc_sindragosa_group_position;
|
||||||
creators["icc sindragosa frost beacon"] = &RaidIccTriggerContext::icc_sindragosa_frost_beacon;
|
creators["icc sindragosa frost beacon"] = &RaidIccTriggerContext::icc_sindragosa_frost_beacon;
|
||||||
|
creators["icc sindragosa hot"] = &RaidIccTriggerContext::icc_sindragosa_hot;
|
||||||
creators["icc sindragosa blistering cold"] = &RaidIccTriggerContext::icc_sindragosa_blistering_cold;
|
creators["icc sindragosa blistering cold"] = &RaidIccTriggerContext::icc_sindragosa_blistering_cold;
|
||||||
creators["icc sindragosa unchained magic"] = &RaidIccTriggerContext::icc_sindragosa_unchained_magic;
|
creators["icc sindragosa unchained magic"] = &RaidIccTriggerContext::icc_sindragosa_unchained_magic;
|
||||||
creators["icc sindragosa chilled to the bone"] = &RaidIccTriggerContext::icc_sindragosa_chilled_to_the_bone;
|
creators["icc sindragosa chilled to the bone"] = &RaidIccTriggerContext::icc_sindragosa_chilled_to_the_bone;
|
||||||
@ -71,6 +75,7 @@ public:
|
|||||||
creators["icc lich king necrotic plague"] = &RaidIccTriggerContext::icc_lich_king_necrotic_plague;
|
creators["icc lich king necrotic plague"] = &RaidIccTriggerContext::icc_lich_king_necrotic_plague;
|
||||||
creators["icc lich king winter"] = &RaidIccTriggerContext::icc_lich_king_winter;
|
creators["icc lich king winter"] = &RaidIccTriggerContext::icc_lich_king_winter;
|
||||||
creators["icc lich king adds"] = &RaidIccTriggerContext::icc_lich_king_adds;
|
creators["icc lich king adds"] = &RaidIccTriggerContext::icc_lich_king_adds;
|
||||||
|
creators["icc lich king spirit bomb"] = &RaidIccTriggerContext::icc_lich_king_spirit_bomb;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -82,27 +87,29 @@ private:
|
|||||||
static Trigger* icc_rotting_frost_giant_tank_position(PlayerbotAI* ai) { return new IccRottingFrostGiantTankPositionTrigger(ai); }
|
static Trigger* icc_rotting_frost_giant_tank_position(PlayerbotAI* ai) { return new IccRottingFrostGiantTankPositionTrigger(ai); }
|
||||||
static Trigger* icc_in_cannon(PlayerbotAI* ai) { return new IccInCannonTrigger(ai); }
|
static Trigger* icc_in_cannon(PlayerbotAI* ai) { return new IccInCannonTrigger(ai); }
|
||||||
static Trigger* icc_gunship_cannon_near(PlayerbotAI* ai) { return new IccGunshipCannonNearTrigger(ai); }
|
static Trigger* icc_gunship_cannon_near(PlayerbotAI* ai) { return new IccGunshipCannonNearTrigger(ai); }
|
||||||
static Trigger* icc_gunship_teleport_ally(PlayerbotAI* ai) { return new IccGunshipTeleportAllyTrigger(ai); }
|
static Trigger* icc_gunship_rocket_jump(PlayerbotAI* ai) { return new IccGunshipRocketJumpTrigger(ai); }
|
||||||
static Trigger* icc_gunship_teleport_horde(PlayerbotAI* ai) { return new IccGunshipTeleportHordeTrigger(ai); }
|
static Trigger* icc_gunship_rocket_pack_setup(PlayerbotAI* ai) { return new IccGunshipRocketPackSetupTrigger(ai); }
|
||||||
|
|
||||||
static Trigger* icc_dbs(PlayerbotAI* ai) { return new IccDbsTrigger(ai); }
|
static Trigger* icc_dbs(PlayerbotAI* ai) { return new IccDbsTrigger(ai); }
|
||||||
static Trigger* icc_dbs_main_tank_rune_of_blood(PlayerbotAI* ai) { return new IccDbsMainTankRuneOfBloodTrigger(ai); }
|
static Trigger* icc_dbs_main_tank_rune_of_blood(PlayerbotAI* ai) { return new IccDbsMainTankRuneOfBloodTrigger(ai); }
|
||||||
|
|
||||||
static Trigger* icc_stinky_precious_main_tank_mortal_wound(PlayerbotAI* ai) { return new IccStinkyPreciousMainTankMortalWoundTrigger(ai); }
|
static Trigger* icc_dogs(PlayerbotAI* ai) { return new IccDogsTrigger(ai); }
|
||||||
|
|
||||||
static Trigger* icc_festergut_group_position(PlayerbotAI* ai) { return new IccFestergutGroupPositionTrigger(ai); }
|
static Trigger* icc_festergut_group_position(PlayerbotAI* ai) { return new IccFestergutGroupPositionTrigger(ai); }
|
||||||
static Trigger* icc_festergut_main_tank_gastric_bloat(PlayerbotAI* ai) { return new IccFestergutMainTankGastricBloatTrigger(ai); }
|
|
||||||
static Trigger* icc_festergut_spore(PlayerbotAI* ai) { return new IccFestergutSporeTrigger(ai); }
|
static Trigger* icc_festergut_spore(PlayerbotAI* ai) { return new IccFestergutSporeTrigger(ai); }
|
||||||
|
static Trigger* icc_festergut_avoid_malleable_goo(PlayerbotAI* ai) { return new IccFestergutAvoidMalleableGooTrigger(ai); }
|
||||||
|
|
||||||
static Trigger* icc_rotface_tank_position(PlayerbotAI* ai) { return new IccRotfaceTankPositionTrigger(ai); }
|
static Trigger* icc_rotface_tank_position(PlayerbotAI* ai) { return new IccRotfaceTankPositionTrigger(ai); }
|
||||||
static Trigger* icc_rotface_group_position(PlayerbotAI* ai) { return new IccRotfaceGroupPositionTrigger(ai); }
|
static Trigger* icc_rotface_group_position(PlayerbotAI* ai) { return new IccRotfaceGroupPositionTrigger(ai); }
|
||||||
static Trigger* icc_rotface_move_away_from_explosion(PlayerbotAI* ai) { return new IccRotfaceMoveAwayFromExplosionTrigger(ai); }
|
static Trigger* icc_rotface_move_away_from_explosion(PlayerbotAI* ai) { return new IccRotfaceMoveAwayFromExplosionTrigger(ai); }
|
||||||
|
static Trigger* icc_rotface_avoid_vile_gas(PlayerbotAI* ai) { return new IccRotfaceAvoidVileGasTrigger(ai); }
|
||||||
|
|
||||||
static Trigger* icc_putricide_volatile_ooze(PlayerbotAI* ai) { return new IccPutricideVolatileOozeTrigger(ai); }
|
static Trigger* icc_putricide_volatile_ooze(PlayerbotAI* ai) { return new IccPutricideVolatileOozeTrigger(ai); }
|
||||||
static Trigger* icc_putricide_gas_cloud(PlayerbotAI* ai) { return new IccPutricideGasCloudTrigger(ai); }
|
static Trigger* icc_putricide_gas_cloud(PlayerbotAI* ai) { return new IccPutricideGasCloudTrigger(ai); }
|
||||||
static Trigger* icc_putricide_growing_ooze_puddle(PlayerbotAI* ai) { return new IccPutricideGrowingOozePuddleTrigger(ai); }
|
static Trigger* icc_putricide_growing_ooze_puddle(PlayerbotAI* ai) { return new IccPutricideGrowingOozePuddleTrigger(ai); }
|
||||||
static Trigger* icc_putricide_main_tank_mutated_plague(PlayerbotAI* ai) { return new IccPutricideMainTankMutatedPlagueTrigger(ai); }
|
static Trigger* icc_putricide_mutated_plague(PlayerbotAI* ai) { return new IccPutricideMutatedPlagueTrigger(ai); }
|
||||||
static Trigger* icc_putricide_malleable_goo(PlayerbotAI* ai) { return new IccPutricideMalleableGooTrigger(ai); }
|
static Trigger* icc_putricide_malleable_goo(PlayerbotAI* ai) { return new IccPutricideMalleableGooTrigger(ai); }
|
||||||
|
static Trigger* icc_putricide_abomination(PlayerbotAI* ai) { return new IccPutricideAbominationTrigger(ai); }
|
||||||
|
|
||||||
static Trigger* icc_bpc_keleseth_tank(PlayerbotAI* ai) { return new IccBpcKelesethTankTrigger(ai); }
|
static Trigger* icc_bpc_keleseth_tank(PlayerbotAI* ai) { return new IccBpcKelesethTankTrigger(ai); }
|
||||||
static Trigger* icc_bpc_main_tank(PlayerbotAI* ai) { return new IccBpcMainTankTrigger(ai); }
|
static Trigger* icc_bpc_main_tank(PlayerbotAI* ai) { return new IccBpcMainTankTrigger(ai); }
|
||||||
@ -120,10 +127,12 @@ private:
|
|||||||
static Trigger* icc_valithria_group(PlayerbotAI* ai) { return new IccValithriaGroupTrigger(ai); }
|
static Trigger* icc_valithria_group(PlayerbotAI* ai) { return new IccValithriaGroupTrigger(ai); }
|
||||||
static Trigger* icc_valithria_portal(PlayerbotAI* ai) { return new IccValithriaPortalTrigger(ai); }
|
static Trigger* icc_valithria_portal(PlayerbotAI* ai) { return new IccValithriaPortalTrigger(ai); }
|
||||||
static Trigger* icc_valithria_heal(PlayerbotAI* ai) { return new IccValithriaHealTrigger(ai); }
|
static Trigger* icc_valithria_heal(PlayerbotAI* ai) { return new IccValithriaHealTrigger(ai); }
|
||||||
|
static Trigger* icc_valithria_zombie_kite(PlayerbotAI* ai) { return new IccValithriaZombieKiteTrigger(ai); }
|
||||||
static Trigger* icc_valithria_dream_cloud(PlayerbotAI* ai) { return new IccValithriaDreamCloudTrigger(ai); }
|
static Trigger* icc_valithria_dream_cloud(PlayerbotAI* ai) { return new IccValithriaDreamCloudTrigger(ai); }
|
||||||
|
|
||||||
static Trigger* icc_sindragosa_group_position(PlayerbotAI* ai) { return new IccSindragosaGroupPositionTrigger(ai); }
|
static Trigger* icc_sindragosa_group_position(PlayerbotAI* ai) { return new IccSindragosaGroupPositionTrigger(ai); }
|
||||||
static Trigger* icc_sindragosa_frost_beacon(PlayerbotAI* ai) { return new IccSindragosaFrostBeaconTrigger(ai); }
|
static Trigger* icc_sindragosa_frost_beacon(PlayerbotAI* ai) { return new IccSindragosaFrostBeaconTrigger(ai); }
|
||||||
|
static Trigger* icc_sindragosa_hot(PlayerbotAI* ai) { return new IccSindragosaHotTrigger(ai); }
|
||||||
static Trigger* icc_sindragosa_blistering_cold(PlayerbotAI* ai) { return new IccSindragosaBlisteringColdTrigger(ai); }
|
static Trigger* icc_sindragosa_blistering_cold(PlayerbotAI* ai) { return new IccSindragosaBlisteringColdTrigger(ai); }
|
||||||
static Trigger* icc_sindragosa_unchained_magic(PlayerbotAI* ai) { return new IccSindragosaUnchainedMagicTrigger(ai); }
|
static Trigger* icc_sindragosa_unchained_magic(PlayerbotAI* ai) { return new IccSindragosaUnchainedMagicTrigger(ai); }
|
||||||
static Trigger* icc_sindragosa_chilled_to_the_bone(PlayerbotAI* ai) { return new IccSindragosaChilledToTheBoneTrigger(ai); }
|
static Trigger* icc_sindragosa_chilled_to_the_bone(PlayerbotAI* ai) { return new IccSindragosaChilledToTheBoneTrigger(ai); }
|
||||||
@ -136,6 +145,7 @@ private:
|
|||||||
static Trigger* icc_lich_king_necrotic_plague(PlayerbotAI* ai) { return new IccLichKingNecroticPlagueTrigger(ai); }
|
static Trigger* icc_lich_king_necrotic_plague(PlayerbotAI* ai) { return new IccLichKingNecroticPlagueTrigger(ai); }
|
||||||
static Trigger* icc_lich_king_winter(PlayerbotAI* ai) { return new IccLichKingWinterTrigger(ai); }
|
static Trigger* icc_lich_king_winter(PlayerbotAI* ai) { return new IccLichKingWinterTrigger(ai); }
|
||||||
static Trigger* icc_lich_king_adds(PlayerbotAI* ai) { return new IccLichKingAddsTrigger(ai); }
|
static Trigger* icc_lich_king_adds(PlayerbotAI* ai) { return new IccLichKingAddsTrigger(ai); }
|
||||||
|
static Trigger* icc_lich_king_spirit_bomb(PlayerbotAI* ai) { return new IccLichKingSpiritBombTrigger(ai); }
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1,5 +1,5 @@
|
|||||||
#include "RaidIccTriggers.h"
|
#include "ICCTriggers.h"
|
||||||
#include "RaidIccActions.h"
|
#include "ICCActions.h"
|
||||||
#include "NearestNpcsValue.h"
|
#include "NearestNpcsValue.h"
|
||||||
#include "PlayerbotAIConfig.h"
|
#include "PlayerbotAIConfig.h"
|
||||||
#include "ObjectAccessor.h"
|
#include "ObjectAccessor.h"
|
||||||
@ -8,6 +8,7 @@
|
|||||||
#include "Trigger.h"
|
#include "Trigger.h"
|
||||||
#include "GridNotifiers.h"
|
#include "GridNotifiers.h"
|
||||||
#include "Vehicle.h"
|
#include "Vehicle.h"
|
||||||
|
#include "ICCScripts.h"
|
||||||
|
|
||||||
//Lord Marrogwar
|
//Lord Marrogwar
|
||||||
bool IccLmTrigger::IsActive()
|
bool IccLmTrigger::IsActive()
|
||||||
@ -37,9 +38,6 @@ bool IccLadyDeathwhisperTrigger::IsActive()
|
|||||||
if (!boss)
|
if (!boss)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (bot->HasAura(SPELL_EXPERIENCED))
|
|
||||||
bot->RemoveAura(SPELL_EXPERIENCED);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,52 +71,60 @@ bool IccGunshipCannonNearTrigger::IsActive()
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
Unit* mount1 = bot->FindNearestCreature(NPC_CANNONA, 100.0f);
|
Unit* mount1 = bot->FindNearestCreature(NPC_CANNONA, 100.0f);
|
||||||
|
|
||||||
Unit* mount2 = bot->FindNearestCreature(NPC_CANNONH, 100.0f);
|
Unit* mount2 = bot->FindNearestCreature(NPC_CANNONH, 100.0f);
|
||||||
|
|
||||||
if (!mount1 && !mount2)
|
if (!mount1 && !mount2)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
// If cannons have Below Zero aura, don't try to enter them
|
||||||
|
Unit* friendlyCannon = nullptr;
|
||||||
|
if (mount1 && mount1->IsFriendlyTo(bot))
|
||||||
|
friendlyCannon = mount1;
|
||||||
|
else if (mount2 && mount2->IsFriendlyTo(bot))
|
||||||
|
friendlyCannon = mount2;
|
||||||
|
|
||||||
|
if (friendlyCannon && friendlyCannon->HasAura(SPELL_BELOW_ZERO))
|
||||||
|
return false;
|
||||||
|
|
||||||
if (!botAI->IsDps(bot))
|
if (!botAI->IsDps(bot))
|
||||||
return false;
|
return false;
|
||||||
// Player* master = botAI->GetMaster();
|
|
||||||
// if (!master)
|
|
||||||
// return false;
|
|
||||||
|
|
||||||
// if (!master->GetVehicle())
|
|
||||||
// return false;
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IccGunshipTeleportAllyTrigger::IsActive()
|
bool IccGunshipRocketJumpTrigger::IsActive()
|
||||||
{
|
{
|
||||||
Unit* boss = bot->FindNearestCreature(NPC_HIGH_OVERLORD_SAURFANG, 100.0f);
|
// The rocket jump mechanic is only needed when the gunship battle is active.
|
||||||
if (!boss || !boss->IsInWorld() || boss->IsDuringRemoveFromWorld())
|
// We detect which ship we are on by checking which enemy boss is present:
|
||||||
return false;
|
// - Saurfang hostile => we are on the Alliance ship
|
||||||
|
// - Muradin hostile => we are on the Horde ship
|
||||||
if (!boss->IsAlive())
|
// Using the hostile boss (not cannon friendliness) avoids conflicting with
|
||||||
return false;
|
// the cannon-near trigger that fires on the same condition.
|
||||||
|
Unit* saurfang = bot->FindNearestCreature(NPC_HIGH_OVERLORD_SAURFANG, 100.0f);
|
||||||
if (!boss->IsHostileTo(bot))
|
if (saurfang && saurfang->IsAlive() && saurfang->IsHostileTo(bot))
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
Unit* muradin = bot->FindNearestCreature(NPC_MURADIN_BRONZEBEARD, 100.0f);
|
||||||
|
if (muradin && muradin->IsAlive() && muradin->IsHostileTo(bot))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IccGunshipTeleportHordeTrigger::IsActive()
|
bool IccGunshipRocketPackSetupTrigger::IsActive()
|
||||||
{
|
{
|
||||||
Unit* boss = bot->FindNearestCreature(NPC_MURADIN_BRONZEBEARD, 100.0f);
|
// Fires any time a bot is standing on a friendly gunship deck, regardless of
|
||||||
if (!boss || !boss->IsInWorld() || boss->IsDuringRemoveFromWorld())
|
// combat state. Lets bots walk to Zafod and equip the rocket pack before the
|
||||||
return false;
|
// encounter starts (and keep it ready if they acquire it mid-fight).
|
||||||
|
Unit* cannonA = bot->FindNearestCreature(NPC_CANNONA, 100.0f);
|
||||||
if (!boss->IsAlive())
|
if (cannonA && cannonA->IsFriendlyTo(bot))
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!boss->IsHostileTo(bot))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
Unit* cannonH = bot->FindNearestCreature(NPC_CANNONH, 100.0f);
|
||||||
|
if (cannonH && cannonH->IsFriendlyTo(bot))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
//DBS
|
//DBS
|
||||||
@ -155,28 +161,12 @@ bool IccDbsMainTankRuneOfBloodTrigger::IsActive()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
//DOGS
|
bool IccDogsTrigger::IsActive()
|
||||||
bool IccStinkyPreciousMainTankMortalWoundTrigger::IsActive()
|
|
||||||
{
|
{
|
||||||
bool bossPresent = false;
|
|
||||||
if (AI_VALUE2(Unit*, "find target", "stinky") || AI_VALUE2(Unit*, "find target", "precious"))
|
if (AI_VALUE2(Unit*, "find target", "stinky") || AI_VALUE2(Unit*, "find target", "precious"))
|
||||||
bossPresent = true;
|
|
||||||
|
|
||||||
if (!bossPresent)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!botAI->IsAssistTankOfIndex(bot, 0))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
Unit* mt = AI_VALUE(Unit*, "main tank");
|
|
||||||
if (!mt)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
Aura* aura = botAI->GetAura("mortal wound", mt, false, true);
|
|
||||||
if (!aura || aura->GetStackAmount() < 8)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
//FESTERGUT
|
//FESTERGUT
|
||||||
@ -192,30 +182,6 @@ bool IccFestergutGroupPositionTrigger::IsActive()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IccFestergutMainTankGastricBloatTrigger::IsActive()
|
|
||||||
{
|
|
||||||
Unit* boss = AI_VALUE2(Unit*, "find target", "festergut");
|
|
||||||
if (!boss)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!botAI->IsAssistTankOfIndex(bot, 0))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Unit* mt = AI_VALUE(Unit*, "main tank");
|
|
||||||
if (!mt)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Aura* aura = botAI->GetAura("Gastric Bloat", mt, false, true);
|
|
||||||
if (!aura || aura->GetStackAmount() < 6)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IccFestergutSporeTrigger::IsActive()
|
bool IccFestergutSporeTrigger::IsActive()
|
||||||
{
|
{
|
||||||
Unit* boss = AI_VALUE2(Unit*, "find target", "festergut");
|
Unit* boss = AI_VALUE2(Unit*, "find target", "festergut");
|
||||||
@ -240,6 +206,71 @@ bool IccFestergutSporeTrigger::IsActive()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IccFestergutAvoidMalleableGooTrigger::IsActive()
|
||||||
|
{
|
||||||
|
Unit* boss = AI_VALUE2(Unit*, "find target", "festergut");
|
||||||
|
if (!boss)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Tanks hold the boss at the fixed tank spot; goo can land on tanks but
|
||||||
|
// moving would lose threat and let goo land on melee stack anyway.
|
||||||
|
if (botAI->IsTank(bot))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// During spore phase, position switching handles goo avoidance — free-dodge
|
||||||
|
// would pull bots out of their assigned spore spots.
|
||||||
|
Group* group = bot->GetGroup();
|
||||||
|
if (group)
|
||||||
|
{
|
||||||
|
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
|
||||||
|
{
|
||||||
|
Player* member = itr->GetSource();
|
||||||
|
if (member && member->HasAura(SPELL_GAS_SPORE))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr uint32 impactLifetimeMs = 8000;
|
||||||
|
constexpr float gooDangerRadius = 12.0f;
|
||||||
|
|
||||||
|
uint32 now = getMSTime();
|
||||||
|
float botX = bot->GetPositionX();
|
||||||
|
float botY = bot->GetPositionY();
|
||||||
|
ObjectGuid botGuid = bot->GetGUID();
|
||||||
|
|
||||||
|
auto impactIt = IcecrownHelpers::malleableGooImpacts.find(bot->GetMap()->GetInstanceId());
|
||||||
|
if (impactIt != IcecrownHelpers::malleableGooImpacts.end())
|
||||||
|
{
|
||||||
|
for (auto const& impact : impactIt->second)
|
||||||
|
{
|
||||||
|
if (getMSTimeDiff(impact.castTime, now) > impactLifetimeMs)
|
||||||
|
continue;
|
||||||
|
float dx = botX - impact.position.GetPositionX();
|
||||||
|
float dy = botY - impact.position.GetPositionY();
|
||||||
|
if (dx * dx + dy * dy < gooDangerRadius * gooDangerRadius)
|
||||||
|
{
|
||||||
|
// Lock bot into wait mode until this impact expires - prevents
|
||||||
|
// group-position from yanking it back into the danger zone.
|
||||||
|
uint32 waitUntil = impact.castTime + impactLifetimeMs;
|
||||||
|
auto& slot = IcecrownHelpers::festergutGooWaitUntil[botGuid];
|
||||||
|
if (waitUntil > slot)
|
||||||
|
slot = waitUntil;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto it = IcecrownHelpers::festergutGooWaitUntil.find(botGuid);
|
||||||
|
if (it != IcecrownHelpers::festergutGooWaitUntil.end())
|
||||||
|
{
|
||||||
|
if (now < it->second)
|
||||||
|
return true;
|
||||||
|
IcecrownHelpers::festergutGooWaitUntil.erase(it);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
//ROTFACE
|
//ROTFACE
|
||||||
bool IccRotfaceTankPositionTrigger::IsActive()
|
bool IccRotfaceTankPositionTrigger::IsActive()
|
||||||
{
|
{
|
||||||
@ -264,11 +295,58 @@ bool IccRotfaceGroupPositionTrigger::IsActive()
|
|||||||
|
|
||||||
bool IccRotfaceMoveAwayFromExplosionTrigger::IsActive()
|
bool IccRotfaceMoveAwayFromExplosionTrigger::IsActive()
|
||||||
{
|
{
|
||||||
Unit* boss = AI_VALUE2(Unit*, "find target", "big ooze");
|
Creature* boss = bot->FindNearestCreature(NPC_BIG_OOZE, 100.0f);
|
||||||
|
bool castingNow = boss && boss->IsAlive() &&
|
||||||
|
boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(SPELL_UNSTABLE_OOZE_EXPLOSION);
|
||||||
|
|
||||||
|
if (castingNow)
|
||||||
|
{
|
||||||
|
_wasCasting = true;
|
||||||
|
_castEndTime = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cast just ended — record the time
|
||||||
|
if (_wasCasting)
|
||||||
|
{
|
||||||
|
_wasCasting = false;
|
||||||
|
if (_castEndTime == 0)
|
||||||
|
_castEndTime = time(nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stay active for 6 seconds after cast ended (2s wait + return movement)
|
||||||
|
if (_castEndTime > 0 && time(nullptr) - _castEndTime < 6)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
_castEndTime = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IccRotfaceAvoidVileGasTrigger::IsActive()
|
||||||
|
{
|
||||||
|
Unit* boss = AI_VALUE2(Unit*, "find target", "rotface");
|
||||||
if (!boss)
|
if (!boss)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(SPELL_UNSTABLE_OOZE_EXPLOSION);
|
uint32 const now = getMSTime();
|
||||||
|
|
||||||
|
auto vgIt = IcecrownHelpers::rotfaceVileGas.find(bot->GetMap()->GetInstanceId());
|
||||||
|
bool const isVictim =
|
||||||
|
vgIt != IcecrownHelpers::rotfaceVileGas.end() &&
|
||||||
|
vgIt->second.victimGuid == bot->GetGUID() &&
|
||||||
|
getMSTimeDiff(vgIt->second.castTime, now) < 8000;
|
||||||
|
if (isVictim)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (botAI->HasAura("Vile Gas", bot))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
auto const& waitMap = IcecrownHelpers::rotfaceVileGasWaitUntil;
|
||||||
|
auto it = waitMap.find(bot->GetGUID());
|
||||||
|
if (it != waitMap.end() && now < it->second)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
//PP
|
//PP
|
||||||
@ -280,25 +358,6 @@ bool IccPutricideGrowingOozePuddleTrigger::IsActive()
|
|||||||
if (!boss)
|
if (!boss)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
Difficulty diff = bot->GetRaidDifficulty();
|
|
||||||
|
|
||||||
if (sPlayerbotAIConfig.EnableICCBuffs && diff && (diff == RAID_DIFFICULTY_10MAN_HEROIC || diff == RAID_DIFFICULTY_25MAN_HEROIC))
|
|
||||||
{
|
|
||||||
//-------CHEAT-------
|
|
||||||
if (!bot->HasAura(SPELL_EXPERIENCED))
|
|
||||||
bot->AddAura(SPELL_EXPERIENCED, bot);
|
|
||||||
|
|
||||||
if (!bot->HasAura(SPELL_AGEIS_OF_DALARAN))
|
|
||||||
bot->AddAura(SPELL_AGEIS_OF_DALARAN, bot);
|
|
||||||
|
|
||||||
if (!bot->HasAura(SPELL_NO_THREAT) && botAI->HasAggro(boss) && !botAI->IsTank(bot))
|
|
||||||
bot->AddAura(SPELL_NO_THREAT, bot);
|
|
||||||
|
|
||||||
if (botAI->IsMainTank(bot) && !bot->HasAura(SPELL_SPITEFULL_FURY) && boss->GetVictim() != bot)
|
|
||||||
bot->AddAura(SPELL_SPITEFULL_FURY, bot);
|
|
||||||
//-------CHEAT-------
|
|
||||||
}
|
|
||||||
|
|
||||||
const GuidVector& npcs = AI_VALUE(GuidVector, "nearest hostile npcs");
|
const GuidVector& npcs = AI_VALUE(GuidVector, "nearest hostile npcs");
|
||||||
for (auto const& npc : npcs)
|
for (auto const& npc : npcs)
|
||||||
{
|
{
|
||||||
@ -344,30 +403,9 @@ bool IccPutricideGasCloudTrigger::IsActive()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IccPutricideMainTankMutatedPlagueTrigger::IsActive()
|
bool IccPutricideMutatedPlagueTrigger::IsActive()
|
||||||
{
|
{
|
||||||
bool bossPresent = false;
|
return AI_VALUE2(Unit*, "find target", "professor putricide") != nullptr;
|
||||||
if (AI_VALUE2(Unit*, "find target", "professor putricide"))
|
|
||||||
bossPresent = true;
|
|
||||||
|
|
||||||
if (!bossPresent)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!botAI->IsAssistTankOfIndex(bot, 0))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Unit* mt = AI_VALUE(Unit*, "main tank");
|
|
||||||
if (!mt)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Aura* aura = botAI->GetAura("Mutated Plague", mt, false, true);
|
|
||||||
if (!aura || aura->GetStackAmount() < 4)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IccPutricideMalleableGooTrigger::IsActive()
|
bool IccPutricideMalleableGooTrigger::IsActive()
|
||||||
@ -376,20 +414,89 @@ bool IccPutricideMalleableGooTrigger::IsActive()
|
|||||||
if (!boss)
|
if (!boss)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (botAI->IsTank(bot))
|
Difficulty const diff = bot->GetRaidDifficulty();
|
||||||
return true;
|
|
||||||
|
|
||||||
Unit* boss1 = AI_VALUE2(Unit*, "find target", "volatile ooze");
|
// Heroic cheat buffs — apply to all group members (bots + real players)
|
||||||
if (boss1)
|
if (boss && sPlayerbotAIConfig.EnableICCBuffs &&
|
||||||
return false;
|
(diff == RAID_DIFFICULTY_10MAN_HEROIC || diff == RAID_DIFFICULTY_25MAN_HEROIC))
|
||||||
|
{
|
||||||
|
if (Group* buffGroup = bot->GetGroup())
|
||||||
|
{
|
||||||
|
for (GroupReference* itr = buffGroup->GetFirstMember(); itr; itr = itr->next())
|
||||||
|
{
|
||||||
|
Player* member = itr->GetSource();
|
||||||
|
if (!member || !member->IsAlive() || !member->IsInWorld())
|
||||||
|
continue;
|
||||||
|
|
||||||
Unit* boss2 = AI_VALUE2(Unit*, "find target", "gas cloud");
|
if (!member->HasAura(SPELL_EXPERIENCED))
|
||||||
if (boss2)
|
member->AddAura(SPELL_EXPERIENCED, member);
|
||||||
return false;
|
|
||||||
|
if (!member->HasAura(SPELL_AGEIS_OF_DALARAN))
|
||||||
|
member->AddAura(SPELL_AGEIS_OF_DALARAN, member);
|
||||||
|
|
||||||
|
if (!PlayerbotAI::IsTank(member) && !member->HasAura(SPELL_NO_THREAT))
|
||||||
|
member->AddAura(SPELL_NO_THREAT, member);
|
||||||
|
|
||||||
|
if (PlayerbotAI::IsTank(member) && !member->HasAura(SPELL_SPITEFULL_FURY) &&
|
||||||
|
boss->GetVictim() != member)
|
||||||
|
member->AddAura(SPELL_SPITEFULL_FURY, member);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IccPutricideAbominationTrigger::IsActive()
|
||||||
|
{
|
||||||
|
Unit* boss = AI_VALUE2(Unit*, "find target", "professor putricide");
|
||||||
|
if (!boss)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!botAI->IsAssistTank(bot))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Already piloting - keep action firing until vehicle drops.
|
||||||
|
if (Unit* veh = bot->GetVehicleBase())
|
||||||
|
{
|
||||||
|
uint32 e = veh->GetEntry();
|
||||||
|
if (e == NPC_MUTATED_ABOMINATION_10 || e == NPC_MUTATED_ABOMINATION_25)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phase 3: boss takes toy back. No transformation.
|
||||||
|
if (boss->HealthBelowPct(35))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Someone else already piloting - do not drink.
|
||||||
|
if (Group* group = bot->GetGroup())
|
||||||
|
{
|
||||||
|
for (GroupReference* itr = group->GetFirstMember(); itr; itr = itr->next())
|
||||||
|
{
|
||||||
|
Player* m = itr->GetSource();
|
||||||
|
if (!m || m == bot || !m->IsAlive())
|
||||||
|
continue;
|
||||||
|
if (Unit* vb = m->GetVehicleBase())
|
||||||
|
{
|
||||||
|
uint32 e = vb->GetEntry();
|
||||||
|
if (e == NPC_MUTATED_ABOMINATION_10 || e == NPC_MUTATED_ABOMINATION_25)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Require at least one Growing Ooze Puddle nearby.
|
||||||
|
GuidVector const npcs = AI_VALUE(GuidVector, "nearest hostile npcs");
|
||||||
|
for (auto const& g : npcs)
|
||||||
|
{
|
||||||
|
if (Unit* u = botAI->GetUnit(g))
|
||||||
|
if (u->GetEntry() == NPC_GROWING_OOZE_PUDDLE)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
//BPC
|
//BPC
|
||||||
bool IccBpcKelesethTankTrigger::IsActive()
|
bool IccBpcKelesethTankTrigger::IsActive()
|
||||||
{
|
{
|
||||||
@ -463,10 +570,10 @@ bool IccBpcKineticBombTrigger::IsActive()
|
|||||||
if (!botAI->IsRanged(bot) || botAI->IsHeal(bot))
|
if (!botAI->IsRanged(bot) || botAI->IsHeal(bot))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Early exit condition - if Shadow Prison has too many stacks
|
// Allow up to 18 stacks for bomb-assigned bots (multiplier handles assignment)
|
||||||
if (Aura* aura = botAI->GetAura("Shadow Prison", bot, false, true))
|
if (Aura* aura = botAI->GetAura("Shadow Prison", bot, false, true))
|
||||||
{
|
{
|
||||||
if (aura->GetStackAmount() > 12)
|
if (aura->GetStackAmount() > 18)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -485,7 +592,7 @@ bool IccBpcKineticBombTrigger::IsActive()
|
|||||||
if (unit->GetEntry() == entry)
|
if (unit->GetEntry() == entry)
|
||||||
{
|
{
|
||||||
// Check if bomb is within valid Z-axis range
|
// Check if bomb is within valid Z-axis range
|
||||||
if (unit->GetPositionZ() - bot->GetPositionZ() < 25.0f)
|
if (unit->GetPositionZ() - bot->GetPositionZ() < 35.0f)
|
||||||
{
|
{
|
||||||
bombFound = true;
|
bombFound = true;
|
||||||
break;
|
break;
|
||||||
@ -523,6 +630,13 @@ bool IccBqlGroupPositionTrigger::IsActive()
|
|||||||
if (!boss)
|
if (!boss)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
Unit* valanar = AI_VALUE2(Unit*, "find target", "prince valanar");
|
||||||
|
Unit* taldaram = AI_VALUE2(Unit*, "find target", "prince taldaram");
|
||||||
|
Unit* keleseth = AI_VALUE2(Unit*, "find target", "prince keleseth");
|
||||||
|
|
||||||
|
if (valanar || taldaram || keleseth)
|
||||||
|
return false;
|
||||||
|
|
||||||
if (bot->HasAura(SPELL_EXPERIENCED))
|
if (bot->HasAura(SPELL_EXPERIENCED))
|
||||||
bot->RemoveAura(SPELL_EXPERIENCED);
|
bot->RemoveAura(SPELL_EXPERIENCED);
|
||||||
|
|
||||||
@ -535,6 +649,13 @@ bool IccBqlPactOfDarkfallenTrigger::IsActive()
|
|||||||
if (!boss)
|
if (!boss)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
Unit* valanar = AI_VALUE2(Unit*, "find target", "prince valanar");
|
||||||
|
Unit* taldaram = AI_VALUE2(Unit*, "find target", "prince taldaram");
|
||||||
|
Unit* keleseth = AI_VALUE2(Unit*, "find target", "prince keleseth");
|
||||||
|
|
||||||
|
if (valanar || taldaram || keleseth)
|
||||||
|
return false;
|
||||||
|
|
||||||
Aura* aura = botAI->GetAura("Pact of the Darkfallen", bot);
|
Aura* aura = botAI->GetAura("Pact of the Darkfallen", bot);
|
||||||
if (!aura)
|
if (!aura)
|
||||||
return false;
|
return false;
|
||||||
@ -548,6 +669,13 @@ bool IccBqlVampiricBiteTrigger::IsActive()
|
|||||||
if (!boss)
|
if (!boss)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
Unit* valanar = AI_VALUE2(Unit*, "find target", "prince valanar");
|
||||||
|
Unit* taldaram = AI_VALUE2(Unit*, "find target", "prince taldaram");
|
||||||
|
Unit* keleseth = AI_VALUE2(Unit*, "find target", "prince keleseth");
|
||||||
|
|
||||||
|
if (valanar || taldaram || keleseth)
|
||||||
|
return false;
|
||||||
|
|
||||||
Aura* aura = botAI->GetAura("Frenzied Bloodthirst", bot);
|
Aura* aura = botAI->GetAura("Frenzied Bloodthirst", bot);
|
||||||
if (!aura)
|
if (!aura)
|
||||||
return false;
|
return false;
|
||||||
@ -590,6 +718,26 @@ bool IccValithriaGroupTrigger::IsActive()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IccValithriaZombieKiteTrigger::IsActive()
|
||||||
|
{
|
||||||
|
Unit* boss = bot->FindNearestCreature(NPC_VALITHRIA_DREAMWALKER, 100.0f);
|
||||||
|
if (!boss)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (botAI->IsTank(bot))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
std::list<Creature*> zombies;
|
||||||
|
bot->GetCreatureListWithEntryInGrid(zombies, NPC_BLISTERING_ZOMBIE, 100.0f);
|
||||||
|
for (Creature* z : zombies)
|
||||||
|
{
|
||||||
|
if (z && z->IsAlive() && z->GetVictim() == bot)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool IccValithriaPortalTrigger::IsActive()
|
bool IccValithriaPortalTrigger::IsActive()
|
||||||
{
|
{
|
||||||
Unit* boss = bot->FindNearestCreature(NPC_VALITHRIA_DREAMWALKER, 100.0f);
|
Unit* boss = bot->FindNearestCreature(NPC_VALITHRIA_DREAMWALKER, 100.0f);
|
||||||
@ -809,15 +957,10 @@ bool IccValithriaHealTrigger::IsActive()
|
|||||||
|
|
||||||
bool IccValithriaDreamCloudTrigger::IsActive()
|
bool IccValithriaDreamCloudTrigger::IsActive()
|
||||||
{
|
{
|
||||||
// Only active if we're in dream state
|
|
||||||
if (!bot->HasAura(SPELL_DREAM_STATE) || bot->HealthBelowPct(50))
|
if (!bot->HasAura(SPELL_DREAM_STATE) || bot->HealthBelowPct(50))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Find nearest cloud of either type
|
return true;
|
||||||
Creature* dreamCloud = bot->FindNearestCreature(NPC_DREAM_CLOUD, 100.0f);
|
|
||||||
Creature* nightmareCloud = bot->FindNearestCreature(NPC_NIGHTMARE_CLOUD, 100.0f);
|
|
||||||
|
|
||||||
return (dreamCloud || nightmareCloud);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//SINDRAGOSA
|
//SINDRAGOSA
|
||||||
@ -832,21 +975,47 @@ bool IccSindragosaGroupPositionTrigger::IsActive()
|
|||||||
if (sPlayerbotAIConfig.EnableICCBuffs && diff && (diff == RAID_DIFFICULTY_10MAN_HEROIC || diff == RAID_DIFFICULTY_25MAN_HEROIC))
|
if (sPlayerbotAIConfig.EnableICCBuffs && diff && (diff == RAID_DIFFICULTY_10MAN_HEROIC || diff == RAID_DIFFICULTY_25MAN_HEROIC))
|
||||||
{
|
{
|
||||||
//-------CHEAT-------
|
//-------CHEAT-------
|
||||||
if (!bot->HasAura(SPELL_EXPERIENCED))
|
// Apply to every alive group member so real players benefit too,
|
||||||
bot->AddAura(SPELL_EXPERIENCED, bot);
|
if (Group* group = bot->GetGroup())
|
||||||
|
{
|
||||||
|
for (GroupReference* itr = group->GetFirstMember(); itr; itr = itr->next())
|
||||||
|
{
|
||||||
|
Player* member = itr->GetSource();
|
||||||
|
if (!member || !member->IsAlive() || !member->IsInWorld())
|
||||||
|
continue;
|
||||||
|
|
||||||
if (!bot->HasAura(SPELL_AGEIS_OF_DALARAN))
|
if (!member->HasAura(SPELL_EXPERIENCED))
|
||||||
bot->AddAura(SPELL_AGEIS_OF_DALARAN, bot);
|
member->AddAura(SPELL_EXPERIENCED, member);
|
||||||
|
|
||||||
if (!bot->HasAura(SPELL_NO_THREAT) && botAI->HasAggro(boss) && !botAI->IsTank(bot))
|
if (!member->HasAura(SPELL_AGEIS_OF_DALARAN))
|
||||||
bot->AddAura(SPELL_NO_THREAT, bot);
|
member->AddAura(SPELL_AGEIS_OF_DALARAN, member);
|
||||||
|
|
||||||
if (botAI->IsMainTank(bot) && !bot->HasAura(SPELL_SPITEFULL_FURY) && boss->GetVictim() != bot)
|
if (!botAI->IsTank(member) && !member->HasAura(SPELL_NO_THREAT))
|
||||||
bot->AddAura(SPELL_SPITEFULL_FURY, bot);
|
member->AddAura(SPELL_NO_THREAT, member);
|
||||||
|
|
||||||
|
if (botAI->IsMainTank(member) && boss->GetVictim() != member &&
|
||||||
|
!member->HasAura(SPELL_SPITEFULL_FURY))
|
||||||
|
member->AddAura(SPELL_SPITEFULL_FURY, member);
|
||||||
|
}
|
||||||
|
}
|
||||||
//-------CHEAT-------
|
//-------CHEAT-------
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!boss || bot->HasAura(SPELL_FROST_BEACON) /*|| bot->HasAura(69762)*/ || boss->GetExactDist2d(ICC_SINDRAGOSA_FLYING_POSITION.GetPositionX(), ICC_SINDRAGOSA_FLYING_POSITION.GetPositionY()) < 50.0f)
|
// Air phase: give all tanks nitro boosts so they can quickly reposition to tombs
|
||||||
|
if (boss->IsInCombat() && botAI->IsTank(bot) &&
|
||||||
|
boss->GetExactDist2d(ICC_SINDRAGOSA_FLYING_POSITION.GetPositionX(), ICC_SINDRAGOSA_FLYING_POSITION.GetPositionY()) < 50.0f)
|
||||||
|
{
|
||||||
|
if (!bot->HasAura(SPELL_NITRO_BOOSTS))
|
||||||
|
bot->AddAura(SPELL_NITRO_BOOSTS, bot);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Last phase: tanks must keep tanking, never run to a tomb spot. Strip
|
||||||
|
// Frost Beacon so the tomb-positioning logic doesn't apply to them.
|
||||||
|
if (botAI->IsTank(bot) && bot->HasAura(SPELL_FROST_BEACON) && boss->HealthBelowPct(35) &&
|
||||||
|
boss->GetExactDist2d(ICC_SINDRAGOSA_FLYING_POSITION.GetPositionX(), ICC_SINDRAGOSA_FLYING_POSITION.GetPositionY()) >= 30.0f)
|
||||||
|
bot->RemoveAura(SPELL_FROST_BEACON);
|
||||||
|
|
||||||
|
if (!boss || bot->HasAura(SPELL_FROST_BEACON) || boss->GetExactDist2d(ICC_SINDRAGOSA_FLYING_POSITION.GetPositionX(), ICC_SINDRAGOSA_FLYING_POSITION.GetPositionY()) < 50.0f)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -854,7 +1023,7 @@ bool IccSindragosaGroupPositionTrigger::IsActive()
|
|||||||
|
|
||||||
bool IccSindragosaFrostBeaconTrigger::IsActive()
|
bool IccSindragosaFrostBeaconTrigger::IsActive()
|
||||||
{
|
{
|
||||||
Unit* boss = AI_VALUE2(Unit*, "find target", "sindragosa");
|
Unit* boss = bot->FindNearestCreature(NPC_SINDRAGOSA, 200.0f);
|
||||||
if (!boss)
|
if (!boss)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@ -879,6 +1048,32 @@ bool IccSindragosaFrostBeaconTrigger::IsActive()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IccSindragosaHotTrigger::IsActive()
|
||||||
|
{
|
||||||
|
if (!botAI->IsHeal(bot))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (bot->HasAura(SPELL_FROST_BEACON))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Unit* boss = bot->FindNearestCreature(NPC_SINDRAGOSA, 200.0f);
|
||||||
|
if (!boss)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Group* group = bot->GetGroup();
|
||||||
|
if (!group)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
|
||||||
|
{
|
||||||
|
Player* member = itr->GetSource();
|
||||||
|
if (member && member->IsAlive() && member->HasAura(SPELL_FROST_BEACON))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool IccSindragosaBlisteringColdTrigger::IsActive()
|
bool IccSindragosaBlisteringColdTrigger::IsActive()
|
||||||
{
|
{
|
||||||
Unit* boss = AI_VALUE2(Unit*, "find target", "sindragosa");
|
Unit* boss = AI_VALUE2(Unit*, "find target", "sindragosa");
|
||||||
@ -974,6 +1169,15 @@ bool IccSindragosaMysticBuffetTrigger::IsActive()
|
|||||||
if (bot->HasAura(SPELL_FROST_BEACON))
|
if (bot->HasAura(SPELL_FROST_BEACON))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
// Blistering Cold takes priority over tomb-hiding in the last phase:
|
||||||
|
// skip hiding so the bot can run to the safe spot instead.
|
||||||
|
if (boss->HasUnitState(UNIT_STATE_CASTING) &&
|
||||||
|
(boss->FindCurrentSpellBySpellId(SPELL_BLISTERING_COLD1) ||
|
||||||
|
boss->FindCurrentSpellBySpellId(SPELL_BLISTERING_COLD2) ||
|
||||||
|
boss->FindCurrentSpellBySpellId(SPELL_BLISTERING_COLD3) ||
|
||||||
|
boss->FindCurrentSpellBySpellId(SPELL_BLISTERING_COLD4)))
|
||||||
|
return false;
|
||||||
|
|
||||||
if (aura->GetStackAmount() >= 1)
|
if (aura->GetStackAmount() >= 1)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
@ -1023,6 +1227,7 @@ bool IccSindragosaMainTankMysticBuffetTrigger::IsActive()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO never triggers since mystic buffet is bypassed in action
|
||||||
bool IccSindragosaTankSwapPositionTrigger::IsActive()
|
bool IccSindragosaTankSwapPositionTrigger::IsActive()
|
||||||
{
|
{
|
||||||
Unit* boss = AI_VALUE2(Unit*, "find target", "sindragosa");
|
Unit* boss = AI_VALUE2(Unit*, "find target", "sindragosa");
|
||||||
@ -1072,9 +1277,12 @@ bool IccSindragosaFrostBombTrigger::IsActive()
|
|||||||
if (!boss)
|
if (!boss)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!bot->IsAlive() || bot->HasAura(SPELL_ICE_TOMB)) // Skip if dead or in Ice Tomb
|
if (!bot->IsAlive()) // Skip if dead
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
// Tombed bots intentionally pass through: the action pins their group to
|
||||||
|
// the current tomb's zone so when freed they don't migrate to the wrong
|
||||||
|
// zone. The action returns false for tombed bots without moving them.
|
||||||
if (boss->GetExactDist2d(ICC_SINDRAGOSA_FLYING_POSITION.GetPositionX(), ICC_SINDRAGOSA_FLYING_POSITION.GetPositionY()) < 50.0f && !boss->HealthBelowPct(25) && !boss->HealthAbovePct(99))
|
if (boss->GetExactDist2d(ICC_SINDRAGOSA_FLYING_POSITION.GetPositionX(), ICC_SINDRAGOSA_FLYING_POSITION.GetPositionY()) < 50.0f && !boss->HealthBelowPct(25) && !boss->HealthAbovePct(99))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
@ -1085,6 +1293,10 @@ bool IccSindragosaFrostBombTrigger::IsActive()
|
|||||||
|
|
||||||
bool IccLichKingShadowTrapTrigger::IsActive()
|
bool IccLichKingShadowTrapTrigger::IsActive()
|
||||||
{
|
{
|
||||||
|
Unit* vdw = bot->FindNearestCreature(NPC_VALITHRIA_DREAMWALKER, 100.0f);
|
||||||
|
if (vdw)
|
||||||
|
return false;
|
||||||
|
|
||||||
Unit* boss = AI_VALUE2(Unit*, "find target", "the lich king");
|
Unit* boss = AI_VALUE2(Unit*, "find target", "the lich king");
|
||||||
if (!boss)
|
if (!boss)
|
||||||
return false;
|
return false;
|
||||||
@ -1118,71 +1330,76 @@ bool IccLichKingShadowTrapTrigger::IsActive()
|
|||||||
|
|
||||||
bool IccLichKingNecroticPlagueTrigger::IsActive()
|
bool IccLichKingNecroticPlagueTrigger::IsActive()
|
||||||
{
|
{
|
||||||
bool hasPlague = botAI->HasAura("Necrotic Plague", bot);
|
Unit* vdw = bot->FindNearestCreature(NPC_VALITHRIA_DREAMWALKER, 100.0f);
|
||||||
|
if (vdw)
|
||||||
|
return false;
|
||||||
|
|
||||||
return hasPlague;
|
if (!AI_VALUE2(Unit*, "find target", "the lich king"))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return botAI->HasAura("Necrotic Plague", bot);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IccLichKingWinterTrigger::IsActive()
|
bool IccLichKingWinterTrigger::IsActive()
|
||||||
{
|
{
|
||||||
|
Unit* vdw = bot->FindNearestCreature(NPC_VALITHRIA_DREAMWALKER, 100.0f);
|
||||||
|
if (vdw)
|
||||||
|
return false;
|
||||||
|
|
||||||
Unit* boss = AI_VALUE2(Unit*, "find target", "the lich king");
|
Unit* boss = AI_VALUE2(Unit*, "find target", "the lich king");
|
||||||
if (!boss)
|
if (!boss)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Check for either Remorseless Winter
|
auto const hasWinterAura = [&]() -> bool
|
||||||
bool hasWinterAura = false;
|
{
|
||||||
if (boss && (boss->HasAura(SPELL_REMORSELESS_WINTER1) || boss->HasAura(SPELL_REMORSELESS_WINTER2) ||
|
return boss->HasAura(SPELL_REMORSELESS_WINTER1) || boss->HasAura(SPELL_REMORSELESS_WINTER2) ||
|
||||||
boss->HasAura(SPELL_REMORSELESS_WINTER3) || boss->HasAura(SPELL_REMORSELESS_WINTER4)))
|
boss->HasAura(SPELL_REMORSELESS_WINTER3) || boss->HasAura(SPELL_REMORSELESS_WINTER4) ||
|
||||||
hasWinterAura = true;
|
boss->HasAura(SPELL_REMORSELESS_WINTER5) || boss->HasAura(SPELL_REMORSELESS_WINTER6) ||
|
||||||
|
boss->HasAura(SPELL_REMORSELESS_WINTER7) || boss->HasAura(SPELL_REMORSELESS_WINTER8);
|
||||||
|
};
|
||||||
|
|
||||||
bool hasWinter2Aura = false;
|
auto const isCastingWinter = [&]() -> bool
|
||||||
if (boss && (boss->HasAura(SPELL_REMORSELESS_WINTER5) || boss->HasAura(SPELL_REMORSELESS_WINTER6) ||
|
{
|
||||||
boss->HasAura(SPELL_REMORSELESS_WINTER7) || boss->HasAura(SPELL_REMORSELESS_WINTER8)))
|
if (!boss->HasUnitState(UNIT_STATE_CASTING))
|
||||||
hasWinter2Aura = true;
|
return false;
|
||||||
|
|
||||||
bool isCasting = false;
|
return boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER1) ||
|
||||||
if (boss && boss->HasUnitState(UNIT_STATE_CASTING))
|
|
||||||
isCasting = true;
|
|
||||||
|
|
||||||
bool isWinter = false;
|
|
||||||
if (boss && boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER1) ||
|
|
||||||
boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER2) ||
|
boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER2) ||
|
||||||
boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER5) ||
|
|
||||||
boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER6) ||
|
|
||||||
boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER3) ||
|
boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER3) ||
|
||||||
boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER4) ||
|
boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER4) ||
|
||||||
|
boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER5) ||
|
||||||
|
boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER6) ||
|
||||||
boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER7) ||
|
boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER7) ||
|
||||||
boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER8))
|
boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER8);
|
||||||
isWinter = true;
|
};
|
||||||
|
|
||||||
if (hasWinterAura || hasWinter2Aura)
|
return hasWinterAura() || isCastingWinter();
|
||||||
return true;
|
|
||||||
|
|
||||||
if (isCasting && isWinter)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IccLichKingAddsTrigger::IsActive()
|
bool IccLichKingAddsTrigger::IsActive()
|
||||||
{
|
{
|
||||||
Unit* boss = AI_VALUE2(Unit*, "find target", "the lich king");
|
Unit* vdw = bot->FindNearestCreature(NPC_VALITHRIA_DREAMWALKER, 100.0f);
|
||||||
|
if (vdw)
|
||||||
bool hasPlague = botAI->HasAura("Necrotic Plague", bot);
|
|
||||||
if (hasPlague)
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
Unit* terenasMenethilHC = bot->FindNearestCreature(NPC_TERENAS_MENETHIL_HC, 55.0f);
|
if (bot->HasAura(SPELL_HARVEST_SOUL_VALKYR))
|
||||||
Unit* terenasMenethil = bot->FindNearestCreature(NPC_TERENAS_MENETHIL, 55.0f);
|
return false;
|
||||||
|
|
||||||
if (terenasMenethilHC)
|
if (botAI->HasAura("Necrotic Plague", bot))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (bot->FindNearestCreature(NPC_TERENAS_MENETHIL_HC, 55.0f) ||
|
||||||
|
bot->FindNearestCreature(NPC_TERENAS_MENETHIL, 55.0f))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (terenasMenethil)
|
Unit* lk = AI_VALUE2(Unit*, "find target", "the lich king");
|
||||||
return true;
|
if (!lk)
|
||||||
|
|
||||||
if (!boss)
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IccLichKingSpiritBombTrigger::IsActive()
|
||||||
|
{
|
||||||
|
return IccLichKingSpiritBombAction::IsBombThreatActive(botAI, bot);
|
||||||
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
#ifndef _PLAYERBOT_RAIDICCTRIGGERS_H
|
#ifndef _PLAYERBOT_ICCT_H
|
||||||
#define _PLAYERBOT_RAIDICCTRIGGERS_H
|
#define _PLAYERBOT_ICCT_H
|
||||||
|
|
||||||
#include "PlayerbotAI.h"
|
#include "PlayerbotAI.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
@ -9,11 +9,23 @@ enum CreatureIdsICC
|
|||||||
{
|
{
|
||||||
|
|
||||||
// Lord Marrowgar
|
// Lord Marrowgar
|
||||||
NPC_SPIKE1 = 36619,
|
NPC_SPIKE1 = 36619, // 10N base
|
||||||
NPC_SPIKE2 = 38711,
|
NPC_SPIKE1_10H = 38233, // 10H
|
||||||
NPC_SPIKE3 = 38712,
|
NPC_SPIKE1_25N = 38459, // 25N
|
||||||
|
NPC_SPIKE1_25H = 38460, // 25H
|
||||||
|
NPC_SPIKE2 = 38711, // 25N base
|
||||||
|
NPC_SPIKE2_10H = 38970,
|
||||||
|
NPC_SPIKE2_25N = 38971,
|
||||||
|
NPC_SPIKE2_25H = 38972,
|
||||||
|
NPC_SPIKE3 = 38712, // 25N base
|
||||||
|
NPC_SPIKE3_10H = 38973,
|
||||||
|
NPC_SPIKE3_25N = 38974,
|
||||||
|
NPC_SPIKE3_25H = 38975,
|
||||||
|
NPC_COLDFLAME = 36672,
|
||||||
|
|
||||||
// Lady Deathwhisper
|
// Lady Deathwhisper
|
||||||
|
NPC_DARNAVAN_10 = 38472,
|
||||||
|
NPC_DARNAVAN_25 = 38485,
|
||||||
NPC_SHADE = 38222,
|
NPC_SHADE = 38222,
|
||||||
|
|
||||||
// Gunship Battle
|
// Gunship Battle
|
||||||
@ -29,6 +41,8 @@ enum CreatureIdsICC
|
|||||||
NPC_CANNONH = 36839,
|
NPC_CANNONH = 36839,
|
||||||
NPC_MURADIN_BRONZEBEARD = 36948,
|
NPC_MURADIN_BRONZEBEARD = 36948,
|
||||||
NPC_HIGH_OVERLORD_SAURFANG = 36939,
|
NPC_HIGH_OVERLORD_SAURFANG = 36939,
|
||||||
|
NPC_ZAFOD_BOOMBOX = 37184,
|
||||||
|
ITEM_GOBLIN_ROCKET_PACK = 49278,
|
||||||
|
|
||||||
// Deathbringer Saurfang
|
// Deathbringer Saurfang
|
||||||
NPC_BLOOD_BEAST1 = 38508,
|
NPC_BLOOD_BEAST1 = 38508,
|
||||||
@ -38,9 +52,11 @@ enum CreatureIdsICC
|
|||||||
|
|
||||||
// Rotface
|
// Rotface
|
||||||
NPC_PUDDLE = 37013,
|
NPC_PUDDLE = 37013,
|
||||||
|
NPC_SMALL_OOZE = 36897,
|
||||||
NPC_BIG_OOZE = 36899,
|
NPC_BIG_OOZE = 36899,
|
||||||
|
|
||||||
// Putricide
|
// Putricide
|
||||||
|
NPC_PROFESSOR_PUTRICIDE = 36678,
|
||||||
NPC_MALLEABLE_OOZE_STALKER = 38556,
|
NPC_MALLEABLE_OOZE_STALKER = 38556,
|
||||||
NPC_GROWING_OOZE_PUDDLE = 37690,
|
NPC_GROWING_OOZE_PUDDLE = 37690,
|
||||||
NPC_CHOKING_GAS_BOMB = 38159,
|
NPC_CHOKING_GAS_BOMB = 38159,
|
||||||
@ -56,6 +72,7 @@ enum CreatureIdsICC
|
|||||||
NPC_KINETIC_BOMB4 = 38777,
|
NPC_KINETIC_BOMB4 = 38777,
|
||||||
NPC_BALL_OF_FLAME = 38332,
|
NPC_BALL_OF_FLAME = 38332,
|
||||||
NPC_BALL_OF_INFERNO_FLAME = 38451,
|
NPC_BALL_OF_INFERNO_FLAME = 38451,
|
||||||
|
NPC_SHOCK_VORTEX = 38422,
|
||||||
|
|
||||||
// Blood Queen Lana'thel
|
// Blood Queen Lana'thel
|
||||||
NPC_SWARMING_SHADOWS = 38163,
|
NPC_SWARMING_SHADOWS = 38163,
|
||||||
@ -133,26 +150,47 @@ enum SpellIdsICC
|
|||||||
SPELL_NO_THREAT = 70115, //reduce threat
|
SPELL_NO_THREAT = 70115, //reduce threat
|
||||||
SPELL_SPITEFULL_FURY = 36886, //500% more threat
|
SPELL_SPITEFULL_FURY = 36886, //500% more threat
|
||||||
SPELL_NITRO_BOOSTS = 54861, //Speed
|
SPELL_NITRO_BOOSTS = 54861, //Speed
|
||||||
|
SPELL_FROST_TRAP1 = 13809, //Hunter slow trap
|
||||||
SPELL_PAIN_SUPPRESION = 69910, //40% dmg reduction
|
SPELL_PAIN_SUPPRESION = 69910, //40% dmg reduction
|
||||||
SPELL_AGEIS_OF_DALARAN = 71638, //268 all ress
|
SPELL_AGEIS_OF_DALARAN = 71638, //268 all ress
|
||||||
SPELL_CYCLONE = 33786,
|
SPELL_CYCLONE = 33786,
|
||||||
SPELL_HAMMER_OF_JUSTICE = 10308, //stun
|
SPELL_HAMMER_OF_JUSTICE = 10308, //stun
|
||||||
|
// Taunt spells (used to reset cooldowns for assist tank)
|
||||||
|
SPELL_TAUNT_WARRIOR = 355,
|
||||||
|
SPELL_TAUNT_PALADIN = 62124, // Hand of Reckoning
|
||||||
|
SPELL_TAUNT_DK = 56222, // Dark Command
|
||||||
|
SPELL_TAUNT_DRUID = 6795, // Growl
|
||||||
|
SPELL_VIPER_STING = 3034,
|
||||||
|
|
||||||
|
// Lord Marrowgar
|
||||||
|
SPELL_LM_IMPALED = 69065,
|
||||||
|
|
||||||
// Lady Deathwhisper
|
// Lady Deathwhisper
|
||||||
SPELL_DARK_RECKONING = 69483,
|
SPELL_DARK_RECKONING = 69483,
|
||||||
|
SPELL_TOUCH_OF_INSIGNIFICANCE = 71204,
|
||||||
|
|
||||||
// Gunship Battle
|
// Gunship Battle
|
||||||
SPELL_DEATH_PLAGUE = 72865,
|
SPELL_DEATH_PLAGUE = 72865,
|
||||||
|
SPELL_FROZEN_CANNON = 69704,
|
||||||
SPELL_BELOW_ZERO = 69705,
|
SPELL_BELOW_ZERO = 69705,
|
||||||
|
SPELL_ROCKET_PACK_USE = 68645,
|
||||||
|
SPELL_ROCKET_PACK_USEABLE = 70348,
|
||||||
|
SPELL_BATTLE_FURY1 = 69637,
|
||||||
|
SPELL_BATTLE_FURY2 = 69638,
|
||||||
|
SPELL_BATTLE_FURY3 = 72306,
|
||||||
|
SPELL_BATTLE_FURY4 = 72307,
|
||||||
|
SPELL_BATTLE_FURY5 = 72308,
|
||||||
|
|
||||||
// Festergut
|
// Festergut
|
||||||
SPELL_GAS_SPORE = 69279,
|
SPELL_GAS_SPORE = 69279,
|
||||||
|
|
||||||
// Rotface
|
// Rotface
|
||||||
SPELL_SLIME_SPRAY = 69508,
|
SPELL_SLIME_SPRAY = 69508,
|
||||||
SPELL_OOZE_FLOOD = 71215,
|
SPELL_OOZE_FLOOD = 71215,
|
||||||
SPELL_UNSTABLE_OOZE_EXPLOSION = 69839,
|
SPELL_UNSTABLE_OOZE_EXPLOSION = 69839,
|
||||||
SPELL_OOZE_FLOOD_VISUAL = 69785,
|
SPELL_OOZE_FLOOD_VISUAL = 69785,
|
||||||
|
// Cast by Professor Putricide from balcony during Rotface heroic.
|
||||||
|
// Single ID (no difficulty variants in spelldifficulty_dbc).
|
||||||
|
SPELL_VILE_GAS_H = 69240,
|
||||||
|
|
||||||
// Putricide
|
// Putricide
|
||||||
SPELL_MALLEABLE_GOO = 70852,
|
SPELL_MALLEABLE_GOO = 70852,
|
||||||
@ -166,6 +204,7 @@ enum SpellIdsICC
|
|||||||
|
|
||||||
// Blood Queen Lana'thel
|
// Blood Queen Lana'thel
|
||||||
SPELL_PACT_OF_THE_DARKFALLEN = 71340,
|
SPELL_PACT_OF_THE_DARKFALLEN = 71340,
|
||||||
|
SPELL_BLOODBOLT_WHIRL = 71772,
|
||||||
|
|
||||||
// Sister Svalna
|
// Sister Svalna
|
||||||
SPELL_AETHER_SHIELD = 71463,
|
SPELL_AETHER_SHIELD = 71463,
|
||||||
@ -173,6 +212,7 @@ enum SpellIdsICC
|
|||||||
// Valithria Dreamwalker
|
// Valithria Dreamwalker
|
||||||
SPELL_DREAM_STATE = 70766,
|
SPELL_DREAM_STATE = 70766,
|
||||||
SPELL_EMERALD_VIGOR = 70873,
|
SPELL_EMERALD_VIGOR = 70873,
|
||||||
|
SPELL_ACID_BURST = 70744,
|
||||||
|
|
||||||
// Sindragosa
|
// Sindragosa
|
||||||
SPELL_FROST_BEACON = 70126,
|
SPELL_FROST_BEACON = 70126,
|
||||||
@ -182,6 +222,9 @@ enum SpellIdsICC
|
|||||||
SPELL_BLISTERING_COLD2 = 71047,
|
SPELL_BLISTERING_COLD2 = 71047,
|
||||||
SPELL_BLISTERING_COLD3 = 71048,
|
SPELL_BLISTERING_COLD3 = 71048,
|
||||||
SPELL_BLISTERING_COLD4 = 71049,
|
SPELL_BLISTERING_COLD4 = 71049,
|
||||||
|
SPELL_HAND_OF_FREEDOM = 1044,
|
||||||
|
ITEM_RED_SMOKE_FLARE = 23769,
|
||||||
|
ITEM_BLUE_SMOKE_FLARE = 23770,
|
||||||
|
|
||||||
// The Lich King
|
// The Lich King
|
||||||
SPELL_HARVEST_SOUL_VALKYR = 68985,
|
SPELL_HARVEST_SOUL_VALKYR = 68985,
|
||||||
@ -194,12 +237,26 @@ enum SpellIdsICC
|
|||||||
SPELL_REMORSELESS_WINTER6 = 74270,
|
SPELL_REMORSELESS_WINTER6 = 74270,
|
||||||
SPELL_REMORSELESS_WINTER7 = 74271,
|
SPELL_REMORSELESS_WINTER7 = 74271,
|
||||||
SPELL_REMORSELESS_WINTER8 = 74272,
|
SPELL_REMORSELESS_WINTER8 = 74272,
|
||||||
|
SPELL_VALKYR_CARRY = 30440,
|
||||||
|
SPELL_HARVEST_SOUL_LK = 68980,
|
||||||
|
SPELL_HARVEST_SOULS_LK_25 = 73654,
|
||||||
|
SPELL_HARVEST_SOULS_LK_H1 = 74295,
|
||||||
|
SPELL_HARVEST_SOULS_LK_H2 = 74296,
|
||||||
|
SPELL_HARVEST_SOULS_LK_H3 = 74297,
|
||||||
};
|
};
|
||||||
|
|
||||||
const uint32 DEFILE_AURAS[] = {72756, 74162, 74163, 74164};
|
inline constexpr uint32 DEFILE_AURAS[] = {72756, 74162, 74163, 74164};
|
||||||
const uint32 DEFILE_CAST_ID = 72762;
|
inline constexpr uint32 DEFILE_CAST_ID = 72762;
|
||||||
const uint32 DEFILE_NPC_ID = 38757;
|
inline constexpr uint32 DEFILE_NPC_ID = 38757;
|
||||||
const size_t DEFILE_AURA_COUNT = 4;
|
inline constexpr size_t DEFILE_AURA_COUNT = 4;
|
||||||
|
|
||||||
|
// Malleable Goo (Putricide / Festergut-heroic). Multiple variants because of
|
||||||
|
// SpellDifficulty remapping and the balcony stalker variant.
|
||||||
|
inline constexpr uint32 SPELL_MALLEABLE_GOO_10N = 70852;
|
||||||
|
inline constexpr uint32 SPELL_MALLEABLE_GOO_25N = 72297;
|
||||||
|
inline constexpr uint32 SPELL_MALLEABLE_GOO_10H = 74280;
|
||||||
|
inline constexpr uint32 SPELL_MALLEABLE_GOO_25H = 74281;
|
||||||
|
inline constexpr uint32 SPELL_MALLEABLE_GOO_BALCONY = 72296;
|
||||||
|
|
||||||
// All fanatics and adherents entry ids Lady Deathwhisper
|
// All fanatics and adherents entry ids Lady Deathwhisper
|
||||||
static const std::array<uint32, 23> addEntriesLady = {
|
static const std::array<uint32, 23> addEntriesLady = {
|
||||||
@ -212,8 +269,8 @@ const std::vector<uint32> spellEntriesFlood = {
|
|||||||
69799, 69801, 69802, 69795};
|
69799, 69801, 69802, 69795};
|
||||||
|
|
||||||
const std::vector<uint32> availableTargetsGS = {
|
const std::vector<uint32> availableTargetsGS = {
|
||||||
NPC_KOR_KRON_AXETHROWER, NPC_KOR_KRON_ROCKETEER, NPC_KOR_KRON_BATTLE_MAGE, NPC_IGB_HIGH_OVERLORD_SAURFANG,
|
NPC_KOR_KRON_ROCKETEER, NPC_KOR_KRON_AXETHROWER, NPC_KOR_KRON_BATTLE_MAGE, NPC_IGB_HIGH_OVERLORD_SAURFANG,
|
||||||
NPC_SKYBREAKER_RIFLEMAN, NPC_SKYBREAKER_MORTAR_SOLDIER, NPC_SKYBREAKER_SORCERER, NPC_IGB_MURADIN_BRONZEBEARD};
|
NPC_SKYBREAKER_MORTAR_SOLDIER, NPC_SKYBREAKER_RIFLEMAN, NPC_SKYBREAKER_SORCERER, NPC_IGB_MURADIN_BRONZEBEARD};
|
||||||
|
|
||||||
static std::vector<ObjectGuid> sporeOrder;
|
static std::vector<ObjectGuid> sporeOrder;
|
||||||
|
|
||||||
@ -262,17 +319,17 @@ public:
|
|||||||
bool IsActive() override;
|
bool IsActive() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
class IccGunshipTeleportAllyTrigger : public Trigger
|
class IccGunshipRocketJumpTrigger : public Trigger
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
IccGunshipTeleportAllyTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc gunship teleport ally") {}
|
IccGunshipRocketJumpTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc gunship rocket jump") {}
|
||||||
bool IsActive() override;
|
bool IsActive() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
class IccGunshipTeleportHordeTrigger : public Trigger
|
class IccGunshipRocketPackSetupTrigger : public Trigger
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
IccGunshipTeleportHordeTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc gunship teleport horde") {}
|
IccGunshipRocketPackSetupTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc gunship rocket pack setup") {}
|
||||||
bool IsActive() override;
|
bool IsActive() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -291,11 +348,10 @@ public:
|
|||||||
bool IsActive() override;
|
bool IsActive() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
//DOGS
|
class IccDogsTrigger : public Trigger
|
||||||
class IccStinkyPreciousMainTankMortalWoundTrigger : public Trigger
|
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
IccStinkyPreciousMainTankMortalWoundTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc stinky precious main tank mortal wound") {}
|
IccDogsTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc dogs") {}
|
||||||
bool IsActive() override;
|
bool IsActive() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -321,6 +377,14 @@ public:
|
|||||||
bool IsActive() override;
|
bool IsActive() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class IccFestergutAvoidMalleableGooTrigger : public Trigger
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccFestergutAvoidMalleableGooTrigger(PlayerbotAI* botAI)
|
||||||
|
: Trigger(botAI, "icc festergut avoid malleable goo") {}
|
||||||
|
bool IsActive() override;
|
||||||
|
};
|
||||||
|
|
||||||
//ROTFACE
|
//ROTFACE
|
||||||
class IccRotfaceTankPositionTrigger : public Trigger
|
class IccRotfaceTankPositionTrigger : public Trigger
|
||||||
{
|
{
|
||||||
@ -339,7 +403,20 @@ public:
|
|||||||
class IccRotfaceMoveAwayFromExplosionTrigger : public Trigger
|
class IccRotfaceMoveAwayFromExplosionTrigger : public Trigger
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
IccRotfaceMoveAwayFromExplosionTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc rotface move away from explosion") {}
|
IccRotfaceMoveAwayFromExplosionTrigger(PlayerbotAI* botAI)
|
||||||
|
: Trigger(botAI, "icc rotface move away from explosion"), _castEndTime(0), _wasCasting(false) {}
|
||||||
|
bool IsActive() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
time_t _castEndTime;
|
||||||
|
bool _wasCasting;
|
||||||
|
};
|
||||||
|
|
||||||
|
class IccRotfaceAvoidVileGasTrigger : public Trigger
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccRotfaceAvoidVileGasTrigger(PlayerbotAI* botAI)
|
||||||
|
: Trigger(botAI, "icc rotface avoid vile gas") {}
|
||||||
bool IsActive() override;
|
bool IsActive() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -365,10 +442,10 @@ public:
|
|||||||
bool IsActive() override;
|
bool IsActive() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
class IccPutricideMainTankMutatedPlagueTrigger : public Trigger
|
class IccPutricideMutatedPlagueTrigger : public Trigger
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
IccPutricideMainTankMutatedPlagueTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc putricide main tank mutated plague") {}
|
IccPutricideMutatedPlagueTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc putricide mutated plague") {}
|
||||||
bool IsActive() override;
|
bool IsActive() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -379,6 +456,13 @@ public:
|
|||||||
bool IsActive() override;
|
bool IsActive() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class IccPutricideAbominationTrigger : public Trigger
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccPutricideAbominationTrigger(PlayerbotAI* ai) : Trigger(ai, "icc putricide abomination") {}
|
||||||
|
bool IsActive() override;
|
||||||
|
};
|
||||||
|
|
||||||
//BPC
|
//BPC
|
||||||
class IccBpcKelesethTankTrigger : public Trigger
|
class IccBpcKelesethTankTrigger : public Trigger
|
||||||
{
|
{
|
||||||
@ -461,6 +545,13 @@ public:
|
|||||||
bool IsActive() override;
|
bool IsActive() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class IccValithriaZombieKiteTrigger : public Trigger
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccValithriaZombieKiteTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc valithria zombie kite") {}
|
||||||
|
bool IsActive() override;
|
||||||
|
};
|
||||||
|
|
||||||
class IccValithriaPortalTrigger : public Trigger
|
class IccValithriaPortalTrigger : public Trigger
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -497,6 +588,13 @@ public:
|
|||||||
bool IsActive() override;
|
bool IsActive() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class IccSindragosaHotTrigger : public Trigger
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccSindragosaHotTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc sindragosa hot") {}
|
||||||
|
bool IsActive() override;
|
||||||
|
};
|
||||||
|
|
||||||
class IccSindragosaBlisteringColdTrigger : public Trigger
|
class IccSindragosaBlisteringColdTrigger : public Trigger
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -575,4 +673,11 @@ public:
|
|||||||
bool IsActive() override;
|
bool IsActive() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class IccLichKingSpiritBombTrigger : public Trigger
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IccLichKingSpiritBombTrigger(PlayerbotAI* botAI) : Trigger(botAI, "icc lich king spirit bomb") {}
|
||||||
|
bool IsActive() override;
|
||||||
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,669 +0,0 @@
|
|||||||
#ifndef _PLAYERBOT_RAIDICCACTIONS_H
|
|
||||||
#define _PLAYERBOT_RAIDICCACTIONS_H
|
|
||||||
|
|
||||||
#include "Action.h"
|
|
||||||
#include "MovementActions.h"
|
|
||||||
#include "PlayerbotAI.h"
|
|
||||||
#include "Playerbots.h"
|
|
||||||
#include "AttackAction.h"
|
|
||||||
#include "LastMovementValue.h"
|
|
||||||
#include "ObjectGuid.h"
|
|
||||||
#include "PlayerbotAIConfig.h"
|
|
||||||
#include "RaidIccStrategy.h"
|
|
||||||
#include "ScriptedCreature.h"
|
|
||||||
#include "SharedDefines.h"
|
|
||||||
#include "Trigger.h"
|
|
||||||
#include "CellImpl.h"
|
|
||||||
#include "GridNotifiers.h"
|
|
||||||
#include "GridNotifiersImpl.h"
|
|
||||||
#include "Vehicle.h"
|
|
||||||
#include "RaidIccTriggers.h"
|
|
||||||
|
|
||||||
const Position ICC_LM_TANK_POSITION = Position(-391.0f, 2259.0f, 42.0f);
|
|
||||||
const Position ICC_DARK_RECKONING_SAFE_POSITION = Position(-523.33386f, 2211.2031f, 62.823116f);
|
|
||||||
const Position ICC_LDW_TANK_POSTION = Position(-570.1f, 2211.2456f, 49.476616f); //-590.0f
|
|
||||||
const Position ICC_ROTTING_FROST_GIANT_TANK_POSITION = Position(-328.5085f, 2225.5142f, 199.97298f);
|
|
||||||
const Position ICC_GUNSHIP_TELEPORT_ALLY = Position (-370.04645f, 1993.3536f, 466.65656f);
|
|
||||||
const Position ICC_GUNSHIP_TELEPORT_ALLY2 = Position (-392.66208f, 2064.893f, 466.5672f, 5.058196f);
|
|
||||||
const Position ICC_GUNSHIP_TELEPORT_HORDE = Position (-449.5343f, 2477.2024f, 470.17648f);
|
|
||||||
const Position ICC_GUNSHIP_TELEPORT_HORDE2 = Position (-429.81586f, 2400.6804f, 471.56537f);
|
|
||||||
const Position ICC_DBS_TANK_POSITION = Position(-494.26517f, 2211.549f, 541.11414f);
|
|
||||||
const Position ICC_FESTERGUT_TANK_POSITION = Position(4269.1772f, 3144.7673f, 360.38577f);
|
|
||||||
const Position ICC_FESTERGUT_RANGED_SPORE = Position(4261.143f, 3109.4146f, 360.38605f);
|
|
||||||
const Position ICC_FESTERGUT_MELEE_SPORE = Position(4269.1772f, 3144.7673f, 360.38577f);
|
|
||||||
const Position ICC_ROTFACE_TANK_POSITION = Position(4447.061f, 3150.9758f, 360.38568f);
|
|
||||||
const Position ICC_ROTFACE_BIG_OOZE_POSITION = Position(4432.687f, 3142.3035f, 360.38623f);
|
|
||||||
const Position ICC_ROTFACE_SAFE_POSITION = Position(4446.557f, 3065.6594f, 360.51843f);
|
|
||||||
const Position ICC_ROTFACE_CENTER_POSITION = Position(4446.0547f, 3144.8677f, 360.38593f); //actual center 4.74089 4445.6616f, 3137.1526f, 360.38608
|
|
||||||
const Position ICC_PUTRICIDE_TANK_POSITION = Position(4373.227f, 3222.058f, 389.4029f);
|
|
||||||
const Position ICC_PUTRICIDE_GREEN_POSITION = Position(4423.4126f, 3194.2715f, 389.37683f);
|
|
||||||
const Position ICC_PUTRICIDE_BAD_POSITION = Position(4356.1724f, 3261.5232f, 389.3985f);
|
|
||||||
//const Position ICC_PUTRICIDE_GAS3_POSITION = Position(4367.753f, 3177.5894f, 389.39575f);
|
|
||||||
//const Position ICC_PUTRICIDE_GAS4_POSITION = Position(4321.8486f, 3206.464f, 389.3982f);
|
|
||||||
const Position ICC_BPC_OT_POSITION = Position(4649.2236f, 2796.0972f, 361.1815f);
|
|
||||||
const Position ICC_BPC_MT_POSITION = Position(4648.5674f, 2744.847f, 361.18222f);
|
|
||||||
const Position ICC_BQL_CENTER_POSITION = Position(4595.0f, 2769.0f, 400.0f);
|
|
||||||
const Position ICC_BQL_LWALL1_POSITION = Position(4624.685f, 2789.4895f, 400.13834f);
|
|
||||||
const Position ICC_BQL_LWALL2_POSITION = Position(4600.749f, 2805.7568f, 400.1374f);
|
|
||||||
const Position ICC_BQL_LWALL3_POSITION = Position(4572.857f, 2797.3872f, 400.1374f);
|
|
||||||
const Position ICC_BQL_RWALL1_POSITION = Position(4625.724f, 2748.9917f, 400.13693f);
|
|
||||||
const Position ICC_BQL_RWALL2_POSITION = Position(4608.3774f, 2735.7466f, 400.13693f);
|
|
||||||
const Position ICC_BQL_RWALL3_POSITION = Position(4576.813f, 2739.6067f, 400.13693f);
|
|
||||||
const Position ICC_BQL_LRWALL4_POSITION = Position(4539.345f, 2769.3853f, 403.7267f);
|
|
||||||
const Position ICC_BQL_TANK_POSITION = Position(4629.746f, 2769.6396f, 401.7479f); //old just in front of stairs 4616.102f, 2768.9167f, 400.13797f
|
|
||||||
const Position ICC_VDW_HEAL_POSITION = Position(4203.752f, 2483.4343f, 364.87274f);
|
|
||||||
const Position ICC_VDW_GROUP1_POSITION = Position(4203.585f, 2464.422f, 364.86887f);
|
|
||||||
const Position ICC_VDW_GROUP2_POSITION = Position(4203.5806f, 2505.2383f, 364.87677f);
|
|
||||||
const Position ICC_VDW_PORTALSTART_POSITION = Position(4202.637f, 2488.171f, 375.00256f);
|
|
||||||
const Position ICC_SINDRAGOSA_TANK_POSITION = Position(4408.016f, 2508.0647f, 203.37955f);
|
|
||||||
const Position ICC_SINDRAGOSA_FLYING_POSITION = Position(4525.6f, 2485.15f, 245.082f);
|
|
||||||
const Position ICC_SINDRAGOSA_RANGED_POSITION = Position(4441.572f, 2484.482f, 203.37836f);
|
|
||||||
const Position ICC_SINDRAGOSA_MELEE_POSITION = Position(4423.4546f, 2491.7175f, 203.37686f);
|
|
||||||
const Position ICC_SINDRAGOSA_BLISTERING_COLD_POSITION = Position(4473.6616f, 2484.8489f, 203.38258f);
|
|
||||||
const Position ICC_SINDRAGOSA_THOMB1_POSITION = Position(4433.6484f, 2469.4133f, 203.3806f);
|
|
||||||
const Position ICC_SINDRAGOSA_THOMB2_POSITION = Position(4434.143f, 2486.201f, 203.37473f);
|
|
||||||
const Position ICC_SINDRAGOSA_THOMB3_POSITION = Position(4436.1147f, 2501.464f, 203.38266f);
|
|
||||||
const Position ICC_SINDRAGOSA_UNCHAINEDMAGIC1_POSITION = Position(4444.9707f, 2455.7322f, 203.38701f);
|
|
||||||
const Position ICC_SINDRAGOSA_UNCHAINEDMAGIC2_POSITION = Position(4461.3945f, 2463.5513f, 203.38727f);
|
|
||||||
const Position ICC_SINDRAGOSA_UNCHAINEDMAGIC3_POSITION = Position(4473.6616f, 2484.8489f, 203.38258f);
|
|
||||||
const Position ICC_SINDRAGOSA_UNCHAINEDMAGIC4_POSITION = Position(4459.9336f, 2507.409f, 203.38606f);
|
|
||||||
const Position ICC_SINDRAGOSA_UNCHAINEDMAGIC5_POSITION = Position(4442.3096f, 2512.4688f, 203.38647f);
|
|
||||||
const Position ICC_SINDRAGOSA_CENTER_POSITION = Position(4408.0464f, 2484.478f, 203.37529f);
|
|
||||||
const Position ICC_SINDRAGOSA_THOMBMB2_POSITION = Position(4436.895f, 2498.1401f, 203.38133f);
|
|
||||||
const Position ICC_SINDRAGOSA_FBOMB_POSITION = Position(4449.3647f, 2486.4524f, 203.379f);
|
|
||||||
const Position ICC_SINDRAGOSA_FBOMB10_POSITION = Position(4449.3647f, 2486.4524f, 203.379f);
|
|
||||||
const Position ICC_SINDRAGOSA_LOS2_POSITION = Position(4441.8286f, 2501.946f, 203.38435f);
|
|
||||||
const Position ICC_LICH_KING_ADDS_POSITION = Position(476.7332f, -2095.3894f, 840.857f); // old 486.63647f, -2095.7915f, 840.857f
|
|
||||||
const Position ICC_LICH_KING_MELEE_POSITION = Position(503.5546f, -2106.8213f, 840.857f);
|
|
||||||
const Position ICC_LICH_KING_RANGED_POSITION = Position(501.3563f, -2085.1816f, 840.857f);
|
|
||||||
const Position ICC_LICH_KING_ASSISTHC_POSITION = Position(517.2145f, -2125.0674f, 840.857f);
|
|
||||||
const Position ICC_LK_FROST1_POSITION = Position(503.96548f, -2183.216f, 840.857f);
|
|
||||||
const Position ICC_LK_FROST2_POSITION = Position(563.07166f, -2125.7578f, 840.857f);
|
|
||||||
const Position ICC_LK_FROST3_POSITION = Position(503.40182f, -2067.3435f, 840.857f);
|
|
||||||
const Position ICC_LK_FROSTR1_POSITION = Position(481.168f, -2177.8723f, 840.857f);
|
|
||||||
const Position ICC_LK_FROSTR2_POSITION = Position(562.20807f, -2100.2393f, 840.857f);
|
|
||||||
const Position ICC_LK_FROSTR3_POSITION = Position(526.35297f, -2071.0317f, 840.857f);
|
|
||||||
|
|
||||||
//Lord Marrogwar
|
|
||||||
class IccLmTankPositionAction : public AttackAction
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
IccLmTankPositionAction(PlayerbotAI* botAI, std::string const name = "icc lm tank position")
|
|
||||||
: AttackAction(botAI, name) {}
|
|
||||||
bool Execute(Event event) override;
|
|
||||||
|
|
||||||
bool MoveTowardPosition(const Position& position, float incrementSize);
|
|
||||||
};
|
|
||||||
|
|
||||||
class IccSpikeAction : public AttackAction
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
IccSpikeAction(PlayerbotAI* botAI) : AttackAction(botAI, "icc spike") {}
|
|
||||||
bool Execute(Event event) override;
|
|
||||||
|
|
||||||
bool HandleSpikeTargeting(Unit* boss);
|
|
||||||
bool MoveTowardPosition(const Position& position, float incrementSize);
|
|
||||||
void UpdateRaidTargetIcon(Unit* target);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Lady Deathwhisper
|
|
||||||
class IccDarkReckoningAction : public MovementAction
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
IccDarkReckoningAction(PlayerbotAI* botAI, std::string const name = "icc dark reckoning")
|
|
||||||
: MovementAction(botAI, name) {}
|
|
||||||
bool Execute(Event event) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
class IccRangedPositionLadyDeathwhisperAction : public AttackAction
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
IccRangedPositionLadyDeathwhisperAction(PlayerbotAI* botAI, std::string const name = "icc ranged position lady deathwhisper")
|
|
||||||
: AttackAction(botAI, name) {}
|
|
||||||
bool Execute(Event event) override;
|
|
||||||
|
|
||||||
bool MaintainRangedSpacing();
|
|
||||||
};
|
|
||||||
|
|
||||||
class IccAddsLadyDeathwhisperAction : public AttackAction
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
IccAddsLadyDeathwhisperAction(PlayerbotAI* botAI, std::string const name = "icc adds lady deathwhisper")
|
|
||||||
: AttackAction(botAI, name) {}
|
|
||||||
bool Execute(Event event) override;
|
|
||||||
|
|
||||||
bool IsTargetedByShade(uint32 shadeEntry);
|
|
||||||
bool MoveTowardPosition(const Position& position, float incrementSize);
|
|
||||||
bool HandleAddTargeting(Unit* boss);
|
|
||||||
void UpdateRaidTargetIcon(Unit* target);
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
class IccShadeLadyDeathwhisperAction : public MovementAction
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
IccShadeLadyDeathwhisperAction(PlayerbotAI* botAI, std::string const name = "icc shade lady deathwhisper")
|
|
||||||
: MovementAction(botAI, name) {}
|
|
||||||
bool Execute(Event event) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
//Gunship Battle
|
|
||||||
class IccRottingFrostGiantTankPositionAction : public AttackAction
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
IccRottingFrostGiantTankPositionAction(PlayerbotAI* botAI, std::string const name = "icc rotting frost giant tank position")
|
|
||||||
: AttackAction(botAI, name) {}
|
|
||||||
bool Execute(Event event) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
class IccCannonFireAction : public Action
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
IccCannonFireAction(PlayerbotAI* botAI, std::string const name = "icc cannon fire")
|
|
||||||
: Action(botAI, name) {}
|
|
||||||
bool Execute(Event event) override;
|
|
||||||
|
|
||||||
Unit* FindValidCannonTarget();
|
|
||||||
bool TryCastCannonSpell(uint32 spellId, Unit* target, Unit* vehicleBase);
|
|
||||||
};
|
|
||||||
|
|
||||||
class IccGunshipEnterCannonAction : public MovementAction
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
IccGunshipEnterCannonAction(PlayerbotAI* botAI, std::string const name = "icc gunship enter cannon")
|
|
||||||
: MovementAction(botAI, name) {}
|
|
||||||
bool Execute(Event event) override;
|
|
||||||
|
|
||||||
bool EnterVehicle(Unit* vehicleBase, bool moveIfFar);
|
|
||||||
Unit* FindBestAvailableCannon();
|
|
||||||
bool IsValidCannon(Unit* vehicle, const uint32 validEntries[]);
|
|
||||||
};
|
|
||||||
|
|
||||||
class IccGunshipTeleportAllyAction : public AttackAction
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
IccGunshipTeleportAllyAction(PlayerbotAI* botAI, std::string const name = "icc gunship teleport ally")
|
|
||||||
: AttackAction(botAI, name) {}
|
|
||||||
bool Execute(Event event) override;
|
|
||||||
|
|
||||||
bool TeleportTo(const Position& position);
|
|
||||||
void CleanupSkullIcon(uint8_t SKULL_ICON_INDEX);
|
|
||||||
void UpdateBossSkullIcon(Unit* boss, uint8_t SKULL_ICON_INDEX);
|
|
||||||
};
|
|
||||||
|
|
||||||
class IccGunshipTeleportHordeAction : public AttackAction
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
IccGunshipTeleportHordeAction(PlayerbotAI* botAI, std::string const name = "icc gunship teleport horde")
|
|
||||||
: AttackAction(botAI, name) {}
|
|
||||||
bool Execute(Event event) override;
|
|
||||||
|
|
||||||
bool TeleportTo(const Position& position);
|
|
||||||
void CleanupSkullIcon(uint8_t SKULL_ICON_INDEX);
|
|
||||||
void UpdateBossSkullIcon(Unit* boss, uint8_t SKULL_ICON_INDEX);
|
|
||||||
};
|
|
||||||
|
|
||||||
//DBS
|
|
||||||
class IccDbsTankPositionAction : public AttackAction
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
IccDbsTankPositionAction(PlayerbotAI* botAI, std::string const name = "icc dbs tank position")
|
|
||||||
: AttackAction(botAI, name) {}
|
|
||||||
bool Execute(Event event) override;
|
|
||||||
|
|
||||||
bool CrowdControlBloodBeasts();
|
|
||||||
bool EvadeBloodBeasts();
|
|
||||||
bool PositionInRangedFormation();
|
|
||||||
};
|
|
||||||
|
|
||||||
class IccAddsDbsAction : public AttackAction
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
IccAddsDbsAction(PlayerbotAI* botAI, std::string const name = "icc adds dbs")
|
|
||||||
: AttackAction(botAI, name) {}
|
|
||||||
bool Execute(Event event) override;
|
|
||||||
|
|
||||||
Unit* FindPriorityTarget(Unit* boss);
|
|
||||||
void UpdateSkullMarker(Unit* priorityTarget);
|
|
||||||
};
|
|
||||||
|
|
||||||
//FESTERGUT
|
|
||||||
class IccFestergutGroupPositionAction : public AttackAction
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
IccFestergutGroupPositionAction(PlayerbotAI* botAI, std::string const name = "icc festergut group position")
|
|
||||||
: AttackAction(botAI, name) {}
|
|
||||||
bool Execute(Event event) override;
|
|
||||||
|
|
||||||
bool HasSporesInGroup();
|
|
||||||
bool PositionNonTankMembers();
|
|
||||||
int CalculatePositionIndex(Group* group);
|
|
||||||
};
|
|
||||||
|
|
||||||
class IccFestergutSporeAction : public AttackAction
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
IccFestergutSporeAction(PlayerbotAI* botAI, std::string const name = "icc festergut spore")
|
|
||||||
: AttackAction(botAI, name) {}
|
|
||||||
bool Execute(Event event) override;
|
|
||||||
|
|
||||||
Position CalculateSpreadPosition();
|
|
||||||
struct SporeInfo
|
|
||||||
{
|
|
||||||
std::vector<Unit*> sporedPlayers;
|
|
||||||
ObjectGuid lowestGuid;
|
|
||||||
bool hasLowestGuid = false;
|
|
||||||
};
|
|
||||||
SporeInfo FindSporedPlayers();
|
|
||||||
Position DetermineTargetPosition(bool hasSpore, const SporeInfo& sporeInfo, const Position& spreadRangedPos);
|
|
||||||
bool CheckMainTankSpore();
|
|
||||||
};
|
|
||||||
|
|
||||||
//Rotface
|
|
||||||
class IccRotfaceTankPositionAction : public AttackAction
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
IccRotfaceTankPositionAction(PlayerbotAI* botAI, std::string const name = "icc rotface tank position")
|
|
||||||
: AttackAction(botAI, name) {}
|
|
||||||
bool Execute(Event event) override;
|
|
||||||
|
|
||||||
void MarkBossWithSkull(Unit* boss);
|
|
||||||
bool PositionMainTankAndMelee(Unit *boss);
|
|
||||||
bool HandleAssistTankPositioning(Unit* boss);
|
|
||||||
bool HandleBigOozePositioning(Unit* boss);
|
|
||||||
};
|
|
||||||
|
|
||||||
class IccRotfaceGroupPositionAction : public AttackAction
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
IccRotfaceGroupPositionAction(PlayerbotAI* botAI, std::string const name = "icc rotface group position")
|
|
||||||
: AttackAction(botAI, name) {}
|
|
||||||
bool Execute(Event event) override;
|
|
||||||
|
|
||||||
//bool MoveAwayFromBigOoze(Unit* bigOoze);
|
|
||||||
bool HandlePuddleAvoidance(Unit* boss);
|
|
||||||
bool MoveAwayFromPuddle(Unit* boss, Unit* puddle, float puddleDistance);
|
|
||||||
bool HandleOozeTargeting();
|
|
||||||
bool HandleOozeMemberPositioning();
|
|
||||||
bool PositionRangedAndHealers(Unit* boss,Unit* smallOoze);
|
|
||||||
bool FindAndMoveFromClosestMember(Unit* smallOoze);
|
|
||||||
};
|
|
||||||
|
|
||||||
class IccRotfaceMoveAwayFromExplosionAction : public MovementAction
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
IccRotfaceMoveAwayFromExplosionAction(PlayerbotAI* botAI, std::string const name = "icc rotface move away from explosion")
|
|
||||||
: MovementAction(botAI, name) {}
|
|
||||||
bool Execute(Event event) override;
|
|
||||||
|
|
||||||
bool MoveToRandomSafeLocation();
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
//PP
|
|
||||||
class IccPutricideGrowingOozePuddleAction : public AttackAction
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
IccPutricideGrowingOozePuddleAction(PlayerbotAI* botAI, std::string const name = "icc putricide growing ooze puddle")
|
|
||||||
: AttackAction(botAI, name) {}
|
|
||||||
bool Execute(Event event) override;
|
|
||||||
|
|
||||||
Unit* FindClosestThreateningPuddle();
|
|
||||||
Position CalculateSafeMovePosition(Unit* closestPuddle);
|
|
||||||
bool IsPositionTooCloseToOtherPuddles(float x, float y, Unit* ignorePuddle);
|
|
||||||
};
|
|
||||||
|
|
||||||
class IccPutricideVolatileOozeAction : public AttackAction
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
IccPutricideVolatileOozeAction(PlayerbotAI* botAI, std::string const name = "icc putricide volatile ooze")
|
|
||||||
: AttackAction(botAI, name) {}
|
|
||||||
bool Execute(Event event) override;
|
|
||||||
|
|
||||||
void MarkOozeWithSkull(Unit* ooze);
|
|
||||||
Unit* FindAuraTarget();
|
|
||||||
};
|
|
||||||
|
|
||||||
class IccPutricideGasCloudAction : public AttackAction
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
IccPutricideGasCloudAction(PlayerbotAI* botAI, std::string const name = "icc putricide gas cloud")
|
|
||||||
: AttackAction(botAI, name) {}
|
|
||||||
bool Execute(Event event) override;
|
|
||||||
|
|
||||||
bool HandleGaseousBloatMovement(Unit* gasCloud);
|
|
||||||
Position CalculateEmergencyPosition(const Position& botPos, float dx, float dy);
|
|
||||||
bool FindSafeMovementPosition(const Position& botPos, const Position& cloudPos, float dx, float dy, int numAngles,
|
|
||||||
Position& resultPos);
|
|
||||||
bool HandleGroupAuraSituation(Unit* gasCloud);
|
|
||||||
bool GroupHasGaseousBloat(Group* group);
|
|
||||||
};
|
|
||||||
|
|
||||||
class IccPutricideAvoidMalleableGooAction : public MovementAction
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
IccPutricideAvoidMalleableGooAction(PlayerbotAI* botAI, std::string const name = "icc putricide avoid malleable goo")
|
|
||||||
: MovementAction(botAI, name) {}
|
|
||||||
bool Execute(Event event) override;
|
|
||||||
|
|
||||||
bool HandleTankPositioning(Unit* boss);
|
|
||||||
bool HandleUnboundPlague(Unit* boss);
|
|
||||||
bool HandleBossPositioning(Unit* boss);
|
|
||||||
Position CalculateBossPosition(Unit* boss, float distance);
|
|
||||||
bool HasObstacleBetween(const Position& from, const Position& to);
|
|
||||||
bool IsOnPath(const Position& from, const Position& to, const Position& point, float threshold);
|
|
||||||
Position CalculateArcPoint(const Position& current, const Position& target, const Position& center);
|
|
||||||
Position CalculateIncrementalMove(const Position& current, const Position& target, float maxDistance);
|
|
||||||
};
|
|
||||||
|
|
||||||
//BPC
|
|
||||||
class IccBpcKelesethTankAction : public AttackAction
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
IccBpcKelesethTankAction(PlayerbotAI* botAI)
|
|
||||||
: AttackAction(botAI, "icc bpc keleseth tank") {}
|
|
||||||
bool Execute(Event event) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
class IccBpcMainTankAction : public AttackAction
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
IccBpcMainTankAction(PlayerbotAI* botAI)
|
|
||||||
: AttackAction(botAI, "icc bpc main tank") {}
|
|
||||||
bool Execute(Event event) override;
|
|
||||||
|
|
||||||
void MarkEmpoweredPrince();
|
|
||||||
};
|
|
||||||
|
|
||||||
class IccBpcEmpoweredVortexAction : public MovementAction
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
IccBpcEmpoweredVortexAction(PlayerbotAI* botAI)
|
|
||||||
: MovementAction(botAI, "icc bpc empowered vortex") {}
|
|
||||||
bool Execute(Event event) override;
|
|
||||||
|
|
||||||
bool MaintainRangedSpacing();
|
|
||||||
bool HandleEmpoweredVortexSpread();
|
|
||||||
};
|
|
||||||
|
|
||||||
class IccBpcKineticBombAction : public AttackAction
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
IccBpcKineticBombAction(PlayerbotAI* botAI)
|
|
||||||
: AttackAction(botAI, "icc bpc kinetic bomb") {}
|
|
||||||
bool Execute(Event event) override;
|
|
||||||
|
|
||||||
Unit* FindOptimalKineticBomb();
|
|
||||||
bool IsBombAlreadyHandled(Unit* bomb, Group* group);
|
|
||||||
};
|
|
||||||
|
|
||||||
class IccBpcBallOfFlameAction : public MovementAction
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
IccBpcBallOfFlameAction(PlayerbotAI* botAI)
|
|
||||||
: MovementAction(botAI, "icc bpc ball of flame") {}
|
|
||||||
bool Execute(Event event) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
//Blood Queen Lana'thel
|
|
||||||
class IccBqlGroupPositionAction : public AttackAction
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
IccBqlGroupPositionAction(PlayerbotAI* botAI)
|
|
||||||
: AttackAction(botAI, "icc group tank position") {}
|
|
||||||
bool Execute(Event event) override;
|
|
||||||
|
|
||||||
bool HandleTankPosition(Unit* boss, Aura* frenzyAura, Aura* shadowAura);
|
|
||||||
bool HandleShadowsMovement();
|
|
||||||
Position AdjustControlPoint(const Position& wall, const Position& center, float factor);
|
|
||||||
Position CalculateBezierPoint(float t, const Position path[4]);
|
|
||||||
bool HandleGroupPosition(Unit* boss, Aura* frenzyAura, Aura* shadowAura);
|
|
||||||
|
|
||||||
private:
|
|
||||||
// Evaluate curves
|
|
||||||
struct CurveInfo
|
|
||||||
{
|
|
||||||
Position moveTarget;
|
|
||||||
int curveIdx = 0;
|
|
||||||
bool foundSafe = false;
|
|
||||||
float minDist = 0.0f;
|
|
||||||
float score = 0.0f;
|
|
||||||
Position closestPoint;
|
|
||||||
float t_closest = 0.0f;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
class IccBqlPactOfDarkfallenAction : public MovementAction
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
IccBqlPactOfDarkfallenAction(PlayerbotAI* botAI)
|
|
||||||
: MovementAction(botAI, "icc bql pact of darkfallen") {}
|
|
||||||
bool Execute(Event event) override;
|
|
||||||
|
|
||||||
void CalculateCenterPosition(Position& targetPos, const std::vector<Player*>& playersWithAura);
|
|
||||||
bool MoveToTargetPosition(const Position& targetPos, int auraCount);
|
|
||||||
};
|
|
||||||
|
|
||||||
class IccBqlVampiricBiteAction : public AttackAction
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
IccBqlVampiricBiteAction(PlayerbotAI* botAI)
|
|
||||||
: AttackAction(botAI, "icc bql vampiric bite") {}
|
|
||||||
bool Execute(Event event) override;
|
|
||||||
|
|
||||||
Player* FindBestBiteTarget(Group* group);
|
|
||||||
bool IsInvalidTarget(Player* player);
|
|
||||||
bool MoveTowardsTarget(Player* target);
|
|
||||||
bool CastVampiricBite(Player* target);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Sister Svalna
|
|
||||||
class IccValkyreSpearAction : public AttackAction
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
IccValkyreSpearAction(PlayerbotAI* botAI)
|
|
||||||
: AttackAction(botAI, "icc valkyre spear") {}
|
|
||||||
bool Execute(Event event) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
class IccSisterSvalnaAction : public AttackAction
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
IccSisterSvalnaAction(PlayerbotAI* botAI)
|
|
||||||
: AttackAction(botAI, "icc sister svalna") {}
|
|
||||||
bool Execute(Event event) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Valithria Dreamwalker
|
|
||||||
|
|
||||||
class IccValithriaGroupAction : public AttackAction
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
IccValithriaGroupAction(PlayerbotAI* botAI)
|
|
||||||
: AttackAction(botAI, "icc valithria group") {}
|
|
||||||
bool Execute(Event event) override;
|
|
||||||
|
|
||||||
bool MoveTowardsPosition(const Position& pos, float increment);
|
|
||||||
bool Handle25ManGroupLogic();
|
|
||||||
bool HandleMarkingLogic(bool inGroup1, bool inGroup2, const Position& group1Pos, const Position& group2Pos);
|
|
||||||
bool Handle10ManGroupLogic();
|
|
||||||
};
|
|
||||||
|
|
||||||
class IccValithriaPortalAction : public MovementAction
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
IccValithriaPortalAction(PlayerbotAI* botAI)
|
|
||||||
: MovementAction(botAI, "icc valithria portal") {}
|
|
||||||
bool Execute(Event event) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
class IccValithriaHealAction : public AttackAction
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
IccValithriaHealAction(PlayerbotAI* botAI)
|
|
||||||
: AttackAction(botAI, "icc valithria heal") {}
|
|
||||||
bool Execute(Event event) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
class IccValithriaDreamCloudAction : public MovementAction
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
IccValithriaDreamCloudAction(PlayerbotAI* botAI)
|
|
||||||
: MovementAction(botAI, "icc valithria dream cloud") {}
|
|
||||||
bool Execute(Event event) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
//Sindragosa
|
|
||||||
class IccSindragosaGroupPositionAction : public AttackAction
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
IccSindragosaGroupPositionAction(PlayerbotAI* botAI)
|
|
||||||
: AttackAction(botAI, "icc sindragosa group position") {}
|
|
||||||
bool Execute(Event event) override;
|
|
||||||
|
|
||||||
bool HandleTankPositioning(Unit* boss);
|
|
||||||
bool HandleNonTankPositioning();
|
|
||||||
bool MoveIncrementallyToPosition(const Position& targetPos, float maxStep);
|
|
||||||
};
|
|
||||||
|
|
||||||
class IccSindragosaFrostBeaconAction : public MovementAction
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
IccSindragosaFrostBeaconAction(PlayerbotAI* botAI)
|
|
||||||
: MovementAction(botAI, "icc sindragosa frost beacon") {}
|
|
||||||
bool Execute(Event event) override;
|
|
||||||
|
|
||||||
void HandleSupportActions();
|
|
||||||
bool HandleBeaconedPlayer(const Unit* boss);
|
|
||||||
bool HandleNonBeaconedPlayer(const Unit* boss);
|
|
||||||
bool MoveToPositionIfNeeded(const Position& position, float tolerance);
|
|
||||||
bool MoveToPosition(const Position& position);
|
|
||||||
bool IsBossFlying(const Unit* boss);
|
|
||||||
|
|
||||||
private:
|
|
||||||
static constexpr uint32 FROST_BEACON_AURA_ID = SPELL_FROST_BEACON;
|
|
||||||
static constexpr uint32 HAND_OF_FREEDOM_SPELL_ID = 1044;
|
|
||||||
static constexpr float POSITION_TOLERANCE = 1.0f;
|
|
||||||
static constexpr float TOMB_POSITION_TOLERANCE = 0.5f;
|
|
||||||
static constexpr float MIN_SAFE_DISTANCE = 13.0f;
|
|
||||||
static constexpr float MOVE_TOLERANCE = 2.0f;
|
|
||||||
};
|
|
||||||
|
|
||||||
class IccSindragosaBlisteringColdAction : public MovementAction
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
IccSindragosaBlisteringColdAction(PlayerbotAI* botAI)
|
|
||||||
: MovementAction(botAI, "icc sindragosa blistering cold") {}
|
|
||||||
bool Execute(Event event) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
class IccSindragosaUnchainedMagicAction : public AttackAction
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
IccSindragosaUnchainedMagicAction(PlayerbotAI* botAI)
|
|
||||||
: AttackAction(botAI, "icc sindragosa unchained magic") {}
|
|
||||||
bool Execute(Event event) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
class IccSindragosaChilledToTheBoneAction : public AttackAction
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
IccSindragosaChilledToTheBoneAction(PlayerbotAI* botAI)
|
|
||||||
: AttackAction(botAI, "icc sindragosa chilled to the bone") {}
|
|
||||||
bool Execute(Event event) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
class IccSindragosaMysticBuffetAction : public MovementAction
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
IccSindragosaMysticBuffetAction(PlayerbotAI* botAI)
|
|
||||||
: MovementAction(botAI, "icc sindragosa mystic buffet") {}
|
|
||||||
bool Execute(Event event) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
class IccSindragosaFrostBombAction : public MovementAction
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
IccSindragosaFrostBombAction(PlayerbotAI* botAI)
|
|
||||||
: MovementAction(botAI, "icc sindragosa frost bomb") {}
|
|
||||||
bool Execute(Event event) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
class IccSindragosaTankSwapPositionAction : public AttackAction
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
IccSindragosaTankSwapPositionAction(PlayerbotAI* botAI)
|
|
||||||
: AttackAction(botAI, "sindragosa tank swap position") {}
|
|
||||||
bool Execute(Event event) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
//LK
|
|
||||||
class IccLichKingShadowTrapAction : public MovementAction
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
IccLichKingShadowTrapAction(PlayerbotAI* botAI)
|
|
||||||
: MovementAction(botAI, "icc lich king shadow trap") {}
|
|
||||||
bool Execute(Event event) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
class IccLichKingNecroticPlagueAction : public MovementAction
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
IccLichKingNecroticPlagueAction(PlayerbotAI* botAI)
|
|
||||||
: MovementAction(botAI, "icc lich king necrotic plague") {}
|
|
||||||
bool Execute(Event event) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
class IccLichKingWinterAction : public AttackAction
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
IccLichKingWinterAction(PlayerbotAI* botAI)
|
|
||||||
: AttackAction(botAI, "icc lich king winter") {}
|
|
||||||
bool Execute(Event event) override;
|
|
||||||
|
|
||||||
void HandlePositionCorrection();
|
|
||||||
bool IsValidCollectibleAdd(Unit* unit);
|
|
||||||
bool IsPositionSafeFromDefile(float x, float y, float minSafeDistance);
|
|
||||||
void HandleTankPositioning();
|
|
||||||
void HandleMeleePositioning();
|
|
||||||
void HandleRangedPositioning();
|
|
||||||
void HandleMainTankAddManagement(const Position* tankPos);
|
|
||||||
void HandleAssistTankAddManagement(const Position* tankPos);
|
|
||||||
|
|
||||||
private:
|
|
||||||
const Position* GetMainTankPosition();
|
|
||||||
const Position* GetMainTankRangedPosition();
|
|
||||||
bool TryMoveToPosition(float targetX, float targetY, float targetZ, bool isForced = true);
|
|
||||||
};
|
|
||||||
|
|
||||||
class IccLichKingAddsAction : public AttackAction
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
IccLichKingAddsAction(PlayerbotAI* botAI)
|
|
||||||
: AttackAction(botAI, "icc lich king adds") {}
|
|
||||||
bool Execute(Event event) override;
|
|
||||||
|
|
||||||
void HandleTeleportationFixes(Difficulty diff, Unit* terenasMenethilHC);
|
|
||||||
bool HandleSpiritBombAvoidance(Difficulty diff, Unit* terenasMenethilHC);
|
|
||||||
void HandleHeroicNonTankPositioning(Difficulty diff, Unit* terenasMenethilHC);
|
|
||||||
void HandleSpiritMarkingAndTargeting(Difficulty diff, Unit* terenasMenethilHC);
|
|
||||||
bool HandleQuakeMechanics(Unit* boss);
|
|
||||||
void HandleShamblingHorrors();
|
|
||||||
bool HandleAssistTankAddManagement(Unit* boss, Difficulty diff);
|
|
||||||
void HandleMeleePositioning(Unit* boss, bool hasPlague, Difficulty diff);
|
|
||||||
void HandleMainTankTargeting(Unit* boss, Difficulty diff);
|
|
||||||
void HandleNonTankHeroicPositioning(Unit* boss, Difficulty diff, bool hasPlague);
|
|
||||||
void HandleRangedPositioning(Unit* boss, bool hasPlague, Difficulty diff);
|
|
||||||
void HandleDefileMechanics(Unit* boss, Difficulty diff);
|
|
||||||
void HandleValkyrMechanics(Difficulty diff);
|
|
||||||
std::vector<size_t> CalculateBalancedGroupSizes(size_t totalAssist, size_t numValkyrs);
|
|
||||||
size_t GetAssignedValkyrIndex(size_t assistIndex, const std::vector<size_t>& groupSizes);
|
|
||||||
std::string GetRTIValueForValkyr(size_t valkyrIndex);
|
|
||||||
void HandleValkyrMarking(const std::vector<Unit*>& grabbingValkyrs, Difficulty diff);
|
|
||||||
void HandleValkyrAssignment(const std::vector<Unit*>& grabbingValkyrs);
|
|
||||||
void ApplyCCToValkyr(Unit* valkyr);
|
|
||||||
bool IsValkyr(Unit* unit);
|
|
||||||
void HandleVileSpiritMechanics();
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@ -1,872 +0,0 @@
|
|||||||
#include "RaidIccMultipliers.h"
|
|
||||||
|
|
||||||
#include "ChooseTargetActions.h"
|
|
||||||
#include "DKActions.h"
|
|
||||||
#include "DruidActions.h"
|
|
||||||
#include "DruidBearActions.h"
|
|
||||||
#include "FollowActions.h"
|
|
||||||
#include "GenericActions.h"
|
|
||||||
#include "GenericSpellActions.h"
|
|
||||||
#include "HunterActions.h"
|
|
||||||
#include "MageActions.h"
|
|
||||||
#include "MovementActions.h"
|
|
||||||
#include "PaladinActions.h"
|
|
||||||
#include "PriestActions.h"
|
|
||||||
#include "RaidIccActions.h"
|
|
||||||
#include "ReachTargetActions.h"
|
|
||||||
#include "RogueActions.h"
|
|
||||||
#include "ShamanActions.h"
|
|
||||||
#include "UseMeetingStoneAction.h"
|
|
||||||
#include "WarriorActions.h"
|
|
||||||
#include "PlayerbotAI.h"
|
|
||||||
#include "RaidIccTriggers.h"
|
|
||||||
|
|
||||||
// LK global variables
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
std::map<ObjectGuid, uint32> g_plagueTimes;
|
|
||||||
std::map<ObjectGuid, bool> g_allowCure;
|
|
||||||
std::mutex g_plagueMutex; // Lock before accessing shared variables
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lady Deathwhisper
|
|
||||||
float IccLadyDeathwhisperMultiplier::GetValue(Action* action)
|
|
||||||
{
|
|
||||||
Unit* boss = AI_VALUE2(Unit*, "find target", "lady deathwhisper");
|
|
||||||
if (!boss)
|
|
||||||
return 1.0f;
|
|
||||||
|
|
||||||
if (dynamic_cast<FleeAction*>(action) || dynamic_cast<FollowAction*>(action) || dynamic_cast<CombatFormationMoveAction*>(action))
|
|
||||||
return 0.0f;
|
|
||||||
|
|
||||||
static constexpr uint32 VENGEFUL_SHADE_ID = NPC_SHADE;
|
|
||||||
|
|
||||||
// Get the nearest hostile NPCs
|
|
||||||
const GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs");
|
|
||||||
|
|
||||||
// Allow the IccShadeLadyDeathwhisperAction to run
|
|
||||||
if (dynamic_cast<IccShadeLadyDeathwhisperAction*>(action))
|
|
||||||
return 1.0f;
|
|
||||||
|
|
||||||
for (auto const& npcGuid : npcs)
|
|
||||||
{
|
|
||||||
Unit* shade = botAI->GetUnit(npcGuid);
|
|
||||||
|
|
||||||
if (!shade || shade->GetEntry() != VENGEFUL_SHADE_ID)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (!shade->GetVictim() || shade->GetVictim()->GetGUID() != bot->GetGUID())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
return 0.0f; // Cancel all other actions when we need to handle Vengeful Shade
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
// dbs
|
|
||||||
float IccAddsDbsMultiplier::GetValue(Action* action)
|
|
||||||
{
|
|
||||||
Unit* boss = AI_VALUE2(Unit*, "find target", "deathbringer saurfang");
|
|
||||||
if (!boss)
|
|
||||||
return 1.0f;
|
|
||||||
|
|
||||||
if (dynamic_cast<DpsAoeAction*>(action) || dynamic_cast<CastHurricaneAction*>(action) ||
|
|
||||||
dynamic_cast<CastVolleyAction*>(action) || dynamic_cast<CastBlizzardAction*>(action) ||
|
|
||||||
dynamic_cast<CastStarfallAction*>(action) || dynamic_cast<FanOfKnivesAction*>(action) ||
|
|
||||||
dynamic_cast<CastWhirlwindAction*>(action) || dynamic_cast<CastMindSearAction*>(action) ||
|
|
||||||
dynamic_cast<CombatFormationMoveAction*>(action) || dynamic_cast<FollowAction*>(action) ||
|
|
||||||
dynamic_cast<FleeAction*>(action))
|
|
||||||
return 0.0f;
|
|
||||||
|
|
||||||
if (botAI->IsRanged(bot))
|
|
||||||
if (dynamic_cast<ReachSpellAction*>(action))
|
|
||||||
return 0.0f;
|
|
||||||
|
|
||||||
if (botAI->IsMainTank(bot))
|
|
||||||
{
|
|
||||||
Aura* aura = botAI->GetAura("rune of blood", bot);
|
|
||||||
if (aura)
|
|
||||||
{
|
|
||||||
if (dynamic_cast<MovementAction*>(action))
|
|
||||||
return 1.0f;
|
|
||||||
else
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
// dogs
|
|
||||||
float IccDogsMultiplier::GetValue(Action* action)
|
|
||||||
{
|
|
||||||
bool bossPresent = false;
|
|
||||||
if (AI_VALUE2(Unit*, "find target", "stinky") || AI_VALUE2(Unit*, "find target", "precious"))
|
|
||||||
bossPresent = true;
|
|
||||||
|
|
||||||
if (!bossPresent)
|
|
||||||
return 1.0f;
|
|
||||||
|
|
||||||
if (botAI->IsMainTank(bot))
|
|
||||||
{
|
|
||||||
Aura* aura = botAI->GetAura("mortal wound", bot, false, true);
|
|
||||||
if (aura && aura->GetStackAmount() >= 8)
|
|
||||||
{
|
|
||||||
if (dynamic_cast<MovementAction*>(action))
|
|
||||||
return 1.0f;
|
|
||||||
else
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 1.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Festergut
|
|
||||||
float IccFestergutMultiplier::GetValue(Action* action)
|
|
||||||
{
|
|
||||||
Unit* boss = AI_VALUE2(Unit*, "find target", "festergut");
|
|
||||||
if (!boss)
|
|
||||||
return 1.0f;
|
|
||||||
|
|
||||||
if (dynamic_cast<CombatFormationMoveAction*>(action) || dynamic_cast<FollowAction*>(action))
|
|
||||||
return 0.0f;
|
|
||||||
|
|
||||||
if (dynamic_cast<FleeAction*>(action))
|
|
||||||
return 0.0f;
|
|
||||||
|
|
||||||
if (botAI->IsMainTank(bot))
|
|
||||||
{
|
|
||||||
Aura* aura = botAI->GetAura("gastric bloat", bot, false, true);
|
|
||||||
if (aura && aura->GetStackAmount() >= 6)
|
|
||||||
{
|
|
||||||
if (dynamic_cast<MovementAction*>(action))
|
|
||||||
return 1.0f;
|
|
||||||
else
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dynamic_cast<IccFestergutSporeAction*>(action))
|
|
||||||
return 1.0f;
|
|
||||||
|
|
||||||
if (bot->HasAura(SPELL_GAS_SPORE))
|
|
||||||
return 0.0f;
|
|
||||||
|
|
||||||
return 1.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rotface
|
|
||||||
float IccRotfaceMultiplier::GetValue(Action* action)
|
|
||||||
{
|
|
||||||
Unit* boss1 = AI_VALUE2(Unit*, "find target", "rotface");
|
|
||||||
if (!boss1)
|
|
||||||
return 1.0f;
|
|
||||||
|
|
||||||
if (dynamic_cast<CombatFormationMoveAction*>(action))
|
|
||||||
return 0.0f;
|
|
||||||
|
|
||||||
if (dynamic_cast<FleeAction*>(action) && !(bot->getClass() == CLASS_HUNTER))
|
|
||||||
return 0.0f;
|
|
||||||
|
|
||||||
if (dynamic_cast<CastBlinkBackAction*>(action))
|
|
||||||
return 0.0f;
|
|
||||||
|
|
||||||
if (botAI->IsAssistTank(bot) && (dynamic_cast<AttackRtiTargetAction*>(action) || dynamic_cast<TankAssistAction*>(action)))
|
|
||||||
return 0.0f;
|
|
||||||
|
|
||||||
Unit* boss = AI_VALUE2(Unit*, "find target", "big ooze");
|
|
||||||
if (!boss)
|
|
||||||
return 1.0f;
|
|
||||||
|
|
||||||
static std::map<ObjectGuid, uint32> lastExplosionTimes;
|
|
||||||
static std::map<ObjectGuid, bool> hasMoved;
|
|
||||||
|
|
||||||
ObjectGuid botGuid = bot->GetGUID();
|
|
||||||
|
|
||||||
// When cast starts, record the time
|
|
||||||
if (boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(SPELL_UNSTABLE_OOZE_EXPLOSION))
|
|
||||||
{
|
|
||||||
if (lastExplosionTimes[botGuid] == 0) // Only set if not already set
|
|
||||||
{
|
|
||||||
lastExplosionTimes[botGuid] = time(nullptr);
|
|
||||||
hasMoved[botGuid] = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If explosion cast is no longer active, reset the timers
|
|
||||||
if (!boss->HasUnitState(UNIT_STATE_CASTING) || !boss->FindCurrentSpellBySpellId(SPELL_UNSTABLE_OOZE_EXPLOSION))
|
|
||||||
{
|
|
||||||
if (lastExplosionTimes[botGuid] > 0 && time(nullptr) - lastExplosionTimes[botGuid] >= 16)
|
|
||||||
{
|
|
||||||
lastExplosionTimes[botGuid] = 0;
|
|
||||||
hasMoved[botGuid] = false;
|
|
||||||
return 1.0f; // Allow normal actions to resume
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If 9 seconds have passed since cast start and we haven't moved yet
|
|
||||||
if (lastExplosionTimes[botGuid] > 0 && !hasMoved[botGuid] && time(nullptr) - lastExplosionTimes[botGuid] >= 9)
|
|
||||||
{
|
|
||||||
if (dynamic_cast<MovementAction*>(action)
|
|
||||||
&& !dynamic_cast<IccRotfaceMoveAwayFromExplosionAction*>(action))
|
|
||||||
{
|
|
||||||
return 0.0f; // Block other movement actions
|
|
||||||
}
|
|
||||||
hasMoved[botGuid] = true; // Mark that we've initiated movement
|
|
||||||
}
|
|
||||||
|
|
||||||
// Continue blocking other movements for 7 seconds after moving
|
|
||||||
if (hasMoved[botGuid] && time(nullptr) - lastExplosionTimes[botGuid] < 16 // 9 seconds wait + 7 seconds stay
|
|
||||||
&& dynamic_cast<MovementAction*>(action)
|
|
||||||
&& !dynamic_cast<IccRotfaceMoveAwayFromExplosionAction*>(action))
|
|
||||||
return 0.0f;
|
|
||||||
|
|
||||||
return 1.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
// pp
|
|
||||||
float IccAddsPutricideMultiplier::GetValue(Action* action)
|
|
||||||
{
|
|
||||||
Unit* boss = AI_VALUE2(Unit*, "find target", "professor putricide");
|
|
||||||
if (!boss)
|
|
||||||
return 1.0f;
|
|
||||||
|
|
||||||
bool hasGaseousBloat = botAI->HasAura("Gaseous Bloat", bot);
|
|
||||||
bool hasUnboundPlague = botAI->HasAura("Unbound Plague", bot);
|
|
||||||
|
|
||||||
if (!(bot->getClass() == CLASS_HUNTER) && dynamic_cast<FleeAction*>(action))
|
|
||||||
return 0.0f;
|
|
||||||
|
|
||||||
if (dynamic_cast<CombatFormationMoveAction*>(action))
|
|
||||||
return 0.0f;
|
|
||||||
|
|
||||||
if (dynamic_cast<CastDisengageAction*>(action))
|
|
||||||
return 0.0f;
|
|
||||||
|
|
||||||
if (dynamic_cast<CastBlinkBackAction*>(action))
|
|
||||||
return 0.0f;
|
|
||||||
|
|
||||||
if (botAI->IsMainTank(bot))
|
|
||||||
{
|
|
||||||
Aura* aura = botAI->GetAura("mutated plague", bot, false, true);
|
|
||||||
if (aura && aura->GetStackAmount() >= 4)
|
|
||||||
{
|
|
||||||
if (dynamic_cast<MovementAction*>(action))
|
|
||||||
return 1.0f;
|
|
||||||
else
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasGaseousBloat)
|
|
||||||
{
|
|
||||||
if (dynamic_cast<IccPutricideGasCloudAction*>(action))
|
|
||||||
return 1.0f;
|
|
||||||
|
|
||||||
if (dynamic_cast<IccPutricideGrowingOozePuddleAction*>(action))
|
|
||||||
return 1.0f;
|
|
||||||
|
|
||||||
if (botAI->IsHeal(bot))
|
|
||||||
return 1.0f;
|
|
||||||
else
|
|
||||||
return 0.0f; // Cancel all other actions when we need to handle Gaseous Bloat
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasUnboundPlague && boss && !boss->HealthBelowPct(35))
|
|
||||||
{
|
|
||||||
if (dynamic_cast<IccPutricideAvoidMalleableGooAction*>(action))
|
|
||||||
return 1.0f;
|
|
||||||
else
|
|
||||||
return 0.0f; // Cancel all other actions when we need to handle Unbound Plague
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dynamic_cast<IccPutricideVolatileOozeAction*>(action))
|
|
||||||
{
|
|
||||||
if (dynamic_cast<IccPutricideAvoidMalleableGooAction*>(action))
|
|
||||||
return 0.0f;
|
|
||||||
if (dynamic_cast<IccPutricideGrowingOozePuddleAction*>(action) && !botAI->IsMainTank(bot))
|
|
||||||
return 0.0f;
|
|
||||||
//if (dynamic_cast<IccPutricideGasCloudAction*>(action) && !hasGaseousBloat)
|
|
||||||
//return 0.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
// bpc
|
|
||||||
float IccBpcAssistMultiplier::GetValue(Action* action)
|
|
||||||
{
|
|
||||||
Unit* keleseth = AI_VALUE2(Unit*, "find target", "prince keleseth");
|
|
||||||
if (!keleseth)
|
|
||||||
return 1.0f;
|
|
||||||
|
|
||||||
if (dynamic_cast<DpsAoeAction*>(action) || dynamic_cast<CastHurricaneAction*>(action) ||
|
|
||||||
dynamic_cast<CastVolleyAction*>(action) || dynamic_cast<CastBlizzardAction*>(action) ||
|
|
||||||
dynamic_cast<CastStarfallAction*>(action) || dynamic_cast<FanOfKnivesAction*>(action) ||
|
|
||||||
dynamic_cast<CastWhirlwindAction*>(action) || dynamic_cast<CastMindSearAction*>(action) ||
|
|
||||||
dynamic_cast<CombatFormationMoveAction*>(action) || dynamic_cast<FollowAction*>(action))
|
|
||||||
return 0.0f;
|
|
||||||
|
|
||||||
Aura* aura = botAI->GetAura("Shadow Prison", bot, false, true);
|
|
||||||
if (aura)
|
|
||||||
{
|
|
||||||
if (aura->GetStackAmount() > 18 && botAI->IsTank(bot))
|
|
||||||
{
|
|
||||||
if (dynamic_cast<MovementAction*>(action))
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (aura->GetStackAmount() > 12 && !botAI->IsTank(bot))
|
|
||||||
{
|
|
||||||
if (dynamic_cast<MovementAction*>(action))
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Unit* valanar = AI_VALUE2(Unit*, "find target", "prince valanar");
|
|
||||||
if (!valanar)
|
|
||||||
return 1.0f;
|
|
||||||
|
|
||||||
if (valanar && valanar->HasUnitState(UNIT_STATE_CASTING) &&
|
|
||||||
(valanar->FindCurrentSpellBySpellId(SPELL_EMPOWERED_SHOCK_VORTEX1) ||
|
|
||||||
valanar->FindCurrentSpellBySpellId(SPELL_EMPOWERED_SHOCK_VORTEX2) ||
|
|
||||||
valanar->FindCurrentSpellBySpellId(SPELL_EMPOWERED_SHOCK_VORTEX3) ||
|
|
||||||
valanar->FindCurrentSpellBySpellId(SPELL_EMPOWERED_SHOCK_VORTEX4)))
|
|
||||||
{
|
|
||||||
if (dynamic_cast<AvoidAoeAction*>(action) || dynamic_cast<IccBpcEmpoweredVortexAction*>(action))
|
|
||||||
return 1.0f;
|
|
||||||
else
|
|
||||||
return 0.0f; // Cancel all other actions when we need to handle Empowered Vortex
|
|
||||||
}
|
|
||||||
|
|
||||||
Unit* flame1 = bot->FindNearestCreature(NPC_BALL_OF_FLAME, 100.0f);
|
|
||||||
Unit* flame2 = bot->FindNearestCreature(NPC_BALL_OF_INFERNO_FLAME, 100.0f);
|
|
||||||
bool ballOfFlame = flame1 && flame1->GetVictim() == bot;
|
|
||||||
bool infernoFlame = flame2 && flame2->GetVictim() == bot;
|
|
||||||
|
|
||||||
if (flame2)
|
|
||||||
{
|
|
||||||
if (dynamic_cast<AvoidAoeAction*>(action) || dynamic_cast<IccBpcKineticBombAction*>(action))
|
|
||||||
return 0.0f;
|
|
||||||
|
|
||||||
if (dynamic_cast<IccBpcBallOfFlameAction*>(action))
|
|
||||||
return 1.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ballOfFlame || infernoFlame)
|
|
||||||
{
|
|
||||||
// If bot is tank, do nothing special
|
|
||||||
if (dynamic_cast<IccBpcBallOfFlameAction*>(action))
|
|
||||||
return 1.0f;
|
|
||||||
else
|
|
||||||
return 0.0f; // Cancel all other actions when we need to handle Ball of Flame
|
|
||||||
}
|
|
||||||
|
|
||||||
static const std::array<uint32, 4> bombEntries = {NPC_KINETIC_BOMB1, NPC_KINETIC_BOMB2, NPC_KINETIC_BOMB3,
|
|
||||||
NPC_KINETIC_BOMB4};
|
|
||||||
const GuidVector bombs = AI_VALUE(GuidVector, "possible targets no los");
|
|
||||||
|
|
||||||
bool bombFound = false;
|
|
||||||
|
|
||||||
for (const auto entry : bombEntries)
|
|
||||||
{
|
|
||||||
for (auto const& guid : bombs)
|
|
||||||
{
|
|
||||||
if (Unit* unit = botAI->GetUnit(guid))
|
|
||||||
{
|
|
||||||
if (unit->GetEntry() == entry)
|
|
||||||
{
|
|
||||||
// Check if bomb is within valid Z-axis range
|
|
||||||
if (unit->GetPositionZ() - bot->GetPositionZ() < 25.0f)
|
|
||||||
{
|
|
||||||
bombFound = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (bombFound)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bombFound && !(aura && aura->GetStackAmount() > 12) && !botAI->IsTank(bot))
|
|
||||||
{
|
|
||||||
// If kinetic bomb action is active, disable these actions
|
|
||||||
if (dynamic_cast<IccBpcKineticBombAction*>(action))
|
|
||||||
return 1.0f;
|
|
||||||
|
|
||||||
if (dynamic_cast<DpsAssistAction*>(action) || dynamic_cast<TankAssistAction*>(action) ||
|
|
||||||
dynamic_cast<AttackRtiTargetAction*>(action))
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
// For assist tank during BPC fight
|
|
||||||
if (botAI->IsAssistTank(bot) && !(aura && aura->GetStackAmount() > 18))
|
|
||||||
{
|
|
||||||
// Allow BPC-specific actions
|
|
||||||
if (dynamic_cast<IccBpcKelesethTankAction*>(action))
|
|
||||||
return 1.0f;
|
|
||||||
|
|
||||||
// Disable normal assist behavior
|
|
||||||
if (dynamic_cast<TankAssistAction*>(action) ||
|
|
||||||
dynamic_cast<FleeAction*>(action) ||
|
|
||||||
dynamic_cast<AttackRtiTargetAction*>(action) ||
|
|
||||||
dynamic_cast<CastConsecrationAction*>(action))
|
|
||||||
return 0.0f;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
//BQL
|
|
||||||
float IccBqlMultiplier::GetValue(Action* action)
|
|
||||||
{
|
|
||||||
Unit* boss = AI_VALUE2(Unit*, "find target", "blood-queen lana'thel");
|
|
||||||
if (!boss)
|
|
||||||
return 1.0f;
|
|
||||||
|
|
||||||
Aura* aura2 = botAI->GetAura("Swarming Shadows", bot);
|
|
||||||
Aura* aura = botAI->GetAura("Frenzied Bloodthirst", bot);
|
|
||||||
|
|
||||||
if (botAI->IsRanged(bot))
|
|
||||||
if (dynamic_cast<AvoidAoeAction*>(action) || dynamic_cast<FleeAction*>(action) ||
|
|
||||||
dynamic_cast<CombatFormationMoveAction*>(action) || dynamic_cast<CastDisengageAction*>(action))
|
|
||||||
return 0.0f;
|
|
||||||
|
|
||||||
// If bot has Pact of Darkfallen aura, return 0 for all other actions
|
|
||||||
if (bot->HasAura(SPELL_PACT_OF_THE_DARKFALLEN))
|
|
||||||
{
|
|
||||||
if (dynamic_cast<IccBqlPactOfDarkfallenAction*>(action))
|
|
||||||
return 1.0f; // Allow Pact of Darkfallen action
|
|
||||||
else
|
|
||||||
return 0.0f; // Cancel all other actions when we need to handle Pact of Darkfallen
|
|
||||||
}
|
|
||||||
|
|
||||||
if (botAI->IsMelee(bot) && ((boss->GetPositionZ() - ICC_BQL_CENTER_POSITION.GetPositionZ()) > 5.0f) && !aura)
|
|
||||||
{
|
|
||||||
if (dynamic_cast<IccBqlGroupPositionAction*>(action))
|
|
||||||
return 1.0f;
|
|
||||||
else
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If bot has frenzied bloodthirst, allow highest priority for bite action
|
|
||||||
if (aura) // If bot has frenzied bloodthirst
|
|
||||||
{
|
|
||||||
if (dynamic_cast<IccBqlVampiricBiteAction*>(action))
|
|
||||||
return 1.0f;
|
|
||||||
else
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (aura2 && !aura)
|
|
||||||
{
|
|
||||||
if (dynamic_cast<IccBqlGroupPositionAction*>(action))
|
|
||||||
return 1.0f;
|
|
||||||
else
|
|
||||||
return 0.0f; // Cancel all other actions when we need to handle Swarming Shadows
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((boss->GetExactDist2d(ICC_BQL_TANK_POSITION.GetPositionX(), ICC_BQL_TANK_POSITION.GetPositionY()) > 10.0f) &&
|
|
||||||
botAI->IsRanged(bot) && !((boss->GetPositionZ() - bot->GetPositionZ()) > 5.0f))
|
|
||||||
{
|
|
||||||
if (dynamic_cast<FleeAction*>(action) || dynamic_cast<CombatFormationMoveAction*>(action))
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
//VDW
|
|
||||||
float IccValithriaDreamCloudMultiplier::GetValue(Action* action)
|
|
||||||
{
|
|
||||||
Unit* boss = bot->FindNearestCreature(NPC_VALITHRIA_DREAMWALKER, 100.0f);
|
|
||||||
|
|
||||||
Aura* twistedNightmares = botAI->GetAura("Twisted Nightmares", bot);
|
|
||||||
Aura* emeraldVigor = botAI->GetAura("Emerald Vigor", bot);
|
|
||||||
|
|
||||||
if (!boss && !bot->HasAura(SPELL_DREAM_STATE))
|
|
||||||
return 1.0f;
|
|
||||||
|
|
||||||
if (dynamic_cast<FollowAction*>(action) || dynamic_cast<CombatFormationMoveAction*>(action))
|
|
||||||
return 0.0f;
|
|
||||||
|
|
||||||
if (botAI->IsTank(bot))
|
|
||||||
{
|
|
||||||
if (dynamic_cast<AttackRtiTargetAction*>(action))
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (botAI->IsHeal(bot) && (twistedNightmares || emeraldVigor))
|
|
||||||
if (dynamic_cast<DpsAssistAction*>(action) || dynamic_cast<AttackRtiTargetAction*>(action))
|
|
||||||
return 0.0f;
|
|
||||||
|
|
||||||
if (bot->HasAura(SPELL_DREAM_STATE) && !bot->HealthBelowPct(50))
|
|
||||||
{
|
|
||||||
if (dynamic_cast<IccValithriaDreamCloudAction*>(action))
|
|
||||||
return 1.0f; // Allow Dream Cloud action
|
|
||||||
else
|
|
||||||
return 0.0f; // Cancel all other actions when we need to handle Dream Cloud
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1.0f;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
//SINDRAGOSA
|
|
||||||
|
|
||||||
float IccSindragosaMultiplier::GetValue(Action* action)
|
|
||||||
{
|
|
||||||
Unit* boss = bot->FindNearestCreature(NPC_SINDRAGOSA, 200.0f);
|
|
||||||
if (!boss)
|
|
||||||
return 1.0f;
|
|
||||||
Aura* aura = botAI->GetAura("Unchained Magic", bot, false, true);
|
|
||||||
|
|
||||||
Difficulty diff = bot->GetRaidDifficulty();
|
|
||||||
|
|
||||||
if (boss->HealthBelowPct(95))
|
|
||||||
{
|
|
||||||
if (dynamic_cast<CombatFormationMoveAction*>(action) || dynamic_cast<FleeAction*>(action) ||
|
|
||||||
dynamic_cast<FollowAction*>(action) || dynamic_cast<CastStarfallAction*>(action))
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (aura && (diff == RAID_DIFFICULTY_10MAN_HEROIC || diff == RAID_DIFFICULTY_25MAN_HEROIC) &&
|
|
||||||
!dynamic_cast<IccSindragosaFrostBombAction*>(action))
|
|
||||||
{
|
|
||||||
if (dynamic_cast<MovementAction*>(action) || dynamic_cast<IccSindragosaUnchainedMagicAction*>(action))
|
|
||||||
return 1.0f;
|
|
||||||
else
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if boss is casting blistering cold (using both normal and heroic spell IDs)
|
|
||||||
if (boss->HasUnitState(UNIT_STATE_CASTING) &&
|
|
||||||
(boss->FindCurrentSpellBySpellId(70123) || boss->FindCurrentSpellBySpellId(71047) ||
|
|
||||||
boss->FindCurrentSpellBySpellId(71048) || boss->FindCurrentSpellBySpellId(71049)))
|
|
||||||
{
|
|
||||||
// If this is the blistering cold action, give it highest priority
|
|
||||||
if (dynamic_cast<IccSindragosaBlisteringColdAction*>(action) ||
|
|
||||||
dynamic_cast<HealPartyMemberAction*>(action) ||
|
|
||||||
dynamic_cast<ReachPartyMemberToHealAction*>(action) ||
|
|
||||||
dynamic_cast<IccSindragosaTankSwapPositionAction*>(action))
|
|
||||||
return 1.0f;
|
|
||||||
|
|
||||||
// Disable all other actions while blistering cold is casting
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Highest priority if we have beacon
|
|
||||||
if (bot->HasAura(SPELL_FROST_BEACON))
|
|
||||||
{
|
|
||||||
if (dynamic_cast<IccSindragosaFrostBeaconAction*>(action))
|
|
||||||
return 1.0f;
|
|
||||||
else
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
Group* group = bot->GetGroup();
|
|
||||||
// Check if anyone in group has Frost Beacon (SPELL_FROST_BEACON)
|
|
||||||
bool anyoneHasFrostBeacon = false;
|
|
||||||
|
|
||||||
if (group)
|
|
||||||
{
|
|
||||||
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
|
||||||
{
|
|
||||||
Player* member = ref->GetSource();
|
|
||||||
if (member && member->IsAlive() && member->HasAura(SPELL_FROST_BEACON))
|
|
||||||
{
|
|
||||||
anyoneHasFrostBeacon = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (anyoneHasFrostBeacon && boss &&
|
|
||||||
boss->GetExactDist2d(ICC_SINDRAGOSA_FLYING_POSITION.GetPositionX(),
|
|
||||||
ICC_SINDRAGOSA_FLYING_POSITION.GetPositionY()) < 30.0f &&
|
|
||||||
!boss->HealthBelowPct(25) && !boss->HealthAbovePct(99))
|
|
||||||
{
|
|
||||||
if (dynamic_cast<IccSindragosaFrostBeaconAction*>(action))
|
|
||||||
return 1.0f;
|
|
||||||
else
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (anyoneHasFrostBeacon && !botAI->IsMainTank(bot))
|
|
||||||
{
|
|
||||||
if (dynamic_cast<IccSindragosaGroupPositionAction*>(action))
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (botAI->IsMainTank(bot))
|
|
||||||
{
|
|
||||||
Aura* aura = botAI->GetAura("mystic buffet", bot, false, true);
|
|
||||||
if (aura && aura->GetStackAmount() >= 6)
|
|
||||||
{
|
|
||||||
if (dynamic_cast<MovementAction*>(action))
|
|
||||||
return 1.0f;
|
|
||||||
else
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!botAI->IsTank(bot) && boss && boss->HealthBelowPct(35))
|
|
||||||
{
|
|
||||||
if (dynamic_cast<IccSindragosaGroupPositionAction*>(action))
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (boss && botAI->IsTank(bot))
|
|
||||||
{
|
|
||||||
if (boss->HealthBelowPct(35))
|
|
||||||
{
|
|
||||||
if (dynamic_cast<IccSindragosaTankSwapPositionAction*>(action) || dynamic_cast<TankFaceAction*>(action) ||
|
|
||||||
dynamic_cast<AttackAction*>(action) || dynamic_cast<MovementAction*>(action))
|
|
||||||
return 1.0f;
|
|
||||||
else
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (boss && boss->GetExactDist2d(ICC_SINDRAGOSA_FLYING_POSITION.GetPositionX(), ICC_SINDRAGOSA_FLYING_POSITION.GetPositionY()) < 30.0f && !boss->HealthBelowPct(25) && !boss->HealthAbovePct(99))
|
|
||||||
{
|
|
||||||
if (dynamic_cast<IccSindragosaFrostBombAction*>(action))
|
|
||||||
return 1.0f;
|
|
||||||
|
|
||||||
if (dynamic_cast<FollowAction*>(action) || dynamic_cast<IccSindragosaBlisteringColdAction*>(action) ||
|
|
||||||
dynamic_cast<IccSindragosaChilledToTheBoneAction*>(action) || dynamic_cast<IccSindragosaMysticBuffetAction*>(action) ||
|
|
||||||
dynamic_cast<IccSindragosaFrostBeaconAction*>(action) || dynamic_cast<IccSindragosaUnchainedMagicAction*>(action) ||
|
|
||||||
dynamic_cast<FleeAction*>(action) || dynamic_cast<CastDisengageAction*>(action) || dynamic_cast<PetAttackAction*>(action) ||
|
|
||||||
dynamic_cast<IccSindragosaGroupPositionAction*>(action) || dynamic_cast<TankAssistAction*>(action) ||
|
|
||||||
dynamic_cast<DpsAoeAction*>(action) || dynamic_cast<CastHurricaneAction*>(action) ||
|
|
||||||
dynamic_cast<CastVolleyAction*>(action) || dynamic_cast<CastBlizzardAction*>(action) ||
|
|
||||||
dynamic_cast<CastStarfallAction*>(action) || dynamic_cast<FanOfKnivesAction*>(action) ||
|
|
||||||
dynamic_cast<CastWhirlwindAction*>(action) || dynamic_cast<CastMindSearAction*>(action) ||
|
|
||||||
dynamic_cast<CastMagmaTotemAction*>(action) || dynamic_cast<CastConsecrationAction*>(action) ||
|
|
||||||
dynamic_cast<CastFlamestrikeAction*>(action) || dynamic_cast<CastExplosiveTrapAction*>(action) ||
|
|
||||||
dynamic_cast<CastExplosiveShotBaseAction*>(action))
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
float IccLichKingAddsMultiplier::GetValue(Action* action)
|
|
||||||
{
|
|
||||||
Unit* terenasMenethilHC = bot->FindNearestCreature(NPC_TERENAS_MENETHIL_HC, 55.0f);
|
|
||||||
|
|
||||||
if (!terenasMenethilHC)
|
|
||||||
if (dynamic_cast<CastStarfallAction*>(action))
|
|
||||||
return 0.0f;
|
|
||||||
|
|
||||||
if (terenasMenethilHC)
|
|
||||||
{
|
|
||||||
Unit* mainTank = AI_VALUE(Unit*, "main tank");
|
|
||||||
|
|
||||||
if (!botAI->IsMainTank(bot) && mainTank && bot->GetExactDist2d(mainTank->GetPositionX(), mainTank->GetPositionY()) < 2.0f)
|
|
||||||
{
|
|
||||||
if (dynamic_cast<MovementAction*>(action))
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (botAI->IsMelee(bot) || (bot->getClass() == CLASS_WARLOCK))
|
|
||||||
{
|
|
||||||
if (dynamic_cast<MovementAction*>(action) || dynamic_cast<IccLichKingAddsAction*>(action))
|
|
||||||
return 1.0f;
|
|
||||||
else
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dynamic_cast<CombatFormationMoveAction*>(action) || dynamic_cast<FollowAction*>(action) ||
|
|
||||||
dynamic_cast<FleeAction*>(action) || dynamic_cast<CastBlinkBackAction*>(action) ||
|
|
||||||
dynamic_cast<CastDisengageAction*>(action) || dynamic_cast<CastChargeAction*>(action) ||
|
|
||||||
dynamic_cast<CastFeralChargeBearAction*>(action) || dynamic_cast<CastIceBlockAction*>(action) ||
|
|
||||||
dynamic_cast<CastRevivePetAction*>(action) || dynamic_cast<TankAssistAction*>(action))
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
Unit* boss = AI_VALUE2(Unit*, "find target", "the lich king");
|
|
||||||
if (!boss)
|
|
||||||
return 1.0f;
|
|
||||||
|
|
||||||
// Handle cure actions
|
|
||||||
if (dynamic_cast<CurePartyMemberAction*>(action) || dynamic_cast<CastCleanseDiseaseAction*>(action) ||
|
|
||||||
dynamic_cast<CastCleanseDiseaseOnPartyAction*>(action) ||
|
|
||||||
dynamic_cast<CastCleanseSpiritCurseOnPartyAction*>(action) || dynamic_cast<CastCleanseSpiritAction*>(action))
|
|
||||||
{
|
|
||||||
Group* group = bot->GetGroup();
|
|
||||||
if (!group)
|
|
||||||
return 1.0f;
|
|
||||||
|
|
||||||
// Check if any bot in the group has plague
|
|
||||||
bool anyBotHasPlague = false;
|
|
||||||
ObjectGuid plaguedPlayerGuid; // Track who has plague
|
|
||||||
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
|
||||||
{
|
|
||||||
if (Player* member = ref->GetSource())
|
|
||||||
{
|
|
||||||
if (botAI->HasAura("Necrotic Plague", member))
|
|
||||||
{
|
|
||||||
anyBotHasPlague = true;
|
|
||||||
plaguedPlayerGuid = member->GetGUID(); // Changed from GetObjectGuid()
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32 currentTime = getMSTime();
|
|
||||||
|
|
||||||
// Reset state if no one has plague
|
|
||||||
if (!anyBotHasPlague)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(g_plagueMutex); // Properly initialized
|
|
||||||
g_plagueTimes.clear();
|
|
||||||
g_allowCure.clear();
|
|
||||||
return 1.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
{ // New scope for lock_guard
|
|
||||||
std::lock_guard<std::mutex> lock(g_plagueMutex); // Properly initialized
|
|
||||||
|
|
||||||
// Start timer if this is a new plague
|
|
||||||
if (g_plagueTimes.find(plaguedPlayerGuid) == g_plagueTimes.end())
|
|
||||||
{
|
|
||||||
g_plagueTimes[plaguedPlayerGuid] = currentTime;
|
|
||||||
g_allowCure[plaguedPlayerGuid] = false;
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Once we allow cure, keep allowing it until plague is gone
|
|
||||||
if (g_allowCure[plaguedPlayerGuid])
|
|
||||||
{
|
|
||||||
return 1.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if enough time has passed (2,5 seconds)
|
|
||||||
if (currentTime - g_plagueTimes[plaguedPlayerGuid] >= 2500)
|
|
||||||
{
|
|
||||||
g_allowCure[plaguedPlayerGuid] = true;
|
|
||||||
return 1.0f;
|
|
||||||
}
|
|
||||||
} // lock_guard is automatically released here
|
|
||||||
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dynamic_cast<FleeAction*>(action) && (bot->getClass() != CLASS_HUNTER))
|
|
||||||
return 0.0f;
|
|
||||||
|
|
||||||
if (dynamic_cast<CombatFormationMoveAction*>(action) || dynamic_cast<FollowAction*>(action) ||
|
|
||||||
dynamic_cast<CastBlinkBackAction*>(action) || dynamic_cast<CastDisengageAction*>(action))
|
|
||||||
return 0.0f;
|
|
||||||
|
|
||||||
if (boss && !boss->HealthBelowPct(71))
|
|
||||||
{
|
|
||||||
if (!botAI->IsTank(bot))
|
|
||||||
if (dynamic_cast<CastConsecrationAction*>(action))
|
|
||||||
return 0.0f;
|
|
||||||
|
|
||||||
if (dynamic_cast<DpsAoeAction*>(action) || dynamic_cast<CastHurricaneAction*>(action) ||
|
|
||||||
dynamic_cast<CastVolleyAction*>(action) || dynamic_cast<CastBlizzardAction*>(action) ||
|
|
||||||
dynamic_cast<CastStarfallAction*>(action) || dynamic_cast<FanOfKnivesAction*>(action) ||
|
|
||||||
dynamic_cast<CastWhirlwindAction*>(action) || dynamic_cast<CastMindSearAction*>(action) ||
|
|
||||||
dynamic_cast<CastMagmaTotemAction*>(action) || dynamic_cast<CastFlamestrikeAction*>(action) ||
|
|
||||||
dynamic_cast<CastExplosiveTrapAction*>(action) || dynamic_cast<CastExplosiveShotBaseAction*>(action))
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
Unit* currentTarget = AI_VALUE(Unit*, "current target");
|
|
||||||
|
|
||||||
bool hasWinterAura = false;
|
|
||||||
if (boss && (boss->HasAura(SPELL_REMORSELESS_WINTER1) || boss->HasAura(SPELL_REMORSELESS_WINTER2) ||
|
|
||||||
boss->HasAura(SPELL_REMORSELESS_WINTER3) || boss->HasAura(SPELL_REMORSELESS_WINTER4)))
|
|
||||||
hasWinterAura = true;
|
|
||||||
|
|
||||||
bool hasWinter2Aura = false;
|
|
||||||
if (boss && (boss->HasAura(SPELL_REMORSELESS_WINTER5) || boss->HasAura(SPELL_REMORSELESS_WINTER6) ||
|
|
||||||
boss->HasAura(SPELL_REMORSELESS_WINTER7) || boss->HasAura(SPELL_REMORSELESS_WINTER8)))
|
|
||||||
hasWinter2Aura = true;
|
|
||||||
|
|
||||||
bool isCasting = false;
|
|
||||||
if (boss && boss->HasUnitState(UNIT_STATE_CASTING))
|
|
||||||
isCasting = true;
|
|
||||||
|
|
||||||
bool isWinter = false;
|
|
||||||
if (boss && (boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER1) ||
|
|
||||||
boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER2) ||
|
|
||||||
boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER5) ||
|
|
||||||
boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER6) ||
|
|
||||||
boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER3) ||
|
|
||||||
boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER4) ||
|
|
||||||
boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER7) ||
|
|
||||||
boss->FindCurrentSpellBySpellId(SPELL_REMORSELESS_WINTER8)))
|
|
||||||
isWinter = true;
|
|
||||||
|
|
||||||
if (hasWinterAura || hasWinter2Aura || (isCasting && isWinter))
|
|
||||||
{
|
|
||||||
if (dynamic_cast<IccLichKingWinterAction*>(action) || dynamic_cast<SetFacingTargetAction*>(action))
|
|
||||||
return 1.0f;
|
|
||||||
|
|
||||||
if (botAI->IsAssistTank(bot) && dynamic_cast<TankAssistAction*>(action))
|
|
||||||
return 0.0f;
|
|
||||||
|
|
||||||
if (dynamic_cast<IccLichKingAddsAction*>(action))
|
|
||||||
return 0.0f;
|
|
||||||
|
|
||||||
if (currentTarget && boss && bot->GetDistance2d(boss->GetPositionX(), boss->GetPositionY()) > 50.0f && currentTarget == boss)
|
|
||||||
{
|
|
||||||
if (dynamic_cast<AttackRtiTargetAction*>(action) || dynamic_cast<ReachSpellAction*>(action) ||
|
|
||||||
dynamic_cast<ReachMeleeAction*>(action) || dynamic_cast<ReachTargetAction*>(action) ||
|
|
||||||
dynamic_cast<TankAssistAction*>(action) || dynamic_cast<DpsAssistAction*>(action) ||
|
|
||||||
dynamic_cast<MovementAction*>(action))
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentTarget && (currentTarget->GetEntry() == NPC_ICE_SPHERE1 || currentTarget->GetEntry() == NPC_ICE_SPHERE2 ||
|
|
||||||
currentTarget->GetEntry() == NPC_ICE_SPHERE3 || currentTarget->GetEntry() == NPC_ICE_SPHERE4))
|
|
||||||
{
|
|
||||||
if (dynamic_cast<MovementAction*>(action) || dynamic_cast<ReachMeleeAction*>(action) ||
|
|
||||||
dynamic_cast<TankAssistAction*>(action))
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if (botAI->IsRanged(bot) && !botAI->GetAura("Harvest Soul", bot, false, false))
|
|
||||||
{
|
|
||||||
// Check for defile presence
|
|
||||||
GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs");
|
|
||||||
bool defilePresent = false;
|
|
||||||
for (auto& npc : npcs)
|
|
||||||
{
|
|
||||||
Unit* unit = botAI->GetUnit(npc);
|
|
||||||
if (unit && unit->IsAlive() && unit->GetEntry() == DEFILE_NPC_ID) // Defile entry
|
|
||||||
{
|
|
||||||
defilePresent = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only disable movement if defile is present
|
|
||||||
if (defilePresent && (
|
|
||||||
dynamic_cast<CombatFormationMoveAction*>(action) ||
|
|
||||||
dynamic_cast<FollowAction*>(action) ||
|
|
||||||
dynamic_cast<FleeAction*>(action) ||
|
|
||||||
dynamic_cast<MoveRandomAction*>(action) ||
|
|
||||||
dynamic_cast<MoveFromGroupAction*>(action)))
|
|
||||||
{
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (botAI->IsAssistTank(bot) && boss && !boss->HealthBelowPct(71) && currentTarget == boss)
|
|
||||||
{
|
|
||||||
if (dynamic_cast<AttackRtiTargetAction*>(action))
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1.0f;
|
|
||||||
}
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
#ifndef _PLAYERBOT_RAIDICCSCRIPTS_H
|
|
||||||
#define _PLAYERBOT_RAIDICCSCRIPTS_H
|
|
||||||
|
|
||||||
#include "../../../../src/server/scripts/Northrend/IcecrownCitadel/icecrown_citadel.h"
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@ -19,7 +19,7 @@
|
|||||||
#include "RaidVoAStrategy.h"
|
#include "RaidVoAStrategy.h"
|
||||||
#include "RaidUlduarStrategy.h"
|
#include "RaidUlduarStrategy.h"
|
||||||
#include "RaidOnyxiaStrategy.h"
|
#include "RaidOnyxiaStrategy.h"
|
||||||
#include "RaidIccStrategy.h"
|
#include "ICCStrategy.h"
|
||||||
|
|
||||||
class RaidStrategyContext : public NamedObjectContext<Strategy>
|
class RaidStrategyContext : public NamedObjectContext<Strategy>
|
||||||
{
|
{
|
||||||
|
|||||||
@ -20,6 +20,7 @@
|
|||||||
#include "Player.h"
|
#include "Player.h"
|
||||||
#include "PlayerbotAI.h"
|
#include "PlayerbotAI.h"
|
||||||
#include "PlayerbotAIConfig.h"
|
#include "PlayerbotAIConfig.h"
|
||||||
|
#include "PlayerbotTextMgr.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
#include "Position.h"
|
#include "Position.h"
|
||||||
#include "QuestDef.h"
|
#include "QuestDef.h"
|
||||||
@ -310,7 +311,10 @@ bool NewRpgBaseAction::InteractWithNpcOrGameObjectForQuest(ObjectGuid guid)
|
|||||||
{
|
{
|
||||||
AcceptQuest(quest, guid);
|
AcceptQuest(quest, guid);
|
||||||
if (botAI->GetMaster())
|
if (botAI->GetMaster())
|
||||||
botAI->TellMasterNoFacing("Quest accepted " + ChatHelper::FormatQuest(quest));
|
botAI->TellMasterNoFacing(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"new_rpg_quest_accepted",
|
||||||
|
"Quest accepted %quest",
|
||||||
|
{{"%quest", ChatHelper::FormatQuest(quest)}}));
|
||||||
BroadcastHelper::BroadcastQuestAccepted(botAI, bot, quest);
|
BroadcastHelper::BroadcastQuestAccepted(botAI, bot, quest);
|
||||||
botAI->rpgStatistic.questAccepted++;
|
botAI->rpgStatistic.questAccepted++;
|
||||||
LOG_DEBUG("playerbots", "[New RPG] {} accept quest {}", bot->GetName(), quest->GetQuestId());
|
LOG_DEBUG("playerbots", "[New RPG] {} accept quest {}", bot->GetName(), quest->GetQuestId());
|
||||||
@ -319,7 +323,10 @@ bool NewRpgBaseAction::InteractWithNpcOrGameObjectForQuest(ObjectGuid guid)
|
|||||||
{
|
{
|
||||||
TurnInQuest(quest, guid);
|
TurnInQuest(quest, guid);
|
||||||
if (botAI->GetMaster())
|
if (botAI->GetMaster())
|
||||||
botAI->TellMasterNoFacing("Quest rewarded " + ChatHelper::FormatQuest(quest));
|
botAI->TellMasterNoFacing(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"new_rpg_quest_rewarded",
|
||||||
|
"Quest rewarded %quest",
|
||||||
|
{{"%quest", ChatHelper::FormatQuest(quest)}}));
|
||||||
BroadcastHelper::BroadcastQuestTurnedIn(botAI, bot, quest);
|
BroadcastHelper::BroadcastQuestTurnedIn(botAI, bot, quest);
|
||||||
botAI->rpgStatistic.questRewarded++;
|
botAI->rpgStatistic.questRewarded++;
|
||||||
LOG_DEBUG("playerbots", "[New RPG] {} turned in quest {}", bot->GetName(), quest->GetQuestId());
|
LOG_DEBUG("playerbots", "[New RPG] {} turned in quest {}", bot->GetName(), quest->GetQuestId());
|
||||||
@ -599,7 +606,10 @@ bool NewRpgBaseAction::OrganizeQuestLog()
|
|||||||
packet << (uint8)i;
|
packet << (uint8)i;
|
||||||
bot->GetSession()->HandleQuestLogRemoveQuest(packet);
|
bot->GetSession()->HandleQuestLogRemoveQuest(packet);
|
||||||
if (botAI->GetMaster())
|
if (botAI->GetMaster())
|
||||||
botAI->TellMasterNoFacing("Quest dropped " + ChatHelper::FormatQuest(quest));
|
botAI->TellMasterNoFacing(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"new_rpg_quest_dropped",
|
||||||
|
"Quest dropped %quest",
|
||||||
|
{{"%quest", ChatHelper::FormatQuest(quest)}}));
|
||||||
botAI->rpgStatistic.questDropped++;
|
botAI->rpgStatistic.questDropped++;
|
||||||
dropped++;
|
dropped++;
|
||||||
}
|
}
|
||||||
@ -626,7 +636,10 @@ bool NewRpgBaseAction::OrganizeQuestLog()
|
|||||||
packet << (uint8)i;
|
packet << (uint8)i;
|
||||||
bot->GetSession()->HandleQuestLogRemoveQuest(packet);
|
bot->GetSession()->HandleQuestLogRemoveQuest(packet);
|
||||||
if (botAI->GetMaster())
|
if (botAI->GetMaster())
|
||||||
botAI->TellMasterNoFacing("Quest dropped " + ChatHelper::FormatQuest(quest));
|
botAI->TellMasterNoFacing(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"new_rpg_quest_dropped",
|
||||||
|
"Quest dropped %quest",
|
||||||
|
{{"%quest", ChatHelper::FormatQuest(quest)}}));
|
||||||
botAI->rpgStatistic.questDropped++;
|
botAI->rpgStatistic.questDropped++;
|
||||||
dropped++;
|
dropped++;
|
||||||
}
|
}
|
||||||
@ -648,7 +661,10 @@ bool NewRpgBaseAction::OrganizeQuestLog()
|
|||||||
packet << (uint8)i;
|
packet << (uint8)i;
|
||||||
bot->GetSession()->HandleQuestLogRemoveQuest(packet);
|
bot->GetSession()->HandleQuestLogRemoveQuest(packet);
|
||||||
if (botAI->GetMaster())
|
if (botAI->GetMaster())
|
||||||
botAI->TellMasterNoFacing("Quest dropped " + ChatHelper::FormatQuest(quest));
|
botAI->TellMasterNoFacing(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"new_rpg_quest_dropped",
|
||||||
|
"Quest dropped %quest",
|
||||||
|
{{"%quest", ChatHelper::FormatQuest(quest)}}));
|
||||||
botAI->rpgStatistic.questDropped++;
|
botAI->rpgStatistic.questDropped++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -13,6 +13,7 @@
|
|||||||
#include "GuildCreateActions.h"
|
#include "GuildCreateActions.h"
|
||||||
#include "LastMovementValue.h"
|
#include "LastMovementValue.h"
|
||||||
#include "MovementActions.h"
|
#include "MovementActions.h"
|
||||||
|
#include "PlayerbotTextMgr.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
#include "PossibleRpgTargetsValue.h"
|
#include "PossibleRpgTargetsValue.h"
|
||||||
#include "SocialMgr.h"
|
#include "SocialMgr.h"
|
||||||
@ -430,12 +431,15 @@ bool RpgTradeUsefulAction::Execute(Event /*event*/)
|
|||||||
if (bot->GetTradeData() && bot->GetTradeData()->HasItem(item->GetGUID()))
|
if (bot->GetTradeData() && bot->GetTradeData()->HasItem(item->GetGUID()))
|
||||||
{
|
{
|
||||||
if (bot->GetGroup() && bot->GetGroup()->IsMember(guidP) && botAI->HasRealPlayerMaster())
|
if (bot->GetGroup() && bot->GetGroup()->IsMember(guidP) && botAI->HasRealPlayerMaster())
|
||||||
botAI->TellMasterNoFacing(
|
botAI->TellMasterNoFacing(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
"You can use this " + chat->FormatItem(item->GetTemplate()) + " better than me, " +
|
"rpg_item_better_for_player",
|
||||||
guidP.GetPlayer()->GetName() /*chat->FormatWorldobject(guidP.GetPlayer())*/ + ".");
|
"You can use this %item better than me, %player.",
|
||||||
|
{{"%item", chat->FormatItem(item->GetTemplate())}, {"%player", guidP.GetPlayer()->GetName()}}));
|
||||||
else
|
else
|
||||||
bot->Say("You can use this " + chat->FormatItem(item->GetTemplate()) + " better than me, " +
|
bot->Say(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
player->GetName() /*chat->FormatWorldobject(player)*/ + ".",
|
"rpg_item_better_for_player",
|
||||||
|
"You can use this %item better than me, %player.",
|
||||||
|
{{"%item", chat->FormatItem(item->GetTemplate())}, {"%player", player->GetName()}}),
|
||||||
(bot->GetTeamId() == TEAM_ALLIANCE ? LANG_COMMON : LANG_ORCISH));
|
(bot->GetTeamId() == TEAM_ALLIANCE ? LANG_COMMON : LANG_ORCISH));
|
||||||
|
|
||||||
if (!urand(0, 4) || items.size() < 2)
|
if (!urand(0, 4) || items.size() < 2)
|
||||||
@ -449,7 +453,10 @@ bool RpgTradeUsefulAction::Execute(Event /*event*/)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
bot->Say("Start trade with" + chat->FormatWorldobject(player),
|
bot->Say(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"rpg_start_trade_with_player",
|
||||||
|
"Start trade with %player",
|
||||||
|
{{"%player", chat->FormatWorldobject(player)}}),
|
||||||
(bot->GetTeamId() == TEAM_ALLIANCE ? LANG_COMMON : LANG_ORCISH));
|
(bot->GetTeamId() == TEAM_ALLIANCE ? LANG_COMMON : LANG_ORCISH));
|
||||||
|
|
||||||
botAI->SetNextCheckDelay(sPlayerbotAIConfig.rpgDelay);
|
botAI->SetNextCheckDelay(sPlayerbotAIConfig.rpgDelay);
|
||||||
|
|||||||
@ -19,7 +19,7 @@
|
|||||||
#include "Ai/Raid/VaultOfArchavon/RaidVoAActionContext.h"
|
#include "Ai/Raid/VaultOfArchavon/RaidVoAActionContext.h"
|
||||||
#include "Ai/Raid/Ulduar/RaidUlduarActionContext.h"
|
#include "Ai/Raid/Ulduar/RaidUlduarActionContext.h"
|
||||||
#include "Ai/Raid/Onyxia/RaidOnyxiaActionContext.h"
|
#include "Ai/Raid/Onyxia/RaidOnyxiaActionContext.h"
|
||||||
#include "Ai/Raid/Icecrown/RaidIccActionContext.h"
|
#include "Ai/Raid/ICC/ICCActionContext.h"
|
||||||
#include "Ai/Dungeon/TbcDungeonActionContext.h"
|
#include "Ai/Dungeon/TbcDungeonActionContext.h"
|
||||||
#include "Ai/Dungeon/WotlkDungeonActionContext.h"
|
#include "Ai/Dungeon/WotlkDungeonActionContext.h"
|
||||||
|
|
||||||
|
|||||||
@ -19,7 +19,7 @@
|
|||||||
#include "Ai/Raid/VaultOfArchavon/RaidVoATriggerContext.h"
|
#include "Ai/Raid/VaultOfArchavon/RaidVoATriggerContext.h"
|
||||||
#include "Ai/Raid/Ulduar/RaidUlduarTriggerContext.h"
|
#include "Ai/Raid/Ulduar/RaidUlduarTriggerContext.h"
|
||||||
#include "Ai/Raid/Onyxia/RaidOnyxiaTriggerContext.h"
|
#include "Ai/Raid/Onyxia/RaidOnyxiaTriggerContext.h"
|
||||||
#include "Ai/Raid/Icecrown/RaidIccTriggerContext.h"
|
#include "Ai/Raid/ICC/ICCTriggerContext.h"
|
||||||
#include "Ai/Dungeon/TbcDungeonTriggerContext.h"
|
#include "Ai/Dungeon/TbcDungeonTriggerContext.h"
|
||||||
#include "Ai/Dungeon/WotlkDungeonTriggerContext.h"
|
#include "Ai/Dungeon/WotlkDungeonTriggerContext.h"
|
||||||
|
|
||||||
|
|||||||
@ -91,9 +91,6 @@ uint8 AiFactory::GetPlayerSpecTab(Player* bot)
|
|||||||
case CLASS_WARLOCK:
|
case CLASS_WARLOCK:
|
||||||
tab = WARLOCK_TAB_DEMONOLOGY;
|
tab = WARLOCK_TAB_DEMONOLOGY;
|
||||||
break;
|
break;
|
||||||
case CLASS_SHAMAN:
|
|
||||||
tab = SHAMAN_TAB_ELEMENTAL;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return tab;
|
return tab;
|
||||||
@ -292,24 +289,24 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
|
|||||||
if (tab == PRIEST_TAB_SHADOW)
|
if (tab == PRIEST_TAB_SHADOW)
|
||||||
engine->addStrategiesNoInit("dps", "shadow debuff", "shadow aoe", nullptr);
|
engine->addStrategiesNoInit("dps", "shadow debuff", "shadow aoe", nullptr);
|
||||||
else if (tab == PRIEST_TAB_DISCIPLINE)
|
else if (tab == PRIEST_TAB_DISCIPLINE)
|
||||||
engine->addStrategiesNoInit("heal", nullptr);
|
engine->addStrategy("heal", false);
|
||||||
else
|
else // if (tab == PRIEST_TAB_HOLY)
|
||||||
engine->addStrategiesNoInit("holy heal", nullptr);
|
engine->addStrategy("holy heal", false);
|
||||||
|
|
||||||
engine->addStrategiesNoInit("dps assist", "cure", nullptr);
|
engine->addStrategiesNoInit("dps assist", "cure", nullptr);
|
||||||
break;
|
break;
|
||||||
case CLASS_MAGE:
|
case CLASS_MAGE:
|
||||||
if (tab == MAGE_TAB_ARCANE)
|
if (tab == MAGE_TAB_ARCANE)
|
||||||
engine->addStrategiesNoInit("arcane", nullptr);
|
engine->addStrategiesNoInit("arcane", "bdps", nullptr);
|
||||||
else if (tab == MAGE_TAB_FIRE)
|
else if (tab == MAGE_TAB_FIRE)
|
||||||
{
|
{
|
||||||
if (player->HasSpell(44614) /*Frostfire Bolt*/ && player->HasAura(15047) /*Ice Shards*/)
|
if (player->HasSpell(44614) /*Frostfire Bolt*/ && player->HasAura(15047) /*Ice Shards*/)
|
||||||
engine->addStrategiesNoInit("frostfire", nullptr);
|
engine->addStrategiesNoInit("frostfire", "bdps", nullptr);
|
||||||
else
|
else
|
||||||
engine->addStrategiesNoInit("fire", nullptr);
|
engine->addStrategiesNoInit("fire", "bdps", nullptr);
|
||||||
}
|
}
|
||||||
else
|
else // if (tab == MAGE_TAB_FROST)
|
||||||
engine->addStrategiesNoInit("frost", nullptr);
|
engine->addStrategiesNoInit("frost", "bmana", nullptr);
|
||||||
|
|
||||||
engine->addStrategiesNoInit("dps", "dps assist", "cure", "cc", "aoe", nullptr);
|
engine->addStrategiesNoInit("dps", "dps assist", "cure", "cc", "aoe", nullptr);
|
||||||
break;
|
break;
|
||||||
@ -318,7 +315,7 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
|
|||||||
engine->addStrategiesNoInit("tank", "tank assist", "pull", "pull back", "aoe", nullptr);
|
engine->addStrategiesNoInit("tank", "tank assist", "pull", "pull back", "aoe", nullptr);
|
||||||
else if (tab == WARRIOR_TAB_ARMS || !player->HasSpell(1680)) // Whirlwind
|
else if (tab == WARRIOR_TAB_ARMS || !player->HasSpell(1680)) // Whirlwind
|
||||||
engine->addStrategiesNoInit("arms", "aoe", "dps assist", nullptr);
|
engine->addStrategiesNoInit("arms", "aoe", "dps assist", nullptr);
|
||||||
else
|
else // if (tab == WARRIOR_TAB_FURY)
|
||||||
engine->addStrategiesNoInit("fury", "aoe", "dps assist", nullptr);
|
engine->addStrategiesNoInit("fury", "aoe", "dps assist", nullptr);
|
||||||
break;
|
break;
|
||||||
case CLASS_SHAMAN:
|
case CLASS_SHAMAN:
|
||||||
@ -326,7 +323,7 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
|
|||||||
engine->addStrategiesNoInit("ele", "stoneskin", "wrath", "mana spring", "wrath of air", nullptr);
|
engine->addStrategiesNoInit("ele", "stoneskin", "wrath", "mana spring", "wrath of air", nullptr);
|
||||||
else if (tab == SHAMAN_TAB_RESTORATION)
|
else if (tab == SHAMAN_TAB_RESTORATION)
|
||||||
engine->addStrategiesNoInit("resto", "stoneskin", "flametongue", "mana spring", "wrath of air", nullptr);
|
engine->addStrategiesNoInit("resto", "stoneskin", "flametongue", "mana spring", "wrath of air", nullptr);
|
||||||
else
|
else // if (tab == SHAMAN_TAB_ENHANCEMENT)
|
||||||
engine->addStrategiesNoInit("enh", "strength of earth", "magma", "healing stream", "windfury", nullptr);
|
engine->addStrategiesNoInit("enh", "strength of earth", "magma", "healing stream", "windfury", nullptr);
|
||||||
|
|
||||||
engine->addStrategiesNoInit("dps assist", "cure", "aoe", nullptr);
|
engine->addStrategiesNoInit("dps assist", "cure", "aoe", nullptr);
|
||||||
@ -336,18 +333,15 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
|
|||||||
engine->addStrategiesNoInit("tank", "tank assist", "pull", "pull back", "bthreat", "barmor", "cure", nullptr);
|
engine->addStrategiesNoInit("tank", "tank assist", "pull", "pull back", "bthreat", "barmor", "cure", nullptr);
|
||||||
else if (tab == PALADIN_TAB_HOLY)
|
else if (tab == PALADIN_TAB_HOLY)
|
||||||
engine->addStrategiesNoInit("heal", "dps assist", "cure", "bcast", nullptr);
|
engine->addStrategiesNoInit("heal", "dps assist", "cure", "bcast", nullptr);
|
||||||
else
|
else // if (tab == PALADIN_TAB_RETRIBUTION)
|
||||||
engine->addStrategiesNoInit("dps", "dps assist", "cure", "baoe", nullptr);
|
engine->addStrategiesNoInit("dps", "dps assist", "cure", "baoe", nullptr);
|
||||||
break;
|
break;
|
||||||
case CLASS_DRUID:
|
case CLASS_DRUID:
|
||||||
if (tab == DRUID_TAB_BALANCE)
|
if (tab == DRUID_TAB_BALANCE)
|
||||||
{
|
engine->addStrategiesNoInit("caster", "cure", "caster aoe", "caster debuff", "dps assist", nullptr);
|
||||||
engine->addStrategiesNoInit("caster", "cure", "caster aoe", "dps assist", nullptr);
|
|
||||||
engine->addStrategy("caster debuff", false);
|
|
||||||
}
|
|
||||||
else if (tab == DRUID_TAB_RESTORATION)
|
else if (tab == DRUID_TAB_RESTORATION)
|
||||||
engine->addStrategiesNoInit("heal", "cure", "dps assist", nullptr);
|
engine->addStrategiesNoInit("heal", "cure", "dps assist", nullptr);
|
||||||
else
|
else // if (tab == DRUID_TAB_FERAL)
|
||||||
{
|
{
|
||||||
if (player->HasSpell(768) /*cat form*/ && !player->HasAura(16931) /*thick hide*/)
|
if (player->HasSpell(768) /*cat form*/ && !player->HasAura(16931) /*thick hide*/)
|
||||||
engine->addStrategiesNoInit("cat", "dps assist", nullptr);
|
engine->addStrategiesNoInit("cat", "dps assist", nullptr);
|
||||||
@ -357,18 +351,18 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
|
|||||||
break;
|
break;
|
||||||
case CLASS_HUNTER:
|
case CLASS_HUNTER:
|
||||||
if (tab == HUNTER_TAB_BEAST_MASTERY)
|
if (tab == HUNTER_TAB_BEAST_MASTERY)
|
||||||
engine->addStrategiesNoInit("bm", nullptr);
|
engine->addStrategy("bm", false);
|
||||||
else if (tab == HUNTER_TAB_MARKSMANSHIP)
|
else if (tab == HUNTER_TAB_MARKSMANSHIP)
|
||||||
engine->addStrategiesNoInit("mm", nullptr);
|
engine->addStrategy("mm", false);
|
||||||
else
|
else // if (tab == HUNTER_TAB_SURVIVAL)
|
||||||
engine->addStrategiesNoInit("surv", nullptr);
|
engine->addStrategy("surv", false);
|
||||||
|
|
||||||
engine->addStrategiesNoInit("cc", "dps assist", "aoe", "bdps", nullptr);
|
engine->addStrategiesNoInit("cc", "dps assist", "aoe", "bdps", nullptr);
|
||||||
break;
|
break;
|
||||||
case CLASS_ROGUE:
|
case CLASS_ROGUE:
|
||||||
if (tab == ROGUE_TAB_ASSASSINATION || tab == ROGUE_TAB_SUBTLETY)
|
if (tab == ROGUE_TAB_ASSASSINATION || tab == ROGUE_TAB_SUBTLETY)
|
||||||
engine->addStrategiesNoInit("melee", "dps assist", "aoe", nullptr);
|
engine->addStrategiesNoInit("melee", "dps assist", "aoe", nullptr);
|
||||||
else
|
else // if (tab == ROGUE_TAB_COMBAT)
|
||||||
engine->addStrategiesNoInit("dps", "dps assist", "aoe", nullptr);
|
engine->addStrategiesNoInit("dps", "dps assist", "aoe", nullptr);
|
||||||
break;
|
break;
|
||||||
case CLASS_WARLOCK:
|
case CLASS_WARLOCK:
|
||||||
@ -376,7 +370,7 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
|
|||||||
engine->addStrategiesNoInit("affli", "curse of agony", nullptr);
|
engine->addStrategiesNoInit("affli", "curse of agony", nullptr);
|
||||||
else if (tab == WARLOCK_TAB_DEMONOLOGY)
|
else if (tab == WARLOCK_TAB_DEMONOLOGY)
|
||||||
engine->addStrategiesNoInit("demo", "curse of agony", "meta melee", nullptr);
|
engine->addStrategiesNoInit("demo", "curse of agony", "meta melee", nullptr);
|
||||||
else
|
else // if (tab == WARLOCK_TAB_DESTRUCTION)
|
||||||
engine->addStrategiesNoInit("destro", "curse of elements", nullptr);
|
engine->addStrategiesNoInit("destro", "curse of elements", nullptr);
|
||||||
|
|
||||||
engine->addStrategiesNoInit("cc", "dps assist", "aoe", nullptr);
|
engine->addStrategiesNoInit("cc", "dps assist", "aoe", nullptr);
|
||||||
@ -386,7 +380,7 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
|
|||||||
engine->addStrategiesNoInit("blood", "tank assist", "pull", "pull back", nullptr);
|
engine->addStrategiesNoInit("blood", "tank assist", "pull", "pull back", nullptr);
|
||||||
else if (tab == DEATH_KNIGHT_TAB_FROST)
|
else if (tab == DEATH_KNIGHT_TAB_FROST)
|
||||||
engine->addStrategiesNoInit("frost", "frost aoe", "dps assist", nullptr);
|
engine->addStrategiesNoInit("frost", "frost aoe", "dps assist", nullptr);
|
||||||
else
|
else // if (tab == DEATH_KNIGHT_TAB_UNHOLY)
|
||||||
engine->addStrategiesNoInit("unholy", "unholy aoe", "dps assist", nullptr);
|
engine->addStrategiesNoInit("unholy", "unholy aoe", "dps assist", nullptr);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -434,7 +428,7 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
|
|||||||
case CLASS_SHAMAN:
|
case CLASS_SHAMAN:
|
||||||
{
|
{
|
||||||
if (tab == SHAMAN_TAB_RESTORATION)
|
if (tab == SHAMAN_TAB_RESTORATION)
|
||||||
engine->addStrategiesNoInit("caster", "caster aoe", "bmana", nullptr);
|
engine->addStrategiesNoInit("caster", "caster aoe", nullptr);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case CLASS_PALADIN:
|
case CLASS_PALADIN:
|
||||||
@ -527,11 +521,6 @@ void AiFactory::AddDefaultNonCombatStrategies(Player* player, PlayerbotAI* const
|
|||||||
nonCombatEngine->addStrategiesNoInit("bdps", "dps assist", "pet", nullptr);
|
nonCombatEngine->addStrategiesNoInit("bdps", "dps assist", "pet", nullptr);
|
||||||
break;
|
break;
|
||||||
case CLASS_SHAMAN:
|
case CLASS_SHAMAN:
|
||||||
if (tab == SHAMAN_TAB_ELEMENTAL || tab == SHAMAN_TAB_RESTORATION)
|
|
||||||
nonCombatEngine->addStrategy("bmana", false);
|
|
||||||
else
|
|
||||||
nonCombatEngine->addStrategy("bdps", false);
|
|
||||||
|
|
||||||
nonCombatEngine->addStrategiesNoInit("dps assist", "cure", nullptr);
|
nonCombatEngine->addStrategiesNoInit("dps assist", "cure", nullptr);
|
||||||
break;
|
break;
|
||||||
case CLASS_MAGE:
|
case CLASS_MAGE:
|
||||||
|
|||||||
@ -850,36 +850,36 @@ void PlayerbotFactory::Refresh()
|
|||||||
|
|
||||||
void PlayerbotFactory::InitConsumables()
|
void PlayerbotFactory::InitConsumables()
|
||||||
{
|
{
|
||||||
int specTab = AiFactory::GetPlayerSpecTab(bot);
|
uint8 specTab = AiFactory::GetPlayerSpecTab(bot);
|
||||||
std::vector<std::pair<uint32, uint32>> items;
|
std::vector<std::pair<uint32, uint32>> items;
|
||||||
|
|
||||||
switch (bot->getClass())
|
switch (bot->getClass())
|
||||||
{
|
{
|
||||||
case CLASS_PRIEST:
|
case CLASS_PRIEST:
|
||||||
{
|
{
|
||||||
// Discipline or Holy: Mana Oil
|
if (specTab == PRIEST_TAB_SHADOW)
|
||||||
if (specTab == 0 || specTab == 1)
|
|
||||||
{
|
{
|
||||||
std::vector<uint32> mana_oils = {BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL};
|
std::vector<uint32> wizard_oils = {
|
||||||
for (uint32 itemId : mana_oils)
|
BRILLIANT_WIZARD_OIL, SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL };
|
||||||
{
|
|
||||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
|
|
||||||
if (proto->RequiredLevel > level || level > 75)
|
|
||||||
continue;
|
|
||||||
items.push_back({itemId, 4});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Shadow: Wizard Oil
|
|
||||||
if (specTab == 2)
|
|
||||||
{
|
|
||||||
std::vector<uint32> wizard_oils = {BRILLIANT_WIZARD_OIL, SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL};
|
|
||||||
for (uint32 itemId : wizard_oils)
|
for (uint32 itemId : wizard_oils)
|
||||||
{
|
{
|
||||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
|
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
|
||||||
if (proto->RequiredLevel > level || level > 75)
|
if (proto->RequiredLevel > level || level > 75)
|
||||||
continue;
|
continue;
|
||||||
items.push_back({itemId, 4});
|
items.push_back({itemId, 2});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::vector<uint32> mana_oils = {
|
||||||
|
BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL };
|
||||||
|
for (uint32 itemId : mana_oils)
|
||||||
|
{
|
||||||
|
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
|
||||||
|
if (proto->RequiredLevel > level || level > 75)
|
||||||
|
continue;
|
||||||
|
items.push_back({itemId, 2});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -887,38 +887,41 @@ void PlayerbotFactory::InitConsumables()
|
|||||||
}
|
}
|
||||||
case CLASS_MAGE:
|
case CLASS_MAGE:
|
||||||
{
|
{
|
||||||
// Always Wizard Oil
|
std::vector<uint32> wizard_oils = {
|
||||||
std::vector<uint32> wizard_oils = {BRILLIANT_WIZARD_OIL, SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL};
|
BRILLIANT_WIZARD_OIL, SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL };
|
||||||
for (uint32 itemId : wizard_oils)
|
for (uint32 itemId : wizard_oils)
|
||||||
{
|
{
|
||||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
|
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
|
||||||
if (proto->RequiredLevel > level || level > 75)
|
if (proto->RequiredLevel > level || level > 75)
|
||||||
continue;
|
continue;
|
||||||
items.push_back({itemId, 4});
|
items.push_back({itemId, 2});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case CLASS_DRUID:
|
case CLASS_DRUID:
|
||||||
{
|
{
|
||||||
// Balance: Wizard Oil
|
if (specTab == DRUID_TAB_BALANCE)
|
||||||
if (specTab == 0)
|
|
||||||
{
|
{
|
||||||
std::vector<uint32> wizard_oils = {BRILLIANT_WIZARD_OIL, SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL};
|
std::vector<uint32> wizard_oils = {
|
||||||
|
BRILLIANT_WIZARD_OIL, SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL };
|
||||||
for (uint32 itemId : wizard_oils)
|
for (uint32 itemId : wizard_oils)
|
||||||
{
|
{
|
||||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
|
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
|
||||||
if (proto->RequiredLevel > level || level > 75)
|
if (proto->RequiredLevel > level || level > 75)
|
||||||
continue;
|
continue;
|
||||||
items.push_back({itemId, 4});
|
items.push_back({itemId, 2});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Feral: Sharpening Stones & Weightstones
|
else if (specTab == DRUID_TAB_FERAL)
|
||||||
else if (specTab == 1)
|
|
||||||
{
|
{
|
||||||
std::vector<uint32> sharpening_stones = {ADAMANTITE_SHARPENING_STONE, FEL_SHARPENING_STONE, DENSE_SHARPENING_STONE, SOLID_SHARPENING_STONE, HEAVY_SHARPENING_STONE, COARSE_SHARPENING_STONE, ROUGH_SHARPENING_STONE};
|
std::vector<uint32> sharpening_stones = {
|
||||||
std::vector<uint32> weightstones = {ADAMANTITE_WEIGHTSTONE, FEL_WEIGHTSTONE, DENSE_WEIGHTSTONE, SOLID_WEIGHTSTONE, HEAVY_WEIGHTSTONE, COARSE_WEIGHTSTONE, ROUGH_WEIGHTSTONE};
|
ADAMANTITE_SHARPENING_STONE, FEL_SHARPENING_STONE, DENSE_SHARPENING_STONE, SOLID_SHARPENING_STONE,
|
||||||
|
HEAVY_SHARPENING_STONE, COARSE_SHARPENING_STONE, ROUGH_SHARPENING_STONE };
|
||||||
|
std::vector<uint32> weightstones = {
|
||||||
|
ADAMANTITE_WEIGHTSTONE, FEL_WEIGHTSTONE, DENSE_WEIGHTSTONE, SOLID_WEIGHTSTONE,
|
||||||
|
HEAVY_WEIGHTSTONE, COARSE_WEIGHTSTONE, ROUGH_WEIGHTSTONE };
|
||||||
for (uint32 itemId : sharpening_stones)
|
for (uint32 itemId : sharpening_stones)
|
||||||
{
|
{
|
||||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
|
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
|
||||||
@ -936,16 +939,16 @@ void PlayerbotFactory::InitConsumables()
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Restoration: Mana Oil
|
else
|
||||||
else if (specTab == 2)
|
|
||||||
{
|
{
|
||||||
std::vector<uint32> mana_oils = {BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL};
|
std::vector<uint32> mana_oils = {
|
||||||
|
BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL };
|
||||||
for (uint32 itemId : mana_oils)
|
for (uint32 itemId : mana_oils)
|
||||||
{
|
{
|
||||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
|
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
|
||||||
if (proto->RequiredLevel > level || level > 75)
|
if (proto->RequiredLevel > level || level > 75)
|
||||||
continue;
|
continue;
|
||||||
items.push_back({itemId, 4});
|
items.push_back({itemId, 2});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -953,37 +956,27 @@ void PlayerbotFactory::InitConsumables()
|
|||||||
}
|
}
|
||||||
case CLASS_PALADIN:
|
case CLASS_PALADIN:
|
||||||
{
|
{
|
||||||
// Holy: Mana Oil
|
if (specTab == PALADIN_TAB_HOLY)
|
||||||
if (specTab == 0)
|
|
||||||
{
|
{
|
||||||
std::vector<uint32> mana_oils = {BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL};
|
std::vector<uint32> mana_oils = {
|
||||||
|
BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL };
|
||||||
for (uint32 itemId : mana_oils)
|
for (uint32 itemId : mana_oils)
|
||||||
{
|
{
|
||||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
|
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
|
||||||
if (proto->RequiredLevel > level || level > 75)
|
if (proto->RequiredLevel > level || level > 75)
|
||||||
continue;
|
continue;
|
||||||
items.push_back({itemId, 4});
|
items.push_back({itemId, 2});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Protection: Wizard Oil (Protection prioritizes Superior over Brilliant)
|
else
|
||||||
else if (specTab == 1)
|
|
||||||
{
|
{
|
||||||
std::vector<uint32> wizard_oils = {BRILLIANT_WIZARD_OIL, SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL};
|
std::vector<uint32> sharpening_stones = {
|
||||||
for (uint32 itemId : wizard_oils)
|
ADAMANTITE_SHARPENING_STONE, FEL_SHARPENING_STONE, DENSE_SHARPENING_STONE, SOLID_SHARPENING_STONE,
|
||||||
{
|
HEAVY_SHARPENING_STONE, COARSE_SHARPENING_STONE, ROUGH_SHARPENING_STONE };
|
||||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
|
std::vector<uint32> weightstones = {
|
||||||
if (proto->RequiredLevel > level || level > 75)
|
ADAMANTITE_WEIGHTSTONE, FEL_WEIGHTSTONE, DENSE_WEIGHTSTONE, SOLID_WEIGHTSTONE,
|
||||||
continue;
|
HEAVY_WEIGHTSTONE, COARSE_WEIGHTSTONE, ROUGH_WEIGHTSTONE };
|
||||||
items.push_back({itemId, 4});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Retribution: Sharpening Stones & Weightstones
|
|
||||||
else if (specTab == 2)
|
|
||||||
{
|
|
||||||
std::vector<uint32> sharpening_stones = {ADAMANTITE_SHARPENING_STONE, FEL_SHARPENING_STONE, DENSE_SHARPENING_STONE, SOLID_SHARPENING_STONE, HEAVY_SHARPENING_STONE, COARSE_SHARPENING_STONE, ROUGH_SHARPENING_STONE};
|
|
||||||
std::vector<uint32> weightstones = {ADAMANTITE_WEIGHTSTONE, FEL_WEIGHTSTONE, DENSE_WEIGHTSTONE, SOLID_WEIGHTSTONE, HEAVY_WEIGHTSTONE, COARSE_WEIGHTSTONE, ROUGH_WEIGHTSTONE};
|
|
||||||
for (uint32 itemId : sharpening_stones)
|
for (uint32 itemId : sharpening_stones)
|
||||||
{
|
{
|
||||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
|
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
|
||||||
@ -1005,10 +998,14 @@ void PlayerbotFactory::InitConsumables()
|
|||||||
}
|
}
|
||||||
case CLASS_WARRIOR:
|
case CLASS_WARRIOR:
|
||||||
case CLASS_HUNTER:
|
case CLASS_HUNTER:
|
||||||
|
case CLASS_DEATH_KNIGHT:
|
||||||
{
|
{
|
||||||
// Sharpening Stones & Weightstones
|
std::vector<uint32> sharpening_stones = {
|
||||||
std::vector<uint32> sharpening_stones = {ADAMANTITE_SHARPENING_STONE, FEL_SHARPENING_STONE, DENSE_SHARPENING_STONE, SOLID_SHARPENING_STONE, HEAVY_SHARPENING_STONE, COARSE_SHARPENING_STONE, ROUGH_SHARPENING_STONE};
|
ADAMANTITE_SHARPENING_STONE, FEL_SHARPENING_STONE, DENSE_SHARPENING_STONE, SOLID_SHARPENING_STONE,
|
||||||
std::vector<uint32> weightstones = {ADAMANTITE_WEIGHTSTONE, FEL_WEIGHTSTONE, DENSE_WEIGHTSTONE, SOLID_WEIGHTSTONE, HEAVY_WEIGHTSTONE, COARSE_WEIGHTSTONE, ROUGH_WEIGHTSTONE};
|
HEAVY_SHARPENING_STONE, COARSE_SHARPENING_STONE, ROUGH_SHARPENING_STONE };
|
||||||
|
std::vector<uint32> weightstones = {
|
||||||
|
ADAMANTITE_WEIGHTSTONE, FEL_WEIGHTSTONE, DENSE_WEIGHTSTONE, SOLID_WEIGHTSTONE,
|
||||||
|
HEAVY_WEIGHTSTONE, COARSE_WEIGHTSTONE, ROUGH_WEIGHTSTONE };
|
||||||
for (uint32 itemId : sharpening_stones)
|
for (uint32 itemId : sharpening_stones)
|
||||||
{
|
{
|
||||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
|
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
|
||||||
@ -1029,9 +1026,12 @@ void PlayerbotFactory::InitConsumables()
|
|||||||
}
|
}
|
||||||
case CLASS_ROGUE:
|
case CLASS_ROGUE:
|
||||||
{
|
{
|
||||||
// Poisons
|
std::vector<uint32> instant_poisons = {
|
||||||
std::vector<uint32> instant_poisons = {INSTANT_POISON_IX, INSTANT_POISON_VIII, INSTANT_POISON_VII, INSTANT_POISON_VI, INSTANT_POISON_V, INSTANT_POISON_IV, INSTANT_POISON_III, INSTANT_POISON_II, INSTANT_POISON};
|
INSTANT_POISON_IX, INSTANT_POISON_VIII, INSTANT_POISON_VII, INSTANT_POISON_VI, INSTANT_POISON_V,
|
||||||
std::vector<uint32> deadly_poisons = {DEADLY_POISON_IX, DEADLY_POISON_VIII, DEADLY_POISON_VII, DEADLY_POISON_VI, DEADLY_POISON_V, DEADLY_POISON_IV, DEADLY_POISON_III, DEADLY_POISON_II, DEADLY_POISON};
|
INSTANT_POISON_IV, INSTANT_POISON_III, INSTANT_POISON_II, INSTANT_POISON };
|
||||||
|
std::vector<uint32> deadly_poisons = {
|
||||||
|
DEADLY_POISON_IX, DEADLY_POISON_VIII, DEADLY_POISON_VII, DEADLY_POISON_VI, DEADLY_POISON_V,
|
||||||
|
DEADLY_POISON_IV, DEADLY_POISON_III, DEADLY_POISON_II, DEADLY_POISON };
|
||||||
for (uint32 itemId : deadly_poisons)
|
for (uint32 itemId : deadly_poisons)
|
||||||
{
|
{
|
||||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
|
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
|
||||||
|
|||||||
@ -38,11 +38,13 @@
|
|||||||
#include "ObjectMgr.h"
|
#include "ObjectMgr.h"
|
||||||
#include "PerfMonitor.h"
|
#include "PerfMonitor.h"
|
||||||
#include "Player.h"
|
#include "Player.h"
|
||||||
|
#include "PlayerbotTextMgr.h"
|
||||||
#include "PlayerbotAIConfig.h"
|
#include "PlayerbotAIConfig.h"
|
||||||
#include "PlayerbotMgr.h"
|
#include "PlayerbotMgr.h"
|
||||||
#include "PlayerbotGuildMgr.h"
|
#include "PlayerbotGuildMgr.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
#include "PositionValue.h"
|
#include "PositionValue.h"
|
||||||
|
#include "RBAC.h"
|
||||||
#include "RandomPlayerbotMgr.h"
|
#include "RandomPlayerbotMgr.h"
|
||||||
#include "SayAction.h"
|
#include "SayAction.h"
|
||||||
#include "ScriptMgr.h"
|
#include "ScriptMgr.h"
|
||||||
@ -450,9 +452,11 @@ void PlayerbotAI::UpdateAIGroupMaster()
|
|||||||
botAI->ChangeStrategy("+follow", BOT_STATE_NON_COMBAT);
|
botAI->ChangeStrategy("+follow", BOT_STATE_NON_COMBAT);
|
||||||
|
|
||||||
if (botAI->GetMaster() == botAI->GetGroupLeader())
|
if (botAI->GetMaster() == botAI->GetGroupLeader())
|
||||||
botAI->TellMaster("Hello, I follow you!");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"hello_follow", "Hello, I follow you!", {}));
|
||||||
else
|
else
|
||||||
botAI->TellMaster(!urand(0, 2) ? "Hello!" : "Hi!");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"hello", "Hello!", {}));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -507,7 +511,7 @@ void PlayerbotAI::UpdateAIInternal([[maybe_unused]] uint32 elapsed, bool minimal
|
|||||||
logout = true;
|
logout = true;
|
||||||
|
|
||||||
if (bot->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_RESTING) || bot->HasUnitState(UNIT_STATE_IN_FLIGHT) ||
|
if (bot->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_RESTING) || bot->HasUnitState(UNIT_STATE_IN_FLIGHT) ||
|
||||||
botWorldSessionPtr->GetSecurity() >= (AccountTypes)sWorld->getIntConfig(CONFIG_INSTANT_LOGOUT))
|
botWorldSessionPtr->HasPermission(rbac::RBAC_PERM_INSTANT_LOGOUT))
|
||||||
{
|
{
|
||||||
logout = true;
|
logout = true;
|
||||||
}
|
}
|
||||||
@ -515,7 +519,7 @@ void PlayerbotAI::UpdateAIInternal([[maybe_unused]] uint32 elapsed, bool minimal
|
|||||||
if (master &&
|
if (master &&
|
||||||
(master->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_RESTING) || master->HasUnitState(UNIT_STATE_IN_FLIGHT) ||
|
(master->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_RESTING) || master->HasUnitState(UNIT_STATE_IN_FLIGHT) ||
|
||||||
(master->GetSession() &&
|
(master->GetSession() &&
|
||||||
master->GetSession()->GetSecurity() >= (AccountTypes)sWorld->getIntConfig(CONFIG_INSTANT_LOGOUT))))
|
master->GetSession()->HasPermission(rbac::RBAC_PERM_INSTANT_LOGOUT))))
|
||||||
{
|
{
|
||||||
logout = true;
|
logout = true;
|
||||||
}
|
}
|
||||||
@ -856,7 +860,8 @@ void PlayerbotAI::Reset(bool full)
|
|||||||
{
|
{
|
||||||
WorldPackets::Character::LogoutCancel data = WorldPacket(CMSG_LOGOUT_CANCEL);
|
WorldPackets::Character::LogoutCancel data = WorldPacket(CMSG_LOGOUT_CANCEL);
|
||||||
bot->GetSession()->HandleLogoutCancelOpcode(data);
|
bot->GetSession()->HandleLogoutCancelOpcode(data);
|
||||||
TellMaster("Logout cancelled!");
|
TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"logout_cancel", "Logout cancelled!", {}));
|
||||||
}
|
}
|
||||||
|
|
||||||
currentEngine = engines[BOT_STATE_NON_COMBAT];
|
currentEngine = engines[BOT_STATE_NON_COMBAT];
|
||||||
@ -3003,7 +3008,7 @@ bool PlayerbotAI::IsTellAllowed(PlayerbotSecurityLevel securityLevel)
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (sPlayerbotAIConfig.whisperDistance && !bot->GetGroup() && sRandomPlayerbotMgr.IsRandomBot(bot) &&
|
if (sPlayerbotAIConfig.whisperDistance && !bot->GetGroup() && sRandomPlayerbotMgr.IsRandomBot(bot) &&
|
||||||
master->GetSession()->GetSecurity() < SEC_GAMEMASTER &&
|
!master->CanBeGameMaster() &&
|
||||||
(bot->GetMapId() != master->GetMapId() ||
|
(bot->GetMapId() != master->GetMapId() ||
|
||||||
ServerFacade::instance().GetDistance2d(bot, master) > sPlayerbotAIConfig.whisperDistance))
|
ServerFacade::instance().GetDistance2d(bot, master) > sPlayerbotAIConfig.whisperDistance))
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@ -27,6 +27,7 @@
|
|||||||
#include "PlayerbotFactory.h"
|
#include "PlayerbotFactory.h"
|
||||||
#include "PlayerbotOperations.h"
|
#include "PlayerbotOperations.h"
|
||||||
#include "PlayerbotSecurity.h"
|
#include "PlayerbotSecurity.h"
|
||||||
|
#include "PlayerbotTextMgr.h"
|
||||||
#include "PlayerbotWorldThreadProcessor.h"
|
#include "PlayerbotWorldThreadProcessor.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
#include "PlayerbotGuildMgr.h"
|
#include "PlayerbotGuildMgr.h"
|
||||||
@ -320,7 +321,8 @@ void PlayerbotMgr::CancelLogout()
|
|||||||
{
|
{
|
||||||
WorldPackets::Character::LogoutCancel data = WorldPacket(CMSG_LOGOUT_CANCEL);
|
WorldPackets::Character::LogoutCancel data = WorldPacket(CMSG_LOGOUT_CANCEL);
|
||||||
bot->GetSession()->HandleLogoutCancelOpcode(data);
|
bot->GetSession()->HandleLogoutCancelOpcode(data);
|
||||||
botAI->TellMaster("Logout cancelled!");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"logout_cancel", "Logout cancelled!", {}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -411,7 +413,8 @@ void PlayerbotHolder::DisablePlayerBot(ObjectGuid guid)
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
botAI->TellMaster("Goodbye!");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"goodbye", "Goodbye!", {}));
|
||||||
bot->StopMoving();
|
bot->StopMoving();
|
||||||
bot->GetMotionMaster()->Clear();
|
bot->GetMotionMaster()->Clear();
|
||||||
|
|
||||||
@ -544,7 +547,8 @@ void PlayerbotHolder::OnBotLogin(Player* const bot)
|
|||||||
// set delay on login
|
// set delay on login
|
||||||
botAI->SetNextCheckDelay(urand(2000, 4000));
|
botAI->SetNextCheckDelay(urand(2000, 4000));
|
||||||
|
|
||||||
botAI->TellMaster("Hello!", PLAYERBOT_SECURITY_TALK);
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"hello", "Hello!", {}), PLAYERBOT_SECURITY_TALK);
|
||||||
|
|
||||||
// Queue group operations for world thread
|
// Queue group operations for world thread
|
||||||
if (master && master->GetGroup() && !group)
|
if (master && master->GetGroup() && !group)
|
||||||
@ -919,7 +923,7 @@ std::vector<std::string> PlayerbotHolder::HandlePlayerbotCommand(char const* arg
|
|||||||
|
|
||||||
if (!strcmp(cmd, "initself"))
|
if (!strcmp(cmd, "initself"))
|
||||||
{
|
{
|
||||||
if (master->GetSession()->GetSecurity() >= SEC_GAMEMASTER)
|
if (master->CanBeGameMaster())
|
||||||
{
|
{
|
||||||
// OnBotLogin(master);
|
// OnBotLogin(master);
|
||||||
PlayerbotFactory factory(master, master->GetLevel(), ITEM_QUALITY_EPIC);
|
PlayerbotFactory factory(master, master->GetLevel(), ITEM_QUALITY_EPIC);
|
||||||
@ -938,7 +942,7 @@ std::vector<std::string> PlayerbotHolder::HandlePlayerbotCommand(char const* arg
|
|||||||
{
|
{
|
||||||
if (!strcmp(cmd, "initself=uncommon"))
|
if (!strcmp(cmd, "initself=uncommon"))
|
||||||
{
|
{
|
||||||
if (master->GetSession()->GetSecurity() >= SEC_GAMEMASTER)
|
if (master->CanBeGameMaster())
|
||||||
{
|
{
|
||||||
// OnBotLogin(master);
|
// OnBotLogin(master);
|
||||||
PlayerbotFactory factory(master, master->GetLevel(), ITEM_QUALITY_UNCOMMON);
|
PlayerbotFactory factory(master, master->GetLevel(), ITEM_QUALITY_UNCOMMON);
|
||||||
@ -954,7 +958,7 @@ std::vector<std::string> PlayerbotHolder::HandlePlayerbotCommand(char const* arg
|
|||||||
}
|
}
|
||||||
if (!strcmp(cmd, "initself=rare"))
|
if (!strcmp(cmd, "initself=rare"))
|
||||||
{
|
{
|
||||||
if (master->GetSession()->GetSecurity() >= SEC_GAMEMASTER)
|
if (master->CanBeGameMaster())
|
||||||
{
|
{
|
||||||
// OnBotLogin(master);
|
// OnBotLogin(master);
|
||||||
PlayerbotFactory factory(master, master->GetLevel(), ITEM_QUALITY_RARE);
|
PlayerbotFactory factory(master, master->GetLevel(), ITEM_QUALITY_RARE);
|
||||||
@ -970,7 +974,7 @@ std::vector<std::string> PlayerbotHolder::HandlePlayerbotCommand(char const* arg
|
|||||||
}
|
}
|
||||||
if (!strcmp(cmd, "initself=epic"))
|
if (!strcmp(cmd, "initself=epic"))
|
||||||
{
|
{
|
||||||
if (master->GetSession()->GetSecurity() >= SEC_GAMEMASTER)
|
if (master->CanBeGameMaster())
|
||||||
{
|
{
|
||||||
// OnBotLogin(master);
|
// OnBotLogin(master);
|
||||||
PlayerbotFactory factory(master, master->GetLevel(), ITEM_QUALITY_EPIC);
|
PlayerbotFactory factory(master, master->GetLevel(), ITEM_QUALITY_EPIC);
|
||||||
@ -986,7 +990,7 @@ std::vector<std::string> PlayerbotHolder::HandlePlayerbotCommand(char const* arg
|
|||||||
}
|
}
|
||||||
if (!strcmp(cmd, "initself=legendary"))
|
if (!strcmp(cmd, "initself=legendary"))
|
||||||
{
|
{
|
||||||
if (master->GetSession()->GetSecurity() >= SEC_GAMEMASTER)
|
if (master->CanBeGameMaster())
|
||||||
{
|
{
|
||||||
// OnBotLogin(master);
|
// OnBotLogin(master);
|
||||||
PlayerbotFactory factory(master, master->GetLevel(), ITEM_QUALITY_LEGENDARY);
|
PlayerbotFactory factory(master, master->GetLevel(), ITEM_QUALITY_LEGENDARY);
|
||||||
@ -1003,7 +1007,7 @@ std::vector<std::string> PlayerbotHolder::HandlePlayerbotCommand(char const* arg
|
|||||||
int32 gs;
|
int32 gs;
|
||||||
if (sscanf(cmd, "initself=%d", &gs) != -1)
|
if (sscanf(cmd, "initself=%d", &gs) != -1)
|
||||||
{
|
{
|
||||||
if (master->GetSession()->GetSecurity() >= SEC_GAMEMASTER)
|
if (master->CanBeGameMaster())
|
||||||
{
|
{
|
||||||
// OnBotLogin(master);
|
// OnBotLogin(master);
|
||||||
PlayerbotFactory factory(master, master->GetLevel(), ITEM_QUALITY_LEGENDARY, gs);
|
PlayerbotFactory factory(master, master->GetLevel(), ITEM_QUALITY_LEGENDARY, gs);
|
||||||
@ -1027,7 +1031,7 @@ std::vector<std::string> PlayerbotHolder::HandlePlayerbotCommand(char const* arg
|
|||||||
|
|
||||||
if (!strcmp(cmd, "reload"))
|
if (!strcmp(cmd, "reload"))
|
||||||
{
|
{
|
||||||
if (master->GetSession()->GetSecurity() >= SEC_GAMEMASTER)
|
if (master->CanBeGameMaster())
|
||||||
{
|
{
|
||||||
sPlayerbotAIConfig.Initialize();
|
sPlayerbotAIConfig.Initialize();
|
||||||
messages.push_back("Config reloaded.");
|
messages.push_back("Config reloaded.");
|
||||||
@ -1059,7 +1063,7 @@ std::vector<std::string> PlayerbotHolder::HandlePlayerbotCommand(char const* arg
|
|||||||
}
|
}
|
||||||
else if (sPlayerbotAIConfig.selfBotLevel == 0)
|
else if (sPlayerbotAIConfig.selfBotLevel == 0)
|
||||||
messages.push_back("Self-bot is disabled");
|
messages.push_back("Self-bot is disabled");
|
||||||
else if (sPlayerbotAIConfig.selfBotLevel == 1 && master->GetSession()->GetSecurity() < SEC_GAMEMASTER)
|
else if (sPlayerbotAIConfig.selfBotLevel == 1 && !master->CanBeGameMaster())
|
||||||
messages.push_back("You do not have permission to enable player botAI");
|
messages.push_back("You do not have permission to enable player botAI");
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -1079,7 +1083,7 @@ std::vector<std::string> PlayerbotHolder::HandlePlayerbotCommand(char const* arg
|
|||||||
|
|
||||||
if (!strcmp(cmd, "addclass"))
|
if (!strcmp(cmd, "addclass"))
|
||||||
{
|
{
|
||||||
if (sPlayerbotAIConfig.addClassCommand == 0 && master->GetSession()->GetSecurity() < SEC_GAMEMASTER)
|
if (sPlayerbotAIConfig.addClassCommand == 0 && !master->CanBeGameMaster())
|
||||||
{
|
{
|
||||||
messages.push_back("You do not have permission to create bot by addclass command");
|
messages.push_back("You do not have permission to create bot by addclass command");
|
||||||
return messages;
|
return messages;
|
||||||
@ -1304,7 +1308,7 @@ std::vector<std::string> PlayerbotHolder::HandlePlayerbotCommand(char const* arg
|
|||||||
else if (master && member != master->GetGUID())
|
else if (master && member != master->GetGUID())
|
||||||
{
|
{
|
||||||
out << ProcessBotCommand(cmdStr, member, master->GetGUID(),
|
out << ProcessBotCommand(cmdStr, member, master->GetGUID(),
|
||||||
master->GetSession()->GetSecurity() >= SEC_GAMEMASTER,
|
master->CanBeGameMaster(),
|
||||||
master->GetSession()->GetAccountId(), master->GetGuildId());
|
master->GetSession()->GetAccountId(), master->GetGuildId());
|
||||||
}
|
}
|
||||||
else if (!master)
|
else if (!master)
|
||||||
@ -1639,7 +1643,7 @@ void PlayerbotMgr::OnPlayerLogin(Player* player)
|
|||||||
|
|
||||||
// For bot texts (DB-driven), prefer the database locale with a safe fallback.
|
// For bot texts (DB-driven), prefer the database locale with a safe fallback.
|
||||||
LocaleConstant usedLocale = databaseLocale;
|
LocaleConstant usedLocale = databaseLocale;
|
||||||
if (usedLocale >= MAX_LOCALES)
|
if (usedLocale >= TOTAL_LOCALES)
|
||||||
usedLocale = LOCALE_enUS; // fallback
|
usedLocale = LOCALE_enUS; // fallback
|
||||||
|
|
||||||
// set locale priority for bot texts
|
// set locale priority for bot texts
|
||||||
|
|||||||
@ -34,6 +34,7 @@
|
|||||||
#include "PlayerbotAI.h"
|
#include "PlayerbotAI.h"
|
||||||
#include "PlayerbotAIConfig.h"
|
#include "PlayerbotAIConfig.h"
|
||||||
#include "PlayerbotFactory.h"
|
#include "PlayerbotFactory.h"
|
||||||
|
#include "PlayerbotTextMgr.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
#include "Position.h"
|
#include "Position.h"
|
||||||
#include "RaceMgr.h"
|
#include "RaceMgr.h"
|
||||||
@ -2589,7 +2590,8 @@ void RandomPlayerbotMgr::OnPlayerLogin(Player* player)
|
|||||||
{
|
{
|
||||||
botAI->SetMaster(player);
|
botAI->SetMaster(player);
|
||||||
botAI->ResetStrategies();
|
botAI->ResetStrategies();
|
||||||
botAI->TellMaster("Hello");
|
botAI->TellMaster(PlayerbotTextMgr::instance().GetBotTextOrDefault(
|
||||||
|
"hello", "Hello", {}));
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|||||||
92
src/Mgr/Item/BisListMgr.cpp
Normal file
92
src/Mgr/Item/BisListMgr.cpp
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||||
|
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "BisListMgr.h"
|
||||||
|
|
||||||
|
#include "DatabaseEnv.h"
|
||||||
|
#include "Field.h"
|
||||||
|
#include "Log.h"
|
||||||
|
#include "QueryResult.h"
|
||||||
|
|
||||||
|
void BisListMgr::LoadAll()
|
||||||
|
{
|
||||||
|
_bis.clear();
|
||||||
|
|
||||||
|
QueryResult result = PlayerbotsDatabase.Query(
|
||||||
|
"SELECT class, tab, slot, faction, auto_gear_score_limit, item_id FROM playerbots_bis_gear");
|
||||||
|
if (!result)
|
||||||
|
{
|
||||||
|
LOG_INFO("server.loading", "playerbots_bis_gear table missing or empty");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32 count = 0;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
Field* fields = result->Fetch();
|
||||||
|
uint8 cls = fields[0].Get<uint8>();
|
||||||
|
uint8 tab = fields[1].Get<uint8>();
|
||||||
|
uint8 slot = fields[2].Get<uint8>();
|
||||||
|
uint8 faction = fields[3].Get<uint8>();
|
||||||
|
uint16 autoGearScoreLimit = fields[4].Get<uint16>();
|
||||||
|
uint32 item = fields[5].Get<uint32>();
|
||||||
|
|
||||||
|
_bis[autoGearScoreLimit][MakeKey(cls, tab)][faction][slot] = item;
|
||||||
|
++count;
|
||||||
|
} while (result->NextRow());
|
||||||
|
|
||||||
|
LOG_INFO("server.loading", "Loaded {} BiS entries across {} item levels",
|
||||||
|
count, static_cast<uint32>(_bis.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::map<uint8, uint32> BisListMgr::GetBisFor(uint16 autoGearScoreLimit, uint8 cls, uint8 tab, uint8 faction) const
|
||||||
|
{
|
||||||
|
auto ilvlIt = _bis.find(autoGearScoreLimit);
|
||||||
|
if (ilvlIt == _bis.end())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
auto comboIt = ilvlIt->second.find(MakeKey(cls, tab));
|
||||||
|
if (comboIt == ilvlIt->second.end())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
std::map<uint8, uint32> result;
|
||||||
|
|
||||||
|
// Base: faction=0 (Both).
|
||||||
|
auto bothIt = comboIt->second.find(0);
|
||||||
|
if (bothIt != comboIt->second.end())
|
||||||
|
result = bothIt->second;
|
||||||
|
|
||||||
|
// Faction-specific overrides Both.
|
||||||
|
if (faction == 1 || faction == 2)
|
||||||
|
{
|
||||||
|
auto facIt = comboIt->second.find(faction);
|
||||||
|
if (facIt != comboIt->second.end())
|
||||||
|
for (auto const& kv : facIt->second)
|
||||||
|
result[kv.first] = kv.second;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::map<uint8, uint32> BisListMgr::GetBisForNearest(uint16 requestedIlvl, uint16 maxDrop, uint8 cls, uint8 tab,
|
||||||
|
uint8 faction, uint16* outResolved) const
|
||||||
|
{
|
||||||
|
uint16 floor = requestedIlvl > maxDrop ? requestedIlvl - maxDrop : 1;
|
||||||
|
for (uint16 try_ilvl = requestedIlvl; try_ilvl >= floor; --try_ilvl)
|
||||||
|
{
|
||||||
|
auto result = GetBisFor(try_ilvl, cls, tab, faction);
|
||||||
|
if (!result.empty())
|
||||||
|
{
|
||||||
|
if (outResolved)
|
||||||
|
*outResolved = try_ilvl;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (try_ilvl == 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (outResolved)
|
||||||
|
*outResolved = 0;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
44
src/Mgr/Item/BisListMgr.h
Normal file
44
src/Mgr/Item/BisListMgr.h
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||||
|
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PLAYERBOT_BISLISTMGR_H
|
||||||
|
#define _PLAYERBOT_BISLISTMGR_H
|
||||||
|
|
||||||
|
#include "Define.h"
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
class BisListMgr
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static BisListMgr* instance()
|
||||||
|
{
|
||||||
|
static BisListMgr inst;
|
||||||
|
return &inst;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoadAll();
|
||||||
|
|
||||||
|
// faction: 1=Alliance, 2=Horde. Faction-specific rows override faction=0 (Both).
|
||||||
|
// Returns slot -> itemId for the matching auto_gear_score_limit tier. Empty map = no data.
|
||||||
|
std::map<uint8, uint32> GetBisFor(uint16 autoGearScoreLimit, uint8 cls, uint8 tab, uint8 faction) const;
|
||||||
|
|
||||||
|
// Closest-lower fallback: scan ilvls down from requested to (requested - maxDrop), return first non-empty set.
|
||||||
|
// outResolved receives the matched ilvl (0 if nothing matched within the window).
|
||||||
|
std::map<uint8, uint32> GetBisForNearest(uint16 requestedIlvl, uint16 maxDrop, uint8 cls, uint8 tab,
|
||||||
|
uint8 faction, uint16* outResolved = nullptr) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
BisListMgr() = default;
|
||||||
|
|
||||||
|
static uint16 MakeKey(uint8 cls, uint8 tab) { return (uint16(cls) << 8) | tab; }
|
||||||
|
|
||||||
|
// autoGearScoreLimit -> (cls<<8|tab) -> faction (0/1/2) -> slot -> itemId
|
||||||
|
std::map<uint16, std::map<uint16, std::map<uint8, std::map<uint8, uint32>>>> _bis;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define sBisListMgr BisListMgr::instance()
|
||||||
|
|
||||||
|
#endif
|
||||||
@ -27,7 +27,7 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GMs always have full access
|
// GMs always have full access
|
||||||
if (from->GetSession()->GetSecurity() >= SEC_GAMEMASTER)
|
if (from->CanBeGameMaster())
|
||||||
return PLAYERBOT_SECURITY_ALLOW_ALL;
|
return PLAYERBOT_SECURITY_ALLOW_ALL;
|
||||||
|
|
||||||
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
|
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
|
||||||
@ -188,8 +188,7 @@ bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent,
|
|||||||
|
|
||||||
Player* master = botAI->GetMaster();
|
Player* master = botAI->GetMaster();
|
||||||
if (master && botAI->IsOpposing(master))
|
if (master && botAI->IsOpposing(master))
|
||||||
if (WorldSession* session = master->GetSession())
|
if (master->GetSession() && !master->CanBeGameMaster())
|
||||||
if (session->GetSecurity() < SEC_GAMEMASTER)
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
std::ostringstream out;
|
std::ostringstream out;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user