ICC V2, Autogear BiS cmd (#2363)

## Pull Request Description
<!-- Describe what this change does and why it is needed -->

Big thanks to @kadeshar for providing the bis list for many raids and
ilvls :D

Video demo for ICC 25HC:
https://studio.youtube.com/video/nACyjn817iQ/edit

Video demo for autogear bis chat command:
https://www.youtube.com/watch?v=2YqyVBaSb2g

split main IccActions.cpp into sperate per boss .cpp files
changed style to be more aligned with
https://www.azerothcore.org/wiki/cpp-code-standards (WIP)
added bisicc chat command for bots to gear with ICC bis gear if autogear
and bisicc is enabled in cfg
https://gist.github.com/metal0/0bb094bf65d27e17044308ad0646cae1 bis list
used

LM

Added multiple spike marking and focus for faster spike clearing, each
spike will get its own kill group, tank spike will never get melee bots
(only assist tank and ranged dps)
Added coldflame detection so that melee bots dont go for spikes that are
in flames
During bonestorm assist tank will go far away spot so that once
bonestorm is fixed, LM will bounce back and forth from MT to AT (atm it
targets randomly, it should always pick furthest target)
Coldflame avoidance is handled by avoid AOE, important to keep it on in
cfg

Tested on ALL diffs

LDW

Improved skull marking of adds, add handling by tanks and dps
Changed 1st position for ranged bots for easier adds handling in HC and
NM
Improved tanking logic for tanks, assist tank will focus on collecting
adds and bring them near boss
Real players will also get cyclone aura when mind controlled
Improved ranged position during 2nd phase, they should not get stuck in
corners/walls anymore
Tanks will remove LDW ToI aura in HC (really hard to tank with it since
many things are happening at once)
Added Cheat for LDW fight to help tanks with agro in 2nd phase of heroic
modes
Changed tank position in phase 2 closer to pillars opposed to stairs
(bots love to fall thru floor and run thru walls if near them) this
fixed the issue
Fixed edge case for escaping from shades, it could happen that multiple
shades would target bot, and it was running from 1st one he found, now
it will run form all that are targeting it
Hunters will cast viper sting now, to increase shield draining speed

Tested on ALL diffs
Edit 19.5. :
Tested 25hc with autogear bis gear, playebots cfg ICC cheats off, world
cfg ICC buff on max (30%)
In short cleared LDW without ICC cheats with bis gear but unoptimized
enchants, talents, gems. I still recommend using ICC cheats for better
and fun experience.

GS

Changed triggers and actions to enable cross faction play
Assist tank will now actually tank adds on friendly ship
Dps will properly jump to attack mage and go back to their ship, if
stuck on enemy ship /p reset, /p summon or /p follow
fixed trigger for cannons, if cannons are frozen bots wont try to mount
them anymore which prevented them from attacking mage properly
bots will use rocket packs to jump to and from enemy ship instead of
teleporting
Main tank will now jump 1st. tank enemy boss and wait until all bots
have jumped back before he jumps back
All bots will wait for main tank to engage enemy captain before jumping
to enemy ship
Cannons will focus rockets 1st, then other adds now (for when gs gets
scripted)
Rdps will focus nearby adds on enemy ship and mark with star rti icon
when there is no deep freeze

todo: remove tanking bypass when core fixes enemy ship boss threat
reseting
Tested crossfaction on horde with single ally bot, ally bot did
everything right, need to test more.
note horde side is heavily bugged due to threat issue of adds, tanks
cant take threat, on ally its somewhat ok, on horde rip. Horde is
doable, but annoying cus of threat issue.


Tested on ALL diffs

DBS

Remade tank taunt logic, tanks should now properly taunt boss and let
other tank taunt it if they get rune of blood
Tanks will tank adds better now, no loose adds anymore

Tested on ALL diffs

Dogs

Remade tank taunt logic, tanks should now properly taunt boss and let
other tank taunt it if they get 8 mortal wounds stacks
Tanks will tank adds better now, no loose adds anymore

Tested on ALL diffs


Festergut

Hunters sometimes populated row 0 which would make them in melee range
of the boss (bad for dps). They should pick correct rows now
Healers will populate row 0 1st then other rows for optimal healing
position
Ranged bots should properly choose unique spots to avoid stacking when
there is no spore present
Remade tank taunt logic, tanks should now properly taunt boss and let
other tank taunt it if they get 6 gastric bloat stacks
Changed ranged spore position closer to boss
Spore bots should be able to attack/do non movement action when they
have spore and are in position
Solved malleable goo detection via direct boss hooks to detect boss
targets!

Tested on ALL diffs

Rotface

Tanks should not fight over big ooze anymore
Improved big ooze kiting
Improved small ooze stacking logic (when no big ooze present, stack at
small ooze position, when big ooze is present move to it)
Fixed edge cases when main and assist tank get small ooze (they used to
move to big ooze, that was really bad since main tank would start to
tank big ooze and get hit by big ooze, assist tank would stop kiting and
get hit by big ooze)
stopped mutated plague from dispelling instantly (as fight goes on,
rotface cast mutated plague more and more, thus making it impossible to
pass due to sheer numbers of small oozes and big oozes on the map, this
will delay their spawning and give enough time for bots to handle them
properly)
Fixed edge case of multiple small oozes and big oozes being alive at
same time (bots would detect wrong oozes and wipe raid or get stuck)
Improved flood avoidance
Improved ranged positioning in heroic mode, instead of letting them
choose positions (which is a nightmare on dynamic fight as rotface, they
now will choose 1 spot of 22 premade ones and populate them based on
guid and adopt spot based on flood position)
Improved Explosion avoidance by making bots remember their starting
position so that they can return to it after big ooze explode, their
movement is not chaotic anymore, and improved timers, they will wait 2
sec at new position before returning to starting position so that they
can avoid explosion projectiles properly, they should also avoid moving
to other bots starting positions.
These changes ensure minimal movements so that bot can do maximum dps
possible.

Tested on ALL diffs

PP

Fixed many logic conflicts that caused bots to freeze, do bad dps to
ooze/clouds
Fixed triggers and multipliers
Improved Gas Cloud avoidance, bloated bot will now remember its previous
position to avoid backtracking/getting stuck in corners
Added boss hooks to finally detect malleable goo, it is not an npc,
object or creature and PP doesn't target anyone, bots will flee from it
now
Boss stacking now only in last phase
Added cheats for players also (if enabled in cfg) only bots used to get
auras
Fixed tank switching in last phase, atm PP doesn't apply aura, but it
should work, since same logic works for dogs, festergut and dbs
Assist tank will now become abo if there is no abo before first puddle
appears
Abo will during puddles, slow oozes, slash boss & oozes
In last phase assist will return to normal

Tested on ALL diffs
Edit 19.5. :
Tested 25hc with autogear bis gear, playebots cfg ICC cheats off, world
cfg ICC buff on max (30%)
Tanks switched in last phase flawlesly and shared stacks as they should
(mutated plague got fixed in core)
In short cleared PP without ICC cheats with bis gear but unoptimized
enchants, talents, gems. I still recommend using ICC cheats for better
and fun experience.

BPC

Added center position to prevent bots from pulling BQL or other adds
when they glitch thru walls/floor and thus resetting raid back to icc
entrance teleporter
Added additional z axis resetting since bots like to "fly" up in the air
when attacking kinetic bombs.
using cheat bypass for ball of inferno flames (atm bugged, doesn't
shrink), bots will simply kill them when they spawn.
Improved tanking for main tanks, improved collection of dark nuclei for
assist tank
Improved kinetics bomb handling
Improved shock vortex spreading
Improved valaran spreading for ranged
Added shock vortex (non empowered) detection to avoid it while moving
into safe positions
Fixed jittery movement

Tested on ALL diffs

BQL

Removed center position block so that bots can spread our easier in 25
mode, not ideal but makes 25hc easier
Replaced repulsion based spreading, now each bot will have its own spot
and move if needed to new spot
Improved air phase spreading
Fixed assist tank taking 1st bite

Tested on ALL diffs

VDW

Due to recent core changes bots got bugged in portals if no real player
entered and changed Z axis, if there was no z axis change bots would
chill under the cloud on the ground and do nothing. I could not figure
out how to fix this (thus breaking immersion) without force teleporting
them to the clouds.
Bots that go into portals will now teleport at the same time to clouds
instead of following leader bot.
Added feature that if players enter the portal, player with lowest guid
will become bot "leader" and they will follow that player so that there
is at least a little bit of immersion left.
Fixed cloud collection for Heroic Mode, bots will now time clouds more
precisely to avoid loosing stack due to not picking them up
Improved RTI marking
Improved group splitting
Improved zombie kiting and avoiding explosion

Tested on ALL diffs

Sindragosa

Bots will mark tomb positions with red smoke bomb in air phase so that
real player know where to go with when beacon on them
in last phase they will mark with blue smoke tomb position
Fixed tank positioning
Fixed wrong tomb choice and positioning
Fixed tomb marking
In last phase healers will stack with melee to allow boss healing
In last phase when waiting for mystic debuff to pass, bots will damage
tomb like in air phase to speed up the kill

todo: tank switch to reset mystic buffet stacks
Tested on ALL diffs
Edit 19.5. :
Tested 25hc with autogear bis gear, playebots cfg ICC cheats off, world
cfg ICC buff on max (30%)
In short cleared LDW without ICC cheats with bis gear but unoptimized
enchants, talents, gems. I still recommend using ICC cheats for better
and fun experience.

LK

Changed add gathering logic for 1st phase and winter phase, instead of
tank moving to shamblings, he will keep taunting until they agro him.
necrotic plague is easy now, ditched complex timing logic for a simple
logic ( move to shambling, wait until dispeled, go back. Healers dont
dispel until defile ally is near shambling )
Fixed winter phase gathering logic, assist tank will now properly move
to raging spirits asap and bring them to main tank, melee dps will no
properly move behind/flank spirits and shamblings to avoid instant
death. Rdps will now properly focus frost orbs and adds, Transition
should also be smoother now, but still needs /p reset if they get stuck.
Other phases are ok, LK fight is now even better than before, but player
still need to know tactics and use multibot addon to help out bots when
needed, especially during defile phase since its random and position
matter for valkyrs and future defiles
Non winter phase AT will collect raging spirits and move them to main
tank, ranged bots will keep distance, melee bots will flank them to
avoid aoe
Defile, ditched complex spreading which was mostly gamble with boss
hooks to detect defile victim. If bot, bot will move away from raid, if
real player main tank will yell Player name move away defile.
bots will stay in center now if safe from defile, raging spirits or vile
spirits
Vile spirits soaking by assist tank. Assist tank will stand between
spirits and raid and chase spirits. healers are allowed to move from
position to heal assist tank. one hunter if alive will be at center
position to place traps to slow down spirits


HC

Real players will also get buffs if cheats are enabled now
Assist tank will now never move towards the raid to gather adds, instead
it will taunt them instead so that they come to it
Assist tank will rotate shamblings at all times away from raid
Assist tank will stun shamblings before transition to avoid shockwave
wipe
Winter phase ice sphere location changed, ranged will focus sphere
faster and better now
Fixed jittery movement and low dps during winter phase
Fixed most of the bots getting stuck during winter phase
Valkyrs will be properly marked now, one by one, in hc bots will now
ignore low hp valkyrs and focus on grabbing valkyrs or boss
After winter raging spirit will have top priority for killing
After winter ranged bots will 1st handle ice spheres then skull targets
Spirit bomb avoidance improved, main tank should not back track into
unsafe positions anymore


Since real player is leader its crucial that player know the tactics,
bots can not handle edge cases during the fight alone,
they need some of reset, follow, summon here and there since its a long
fight and things can go wrong.

Tested on ALL diffs
NOTE: If server crash, bots will sometimes drop ICC strategy even though
they are in ICC, simply re enter or write /p nc +ICC to re enable.
NOTE: addons that mark icons during fight could break bots, since icons
are used for RTI by bots
NOTE: I did not use any raiding addons besides unbot and multibot to
control bots
NOTE: In theory everything should work wihout ICC buff from world cfg,
and ICC cheats from playerbots cfg, didnt test it, didnt try, its too
hard core for hc mode to go raw, but it should be possible good luck :)
NOTE: For normal about 5k gs should be enough to do most bosses. For HC
T10 set + ICC 25 nm or HC gear + gems + enchants + buffs from cfg for
fun experience.
NOTE: As player its good to know every strategy for Bosses, so that you
can spot and help out with reset, follow, summon if bots seem stuck or
are doing something strange, a lot of stuff is happening on most fights
so expect some intervention with reset, summon, follow.

