Compare commits

..

16 Commits

Author SHA1 Message Date
bashermens
5c9451b73f Quickfix, XP gain feature has huge performance impact 2025-12-25 02:00:40 +01:00
privatecore
88016789ba Quick fix for CMSG_FORCE_MOVE_ROOT_ACK and CMSG_FORCE_MOVE_UNROOT_ACK (#1937)
**Original issue:**
https://github.com/mod-playerbots/mod-playerbots/issues/1902
**Original cause:** charmed bot (with lost client control) got rooted at
the same time.

**How to reproduce:**
1. Spawn creatures 11350 (x3) and 11830 (x3) using the command: `.npc
add <entry>` in a quiet place.
2. Create any party with random bots or alt bots (should be 60-65
levels), make sure there is at least one healer.
3. Set the healer's mana to a high value, like 100M, using command:
`.mod mana <value>`.
4. Start the fight while continuously respawning creatures with: `.resp
all`.
5. When console starts to spam 'heartbeat' messages, check your party
members' movement flags to identify which one has `MOVEMENTFLAG_ROOT`
0x00000800 (2048) using the command: `.debug move`.

This PR will not fix ALL 'heartbeat' issues, as
`ServerFacade::SetFacingTo` still sends `SendMovementFlagUpdate` while
bots can have the `MOVEMENTFLAG_ROOT` flag.
2025-12-25 00:01:42 +01:00
bashermens
6be860c967 [Stability] Various crash fixes based on Regrad fixes and crashlogs. (#1928)
These contains various fixes, fixes that have history worked one in past
more then once as person as group, aswell @Wishmaster117. But due
various reasons we had to drop them due priority or simply timewise.
These fixes have recollected again by @Regrad based on his crash logs.

Most crash logs we have, i am talking 30+ of them, to many to post here.
@Regrad running a larger server 100+ real players with bots, which means
he will walk into issues that most of us wont or are extremely difficult
to reproduce.

@Regrad used LLM to solve them based on crash log and mentioned his
server crashes almost disappeared, instead of redoing every single PR
and pull them apart. I tried to keep his bunch of changes together as
whole, reviewed them, some redone, verified again etc etc. This is not
how would normally do this. But since i want @Regrad being able to
confirm, we need this in a package as a whole. Pulling them apart in the
current situation is simply to much, to complicated in the verification
process.

So this PR is open and in my opinion has priority above others, but
@Regrad is only person who can give the green light for the
mod-playerbot changes for now.

I, we spend huge amount of time into these issues over last couple of
months. I will put other PR's on hold for abit.

---------

Signed-off-by: Engardium <regradius@gmail.com>
Co-authored-by: Engardium <regradius@gmail.com>
2025-12-24 13:24:29 +01:00
Alex Dcnh
9971622093 Core - Fixe raid markers persists after target dead causes issue with bots running off (#1845)
This PR fixes #1833 where bots could keep chasing a raid target icon
(usually skull) across very large distances or even different maps after
the mark was set. It replaces the previous attempt with a simpler design
that keeps values generic and moves context logic into triggers/actions.

Changes

- RtiTargetValue now ignores RTI targets that are farther than
sPlayerbotAIConfig->sightDistance from the bot or the master (same map
only), while still using AttackersValue::IsValidTarget and LOS checks.
- AttackersValue is reverted to its original behavior (no special RTI +
IsInCombat logic).
- RTI triggers only react to "rti target" in combat, as suggested in
review (“that condition should be check in trigger…”).
- AttackRtiTargetAction keeps a small local fallback: if "rti target" is
null, it resolves the raid icon directly from the group so chat commands
like “attack rti target” still work, including out of combat.

Behavior

- Bots no longer run across the world to chase a stale skull far away
from the group.
- When the group comes back near the marked mob (within sight distance),
the skull is again used as a normal focus hint.
- Automatic RTI behavior is limited to combat via triggers, while
explicit chat commands still work out of combat.

Testing

- Marked a mob with skull, killed it, moved far away / changed zone,
then let it respawn: bots did not chase it from afar.
- In a normal dungeon/raid pack, bots still focus skull in combat.
- Passing near a previously marked mob: bots only react once it is back
within sight distance.

---------

Co-authored-by: Keleborn <22352763+Celandriel@users.noreply.github.com>
2025-12-23 20:42:29 +01:00
Crow
895df9b197 Magtheridon timer keys fix (#1936)
Same deal as Karazhan--changing map id to instance id for timer keys to
prevent conflicts from multiple groups (plus other tweaks with respect
to the timers). This strategy could use a broader refactor, but that's
for another day--this PR is just to address the timer logic.
2025-12-23 09:04:36 +01:00
Crow
467b63b840 Fix Karazhan timers (#1926)
I'm not 100% sure but think that the current implementation of timers
for Attumen, Netherspite, and Nightbane with the instance's map id as
the key would result in the timers being shared across separate raids
running the instance at the same time. I've refactored to use the
instance id as the key for all shared timers so to the extent this was
an issue, it shouldn't be anymore.

I also made some minor tweaks to the tank positioning for BBW and
Curator as I noticed there was some weirdness in the logic that I
neglected to address with the refactor.
2025-12-21 21:40:19 +01:00
HennyWilly
66f5f597bb Re-enable no-xp-feature with better checks (#1935)
The feature `Disable bot XP gain when master has XP turned off` (#1910)
was unintentionally disabled in #1929.
This PR re-enables it with better checks in order to prevent crashes
when playing together with the individual progression module.
2025-12-21 21:28:39 +01:00
privatecore
cafbd4681e Hotfix after recent changes related to the refactoring of IsMovingAllowed (#1933)
Hotfix for the issue where bots can't enter/move on transports
(elevators, zeppelins, ships, etc.). Change the logic to determine if
bot is on a vehicle. According to the current implementation in
AzerothCore, `GetVehicle()` is the most common approach for this.
Additionally, other checks related to the vehicle: `GetBase`, `IsAlive`,
`GetVehicleInfo`, and `GetSeatForPassenger` -- are processed inside
`IsInVehicle`.

This should be more than enough to determine if the bot is on a vehicle
and can/cant control it.

Issue: https://github.com/mod-playerbots/mod-playerbots/issues/1927
2025-12-20 23:51:32 +01:00
bashermens
f5c84ee7ff server_crash_fix: OnPlayerGiveXP (#1929)
[fef0ed4072c8_worldserver.exe_.18-12_23-30-44.txt](https://github.com/user-attachments/files/24258638/fef0ed4072c8_worldserver.exe_.18-12_23-30-44.txt)

- Just added common logic and defense coding
- Optimized isRandombot while at it

Just for clarification i am aware the trigger of bug lies with the
progression mod. Regardless it should not happen.
2025-12-19 23:08:18 +01:00
Keleborn
b6f882886d FixListSpellsWithCache (#1931)
This is based off of Wishmasters rewrite of spell PR. #1912 and #1843,
and partial #1918
I created a new cache singleton with the required code to reference as
needed.
I clarified some variable names for additional clarity in how they
functioned.

This requires a wiki update to better describe the functionality that
was already defined in code.

Commands
Spells - Returns all spells
Spells <Profession> Returns only the spells in that profession
+<profession> Returns only the recipies that the bot has materials to
craft
<profession> `x - y` Craftable items within those levels
<profession> <slot> e.g. Chest, returns craftable items within that
slot.

Its messy whether what combinations work for commands, but fixing that
will come when bot professions are enabled.

Edit:
To test you can teach a bot various professions by going to a trainer
with them.
Using gm command .setskill you increase their skill level and with
maintenance teach the commands.

From wishmasters PR he detailed various commands to test

spells
spells first aid
spells tailoring
spells 20-40
spells +tailoring
spells head

---------

Co-authored-by: Wishmaster117 <140754794+Wishmaster117@users.noreply.github.com>
2025-12-19 23:08:01 +01:00
HennyWilly
c1222da8b0 Disable bot XP gain when master has XP turned off (#1910)
Addresses #1846

This PR disables XP gain for bots whose non-bot master has disabled XP
gain via an "Experience Eliminator" NPC.

If the current master has disabled XP gain, randombots in the group and
addclass-bots belonging to the player won't gain XP.
Randombots not grouped with a player will continue to gain XP as normal.

Discussed points:
- `Should this feature only be enabled via a new configuration value?`:
Comments currently tend towards no config value.

Open points:
- Should bots be allowed to gain XP until they reach the player's level,
even if the player has disabled XP gain?
2025-12-16 22:23:34 +01:00
NoxMax
00cb177c86 Fix: Allow bots to duel in PVP prohibited areas (#1906)
Noticed that if you ask a bot to duel in a PVP prohibited area, it will
accept, and do nothing. I thought about making the bot reject the
request, but if you (the real player) want to duel with it, the duel
should happen. This is just a minor fix to allow bots to duel if you ask
them to in such areas.

Tested with bots in party, random bots of the same faction, and random
bots of the opposite faction. All behaved the same before and after fix.
An example place to test is Zim'Torga in Zul'Drak which is by default is
a PVP prohibited area.

- Before fix, you challenge a bot, they accept and turn red, then they
either just stay where they are or wander off.

- After fix, bot attacks you within the PVP prohibited area when the
duel starts.
2025-12-16 18:38:50 +01:00
privatecore
5f697e806e Rerwite is moving allowed logic + fix root flag heartbeat spam (#1908)
Okay, what have been done:

1. Fix heartbeat spam for root flag: check against `MOVEMENTFLAG_ROOT`
flag (`IsRooted`) instead of `HasRootAura`.
2. Rewrite `IsMovingAllowed` - place checks from most common to the
rarest.
3. Remove unnecessary checks: `HasRootAura`, `HasConfuseAura`,
`HasStunAura` - handled by AuraEffects and set unit state flags
`UNIT_STATE_ROOT`, `UNIT_STATE_CONFUSED`, `UNIT_STATE_STUNNED` -
`UNIT_STATE_LOST_CONTROL` already handles confused and stunned (rooted
checked with `IsRooted` method).
4. Combine traveling state checks for taxi flights:
`UNIT_STATE_IN_FLIGHT` + MM flag `FLIGHT_MOTION_TYPE`.
5. Simplify check against being in vehicle: use
`MOVEMENTFLAG_ONTRANSPORT` as an indicator that the unit is in the
vehicle.

Also, update `UpdateMovementState` method with simplified checks and the
updated logic (common > rare).

This should fix issues:
https://github.com/mod-playerbots/mod-playerbots/issues/1903 and
https://github.com/mod-playerbots/mod-playerbots/issues/1902

NOTE: The `PlayerbotAI` class has a method `CanMove` with the same
checks, but this method is only used once in the code. We should decide
how to properly check if the bot can move or not:

1. Place all logic into `IsMovingAllowed` and drop `CanMove`.
2. Place all logic into `CanMove` and use it inside `IsMovingAllowed`.
3. Use them for different approaches: 
- `CanMove`: simple checks (unit flags, CC state, death state, travel
state, vehicle state);
- `IsMovingAllowed`: everything from `CanMove` + MM flags checks (not
sure about rooted since it still checks for movement flags...).
2025-12-15 15:32:49 +01:00
Crow
934e73ae20 Add Ogri'la and Blackwind Landing to PvP Restricted Areas (#1915)
This PR adds a couple of neutral quest hubs in Outland to PvP restricted
areas (and makes a couple of very minor formatting fixes to the Karazhan
files).

3786: Ogri'la
3973: Blackwind Landing (Sha'tari Skyguard quest hub in Skettis)
2025-12-15 15:26:38 +01:00
Crow
f4b4d8967f Karazhan Refactor + Nightbane Strategy (#1847)
This PR completely refactors the Karazhan raid strategies to clean up
the code and polish up/introduce new strategies, including for
Nightbane.

General changes with respect to the code:
- Moved gating checks for action methods to triggers instead of relying
on isUseful functions
- Got rid of the nonsensical helper class I made and switched to a
helper namespace
- Broke up some longer action classes into separate methods and/or
private members
- Deleted/consolidated some excess code
- Made greater use of early returns
- Renamed methods, multipliers, and triggers to make them easier to
understand
- Generally made edits to conform with AC code standards

Below are the implemented strategies. I’m including all of them, not
just new/modified ones, because I don’t think the first set of
strategies was ever tested. So each boss could probably use complete
testing.

### **Trash**

I added strategies for a trash mob that I find particularly annoying:
- Mana Warp: These are, IMO, the most annoying trash mobs in Karazhan.
They blow up when low on health, and having two blow up in quick
succession is enough to take out a decent chunk of your raid. The
strategy directs bots to use pretty much every stun in the book on Mana
Warps when they’re low on HP, which is the only way to prevent the
explosion.

### **Attumen**

- During the first phase, bots will focus on Midnight (taking either
Midnight or Attumen to 25% starts the next phase). When Attumen spawns,
the assist tank will pick him up and move him away so that bots don’t
get cleaved.
- When Attumen mounts Midnight, starting phase 2, threat is wiped, and
bots will pause DPS for a few seconds to allow the main tank to get
aggro. All bots, other than the main tank and any bot that pulls aggro,
will stack behind Attumen (~6 yards for ranged so Hunters can still
attack).

### **Moroes**

- As before, bots will mark and prioritize adds and the boss in the
recommended kill order: Dorothea, Catriona, Keira, Rafe, Robin, Crispin,
and Moroes. In practice, the enemies will probably be stacked up, and
the bots will AoE them down in accordance with their typical AoE
strategies, but classes without AoE capabilities should still prioritize
the skull.
- Based on testing feedback, added a method for the main tank to
prioritize Moroes

### **Maiden of Virtue**

I’ve made only minor changes to Revision’s original strategy here. 

- The tank with aggro will position Maiden in the middle of the room and
move her to a healer when Repentance is cast so that the Holy Ground
will break the healer’s stun.
- Ranged bots have assigned positions between the columns around the
middle of the room (to prevent chain damage from Holy Wrath).

### **The Big Bad Wolf**

- The tank with aggro brings the boss to the front left of the stage. 
- If a bot gets turned into Little Red Riding Hood, it will run around
the stage in a counter-clockwise rectangle until the transformation
fades. I tweaked this strategy a bit; it's still not perfect, but it
works better than before.

### **Romulo and Julianne**

There are no substantive changes to this strategy. As before, in phase
3, when both bosses are active, the bots switch back-and-forth between
the bosses by alternating the skull icon based on which boss has lower
HP (10% differential).

### **The Wizard of Oz**

There are no substantive changes to this strategy. As before, bots mark
the bosses with a skull icon in their recommended kill order: Dorothee,
Tito (assuming he spawns before you kill Dorothee), Roar, Strawman,
Tinhead, and the Crone. Additionally, Mages will spam Scorch on Strawman
to daze him.

### **The Curator**

- The tank will drag the boss to a fixed spot down the hallway. Ranged
bots will spread out to avoid chain damage from Arcing Sear, and bots
will mark Astral Flares with the skull icon to prioritize them down.
- Those strategies already existed, but now I made the assist tank also
focus on the boss to try to stay second in aggro and therefore absorb
Hateful Bolts.
- Added a multiplier to save Bloodlust/Heroism until Evocation.

### **Terestian Illhoof**

There are no substantive changes to this strategy. The bots will mark
targets with the skull icon in the following order: Demonic Chains,
Kil'rek, and Illhoof.

### **Shade of Aran**
I redid the strategies a bit, and I think (hope) they work better.

- Flame Wreath: Bots will stop moving until the aura fades. There is a
bug in which Flame Wreath will sometimes persist long beyond its
supposed 20-second duration. If Aran casts Arcane Explosion during this
time, you will almost certainly wipe, so that’s frustrating. I made it
so that bots will stay in place still and eat the Arcane Explosion
because it’s the lesser of two evils, and if you are overgeared, you may
be able to survive. In the previous strategy, bots would stop actions
entirely, but now they should keep attacking/casting without moving.
- Arcane Explosion: No substantive changes here--bots will run out
immediately and stay out of range until the cast finishes.
- Conjured Elementals: No substantive changes here--they will be marked
one-by-one by the skull icon, except that the marking will skip any
elemental that is banished.
- Ranged Positioning: I redid this strategy. Ranged bots will now
maintain a distance between 11 and 15 yards from the boss. This keeps
them out of the 10-yard radius in which Aran silences while also keeping
them from getting too far away and getting stuck in the alcoves.

### **Netherspite**

I significantly refactored the action methods here, but substantively
the original strategy remains mostly intact.

- Red (Tank) Beam: One tank will be assigned to block the beam for each
Portal Phase. The assigned tank will dance in and out of the beam (5
seconds in, 5 seconds out). Tanks intentionally do not avoid Void Zones
(it was the lesser of two evils for them to take that damage vs. trying
to dynamically avoid them, moving the boss, and possibly getting
everybody out of position.
- Blue (DPS) Beam: DPS other than Rogues and Warriors are eligible to be
assigned (one-by-one) to block this beam. When the assigned blocker
reaches 25 stacks of the debuff, they will leave the beam, and the next
assigned blocker will take their place. If a Void Zone drops under the
assigned blocker, the bot will move along the beam to get out of the
Void Zone so that they do not stop blocking.
- Green (Healer) Beam: This works the same way as the Blue Beam, except
that eligible blockers are healers, Rogues, and DPS Warriors. Healers
that are assigned to block will swap in the same way as Blue Beam
blockers. If a Rogue or DPS Warrior is the assigned blocker, however,
they will stand in the beam for the entire Portal Phase since they do
not suffer any adverse effects from the beam. In this PR, I made the
strategy prioritize Rogues and DPS Warriors over healers to try to avoid
the need for bots to swap (and to avoid the irritating scenario in which
a healer would block the beam for the first half of a phase and then tag
in a Rogue or DPS Warrior, which would be wasted by blocking only half
of a phase).
- Non-Blockers: They will stay at least 5 yards away from each beam
until called to be an assigned blocker. They will also avoid Void Zones.
- Banish Phase: The only strategy I implemented was for bots to avoid
residual Void Zones from the Portal Phase.
- Phase Transitions: Bots should pause DPS at the beginning of the
encounter and whenever Netherspite transitions back from the Banish
Phase to the Portal Phase (which is an aggro reset). Note that this
doesn't wipe DOTs, and there's not much I can do about that.

### **Prince Malchezaar**

The action methods are significantly refactored, but the strategy
substantively is not changed very much.

- Bots will maintain distance from Infernals. The tank has a larger
avoidance radius to give DPS a little bit of margin to work with.
Depending on Infernal placement, it is possible for bots to get stuck in
some bad positions. In that case, the best solution is to put them on
“flee” and lead them to a better position.
- Bots that get Enfeebled will run out of Shadow Nova range. They should
pick a path that does not cross within any Infernal's Hellfire radius.
- Added a multiplier to save Bloodlust/Heroism until Phase 3.

### **Nightbane**

**Disclaimer**: Bots are terrible at this encounter, in large part
because the map is awful (even before the recent Core changes). So the
strategies are not ideal because they need to operate within the bots’
limitations. I STRONGLY suggest you clear the entire Livery Stables
(including the upper level) because the mobs in them have a high risk of
pulling through the floor of the Master’s Terrace. Ideally, you should
clear out the Scullery too.

The strategy uses waypoints toward the Northeastern door to the Master’s
Terrace. I tried several different locations, and that worked best for
me based on where Nightbane lands (note that he has a different landing
spot for the encounter start vs. the start subsequent ground phases).

- Ground Phase, main tank: The main tank uses two waypoints after it
picks up the boss—the movement pattern should be kind of like a reverse
checkmark, where the tank moves back along the inner edge of the
terrace, then pivots and moves at a bit of an angle to the outer edge. I
did this as a way to get the tank to face Nightbane sideways across the
terrace, which is how he’s supposed to be tanked. The main tank will not
get out of Charred Earth. This is intended. The tank cannot move
dynamically enough to avoid it while also not turning the boss and
wiping the raid.
- Ground phase, ranged: Ranged bots rotate between three waypoints. They
start stacked at the same position. If Charred Earth is dropped on that
position, the bots will rotate to the second position. If Charred Earth
is dropped on that position, they will rotate to the third position. The
maximum number of Charred Earths that can be active is two, so if one is
dropped on the third position, the ranged bots should rotate back to the
first position.
- Ground Phase, other melee: Melee bots have no coded Charred Earth
avoidance strategy. They do decently enough with the general “avoid aoe”
strategy.
- Flight Phase: Bots move to Nightbane’s flight position—Nightbane is
bugged and cannot be attacked in the air in AC, but all bots still need
to stay near him or he will wipe the raid with Fireball Barrage. Bots
stack on the same position; when Rain of Bones is cast (one time,
snapshotted on the target’s location), all bots will move away to a
second nearby position. They will then kill the Restless Skeletons. The
Flight Phase lasts for 45 seconds, but Nightbane emotes after 35 seconds
and goes to land—during the final 10-second period, bots are freed from
all strategies and will follow the master. You need to lead them back to
the Northeastern part of the terrace before Nightbane lands so that bots
can get immediately positioned when he lands.
- Phase Changes: Whenever Nightbane lands, bots should pause DPS for the
main tank to get aggro.
- Managing bots/pets: During the Flight Phase, bots and pets have a
tendency to chase Nightbane outside of the boundaries of the map and
pull mobs from other parts of the instance as well as cause Restless
Skeletons to spawn out of bounds (and then further cause bots/pets to go
out of bounds to attack the skeletons). The strategy should solve for
this, but if there are still issues, it should be noted. My resolution
was to implement the following: (1) when Nightbane takes flight, bots
stop attacking and mark him with the moon icon, and Hunters and Warlocks
put pets on passive and recall them (they will put pets on defensive
again when Nightbane lands), and (2) all temporary pets are disabled for
the duration of the fight (Water Elementals, Shaman wolves, Druid
treants, etc.).

**Known Issues:**

- The approach to getting Nightbane to turn sideways is not always spot
on since it’s a workaround to account for what should be dynamic
movement. He can end up at a bit of an angle. I’ve tweaked the positions
such that if he is at an angle, it should not be a situation in which
any ranged position is exposed to Smoldering Breath or Cleave. That does
increase the risk that poor positioning may expose a ranged position to
Tail Sweep. This is obviously suboptimal, but that attack is much more
survivable.
- Ranged bots move between the three waypoints by reading the presence
of the Charred Earth aura on themselves. Sometimes, a bot may reach the
next waypoint with the Charred Earth aura from the previous waypoint
still on them (depending on timing of ticks), in which case the bot will
keep moving to the next waypoint. This can cause an issue in which the
ranged group gets split. So you could have Charred Earth dropped on
position 1, and some ranged bots will go to position 2 but others will
go to position 3, and then if the second Charred Earth is dropped on
position 3, the bots at position 3 will move back to position 1 (which
is still in Charred Earth) before then moving to position 2. Balancing
spacing between waypoints, positioning with respect to the boss, line of
sight, and maximum distance is very difficult to do. I messed with the
positioning a lot, and I am not sure if this possibility cannot be
entirely avoided without creating other issues. I have at least reached
a result in which I have not seen any bots cycle indefinitely (though if
testing observes this, it is obviously a problem).

Ultimately, I wouldn’t say I’m totally satisfied with the strategy.
Wipes are still possible on account of bad RNG. But I think it does make
the fight a lot more manageable, as it is a fight that is very difficult
with IP nerfs in the absence of any strategy. Previously, I needed to
bring 3 healers, and they would still all be out of mana by the first
Flight Phase and reliant on MP5 for the remainder of the fight, and I
would wipe multiple times before a kill.
2025-12-10 21:08:25 +01:00
Nicolas Lebacq
910b8a9c53 fix: Made bots roll in a more reasonable time on group loots. (#1857)
# Description

This PR changes the way loot rolls are being evaluated.

It puts a maximum priority on the loot action so it does not hang for so
long.
2025-12-09 10:29:57 -08:00
43 changed files with 4257 additions and 2412 deletions

View File

@@ -630,7 +630,7 @@ AiPlayerbot.RandomBotHordeRatio = 50
AiPlayerbot.DisableDeathKnightLogin = 0
# Enable simulated expansion limitation for talents and glyphs
# If enabled, limits talent trees to 5 rows plus the middle talent of the 6th row for bots until level 61
# If enabled, limits talent trees to 5 rows plus the middle talent of the 6th row for bots until level 61
# and 7 rows plus the middle talent of the 8th row for bots from level 61 until level 71
# Default: 0 (disabled)
AiPlayerbot.LimitTalentsExpansion = 0
@@ -1185,7 +1185,7 @@ AiPlayerbot.DeleteRandomBotArenaTeams = 0
AiPlayerbot.PvpProhibitedZoneIds = "2255,656,2361,2362,2363,976,35,2268,3425,392,541,1446,3828,3712,3738,3565,3539,3623,4152,3988,4658,4284,4418,4436,4275,4323,4395,3703,4298,3951"
# PvP Restricted Areas (bots don't pvp)
AiPlayerbot.PvpProhibitedAreaIds = "976,35,392,2268,4161,4010,4317,4312,3649,3887,3958,3724,4080,3938,3754"
AiPlayerbot.PvpProhibitedAreaIds = "976,35,392,2268,4161,4010,4317,4312,3649,3887,3958,3724,4080,3938,3754,3786,3973"
# Improve reaction speeds in battlegrounds and arenas (may cause lag)
AiPlayerbot.FastReactInBG = 1

View File

@@ -242,8 +242,8 @@ void PlayerbotAI::UpdateAI(uint32 elapsed, bool minimal)
nextAICheckDelay = 0;
// Early return if bot is in invalid state
if (!bot || !bot->IsInWorld() || !bot->GetSession() || bot->GetSession()->isLogingOut() ||
bot->IsDuringRemoveFromWorld())
if (!bot || !bot->GetSession() || !bot->IsInWorld() || bot->IsBeingTeleported() ||
bot->GetSession()->isLogingOut() || bot->IsDuringRemoveFromWorld())
return;
// Handle cheat options (set bot health and power if cheats are enabled)
@@ -713,39 +713,59 @@ void PlayerbotAI::HandleTeleportAck()
if (IsRealPlayer())
return;
// Clearing motion generators and stopping movement which prevents
// conflicts between teleport and any active motion (walk, run, swim, flight, etc.)
bot->GetMotionMaster()->Clear(true);
bot->StopMoving();
// Near teleport (within map/instance)
if (bot->IsBeingTeleportedNear())
{
// Temporary fix for instance can not enter
if (!bot->IsInWorld())
{
bot->GetMap()->AddPlayerToMap(bot);
}
while (bot->IsInWorld() && bot->IsBeingTeleportedNear())
{
Player* plMover = bot->m_mover->ToPlayer();
if (!plMover)
return;
WorldPacket p = WorldPacket(MSG_MOVE_TELEPORT_ACK, 20);
p << plMover->GetPackGUID();
p << (uint32)0; // supposed to be flags? not used currently
p << (uint32)0; // time - not currently used
bot->GetSession()->HandleMoveTeleportAck(p);
};
// Previous versions manually added the bot to the map if it was not in the world.
// not needed: HandleMoveTeleportAckOpcode() safely attaches the player to the map
// and clears IsBeingTeleportedNear().
Player* plMover = bot->m_mover->ToPlayer();
if (!plMover)
return;
// Send the near teleport ACK packet
WorldPacket p(MSG_MOVE_TELEPORT_ACK, 20);
p << plMover->GetPackGUID();
p << uint32(0);
p << uint32(0);
bot->GetSession()->HandleMoveTeleportAck(p);
// Simulate teleport latency and prevent AI from running too early (used cmangos delays)
SetNextCheckDelay(urand(1000, 2000));
}
// Far teleport (worldport / different map)
if (bot->IsBeingTeleportedFar())
{
while (bot->IsBeingTeleportedFar())
// Handle far teleport ACK:
// Moves the bot to the new map, clears IsBeingTeleportedFar(), updates session/map references
bot->GetSession()->HandleMoveWorldportAck();
// Ensure bot now has a valid map. If this fails, there is a core/session bug?
if (!bot->GetMap())
{
bot->GetSession()->HandleMoveWorldportAck();
LOG_ERROR("playerbot", "Bot {} has no map after worldport ACK", bot->GetGUID().ToString());
}
// SetNextCheckDelay(urand(2000, 5000));
// Instance strategies after teleport
if (sPlayerbotAIConfig->applyInstanceStrategies)
ApplyInstanceStrategies(bot->GetMapId(), true);
// healer DPS strategies if restrictions are enabled
if (sPlayerbotAIConfig->restrictHealerDPS)
EvaluateHealerDpsStrategy();
// Reset AI state to to before teleport conditions
Reset(true);
// Slightly longer delay to simulate far teleport latency (used cmangos delays)
SetNextCheckDelay(urand(2000, 5000));
}
SetNextCheckDelay(sPlayerbotAIConfig->globalCoolDown);
@@ -988,10 +1008,10 @@ void PlayerbotAI::HandleBotOutgoingPacket(WorldPacket const& packet)
{
if (packet.empty())
return;
if (!bot || !bot->IsInWorld() || bot->IsDuringRemoveFromWorld())
{
return;
}
switch (packet.GetOpcode())
{
case SMSG_SPELL_FAILURE:
@@ -1159,7 +1179,26 @@ void PlayerbotAI::HandleBotOutgoingPacket(WorldPacket const& packet)
return;
}
case SMSG_MOVE_KNOCK_BACK: // handle knockbacks
case SMSG_FORCE_MOVE_ROOT: // CMSG_FORCE_MOVE_ROOT_ACK
case SMSG_FORCE_MOVE_UNROOT: // CMSG_FORCE_MOVE_UNROOT_ACK
{
// Quick fix for CMSG_FORCE_MOVE_ROOT_ACK and CMSG_FORCE_MOVE_UNROOT_ACK:
// this should resolve issues with MOVEMENTFLAG_ROOT being permanently set
// when rooted during lost client control (charm + root effects)
// @see https://github.com/azerothcore/azerothcore-wotlk/pull/23147
bool forceRoot = (packet.GetOpcode() == SMSG_FORCE_MOVE_ROOT);
if (forceRoot)
{
bot->m_movementInfo.RemoveMovementFlag(MOVEMENTFLAG_MASK_MOVING_FLY);
bot->m_movementInfo.AddMovementFlag(MOVEMENTFLAG_ROOT);
bot->StopMoving();
}
else
bot->m_movementInfo.RemoveMovementFlag(MOVEMENTFLAG_ROOT);
return;
}
case SMSG_MOVE_KNOCK_BACK: // CMSG_MOVE_KNOCK_BACK_ACK
{
WorldPacket p(packet);
p.rpos(0);
@@ -1331,10 +1370,6 @@ void PlayerbotAI::DoNextAction(bool min)
bool isBotAlive = bot->IsAlive();
if (currentEngine != engines[BOT_STATE_DEAD] && !isBotAlive)
{
bot->StopMoving();
bot->GetMotionMaster()->Clear();
bot->GetMotionMaster()->MoveIdle();
// Death Count to prevent skeleton piles
// Player* master = GetMaster(); // warning here - whipowill
if (!HasActivePlayerMaster() && !bot->InBattleground())
@@ -1355,6 +1390,8 @@ void PlayerbotAI::DoNextAction(bool min)
// Change engine if just ressed
if (currentEngine == engines[BOT_STATE_DEAD] && isBotAlive)
{
bot->SendMovementFlagUpdate();
ChangeEngine(BOT_STATE_NON_COMBAT);
return;
}
@@ -1384,9 +1421,6 @@ void PlayerbotAI::DoNextAction(bool min)
else if (bot->isAFK())
bot->ToggleAFK();
Group* group = bot->GetGroup();
PlayerbotAI* masterBotAI = nullptr;
if (master && master->IsInWorld())
{
float distance = sServerFacade->GetDistance2d(bot, master);
@@ -4106,8 +4140,7 @@ Player* PlayerbotAI::FindNewMaster()
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
{
Player* member = gref->GetSource();
if (!member || member == bot || !member->IsInWorld() ||
!member->IsInSameRaidWith(bot))
if (!member || member == bot || !member->IsInWorld() || !member->IsInSameRaidWith(bot))
continue;
PlayerbotAI* memberBotAI = GET_PLAYERBOT_AI(member);
@@ -4342,6 +4375,11 @@ inline bool ZoneHasRealPlayers(Player* bot)
bool PlayerbotAI::AllowActive(ActivityType activityType)
{
// Early return if bot is in invalid state
if (!bot || !bot->GetSession() || !bot->IsInWorld() || bot->IsBeingTeleported() ||
bot->GetSession()->isLogingOut() || bot->IsDuringRemoveFromWorld())
return false;
// when botActiveAlone is 100% and smartScale disabled
if (sPlayerbotAIConfig->botActiveAlone >= 100 && !sPlayerbotAIConfig->botActiveAloneSmartScale)
{
@@ -4432,10 +4470,8 @@ bool PlayerbotAI::AllowActive(ActivityType activityType)
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
{
Player* member = gref->GetSource();
if ((!member || !member->IsInWorld()) && member->GetMapId() != bot->GetMapId())
{
if (!member || !member->IsInWorld() || member->GetMapId() != bot->GetMapId())
continue;
}
if (member == bot)
{
@@ -4486,23 +4522,23 @@ bool PlayerbotAI::AllowActive(ActivityType activityType)
// HasFriend
if (sPlayerbotAIConfig->BotActiveAloneForceWhenIsFriend)
{
if (!bot || !bot->IsInWorld() || !bot->GetGUID())
// shouldnt be needed analyse in future
if (!bot->GetGUID())
return false;
for (auto& player : sRandomPlayerbotMgr->GetPlayers())
{
if (!player || !player->IsInWorld())
if (!player || !player->GetSession() || !player->IsInWorld() || player->IsDuringRemoveFromWorld() ||
player->GetSession()->isLogingOut())
continue;
Player* connectedPlayer = ObjectAccessor::FindPlayer(player->GetGUID());
if (!connectedPlayer)
PlayerbotAI* playerAI = GET_PLAYERBOT_AI(player);
if (!playerAI || !playerAI->IsRealPlayer())
continue;
// if a real player has the bot as a friend
PlayerSocial* social = player->GetSocial();
if (!social)
continue;
if (social->HasFriend(bot->GetGUID()))
if (social && social->HasFriend(bot->GetGUID()))
return true;
}
}
@@ -4516,7 +4552,7 @@ bool PlayerbotAI::AllowActive(ActivityType activityType)
}
}
// Bots don't need to move using PathGenerator.
// Bots don't need react to PathGenerator activities
if (activityType == DETAILED_MOVE_ACTIVITY)
{
return false;
@@ -4552,15 +4588,25 @@ bool PlayerbotAI::AllowActive(ActivityType activityType)
bool PlayerbotAI::AllowActivity(ActivityType activityType, bool checkNow)
{
if (!allowActiveCheckTimer[activityType])
allowActiveCheckTimer[activityType] = time(nullptr);
const int activityIndex = static_cast<int>(activityType);
if (!checkNow && time(nullptr) < (allowActiveCheckTimer[activityType] + 5))
return allowActive[activityType];
// Unknown/out-of-range avoid blocking, added logging for further analysing should not happen in the first place.
if (activityIndex <= 0 || activityIndex >= MAX_ACTIVITY_TYPE)
{
LOG_ERROR("playerbots", "AllowActivity received invalid activity type value: {}", activityIndex);
return true;
}
if (!allowActiveCheckTimer[activityIndex])
allowActiveCheckTimer[activityIndex] = time(nullptr);
if (!checkNow && time(nullptr) < (allowActiveCheckTimer[activityIndex] + 5))
return allowActive[activityIndex];
const bool allowed = AllowActive(activityType);
allowActive[activityIndex] = allowed;
allowActiveCheckTimer[activityIndex] = time(nullptr);
bool allowed = AllowActive(activityType);
allowActive[activityType] = allowed;
allowActiveCheckTimer[activityType] = time(nullptr);
return allowed;
}
@@ -5344,15 +5390,13 @@ Item* PlayerbotAI::FindStoneFor(Item* weapon) const
if (!item_template)
return nullptr;
static const std::vector<uint32_t> uPrioritizedSharpStoneIds = {
ADAMANTITE_SHARPENING_STONE, FEL_SHARPENING_STONE, ELEMENTAL_SHARPENING_STONE, DENSE_SHARPENING_STONE,
SOLID_SHARPENING_STONE, HEAVY_SHARPENING_STONE, COARSE_SHARPENING_STONE, ROUGH_SHARPENING_STONE
};
static const std::vector<uint32_t> uPrioritizedSharpStoneIds = {
ADAMANTITE_SHARPENING_STONE, FEL_SHARPENING_STONE, ELEMENTAL_SHARPENING_STONE, DENSE_SHARPENING_STONE,
SOLID_SHARPENING_STONE, HEAVY_SHARPENING_STONE, COARSE_SHARPENING_STONE, ROUGH_SHARPENING_STONE};
static const std::vector<uint32_t> uPrioritizedWeightStoneIds = {
ADAMANTITE_WEIGHTSTONE, FEL_WEIGHTSTONE, DENSE_WEIGHTSTONE, SOLID_WEIGHTSTONE,
HEAVY_WEIGHTSTONE, COARSE_WEIGHTSTONE, ROUGH_WEIGHTSTONE
};
static const std::vector<uint32_t> uPrioritizedWeightStoneIds = {
ADAMANTITE_WEIGHTSTONE, FEL_WEIGHTSTONE, DENSE_WEIGHTSTONE, SOLID_WEIGHTSTONE,
HEAVY_WEIGHTSTONE, COARSE_WEIGHTSTONE, ROUGH_WEIGHTSTONE};
Item* stone = nullptr;
ItemTemplate const* pProto = weapon->GetTemplate();
@@ -5388,7 +5432,6 @@ static const std::vector<uint32_t> uPrioritizedWeightStoneIds = {
Item* PlayerbotAI::FindOilFor(Item* weapon) const
{
if (!weapon)
return nullptr;
@@ -5397,12 +5440,12 @@ Item* PlayerbotAI::FindOilFor(Item* weapon) const
return nullptr;
static const std::vector<uint32_t> uPrioritizedWizardOilIds = {
BRILLIANT_WIZARD_OIL, SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL,
BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL};
BRILLIANT_WIZARD_OIL, SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL,
BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL};
static const std::vector<uint32_t> uPrioritizedManaOilIds = {
BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL,
BRILLIANT_WIZARD_OIL, SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL};
BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL, BRILLIANT_WIZARD_OIL,
SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL};
Item* oil = nullptr;
int botClass = bot->getClass();
@@ -5418,22 +5461,22 @@ Item* PlayerbotAI::FindOilFor(Item* weapon) const
prioritizedOils = &uPrioritizedWizardOilIds;
break;
case CLASS_DRUID:
if (specTab == 0) // Balance
if (specTab == 0) // Balance
prioritizedOils = &uPrioritizedWizardOilIds;
else if (specTab == 1) // Feral
else if (specTab == 1) // Feral
prioritizedOils = nullptr;
else // Restoration (specTab == 2) or any other/unspecified spec
else // Restoration (specTab == 2) or any other/unspecified spec
prioritizedOils = &uPrioritizedManaOilIds;
break;
case CLASS_HUNTER:
prioritizedOils = &uPrioritizedManaOilIds;
break;
case CLASS_PALADIN:
if (specTab == 1) // Protection
if (specTab == 1) // Protection
prioritizedOils = &uPrioritizedWizardOilIds;
else if (specTab == 2) // Retribution
else if (specTab == 2) // Retribution
prioritizedOils = nullptr;
else // Holy (specTab == 0) or any other/unspecified spec
else // Holy (specTab == 0) or any other/unspecified spec
prioritizedOils = &uPrioritizedManaOilIds;
break;
default:

View File

@@ -165,7 +165,7 @@ bool PlayerbotAIConfig::Initialize()
pvpProhibitedZoneIds);
LoadList<std::vector<uint32>>(
sConfigMgr->GetOption<std::string>("AiPlayerbot.PvpProhibitedAreaIds",
"976,35,392,2268,4161,4010,4317,4312,3649,3887,3958,3724,4080,3938,3754"),
"976,35,392,2268,4161,4010,4317,4312,3649,3887,3958,3724,4080,3938,3754,3786,3973"),
pvpProhibitedAreaIds);
fastReactInBG = sConfigMgr->GetOption<bool>("AiPlayerbot.FastReactInBG", true);
LoadList<std::vector<uint32>>(

View File

@@ -9,9 +9,10 @@
#include <cstring>
#include <istream>
#include <string>
#include <openssl/sha.h>
#include <unordered_set>
#include <openssl/sha.h>
#include <iomanip>
#include <algorithm>
#include "ChannelMgr.h"
#include "CharacterCache.h"
@@ -34,12 +35,9 @@
#include "RandomPlayerbotMgr.h"
#include "SharedDefines.h"
#include "WorldSession.h"
#include "ChannelMgr.h"
#include "BroadcastHelper.h"
#include "PlayerbotDbStore.h"
#include "WorldSessionMgr.h"
#include "DatabaseEnv.h" // Added for gender choice
#include <algorithm> // Added for gender choice
#include "DatabaseEnv.h"
class BotInitGuard
{
@@ -68,6 +66,7 @@ private:
};
std::unordered_set<ObjectGuid> BotInitGuard::botsBeingInitialized;
std::unordered_set<ObjectGuid> PlayerbotHolder::botLoading;
PlayerbotHolder::PlayerbotHolder() : PlayerbotAIBase(false) {}
class PlayerbotLoginQueryHolder : public LoginQueryHolder
@@ -76,13 +75,12 @@ private:
uint32 masterAccountId;
PlayerbotHolder* playerbotHolder;
public:
PlayerbotLoginQueryHolder(PlayerbotHolder* playerbotHolder, uint32 masterAccount, uint32 accountId, ObjectGuid guid)
: LoginQueryHolder(accountId, guid), masterAccountId(masterAccount), playerbotHolder(playerbotHolder)
PlayerbotLoginQueryHolder(uint32 masterAccount, uint32 accountId, ObjectGuid guid)
: LoginQueryHolder(accountId, guid), masterAccountId(masterAccount)
{
}
uint32 GetMasterAccountId() const { return masterAccountId; }
PlayerbotHolder* GetPlayerbotHolder() { return playerbotHolder; }
};
void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId)
@@ -143,7 +141,7 @@ void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId
return;
}
std::shared_ptr<PlayerbotLoginQueryHolder> holder =
std::make_shared<PlayerbotLoginQueryHolder>(this, masterAccountId, accountId, playerGuid);
std::make_shared<PlayerbotLoginQueryHolder>(masterAccountId, accountId, playerGuid);
if (!holder->Initialize())
{
return;
@@ -153,8 +151,27 @@ void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId
// Always login in with world session to avoid race condition
sWorld->AddQueryHolderCallback(CharacterDatabase.DelayQueryHolder(holder))
.AfterComplete([this](SQLQueryHolderBase const& holder)
{ HandlePlayerBotLoginCallback(static_cast<PlayerbotLoginQueryHolder const&>(holder)); });
.AfterComplete(
[](SQLQueryHolderBase const& queryHolder)
{
PlayerbotLoginQueryHolder const& holder = static_cast<PlayerbotLoginQueryHolder const&>(queryHolder);
PlayerbotHolder* mgr = sRandomPlayerbotMgr; // could be null
uint32 masterAccountId = holder.GetMasterAccountId();
if (masterAccountId)
{
// verify and find current world session of master
WorldSession* masterSession = sWorldSessionMgr->FindSession(masterAccountId);
Player* masterPlayer = masterSession ? masterSession->GetPlayer() : nullptr;
if (masterPlayer)
mgr = GET_PLAYERBOT_MGR(masterPlayer);
}
if (mgr)
mgr->HandlePlayerBotLoginCallback(holder);
else
PlayerbotHolder::botLoading.erase(holder.GetGuid());
});
}
bool PlayerbotHolder::IsAccountLinked(uint32 accountId, uint32 linkedAccountId)
@@ -169,8 +186,9 @@ void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder con
uint32 botAccountId = holder.GetAccountId();
// At login DBC locale should be what the server is set to use by default (as spells etc are hardcoded to ENUS this
// allows channels to work as intended)
WorldSession* botSession = new WorldSession(botAccountId, "", 0x0, nullptr, SEC_PLAYER, EXPANSION_WRATH_OF_THE_LICH_KING,
time_t(0), sWorld->GetDefaultDbcLocale(), 0, false, false, 0, true);
WorldSession* botSession =
new WorldSession(botAccountId, "", 0x0, nullptr, SEC_PLAYER, EXPANSION_WRATH_OF_THE_LICH_KING, time_t(0),
sWorld->GetDefaultDbcLocale(), 0, false, false, 0, true);
botSession->HandlePlayerLoginFromDB(holder); // will delete lqh
@@ -181,26 +199,27 @@ void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder con
LOG_DEBUG("mod-playerbots", "Bot player could not be loaded for account ID: {}", botAccountId);
botSession->LogoutPlayer(true);
delete botSession;
botLoading.erase(holder.GetGuid());
PlayerbotHolder::botLoading.erase(holder.GetGuid());
return;
}
uint32 masterAccount = holder.GetMasterAccountId();
WorldSession* masterSession = masterAccount ? sWorldSessionMgr->FindSession(masterAccount) : nullptr;
uint32 masterAccountId = holder.GetMasterAccountId();
WorldSession* masterSession = masterAccountId ? sWorldSessionMgr->FindSession(masterAccountId) : nullptr;
// Check if masterSession->GetPlayer() is valid
Player* masterPlayer = masterSession ? masterSession->GetPlayer() : nullptr;
if (masterSession && !masterPlayer)
{
LOG_DEBUG("mod-playerbots", "Master session found but no player is associated for master account ID: {}", masterAccount);
LOG_DEBUG("mod-playerbots", "Master session found but no player is associated for master account ID: {}",
masterAccountId);
}
sRandomPlayerbotMgr->OnPlayerLogin(bot);
auto op = std::make_unique<OnBotLoginOperation>(bot->GetGUID(), this);
auto op = std::make_unique<OnBotLoginOperation>(bot->GetGUID(), masterAccountId);
sPlayerbotWorldProcessor->QueueOperation(std::move(op));
botLoading.erase(holder.GetGuid());
PlayerbotHolder::botLoading.erase(holder.GetGuid());
}
void PlayerbotHolder::UpdateSessions()

View File

@@ -60,7 +60,7 @@ protected:
virtual void OnBotLoginInternal(Player* const bot) = 0;
PlayerBotMap playerBots;
std::unordered_set<ObjectGuid> botLoading;
static std::unordered_set<ObjectGuid> botLoading;
};
class PlayerbotMgr : public PlayerbotHolder

View File

@@ -9,6 +9,7 @@
#include "Group.h"
#include "GroupMgr.h"
#include "GuildMgr.h"
#include "Playerbots.h"
#include "ObjectAccessor.h"
#include "PlayerbotOperation.h"
#include "Player.h"
@@ -16,6 +17,8 @@
#include "PlayerbotMgr.h"
#include "PlayerbotDbStore.h"
#include "RandomPlayerbotMgr.h"
#include "WorldSession.h"
#include "WorldSessionMgr.h"
// Group invite operation
class GroupInviteOperation : public PlayerbotOperation
@@ -468,18 +471,31 @@ private:
class OnBotLoginOperation : public PlayerbotOperation
{
public:
OnBotLoginOperation(ObjectGuid botGuid, PlayerbotHolder* holder)
: m_botGuid(botGuid), m_holder(holder)
OnBotLoginOperation(ObjectGuid botGuid, uint32 masterAccountId)
: m_botGuid(botGuid), m_masterAccountId(masterAccountId)
{
}
bool Execute() override
{
// find and verify bot still exists
Player* bot = ObjectAccessor::FindConnectedPlayer(m_botGuid);
if (!bot || !m_holder)
if (!bot)
return false;
m_holder->OnBotLogin(bot);
PlayerbotHolder* holder = sRandomPlayerbotMgr;
if (m_masterAccountId)
{
WorldSession* masterSession = sWorldSessionMgr->FindSession(m_masterAccountId);
Player* masterPlayer = masterSession ? masterSession->GetPlayer() : nullptr;
if (masterPlayer)
holder = GET_PLAYERBOT_MGR(masterPlayer);
}
if (!holder)
return false;
holder->OnBotLogin(bot);
return true;
}
@@ -487,14 +503,11 @@ public:
uint32 GetPriority() const override { return 100; }
std::string GetName() const override { return "OnBotLogin"; }
bool IsValid() const override
{
return ObjectAccessor::FindConnectedPlayer(m_botGuid) != nullptr;
}
bool IsValid() const override { return ObjectAccessor::FindConnectedPlayer(m_botGuid) != nullptr; }
private:
ObjectGuid m_botGuid;
PlayerbotHolder* m_holder;
uint32 m_masterAccountId = 0;
};
#endif

View File

@@ -17,14 +17,28 @@ PlayerbotSecurity::PlayerbotSecurity(Player* const bot) : bot(bot)
PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* reason, bool ignoreGroup)
{
// Basic pointer validity checks
if (!bot || !from || !from->GetSession())
{
if (reason)
*reason = PLAYERBOT_DENY_NONE;
return PLAYERBOT_SECURITY_DENY_ALL;
}
// GMs always have full access
if (from->GetSession()->GetSecurity() >= SEC_GAMEMASTER)
return PLAYERBOT_SECURITY_ALLOW_ALL;
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
if (!botAI)
{
if (reason)
*reason = PLAYERBOT_DENY_NONE;
return PLAYERBOT_SECURITY_DENY_ALL;
}
if (botAI->IsOpposing(from))
{
if (reason)
@@ -35,6 +49,7 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea
if (sPlayerbotAIConfig->IsInRandomAccountList(account))
{
// (duplicate check in case of faction change)
if (botAI->IsOpposing(from))
{
if (reason)
@@ -43,27 +58,17 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea
return PLAYERBOT_SECURITY_DENY_ALL;
}
// if (sLFGMgr->GetState(bot->GetGUID()) != lfg::LFG_STATE_NONE)
// {
// if (!bot->GetGuildId() || bot->GetGuildId() != from->GetGuildId())
// {
// if (reason)
// *reason = PLAYERBOT_DENY_LFG;
Group* fromGroup = from->GetGroup();
Group* botGroup = bot->GetGroup();
// return PLAYERBOT_SECURITY_TALK;
// }
// }
Group* group = from->GetGroup();
if (group && group == bot->GetGroup() && !ignoreGroup && botAI->GetMaster() == from)
if (fromGroup && botGroup && fromGroup == botGroup && !ignoreGroup)
{
return PLAYERBOT_SECURITY_ALLOW_ALL;
}
if (botAI->GetMaster() == from)
return PLAYERBOT_SECURITY_ALLOW_ALL;
if (group && group == bot->GetGroup() && !ignoreGroup && botAI->GetMaster() != from)
{
if (reason)
*reason = PLAYERBOT_DENY_NOT_YOURS;
return PLAYERBOT_SECURITY_TALK;
}
@@ -75,27 +80,34 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea
return PLAYERBOT_SECURITY_TALK;
}
if (sPlayerbotAIConfig->groupInvitationPermission <= 1 && (int32)bot->GetLevel() - (int8)from->GetLevel() > 5)
if (sPlayerbotAIConfig->groupInvitationPermission <= 1)
{
if (!bot->GetGuildId() || bot->GetGuildId() != from->GetGuildId())
int32 levelDiff = int32(bot->GetLevel()) - int32(from->GetLevel());
if (levelDiff > 5)
{
if (reason)
*reason = PLAYERBOT_DENY_LOW_LEVEL;
if (!bot->GetGuildId() || bot->GetGuildId() != from->GetGuildId())
{
if (reason)
*reason = PLAYERBOT_DENY_LOW_LEVEL;
return PLAYERBOT_SECURITY_TALK;
return PLAYERBOT_SECURITY_TALK;
}
}
}
int32 botGS = (int32)botAI->GetEquipGearScore(bot/*, false, false*/);
int32 fromGS = (int32)botAI->GetEquipGearScore(from/*, false, false*/);
if (sPlayerbotAIConfig->gearscorecheck)
int32 botGS = static_cast<int32>(botAI->GetEquipGearScore(bot));
int32 fromGS = static_cast<int32>(botAI->GetEquipGearScore(from));
if (sPlayerbotAIConfig->gearscorecheck && botGS && bot->GetLevel() > 15 && botGS > fromGS)
{
if (botGS && bot->GetLevel() > 15 && botGS > fromGS &&
static_cast<float>(100 * (botGS - fromGS) / botGS) >=
static_cast<float>(12 * sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL) / from->GetLevel()))
uint32 diffPct = uint32(100 * (botGS - fromGS) / botGS);
uint32 reqPct = uint32(12 * sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL) / from->GetLevel());
if (diffPct >= reqPct)
{
if (reason)
*reason = PLAYERBOT_DENY_GEARSCORE;
return PLAYERBOT_SECURITY_TALK;
}
}
@@ -111,35 +123,17 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea
}
}
/*if (bot->isDead())
// If the bot is not in the group, we offer an invite
botGroup = bot->GetGroup();
if (!botGroup)
{
if (reason)
*reason = PLAYERBOT_DENY_DEAD;
return PLAYERBOT_SECURITY_TALK;
}*/
group = bot->GetGroup();
if (!group)
{
/*if (bot->GetMapId() != from->GetMapId() || bot->GetDistance(from) > sPlayerbotAIConfig->whisperDistance)
{
if (!bot->GetGuildId() || bot->GetGuildId() != from->GetGuildId())
{
if (reason)
*reason = PLAYERBOT_DENY_FAR;
return PLAYERBOT_SECURITY_TALK;
}
}*/
if (reason)
*reason = PLAYERBOT_DENY_INVITE;
return PLAYERBOT_SECURITY_INVITE;
}
if (!ignoreGroup && group->IsFull())
if (!ignoreGroup && botGroup->IsFull())
{
if (reason)
*reason = PLAYERBOT_DENY_FULL_GROUP;
@@ -147,27 +141,22 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea
return PLAYERBOT_SECURITY_TALK;
}
if (!ignoreGroup && group->GetLeaderGUID() != bot->GetGUID())
if (!ignoreGroup && botGroup->GetLeaderGUID() != bot->GetGUID())
{
if (reason)
*reason = PLAYERBOT_DENY_NOT_LEADER;
return PLAYERBOT_SECURITY_TALK;
}
else
{
if (reason)
*reason = PLAYERBOT_DENY_IS_LEADER;
return PLAYERBOT_SECURITY_INVITE;
}
// The bot is the group leader, you can invite the initiator
if (reason)
*reason = PLAYERBOT_DENY_INVITE;
*reason = PLAYERBOT_DENY_IS_LEADER;
return PLAYERBOT_SECURITY_INVITE;
}
// Non-random bots: only their master has full access
if (botAI->GetMaster() == from)
return PLAYERBOT_SECURITY_ALLOW_ALL;
@@ -179,8 +168,13 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea
bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent, Player* from, bool ignoreGroup)
{
// If something is wrong with the pointers, we silently refuse
if (!bot || !from || !from->GetSession())
return false;
DenyReason reason = PLAYERBOT_DENY_NONE;
PlayerbotSecurityLevel realLevel = LevelFor(from, &reason, ignoreGroup);
if (realLevel >= level || from == bot)
return true;
@@ -189,11 +183,17 @@ bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent,
return false;
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
Player* master = botAI->GetMaster();
if (master && botAI && botAI->IsOpposing(master) && master->GetSession()->GetSecurity() < SEC_GAMEMASTER)
if (!botAI)
return false;
Player* master = botAI->GetMaster();
if (master && botAI->IsOpposing(master))
if (WorldSession* session = master->GetSession())
if (session->GetSecurity() < SEC_GAMEMASTER)
return false;
std::ostringstream out;
switch (realLevel)
{
case PLAYERBOT_SECURITY_DENY_ALL:
@@ -206,19 +206,20 @@ bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent,
out << "I'll do it later";
break;
case PLAYERBOT_DENY_LOW_LEVEL:
out << "You are too low level: |cffff0000" << (uint32)from->GetLevel() << "|cffffffff/|cff00ff00"
<< (uint32)bot->GetLevel();
out << "You are too low level: |cffff0000" << uint32(from->GetLevel()) << "|cffffffff/|cff00ff00"
<< uint32(bot->GetLevel());
break;
case PLAYERBOT_DENY_GEARSCORE:
{
int botGS = (int)botAI->GetEquipGearScore(bot/*, false, false*/);
int fromGS = (int)botAI->GetEquipGearScore(from/*, false, false*/);
int botGS = int(botAI->GetEquipGearScore(bot));
int fromGS = int(botAI->GetEquipGearScore(from));
int diff = (100 * (botGS - fromGS) / botGS);
int req = 12 * sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL) / from->GetLevel();
out << "Your gearscore is too low: |cffff0000" << fromGS << "|cffffffff/|cff00ff00" << botGS
<< " |cffff0000" << diff << "%|cffffffff/|cff00ff00" << req << "%";
break;
}
break;
case PLAYERBOT_DENY_NOT_YOURS:
out << "I have a master already";
break;
@@ -237,13 +238,10 @@ bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent,
case PLAYERBOT_DENY_FAR:
{
out << "You must be closer to invite me to your group. I am in ";
if (AreaTableEntry const* entry = sAreaTableStore.LookupEntry(bot->GetAreaId()))
{
out << " |cffffffff(|cffff0000" << entry->area_name[0] << "|cffffffff)";
}
break;
}
break;
case PLAYERBOT_DENY_FULL_GROUP:
out << "I am in a full group. Will do it later";
break;
@@ -251,15 +249,10 @@ bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent,
out << "I am currently leading a group. I can invite you if you want.";
break;
case PLAYERBOT_DENY_NOT_LEADER:
if (botAI->GetGroupLeader())
{
out << "I am in a group with " << botAI->GetGroupLeader()->GetName()
<< ". You can ask him for invite.";
}
if (Player* leader = botAI->GetGroupLeader())
out << "I am in a group with " << leader->GetName() << ". You can ask him for invite.";
else
{
out << "I am in a group with someone else. You can ask him for invite.";
}
break;
case PLAYERBOT_DENY_BG:
out << "I am in a queue for BG. Will do it later";
@@ -283,10 +276,14 @@ bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent,
std::string const text = out.str();
ObjectGuid guid = from->GetGUID();
time_t lastSaid = whispers[guid][text];
if (!lastSaid || (time(nullptr) - lastSaid) >= sPlayerbotAIConfig->repeatDelay / 1000)
{
whispers[guid][text] = time(nullptr);
bot->Whisper(text, LANG_UNIVERSAL, from);
// Additional protection against crashes during logout
if (bot->IsInWorld() && from->IsInWorld())
bot->Whisper(text, LANG_UNIVERSAL, from);
}
return false;

View File

@@ -25,6 +25,7 @@
#include "Metric.h"
#include "PlayerScript.h"
#include "PlayerbotAIConfig.h"
#include "PlayerbotSpellCache.h"
#include "PlayerbotWorldThreadProcessor.h"
#include "RandomPlayerbotMgr.h"
#include "ScriptMgr.h"
@@ -123,24 +124,49 @@ public:
}
}
bool OnPlayerBeforeTeleport(Player* player, uint32 mapid, float /*x*/, float /*y*/, float /*z*/, float /*orientation*/, uint32 /*options*/, Unit* /*target*/) override
bool OnPlayerBeforeTeleport(Player* /*player*/, uint32 /*mapid*/, float /*x*/, float /*y*/, float /*z*/, float /*orientation*/, uint32 /*options*/, Unit* /*target*/) override
{
// Only apply to bots to prevent affecting real players
if (!player || !player->GetSession()->IsBot())
/* for now commmented out until proven its actually required
* havent seen any proof CleanVisibilityReferences() is needed
// If the player is not safe to touch, do nothing
if (!player)
return true;
// If changing maps, proactively clean visibility references to prevent
// stale pointers in other players' visibility maps during the teleport.
// This fixes a race condition where:
// 1. Bot A teleports and its visible objects start getting cleaned up
// 2. Bot B is simultaneously updating visibility and tries to access objects in Bot A's old visibility map
// 3. Those objects may already be freed, causing a segmentation fault
if (player->GetMapId() != mapid && player->IsInWorld())
{
player->GetObjectVisibilityContainer().CleanVisibilityReferences();
}
// If same map or not in world do nothing
if (!player->IsInWorld() || player->GetMapId() == mapid)
return true;
return true; // Allow teleport to continue
// If real player do nothing
PlayerbotAI* ai = GET_PLAYERBOT_AI(player);
if (!ai || ai->IsRealPlayer())
return true;
// Cross-map bot teleport: defer visibility reference cleanup.
// CleanVisibilityReferences() erases this bot's GUID from other objects' visibility containers.
// This is intentionally done via the event queue (instead of directly here) because erasing
// from other players' visibility maps inside the teleport call stack can hit unsafe re-entrancy
// or iterator invalidation while visibility updates are in progress
ObjectGuid guid = player->GetGUID();
player->m_Events.AddEventAtOffset(
[guid, mapid]()
{
// do nothing, if the player is not safe to touch
Player* p = ObjectAccessor::FindPlayer(guid);
if (!p || !p->IsInWorld() || p->IsDuringRemoveFromWorld())
return;
// do nothing if we are already on the target map
if (p->GetMapId() == mapid)
return;
p->GetObjectVisibilityContainer().CleanVisibilityReferences();
},
Milliseconds(0));
*/
return true;
}
void OnPlayerAfterUpdate(Player* player, uint32 diff) override
@@ -337,6 +363,9 @@ public:
LOG_INFO("server.loading", ">> Loaded playerbots config in {} ms", GetMSTimeDiffToNow(oldMSTime));
LOG_INFO("server.loading", " ");
sPlayerbotSpellCache->Initialize();
LOG_INFO("server.loading", "Playerbots World Thread Processor initialized");
}

View File

@@ -26,7 +26,6 @@
#include "DatabaseEnv.h"
#include "Define.h"
#include "FleeManager.h"
#include "GameTime.h"
#include "GridNotifiers.h"
#include "GridNotifiersImpl.h"
#include "GuildMgr.h"
@@ -670,9 +669,9 @@ void RandomPlayerbotMgr::AssignAccountTypes()
uint32 toAssign = neededAddClassAccounts - existingAddClassAccounts;
uint32 assigned = 0;
for (int i = allRandomBotAccounts.size() - 1; i >= 0 && assigned < toAssign; i--)
for (size_t idx = allRandomBotAccounts.size(); idx-- > 0 && assigned < toAssign;)
{
uint32 accountId = allRandomBotAccounts[i];
uint32 accountId = allRandomBotAccounts[idx];
if (currentAssignments[accountId] == 0) // Unassigned
{
PlayerbotsDatabase.Execute("UPDATE playerbots_account_type SET account_type = 2, assignment_date = NOW() WHERE account_id = {}", accountId);
@@ -1425,7 +1424,7 @@ bool RandomPlayerbotMgr::ProcessBot(uint32 bot)
LOG_DEBUG("playerbots", "Bot #{}: log out", bot);
SetEventValue(bot, "add", 0, 0);
currentBots.erase(std::remove(currentBots.begin(), currentBots.end(), bot), currentBots.end());
currentBots.remove(bot);
if (player)
LogoutPlayerBot(botGUID);
@@ -2594,17 +2593,14 @@ void RandomPlayerbotMgr::Refresh(Player* bot)
bool RandomPlayerbotMgr::IsRandomBot(Player* bot)
{
if (bot && GET_PLAYERBOT_AI(bot))
{
if (GET_PLAYERBOT_AI(bot)->IsRealPlayer())
return false;
}
if (bot)
{
return IsRandomBot(bot->GetGUID().GetCounter());
}
if (!bot)
return false;
return false;
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
if (!botAI || botAI->IsRealPlayer())
return false;
return IsRandomBot(bot->GetGUID().GetCounter());
}
bool RandomPlayerbotMgr::IsRandomBot(ObjectGuid::LowType bot)
@@ -2712,69 +2708,73 @@ std::vector<uint32> RandomPlayerbotMgr::GetBgBots(uint32 bracket)
return std::move(BgBots);
}
uint32 RandomPlayerbotMgr::GetEventValue(uint32 bot, std::string const event)
CachedEvent* RandomPlayerbotMgr::FindEvent(uint32 bot, std::string const& event)
{
// load all events at once on first event load
if (eventCache[bot].empty())
BotEventCache& cache = eventCache[bot];
// Load once
if (!cache.loaded)
{
cache.events.clear();
PlayerbotsDatabasePreparedStatement* stmt =
PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_SEL_RANDOM_BOTS_BY_OWNER_AND_BOT);
stmt->SetData(0, 0);
stmt->SetData(1, bot);
if (PreparedQueryResult result = PlayerbotsDatabase.Query(stmt))
{
do
{
Field* fields = result->Fetch();
std::string const eventName = fields[0].Get<std::string>();
CachedEvent e;
e.value = fields[1].Get<uint32>();
e.lastChangeTime = fields[2].Get<uint32>();
e.validIn = fields[3].Get<uint32>();
e.data = fields[4].Get<std::string>();
eventCache[bot][eventName] = std::move(e);
cache.events.emplace(fields[0].Get<std::string>(), std::move(e));
} while (result->NextRow());
}
cache.loaded = true;
}
CachedEvent& e = eventCache[bot][event];
/*if (e.IsEmpty())
auto it = cache.events.find(event);
if (it == cache.events.end())
return nullptr;
CachedEvent& e = it->second;
// remove expired events
if (e.validIn && (NowSeconds() - e.lastChangeTime) >= e.validIn && event != "specNo" && event != "specLink")
{
QueryResult results = PlayerbotsDatabase.Query("SELECT `value`, `time`, validIn, `data` FROM
playerbots_random_bots WHERE owner = 0 AND bot = {} AND event = {}", bot, event.c_str());
if (results)
{
Field* fields = results->Fetch();
e.value = fields[0].Get<uint32>();
e.lastChangeTime = fields[1].Get<uint32>();
e.validIn = fields[2].Get<uint32>();
e.data = fields[3].Get<std::string>();
}
cache.events.erase(it);
return nullptr;
}
*/
if ((time(0) - e.lastChangeTime) >= e.validIn && event != "specNo" && event != "specLink")
e.value = 0;
return e.value;
return &e;
}
std::string const RandomPlayerbotMgr::GetEventData(uint32 bot, std::string const event)
uint32 RandomPlayerbotMgr::GetEventValue(uint32 bot, std::string const& event)
{
std::string data = "";
if (GetEventValue(bot, event))
{
CachedEvent e = eventCache[bot][event];
data = e.data;
}
if (CachedEvent* e = FindEvent(bot, event))
return e->value;
return data;
return 0;
}
uint32 RandomPlayerbotMgr::SetEventValue(uint32 bot, std::string const event, uint32 value, uint32 validIn,
std::string const data)
std::string RandomPlayerbotMgr::GetEventData(uint32 bot, std::string const& event)
{
if (CachedEvent* e = FindEvent(bot, event))
return e->data;
return "";
}
uint32 RandomPlayerbotMgr::SetEventValue(uint32 bot, std::string const& event, uint32 value, uint32 validIn,
std::string const& data)
{
PlayerbotsDatabaseTransaction trans = PlayerbotsDatabase.BeginTransaction();
@@ -2790,43 +2790,55 @@ uint32 RandomPlayerbotMgr::SetEventValue(uint32 bot, std::string const event, ui
stmt = PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_INS_RANDOM_BOTS);
stmt->SetData(0, 0);
stmt->SetData(1, bot);
stmt->SetData(2, static_cast<uint32>(GameTime::GetGameTime().count()));
stmt->SetData(2, NowSeconds());
stmt->SetData(3, validIn);
stmt->SetData(4, event.c_str());
stmt->SetData(5, value);
if (data != "")
{
if (!data.empty())
stmt->SetData(6, data.c_str());
}
else
{
stmt->SetData(6);
}
stmt->SetData(6); // NULL
trans->Append(stmt);
}
PlayerbotsDatabase.CommitTransaction(trans);
CachedEvent e(value, (uint32)time(nullptr), validIn, data);
eventCache[bot][event] = std::move(e);
// Update in-memory cache
BotEventCache& cache = eventCache[bot];
cache.loaded = true;
if (!value)
{
cache.events.erase(event);
return 0;
}
CachedEvent& e = cache.events[event]; // create-on-write is OK here
e.value = value;
e.lastChangeTime = NowSeconds();
e.validIn = validIn;
e.data = data;
return value;
}
uint32 RandomPlayerbotMgr::GetValue(uint32 bot, std::string const type) { return GetEventValue(bot, type); }
uint32 RandomPlayerbotMgr::GetValue(uint32 bot, std::string const& type) { return GetEventValue(bot, type); }
uint32 RandomPlayerbotMgr::GetValue(Player* bot, std::string const type)
uint32 RandomPlayerbotMgr::GetValue(Player* bot, std::string const& type)
{
return GetValue(bot->GetGUID().GetCounter(), type);
}
std::string const RandomPlayerbotMgr::GetData(uint32 bot, std::string const type) { return GetEventData(bot, type); }
std::string RandomPlayerbotMgr::GetData(uint32 bot, std::string const& type) { return GetEventData(bot, type); }
void RandomPlayerbotMgr::SetValue(uint32 bot, std::string const type, uint32 value, std::string const data)
void RandomPlayerbotMgr::SetValue(uint32 bot, std::string const& type, uint32 value, std::string const& data)
{
SetEventValue(bot, type, value, sPlayerbotAIConfig->maxRandomBotInWorldTime, data);
}
void RandomPlayerbotMgr::SetValue(Player* bot, std::string const type, uint32 value, std::string const data)
void RandomPlayerbotMgr::SetValue(Player* bot, std::string const& type, uint32 value, std::string const& data)
{
SetValue(bot->GetGUID().GetCounter(), type, value, data);
}
@@ -3115,7 +3127,7 @@ void RandomPlayerbotMgr::OnPlayerLogin(Player* player)
void RandomPlayerbotMgr::OnPlayerLoginError(uint32 bot)
{
SetEventValue(bot, "add", 0, 0);
currentBots.erase(std::remove(currentBots.begin(), currentBots.end(), bot), currentBots.end());
currentBots.remove(bot);
}
Player* RandomPlayerbotMgr::GetRandomPlayer()
@@ -3497,7 +3509,8 @@ void RandomPlayerbotMgr::Remove(Player* bot)
stmt->SetData(1, owner.GetCounter());
PlayerbotsDatabase.Execute(stmt);
eventCache[owner.GetCounter()].clear();
uint32 botId = owner.GetCounter();
eventCache.erase(botId);
LogoutPlayerBot(owner);
}
@@ -3514,7 +3527,7 @@ CreatureData const* RandomPlayerbotMgr::GetCreatureDataByEntry(uint32 entry)
return nullptr;
}
ObjectGuid const RandomPlayerbotMgr::GetBattleMasterGUID(Player* bot, BattlegroundTypeId bgTypeId)
ObjectGuid RandomPlayerbotMgr::GetBattleMasterGUID(Player* bot, BattlegroundTypeId bgTypeId)
{
ObjectGuid battleMasterGUID = ObjectGuid::Empty;

View File

@@ -9,6 +9,7 @@
#include "NewRpgInfo.h"
#include "ObjectGuid.h"
#include "PlayerbotMgr.h"
#include "GameTime.h"
struct BattlegroundInfo
{
@@ -45,25 +46,20 @@ class ChatHandler;
class PerformanceMonitorOperation;
class WorldLocation;
class CachedEvent
struct CachedEvent
{
public:
CachedEvent() : value(0), lastChangeTime(0), validIn(0), data("") {}
CachedEvent(const CachedEvent& other)
: value(other.value), lastChangeTime(other.lastChangeTime), validIn(other.validIn), data(other.data)
{
}
CachedEvent(uint32 value, uint32 lastChangeTime, uint32 validIn, std::string const data = "")
: value(value), lastChangeTime(lastChangeTime), validIn(validIn), data(data)
{
}
bool IsEmpty() { return !lastChangeTime; }
uint32 value;
uint32 lastChangeTime;
uint32 validIn;
uint32 value = 0;
uint32 lastChangeTime = 0;
uint32 validIn = 0;
std::string data;
bool IsEmpty() const { return !lastChangeTime; }
};
struct BotEventCache
{
bool loaded = false;
std::unordered_map<std::string, CachedEvent> events;
};
// https://gist.github.com/bradley219/5373998
@@ -139,13 +135,13 @@ public:
void Revive(Player* player);
void ChangeStrategy(Player* player);
void ChangeStrategyOnce(Player* player);
uint32 GetValue(Player* bot, std::string const type);
uint32 GetValue(uint32 bot, std::string const type);
std::string const GetData(uint32 bot, std::string const type);
void SetValue(uint32 bot, std::string const type, uint32 value, std::string const data = "");
void SetValue(Player* bot, std::string const type, uint32 value, std::string const data = "");
uint32 GetValue(Player* bot, std::string const& type);
uint32 GetValue(uint32 bot, std::string const& type);
std::string GetData(uint32 bot, std::string const& type);
void SetValue(uint32 bot, std::string const& type, uint32 value, std::string const& data = "");
void SetValue(Player* bot, std::string const& type, uint32 value, std::string const& data = "");
void Remove(Player* bot);
ObjectGuid const GetBattleMasterGUID(Player* bot, BattlegroundTypeId bgTypeId);
ObjectGuid GetBattleMasterGUID(Player* bot, BattlegroundTypeId bgTypeId);
CreatureData const* GetCreatureDataByEntry(uint32 entry);
void LoadBattleMastersCache();
std::map<uint32, std::map<uint32, BattlegroundInfo>> BattlegroundData;
@@ -203,10 +199,11 @@ private:
bool _isBotInitializing = true;
bool _isBotLogging = true;
NewRpgStatistic rpgStasticTotal;
uint32 GetEventValue(uint32 bot, std::string const event);
std::string const GetEventData(uint32 bot, std::string const event);
uint32 SetEventValue(uint32 bot, std::string const event, uint32 value, uint32 validIn,
std::string const data = "");
CachedEvent* FindEvent(uint32 bot, std::string const& event);
uint32 GetEventValue(uint32 bot, std::string const& event);
std::string GetEventData(uint32 bot, std::string const& event);
uint32 SetEventValue(uint32 bot, std::string const& event, uint32 value, uint32 validIn,
std::string const& data = "");
void GetBots();
std::vector<uint32> GetBgBots(uint32 bracket);
time_t BgCheckTimer;
@@ -228,7 +225,7 @@ private:
// std::map<uint32, std::vector<WorldLocation>> rpgLocsCache;
std::map<uint32, std::map<uint32, std::vector<WorldLocation>>> rpgLocsCacheLevel;
std::map<TeamId, std::map<BattlegroundTypeId, std::vector<uint32>>> BattleMastersCache;
std::map<uint32, std::map<std::string, CachedEvent>> eventCache;
std::unordered_map<uint32, BotEventCache> eventCache;
std::list<uint32> currentBots;
uint32 bgBotsCount;
uint32 playersLevel;
@@ -238,6 +235,7 @@ private:
std::vector<uint32> addClassTypeAccounts; // Accounts marked as AddClass (type 2)
//void ScaleBotActivity(); // Deprecated function
static inline uint32 NowSeconds() { return static_cast<uint32>(GameTime::GetGameTime().count()); }
};
#define sRandomPlayerbotMgr RandomPlayerbotMgr::instance()

View File

@@ -0,0 +1,45 @@
#include "PlayerbotSpellCache.h"
void PlayerbotSpellCache::Initialize()
{
LOG_INFO("playerbots",
"Playerbots: ListSpellsAction caches initialized");
for (uint32 j = 0; j < sSkillLineAbilityStore.GetNumRows(); ++j)
{
if (SkillLineAbilityEntry const* skillLine = sSkillLineAbilityStore.LookupEntry(j))
skillSpells[skillLine->Spell] = skillLine;
}
// Fill the vendorItems cache once from the world database.
QueryResult results = WorldDatabase.Query("SELECT item FROM npc_vendor WHERE maxcount = 0");
if (results)
{
do
{
Field* fields = results->Fetch();
int32 entry = fields[0].Get<int32>();
if (entry <= 0)
continue;
vendorItems.insert(static_cast<uint32>(entry));
}
while (results->NextRow());
}
LOG_DEBUG("playerbots",
"ListSpellsAction: initialized caches (skillSpells={}, vendorItems={}).",
skillSpells.size(), vendorItems.size());
}
SkillLineAbilityEntry const* PlayerbotSpellCache::GetSkillLine(uint32 spellId) const
{
auto itr = skillSpells.find(spellId);
if (itr != skillSpells.end())
return itr->second;
return nullptr;
}
bool PlayerbotSpellCache::IsItemBuyable(uint32 itemId) const
{
return vendorItems.find(itemId) != vendorItems.end();
}

View File

@@ -0,0 +1,34 @@
/*
* 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_PLAYERBOTSPELLCACHE_H
#define _PLAYERBOT_PLAYERBOTSPELLCACHE_H
#include "Playerbots.h"
class PlayerbotSpellCache
{
public:
static PlayerbotSpellCache* Instance()
{
static PlayerbotSpellCache instance;
return &instance;
}
void Initialize(); // call once on startup
SkillLineAbilityEntry const* GetSkillLine(uint32 spellId) const;
bool IsItemBuyable(uint32 itemId) const;
private:
PlayerbotSpellCache() = default;
std::map<uint32, SkillLineAbilityEntry const*> skillSpells;
std::set<uint32> vendorItems;
};
#define sPlayerbotSpellCache PlayerbotSpellCache::Instance()
#endif

View File

@@ -23,7 +23,5 @@ bool AcceptResurrectAction::Execute(Event event)
packet << uint8(1); // accept
bot->GetSession()->HandleResurrectResponseOpcode(packet); // queue the packet to get around race condition
botAI->ChangeEngine(BOT_STATE_NON_COMBAT);
return true;
}

View File

@@ -15,20 +15,19 @@
#include "SharedDefines.h"
#include "Unit.h"
bool AttackAction::Execute(Event event)
bool AttackAction::Execute(Event /*event*/)
{
Unit* target = GetTarget();
if (!target)
return false;
if (!target->IsInWorld())
{
return false;
}
return Attack(target);
}
bool AttackMyTargetAction::Execute(Event event)
bool AttackMyTargetAction::Execute(Event /*event*/)
{
Player* master = GetMaster();
if (!master)
@@ -51,7 +50,7 @@ bool AttackMyTargetAction::Execute(Event event)
return result;
}
bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/)
{
Unit* oldTarget = context->GetValue<Unit*>("current target")->Get();
bool shouldMelee = bot->IsWithinMeleeRange(target) || botAI->IsMelee(bot);
@@ -81,11 +80,13 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
{
if (verbose)
botAI->TellError(std::string(target->GetName()) + " is no longer in the world.");
return false;
}
// Check if bot OR target is in prohibited zone/area
// Check if bot OR target is in prohibited zone/area (skip for duels)
if ((target->IsPlayer() || target->IsPet()) &&
(!bot->duel || bot->duel->Opponent != target) &&
(sPlayerbotAIConfig->IsPvpProhibited(bot->GetZoneId(), bot->GetAreaId()) ||
sPlayerbotAIConfig->IsPvpProhibited(target->GetZoneId(), target->GetAreaId())))
{
@@ -99,6 +100,7 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
{
if (verbose)
botAI->TellError(std::string(target->GetName()) + " is friendly to me.");
return false;
}
@@ -106,6 +108,7 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
{
if (verbose)
botAI->TellError(std::string(target->GetName()) + " is dead.");
return false;
}
@@ -113,6 +116,7 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
{
if (verbose)
botAI->TellError(std::string(target->GetName()) + " is not in my sight.");
return false;
}
@@ -120,6 +124,7 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
{
if (verbose)
botAI->TellError("I am already attacking " + std::string(target->GetName()) + ".");
return false;
}
@@ -155,9 +160,8 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
}
if (IsMovingAllowed() && !bot->HasInArc(CAST_ANGLE_IN_FRONT, target))
{
sServerFacade->SetFacingTo(bot, target);
}
botAI->ChangeEngine(BOT_STATE_COMBAT);
bot->Attack(target, shouldMelee);
@@ -187,4 +191,4 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
bool AttackDuelOpponentAction::isUseful() { return AI_VALUE(Unit*, "duel target"); }
bool AttackDuelOpponentAction::Execute(Event event) { return Attack(AI_VALUE(Unit*, "duel target")); }
bool AttackDuelOpponentAction::Execute(Event /*event*/) { return Attack(AI_VALUE(Unit*, "duel target")); }

View File

@@ -224,42 +224,36 @@ bool BuyAction::Execute(Event event)
bool BuyAction::BuyItem(VendorItemData const* tItems, ObjectGuid vendorguid, ItemTemplate const* proto)
{
uint32 oldCount = AI_VALUE2(uint32, "item count", proto->Name1);
if (!tItems)
if (!tItems || !proto)
return false;
uint32 itemId = proto->ItemId;
for (uint32 slot = 0; slot < tItems->GetItemCount(); slot++)
uint32 oldCount = bot->GetItemCount(itemId, false);
for (uint32 slot = 0; slot < tItems->GetItemCount(); ++slot)
{
if (tItems->GetItem(slot)->item == itemId)
if (tItems->GetItem(slot)->item != itemId)
continue;
uint32 botMoney = bot->GetMoney();
if (botAI->HasCheat(BotCheatMask::gold))
bot->SetMoney(10000000);
bot->BuyItemFromVendorSlot(vendorguid, slot, itemId, 1, NULL_BAG, NULL_SLOT);
if (botAI->HasCheat(BotCheatMask::gold))
bot->SetMoney(botMoney);
uint32 newCount = bot->GetItemCount(itemId, false);
if (newCount > oldCount)
{
uint32 botMoney = bot->GetMoney();
if (botAI->HasCheat(BotCheatMask::gold))
{
bot->SetMoney(10000000);
}
bot->BuyItemFromVendorSlot(vendorguid, slot, itemId, 1, NULL_BAG, NULL_SLOT);
if (botAI->HasCheat(BotCheatMask::gold))
{
bot->SetMoney(botMoney);
}
if (oldCount <
AI_VALUE2(
uint32, "item count",
proto->Name1)) // BuyItem Always returns false (unless unique) so we have to check the item counts.
{
std::ostringstream out;
out << "Buying " << ChatHelper::FormatItem(proto);
botAI->TellMaster(out.str());
return true;
}
return false;
std::ostringstream out;
out << "Buying " << ChatHelper::FormatItem(proto);
botAI->TellMaster(out.str());
return true;
}
return false;
}
return false;

View File

@@ -10,6 +10,7 @@
#include "LootObjectStack.h"
#include "NewRpgStrategy.h"
#include "Playerbots.h"
#include "RtiTargetValue.h"
#include "PossibleRpgTargetsValue.h"
#include "PvpTriggers.h"
#include "ServerFacade.h"
@@ -87,9 +88,7 @@ bool DropTargetAction::Execute(Event event)
{
Spell const* spell = bot->GetCurrentSpell(CURRENT_AUTOREPEAT_SPELL); // Get the current spell being cast by the bot
if (spell && spell->m_spellInfo->Id == 75) //Check spell is not nullptr before accessing m_spellInfo
{
bot->InterruptSpell(CURRENT_AUTOREPEAT_SPELL); // Interrupt Auto Shot
}
}
bot->AttackStop();
@@ -142,6 +141,23 @@ bool AttackRtiTargetAction::Execute(Event event)
{
Unit* rtiTarget = AI_VALUE(Unit*, "rti target");
// Fallback: if the "rti target" value did not resolve a valid unit yet,
// try to resolve the raid icon directly from the group.
if (!rtiTarget)
{
if (Group* group = bot->GetGroup())
{
std::string const rti = AI_VALUE(std::string, "rti");
int32 const index = RtiTargetValue::GetRtiIndex(rti);
if (index >= 0)
{
ObjectGuid const guid = group->GetTargetIcon(index);
if (!guid.IsEmpty())
rtiTarget = botAI->GetUnit(guid);
}
}
}
if (rtiTarget && rtiTarget->IsInWorld() && rtiTarget->GetMapId() == bot->GetMapId())
{
botAI->GetAiObjectContext()->GetValue<GuidVector>("prioritized targets")->Set({rtiTarget->GetGUID()});
@@ -153,9 +169,7 @@ bool AttackRtiTargetAction::Execute(Event event)
}
}
else
{
botAI->TellError("I dont see my rti attack target");
}
return false;
}

View File

@@ -7,90 +7,43 @@
#include "Event.h"
#include "Playerbots.h"
#include "PlayerbotSpellCache.h"
std::map<uint32, SkillLineAbilityEntry const*> ListSpellsAction::skillSpells;
std::set<uint32> ListSpellsAction::vendorItems;
using SpellListEntry = std::pair<uint32, std::string>;
bool CompareSpells(const std::pair<uint32, std::string>& s1, const std::pair<uint32, std::string>& s2)
// CHANGE: Simplified and cheap comparator used in MapUpdater worker thread.
// It now avoids scanning the entire SkillLineAbilityStore for each comparison
// and only relies on spell school and spell name to keep sorting fast and bounded.
// lhs = the left element, rhs = the right element.
static bool CompareSpells(SpellListEntry const& lhSpell, SpellListEntry const& rhSpell)
{
SpellInfo const* si1 = sSpellMgr->GetSpellInfo(s1.first);
SpellInfo const* si2 = sSpellMgr->GetSpellInfo(s2.first);
if (!si1 || !si2)
SpellInfo const* lhSpellInfo = sSpellMgr->GetSpellInfo(lhSpell.first);
SpellInfo const* rhSpellInfo = sSpellMgr->GetSpellInfo(rhSpell.first);
if (!lhSpellInfo || !rhSpellInfo)
{
LOG_ERROR("playerbots", "SpellInfo missing. {} {}", s1.first, s2.first);
return false;
}
uint32 p1 = si1->SchoolMask * 20000;
uint32 p2 = si2->SchoolMask * 20000;
uint32 skill1 = 0, skill2 = 0;
uint32 skillValue1 = 0, skillValue2 = 0;
for (uint32 j = 0; j < sSkillLineAbilityStore.GetNumRows(); ++j)
{
if (SkillLineAbilityEntry const* skillLine = sSkillLineAbilityStore.LookupEntry(j))
{
if (skillLine->Spell == s1.first)
{
skill1 = skillLine->SkillLine;
skillValue1 = skillLine->TrivialSkillLineRankLow;
}
if (skillLine->Spell == s2.first)
{
skill2 = skillLine->SkillLine;
skillValue2 = skillLine->TrivialSkillLineRankLow;
}
}
if (skill1 && skill2)
break;
LOG_ERROR("playerbots", "SpellInfo missing for spell {} or {}", lhSpell.first, rhSpell.first);
// Fallback: order by spell id to keep comparator strict and deterministic.
return lhSpell.first < rhSpell.first;
}
p1 += skill1 * 500;
p2 += skill2 * 500;
uint32 lhsKey = lhSpellInfo->SchoolMask;
uint32 rhsKey = rhSpellInfo->SchoolMask;
p1 += skillValue1;
p2 += skillValue2;
if (p1 == p2)
if (lhsKey == rhsKey)
{
return strcmp(si1->SpellName[0], si2->SpellName[0]) > 0;
}
// Defensive check: if DBC data is broken and spell names are nullptr,
// fall back to id ordering instead of risking a crash in std::strcmp.
if (!lhSpellInfo->SpellName[0] || !rhSpellInfo->SpellName[0])
return lhSpell.first < rhSpell.first;
return p1 > p2;
return std::strcmp(lhSpellInfo->SpellName[0], rhSpellInfo->SpellName[0]) > 0;
}
return lhsKey > rhsKey;
}
std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::string filter)
{
if (skillSpells.empty())
{
for (uint32 j = 0; j < sSkillLineAbilityStore.GetNumRows(); ++j)
{
if (SkillLineAbilityEntry const* skillLine = sSkillLineAbilityStore.LookupEntry(j))
skillSpells[skillLine->Spell] = skillLine;
}
}
if (vendorItems.empty())
{
QueryResult results = WorldDatabase.Query("SELECT item FROM npc_vendor WHERE maxcount = 0");
if (results)
{
do
{
Field* fields = results->Fetch();
int32 entry = fields[0].Get<int32>();
if (entry <= 0)
continue;
vendorItems.insert(entry);
} while (results->NextRow());
}
}
std::ostringstream posOut;
std::ostringstream negOut;
uint32 skill = 0;
std::vector<std::string> ss = split(filter, ' ');
@@ -99,13 +52,15 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
skill = chat->parseSkill(ss[0]);
if (skill != SKILL_NONE)
{
filter = ss.size() > 1 ? filter = ss[1] : "";
filter = ss.size() > 1 ? ss[1] : "";
}
if (ss[0] == "first" && ss[1] == "aid")
// Guard access to ss[1]/ss[2] to avoid out-of-bounds
// when the player only types "first" without "aid".
if (ss[0] == "first" && ss.size() > 1 && ss[1] == "aid")
{
skill = SKILL_FIRST_AID;
filter = ss.size() > 2 ? filter = ss[2] : "";
filter = ss.size() > 2 ? ss[2] : "";
}
}
@@ -115,26 +70,57 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
uint32 minLevel = 0;
uint32 maxLevel = 0;
if (filter.find("-") != std::string::npos)
if (filter.find('-') != std::string::npos)
{
std::vector<std::string> ff = split(filter, '-');
minLevel = atoi(ff[0].c_str());
maxLevel = atoi(ff[1].c_str());
filter = "";
if (ff.size() >= 2)
{
minLevel = std::atoi(ff[0].c_str());
maxLevel = std::atoi(ff[1].c_str());
if (minLevel > maxLevel)
std::swap(minLevel, maxLevel);
}
filter.clear();
}
bool craftableOnly = false;
if (filter.find("+") != std::string::npos)
bool canCraftNow = false;
if (filter.find('+') != std::string::npos)
{
craftableOnly = true;
canCraftNow = true;
// Support "+<skill>" syntax (e.g. "spells +tailoring" or "spells tailoring+").
// If no explicit skill was detected yet, try to parse the filter (without '+')
// as a profession/skill name so that craftable-only filters still work with skills.
if (skill == SKILL_NONE)
{
std::string skillFilter = filter;
// Remove '+' before trying to interpret the first token as a skill name.
skillFilter.erase(remove(skillFilter.begin(), skillFilter.end(), '+'), skillFilter.end());
std::vector<std::string> skillTokens = split(skillFilter, ' ');
if (!skillTokens.empty())
{
uint32 parsedSkill = chat->parseSkill(skillTokens[0]);
if (parsedSkill != SKILL_NONE)
{
skill = parsedSkill;
// Any remaining text after the skill token becomes the "name" filter
// (e.g. "spells +tailoring cloth" -> skill = tailoring, filter = "cloth").
filter = skillTokens.size() > 1 ? skillTokens[1] : "";
}
}
}
// Finally remove '+' from the filter that will be used for name/range parsing.
filter.erase(remove(filter.begin(), filter.end(), '+'), filter.end());
}
uint32 slot = chat->parseSlot(filter);
if (slot != EQUIPMENT_SLOT_END)
filter = "";
filter.clear();
std::vector<std::pair<uint32, std::string>> spells;
std::vector<SpellListEntry> spells;
for (PlayerSpellMap::iterator itr = bot->GetSpellMap().begin(); itr != bot->GetSpellMap().end(); ++itr)
{
if (itr->second->State == PLAYERSPELL_REMOVED || !itr->second->Active)
@@ -150,7 +136,7 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
if (spellInfo->IsPassive())
continue;
SkillLineAbilityEntry const* skillLine = skillSpells[itr->first];
SkillLineAbilityEntry const* skillLine = sPlayerbotSpellCache->GetSkillLine(itr->first);
if (skill != SKILL_NONE && (!skillLine || skillLine->SkillLine != skill))
continue;
@@ -162,7 +148,7 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
continue;
bool first = true;
int32 craftCount = -1;
int32 craftsPossible = -1;
std::ostringstream materials;
for (uint32 x = 0; x < MAX_SPELL_REAGENTS; ++x)
{
@@ -189,12 +175,12 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
FindItemByIdVisitor visitor(itemid);
uint32 reagentsInInventory = InventoryAction::GetItemCount(&visitor);
bool buyable = (vendorItems.find(itemid) != vendorItems.end());
bool buyable = sPlayerbotSpellCache->IsItemBuyable(itemid);
if (!buyable)
{
uint32 craftable = reagentsInInventory / reagentsRequired;
if (craftCount < 0 || craftCount > craftable)
craftCount = craftable;
if (craftsPossible < 0 || craftsPossible > static_cast<int32>(craftable))
craftsPossible = static_cast<int32>(craftable);
}
if (reagentsInInventory)
@@ -205,8 +191,8 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
}
}
if (craftCount < 0)
craftCount = 0;
if (craftsPossible < 0)
craftsPossible = 0;
std::ostringstream out;
bool filtered = false;
@@ -218,8 +204,8 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
{
if (ItemTemplate const* proto = sObjectMgr->GetItemTemplate(spellInfo->Effects[i].ItemType))
{
if (craftCount)
out << "|cffffff00(x" << craftCount << ")|r ";
if (craftsPossible)
out << "|cffffff00(x" << craftsPossible << ")|r ";
out << chat->FormatItem(proto);
@@ -246,7 +232,7 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
if (filtered)
continue;
if (craftableOnly && !craftCount)
if (canCraftNow && !craftsPossible)
continue;
out << materials.str();
@@ -275,10 +261,9 @@ std::vector<std::pair<uint32, std::string>> ListSpellsAction::GetSpellList(std::
continue;
if (itr->first == 0)
{
LOG_ERROR("playerbots", "?! {}", itr->first);
}
spells.push_back(std::pair<uint32, std::string>(itr->first, out.str()));
spells.emplace_back(itr->first, out.str());
alreadySeenList += spellInfo->SpellName[0];
alreadySeenList += ",";
}
@@ -294,25 +279,28 @@ bool ListSpellsAction::Execute(Event event)
std::string const filter = event.getParam();
std::vector<std::pair<uint32, std::string>> spells = GetSpellList(filter);
std::vector<SpellListEntry> spells = GetSpellList(filter);
if (spells.empty())
{
// CHANGE: Give early feedback when no spells match the filter.
botAI->TellMaster("No spells found.");
return true;
}
botAI->TellMaster("=== Spells ===");
std::sort(spells.begin(), spells.end(), CompareSpells);
uint32 count = 0;
for (std::vector<std::pair<uint32, std::string>>::iterator i = spells.begin(); i != spells.end(); ++i)
{
// CHANGE: Send the full spell list again so client-side addons
// (e.g. Multibot / Unbot) can reconstruct the
// complete spellbook for configuration. The heavy part that caused
// freezes before was the old CompareSpells implementation scanning
// the entire SkillLineAbility DBC on every comparison. With the new
// cheap comparator above, sending all lines here is safe and keeps
// behaviour compatible with existing addons.
for (std::vector<SpellListEntry>::const_iterator i = spells.begin(); i != spells.end(); ++i)
botAI->TellMasterNoFacing(i->second);
// if (++count >= 50)
// {
// std::ostringstream msg;
// msg << (spells.size() - 50) << " more...";
// botAI->TellMasterNoFacing(msg.str());
// break;
// }
}
return true;
}
}

View File

@@ -18,9 +18,8 @@ public:
bool Execute(Event event) override;
virtual std::vector<std::pair<uint32, std::string>> GetSpellList(std::string filter = "");
private:
static std::map<uint32, SkillLineAbilityEntry const*> skillSpells;
static std::set<uint32> vendorItems;
static void InitSpellCaches();
};
#endif

View File

@@ -946,25 +946,36 @@ bool MovementAction::IsWaitingForLastMove(MovementPriority priority)
bool MovementAction::IsMovingAllowed()
{
// do not allow if not vehicle driver
if (botAI->IsInVehicle() && !botAI->IsInVehicle(true))
// Most common checks: confused, stunned, fleeing, jumping, charging. All these
// states are set when handling certain aura effects. We don't check against
// UNIT_STATE_ROOT here, because this state is used by vehicles.
if (bot->HasUnitState(UNIT_STATE_LOST_CONTROL))
return false;
if (bot->isFrozen() || bot->IsPolymorphed() || (bot->isDead() && !bot->HasPlayerFlag(PLAYER_FLAGS_GHOST)) ||
bot->IsBeingTeleported() || bot->HasRootAura() || bot->HasSpiritOfRedemptionAura() || bot->HasConfuseAura() ||
bot->IsCharmed() || bot->HasStunAura() || bot->IsInFlight() || bot->HasUnitState(UNIT_STATE_LOST_CONTROL))
// Death state (w/o spirit release) and Spirit of Redemption aura (priest)
if ((bot->isDead() && !bot->HasPlayerFlag(PLAYER_FLAGS_GHOST)) || bot->HasSpiritOfRedemptionAura())
return false;
// Common CC effects, ordered by frequency: rooted > frozen > polymorphed
if (bot->IsRooted() || bot->isFrozen() || bot->IsPolymorphed())
return false;
// Check for the MM controlled slot types: feared, confused, fleeing, etc.
if (bot->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_CONTROLLED) != NULL_MOTION_TYPE)
{
return false;
}
// if (bot->HasUnitMovementFlag(MOVEMENTFLAG_FALLING))
// {
// return false;
// }
return bot->GetMotionMaster()->GetCurrentMovementGeneratorType() != FLIGHT_MOTION_TYPE;
// Traveling state: taxi flight and being teleported (relatively rare)
if (bot->IsInFlight() || bot->GetMotionMaster()->GetCurrentMovementGeneratorType() == FLIGHT_MOTION_TYPE ||
bot->IsBeingTeleported())
return false;
// Vehicle state: is in the vehicle and can control it (rare, content-specific).
// We need to check charmed state AFTER vehicle one, cuz that's how it works:
// passengers are set to charmed by vehicle with CHARM_TYPE_VEHICLE.
if ((bot->GetVehicle() && !botAI->IsInVehicle(true)) || bot->IsCharmed())
return false;
return true;
}
bool MovementAction::Follow(Unit* target, float distance) { return Follow(target, distance, GetFollowAngle()); }
@@ -972,12 +983,10 @@ bool MovementAction::Follow(Unit* target, float distance) { return Follow(target
void MovementAction::UpdateMovementState()
{
const bool isCurrentlyRestricted = // see if the bot is currently slowed, rooted, or otherwise unable to move
bot->HasUnitState(UNIT_STATE_LOST_CONTROL) ||
bot->IsRooted() ||
bot->isFrozen() ||
bot->IsPolymorphed() ||
bot->HasRootAura() ||
bot->HasStunAura() ||
bot->HasConfuseAura() ||
bot->HasUnitState(UNIT_STATE_LOST_CONTROL);
bot->IsPolymorphed();
// no update movement flags while movement is current restricted.
if (!isCurrentlyRestricted && bot->IsAlive())

View File

@@ -18,9 +18,7 @@ bool PetsAction::Execute(Event event)
// Extract the command parameter from the event (e.g., "aggressive", "defensive", "attack", etc.)
std::string param = event.getParam();
if (param.empty() && !defaultCmd.empty())
{
param = defaultCmd;
}
if (param.empty())
{
@@ -129,9 +127,7 @@ bool PetsAction::Execute(Event event)
{
ObjectGuid masterTargetGuid = master->GetTarget();
if (!masterTargetGuid.IsEmpty())
{
targetUnit = botAI->GetUnit(masterTargetGuid);
}
}
// If no valid target is selected, show an error and return.
@@ -156,8 +152,9 @@ bool PetsAction::Execute(Event event)
botAI->TellError(text);
return false;
}
if (sPlayerbotAIConfig->IsPvpProhibited(bot->GetZoneId(), bot->GetAreaId())
&& (targetUnit->IsPlayer() || targetUnit->IsPet()))
if (sPlayerbotAIConfig->IsPvpProhibited(bot->GetZoneId(), bot->GetAreaId()) &&
(targetUnit->IsPlayer() || targetUnit->IsPet()) &&
(!bot->duel || bot->duel->Opponent != targetUnit))
{
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
"pet_pvp_prohibited_error", "I cannot command my pet to attack players in PvP prohibited areas.", {});

View File

@@ -3,8 +3,11 @@
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "ReadyCheckAction.h"
#include <memory>
#include <mutex>
#include <vector>
#include "ReadyCheckAction.h"
#include "Event.h"
#include "Playerbots.h"
@@ -27,14 +30,17 @@ std::string const formatPercent(std::string const name, uint8 value, float perce
class ReadyChecker
{
public:
virtual ~ReadyChecker() = default;
virtual bool Check(PlayerbotAI* botAI, AiObjectContext* context) = 0;
virtual std::string const getName() = 0;
virtual bool PrintAlways() { return true; }
static std::vector<ReadyChecker*> checkers;
static std::vector<std::unique_ptr<ReadyChecker>> checkers;
static std::once_flag initFlag;
};
std::vector<ReadyChecker*> ReadyChecker::checkers;
std::vector<std::unique_ptr<ReadyChecker>> ReadyChecker::checkers;
std::once_flag ReadyChecker::initFlag;
class HealthChecker : public ReadyChecker
{
@@ -160,25 +166,30 @@ bool ReadyCheckAction::Execute(Event event)
bool ReadyCheckAction::ReadyCheck()
{
if (ReadyChecker::checkers.empty())
{
ReadyChecker::checkers.push_back(new HealthChecker());
ReadyChecker::checkers.push_back(new ManaChecker());
ReadyChecker::checkers.push_back(new DistanceChecker());
ReadyChecker::checkers.push_back(new HunterChecker());
std::call_once(
ReadyChecker::initFlag,
[]()
{
ReadyChecker::checkers.reserve(8);
ReadyChecker::checkers.push_back(new ItemCountChecker("food", "Food"));
ReadyChecker::checkers.push_back(new ManaPotionChecker("drink", "Water"));
ReadyChecker::checkers.push_back(new ItemCountChecker("healing potion", "Hpot"));
ReadyChecker::checkers.push_back(new ManaPotionChecker("mana potion", "Mpot"));
}
ReadyChecker::checkers.emplace_back(std::make_unique<HealthChecker>());
ReadyChecker::checkers.emplace_back(std::make_unique<ManaChecker>());
ReadyChecker::checkers.emplace_back(std::make_unique<DistanceChecker>());
ReadyChecker::checkers.emplace_back(std::make_unique<HunterChecker>());
ReadyChecker::checkers.emplace_back(std::make_unique<ItemCountChecker>("food", "Food"));
ReadyChecker::checkers.emplace_back(std::make_unique<ManaPotionChecker>("drink", "Water"));
ReadyChecker::checkers.emplace_back(std::make_unique<ItemCountChecker>("healing potion", "Hpot"));
ReadyChecker::checkers.emplace_back(std::make_unique<ManaPotionChecker>("mana potion", "Mpot"));
});
bool result = true;
for (std::vector<ReadyChecker*>::iterator i = ReadyChecker::checkers.begin(); i != ReadyChecker::checkers.end();
++i)
for (auto const& checkerPtr : ReadyChecker::checkers)
{
ReadyChecker* checker = *i;
bool ok = checker->Check(botAI, context);
if (!checkerPtr)
continue;
bool ok = checkerPtr->Check(botAI, context);
result = result && ok;
}

View File

@@ -5,8 +5,6 @@
#include "WorldPacketHandlerStrategy.h"
#include "Playerbots.h"
void WorldPacketHandlerStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
PassTroughStrategy::InitTriggers(triggers);
@@ -69,7 +67,7 @@ void WorldPacketHandlerStrategy::InitTriggers(std::vector<TriggerNode*>& trigger
triggers.push_back(new TriggerNode("questgiver quest details", NextAction::array(0, new NextAction("turn in query quest", relevance), nullptr)));
// loot roll
triggers.push_back(new TriggerNode("very often", NextAction::array(0, new NextAction("loot roll", 10.0f), nullptr)));
triggers.push_back(new TriggerNode("very often", NextAction::array(0, new NextAction("loot roll", relevance), nullptr)));
}
WorldPacketHandlerStrategy::WorldPacketHandlerStrategy(PlayerbotAI* botAI) : PassTroughStrategy(botAI)

View File

@@ -1,5 +1,5 @@
#ifndef _PLAYERBOT_RAIDKARAZHANACTIONS_CONTEXT_H
#define _PLAYERBOT_RAIDKARAZHANACTIONS_CONTEXT_H
#ifndef _PLAYERBOT_RAIDKARAZHANACTIONCONTEXT_H
#define _PLAYERBOT_RAIDKARAZHANACTIONCONTEXT_H
#include "RaidKarazhanActions.h"
#include "NamedObjectContext.h"
@@ -9,77 +9,255 @@ class RaidKarazhanActionContext : public NamedObjectContext<Action>
public:
RaidKarazhanActionContext()
{
creators["karazhan attumen the huntsman stack behind"] = &RaidKarazhanActionContext::karazhan_attumen_the_huntsman_stack_behind;
// Trash
creators["mana warp stun creature before warp breach"] =
&RaidKarazhanActionContext::mana_warp_stun_creature_before_warp_breach;
creators["karazhan moroes mark target"] = &RaidKarazhanActionContext::karazhan_moroes_mark_target;
// Attumen the Huntsman
creators["attumen the huntsman mark target"] =
&RaidKarazhanActionContext::attumen_the_huntsman_mark_target;
creators["karazhan maiden of virtue position boss"] = &RaidKarazhanActionContext::karazhan_maiden_of_virtue_position_boss;
creators["karazhan maiden of virtue position ranged"] = &RaidKarazhanActionContext::karazhan_maiden_of_virtue_position_ranged;
creators["attumen the huntsman split bosses"] =
&RaidKarazhanActionContext::attumen_the_huntsman_split_bosses;
creators["karazhan big bad wolf position boss"] = &RaidKarazhanActionContext::karazhan_big_bad_wolf_position_boss;
creators["karazhan big bad wolf run away"] = &RaidKarazhanActionContext::karazhan_big_bad_wolf_run_away;
creators["attumen the huntsman stack behind"] =
&RaidKarazhanActionContext::attumen_the_huntsman_stack_behind;
creators["karazhan romulo and julianne mark target"] = &RaidKarazhanActionContext::karazhan_romulo_and_julianne_mark_target;
creators["attumen the huntsman manage dps timer"] =
&RaidKarazhanActionContext::attumen_the_huntsman_manage_dps_timer;
creators["karazhan wizard of oz mark target"] = &RaidKarazhanActionContext::karazhan_wizard_of_oz_mark_target;
creators["karazhan wizard of oz scorch strawman"] = &RaidKarazhanActionContext::karazhan_wizard_of_oz_scorch_strawman;
// Moroes
creators["moroes main tank attack boss"] =
&RaidKarazhanActionContext::moroes_main_tank_attack_boss;
creators["karazhan the curator mark target"] = &RaidKarazhanActionContext::karazhan_the_curator_mark_target;
creators["karazhan the curator position boss"] = &RaidKarazhanActionContext::karazhan_the_curator_position_boss;
creators["karazhan the curator spread ranged"] = &RaidKarazhanActionContext::karazhan_the_curator_spread_ranged;
creators["moroes mark target"] =
&RaidKarazhanActionContext::moroes_mark_target;
creators["karazhan terestian illhoof mark target"] = &RaidKarazhanActionContext::karazhan_terestian_illhoof_mark_target;
// Maiden of Virtue
creators["maiden of virtue move boss to healer"] =
&RaidKarazhanActionContext::maiden_of_virtue_move_boss_to_healer;
creators["karazhan shade of aran arcane explosion run away"] = &RaidKarazhanActionContext::karazhan_shade_of_aran_arcane_explosion_run_away;
creators["karazhan shade of aran flame wreath stop movement"] = &RaidKarazhanActionContext::karazhan_shade_of_aran_flame_wreath_stop_movement;
creators["karazhan shade of aran mark conjured elemental"] = &RaidKarazhanActionContext::karazhan_shade_of_aran_mark_conjured_elemental;
creators["karazhan shade of aran spread ranged"] = &RaidKarazhanActionContext::karazhan_shade_of_aran_spread_ranged;
creators["maiden of virtue position ranged"] =
&RaidKarazhanActionContext::maiden_of_virtue_position_ranged;
creators["karazhan netherspite block red beam"] = &RaidKarazhanActionContext::karazhan_netherspite_block_red_beam;
creators["karazhan netherspite block blue beam"] = &RaidKarazhanActionContext::karazhan_netherspite_block_blue_beam;
creators["karazhan netherspite block green beam"] = &RaidKarazhanActionContext::karazhan_netherspite_block_green_beam;
creators["karazhan netherspite avoid beam and void zone"] = &RaidKarazhanActionContext::karazhan_netherspite_avoid_beam_and_void_zone;
creators["karazhan netherspite banish phase avoid void zone"] = &RaidKarazhanActionContext::karazhan_netherspite_banish_phase_avoid_void_zone;
// The Big Bad Wolf
creators["big bad wolf position boss"] =
&RaidKarazhanActionContext::big_bad_wolf_position_boss;
creators["karazhan prince malchezaar non tank avoid hazard"] = &RaidKarazhanActionContext::karazhan_prince_malchezaar_non_tank_avoid_hazard;
creators["karazhan prince malchezaar tank avoid hazard"] = &RaidKarazhanActionContext::karazhan_prince_malchezaar_tank_avoid_hazard;
creators["big bad wolf run away from boss"] =
&RaidKarazhanActionContext::big_bad_wolf_run_away_from_boss;
// Romulo and Julianne
creators["romulo and julianne mark target"] =
&RaidKarazhanActionContext::romulo_and_julianne_mark_target;
// The Wizard of Oz
creators["wizard of oz mark target"] =
&RaidKarazhanActionContext::wizard_of_oz_mark_target;
creators["wizard of oz scorch strawman"] =
&RaidKarazhanActionContext::wizard_of_oz_scorch_strawman;
// The Curator
creators["the curator mark astral flare"] =
&RaidKarazhanActionContext::the_curator_mark_astral_flare;
creators["the curator position boss"] =
&RaidKarazhanActionContext::the_curator_position_boss;
creators["the curator spread ranged"] =
&RaidKarazhanActionContext::the_curator_spread_ranged;
// Terestian Illhoof
creators["terestian illhoof mark target"] =
&RaidKarazhanActionContext::terestian_illhoof_mark_target;
// Shade of Aran
creators["shade of aran run away from arcane explosion"] =
&RaidKarazhanActionContext::shade_of_aran_run_away_from_arcane_explosion;
creators["shade of aran stop moving during flame wreath"] =
&RaidKarazhanActionContext::shade_of_aran_stop_moving_during_flame_wreath;
creators["shade of aran mark conjured elemental"] =
&RaidKarazhanActionContext::shade_of_aran_mark_conjured_elemental;
creators["shade of aran ranged maintain distance"] =
&RaidKarazhanActionContext::shade_of_aran_ranged_maintain_distance;
// Netherspite
creators["netherspite block red beam"] =
&RaidKarazhanActionContext::netherspite_block_red_beam;
creators["netherspite block blue beam"] =
&RaidKarazhanActionContext::netherspite_block_blue_beam;
creators["netherspite block green beam"] =
&RaidKarazhanActionContext::netherspite_block_green_beam;
creators["netherspite avoid beam and void zone"] =
&RaidKarazhanActionContext::netherspite_avoid_beam_and_void_zone;
creators["netherspite banish phase avoid void zone"] =
&RaidKarazhanActionContext::netherspite_banish_phase_avoid_void_zone;
creators["netherspite manage timers and trackers"] =
&RaidKarazhanActionContext::netherspite_manage_timers_and_trackers;
// Prince Malchezaar
creators["prince malchezaar enfeebled avoid hazard"] =
&RaidKarazhanActionContext::prince_malchezaar_enfeebled_avoid_hazard;
creators["prince malchezaar non tank avoid infernal"] =
&RaidKarazhanActionContext::prince_malchezaar_non_tank_avoid_infernal;
creators["prince malchezaar main tank movement"] =
&RaidKarazhanActionContext::prince_malchezaar_main_tank_movement;
// Nightbane
creators["nightbane ground phase position boss"] =
&RaidKarazhanActionContext::nightbane_ground_phase_position_boss;
creators["nightbane ground phase rotate ranged positions"] =
&RaidKarazhanActionContext::nightbane_ground_phase_rotate_ranged_positions;
creators["nightbane cast fear ward on main tank"] =
&RaidKarazhanActionContext::nightbane_cast_fear_ward_on_main_tank;
creators["nightbane control pet aggression"] =
&RaidKarazhanActionContext::nightbane_control_pet_aggression;
creators["nightbane flight phase movement"] =
&RaidKarazhanActionContext::nightbane_flight_phase_movement;
creators["nightbane manage timers and trackers"] =
&RaidKarazhanActionContext::nightbane_manage_timers_and_trackers;
}
private:
static Action* karazhan_attumen_the_huntsman_stack_behind(PlayerbotAI* botAI) { return new KarazhanAttumenTheHuntsmanStackBehindAction(botAI); }
// Trash
static Action* mana_warp_stun_creature_before_warp_breach(
PlayerbotAI* botAI) { return new ManaWarpStunCreatureBeforeWarpBreachAction(botAI); }
static Action* karazhan_moroes_mark_target(PlayerbotAI* botAI) { return new KarazhanMoroesMarkTargetAction(botAI); }
// Attumen the Huntsman
static Action* attumen_the_huntsman_mark_target(
PlayerbotAI* botAI) { return new AttumenTheHuntsmanMarkTargetAction(botAI); }
static Action* karazhan_maiden_of_virtue_position_boss(PlayerbotAI* botAI) { return new KarazhanMaidenOfVirtuePositionBossAction(botAI); }
static Action* karazhan_maiden_of_virtue_position_ranged(PlayerbotAI* botAI) { return new KarazhanMaidenOfVirtuePositionRangedAction(botAI); }
static Action* attumen_the_huntsman_split_bosses(
PlayerbotAI* botAI) { return new AttumenTheHuntsmanSplitBossesAction(botAI); }
static Action* karazhan_big_bad_wolf_position_boss(PlayerbotAI* botAI) { return new KarazhanBigBadWolfPositionBossAction(botAI); }
static Action* karazhan_big_bad_wolf_run_away(PlayerbotAI* botAI) { return new KarazhanBigBadWolfRunAwayAction(botAI); }
static Action* attumen_the_huntsman_stack_behind(
PlayerbotAI* botAI) { return new AttumenTheHuntsmanStackBehindAction(botAI); }
static Action* karazhan_romulo_and_julianne_mark_target(PlayerbotAI* botAI) { return new KarazhanRomuloAndJulianneMarkTargetAction(botAI); }
static Action* attumen_the_huntsman_manage_dps_timer(
PlayerbotAI* botAI) { return new AttumenTheHuntsmanManageDpsTimerAction(botAI); }
static Action* karazhan_wizard_of_oz_mark_target(PlayerbotAI* botAI) { return new KarazhanWizardOfOzMarkTargetAction(botAI); }
static Action* karazhan_wizard_of_oz_scorch_strawman(PlayerbotAI* botAI) { return new KarazhanWizardOfOzScorchStrawmanAction(botAI); }
// Moroes
static Action* moroes_main_tank_attack_boss(
PlayerbotAI* botAI) { return new MoroesMainTankAttackBossAction(botAI); }
static Action* karazhan_the_curator_mark_target(PlayerbotAI* botAI) { return new KarazhanTheCuratorMarkTargetAction(botAI); }
static Action* karazhan_the_curator_position_boss(PlayerbotAI* botAI) { return new KarazhanTheCuratorPositionBossAction(botAI); }
static Action* karazhan_the_curator_spread_ranged(PlayerbotAI* botAI) { return new KarazhanTheCuratorSpreadRangedAction(botAI); }
static Action* moroes_mark_target(
PlayerbotAI* botAI) { return new MoroesMarkTargetAction(botAI); }
static Action* karazhan_terestian_illhoof_mark_target(PlayerbotAI* botAI) { return new KarazhanTerestianIllhoofMarkTargetAction(botAI); }
// Maiden of Virtue
static Action* maiden_of_virtue_move_boss_to_healer(
PlayerbotAI* botAI) { return new MaidenOfVirtueMoveBossToHealerAction(botAI); }
static Action* karazhan_shade_of_aran_arcane_explosion_run_away(PlayerbotAI* botAI) { return new KarazhanShadeOfAranArcaneExplosionRunAwayAction(botAI); }
static Action* karazhan_shade_of_aran_flame_wreath_stop_movement(PlayerbotAI* botAI) { return new KarazhanShadeOfAranFlameWreathStopMovementAction(botAI); }
static Action* karazhan_shade_of_aran_mark_conjured_elemental(PlayerbotAI* botAI) { return new KarazhanShadeOfAranMarkConjuredElementalAction(botAI); }
static Action* karazhan_shade_of_aran_spread_ranged(PlayerbotAI* botAI) { return new KarazhanShadeOfAranSpreadRangedAction(botAI); }
static Action* maiden_of_virtue_position_ranged(
PlayerbotAI* botAI) { return new MaidenOfVirtuePositionRangedAction(botAI); }
static Action* karazhan_netherspite_block_red_beam(PlayerbotAI* botAI) { return new KarazhanNetherspiteBlockRedBeamAction(botAI); }
static Action* karazhan_netherspite_block_blue_beam(PlayerbotAI* botAI) { return new KarazhanNetherspiteBlockBlueBeamAction(botAI); }
static Action* karazhan_netherspite_block_green_beam(PlayerbotAI* botAI) { return new KarazhanNetherspiteBlockGreenBeamAction(botAI); }
static Action* karazhan_netherspite_avoid_beam_and_void_zone(PlayerbotAI* botAI) { return new KarazhanNetherspiteAvoidBeamAndVoidZoneAction(botAI); }
static Action* karazhan_netherspite_banish_phase_avoid_void_zone(PlayerbotAI* botAI) { return new KarazhanNetherspiteBanishPhaseAvoidVoidZoneAction(botAI); }
// The Big Bad Wolf
static Action* big_bad_wolf_position_boss(
PlayerbotAI* botAI) { return new BigBadWolfPositionBossAction(botAI); }
static Action* karazhan_prince_malchezaar_non_tank_avoid_hazard(PlayerbotAI* botAI) { return new KarazhanPrinceMalchezaarNonTankAvoidHazardAction(botAI); }
static Action* karazhan_prince_malchezaar_tank_avoid_hazard(PlayerbotAI* botAI) { return new KarazhanPrinceMalchezaarTankAvoidHazardAction(botAI); }
static Action* big_bad_wolf_run_away_from_boss(
PlayerbotAI* botAI) { return new BigBadWolfRunAwayFromBossAction(botAI); }
// Romulo and Julianne
static Action* romulo_and_julianne_mark_target(
PlayerbotAI* botAI) { return new RomuloAndJulianneMarkTargetAction(botAI); }
// The Wizard of Oz
static Action* wizard_of_oz_mark_target(
PlayerbotAI* botAI) { return new WizardOfOzMarkTargetAction(botAI); }
static Action* wizard_of_oz_scorch_strawman(
PlayerbotAI* botAI) { return new WizardOfOzScorchStrawmanAction(botAI); }
// The Curator
static Action* the_curator_mark_astral_flare(
PlayerbotAI* botAI) { return new TheCuratorMarkAstralFlareAction(botAI); }
static Action* the_curator_position_boss(
PlayerbotAI* botAI) { return new TheCuratorPositionBossAction(botAI); }
static Action* the_curator_spread_ranged(
PlayerbotAI* botAI) { return new TheCuratorSpreadRangedAction(botAI); }
// Terestian Illhoof
static Action* terestian_illhoof_mark_target(
PlayerbotAI* botAI) { return new TerestianIllhoofMarkTargetAction(botAI); }
// Shade of Aran
static Action* shade_of_aran_run_away_from_arcane_explosion(
PlayerbotAI* botAI) { return new ShadeOfAranRunAwayFromArcaneExplosionAction(botAI); }
static Action* shade_of_aran_stop_moving_during_flame_wreath(
PlayerbotAI* botAI) { return new ShadeOfAranStopMovingDuringFlameWreathAction(botAI); }
static Action* shade_of_aran_mark_conjured_elemental(
PlayerbotAI* botAI) { return new ShadeOfAranMarkConjuredElementalAction(botAI); }
static Action* shade_of_aran_ranged_maintain_distance(
PlayerbotAI* botAI) { return new ShadeOfAranRangedMaintainDistanceAction(botAI); }
// Netherspite
static Action* netherspite_block_red_beam(
PlayerbotAI* botAI) { return new NetherspiteBlockRedBeamAction(botAI); }
static Action* netherspite_block_blue_beam(
PlayerbotAI* botAI) { return new NetherspiteBlockBlueBeamAction(botAI); }
static Action* netherspite_block_green_beam(
PlayerbotAI* botAI) { return new NetherspiteBlockGreenBeamAction(botAI); }
static Action* netherspite_avoid_beam_and_void_zone(
PlayerbotAI* botAI) { return new NetherspiteAvoidBeamAndVoidZoneAction(botAI); }
static Action* netherspite_banish_phase_avoid_void_zone(
PlayerbotAI* botAI) { return new NetherspiteBanishPhaseAvoidVoidZoneAction(botAI); }
static Action* netherspite_manage_timers_and_trackers(
PlayerbotAI* botAI) { return new NetherspiteManageTimersAndTrackersAction(botAI); }
// Prince Malchezaar
static Action* prince_malchezaar_enfeebled_avoid_hazard(
PlayerbotAI* botAI) { return new PrinceMalchezaarEnfeebledAvoidHazardAction(botAI); }
static Action* prince_malchezaar_non_tank_avoid_infernal(
PlayerbotAI* botAI) { return new PrinceMalchezaarNonTankAvoidInfernalAction(botAI); }
static Action* prince_malchezaar_main_tank_movement(
PlayerbotAI* botAI) { return new PrinceMalchezaarMainTankMovementAction(botAI); }
// Nightbane
static Action* nightbane_ground_phase_position_boss(
PlayerbotAI* botAI) { return new NightbaneGroundPhasePositionBossAction(botAI); }
static Action* nightbane_ground_phase_rotate_ranged_positions(
PlayerbotAI* botAI) { return new NightbaneGroundPhaseRotateRangedPositionsAction(botAI); }
static Action* nightbane_cast_fear_ward_on_main_tank(
PlayerbotAI* botAI) { return new NightbaneCastFearWardOnMainTankAction(botAI); }
static Action* nightbane_control_pet_aggression(
PlayerbotAI* botAI) { return new NightbaneControlPetAggressionAction(botAI); }
static Action* nightbane_flight_phase_movement(
PlayerbotAI* botAI) { return new NightbaneFlightPhaseMovementAction(botAI); }
static Action* nightbane_manage_timers_and_trackers(
PlayerbotAI* botAI) { return new NightbaneManageTimersAndTrackersAction(botAI); }
};
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -2,217 +2,321 @@
#define _PLAYERBOT_RAIDKARAZHANACTIONS_H
#include "Action.h"
#include "AttackAction.h"
#include "MovementActions.h"
class KarazhanAttumenTheHuntsmanStackBehindAction : public MovementAction
class ManaWarpStunCreatureBeforeWarpBreachAction : public AttackAction
{
public:
KarazhanAttumenTheHuntsmanStackBehindAction(PlayerbotAI* botAI, std::string const name = "karazhan attumen the huntsman stack behind") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
bool isUseful() override;
};
class KarazhanMoroesMarkTargetAction : public Action
{
public:
KarazhanMoroesMarkTargetAction(PlayerbotAI* botAI, std::string const name = "karazhan moroes mark target") : Action(botAI, name) {}
ManaWarpStunCreatureBeforeWarpBreachAction(
PlayerbotAI* botAI, std::string const name = "mana warp stun creature before warp breach") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class KarazhanMaidenOfVirtuePositionBossAction : public MovementAction
class AttumenTheHuntsmanMarkTargetAction : public AttackAction
{
public:
KarazhanMaidenOfVirtuePositionBossAction(PlayerbotAI* botAI, std::string const name = "karazhan maiden of virtue position boss") : MovementAction(botAI, name) {}
AttumenTheHuntsmanMarkTargetAction(
PlayerbotAI* botAI, std::string const name = "attumen the huntsman mark target") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
bool isUseful() override;
};
class KarazhanMaidenOfVirtuePositionRangedAction : public MovementAction
class AttumenTheHuntsmanSplitBossesAction : public AttackAction
{
public:
KarazhanMaidenOfVirtuePositionRangedAction(PlayerbotAI* botAI, std::string const name = "karazhan maiden of virtue position ranged") : MovementAction(botAI, name) {}
AttumenTheHuntsmanSplitBossesAction(
PlayerbotAI* botAI, std::string const name = "attumen the huntsman split bosses") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
bool isUseful() override;
};
class KarazhanBigBadWolfPositionBossAction : public MovementAction
class AttumenTheHuntsmanStackBehindAction : public MovementAction
{
public:
KarazhanBigBadWolfPositionBossAction(PlayerbotAI* botAI, std::string const name = "karazhan big bad wolf position boss") : MovementAction(botAI, name) {}
AttumenTheHuntsmanStackBehindAction(
PlayerbotAI* botAI, std::string const name = "attumen the huntsman stack behind") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
bool isUseful() override;
};
class KarazhanBigBadWolfRunAwayAction : public MovementAction
class AttumenTheHuntsmanManageDpsTimerAction : public Action
{
public:
KarazhanBigBadWolfRunAwayAction(PlayerbotAI* botAI, std::string const name = "karazhan big bad wolf run away") : MovementAction(botAI, name) {}
AttumenTheHuntsmanManageDpsTimerAction(
PlayerbotAI* botAI, std::string const name = "attumen the huntsman manage dps timer") : Action(botAI, name) {}
bool Execute(Event event) override;
};
class MoroesMainTankAttackBossAction : public AttackAction
{
public:
MoroesMainTankAttackBossAction(
PlayerbotAI* botAI, std::string const name = "moroes main tank attack boss") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class MoroesMarkTargetAction : public Action
{
public:
MoroesMarkTargetAction(
PlayerbotAI* botAI, std::string const name = "moroes mark target") : Action(botAI, name) {}
bool Execute(Event event) override;
};
class MaidenOfVirtueMoveBossToHealerAction : public AttackAction
{
public:
MaidenOfVirtueMoveBossToHealerAction(
PlayerbotAI* botAI, std::string const name = "maiden of virtue move boss to healer") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class MaidenOfVirtuePositionRangedAction : public MovementAction
{
public:
MaidenOfVirtuePositionRangedAction(
PlayerbotAI* botAI, std::string const name = "maiden of virtue position ranged") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class BigBadWolfPositionBossAction : public AttackAction
{
public:
BigBadWolfPositionBossAction(
PlayerbotAI* botAI, std::string const name = "big bad wolf position boss") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class BigBadWolfRunAwayFromBossAction : public MovementAction
{
public:
BigBadWolfRunAwayFromBossAction(
PlayerbotAI* botAI, std::string const name = "big bad wolf run away from boss") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class RomuloAndJulianneMarkTargetAction : public Action
{
public:
RomuloAndJulianneMarkTargetAction(
PlayerbotAI* botAI, std::string const name = "romulo and julianne mark target") : Action(botAI, name) {}
bool Execute(Event event) override;
};
class WizardOfOzMarkTargetAction : public Action
{
public:
WizardOfOzMarkTargetAction(
PlayerbotAI* botAI, std::string const name = "wizard of oz mark target") : Action(botAI, name) {}
bool Execute(Event event) override;
};
class WizardOfOzScorchStrawmanAction : public Action
{
public:
WizardOfOzScorchStrawmanAction(
PlayerbotAI* botAI, std::string const name = "wizard of oz scorch strawman") : Action(botAI, name) {}
bool Execute(Event event) override;
};
class TheCuratorMarkAstralFlareAction : public Action
{
public:
TheCuratorMarkAstralFlareAction(
PlayerbotAI* botAI, std::string const name = "the curator mark astral flare") : Action(botAI, name) {}
bool Execute(Event event) override;
};
class TheCuratorPositionBossAction : public AttackAction
{
public:
TheCuratorPositionBossAction(
PlayerbotAI* botAI, std::string const name = "the curator position boss") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class TheCuratorSpreadRangedAction : public MovementAction
{
public:
TheCuratorSpreadRangedAction(
PlayerbotAI* botAI, std::string const name = "the curator spread ranged") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class TerestianIllhoofMarkTargetAction : public Action
{
public:
TerestianIllhoofMarkTargetAction(
PlayerbotAI* botAI, std::string const name = "terestian illhoof mark target") : Action(botAI, name) {}
bool Execute(Event event) override;
};
class ShadeOfAranRunAwayFromArcaneExplosionAction : public MovementAction
{
public:
ShadeOfAranRunAwayFromArcaneExplosionAction(
PlayerbotAI* botAI, std::string const name = "shade of aran run away from arcane explosion") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class ShadeOfAranStopMovingDuringFlameWreathAction : public MovementAction
{
public:
ShadeOfAranStopMovingDuringFlameWreathAction(
PlayerbotAI* botAI, std::string const name = "shade of aran stop moving during flame wreath") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class ShadeOfAranMarkConjuredElementalAction : public Action
{
public:
ShadeOfAranMarkConjuredElementalAction(
PlayerbotAI* botAI, std::string const name = "shade of aran mark conjured elemental") : Action(botAI, name) {}
bool Execute(Event event) override;
};
class ShadeOfAranRangedMaintainDistanceAction : public MovementAction
{
public:
ShadeOfAranRangedMaintainDistanceAction(
PlayerbotAI* botAI, std::string const name = "shade of aran ranged maintain distance") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class NetherspiteBlockRedBeamAction : public MovementAction
{
public:
NetherspiteBlockRedBeamAction(
PlayerbotAI* botAI, std::string const name = "netherspite block red beam") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
bool isUseful() override;
private:
size_t currentIndex = 0;
Position GetPositionOnBeam(Unit* netherspite, Unit* portal, float distanceFromBoss);
std::unordered_map<ObjectGuid, bool> _wasBlockingRedBeam;
};
class KarazhanRomuloAndJulianneMarkTargetAction : public Action
class NetherspiteBlockBlueBeamAction : public MovementAction
{
public:
KarazhanRomuloAndJulianneMarkTargetAction(PlayerbotAI* botAI, std::string const name = "karazhan romulo and julianne mark target") : Action(botAI, name) {}
NetherspiteBlockBlueBeamAction(
PlayerbotAI* botAI, std::string const name = "netherspite block blue beam") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
private:
std::unordered_map<ObjectGuid, bool> _wasBlockingBlueBeam;
};
class NetherspiteBlockGreenBeamAction : public MovementAction
{
public:
NetherspiteBlockGreenBeamAction(
PlayerbotAI* botAI, std::string const name = "netherspite block green beam") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
private:
std::unordered_map<ObjectGuid, bool> _wasBlockingGreenBeam;
};
class NetherspiteAvoidBeamAndVoidZoneAction : public MovementAction
{
public:
NetherspiteAvoidBeamAndVoidZoneAction(
PlayerbotAI* botAI, std::string const name = "netherspite avoid beam and void zone") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
private:
struct BeamAvoid
{
Unit* portal;
float minDist, maxDist;
};
bool IsAwayFromBeams(float x, float y, const std::vector<BeamAvoid>& beams, Unit* netherspite);
};
class NetherspiteBanishPhaseAvoidVoidZoneAction : public MovementAction
{
public:
NetherspiteBanishPhaseAvoidVoidZoneAction(
PlayerbotAI* botAI, std::string const name = "netherspite banish phase avoid void zone") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class KarazhanWizardOfOzMarkTargetAction : public Action
class NetherspiteManageTimersAndTrackersAction : public Action
{
public:
KarazhanWizardOfOzMarkTargetAction(PlayerbotAI* botAI, std::string const name = "karazhan wizard of oz mark target") : Action(botAI, name) {}
NetherspiteManageTimersAndTrackersAction(
PlayerbotAI* botAI, std::string const name = "netherspite manage timers and trackers") : Action(botAI, name) {}
bool Execute(Event event) override;
};
class KarazhanWizardOfOzScorchStrawmanAction : public Action
class PrinceMalchezaarEnfeebledAvoidHazardAction : public MovementAction
{
public:
KarazhanWizardOfOzScorchStrawmanAction(PlayerbotAI* botAI, std::string const name = "karazhan wizard of oz scorch strawman") : Action(botAI, name) {}
PrinceMalchezaarEnfeebledAvoidHazardAction(
PlayerbotAI* botAI, std::string const name = "prince malchezaar enfeebled avoid hazard") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class KarazhanTheCuratorMarkTargetAction : public Action
class PrinceMalchezaarNonTankAvoidInfernalAction : public MovementAction
{
public:
KarazhanTheCuratorMarkTargetAction(PlayerbotAI* botAI, std::string const name = "karazhan the curator mark target") : Action(botAI, name) {}
PrinceMalchezaarNonTankAvoidInfernalAction(
PlayerbotAI* botAI, std::string const name = "prince malchezaar non tank avoid infernal") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class KarazhanTheCuratorPositionBossAction : public MovementAction
class PrinceMalchezaarMainTankMovementAction : public AttackAction
{
public:
KarazhanTheCuratorPositionBossAction(PlayerbotAI* botAI, std::string const name = "karazhan the curator position boss") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
bool isUseful() override;
};
class KarazhanTheCuratorSpreadRangedAction : public MovementAction
{
public:
KarazhanTheCuratorSpreadRangedAction(PlayerbotAI* botAI, std::string const name = "karazhan the curator spread ranged") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
bool isUseful() override;
};
class KarazhanTerestianIllhoofMarkTargetAction : public Action
{
public:
KarazhanTerestianIllhoofMarkTargetAction(PlayerbotAI* botAI, std::string const name = "karazhan terestian illhoof mark target") : Action(botAI, name) {}
PrinceMalchezaarMainTankMovementAction(
PlayerbotAI* botAI, std::string const name = "prince malchezaar main tank movement") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class KarazhanShadeOfAranArcaneExplosionRunAwayAction : public MovementAction
class NightbaneGroundPhasePositionBossAction : public AttackAction
{
public:
KarazhanShadeOfAranArcaneExplosionRunAwayAction(PlayerbotAI* botAI, std::string const name = "karazhan shade of aran arcane explosion run away") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
bool isUseful() override;
};
class KarazhanShadeOfAranFlameWreathStopMovementAction : public MovementAction
{
public:
KarazhanShadeOfAranFlameWreathStopMovementAction(PlayerbotAI* botAI, std::string const name = "karazhan shade of aran flame wreath stop bot") : MovementAction(botAI, name) {}
NightbaneGroundPhasePositionBossAction(
PlayerbotAI* botAI, std::string const name = "nightbane ground phase position boss") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class KarazhanShadeOfAranMarkConjuredElementalAction : public Action
class NightbaneGroundPhaseRotateRangedPositionsAction : public MovementAction
{
public:
KarazhanShadeOfAranMarkConjuredElementalAction(PlayerbotAI* botAI, std::string const name = "karazhan shade of aran mark conjured elemental") : Action(botAI, name) {}
NightbaneGroundPhaseRotateRangedPositionsAction(
PlayerbotAI* botAI, std::string const name = "nightbane ground phase rotate ranged positions") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class KarazhanShadeOfAranSpreadRangedAction : public MovementAction
class NightbaneCastFearWardOnMainTankAction : public Action
{
public:
KarazhanShadeOfAranSpreadRangedAction(PlayerbotAI* botAI, std::string const name = "karazhan shade of aran spread ranged") : MovementAction(botAI, name) {}
NightbaneCastFearWardOnMainTankAction(
PlayerbotAI* botAI, std::string const name = "nightbane cast fear ward on main tank") : Action(botAI, name) {}
bool Execute(Event event) override;
bool isUseful() override;
};
class KarazhanNetherspiteBlockRedBeamAction : public MovementAction
class NightbaneControlPetAggressionAction : public Action
{
public:
KarazhanNetherspiteBlockRedBeamAction(PlayerbotAI* botAI, std::string const name = "karazhan netherspite block red beam") : MovementAction(botAI, name) {}
NightbaneControlPetAggressionAction(
PlayerbotAI* botAI, std::string const name = "nightbane control pet aggression") : Action(botAI, name) {}
bool Execute(Event event) override;
bool isUseful() override;
};
class KarazhanNetherspiteBlockBlueBeamAction : public MovementAction
class NightbaneFlightPhaseMovementAction : public MovementAction
{
public:
KarazhanNetherspiteBlockBlueBeamAction(PlayerbotAI* botAI, std::string const name = "karazhan netherspite block blue beam") : MovementAction(botAI, name) {}
NightbaneFlightPhaseMovementAction(
PlayerbotAI* botAI, std::string const name = "nightbane flight phase movement") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
bool isUseful() override;
};
class KarazhanNetherspiteBlockGreenBeamAction : public MovementAction
class NightbaneManageTimersAndTrackersAction : public Action
{
public:
KarazhanNetherspiteBlockGreenBeamAction(PlayerbotAI* botAI, std::string const name = "karazhan netherspite block green beam") : MovementAction(botAI, name) {}
NightbaneManageTimersAndTrackersAction(
PlayerbotAI* botAI, std::string const name = "nightbane manage timers and trackers") : Action(botAI, name) {}
bool Execute(Event event) override;
bool isUseful() override;
};
class KarazhanNetherspiteAvoidBeamAndVoidZoneAction : public MovementAction
{
public:
KarazhanNetherspiteAvoidBeamAndVoidZoneAction(PlayerbotAI* botAI, std::string const name = "karazhan netherspite avoid beam and void zone") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
bool isUseful() override;
};
class KarazhanNetherspiteBanishPhaseAvoidVoidZoneAction : public MovementAction
{
public:
KarazhanNetherspiteBanishPhaseAvoidVoidZoneAction(PlayerbotAI* botAI, std::string const name = "karazhan netherspite banish phase avoid void zone") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
bool isUseful() override;
};
class KarazhanPrinceMalchezaarNonTankAvoidHazardAction : public MovementAction
{
public:
KarazhanPrinceMalchezaarNonTankAvoidHazardAction(PlayerbotAI* botAI, std::string const name = "karazhan prince malchezaar non-tank avoid hazard") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
bool isUseful() override;
};
class KarazhanPrinceMalchezaarTankAvoidHazardAction : public MovementAction
{
public:
KarazhanPrinceMalchezaarTankAvoidHazardAction(PlayerbotAI* botAI, std::string const name = "karazhan prince malchezaar tank avoid hazard") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
bool isUseful() override;
};
#endif

View File

@@ -1,316 +1,356 @@
#include <algorithm>
#include <map>
#include "RaidKarazhanHelpers.h"
#include "RaidKarazhanActions.h"
#include "AiObjectContext.h"
#include "PlayerbotMgr.h"
#include "Position.h"
#include "Spell.h"
#include "Playerbots.h"
#include "RtiTargetValue.h"
const Position KARAZHAN_MAIDEN_OF_VIRTUE_BOSS_POSITION = Position(-10945.881f, -2103.782f, 92.712f);
const Position KARAZHAN_MAIDEN_OF_VIRTUE_RANGED_POSITION[8] =
namespace KarazhanHelpers
{
{ -10931.178f, -2116.580f, 92.179f },
{ -10925.828f, -2102.425f, 92.180f },
{ -10933.089f, -2088.5017f, 92.180f },
{ -10947.59f, -2082.8147f, 92.180f },
{ -10960.912f, -2090.4368f, 92.179f },
{ -10966.017f, -2105.288f, 92.175f },
{ -10959.242f, -2119.6172f, 92.180f },
{ -10944.495f, -2123.857f, 92.180f },
};
// Attumen the Huntsman
std::unordered_map<uint32, time_t> attumenDpsWaitTimer;
// Big Bad Wolf
std::unordered_map<ObjectGuid, uint8> bigBadWolfRunIndex;
// Netherspite
std::unordered_map<uint32, time_t> netherspiteDpsWaitTimer;
std::unordered_map<ObjectGuid, time_t> redBeamMoveTimer;
std::unordered_map<ObjectGuid, bool> lastBeamMoveSideways;
// Nightbane
std::unordered_map<uint32, time_t> nightbaneDpsWaitTimer;
std::unordered_map<ObjectGuid, uint8> nightbaneTankStep;
std::unordered_map<ObjectGuid, uint8> nightbaneRangedStep;
std::unordered_map<uint32, time_t> nightbaneFlightPhaseStartTimer;
std::unordered_map<ObjectGuid, bool> nightbaneRainOfBonesHit;
const Position KARAZHAN_BIG_BAD_WOLF_BOSS_POSITION = Position(-10913.391f, -1773.508f, 90.477f);
const Position KARAZHAN_BIG_BAD_WOLF_RUN_POSITION[4] =
{
{ -10875.456f, -1779.036f, 90.477f },
{ -10872.281f, -1751.638f, 90.477f },
{ -10910.492f, -1747.401f, 90.477f },
{ -10913.391f, -1773.508f, 90.477f },
};
const Position KARAZHAN_THE_CURATOR_BOSS_POSITION = Position(-11139.463f, -1884.645f, 165.765f);
void RaidKarazhanHelpers::MarkTargetWithSkull(Unit* target)
{
if (!target)
const Position MAIDEN_OF_VIRTUE_BOSS_POSITION = { -10945.881f, -2103.782f, 92.712f };
const Position MAIDEN_OF_VIRTUE_RANGED_POSITION[8] =
{
return;
}
{ -10931.178f, -2116.580f, 92.179f },
{ -10925.828f, -2102.425f, 92.180f },
{ -10933.089f, -2088.502f, 92.180f },
{ -10947.590f, -2082.815f, 92.180f },
{ -10960.912f, -2090.437f, 92.179f },
{ -10966.017f, -2105.288f, 92.175f },
{ -10959.242f, -2119.617f, 92.180f },
{ -10944.495f, -2123.857f, 92.180f },
};
if (Group* group = bot->GetGroup())
const Position BIG_BAD_WOLF_BOSS_POSITION = { -10913.391f, -1773.508f, 90.477f };
const Position BIG_BAD_WOLF_RUN_POSITION[4] =
{
constexpr uint8_t skullIconId = 7;
ObjectGuid skullGuid = group->GetTargetIcon(skullIconId);
{ -10875.456f, -1779.036f, 90.477f },
{ -10872.281f, -1751.638f, 90.477f },
{ -10910.492f, -1747.401f, 90.477f },
{ -10913.391f, -1773.508f, 90.477f },
};
if (skullGuid != target->GetGUID())
const Position THE_CURATOR_BOSS_POSITION = { -11139.463f, -1884.645f, 165.765f };
const Position NIGHTBANE_TRANSITION_BOSS_POSITION = { -11160.646f, -1932.773f, 91.473f }; // near some ribs
const Position NIGHTBANE_FINAL_BOSS_POSITION = { -11173.530f, -1940.707f, 91.473f };
const Position NIGHTBANE_RANGED_POSITION1 = { -11145.949f, -1970.927f, 91.473f };
const Position NIGHTBANE_RANGED_POSITION2 = { -11143.594f, -1954.981f, 91.473f };
const Position NIGHTBANE_RANGED_POSITION3 = { -11159.778f, -1961.031f, 91.473f };
const Position NIGHTBANE_FLIGHT_STACK_POSITION = { -11159.555f, -1893.526f, 91.473f }; // Broken Barrel
const Position NIGHTBANE_RAIN_OF_BONES_POSITION = { -11165.233f, -1911.123f, 91.473f };
void MarkTargetWithIcon(Player* bot, Unit* target, uint8 iconId)
{
if (!target)
return;
if (Group* group = bot->GetGroup())
{
group->SetTargetIcon(skullIconId, bot->GetGUID(), target->GetGUID());
}
}
}
Unit* RaidKarazhanHelpers::GetFirstAliveUnit(const std::vector<Unit*>& units)
{
for (Unit* unit : units)
{
if (unit && unit->IsAlive())
{
return unit;
ObjectGuid currentGuid = group->GetTargetIcon(iconId);
if (currentGuid != target->GetGUID())
group->SetTargetIcon(iconId, bot->GetGUID(), target->GetGUID());
}
}
return nullptr;
}
Unit* RaidKarazhanHelpers::GetFirstAliveUnitByEntry(uint32 entry)
{
const GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs");
for (auto const& npcGuid : npcs)
void MarkTargetWithSkull(Player* bot, Unit* target)
{
Unit* unit = botAI->GetUnit(npcGuid);
MarkTargetWithIcon(bot, target, RtiTargetValue::skullIndex);
}
if (unit && unit->IsAlive() && unit->GetEntry() == entry)
void MarkTargetWithSquare(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::squareIndex);
}
void MarkTargetWithStar(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::starIndex);
}
void MarkTargetWithCircle(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::circleIndex);
}
void MarkTargetWithMoon(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::moonIndex);
}
void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target)
{
if (!target)
return;
std::string currentRti = botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Get();
Unit* currentTarget = botAI->GetAiObjectContext()->GetValue<Unit*>("rti target")->Get();
if (currentRti != rtiName || currentTarget != target)
{
return unit;
botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Set(rtiName);
botAI->GetAiObjectContext()->GetValue<Unit*>("rti target")->Set(target);
}
}
return nullptr;
}
Unit* RaidKarazhanHelpers::GetNearestPlayerInRadius(float radius)
{
if (Group* group = bot->GetGroup())
// Only one bot is needed to set/reset instance-wide timers
bool IsInstanceTimerManager(PlayerbotAI* botAI, Player* bot)
{
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
if (Group* group = bot->GetGroup())
{
Player* member = itr->GetSource();
if (!member || !member->IsAlive() || member == bot)
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
continue;
}
if (bot->GetExactDist2d(member) < radius)
{
return member;
Player* member = ref->GetSource();
if (member && member->IsAlive() && botAI->IsDps(member) && GET_PLAYERBOT_AI(member))
return member == bot;
}
}
return false;
}
return nullptr;
}
bool RaidKarazhanHelpers::IsFlameWreathActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "shade of aran");
Spell* currentSpell = boss ? boss->GetCurrentSpell(CURRENT_GENERIC_SPELL) : nullptr;
if (currentSpell && currentSpell->m_spellInfo && currentSpell->m_spellInfo->Id == SPELL_FLAME_WREATH)
Unit* GetFirstAliveUnit(const std::vector<Unit*>& units)
{
return true;
}
if (Group* group = bot->GetGroup())
{
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
for (Unit* unit : units)
{
Player* member = itr->GetSource();
if (!member || !member->IsAlive())
if (unit && unit->IsAlive())
return unit;
}
return nullptr;
}
Unit* GetFirstAliveUnitByEntry(PlayerbotAI* botAI, uint32 entry)
{
const GuidVector npcs = botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest hostile npcs")->Get();
for (auto const& npcGuid : npcs)
{
Unit* unit = botAI->GetUnit(npcGuid);
if (unit && unit->IsAlive() && unit->GetEntry() == entry)
return unit;
}
return nullptr;
}
Unit* GetNearestPlayerInRadius(Player* bot, float radius)
{
Unit* nearestPlayer = nullptr;
float nearestDistance = radius;
if (Group* group = bot->GetGroup())
{
for (GroupReference* ref = group->GetFirstMember(); ref != nullptr; ref = ref->next())
{
continue;
}
if (member->HasAura(SPELL_AURA_FLAME_WREATH))
{
return true;
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || member == bot)
continue;
float distance = bot->GetExactDist2d(member);
if (distance < nearestDistance)
{
nearestDistance = distance;
nearestPlayer = member;
}
}
}
return nearestPlayer;
}
return false;
}
// Red beam blockers: tank bots, no Nether Exhaustion Red
std::vector<Player*> RaidKarazhanHelpers::GetRedBlockers()
{
std::vector<Player*> redBlockers;
if (Group* group = bot->GetGroup())
bool IsFlameWreathActive(PlayerbotAI* botAI, Player* bot)
{
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
{
Player* member = itr->GetSource();
if (!member || !member->IsAlive() || !botAI->IsTank(member) || !GET_PLAYERBOT_AI(member) ||
member->HasAura(SPELL_NETHER_EXHAUSTION_RED))
{
continue;
}
redBlockers.push_back(member);
}
}
Unit* aran = botAI->GetAiObjectContext()->GetValue<Unit*>("find target", "shade of aran")->Get();
Spell* currentSpell = aran ? aran->GetCurrentSpell(CURRENT_GENERIC_SPELL) : nullptr;
return redBlockers;
}
if (currentSpell && currentSpell->m_spellInfo &&
currentSpell->m_spellInfo->Id == SPELL_FLAME_WREATH_CAST)
return true;
// Blue beam blockers: non-Rogue/Warrior DPS bots, no Nether Exhaustion Blue and ≤25 stacks of Blue Beam debuff
std::vector<Player*> RaidKarazhanHelpers::GetBlueBlockers()
{
std::vector<Player*> blueBlockers;
if (Group* group = bot->GetGroup())
{
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
if (Group* group = bot->GetGroup())
{
Player* member = itr->GetSource();
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member))
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
continue;
}
bool isDps = botAI->IsDps(member);
bool isWarrior = member->getClass() == CLASS_WARRIOR;
bool isRogue = member->getClass() == CLASS_ROGUE;
bool hasExhaustion = member->HasAura(SPELL_NETHER_EXHAUSTION_BLUE);
Aura* blueBuff = member->GetAura(SPELL_BLUE_BEAM_DEBUFF);
bool overStack = blueBuff && blueBuff->GetStackAmount() >= 26;
if (isDps && !isWarrior && !isRogue && !hasExhaustion && !overStack)
{
blueBlockers.push_back(member);
Player* member = ref->GetSource();
if (!member || !member->IsAlive())
continue;
if (member->HasAura(SPELL_FLAME_WREATH_AURA))
return true;
}
}
return false;
}
return blueBlockers;
}
// Green beam blockers:
// (1) Rogue and non-tank Warrior bots, no Nether Exhaustion Green
// (2) Healer bots, no Nether Exhaustion Green and ≤25 stacks of Green Beam debuff
std::vector<Player*> RaidKarazhanHelpers::GetGreenBlockers()
{
std::vector<Player*> greenBlockers;
if (Group* group = bot->GetGroup())
// Red beam blockers: tank bots, no Nether Exhaustion Red
std::vector<Player*> GetRedBlockers(PlayerbotAI* botAI, Player* bot)
{
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
std::vector<Player*> redBlockers;
if (Group* group = bot->GetGroup())
{
Player* member = itr->GetSource();
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member))
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
continue;
}
bool hasExhaustion = member->HasAura(SPELL_NETHER_EXHAUSTION_GREEN);
Aura* greenBuff = member->GetAura(SPELL_GREEN_BEAM_DEBUFF);
bool overStack = greenBuff && greenBuff->GetStackAmount() >= 26;
bool isRogue = member->getClass() == CLASS_ROGUE;
bool isDpsWarrior = member->getClass() == CLASS_WARRIOR && botAI->IsDps(member);
bool eligibleRogueWarrior = (isRogue || isDpsWarrior) && !hasExhaustion;
bool isHealer = botAI->IsHeal(member);
bool eligibleHealer = isHealer && !hasExhaustion && !overStack;
if (eligibleRogueWarrior || eligibleHealer)
{
greenBlockers.push_back(member);
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || !botAI->IsTank(member) || !GET_PLAYERBOT_AI(member) ||
member->HasAura(SPELL_NETHER_EXHAUSTION_RED))
continue;
redBlockers.push_back(member);
}
}
return redBlockers;
}
return greenBlockers;
}
Position RaidKarazhanHelpers::GetPositionOnBeam(Unit* boss, Unit* portal, float distanceFromBoss)
{
float bx = boss->GetPositionX();
float by = boss->GetPositionY();
float bz = boss->GetPositionZ();
float px = portal->GetPositionX();
float py = portal->GetPositionY();
float dx = px - bx;
float dy = py - by;
float length = sqrt(dx*dx + dy*dy);
if (length == 0.0f)
// Blue beam blockers: non-Rogue/Warrior DPS bots, no Nether Exhaustion Blue and <24 stacks of Blue Beam debuff
std::vector<Player*> GetBlueBlockers(PlayerbotAI* botAI, Player* bot)
{
return Position(bx, by, bz);
std::vector<Player*> blueBlockers;
if (Group* group = bot->GetGroup())
{
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member))
continue;
bool hasExhaustion = member->HasAura(SPELL_NETHER_EXHAUSTION_BLUE);
Aura* blueBuff = member->GetAura(SPELL_BLUE_BEAM_DEBUFF);
bool overStack = blueBuff && blueBuff->GetStackAmount() >= 24;
bool isDps = botAI->IsDps(member);
bool isWarrior = member->getClass() == CLASS_WARRIOR;
bool isRogue = member->getClass() == CLASS_ROGUE;
if (isDps && !isWarrior && !isRogue && !hasExhaustion && !overStack)
blueBlockers.push_back(member);
}
}
return blueBlockers;
}
dx /= length;
dy /= length;
float targetX = bx + dx * distanceFromBoss;
float targetY = by + dy * distanceFromBoss;
float targetZ = bz;
return Position(targetX, targetY, targetZ);
}
std::tuple<Player*, Player*, Player*> RaidKarazhanHelpers::GetCurrentBeamBlockers()
{
static ObjectGuid currentRedBlocker;
static ObjectGuid currentGreenBlocker;
static ObjectGuid currentBlueBlocker;
Player* redBlocker = nullptr;
Player* greenBlocker = nullptr;
Player* blueBlocker = nullptr;
std::vector<Player*> redBlockers = GetRedBlockers();
if (!redBlockers.empty())
// Green beam blockers:
// (1) Prioritize Rogues and non-tank Warrior bots, no Nether Exhaustion Green
// (2) Then assign Healer bots, no Nether Exhaustion Green and <24 stacks of Green Beam debuff
std::vector<Player*> GetGreenBlockers(PlayerbotAI* botAI, Player* bot)
{
auto it = std::find_if(redBlockers.begin(), redBlockers.end(), [](Player* p)
std::vector<Player*> greenBlockers;
if (Group* group = bot->GetGroup())
{
return p && p->GetGUID() == currentRedBlocker;
});
if (it != redBlockers.end())
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member))
continue;
bool hasExhaustion = member->HasAura(SPELL_NETHER_EXHAUSTION_GREEN);
bool isRogue = member->getClass() == CLASS_ROGUE;
bool isDpsWarrior = member->getClass() == CLASS_WARRIOR && botAI->IsDps(member);
bool eligibleRogueWarrior = (isRogue || isDpsWarrior) && !hasExhaustion;
if (eligibleRogueWarrior)
greenBlockers.push_back(member);
}
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member))
continue;
bool hasExhaustion = member->HasAura(SPELL_NETHER_EXHAUSTION_GREEN);
Aura* greenBuff = member->GetAura(SPELL_GREEN_BEAM_DEBUFF);
bool overStack = greenBuff && greenBuff->GetStackAmount() >= 24;
bool isHealer = botAI->IsHeal(member);
bool eligibleHealer = isHealer && !hasExhaustion && !overStack;
if (eligibleHealer)
greenBlockers.push_back(member);
}
}
return greenBlockers;
}
std::tuple<Player*, Player*, Player*> GetCurrentBeamBlockers(PlayerbotAI* botAI, Player* bot)
{
static ObjectGuid currentRedBlocker;
static ObjectGuid currentGreenBlocker;
static ObjectGuid currentBlueBlocker;
Player* redBlocker = nullptr;
Player* greenBlocker = nullptr;
Player* blueBlocker = nullptr;
std::vector<Player*> redBlockers = GetRedBlockers(botAI, bot);
if (!redBlockers.empty())
{
redBlocker = *it;
auto it = std::find_if(redBlockers.begin(), redBlockers.end(), [](Player* player)
{
return player && player->GetGUID() == currentRedBlocker;
});
if (it != redBlockers.end())
redBlocker = *it;
else
redBlocker = redBlockers.front();
currentRedBlocker = redBlocker ? redBlocker->GetGUID() : ObjectGuid::Empty;
}
else
{
redBlocker = redBlockers.front();
currentRedBlocker = ObjectGuid::Empty;
redBlocker = nullptr;
}
currentRedBlocker = redBlocker ? redBlocker->GetGUID() : ObjectGuid::Empty;
}
else
{
currentRedBlocker = ObjectGuid::Empty;
redBlocker = nullptr;
}
std::vector<Player*> greenBlockers = GetGreenBlockers();
if (!greenBlockers.empty())
{
auto it = std::find_if(greenBlockers.begin(), greenBlockers.end(), [](Player* p)
std::vector<Player*> greenBlockers = GetGreenBlockers(botAI, bot);
if (!greenBlockers.empty())
{
return p && p->GetGUID() == currentGreenBlocker;
});
if (it != greenBlockers.end())
{
greenBlocker = *it;
auto it = std::find_if(greenBlockers.begin(), greenBlockers.end(), [](Player* player)
{
return player && player->GetGUID() == currentGreenBlocker;
});
if (it != greenBlockers.end())
greenBlocker = *it;
else
greenBlocker = greenBlockers.front();
currentGreenBlocker = greenBlocker ? greenBlocker->GetGUID() : ObjectGuid::Empty;
}
else
{
greenBlocker = greenBlockers.front();
currentGreenBlocker = ObjectGuid::Empty;
greenBlocker = nullptr;
}
currentGreenBlocker = greenBlocker ? greenBlocker->GetGUID() : ObjectGuid::Empty;
}
else
{
currentGreenBlocker = ObjectGuid::Empty;
greenBlocker = nullptr;
}
std::vector<Player*> blueBlockers = GetBlueBlockers();
std::vector<Player*> blueBlockers = GetBlueBlockers(botAI, bot);
if (!blueBlockers.empty())
{
auto it = std::find_if(blueBlockers.begin(), blueBlockers.end(), [](Player* p)
auto it = std::find_if(blueBlockers.begin(), blueBlockers.end(), [](Player* player)
{
return p && p->GetGUID() == currentBlueBlocker;
return player && player->GetGUID() == currentBlueBlocker;
});
if (it != blueBlockers.end())
{
blueBlocker = *it;
}
else
{
blueBlocker = blueBlockers.front();
}
currentBlueBlocker = blueBlocker ? blueBlocker->GetGUID() : ObjectGuid::Empty;
}
else
@@ -319,91 +359,132 @@ std::tuple<Player*, Player*, Player*> RaidKarazhanHelpers::GetCurrentBeamBlocker
blueBlocker = nullptr;
}
return std::make_tuple(redBlocker, greenBlocker, blueBlocker);
}
std::vector<Unit*> RaidKarazhanHelpers::GetAllVoidZones()
{
std::vector<Unit*> voidZones;
const float radius = 30.0f;
const GuidVector npcs = botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest npcs")->Get();
for (auto const& npcGuid : npcs)
{
Unit* unit = botAI->GetUnit(npcGuid);
if (!unit || unit->GetEntry() != NPC_VOID_ZONE)
{
continue;
}
float dist = bot->GetExactDist2d(unit);
if (dist < radius)
{
voidZones.push_back(unit);
}
return std::make_tuple(redBlocker, greenBlocker, blueBlocker);
}
return voidZones;
}
bool RaidKarazhanHelpers::IsSafePosition(float x, float y, float z,
const std::vector<Unit*>& hazards, float hazardRadius)
{
for (Unit* hazard : hazards)
std::vector<Unit*> GetAllVoidZones(PlayerbotAI* botAI, Player* bot)
{
float dist = std::sqrt(std::pow(x - hazard->GetPositionX(), 2) + std::pow(y - hazard->GetPositionY(), 2));
if (dist < hazardRadius)
std::vector<Unit*> voidZones;
const float radius = 30.0f;
const GuidVector npcs = botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest npcs")->Get();
for (auto const& npcGuid : npcs)
{
return false;
Unit* unit = botAI->GetUnit(npcGuid);
if (!unit || unit->GetEntry() != NPC_VOID_ZONE)
continue;
float dist = bot->GetExactDist2d(unit);
if (dist < radius)
voidZones.push_back(unit);
}
return voidZones;
}
return true;
}
std::vector<Unit*> RaidKarazhanHelpers::GetSpawnedInfernals() const
{
std::vector<Unit*> infernals;
const GuidVector npcs = botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest npcs")->Get();
for (auto const& npcGuid : npcs)
bool IsSafePosition(float x, float y, float z, const std::vector<Unit*>& hazards, float hazardRadius)
{
Unit* unit = botAI->GetUnit(npcGuid);
if (unit && unit->GetEntry() == NPC_NETHERSPITE_INFERNAL)
for (Unit* hazard : hazards)
{
infernals.push_back(unit);
float dist = hazard->GetExactDist2d(x, y);
if (dist < hazardRadius)
return false;
}
}
return infernals;
}
bool RaidKarazhanHelpers::IsStraightPathSafe(const Position& start, const Position& target, const std::vector<Unit*>& hazards, float hazardRadius, float stepSize)
{
float sx = start.GetPositionX();
float sy = start.GetPositionY();
float sz = start.GetPositionZ();
float tx = target.GetPositionX();
float ty = target.GetPositionY();
float tz = target.GetPositionZ();
float totalDist = std::sqrt(std::pow(tx - sx, 2) + std::pow(ty - sy, 2));
if (totalDist == 0.0f)
{
return true;
}
for (float checkDist = 0.0f; checkDist <= totalDist; checkDist += stepSize)
std::vector<Unit*> GetSpawnedInfernals(PlayerbotAI* botAI)
{
float t = checkDist / totalDist;
float checkX = sx + (tx - sx) * t;
float checkY = sy + (ty - sy) * t;
float checkZ = sz + (tz - sz) * t;
for (Unit* hazard : hazards)
std::vector<Unit*> infernals;
const GuidVector npcs = botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest npcs")->Get();
for (auto const& npcGuid : npcs)
{
float hazardDist = std::sqrt(std::pow(checkX - hazard->GetPositionX(), 2) + std::pow(checkY - hazard->GetPositionY(), 2));
if (hazardDist < hazardRadius)
{
return false;
}
Unit* unit = botAI->GetUnit(npcGuid);
if (unit && unit->GetEntry() == NPC_NETHERSPITE_INFERNAL)
infernals.push_back(unit);
}
return infernals;
}
return true;
bool IsStraightPathSafe(const Position& start, const Position& target, const std::vector<Unit*>& hazards,
float hazardRadius, float stepSize)
{
float sx = start.GetPositionX();
float sy = start.GetPositionY();
float sz = start.GetPositionZ();
float tx = target.GetPositionX();
float ty = target.GetPositionY();
float tz = target.GetPositionZ();
const float totalDist = start.GetExactDist2d(target.GetPositionX(), target.GetPositionY());
if (totalDist == 0.0f)
return true;
for (float checkDist = 0.0f; checkDist <= totalDist; checkDist += stepSize)
{
float t = checkDist / totalDist;
float checkX = sx + (tx - sx) * t;
float checkY = sy + (ty - sy) * t;
float checkZ = sz + (tz - sz) * t;
for (Unit* hazard : hazards)
{
const float hx = checkX - hazard->GetPositionX();
const float hy = checkY - hazard->GetPositionY();
if ((hx*hx + hy*hy) < hazardRadius * hazardRadius)
return false;
}
}
return true;
}
bool TryFindSafePositionWithSafePath(
Player* bot, float originX, float originY, float originZ, float centerX, float centerY, float centerZ,
const std::vector<Unit*>& hazards, float safeDistance, float stepSize, uint8 numAngles,
float maxSampleDist, bool requireSafePath, float& bestDestX, float& bestDestY, float& bestDestZ)
{
float bestMoveDist = std::numeric_limits<float>::max();
bool found = false;
for (int i = 0; i < numAngles; ++i)
{
float angle = (2.0f * M_PI * i) / numAngles;
float dx = cos(angle);
float dy = sin(angle);
for (float dist = stepSize; dist <= maxSampleDist; dist += stepSize)
{
float x = centerX + dx * dist;
float y = centerY + dy * dist;
float z = centerZ;
float destX = x, destY = y, destZ = z;
if (!bot->GetMap()->CheckCollisionAndGetValidCoords(bot, centerX, centerY, centerZ,
destX, destY, destZ, true))
continue;
if (!IsSafePosition(destX, destY, destZ, hazards, safeDistance))
continue;
if (requireSafePath)
{
if (!IsStraightPathSafe(Position(originX, originY, originZ), Position(destX, destY, destZ),
hazards, safeDistance, stepSize))
continue;
}
const float moveDist = Position(originX, originY, originZ).GetExactDist2d(destX, destY);
if (moveDist < bestMoveDist)
{
bestMoveDist = moveDist;
bestDestX = destX;
bestDestY = destY;
bestDestZ = destZ;
found = true;
}
}
}
return found;
}
}

