Merge pull request #2191 from mod-playerbots/test-staging

Test staging to master
This commit is contained in:
kadeshar
2026-03-13 22:20:09 +01:00
committed by GitHub
60 changed files with 3821 additions and 261 deletions

View File

@@ -2,6 +2,7 @@ name: Enforce test-staging → master
on:
pull_request:
types: [opened, synchronize, reopened, edited]
branches:
- master
- test-staging

View File

@@ -1,124 +1,103 @@
# Pull Request
<!--
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.
Describe what this change does and why it is needed...
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.
## Design Philosophy
Before submitting, make sure your changes aligns with these principles.
-->
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.
## Pull Request Description
<!-- Describe what this change does and why it is needed -->
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
<!--
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:
<!-- 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.
- 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, number of bots, specific configuration).
- Expected behavior and how to verify it.
-->
- 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?
- - [ ] No
- - [ ] Yes (**explain below**)
## 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**)
Does this change increase per-bot or per-tick processing?
- - [ ] No
- - [ ] Yes (**describe and justify impact**)
Could this logic scale poorly under load?
- - [ ] No
- - [ ] Yes (**explain why**)
---
## Defaults & Configuration
- Does this change modify default bot behavior?
- [ ] No
- [ ] Yes (**explain why**)
Does this change modify default bot behavior?
- - [ ] No
- - [ ] Yes (**explain why**)
If this introduces more advanced or AI-heavy logic:
- - [ ] Lightweight mode remains the default
- - [ ] More complex behavior is optional and thereby configurable
---
- Does this change add new decision branches or increase maintenance complexity?
- [ ] 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?
- [ ] No
- [ ] Yes (**list messages in the table**)
| Message key | Default message |
| --------------- | ------------------ |
| | |
| | |
## AI Assistance
Was AI assistance (e.g. ChatGPT or similar tools) used while working on this change?
- - [ ] 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.
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**)
<!--
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
- - [ ] Stability is not compromised
- - [ ] Performance impact is understood, tested, and acceptable
- - [ ] Added logic complexity is justified and explained
- - [ ] Documentation updated if needed
---
- [ ] 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 that significantly improves realism at the cost of stability or performance should be carefully discussed
before merging.
<!-- Anything else that's helpful to review or test your pull request. -->

View File

@@ -566,7 +566,10 @@ AiPlayerbot.AutoGearScoreLimit = 0
# Default: food, taxi, and raid are enabled
AiPlayerbot.BotCheats = "food,taxi,raid"
# Attunement quests (comma-separated list of quest IDs)
# List of attunement quests (comma-separated list of quest IDs) that are automatically completed for all bots.
# While mod-playerbots does not restore removed attunement requirements, although other mods, such as mod-individual-progression, may do so.
# This is meant to exclude bots from such requirements.
#
# Default:
# Caverns of Time - Part 1
# - 10279, To The Master's Lair
@@ -592,7 +595,17 @@ AiPlayerbot.BotCheats = "food,taxi,raid"
#
# Serpentshrine Cavern
# - 10901, The Cudgel of Kar'desh
AiPlayerbot.AttunementQuests = 10279,10277,10282,10283,10284,10285,10296,10297,10298,11481,11482,11488,11490,11492,10901
#
# The Eye
# - 10888, Trial of the Naaru: Magtheridon
#
# Mount Hyjal
# - 10445, The Vials of Eternity
#
# Black Temple
# - 10985, A Distraction for Akama
#
AiPlayerbot.AttunementQuests = 10279,10277,10282,10283,10284,10285,10296,10297,10298,11481,11482,11488,11490,11492,10901,10888,10445,10985
#
#
@@ -796,6 +809,10 @@ AiPlayerbot.LimitGearExpansion = 1
# Set between 0 (0%) and 1 (100%)
AiPlayerbot.RandomGearLoweringChance = 0
# Unobtainable or unusable items (comma-separated list of item IDs)
# Default: Chilton Wand (12468), Totem of the Earthen Ring (46978)
AiPlayerbot.UnobtainableItems = 12468,46978
# Randombots check player's gearscore level and deny the group invitation if it's too low
# Default: 0 (disabled)
AiPlayerbot.GearScoreCheck = 0
@@ -2213,4 +2230,4 @@ AiPlayerbot.SummonAtInnkeepersEnabled = 1
# 30% more damage, 40% damage reduction (tank bots), increased all resistances, reduced threat for non tank bots, increased threat for tank bots.
# Buffs will be applied on PP, Sindragosa and Lich King
AiPlayerbot.EnableICCBuffs = 1
AiPlayerbot.EnableICCBuffs = 1

View File

@@ -0,0 +1 @@
UPDATE `ai_playerbot_texts` SET `text_loc3`='%s, du hörst den triefenden Sarkasmus in meinem text nicht' WHERE `id`=1353;

View File

@@ -237,6 +237,20 @@ bool MaxDpsChatShortcutAction::Execute(Event /*event*/)
return true;
}
bool NaxxChatShortcutAction::Execute(Event /*event*/)
{
Player* master = GetMaster();
if (!master)
return false;
botAI->Reset();
botAI->ChangeStrategy("+naxx", BOT_STATE_NON_COMBAT);
botAI->ChangeStrategy("+naxx", BOT_STATE_COMBAT);
botAI->TellMasterNoFacing("Add Naxx Strategies!");
// bot->Say("Add Naxx Strategies!", LANG_UNIVERSAL);
return true;
}
bool BwlChatShortcutAction::Execute(Event /*event*/)
{
Player* master = GetMaster();

View File

@@ -85,6 +85,13 @@ public:
bool Execute(Event event) override;
};
class NaxxChatShortcutAction : public Action
{
public:
NaxxChatShortcutAction(PlayerbotAI* ai) : Action(ai, "naxx chat shortcut") {}
virtual bool Execute(Event event);
};
class BwlChatShortcutAction : public Action
{
public:

View File

@@ -854,6 +854,11 @@ float MovementAction::GetFollowAngle()
if (!group)
return 0.0f;
// Prevent bots with orphaned raid groups from dividing by 0, which freezes the server.
uint32 memberCount = group->GetMembersCount();
if (memberCount <= 1)
return 0.0f;
uint32 index = 1;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
@@ -861,7 +866,7 @@ float MovementAction::GetFollowAngle()
continue;
if (ref->GetSource() == bot)
return 2 * M_PI / (group->GetMembersCount() - 1) * index;
return 2 * M_PI / (memberCount - 1) * index;
++index;
}

View File

@@ -187,6 +187,7 @@ public:
creators["guild leave"] = &ChatActionContext::guild_leave;
creators["rtsc"] = &ChatActionContext::rtsc;
creators["bwl chat shortcut"] = &ChatActionContext::bwl_chat_shortcut;
creators["naxx chat shortcut"] = &ChatActionContext::naxx_chat_shortcut;
creators["tell estimated dps"] = &ChatActionContext::tell_estimated_dps;
creators["join"] = &ChatActionContext::join;
creators["lfg"] = &ChatActionContext::lfg;
@@ -298,6 +299,7 @@ private:
static Action* guild_remove(PlayerbotAI* botAI) { return new GuildRemoveAction(botAI); }
static Action* guild_leave(PlayerbotAI* botAI) { return new GuildLeaveAction(botAI); }
static Action* rtsc(PlayerbotAI* botAI) { return new RTSCAction(botAI); }
static Action* naxx_chat_shortcut(PlayerbotAI* ai) { return new NaxxChatShortcutAction(ai); }
static Action* bwl_chat_shortcut(PlayerbotAI* ai) { return new BwlChatShortcutAction(ai); }
static Action* tell_estimated_dps(PlayerbotAI* ai) { return new TellEstimatedDpsAction(ai); }
static Action* join(PlayerbotAI* ai) { return new JoinGroupAction(ai); }

View File

@@ -127,7 +127,6 @@ public:
creators["guild leave"] = &ChatTriggerContext::guild_leave;
creators["rtsc"] = &ChatTriggerContext::rtsc;
creators["drink"] = &ChatTriggerContext::drink;
// creators["bwl"] = &ChatTriggerContext::bwl;
creators["dps"] = &ChatTriggerContext::dps;
creators["disperse"] = &ChatTriggerContext::disperse;
creators["calc"] = &ChatTriggerContext::calc;
@@ -245,7 +244,6 @@ private:
static Trigger* guild_leave(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "guild leave"); }
static Trigger* rtsc(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "rtsc"); }
static Trigger* drink(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "drink"); }
// static Trigger* bwl(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "bwl"); }
static Trigger* dps(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "dps"); }
static Trigger* disperse(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "disperse"); }
static Trigger* calc(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "calc"); }

View File

@@ -83,6 +83,8 @@ void ChatCommandHandlerStrategy::InitTriggers(std::vector<TriggerNode*>& trigger
new TriggerNode("target", { NextAction("tell target", relevance) }));
triggers.push_back(
new TriggerNode("ready", { NextAction("ready check", relevance) }));
triggers.push_back(
new TriggerNode("naxx", {NextAction("naxx chat shortcut", relevance)}));
triggers.push_back(
new TriggerNode("bwl", { NextAction("bwl chat shortcut", relevance) }));
triggers.push_back(

View File

@@ -116,9 +116,9 @@ bool AttumenTheHuntsmanStackBehindAction::Execute(Event /*event*/)
float rearX = attumenMounted->GetPositionX() + std::cos(orientation) * distanceBehind;
float rearY = attumenMounted->GetPositionY() + std::sin(orientation) * distanceBehind;
if (bot->GetExactDist2d(rearX, rearY) > 1.0f)
if (bot->GetDistance2d(rearX, rearY) > 1.0f)
{
return MoveTo(KARAZHAN_MAP_ID, rearX, rearY, attumenMounted->GetPositionZ(), false, false, false, false,
return MoveTo(KARAZHAN_MAP_ID, rearX, rearY, bot->GetPositionZ(), false, false, false, false,
MovementPriority::MOVEMENT_FORCED, true, false);
}
@@ -1178,7 +1178,7 @@ bool PrinceMalchezaarNonTankAvoidInfernalAction::Execute(Event /*event*/)
bot->AttackStop();
bot->InterruptNonMeleeSpells(true);
return MoveTo(KARAZHAN_MAP_ID, bestDestX, bestDestY, bestDestZ, false, false, false, false,
MovementPriority::MOVEMENT_FORCED, true, false);
MovementPriority::MOVEMENT_COMBAT, true, false);
}
}
@@ -1244,7 +1244,7 @@ bool PrinceMalchezaarMainTankMovementAction::Execute(Event /*event*/)
{
bot->AttackStop();
return MoveTo(KARAZHAN_MAP_ID, bestDestX, bestDestY, bestDestZ, false, false, false, false,
MovementPriority::MOVEMENT_FORCED, true, false);
MovementPriority::MOVEMENT_COMBAT, true, true);
}
}

View File

@@ -80,6 +80,19 @@ float AttumenTheHuntsmanWaitForDpsMultiplier::GetValue(Action* action)
return 1.0f;
}
// Disables co +disperse and co +tank face
float MaidenOfVirtueDisableCombatFormationMoveMultiplier::GetValue(Action* action)
{
if (!AI_VALUE2(Unit*, "find target", "maiden of virtue"))
return 1.0f;
if (dynamic_cast<CombatFormationMoveAction*>(action) &&
!dynamic_cast<SetBehindTargetAction*>(action))
return 0.0f;
return 1.0f;
}
// The assist tank should stay on the boss to be 2nd on aggro and tank Hateful Bolts
float TheCuratorDisableTankAssistMultiplier::GetValue(Action* action)
{
@@ -93,6 +106,19 @@ float TheCuratorDisableTankAssistMultiplier::GetValue(Action* action)
return 1.0f;
}
// Disables co +disperse and co +tank face
float TheCuratorDisableCombatFormationMoveMultiplier::GetValue(Action* action)
{
if (!AI_VALUE2(Unit*, "find target", "the curator"))
return 1.0f;
if (dynamic_cast<CombatFormationMoveAction*>(action) &&
!dynamic_cast<SetBehindTargetAction*>(action))
return 0.0f;
return 1.0f;
}
// Save Bloodlust/Heroism for Evocation (100% increased damage)
float TheCuratorDelayBloodlustAndHeroismMultiplier::GetValue(Action* action)
{
@@ -350,17 +376,11 @@ float NightbaneDisableMovementMultiplier::GetValue(Action* action)
if (dynamic_cast<CastBlinkBackAction*>(action) ||
dynamic_cast<CastDisengageAction*>(action) ||
dynamic_cast<FleeAction*>(action))
return 0.0f;
// Disable CombatFormationMoveAction for all bots except:
// (1) main tank and (2) only during the ground phase, other melee
if (botAI->IsRanged(bot) ||
(botAI->IsMelee(bot) && !botAI->IsMainTank(bot) &&
nightbane->GetPositionZ() > NIGHTBANE_FLIGHT_Z))
dynamic_cast<FleeAction*>(action) ||
(dynamic_cast<CombatFormationMoveAction*>(action) &&
!dynamic_cast<SetBehindTargetAction*>(action)))
{
if (dynamic_cast<CombatFormationMoveAction*>(action))
return 0.0f;
return 0.0f;
}
return 1.0f;

View File