10 MAN 2-3 Healers, 2 Tanks, at least 1 hunter, at least one druid for
bress (its not set in stone, but most success with this setup)
25 MAN 6-7 Healers, 2 tanks, at least 1 hunter, at least 3-4 druids for
bress (its not set in stone, but most success with this setup)

GL & HF, happy raiding :D

Closes #1421 #2120
Fixes #1219 NOTE: Not all of them, I have updated affected changes in
#1219. Trash, quest, cheats are still nice to haves, but I don't see
working on that in near future.

Before posting bugs check #1219 and write there. As I said, I dont plan
to implement certain things in near future, but I am more than willing
to fix bugs that crash server if they happen ASAP.

<!--
Thank you for contributing to mod-playerbots, please make sure that
you...
1. Submit your PR to the test-staging branch, not master.
2. Read the guidelines below before submitting.
3. Don't delete parts of this template.

DESIGN PHILOSOPHY: We prioritize STABILITY, PERFORMANCE, AND
PREDICTABILITY over behavioral realism.

Every action and decision executes PER BOT AND PER TRIGGER. Small
increases in logic complexity scale
poorly across thousands of bots and negatively affect all. We prioritize
a stable system over a smarter
one. Bots don't need to behave perfectly; believable behavior is the
goal, not human simulation.
Default behavior must be cheap in processing; expensive behavior must be
opt-in.