View File

@@ -1,85 +1,136 @@
#ifndef _PLAYERBOT_RAIDKARAZHANHELPERS_H_
#define _PLAYERBOT_RAIDKARAZHANHELPERS_H_
#include <ctime>
#include <unordered_map>
#include "AiObject.h"
#include "Playerbots.h"
#include "Position.h"
#include "Unit.h"
enum KarazhanSpells
namespace KarazhanHelpers
{
// Maiden of Virtue
SPELL_REPENTANCE = 29511,
enum KarazhanSpells
{
// Maiden of Virtue
SPELL_REPENTANCE = 29511,
// Opera Event
SPELL_LITTLE_RED_RIDING_HOOD = 30756,
// Opera Event
SPELL_LITTLE_RED_RIDING_HOOD = 30756,
// Shade of Aran
SPELL_FLAME_WREATH = 30004,
SPELL_AURA_FLAME_WREATH = 29946,
SPELL_ARCANE_EXPLOSION = 29973,
SPELL_WARLOCK_BANISH = 18647, // Rank 2
// The Curator
SPELL_CURATOR_EVOCATION = 30254,
// Netherspite
SPELL_GREEN_BEAM_DEBUFF = 30422,
SPELL_BLUE_BEAM_DEBUFF = 30423,
SPELL_NETHER_EXHAUSTION_RED = 38637,
SPELL_NETHER_EXHAUSTION_GREEN = 38638,
SPELL_NETHER_EXHAUSTION_BLUE = 38639,
SPELL_NETHERSPITE_BANISHED = 39833,
// Shade of Aran
SPELL_FLAME_WREATH_CAST = 30004,
SPELL_FLAME_WREATH_AURA = 29946,
SPELL_ARCANE_EXPLOSION = 29973,
// Prince Malchezaar
SPELL_ENFEEBLE = 30843,
};
// Netherspite
SPELL_RED_BEAM_DEBUFF = 30421, // "Nether Portal - Perseverance" (player aura)
SPELL_GREEN_BEAM_DEBUFF = 30422, // "Nether Portal - Serenity" (player aura)
SPELL_BLUE_BEAM_DEBUFF = 30423, // "Nether Portal - Dominance" (player aura)
SPELL_GREEN_BEAM_HEAL = 30467, // "Nether Portal - Serenity" (Netherspite aura)
SPELL_NETHER_EXHAUSTION_RED = 38637,
SPELL_NETHER_EXHAUSTION_GREEN = 38638,
SPELL_NETHER_EXHAUSTION_BLUE = 38639,
SPELL_NETHERSPITE_BANISHED = 39833, // "Vortex Shade Black"
// Prince Malchezaar
SPELL_ENFEEBLE = 30843,
// Nightbane
SPELL_CHARRED_EARTH = 30129,
SPELL_BELLOWING_ROAR = 36922,
SPELL_RAIN_OF_BONES = 37091,
// Warlock
SPELL_WARLOCK_BANISH = 18647,
// Priest
SPELL_FEAR_WARD = 6346,
};
enum KarazhanNPCs
{
// Trash
NPC_SPECTRAL_RETAINER = 16410,
NPC_MANA_WARP = 16530,
// Attumen the Huntsman
NPC_ATTUMEN_THE_HUNTSMAN = 15550,
NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED = 16152,
// Shade of Aran
NPC_CONJURED_ELEMENTAL = 17167,
// Netherspite
NPC_VOID_ZONE = 16697,
NPC_GREEN_PORTAL = 17367, // "Nether Portal - Serenity <Healing Portal>"
NPC_BLUE_PORTAL = 17368, // "Nether Portal - Dominance <Damage Portal>"
NPC_RED_PORTAL = 17369, // "Nether Portal - Perseverance <Tanking Portal>"
// Prince Malchezaar
NPC_NETHERSPITE_INFERNAL = 17646,
};
const uint32 KARAZHAN_MAP_ID = 532;
const float NIGHTBANE_FLIGHT_Z = 95.0f;
enum KarazhanNpcs
{
// Attumen the Huntsman
NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED = 16152,
// Terestian Illhoof
NPC_KILREK = 17229,
NPC_DEMON_CHAINS = 17248,
// Shade of Aran
NPC_CONJURED_ELEMENTAL = 17167,
extern std::unordered_map<uint32, time_t> attumenDpsWaitTimer;
// Big Bad Wolf
extern std::unordered_map<ObjectGuid, uint8> bigBadWolfRunIndex;
// Netherspite
NPC_VOID_ZONE = 16697,
NPC_RED_PORTAL = 17369,
NPC_BLUE_PORTAL = 17368,
NPC_GREEN_PORTAL = 17367,
extern std::unordered_map<uint32, time_t> netherspiteDpsWaitTimer;
extern std::unordered_map<ObjectGuid, time_t> redBeamMoveTimer;
extern std::unordered_map<ObjectGuid, bool> lastBeamMoveSideways;
// Nightbane
extern std::unordered_map<uint32, time_t> nightbaneDpsWaitTimer;
extern std::unordered_map<ObjectGuid, uint8> nightbaneTankStep;
extern std::unordered_map<ObjectGuid, uint8> nightbaneRangedStep;
extern std::unordered_map<uint32, time_t> nightbaneFlightPhaseStartTimer;
extern std::unordered_map<ObjectGuid, bool> nightbaneRainOfBonesHit;
// Prince Malchezaar
NPC_NETHERSPITE_INFERNAL = 17646,
};
extern const Position MAIDEN_OF_VIRTUE_BOSS_POSITION;
extern const Position MAIDEN_OF_VIRTUE_RANGED_POSITION[8];
extern const Position BIG_BAD_WOLF_BOSS_POSITION;
extern const Position BIG_BAD_WOLF_RUN_POSITION[4];
extern const Position THE_CURATOR_BOSS_POSITION;
extern const Position NIGHTBANE_TRANSITION_BOSS_POSITION;
extern const Position NIGHTBANE_FINAL_BOSS_POSITION;
extern const Position NIGHTBANE_RANGED_POSITION1;
extern const Position NIGHTBANE_RANGED_POSITION2;
extern const Position NIGHTBANE_RANGED_POSITION3;
extern const Position NIGHTBANE_FLIGHT_STACK_POSITION;
extern const Position NIGHTBANE_RAIN_OF_BONES_POSITION;
extern const Position KARAZHAN_MAIDEN_OF_VIRTUE_BOSS_POSITION;
extern const Position KARAZHAN_MAIDEN_OF_VIRTUE_RANGED_POSITION[8];
extern const Position KARAZHAN_BIG_BAD_WOLF_BOSS_POSITION;
extern const Position KARAZHAN_BIG_BAD_WOLF_RUN_POSITION[4];
extern const Position KARAZHAN_THE_CURATOR_BOSS_POSITION;
class RaidKarazhanHelpers : public AiObject
{
public:
explicit RaidKarazhanHelpers(PlayerbotAI* botAI) : AiObject(botAI) {}
void MarkTargetWithSkull(Unit* /*target*/);
Unit* GetFirstAliveUnit(const std::vector<Unit*>& /*units*/);
Unit* GetFirstAliveUnitByEntry(uint32 /*entry*/);
Unit* GetNearestPlayerInRadius(float /*radius*/ = 5.0f);
bool IsFlameWreathActive();
Position GetPositionOnBeam(Unit* boss, Unit* portal, float distanceFromBoss);
std::vector<Player*> GetRedBlockers();
std::vector<Player*> GetBlueBlockers();
std::vector<Player*> GetGreenBlockers();
std::tuple<Player*, Player*, Player*> GetCurrentBeamBlockers();
std::vector<Unit*> GetAllVoidZones();
bool IsSafePosition (float x, float y, float z,
const std::vector<Unit*>& hazards, float hazardRadius);
std::vector<Unit*> GetSpawnedInfernals() const;
bool IsStraightPathSafe(const Position& start, const Position& target,
const std::vector<Unit*>& hazards, float hazardRadius, float stepSize);
};
void MarkTargetWithIcon(Player* bot, Unit* target, uint8 iconId);
void MarkTargetWithSkull(Player* bot, Unit* target);
void MarkTargetWithSquare(Player* bot, Unit* target);
void MarkTargetWithStar(Player* bot, Unit* target);
void MarkTargetWithCircle(Player* bot, Unit* target);
void MarkTargetWithMoon(Player* bot, Unit* target);
void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target);
bool IsInstanceTimerManager(PlayerbotAI* botAI, Player* bot);
Unit* GetFirstAliveUnit(const std::vector<Unit*>& units);
Unit* GetFirstAliveUnitByEntry(PlayerbotAI* botAI, uint32 entry);
Unit* GetNearestPlayerInRadius(Player* bot, float radius);
bool IsFlameWreathActive(PlayerbotAI* botAI, Player* bot);
std::vector<Player*> GetRedBlockers(PlayerbotAI* botAI, Player* bot);
std::vector<Player*> GetBlueBlockers(PlayerbotAI* botAI, Player* bot);
std::vector<Player*> GetGreenBlockers(PlayerbotAI* botAI, Player* bot);
std::tuple<Player*, Player*, Player*> GetCurrentBeamBlockers(PlayerbotAI* botAI, Player* bot);
std::vector<Unit*> GetAllVoidZones(PlayerbotAI *botAI, Player* bot);
bool IsSafePosition (float x, float y, float z, const std::vector<Unit*>& hazards, float hazardRadius);
std::vector<Unit*> GetSpawnedInfernals(PlayerbotAI* botAI);
bool IsStraightPathSafe(
const Position& start, const Position& target,
const std::vector<Unit*>& hazards, float hazardRadius, float stepSize);
bool TryFindSafePositionWithSafePath(
Player* bot, float originX, float originY, float originZ, float centerX, float centerY, float centerZ,
const std::vector<Unit*>& hazards, float safeDistance, float stepSize, uint8 numAngles,
float maxSampleDist, bool requireSafePath, float& bestDestX, float& bestDestY, float& bestDestZ);
}
#endif