@@ -27,6 +27,14 @@ public:
virtual float GetValue(Action* action);
};
class MaidenOfVirtueDisableCombatFormationMoveMultiplier : public Multiplier
{
public:
MaidenOfVirtueDisableCombatFormationMoveMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "maiden of virtue disable combat formation move multiplier") {}
virtual float GetValue(Action* action);
};
class TheCuratorDisableTankAssistMultiplier : public Multiplier
{
public:
@@ -35,6 +43,14 @@ public:
virtual float GetValue(Action* action);
};
class TheCuratorDisableCombatFormationMoveMultiplier : public Multiplier
{
public:
TheCuratorDisableCombatFormationMoveMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "the curator disable combat formation move multiplier") {}
virtual float GetValue(Action* action);
};
class TheCuratorDelayBloodlustAndHeroismMultiplier : public Multiplier
{
public:

View File

@@ -146,7 +146,9 @@ void RaidKarazhanStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers
multipliers.push_back(new AttumenTheHuntsmanDisableTankAssistMultiplier(botAI));
multipliers.push_back(new AttumenTheHuntsmanStayStackedMultiplier(botAI));
multipliers.push_back(new AttumenTheHuntsmanWaitForDpsMultiplier(botAI));
multipliers.push_back(new MaidenOfVirtueDisableCombatFormationMoveMultiplier(botAI));
multipliers.push_back(new TheCuratorDisableTankAssistMultiplier(botAI));
multipliers.push_back(new TheCuratorDisableCombatFormationMoveMultiplier(botAI));
multipliers.push_back(new TheCuratorDelayBloodlustAndHeroismMultiplier(botAI));
multipliers.push_back(new ShadeOfAranArcaneExplosionDisableChargeMultiplier(botAI));
multipliers.push_back(new ShadeOfAranFlameWreathDisableMovementMultiplier(botAI));

View File

@@ -62,7 +62,6 @@ namespace KarazhanHelpers
NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED = 16152,
// Terestian Illhoof
NPC_TERESTIAN_ILLHOOF = 15688,
NPC_DEMON_CHAINS = 17248,
NPC_KILREK = 17229,

View File

@@ -0,0 +1,326 @@
#ifndef _PLAYERBOT_RAIDNAXXACTIONS_H
#define _PLAYERBOT_RAIDNAXXACTIONS_H
#include "Action.h"
#include "AttackAction.h"
#include "GenericActions.h"
#include "MovementActions.h"
#include "PlayerbotAI.h"
#include "Playerbots.h"
#include "RaidNaxxBossHelper.h"
class GrobbulusGoBehindAction : public MovementAction
{
public:
GrobbulusGoBehindAction(PlayerbotAI* ai, float distance = 24.0f, float delta_angle = M_PI / 8)
: MovementAction(ai, "grobbulus go behind")
{
this->distance = distance;
this->delta_angle = delta_angle;
}
virtual bool Execute(Event event);
protected:
float distance, delta_angle;
};
class GrobbulusRotateAction : public RotateAroundTheCenterPointAction
{
public:
GrobbulusRotateAction(PlayerbotAI* botAI)
: RotateAroundTheCenterPointAction(botAI, "rotate grobbulus", 3281.23f, -3310.38f, 35.0f, 8, true, M_PI) {}
virtual bool isUseful() override
{
return RotateAroundTheCenterPointAction::isUseful() && botAI->IsMainTank(bot) &&
AI_VALUE2(bool, "has aggro", "boss target");
}
uint32 GetCurrWaypoint() override;
};
class GrobblulusMoveCenterAction : public MoveInsideAction
{
public:
GrobblulusMoveCenterAction(PlayerbotAI* ai) : MoveInsideAction(ai, 3281.23f, -3310.38f, 5.0f) {}
};
class GrobbulusMoveAwayAction : public MovementAction
{
public:
GrobbulusMoveAwayAction(PlayerbotAI* ai, float distance = 18.0f)
: MovementAction(ai, "grobbulus move away"), distance(distance)
{
}
bool Execute(Event event) override;
private:
float distance;
};
//class HeiganDanceAction : public MovementAction
//{
//public:
// HeiganDanceAction(PlayerbotAI* ai) : MovementAction(ai, "heigan dance")
// {
// this->last_eruption_ms = 0;
// this->platform_phase = false;
// ResetSafe();
// waypoints.push_back(std::make_pair(2794.88f, -3668.12f));
// waypoints.push_back(std::make_pair(2775.49f, -3674.43f));
// waypoints.push_back(std::make_pair(2762.30f, -3684.59f));
// waypoints.push_back(std::make_pair(2755.99f, -3703.96f));
// platform = std::make_pair(2794.26f, -3706.67f);
// }
//
//protected:
// bool CalculateSafe();
// void ResetSafe()
// {
// curr_safe = 0;
// curr_dir = 1;
// }
// void NextSafe()
// {
// curr_safe += curr_dir;
// if (curr_safe == 3 || curr_safe == 0)
// {
// curr_dir = -curr_dir;
// }
// }
// uint32 last_eruption_ms;
// bool platform_phase;
// uint32 curr_safe, curr_dir;
// std::vector<std::pair<float, float>> waypoints;
// std::pair<float, float> platform;
//};
//
//class HeiganDanceMeleeAction : public HeiganDanceAction
//{
//public:
// HeiganDanceMeleeAction(PlayerbotAI* ai) : HeiganDanceAction(ai) {}
// virtual bool Execute(Event event);
//};
//
//class HeiganDanceRangedAction : public HeiganDanceAction
//{
//public:
// HeiganDanceRangedAction(PlayerbotAI* ai) : HeiganDanceAction(ai) {}
// virtual bool Execute(Event event);
//};
class ThaddiusAttackNearestPetAction : public AttackAction
{
public:
ThaddiusAttackNearestPetAction(PlayerbotAI* ai) : AttackAction(ai, "thaddius attack nearest pet"), helper(ai) {}
virtual bool Execute(Event event);
virtual bool isUseful();
private:
ThaddiusBossHelper helper;
};
// class ThaddiusMeleeToPlaceAction : public MovementAction
// {
// public:
// ThaddiusMeleeToPlaceAction(PlayerbotAI* ai) : MovementAction(ai, "thaddius melee to place") {}
// virtual bool Execute(Event event);
// virtual bool isUseful();
// };
// class ThaddiusRangedToPlaceAction : public MovementAction
// {
// public:
// ThaddiusRangedToPlaceAction(PlayerbotAI* ai) : MovementAction(ai, "thaddius ranged to place") {}
// virtual bool Execute(Event event);
// virtual bool isUseful();
// };
class ThaddiusMoveToPlatformAction : public MovementAction
{
public:
ThaddiusMoveToPlatformAction(PlayerbotAI* ai) : MovementAction(ai, "thaddius move to platform") {}
virtual bool Execute(Event event);
virtual bool isUseful();
};
class ThaddiusMovePolarityAction : public MovementAction
{
public:
ThaddiusMovePolarityAction(PlayerbotAI* ai) : MovementAction(ai, "thaddius move polarity") {}
virtual bool Execute(Event event);
virtual bool isUseful();
};
class RazuviousUseObedienceCrystalAction : public MovementAction
{
public:
RazuviousUseObedienceCrystalAction(PlayerbotAI* ai)
: MovementAction(ai, "razuvious use obedience crystal"), helper(ai)
{
}
bool Execute(Event event) override;
private:
RazuviousBossHelper helper;
};
class RazuviousTargetAction : public AttackAction
{
public:
RazuviousTargetAction(PlayerbotAI* ai) : AttackAction(ai, "razuvious target"), helper(ai) {}
bool Execute(Event event) override;
private:
RazuviousBossHelper helper;
};
class HorsemanAttractAlternativelyAction : public AttackAction
{
public:
HorsemanAttractAlternativelyAction(PlayerbotAI* ai) : AttackAction(ai, "horseman attract alternatively"), helper(ai)
{
}
bool Execute(Event event) override;
protected:
FourhorsemanBossHelper helper;
};
class HorsemanAttactInOrderAction : public AttackAction
{
public:
HorsemanAttactInOrderAction(PlayerbotAI* ai) : AttackAction(ai, "horseman attact in order"), helper(ai) {}
bool Execute(Event event) override;
protected:
FourhorsemanBossHelper helper;
};
// class SapphironGroundMainTankPositionAction : public MovementAction
// {
// public:
// SapphironGroundMainTankPositionAction(PlayerbotAI* ai) : MovementAction(ai, "sapphiron ground main tank
// position") {} virtual bool Execute(Event event);
// };
class SapphironGroundPositionAction : public MovementAction
{
public:
SapphironGroundPositionAction(PlayerbotAI* ai) : MovementAction(ai, "sapphiron ground position"), helper(ai) {}
bool Execute(Event event) override;
protected:
SapphironBossHelper helper;
};
class SapphironFlightPositionAction : public MovementAction
{
public:
SapphironFlightPositionAction(PlayerbotAI* ai) : MovementAction(ai, "sapphiron flight position"), helper(ai) {}
bool Execute(Event event) override;
protected:
SapphironBossHelper helper;
bool MoveToNearestIcebolt();
};
// class SapphironAvoidChillAction : public MovementAction
// {
// public:
// SapphironAvoidChillAction(PlayerbotAI* ai) : MovementAction(ai, "sapphiron avoid chill") {}
// virtual bool Execute(Event event);
// };
class KelthuzadChooseTargetAction : public AttackAction
{
public:
KelthuzadChooseTargetAction(PlayerbotAI* ai) : AttackAction(ai, "kel'thuzad choose target"), helper(ai) {}
virtual bool Execute(Event event);
private:
KelthuzadBossHelper helper;
};
class KelthuzadPositionAction : public MovementAction
{
public:
KelthuzadPositionAction(PlayerbotAI* ai) : MovementAction(ai, "kel'thuzad position"), helper(ai) {}
virtual bool Execute(Event event);
private:
KelthuzadBossHelper helper;
};
class AnubrekhanChooseTargetAction : public AttackAction
{
public:
AnubrekhanChooseTargetAction(PlayerbotAI* ai) : AttackAction(ai, "anub'rekhan choose target") {}
bool Execute(Event event) override;
};
class AnubrekhanPositionAction : public RotateAroundTheCenterPointAction
{
public:
AnubrekhanPositionAction(PlayerbotAI* ai)
: RotateAroundTheCenterPointAction(ai, "anub'rekhan position", 3272.49f, -3476.27f, 45.0f, 16) {}
bool Execute(Event event) override;
};
class GluthChooseTargetAction : public AttackAction
{
public:
GluthChooseTargetAction(PlayerbotAI* ai) : AttackAction(ai, "gluth choose target"), helper(ai) {}
bool Execute(Event event) override;
private:
GluthBossHelper helper;
};
class GluthPositionAction : public RotateAroundTheCenterPointAction
{
public:
GluthPositionAction(PlayerbotAI* ai)
: RotateAroundTheCenterPointAction(ai, "gluth position", 3293.61f, -3149.01f, 12.0f, 12), helper(ai) {}
bool Execute(Event event) override;
private:
GluthBossHelper helper;
};
class GluthSlowdownAction : public Action
{
public:
GluthSlowdownAction(PlayerbotAI* ai) : Action(ai, "gluth slowdown"), helper(ai) {}
bool Execute(Event event) override;
private:
GluthBossHelper helper;
};
class LoathebPositionAction : public MovementAction
{
public:
LoathebPositionAction(PlayerbotAI* ai) : MovementAction(ai, "loatheb position"), helper(ai) {}
virtual bool Execute(Event event);
private:
LoathebBossHelper helper;
};
class LoathebChooseTargetAction : public AttackAction
{
public:
LoathebChooseTargetAction(PlayerbotAI* ai) : AttackAction(ai, "loatheb choose target"), helper(ai) {}
virtual bool Execute(Event event);
private:
LoathebBossHelper helper;
};
//class PatchwerkRangedPositionAction : public MovementAction
//{
//public:
// PatchwerkRangedPositionAction(PlayerbotAI* ai) : MovementAction(ai, "patchwerk ranged position") {}
// bool Execute(Event event) override;
//};
#endif

View File

@@ -0,0 +1,81 @@
#include "RaidNaxxActions.h"
#include "ObjectGuid.h"
#include "Playerbots.h"
bool AnubrekhanChooseTargetAction::Execute(Event /*event*/)
{
GuidVector attackers = context->GetValue<GuidVector>("attackers")->Get();
Unit* target = nullptr;
Unit* target_boss = nullptr;
std::vector<Unit*> target_guards;
for (ObjectGuid const guid : attackers)
{
Unit* unit = botAI->GetUnit(guid);
if (!unit)
continue;
if (botAI->EqualLowercaseName(unit->GetName(), "crypt guard"))
target_guards.push_back(unit);
if (botAI->EqualLowercaseName(unit->GetName(), "anub'rekhan"))
target_boss = unit;
}
if (botAI->IsMainTank(bot))
target = target_boss;
else
{
if (target_guards.size() == 0)
target = target_boss;
else
{
if (botAI->IsAssistTank(bot))
{
for (Unit* t : target_guards)
{
if (target == nullptr || (target->GetVictim() && target->GetVictim()->ToPlayer() &&
botAI->IsTank(target->GetVictim()->ToPlayer())))
target = t;
}
}
else
{
for (Unit* t : target_guards)
{
if (target == nullptr || target->GetHealthPct() > t->GetHealthPct())
target = t;
}
}
}
}
if (context->GetValue<Unit*>("current target")->Get() == target)
return false;
return Attack(target);
}
bool AnubrekhanPositionAction::Execute(Event /*event*/)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "anub'rekhan");
if (!boss)
return false;
bool inPhase = botAI->HasAura("locust swarm", boss) || boss->GetCurrentSpell(CURRENT_GENERIC_SPELL);
if (inPhase)
{
if (botAI->IsMainTank(bot))
{
uint32 nearest = FindNearestWaypoint();
uint32 next_point;
if (inPhase)
next_point = (nearest + 1) % intervals;
else
next_point = nearest;
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);
}
return false;
}

View File

@@ -0,0 +1,3 @@
#include "RaidNaxxActions.h"
// Reserved for Faerlina-specific actions.

View File

@@ -0,0 +1,59 @@
#include "RaidNaxxActions.h"
#include "Playerbots.h"
bool HorsemanAttractAlternativelyAction::Execute(Event /*event*/)
{
if (!helper.UpdateBossAI())
return false;
helper.CalculatePosToGo(bot);
auto [posX, posY] = helper.CurrentAttractPos();
if (MoveTo(bot->GetMapId(), posX, posY, helper.posZ, false, false, false, false, MovementPriority::MOVEMENT_COMBAT))
return true;
Unit* attackTarget = helper.CurrentAttackTarget();
if (context->GetValue<Unit*>("current target")->Get() != attackTarget)
return Attack(attackTarget);
return false;
}
bool HorsemanAttactInOrderAction::Execute(Event /*event*/)
{
if (!helper.UpdateBossAI())
return false;
Unit* target = nullptr;
Unit* thane = AI_VALUE2(Unit*, "find target", "thane korth'azz");
Unit* lady = AI_VALUE2(Unit*, "find target", "lady blaumeux");
Unit* sir = AI_VALUE2(Unit*, "find target", "sir zeliek");
Unit* fourth = AI_VALUE2(Unit*, "find target", "baron rivendare");
if (!fourth)
fourth = AI_VALUE2(Unit*, "find target", "highlord mograine");
std::vector<Unit*> attack_order;
if (botAI->IsAssistTank(bot))
attack_order = {fourth, thane, lady, sir};
else
attack_order = {thane, fourth, lady, sir};
for (Unit* t : attack_order)
{
if (t && t->IsAlive())
{
target = t;
break;
}
}
if (target)
{
if (context->GetValue<Unit*>("current target")->Get() == target && botAI->GetState() == BOT_STATE_COMBAT)
return false;
if (!bot->IsWithinLOSInMap(target))
return MoveNear(target, 22.0f, MovementPriority::MOVEMENT_COMBAT);
return Attack(target);
}
return false;
}

View File

@@ -0,0 +1,178 @@
#include "RaidNaxxActions.h"
#include "PlayerbotAIConfig.h"
#include "Playerbots.h"
#include "SharedDefines.h"
bool GluthChooseTargetAction::Execute(Event /*event*/)
{
if (!helper.UpdateBossAI())
return false;
GuidVector attackers = context->GetValue<GuidVector>("possible targets")->Get();
Unit* target = nullptr;
Unit* target_boss = nullptr;
std::vector<Unit*> target_zombies;
for (GuidVector::iterator i = attackers.begin(); i != attackers.end(); ++i)
{
Unit* unit = botAI->GetUnit(*i);
if (!unit)
continue;
if (!unit->IsAlive())
continue;
if (botAI->EqualLowercaseName(unit->GetName(), "zombie chow"))
target_zombies.push_back(unit);
if (botAI->EqualLowercaseName(unit->GetName(), "gluth"))
target_boss = unit;
}
if (botAI->IsMainTank(bot) || botAI->IsAssistTankOfIndex(bot, 0))
target = target_boss;
else if (botAI->IsAssistTankOfIndex(bot, 1))
{
for (Unit* t : target_zombies)
{
if (t->GetHealthPct() > helper.decimatedZombiePct && t->GetVictim() != bot && t->GetDistance2d(bot) <= 10.0f)
{
if (!target || t->GetDistance2d(bot) < target->GetDistance2d(bot))
target = t;
}
}
}
else if (botAI->GetClassIndex(bot, CLASS_HUNTER) == 0 || botAI->GetClassIndex(bot, CLASS_HUNTER) == 1)
{
// prevent zombie go straight to gluth
for (Unit* t : target_zombies)
{
if (t->GetHealthPct() > helper.decimatedZombiePct && t->GetVictim() == target_boss &&
t->GetDistance2d(bot) <= sPlayerbotAIConfig.spellDistance)
{
if (!target || t->GetDistance2d(bot) < target->GetDistance2d(bot))
target = t;
}
}
if (!target)
target = target_boss;
}
else
{
for (Unit* t : target_zombies)
{
if (t->GetHealthPct() <= helper.decimatedZombiePct)
{
if (target == nullptr ||
target->GetDistance2d(helper.mainTankPos25.first, helper.mainTankPos25.second) >
t->GetDistance2d(helper.mainTankPos25.first, helper.mainTankPos25.second))
target = t;
}
}
if (target == nullptr)
target = target_boss;
}
if (!target || context->GetValue<Unit*>("current target")->Get() == target)
return false;
if (target_boss && target == target_boss)
return Attack(target, true);
return Attack(target, false);
// return Attack(target);
}
bool GluthPositionAction::Execute(Event /*event*/)
{
if (!helper.UpdateBossAI())
return false;
bool raid25 = bot->GetRaidDifficulty() == RAID_DIFFICULTY_25MAN_NORMAL;
if (botAI->IsMainTank(bot) || botAI->IsAssistTankOfIndex(bot, 0))
{
if (AI_VALUE2(bool, "has aggro", "boss target"))
{
if (raid25)
{
if (MoveTo(NAXX_MAP_ID, helper.mainTankPos25.first, helper.mainTankPos25.second, bot->GetPositionZ(), false, false, false,
false, MovementPriority::MOVEMENT_COMBAT))
return true;
return MoveInside(NAXX_MAP_ID, helper.mainTankPos25.first, helper.mainTankPos25.second, bot->GetPositionZ(), 2.0f,
MovementPriority::MOVEMENT_COMBAT);
}
else
{
if (MoveTo(NAXX_MAP_ID, helper.mainTankPos10.first, helper.mainTankPos10.second, bot->GetPositionZ(), false, false, false,
false, MovementPriority::MOVEMENT_COMBAT))
return true;
return MoveInside(NAXX_MAP_ID, helper.mainTankPos10.first, helper.mainTankPos10.second, bot->GetPositionZ(), 2.0f,
MovementPriority::MOVEMENT_COMBAT);
}
}
}
else if (botAI->IsAssistTankOfIndex(bot, 1))
{
if (helper.BeforeDecimate())
{
if (MoveTo(bot->GetMapId(), helper.beforeDecimatePos.first, helper.beforeDecimatePos.second, bot->GetPositionZ(), false, false,
false, false, MovementPriority::MOVEMENT_COMBAT))
return true;
return MoveInside(bot->GetMapId(), helper.beforeDecimatePos.first, helper.beforeDecimatePos.second, bot->GetPositionZ(), 2.0f,
MovementPriority::MOVEMENT_COMBAT);
}
else
{
if (AI_VALUE2(bool, "has aggro", "current target"))
{
uint32 nearest = FindNearestWaypoint();
uint32 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);
}
}
}
else if (botAI->IsRangedDps(bot))
{
if (raid25)
{
if (botAI->GetClassIndex(bot, CLASS_HUNTER) == 0)
return MoveInside(NAXX_MAP_ID, helper.leftSlowDownPos.first, helper.leftSlowDownPos.second, bot->GetPositionZ(), 0.0f,
MovementPriority::MOVEMENT_COMBAT);
if (botAI->GetClassIndex(bot, CLASS_HUNTER) == 1)
return MoveInside(NAXX_MAP_ID, helper.rightSlowDownPos.first, helper.rightSlowDownPos.second, bot->GetPositionZ(), 0.0f,
MovementPriority::MOVEMENT_COMBAT);
}
return MoveInside(NAXX_MAP_ID, helper.rangedPos.first, helper.rangedPos.second, bot->GetPositionZ(), 3.0f,
MovementPriority::MOVEMENT_COMBAT);
}
else if (botAI->IsHeal(bot))
return MoveInside(NAXX_MAP_ID, helper.healPos.first, helper.healPos.second, bot->GetPositionZ(), 0.0f,
MovementPriority::MOVEMENT_COMBAT);
return false;
}
bool GluthSlowdownAction::Execute(Event /*event*/)
{
if (!helper.UpdateBossAI())
return false;
bool raid25 = bot->GetRaidDifficulty() == RAID_DIFFICULTY_25MAN_NORMAL;
if (!raid25)
return false;
if (helper.JustStartCombat())
return false;
switch (bot->getClass())
{
case CLASS_HUNTER:
return botAI->CastSpell("frost trap", bot);
break;
default:
break;
}
return false;
}

View File

@@ -0,0 +1,3 @@
#include "RaidNaxxActions.h"
// Reserved for Gothik-specific actions.

View File