Before submitting, make sure your changes aligns with these principles.
-->





## Feature Evaluation
<!--
If your PR is very minimal (comment typo, wrong ID reference, etc), and
it is very obvious it will not have
any impact on performance, you may skip these question. If necessary, a
maintainer may ask you for them later.
-->

<!-- Please answer the following: -->
- Describe the **minimum logic** required to achieve the intended
behavior.
- Describe the **processing cost** when this logic executes across many
bots.



## How to Test the Changes
<!--
- Step-by-step instructions to test the change.
Enter ICC
Test Bosses

bis ICC command
type bisicc into party chat or whisper and bots will reply and equip
gear

- Any required setup (e.g. multiple players, number of bots, specific
configuration).
NOTE: If server crash, bots will sometimes drop ICC strategy even though
they are in ICC, simply re enter or write /p nc +ICC to re enable.
NOTE: addons that mark icons during fight could break bots, since icons
are used for RTI by bots
NOTE: I did not use any raiding addons besides unbot and multibot to
control bots
NOTE: In theory everything should work wihout ICC buff from world cfg,
and ICC cheats from playerbots cfg, didnt test it, didnt try, its too
hard core for hc mode to go raw, but it should be possible good luck :)
NOTE: For normal about 5k gs should be enough to do most bosses. For HC
T10 set + ICC 25 nm or HC gear + gems + enchants + buffs from cfg for
fun experience.
NOTE: As player its good to know every strategy for Bosses, so that you
can spot and help out with reset, follow, summon if bots seem stuck or
are doing something strange, a lot of stuff is happening on most fights
so expect some intervention with reset, summon, follow.