View File

@@ -1,265 +1,362 @@
#include "RaidKarazhanMultipliers.h"
#include "RaidKarazhanActions.h"
#include "RaidKarazhanHelpers.h"
#include "AiObjectContext.h"
#include "AttackAction.h"
#include "DruidBearActions.h"
#include "DruidCatActions.h"
#include "ChooseTargetActions.h"
#include "DruidActions.h"
#include "FollowActions.h"
#include "GenericActions.h"
#include "HunterActions.h"
#include "MageActions.h"
#include "Playerbots.h"
#include "PriestActions.h"
#include "ReachTargetActions.h"
#include "RogueActions.h"
#include "WarriorActions.h"
#include "ShamanActions.h"
static bool IsChargeAction(Action* action)
{
return dynamic_cast<CastChargeAction*>(action) ||
dynamic_cast<CastInterceptAction*>(action) ||
dynamic_cast<CastFeralChargeBearAction*>(action) ||
dynamic_cast<CastFeralChargeCatAction*>(action);
}
using namespace KarazhanHelpers;
float KarazhanAttumenTheHuntsmanMultiplier::GetValue(Action* action)
// Keep tanks from jumping back and forth between Attumen and Midnight
float AttumenTheHuntsmanDisableTankAssistMultiplier::GetValue(Action* action)
{
RaidKarazhanHelpers karazhanHelper(botAI);
Unit* boss = karazhanHelper.GetFirstAliveUnitByEntry(NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED);
if (boss && !(botAI->IsTank(bot) && botAI->HasAggro(boss) && boss->GetVictim() == bot) &&
(dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<KarazhanAttumenTheHuntsmanStackBehindAction*>(action)))
{
Unit* midnight = AI_VALUE2(Unit*, "find target", "midnight");
if (!midnight)
return 1.0f;
Unit* attumen = AI_VALUE2(Unit*, "find target", "attumen the huntsman");
if (!attumen)
return 1.0f;
if (bot->GetVictim() != nullptr && dynamic_cast<TankAssistAction*>(action))
return 0.0f;
return 1.0f;
}
// Try to get rid of jittering when bots are stacked behind Attumen
float AttumenTheHuntsmanStayStackedMultiplier::GetValue(Action* action)
{
Unit* attumenMounted = GetFirstAliveUnitByEntry(botAI, NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED);
if (!attumenMounted)
return 1.0f;
if (!botAI->IsMainTank(bot) && attumenMounted->GetVictim() != bot)
{
if (dynamic_cast<CombatFormationMoveAction*>(action) ||
dynamic_cast<FleeAction*>(action) ||
dynamic_cast<CastBlinkBackAction*>(action) ||
dynamic_cast<CastDisengageAction*>(action) ||
dynamic_cast<CastReachTargetSpellAction*>(action))
return 0.0f;
}
return 1.0f;
}
float KarazhanBigBadWolfMultiplier::GetValue(Action* action)
// Give the main tank 8 seconds to grab aggro when Attumen mounts Midnight
// In reality it's shorter because it takes Attumen a few seconds to aggro after mounting
float AttumenTheHuntsmanWaitForDpsMultiplier::GetValue(Action* action)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "the big bad wolf");
if (!boss)
{
Unit* attumenMounted = GetFirstAliveUnitByEntry(botAI, NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED);
if (!attumenMounted)
return 1.0f;
}
if (bot->HasAura(SPELL_LITTLE_RED_RIDING_HOOD))
const uint32 instanceId = attumenMounted->GetMap()->GetInstanceId();
const time_t now = std::time(nullptr);
const uint8 dpsWaitSeconds = 8;
auto it = attumenDpsWaitTimer.find(instanceId);
if (it == attumenDpsWaitTimer.end() || (now - it->second) < dpsWaitSeconds)
{
if ((dynamic_cast<MovementAction*>(action) && !dynamic_cast<KarazhanBigBadWolfRunAwayAction*>(action)) ||
(dynamic_cast<AttackAction*>(action)))
if (!botAI->IsMainTank(bot))
{
return 0.0f;
}
}
return 1.0f;
}
float KarazhanShadeOfAranMultiplier::GetValue(Action* action)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "shade of aran");
if (!boss)
{
return 1.0f;
}
if (boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(SPELL_ARCANE_EXPLOSION))
{
if (IsChargeAction(action))
{
return 0.0f;
}
if (dynamic_cast<MovementAction*>(action))
{
const float safeDistance = 20.0f;
if (bot->GetDistance2d(boss) >= safeDistance)
{
if (dynamic_cast<AttackAction*>(action) || (dynamic_cast<CastSpellAction*>(action) &&
!dynamic_cast<CastHealingSpellAction*>(action)))
return 0.0f;
}
}
}
bool flameWreathActive = boss->HasAura(SPELL_FLAME_WREATH);
if (!flameWreathActive && bot->GetGroup())
return 1.0f;
}
// The assist tank should stay on the boss to be 2nd on aggro and tank Hateful Bolts
float TheCuratorDisableTankAssistMultiplier::GetValue(Action* action)
{
Unit* curator = AI_VALUE2(Unit*, "find target", "the curator");
if (!curator)
return 1.0f;
if (bot->GetVictim() != nullptr && dynamic_cast<TankAssistAction*>(action))
return 0.0f;
return 1.0f;
}
// Save Bloodlust/Heroism for Evocation (100% increased damage)
float TheCuratorDelayBloodlustAndHeroismMultiplier::GetValue(Action* action)
{
Unit* curator = AI_VALUE2(Unit*, "find target", "the curator");
if (!curator)
return 1.0f;
if (!curator->HasAura(SPELL_CURATOR_EVOCATION))
{
for (GroupReference* itr = bot->GetGroup()->GetFirstMember(); itr != nullptr; itr = itr->next())
if (dynamic_cast<CastBloodlustAction*>(action) ||
dynamic_cast<CastHeroismAction*>(action))
return 0.0f;
}
return 1.0f;
}
// Don't charge back in when running from Arcane Explosion
float ShadeOfAranArcaneExplosionDisableChargeMultiplier::GetValue(Action* action)
{
Unit* aran = AI_VALUE2(Unit*, "find target", "shade of aran");
if (!aran)
return 1.0f;
if (aran->HasUnitState(UNIT_STATE_CASTING) &&
aran->FindCurrentSpellBySpellId(SPELL_ARCANE_EXPLOSION))
{
if (dynamic_cast<CastReachTargetSpellAction*>(action))
return 0.0f;
if (bot->GetDistance2d(aran) >= 20.0f)
{
Player* member = itr->GetSource();
if (member && member->HasAura(SPELL_AURA_FLAME_WREATH))
{
flameWreathActive = true;
break;
}
if (dynamic_cast<CombatFormationMoveAction*>(action) ||
dynamic_cast<FleeAction*>(action) ||
dynamic_cast<FollowAction*>(action) ||
dynamic_cast<ReachTargetAction*>(action) ||
dynamic_cast<AvoidAoeAction*>(action))
return 0.0f;
}
}
if (flameWreathActive)
return 1.0f;
}
// I will not move when Flame Wreath is cast or the raid blows up
float ShadeOfAranFlameWreathDisableMovementMultiplier::GetValue(Action* action)
{
Unit* aran = AI_VALUE2(Unit*, "find target", "shade of aran");
if (!aran)
return 1.0f;
if (IsFlameWreathActive(botAI, bot))
{
if (dynamic_cast<MovementAction*>(action) || IsChargeAction(action))
if (dynamic_cast<CombatFormationMoveAction*>(action) ||
dynamic_cast<FleeAction*>(action) ||
dynamic_cast<FollowAction*>(action) ||
dynamic_cast<ReachTargetAction*>(action) ||
dynamic_cast<AvoidAoeAction*>(action) ||
dynamic_cast<CastKillingSpreeAction*>(action) ||
dynamic_cast<CastBlinkBackAction*>(action) ||
dynamic_cast<CastDisengageAction*>(action) ||
dynamic_cast<CastReachTargetSpellAction*>(action))
return 0.0f;
}
return 1.0f;
}
// Try to rid of the jittering when blocking beams
float NetherspiteKeepBlockingBeamMultiplier::GetValue(Action* action)
{
Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite");
if (!netherspite || netherspite->HasAura(SPELL_NETHERSPITE_BANISHED))
return 1.0f;
auto [redBlocker, greenBlocker, blueBlocker] = GetCurrentBeamBlockers(botAI, bot);
if (bot == redBlocker)
{
if (dynamic_cast<CombatFormationMoveAction*>(action))
return 0.0f;
}
if (bot == blueBlocker)
{
if (dynamic_cast<CombatFormationMoveAction*>(action) ||
dynamic_cast<ReachTargetAction*>(action))
return 0.0f;
}
if (bot == greenBlocker)
{
if (dynamic_cast<CombatFormationMoveAction*>(action) ||
dynamic_cast<ReachTargetAction*>(action) ||
dynamic_cast<FleeAction*>(action) ||
dynamic_cast<CastKillingSpreeAction*>(action) ||
dynamic_cast<CastReachTargetSpellAction*>(action))
return 0.0f;
}
return 1.0f;
}
// Give tanks 5 seconds to get aggro during phase transitions
float NetherspiteWaitForDpsMultiplier::GetValue(Action* action)
{
Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite");
if (!netherspite || netherspite->HasAura(SPELL_NETHERSPITE_BANISHED))
return 1.0f;
const uint32 instanceId = netherspite->GetMap()->GetInstanceId();
const time_t now = std::time(nullptr);
const uint8 dpsWaitSeconds = 5;
auto it = netherspiteDpsWaitTimer.find(instanceId);
if (it == netherspiteDpsWaitTimer.end() || (now - it->second) < dpsWaitSeconds)
{
if (!botAI->IsTank(bot))
{
if (dynamic_cast<AttackAction*>(action) || (dynamic_cast<CastSpellAction*>(action) &&
!dynamic_cast<CastHealingSpellAction*>(action)))
return 0.0f;
}
}
return 1.0f;
return 1.0f;
}
float KarazhanNetherspiteBlueAndGreenBeamMultiplier::GetValue(Action* action)
// Disable standard "avoid aoe" strategy, which may interfere with scripted avoidance
float PrinceMalchezaarDisableAvoidAoeMultiplier::GetValue(Action* action)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "netherspite");
if (!boss || !boss->IsAlive())
{
Unit* malchezaar = AI_VALUE2(Unit*, "find target", "prince malchezaar");
if (!malchezaar)
return 1.0f;
}
if (dynamic_cast<AvoidAoeAction*>(action) || dynamic_cast<CastKillingSpreeAction*>(action))
{
return 0.0f;
}
RaidKarazhanHelpers karazhanHelper(botAI);
auto [redBlocker /*unused*/, greenBlocker, blueBlocker] = karazhanHelper.GetCurrentBeamBlockers();
bool isBlocker = (bot == greenBlocker || bot == blueBlocker);
if (isBlocker)
{
Unit* bluePortal = bot->FindNearestCreature(NPC_BLUE_PORTAL, 150.0f);
Unit* greenPortal = bot->FindNearestCreature(NPC_GREEN_PORTAL, 150.0f);
bool inBeam = false;
for (Unit* portal : {bluePortal, greenPortal})
{
if (!portal)
{
continue;
}
float bx = boss->GetPositionX(), by = boss->GetPositionY();
float px = portal->GetPositionX(), py = portal->GetPositionY();
float dx = px - bx, dy = py - by;
float length = sqrt(dx*dx + dy*dy);
if (length == 0.0f)
{
continue;
}
dx /= length; dy /= length;
float botdx = bot->GetPositionX() - bx, botdy = bot->GetPositionY() - by;
float t = (botdx * dx + botdy * dy);
float beamX = bx + dx * t, beamY = by + dy * t;
float distToBeam = sqrt(pow(bot->GetPositionX() - beamX, 2) + pow(bot->GetPositionY() - beamY, 2));
if (distToBeam < 0.3f && t > 0.0f && t < length)
{
inBeam = true;
break;
}
}
if (inBeam)
{
std::vector<Unit*> voidZones = karazhanHelper.GetAllVoidZones();
bool inVoidZone = false;
for (Unit* vz : voidZones)
{
if (bot->GetExactDist2d(vz) < 4.0f)
{
inVoidZone = true;
break;
}
}
if (!inVoidZone)
{
if (dynamic_cast<MovementAction*>(action) || IsChargeAction(action))
{
return 0.0f;
}
}
}
}
return 1.0f;
}
float KarazhanNetherspiteRedBeamMultiplier::GetValue(Action* action)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "netherspite");
if (!boss || !boss->IsAlive())
{
return 1.0f;
}
if (dynamic_cast<AvoidAoeAction*>(action))
{
return 0.0f;
return 1.0f;
}
// Don't run back into Shadow Nova when Enfeebled
float PrinceMalchezaarEnfeebleKeepDistanceMultiplier::GetValue(Action* action)
{
Unit* malchezaar = AI_VALUE2(Unit*, "find target", "prince malchezaar");
if (!malchezaar)
return 1.0f;
if (bot->HasAura(SPELL_ENFEEBLE))
{
if (dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<PrinceMalchezaarEnfeebledAvoidHazardAction*>(action))
return 0.0f;
}
RaidKarazhanHelpers karazhanHelper(botAI);
auto [redBlocker, greenBlocker /*unused*/, blueBlocker /*unused*/] = karazhanHelper.GetCurrentBeamBlockers();
static std::map<ObjectGuid, uint32> beamMoveTimes;
static std::map<ObjectGuid, bool> lastBeamMoveSideways;
ObjectGuid botGuid = bot->GetGUID();
Unit* redPortal = bot->FindNearestCreature(NPC_RED_PORTAL, 150.0f);
if (bot == redBlocker && boss && redPortal)
{
Position blockingPos = karazhanHelper.GetPositionOnBeam(boss, redPortal, 18.0f);
float bx = boss->GetPositionX();
float by = boss->GetPositionY();
float px = redPortal->GetPositionX();
float py = redPortal->GetPositionY();
float dx = px - bx;
float dy = py - by;
float length = sqrt(dx*dx + dy*dy);
if (length != 0.0f)
{
dx /= length;
dy /= length;
float perpDx = -dy;
float perpDy = dx;
Position sidewaysPos(blockingPos.GetPositionX() + perpDx * 3.0f,
blockingPos.GetPositionY() + perpDy * 3.0f,
blockingPos.GetPositionZ());
return 1.0f;
}
uint32 intervalSecs = 5;
if (beamMoveTimes[botGuid] == 0)
{
beamMoveTimes[botGuid] = time(nullptr);
lastBeamMoveSideways[botGuid] = false;
}
if (time(nullptr) - beamMoveTimes[botGuid] >= intervalSecs)
{
lastBeamMoveSideways[botGuid] = !lastBeamMoveSideways[botGuid];
beamMoveTimes[botGuid] = time(nullptr);
}
Position targetPos = lastBeamMoveSideways[botGuid] ? sidewaysPos : blockingPos;
float distToTarget = bot->GetExactDist2d(targetPos.GetPositionX(), targetPos.GetPositionY());
const float positionTolerance = 1.5f;
if (distToTarget < positionTolerance)
{
if (dynamic_cast<MovementAction*>(action) || IsChargeAction(action))
{
return 0.0f;
}
}
// Wait until Phase 3 to use Bloodlust/Heroism
float PrinceMalchezaarDelayBloodlustAndHeroismMultiplier::GetValue(Action* action)
{
Unit* malchezaar = AI_VALUE2(Unit*, "find target", "prince malchezaar");
if (!malchezaar)
return 1.0f;
if (malchezaar->GetHealthPct() > 30.0f)
{
if (dynamic_cast<CastBloodlustAction*>(action) ||
dynamic_cast<CastHeroismAction*>(action))
return 0.0f;
}
return 1.0f;
}
// Pets tend to run out of bounds and cause skeletons to spawn off the map
// Pets also tend to pull adds from inside of the tower through the floor
// This multiplier DOES NOT impact Hunter and Warlock pets
// Hunter and Warlock pets are addressed in ControlPetAggressionAction
float NightbaneDisablePetsMultiplier::GetValue(Action* action)
{
Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane");
if (!nightbane)
return 1.0f;
if (dynamic_cast<CastForceOfNatureAction*>(action) ||
dynamic_cast<CastFeralSpiritAction*>(action) ||
dynamic_cast<CastFireElementalTotemAction*>(action) ||
dynamic_cast<CastFireElementalTotemMeleeAction*>(action) ||
dynamic_cast<CastSummonWaterElementalAction*>(action) ||
dynamic_cast<CastShadowfiendAction*>(action))
return 0.0f;
if (nightbane->GetPositionZ() > NIGHTBANE_FLIGHT_Z)
{
if (dynamic_cast<PetAttackAction*>(action))
return 0.0f;
}
return 1.0f;
}
// Give the main tank 8 seconds to get aggro during phase transitions
float NightbaneWaitForDpsMultiplier::GetValue(Action* action)
{
Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane");
if (!nightbane || nightbane->GetPositionZ() > NIGHTBANE_FLIGHT_Z)
return 1.0f;
const uint32 instanceId = nightbane->GetMap()->GetInstanceId();
const time_t now = std::time(nullptr);
const uint8 dpsWaitSeconds = 8;
auto it = nightbaneDpsWaitTimer.find(instanceId);
if (it == nightbaneDpsWaitTimer.end() || (now - it->second) < dpsWaitSeconds)
{
if (!botAI->IsMainTank(bot))
{
if (dynamic_cast<AttackAction*>(action) || (dynamic_cast<CastSpellAction*>(action) &&
!dynamic_cast<CastHealingSpellAction*>(action)))
return 0.0f;
}
}
return 1.0f;
}
float KarazhanPrinceMalchezaarMultiplier::GetValue(Action* action)
// The "avoid aoe" strategy must be disabled for the main tank
// Otherwise, the main tank will spin Nightbane to avoid Charred Earth and wipe the raid
// It is also disabled for all bots during the flight phase
float NightbaneDisableAvoidAoeMultiplier::GetValue(Action* action)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "prince malchezaar");
if (!boss || !boss->IsAlive())
{
Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane");
if (!nightbane)
return 1.0f;
}
if (dynamic_cast<AvoidAoeAction*>(action))
if (nightbane->GetPositionZ() > NIGHTBANE_FLIGHT_Z || botAI->IsMainTank(bot))
{
return 0.0f;
}
if (botAI->IsMelee(bot) && bot->HasAura(SPELL_ENFEEBLE) &&
!dynamic_cast<KarazhanPrinceMalchezaarNonTankAvoidHazardAction*>(action))
{
return 0.0f;
}
if (botAI->IsRanged(bot) && bot->HasAura(SPELL_ENFEEBLE) &&
(dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<KarazhanPrinceMalchezaarNonTankAvoidHazardAction*>(action)))
{
return 0.0f;
if (dynamic_cast<AvoidAoeAction*>(action))
return 0.0f;
}
return 1.0f;
}
// Disable some movement actions that conflict with the strategies
float NightbaneDisableMovementMultiplier::GetValue(Action* action)
{
Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane");
if (!nightbane)
return 1.0f;
if (dynamic_cast<CastBlinkBackAction*>(action) ||
dynamic_cast<CastDisengageAction*>(action) ||
dynamic_cast<FleeAction*>(action))
return 0.0f;
// Disable CombatFormationMoveAction for all bots except:
// (1) main tank and (2) only during the ground phase, other melee
if (botAI->IsRanged(bot) ||
(botAI->IsMelee(bot) && !botAI->IsMainTank(bot) &&
nightbane->GetPositionZ() > NIGHTBANE_FLIGHT_Z))
{
if (dynamic_cast<CombatFormationMoveAction*>(action))
return 0.0f;
}
return 1.0f;

View File

@@ -3,45 +3,131 @@
#include "Multiplier.h"
class KarazhanAttumenTheHuntsmanMultiplier : public Multiplier
class AttumenTheHuntsmanDisableTankAssistMultiplier : public Multiplier
{
public:
KarazhanAttumenTheHuntsmanMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "karazhan attumen the huntsman multiplier") {}
AttumenTheHuntsmanDisableTankAssistMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "attumen the huntsman disable tank assist multiplier") {}
virtual float GetValue(Action* action);
};
class KarazhanBigBadWolfMultiplier : public Multiplier
class AttumenTheHuntsmanStayStackedMultiplier : public Multiplier
{
public:
KarazhanBigBadWolfMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "karazhan big bad wolf multiplier") {}
AttumenTheHuntsmanStayStackedMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "attumen the huntsman stay stacked multiplier") {}
virtual float GetValue(Action* action);
};
class KarazhanShadeOfAranMultiplier : public Multiplier
class AttumenTheHuntsmanWaitForDpsMultiplier : public Multiplier
{
public:
KarazhanShadeOfAranMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "karazhan shade of aran multiplier") {}
AttumenTheHuntsmanWaitForDpsMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "attumen the huntsman wait for dps multiplier") {}
virtual float GetValue(Action* action);
};
class KarazhanNetherspiteBlueAndGreenBeamMultiplier : public Multiplier
class TheCuratorDisableTankAssistMultiplier : public Multiplier
{
public:
KarazhanNetherspiteBlueAndGreenBeamMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "karazhan netherspite blue and green beam multiplier") {}
TheCuratorDisableTankAssistMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "the curator disable tank assist multiplier") {}
virtual float GetValue(Action* action);
};
class KarazhanNetherspiteRedBeamMultiplier : public Multiplier
class TheCuratorDelayBloodlustAndHeroismMultiplier : public Multiplier
{
public:
KarazhanNetherspiteRedBeamMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "karazhan netherspite red beam multiplier") {}
TheCuratorDelayBloodlustAndHeroismMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "the curator delay bloodlust and heroism multiplier") {}
virtual float GetValue(Action* action);
};
class KarazhanPrinceMalchezaarMultiplier : public Multiplier
class ShadeOfAranArcaneExplosionDisableChargeMultiplier : public Multiplier
{
public:
KarazhanPrinceMalchezaarMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "karazhan prince malchezaar multiplier") {}
ShadeOfAranArcaneExplosionDisableChargeMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "shade of aran arcane explosion disable charge multiplier") {}
virtual float GetValue(Action* action);
};
class ShadeOfAranFlameWreathDisableMovementMultiplier : public Multiplier
{
public:
ShadeOfAranFlameWreathDisableMovementMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "shade of aran flame wreath disable movement multiplier") {}
virtual float GetValue(Action* action);
};
class NetherspiteKeepBlockingBeamMultiplier : public Multiplier
{
public:
NetherspiteKeepBlockingBeamMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "netherspite keep blocking beam multiplier") {}
virtual float GetValue(Action* action);
};
class NetherspiteWaitForDpsMultiplier : public Multiplier
{
public:
NetherspiteWaitForDpsMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "netherspite wait for dps multiplier") {}
virtual float GetValue(Action* action);
};
class PrinceMalchezaarDisableAvoidAoeMultiplier : public Multiplier
{
public:
PrinceMalchezaarDisableAvoidAoeMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "prince malchezaar disable avoid aoe multiplier") {}
virtual float GetValue(Action* action);
};
class PrinceMalchezaarEnfeebleKeepDistanceMultiplier : public Multiplier
{
public:
PrinceMalchezaarEnfeebleKeepDistanceMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "prince malchezaar enfeeble keep distance multiplier") {}
virtual float GetValue(Action* action);
};
class PrinceMalchezaarDelayBloodlustAndHeroismMultiplier : public Multiplier
{
public:
PrinceMalchezaarDelayBloodlustAndHeroismMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "prince malchezaar delay bloodlust and heroism multiplier") {}
virtual float GetValue(Action* action);
};
class NightbaneDisablePetsMultiplier : public Multiplier
{
public:
NightbaneDisablePetsMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "nightbane disable pets multiplier") {}
virtual float GetValue(Action* action);
};
class NightbaneWaitForDpsMultiplier : public Multiplier
{
public:
NightbaneWaitForDpsMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "nightbane wait for dps multiplier") {}
virtual float GetValue(Action* action);
};
class NightbaneDisableAvoidAoeMultiplier : public Multiplier
{
public:
NightbaneDisableAvoidAoeMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "nightbane disable avoid aoe multiplier") {}
virtual float GetValue(Action* action);
};
class NightbaneDisableMovementMultiplier : public Multiplier
{
public:
NightbaneDisableMovementMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "nightbane disable movement multiplier") {}
virtual float GetValue(Action* action);
};