@@ -0,0 +1,46 @@
#include "RaidNaxxActions.h"
#include "Playerbots.h"
bool GrobbulusGoBehindAction::Execute(Event /*event*/)
{
Unit* boss = AI_VALUE(Unit*, "boss target");
if (!boss)
return false;
// Position* pos = boss->GetPosition();
float orientation = boss->GetOrientation() + M_PI + delta_angle;
float x = boss->GetPositionX();
float y = boss->GetPositionY();
float z = boss->GetPositionZ();
float rx = x + cos(orientation) * distance;
float ry = y + sin(orientation) * distance;
return MoveTo(bot->GetMapId(), rx, ry, z, false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
bool GrobbulusMoveAwayAction::Execute(Event /*event*/)
{
Unit* boss = AI_VALUE(Unit*, "boss target");
if (!boss)
return false;
const float currentDistance = bot->GetExactDist2d(boss);
if (currentDistance >= distance)
return false;
const float angle = boss->GetAngle(bot);
const float x = boss->GetPositionX() + cos(angle) * distance;
const float y = boss->GetPositionY() + sin(angle) * distance;
const float z = bot->GetPositionZ();
return MoveTo(bot->GetMapId(), x, y, z, false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
uint32 GrobbulusRotateAction::GetCurrWaypoint()
{
uint32 current = FindNearestWaypoint();
if (clockwise)
return (current + 1) % intervals;
return (current + intervals - 1) % intervals;
}

View File

@@ -0,0 +1,77 @@
#include "Playerbots.h"
#include "RaidNaxxActions.h"
#include "RaidNaxxSpellIds.h"
#include "Spell.h"
#include "Timer.h"
//bool HeiganDanceAction::CalculateSafe()
//{
// Unit* boss = AI_VALUE2(Unit*, "find target", "heigan the unclean");
// if (!boss)
// {
// return false;
// }
// uint32 now = getMSTime();
// platform_phase = boss->IsWithinDist2d(platform.first, platform.second, 10.0f);
// if (last_eruption_ms != 0 && now - last_eruption_ms > 15000)
// {
// ResetSafe();
// }
// if (boss->HasUnitState(UNIT_STATE_CASTING))
// {
// Spell* spell = boss->GetCurrentSpell(CURRENT_GENERIC_SPELL);
// if (!spell)
// {
// spell = boss->GetCurrentSpell(CURRENT_CHANNELED_SPELL);
// }
// if (spell)
// {
// SpellInfo const* info = spell->GetSpellInfo();
// bool isEruption = NaxxSpellIds::MatchesAnySpellId(info, {NaxxSpellIds::Eruption10});
// if (!isEruption && info && info->SpellName[LOCALE_enUS])
// {
// // Fallback to name for custom spell data.
// isEruption = botAI->EqualLowercaseName(info->SpellName[LOCALE_enUS], "eruption");
// }
// if (isEruption)
// {
// if (last_eruption_ms == 0 || now - last_eruption_ms > 500)
// {
// NextSafe();
// }
// last_eruption_ms = now;
// }
// }
// }
// return true;
//}
//
//bool HeiganDanceMeleeAction::Execute(Event event)
//{
// CalculateSafe();
// if (!platform_phase && botAI->IsMainTank(bot) && !AI_VALUE2(bool, "has aggro", "boss target"))
// {
// return false;
// }
// assert(curr_safe >= 0 && curr_safe <= 3);
// return MoveInside(bot->GetMapId(), waypoints[curr_safe].first, waypoints[curr_safe].second, bot->GetPositionZ(),
// botAI->IsMainTank(bot) ? 0 : 0, MovementPriority::MOVEMENT_COMBAT);
//}
//
//bool HeiganDanceRangedAction::Execute(Event event)
//{
// CalculateSafe();
// if (!platform_phase)
// {
// if (MoveTo(bot->GetMapId(), platform.first, platform.second, 276.54f, false, false, false, false,
// MovementPriority::MOVEMENT_COMBAT))
// {
// return true;
// }
// return MoveInside(bot->GetMapId(), platform.first, platform.second, 276.54f, 2.0f,
// MovementPriority::MOVEMENT_COMBAT);
// }
// botAI->InterruptSpell();
// return MoveInside(bot->GetMapId(), waypoints[curr_safe].first, waypoints[curr_safe].second, bot->GetPositionZ(), 0,
// MovementPriority::MOVEMENT_COMBAT);
//}

View File

@@ -0,0 +1,177 @@
#include "RaidNaxxActions.h"
#include "PlayerbotAIConfig.h"
#include "Playerbots.h"
bool KelthuzadChooseTargetAction::Execute(Event /*event*/)
{
if (!helper.UpdateBossAI())
return false;
GuidVector attackers = context->GetValue<GuidVector>("attackers")->Get();
Unit* target = nullptr;
Unit *target_soldier = nullptr, *target_weaver = nullptr, *target_abomination = nullptr, *target_kelthuzad = nullptr,
*target_guardian = nullptr;
for (auto i = attackers.begin(); i != attackers.end(); ++i)
{
Unit* unit = botAI->GetUnit(*i);
if (!unit)
continue;
if (botAI->EqualLowercaseName(unit->GetName(), "guardian of icecrown"))
{
if (!target_guardian)
target_guardian = unit;
else if (unit->GetVictim() && target_guardian->GetVictim() && unit->GetVictim()->ToPlayer() &&
target_guardian->GetVictim()->ToPlayer() && !botAI->IsAssistTank(unit->GetVictim()->ToPlayer()) &&
botAI->IsAssistTank(target_guardian->GetVictim()->ToPlayer()))
{
target_guardian = unit;
}
else if (unit->GetVictim() && target_guardian->GetVictim() && unit->GetVictim()->ToPlayer() &&
target_guardian->GetVictim()->ToPlayer() && !botAI->IsAssistTank(unit->GetVictim()->ToPlayer()) &&
!botAI->IsAssistTank(target_guardian->GetVictim()->ToPlayer()) &&
target_guardian->GetDistance2d(helper.center.first, helper.center.second) >
bot->GetDistance2d(unit))
{
target_guardian = unit;
}
}
if (unit->GetDistance2d(helper.center.first, helper.center.second) > 30.0f)
continue;
if (bot->GetDistance2d(unit) > sPlayerbotAIConfig.spellDistance)
continue;
if (botAI->EqualLowercaseName(unit->GetName(), "unstoppable abomination"))
{
if (target_abomination == nullptr ||
target_abomination->GetDistance2d(helper.center.first, helper.center.second) >
unit->GetDistance2d(helper.center.first, helper.center.second))
{
target_abomination = unit;
}
}
if (botAI->EqualLowercaseName(unit->GetName(), "soldier of the frozen wastes"))
{
if (target_soldier == nullptr ||
target_soldier->GetDistance2d(helper.center.first, helper.center.second) >
unit->GetDistance2d(helper.center.first, helper.center.second))
{
target_soldier = unit;
}
}
if (botAI->EqualLowercaseName(unit->GetName(), "soul weaver"))
{
if (target_weaver == nullptr || target_weaver->GetDistance2d(helper.center.first, helper.center.second) >
unit->GetDistance2d(helper.center.first, helper.center.second))
target_weaver = unit;
}
if (botAI->EqualLowercaseName(unit->GetName(), "kel'thuzad"))
target_kelthuzad = unit;
}
std::vector<Unit*> targets;
if (botAI->IsRanged(bot))
{
if (botAI->GetRangedDpsIndex(bot) <= 1)
targets = {target_soldier, target_weaver, target_abomination, target_kelthuzad};
else
targets = {target_weaver, target_soldier, target_abomination, target_kelthuzad};
}
else if (botAI->IsAssistTank(bot))
targets = {target_abomination, target_guardian, target_kelthuzad};
else
targets = {target_abomination, target_kelthuzad};
for (Unit* t : targets)
{
if (t)
{
target = t;
break;
}
}
if (context->GetValue<Unit*>("current target")->Get() == target)
return false;
if (target_kelthuzad && target == target_kelthuzad)
return Attack(target, true);
return Attack(target, false);
}
bool KelthuzadPositionAction::Execute(Event /*event*/)
{
if (!helper.UpdateBossAI())
return false;
if (helper.IsPhaseOne())
{
if (AI_VALUE(Unit*, "current target") == nullptr)
return MoveInside(NAXX_MAP_ID, helper.center.first, helper.center.second, bot->GetPositionZ(), 3.0f,
MovementPriority::MOVEMENT_COMBAT);
}
else if (helper.IsPhaseTwo())
{
Unit* shadow_fissure = helper.GetAnyShadowFissure();
if (!shadow_fissure || !bot->IsWithinDistInMap(shadow_fissure, 10.0f))
{
float distance, angle;
if (botAI->IsMainTank(bot))
{
if (AI_VALUE2(bool, "has aggro", "current target"))
return MoveTo(NAXX_MAP_ID, helper.tank_pos.first, helper.tank_pos.second, bot->GetPositionZ(), false, false, false,
false, MovementPriority::MOVEMENT_COMBAT);
else
return false;
}
else if (botAI->IsRanged(bot))
{
uint32 index = botAI->GetRangedIndex(bot);
if (index < 8)
{
distance = 20.0f;
angle = index * M_PI / 4;
}
else
{
distance = 32.0f;
angle = (index - 8) * M_PI / 4;
}
float dx, dy;
dx = helper.center.first + cos(angle) * distance;
dy = helper.center.second + sin(angle) * distance;
return MoveTo(NAXX_MAP_ID, dx, dy, bot->GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
else if (botAI->IsTank(bot))
{
Unit* cur_tar = AI_VALUE(Unit*, "current target");
if (cur_tar && cur_tar->GetVictim() && cur_tar->GetVictim()->ToPlayer() &&
botAI->EqualLowercaseName(cur_tar->GetName(), "guardian of icecrown") &&
botAI->IsAssistTank(cur_tar->GetVictim()->ToPlayer()))
{
return MoveTo(NAXX_MAP_ID, helper.assist_tank_pos.first, helper.assist_tank_pos.second, bot->GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
else
return false;
}
}
else
{
float dx, dy;
float angle;
if (!botAI->IsRanged(bot))
angle = shadow_fissure->GetAngle(helper.center.first, helper.center.second);
else
angle = bot->GetAngle(shadow_fissure) + M_PI;
dx = shadow_fissure->GetPositionX() + cos(angle) * 10.0f;
dy = shadow_fissure->GetPositionY() + sin(angle) * 10.0f;
return MoveTo(NAXX_MAP_ID, dx, dy, bot->GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
}
return false;
}

View File

@@ -0,0 +1,55 @@
#include "RaidNaxxActions.h"
#include "Playerbots.h"
bool LoathebPositionAction::Execute(Event /*event*/)
{
if (!helper.UpdateBossAI())
return false;
if (botAI->IsTank(bot))
{
if (AI_VALUE2(bool, "has aggro", "boss target"))
return MoveTo(533, helper.mainTankPos.first, helper.mainTankPos.second, bot->GetPositionZ(), false, false, false, false,
MovementPriority::MOVEMENT_COMBAT);
}
else if (botAI->IsRanged(bot))
return MoveInside(533, helper.rangePos.first, helper.rangePos.second, bot->GetPositionZ(), 1.0f,
MovementPriority::MOVEMENT_COMBAT);
return false;
}
bool LoathebChooseTargetAction::Execute(Event /*event*/)
{
if (!helper.UpdateBossAI())
return false;
GuidVector attackers = context->GetValue<GuidVector>("attackers")->Get();
Unit* target = nullptr;
Unit* target_boss = nullptr;
Unit* target_spore = nullptr;
for (auto i = attackers.begin(); i != attackers.end(); ++i)
{
Unit* unit = botAI->GetUnit(*i);
if (!unit)
continue;
if (!unit->IsAlive())
continue;
if (botAI->EqualLowercaseName(unit->GetName(), "spore"))
target_spore = unit;
if (botAI->EqualLowercaseName(unit->GetName(), "loatheb"))
target_boss = unit;
}
if (target_spore && bot->GetDistance2d(target_spore) <= 1.0f)
target = target_spore;
else
target = target_boss;
if (!target || context->GetValue<Unit*>("current target")->Get() == target)
return false;
return Attack(target);
}

View File

@@ -0,0 +1,3 @@
#include "RaidNaxxActions.h"
// Reserved for Maexxna-specific actions.

View File

@@ -0,0 +1,3 @@
#include "RaidNaxxActions.h"
// Reserved for Noth-specific actions.

View File

@@ -0,0 +1,31 @@
#include "RaidNaxxActions.h"
#include <algorithm>
#include <cmath>
//bool PatchwerkRangedPositionAction::Execute(Event event)
//{
// Unit* boss = AI_VALUE2(Unit*, "find target", "patchwerk");
// if (!boss)
// return false;
//
// constexpr float minDistance = 12.0f;
// constexpr float maxDistance = 15.0f;
// const float distance = bot->GetExactDist2d(boss);
//
// if (distance >= minDistance && distance <= maxDistance)
// return false;
//
// const float desiredDistance = std::clamp(distance, minDistance, maxDistance);
// float angle = boss->GetAngle(bot);
//
// if (distance < 0.1f)
// angle = boss->GetOrientation();
//
// const float x = boss->GetPositionX() + std::cos(angle) * desiredDistance;
// const float y = boss->GetPositionY() + std::sin(angle) * desiredDistance;
// const float z = bot->GetPositionZ();
//
// return MoveTo(boss->GetMapId(), x, y, z, false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true,
// false);
//}

View File

@@ -0,0 +1,147 @@
#include "RaidNaxxActions.h"
#include "ObjectGuid.h"
#include "PlayerbotAIConfig.h"
#include "Playerbots.h"
#include "SharedDefines.h"
bool RazuviousUseObedienceCrystalAction::Execute(Event /*event*/)
{
if (!helper.UpdateBossAI())
return false;
// bot->GetCharm
if (Unit* charm = bot->GetCharm())
{
Unit* target = AI_VALUE2(Unit*, "find target", "instructor razuvious");
if (!target)
return false;
if (charm->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_ACTIVE) == NULL_MOTION_TYPE)
{
charm->GetMotionMaster()->Clear();
charm->GetMotionMaster()->MoveChase(target);
charm->GetAI()->AttackStart(target);
}
Aura* forceObedience = botAI->GetAura("force obedience", charm);
uint32 duration_time;
if (!forceObedience)
{
forceObedience = botAI->GetAura("mind control", charm);
duration_time = 60000;
}
else
duration_time = 90000;
if (!forceObedience)
return false;
if (charm->GetDistance(target) <= 0.51f)
{
// taunt
bool tauntUseful = true;
if (forceObedience->GetDuration() <= (duration_time - 5000))
{
if (target->GetVictim() && botAI->HasAura(29061, target->GetVictim()))
tauntUseful = false;
if (forceObedience->GetDuration() <= 3000)
tauntUseful = false;
}
if (forceObedience->GetDuration() >= (duration_time - 500))
tauntUseful = false;
if (tauntUseful && !charm->HasSpellCooldown(29060))
{
// shield
if (!charm->HasSpellCooldown(29061))
{
charm->CastSpell(charm, 29061, true);
charm->AddSpellCooldown(29061, 0, 30 * 1000);
}
charm->CastSpell(target, 29060, true);
charm->AddSpellCooldown(29060, 0, 20 * 1000);
}
// strike
if (!charm->HasSpellCooldown(61696))
{
charm->CastSpell(target, 61696, true);
charm->AddSpellCooldown(61696, 0, 4 * 1000);
}
}
}
else
{
Difficulty diff = bot->GetRaidDifficulty();
if (diff == RAID_DIFFICULTY_10MAN_NORMAL)
{
GuidVector npcs = AI_VALUE(GuidVector, "nearest npcs");
for (auto i = npcs.begin(); i != npcs.end(); i++)
{
Creature* unit = botAI->GetCreature(*i);
if (!unit)
continue;
if (botAI->IsMainTank(bot) && unit->GetSpawnId() != 128352)
continue;
if (!botAI->IsMainTank(bot) && unit->GetSpawnId() != 128353)
continue;
if (MoveTo(unit, 0.0f, MovementPriority::MOVEMENT_COMBAT))
return true;
Creature* creature = bot->GetNPCIfCanInteractWith(*i, UNIT_NPC_FLAG_SPELLCLICK);
if (!creature)
continue;
creature->HandleSpellClick(bot);
return true;
}
}
else
{
GuidVector attackers = context->GetValue<GuidVector>("attackers")->Get();
Unit* target = nullptr;
for (auto i = attackers.begin(); i != attackers.end(); ++i)
{
Unit* unit = botAI->GetUnit(*i);
if (!unit)
continue;
if (botAI->EqualLowercaseName(unit->GetName(), "death knight understudy"))
{
target = unit;
break;
}
}
if (target)
{
if (bot->GetDistance2d(target) > sPlayerbotAIConfig.spellDistance)
return MoveNear(target, sPlayerbotAIConfig.spellDistance, MovementPriority::MOVEMENT_COMBAT);
else
return botAI->CastSpell("mind control", target);
}
}
}
return false;
}
bool RazuviousTargetAction::Execute(Event /*event*/)
{
if (!helper.UpdateBossAI())
return false;
Unit* razuvious = AI_VALUE2(Unit*, "find target", "instructor razuvious");
Unit* understudy = AI_VALUE2(Unit*, "find target", "death knight understudy");
Unit* target = nullptr;
if (botAI->IsTank(bot))
target = understudy;
else
target = razuvious;
if (AI_VALUE(Unit*, "current target") == target)
return false;
return Attack(target);
}

View File

@@ -0,0 +1,104 @@
#include "RaidNaxxActions.h"
#include "PlayerbotAIConfig.h"
#include "Playerbots.h"
#include "RaidNaxxBossHelper.h"
#include "RaidNaxxSpellIds.h"
bool SapphironGroundPositionAction::Execute(Event /*event*/)
{
if (!helper.UpdateBossAI())
return false;
if (botAI->IsMainTank(bot))
{
if (AI_VALUE2(bool, "has aggro", "current target"))
return MoveTo(NAXX_MAP_ID, helper.mainTankPos.first, helper.mainTankPos.second, helper.GENERIC_HEIGHT, false, false, false,
false, MovementPriority::MOVEMENT_COMBAT);
return false;
}
if (helper.JustLanded())
{
uint32 index = botAI->GetGroupSlotIndex(bot);
float start_angle = 0.85 * M_PI;
float offset_angle = M_PI * 0.02 * index;
float angle = start_angle + offset_angle;
float distance;
if (botAI->IsRanged(bot))
distance = 35.0f;
else if (botAI->IsHeal(bot))
distance = 30.0f;
else
distance = 5.0f;
float posX = helper.center.first + cos(angle) * distance;
float posY = helper.center.second + sin(angle) * distance;
if (MoveTo(NAXX_MAP_ID, posX, posY, helper.GENERIC_HEIGHT, false, false, false, false, MovementPriority::MOVEMENT_COMBAT))
return true;
return MoveInside(NAXX_MAP_ID, posX, posY, helper.GENERIC_HEIGHT, 2.0f, MovementPriority::MOVEMENT_COMBAT);
}
else
{
std::vector<float> dest;
if (helper.FindPosToAvoidChill(dest))
return MoveTo(NAXX_MAP_ID, dest[0], dest[1], dest[2], false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
return false;
}
bool SapphironFlightPositionAction::Execute(Event /*event*/)
{
if (!helper.UpdateBossAI())
return false;
if (helper.WaitForExplosion())
return MoveToNearestIcebolt();
else
{
std::vector<float> dest;
if (helper.FindPosToAvoidChill(dest))
return MoveTo(NAXX_MAP_ID, dest[0], dest[1], dest[2], false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
return false;
}
bool SapphironFlightPositionAction::MoveToNearestIcebolt()
{
Group* group = bot->GetGroup();
if (!group)
return false;
Group::MemberSlotList const& slots = group->GetMemberSlots();
Player* playerWithIcebolt = nullptr;
float minDistance;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (NaxxSpellIds::HasAnyAura(botAI, member, {NaxxSpellIds::Icebolt10, NaxxSpellIds::Icebolt25}) ||
botAI->HasAura("icebolt", member, false, false, -1, true))
{
if (!playerWithIcebolt || minDistance > bot->GetDistance(member))
{
playerWithIcebolt = member;
minDistance = bot->GetDistance(member);
}
}
}
if (playerWithIcebolt)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "sapphiron");
if (boss)
{
float angle = boss->GetAngle(playerWithIcebolt);
float posX = playerWithIcebolt->GetPositionX() + cos(angle) * 3.0f;
float posY = playerWithIcebolt->GetPositionY() + sin(angle) * 3.0f;
if (MoveTo(NAXX_MAP_ID, posX, posY, helper.GENERIC_HEIGHT, false, false, false, false, MovementPriority::MOVEMENT_COMBAT))
return true;
return MoveNear(playerWithIcebolt, 3.0f, MovementPriority::MOVEMENT_COMBAT);
}
}
return false;
}

View File

@@ -0,0 +1,18 @@
#include "RaidNaxxActions.h"
uint32 RotateAroundTheCenterPointAction::FindNearestWaypoint()
{
float minDistance = 0;
int ret = -1;
for (int i = 0; i < intervals; i++)
{
float w_x = waypoints[i].first, w_y = waypoints[i].second;
float dis = bot->GetDistance2d(w_x, w_y);
if (ret == -1 || dis < minDistance)
{
ret = i;
minDistance = dis;
}
}
return ret;
}

View File

@@ -0,0 +1,134 @@
#include "RaidNaxxActions.h"
#include "PlayerbotAIConfig.h"
#include "Playerbots.h"
#include "RaidNaxxSpellIds.h"
bool ThaddiusAttackNearestPetAction::isUseful()
{
if (!helper.UpdateBossAI())
return false;
if (!helper.IsPhasePet())
return false;
Unit* target = helper.GetNearestPet();
if (!bot->IsWithinDistInMap(target, 50.0f))
return false;
return true;
}
bool ThaddiusAttackNearestPetAction::Execute(Event /*event*/)
{
Unit* target = helper.GetNearestPet();
if (!bot->IsWithinLOSInMap(target))
return MoveTo(target, 0, MovementPriority::MOVEMENT_COMBAT);
if (AI_VALUE(Unit*, "current target") != target)
return Attack(target);
if (botAI->IsTank(bot) && AI_VALUE2(bool, "has aggro", "current target"))
{
std::pair<float, float> posForTank = helper.PetPhaseGetPosForTank();
return MoveTo(533, posForTank.first, posForTank.second, helper.tankPosZ, false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
if (botAI->IsRanged(bot))
{
std::pair<float, float> posForRanged = helper.PetPhaseGetPosForRanged();
return MoveTo(533, posForRanged.first, posForRanged.second, helper.tankPosZ, false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
return false;
}
bool ThaddiusMoveToPlatformAction::isUseful() { return true; }
bool ThaddiusMoveToPlatformAction::Execute(Event /*event*/)
{
std::vector<std::pair<float, float>> position = {
// high left
{3462.99f, -2918.90f},
// high right
{3520.65f, -2976.51f},
// low left
{3471.36f, -2910.65f},
// low right
{3528.80f, -2967.04f},
// center
{3512.19f, -2928.58f},
};
float high_z = 312.00f, low_z = 304.02f;
bool is_left = bot->GetDistance2d(position[0].first, position[0].second) <
bot->GetDistance2d(position[1].first, position[1].second);
if (bot->GetPositionZ() >= (high_z - 3.0f))
{
if (is_left)
{
if (!MoveTo(bot->GetMapId(), position[0].first, position[0].second, high_z, false, false, false, false, MovementPriority::MOVEMENT_COMBAT))
{
float distance = bot->GetExactDist2d(position[0].first, position[0].second);
if (distance < sPlayerbotAIConfig.contactDistance)
JumpTo(bot->GetMapId(), position[2].first, position[2].second, low_z, MovementPriority::MOVEMENT_COMBAT);
// bot->TeleportTo(bot->GetMapId(), position[2].first, position[2].second, low_z, bot->GetOrientation());
}
}
else
{
if (!MoveTo(bot->GetMapId(), position[1].first, position[1].second, high_z, false, false, false, false, MovementPriority::MOVEMENT_COMBAT))
{
float distance = bot->GetExactDist2d(position[1].first, position[1].second);
if (distance < sPlayerbotAIConfig.contactDistance)
JumpTo(bot->GetMapId(), position[3].first, position[3].second, low_z, MovementPriority::MOVEMENT_COMBAT);
// bot->TeleportTo(bot->GetMapId(), position[3].first, position[3].second, low_z, bot->GetOrientation());
}
}
}
else
return MoveTo(bot->GetMapId(), position[4].first, position[4].second, low_z, false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
return true;
}
bool ThaddiusMovePolarityAction::isUseful()
{
return !botAI->IsMainTank(bot) || AI_VALUE2(bool, "has aggro", "current target");
}
bool ThaddiusMovePolarityAction::Execute(Event /*event*/)
{
std::vector<std::pair<float, float>> position = {
// left melee
{3508.29f, -2920.12f},
// left ranged
{3501.72f, -2913.36f},
// right melee
{3519.74f, -2931.69f},
// right ranged
{3524.32f, -2936.26f},
// center melee
{3512.19f, -2928.58f},
// center ranged
{3504.68f, -2936.68f},
};
uint32 idx;
if (NaxxSpellIds::HasAnyAura(
botAI, bot,
{NaxxSpellIds::NegativeCharge10, NaxxSpellIds::NegativeCharge25, NaxxSpellIds::NegativeChargeStack}) ||
botAI->HasAura("negative charge", bot, false, false, -1, true))
{
idx = 0;
}
else if (NaxxSpellIds::HasAnyAura(
botAI, bot,
{NaxxSpellIds::PositiveCharge10, NaxxSpellIds::PositiveCharge25, NaxxSpellIds::PositiveChargeStack}) ||
botAI->HasAura("positive charge", bot, false, false, -1, true))
{
idx = 1;
}
else
{
idx = 2;
}
idx = idx * 2 + botAI->IsRanged(bot);
return MoveTo(bot->GetMapId(), position[idx].first, position[idx].second, bot->GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}

View File

@@ -0,0 +1,322 @@
#include "RaidNaxxMultipliers.h"
#include "ChooseTargetActions.h"
#include "DKActions.h"
#include "DruidActions.h"
#include "DruidBearActions.h"
#include "FollowActions.h"
#include "GenericActions.h"
#include "GenericSpellActions.h"
#include "HunterActions.h"
#include "MageActions.h"
#include "MovementActions.h"
#include "PaladinActions.h"
#include "PriestActions.h"
#include "RaidNaxxActions.h"
#include "RaidNaxxSpellIds.h"
#include "ReachTargetActions.h"
#include "RogueActions.h"
#include "ScriptedCreature.h"
#include "ShamanActions.h"
#include "Spell.h"
#include "UseMeetingStoneAction.h"
#include "WarriorActions.h"
float GrobbulusMultiplier::GetValue(Action* action)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "grobbulus");
if (!boss)
return 1.0f;
if (dynamic_cast<AvoidAoeAction*>(action))
return botAI->IsMainTank(bot) ? 0.0f : 1.0f;
if (dynamic_cast<CombatFormationMoveAction*>(action))
return 0.0f;
return 1.0f;
}
//float HeiganDanceMultiplier::GetValue(Action* action)
//{
// Unit* boss = AI_VALUE2(Unit*, "find target", "heigan the unclean");
// if (!boss)
// {
// return 1.0f;
// }
// bool platform_phase = boss->IsWithinDist2d(2794.26f, -3706.67f, 10.0f);
// bool eruption_casting = false;
// if (boss->HasUnitState(UNIT_STATE_CASTING))
// {
// Spell* spell = boss->GetCurrentSpell(CURRENT_GENERIC_SPELL);
// if (!spell)
// {
// spell = boss->GetCurrentSpell(CURRENT_CHANNELED_SPELL);
// }
// if (spell)
// {
// SpellInfo const* info = spell->GetSpellInfo();
// bool isEruption = NaxxSpellIds::MatchesAnySpellId(info, {NaxxSpellIds::Eruption10});
// if (!isEruption && info && info->SpellName[LOCALE_enUS])
// {
// // Fallback to name for custom spell data.
// isEruption = botAI->EqualLowercaseName(info->SpellName[LOCALE_enUS], "eruption");
// }
// if (isEruption)
// {
// eruption_casting = true;
// }
// }
// }
// if (dynamic_cast<CombatFormationMoveAction*>(action) ||
// dynamic_cast<CastDisengageAction*>(action) ||
// dynamic_cast<CastBlinkBackAction*>(action) )
// {
// return 0.0f;
// }
// if (!platform_phase && !eruption_casting)
// {
// return 1.0f;
// }
// if (dynamic_cast<HeiganDanceAction*>(action) || dynamic_cast<CurePartyMemberAction*>(action))
// {
// return 1.0f;
// }
// if (dynamic_cast<CastSpellAction*>(action) && !dynamic_cast<CastMeleeSpellAction*>(action))
// {
// CastSpellAction* spellAction = dynamic_cast<CastSpellAction*>(action);
// uint32 spellId = AI_VALUE2(uint32, "spell id", spellAction->getSpell());
// SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
// if (!spellInfo)
// {
// return 0.0f;
// }
// uint32 castTime = spellInfo->CalcCastTime();
// if (castTime == 0 && !spellInfo->IsChanneled())
// {
// return 1.0f;
// }
// }
// return 0.0f;
//}
float LoathebGenericMultiplier::GetValue(Action* action)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "loatheb");
if (!boss)
return 1.0f;
context->GetValue<bool>("neglect threat")->Set(true);
if (botAI->GetState() == BOT_STATE_COMBAT &&
(dynamic_cast<DpsAssistAction*>(action) || dynamic_cast<TankAssistAction*>(action) ||
dynamic_cast<CastDebuffSpellOnAttackerAction*>(action) || dynamic_cast<FleeAction*>(action) ||
dynamic_cast<CombatFormationMoveAction*>(action)))
{
return 0.0f;
}
if (!dynamic_cast<CastHealingSpellAction*>(action))
return 1.0f;
Aura* aura = NaxxSpellIds::GetAnyAura(bot, {NaxxSpellIds::NecroticAura10});
if (!aura)
{
// Fallback to name for custom spell data.
aura = botAI->GetAura("necrotic aura", bot);
}
if (!aura || aura->GetDuration() <= 1500)
return 1.0f;
return 0.0f;
}
float ThaddiusGenericMultiplier::GetValue(Action* action)
{
if (!helper.UpdateBossAI())
return 1.0f;
if (dynamic_cast<CombatFormationMoveAction*>(action))
return 0.0f;
// pet phase
if (helper.IsPhasePet() &&
(dynamic_cast<DpsAssistAction*>(action) || dynamic_cast<TankAssistAction*>(action) ||
dynamic_cast<CastDebuffSpellOnAttackerAction*>(action) ||
dynamic_cast<ReachPartyMemberToHealAction*>(action) || dynamic_cast<BuffOnMainTankAction*>(action)))
{
return 0.0f;
}
// die at the same time
Unit* target = AI_VALUE(Unit*, "current target");
Unit* feugen = AI_VALUE2(Unit*, "find target", "feugen");
Unit* stalagg = AI_VALUE2(Unit*, "find target", "stalagg");
if (helper.IsPhasePet() && target && feugen && stalagg && target->GetHealthPct() <= 40 &&
(feugen->GetHealthPct() >= target->GetHealthPct() + 3 || stalagg->GetHealthPct() >= target->GetHealthPct() + 3))
{
if (dynamic_cast<CastSpellAction*>(action) && !dynamic_cast<CastHealingSpellAction*>(action))
return 0.0f;
}
// magnetic pull
// uint32 curr_timer = eventMap->GetTimer();
// // if (curr_phase == 2 && bot->GetPositionZ() > 312.5f && dynamic_cast<MovementAction*>(action))
// {
// if (curr_phase == 2 && (curr_timer % 20000 >= 18000 || curr_timer % 20000 <= 2000) &&
// dynamic_cast<MovementAction*>(action))
// {
// // MotionMaster *mm = bot->GetMotionMaster();
// // mm->Clear();
// return 0.0f;
// }
// thaddius phase
// if (curr_phase == 8 && dynamic_cast<FleeAction*>(action))
// {
// return 0.0f;
// }
return 1.0f;
}
float SapphironGenericMultiplier::GetValue(Action* action)
{
if (!helper.UpdateBossAI())
return 1.0f;
if (dynamic_cast<CastDeathGripAction*>(action) || dynamic_cast<CombatFormationMoveAction*>(action))
return 0.0f;
return 1.0f;
}
float InstructorRazuviousGenericMultiplier::GetValue(Action* action)
{
if (!helper.UpdateBossAI())
return 1.0f;
context->GetValue<bool>("neglect threat")->Set(true);
if (botAI->GetState() == BOT_STATE_COMBAT &&
(dynamic_cast<DpsAssistAction*>(action) || dynamic_cast<TankAssistAction*>(action) ||
dynamic_cast<CastTauntAction*>(action) || dynamic_cast<CastDarkCommandAction*>(action) ||
dynamic_cast<CastHandOfReckoningAction*>(action) || dynamic_cast<CastGrowlAction*>(action)))
{
return 0.0f;
}
return 1.0f;
}
float KelthuzadGenericMultiplier::GetValue(Action* action)
{
if (!helper.UpdateBossAI())
return 1.0f;
if ((dynamic_cast<DpsAssistAction*>(action) || dynamic_cast<TankAssistAction*>(action) ||
dynamic_cast<CastDebuffSpellOnAttackerAction*>(action) || dynamic_cast<FleeAction*>(action)))
{
return 0.0f;
}
if (helper.IsPhaseOne())
{
if (dynamic_cast<CastTotemAction*>(action) || dynamic_cast<CastShadowfiendAction*>(action) ||
dynamic_cast<CastRaiseDeadAction*>(action) || dynamic_cast<CastFeignDeathAction*>(action) ||
dynamic_cast<CastInvisibilityAction*>(action) || dynamic_cast<CastVanishAction*>(action) ||
dynamic_cast<PetAttackAction*>(action))
{
return 0.0f;
}
}
if (helper.IsPhaseTwo())
{
if (dynamic_cast<CastBlizzardAction*>(action) || dynamic_cast<CastFrostNovaAction*>(action))
return 0.0f;
}
return 1.0f;
}
float AnubrekhanGenericMultiplier::GetValue(Action* action)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "anub'rekhan");
if (!boss)
return 1.0f;
if (NaxxSpellIds::HasAnyAura(
botAI, boss, {NaxxSpellIds::LocustSwarm10, NaxxSpellIds::LocustSwarm10Alt, NaxxSpellIds::LocustSwarm25}) ||
botAI->HasAura("locust swarm", boss))
{
if (dynamic_cast<FleeAction*>(action))
return 0.0f;
}
return 1.0f;
}
float FourhorsemanGenericMultiplier::GetValue(Action* action)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "sir zeliek");
if (!boss)
return 1.0f;
context->GetValue<bool>("neglect threat")->Set(true);
if ((dynamic_cast<DpsAssistAction*>(action) || dynamic_cast<TankAssistAction*>(action)))
return 0.0f;
return 1.0f;
}
// float GothikGenericMultiplier::GetValue(Action* action)
// {
// Unit* boss = AI_VALUE2(Unit*, "find target", "gothik the harvester");
// if (!boss)
// {
// return 1.0f;
// }
// BossAI* boss_ai = dynamic_cast<BossAI*>(boss->GetAI());
// EventMap* eventMap = boss_botAI->GetEvents();
// uint32 curr_phase = eventMap->GetPhaseMask();
// if (curr_phase == 1 && (dynamic_cast<FollowAction*>(action)))
// {
// return 0.0f;
// }
// if (curr_phase == 1 && (dynamic_cast<AttackAction*>(action)))
// {
// Unit* target = action->GetTarget();
// if (target == boss)
// {
// return 0.0f;
// }
// }
// return 1.0f;
// }
float GluthGenericMultiplier::GetValue(Action* action)
{
if (!helper.UpdateBossAI())
return 1.0f;
if ((dynamic_cast<DpsAssistAction*>(action) || dynamic_cast<TankAssistAction*>(action) ||
dynamic_cast<FleeAction*>(action) || dynamic_cast<CastDebuffSpellOnAttackerAction*>(action) ||
dynamic_cast<CastStarfallAction*>(action)))
{
return 0.0f;
}
if (botAI->IsMainTank(bot))
{
Aura* aura = NaxxSpellIds::GetAnyAura(bot, {NaxxSpellIds::MortalWound10, NaxxSpellIds::MortalWound25});
if (!aura)
{
// Fallback to name for custom spell data.
aura = botAI->GetAura("mortal wound", bot, false, true);
}
if (aura && aura->GetStackAmount() >= 5)
{
if (dynamic_cast<CastTauntAction*>(action) || dynamic_cast<CastDarkCommandAction*>(action) ||
dynamic_cast<CastHandOfReckoningAction*>(action) || dynamic_cast<CastGrowlAction*>(action))
{
return 0.0f;
}
}
}
if (dynamic_cast<PetAttackAction*>(action))
{
Unit* target = AI_VALUE(Unit*, "current target");
if (helper.IsZombieChow(target))
return 0.0f;
}
return 1.0f;
}

View File

@@ -0,0 +1,115 @@
#ifndef _PLAYERRBOT_RAIDNAXXMULTIPLIERS_H
#define _PLAYERRBOT_RAIDNAXXMULTIPLIERS_H
#include "Multiplier.h"
#include "RaidNaxxBossHelper.h"
class GrobbulusMultiplier : public Multiplier
{
public:
GrobbulusMultiplier(PlayerbotAI* ai) : Multiplier(ai, "grobbulus") {}
public:
virtual float GetValue(Action* action);
};
//class HeiganDanceMultiplier : public Multiplier
//{
//public:
// HeiganDanceMultiplier(PlayerbotAI* ai) : Multiplier(ai, "helgan dance") {}
//
//public:
// virtual float GetValue(Action* action);
//};
class LoathebGenericMultiplier : public Multiplier
{
public:
LoathebGenericMultiplier(PlayerbotAI* ai) : Multiplier(ai, "loatheb generic") {}
public:
virtual float GetValue(Action* action);
};
class ThaddiusGenericMultiplier : public Multiplier
{
public:
ThaddiusGenericMultiplier(PlayerbotAI* ai) : Multiplier(ai, "thaddius generic"), helper(ai) {}
public:
virtual float GetValue(Action* action);
private:
ThaddiusBossHelper helper;
};
class SapphironGenericMultiplier : public Multiplier
{
public:
SapphironGenericMultiplier(PlayerbotAI* ai) : Multiplier(ai, "sapphiron generic"), helper(ai) {}
virtual float GetValue(Action* action);
private:
SapphironBossHelper helper;
};
class InstructorRazuviousGenericMultiplier : public Multiplier
{
public:
InstructorRazuviousGenericMultiplier(PlayerbotAI* ai) : Multiplier(ai, "instructor razuvious generic"), helper(ai) {}
virtual float GetValue(Action* action);
private:
RazuviousBossHelper helper;
};
class KelthuzadGenericMultiplier : public Multiplier
{
public:
KelthuzadGenericMultiplier(PlayerbotAI* ai) : Multiplier(ai, "kelthuzad generic"), helper(ai) {}
virtual float GetValue(Action* action);
private:
KelthuzadBossHelper helper;
};
class AnubrekhanGenericMultiplier : public Multiplier
{
public:
AnubrekhanGenericMultiplier(PlayerbotAI* ai) : Multiplier(ai, "anubrekhan generic") {}
public:
virtual float GetValue(Action* action);
};
class FourhorsemanGenericMultiplier : public Multiplier
{
public:
FourhorsemanGenericMultiplier(PlayerbotAI* ai) : Multiplier(ai, "fourhorseman generic") {}
public:
virtual float GetValue(Action* action);
};
// class GothikGenericMultiplier : public Multiplier
// {
// public:
// GothikGenericMultiplier(PlayerbotAI* ai) : Multiplier(ai, "gothik generic") {}
// public:
// virtual float GetValue(Action* action);
// };
class GluthGenericMultiplier : public Multiplier
{
public:
GluthGenericMultiplier(PlayerbotAI* ai) : Multiplier(ai, "gluth generic"), helper(ai) {}
float GetValue(Action* action) override;
private:
GluthBossHelper helper;
};
#endif

View File

@@ -0,0 +1,95 @@
// /*
// * 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_RAIDNAXXACTIONCONTEXT_H
#define _PLAYERBOT_RAIDNAXXACTIONCONTEXT_H
#include "Action.h"
#include "NamedObjectContext.h"
#include "RaidNaxxActions.h"
class RaidNaxxActionContext : public NamedObjectContext<Action>
{
public:
RaidNaxxActionContext()
{
creators["grobbulus go behind the boss"] = &RaidNaxxActionContext::go_behind_the_boss;
creators["rotate grobbulus"] = &RaidNaxxActionContext::rotate_grobbulus;
creators["grobbulus move center"] = &RaidNaxxActionContext::grobbulus_move_center;
creators["grobbulus move away"] = &RaidNaxxActionContext::grobbulus_move_away;
//creators["heigan dance melee"] = &RaidNaxxActionContext::heigan_dance_melee;
//creators["heigan dance ranged"] = &RaidNaxxActionContext::heigan_dance_ranged;
creators["thaddius attack nearest pet"] = &RaidNaxxActionContext::thaddius_attack_nearest_pet;
// creators["thaddius melee to place"] = &RaidNaxxActionContext::thaddius_tank_to_place;
// creators["thaddius ranged to place"] = &RaidNaxxActionContext::thaddius_ranged_to_place;
creators["thaddius move to platform"] = &RaidNaxxActionContext::thaddius_move_to_platform;
creators["thaddius move polarity"] = &RaidNaxxActionContext::thaddius_move_polarity;
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["sapphiron ground position"] = &RaidNaxxActionContext::sapphiron_ground_position;
creators["sapphiron flight position"] = &RaidNaxxActionContext::sapphiron_flight_position;
creators["kel'thuzad choose target"] = &RaidNaxxActionContext::kelthuzad_choose_target;
creators["kel'thuzad position"] = &RaidNaxxActionContext::kelthuzad_position;
creators["anub'rekhan choose target"] = &RaidNaxxActionContext::anubrekhan_choose_target;
creators["anub'rekhan position"] = &RaidNaxxActionContext::anubrekhan_position;
creators["gluth choose target"] = &RaidNaxxActionContext::gluth_choose_target;
creators["gluth position"] = &RaidNaxxActionContext::gluth_position;
creators["gluth slowdown"] = &RaidNaxxActionContext::gluth_slowdown;
//creators["patchwerk ranged position"] = &RaidNaxxActionContext::patchwerk_ranged_position;
creators["loatheb position"] = &RaidNaxxActionContext::loatheb_position;
creators["loatheb choose target"] = &RaidNaxxActionContext::loatheb_choose_target;
}
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_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); }
static Action* thaddius_attack_nearest_pet(PlayerbotAI* ai) { return new ThaddiusAttackNearestPetAction(ai); }
// static Action* thaddius_tank_to_place(PlayerbotAI* ai) { return new ThaddiusMeleeToPlaceAction(ai); }
// static Action* thaddius_ranged_to_place(PlayerbotAI* ai) { return new ThaddiusRangedToPlaceAction(ai); }
static Action* thaddius_move_to_platform(PlayerbotAI* ai) { return new ThaddiusMoveToPlatformAction(ai); }
static Action* thaddius_move_polarity(PlayerbotAI* ai) { return new ThaddiusMovePolarityAction(ai); }
static Action* razuvious_target(PlayerbotAI* ai) { return new RazuviousTargetAction(ai); }
static Action* razuvious_use_obedience_crystal(PlayerbotAI* ai)
{
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* sapphiron_ground_main_tank_position(PlayerbotAI* ai) { return new
// SapphironGroundMainTankPositionAction(ai); }
static Action* sapphiron_ground_position(PlayerbotAI* ai) { return new SapphironGroundPositionAction(ai); }
static Action* sapphiron_flight_position(PlayerbotAI* ai) { return new SapphironFlightPositionAction(ai); }
// static Action* sapphiron_avoid_chill(PlayerbotAI* ai) { return new SapphironAvoidChillAction(ai); }
static Action* kelthuzad_choose_target(PlayerbotAI* ai) { return new KelthuzadChooseTargetAction(ai); }
static Action* kelthuzad_position(PlayerbotAI* ai) { return new KelthuzadPositionAction(ai); }
static Action* anubrekhan_choose_target(PlayerbotAI* ai) { return new AnubrekhanChooseTargetAction(ai); }
static Action* anubrekhan_position(PlayerbotAI* ai) { return new AnubrekhanPositionAction(ai); }
static Action* gluth_choose_target(PlayerbotAI* ai) { return new GluthChooseTargetAction(ai); }
static Action* gluth_position(PlayerbotAI* ai) { return new GluthPositionAction(ai); }
static Action* gluth_slowdown(PlayerbotAI* ai) { return new GluthSlowdownAction(ai); }
//static Action* patchwerk_ranged_position(PlayerbotAI* ai) { return new PatchwerkRangedPositionAction(ai); }
static Action* loatheb_position(PlayerbotAI* ai) { return new LoathebPositionAction(ai); }
static Action* loatheb_choose_target(PlayerbotAI* ai) { return new LoathebChooseTargetAction(ai); }
};
#endif

View File

@@ -0,0 +1,86 @@
// /*
// * 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_RAIDNAXXTRIGGERCONTEXT_H
#define _PLAYERBOT_RAIDNAXXTRIGGERCONTEXT_H
#include "AiObjectContext.h"
#include "NamedObjectContext.h"
#include "RaidNaxxTriggers.h"
class RaidNaxxTriggerContext : public NamedObjectContext<Trigger>
{
public:
RaidNaxxTriggerContext()
{
creators["mutating injection melee"] = &RaidNaxxTriggerContext::mutating_injection_melee;
creators["mutating injection ranged"] = &RaidNaxxTriggerContext::mutating_injection_ranged;
creators["mutating injection removed"] = &RaidNaxxTriggerContext::mutating_injection_removed;
creators["grobbulus cloud"] = &RaidNaxxTriggerContext::grobbulus_cloud;
//creators["heigan melee"] = &RaidNaxxTriggerContext::heigan_melee;
//creators["heigan ranged"] = &RaidNaxxTriggerContext::heigan_ranged;
creators["thaddius phase pet"] = &RaidNaxxTriggerContext::thaddius_phase_pet;
creators["thaddius phase pet lose aggro"] = &RaidNaxxTriggerContext::thaddius_phase_pet_lose_aggro;
creators["thaddius phase transition"] = &RaidNaxxTriggerContext::thaddius_phase_transition;
creators["thaddius phase thaddius"] = &RaidNaxxTriggerContext::thaddius_phase_thaddius;
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["sapphiron ground"] = &RaidNaxxTriggerContext::sapphiron_ground;
creators["sapphiron flight"] = &RaidNaxxTriggerContext::sapphiron_flight;
creators["kel'thuzad"] = &RaidNaxxTriggerContext::kelthuzad;
creators["anub'rekhan"] = &RaidNaxxTriggerContext::anubrekhan;
creators["faerlina"] = &RaidNaxxTriggerContext::faerlina;
creators["maexxna"] = &RaidNaxxTriggerContext::maexxna;
//creators["patchwerk tank"] = &RaidNaxxTriggerContext::patchwerk_tank;
//creators["patchwerk non-tank"] = &RaidNaxxTriggerContext::patchwerk_non_tank;
//creators["patchwerk ranged"] = &RaidNaxxTriggerContext::patchwerk_ranged;
creators["gluth"] = &RaidNaxxTriggerContext::gluth;
creators["gluth main tank mortal wound"] = &RaidNaxxTriggerContext::gluth_main_tank_mortal_wound;
creators["loatheb"] = &RaidNaxxTriggerContext::loatheb;
}
private:
static Trigger* mutating_injection_melee(PlayerbotAI* ai) { return new MutatingInjectionMeleeTrigger(ai); }
static Trigger* mutating_injection_ranged(PlayerbotAI* ai) { return new MutatingInjectionRangedTrigger(ai); }
static Trigger* mutating_injection_removed(PlayerbotAI* ai) { return new MutatingInjectionRemovedTrigger(ai); }
static Trigger* grobbulus_cloud(PlayerbotAI* ai) { return new GrobbulusCloudTrigger(ai); }
//static Trigger* heigan_melee(PlayerbotAI* ai) { return new HeiganMeleeTrigger(ai); }
//static Trigger* heigan_ranged(PlayerbotAI* ai) { return new HeiganRangedTrigger(ai); }
static Trigger* thaddius_phase_pet(PlayerbotAI* ai) { return new ThaddiusPhasePetTrigger(ai); }
static Trigger* thaddius_phase_pet_lose_aggro(PlayerbotAI* ai) { return new ThaddiusPhasePetLoseAggroTrigger(ai); }
static Trigger* thaddius_phase_transition(PlayerbotAI* ai) { return new ThaddiusPhaseTransitionTrigger(ai); }
static Trigger* thaddius_phase_thaddius(PlayerbotAI* ai) { return new ThaddiusPhaseThaddiusTrigger(ai); }
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* sapphiron_ground(PlayerbotAI* ai) { return new SapphironGroundTrigger(ai); }
static Trigger* sapphiron_flight(PlayerbotAI* ai) { return new SapphironFlightTrigger(ai); }
static Trigger* kelthuzad(PlayerbotAI* ai) { return new KelthuzadTrigger(ai); }
static Trigger* anubrekhan(PlayerbotAI* ai) { return new AnubrekhanTrigger(ai); }
static Trigger* faerlina(PlayerbotAI* ai) { return new FaerlinaTrigger(ai); }
static Trigger* maexxna(PlayerbotAI* ai) { return new MaexxnaTrigger(ai); }
//static Trigger* patchwerk_tank(PlayerbotAI* ai) { return new PatchwerkTankTrigger(ai); }
//static Trigger* patchwerk_non_tank(PlayerbotAI* ai) { return new PatchwerkNonTankTrigger(ai); }
//static Trigger* patchwerk_ranged(PlayerbotAI* ai) { return new PatchwerkRangedTrigger(ai); }
static Trigger* gluth(PlayerbotAI* ai) { return new GluthTrigger(ai); }
static Trigger* gluth_main_tank_mortal_wound(PlayerbotAI* ai) { return new GluthMainTankMortalWoundTrigger(ai); }
static Trigger* loatheb(PlayerbotAI* ai) { return new LoathebTrigger(ai); }
};
#endif

View File

@@ -0,0 +1,156 @@
#include "RaidNaxxStrategy.h"
#include "RaidNaxxMultipliers.h"
void RaidNaxxStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
// Grobbulus
triggers.push_back(new TriggerNode("mutating injection melee",
{ NextAction("grobbulus move away", ACTION_RAID + 2) }
));
triggers.push_back(new TriggerNode("mutating injection ranged",
{ NextAction("grobbulus go behind the boss", ACTION_RAID + 2) }
));
triggers.push_back(new TriggerNode("mutating injection removed",
{ NextAction("grobbulus move center", ACTION_RAID + 1) }
));
triggers.push_back(new TriggerNode("grobbulus cloud",
{ NextAction("rotate grobbulus", ACTION_RAID + 1) }
));
// Heigan the Unclean
//triggers.push_back(new TriggerNode("heigan melee",
// { NextAction("heigan dance melee", ACTION_RAID + 1) }
//));
//triggers.push_back(new TriggerNode("heigan ranged",
// { NextAction("heigan dance ranged", ACTION_RAID + 1) }
//));
// Kel'Thuzad
triggers.push_back(
new TriggerNode("kel'thuzad",
{
NextAction("kel'thuzad position", ACTION_RAID + 2),
NextAction("kel'thuzad choose target", ACTION_RAID + 1)
})
);
// Anub'Rekhan
triggers.push_back(new TriggerNode("anub'rekhan",
{ NextAction("anub'rekhan position", ACTION_RAID + 1) }
));
// Grand Widow Faerlina
triggers.push_back(new TriggerNode("faerlina",
{ NextAction("avoid aoe", ACTION_RAID + 1) }
));
// Maexxna
triggers.push_back(
new TriggerNode("maexxna",
{
NextAction("rear flank", ACTION_RAID + 1),
NextAction("avoid aoe", ACTION_RAID + 1)
})
);
// Patchwerk
//triggers.push_back(new TriggerNode("patchwerk tank",
// { NextAction("tank face", ACTION_RAID + 2) }
//));
//triggers.push_back(new TriggerNode("patchwerk ranged",
// { NextAction("patchwerk ranged position", ACTION_RAID + 2) }
//));
//triggers.push_back(new TriggerNode("patchwerk non-tank",
// { NextAction("rear flank", ACTION_RAID + 1) }
//));
// Thaddius
triggers.push_back(new TriggerNode("thaddius phase pet",
{ NextAction("thaddius attack nearest pet", ACTION_RAID + 1) }
));
triggers.push_back(new TriggerNode("thaddius phase pet lose aggro",
{ NextAction("taunt spell", ACTION_RAID + 2) }
));
triggers.push_back(new TriggerNode("thaddius phase transition",
{ NextAction("thaddius move to platform", ACTION_RAID + 1) }
));
triggers.push_back(new TriggerNode("thaddius phase thaddius",
{ NextAction("thaddius move polarity", ACTION_RAID + 1) }
));
// Instructor Razuvious
triggers.push_back(new TriggerNode("razuvious tank",
{ NextAction("razuvious use obedience crystal", ACTION_RAID + 1) }
));
triggers.push_back(new TriggerNode("razuvious nontank",
{ NextAction("razuvious target", ACTION_RAID + 1) }
));
// four horseman
triggers.push_back(new TriggerNode("horseman attractors",
{ NextAction("horseman attract alternatively", ACTION_RAID + 1) }
));
triggers.push_back(new TriggerNode("horseman except attractors",
{ NextAction("horseman attack in order", ACTION_RAID + 1) }
));
// sapphiron
triggers.push_back(new TriggerNode("sapphiron ground",
{ NextAction("sapphiron ground position", ACTION_RAID + 1) }
));
triggers.push_back(new TriggerNode("sapphiron flight",
{ NextAction("sapphiron flight position", ACTION_RAID + 1) }
));
// Gluth
triggers.push_back(
new TriggerNode("gluth",
{
NextAction("gluth choose target", ACTION_RAID + 1),
NextAction("gluth position", ACTION_RAID + 1),
NextAction("gluth slowdown", ACTION_RAID)
})
);
triggers.push_back(new TriggerNode("gluth main tank mortal wound",
{ NextAction("taunt spell", ACTION_RAID + 1) }
));
// Loatheb
triggers.push_back(
new TriggerNode("loatheb",
{
NextAction("loatheb position", ACTION_RAID + 1),
NextAction("loatheb choose target", ACTION_RAID + 1)
})
);
}
void RaidNaxxStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers)
{
multipliers.push_back(new GrobbulusMultiplier(botAI));
//multipliers.push_back(new HeiganDanceMultiplier(botAI));
multipliers.push_back(new LoathebGenericMultiplier(botAI));
multipliers.push_back(new ThaddiusGenericMultiplier(botAI));
multipliers.push_back(new SapphironGenericMultiplier(botAI));
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 GothikGenericMultiplier(botAI));
multipliers.push_back(new GluthGenericMultiplier(botAI));
}

View File

@@ -0,0 +1,18 @@
#ifndef _PLAYERBOT_RAIDNAXXSTRATEGY_H
#define _PLAYERBOT_RAIDNAXXSTRATEGY_H
#include "AiObjectContext.h"
#include "Multiplier.h"
#include "Strategy.h"
class RaidNaxxStrategy : public Strategy
{
public:
RaidNaxxStrategy(PlayerbotAI* ai) : Strategy(ai) {}
virtual std::string const getName() override { return "naxx"; }
virtual void InitTriggers(std::vector<TriggerNode*>& triggers) override;
virtual void InitMultipliers(std::vector<Multiplier*>& multipliers) override;
};
#endif

View File

@@ -0,0 +1,257 @@
#include "RaidNaxxTriggers.h"
#include "Playerbots.h"
#include "RaidNaxxSpellIds.h"
#include "Timer.h"
#include "Trigger.h"
bool MutatingInjectionMeleeTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "grobbulus");
if (!boss)
return false;
return MutatingInjectionTrigger::IsActive() && !botAI->IsRanged(bot);
}
bool MutatingInjectionRangedTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "grobbulus");
if (!boss)
return false;
return MutatingInjectionTrigger::IsActive() && botAI->IsRanged(bot);
}
bool AuraRemovedTrigger::IsActive()
{
bool check = botAI->HasAura(name, bot, false, false, -1, true);
bool ret = false;
if (prev_check && !check)
ret = true;
prev_check = check;
return ret;
}
bool MutatingInjectionRemovedTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "grobbulus");
if (!boss)
return false;
return HasNoAuraTrigger::IsActive() && botAI->GetState() == BOT_STATE_COMBAT && botAI->IsRanged(bot);
}
bool GrobbulusCloudTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "grobbulus");
if (!boss)
return false;
if (!botAI->IsMainTank(bot))
return false;
// bot->Yell("has aggro on " + boss->GetName() + " : " + to_string(AI_VALUE2(bool, "has aggro", "boss target")),
// LANG_UNIVERSAL);
if (!AI_VALUE2(bool, "has aggro", "boss target"))
return false;
uint32 now = getMSTime();
bool poison_cloud_casting = false;
if (boss->HasUnitState(UNIT_STATE_CASTING))
{
Spell* spell = boss->GetCurrentSpell(CURRENT_GENERIC_SPELL);
if (!spell)
spell = boss->GetCurrentSpell(CURRENT_CHANNELED_SPELL);
if (spell)
poison_cloud_casting = NaxxSpellIds::MatchesAnySpellId(spell->GetSpellInfo(), {NaxxSpellIds::PoisonCloud});
}
if (!poison_cloud_casting && last_cloud_ms != 0 && now - last_cloud_ms < CloudRotationDelayMs)
return false;
last_cloud_ms = now;
return true;
}
//bool HeiganMeleeTrigger::IsActive()
//{
// Unit* heigan = AI_VALUE2(Unit*, "find target", "heigan the unclean");
// if (!heigan)
// {
// return false;
// }
// return !botAI->IsRanged(bot);
//}
//
//bool HeiganRangedTrigger::IsActive()
//{
// Unit* heigan = AI_VALUE2(Unit*, "find target", "heigan the unclean");
// if (!heigan)
// {
// return false;
// }
// return botAI->IsRanged(bot);
//}
bool RazuviousTankTrigger::IsActive()
{
Difficulty diff = bot->GetRaidDifficulty();
if (diff == RAID_DIFFICULTY_10MAN_NORMAL)
return helper.UpdateBossAI() && botAI->IsTank(bot);
return helper.UpdateBossAI() && bot->getClass() == CLASS_PRIEST;
}
bool RazuviousNontankTrigger::IsActive()
{
Difficulty diff = bot->GetRaidDifficulty();
if (diff == RAID_DIFFICULTY_10MAN_NORMAL)
return helper.UpdateBossAI() && !(botAI->IsTank(bot));
return helper.UpdateBossAI() && !(bot->getClass() == CLASS_PRIEST);
}
bool HorsemanAttractorsTrigger::IsActive()
{
if (!helper.UpdateBossAI())
return false;
return helper.IsAttracter(bot);
}
bool HorsemanExceptAttractorsTrigger::IsActive()
{
if (!helper.UpdateBossAI())
return false;
return !helper.IsAttracter(bot);
}
bool SapphironGroundTrigger::IsActive()
{
if (!helper.UpdateBossAI())
return false;
return helper.IsPhaseGround();
}
bool SapphironFlightTrigger::IsActive()
{
if (!helper.UpdateBossAI())
return false;
return helper.IsPhaseFlight();
}
bool GluthTrigger::IsActive() { return helper.UpdateBossAI(); }
bool GluthMainTankMortalWoundTrigger::IsActive()
{
if (!helper.UpdateBossAI())
return false;
if (!botAI->IsAssistTankOfIndex(bot, 0))
return false;
Unit* mt = AI_VALUE(Unit*, "main tank");
if (!mt)
return false;
Aura* aura = NaxxSpellIds::GetAnyAura(mt, {NaxxSpellIds::MortalWound10, NaxxSpellIds::MortalWound25});
if (!aura)
{
// Fallback to name for custom spell data.
aura = botAI->GetAura("mortal wound", mt, false, true);
}
if (!aura || aura->GetStackAmount() < 5)
return false;
return true;
}
bool KelthuzadTrigger::IsActive() { return helper.UpdateBossAI(); }
bool AnubrekhanTrigger::IsActive() {
Unit* boss = AI_VALUE2(Unit*, "find target", "anub'rekhan");
if (!boss)
return false;
return true;
}
bool FaerlinaTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "grand widow faerlina");
if (!boss)
return false;
return true;
}
bool MaexxnaTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "maexxna");
if (!boss)
return false;
return !botAI->IsTank(bot);
}
//bool PatchwerkTankTrigger::IsActive()
//{
// Unit* boss = AI_VALUE2(Unit*, "find target", "patchwerk");
// if (!boss)
// {
// return false;
// }
// return !botAI->IsTank(bot) && !botAI->IsRanged(bot);
//}
//
//bool PatchwerkRangedTrigger::IsActive()
//{
// Unit* boss = AI_VALUE2(Unit*, "find target", "patchwerk");
// if (!boss)
// {
// return false;
// }
// return !botAI->IsTank(bot) && botAI->IsRanged(bot);
//}
//
//bool PatchwerkNonTankTrigger::IsActive()
//{
// Unit* boss = AI_VALUE2(Unit*, "find target", "patchwerk");
// if (!boss)
// {
// return false;
// }
// return !botAI->IsTank(bot);
//}
bool LoathebTrigger::IsActive() { return helper.UpdateBossAI(); }
bool ThaddiusPhasePetTrigger::IsActive()
{
if (!helper.UpdateBossAI())
return false;
return helper.IsPhasePet();
}
bool ThaddiusPhaseTransitionTrigger::IsActive()
{
if (!helper.UpdateBossAI())
return false;
return helper.IsPhaseTransition();
}
bool ThaddiusPhaseThaddiusTrigger::IsActive()
{
if (!helper.UpdateBossAI())
return false;
return helper.IsPhaseThaddius();
}