10 MAN 2-3 Healers, 2 Tanks, at least 1 hunter, at least one druid for
bress (its not set in stone, but most success with this setup)
25 MAN 6-7 Healers, 2 tanks, at least 1 hunter, at least 3-4 druids for
bress (its not set in stone, but most success with this setup)
- Expected behavior and how to verify it.
If requirements are met, bots should not struggle with killing bosses
Compare to https://www.youtube.com/watch?v=nACyjn817iQ&t=460s
-->



## Impact Assessment
<!-- As a generic test, before and after measure of pmon (playerbot pmon
tick) can help you here. -->
- Does this change increase per-bot/per-tick processing or risk scaling
poorly with thousands of bots?
    - - [ ] No, not at all
    - - [X ] Minimal impact (**explain below**)
In theory it should not impact, didnt test with hi bot count or large
player count
    - - [ ] Moderate impact (**explain below**)



- Does this change modify default bot behavior?
    - - [X ] No
    - - [ ] Yes (**explain why**)



- Does this change add new decision branches or increase maintenance
complexity?
    - - [ ] No
    - - [X ] Yes (**explain below**)

Impacts in raid, new actions, triggers
Impacts with new bisicc cmd that will gear bots
Everything should make it easier for maintenance since each boss is in
seperate file now


## AI Assistance
<!--
AI assistance is allowed, but all submitted code must be fully
understood, reviewed, and owned by the contributor.
We expect contributors to be honest about what they do and do not
understand.
-->
Was AI assistance used while working on this change?
- - [ ] No
- - [x ] Yes (**explain below**)
<!--
If yes, please specify:
- Purpose of usage (e.g. brainstorming, refactoring, documentation, code
generation).
- Which parts of the change were influenced or generated, and whether it
was thoroughly reviewed.
-->