View File

@@ -3,79 +3,160 @@
void RaidKarazhanStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(new TriggerNode(
"karazhan attumen the huntsman", NextAction::array(0,
new NextAction("karazhan attumen the huntsman stack behind", ACTION_RAID + 1),
nullptr)));
// Trash
triggers.push_back(new TriggerNode("mana warp is about to explode",
NextAction::array(0, new NextAction("mana warp stun creature before warp breach", ACTION_EMERGENCY + 6), nullptr)
));
triggers.push_back(new TriggerNode(
"karazhan moroes", NextAction::array(0,
new NextAction("karazhan moroes mark target", ACTION_RAID + 1),
nullptr)));
// Attumen the Huntsman
triggers.push_back(new TriggerNode("attumen the huntsman need target priority",
NextAction::array(0, new NextAction("attumen the huntsman mark target", ACTION_RAID + 1), nullptr)
));
triggers.push_back(new TriggerNode("attumen the huntsman attumen spawned",
NextAction::array(0, new NextAction("attumen the huntsman split bosses", ACTION_RAID + 2), nullptr)
));
triggers.push_back(new TriggerNode("attumen the huntsman attumen is mounted",
NextAction::array(0, new NextAction("attumen the huntsman stack behind", ACTION_RAID + 1), nullptr)
));
triggers.push_back(new TriggerNode("attumen the huntsman boss wipes aggro when mounting",
NextAction::array(0, new NextAction("attumen the huntsman manage dps timer", ACTION_RAID + 2), nullptr)
));
triggers.push_back(new TriggerNode(
"karazhan maiden of virtue", NextAction::array(0,
new NextAction("karazhan maiden of virtue position ranged", ACTION_RAID + 1),
new NextAction("karazhan maiden of virtue position boss", ACTION_RAID + 1),
nullptr)));
// Moroes
triggers.push_back(new TriggerNode("moroes boss engaged by main tank",
NextAction::array(0, new NextAction("moroes main tank attack boss", ACTION_RAID + 1), nullptr)
));
triggers.push_back(new TriggerNode("moroes need target priority",
NextAction::array(0, new NextAction("moroes mark target", ACTION_RAID + 1), nullptr)
));
triggers.push_back(new TriggerNode(
"karazhan big bad wolf", NextAction::array(0,
new NextAction("karazhan big bad wolf run away", ACTION_EMERGENCY + 6),
new NextAction("karazhan big bad wolf position boss", ACTION_RAID + 1),
nullptr)));
// Maiden of Virtue
triggers.push_back(new TriggerNode("maiden of virtue healers are stunned by repentance",
NextAction::array(0, new NextAction("maiden of virtue move boss to healer", ACTION_RAID + 1), nullptr)
));
triggers.push_back(new TriggerNode("maiden of virtue holy wrath deals chain damage",
NextAction::array(0, new NextAction("maiden of virtue position ranged", ACTION_RAID + 1), nullptr)
));
triggers.push_back(new TriggerNode(
"karazhan romulo and julianne", NextAction::array(0,
new NextAction("karazhan romulo and julianne mark target", ACTION_RAID + 1),
nullptr)));
// The Big Bad Wolf
triggers.push_back(new TriggerNode("big bad wolf boss is chasing little red riding hood",
NextAction::array(0, new NextAction("big bad wolf run away from boss", ACTION_EMERGENCY + 6), nullptr)
));
triggers.push_back(new TriggerNode("big bad wolf boss engaged by tank",
NextAction::array(0, new NextAction("big bad wolf position boss", ACTION_RAID + 1), nullptr)
));
triggers.push_back(new TriggerNode(
"karazhan wizard of oz", NextAction::array(0,
new NextAction("karazhan wizard of oz scorch strawman", ACTION_RAID + 2),
new NextAction("karazhan wizard of oz mark target", ACTION_RAID + 1),
nullptr)));
// Romulo and Julianne
triggers.push_back(new TriggerNode("romulo and julianne both bosses revived",
NextAction::array(0, new NextAction("romulo and julianne mark target", ACTION_RAID + 1), nullptr)
));
triggers.push_back(new TriggerNode(
"karazhan the curator", NextAction::array(0,
new NextAction("karazhan the curator spread ranged", ACTION_RAID + 2),
new NextAction("karazhan the curator position boss", ACTION_RAID + 2),
new NextAction("karazhan the curator mark target", ACTION_RAID + 1),
nullptr)));
// The Wizard of Oz
triggers.push_back(new TriggerNode("wizard of oz need target priority",
NextAction::array(0, new NextAction("wizard of oz mark target", ACTION_RAID + 1), nullptr)
));
triggers.push_back(new TriggerNode("wizard of oz strawman is vulnerable to fire",
NextAction::array(0, new NextAction("wizard of oz scorch strawman", ACTION_RAID + 2), nullptr)
));
triggers.push_back(new TriggerNode(
"karazhan terestian illhoof", NextAction::array(0,
new NextAction("karazhan terestian illhoof mark target", ACTION_RAID + 1),
nullptr)));
// The Curator
triggers.push_back(new TriggerNode("the curator astral flare spawned",
NextAction::array(0, new NextAction("the curator mark astral flare", ACTION_RAID + 1), nullptr)
));
triggers.push_back(new TriggerNode("the curator boss engaged by tanks",
NextAction::array(0, new NextAction("the curator position boss", ACTION_RAID + 2), nullptr)
));
triggers.push_back(new TriggerNode("the curator astral flares cast arcing sear",
NextAction::array(0, new NextAction("the curator spread ranged", ACTION_RAID + 2), nullptr)
));
triggers.push_back(new TriggerNode(
"karazhan shade of aran", NextAction::array(0,
new NextAction("karazhan shade of aran flame wreath stop movement", ACTION_EMERGENCY + 7),
new NextAction("karazhan shade of aran arcane explosion run away", ACTION_EMERGENCY + 6),
new NextAction("karazhan shade of aran spread ranged", ACTION_RAID + 2),
new NextAction("karazhan shade of aran mark conjured elemental", ACTION_RAID + 1),
nullptr)));
// Terestian Illhoof
triggers.push_back(new TriggerNode("terestian illhoof need target priority",
NextAction::array(0, new NextAction("terestian illhoof mark target", ACTION_RAID + 1), nullptr)
));
triggers.push_back(new TriggerNode(
"karazhan netherspite", NextAction::array(0,
new NextAction("karazhan netherspite block red beam", ACTION_EMERGENCY + 8),
new NextAction("karazhan netherspite block blue beam", ACTION_EMERGENCY + 8),
new NextAction("karazhan netherspite block green beam", ACTION_EMERGENCY + 8),
new NextAction("karazhan netherspite avoid beam and void zone", ACTION_EMERGENCY + 7),
new NextAction("karazhan netherspite banish phase avoid void zone", ACTION_RAID + 1),
nullptr)));
// Shade of Aran
triggers.push_back(new TriggerNode("shade of aran arcane explosion is casting",
NextAction::array(0, new NextAction("shade of aran run away from arcane explosion", ACTION_EMERGENCY + 6), nullptr)
));
triggers.push_back(new TriggerNode("shade of aran flame wreath is active",
NextAction::array(0, new NextAction("shade of aran stop moving during flame wreath", ACTION_EMERGENCY + 7), nullptr)
));
triggers.push_back(new TriggerNode("shade of aran conjured elementals summoned",
NextAction::array(0, new NextAction("shade of aran mark conjured elemental", ACTION_RAID + 1), nullptr)
));
triggers.push_back(new TriggerNode("shade of aran boss uses counterspell and blizzard",
NextAction::array(0, new NextAction("shade of aran ranged maintain distance", ACTION_RAID + 2), nullptr)
));
triggers.push_back(new TriggerNode(
"karazhan prince malchezaar", NextAction::array(0,
new NextAction("karazhan prince malchezaar non tank avoid hazard", ACTION_EMERGENCY + 6),
new NextAction("karazhan prince malchezaar tank avoid hazard", ACTION_EMERGENCY + 6),
nullptr)));
// Netherspite
triggers.push_back(new TriggerNode("netherspite red beam is active",
NextAction::array(0, new NextAction("netherspite block red beam", ACTION_EMERGENCY + 8), nullptr)
));
triggers.push_back(new TriggerNode("netherspite blue beam is active",
NextAction::array(0, new NextAction("netherspite block blue beam", ACTION_EMERGENCY + 8), nullptr)
));
triggers.push_back(new TriggerNode("netherspite green beam is active",
NextAction::array(0, new NextAction("netherspite block green beam", ACTION_EMERGENCY + 8), nullptr)
));
triggers.push_back(new TriggerNode("netherspite bot is not beam blocker",
NextAction::array(0, new NextAction("netherspite avoid beam and void zone", ACTION_EMERGENCY + 7), nullptr)
));
triggers.push_back(new TriggerNode("netherspite boss is banished",
NextAction::array(0, new NextAction("netherspite banish phase avoid void zone", ACTION_RAID + 1), nullptr)
));
triggers.push_back(new TriggerNode("netherspite need to manage timers and trackers",
NextAction::array(0, new NextAction("netherspite manage timers and trackers", ACTION_EMERGENCY + 10), nullptr)
));
// Prince Malchezaar
triggers.push_back(new TriggerNode("prince malchezaar bot is enfeebled",
NextAction::array(0, new NextAction("prince malchezaar enfeebled avoid hazard", ACTION_EMERGENCY + 6), nullptr)
));
triggers.push_back(new TriggerNode("prince malchezaar infernals are spawned",
NextAction::array(0, new NextAction("prince malchezaar non tank avoid infernal", ACTION_EMERGENCY + 1), nullptr)
));
triggers.push_back(new TriggerNode("prince malchezaar boss engaged by main tank",
NextAction::array(0, new NextAction("prince malchezaar main tank movement", ACTION_EMERGENCY + 6), nullptr)
));
// Nightbane
triggers.push_back(new TriggerNode("nightbane boss engaged by main tank",
NextAction::array(0, new NextAction("nightbane ground phase position boss", ACTION_RAID + 1), nullptr)
));
triggers.push_back(new TriggerNode("nightbane ranged bots are in charred earth",
NextAction::array(0, new NextAction("nightbane ground phase rotate ranged positions", ACTION_EMERGENCY + 1), nullptr)
));
triggers.push_back(new TriggerNode("nightbane main tank is susceptible to fear",
NextAction::array(0, new NextAction("nightbane cast fear ward on main tank", ACTION_RAID + 2), nullptr)
));
triggers.push_back(new TriggerNode("nightbane pets ignore collision to chase flying boss",
NextAction::array(0, new NextAction("nightbane control pet aggression", ACTION_RAID + 2), nullptr)
));
triggers.push_back(new TriggerNode("nightbane boss is flying",
NextAction::array(0, new NextAction("nightbane flight phase movement", ACTION_RAID + 1), nullptr)
));
triggers.push_back(new TriggerNode("nightbane need to manage timers and trackers",
NextAction::array(0, new NextAction("nightbane manage timers and trackers", ACTION_EMERGENCY + 10), nullptr)
));
}
void RaidKarazhanStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers)
{
multipliers.push_back(new KarazhanShadeOfAranMultiplier(botAI));
multipliers.push_back(new KarazhanNetherspiteBlueAndGreenBeamMultiplier(botAI));
multipliers.push_back(new KarazhanNetherspiteRedBeamMultiplier(botAI));
multipliers.push_back(new KarazhanPrinceMalchezaarMultiplier(botAI));
multipliers.push_back(new AttumenTheHuntsmanDisableTankAssistMultiplier(botAI));
multipliers.push_back(new AttumenTheHuntsmanStayStackedMultiplier(botAI));
multipliers.push_back(new AttumenTheHuntsmanWaitForDpsMultiplier(botAI));
multipliers.push_back(new TheCuratorDisableTankAssistMultiplier(botAI));
multipliers.push_back(new TheCuratorDelayBloodlustAndHeroismMultiplier(botAI));
multipliers.push_back(new ShadeOfAranArcaneExplosionDisableChargeMultiplier(botAI));
multipliers.push_back(new ShadeOfAranFlameWreathDisableMovementMultiplier(botAI));
multipliers.push_back(new NetherspiteKeepBlockingBeamMultiplier(botAI));
multipliers.push_back(new NetherspiteWaitForDpsMultiplier(botAI));
multipliers.push_back(new PrinceMalchezaarDisableAvoidAoeMultiplier(botAI));
multipliers.push_back(new PrinceMalchezaarEnfeebleKeepDistanceMultiplier(botAI));
multipliers.push_back(new PrinceMalchezaarDelayBloodlustAndHeroismMultiplier(botAI));
multipliers.push_back(new NightbaneDisablePetsMultiplier(botAI));
multipliers.push_back(new NightbaneWaitForDpsMultiplier(botAI));
multipliers.push_back(new NightbaneDisableAvoidAoeMultiplier(botAI));
multipliers.push_back(new NightbaneDisableMovementMultiplier(botAI));
}