View File

@@ -0,0 +1,259 @@
#ifndef _PLAYERBOT_RAIDNAXXTRIGGERS_H
#define _PLAYERBOT_RAIDNAXXTRIGGERS_H
#include "EventMap.h"
#include "GenericTriggers.h"
#include "PlayerbotAIConfig.h"
#include "RaidNaxxBossHelper.h"
#include "Trigger.h"
class MutatingInjectionTrigger : public HasAuraTrigger
{
public:
MutatingInjectionTrigger(PlayerbotAI* ai) : HasAuraTrigger(ai, "mutating injection", 1) {}
};
class MutatingInjectionMeleeTrigger : public MutatingInjectionTrigger
{
public:
MutatingInjectionMeleeTrigger(PlayerbotAI* ai) : MutatingInjectionTrigger(ai) {}
bool IsActive() override;
};
class MutatingInjectionRangedTrigger : public MutatingInjectionTrigger
{
public:
MutatingInjectionRangedTrigger(PlayerbotAI* ai) : MutatingInjectionTrigger(ai) {}
bool IsActive() override;
};
class AuraRemovedTrigger : public Trigger
{
public:
AuraRemovedTrigger(PlayerbotAI* botAI, std::string name) : Trigger(botAI, name, 1)
{
this->prev_check = false;
}
virtual bool IsActive() override;
protected:
bool prev_check;
};
class MutatingInjectionRemovedTrigger : public HasNoAuraTrigger
{
public:
MutatingInjectionRemovedTrigger(PlayerbotAI* ai) : HasNoAuraTrigger(ai, "mutating injection") {}
virtual bool IsActive();
};
class GrobbulusCloudTrigger : public Trigger
{
public:
GrobbulusCloudTrigger(PlayerbotAI* ai) : Trigger(ai, "grobbulus cloud event"), last_cloud_ms(0) {}
bool IsActive() override;
private:
uint32 last_cloud_ms;
static constexpr uint32 CloudRotationDelayMs = 15000;
};
//class HeiganMeleeTrigger : public Trigger
//{
//public:
// HeiganMeleeTrigger(PlayerbotAI* ai) : Trigger(ai, "heigan melee") {}
// virtual bool IsActive();
//};
//
//class HeiganRangedTrigger : public Trigger
//{
//public:
// HeiganRangedTrigger(PlayerbotAI* ai) : Trigger(ai, "heigan ranged") {}
// bool IsActive() override;
//};
class RazuviousTankTrigger : public Trigger
{
public:
RazuviousTankTrigger(PlayerbotAI* ai) : Trigger(ai, "instructor razuvious tank"), helper(ai) {}
bool IsActive() override;
private:
RazuviousBossHelper helper;
};
class RazuviousNontankTrigger : public Trigger
{
public:
RazuviousNontankTrigger(PlayerbotAI* ai) : Trigger(ai, "instructor razuvious non-tank"), helper(ai) {}
bool IsActive() override;
private:
RazuviousBossHelper helper;
};
class KelthuzadTrigger : public Trigger
{
public:
KelthuzadTrigger(PlayerbotAI* ai) : Trigger(ai, "kel'thuzad trigger"), helper(ai) {}
bool IsActive() override;
private:
KelthuzadBossHelper helper;
};
class AnubrekhanTrigger : public Trigger
{
public:
AnubrekhanTrigger(PlayerbotAI* ai) : Trigger(ai, "anub'rekhan") {}
bool IsActive() override;
};
class FaerlinaTrigger : public Trigger
{
public:
FaerlinaTrigger(PlayerbotAI* ai) : Trigger(ai, "faerlina") {}
bool IsActive() override;
};
class MaexxnaTrigger : public Trigger
{
public:
MaexxnaTrigger(PlayerbotAI* ai) : Trigger(ai, "maexxna") {}
bool IsActive() override;
};
//class PatchwerkTankTrigger : public Trigger
//{
//public:
// PatchwerkTankTrigger(PlayerbotAI* ai) : Trigger(ai, "patchwerk tank") {}
// bool IsActive() override;
//};
//
//class PatchwerkNonTankTrigger : public Trigger
//{
//public:
// PatchwerkNonTankTrigger(PlayerbotAI* ai) : Trigger(ai, "patchwerk non-tank") {}
// bool IsActive() override;
//};
//
//class PatchwerkRangedTrigger : public Trigger
//{
//public:
// PatchwerkRangedTrigger(PlayerbotAI* ai) : Trigger(ai, "patchwerk ranged") {}
// bool IsActive() override;
//};
class ThaddiusPhasePetTrigger : public Trigger
{
public:
ThaddiusPhasePetTrigger(PlayerbotAI* ai) : Trigger(ai, "thaddius phase pet"), helper(ai) {}
bool IsActive() override;
private:
ThaddiusBossHelper helper;
};
class ThaddiusPhasePetLoseAggroTrigger : public ThaddiusPhasePetTrigger
{
public:
ThaddiusPhasePetLoseAggroTrigger(PlayerbotAI* ai) : ThaddiusPhasePetTrigger(ai) {}
virtual bool IsActive()
{
Unit* target = AI_VALUE(Unit*, "current target");
return ThaddiusPhasePetTrigger::IsActive() && botAI->IsTank(bot) && target && target->GetVictim() != bot;
}
};
class ThaddiusPhaseTransitionTrigger : public Trigger
{
public:
ThaddiusPhaseTransitionTrigger(PlayerbotAI* ai) : Trigger(ai, "thaddius phase transition"), helper(ai) {}
bool IsActive() override;
private:
ThaddiusBossHelper helper;
};
class ThaddiusPhaseThaddiusTrigger : public Trigger
{
public:
ThaddiusPhaseThaddiusTrigger(PlayerbotAI* ai) : Trigger(ai, "thaddius phase thaddius"), helper(ai) {}
bool IsActive() override;
private:
ThaddiusBossHelper helper;
};
class HorsemanAttractorsTrigger : public Trigger
{
public:
HorsemanAttractorsTrigger(PlayerbotAI* ai) : Trigger(ai, "fourhorsemen attractors"), helper(ai) {}
bool IsActive() override;
private:
FourhorsemanBossHelper helper;
};
class HorsemanExceptAttractorsTrigger : public Trigger
{
public:
HorsemanExceptAttractorsTrigger(PlayerbotAI* ai) : Trigger(ai, "fourhorsemen except attractors"), helper(ai) {}
bool IsActive() override;
private:
FourhorsemanBossHelper helper;
};
class SapphironGroundTrigger : public Trigger
{
public:
SapphironGroundTrigger(PlayerbotAI* ai) : Trigger(ai, "sapphiron ground"), helper(ai) {}
bool IsActive() override;
private:
SapphironBossHelper helper;
};
class SapphironFlightTrigger : public Trigger
{
public:
SapphironFlightTrigger(PlayerbotAI* ai) : Trigger(ai, "sapphiron flight"), helper(ai) {}
bool IsActive() override;
private:
SapphironBossHelper helper;
};
class GluthTrigger : public Trigger
{
public:
GluthTrigger(PlayerbotAI* ai) : Trigger(ai, "gluth trigger"), helper(ai) {}
bool IsActive() override;
private:
GluthBossHelper helper;
};
class GluthMainTankMortalWoundTrigger : public Trigger
{
public:
GluthMainTankMortalWoundTrigger(PlayerbotAI* ai) : Trigger(ai, "gluth main tank mortal wound trigger"), helper(ai) {}
bool IsActive() override;
private:
GluthBossHelper helper;
};
class LoathebTrigger : public Trigger
{
public:
LoathebTrigger(PlayerbotAI* ai) : Trigger(ai, "loatheb"), helper(ai) {}
bool IsActive() override;
private:
LoathebBossHelper helper;
};
#endif