AI was used for analyzing code for ac code standard violations, edits
were made by me. It was used for fixing bugs, brainstorming and code
generation (for complex math problems, such as dynamicaly kiting oozes
around, assiging positions during multiple complex situations in rotface
encouter. Everything was checked and tested multiple times until it was
polished (to my abilites and understanding). It helped me to solve
Malleable goo detection, defile, by hooking directly to boss in order to
detect it, since it was detectable only by split second since it was not
npc, spell or object.


<!--
TRANSLATIONS:
Anything new that the bots say in chat must be in a translatable format.
This is done using GetBotTextOrDefault,
which you can search for in the codebase to find examples. Your code
needs to have English as the default fallback,
while the full translations need to be in an SQL update file. The
languages in the file are the nine language
options supported by AzerothCore: English, Korean, French, German,
Chinese, Taiwanese, Spanish, Spanish Mexico, and
Russian. See
data/sql/playerbots/updates/2025_12_27_ai_playerbot_fishing_text.sql as
an example of a translation SQL
update, whose content are called within the codebase at
src/strategy/actions/FishingAction.cpp
-->

## Final Checklist

- - [ x] Stability is not compromised.
- - [x ] Performance impact is understood, tested, and acceptable.
- - [x ] Added logic complexity is justified and explained.
- - [x] Any new bot dialogue lines are translated.
- - [x ] Documentation updated if needed (Conf comments, WiKi commands).

## Notes for Reviewers
<!-- Anything else that's helpful to review or test your pull request.
-->
I have not tested with multiple players, or large servers or with 3k+
bots

---------

Co-authored-by: Keleborn <22352763+Celandriel@users.noreply.github.com>
Co-authored-by: bash <hermensb@gmail.com>
Co-authored-by: Revision <tkn963@gmail.com>
Co-authored-by: kadeshar <kadeshar@gmail.com>
This commit is contained in:
Mat 2026-05-23 04:23:35 +02:00 committed by GitHub
parent 2973083dda
commit c7b4b9aa80
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
45 changed files with 27967 additions and 11070 deletions

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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);

View File

@ -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)

View File

@ -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

View File

@ -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); }

View File

@ -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"); }

View File

@ -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");

View 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

View 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;
}

File diff suppressed because it is too large Load Diff

View 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;
}

View 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;
}

View 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;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View 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();
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View 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;
}

File diff suppressed because it is too large Load Diff

View File

@ -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); }
}; };

File diff suppressed because it is too large Load Diff

View File

@ -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

View 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();
}

View 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

View File

@ -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));
} }

View File

@ -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"

View File

@ -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); }
}; };

View File

@ -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);
}

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -1,6 +0,0 @@
#ifndef _PLAYERBOT_RAIDICCSCRIPTS_H
#define _PLAYERBOT_RAIDICCSCRIPTS_H
#include "../../../../src/server/scripts/Northrend/IcecrownCitadel/icecrown_citadel.h"
#endif