View File

@@ -7,7 +7,7 @@
class RaidKarazhanStrategy : public Strategy
{
public:
RaidKarazhanStrategy(PlayerbotAI* ai) : Strategy(ai) {}
RaidKarazhanStrategy(PlayerbotAI* botAI) : Strategy(botAI) {}
std::string const getName() override { return "karazhan"; }

View File

@@ -9,31 +9,255 @@ class RaidKarazhanTriggerContext : public NamedObjectContext<Trigger>
public:
RaidKarazhanTriggerContext()
{
creators["karazhan attumen the huntsman"] = &RaidKarazhanTriggerContext::karazhan_attumen_the_huntsman;
creators["karazhan moroes"] = &RaidKarazhanTriggerContext::karazhan_moroes;
creators["karazhan maiden of virtue"] = &RaidKarazhanTriggerContext::karazhan_maiden_of_virtue;
creators["karazhan big bad wolf"] = &RaidKarazhanTriggerContext::karazhan_big_bad_wolf;
creators["karazhan romulo and julianne"] = &RaidKarazhanTriggerContext::karazhan_romulo_and_julianne;
creators["karazhan wizard of oz"] = &RaidKarazhanTriggerContext::karazhan_wizard_of_oz;
creators["karazhan the curator"] = &RaidKarazhanTriggerContext::karazhan_the_curator;
creators["karazhan terestian illhoof"] = &RaidKarazhanTriggerContext::karazhan_terestian_illhoof;
creators["karazhan shade of aran"] = &RaidKarazhanTriggerContext::karazhan_shade_of_aran;
creators["karazhan netherspite"] = &RaidKarazhanTriggerContext::karazhan_netherspite;
creators["karazhan prince malchezaar"] = &RaidKarazhanTriggerContext::karazhan_prince_malchezaar;
// Trash
creators["mana warp is about to explode"] =
&RaidKarazhanTriggerContext::mana_warp_is_about_to_explode;
// Attumen the Huntsman
creators["attumen the huntsman need target priority"] =
&RaidKarazhanTriggerContext::attumen_the_huntsman_need_target_priority;
creators["attumen the huntsman attumen spawned"] =
&RaidKarazhanTriggerContext::attumen_the_huntsman_attumen_spawned;
creators["attumen the huntsman attumen is mounted"] =
&RaidKarazhanTriggerContext::attumen_the_huntsman_attumen_is_mounted;
creators["attumen the huntsman boss wipes aggro when mounting"] =
&RaidKarazhanTriggerContext::attumen_the_huntsman_boss_wipes_aggro_when_mounting;
// Moroes
creators["moroes boss engaged by main tank"] =
&RaidKarazhanTriggerContext::moroes_boss_engaged_by_main_tank;
creators["moroes need target priority"] =
&RaidKarazhanTriggerContext::moroes_need_target_priority;
// Maiden of Virtue
creators["maiden of virtue healers are stunned by repentance"] =
&RaidKarazhanTriggerContext::maiden_of_virtue_healers_are_stunned_by_repentance;
creators["maiden of virtue holy wrath deals chain damage"] =
&RaidKarazhanTriggerContext::maiden_of_virtue_holy_wrath_deals_chain_damage;
// The Big Bad Wolf
creators["big bad wolf boss engaged by tank"] =
&RaidKarazhanTriggerContext::big_bad_wolf_boss_engaged_by_tank;
creators["big bad wolf boss is chasing little red riding hood"] =
&RaidKarazhanTriggerContext::big_bad_wolf_boss_is_chasing_little_red_riding_hood;
// Romulo and Julianne
creators["romulo and julianne both bosses revived"] =
&RaidKarazhanTriggerContext::romulo_and_julianne_both_bosses_revived;
// The Wizard of Oz
creators["wizard of oz need target priority"] =
&RaidKarazhanTriggerContext::wizard_of_oz_need_target_priority;
creators["wizard of oz strawman is vulnerable to fire"] =
&RaidKarazhanTriggerContext::wizard_of_oz_strawman_is_vulnerable_to_fire;
// The Curator
creators["the curator astral flare spawned"] =
&RaidKarazhanTriggerContext::the_curator_astral_flare_spawned;
creators["the curator boss engaged by tanks"] =
&RaidKarazhanTriggerContext::the_curator_boss_engaged_by_tanks;
creators["the curator astral flares cast arcing sear"] =
&RaidKarazhanTriggerContext::the_curator_astral_flares_cast_arcing_sear;
// Terestian Illhoof
creators["terestian illhoof need target priority"] =
&RaidKarazhanTriggerContext::terestian_illhoof_need_target_priority;
// Shade of Aran
creators["shade of aran arcane explosion is casting"] =
&RaidKarazhanTriggerContext::shade_of_aran_arcane_explosion_is_casting;
creators["shade of aran flame wreath is active"] =
&RaidKarazhanTriggerContext::shade_of_aran_flame_wreath_is_active;
creators["shade of aran conjured elementals summoned"] =
&RaidKarazhanTriggerContext::shade_of_aran_conjured_elementals_summoned;
creators["shade of aran boss uses counterspell and blizzard"] =
&RaidKarazhanTriggerContext::shade_of_aran_boss_uses_counterspell_and_blizzard;
// Netherspite
creators["netherspite red beam is active"] =
&RaidKarazhanTriggerContext::netherspite_red_beam_is_active;
creators["netherspite blue beam is active"] =
&RaidKarazhanTriggerContext::netherspite_blue_beam_is_active;
creators["netherspite green beam is active"] =
&RaidKarazhanTriggerContext::netherspite_green_beam_is_active;
creators["netherspite bot is not beam blocker"] =
&RaidKarazhanTriggerContext::netherspite_bot_is_not_beam_blocker;
creators["netherspite boss is banished"] =
&RaidKarazhanTriggerContext::netherspite_boss_is_banished;
creators["netherspite need to manage timers and trackers"] =
&RaidKarazhanTriggerContext::netherspite_need_to_manage_timers_and_trackers;
// Prince Malchezaar
creators["prince malchezaar bot is enfeebled"] =
&RaidKarazhanTriggerContext::prince_malchezaar_bot_is_enfeebled;
creators["prince malchezaar infernals are spawned"] =
&RaidKarazhanTriggerContext::prince_malchezaar_infernals_are_spawned;
creators["prince malchezaar boss engaged by main tank"] =
&RaidKarazhanTriggerContext::prince_malchezaar_boss_engaged_by_main_tank;
// Nightbane
creators["nightbane boss engaged by main tank"] =
&RaidKarazhanTriggerContext::nightbane_boss_engaged_by_main_tank;
creators["nightbane ranged bots are in charred earth"] =
&RaidKarazhanTriggerContext::nightbane_ranged_bots_are_in_charred_earth;
creators["nightbane main tank is susceptible to fear"] =
&RaidKarazhanTriggerContext::nightbane_main_tank_is_susceptible_to_fear;
creators["nightbane pets ignore collision to chase flying boss"] =
&RaidKarazhanTriggerContext::nightbane_pets_ignore_collision_to_chase_flying_boss;
creators["nightbane boss is flying"] =
&RaidKarazhanTriggerContext::nightbane_boss_is_flying;
creators["nightbane need to manage timers and trackers"] =
&RaidKarazhanTriggerContext::nightbane_need_to_manage_timers_and_trackers;
}
private:
static Trigger* karazhan_attumen_the_huntsman(PlayerbotAI* botAI) { return new KarazhanAttumenTheHuntsmanTrigger(botAI); }
static Trigger* karazhan_moroes(PlayerbotAI* botAI) { return new KarazhanMoroesTrigger(botAI); }
static Trigger* karazhan_maiden_of_virtue(PlayerbotAI* botAI) { return new KarazhanMaidenOfVirtueTrigger(botAI); }
static Trigger* karazhan_big_bad_wolf(PlayerbotAI* botAI) { return new KarazhanBigBadWolfTrigger(botAI); }
static Trigger* karazhan_romulo_and_julianne(PlayerbotAI* botAI) { return new KarazhanRomuloAndJulianneTrigger(botAI); }
static Trigger* karazhan_wizard_of_oz(PlayerbotAI* botAI) { return new KarazhanWizardOfOzTrigger(botAI); }
static Trigger* karazhan_the_curator(PlayerbotAI* botAI) { return new KarazhanTheCuratorTrigger(botAI); }
static Trigger* karazhan_terestian_illhoof(PlayerbotAI* botAI) { return new KarazhanTerestianIllhoofTrigger(botAI); }
static Trigger* karazhan_shade_of_aran(PlayerbotAI* botAI) { return new KarazhanShadeOfAranTrigger(botAI); }
static Trigger* karazhan_netherspite(PlayerbotAI* botAI) { return new KarazhanNetherspiteTrigger(botAI); }
static Trigger* karazhan_prince_malchezaar(PlayerbotAI* botAI) { return new KarazhanPrinceMalchezaarTrigger(botAI); }
// Trash
static Trigger* mana_warp_is_about_to_explode(
PlayerbotAI* botAI) { return new ManaWarpIsAboutToExplodeTrigger(botAI); }
// Attumen the Huntsman
static Trigger* attumen_the_huntsman_need_target_priority(
PlayerbotAI* botAI) { return new AttumenTheHuntsmanNeedTargetPriorityTrigger(botAI); }
static Trigger* attumen_the_huntsman_attumen_spawned(
PlayerbotAI* botAI) { return new AttumenTheHuntsmanAttumenSpawnedTrigger(botAI); }
static Trigger* attumen_the_huntsman_attumen_is_mounted(
PlayerbotAI* botAI) { return new AttumenTheHuntsmanAttumenIsMountedTrigger(botAI); }
static Trigger* attumen_the_huntsman_boss_wipes_aggro_when_mounting(
PlayerbotAI* botAI) { return new AttumenTheHuntsmanBossWipesAggroWhenMountingTrigger(botAI); }
// Moroes
static Trigger* moroes_boss_engaged_by_main_tank(
PlayerbotAI* botAI) { return new MoroesBossEngagedByMainTankTrigger(botAI); }
static Trigger* moroes_need_target_priority(
PlayerbotAI* botAI) { return new MoroesNeedTargetPriorityTrigger(botAI); }
// Maiden of Virtue
static Trigger* maiden_of_virtue_healers_are_stunned_by_repentance(
PlayerbotAI* botAI) { return new MaidenOfVirtueHealersAreStunnedByRepentanceTrigger(botAI); }
static Trigger* maiden_of_virtue_holy_wrath_deals_chain_damage(
PlayerbotAI* botAI) { return new MaidenOfVirtueHolyWrathDealsChainDamageTrigger(botAI); }
// The Big Bad Wolf
static Trigger* big_bad_wolf_boss_engaged_by_tank(
PlayerbotAI* botAI) { return new BigBadWolfBossEngagedByTankTrigger(botAI); }
static Trigger* big_bad_wolf_boss_is_chasing_little_red_riding_hood(
PlayerbotAI* botAI) { return new BigBadWolfBossIsChasingLittleRedRidingHoodTrigger(botAI); }
// Romulo and Julianne
static Trigger* romulo_and_julianne_both_bosses_revived(
PlayerbotAI* botAI) { return new RomuloAndJulianneBothBossesRevivedTrigger(botAI); }
// The Wizard of Oz
static Trigger* wizard_of_oz_need_target_priority(
PlayerbotAI* botAI) { return new WizardOfOzNeedTargetPriorityTrigger(botAI); }
static Trigger* wizard_of_oz_strawman_is_vulnerable_to_fire(
PlayerbotAI* botAI) { return new WizardOfOzStrawmanIsVulnerableToFireTrigger(botAI); }
// The Curator
static Trigger* the_curator_astral_flare_spawned(
PlayerbotAI* botAI) { return new TheCuratorAstralFlareSpawnedTrigger(botAI); }
static Trigger* the_curator_boss_engaged_by_tanks(
PlayerbotAI* botAI) { return new TheCuratorBossEngagedByTanksTrigger(botAI); }
static Trigger* the_curator_astral_flares_cast_arcing_sear(
PlayerbotAI* botAI) { return new TheCuratorBossAstralFlaresCastArcingSearTrigger(botAI); }
// Terestian Illhoof
static Trigger* terestian_illhoof_need_target_priority(
PlayerbotAI* botAI) { return new TerestianIllhoofNeedTargetPriorityTrigger(botAI); }
// Shade of Aran
static Trigger* shade_of_aran_arcane_explosion_is_casting(
PlayerbotAI* botAI) { return new ShadeOfAranArcaneExplosionIsCastingTrigger(botAI); }
static Trigger* shade_of_aran_flame_wreath_is_active(
PlayerbotAI* botAI) { return new ShadeOfAranFlameWreathIsActiveTrigger(botAI); }
static Trigger* shade_of_aran_conjured_elementals_summoned(
PlayerbotAI* botAI) { return new ShadeOfAranConjuredElementalsSummonedTrigger(botAI); }
static Trigger* shade_of_aran_boss_uses_counterspell_and_blizzard(
PlayerbotAI* botAI) { return new ShadeOfAranBossUsesCounterspellAndBlizzardTrigger(botAI); }
// Netherspite
static Trigger* netherspite_red_beam_is_active(
PlayerbotAI* botAI) { return new NetherspiteRedBeamIsActiveTrigger(botAI); }
static Trigger* netherspite_blue_beam_is_active(
PlayerbotAI* botAI) { return new NetherspiteBlueBeamIsActiveTrigger(botAI); }
static Trigger* netherspite_green_beam_is_active(
PlayerbotAI* botAI) { return new NetherspiteGreenBeamIsActiveTrigger(botAI); }
static Trigger* netherspite_bot_is_not_beam_blocker(
PlayerbotAI* botAI) { return new NetherspiteBotIsNotBeamBlockerTrigger(botAI); }
static Trigger* netherspite_boss_is_banished(
PlayerbotAI* botAI) { return new NetherspiteBossIsBanishedTrigger(botAI); }
static Trigger* netherspite_need_to_manage_timers_and_trackers(
PlayerbotAI* botAI) { return new NetherspiteNeedToManageTimersAndTrackersTrigger(botAI); }
// Prince Malchezaar
static Trigger* prince_malchezaar_bot_is_enfeebled(
PlayerbotAI* botAI) { return new PrinceMalchezaarBotIsEnfeebledTrigger(botAI); }
static Trigger* prince_malchezaar_infernals_are_spawned(
PlayerbotAI* botAI) { return new PrinceMalchezaarInfernalsAreSpawnedTrigger(botAI); }
static Trigger* prince_malchezaar_boss_engaged_by_main_tank(
PlayerbotAI* botAI) { return new PrinceMalchezaarBossEngagedByMainTankTrigger(botAI); }
// Nightbane
static Trigger* nightbane_boss_engaged_by_main_tank(
PlayerbotAI* botAI) { return new NightbaneBossEngagedByMainTankTrigger(botAI); }
static Trigger* nightbane_ranged_bots_are_in_charred_earth(
PlayerbotAI* botAI) { return new NightbaneRangedBotsAreInCharredEarthTrigger(botAI); }
static Trigger* nightbane_main_tank_is_susceptible_to_fear(
PlayerbotAI* botAI) { return new NightbaneMainTankIsSusceptibleToFearTrigger(botAI); }
static Trigger* nightbane_pets_ignore_collision_to_chase_flying_boss(
PlayerbotAI* botAI) { return new NightbanePetsIgnoreCollisionToChaseFlyingBossTrigger(botAI); }
static Trigger* nightbane_boss_is_flying(
PlayerbotAI* botAI) { return new NightbaneBossIsFlyingTrigger(botAI); }
static Trigger* nightbane_need_to_manage_timers_and_trackers(
PlayerbotAI* botAI) { return new NightbaneNeedToManageTimersAndTrackersTrigger(botAI); }
};
#endif
#endif