View File

@@ -0,0 +1,533 @@
#ifndef _PLAYERBOT_RAIDNAXXBOSSHELPER_H
#define _PLAYERBOT_RAIDNAXXBOSSHELPER_H
#include <string>
#include "AiObject.h"
#include "AiObjectContext.h"
#include "EventMap.h"
#include "Log.h"
#include "NamedObjectContext.h"
#include "ObjectGuid.h"
#include "Player.h"
#include "PlayerbotAI.h"
#include "Playerbots.h"
#include "ScriptedCreature.h"
#include "SharedDefines.h"
#include "Spell.h"
#include "Timer.h"
#include "RaidNaxxSpellIds.h"
const uint32 NAXX_MAP_ID = 533;
template <class BossAiType>
class GenericBossHelper : public AiObject
{
public:
GenericBossHelper(PlayerbotAI* botAI, std::string name) : AiObject(botAI), _name(name) {}
virtual bool UpdateBossAI()
{
if (!bot->IsInCombat())
_unit = nullptr;
if (_unit && (!_unit->IsInWorld() || !_unit->IsAlive()))
_unit = nullptr;
if (!_unit)
{
_unit = AI_VALUE2(Unit*, "find target", _name);
if (!_unit)
return false;
_target = _unit->ToCreature();
if (!_target)
return false;
_ai = dynamic_cast<BossAiType*>(_target->GetAI());
if (!_ai)
return false;
_event_map = &_ai->events;
if (!_event_map)
return false;
}
if (!_event_map)
return false;
_timer = getMSTime();
return true;
}
virtual void Reset()
{
_unit = nullptr;
_target = nullptr;
_ai = nullptr;
_event_map = nullptr;
_timer = 0;
}
protected:
std::string _name;
Unit* _unit = nullptr;
Creature* _target = nullptr;
BossAiType* _ai = nullptr;
EventMap* _event_map = nullptr;
uint32 _timer = 0;
};
class KelthuzadBossHelper : public AiObject
{
public:
KelthuzadBossHelper(PlayerbotAI* botAI) : AiObject(botAI) {}
const std::pair<float, float> center = {3716.19f, -5106.58f};
const std::pair<float, float> tank_pos = {3709.19f, -5104.86f};
const std::pair<float, float> assist_tank_pos = {3746.05f, -5112.74f};
bool UpdateBossAI()
{
if (!bot->IsInCombat())
Reset();
if (_unit && (!_unit->IsInWorld() || !_unit->IsAlive()))
Reset();
if (!_unit)
_unit = AI_VALUE2(Unit*, "find target", "kel'thuzad");
return _unit != nullptr;
}
bool IsPhaseOne() { return _unit && _unit->HasUnitFlag(UNIT_FLAG_NON_ATTACKABLE); }
bool IsPhaseTwo() { return _unit && !_unit->HasUnitFlag(UNIT_FLAG_NON_ATTACKABLE); }
Unit* GetAnyShadowFissure()
{
Unit* shadow_fissure = nullptr;
GuidVector units = *context->GetValue<GuidVector>("nearest triggers");
for (auto i = units.begin(); i != units.end(); i++)
{
Unit* unit = botAI->GetUnit(*i);
if (!unit)
continue;
if (botAI->EqualLowercaseName(unit->GetName(), "shadow fissure"))
shadow_fissure = unit;
}
return shadow_fissure;
}
private:
void Reset() { _unit = nullptr; }
Unit* _unit = nullptr;
};
class RazuviousBossHelper : public AiObject
{
public:
RazuviousBossHelper(PlayerbotAI* botAI) : AiObject(botAI) {}
bool UpdateBossAI()
{
if (!bot->IsInCombat())
Reset();
if (_unit && (!_unit->IsInWorld() || !_unit->IsAlive()))
Reset();
if (!_unit)
_unit = AI_VALUE2(Unit*, "find target", "instructor razuvious");
return _unit != nullptr;
}
private:
void Reset() { _unit = nullptr; }
Unit* _unit = nullptr;
};
class SapphironBossHelper : public AiObject
{
public:
const std::pair<float, float> mainTankPos = {3512.07f, -5274.06f};
const std::pair<float, float> center = {3517.31f, -5253.74f};
const float GENERIC_HEIGHT = 137.29f;
SapphironBossHelper(PlayerbotAI* botAI) : AiObject(botAI) {}
bool UpdateBossAI()
{
if (!bot->IsInCombat())
Reset();
if (_unit && (!_unit->IsInWorld() || !_unit->IsAlive()))
Reset();
if (!_unit)
{
_unit = AI_VALUE2(Unit*, "find target", "sapphiron");
if (!_unit)
return false;
}
bool now_flying = _unit->IsFlying();
if (_was_flying && !now_flying)
_last_land_ms = getMSTime();
_was_flying = now_flying;
return true;
}
bool IsPhaseGround() { return _unit && !_unit->IsFlying(); }
bool IsPhaseFlight() { return _unit && _unit->IsFlying(); }
bool JustLanded()
{
if (!_last_land_ms)
return false;
return getMSTime() - _last_land_ms <= POSITION_TIME_AFTER_LANDED;
}
bool WaitForExplosion()
{
if (!IsPhaseFlight())
return false;
Group* group = bot->GetGroup();
if (!group)
return false;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (member &&
(NaxxSpellIds::HasAnyAura(botAI, member, {NaxxSpellIds::Icebolt10, NaxxSpellIds::Icebolt25}) ||
botAI->HasAura("icebolt", member, false, false, -1, true)))
{
return true;
}
}
return false;
}
bool FindPosToAvoidChill(std::vector<float>& dest)
{
Aura* aura = NaxxSpellIds::GetAnyAura(bot, {NaxxSpellIds::Chill25});
if (!aura)
{
// Fallback to name for custom spell data.
aura = botAI->GetAura("chill", bot);
}
if (!aura)
return false;
DynamicObject* dyn_obj = aura->GetDynobjOwner();
if (!dyn_obj)
return false;
Unit* currentTarget = AI_VALUE(Unit*, "current target");
float angle = 0;
uint32 index = botAI->GetGroupSlotIndex(bot);
if (currentTarget)
{
if (botAI->IsRanged(bot))
{
if (bot->GetExactDist2d(currentTarget) <= 45.0f)
angle = bot->GetAngle(dyn_obj) - M_PI + (rand_norm() - 0.5) * M_PI / 2;
else
{
if (index % 2 == 0)
angle = bot->GetAngle(currentTarget) + M_PI / 2;
else
angle = bot->GetAngle(currentTarget) - M_PI / 2;
}
}
else
{
if (index % 3 == 0)
angle = bot->GetAngle(currentTarget);
else if (index % 3 == 1)
angle = bot->GetAngle(currentTarget) + M_PI / 2;
else
angle = bot->GetAngle(currentTarget) - M_PI / 2;
}
}
else
angle = bot->GetAngle(dyn_obj) - M_PI + (rand_norm() - 0.5) * M_PI / 2;
dest = {bot->GetPositionX() + cos(angle) * 5.0f, bot->GetPositionY() + sin(angle) * 5.0f, bot->GetPositionZ()};
return true;
}
private:
void Reset()
{
_unit = nullptr;
_was_flying = false;
_last_land_ms = 0;
}
const uint32 POSITION_TIME_AFTER_LANDED = 5000;
Unit* _unit = nullptr;
bool _was_flying = false;
uint32 _last_land_ms = 0;
};
class GluthBossHelper : public AiObject
{
public:
const std::pair<float, float> mainTankPos25 = {3331.48f, -3109.06f};
const std::pair<float, float> mainTankPos10 = {3278.29f, -3162.06f};
const std::pair<float, float> beforeDecimatePos = {3267.34f, -3175.68f};
const std::pair<float, float> leftSlowDownPos = {3290.68f, -3141.65f};
const std::pair<float, float> rightSlowDownPos = {3300.78f, -3151.98f};
const std::pair<float, float> rangedPos = {3301.45f, -3139.29f};
const std::pair<float, float> healPos = {3303.09f, -3135.24f};
const float decimatedZombiePct = 10.0f;
GluthBossHelper(PlayerbotAI* botAI) : AiObject(botAI) {}
bool UpdateBossAI()
{
if (!bot->IsInCombat())
Reset();
if (_unit && (!_unit->IsInWorld() || !_unit->IsAlive()))
Reset();
if (!_unit)
{
_unit = AI_VALUE2(Unit*, "find target", "gluth");
if (!_unit)
return false;
}
if (_unit->IsInCombat())
{
if (_combat_start_ms == 0)
_combat_start_ms = getMSTime();
}
else
_combat_start_ms = 0;
return true;
}
bool BeforeDecimate()
{
if (!_unit || !_unit->HasUnitState(UNIT_STATE_CASTING))
return false;
Spell* spell = _unit->GetCurrentSpell(CURRENT_GENERIC_SPELL);
if (!spell)
spell = _unit->GetCurrentSpell(CURRENT_CHANNELED_SPELL);
if (!spell)
return false;
SpellInfo const* info = spell->GetSpellInfo();
if (!info)
return false;
if (NaxxSpellIds::MatchesAnySpellId(
info, {NaxxSpellIds::Decimate10, NaxxSpellIds::Decimate25, NaxxSpellIds::Decimate25Alt}))
return true;
// Fallback to name for custom spell data.
return info->SpellName[LOCALE_enUS] && botAI->EqualLowercaseName(info->SpellName[LOCALE_enUS], "decimate");
}
bool JustStartCombat() const { return _combat_start_ms != 0 && getMSTime() - _combat_start_ms < 10000; }
bool IsZombieChow(Unit* unit) const { return unit && botAI->EqualLowercaseName(unit->GetName(), "zombie chow"); }
private:
void Reset()
{
_unit = nullptr;
_combat_start_ms = 0;
}
Unit* _unit = nullptr;
uint32 _combat_start_ms = 0;
};
class LoathebBossHelper : public AiObject
{
public:
const std::pair<float, float> mainTankPos = {2877.57f, -3967.00f};
const std::pair<float, float> rangePos = {2896.96f, -3980.61f};
LoathebBossHelper(PlayerbotAI* botAI) : AiObject(botAI) {}
bool UpdateBossAI()
{
if (!bot->IsInCombat())
Reset();
if (_unit && (!_unit->IsInWorld() || !_unit->IsAlive()))
Reset();
if (!_unit)
_unit = AI_VALUE2(Unit*, "find target", "loatheb");
return _unit != nullptr;
}
private:
void Reset() { _unit = nullptr; }
Unit* _unit = nullptr;
};
class FourhorsemanBossHelper : 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) {}
bool UpdateBossAI()
{
if (!bot->IsInCombat())
Reset();
else if (_combat_start_ms == 0)
_combat_start_ms = getMSTime();
if (_sir && (!_sir->IsInWorld() || !_sir->IsAlive()))
Reset();
if (!_sir)
{
_sir = AI_VALUE2(Unit*, "find target", "sir zeliek");
if (!_sir)
return false;
}
_lady = AI_VALUE2(Unit*, "find target", "lady blaumeux");
return true;
}
void Reset()
{
_sir = nullptr;
_lady = nullptr;
_combat_start_ms = 0;
posToGo = 0;
}
bool IsAttracter(Player* bot)
{
Difficulty diff = bot->GetRaidDifficulty();
if (diff == RAID_DIFFICULTY_25MAN_NORMAL)
{
return botAI->IsAssistRangedDpsOfIndex(bot, 0) || botAI->IsAssistHealOfIndex(bot, 0) ||
botAI->IsAssistHealOfIndex(bot, 1) || botAI->IsAssistHealOfIndex(bot, 2);
}
return botAI->IsAssistRangedDpsOfIndex(bot, 0) || botAI->IsAssistHealOfIndex(bot, 0);
}
void CalculatePosToGo(Player* bot)
{
bool raid25 = bot->GetRaidDifficulty() == RAID_DIFFICULTY_25MAN_NORMAL;
Unit* lady = _lady;
if (!lady)
posToGo = 0;
else
{
uint32 elapsed_ms = _combat_start_ms ? getMSTime() - _combat_start_ms : 0;
// Interval: 24s - 15s - 15s - ...
posToGo = !(elapsed_ms <= 9000 || ((elapsed_ms - 9000) / 67500) % 2 == 0);
if (botAI->IsAssistRangedDpsOfIndex(bot, 0) || (raid25 && botAI->IsAssistHealOfIndex(bot, 1)))
posToGo = 1 - posToGo;
}
}
std::pair<float, float> CurrentAttractPos()
{
bool raid25 = bot->GetRaidDifficulty() == RAID_DIFFICULTY_25MAN_NORMAL;
float posX = attractPos[posToGo].first, posY = attractPos[posToGo].second;
if (posToGo == 1)
{
float offset_x = 0.0f;
float offset_y = 0.0f;
float bias = 4.5f;
if (raid25)
{
offset_x = -bias;
offset_y = bias;
}
posX += offset_x;
posY += offset_y;
}
return {posX, posY};
}
Unit* CurrentAttackTarget()
{
if (posToGo == 0)
return _sir;
return _lady;
}
protected:
Unit* _sir = nullptr;
Unit* _lady = nullptr;
uint32 _combat_start_ms = 0;
int posToGo = 0;
};
class ThaddiusBossHelper : public AiObject
{
public:
const std::pair<float, float> tankPosFeugen = {3522.94f, -3002.60f};
const std::pair<float, float> tankPosStalagg = {3436.14f, -2919.98f};
const std::pair<float, float> rangedPosFeugen = {3500.45f, -2997.92f};
const std::pair<float, float> rangedPosStalagg = {3441.01f, -2942.04f};
const float tankPosZ = 312.61f;
ThaddiusBossHelper(PlayerbotAI* botAI) : AiObject(botAI) {}
bool UpdateBossAI()
{
if (!bot->IsInCombat())
Reset();
if (_unit && (!_unit->IsInWorld() || !_unit->IsAlive()))
Reset();
if (!_unit)
{
_unit = AI_VALUE2(Unit*, "find target", "thaddius");
if (!_unit)
return false;
}
feugen = AI_VALUE2(Unit*, "find target", "feugen");
stalagg = AI_VALUE2(Unit*, "find target", "stalagg");
return true;
}
bool IsPhasePet() { return (feugen && feugen->IsAlive()) || (stalagg && stalagg->IsAlive()); }
bool IsPhaseTransition()
{
if (IsPhasePet())
return false;
return _unit && _unit->HasUnitFlag(UNIT_FLAG_NON_ATTACKABLE);
}
bool IsPhaseThaddius() { return !IsPhasePet() && !IsPhaseTransition(); }
Unit* GetNearestPet()
{
Unit* unit = nullptr;
if (feugen && feugen->IsAlive())
unit = feugen;
if (stalagg && stalagg->IsAlive() && (!feugen || bot->GetDistance(stalagg) < bot->GetDistance(feugen)))
unit = stalagg;
return unit;
}
std::pair<float, float> PetPhaseGetPosForTank()
{
if (GetNearestPet() == feugen)
return tankPosFeugen;
return tankPosStalagg;
}
std::pair<float, float> PetPhaseGetPosForRanged()
{
if (GetNearestPet() == feugen)
return rangedPosFeugen;
return rangedPosStalagg;
}
protected:
void Reset()
{
_unit = nullptr;
feugen = nullptr;
stalagg = nullptr;
}
Unit* _unit = nullptr;
Unit* feugen = nullptr;
Unit* stalagg = nullptr;
};
#endif