View File

@ -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>
{ {

View File

@ -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"

View File

@ -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"

View 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
View 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

View File

@ -5,6 +5,7 @@
#include "PlayerbotAIConfig.h" #include "PlayerbotAIConfig.h"
#include <iostream> #include <iostream>
#include "BisListMgr.h"
#include "Config.h" #include "Config.h"
#include "NewRpgInfo.h" #include "NewRpgInfo.h"
#include "PlayerbotDungeonRepository.h" #include "PlayerbotDungeonRepository.h"
@ -595,6 +596,7 @@ bool PlayerbotAIConfig::Initialize()
autoGearCommand = sConfigMgr->GetOption<int32>("AiPlayerbot.AutoGearCommand", 1); autoGearCommand = sConfigMgr->GetOption<int32>("AiPlayerbot.AutoGearCommand", 1);
autoGearCommandAltBots = sConfigMgr->GetOption<int32>("AiPlayerbot.AutoGearCommandAltBots", 1); autoGearCommandAltBots = sConfigMgr->GetOption<int32>("AiPlayerbot.AutoGearCommandAltBots", 1);
autoGearBisCommand = sConfigMgr->GetOption<int32>("AiPlayerbot.AutoGearBisCommand", 0);
autoGearQualityLimit = sConfigMgr->GetOption<int32>("AiPlayerbot.AutoGearQualityLimit", 3); autoGearQualityLimit = sConfigMgr->GetOption<int32>("AiPlayerbot.AutoGearQualityLimit", 3);
autoGearScoreLimit = sConfigMgr->GetOption<int32>("AiPlayerbot.AutoGearScoreLimit", 0); autoGearScoreLimit = sConfigMgr->GetOption<int32>("AiPlayerbot.AutoGearScoreLimit", 0);
@ -692,6 +694,7 @@ bool PlayerbotAIConfig::Initialize()
PlayerbotGuildMgr::instance().Init(); PlayerbotGuildMgr::instance().Init();
sRandomItemMgr.Init(); sRandomItemMgr.Init();
sRandomItemMgr.InitAfterAhBot(); sRandomItemMgr.InitAfterAhBot();
sBisListMgr->LoadAll();
PlayerbotTextMgr::instance().LoadBotTexts(); PlayerbotTextMgr::instance().LoadBotTexts();
PlayerbotTextMgr::instance().LoadBotTextChance(); PlayerbotTextMgr::instance().LoadBotTextChance();
PlayerbotFactory::Init(); PlayerbotFactory::Init();

View File

@ -427,6 +427,7 @@ public:
altMaintenanceKeyring, altMaintenanceKeyring,
altMaintenanceGemsEnchants; altMaintenanceGemsEnchants;
int32 autoGearCommand, autoGearCommandAltBots, autoGearQualityLimit, autoGearScoreLimit; int32 autoGearCommand, autoGearCommandAltBots, autoGearQualityLimit, autoGearScoreLimit;
int32 autoGearBisCommand;
uint32 useGroundMountAtMinLevel; uint32 useGroundMountAtMinLevel;
uint32 useFastGroundMountAtMinLevel; uint32 useFastGroundMountAtMinLevel;

View File

@ -526,6 +526,7 @@ public:
void AddPlayerbotsSecureLoginScripts(); void AddPlayerbotsSecureLoginScripts();
void AddSC_TempestKeepBotScripts(); void AddSC_TempestKeepBotScripts();
void AddSC_IcecrownBotScripts();
void AddSC_HyjalSummitBotScripts(); void AddSC_HyjalSummitBotScripts();
void AddPlayerbotsScripts() void AddPlayerbotsScripts()
@ -542,5 +543,6 @@ void AddPlayerbotsScripts()
AddPlayerbotsCommandscripts(); AddPlayerbotsCommandscripts();
PlayerBotsGuildValidationScript(); PlayerBotsGuildValidationScript();
AddSC_TempestKeepBotScripts(); AddSC_TempestKeepBotScripts();
AddSC_IcecrownBotScripts();
AddSC_HyjalSummitBotScripts(); AddSC_HyjalSummitBotScripts();
} }