Compare commits

...

8 Commits

Author SHA1 Message Date
kadeshar
4b7b0958dd
Merge pull request #2205 from mod-playerbots/test-staging
Test staging to master
2026-03-20 20:36:12 +01:00
Keleborn
a473432b8f
Core update (#2221)
<!--
Thank you for contributing to mod-playerbots, please make sure that
you...
1. Submit your PR to the test-staging branch, not master.
2. Read the guidelines below before submitting.
3. Don't delete parts of this template.

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

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

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

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

Update based on @TakenBacon feedback. Thank you for the correction to
the PR.


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

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



## How to Test the Changes
<!--
- Step-by-step instructions to test the change.
- Any required setup (e.g. multiple players, number of bots, specific
configuration).
- Expected behavior and how to verify it.
-->



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



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



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



## Messages to Translate
<!--
Bot messages have to be translatable, but you don't need to do the
translations here. You only need to make sure
the message is in a translatable format, and list in the table the
message_key and the default English message.
Search for GetBotTextOrDefault in the codebase for examples.
-->
Does this change add bot messages to translate?
- [x] No
- [ ] Yes (**list messages in the table**)

| Message key  | Default message |
| --------------- | ------------------ |
|			 |			      |
|			 |			      |

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



## Final Checklist

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

## Notes for Reviewers
<!-- Anything else that's helpful to review or test your pull request.
-->
2026-03-19 22:04:54 +01:00
kadeshar
545b21ec0c
PR template fix (#2216)
Maintenance PR to remove tasklist visible on github
2026-03-17 06:51:50 +01:00
Keleborn
d6f396ab50
CoreUpdate (#2207)
Core PR update.
https://github.com/mod-playerbots/azerothcore-wotlk/pull/178

Core set packet as const, and so had to recast.
2026-03-14 11:50:20 +01:00
kadeshar
a695ac77fa
Small fixes to naxxramas strategy (#2201)
# Pull Request

Small fixes to naxxramas strategy

---

## Complexity & Impact

Does this change add new decision branches?
- - [x] No
- - [ ] Yes (**explain below**)

Does this change increase per-bot or per-tick processing?
- - [x] No
- - [ ] Yes (**describe and justify impact**)

Could this logic scale poorly under load?
- - [x] No
- - [ ] Yes (**explain why**)
---

## Defaults & Configuration

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

If this introduces more advanced or AI-heavy logic:
- - [x] Lightweight mode remains the default
- - [ ] More complex behavior is optional and thereby configurable
---

## AI Assistance

Was AI assistance (e.g. ChatGPT or similar tools) used while working on
this change?
- - [x] No
- - [ ] Yes (**explain below**)

If yes, please specify:

Copilot CLI to code review

---

## Final Checklist

- - [x] Stability is not compromised
- - [x] Performance impact is understood, tested, and acceptable
- - [x] Added logic complexity is justified and explained
- - [x] Documentation updated if needed
2026-03-13 22:22:11 +01:00
Keleborn
5e7613f719
Change reinterpret cast to dynamic cast. (#2182)
# Pull Request

In a few instances the code used reinterpret cast. This is potentially
risky if the object is incorrect. This is a safer approach.

---

## Design Philosophy

We prioritize **stability, performance, and predictability** over
behavioral realism.
Complex player-mimicking logic is intentionally limited due to its
negative impact on scalability, maintainability, and
long-term robustness.

Excessive processing overhead can lead to server hiccups, increased CPU
usage, and degraded performance for all
participants. Because every action and
decision tree is executed **per bot and per trigger**, even small
increases in logic complexity can scale poorly and
negatively affect both players and
world (random) bots. Bots are not expected to behave perfectly, and
perfect simulation of human decision-making is not a
project goal. Increased behavioral
realism often introduces disproportionate cost, reduced predictability,
and significantly higher maintenance overhead.

Every additional branch of logic increases long-term responsibility. All
decision paths must be tested, validated, and
maintained continuously as the system evolves.
If advanced or AI-intensive behavior is introduced, the **default
configuration must remain the lightweight decision
model**. More complex behavior should only be
available as an **explicit opt-in option**, clearly documented as having
a measurable performance cost.

Principles:

- **Stability before intelligence**  
  A stable system is always preferred over a smarter one.

- **Performance is a shared resource**  
  Any increase in bot cost affects all players and all bots.

- **Simple logic scales better than smart logic**  
Predictable behavior under load is more valuable than perfect decisions.

- **Complexity must justify itself**  
  If a feature cannot clearly explain its cost, it should not exist.

- **Defaults must be cheap**  
  Expensive behavior must always be optional and clearly communicated.

- **Bots should look reasonable, not perfect**  
  The goal is believable behavior, not human simulation.

Before submitting, confirm that this change aligns with those
principles.

---

## Feature Evaluation

Please answer the following:

- Describe the **minimum logic** required to achieve the intended
behavior?
- Describe the **cheapest implementation** that produces an acceptable
result?
- Describe the **runtime cost** when this logic executes across many
bots?

---

## How to Test the Changes

- Step-by-step instructions to test the change
- Any required setup (e.g. multiple players, bots, specific
configuration)
- Expected behavior and how to verify it

## Complexity & Impact

Does this change add new decision branches?
- - [x] No
- - [ ] Yes (**explain below**)

Does this change increase per-bot or per-tick processing?
- - [x] No
- - [ ] Yes (**describe and justify impact**)

Could this logic scale poorly under load?
- - [x] No
- - [ ] Yes (**explain why**)
---

## Defaults & Configuration

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

If this introduces more advanced or AI-heavy logic:
- - [x] Lightweight mode remains the default
- - [ ] More complex behavior is optional and thereby configurable
---

## AI Assistance

Was AI assistance (e.g. ChatGPT or similar tools) used while working on
this change?
- - [x] No
- - [ ] Yes (**explain below**)

If yes, please specify:

- AI tool or model used (e.g. ChatGPT, GPT-4, Claude, etc.)
- Purpose of usage (e.g. brainstorming, refactoring, documentation, code
generation)
- Which parts of the change were influenced or generated
- Whether the result was manually reviewed and adapted

AI assistance is allowed, but all submitted code must be fully
understood, reviewed, and owned by the contributor.
Any AI-influenced changes must be verified against existing CORE and PB
logic. We expect contributors to be honest
about what they do and do not understand.

---

## Final Checklist

- - [x] Stability is not compromised
- - [x] Performance impact is understood, tested, and acceptable
- - [x] Added logic complexity is justified and explained
- - [x] Documentation updated if needed

---

## Notes for Reviewers

Anything that significantly improves realism at the cost of stability or
performance should be carefully discussed
before merging.
2026-03-13 22:21:55 +01:00
Crow
dca0be2932
Updates to SSC Strategies (#2138)
# Pull Request

Most of the changes are not functional but are to modify style based on
comments received to the TK PR (e.g., eliminate nesting of if
statements) and leverage general boss helpers. There is some reordering
of returns and other changes to try to consolidate or clean-up the code
(such as removing unnecessary parameters).

The strategies themselves have only minor changes.

- Main tank no longer uses tangential movement for Lurker Spout, unlike
other bots. The MT will just call moves directly to a position behind
Lurker. This is because I found tangential movement was taking too long
for the MT to get in place since it starts right in front of Lurker.
- Vashj MT group Shaman will now use Grounding Totem without actually
switching to the Grounding Totem strategy. I have now eliminated all
strategy swaps, which I dislike because they persist after the
encounter, and it's better not to mess with player strategies since
presumably people are generally using Windfury or Wrath of Air for the
Air Totem.
- I made a ton of changes to Vashj core passing as I noticed the
existing logic is nonfunctional in several ways. It generally works fine
ingame, but the changes should make things much smoother. For example,
the storing of the nearest trigger NPC for generators in the existing
strategy is useless because it relies on insert_or_assign for an
unordered map that will continue to run during the course of the core
passing logic, and a similar issue exists with respect to the map to
store the last time a bot held a core. The result is that there is
slight movement of bots when the core is flying through the air and not
held by any bot because the trigger for core passing does not fire
during that period. In practice, the time is brief enough that the
sequence works OK, but it looks stupid because the bots should not be
moving at all. So that should be fixed.

There is a known issue re: core passing that would take extreme effort
to fix and I am not going to do it because it is a fringe situation.
There are a couple of spots where the Tainted Elemental can rarely spawn
that can result in a straight-line passing sequence to the nearest
generator that is blocked by LoS. Fixing this would be extremely
difficult and niche, so what you will need to do if this happens is to
just command your bots to destroy the core and try again with the next
spawn.

---

## Design Philosophy

We prioritize **stability, performance, and predictability** over
behavioral realism.
Complex player-mimicking logic is intentionally limited due to its
negative impact on scalability, maintainability, and
long-term robustness.

Excessive processing overhead can lead to server hiccups, increased CPU
usage, and degraded performance for all
participants. Because every action and
decision tree is executed **per bot and per trigger**, even small
increases in logic complexity can scale poorly and
negatively affect both players and
world (random) bots. Bots are not expected to behave perfectly, and
perfect simulation of human decision-making is not a
project goal. Increased behavioral
realism often introduces disproportionate cost, reduced predictability,
and significantly higher maintenance overhead.

Every additional branch of logic increases long-term responsibility. All
decision paths must be tested, validated, and
maintained continuously as the system evolves.
If advanced or AI-intensive behavior is introduced, the **default
configuration must remain the lightweight decision
model**. More complex behavior should only be
available as an **explicit opt-in option**, clearly documented as having
a measurable performance cost.

Principles:

- **Stability before intelligence**  
  A stable system is always preferred over a smarter one.

- **Performance is a shared resource**  
  Any increase in bot cost affects all players and all bots.

- **Simple logic scales better than smart logic**  
Predictable behavior under load is more valuable than perfect decisions.

- **Complexity must justify itself**  
  If a feature cannot clearly explain its cost, it should not exist.

- **Defaults must be cheap**  
  Expensive behavior must always be optional and clearly communicated.

- **Bots should look reasonable, not perfect**  
  The goal is believable behavior, not human simulation.

Before submitting, confirm that this change aligns with those
principles.

---

## Feature Evaluation

Please answer the following:

- Describe the **minimum logic** required to achieve the intended
behavior?
- Describe the **cheapest implementation** that produces an acceptable
result?
- Describe the **runtime cost** when this logic executes across many
bots?

I have attempted to streamline methods and even remove some. The
strategy is admittedly somewhat performance heavy due to the need for
function calls such as iterating over inventory items. However, the new
version should be less performance intensive than the merged
strategy--for example, there were places where all members of the raid
would have their inventory checked, but I've now limited the check to
only the 5 core handler bots. I've run the instance with pmon on, and
there are no methods that stand out as particularly resource intensive
when not in a boss encounter, and I view that as the most important
thing (though I make effort to reduce performance impact during
encounters also). Expensive checks that are unavoidable for the strategy
to work such as grid searches are gated behind cheaper checks.

---

## How to Test the Changes

- Step-by-step instructions to test the change
- Any required setup (e.g. multiple players, bots, specific
configuration)
- Expected behavior and how to verify it

Enter SSC with a raid group and run the instance, including all bosses.
Every boss should be killable, and every major mechanic should be
addressed by bots. I will work with Dreathean to get the Wiki up soon so
that should be a reference for testing strategies.

## Complexity & Impact

Does this change add new decision branches?
- - [ ] No
- - [x] Yes (**explain below**)

Only within the context of strategies, which are basically all new
decision branches, and there are some tweaks here to what is currently
merged.

Does this change increase per-bot or per-tick processing?
- - [x] No
- - [ ] Yes (**describe and justify impact**)

Could this logic scale poorly under load?
- - [ ] No
- - [x] Yes (**explain why**)

I'm sure if you have a large server, with multiple raid groups running
the instance at the same time, the performance impact could be
significant. But I have done my best to limit it, and I think some
notable performance impact is unavoidable with the current framework for
raid strategies.

---

## Defaults & Configuration

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

Only for strategies in the instance.

If this introduces more advanced or AI-heavy logic:
- - [ ] Lightweight mode remains the default
- - [x] More complex behavior is optional and thereby configurable

There should be no impact if co +ssc and nc +ssc are not added to bots.
Because raid strategies are currently not removed after leaving an
instance, players should manually remove them (or reset botAI). This is
a general issue that needs to be addressed with the module.

---

## AI Assistance

Was AI assistance (e.g. ChatGPT or similar tools) used while working on
this change?
- - [ ] No
- - [x] Yes (**explain below**)

If yes, please specify:

- AI tool or model used (e.g. ChatGPT, GPT-4, Claude, etc.)
- Purpose of usage (e.g. brainstorming, refactoring, documentation, code
generation)
- Which parts of the change were influenced or generated
- Whether the result was manually reviewed and adapted

AI assistance is allowed, but all submitted code must be fully
understood, reviewed, and owned by the contributor.
Any AI-influenced changes must be verified against existing CORE and PB
logic. We expect contributors to be honest
about what they do and do not understand.

GPT-4 because I don't like to use up my premium requests in CoPilot and
I generally like it better than GPT-5 =P

I use LLMs to draft code snippets but do review everything and have
become less-and-less reliant over time. I don't use agent mode, only
ask. For this PR, I had it do the updated version of
AnyRecentCoreInInventory(), which is more complicated than before and
uses indexing for each bot to consider status of only prior bots in the
passing chain. Everything else either I wrote or could have written but
had the AI help and just edited afterward to save time.

---

## Final Checklist

- - [x] Stability is not compromised
- - [x] Performance impact is understood, tested, and acceptable
- - [x] Added logic complexity is justified and explained
- - [x] Documentation updated if needed

---

## Notes for Reviewers

Anything that significantly improves realism at the cost of stability or
performance should be carefully discussed
before merging.

---------

Co-authored-by: Keleborn <22352763+Celandriel@users.noreply.github.com>
Co-authored-by: bash <hermensb@gmail.com>
Co-authored-by: Revision <tkn963@gmail.com>
Co-authored-by: kadeshar <kadeshar@gmail.com>
2026-03-13 22:21:37 +01:00
Alex Dcnh
ca19548cc5
Fix transport boarding when master is on a transport (Zep/Boats) (#1830)
Summary
This PR improves Follow related behaviour when the master is on a
transport (zeppelin/boat). It makes follow actions safer and less
disruptive by:

Detecting when the master is on a transport and handling boarding
correctly
Avoiding teleport-under-floor issues by using a small positional offset
when teleporting the bot near the master
Preventing movement conflicts between MoveSpline/MotionMaster and the
transport driver by forcing a MotionMaster cleanup and MoveIdle after
boarding
Clearing movement flags (forward / walking) after boarding so the bot
does not remain in a walking/march state
Next-check delay after boarding to allow the server to update
transport/position state

Before this change, bots get stuck when attempting to board

Fight the server-side transport movement because local
MoveSpline/MotionMaster was still active
Repeatedly attempt movement on every follow tick while already a
passenger, causing jitter and CPU/noise This PR reduces stuck/jitter
cases, avoids conflicting movement commands, and makes boarding more
robust.

**Key changes**
Check master->GetTransport() and handle three main cases:
If bot already passenger of same transport: stabilize (StopMoving,
Clear(true), MoveIdle, StopMovingOnCurrentPos) and set a longer
next-check delay; return false (no new movement in theory).
If bot passenger of another transport: do nothing (avoid conflicting
behaviour).
If bot not a passenger of master transport: teleport bot near master
(with offsets) and call Transport::AddPassenger(bot, true),
then force:

bot->StopMoving()
bot->GetMotionMaster()->Clear(true)
bot->GetMotionMaster()->MoveIdle()

Remove movement flags MOVEMENTFLAG_FORWARD and MOVEMENTFLAG_WALKING
SetNextCheckDelay to random 1000–2500 ms
Log boarding with bot name, transport GUID and coordinates
Preserve earlier follow logic when master is not on a transport

Tests performed
Manual tests on a local server:

Master on boat/zeppelin -> bot teleports to a safe offset position and
becomes a passenger without getting stuck
Bot already passenger on same transport -> bot no longer issues movement
commands and stabilizes
Bot on a different transport -> no boarding attempt for master's
transport (no interference)
Movement flags cleared after boarding; bot stops local movement and does
not fight server transport movement

Now the bots follow their masters in the zeppelins and boats, although
sometimes they move around a bit inside when the zeppelin starts (they
must have smoked something bad).

---------

Co-authored-by: Keleborn <22352763+Celandriel@users.noreply.github.com>
Co-authored-by: bash <hermensb@gmail.com>
Co-authored-by: bashermens <31279994+hermensbas@users.noreply.github.com>
2026-03-13 22:21:17 +01:00
32 changed files with 1544 additions and 1799 deletions

View File

@ -43,21 +43,21 @@ any impact on performance, you may skip these question. If necessary, a maintain
## Impact Assessment
<!-- As a generic test, before and after measure of pmon (playerbot pmon tick) can help you here. -->
- Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots?
- [ ] No, not at all
- [ ] Minimal impact (**explain below**)
- [ ] Moderate impact (**explain below**)
- - [ ] No, not at all
- - [ ] Minimal impact (**explain below**)
- - [ ] Moderate impact (**explain below**)
- Does this change modify default bot behavior?
- [ ] No
- [ ] Yes (**explain why**)
- - [ ] No
- - [ ] Yes (**explain why**)
- Does this change add new decision branches or increase maintenance complexity?
- [ ] No
- [ ] Yes (**explain below**)
- - [ ] No
- - [ ] Yes (**explain below**)
@ -68,8 +68,8 @@ the message is in a translatable format, and list in the table the message_key a
Search for GetBotTextOrDefault in the codebase for examples.
-->
Does this change add bot messages to translate?
- [ ] No
- [ ] Yes (**list messages in the table**)
- - [ ] No
- - [ ] Yes (**list messages in the table**)
| Message key | Default message |
| --------------- | ------------------ |
@ -82,8 +82,8 @@ AI assistance is allowed, but all submitted code must be fully understood, revie
We expect contributors to be honest about what they do and do not understand.
-->
Was AI assistance used while working on this change?
- [ ] No
- [ ] Yes (**explain below**)
- - [ ] No
- - [ ] Yes (**explain below**)
<!--
If yes, please specify:
- Purpose of usage (e.g. brainstorming, refactoring, documentation, code generation).
@ -94,10 +94,10 @@ If yes, please specify:
## Final Checklist
- [ ] Stability is not compromised.
- [ ] Performance impact is understood, tested, and acceptable.
- [ ] Added logic complexity is justified and explained.
- [ ] Documentation updated if needed (Conf comments, WiKi commands).
- - [ ] Stability is not compromised.
- - [ ] Performance impact is understood, tested, and acceptable.
- - [ ] Added logic complexity is justified and explained.
- - [ ] Documentation updated if needed (Conf comments, WiKi commands).
## Notes for Reviewers
<!-- Anything else that's helpful to review or test your pull request. -->

View File

@ -5,18 +5,211 @@
#include "FollowActions.h"
#include <algorithm>
#include <cmath>
#include <array>
#include "Event.h"
#include "Formations.h"
#include "LastMovementValue.h"
#include "MotionMaster.h"
#include "PlayerbotAI.h"
#include "Playerbots.h"
#include "ServerFacade.h"
#include "Transport.h"
#include "Map.h"
namespace
{
Transport* GetTransportForPosTolerant(Map* map, WorldObject* ref, uint32 phaseMask, float x, float y, float z)
{
if (!map || !ref)
return nullptr;
std::array<float, 4> const probes = { z, z + 0.5f, z + 1.5f, z - 0.5f };
for (float const pz : probes)
{
if (Transport* t = map->GetTransportForPos(phaseMask, x, y, pz, ref))
return t;
}
return nullptr;
}
// Attempts to find a point on the leader's transport that is closer to the bot,
// by probing along the segment from master -> bot and returning the last point
// that is still detected as being on the expected transport.
bool FindBoardingPointOnTransport(Map* map, Transport* expectedTransport, WorldObject* ref,
float masterX, float masterY, float masterZ,
float botX, float botY, float botZ,
float& outX, float& outY, float& outZ)
{
if (!map || !expectedTransport || !ref)
return false;
uint32 const phaseMask = ref->GetPhaseMask();
// Ensure master is actually detected on that transport (tolerant).
if (GetTransportForPosTolerant(map, ref, phaseMask, masterX, masterY, masterZ) != expectedTransport)
return false;
// The raycast in GetTransportForPos starts at (z + 2). Probe with a safe Z.
float const probeZ = std::max(masterZ, botZ);
// Adaptive step count: small platforms need tighter sampling.
float const dx2 = botX - masterX;
float const dy2 = botY - masterY;
float const dist2d = std::sqrt(dx2 * dx2 + dy2 * dy2);
int32 const steps = std::clamp(static_cast<int32>(dist2d / 0.75f), 10, 28);
float const dx = (botX - masterX) / static_cast<float>(steps);
float const dy = (botY - masterY) / static_cast<float>(steps);
// Master must actually be on the expected transport for this to work.
if (map->GetTransportForPos(ref->GetPhaseMask(), masterX, masterY, probeZ, ref) != expectedTransport)
return false;
float lastX = masterX;
float lastY = masterY;
bool found = false;
for (int32 i = 1; i <= steps; ++i)
{
float const px = masterX + dx * i;
float const py = masterY + dy * i;
Transport* const t = GetTransportForPosTolerant(map, ref, phaseMask, px, py, probeZ);
if (t != expectedTransport)
break;
lastX = px;
lastY = py;
found = true;
}
if (!found)
return false;
outX = lastX;
outY = lastY;
outZ = masterZ; // keep deck-level Z to encourage stepping onto the platform/boat
return true;
}
}
bool FollowAction::Execute(Event /*event*/)
{
Formation* formation = AI_VALUE(Formation*, "formation");
std::string const target = formation->GetTargetName();
// Transport handling for moving transports only (boats/zeppelins).
Player* master = botAI->GetMaster();
if (master && master->IsInWorld() && bot->IsInWorld() && bot->GetMapId() == master->GetMapId())
{
Map* map = master->GetMap();
uint32 const mapId = bot->GetMapId();
Transport* transport = nullptr;
bool masterOnTransport = false;
if (master->GetTransport())
{
transport = master->GetTransport();
masterOnTransport = true;
}
else if (map)
{
transport = GetTransportForPosTolerant(map, master, master->GetPhaseMask(),
master->GetPositionX(), master->GetPositionY(), master->GetPositionZ());
masterOnTransport = (transport != nullptr);
}
// Ignore static transports (elevators/trams): only keep boats/zeppelins here.
if (transport && transport->IsStaticTransport())
transport = nullptr;
if (transport && map && bot->GetTransport() != transport)
{
float const botProbeZ = std::max(bot->GetPositionZ(), transport->GetPositionZ());
Transport* botSurfaceTransport = GetTransportForPosTolerant(map, bot, bot->GetPhaseMask(),
bot->GetPositionX(), bot->GetPositionY(), botProbeZ);
if (botSurfaceTransport == transport)
{
transport->AddPassenger(bot, true);
bot->StopMovingOnCurrentPos();
return true;
}
float const boardingAssistDistance = 60.0f;
float const dist2d = ServerFacade::instance().GetDistance2d(bot, master);
bool const inAssist = ServerFacade::instance().IsDistanceLessOrEqualThan(dist2d, boardingAssistDistance);
if (inAssist)
{
float destX = masterOnTransport ? master->GetPositionX() : transport->GetPositionX();
float destY = masterOnTransport ? master->GetPositionY() : transport->GetPositionY();
float destZ = masterOnTransport ? master->GetPositionZ() : transport->GetPositionZ();
float edgeX = 0.0f;
float edgeY = 0.0f;
float edgeZ = 0.0f;
if (masterOnTransport &&
FindBoardingPointOnTransport(map, transport, master,
master->GetPositionX(), master->GetPositionY(), master->GetPositionZ(),
bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(),
edgeX, edgeY, edgeZ))
{
destX = edgeX;
destY = edgeY;
destZ = edgeZ;
}
MovementPriority const priority = botAI->GetState() == BOT_STATE_COMBAT
? MovementPriority::MOVEMENT_COMBAT
: MovementPriority::MOVEMENT_NORMAL;
bool const movingAllowed = IsMovingAllowed(mapId, destX, destY, destZ);
bool const dupMove = IsDuplicateMove(mapId, destX, destY, destZ);
bool const waiting = IsWaitingForLastMove(priority);
if (movingAllowed && !dupMove && !waiting)
{
if (bot->IsSitState())
bot->SetStandState(UNIT_STAND_STATE_STAND);
if (bot->IsNonMeleeSpellCast(true))
{
bot->CastStop();
botAI->InterruptSpell();
}
if (MotionMaster* mm = bot->GetMotionMaster())
{
mm->MovePoint(
/*id*/ 0,
/*coords*/ destX, destY, destZ,
/*forcedMovement*/ FORCED_MOVEMENT_NONE,
/*speed*/ 0.0f,
/*orientation*/ 0.0f,
/*generatePath*/ false,
/*forceDestination*/ false);
}
else
return false;
float delay = 1000.0f * MoveDelay(bot->GetExactDist(destX, destY, destZ));
delay = std::clamp(delay, 0.0f, static_cast<float>(sPlayerbotAIConfig.maxWaitForMove));
AI_VALUE(LastMovement&, "last movement")
.Set(mapId, destX, destY, destZ, bot->GetOrientation(), delay, priority);
ClearIdleState();
return true;
}
}
}
}
// end unified transport handling
bool moved = false;
if (!target.empty())
{

View File

@ -37,10 +37,10 @@ public:
uint32 GetCurrWaypoint() override;
};
class GrobblulusMoveCenterAction : public MoveInsideAction
class GrobbulusMoveCenterAction : public MoveInsideAction
{
public:
GrobblulusMoveCenterAction(PlayerbotAI* ai) : MoveInsideAction(ai, 3281.23f, -3310.38f, 5.0f) {}
GrobbulusMoveCenterAction(PlayerbotAI* ai) : MoveInsideAction(ai, 3281.23f, -3310.38f, 5.0f) {}
};
class GrobbulusMoveAwayAction : public MovementAction
@ -173,26 +173,26 @@ private:
RazuviousBossHelper helper;
};
class HorsemanAttractAlternativelyAction : public AttackAction
class FourHorsemenAttractAlternativelyAction : public AttackAction
{
public:
HorsemanAttractAlternativelyAction(PlayerbotAI* ai) : AttackAction(ai, "horseman attract alternatively"), helper(ai)
FourHorsemenAttractAlternativelyAction(PlayerbotAI* ai) : AttackAction(ai, "four horsemen attract alternatively"), helper(ai)
{
}
bool Execute(Event event) override;
protected:
FourhorsemanBossHelper helper;
FourHorsemenBossHelper helper;
};
class HorsemanAttactInOrderAction : public AttackAction
class FourHorsemenAttackInOrderAction : public AttackAction
{
public:
HorsemanAttactInOrderAction(PlayerbotAI* ai) : AttackAction(ai, "horseman attact in order"), helper(ai) {}
FourHorsemenAttackInOrderAction(PlayerbotAI* ai) : AttackAction(ai, "four horsemen attack in order"), helper(ai) {}
bool Execute(Event event) override;
protected:
FourhorsemanBossHelper helper;
FourHorsemenBossHelper helper;
};
// class SapphironGroundMainTankPositionAction : public MovementAction

View File

@ -1,7 +1,6 @@
#include "RaidNaxxActions.h"
#include "ObjectGuid.h"
#include "Playerbots.h"
#include "RaidNaxxActions.h"
bool AnubrekhanChooseTargetAction::Execute(Event /*event*/)
{
@ -66,13 +65,10 @@ bool AnubrekhanPositionAction::Execute(Event /*event*/)
{
uint32 nearest = FindNearestWaypoint();
uint32 next_point;
if (inPhase)
next_point = (nearest + 1) % intervals;
else
next_point = nearest;
next_point = (nearest + 1) % intervals;
return MoveTo(bot->GetMapId(), waypoints[next_point].first, waypoints[next_point].second, bot->GetPositionZ(), false, false,
false, false, MovementPriority::MOVEMENT_COMBAT);
return MoveTo(bot->GetMapId(), waypoints[next_point].first, waypoints[next_point].second,
bot->GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
else
return MoveInside(533, 3272.49f, -3476.27f, bot->GetPositionZ(), 3.0f, MovementPriority::MOVEMENT_COMBAT);

View File

@ -2,7 +2,7 @@
#include "Playerbots.h"
bool HorsemanAttractAlternativelyAction::Execute(Event /*event*/)
bool FourHorsemenAttractAlternativelyAction::Execute(Event /*event*/)
{
if (!helper.UpdateBossAI())
return false;
@ -13,13 +13,13 @@ bool HorsemanAttractAlternativelyAction::Execute(Event /*event*/)
return true;
Unit* attackTarget = helper.CurrentAttackTarget();
if (context->GetValue<Unit*>("current target")->Get() != attackTarget)
if (attackTarget && context->GetValue<Unit*>("current target")->Get() != attackTarget)
return Attack(attackTarget);
return false;
}
bool HorsemanAttactInOrderAction::Execute(Event /*event*/)
bool FourHorsemenAttackInOrderAction::Execute(Event /*event*/)
{
if (!helper.UpdateBossAI())
return false;

View File

@ -70,7 +70,6 @@ bool SapphironFlightPositionAction::MoveToNearestIcebolt()
if (!group)
return false;
Group::MemberSlotList const& slots = group->GetMemberSlots();
Player* playerWithIcebolt = nullptr;
float minDistance;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())

View File

@ -13,7 +13,7 @@ bool ThaddiusAttackNearestPetAction::isUseful()
return false;
Unit* target = helper.GetNearestPet();
if (!bot->IsWithinDistInMap(target, 50.0f))
if (!target || !bot->IsWithinDistInMap(target, 50.0f))
return false;
return true;
@ -22,7 +22,7 @@ bool ThaddiusAttackNearestPetAction::isUseful()
bool ThaddiusAttackNearestPetAction::Execute(Event /*event*/)
{
Unit* target = helper.GetNearestPet();
if (!bot->IsWithinLOSInMap(target))
if (!target || !bot->IsWithinLOSInMap(target))
return MoveTo(target, 0, MovementPriority::MOVEMENT_COMBAT);
if (AI_VALUE(Unit*, "current target") != target)

View File

@ -245,7 +245,7 @@ float AnubrekhanGenericMultiplier::GetValue(Action* action)
return 1.0f;
}
float FourhorsemanGenericMultiplier::GetValue(Action* action)
float FourHorsemenGenericMultiplier::GetValue(Action* action)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "sir zeliek");
if (!boss)

View File

@ -1,6 +1,6 @@
#ifndef _PLAYERRBOT_RAIDNAXXMULTIPLIERS_H
#define _PLAYERRBOT_RAIDNAXXMULTIPLIERS_H
#ifndef _PLAYERBOT_RAIDNAXXMULTIPLIERS_H
#define _PLAYERBOT_RAIDNAXXMULTIPLIERS_H
#include "Multiplier.h"
#include "RaidNaxxBossHelper.h"
@ -84,10 +84,10 @@ public:
virtual float GetValue(Action* action);
};
class FourhorsemanGenericMultiplier : public Multiplier
class FourHorsemenGenericMultiplier : public Multiplier
{
public:
FourhorsemanGenericMultiplier(PlayerbotAI* ai) : Multiplier(ai, "fourhorseman generic") {}
FourHorsemenGenericMultiplier(PlayerbotAI* ai) : Multiplier(ai, "four horsemen generic") {}
public:
virtual float GetValue(Action* action);

View File

@ -31,8 +31,8 @@ public:
creators["razuvious use obedience crystal"] = &RaidNaxxActionContext::razuvious_use_obedience_crystal;
creators["razuvious target"] = &RaidNaxxActionContext::razuvious_target;
creators["horseman attract alternatively"] = &RaidNaxxActionContext::horseman_attract_alternatively;
creators["horseman attack in order"] = &RaidNaxxActionContext::horseman_attack_in_order;
creators["four horsemen attract alternatively"] = &RaidNaxxActionContext::four_horsemen_attract_alternatively;
creators["four horsemen attack in order"] = &RaidNaxxActionContext::four_horsemen_attack_in_order;
creators["sapphiron ground position"] = &RaidNaxxActionContext::sapphiron_ground_position;
creators["sapphiron flight position"] = &RaidNaxxActionContext::sapphiron_flight_position;
@ -56,7 +56,7 @@ public:
private:
static Action* go_behind_the_boss(PlayerbotAI* ai) { return new GrobbulusGoBehindAction(ai); }
static Action* rotate_grobbulus(PlayerbotAI* ai) { return new GrobbulusRotateAction(ai); }
static Action* grobbulus_move_center(PlayerbotAI* ai) { return new GrobblulusMoveCenterAction(ai); }
static Action* grobbulus_move_center(PlayerbotAI* ai) { return new GrobbulusMoveCenterAction(ai); }
static Action* grobbulus_move_away(PlayerbotAI* ai) { return new GrobbulusMoveAwayAction(ai); }
//static Action* heigan_dance_melee(PlayerbotAI* ai) { return new HeiganDanceMeleeAction(ai); }
//static Action* heigan_dance_ranged(PlayerbotAI* ai) { return new HeiganDanceRangedAction(ai); }
@ -70,11 +70,8 @@ private:
{
return new RazuviousUseObedienceCrystalAction(ai);
}
static Action* horseman_attract_alternatively(PlayerbotAI* ai)
{
return new HorsemanAttractAlternativelyAction(ai);
}
static Action* horseman_attack_in_order(PlayerbotAI* ai) { return new HorsemanAttactInOrderAction(ai); }
static Action* four_horsemen_attract_alternatively(PlayerbotAI* ai) { return new FourHorsemenAttractAlternativelyAction(ai); }
static Action* four_horsemen_attack_in_order(PlayerbotAI* ai) { return new FourHorsemenAttackInOrderAction(ai); }
// static Action* sapphiron_ground_main_tank_position(PlayerbotAI* ai) { return new
// SapphironGroundMainTankPositionAction(ai); }
static Action* sapphiron_ground_position(PlayerbotAI* ai) { return new SapphironGroundPositionAction(ai); }

View File

@ -30,8 +30,8 @@ public:
creators["razuvious tank"] = &RaidNaxxTriggerContext::razuvious_tank;
creators["razuvious nontank"] = &RaidNaxxTriggerContext::razuvious_nontank;
creators["horseman attractors"] = &RaidNaxxTriggerContext::horseman_attractors;
creators["horseman except attractors"] = &RaidNaxxTriggerContext::horseman_except_attractors;
creators["four horsemen attractors"] = &RaidNaxxTriggerContext::four_horsemen_attractors;
creators["four horsemen except attractors"] = &RaidNaxxTriggerContext::four_horsemen_except_attractors;
creators["sapphiron ground"] = &RaidNaxxTriggerContext::sapphiron_ground;
creators["sapphiron flight"] = &RaidNaxxTriggerContext::sapphiron_flight;
@ -66,8 +66,8 @@ private:
static Trigger* razuvious_tank(PlayerbotAI* ai) { return new RazuviousTankTrigger(ai); }
static Trigger* razuvious_nontank(PlayerbotAI* ai) { return new RazuviousNontankTrigger(ai); }
static Trigger* horseman_attractors(PlayerbotAI* ai) { return new HorsemanAttractorsTrigger(ai); }
static Trigger* horseman_except_attractors(PlayerbotAI* ai) { return new HorsemanExceptAttractorsTrigger(ai); }
static Trigger* four_horsemen_attractors(PlayerbotAI* ai) { return new FourHorsemenAttractorsTrigger(ai); }
static Trigger* four_horsemen_except_attractors(PlayerbotAI* ai) { return new FourHorsemenExceptAttractorsTrigger(ai); }
static Trigger* sapphiron_ground(PlayerbotAI* ai) { return new SapphironGroundTrigger(ai); }
static Trigger* sapphiron_flight(PlayerbotAI* ai) { return new SapphironFlightTrigger(ai); }

View File

@ -97,13 +97,13 @@ void RaidNaxxStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{ NextAction("razuvious target", ACTION_RAID + 1) }
));
// four horseman
triggers.push_back(new TriggerNode("horseman attractors",
{ NextAction("horseman attract alternatively", ACTION_RAID + 1) }
// four horsemen
triggers.push_back(new TriggerNode("four horsemen attractors",
{ NextAction("four horsemen attract alternatively", ACTION_RAID + 1) }
));
triggers.push_back(new TriggerNode("horseman except attractors",
{ NextAction("horseman attack in order", ACTION_RAID + 1) }
triggers.push_back(new TriggerNode("four horsemen except attractors",
{ NextAction("four horsemen attack in order", ACTION_RAID + 1) }
));
// sapphiron
@ -150,7 +150,7 @@ void RaidNaxxStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers)
multipliers.push_back(new InstructorRazuviousGenericMultiplier(botAI));
multipliers.push_back(new KelthuzadGenericMultiplier(botAI));
multipliers.push_back(new AnubrekhanGenericMultiplier(botAI));
multipliers.push_back(new FourhorsemanGenericMultiplier(botAI));
multipliers.push_back(new FourHorsemenGenericMultiplier(botAI));
// multipliers.push_back(new GothikGenericMultiplier(botAI));
multipliers.push_back(new GluthGenericMultiplier(botAI));
}

View File

@ -114,7 +114,7 @@ bool RazuviousNontankTrigger::IsActive()
return helper.UpdateBossAI() && !(bot->getClass() == CLASS_PRIEST);
}
bool HorsemanAttractorsTrigger::IsActive()
bool FourHorsemenAttractorsTrigger::IsActive()
{
if (!helper.UpdateBossAI())
return false;
@ -122,7 +122,7 @@ bool HorsemanAttractorsTrigger::IsActive()
return helper.IsAttracter(bot);
}
bool HorsemanExceptAttractorsTrigger::IsActive()
bool FourHorsemenExceptAttractorsTrigger::IsActive()
{
if (!helper.UpdateBossAI())
return false;

View File

@ -186,24 +186,24 @@ private:
ThaddiusBossHelper helper;
};
class HorsemanAttractorsTrigger : public Trigger
class FourHorsemenAttractorsTrigger : public Trigger
{
public:
HorsemanAttractorsTrigger(PlayerbotAI* ai) : Trigger(ai, "fourhorsemen attractors"), helper(ai) {}
FourHorsemenAttractorsTrigger(PlayerbotAI* ai) : Trigger(ai, "four horsemen attractors"), helper(ai) {}
bool IsActive() override;
private:
FourhorsemanBossHelper helper;
FourHorsemenBossHelper helper;
};
class HorsemanExceptAttractorsTrigger : public Trigger
class FourHorsemenExceptAttractorsTrigger : public Trigger
{
public:
HorsemanExceptAttractorsTrigger(PlayerbotAI* ai) : Trigger(ai, "fourhorsemen except attractors"), helper(ai) {}
FourHorsemenExceptAttractorsTrigger(PlayerbotAI* ai) : Trigger(ai, "four horsemen except attractors"), helper(ai) {}
bool IsActive() override;
private:
FourhorsemanBossHelper helper;
FourHorsemenBossHelper helper;
};
class SapphironGroundTrigger : public Trigger

View File

@ -202,7 +202,7 @@ public:
}
bool FindPosToAvoidChill(std::vector<float>& dest)
{
Aura* aura = NaxxSpellIds::GetAnyAura(bot, {NaxxSpellIds::Chill25});
Aura* aura = NaxxSpellIds::GetAnyAura(bot, {NaxxSpellIds::Chill10, NaxxSpellIds::Chill25});
if (!aura)
{
// Fallback to name for custom spell data.
@ -363,13 +363,13 @@ private:
Unit* _unit = nullptr;
};
class FourhorsemanBossHelper : public AiObject
class FourHorsemenBossHelper : public AiObject
{
public:
const float posZ = 241.27f;
const std::pair<float, float> attractPos[2] = {{2502.03f, -2910.90f},
{2484.61f, -2947.07f}}; // left (sir zeliek), right (lady blaumeux)
FourhorsemanBossHelper(PlayerbotAI* botAI) : AiObject(botAI) {}
FourHorsemenBossHelper(PlayerbotAI* botAI) : AiObject(botAI) {}
bool UpdateBossAI()
{
if (!bot->IsInCombat())
@ -497,7 +497,8 @@ public:
if (feugen && feugen->IsAlive())
unit = feugen;
if (stalagg && stalagg->IsAlive() && (!feugen || bot->GetDistance(stalagg) < bot->GetDistance(feugen)))
if (stalagg && stalagg->IsAlive() &&
(!feugen || !feugen->IsAlive() || bot->GetDistance(stalagg) < bot->GetDistance(feugen)))
unit = stalagg;
return unit;

View File

@ -58,6 +58,7 @@ namespace NaxxSpellIds
// Sapphiron
static constexpr uint32 Icebolt10 = 28522;
static constexpr uint32 Icebolt25 = 28526;
static constexpr uint32 Chill10 = 28547;
static constexpr uint32 Chill25 = 55699;
/*
// Fight

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,8 @@
/*
* 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_RAIDSSCACTIONS_H
#define _PLAYERBOT_RAIDSSCACTIONS_H
@ -75,8 +80,8 @@ public:
bool Execute(Event event) override;
private:
bool TryMisdirectToFrostTank(Unit* hydross, Group* group);
bool TryMisdirectToNatureTank(Unit* hydross, Group* group);
bool TryMisdirectToFrostTank(Unit* hydross);
bool TryMisdirectToNatureTank(Unit* hydross);
};
class HydrossTheUnstableStopDpsUponPhaseChangeAction : public Action
@ -413,10 +418,10 @@ private:
bool LineUpSecondCorePasser(Player* firstCorePasser, Unit* closestTrigger);
bool LineUpThirdCorePasser(Player* designatedLooter, Player* firstCorePasser, Player* secondCorePasser, Unit* closestTrigger);
bool LineUpFourthCorePasser(Player* firstCorePasser, Player* secondCorePasser, Player* thirdCorePasser, Unit* closestTrigger);
bool IsFirstCorePasserInIntendedPosition(Player* designatedLooter, Player* firstCorePasser, Unit* closestTrigger);
bool IsSecondCorePasserInIntendedPosition(Player* firstCorePasser, Player* secondCorePasser, Unit* closestTrigger);
bool IsThirdCorePasserInIntendedPosition(Player* secondCorePasser, Player* thirdCorePasser, Unit* closestTrigger);
bool IsFourthCorePasserInIntendedPosition(Player* thirdCorePasser, Player* fourthCorePasser, Unit* closestTrigger);
bool IsFirstCorePasserInPosition(Player* designatedLooter, Player* firstCorePasser, Unit* closestTrigger);
bool IsSecondCorePasserInPosition(Player* firstCorePasser, Player* secondCorePasser, Unit* closestTrigger);
bool IsThirdCorePasserInPosition(Player* secondCorePasser, Player* thirdCorePasser, Unit* closestTrigger);
bool IsFourthCorePasserInPosition(Player* thirdCorePasser, Player* fourthCorePasser, Unit* closestTrigger);
void ScheduleTransferCoreAfterImbue(PlayerbotAI* botAI, Player* giver, Player* receiver);
bool UseCoreOnNearestGenerator(const uint32 instanceId);
};
@ -428,19 +433,12 @@ public:
bool Execute(Event event) override;
};
class LadyVashjEraseCorePassingTrackersAction : public Action
{
public:
LadyVashjEraseCorePassingTrackersAction(PlayerbotAI* botAI, std::string const name = "lady vashj erase core passing trackers") : Action(botAI, name) {}
bool Execute(Event event) override;
};
class LadyVashjAvoidToxicSporesAction : public MovementAction
{
public:
LadyVashjAvoidToxicSporesAction(PlayerbotAI* botAI, std::string const name = "lady vashj avoid toxic spores") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
static std::vector<Unit*> GetAllSporeDropTriggers(PlayerbotAI* botAI, Player* bot);
static std::vector<Unit*> GetAllSporeDropTriggers(Player* bot);
private:
Position FindSafestNearbyPosition(const std::vector<Unit*>& spores, const Position& position, float maxRadius, float hazardRadius);

View File

@ -1,3 +1,8 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "RaidSSCMultipliers.h"
#include "RaidSSCActions.h"
#include "RaidSSCHelpers.h"
@ -28,12 +33,10 @@ using namespace SerpentShrineCavernHelpers;
float UnderbogColossusEscapeToxicPoolMultiplier::GetValue(Action* action)
{
if (bot->HasAura(SPELL_TOXIC_POOL))
{
if (dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<UnderbogColossusEscapeToxicPoolAction*>(action))
return 0.0f;
}
if (bot->HasAura(SPELL_TOXIC_POOL) &&
dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<UnderbogColossusEscapeToxicPoolAction*>(action))
return 0.0f;
return 1.0f;
}
@ -53,16 +56,16 @@ float HydrossTheUnstableDisableTankActionsMultiplier::GetValue(Action* action)
dynamic_cast<CombatFormationMoveAction*>(action))
return 0.0f;
if ((botAI->IsMainTank(bot) && !hydross->HasAura(SPELL_CORRUPTION)) ||
(botAI->IsAssistTankOfIndex(bot, 0, true) && hydross->HasAura(SPELL_CORRUPTION)))
return 1.0f;
if (dynamic_cast<CastReachTargetSpellAction*>(action) ||
dynamic_cast<ReachTargetAction*>(action) ||
(dynamic_cast<AttackAction*>(action) &&
!dynamic_cast<HydrossTheUnstablePositionFrostTankAction*>(action) &&
!dynamic_cast<HydrossTheUnstablePositionNatureTankAction*>(action)))
{
if ((botAI->IsMainTank(bot) && hydross->HasAura(SPELL_CORRUPTION)) ||
(botAI->IsAssistTankOfIndex(bot, 0, true) && !hydross->HasAura(SPELL_CORRUPTION)))
return 0.0f;
}
return 0.0f;
return 1.0f;
}
@ -97,13 +100,13 @@ float HydrossTheUnstableWaitForDpsMultiplier::GetValue(Action* action)
bool aboutToChange = (itPhase != hydrossChangeToFrostPhaseTimer.end() &&
(now - itPhase->second) > phaseChangeWaitSeconds);
if (justChanged || aboutToChange)
{
if (dynamic_cast<AttackAction*>(action) ||
(dynamic_cast<CastSpellAction*>(action) &&
!dynamic_cast<CastHealingSpellAction*>(action)))
return 0.0f;
}
if (!justChanged && !aboutToChange)
return 1.0f;
if (dynamic_cast<AttackAction*>(action) ||
(dynamic_cast<CastSpellAction*>(action) &&
!dynamic_cast<CastHealingSpellAction*>(action)))
return 0.0f;
}
if (hydross->HasAura(SPELL_CORRUPTION) && !botAI->IsAssistTankOfIndex(bot, 0, true))
@ -116,13 +119,13 @@ float HydrossTheUnstableWaitForDpsMultiplier::GetValue(Action* action)
bool aboutToChange = (itPhase != hydrossChangeToNaturePhaseTimer.end() &&
(now - itPhase->second) > phaseChangeWaitSeconds);
if (justChanged || aboutToChange)
{
if (dynamic_cast<AttackAction*>(action) ||
(dynamic_cast<CastSpellAction*>(action) &&
!dynamic_cast<CastHealingSpellAction*>(action)))
return 0.0f;
}
if (!justChanged && !aboutToChange)
return 1.0f;
if (dynamic_cast<AttackAction*>(action) ||
(dynamic_cast<CastSpellAction*>(action) &&
!dynamic_cast<CastHealingSpellAction*>(action)))
return 0.0f;
}
return 1.0f;
@ -133,11 +136,9 @@ float HydrossTheUnstableControlMisdirectionMultiplier::GetValue(Action* action)
if (bot->getClass() != CLASS_HUNTER)
return 1.0f;
if (AI_VALUE2(Unit*, "find target", "hydross the unstable"))
{
if (dynamic_cast<CastMisdirectionOnMainTankAction*>(action))
return 0.0f;
}
if (AI_VALUE2(Unit*, "find target", "hydross the unstable") &&
dynamic_cast<CastMisdirectionOnMainTankAction*>(action))
return 0.0f;
return 1.0f;
}
@ -175,14 +176,14 @@ float TheLurkerBelowMaintainRangedSpreadMultiplier::GetValue(Action* action)
if (!botAI->IsRanged(bot))
return 1.0f;
if (AI_VALUE2(Unit*, "find target", "the lurker below"))
{
if (dynamic_cast<CombatFormationMoveAction*>(action) ||
dynamic_cast<FleeAction*>(action) ||
dynamic_cast<CastDisengageAction*>(action) ||
dynamic_cast<CastBlinkBackAction*>(action))
return 0.0f;
}
if (!AI_VALUE2(Unit*, "find target", "the lurker below"))
return 1.0f;
if (dynamic_cast<CombatFormationMoveAction*>(action) ||
dynamic_cast<FleeAction*>(action) ||
dynamic_cast<CastDisengageAction*>(action) ||
dynamic_cast<CastBlinkBackAction*>(action))
return 0.0f;
return 1.0f;
}
@ -215,11 +216,11 @@ float TheLurkerBelowDisableTankAssistMultiplier::GetValue(Action* action)
++tankCount;
}
if (tankCount >= 3)
{
if (dynamic_cast<TankAssistAction*>(action))
return 0.0f;
}
if (tankCount < 3)
return 1.0f;
if (dynamic_cast<TankAssistAction*>(action))
return 0.0f;
return 1.0f;
}
@ -234,22 +235,18 @@ float LeotherasTheBlindAvoidWhirlwindMultiplier::GetValue(Action* action)
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
return 1.0f;
Unit* leotherasHuman = GetLeotherasHuman(botAI);
if (!leotherasHuman)
Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind");
if (!leotheras || (!leotheras->HasAura(SPELL_WHIRLWIND) &&
!leotheras->HasAura(SPELL_WHIRLWIND_CHANNEL)))
return 1.0f;
if (!leotherasHuman->HasAura(SPELL_LEOTHERAS_BANISHED) &&
(leotherasHuman->HasAura(SPELL_WHIRLWIND) ||
leotherasHuman->HasAura(SPELL_WHIRLWIND_CHANNEL)))
{
if (dynamic_cast<CastReachTargetSpellAction*>(action))
return 0.0f;
if (dynamic_cast<CastReachTargetSpellAction*>(action))
return 0.0f;
if (dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<AttackAction*>(action) &&
!dynamic_cast<LeotherasTheBlindRunAwayFromWhirlwindAction*>(action))
return 0.0f;
}
if (dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<AttackAction*>(action) &&
!dynamic_cast<LeotherasTheBlindRunAwayFromWhirlwindAction*>(action))
return 0.0f;
return 1.0f;
}
@ -262,10 +259,10 @@ float LeotherasTheBlindDisableTankActionsMultiplier::GetValue(Action* action)
if (!AI_VALUE2(Unit*, "find target", "leotheras the blind"))
return 1.0f;
if (GetPhase2LeotherasDemon(botAI) && dynamic_cast<AttackAction*>(action))
if (GetPhase2LeotherasDemon(bot) && dynamic_cast<AttackAction*>(action))
return 0.0f;
if (!GetPhase3LeotherasDemon(botAI) && dynamic_cast<CastBerserkAction*>(action))
if (!GetPhase3LeotherasDemon(bot) && dynamic_cast<CastBerserkAction*>(action))
return 0.0f;
return 1.0f;
@ -273,21 +270,21 @@ float LeotherasTheBlindDisableTankActionsMultiplier::GetValue(Action* action)
float LeotherasTheBlindFocusOnInnerDemonMultiplier::GetValue(Action* action)
{
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
{
if (dynamic_cast<TankAssistAction*>(action) ||
dynamic_cast<DpsAssistAction*>(action) ||
dynamic_cast<CastHealingSpellAction*>(action) ||
dynamic_cast<CastCureSpellAction*>(action) ||
dynamic_cast<CurePartyMemberAction*>(action) ||
dynamic_cast<CastBuffSpellAction*>(action) ||
dynamic_cast<ResurrectPartyMemberAction*>(action) ||
dynamic_cast<PartyMemberActionNameSupport*>(action) ||
dynamic_cast<CastBearFormAction*>(action) ||
dynamic_cast<CastDireBearFormAction*>(action) ||
dynamic_cast<CastTreeFormAction*>(action))
return 0.0f;
}
if (!bot->HasAura(SPELL_INSIDIOUS_WHISPER))
return 1.0f;
if (dynamic_cast<TankAssistAction*>(action) ||
dynamic_cast<DpsAssistAction*>(action) ||
dynamic_cast<CastHealingSpellAction*>(action) ||
dynamic_cast<CastCureSpellAction*>(action) ||
dynamic_cast<CurePartyMemberAction*>(action) ||
dynamic_cast<CastBuffSpellAction*>(action) ||
dynamic_cast<ResurrectPartyMemberAction*>(action) ||
dynamic_cast<PartyMemberActionNameSupport*>(action) ||
dynamic_cast<CastBearFormAction*>(action) ||
dynamic_cast<CastDireBearFormAction*>(action) ||
dynamic_cast<CastTreeFormAction*>(action))
return 0.0f;
return 1.0f;
}
@ -297,19 +294,19 @@ float LeotherasTheBlindMeleeDpsAvoidChaosBlastMultiplier::GetValue(Action* actio
if (botAI->IsRanged(bot) || botAI->IsTank(bot))
return 1.0f;
if (!GetPhase2LeotherasDemon(botAI))
if (!GetPhase2LeotherasDemon(bot))
return 1.0f;
Aura* chaosBlast = bot->GetAura(SPELL_CHAOS_BLAST);
if (chaosBlast && chaosBlast->GetStackAmount() >= 5)
{
if (dynamic_cast<AttackAction*>(action) ||
dynamic_cast<ReachTargetAction*>(action) ||
dynamic_cast<CombatFormationMoveAction*>(action) ||
dynamic_cast<CastReachTargetSpellAction*>(action) ||
dynamic_cast<CastKillingSpreeAction*>(action))
return 0.0f;
}
if (!chaosBlast || chaosBlast->GetStackAmount() < 5)
return 1.0f;
if (dynamic_cast<AttackAction*>(action) ||
dynamic_cast<ReachTargetAction*>(action) ||
dynamic_cast<CombatFormationMoveAction*>(action) ||
dynamic_cast<CastReachTargetSpellAction*>(action) ||
dynamic_cast<CastKillingSpreeAction*>(action))
return 0.0f;
return 1.0f;
}
@ -330,8 +327,8 @@ float LeotherasTheBlindWaitForDpsMultiplier::GetValue(Action* action)
const time_t now = std::time(nullptr);
constexpr uint8 dpsWaitSecondsPhase1 = 5;
Unit* leotherasHuman = GetLeotherasHuman(botAI);
Unit* leotherasPhase3Demon = GetPhase3LeotherasDemon(botAI);
Unit* leotherasHuman = GetLeotherasHuman(bot);
Unit* leotherasPhase3Demon = GetPhase3LeotherasDemon(bot);
if (leotherasHuman && !leotherasHuman->HasAura(SPELL_LEOTHERAS_BANISHED) &&
!leotherasPhase3Demon)
{
@ -345,12 +342,12 @@ float LeotherasTheBlindWaitForDpsMultiplier::GetValue(Action* action)
if (dynamic_cast<AttackAction*>(action) ||
(dynamic_cast<CastSpellAction*>(action) &&
!dynamic_cast<CastHealingSpellAction*>(action)))
return 0.0f;
return 0.0f;
}
}
constexpr uint8 dpsWaitSecondsPhase2 = 12;
Unit* leotherasPhase2Demon = GetPhase2LeotherasDemon(botAI);
Unit* leotherasPhase2Demon = GetPhase2LeotherasDemon(bot);
Player* demonFormTank = GetLeotherasDemonFormTank(bot);
if (leotherasPhase2Demon)
{
@ -367,7 +364,7 @@ float LeotherasTheBlindWaitForDpsMultiplier::GetValue(Action* action)
if (dynamic_cast<AttackAction*>(action) ||
(dynamic_cast<CastSpellAction*>(action) &&
!dynamic_cast<CastHealingSpellAction*>(action)))
return 0.0f;
return 0.0f;
}
}
@ -384,7 +381,7 @@ float LeotherasTheBlindWaitForDpsMultiplier::GetValue(Action* action)
if (dynamic_cast<AttackAction*>(action) ||
(dynamic_cast<CastSpellAction*>(action) &&
!dynamic_cast<CastHealingSpellAction*>(action)))
return 0.0f;
return 0.0f;
}
}
@ -398,12 +395,12 @@ float LeotherasTheBlindDelayBloodlustAndHeroismMultiplier::GetValue(Action* acti
return 1.0f;
Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind");
if (leotheras && leotheras->HasAura(SPELL_LEOTHERAS_BANISHED))
{
if (dynamic_cast<CastHeroismAction*>(action) ||
dynamic_cast<CastBloodlustAction*>(action))
return 0.0f;
}
if (!leotheras || !leotheras->HasAura(SPELL_LEOTHERAS_BANISHED))
return 1.0f;
if (dynamic_cast<CastHeroismAction*>(action) ||
dynamic_cast<CastBloodlustAction*>(action))
return 0.0f;
return 1.0f;
}
@ -447,14 +444,12 @@ float FathomLordKarathressDisableAoeMultiplier::GetValue(Action* action)
if (!botAI->IsDps(bot))
return 1.0f;
if (AI_VALUE2(Unit*, "find target", "fathom-lord karathress"))
{
if (auto castSpellAction = dynamic_cast<CastSpellAction*>(action))
{
if (castSpellAction->getThreatType() == Action::ActionThreatType::Aoe)
return 0.0f;
}
}
if (!AI_VALUE2(Unit*, "find target", "fathom-lord karathress"))
return 1.0f;
auto castSpellAction = dynamic_cast<CastSpellAction*>(action);
if (castSpellAction && castSpellAction->getThreatType() == Action::ActionThreatType::Aoe)
return 0.0f;
return 1.0f;
}
@ -464,11 +459,11 @@ float FathomLordKarathressControlMisdirectionMultiplier::GetValue(Action* action
if (bot->getClass() != CLASS_HUNTER)
return 1.0f;
if (AI_VALUE2(Unit*, "find target", "fathom-lord karathress"))
{
if (dynamic_cast<CastMisdirectionOnMainTankAction*>(action))
return 0.0f;
}
if (!AI_VALUE2(Unit*, "find target", "fathom-lord karathress"))
return 1.0f;
if (dynamic_cast<CastMisdirectionOnMainTankAction*>(action))
return 0.0f;
return 1.0f;
}
@ -494,7 +489,7 @@ float FathomLordKarathressWaitForDpsMultiplier::GetValue(Action* action)
if (dynamic_cast<AttackAction*>(action) ||
(dynamic_cast<CastSpellAction*>(action) &&
!dynamic_cast<CastHealingSpellAction*>(action)))
return 0.0f;
return 0.0f;
}
return 1.0f;
@ -505,12 +500,12 @@ float FathomLordKarathressCaribdisTankHealerMaintainPositionMultiplier::GetValue
if (!botAI->IsAssistHealOfIndex(bot, 0, true))
return 1.0f;
if (AI_VALUE2(Unit*, "find target", "fathom-guard caribdis"))
{
if (dynamic_cast<FleeAction*>(action) ||
dynamic_cast<FollowAction*>(action))
return 0.0f;
}
if (!AI_VALUE2(Unit*, "find target", "fathom-guard caribdis"))
return 1.0f;
if (dynamic_cast<FleeAction*>(action) ||
dynamic_cast<FollowAction*>(action))
return 0.0f;
return 1.0f;
}
@ -526,12 +521,12 @@ float MorogrimTidewalkerDelayBloodlustAndHeroismMultiplier::GetValue(Action* act
if (!AI_VALUE2(Unit*, "find target", "morogrim tidewalker"))
return 1.0f;
if (!AI_VALUE2(Unit*, "find target", "tidewalker lurker"))
{
if (dynamic_cast<CastHeroismAction*>(action) ||
dynamic_cast<CastBloodlustAction*>(action))
return 0.0f;
}
if (AI_VALUE2(Unit*, "find target", "tidewalker lurker"))
return 1.0f;
if (dynamic_cast<CastHeroismAction*>(action) ||
dynamic_cast<CastBloodlustAction*>(action))
return 0.0f;
return 1.0f;
}
@ -541,11 +536,11 @@ float MorogrimTidewalkerDisableTankActionsMultiplier::GetValue(Action* action)
if (!botAI->IsMainTank(bot))
return 1.0f;
if (AI_VALUE2(Unit*, "find target", "morogrim tidewalker"))
{
if (dynamic_cast<CombatFormationMoveAction*>(action))
return 0.0f;
}
if (!AI_VALUE2(Unit*, "find target", "morogrim tidewalker"))
return 1.0f;
if (dynamic_cast<CombatFormationMoveAction*>(action))
return 0.0f;
return 1.0f;
}
@ -556,17 +551,14 @@ float MorogrimTidewalkerMaintainPhase2StackingMultiplier::GetValue(Action* actio
return 1.0f;
Unit* tidewalker = AI_VALUE2(Unit*, "find target", "morogrim tidewalker");
if (!tidewalker)
if (!tidewalker || tidewalker->GetHealthPct() > 25.0f)
return 1.0f;
if (tidewalker->GetHealthPct() < 25.0f)
{
if (dynamic_cast<CombatFormationMoveAction*>(action) ||
dynamic_cast<FleeAction*>(action) ||
dynamic_cast<CastDisengageAction*>(action) ||
dynamic_cast<CastBlinkBackAction*>(action))
return 0.0f;
}
if (dynamic_cast<CombatFormationMoveAction*>(action) ||
dynamic_cast<FleeAction*>(action) ||
dynamic_cast<CastDisengageAction*>(action) ||
dynamic_cast<CastBlinkBackAction*>(action))
return 0.0f;
return 1.0f;
}
@ -580,41 +572,59 @@ float LadyVashjDelayCooldownsMultiplier::GetValue(Action* action)
if (!AI_VALUE2(Unit*, "find target", "lady vashj"))
return 1.0f;
if (bot->getClass() == CLASS_SHAMAN)
{
if (IsLadyVashjInPhase3(botAI))
return 1.0f;
if (bot->getClass() == CLASS_SHAMAN &&
!IsLadyVashjInPhase3(botAI) &&
(dynamic_cast<CastBloodlustAction*>(action) ||
dynamic_cast<CastHeroismAction*>(action)))
return 0.0f;
if (dynamic_cast<CastBloodlustAction*>(action) ||
dynamic_cast<CastHeroismAction*>(action))
return 0.0f;
}
if (!botAI->IsDps(bot) || !IsLadyVashjInPhase1(botAI))
return 1.0f;
if (botAI->IsDps(bot) && IsLadyVashjInPhase1(botAI))
{
if (dynamic_cast<CastMetamorphosisAction*>(action) ||
dynamic_cast<CastAdrenalineRushAction*>(action) ||
dynamic_cast<CastBladeFlurryAction*>(action) ||
dynamic_cast<CastIcyVeinsAction*>(action) ||
dynamic_cast<CastColdSnapAction*>(action) ||
dynamic_cast<CastArcanePowerAction*>(action) ||
dynamic_cast<CastPresenceOfMindAction*>(action) ||
dynamic_cast<CastCombustionAction*>(action) ||
dynamic_cast<CastRapidFireAction*>(action) ||
dynamic_cast<CastReadinessAction*>(action) ||
dynamic_cast<CastAvengingWrathAction*>(action) ||
dynamic_cast<CastElementalMasteryAction*>(action) ||
dynamic_cast<CastFeralSpiritAction*>(action) ||
dynamic_cast<CastFireElementalTotemAction*>(action) ||
dynamic_cast<CastFireElementalTotemMeleeAction*>(action) ||
dynamic_cast<CastForceOfNatureAction*>(action) ||
dynamic_cast<CastArmyOfTheDeadAction*>(action) ||
dynamic_cast<CastSummonGargoyleAction*>(action) ||
dynamic_cast<CastBerserkingAction*>(action) ||
dynamic_cast<CastBloodFuryAction*>(action) ||
dynamic_cast<UseTrinketAction*>(action))
return 0.0f;
}
if (dynamic_cast<CastMetamorphosisAction*>(action) ||
dynamic_cast<CastAdrenalineRushAction*>(action) ||
dynamic_cast<CastBladeFlurryAction*>(action) ||
dynamic_cast<CastIcyVeinsAction*>(action) ||
dynamic_cast<CastColdSnapAction*>(action) ||
dynamic_cast<CastArcanePowerAction*>(action) ||
dynamic_cast<CastPresenceOfMindAction*>(action) ||
dynamic_cast<CastCombustionAction*>(action) ||
dynamic_cast<CastRapidFireAction*>(action) ||
dynamic_cast<CastReadinessAction*>(action) ||
dynamic_cast<CastAvengingWrathAction*>(action) ||
dynamic_cast<CastElementalMasteryAction*>(action) ||
dynamic_cast<CastFeralSpiritAction*>(action) ||
dynamic_cast<CastFireElementalTotemAction*>(action) ||
dynamic_cast<CastFireElementalTotemMeleeAction*>(action) ||
dynamic_cast<CastForceOfNatureAction*>(action) ||
dynamic_cast<CastArmyOfTheDeadAction*>(action) ||
dynamic_cast<CastSummonGargoyleAction*>(action) ||
dynamic_cast<CastBerserkingAction*>(action) ||
dynamic_cast<CastBloodFuryAction*>(action) ||
dynamic_cast<UseTrinketAction*>(action))
return 0.0f;
return 1.0f;
}
float LadyVashjMainTankGroupShamanUseGroundingTotemMultiplier::GetValue(Action* action)
{
if (bot->getClass() != CLASS_SHAMAN)
return 1.0f;
if (!AI_VALUE2(Unit*, "find target", "lady vashj"))
return 1.0f;
if (!IsMainTankInSameSubgroup(botAI, bot))
return 1.0f;
if (dynamic_cast<CastWindfuryTotemAction*>(action) ||
dynamic_cast<SetWindfuryTotemAction*>(action) ||
dynamic_cast<CastWrathOfAirTotemAction*>(action) ||
dynamic_cast<SetWrathOfAirTotemAction*>(action) ||
dynamic_cast<CastNatureResistanceTotemAction*>(action) ||
dynamic_cast<SetNatureResistanceTotemAction*>(action))
return 0.0f;
return 1.0f;
}
@ -624,15 +634,15 @@ float LadyVashjMaintainPhase1RangedSpreadMultiplier::GetValue(Action* action)
if (!botAI->IsRanged(bot))
return 1.0f;
if (AI_VALUE2(Unit*, "find target", "lady vashj") &&
IsLadyVashjInPhase1(botAI))
{
if (dynamic_cast<CombatFormationMoveAction*>(action) ||
dynamic_cast<FleeAction*>(action) ||
dynamic_cast<CastDisengageAction*>(action) ||
dynamic_cast<CastBlinkBackAction*>(action))
return 0.0f;
}
if (!AI_VALUE2(Unit*, "find target", "lady vashj") ||
!IsLadyVashjInPhase1(botAI))
return 1.0f;
if (dynamic_cast<CombatFormationMoveAction*>(action) ||
dynamic_cast<FleeAction*>(action) ||
dynamic_cast<CastDisengageAction*>(action) ||
dynamic_cast<CastBlinkBackAction*>(action))
return 0.0f;
return 1.0f;
}
@ -658,19 +668,18 @@ float LadyVashjStaticChargeStayAwayFromGroupMultiplier::GetValue(Action* action)
// Bots should not loot the core with normal looting logic
float LadyVashjDoNotLootTheTaintedCoreMultiplier::GetValue(Action* action)
{
if (AI_VALUE2(Unit*, "find target", "lady vashj"))
{
if (dynamic_cast<LootAction*>(action))
return 0.0f;
}
if (!AI_VALUE2(Unit*, "find target", "lady vashj"))
return 1.0f;
if (dynamic_cast<LootAction*>(action))
return 0.0f;
return 1.0f;
}
float LadyVashjCorePassersPrioritizePositioningMultiplier::GetValue(Action* action)
{
if (!AI_VALUE2(Unit*, "find target", "lady vashj") ||
!IsLadyVashjInPhase2(botAI))
if (!AI_VALUE2(Unit*, "find target", "lady vashj") || !IsLadyVashjInPhase2(botAI))
return 1.0f;
if (dynamic_cast<WipeAction*>(action) ||
@ -678,65 +687,43 @@ float LadyVashjCorePassersPrioritizePositioningMultiplier::GetValue(Action* acti
dynamic_cast<LadyVashjDestroyTaintedCoreAction*>(action))
return 1.0f;
Group* group = bot->GetGroup();
if (!group)
return 1.0f;
auto coreHandlers = GetCoreHandlers(botAI, bot);
Player* designatedLooter = GetDesignatedCoreLooter(group, botAI);
Player* firstCorePasser = GetFirstTaintedCorePasser(group, botAI);
Player* secondCorePasser = GetSecondTaintedCorePasser(group, botAI);
Player* thirdCorePasser = GetThirdTaintedCorePasser(group, botAI);
Player* fourthCorePasser = GetFourthTaintedCorePasser(group, botAI);
bool isCoreHandler = false;
int myIndex = -1;
for (int i = 0; i < static_cast<int>(coreHandlers.size()); ++i)
{
if (coreHandlers[i] && coreHandlers[i] == bot)
{
isCoreHandler = true;
myIndex = i;
}
}
if (!isCoreHandler)
return 1.0f;
auto hasCore = [](Player* player)
{
return player && player->HasItemCount(ITEM_TAINTED_CORE, 1, false);
};
if (hasCore(bot))
{
if (!dynamic_cast<LadyVashjPassTheTaintedCoreAction*>(action))
return 0.0f;
}
// If the bot actually has the core, only allow core handling
if (hasCore(bot) && !dynamic_cast<LadyVashjPassTheTaintedCoreAction*>(action))
return 0.0f;
if (bot == designatedLooter)
{
if (!hasCore(bot))
return 1.0f;
}
else if (bot == firstCorePasser)
{
if (hasCore(secondCorePasser) || hasCore(thirdCorePasser) ||
hasCore(fourthCorePasser))
return 1.0f;
}
else if (bot == secondCorePasser)
{
if (hasCore(thirdCorePasser) || hasCore(fourthCorePasser))
return 1.0f;
}
else if (bot == thirdCorePasser)
{
if (hasCore(fourthCorePasser))
return 1.0f;
}
else if (bot != fourthCorePasser)
return 1.0f;
// First and second passers block movement when the looter teleports to the elemental
Unit* tainted = AI_VALUE2(Unit*, "find target", "tainted elemental");
if (tainted && coreHandlers[0]->GetExactDist2d(tainted) < 5.0f &&
(bot == coreHandlers[1] || bot == coreHandlers[2]) &&
(dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<LadyVashjPassTheTaintedCoreAction*>(action)))
return 0.0f;
if (AI_VALUE2(Unit*, "find target", "tainted elemental") &&
(bot == firstCorePasser || bot == secondCorePasser))
{
if (dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<LadyVashjPassTheTaintedCoreAction*>(action))
return 0.0f;
}
if (AnyRecentCoreInInventory(group, botAI))
{
if (dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<LadyVashjPassTheTaintedCoreAction*>(action))
return 0.0f;
}
// If any prior handler (including self) recently had the core, block other movement
if (AnyRecentCoreInInventory(botAI, bot) &&
dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<LadyVashjPassTheTaintedCoreAction*>(action))
return 0.0f;
return 1.0f;
}
@ -745,7 +732,8 @@ float LadyVashjCorePassersPrioritizePositioningMultiplier::GetValue(Action* acti
// So the standard target selection system must be disabled
float LadyVashjDisableAutomaticTargetingAndMovementModifier::GetValue(Action *action)
{
if (!AI_VALUE2(Unit*, "find target", "lady vashj"))
Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj");
if (!vashj)
return 1.0f;
if (dynamic_cast<AvoidAoeAction*>(action))
@ -755,24 +743,26 @@ float LadyVashjDisableAutomaticTargetingAndMovementModifier::GetValue(Action *ac
{
if (dynamic_cast<DpsAssistAction*>(action) ||
dynamic_cast<TankAssistAction*>(action) ||
dynamic_cast<FollowAction*>(action) ||
dynamic_cast<FleeAction*>(action))
return 0.0f;
if (bot->GetExactDist2d(vashj) < 60.0f &&
dynamic_cast<FollowAction*>(action))
return 0.0f;
if (!botAI->IsHeal(bot) && dynamic_cast<CastHealingSpellAction*>(action))
return 0.0f;
Unit* enchanted = AI_VALUE2(Unit*, "find target", "enchanted elemental");
if (enchanted && bot->GetVictim() == enchanted)
{
if (dynamic_cast<CastDebuffSpellOnAttackerAction*>(action))
return 0.0f;
}
if (enchanted && bot->GetVictim() == enchanted &&
dynamic_cast<CastDebuffSpellOnAttackerAction*>(action))
return 0.0f;
}
if (IsLadyVashjInPhase3(botAI))
{
if (dynamic_cast<DpsAssistAction*>(action))
if (dynamic_cast<DpsAssistAction*>(action) ||
dynamic_cast<TankAssistAction*>(action))
return 0.0f;
Unit* enchanted = AI_VALUE2(Unit*, "find target", "enchanted elemental");
@ -780,16 +770,13 @@ float LadyVashjDisableAutomaticTargetingAndMovementModifier::GetValue(Action *ac
Unit* elite = AI_VALUE2(Unit*, "find target", "coilfang elite");
if (enchanted || strider || elite)
{
if (dynamic_cast<TankAssistAction*>(action) ||
dynamic_cast<FollowAction*>(action) ||
if (dynamic_cast<FollowAction*>(action) ||
dynamic_cast<FleeAction*>(action))
return 0.0f;
if (enchanted && bot->GetVictim() == enchanted)
{
if (dynamic_cast<CastDebuffSpellOnAttackerAction*>(action))
return 0.0f;
}
if (enchanted && bot->GetVictim() == enchanted &&
dynamic_cast<CastDebuffSpellOnAttackerAction*>(action))
return 0.0f;
}
else if (dynamic_cast<CombatFormationMoveAction*>(action))
return 0.0f;

View File

@ -1,3 +1,8 @@
/*
* 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_RAIDSSCMULTIPLIERS_H
#define _PLAYERBOT_RAIDSSCMULTIPLIERS_H
@ -193,6 +198,14 @@ public:
virtual float GetValue(Action* action);
};
class LadyVashjMainTankGroupShamanUseGroundingTotemMultiplier : public Multiplier
{
public:
LadyVashjMainTankGroupShamanUseGroundingTotemMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "lady vashj main tank group shaman use grounding totem") {}
virtual float GetValue(Action* action);
};
class LadyVashjMaintainPhase1RangedSpreadMultiplier : public Multiplier
{
public:

View File

@ -1,3 +1,8 @@
/*
* 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_RAIDSSCACTIONCONTEXT_H
#define _PLAYERBOT_RAIDSSCACTIONCONTEXT_H
@ -161,9 +166,6 @@ public:
creators["lady vashj destroy tainted core"] =
&RaidSSCActionContext::lady_vashj_destroy_tainted_core;
creators["lady vashj erase core passing trackers"] =
&RaidSSCActionContext::lady_vashj_erase_core_passing_trackers;
creators["lady vashj avoid toxic spores"] =
&RaidSSCActionContext::lady_vashj_avoid_toxic_spores;
@ -324,9 +326,6 @@ private:
static Action* lady_vashj_destroy_tainted_core(
PlayerbotAI* botAI) { return new LadyVashjDestroyTaintedCoreAction(botAI); }
static Action* lady_vashj_erase_core_passing_trackers(
PlayerbotAI* botAI) { return new LadyVashjEraseCorePassingTrackersAction(botAI); }
static Action* lady_vashj_avoid_toxic_spores(
PlayerbotAI* botAI) { return new LadyVashjAvoidToxicSporesAction(botAI); }

View File

@ -1,3 +1,8 @@
/*
* 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_RAIDSSCTRIGGERCONTEXT_H
#define _PLAYERBOT_RAIDSSCTRIGGERCONTEXT_H
@ -155,9 +160,6 @@ public:
creators["lady vashj tainted core is unusable"] =
&RaidSSCTriggerContext::lady_vashj_tainted_core_is_unusable;
creators["lady vashj need to reset core passing trackers"] =
&RaidSSCTriggerContext::lady_vashj_need_to_reset_core_passing_trackers;
creators["lady vashj toxic sporebats are spewing poison clouds"] =
&RaidSSCTriggerContext::lady_vashj_toxic_sporebats_are_spewing_poison_clouds;
@ -312,9 +314,6 @@ private:
static Trigger* lady_vashj_tainted_core_is_unusable(
PlayerbotAI* botAI) { return new LadyVashjTaintedCoreIsUnusableTrigger(botAI); }
static Trigger* lady_vashj_need_to_reset_core_passing_trackers(
PlayerbotAI* botAI) { return new LadyVashjNeedToResetCorePassingTrackersTrigger(botAI); }
static Trigger* lady_vashj_toxic_sporebats_are_spewing_poison_clouds(
PlayerbotAI* botAI) { return new LadyVashjToxicSporebatsAreSpewingPoisonCloudsTrigger(botAI); }

View File

@ -1,3 +1,8 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "RaidSSCStrategy.h"
#include "RaidSSCMultipliers.h"
@ -144,9 +149,6 @@ void RaidSSCStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
triggers.push_back(new TriggerNode("lady vashj tainted core is unusable", {
NextAction("lady vashj destroy tainted core", ACTION_EMERGENCY + 1) }));
triggers.push_back(new TriggerNode("lady vashj need to reset core passing trackers", {
NextAction("lady vashj erase core passing trackers", ACTION_EMERGENCY + 10) }));
triggers.push_back(new TriggerNode("lady vashj adds spawn in phase 2 and phase 3", {
NextAction("lady vashj assign phase 2 and phase 3 dps priority", ACTION_RAID + 1) }));
@ -198,6 +200,7 @@ void RaidSSCStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers)
// Lady Vashj <Coilfang Matron>
multipliers.push_back(new LadyVashjDelayCooldownsMultiplier(botAI));
multipliers.push_back(new LadyVashjMainTankGroupShamanUseGroundingTotemMultiplier(botAI));
multipliers.push_back(new LadyVashjMaintainPhase1RangedSpreadMultiplier(botAI));
multipliers.push_back(new LadyVashjStaticChargeStayAwayFromGroupMultiplier(botAI));
multipliers.push_back(new LadyVashjDoNotLootTheTaintedCoreMultiplier(botAI));

View File

@ -1,3 +1,8 @@
/*
* 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_RAIDSSCSTRATEGY_H_
#define _PLAYERBOT_RAIDSSCSTRATEGY_H_

View File

@ -1,3 +1,8 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "RaidSSCTriggers.h"
#include "RaidSSCHelpers.h"
#include "RaidSSCActions.h"
@ -26,35 +31,37 @@ bool UnderbogColossusSpawnedToxicPoolAfterDeathTrigger::IsActive()
bool GreyheartTidecallerWaterElementalTotemSpawnedTrigger::IsActive()
{
return botAI->IsDps(bot) &&
GetFirstAliveUnitByEntry(botAI, NPC_WATER_ELEMENTAL_TOTEM);
AI_VALUE2(Unit*, "find target", "greyheart tidecaller");
}
// Hydross the Unstable <Duke of Currents>
bool HydrossTheUnstableBotIsFrostTankTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "hydross the unstable") &&
botAI->IsMainTank(bot);
return botAI->IsMainTank(bot) &&
AI_VALUE2(Unit*, "find target", "hydross the unstable");
}
bool HydrossTheUnstableBotIsNatureTankTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "hydross the unstable") &&
botAI->IsAssistTankOfIndex(bot, 0, true);
return botAI->IsAssistTankOfIndex(bot, 0, true) &&
AI_VALUE2(Unit*, "find target", "hydross the unstable");
}
bool HydrossTheUnstableElementalsSpawnedTrigger::IsActive()
{
if (botAI->IsHeal(bot))
return false;
Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable");
if (hydross && hydross->GetHealthPct() < 10.0f)
if (!hydross || hydross->GetHealthPct() < 10.0f)
return false;
if (!AI_VALUE2(Unit*, "find target", "pure spawn of hydross") &&
!AI_VALUE2(Unit*, "find target", "tainted spawn of hydross"))
if (botAI->IsMainTank(bot) || botAI->IsAssistTankOfIndex(bot, 0, true))
return false;
return !botAI->IsHeal(bot) && !botAI->IsMainTank(bot) &&
!botAI->IsAssistTankOfIndex(bot, 0, true);
return AI_VALUE2(Unit*, "find target", "pure spawn of hydross") ||
AI_VALUE2(Unit*, "find target", "tainted spawn of hydross");
}
bool HydrossTheUnstableDangerFromWaterTombsTrigger::IsActive()
@ -71,19 +78,19 @@ bool HydrossTheUnstableTankNeedsAggroUponPhaseChangeTrigger::IsActive()
bool HydrossTheUnstableAggroResetsUponPhaseChangeTrigger::IsActive()
{
if (!AI_VALUE2(Unit*, "find target", "hydross the unstable"))
if (bot->getClass() == CLASS_HUNTER ||
botAI->IsHeal(bot) ||
botAI->IsMainTank(bot) ||
botAI->IsAssistTankOfIndex(bot, 0, true))
return false;
return bot->getClass() != CLASS_HUNTER &&
!botAI->IsHeal(bot) &&
!botAI->IsMainTank(bot) &&
!botAI->IsAssistTankOfIndex(bot, 0, true);
return AI_VALUE2(Unit*, "find target", "hydross the unstable");
}
bool HydrossTheUnstableNeedToManageTimersTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "hydross the unstable") &&
IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID, nullptr);
return IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID) &&
AI_VALUE2(Unit*, "find target", "hydross the unstable");
}
// The Lurker Below
@ -102,11 +109,11 @@ bool TheLurkerBelowSpoutIsActiveTrigger::IsActive()
bool TheLurkerBelowBossIsActiveForMainTankTrigger::IsActive()
{
Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below");
if (!lurker)
if (!botAI->IsMainTank(bot))
return false;
if (!botAI->IsMainTank(bot))
Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below");
if (!lurker)
return false;
const time_t now = std::time(nullptr);
@ -135,35 +142,16 @@ bool TheLurkerBelowBossCastsGeyserTrigger::IsActive()
// Trigger will be active only if there are at least 3 tanks in the raid
bool TheLurkerBelowBossIsSubmergedTrigger::IsActive()
{
if (!botAI->IsTank(bot))
return false;
Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below");
if (!lurker || lurker->getStandState() != UNIT_STAND_STATE_SUBMERGED)
return false;
Player* mainTank = nullptr;
Player* firstAssistTank = nullptr;
Player* secondAssistTank = nullptr;
Group* group = bot->GetGroup();
if (!group)
return false;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive())
continue;
PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member);
if (!memberAI)
continue;
if (!mainTank && memberAI->IsMainTank(member))
mainTank = member;
else if (!firstAssistTank && memberAI->IsAssistTankOfIndex(member, 0, true))
firstAssistTank = member;
else if (!secondAssistTank && memberAI->IsAssistTankOfIndex(member, 1, true))
secondAssistTank = member;
}
Player* mainTank = GetGroupMainTank(botAI, bot);
Player* firstAssistTank = GetGroupAssistTank(botAI, bot, 0);
Player* secondAssistTank = GetGroupAssistTank(botAI, bot, 1);
if (!mainTank || !firstAssistTank || !secondAssistTank)
return false;
@ -173,51 +161,55 @@ bool TheLurkerBelowBossIsSubmergedTrigger::IsActive()
bool TheLurkerBelowNeedToPrepareTimerForSpoutTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "the lurker below") &&
IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID, nullptr);
return IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID) &&
AI_VALUE2(Unit*, "find target", "the lurker below");
}
// Leotheras the Blind
bool LeotherasTheBlindBossIsInactiveTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "greyheart spellbinder");
return IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID) &&
AI_VALUE2(Unit*, "find target", "greyheart spellbinder");
}
bool LeotherasTheBlindBossTransformedIntoDemonFormTrigger::IsActive()
{
if (bot->getClass() != CLASS_WARLOCK)
return false;
if (!AI_VALUE2(Unit*, "find target", "leotheras the blind"))
return false;
if (GetLeotherasDemonFormTank(bot) != bot)
return false;
return GetActiveLeotherasDemon(botAI);
return GetActiveLeotherasDemon(bot);
}
bool LeotherasTheBlindOnlyWarlockShouldTankDemonFormTrigger::IsActive()
{
if (botAI->IsRanged(bot) || !botAI->IsTank(bot))
if (!botAI->IsTank(bot))
return false;
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
return false;
if (!AI_VALUE2(Unit*, "find target", "leotheras the blind"))
return false;
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
return false;
if (!GetLeotherasDemonFormTank(bot))
return false;
return GetPhase2LeotherasDemon(botAI);
return GetPhase2LeotherasDemon(bot);
}
bool LeotherasTheBlindBossEngagedByRangedTrigger::IsActive()
{
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
if (!botAI->IsRanged(bot))
return false;
if (!botAI->IsRanged(bot))
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
return false;
Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind");
@ -231,14 +223,14 @@ bool LeotherasTheBlindBossEngagedByRangedTrigger::IsActive()
bool LeotherasTheBlindBossChannelingWhirlwindTrigger::IsActive()
{
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
return false;
if (botAI->IsTank(bot) && botAI->IsMelee(bot))
if (botAI->IsTank(bot))
return false;
Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind");
if (!leotheras || leotheras->HasAura(SPELL_LEOTHERAS_BANISHED))
if (!leotheras)
return false;
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
return false;
return leotheras->HasAura(SPELL_WHIRLWIND) ||
@ -247,10 +239,13 @@ bool LeotherasTheBlindBossChannelingWhirlwindTrigger::IsActive()
bool LeotherasTheBlindBotHasTooManyChaosBlastStacksTrigger::IsActive()
{
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
if (botAI->IsRanged(bot))
return false;
if (botAI->IsRanged(bot))
if (!AI_VALUE2(Unit*, "find target", "leotheras the blind"))
return false;
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
return false;
Aura* chaosBlast = bot->GetAura(SPELL_CHAOS_BLAST);
@ -260,7 +255,7 @@ bool LeotherasTheBlindBotHasTooManyChaosBlastStacksTrigger::IsActive()
if (!GetLeotherasDemonFormTank(bot) && botAI->IsMainTank(bot))
return false;
return GetPhase2LeotherasDemon(botAI);
return GetPhase2LeotherasDemon(bot);
}
bool LeotherasTheBlindInnerDemonHasAwakenedTrigger::IsActive()
@ -271,89 +266,68 @@ bool LeotherasTheBlindInnerDemonHasAwakenedTrigger::IsActive()
bool LeotherasTheBlindEnteredFinalPhaseTrigger::IsActive()
{
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
return false;
if (botAI->IsHeal(bot))
return false;
if (GetLeotherasDemonFormTank(bot) == bot)
if (!AI_VALUE2(Unit*, "find target", "leotheras the blind"))
return false;
return GetPhase3LeotherasDemon(botAI) &&
GetLeotherasHuman(botAI);
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
return false;
if (bot->getClass() == CLASS_WARLOCK && GetLeotherasDemonFormTank(bot) == bot)
return false;
return GetPhase3LeotherasDemon(bot);
}
bool LeotherasTheBlindDemonFormTankNeedsAggro::IsActive()
{
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
return false;
if (bot->getClass() != CLASS_HUNTER)
return false;
return AI_VALUE2(Unit*, "find target", "leotheras the blind");
if (!AI_VALUE2(Unit*, "find target", "leotheras the blind"))
return false;
return !bot->HasAura(SPELL_INSIDIOUS_WHISPER);
}
bool LeotherasTheBlindBossWipesAggroUponPhaseChangeTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "leotheras the blind") &&
IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID, nullptr);
return IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID) &&
AI_VALUE2(Unit*, "find target", "leotheras the blind");
}
// Fathom-Lord Karathress
bool FathomLordKarathressBossEngagedByMainTankTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "fathom-lord karathress") &&
botAI->IsMainTank(bot);
return botAI->IsMainTank(bot) &&
AI_VALUE2(Unit*, "find target", "fathom-lord karathress");
}
bool FathomLordKarathressCaribdisEngagedByFirstAssistTankTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "fathom-guard caribdis") &&
botAI->IsAssistTankOfIndex(bot, 0, false);
return botAI->IsAssistTankOfIndex(bot, 0, false) &&
AI_VALUE2(Unit*, "find target", "fathom-guard caribdis");
}
bool FathomLordKarathressSharkkisEngagedBySecondAssistTankTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "fathom-guard sharkkis") &&
botAI->IsAssistTankOfIndex(bot, 1, false);
return botAI->IsAssistTankOfIndex(bot, 1, false) &&
AI_VALUE2(Unit*, "find target", "fathom-guard sharkkis");
}
bool FathomLordKarathressTidalvessEngagedByThirdAssistTankTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "fathom-guard tidalvess") &&
botAI->IsAssistTankOfIndex(bot, 2, false);
return botAI->IsAssistTankOfIndex(bot, 2, false) &&
AI_VALUE2(Unit*, "find target", "fathom-guard tidalvess");
}
bool FathomLordKarathressCaribdisTankNeedsDedicatedHealerTrigger::IsActive()
{
Unit* caribdis = AI_VALUE2(Unit*, "find target", "fathom-guard caribdis");
if (!caribdis)
return false;
if (!botAI->IsAssistHealOfIndex(bot, 0, true))
return false;
Player* firstAssistTank = nullptr;
if (Group* group = bot->GetGroup())
{
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive())
continue;
if (botAI->IsAssistTankOfIndex(member, 0, false))
{
firstAssistTank = member;
break;
}
}
}
return firstAssistTank;
return botAI->IsAssistHealOfIndex(bot, 0, true) &&
AI_VALUE2(Unit*, "find target", "fathom-guard caribdis");
}
bool FathomLordKarathressPullingBossesTrigger::IsActive()
@ -367,10 +341,10 @@ bool FathomLordKarathressPullingBossesTrigger::IsActive()
bool FathomLordKarathressDeterminingKillOrderTrigger::IsActive()
{
if (!AI_VALUE2(Unit*, "find target", "fathom-lord karathress"))
if (botAI->IsHeal(bot))
return false;
if (botAI->IsHeal(bot))
if (!AI_VALUE2(Unit*, "find target", "fathom-lord karathress"))
return false;
if (botAI->IsDps(bot))
@ -387,8 +361,8 @@ bool FathomLordKarathressDeterminingKillOrderTrigger::IsActive()
bool FathomLordKarathressTanksNeedToEstablishAggroTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "fathom-lord karathress") &&
IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID, nullptr);
return IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID) &&
AI_VALUE2(Unit*, "find target", "fathom-lord karathress");
}
// Morogrim Tidewalker
@ -404,8 +378,8 @@ bool MorogrimTidewalkerPullingBossTrigger::IsActive()
bool MorogrimTidewalkerBossEngagedByMainTankTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "morogrim tidewalker") &&
botAI->IsMainTank(bot);
return botAI->IsMainTank(bot) &&
AI_VALUE2(Unit*, "find target", "morogrim tidewalker");
}
bool MorogrimTidewalkerWaterGlobulesAreIncomingTrigger::IsActive()
@ -421,8 +395,11 @@ bool MorogrimTidewalkerWaterGlobulesAreIncomingTrigger::IsActive()
bool LadyVashjBossEngagedByMainTankTrigger::IsActive()
{
if (!botAI->IsMainTank(bot))
return false;
return AI_VALUE2(Unit*, "find target", "lady vashj") &&
!IsLadyVashjInPhase2(botAI) && botAI->IsMainTank(bot);
!IsLadyVashjInPhase2(botAI);
}
bool LadyVashjBossEngagedByRangedInPhase1Trigger::IsActive()
@ -439,10 +416,7 @@ bool LadyVashjCastsShockBlastOnHighestAggroTrigger::IsActive()
IsLadyVashjInPhase2(botAI))
return false;
if (!IsMainTankInSameSubgroup(bot))
return false;
return true;
return IsMainTankInSameSubgroup(botAI, bot);
}
bool LadyVashjBotHasStaticChargeTrigger::IsActive()
@ -450,14 +424,15 @@ bool LadyVashjBotHasStaticChargeTrigger::IsActive()
if (!AI_VALUE2(Unit*, "find target", "lady vashj"))
return false;
if (Group* group = bot->GetGroup())
Group* group = bot->GetGroup();
if (!group)
return false;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (member && member->HasAura(SPELL_STATIC_CHARGE))
return true;
}
Player* member = ref->GetSource();
if (member && member->HasAura(SPELL_STATIC_CHARGE))
return true;
}
return false;
@ -500,9 +475,10 @@ bool LadyVashjTaintedElementalCheatTrigger::IsActive()
return false;
bool taintedPresent = false;
Unit* taintedUnit = AI_VALUE2(Unit*, "find target", "tainted elemental");
if (taintedUnit)
if (AI_VALUE2(Unit*, "find target", "tainted elemental"))
{
taintedPresent = true;
}
else
{
GuidVector corpses = AI_VALUE(GuidVector, "nearest corpses");
@ -513,13 +489,11 @@ bool LadyVashjTaintedElementalCheatTrigger::IsActive()
if (!object)
continue;
if (Creature* creature = object->ToCreature())
if (Creature* creature = object->ToCreature();
creature->GetEntry() == NPC_TAINTED_ELEMENTAL && !creature->IsAlive())
{
if (creature->GetEntry() == NPC_TAINTED_ELEMENTAL && !creature->IsAlive())
{
taintedPresent = true;
break;
}
taintedPresent = true;
break;
}
}
}
@ -527,12 +501,8 @@ bool LadyVashjTaintedElementalCheatTrigger::IsActive()
if (!taintedPresent)
return false;
Group* group = bot->GetGroup();
if (!group)
return false;
return (GetDesignatedCoreLooter(group, botAI) == bot &&
!bot->HasItemCount(ITEM_TAINTED_CORE, 1, false));
return GetDesignatedCoreLooter(botAI, bot) == bot &&
!bot->HasItemCount(ITEM_TAINTED_CORE, 1, false);
}
bool LadyVashjTaintedCoreWasLootedTrigger::IsActive()
@ -540,54 +510,24 @@ bool LadyVashjTaintedCoreWasLootedTrigger::IsActive()
if (!AI_VALUE2(Unit*, "find target", "lady vashj") || !IsLadyVashjInPhase2(botAI))
return false;
Group* group = bot->GetGroup();
if (!group)
auto coreHandlers = GetCoreHandlers(botAI, bot);
bool isCoreHandler = false;
for (Player* handler : coreHandlers)
if (handler == bot)
isCoreHandler = true;
if (!isCoreHandler)
return false;
Player* designatedLooter = GetDesignatedCoreLooter(group, botAI);
Player* firstCorePasser = GetFirstTaintedCorePasser(group, botAI);
Player* secondCorePasser = GetSecondTaintedCorePasser(group, botAI);
Player* thirdCorePasser = GetThirdTaintedCorePasser(group, botAI);
Player* fourthCorePasser = GetFourthTaintedCorePasser(group, botAI);
auto hasCore = [](Player* player) -> bool
{
return player && player->HasItemCount(ITEM_TAINTED_CORE, 1, false);
};
if (bot == designatedLooter)
{
if (!hasCore(bot))
return false;
}
else if (bot == firstCorePasser)
{
if (hasCore(secondCorePasser) || hasCore(thirdCorePasser) ||
hasCore(fourthCorePasser))
return false;
}
else if (bot == secondCorePasser)
{
if (hasCore(thirdCorePasser) || hasCore(fourthCorePasser))
return false;
}
else if (bot == thirdCorePasser)
{
if (hasCore(fourthCorePasser))
return false;
}
else if (bot != fourthCorePasser)
return false;
if (AnyRecentCoreInInventory(group, botAI))
return true;
// First and second passers move to positions as soon as the elemental appears
if (AI_VALUE2(Unit*, "find target", "tainted elemental") &&
(bot == firstCorePasser || bot == secondCorePasser))
Unit* tainted = AI_VALUE2(Unit*, "find target", "tainted elemental");
if (tainted && coreHandlers[0]->GetExactDist2d(tainted) < 5.0f &&
(bot == coreHandlers[1] || bot == coreHandlers[2]))
return true;
return false;
// Main logic: run if core is in play for this bot or a prior handler
return AnyRecentCoreInInventory(botAI, bot);
}
bool LadyVashjTaintedCoreIsUnusableTrigger::IsActive()
@ -599,18 +539,7 @@ bool LadyVashjTaintedCoreIsUnusableTrigger::IsActive()
if (!IsLadyVashjInPhase2(botAI))
return bot->HasItemCount(ITEM_TAINTED_CORE, 1, false);
Group* group = bot->GetGroup();
if (!group)
return false;
Player* coreHandlers[] =
{
GetDesignatedCoreLooter(group, botAI),
GetFirstTaintedCorePasser(group, botAI),
GetSecondTaintedCorePasser(group, botAI),
GetThirdTaintedCorePasser(group, botAI),
GetFourthTaintedCorePasser(group, botAI)
};
auto coreHandlers = GetCoreHandlers(botAI, bot);
if (bot->HasItemCount(ITEM_TAINTED_CORE, 1, false))
{
@ -625,24 +554,6 @@ bool LadyVashjTaintedCoreIsUnusableTrigger::IsActive()
return false;
}
bool LadyVashjNeedToResetCorePassingTrackersTrigger::IsActive()
{
Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj");
if (!vashj || IsLadyVashjInPhase2(botAI))
return false;
Group* group = bot->GetGroup();
if (!group)
return false;
return IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID, nullptr) ||
GetDesignatedCoreLooter(group, botAI) == bot ||
GetFirstTaintedCorePasser(group, botAI) == bot ||
GetSecondTaintedCorePasser(group, botAI) == bot ||
GetThirdTaintedCorePasser(group, botAI) == bot ||
GetFourthTaintedCorePasser(group, botAI) == bot;
}
bool LadyVashjToxicSporebatsAreSpewingPoisonCloudsTrigger::IsActive()
{
return IsLadyVashjInPhase3(botAI);
@ -653,17 +564,18 @@ bool LadyVashjBotIsEntangledInToxicSporesOrStaticChargeTrigger::IsActive()
if (!AI_VALUE2(Unit*, "find target", "lady vashj"))
return false;
if (Group* group = bot->GetGroup())
{
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->HasAura(SPELL_ENTANGLE))
continue;
Group* group = bot->GetGroup();
if (!group)
return false;
if (botAI->IsMelee(member))
return true;
}
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->HasAura(SPELL_ENTANGLE))
continue;
if (botAI->IsMelee(member))
return true;
}
return false;

View File

@ -1,3 +1,8 @@
/*
* 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_RAIDSSCTRIGGERS_H
#define _PLAYERBOT_RAIDSSCTRIGGERS_H
@ -387,14 +392,6 @@ public:
bool IsActive() override;
};
class LadyVashjNeedToResetCorePassingTrackersTrigger : public Trigger
{
public:
LadyVashjNeedToResetCorePassingTrackersTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "lady vashj need to reset core passing trackers") {}
bool IsActive() override;
};
class LadyVashjToxicSporebatsAreSpewingPoisonCloudsTrigger : public Trigger
{
public:

View File

@ -1,3 +1,8 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "RaidSSCHelpers.h"
#include "AiFactory.h"
#include "Creature.h"
@ -79,61 +84,54 @@ namespace SerpentShrineCavernHelpers
std::unordered_map<uint32, time_t> leotherasDemonFormDpsWaitTimer;
std::unordered_map<uint32, time_t> leotherasFinalPhaseDpsWaitTimer;
Unit* GetLeotherasHuman(PlayerbotAI* botAI)
Unit* GetLeotherasHuman(Player* bot)
{
auto const& npcs =
botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest hostile npcs")->Get();
for (auto const& guid : npcs)
{
Unit* unit = botAI->GetUnit(guid);
if (unit && unit->GetEntry() == NPC_LEOTHERAS_THE_BLIND &&
unit->IsInCombat() && !unit->HasAura(SPELL_METAMORPHOSIS))
return unit;
}
constexpr float searchRadius = 100.0f;
Creature* leotheras =
bot->FindNearestCreature(NPC_LEOTHERAS_THE_BLIND, searchRadius, true);
if (leotheras && leotheras->IsInCombat() &&
!leotheras->HasAura(SPELL_METAMORPHOSIS))
return leotheras;
return nullptr;
}
Unit* GetPhase2LeotherasDemon(PlayerbotAI* botAI)
Unit* GetPhase2LeotherasDemon(Player* bot)
{
auto const& npcs =
botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest hostile npcs")->Get();
for (auto const& guid : npcs)
{
Unit* unit = botAI->GetUnit(guid);
if (unit && unit->GetEntry() == NPC_LEOTHERAS_THE_BLIND &&
unit->HasAura(SPELL_METAMORPHOSIS))
return unit;
}
constexpr float searchRadius = 100.0f;
Creature* leotheras =
bot->FindNearestCreature(NPC_LEOTHERAS_THE_BLIND, searchRadius, true);
if (leotheras && leotheras->HasAura(SPELL_METAMORPHOSIS))
return leotheras;
return nullptr;
}
Unit* GetPhase3LeotherasDemon(PlayerbotAI* botAI)
Unit* GetPhase3LeotherasDemon(Player* bot)
{
auto const& npcs =
botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest hostile npcs")->Get();
for (auto const& guid : npcs)
{
Unit* unit = botAI->GetUnit(guid);
if (unit && unit->GetEntry() == NPC_SHADOW_OF_LEOTHERAS)
return unit;
}
return nullptr;
constexpr float searchRadius = 100.0f;
return bot->FindNearestCreature(NPC_SHADOW_OF_LEOTHERAS, searchRadius, true);
}
Unit* GetActiveLeotherasDemon(PlayerbotAI* botAI)
Unit* GetActiveLeotherasDemon(Player* bot)
{
Unit* phase2 = GetPhase2LeotherasDemon(botAI);
Unit* phase3 = GetPhase3LeotherasDemon(botAI);
Unit* phase2 = GetPhase2LeotherasDemon(bot);
Unit* phase3 = GetPhase3LeotherasDemon(bot);
return phase2 ? phase2 : phase3;
}
// (1) First priority is an assistant Warlock (real player or bot)
// (2) If no assistant Warlock, then look for any Warlock bot
Player* GetLeotherasDemonFormTank(Player* bot)
{
Group* group = bot->GetGroup();
if (!group)
return nullptr;
// (1) First loop: Return the first assistant Warlock (real player or bot)
Player* fallbackWarlock = nullptr;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
@ -142,21 +140,12 @@ namespace SerpentShrineCavernHelpers
if (group->IsAssistant(member->GetGUID()))
return member;
if (!fallbackWarlock && GET_PLAYERBOT_AI(member))
fallbackWarlock = member;
}
// (2) Fall back to first found bot Warlock
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member) ||
member->getClass() != CLASS_WARLOCK)
continue;
return member;
}
// (3) Return nullptr if none found
return nullptr;
return fallbackWarlock;
}
// Fathom-Lord Karathress
@ -182,16 +171,15 @@ namespace SerpentShrineCavernHelpers
// Lady Vashj <Coilfang Matron>
const Position VASHJ_PLATFORM_CENTER_POSITION = { 29.634f, -923.541f, 42.985f };
const Position VASHJ_PLATFORM_CENTER_POSITION = { 29.634f, -923.541f, 42.902f };
std::unordered_map<ObjectGuid, Position> vashjRangedPositions;
std::unordered_map<ObjectGuid, bool> hasReachedVashjRangedPosition;
std::unordered_map<uint32, ObjectGuid> nearestTriggerGuid;
std::unordered_map<ObjectGuid, Position> intendedLineup;
std::unordered_map<uint32, time_t> lastImbueAttempt;
std::unordered_map<uint32, time_t> lastCoreInInventoryTime;
std::unordered_map<ObjectGuid, time_t> lastCoreInInventoryTime;
bool IsMainTankInSameSubgroup(Player* bot)
bool IsMainTankInSameSubgroup(PlayerbotAI* botAI, Player* bot)
{
Group* group = bot->GetGroup();
if (!group || !group->isRaidGroup())
@ -210,11 +198,8 @@ namespace SerpentShrineCavernHelpers
if (group->GetMemberGroup(member->GetGUID()) != botSubGroup)
continue;
if (PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member))
{
if (memberAI->IsMainTank(member))
return true;
}
if (botAI->IsMainTank(member))
return true;
}
return false;
@ -277,38 +262,9 @@ namespace SerpentShrineCavernHelpers
return false;
}
bool AnyRecentCoreInInventory(Group* group, PlayerbotAI* botAI, uint32 graceSeconds)
{
Unit* vashj =
botAI->GetAiObjectContext()->GetValue<Unit*>("find target", "lady vashj")->Get();
if (!vashj)
return false;
if (group)
{
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (member && member->HasItemCount(ITEM_TAINTED_CORE, 1, false))
return true;
}
}
const uint32 instanceId = vashj->GetMap()->GetInstanceId();
const time_t now = std::time(nullptr);
auto it = lastCoreInInventoryTime.find(instanceId);
if (it != lastCoreInInventoryTime.end())
{
if ((now - it->second) <= static_cast<time_t>(graceSeconds))
return true;
}
return false;
}
Player* GetDesignatedCoreLooter(Group* group, PlayerbotAI* botAI)
Player* GetDesignatedCoreLooter(PlayerbotAI* botAI, Player* bot)
{
Group* group = bot->GetGroup();
if (!group)
return nullptr;
@ -317,10 +273,15 @@ namespace SerpentShrineCavernHelpers
if (!leaderGuid.IsEmpty())
leader = ObjectAccessor::FindPlayer(leaderGuid);
// If cheats are disabled, the group leader will be the designated looter
if (!botAI->HasCheat(BotCheatMask::raid))
return leader;
Player* fallback = leader;
// Priority: (1) assistant melee DPS, (2) other melee DPS, (3) any ranged DPS
Player* meleeDpsAssistant = nullptr;
Player* meleeDps = nullptr;
Player* rangedDps = nullptr;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
@ -331,22 +292,36 @@ namespace SerpentShrineCavernHelpers
if (!memberAI)
continue;
if (memberAI->IsMelee(member) && memberAI->IsDps(member))
return member;
if (!meleeDpsAssistant && memberAI->IsMelee(member) &&
memberAI->IsDps(member) && group->IsAssistant(member->GetGUID()))
{
meleeDpsAssistant = member;
break;
}
if (!fallback && memberAI->IsRangedDps(member))
fallback = member;
if (!meleeDps && memberAI->IsMelee(member) && memberAI->IsDps(member))
meleeDps = member;
if (!rangedDps && memberAI->IsRangedDps(member))
rangedDps = member;
}
return fallback ? fallback : leader;
if (meleeDpsAssistant)
return meleeDpsAssistant;
if (meleeDps)
return meleeDps;
if (rangedDps)
return rangedDps;
return leader;
}
Player* GetFirstTaintedCorePasser(Group* group, PlayerbotAI* botAI)
Player* GetFirstTaintedCorePasser(PlayerbotAI* botAI, Player* bot)
{
Group* group = bot->GetGroup();
if (!group)
return nullptr;
Player* designatedLooter = GetDesignatedCoreLooter(group, botAI);
Player* designatedLooter = GetDesignatedCoreLooter(botAI, bot);
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
@ -355,32 +330,29 @@ namespace SerpentShrineCavernHelpers
continue;
PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member);
if (!memberAI)
continue;
if (memberAI->IsAssistHealOfIndex(member, 0, true))
if (memberAI && memberAI->IsAssistHealOfIndex(member, 0, true))
return member;
}
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member) ||
botAI->IsTank(member) || member == designatedLooter)
continue;
return member;
if (member && member->IsAlive() && GET_PLAYERBOT_AI(member) &&
!botAI->IsTank(member) && member != designatedLooter)
return member;
}
return nullptr;
}
Player* GetSecondTaintedCorePasser(Group* group, PlayerbotAI* botAI)
Player* GetSecondTaintedCorePasser(PlayerbotAI* botAI, Player* bot)
{
Group* group = bot->GetGroup();
if (!group)
return nullptr;
Player* designatedLooter = GetDesignatedCoreLooter(group, botAI);
Player* firstCorePasser = GetFirstTaintedCorePasser(group, botAI);
Player* designatedLooter = GetDesignatedCoreLooter(botAI, bot);
Player* firstCorePasser = GetFirstTaintedCorePasser(botAI, bot);
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
@ -390,34 +362,31 @@ namespace SerpentShrineCavernHelpers
continue;
PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member);
if (!memberAI)
continue;
if (memberAI->IsAssistHealOfIndex(member, 1, true))
if (memberAI && memberAI->IsAssistHealOfIndex(member, 1, true))
return member;
}
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member) ||
botAI->IsTank(member) || member == designatedLooter ||
member == firstCorePasser)
continue;
return member;
if (member && member->IsAlive() && GET_PLAYERBOT_AI(member) &&
!botAI->IsTank(member) && member != designatedLooter &&
member != firstCorePasser)
return member;
}
return nullptr;
}
Player* GetThirdTaintedCorePasser(Group* group, PlayerbotAI* botAI)
Player* GetThirdTaintedCorePasser(PlayerbotAI* botAI, Player* bot)
{
Group* group = bot->GetGroup();
if (!group)
return nullptr;
Player* designatedLooter = GetDesignatedCoreLooter(group, botAI);
Player* firstCorePasser = GetFirstTaintedCorePasser(group, botAI);
Player* secondCorePasser = GetSecondTaintedCorePasser(group, botAI);
Player* designatedLooter = GetDesignatedCoreLooter(botAI, bot);
Player* firstCorePasser = GetFirstTaintedCorePasser(botAI, bot);
Player* secondCorePasser = GetSecondTaintedCorePasser(botAI, bot);
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
@ -427,35 +396,32 @@ namespace SerpentShrineCavernHelpers
continue;
PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member);
if (!memberAI)
continue;
if (memberAI->IsAssistHealOfIndex(member, 2, true))
if (memberAI && memberAI->IsAssistHealOfIndex(member, 2, true))
return member;
}
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member) ||
botAI->IsTank(member) || member == designatedLooter ||
member == firstCorePasser || member == secondCorePasser)
continue;
return member;
if (member && member->IsAlive() && GET_PLAYERBOT_AI(member) &&
!botAI->IsTank(member) && member != designatedLooter &&
member != firstCorePasser && member != secondCorePasser)
return member;
}
return nullptr;
}
Player* GetFourthTaintedCorePasser(Group* group, PlayerbotAI* botAI)
Player* GetFourthTaintedCorePasser(PlayerbotAI* botAI, Player* bot)
{
Group* group = bot->GetGroup();
if (!group)
return nullptr;
Player* designatedLooter = GetDesignatedCoreLooter(group, botAI);
Player* firstCorePasser = GetFirstTaintedCorePasser(group, botAI);
Player* secondCorePasser = GetSecondTaintedCorePasser(group, botAI);
Player* thirdCorePasser = GetThirdTaintedCorePasser(group, botAI);
Player* designatedLooter = GetDesignatedCoreLooter(botAI, bot);
Player* firstCorePasser = GetFirstTaintedCorePasser(botAI, bot);
Player* secondCorePasser = GetSecondTaintedCorePasser(botAI, bot);
Player* thirdCorePasser = GetThirdTaintedCorePasser(botAI, bot);
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
@ -466,27 +432,75 @@ namespace SerpentShrineCavernHelpers
continue;
PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member);
if (!memberAI)
continue;
if (memberAI->IsAssistRangedDpsOfIndex(member, 0, true))
if (memberAI && memberAI->IsAssistRangedDpsOfIndex(member, 0, true))
return member;
}
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member) ||
botAI->IsTank(member) || member == designatedLooter ||
member == firstCorePasser || member == secondCorePasser ||
member == thirdCorePasser)
continue;
return member;
if (member && member->IsAlive() && GET_PLAYERBOT_AI(member) &&
!botAI->IsTank(member) && member != designatedLooter &&
member != firstCorePasser && member != secondCorePasser &&
member != thirdCorePasser)
return member;
}
return nullptr;
}
std::array<Player*, 5> GetCoreHandlers(PlayerbotAI* botAI, Player* bot)
{
return
{
GetDesignatedCoreLooter(botAI, bot),
GetFirstTaintedCorePasser(botAI, bot),
GetSecondTaintedCorePasser(botAI, bot),
GetThirdTaintedCorePasser(botAI, bot),
GetFourthTaintedCorePasser(botAI, bot)
};
}
// Checks if any bot from earlier in the passing sequence has the Tainted Core or
// had it within the prior 3 seconds so the chain is not broken when the Core is in transit
bool AnyRecentCoreInInventory(PlayerbotAI* botAI, Player* bot)
{
Unit* vashj =
botAI->GetAiObjectContext()->GetValue<Unit*>("find target", "lady vashj")->Get();
if (!vashj)
return false;
auto coreHandlers = GetCoreHandlers(botAI, bot);
int8 myIndex = -1;
for (int8 i = 0; i < 5; ++i)
if (coreHandlers[i] && coreHandlers[i] == bot)
myIndex = i;
if (myIndex == -1)
return false;
const time_t now = std::time(nullptr);
constexpr uint8 lookbackSeconds = 3;
for (int8 i = 0; i <= myIndex; ++i)
{
Player* handler = coreHandlers[i];
if (!handler)
continue;
if (handler->HasItemCount(ITEM_TAINTED_CORE, 1, false))
return true;
auto it = lastCoreInInventoryTime.find(handler->GetGUID());
if (it != lastCoreInInventoryTime.end() &&
(now - it->second) <= static_cast<time_t>(lookbackSeconds))
return true;
}
return false;
}
const std::vector<uint32> SHIELD_GENERATOR_DB_GUIDS =
{
47482, // NW
@ -510,10 +524,7 @@ namespace SerpentShrineCavernHelpers
continue;
GameObject* go = bounds.first->second;
if (!go)
continue;
if (go->GetGoState() != GO_STATE_READY)
if (!go || go->GetGoState() != GO_STATE_READY)
continue;
GeneratorInfo info;
@ -529,7 +540,7 @@ namespace SerpentShrineCavernHelpers
// Returns the nearest active Shield Generator to the bot
// Active generators are powered by NPC_WORLD_INVISIBLE_TRIGGER creatures,
// which depawn after use
// which despawn after use
Unit* GetNearestActiveShieldGeneratorTriggerByEntry(Unit* reference)
{
if (!reference)

View File

@ -1,3 +1,8 @@
/*
* 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_RAIDSSCHELPERS_H_
#define _PLAYERBOT_RAIDSSCHELPERS_H_
@ -64,9 +69,6 @@ namespace SerpentShrineCavernHelpers
// Warlock
SPELL_CURSE_OF_EXHAUSTION = 18223,
// Item
SPELL_HEAVY_NETHERWEAVE_NET = 31368,
};
enum SerpentShrineCavernNPCs
@ -105,9 +107,6 @@ namespace SerpentShrineCavernHelpers
{
// Lady Vashj <Coilfang Matron>
ITEM_TAINTED_CORE = 31088,
// Tailoring
ITEM_HEAVY_NETHERWEAVE_NET = 24269,
};
constexpr uint32 SSC_MAP_ID = 548;
@ -134,10 +133,10 @@ namespace SerpentShrineCavernHelpers
extern std::unordered_map<uint32, time_t> leotherasHumanFormDpsWaitTimer;
extern std::unordered_map<uint32, time_t> leotherasDemonFormDpsWaitTimer;
extern std::unordered_map<uint32, time_t> leotherasFinalPhaseDpsWaitTimer;
Unit* GetLeotherasHuman(PlayerbotAI* botAI);
Unit* GetPhase2LeotherasDemon(PlayerbotAI* botAI);
Unit* GetPhase3LeotherasDemon(PlayerbotAI* botAI);
Unit* GetActiveLeotherasDemon(PlayerbotAI* botAI);
Unit* GetLeotherasHuman(Player* bot);
Unit* GetPhase2LeotherasDemon(Player* bot);
Unit* GetPhase3LeotherasDemon(Player* bot);
Unit* GetActiveLeotherasDemon(Player* bot);
Player* GetLeotherasDemonFormTank(Player* bot);
// Fathom-Lord Karathress
@ -158,25 +157,26 @@ namespace SerpentShrineCavernHelpers
extern std::unordered_map<ObjectGuid, uint8> tidewalkerRangedStep;
// Lady Vashj <Coilfang Matron>
constexpr float VASHJ_PLATFORM_Z = 42.985f;
constexpr float VASHJ_PLATFORM_CENTER_Z = 42.902f;
constexpr float VASHJ_PLATFORM_EDGE_Z = 41.097f;
extern const Position VASHJ_PLATFORM_CENTER_POSITION;
extern std::unordered_map<ObjectGuid, Position> vashjRangedPositions;
extern std::unordered_map<ObjectGuid, bool> hasReachedVashjRangedPosition;
extern std::unordered_map<uint32, ObjectGuid> nearestTriggerGuid;
extern std::unordered_map<ObjectGuid, Position> intendedLineup;
extern std::unordered_map<uint32, time_t> lastImbueAttempt;
extern std::unordered_map<uint32, time_t> lastCoreInInventoryTime;
bool IsMainTankInSameSubgroup(Player* bot);
extern std::unordered_map<ObjectGuid, time_t> lastCoreInInventoryTime;
bool IsMainTankInSameSubgroup(PlayerbotAI* botAI, Player* bot);
bool IsLadyVashjInPhase1(PlayerbotAI* botAI);
bool IsLadyVashjInPhase2(PlayerbotAI* botAI);
bool IsLadyVashjInPhase3(PlayerbotAI* botAI);
bool IsValidLadyVashjCombatNpc(Unit* unit, PlayerbotAI* botAI);
bool AnyRecentCoreInInventory(Group* group, PlayerbotAI* botAI, uint32 graceSeconds = 3);
Player* GetDesignatedCoreLooter(Group* group, PlayerbotAI* botAI);
Player* GetFirstTaintedCorePasser(Group* group, PlayerbotAI* botAI);
Player* GetSecondTaintedCorePasser(Group* group, PlayerbotAI* botAI);
Player* GetThirdTaintedCorePasser(Group* group, PlayerbotAI* botAI);
Player* GetFourthTaintedCorePasser(Group* group, PlayerbotAI* botAI);
Player* GetDesignatedCoreLooter(PlayerbotAI* botAI, Player* bot);
Player* GetFirstTaintedCorePasser(PlayerbotAI* botAI, Player* bot);
Player* GetSecondTaintedCorePasser(PlayerbotAI* botAI, Player* bot);
Player* GetThirdTaintedCorePasser(PlayerbotAI* botAI, Player* bot);
Player* GetFourthTaintedCorePasser(PlayerbotAI* botAI, Player* bot);
std::array<Player*, 5> GetCoreHandlers(PlayerbotAI* botAI, Player* bot);
bool AnyRecentCoreInInventory(PlayerbotAI* botAI, Player* bot);
struct GeneratorInfo { ObjectGuid guid; float x, y, z; };
extern const std::vector<uint32> SHIELD_GENERATOR_DB_GUIDS;
std::vector<GeneratorInfo> GetAllGeneratorInfosByDbGuids(

View File

@ -1796,7 +1796,7 @@ PlayerbotAI* PlayerbotsMgr::GetPlayerbotAI(Player* player)
if (itr != _playerbotsAIMap.end())
{
if (itr->second->IsBotAI())
return reinterpret_cast<PlayerbotAI*>(itr->second);
return dynamic_cast<PlayerbotAI*>(itr->second);
}
return nullptr;
@ -1812,7 +1812,7 @@ PlayerbotMgr* PlayerbotsMgr::GetPlayerbotMgr(Player* player)
if (itr != _playerbotsMgrMap.end())
{
if (!itr->second->IsBotAI())
return reinterpret_cast<PlayerbotMgr*>(itr->second);
return dynamic_cast<PlayerbotMgr*>(itr->second);
}
return nullptr;

View File

@ -1070,7 +1070,7 @@ void GuildTaskMgr::CheckKillTaskInternal(Player* player, Unit* victim)
if (!victim->IsCreature())
return;
Creature* creature = reinterpret_cast<Creature*>(victim);
Creature* creature = dynamic_cast<Creature*>(victim);
if (!creature)
return;

View File

@ -1753,190 +1753,75 @@ void TravelNodeMap::generateTransportNodes()
for (auto const& itr : *sObjectMgr->GetGameObjectTemplates())
{
GameObjectTemplate const* data = &itr.second;
if (data && (data->type == GAMEOBJECT_TYPE_TRANSPORT || data->type == GAMEOBJECT_TYPE_MO_TRANSPORT))
if (!data || (data->type != GAMEOBJECT_TYPE_TRANSPORT && data->type != GAMEOBJECT_TYPE_MO_TRANSPORT))
continue;
uint32 pathId = data->moTransport.taxiPathId;
float moveSpeed = data->moTransport.moveSpeed;
if (pathId >= sTaxiPathNodesByPath.size())
continue;
TaxiPathNodeList const& path = sTaxiPathNodesByPath[pathId];
// Keep only transports with taxi paths (boats/zeppelins).
if (path.empty())
continue;
std::vector<WorldPosition> ppath;
TravelNode* prevNode = nullptr;
// Loop over the path and connect stop locations.
for (auto& p : path)
{
TransportAnimation const* animation = sTransportMgr->GetTransportAnimInfo(itr.first);
WorldPosition pos = WorldPosition(p->mapid, p->x, p->y, p->z, 0);
uint32 pathId = data->moTransport.taxiPathId;
float moveSpeed = data->moTransport.moveSpeed;
if (pathId >= sTaxiPathNodesByPath.size())
continue;
if (prevNode)
ppath.push_back(pos);
TaxiPathNodeList const& path = sTaxiPathNodesByPath[pathId];
std::vector<WorldPosition> ppath;
TravelNode* prevNode = nullptr;
// Elevators/Trams
if (path.empty())
if (p->delay > 0)
{
if (animation)
TravelNode* node = TravelNodeMap::instance().addNode(pos, data->name, true, true, true, itr.first);
if (!prevNode)
{
TransportPathContainer aPath = animation->Path;
float timeStart;
for (auto& transport : WorldPosition().getGameObjectsNear(0, itr.first))
{
prevNode = nullptr;
WorldPosition basePos(transport->mapid, transport->posX, transport->posY, transport->posZ,
transport->orientation);
WorldPosition lPos = WorldPosition();
for (auto& p : aPath)
{
float dx = -1 * p.second->X;
float dy = -1 * p.second->Y;
WorldPosition pos =
WorldPosition(basePos.GetMapId(), basePos.GetPositionX() + dx,
basePos.GetPositionY() + dy, basePos.GetPositionZ() + p.second->Z,
basePos.GetOrientation());
if (prevNode)
{
ppath.push_back(pos);
}
if (pos.distance(&lPos) == 0)
{
TravelNode* node =
TravelNodeMap::instance().addNode(pos, data->name, true, true, true, itr.first);
if (!prevNode)
{
ppath.push_back(pos);
timeStart = p.second->TimeSeg;
}
else
{
float totalTime = (p.second->TimeSeg - timeStart) / 1000.0f;
TravelNodePath travelPath(0.1f, totalTime, (uint8)TravelNodePathType::transport,
itr.first, true);
node->setPathTo(prevNode, travelPath);
ppath.clear();
ppath.push_back(pos);
timeStart = p.second->TimeSeg;
}
prevNode = node;
}
lPos = pos;
}
if (prevNode)
{
for (auto& p : aPath)
{
float dx = -1 * p.second->X;
float dy = -1 * p.second->Y;
WorldPosition pos =
WorldPosition(basePos.GetMapId(), basePos.GetPositionX() + dx,
basePos.GetPositionY() + dy, basePos.GetPositionZ() + p.second->Z,
basePos.GetOrientation());
ppath.push_back(pos);
if (pos.distance(&lPos) == 0)
{
TravelNode* node =
TravelNodeMap::instance().addNode(pos, data->name, true, true, true, itr.first);
if (node != prevNode)
{
if (p.second->TimeSeg < timeStart)
timeStart = 0;
float totalTime = (p.second->TimeSeg - timeStart) / 1000.0f;
TravelNodePath travelPath(0.1f, totalTime, (uint8)TravelNodePathType::transport,
itr.first, true);
travelPath.setPath(ppath);
node->setPathTo(prevNode, travelPath);
ppath.clear();
ppath.push_back(pos);
timeStart = p.second->TimeSeg;
}
}
lPos = pos;
}
}
ppath.clear();
}
ppath.push_back(pos);
}
}
else // Boats/Zepelins
{
// Loop over the path and connect stop locations.
for (auto& p : path)
else
{
WorldPosition pos = WorldPosition(p->mapid, p->x, p->y, p->z, 0);
// if (data->displayId == 3015)
// pos.setZ(pos.getZ() + 6.0f);
// else if (data->displayId == 3031)
// pos.setZ(pos.getZ() - 17.0f);
if (prevNode)
{
ppath.push_back(pos);
}
if (p->delay > 0)
{
TravelNode* node = TravelNodeMap::instance().addNode(pos, data->name, true, true, true, itr.first);
if (!prevNode)
{
ppath.push_back(pos);
}
else
{
TravelNodePath travelPath(0.1f, 0.0, (uint8)TravelNodePathType::transport, itr.first, true);
travelPath.setPathAndCost(ppath, moveSpeed);
node->setPathTo(prevNode, travelPath);
ppath.clear();
ppath.push_back(pos);
}
prevNode = node;
}
TravelNodePath travelPath(0.1f, 0.0, (uint8)TravelNodePathType::transport, itr.first, true);
travelPath.setPathAndCost(ppath, moveSpeed);
node->setPathTo(prevNode, travelPath);
ppath.clear();
ppath.push_back(pos);
}
if (prevNode)
{
// Continue from start until first stop and connect to end.
for (auto& p : path)
{
WorldPosition pos = WorldPosition(p->mapid, p->x, p->y, p->z, 0);
// if (data->displayId == 3015)
// pos.setZ(pos.getZ() + 6.0f);
// else if (data->displayId == 3031)
// pos.setZ(pos.getZ() - 17.0f);
ppath.push_back(pos);
if (p->delay > 0)
{
TravelNode* node = TravelNodeMap::instance().getNode(pos, nullptr, 5.0f);
if (node != prevNode)
{
TravelNodePath travelPath(0.1f, 0.0, (uint8)TravelNodePathType::transport, itr.first,
true);
travelPath.setPathAndCost(ppath, moveSpeed);
node->setPathTo(prevNode, travelPath);
}
}
}
}
ppath.clear();
prevNode = node;
}
}
if (!prevNode)
continue;
// Continue from start until first stop and connect to end.
for (auto& p : path)
{
WorldPosition pos = WorldPosition(p->mapid, p->x, p->y, p->z, 0);
ppath.push_back(pos);
if (p->delay > 0)
{
TravelNode* node = TravelNodeMap::instance().getNode(pos, nullptr, 5.0f);
if (node != prevNode)
{
TravelNodePath travelPath(0.1f, 0.0, (uint8)TravelNodePathType::transport, itr.first, true);
travelPath.setPathAndCost(ppath, moveSpeed);
node->setPathTo(prevNode, travelPath);
}
}
}
ppath.clear();
}
}

View File

@ -52,15 +52,14 @@ public:
PlayerbotsSecureLoginServerScript()
: ServerScript("PlayerbotsSecureLoginServerScript", { SERVERHOOK_CAN_PACKET_RECEIVE }) {}
bool CanPacketReceive(WorldSession* /*session*/, WorldPacket& packet) override
bool CanPacketReceive(WorldSession* /*session*/, WorldPacket const& packet) override
{
if (packet.GetOpcode() != CMSG_PLAYER_LOGIN)
return true;
auto const oldPos = packet.rpos();
WorldPacket pkt(packet);
ObjectGuid loginGuid;
packet >> loginGuid;
packet.rpos(oldPos);
pkt >> loginGuid;
if (!loginGuid)
return true;