View File

@@ -3,17 +3,64 @@
#include "RaidKarazhanActions.h"
#include "Playerbots.h"
bool KarazhanAttumenTheHuntsmanTrigger::IsActive()
{
RaidKarazhanHelpers helpers(botAI);
Unit* boss = helpers.GetFirstAliveUnitByEntry(NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED);
using namespace KarazhanHelpers;
return boss && boss->IsAlive();
bool ManaWarpIsAboutToExplodeTrigger::IsActive()
{
Unit* manaWarp = AI_VALUE2(Unit*, "find target", "mana warp");
return manaWarp && manaWarp->GetHealthPct() < 15;
}
bool KarazhanMoroesTrigger::IsActive()
bool AttumenTheHuntsmanNeedTargetPriorityTrigger::IsActive()
{
if (botAI->IsHeal(bot))
return false;
Unit* midnight = AI_VALUE2(Unit*, "find target", "midnight");
return midnight != nullptr;
}
bool AttumenTheHuntsmanAttumenSpawnedTrigger::IsActive()
{
if (!botAI->IsAssistTankOfIndex(bot, 0))
return false;
Unit* attumen = GetFirstAliveUnitByEntry(botAI, NPC_ATTUMEN_THE_HUNTSMAN);
return attumen != nullptr;
}
bool AttumenTheHuntsmanAttumenIsMountedTrigger::IsActive()
{
if (botAI->IsMainTank(bot))
return false;
Unit* attumenMounted = GetFirstAliveUnitByEntry(botAI, NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED);
return attumenMounted && attumenMounted->GetVictim() != bot;
}
bool AttumenTheHuntsmanBossWipesAggroWhenMountingTrigger::IsActive()
{
if (!IsInstanceTimerManager(botAI, bot))
return false;
Unit* midnight = AI_VALUE2(Unit*, "find target", "midnight");
return midnight != nullptr;
}
bool MoroesBossEngagedByMainTankTrigger::IsActive()
{
if (!botAI->IsMainTank(bot))
return false;
Unit* moroes = AI_VALUE2(Unit*, "find target", "moroes");
return moroes != nullptr;
}
bool MoroesNeedTargetPriorityTrigger::IsActive()
{
if (!botAI->IsDps(bot))
return false;
Unit* dorothea = AI_VALUE2(Unit*, "find target", "baroness dorothea millstipe");
Unit* catriona = AI_VALUE2(Unit*, "find target", "lady catriona von'indi");
Unit* keira = AI_VALUE2(Unit*, "find target", "lady keira berrybuck");
@@ -21,39 +68,67 @@ bool KarazhanMoroesTrigger::IsActive()
Unit* robin = AI_VALUE2(Unit*, "find target", "lord robin daris");
Unit* crispin = AI_VALUE2(Unit*, "find target", "lord crispin ference");
return ((moroes && moroes->IsAlive()) ||
(dorothea && dorothea->IsAlive()) ||
(catriona && catriona->IsAlive()) ||
(keira && keira->IsAlive()) ||
(rafe && rafe->IsAlive()) ||
(robin && robin->IsAlive()) ||
(crispin && crispin->IsAlive()));
Unit* target = GetFirstAliveUnit({ dorothea, catriona, keira, rafe, robin, crispin });
return target != nullptr;
}
bool KarazhanMaidenOfVirtueTrigger::IsActive()
bool MaidenOfVirtueHealersAreStunnedByRepentanceTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "maiden of virtue");
if (!botAI->IsTank(bot))
return false;
return boss && boss->IsAlive();
Unit* maiden = AI_VALUE2(Unit*, "find target", "maiden of virtue");
return maiden && maiden->GetVictim() == bot;
}
bool KarazhanBigBadWolfTrigger::IsActive()
bool MaidenOfVirtueHolyWrathDealsChainDamageTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "the big bad wolf");
if (!botAI->IsRanged(bot))
return false;
return boss && boss->IsAlive();
Unit* maiden = AI_VALUE2(Unit*, "find target", "maiden of virtue");
return maiden != nullptr;
}
bool KarazhanRomuloAndJulianneTrigger::IsActive()
bool BigBadWolfBossEngagedByTankTrigger::IsActive()
{
Unit* julianne = AI_VALUE2(Unit*, "find target", "julianne");
if (!botAI->IsTank(bot) || bot->HasAura(SPELL_LITTLE_RED_RIDING_HOOD))
return false;
Unit* wolf = AI_VALUE2(Unit*, "find target", "the big bad wolf");
return wolf != nullptr;
}
bool BigBadWolfBossIsChasingLittleRedRidingHoodTrigger::IsActive()
{
if (!bot->HasAura(SPELL_LITTLE_RED_RIDING_HOOD))
return false;
Unit* wolf = AI_VALUE2(Unit*, "find target", "the big bad wolf");
return wolf != nullptr;
}
bool RomuloAndJulianneBothBossesRevivedTrigger::IsActive()
{
if (!IsInstanceTimerManager(botAI, bot))
return false;
Unit* romulo = AI_VALUE2(Unit*, "find target", "romulo");
if (!romulo)
return false;
return julianne && julianne->IsAlive() && romulo && romulo->IsAlive();
Unit* julianne = AI_VALUE2(Unit*, "find target", "julianne");
if (!julianne)
return false;
return true;
}
bool KarazhanWizardOfOzTrigger::IsActive()
bool WizardOfOzNeedTargetPriorityTrigger::IsActive()
{
if (!IsInstanceTimerManager(botAI, bot))
return false;
Unit* dorothee = AI_VALUE2(Unit*, "find target", "dorothee");
Unit* tito = AI_VALUE2(Unit*, "find target", "tito");
Unit* roar = AI_VALUE2(Unit*, "find target", "roar");
@@ -61,45 +136,250 @@ bool KarazhanWizardOfOzTrigger::IsActive()
Unit* tinhead = AI_VALUE2(Unit*, "find target", "tinhead");
Unit* crone = AI_VALUE2(Unit*, "find target", "the crone");
return ((dorothee && dorothee->IsAlive()) ||
(tito && tito->IsAlive()) ||
(roar && roar->IsAlive()) ||
(strawman && strawman->IsAlive()) ||
(tinhead && tinhead->IsAlive()) ||
(crone && crone->IsAlive()));
Unit* target = GetFirstAliveUnit({ dorothee, tito, roar, strawman, tinhead, crone });
return target != nullptr;
}
bool KarazhanTheCuratorTrigger::IsActive()
bool WizardOfOzStrawmanIsVulnerableToFireTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "the curator");
if (bot->getClass() != CLASS_MAGE)
return false;
return boss && boss->IsAlive();
Unit* strawman = AI_VALUE2(Unit*, "find target", "strawman");
return strawman && strawman->IsAlive();
}
bool KarazhanTerestianIllhoofTrigger::IsActive()
bool TheCuratorAstralFlareSpawnedTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "terestian illhoof");
if (!botAI->IsDps(bot))
return false;
return boss && boss->IsAlive();
Unit* flare = AI_VALUE2(Unit*, "find target", "astral flare");
return flare != nullptr;
}
bool KarazhanShadeOfAranTrigger::IsActive()
bool TheCuratorBossEngagedByTanksTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "shade of aran");
if (!botAI->IsMainTank(bot) && !botAI->IsAssistTankOfIndex(bot, 0))
return false;
return boss && boss->IsAlive();
Unit* curator = AI_VALUE2(Unit*, "find target", "the curator");
return curator != nullptr;
}
bool KarazhanNetherspiteTrigger::IsActive()
bool TheCuratorBossAstralFlaresCastArcingSearTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "netherspite");
if (!botAI->IsRanged(bot))
return false;
return boss && boss->IsAlive();
Unit* curator = AI_VALUE2(Unit*, "find target", "the curator");
return curator != nullptr;
}
bool KarazhanPrinceMalchezaarTrigger::IsActive()
bool TerestianIllhoofNeedTargetPriorityTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "prince malchezaar");
if (!IsInstanceTimerManager(botAI, bot))
return false;
return boss && boss->IsAlive();
Unit* illhoof = AI_VALUE2(Unit*, "find target", "terestian illhoof");
return illhoof != nullptr;
}
bool ShadeOfAranArcaneExplosionIsCastingTrigger::IsActive()
{
Unit* aran = AI_VALUE2(Unit*, "find target", "shade of aran");
return aran && aran->HasUnitState(UNIT_STATE_CASTING) &&
aran->FindCurrentSpellBySpellId(SPELL_ARCANE_EXPLOSION) &&
!IsFlameWreathActive(botAI, bot);
}
bool ShadeOfAranFlameWreathIsActiveTrigger::IsActive()
{
Unit* aran = AI_VALUE2(Unit*, "find target", "shade of aran");
return aran && IsFlameWreathActive(botAI, bot);
}
// Exclusion of Banish is so the player may Banish elementals if they wish
bool ShadeOfAranConjuredElementalsSummonedTrigger::IsActive()
{
if (!IsInstanceTimerManager(botAI, bot))
return false;
Unit* elemental = AI_VALUE2(Unit*, "find target", "conjured elemental");
return elemental && elemental->IsAlive() &&
!elemental->HasAura(SPELL_WARLOCK_BANISH);
}
bool ShadeOfAranBossUsesCounterspellAndBlizzardTrigger::IsActive()
{
if (!botAI->IsRanged(bot))
return false;
Unit* aran = AI_VALUE2(Unit*, "find target", "shade of aran");
return aran && !(aran->HasUnitState(UNIT_STATE_CASTING) &&
aran->FindCurrentSpellBySpellId(SPELL_ARCANE_EXPLOSION)) &&
!IsFlameWreathActive(botAI, bot);
}
bool NetherspiteRedBeamIsActiveTrigger::IsActive()
{
Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite");
if (!netherspite || netherspite->HasAura(SPELL_NETHERSPITE_BANISHED))
return false;
Unit* redPortal = bot->FindNearestCreature(NPC_RED_PORTAL, 150.0f);
return redPortal != nullptr;
}
bool NetherspiteBlueBeamIsActiveTrigger::IsActive()
{
Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite");
if (!netherspite || netherspite->HasAura(SPELL_NETHERSPITE_BANISHED))
return false;
Unit* bluePortal = bot->FindNearestCreature(NPC_BLUE_PORTAL, 150.0f);
return bluePortal != nullptr;
}
bool NetherspiteGreenBeamIsActiveTrigger::IsActive()
{
Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite");
if (!netherspite || netherspite->HasAura(SPELL_NETHERSPITE_BANISHED))
return false;
Unit* greenPortal = bot->FindNearestCreature(NPC_GREEN_PORTAL, 150.0f);
return greenPortal != nullptr;
}
bool NetherspiteBotIsNotBeamBlockerTrigger::IsActive()
{
Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite");
if (!netherspite || netherspite->HasAura(SPELL_NETHERSPITE_BANISHED))
return false;
auto [redBlocker, greenBlocker, blueBlocker] = GetCurrentBeamBlockers(botAI, bot);
return bot != redBlocker && bot != blueBlocker && bot != greenBlocker;
}
bool NetherspiteBossIsBanishedTrigger::IsActive()
{
Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite");
if (!netherspite || !netherspite->HasAura(SPELL_NETHERSPITE_BANISHED))
return false;
std::vector<Unit*> voidZones = GetAllVoidZones(botAI, bot);
for (Unit* vz : voidZones)
{
if (bot->GetExactDist2d(vz) < 4.0f)
return true;
}
return false;
}
bool NetherspiteNeedToManageTimersAndTrackersTrigger::IsActive()
{
if (!botAI->IsTank(bot) && !IsInstanceTimerManager(botAI, bot))
return false;
Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite");
return netherspite != nullptr;
}
bool PrinceMalchezaarBotIsEnfeebledTrigger::IsActive()
{
return bot->HasAura(SPELL_ENFEEBLE);
}
bool PrinceMalchezaarInfernalsAreSpawnedTrigger::IsActive()
{
if (botAI->IsMainTank(bot) || bot->HasAura(SPELL_ENFEEBLE))
return false;
Unit* malchezaar = AI_VALUE2(Unit*, "find target", "prince malchezaar");
return malchezaar != nullptr;
}
bool PrinceMalchezaarBossEngagedByMainTankTrigger::IsActive()
{
if (!botAI->IsMainTank(bot))
return false;
Unit* malchezaar = AI_VALUE2(Unit*, "find target", "prince malchezaar");
return malchezaar != nullptr;
}
bool NightbaneBossEngagedByMainTankTrigger::IsActive()
{
if (!botAI->IsMainTank(bot))
return false;
Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane");
return nightbane && nightbane->GetPositionZ() <= NIGHTBANE_FLIGHT_Z;
}
bool NightbaneRangedBotsAreInCharredEarthTrigger::IsActive()
{
if (!botAI->IsRanged(bot))
return false;
Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane");
return nightbane && nightbane->GetPositionZ() <= NIGHTBANE_FLIGHT_Z;
}
bool NightbaneMainTankIsSusceptibleToFearTrigger::IsActive()
{
if (bot->getClass() != CLASS_PRIEST)
return false;
Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane");
if (!nightbane)
return false;
Player* mainTank = nullptr;
if (Group* group = bot->GetGroup())
{
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (member && botAI->IsMainTank(member))
{
mainTank = member;
break;
}
}
}
return mainTank && !mainTank->HasAura(SPELL_FEAR_WARD) &&
botAI->CanCastSpell("fear ward", mainTank);
}
bool NightbanePetsIgnoreCollisionToChaseFlyingBossTrigger::IsActive()
{
Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane");
if (!nightbane)
return false;
Pet* pet = bot->GetPet();
return pet && pet->IsAlive();
}
bool NightbaneBossIsFlyingTrigger::IsActive()
{
Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane");
if (!nightbane || nightbane->GetPositionZ() <= NIGHTBANE_FLIGHT_Z)
return false;
const uint32 instanceId = nightbane->GetMap()->GetInstanceId();
const time_t now = std::time(nullptr);
const uint8 flightPhaseDurationSeconds = 35;
return nightbaneFlightPhaseStartTimer.find(instanceId) != nightbaneFlightPhaseStartTimer.end() &&
(now - nightbaneFlightPhaseStartTimer[instanceId] < flightPhaseDurationSeconds);
}
bool NightbaneNeedToManageTimersAndTrackersTrigger::IsActive()
{
Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane");
return nightbane != nullptr;
}