View File

@@ -0,0 +1,165 @@
#ifndef _PLAYERBOT_RAIDNAXXSPELLIDS_H
#define _PLAYERBOT_RAIDNAXXSPELLIDS_H
#include <initializer_list>
#include "PlayerbotAI.h"
// use src/server/scripts/Northrend/Naxxramas/naxxramas.h for CreatureId, NaxxramasSay, NaxxramasEvent, NaxxramasMisc
namespace NaxxSpellIds
{
// Heigan
static constexpr uint32 Eruption10 = 29371;
/*
SPELL_SPELL_DISRUPTION = 29310,
SPELL_DECREPIT_FEVER = 29998,
SPELL_PLAGUE_CLOUD = 29350,
SPELL_TELEPORT_SELF = 30211
*/
// Grobbulus
static constexpr uint32 PoisonCloud = 28240;
// Thaddius polarity
static constexpr uint32 PositiveCharge10 = 28059;
static constexpr uint32 PositiveCharge25 = 28062;
static constexpr uint32 PositiveChargeStack = 29659;
static constexpr uint32 NegativeCharge10 = 28084;
static constexpr uint32 NegativeCharge25 = 28085;
static constexpr uint32 NegativeChargeStack = 29660;
/*
SPELL_MAGNETIC_PULL = 28337,
SPELL_TESLA_SHOCK = 28099,
SPELL_SHOCK_VISUAL = 28159,
// Stalagg
SPELL_POWER_SURGE = 54529,
SPELL_STALAGG_CHAIN = 28096,
// Feugen
SPELL_STATIC_FIELD = 28135,
SPELL_FEUGEN_CHAIN = 28111,
// Thaddius
SPELL_POLARITY_SHIFT = 28089,
SPELL_BALL_LIGHTNING = 28299,
SPELL_CHAIN_LIGHTNING = 28167,
SPELL_BERSERK = 27680,
SPELL_THADDIUS_VISUAL_LIGHTNING = 28136,
SPELL_THADDIUS_SPAWN_STUN = 28160,
SPELL_POSITIVE_CHARGE = 28062,
SPELL_POSITIVE_CHARGE_STACK = 29659,
SPELL_NEGATIVE_CHARGE = 28085,
SPELL_NEGATIVE_CHARGE_STACK = 29660,
SPELL_POSITIVE_POLARITY = 28059,
SPELL_NEGATIVE_POLARITY = 28084
*/
// Sapphiron
static constexpr uint32 Icebolt10 = 28522;
static constexpr uint32 Icebolt25 = 28526;
static constexpr uint32 Chill25 = 55699;
/*
// Fight
SPELL_FROST_AURA = 28531,
SPELL_CLEAVE = 19983,
SPELL_TAIL_SWEEP = 55697,
SPELL_SUMMON_BLIZZARD = 28560,
SPELL_LIFE_DRAIN = 28542,
SPELL_BERSERK = 26662,
// Ice block
SPELL_ICEBOLT_CAST = 28526,
SPELL_ICEBOLT_TRIGGER = 28522,
SPELL_FROST_MISSILE = 30101,
SPELL_FROST_EXPLOSION = 28524,
// Visuals
SPELL_SAPPHIRON_DIES = 29357
*/
// Gluth
static constexpr uint32 Decimate10 = 28374;
static constexpr uint32 Decimate25 = 54426;
static constexpr uint32 Decimate25Alt = 28375;
static constexpr uint32 MortalWound10 = 25646;
static constexpr uint32 MortalWound25 = 54378;
/*
SPELL_MORTAL_WOUND = 25646,
SPELL_ENRAGE = 28371,
SPELL_DECIMATE = 28374,
SPELL_DECIMATE_DAMAGE = 28375,
SPELL_BERSERK = 26662,
SPELL_INFECTED_WOUND = 29306,
SPELL_CHOW_SEARCHER = 28404
*/
// Anub'Rekhan
static constexpr uint32 LocustSwarm10 = 28785;
static constexpr uint32 LocustSwarm10Alt = 28786;
static constexpr uint32 LocustSwarm25 = 54021; // 25-man Locust Swarm
/*
SPELL_IMPALE = 28783,
SPELL_LOCUST_SWARM = 28785,
SPELL_SUMMON_CORPSE_SCARABS_5 = 29105,
SPELL_SUMMON_CORPSE_SCARABS_10 = 28864,
SPELL_BERSERK = 26662
ACHIEV_TIMED_START_EVENT = 9891,
EVENT_SPAWN_CRYPT_GUARDS_1 = 0,
EVENT_BERSERK = 1,
////
Position const cryptguardPositions[] = {
{ 3299.732f, -3502.489f, 287.077f, 2.378f },
{ 3299.086f, -3450.929f, 287.077f, 3.999f },
{ 3331.217f, -3476.607f, 287.074f, 3.269f }
};
*/
// Loatheb
static constexpr uint32 NecroticAura10 = 55593;
/*
SPELL_NECROTIC_AURA = 55593,
SPELL_SUMMON_SPORE = 29234,
SPELL_DEATHBLOOM = 29865,
SPELL_INEVITABLE_DOOM = 29204,
SPELL_BERSERK = 26662
*/
inline bool HasAnyAura(PlayerbotAI* botAI, Unit* unit, std::initializer_list<uint32> spellIds)
{
if (!botAI || !unit)
return false;
for (uint32 spellId : spellIds)
{
if (botAI->HasAura(spellId, unit))
return true;
}
return false;
}
inline Aura* GetAnyAura(Unit* unit, std::initializer_list<uint32> spellIds)
{
if (!unit)
return nullptr;
for (uint32 spellId : spellIds)
{
if (Aura* aura = unit->GetAura(spellId))
return aura;
}
return nullptr;
}
inline bool MatchesAnySpellId(SpellInfo const* info, std::initializer_list<uint32> spellIds)
{
if (!info)
return false;
for (uint32 spellId : spellIds)
{
if (info->Id == spellId)
return true;
}
return false;
}
} // namespace NaxxSpellIds
#endif