View File

@@ -3,80 +3,298 @@
#include "Trigger.h"
class KarazhanAttumenTheHuntsmanTrigger : public Trigger
class ManaWarpIsAboutToExplodeTrigger : public Trigger
{
public:
KarazhanAttumenTheHuntsmanTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan attumen the huntsman") {}
ManaWarpIsAboutToExplodeTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "mana warp is about to explode") {}
bool IsActive() override;
};
class KarazhanMoroesTrigger : public Trigger
class AttumenTheHuntsmanNeedTargetPriorityTrigger : public Trigger
{
public:
KarazhanMoroesTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan moroes") {}
AttumenTheHuntsmanNeedTargetPriorityTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "attumen the huntsman need target priority") {}
bool IsActive() override;
};
class KarazhanMaidenOfVirtueTrigger : public Trigger
class AttumenTheHuntsmanAttumenSpawnedTrigger : public Trigger
{
public:
KarazhanMaidenOfVirtueTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan maiden of virtue") {}
AttumenTheHuntsmanAttumenSpawnedTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "attumen the huntsman attumen spawned") {}
bool IsActive() override;
};
class KarazhanBigBadWolfTrigger : public Trigger
class AttumenTheHuntsmanAttumenIsMountedTrigger : public Trigger
{
public:
KarazhanBigBadWolfTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan big bad wolf") {}
AttumenTheHuntsmanAttumenIsMountedTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "attumen the huntsman attumen is mounted") {}
bool IsActive() override;
};
class KarazhanRomuloAndJulianneTrigger : public Trigger
class AttumenTheHuntsmanBossWipesAggroWhenMountingTrigger : public Trigger
{
public:
KarazhanRomuloAndJulianneTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan romulo and julianne") {}
AttumenTheHuntsmanBossWipesAggroWhenMountingTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "attumen the huntsman boss wipes aggro when mounting") {}
bool IsActive() override;
};
class KarazhanWizardOfOzTrigger : public Trigger
class MoroesBossEngagedByMainTankTrigger : public Trigger
{
public:
KarazhanWizardOfOzTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan wizard of oz") {}
MoroesBossEngagedByMainTankTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "moroes boss engaged by main tank") {}
bool IsActive() override;
};
class KarazhanTheCuratorTrigger : public Trigger
class MoroesNeedTargetPriorityTrigger : public Trigger
{
public:
KarazhanTheCuratorTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan the curator") {}
MoroesNeedTargetPriorityTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "moroes need target priority") {}
bool IsActive() override;
};
class KarazhanTerestianIllhoofTrigger : public Trigger
class MaidenOfVirtueHealersAreStunnedByRepentanceTrigger : public Trigger
{
public:
KarazhanTerestianIllhoofTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan terestian illhoof") {}
MaidenOfVirtueHealersAreStunnedByRepentanceTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "maiden of virtue healers are stunned by repentance") {}
bool IsActive() override;
};
class KarazhanShadeOfAranTrigger : public Trigger
class MaidenOfVirtueHolyWrathDealsChainDamageTrigger : public Trigger
{
public:
KarazhanShadeOfAranTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan shade of aran") {}
MaidenOfVirtueHolyWrathDealsChainDamageTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "maiden of virtue holy wrath deals chain damage") {}
bool IsActive() override;
};
class KarazhanNetherspiteTrigger : public Trigger
class BigBadWolfBossEngagedByTankTrigger : public Trigger
{
public:
KarazhanNetherspiteTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan netherspite") {}
BigBadWolfBossEngagedByTankTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "big bad wolf boss engaged by tank") {}
bool IsActive() override;
};
class KarazhanPrinceMalchezaarTrigger : public Trigger
class BigBadWolfBossIsChasingLittleRedRidingHoodTrigger : public Trigger
{
public:
KarazhanPrinceMalchezaarTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan prince malchezaar") {}
BigBadWolfBossIsChasingLittleRedRidingHoodTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "big bad wolf boss is chasing little red riding hood") {}
bool IsActive() override;
};
class RomuloAndJulianneBothBossesRevivedTrigger : public Trigger
{
public:
RomuloAndJulianneBothBossesRevivedTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "romulo and julianne both bosses revived") {}
bool IsActive() override;
};
class WizardOfOzNeedTargetPriorityTrigger : public Trigger
{
public:
WizardOfOzNeedTargetPriorityTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "wizard of oz need target priority") {}
bool IsActive() override;
};
class WizardOfOzStrawmanIsVulnerableToFireTrigger : public Trigger
{
public:
WizardOfOzStrawmanIsVulnerableToFireTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "wizard of oz strawman is vulnerable to fire") {}
bool IsActive() override;
};
class TheCuratorAstralFlareSpawnedTrigger : public Trigger
{
public:
TheCuratorAstralFlareSpawnedTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "the curator astral flare spawned") {}
bool IsActive() override;
};
class TheCuratorBossEngagedByTanksTrigger : public Trigger
{
public:
TheCuratorBossEngagedByTanksTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "the curator boss engaged by tanks") {}
bool IsActive() override;
};
class TheCuratorBossAstralFlaresCastArcingSearTrigger : public Trigger
{
public:
TheCuratorBossAstralFlaresCastArcingSearTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "the curator astral flares cast arcing sear") {}
bool IsActive() override;
};
class TerestianIllhoofNeedTargetPriorityTrigger : public Trigger
{
public:
TerestianIllhoofNeedTargetPriorityTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "terestian illhoof need target priority") {}
bool IsActive() override;
};
class ShadeOfAranArcaneExplosionIsCastingTrigger : public Trigger
{
public:
ShadeOfAranArcaneExplosionIsCastingTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "shade of aran arcane explosion is casting") {}
bool IsActive() override;
};
class ShadeOfAranFlameWreathIsActiveTrigger : public Trigger
{
public:
ShadeOfAranFlameWreathIsActiveTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "shade of aran flame wreath is active") {}
bool IsActive() override;
};
class ShadeOfAranConjuredElementalsSummonedTrigger : public Trigger
{
public:
ShadeOfAranConjuredElementalsSummonedTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "shade of aran conjured elementals summoned") {}
bool IsActive() override;
};
class ShadeOfAranBossUsesCounterspellAndBlizzardTrigger : public Trigger
{
public:
ShadeOfAranBossUsesCounterspellAndBlizzardTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "shade of aran boss uses counterspell and blizzard") {}
bool IsActive() override;
};
class NetherspiteRedBeamIsActiveTrigger : public Trigger
{
public:
NetherspiteRedBeamIsActiveTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "netherspite red beam is active") {}
bool IsActive() override;
};
class NetherspiteBlueBeamIsActiveTrigger : public Trigger
{
public:
NetherspiteBlueBeamIsActiveTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "netherspite blue beam is active") {}
bool IsActive() override;
};
class NetherspiteGreenBeamIsActiveTrigger : public Trigger
{
public:
NetherspiteGreenBeamIsActiveTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "netherspite green beam is active") {}
bool IsActive() override;
};
class NetherspiteBotIsNotBeamBlockerTrigger : public Trigger
{
public:
NetherspiteBotIsNotBeamBlockerTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "netherspite bot is not beam blocker") {}
bool IsActive() override;
};
class NetherspiteBossIsBanishedTrigger : public Trigger
{
public:
NetherspiteBossIsBanishedTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "netherspite boss is banished") {}
bool IsActive() override;
};
class NetherspiteNeedToManageTimersAndTrackersTrigger : public Trigger
{
public:
NetherspiteNeedToManageTimersAndTrackersTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "netherspite need to manage timers and trackers") {}
bool IsActive() override;
};
class PrinceMalchezaarBotIsEnfeebledTrigger : public Trigger
{
public:
PrinceMalchezaarBotIsEnfeebledTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "prince malchezaar bot is enfeebled") {}
bool IsActive() override;
};
class PrinceMalchezaarInfernalsAreSpawnedTrigger : public Trigger
{
public:
PrinceMalchezaarInfernalsAreSpawnedTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "prince malchezaar infernals are spawned") {}
bool IsActive() override;
};
class PrinceMalchezaarBossEngagedByMainTankTrigger : public Trigger
{
public:
PrinceMalchezaarBossEngagedByMainTankTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "prince malchezaar boss engaged by main tank") {}
bool IsActive() override;
};
class NightbaneBossEngagedByMainTankTrigger : public Trigger
{
public:
NightbaneBossEngagedByMainTankTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "nightbane boss engaged by main tank") {}
bool IsActive() override;
};
class NightbaneRangedBotsAreInCharredEarthTrigger : public Trigger
{
public:
NightbaneRangedBotsAreInCharredEarthTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "nightbane ranged bots are in charred earth") {}
bool IsActive() override;
};
class NightbaneMainTankIsSusceptibleToFearTrigger : public Trigger
{
public:
NightbaneMainTankIsSusceptibleToFearTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "nightbane main tank is susceptible to fear") {}
bool IsActive() override;
};
class NightbanePetsIgnoreCollisionToChaseFlyingBossTrigger : public Trigger
{
public:
NightbanePetsIgnoreCollisionToChaseFlyingBossTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "nightbane pets ignore collision to chase flying boss") {}
bool IsActive() override;
};
class NightbaneBossIsFlyingTrigger : public Trigger
{
public:
NightbaneBossIsFlyingTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "nightbane boss is flying") {}
bool IsActive() override;
};
class NightbaneNeedToManageTimersAndTrackersTrigger : public Trigger
{
public:
NightbaneNeedToManageTimersAndTrackersTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "nightbane need to manage timers and trackers") {}
bool IsActive() override;
};

View File

@@ -401,14 +401,20 @@ std::unordered_map<ObjectGuid, bool> MagtheridonSpreadRangedAction::hasReachedIn
bool MagtheridonSpreadRangedAction::Execute(Event event)
{
Unit* magtheridon = AI_VALUE2(Unit*, "find target", "magtheridon");
if (!magtheridon)
return false;
Group* group = bot->GetGroup();
if (!group)
return false;
const uint32 instanceId = magtheridon->GetMap()->GetInstanceId();
// Wait for 6 seconds after Magtheridon activates to spread
const uint8 spreadWaitSeconds = 6;
auto it = magtheridonSpreadWaitTimer.find(bot->GetMapId());
if (it == magtheridonSpreadWaitTimer.end() ||
auto it = spreadWaitTimer.find(instanceId);
if (it == spreadWaitTimer.end() ||
(time(nullptr) - it->second) < spreadWaitSeconds)
return false;
@@ -416,8 +422,8 @@ bool MagtheridonSpreadRangedAction::Execute(Event event)
if (cubeIt != botToCubeAssignment.end())
{
time_t now = time(nullptr);
auto timerIt = magtheridonBlastNovaTimer.find(bot->GetMapId());
if (timerIt != magtheridonBlastNovaTimer.end())
auto timerIt = blastNovaTimer.find(instanceId);
if (timerIt != blastNovaTimer.end())
{
time_t lastBlastNova = timerIt->second;
if (now - lastBlastNova >= 49)
@@ -559,8 +565,8 @@ bool MagtheridonUseManticronCubeAction::HandleCubeRelease(Unit* magtheridon, Gam
bool MagtheridonUseManticronCubeAction::ShouldActivateCubeLogic(Unit* magtheridon)
{
auto timerIt = magtheridonBlastNovaTimer.find(bot->GetMapId());
if (timerIt == magtheridonBlastNovaTimer.end())
auto timerIt = blastNovaTimer.find(magtheridon->GetMap()->GetInstanceId());
if (timerIt == blastNovaTimer.end())
return false;
time_t now = time(nullptr);
@@ -650,52 +656,38 @@ bool MagtheridonManageTimersAndAssignmentsAction::Execute(Event event)
if (!magtheridon)
return false;
uint32 mapId = magtheridon->GetMapId();
const uint32 instanceId = magtheridon->GetMap()->GetInstanceId();
const time_t now = time(nullptr);
bool blastNovaActive = magtheridon->HasUnitState(UNIT_STATE_CASTING) &&
magtheridon->FindCurrentSpellBySpellId(SPELL_BLAST_NOVA);
bool lastBlastNova = lastBlastNovaState[mapId];
bool lastBlastNova = lastBlastNovaState[instanceId];
if (lastBlastNova && !blastNovaActive && IsMapIDTimerManager(botAI, bot))
magtheridonBlastNovaTimer[mapId] = time(nullptr);
lastBlastNovaState[mapId] = blastNovaActive;
if (lastBlastNova && !blastNovaActive && IsInstanceTimerManager(botAI, bot))
blastNovaTimer[instanceId] = now;
if (IsMapIDTimerManager(botAI, bot))
lastBlastNovaState[instanceId] = blastNovaActive;
if (!magtheridon->HasAura(SPELL_SHADOW_CAGE))
{
if (!magtheridon->HasAura(SPELL_SHADOW_CAGE))
if (IsInstanceTimerManager(botAI, bot))
{
if (magtheridonSpreadWaitTimer.find(mapId) == magtheridonSpreadWaitTimer.end())
magtheridonSpreadWaitTimer[mapId] = time(nullptr);
if (magtheridonBlastNovaTimer.find(mapId) == magtheridonBlastNovaTimer.end())
magtheridonBlastNovaTimer[mapId] = time(nullptr);
if (magtheridonAggroWaitTimer.find(mapId) == magtheridonAggroWaitTimer.end())
magtheridonAggroWaitTimer[mapId] = time(nullptr);
spreadWaitTimer.try_emplace(instanceId, now);
blastNovaTimer.try_emplace(instanceId, now);
dpsWaitTimer.try_emplace(instanceId, now);
}
}
if (magtheridon->HasAura(SPELL_SHADOW_CAGE))
else
{
if (!MagtheridonSpreadRangedAction::initialPositions.empty())
MagtheridonSpreadRangedAction::initialPositions.clear();
MagtheridonSpreadRangedAction::initialPositions.clear();
MagtheridonSpreadRangedAction::hasReachedInitialPosition.clear();
botToCubeAssignment.clear();
if (!MagtheridonSpreadRangedAction::hasReachedInitialPosition.empty())
MagtheridonSpreadRangedAction::hasReachedInitialPosition.clear();
if (!botToCubeAssignment.empty())
botToCubeAssignment.clear();
if (IsMapIDTimerManager(botAI, bot))
if (IsInstanceTimerManager(botAI, bot))
{
if (magtheridonSpreadWaitTimer.find(mapId) != magtheridonSpreadWaitTimer.end())
magtheridonSpreadWaitTimer.erase(mapId);
if (magtheridonBlastNovaTimer.find(mapId) != magtheridonBlastNovaTimer.end())
magtheridonBlastNovaTimer.erase(mapId);
if (magtheridonAggroWaitTimer.find(mapId) != magtheridonAggroWaitTimer.end())
magtheridonAggroWaitTimer.erase(mapId);
spreadWaitTimer.erase(instanceId);
blastNovaTimer.erase(instanceId);
dpsWaitTimer.erase(instanceId);
}
}

View File

@@ -170,9 +170,9 @@ namespace MagtheridonHelpers
}
std::unordered_map<uint32, bool> lastBlastNovaState;
std::unordered_map<uint32, time_t> magtheridonBlastNovaTimer;
std::unordered_map<uint32, time_t> magtheridonSpreadWaitTimer;
std::unordered_map<uint32, time_t> magtheridonAggroWaitTimer;
std::unordered_map<uint32, time_t> blastNovaTimer;
std::unordered_map<uint32, time_t> spreadWaitTimer;
std::unordered_map<uint32, time_t> dpsWaitTimer;
bool IsSafeFromMagtheridonHazards(PlayerbotAI* botAI, Player* bot, float x, float y, float z)
{
@@ -209,14 +209,14 @@ namespace MagtheridonHelpers
return true;
}
bool IsMapIDTimerManager(PlayerbotAI* botAI, Player* bot)
bool IsInstanceTimerManager(PlayerbotAI* botAI, Player* bot)
{
if (Group* group = bot->GetGroup())
{
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (member && member->IsAlive() && !botAI->IsMainTank(member) && GET_PLAYERBOT_AI(member))
if (member && member->IsAlive() && botAI->IsDps(member) && GET_PLAYERBOT_AI(member))
return member == bot;
}
}

View File

@@ -54,7 +54,7 @@ namespace MagtheridonHelpers
void MarkTargetWithCross(Player* bot, Unit* target);
void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target);
bool IsSafeFromMagtheridonHazards(PlayerbotAI* botAI, Player* bot, float x, float y, float z);
bool IsMapIDTimerManager(PlayerbotAI* botAI, Player* bot);
bool IsInstanceTimerManager(PlayerbotAI* botAI, Player* bot);
struct Location
{
@@ -82,9 +82,9 @@ namespace MagtheridonHelpers
std::vector<CubeInfo> GetAllCubeInfosByDbGuids(Map* map, const std::vector<uint32>& cubeDbGuids);
void AssignBotsToCubesByGuidAndCoords(Group* group, const std::vector<CubeInfo>& cubes, PlayerbotAI* botAI);
extern std::unordered_map<uint32, bool> lastBlastNovaState;
extern std::unordered_map<uint32, time_t> magtheridonBlastNovaTimer;
extern std::unordered_map<uint32, time_t> magtheridonSpreadWaitTimer;
extern std::unordered_map<uint32, time_t> magtheridonAggroWaitTimer;
extern std::unordered_map<uint32, time_t> blastNovaTimer;
extern std::unordered_map<uint32, time_t> spreadWaitTimer;
extern std::unordered_map<uint32, time_t> dpsWaitTimer;
}
#endif

View File

@@ -41,10 +41,10 @@ float MagtheridonWaitToAttackMultiplier::GetValue(Action* action)
if (!magtheridon || magtheridon->HasAura(SPELL_SHADOW_CAGE))
return 1.0f;
const uint8 aggroWaitSeconds = 6;
auto it = magtheridonAggroWaitTimer.find(bot->GetMapId());
if (it == magtheridonAggroWaitTimer.end() ||
(time(nullptr) - it->second) < aggroWaitSeconds)
const uint8 dpsWaitSeconds = 6;
auto it = dpsWaitTimer.find(magtheridon->GetMap()->GetInstanceId());
if (it == dpsWaitTimer.end() ||
(time(nullptr) - it->second) < dpsWaitSeconds)
{
if (!botAI->IsMainTank(bot) && (dynamic_cast<AttackAction*>(action) ||
(!botAI->IsHeal(bot) && dynamic_cast<CastSpellAction*>(action))))

View File

@@ -7,4 +7,14 @@
#include "Playerbots.h"
bool NoRtiTrigger::IsActive() { return !AI_VALUE(Unit*, "rti target"); }
bool NoRtiTrigger::IsActive()
{
// Do not auto-react to raid icons while out of combat.
// Out-of-combat RTI usage (explicit chat commands) is handled by chat triggers,
// not by this generic trigger.
if (!bot->IsInCombat())
return false;
Unit* target = AI_VALUE(Unit*, "rti target");
return target != nullptr;
}

View File

@@ -33,18 +33,14 @@ GuidVector AttackersValue::Calculate()
{
Unit* unit = botAI->GetUnit(target);
if (unit && IsValidTarget(unit, bot))
{
targets.insert(unit);
}
}
if (Group* group = bot->GetGroup())
{
ObjectGuid skullGuid = group->GetTargetIcon(7);
Unit* skullTarget = botAI->GetUnit(skullGuid);
if (skullTarget && IsValidTarget(skullTarget, bot))
{
targets.insert(skullTarget);
}
}
for (Unit* unit : targets)
@@ -61,9 +57,7 @@ GuidVector AttackersValue::Calculate()
{
Unit* unit = botAI->GetUnit(guid);
if (unit && unit->IsPlayer() && IsValidTarget(unit, bot))
{
result.push_back(unit->GetGUID());
}
}
}
@@ -110,9 +104,8 @@ void AttackersValue::AddAttackersOf(Player* player, std::unordered_set<Unit*>& t
if (player->IsValidAttackTarget(attacker) &&
player->GetDistance2d(attacker) < sPlayerbotAIConfig->sightDistance)
{
targets.insert(attacker);
}
ref = ref->next();
}
}
@@ -180,8 +173,9 @@ bool AttackersValue::IsPossibleTarget(Unit* attacker, Player* bot, float /*range
if (!bot->CanSeeOrDetect(attacker))
return false;
// PvP prohibition checks
// PvP prohibition checks (skip for duels)
if ((attacker->GetGUID().IsPlayer() || attacker->GetGUID().IsPet()) &&
(!bot->duel || bot->duel->Opponent != attacker) &&
(sPlayerbotAIConfig->IsPvpProhibited(attacker->GetZoneId(), attacker->GetAreaId()) ||
sPlayerbotAIConfig->IsPvpProhibited(bot->GetZoneId(), bot->GetAreaId())))
{

View File

@@ -21,11 +21,37 @@ bool IsSameLocation(WorldLocation const& a, WorldLocation const& b)
bool Formation::IsNullLocation(WorldLocation const& loc) { return IsSameLocation(loc, Formation::NullLocation); }
bool ValidateTargetContext(Unit* a, Unit* b, Map*& outMap)
{
if (!a || !b || a == b)
return false;
if (!a->IsInWorld() || !b->IsInWorld())
return false;
if (a->IsDuringRemoveFromWorld() || b->IsDuringRemoveFromWorld())
return false;
Map* map = a->GetMap();
if (!map || map != b->GetMap())
return false;
outMap = map;
return true;
}
bool ValidateTargetContext(Unit* a, Unit* b)
{
Map* unused = nullptr;
return ValidateTargetContext(a, b, unused);
}
WorldLocation MoveAheadFormation::GetLocation()
{
Player* master = GetMaster();
if (!master || master == bot)
return WorldLocation();
if (!ValidateTargetContext(master, bot))
return Formation::NullLocation;
WorldLocation loc = GetLocationInternal();
if (Formation::IsNullLocation(loc))
@@ -40,7 +66,7 @@ WorldLocation MoveAheadFormation::GetLocation()
// float ori = master->GetOrientation();
// float x1 = x + sPlayerbotAIConfig->tooCloseDistance * cos(ori);
// float y1 = y + sPlayerbotAIConfig->tooCloseDistance * sin(ori);
// float ground = master->GetMap()->GetHeight(x1, y1, z);
// float ground = map->GetHeight(x1, y1, z);
// if (ground > INVALID_HEIGHT)
// {
// x = x1;
@@ -48,7 +74,7 @@ WorldLocation MoveAheadFormation::GetLocation()
// }
// }
// float ground = master->GetMap()->GetHeight(x, y, z);
// float ground = map->GetHeight(x, y, z);
// if (ground <= INVALID_HEIGHT)
// return Formation::NullLocation;
@@ -81,16 +107,17 @@ public:
WorldLocation GetLocationInternal() override
{
Player* master = GetMaster();
if (!master)
return WorldLocation();
Map* map = nullptr;
if (!ValidateTargetContext(master, bot, map))
return Formation::NullLocation;
float range = sPlayerbotAIConfig->followDistance;
float angle = GetFollowAngle();
float x = master->GetPositionX() + cos(angle) * range;
float y = master->GetPositionY() + sin(angle) * range;
float z = master->GetPositionZ() + master->GetHoverHeight();
if (!master->GetMap()->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(),
master->GetPositionZ(), x, y, z))
if (!map->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(),
master->GetPositionZ(), x, y, z))
{
x = master->GetPositionX() + cos(angle) * range;
y = master->GetPositionY() + sin(angle) * range;
@@ -111,8 +138,9 @@ public:
WorldLocation GetLocationInternal() override
{
Player* master = GetMaster();
if (!master)
return WorldLocation();
Map* map = nullptr;
if (!ValidateTargetContext(master, bot, map))
return Formation::NullLocation;
float range = sPlayerbotAIConfig->followDistance;
float angle = GetFollowAngle();
@@ -120,49 +148,28 @@ public:
time_t now = time(nullptr);
if (!lastChangeTime || now - lastChangeTime >= 3)
{
Player* master = GetMaster();
if (!master)
return WorldLocation();
lastChangeTime = now;
float range = sPlayerbotAIConfig->followDistance;
float angle = GetFollowAngle();
time_t now = time(nullptr);
if (!lastChangeTime || now - lastChangeTime >= 3)
{
lastChangeTime = now;
dx = (urand(0, 10) / 10.0 - 0.5) * sPlayerbotAIConfig->tooCloseDistance;
dy = (urand(0, 10) / 10.0 - 0.5) * sPlayerbotAIConfig->tooCloseDistance;
dr = sqrt(dx * dx + dy * dy);
}
float x = master->GetPositionX() + cos(angle) * range + dx;
float y = master->GetPositionY() + sin(angle) * range + dy;
float z = master->GetPositionZ() + master->GetHoverHeight();
if (!master->GetMap()->CheckCollisionAndGetValidCoords(
master, master->GetPositionX(), master->GetPositionY(), master->GetPositionZ(), x, y, z))
{
x = master->GetPositionX() + cos(angle) * range + dx;
y = master->GetPositionY() + sin(angle) * range + dy;
z = master->GetPositionZ() + master->GetHoverHeight();
master->UpdateAllowedPositionZ(x, y, z);
}
// bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(),
// bot->GetPositionZ(), x, y, z);
return WorldLocation(master->GetMapId(), x, y, z);
dx = (urand(0, 10) / 10.0f - 0.5f) * sPlayerbotAIConfig->tooCloseDistance;
dy = (urand(0, 10) / 10.0f - 0.5f) * sPlayerbotAIConfig->tooCloseDistance;
dr = std::sqrt(dx * dx + dy * dy);
}
float x = master->GetPositionX() + cos(angle) * range + dx;
float y = master->GetPositionY() + sin(angle) * range + dy;
float x = master->GetPositionX() + std::cos(angle) * range + dx;
float y = master->GetPositionY() + std::sin(angle) * range + dy;
float z = master->GetPositionZ() + master->GetHoverHeight();
if (!master->GetMap()->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(),
master->GetPositionZ(), x, y, z))
if (!map->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(),
master->GetPositionZ(), x, y, z))
{
x = master->GetPositionX() + cos(angle) * range + dx;
y = master->GetPositionY() + sin(angle) * range + dy;
// Recompute a clean fallback and clamp Z
x = master->GetPositionX() + std::cos(angle) * range + dx;
y = master->GetPositionY() + std::sin(angle) * range + dy;
z = master->GetPositionZ() + master->GetHoverHeight();
master->UpdateAllowedPositionZ(x, y, z);
}
return WorldLocation(master->GetMapId(), x, y, z);
}
@@ -182,16 +189,18 @@ public:
WorldLocation GetLocation() override
{
float range = 2.0f;
Unit* target = AI_VALUE(Unit*, "current target");
Player* master = GetMaster();
if (!target && target != bot)
// Fix: if no target OR target is the bot, fall back to master
if (!target || target == bot)
target = master;
if (!target)
Map* map = nullptr;
if (!ValidateTargetContext(master, bot, map))
return Formation::NullLocation;
float range = 2.0f;
switch (bot->getClass())
{
case CLASS_HUNTER:
@@ -214,14 +223,16 @@ public:
float x = target->GetPositionX() + cos(angle) * range;
float y = target->GetPositionY() + sin(angle) * range;
float z = target->GetPositionZ();
if (!target->GetMap()->CheckCollisionAndGetValidCoords(target, target->GetPositionX(), target->GetPositionY(),
target->GetPositionZ(), x, y, z))
if (!map->CheckCollisionAndGetValidCoords(target, target->GetPositionX(), target->GetPositionY(),
target->GetPositionZ(), x, y, z))
{
x = target->GetPositionX() + cos(angle) * range;
y = target->GetPositionY() + sin(angle) * range;
z = target->GetPositionZ();
target->UpdateAllowedPositionZ(x, y, z);
}
return WorldLocation(bot->GetMapId(), x, y, z);
}
};
@@ -249,18 +260,18 @@ public:
float orientation = master->GetOrientation();
std::vector<Player*> players;
GroupReference* gref = group->GetFirstMember();
while (gref)
players.reserve(group->GetMembersCount());
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
{
Player* member = gref->GetSource();
if (member != master)
players.push_back(member);
if (!member || member == master)
continue;
gref = gref->next();
players.push_back(member);
}
players.insert(players.begin() + group->GetMembersCount() / 2, master);
players.insert(players.begin() + players.size() / 2, master);
return MoveLine(players, 0.0f, x, y, z, orientation, range);
}
};
@@ -289,19 +300,17 @@ public:
std::vector<Player*> tanks;
std::vector<Player*> dps;
GroupReference* gref = group->GetFirstMember();
while (gref)
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
{
Player* member = gref->GetSource();
if (member != master)
{
if (botAI->IsTank(member))
tanks.push_back(member);
else
dps.push_back(member);
}
if (!member || member == master)
continue;
gref = gref->next();
if (botAI->IsTank(member))
tanks.push_back(member);
else
dps.push_back(member);
}
if (botAI->IsTank(master))
@@ -310,25 +319,21 @@ public:
dps.insert(dps.begin() + (dps.size() + 1) / 2, master);
if (botAI->IsTank(bot) && botAI->IsTank(master))
{
return MoveLine(tanks, 0.0f, x, y, z, orientation, range);
}
if (!botAI->IsTank(bot) && !botAI->IsTank(master))
{
return MoveLine(dps, 0.0f, x, y, z, orientation, range);
}
if (botAI->IsTank(bot) && !botAI->IsTank(master))
{
float diff = tanks.size() % 2 == 0 ? -sPlayerbotAIConfig->tooCloseDistance / 2.0f : 0.0f;
float diff = (tanks.size() % 2 == 0) ? -sPlayerbotAIConfig->tooCloseDistance / 2.0f : 0.0f;
return MoveLine(tanks, diff, x + cos(orientation) * range, y + sin(orientation) * range, z, orientation,
range);
}
if (!botAI->IsTank(bot) && botAI->IsTank(master))
{
float diff = dps.size() % 2 == 0 ? -sPlayerbotAIConfig->tooCloseDistance / 2.0f : 0.0f;
float diff = (dps.size() % 2 == 0) ? -sPlayerbotAIConfig->tooCloseDistance / 2.0f : 0.0f;
return MoveLine(dps, diff, x - cos(orientation) * range, y - sin(orientation) * range, z, orientation,
range);
}
@@ -344,65 +349,69 @@ public:
WorldLocation GetLocation() override
{
Player* master = GetMaster();
Map* map = nullptr;
if (!ValidateTargetContext(master, bot, map))
return Formation::NullLocation;
float range = sPlayerbotAIConfig->farDistance;
float followRange = sPlayerbotAIConfig->followDistance;
Player* master = GetMaster();
if (!master)
return Formation::NullLocation;
if (sServerFacade->GetDistance2d(bot, master) <= range)
return Formation::NullLocation;
float angle = master->GetAngle(bot);
float angleToBot = master->GetAngle(bot);
float followAngle = GetFollowAngle();
float x = master->GetPositionX() + cos(angle) * range + cos(followAngle) * followRange;
float y = master->GetPositionY() + sin(angle) * range + sin(followAngle) * followRange;
float x = master->GetPositionX() + cos(angleToBot) * range + cos(followAngle) * followRange;
float y = master->GetPositionY() + sin(angleToBot) * range + sin(followAngle) * followRange;
float z = master->GetPositionZ();
float ground = master->GetMapHeight(x, y, z + 30.0f);
if (ground <= INVALID_HEIGHT)
{
float minDist = 0, minX = 0, minY = 0;
for (float angle = 0.0f; angle <= 2 * M_PI; angle += M_PI / 16.0f)
float minDist = 0.f;
float minX = 0.f, minY = 0.f;
for (float a = 0.0f; a <= 2 * M_PI; a += M_PI / 16.0f)
{
x = master->GetPositionX() + cos(angle) * range + cos(followAngle) * followRange;
y = master->GetPositionY() + sin(angle) * range + sin(followAngle) * followRange;
float dist = sServerFacade->GetDistance2d(bot, x, y);
float ground = master->GetMapHeight(x, y, z + 30.0f);
if (ground > INVALID_HEIGHT && (!minDist || minDist > dist))
float tx = master->GetPositionX() + cos(a) * range + cos(followAngle) * followRange;
float ty = master->GetPositionY() + sin(a) * range + sin(followAngle) * followRange;
float dist = sServerFacade->GetDistance2d(bot, tx, ty);
float tg = master->GetMapHeight(tx, ty, z + 30.0f);
if (tg > INVALID_HEIGHT && (!minDist || dist < minDist))
{
minDist = dist;
minX = x;
minY = y;
minX = tx;
minY = ty;
}
}
if (minDist)
if (!minDist)
return Formation::NullLocation;
float lz = z;
if (!map->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(),
master->GetPositionZ(), minX, minY, lz))
{
if (!master->GetMap()->CheckCollisionAndGetValidCoords(
master, master->GetPositionX(), master->GetPositionY(), master->GetPositionZ(), x, y, z))
{
x = master->GetPositionX() + cos(angle) * range + cos(followAngle) * followRange;
y = master->GetPositionY() + sin(angle) * range + sin(followAngle) * followRange;
z = master->GetPositionZ() + master->GetHoverHeight();
master->UpdateAllowedPositionZ(x, y, z);
}
return WorldLocation(bot->GetMapId(), minX, minY, z);
lz = z + master->GetHoverHeight();
master->UpdateAllowedPositionZ(minX, minY, lz);
}
return Formation::NullLocation;
return WorldLocation(bot->GetMapId(), minX, minY, lz);
}
if (!master->GetMap()->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(),
master->GetPositionZ(), x, y, z))
if (!map->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(),
master->GetPositionZ(), x, y, z))
{
x = master->GetPositionX() + cos(angle) * range + cos(followAngle) * followRange;
y = master->GetPositionY() + sin(angle) * range + sin(followAngle) * followRange;
x = master->GetPositionX() + cos(angleToBot) * range + cos(followAngle) * followRange;
y = master->GetPositionY() + sin(angleToBot) * range + sin(followAngle) * followRange;
z = master->GetPositionZ() + master->GetHoverHeight();
master->UpdateAllowedPositionZ(x, y, z);
}
return WorldLocation(bot->GetMapId(), x, y, z);
}
};
@@ -653,31 +662,36 @@ WorldLocation MoveFormation::MoveSingleLine(std::vector<Player*> line, float dif
float orientation, float range)
{
float count = line.size();
float angle = orientation - M_PI / 2.0f;
float x = cx + cos(angle) * (range * floor(count / 2.0f) + diff);
float y = cy + sin(angle) * (range * floor(count / 2.0f) + diff);
float angleLeft = orientation - M_PI / 2.0f;
float x0 = cx + std::cos(angleLeft) * (range * std::floor(count / 2.0f) + diff);
float y0 = cy + std::sin(angleLeft) * (range * std::floor(count / 2.0f) + diff);
uint32 index = 0;
for (Player* member : line)
{
if (member == bot)
{
float angle = orientation + M_PI / 2.0f;
float angleRight = orientation + M_PI / 2.0f;
float radius = range * index;
float lx = x + cos(angle) * radius;
float ly = y + sin(angle) * radius;
float lx = x0 + std::cos(angleRight) * radius;
float ly = y0 + std::sin(angleRight) * radius;
float lz = cz;
Player* master = botAI->GetMaster();
if (!master || !master->GetMap()->CheckCollisionAndGetValidCoords(
master, master->GetPositionX(), master->GetPositionY(), master->GetPositionZ(), lx, ly, lz))
Map* map = master ? master->GetMap() : nullptr;
// if not fully in world ignore collision corrections.
if (!master || !map || !bot || map != bot->GetMap() || !master->IsInWorld() ||
master->IsDuringRemoveFromWorld() || !bot->IsInWorld() || bot->IsDuringRemoveFromWorld())
{
lx = x + cos(angle) * radius;
ly = y + sin(angle) * radius;
lz = cz;
return WorldLocation(bot->GetMapId(), lx, ly, lz);
}
// if fully loaded check collision and applies coordinate corrections if needed
map->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(),
master->GetPositionZ(), lx, ly, lz);
return WorldLocation(bot->GetMapId(), lx, ly, lz);
}

View File

@@ -80,7 +80,7 @@ ItemUsage ItemUsageValue::Calculate()
return ITEM_USAGE_USE;
if (proto->Class == ITEM_CLASS_CONSUMABLE &&
(proto->MaxCount == 0 || AI_VALUE2(uint32, "item count", proto->Name1) < proto->MaxCount))
(proto->MaxCount == 0 || bot->GetItemCount(itemId, false) < proto->MaxCount))
{
std::string const foodType = GetConsumableType(proto, bot->GetPower(POWER_MANA));
@@ -520,7 +520,7 @@ bool ItemUsageValue::IsItemUsefulForQuest(Player* player, ItemTemplate const* pr
{
if (quest->RequiredItemId[i] == proto->ItemId)
{
if (AI_VALUE2(uint32, "item count", proto->Name1) >= quest->RequiredItemCount[i])
if (player->GetItemCount(proto->ItemId, false) >= quest->RequiredItemCount[i])
continue;
return true; // Item is directly required for a quest
@@ -549,7 +549,7 @@ bool ItemUsageValue::IsItemUsefulForQuest(Player* player, ItemTemplate const* pr
{
if (quest->RequiredItemId[j] == createdItemId)
{
if (AI_VALUE2(uint32, "item count", createdItemId) >= quest->RequiredItemCount[j])
if (player->GetItemCount(createdItemId, false) >= quest->RequiredItemCount[j])
continue;
return true; // Item is useful because it creates a required quest item

View File

@@ -64,5 +64,15 @@ Unit* RtiTargetValue::Calculate()
sPlayerbotAIConfig->sightDistance))
return nullptr;
// Also prevent chasing raid icon targets that are too far away from the master,
// even if they are technically visible to the bot.
if (Player* master = botAI->GetMaster())
{
if (master->IsInWorld() && master->GetMapId() == unit->GetMapId() &&
sServerFacade->IsDistanceGreaterThan(sServerFacade->GetDistance2d(master, unit),
sPlayerbotAIConfig->sightDistance))
return nullptr;
}
return unit;
}