View File

@@ -8,6 +8,7 @@
#include "RaidKarazhanStrategy.h"
#include "RaidGruulsLairStrategy.h"
#include "RaidMagtheridonStrategy.h"
#include "RaidNaxxStrategy.h"
#include "RaidSSCStrategy.h"
#include "RaidTempestKeepStrategy.h"
#include "RaidOsStrategy.h"
@@ -28,6 +29,7 @@ public:
creators["karazhan"] = &RaidStrategyContext::karazhan;
creators["gruulslair"] = &RaidStrategyContext::gruulslair;
creators["magtheridon"] = &RaidStrategyContext::magtheridon;
creators["naxx"] = &RaidStrategyContext::naxx;
creators["ssc"] = &RaidStrategyContext::ssc;
creators["tempestkeep"] = &RaidStrategyContext::tempestkeep;
creators["wotlk-os"] = &RaidStrategyContext::wotlk_os;
@@ -45,6 +47,7 @@ private:
static Strategy* karazhan(PlayerbotAI* botAI) { return new RaidKarazhanStrategy(botAI); }
static Strategy* gruulslair(PlayerbotAI* botAI) { return new RaidGruulsLairStrategy(botAI); }
static Strategy* magtheridon(PlayerbotAI* botAI) { return new RaidMagtheridonStrategy(botAI); }
static Strategy* naxx(PlayerbotAI* botAI) { return new RaidNaxxStrategy(botAI); }
static Strategy* ssc(PlayerbotAI* botAI) { return new RaidSSCStrategy(botAI); }
static Strategy* tempestkeep(PlayerbotAI* botAI) { return new RaidTempestKeepStrategy(botAI); }
static Strategy* wotlk_os(PlayerbotAI* botAI) { return new RaidOsStrategy(botAI); }

View File

@@ -598,9 +598,9 @@ uint32 ChatHelper::parseSlot(std::string const text)
return EQUIPMENT_SLOT_END;
}
bool ChatHelper::parseable(std::string const text)
bool ChatHelper::parseableItem(std::string const text)
{
return text.find("|H") != std::string::npos || text == "questitem" || text == "ammo" ||
return text.find("|Hitem:") != std::string::npos || text == "questitem" || text == "ammo" ||
substrContainsInMap<uint32>(text, consumableSubClasses) ||
substrContainsInMap<uint32>(text, tradeSubClasses) || substrContainsInMap<uint32>(text, itemQualities) ||
substrContainsInMap<uint32>(text, slots) || substrContainsInMap<ChatMsg>(text, chats) ||

View File

@@ -66,7 +66,7 @@ public:
static uint32 parseSlot(std::string const text);
uint32 parseSkill(std::string const text);
static bool parseable(std::string const text);
static bool parseableItem(std::string const text);
void eraseAllSubStr(std::string& mainStr, std::string const toErase);

View File

@@ -4,59 +4,17 @@
*/
#include "AiObjectContext.h"
#include "ActionContext.h"
#include "ChatActionContext.h"
#include "ChatTriggerContext.h"
#include "Helpers.h"
#include "DKAiObjectContext.h"
#include "DruidAiObjectContext.h"
#include "HunterAiObjectContext.h"
#include "MageAiObjectContext.h"
#include "PaladinAiObjectContext.h"
#include "Playerbots.h"
#include "PriestAiObjectContext.h"
#include "RogueAiObjectContext.h"
#include "ShamanAiObjectContext.h"
#include "SharedValueContext.h"
#include "StrategyContext.h"
#include "TriggerContext.h"
#include "ValueContext.h"
#include "WarlockAiObjectContext.h"
#include "WarriorAiObjectContext.h"
#include "WorldPacketActionContext.h"
#include "WorldPacketTriggerContext.h"
#include "Ai/Dungeon/DungeonStrategyContext.h"
#include "Ai/Dungeon/WotlkDungeonActionContext.h"
#include "Ai/Dungeon/WotlkDungeonTriggerContext.h"
#include "Ai/Raid/RaidStrategyContext.h"
#include "Ai/Raid/Aq20/RaidAq20ActionContext.h"
#include "Ai/Raid/Aq20/RaidAq20TriggerContext.h"
#include "Ai/Raid/MoltenCore/RaidMcActionContext.h"
#include "Ai/Raid/MoltenCore/RaidMcTriggerContext.h"
#include "Ai/Raid/BlackwingLair/RaidBwlActionContext.h"
#include "Ai/Raid/BlackwingLair/RaidBwlTriggerContext.h"
#include "Ai/Raid/Karazhan/RaidKarazhanActionContext.h"
#include "Ai/Raid/Karazhan/RaidKarazhanTriggerContext.h"
#include "Ai/Raid/GruulsLair/RaidGruulsLairActionContext.h"
#include "Ai/Raid/GruulsLair/RaidGruulsLairTriggerContext.h"
#include "Ai/Raid/Magtheridon/RaidMagtheridonActionContext.h"
#include "Ai/Raid/Magtheridon/RaidMagtheridonTriggerContext.h"
#include "Ai/Raid/SerpentshrineCavern/RaidSSCActionContext.h"
#include "Ai/Raid/SerpentshrineCavern/RaidSSCTriggerContext.h"
#include "Ai/Raid/TempestKeep/RaidTempestKeepActionContext.h"
#include "Ai/Raid/TempestKeep/RaidTempestKeepTriggerContext.h"
#include "Ai/Raid/EyeOfEternity/RaidEoEActionContext.h"
#include "Ai/Raid/EyeOfEternity/RaidEoETriggerContext.h"
#include "Ai/Raid/VaultOfArchavon/RaidVoAActionContext.h"
#include "Ai/Raid/VaultOfArchavon/RaidVoATriggerContext.h"
#include "Ai/Raid/ObsidianSanctum/RaidOsActionContext.h"
#include "Ai/Raid/ObsidianSanctum/RaidOsTriggerContext.h"
#include "Ai/Raid/Ulduar/RaidUlduarActionContext.h"
#include "Ai/Raid/Ulduar/RaidUlduarTriggerContext.h"
#include "Ai/Raid/Onyxia/RaidOnyxiaActionContext.h"
#include "Ai/Raid/Onyxia/RaidOnyxiaTriggerContext.h"
#include "Ai/Raid/Icecrown/RaidIccActionContext.h"
#include "Ai/Raid/Icecrown/RaidIccTriggerContext.h"
SharedNamedObjectContextList<Strategy> AiObjectContext::sharedStrategyContexts;
SharedNamedObjectContextList<Action> AiObjectContext::sharedActionContexts;
@@ -98,93 +56,6 @@ void AiObjectContext::BuildSharedContexts()
BuildSharedValueContexts(sharedValueContexts);
}
void AiObjectContext::BuildSharedStrategyContexts(SharedNamedObjectContextList<Strategy>& strategyContexts)
{
strategyContexts.Add(new StrategyContext());
strategyContexts.Add(new MovementStrategyContext());
strategyContexts.Add(new AssistStrategyContext());
strategyContexts.Add(new QuestStrategyContext());
strategyContexts.Add(new DungeonStrategyContext());
strategyContexts.Add(new RaidStrategyContext());
}
void AiObjectContext::BuildSharedActionContexts(SharedNamedObjectContextList<Action>& actionContexts)
{
actionContexts.Add(new ActionContext());
actionContexts.Add(new ChatActionContext());
actionContexts.Add(new WorldPacketActionContext());
actionContexts.Add(new RaidAq20ActionContext());
actionContexts.Add(new RaidMcActionContext());
actionContexts.Add(new RaidBwlActionContext());
actionContexts.Add(new RaidKarazhanActionContext());
actionContexts.Add(new RaidGruulsLairActionContext());
actionContexts.Add(new RaidMagtheridonActionContext());
actionContexts.Add(new RaidSSCActionContext());
actionContexts.Add(new RaidTempestKeepActionContext());
actionContexts.Add(new RaidOsActionContext());
actionContexts.Add(new RaidEoEActionContext());
actionContexts.Add(new RaidVoAActionContext());
actionContexts.Add(new RaidUlduarActionContext());
actionContexts.Add(new RaidOnyxiaActionContext());
actionContexts.Add(new RaidIccActionContext());
actionContexts.Add(new WotlkDungeonUKActionContext());
actionContexts.Add(new WotlkDungeonNexActionContext());
actionContexts.Add(new WotlkDungeonANActionContext());
actionContexts.Add(new WotlkDungeonOKActionContext());
actionContexts.Add(new WotlkDungeonDTKActionContext());
actionContexts.Add(new WotlkDungeonVHActionContext());
actionContexts.Add(new WotlkDungeonGDActionContext());
actionContexts.Add(new WotlkDungeonHoSActionContext());
actionContexts.Add(new WotlkDungeonHoLActionContext());
actionContexts.Add(new WotlkDungeonOccActionContext());
actionContexts.Add(new WotlkDungeonUPActionContext());
actionContexts.Add(new WotlkDungeonCoSActionContext());
actionContexts.Add(new WotlkDungeonFoSActionContext());
actionContexts.Add(new WotlkDungeonPoSActionContext());
actionContexts.Add(new WotlkDungeonToCActionContext());
}
void AiObjectContext::BuildSharedTriggerContexts(SharedNamedObjectContextList<Trigger>& triggerContexts)
{
triggerContexts.Add(new TriggerContext());
triggerContexts.Add(new ChatTriggerContext());
triggerContexts.Add(new WorldPacketTriggerContext());
triggerContexts.Add(new RaidAq20TriggerContext());
triggerContexts.Add(new RaidMcTriggerContext());
triggerContexts.Add(new RaidBwlTriggerContext());
triggerContexts.Add(new RaidKarazhanTriggerContext());
triggerContexts.Add(new RaidGruulsLairTriggerContext());
triggerContexts.Add(new RaidMagtheridonTriggerContext());
triggerContexts.Add(new RaidSSCTriggerContext());
triggerContexts.Add(new RaidTempestKeepTriggerContext());
triggerContexts.Add(new RaidOsTriggerContext());
triggerContexts.Add(new RaidEoETriggerContext());
triggerContexts.Add(new RaidVoATriggerContext());
triggerContexts.Add(new RaidUlduarTriggerContext());
triggerContexts.Add(new RaidOnyxiaTriggerContext());
triggerContexts.Add(new RaidIccTriggerContext());
triggerContexts.Add(new WotlkDungeonUKTriggerContext());
triggerContexts.Add(new WotlkDungeonNexTriggerContext());
triggerContexts.Add(new WotlkDungeonANTriggerContext());
triggerContexts.Add(new WotlkDungeonOKTriggerContext());
triggerContexts.Add(new WotlkDungeonDTKTriggerContext());
triggerContexts.Add(new WotlkDungeonVHTriggerContext());
triggerContexts.Add(new WotlkDungeonGDTriggerContext());
triggerContexts.Add(new WotlkDungeonHoSTriggerContext());
triggerContexts.Add(new WotlkDungeonHoLTriggerContext());
triggerContexts.Add(new WotlkDungeonOccTriggerContext());
triggerContexts.Add(new WotlkDungeonUPTriggerContext());
triggerContexts.Add(new WotlkDungeonCoSTriggerContext());
triggerContexts.Add(new WotlkDungeonFoSTriggerContext());
triggerContexts.Add(new WotlkDungeonPoSTriggerContext());
triggerContexts.Add(new WotlkDungeonToCTriggerContext());
}
void AiObjectContext::BuildSharedValueContexts(SharedNamedObjectContextList<UntypedValue>& valueContexts)
{
valueContexts.Add(new ValueContext());
}
std::vector<std::string> AiObjectContext::Save()
{
std::vector<std::string> result;

View File

@@ -0,0 +1,57 @@
#include "AiObjectContext.h"
#include "ActionContext.h"
#include "ChatActionContext.h"
#include "WorldPacketActionContext.h"
#include "Ai/Raid/Aq20/RaidAq20ActionContext.h"
#include "Ai/Raid/MoltenCore/RaidMcActionContext.h"
#include "Ai/Raid/BlackwingLair/RaidBwlActionContext.h"
#include "Ai/Raid/Karazhan/RaidKarazhanActionContext.h"
#include "Ai/Raid/GruulsLair/RaidGruulsLairActionContext.h"
#include "Ai/Raid/Naxxramas/RaidNaxxActionContext.h"
#include "Ai/Raid/Magtheridon/RaidMagtheridonActionContext.h"
#include "Ai/Raid/SerpentshrineCavern/RaidSSCActionContext.h"
#include "Ai/Raid/TempestKeep/RaidTempestKeepActionContext.h"
#include "Ai/Raid/ObsidianSanctum/RaidOsActionContext.h"
#include "Ai/Raid/EyeOfEternity/RaidEoEActionContext.h"
#include "Ai/Raid/VaultOfArchavon/RaidVoAActionContext.h"
#include "Ai/Raid/Ulduar/RaidUlduarActionContext.h"
#include "Ai/Raid/Onyxia/RaidOnyxiaActionContext.h"
#include "Ai/Raid/Icecrown/RaidIccActionContext.h"
#include "Ai/Dungeon/WotlkDungeonActionContext.h"
void AiObjectContext::BuildSharedActionContexts(SharedNamedObjectContextList<Action>& actionContexts)
{
actionContexts.Add(new ActionContext());
actionContexts.Add(new ChatActionContext());
actionContexts.Add(new WorldPacketActionContext());
actionContexts.Add(new RaidAq20ActionContext());
actionContexts.Add(new RaidMcActionContext());
actionContexts.Add(new RaidBwlActionContext());
actionContexts.Add(new RaidKarazhanActionContext());
actionContexts.Add(new RaidGruulsLairActionContext());
actionContexts.Add(new RaidMagtheridonActionContext());
actionContexts.Add(new RaidSSCActionContext());
actionContexts.Add(new RaidTempestKeepActionContext());
actionContexts.Add(new RaidNaxxActionContext());
actionContexts.Add(new RaidOsActionContext());
actionContexts.Add(new RaidEoEActionContext());
actionContexts.Add(new RaidVoAActionContext());
actionContexts.Add(new RaidUlduarActionContext());
actionContexts.Add(new RaidOnyxiaActionContext());
actionContexts.Add(new RaidIccActionContext());
actionContexts.Add(new WotlkDungeonUKActionContext());
actionContexts.Add(new WotlkDungeonNexActionContext());
actionContexts.Add(new WotlkDungeonANActionContext());
actionContexts.Add(new WotlkDungeonOKActionContext());
actionContexts.Add(new WotlkDungeonDTKActionContext());
actionContexts.Add(new WotlkDungeonVHActionContext());
actionContexts.Add(new WotlkDungeonGDActionContext());
actionContexts.Add(new WotlkDungeonHoSActionContext());
actionContexts.Add(new WotlkDungeonHoLActionContext());
actionContexts.Add(new WotlkDungeonOccActionContext());
actionContexts.Add(new WotlkDungeonUPActionContext());
actionContexts.Add(new WotlkDungeonCoSActionContext());
actionContexts.Add(new WotlkDungeonFoSActionContext());
actionContexts.Add(new WotlkDungeonPoSActionContext());
actionContexts.Add(new WotlkDungeonToCActionContext());
}

View File

@@ -0,0 +1,14 @@
#include "AiObjectContext.h"
#include "StrategyContext.h"
#include "Ai/Dungeon/DungeonStrategyContext.h"
#include "Ai/Raid/RaidStrategyContext.h"
void AiObjectContext::BuildSharedStrategyContexts(SharedNamedObjectContextList<Strategy>& strategyContexts)
{
strategyContexts.Add(new StrategyContext());
strategyContexts.Add(new MovementStrategyContext());
strategyContexts.Add(new AssistStrategyContext());
strategyContexts.Add(new QuestStrategyContext());
strategyContexts.Add(new DungeonStrategyContext());
strategyContexts.Add(new RaidStrategyContext());
}

View File

@@ -0,0 +1,57 @@
#include "AiObjectContext.h"
#include "TriggerContext.h"
#include "ChatTriggerContext.h"
#include "WorldPacketTriggerContext.h"
#include "Ai/Raid/Aq20/RaidAq20TriggerContext.h"
#include "Ai/Raid/MoltenCore/RaidMcTriggerContext.h"
#include "Ai/Raid/BlackwingLair/RaidBwlTriggerContext.h"
#include "Ai/Raid/Karazhan/RaidKarazhanTriggerContext.h"
#include "Ai/Raid/GruulsLair/RaidGruulsLairTriggerContext.h"
#include "Ai/Raid/Magtheridon/RaidMagtheridonTriggerContext.h"
#include "Ai/Raid/Naxxramas/RaidNaxxTriggerContext.h"
#include "Ai/Raid/SerpentshrineCavern/RaidSSCTriggerContext.h"
#include "Ai/Raid/TempestKeep/RaidTempestKeepTriggerContext.h"
#include "Ai/Raid/ObsidianSanctum/RaidOsTriggerContext.h"
#include "Ai/Raid/EyeOfEternity/RaidEoETriggerContext.h"
#include "Ai/Raid/VaultOfArchavon/RaidVoATriggerContext.h"
#include "Ai/Raid/Ulduar/RaidUlduarTriggerContext.h"
#include "Ai/Raid/Onyxia/RaidOnyxiaTriggerContext.h"
#include "Ai/Raid/Icecrown/RaidIccTriggerContext.h"
#include "Ai/Dungeon/WotlkDungeonTriggerContext.h"
void AiObjectContext::BuildSharedTriggerContexts(SharedNamedObjectContextList<Trigger>& triggerContexts)
{
triggerContexts.Add(new TriggerContext());
triggerContexts.Add(new ChatTriggerContext());
triggerContexts.Add(new WorldPacketTriggerContext());
triggerContexts.Add(new RaidAq20TriggerContext());
triggerContexts.Add(new RaidMcTriggerContext());
triggerContexts.Add(new RaidBwlTriggerContext());
triggerContexts.Add(new RaidKarazhanTriggerContext());
triggerContexts.Add(new RaidGruulsLairTriggerContext());
triggerContexts.Add(new RaidMagtheridonTriggerContext());
triggerContexts.Add(new RaidNaxxTriggerContext());
triggerContexts.Add(new RaidSSCTriggerContext());
triggerContexts.Add(new RaidTempestKeepTriggerContext());
triggerContexts.Add(new RaidOsTriggerContext());
triggerContexts.Add(new RaidEoETriggerContext());
triggerContexts.Add(new RaidVoATriggerContext());
triggerContexts.Add(new RaidUlduarTriggerContext());
triggerContexts.Add(new RaidOnyxiaTriggerContext());
triggerContexts.Add(new RaidIccTriggerContext());
triggerContexts.Add(new WotlkDungeonUKTriggerContext());
triggerContexts.Add(new WotlkDungeonNexTriggerContext());
triggerContexts.Add(new WotlkDungeonANTriggerContext());
triggerContexts.Add(new WotlkDungeonOKTriggerContext());
triggerContexts.Add(new WotlkDungeonDTKTriggerContext());
triggerContexts.Add(new WotlkDungeonVHTriggerContext());
triggerContexts.Add(new WotlkDungeonGDTriggerContext());
triggerContexts.Add(new WotlkDungeonHoSTriggerContext());
triggerContexts.Add(new WotlkDungeonHoLTriggerContext());
triggerContexts.Add(new WotlkDungeonOccTriggerContext());
triggerContexts.Add(new WotlkDungeonUPTriggerContext());
triggerContexts.Add(new WotlkDungeonCoSTriggerContext());
triggerContexts.Add(new WotlkDungeonFoSTriggerContext());
triggerContexts.Add(new WotlkDungeonPoSTriggerContext());
triggerContexts.Add(new WotlkDungeonToCTriggerContext());
}

View File

@@ -0,0 +1,7 @@
#include "AiObjectContext.h"
#include "ValueContext.h"
void AiObjectContext::BuildSharedValueContexts(SharedNamedObjectContextList<UntypedValue>& valueContexts)
{
valueContexts.Add(new ValueContext());
}

View File

@@ -30,7 +30,7 @@ bool ExternalEventHelper::ParseChatCommand(std::string const command, Player* ow
return true;
}
if (!ChatHelper::parseable(command))
if (!ChatHelper::parseableItem(command))
return false;
HandleCommand("c", command, owner);

View File

@@ -9,6 +9,7 @@
#include "ArenaTeamMgr.h"
#include "DatabaseEnv.h"
#include "PlayerbotAI.h"
#include "RaceMgr.h"
#include "ScriptMgr.h"
#include "SharedDefines.h"
#include "SocialMgr.h"
@@ -60,7 +61,7 @@ Player* RandomPlayerbotFactory::CreateRandomBot(WorldSession* session, uint8 cls
const bool alliance = static_cast<bool>(urand(0, 1));
std::vector<uint8> raceOptions;
for (uint8 race = RACE_HUMAN; race < MAX_RACES; ++race)
for (uint8 race = RACE_HUMAN; race < sRaceMgr->GetMaxRaces(); ++race)
{
// skip disabled with config races
if ((1 << (race - 1)) & sWorld->getIntConfig(CONFIG_CHARACTER_CREATING_DISABLED_RACEMASK))

View File

@@ -1532,6 +1532,21 @@ std::vector<std::string> PlayerbotAI::GetStrategies(BotState type)
void PlayerbotAI::ApplyInstanceStrategies(uint32 mapId, bool tellMaster)
{
static const std::vector<std::string> allInstanceStrategies =
{
"aq20", "bwl", "karazhan", "gruulslair", "icc", "magtheridon", "moltencore",
"naxx", "onyxia", "ssc", "tempestkeep", "ulduar", "voa", "wotlk-an", "wotlk-cos",
"wotlk-dtk", "wotlk-eoe", "wotlk-fos", "wotlk-gd", "wotlk-hol", "wotlk-hor",
"wotlk-hos", "wotlk-nex", "wotlk-occ", "wotlk-ok", "wotlk-os", "wotlk-pos",
"wotlk-toc", "wotlk-uk", "wotlk-up", "wotlk-vh"
};
for (const std::string& strat : allInstanceStrategies)
{
engines[BOT_STATE_COMBAT]->removeStrategy(strat);
engines[BOT_STATE_NON_COMBAT]->removeStrategy(strat);
}
std::string strategyName;
switch (mapId)
{
@@ -1550,6 +1565,9 @@ void PlayerbotAI::ApplyInstanceStrategies(uint32 mapId, bool tellMaster)
case 532:
strategyName = "karazhan"; // Karazhan
break;
case 533:
strategyName = "naxx"; // Naxxramas
break;
case 544:
strategyName = "magtheridon"; // Magtheridon's Lair
break;
@@ -1628,10 +1646,13 @@ void PlayerbotAI::ApplyInstanceStrategies(uint32 mapId, bool tellMaster)
default:
break;
}
if (strategyName.empty())
return;
engines[BOT_STATE_COMBAT]->addStrategy(strategyName);
engines[BOT_STATE_NON_COMBAT]->addStrategy(strategyName);
if (tellMaster && !strategyName.empty())
{
std::ostringstream out;

View File

@@ -37,6 +37,7 @@
#include "PlayerbotFactory.h"
#include "Playerbots.h"
#include "Position.h"
#include "RaceMgr.h"
#include "Random.h"
#include "RandomPlayerbotFactory.h"
#include "ServerFacade.h"
@@ -1995,7 +1996,7 @@ void RandomPlayerbotMgr::PrepareTeleportCache()
}
// add all initial position
for (uint32 i = 1; i < MAX_RACES; i++)
for (uint32 i = 1; i < sRaceMgr->GetMaxRaces(); i++)
{
for (uint32 j = 1; j < MAX_CLASSES; j++)
{
@@ -2008,7 +2009,7 @@ void RandomPlayerbotMgr::PrepareTeleportCache()
for (int32 l = 1; l <= 5; l++)
{
if ((1 << (i - 1)) & RACEMASK_ALLIANCE)
if ((1 << (i - 1)) & sRaceMgr->GetAllianceRaceMask())
allianceStarterPerLevelCache[(uint8)l].push_back(pos);
else
hordeStarterPerLevelCache[(uint8)l].push_back(pos);
@@ -3126,7 +3127,7 @@ void RandomPlayerbotMgr::PrintStats()
std::map<uint8, uint32> lvlPerRace;
std::map<uint8, uint32> lvlPerClass;
for (uint8 race = RACE_HUMAN; race < MAX_RACES; ++race)
for (uint8 race = RACE_HUMAN; race < sRaceMgr->GetMaxRaces(); ++race)
{
perRace[race] = 0;
lvlPerRace[race] = 0;
@@ -3273,7 +3274,7 @@ void RandomPlayerbotMgr::PrintStats()
}
LOG_INFO("playerbots", "Bots race:");
for (uint8 race = RACE_HUMAN; race < MAX_RACES; ++race)
for (uint8 race = RACE_HUMAN; race < sRaceMgr->GetMaxRaces(); ++race)
{
if (perRace[race])
{

View File

@@ -2255,10 +2255,7 @@ void RandomItemMgr::BuildEquipCacheNew()
continue;
}
// Unobtainable or unusable items
if (itemId == 12468 || // Chilton Wand
itemId == 22784 || // Sunwell Orb
itemId == 46978) // Totem of the Earthen Ring
if (sPlayerbotAIConfig.unobtainableItems.find(itemId) != sPlayerbotAIConfig.unobtainableItems.end())
continue;
equipCacheNew[proto->RequiredLevel][proto->InventoryType].push_back(itemId);

View File

@@ -14,6 +14,7 @@
#include "MapMgr.h"
#include "PathGenerator.h"
#include "Playerbots.h"
#include "RaceMgr.h"
#include "TransportMgr.h"
#include "VMapFactory.h"
#include "VMapMgr2.h"
@@ -3335,7 +3336,7 @@ void TravelMgr::LoadQuestTravelTable()
std::ostringstream out;
for (uint8 race = RACE_HUMAN; race < MAX_RACES; race++)
for (uint8 race = RACE_HUMAN; race < sRaceMgr->GetMaxRaces(); race++)
{
for (uint8 cls = CLASS_WARRIOR; cls < MAX_CLASSES; ++cls)
{

View File

@@ -11,6 +11,7 @@
#include "BudgetValues.h"
#include "PathGenerator.h"
#include "Playerbots.h"
#include "RaceMgr.h"
#include "ServerFacade.h"
#include "TransportMgr.h"
@@ -1660,7 +1661,7 @@ void TravelNodeMap::generateStartNodes()
startNames[RACE_GNOME] = "Dwarf and Gnome";
startNames[RACE_TROLL] = "Orc and Troll";
for (uint32 i = 0; i < MAX_RACES; i++)
for (uint32 i = 0; i < sRaceMgr->GetMaxRaces(); i++)
{
for (uint32 j = 0; j < MAX_CLASSES; j++)
{

View File

@@ -180,8 +180,13 @@ bool PlayerbotAIConfig::Initialize()
"165739,165738,175245,175970,176325,176327,123329,2560"),
disallowedGameObjects);
LoadSet<std::set<uint32>>(
sConfigMgr->GetOption<std::string>("AiPlayerbot.AttunementQuests", "10279,10277,10282,10283,10284,10285,10296,10297,10298,11481,11482,11488,11490,11492,10901"),
sConfigMgr->GetOption<std::string>("AiPlayerbot.AttunementQuests", "10279,10277,10282,10283,10284,10285,10296,10297,10298,11481,11482,11488,11490,11492,10901,10888,10445,10985"),
attunementQuests);
LoadSet<std::set<uint32>>(
sConfigMgr->GetOption<std::string>("AiPlayerbot.UnobtainableItems", "12468,46978"),
unobtainableItems);
botAutologin = sConfigMgr->GetOption<bool>("AiPlayerbot.BotAutologin", false);
randomBotAutologin = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotAutologin", true);
minRandomBots = sConfigMgr->GetOption<int32>("AiPlayerbot.MinRandomBots", 500);

View File

@@ -99,6 +99,7 @@ public:
bool tellWhenAvoidAoe;
std::set<uint32> disallowedGameObjects;
std::set<uint32> attunementQuests;
std::set<uint32> unobtainableItems;
uint32 openGoSpell;
bool randomBotAutologin;

View File

@@ -17,6 +17,7 @@
#include "Playerbots.h"
#include "BattlefieldScript.h"
#include "Channel.h"
#include "Config.h"
#include "DatabaseEnv.h"
@@ -518,12 +519,20 @@ public:
void OnBattlegroundEnd(Battleground* bg, TeamId /*winnerTeam*/) override { bgStrategies.erase(bg->GetInstanceID()); }
};
// Workaround for missing InitEnabledHooksIfNeeded for new BattlefieldScript in ScriptMgr
class PlayerbotsBattlefieldScript : public BattlefieldScript
{
public:
PlayerbotsBattlefieldScript() : BattlefieldScript("PlayerbotsBattlefieldScript") { }
};
void AddPlayerbotsSecureLoginScripts();
void AddSC_TempestKeepBotScripts();
void AddPlayerbotsScripts()
{
new PlayerbotsBattlefieldScript();
new PlayerbotsDatabaseScript();
new PlayerbotsPlayerScript();
new PlayerbotsMiscScript();