Compare commits

..

18 Commits

Author SHA1 Message Date
NoxMax
02e8465a3b Fix/Feat: Stop bots in party from PVP when master isn't, and PVP probablity system (#1914)
There are two related PVP components in this PR. First is the simple yet
fundamental change to bot behaviour when they are in party. Right now
bots with a master will go into PVP when there's a nearby PVP target,
even if master is not in PVP. This absolutely should not happen. Bots
should not consider PVP at all if master is not in PVP. The fix is only
3 lines in EnemyPlayerValue

The second component is introducing PVP probabilities, to make decisions
more realistic. Right now even a level 1 bot will 100% go into PVP if it
sees a level 80 PVP target. They can't help themselves. So the change
here addresses that insanity. Several thresholds (subject to community
review) are introduced:

1. Bots will not fight a target 5 or more levels higher than them
2. Bots have a 25% chance starting a fight with a target +/- 4 levels
from them.
3. Bots have a 50% chance starting a fight with a target +/- 3 levels
from them.
4. Bots have a 75% chance starting a fight with a target +/- 2 levels
from them.
5. Bots have a 100% chance starting a fight with a target +/- 1 level
from them.
6. Bots have a 25% chance starting a fight with a target 5 or more
levels below them (ganking. thought it would be funny, and technically
realistic of player behaviour)

Exception of course exist for BG/Arena/Duel, and in capitals where bots
will always PVP. Also bots will always defend themselves if attacked.

Few notes: 
1. The if/ else if logic can be further simplified, but only if we use
thresholds that are different by one. So current logic allows for
flexibility of using values like 10/7/5/3 instead of 5/4/3/2.
2. The caching system is per-bot basis. So for some target X, if some
bot decides to attack it, another bot will make its own decision. At
first I used a simplified global system (thinking there might be
performance concerns) where if one bot decides to attack a target then
they all do, but when I switched to the more realistic per-bot basis, I
didn't see an effect on performance.
3. Variables are obviously not configurable right now. I'm starting to
see Bash's POV that maybe we have too many configs 😬 Still,
they can be easily exposed in the future, and if someone is reading this
then, remember to change constexpr to const.
2026-01-08 22:44:09 +01:00
Crow
f53a8704eb Remove InitSpells() (#1983)
InitSpells() is never called, and even if it were to be, it would do
nothing but call InitAvailableSpells().
2026-01-08 11:46:36 -08:00
bashermens
e5525958c8 [BUG FIX] Movement flag update called for bots while rooted (#1991)
Fix for the error "Attempted sending heartbeat with root flag for guid"

The core does not allow movement flag updates when unit/player has the
MOVEMENTFLAG_ROOT flag. Change scope bots alone.
2026-01-08 20:38:49 +01:00
Alex Dcnh
9ae457d069 [BUG FIX] - Preserve buffs on summon by avoiding aura clears (#1958)
### Summary

- Add a preserveAuras flag to summon teleport helpers.
- Keep auras when using the summon command.
- Leave meeting stone behavior unchanged (auras can still be cleared
there).

### Motivation
Summon command was clearing buffs (e.g., Hellscream’s Warsong in ICC)
because auras were explicitly interrupted before teleporting. This
change keeps existing meeting stone behavior but preserves auras for the
summon path to avoid losing valid buffs.

### Details
Solve : 
https://github.com/mod-playerbots/mod-playerbots/issues/1862
https://github.com/mod-playerbots/mod-playerbots/issues/1942

### Testing
Go to ICC and téléport Bots
2026-01-07 15:24:41 +01:00
privatecore
c9e98a6b4e [BUG FIX] the issue where bots on vehicles cant move (#1969)
Fix the [issue
#1964](https://github.com/mod-playerbots/mod-playerbots/issues/1964)
where bots on vehicles cant move. Transfer the allowed movement logic to
`PlayerbotAI::CanMove` -- Ulduar raid strategy also used this method for
the Malady of the Mind trigger.

The only remaining `IsRooted` check is in
`MovementAction::UpdateMovementState`. I'm not sure if this also needs
to be updated for the vehicles case, but on paper, everything should
work as intended. A more complex solution to fix this issue can be found
in the
[comment](https://github.com/mod-playerbots/mod-playerbots/issues/1902#issuecomment-3703795779),
but for now, these changes are safer.
2026-01-07 15:24:18 +01:00
bashermens
3d9623f119 [CRASH FIX] Various crash fixes in regard to defensive checks around unit/targets and finer grained checks on tele ack (#1951)
Some basic defense checks on mainly unit/targets used in public
functions, and some minor tweaks with teleport ack.
https://github.com/mod-playerbots/mod-playerbots/issues/1934
https://github.com/mod-playerbots/mod-playerbots/issues/1957
2026-01-07 15:22:36 +01:00
Nicolas Lebacq
c9cc4324d3 NextAction refactoring to eliminate sentinel arrays and pointers (#1923)
<!--
> [!WARNING]
> **This is a DRAFT PR.**
> The structure is not definitive. The code might not be optimised yet.
It might not even start nor compile yet.
> **Don't panic.  It's going to be ok. 👌 We can make modifications, we
can fix things.** 😁
-->
# Description

This PR aims to refactor the NextAction declaration to achieve two
goals:

## Eliminate C-style sentinel arrays

Currently, a double pointer (`NextAction**`) approach is being used.
This an old pre-C++11 (< 2011) trick before `std::vector<>` became a
thing.
This approach is painful for developers because they constantly need to
declare their `NextAction` arrays as:
```cpp
NextAction::array(0, new NextAction("foo", 1.0f), nullptr)
```
Instead of:
```cpp
{ new NextAction("foo", 1.0f) }
```
The first argument of `NextAction::array` is actually a hack. It is used
to have a named argument so `va_args` can find the remaining arguments.
It is set to 0 everywhere but in fact does nothing. This is very
confusing to people unfamiliar with this antiquated syntax.
The last argument `nullptr` is what we call a sentinel. It's a `nullptr`
because `va_args` is looking for a `nullptr` to stop iterating. It's
also a hack and also leads to confusion.

## Eliminate unnecessary pointers for `NextAction`

Pointers can be used for several reasons, to cite a few:
- Indicate strong, absolute identity.
- Provide strong but transferable ownership (unlike references).
- When a null value is acceptable (`nullptr`).
- When copy is expensive.

`NextAction` meets none of these criteria:
- It has no identity because it is purely behavioural.
- It is never owned by anything as it is passed around and never fetched
from a registry.
- The only situations where it can be `nullptr` are errors that should
in fact throw an `std::invalid_argument` instead.
- They are extremely small objects that embark a single `std::string`
and a single `float`.

Pointers should be avoided when not strictly necessary because they can
quickly lead to undefined behaviour due to unhandled `nullptr`
situations. They also make the syntax heavier due to the necessity to
constantly check for `nullptr`. Finally, they aren't even good for
performance in that situation because shifting a pointer so many times
is likely more expensive than copying such a trivial object.

# End goal

The end goal is to declare `NextAction` arrays this way:
```cpp
{ NextAction("foo", 1.0f) }
```

> [!NOTE]
> Additional note: `NextAction` is nothing but a hacky proxy to an
`Action` constructor. This should eventually be reworked to use handles
instead of strings. This would make copying `NextAction` even cheaper
and remove the need for the extremely heavy stringly typed current
approach. Stringly typed entities are a known anti-pattern so we need to
move on from those.
2026-01-06 12:37:39 +01:00
Keleborn
b13fb7d12a Bugfix - PlayerbotGuildMgr not respecting real player guilds. (#1972)
Minor fix to address small bug introduced by #1913
2026-01-06 01:31:02 +01:00
Revision
962fdeb3d1 Updated to support latest master (#1965)
This needs extensive testing.

What's important is spells given to bots. Class spells, mounts,
professions etc. Make sure they get the spells they should, when they
should.

Requires https://github.com/mod-playerbots/azerothcore-wotlk/pull/132
2026-01-05 15:06:45 +01:00
Keleborn
83c6977de5 Refactor guild managment into a singleton (#1913)
The idea is to centralize the creation, assignment, and management of
bot guilds into a single class that can be referenced.

The way this is intended to work.

when the manager is created, if the config option to delete guilds is
set, then it deletes all bot guilds.

On startup 
1. Load all guild names from database. Shuffle keys for some
randomization.
2. Load Guilds from database
3. For existing guilds, identify the guild faction, number of members,
and assess if the guild is 'full' based on the number of bots set in
config.
4. Determine if the leader of the guild is a real player based on the
leader account.
5. Mark any playerbot guild names as not available (false).

The validation process (2-5) is set to run once an hour. 

Guild Creation.
Now guild creation occurs on an as needed bases during the
initialization process. Previously, all of the guilds would be created
at once, and then randomly assigned.
When a bot is not in a guild during initialization, it will check if
there are any partially filled guilds of that bots faction where the bot
can be assigned to. If not, and the cache of bot guilds is less than the
set number in config, it will randomly return the available name. This
then goes to the CreateGuild function where the core guild manager
creates a guild, the guild emblem is set, and the cache updated.
If a bot is assigned to guild, but fails to join then it throws an
error.

Checking for real player guilds function now lives in the guild manager.

---------

Co-authored-by: bashermens <31279994+hermensbas@users.noreply.github.com>
2026-01-03 15:15:28 +01:00
Revision
686fe513b2 Removed strategies for Naxxramas until they can be rewritten without core changes (#1961)
Merging this isn't required but rather to provide an option to core
changes.

The scripts can be written from scratch when someone feels like doing it
without touching the core.

This shouldn't be merged unless
https://github.com/mod-playerbots/azerothcore-wotlk/pull/130 is merged
too.
2026-01-03 14:50:35 +01:00
Revision
61402e83a1 Fixed filename (#1963)
Added missing underscores to the filename
2026-01-03 14:47:31 +01:00
Crow
b16789fa54 Remove "potion" strategy from arenas + code cleanups (#1922)
Lines 481 through 484 are the only substantive changes. Bots were having
the potion strategy added in arenas, and since potions are not allowed
in arenas, this caused them to lock up and attempt to repeatedly drink
potions when under the applicable health or mana trigger thresholds. Now
they won't have the potion strategy in arenas.

Otherwise, I noticed a bunch of magic numbers for spec tabs and so went
ahead and did some refactoring:
1. All references to spec tab numbers now use the appropriate constant
name.
2. A bunch of extra braces were deleted.
3. DEATHKNIGHT_TAB was changed to DEATH_KNIGHT_TAB, and
HUNTER_TAB_BEASTMASTERY was changed to HUNTER_TAB_BEAST_MASTERY, to
reflect the correct names of the class and spec.
4. Deleted some comments that were clearly unneeded. There's much more
that can be cleaned up, and probably the entire logic sequence for
adding/removing strategies in AiFactory.cpp can be redone, but that's
above my pay grade.
2025-12-30 21:17:14 +01:00
BeardBear
8f638b6a66 Add secure login handling for playerbots (#1953)
Introduces PlayerbotsSecureLoginServerScript to handle secure login
scenarios for playerbots. Ensures that if a playerbot is already online
when a login is attempted, it is properly logged out before allowing the
new session. Registers the new script in the Playerbots initialization.
2025-12-30 21:13:36 +01:00
Keleborn
33f5e733dc feat. Enable bots to respond to GM messages. (#1909)
I found an existing config option that allows bots to speak without a
master, and extended its use so that protected commands (nc, co) are
taken from a bot by GM players. This is primarily a debugging tool and
shouldnt be on generally.

My testing of this is the following. 
With the option off, no error is seen, but no text output is shown. 

With the option on 
GM accounts (Whether GM is on or off) will respond to nc and co commands
by just saying the output directly. Strategies can be changed by GMs for
bots whom they are not masters for (Note, im pretty sure that a GM could
change the strategies even before this change, you just couldnt see the
output of that.)

For non GM characters you get "Invite me to your group first" with the
option on/off.
2025-12-30 00:53:37 +01:00
Keleborn
9917863ca1 Feat. Add Fishing action and fish with master. (#1433)
### Update :Thank you to @notOrrytrout from prompting me to work on
this. Its been a huge learning experience.

With @notOrrytrout I started working on enabling bot fishing with
master, but also on their own.
The first commit didnt crash, showing that it was possible to have a bot
cast when master does. Currently it compiles but crashes when you try to
fish with a bot in the group, whether the bot has fishing or not. It
makes me think that the check in FishingValues is broken somehow, but I
cant figure out how.

---------

Co-authored-by: bash <31279994+hermensbas@users.noreply.github.com>
2025-12-27 19:50:18 +01:00
bashermens
2317652d72 Full revert: no xp bot when master has xp disabled (#1948)
Will reimplement the feature later in time.
2025-12-25 03:00:21 +01:00
bashermens
1fcd6c5cda Quickfix: performance problems (XP gain when master has XP turned off) (#1947)
Disable bot XP gain when master has XP turned off feature has a big
performance impact.

Instead of reverting placed back to original code before the feature
PR's for now as quickfix
2025-12-25 02:07:59 +01:00
263 changed files with 8675 additions and 7821 deletions

View File

@@ -25,6 +25,7 @@
# CHEATS
# SPELLS
# FLIGHTPATH
# PROFESSIONS
# RANDOMBOT-SPECIFIC SETTINGS
# GENERAL
# LEVELS
@@ -44,7 +45,7 @@
# HUNTER
# ROGUE
# PRIEST
# DEATHKNIGHT
# DEATH KNIGHT
# SHAMAN
# MAGE
# WARLOCK
@@ -55,7 +56,7 @@
# HUNTER
# ROGUE
# PRIEST
# DEATHKNIGHT
# DEATH KNIGHT
# SHAMAN
# MAGE
# WARLOCK
@@ -579,6 +580,24 @@ AiPlayerbot.BotTaxiGapJitterMs = 100
#
####################################################################################################
####################################################################################################
# PROFESSIONS
# Random bots currently do not get professions.
#
# EnableFishingWithMaster automatically adds the 'master fishing' strategy to bots that can fish that can.
# Default: 1 (Enabled)
AiPlayerbot.EnableFishingWithMaster = 1
#FishingDistance sets how far a bot without a master will search for water, while FishingDistanceFromMaster limits it to a closer range, and overrides the following distance to the same value. EndFishingWithMaster sets the distance from water a bot needs to have to automatically drop the 'master fishing' strategy.
AiPlayerbot.FishingDistanceFromMaster = 10.0
AiPlayerbot.FishingDistance = 40.0
AiPlayerbot.EndFishingWithMaster = 30.0
#
#
#
####################################################################################################
#######################################
# #
# RANDOMBOT-SPECIFIC SETTINGS #
@@ -1455,7 +1474,7 @@ AiPlayerbot.PremadeSpecLink.5.5.80 = 50332031003--005323241223112003102311351
####################################################################################################
####################################################################################################
# DEATHKNIGHT
# DEATH KNIGHT
#
#
@@ -1778,7 +1797,7 @@ AiPlayerbot.RandomClassSpecIndex.5.2 = 2
####################################################################################################
####################################################################################################
# DEATHKNIGHT
# DEATH KNIGHT
#
#

View File

@@ -0,0 +1,15 @@
DELETE FROM ai_playerbot_texts WHERE name IN ('no_fishing_pole_error');
DELETE FROM ai_playerbot_texts_chance WHERE name IN ('no_fishing_pole_error');
INSERT INTO ai_playerbot_texts (id, name, text, say_type, reply_type, text_loc1, text_loc2, text_loc3, text_loc4, text_loc5, text_loc6, text_loc7, text_loc8) VALUES
(1736, 'no_fishing_pole_error', "I don't have a Fishing Pole", 0, 0,
"낚싯대가 없습니다",
"Je nai pas de canne à pêche",
"Ich habe keine Angelrute",
"我沒有釣魚竿",
"我没有钓鱼竿",
"No tengo una caña de pescar",
"No tengo una caña de pescar",
"У меня нет удочки");
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES ('no_fishing_pole_error', 100);

View File

@@ -140,37 +140,37 @@ BotRoles AiFactory::GetPlayerRoles(Player* player)
switch (player->getClass())
{
case CLASS_PRIEST:
if (tab == 2)
if (tab == PRIEST_TAB_SHADOW)
role = BOT_ROLE_DPS;
else
role = BOT_ROLE_HEALER;
break;
case CLASS_SHAMAN:
if (tab == 2)
if (tab == SHAMAN_TAB_RESTORATION)
role = BOT_ROLE_HEALER;
else
role = BOT_ROLE_DPS;
break;
case CLASS_WARRIOR:
if (tab == 2)
if (tab == WARRIOR_TAB_PROTECTION)
role = BOT_ROLE_TANK;
else
role = BOT_ROLE_DPS;
break;
case CLASS_PALADIN:
if (tab == 0)
if (tab == PALADIN_TAB_HOLY)
role = BOT_ROLE_HEALER;
else if (tab == 1)
else if (tab == PALADIN_TAB_PROTECTION)
role = BOT_ROLE_TANK;
else if (tab == 2)
else if (tab == PALADIN_TAB_RETRIBUTION)
role = BOT_ROLE_DPS;
break;
case CLASS_DRUID:
if (tab == 0)
if (tab == DRUID_TAB_BALANCE)
role = BOT_ROLE_DPS;
else if (tab == 1)
else if (tab == DRUID_TAB_FERAL)
role = (BotRoles)(BOT_ROLE_TANK | BOT_ROLE_DPS);
else if (tab == 2)
else if (tab == DRUID_TAB_RESTORATION)
role = BOT_ROLE_HEALER;
break;
default:
@@ -188,84 +188,83 @@ std::string AiFactory::GetPlayerSpecName(Player* player)
switch (player->getClass())
{
case CLASS_PRIEST:
if (tab == 2)
if (tab == PRIEST_TAB_SHADOW)
specName = "shadow";
else if (tab == 1)
else if (tab == PRIEST_TAB_HOLY)
specName = "holy";
else
specName = "disc";
;
break;
case CLASS_SHAMAN:
if (tab == 2)
if (tab == SHAMAN_TAB_RESTORATION)
specName = "resto";
else if (tab == 1)
else if (tab == SHAMAN_TAB_ENHANCEMENT)
specName = "enhance";
else
specName = "elem";
break;
case CLASS_WARRIOR:
if (tab == 2)
if (tab == WARRIOR_TAB_PROTECTION)
specName = "prot";
else if (tab == 1)
else if (tab == WARRIOR_TAB_FURY)
specName = "fury";
else
specName = "arms";
break;
case CLASS_PALADIN:
if (tab == 0)
if (tab == PALADIN_TAB_HOLY)
specName = "holy";
else if (tab == 1)
else if (tab == PALADIN_TAB_PROTECTION)
specName = "prot";
else if (tab == 2)
else if (tab == PALADIN_TAB_RETRIBUTION)
specName = "retrib";
break;
case CLASS_DRUID:
if (tab == 0)
if (tab == DRUID_TAB_BALANCE)
specName = "balance";
else if (tab == 1)
else if (tab == DRUID_TAB_FERAL)
specName = "feraldps";
else if (tab == 2)
else if (tab == DRUID_TAB_RESTORATION)
specName = "resto";
break;
case CLASS_ROGUE:
if (tab == 0)
if (tab == ROGUE_TAB_ASSASSINATION)
specName = "assas";
else if (tab == 1)
else if (tab == ROGUE_TAB_COMBAT)
specName = "combat";
else if (tab == 2)
else if (tab == ROGUE_TAB_SUBTLETY)
specName = "subtle";
break;
case CLASS_HUNTER:
if (tab == 0)
if (tab == HUNTER_TAB_BEAST_MASTERY)
specName = "beast";
else if (tab == 1)
else if (tab == HUNTER_TAB_MARKSMANSHIP)
specName = "marks";
else if (tab == 2)
else if (tab == HUNTER_TAB_SURVIVAL)
specName = "surv";
break;
case CLASS_DEATH_KNIGHT:
if (tab == 0)
if (tab == DEATH_KNIGHT_TAB_BLOOD)
specName = "blooddps";
else if (tab == 1)
else if (tab == DEATH_KNIGHT_TAB_FROST)
specName = "frostdps";
else if (tab == 2)
else if (tab == DEATH_KNIGHT_TAB_UNHOLY)
specName = "unholydps";
break;
case CLASS_MAGE:
if (tab == 0)
if (tab == MAGE_TAB_ARCANE)
specName = "arcane";
else if (tab == 1)
else if (tab == MAGE_TAB_FIRE)
specName = "fire";
else if (tab == 2)
else if (tab == MAGE_TAB_FROST)
specName = "frost";
break;
case CLASS_WARLOCK:
if (tab == 0)
if (tab == WARLOCK_TAB_AFFLICTION)
specName = "afflic";
else if (tab == 1)
else if (tab == WARLOCK_TAB_DEMONOLOGY)
specName = "demo";
else if (tab == 2)
else if (tab == WARLOCK_TAB_DESTRUCTION)
specName = "destro";
break;
default:
@@ -280,147 +279,124 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
uint8 tab = GetPlayerSpecTab(player);
if (!player->InBattleground())
{
engine->addStrategiesNoInit("racials", "chat", "default", "cast time", "potions", "duel", "boost", nullptr);
}
if (sPlayerbotAIConfig->autoAvoidAoe && facade->HasRealPlayerMaster())
{
engine->addStrategy("avoid aoe", false);
}
engine->addStrategy("formation", false);
switch (player->getClass())
{
case CLASS_PRIEST:
if (tab == 2)
{
if (tab == PRIEST_TAB_SHADOW)
engine->addStrategiesNoInit("dps", "shadow debuff", "shadow aoe", nullptr);
}
else if (tab == PRIEST_TAB_DISCIPLINE)
{
engine->addStrategiesNoInit("heal", nullptr);
}
else
{
engine->addStrategiesNoInit("holy heal", nullptr);
}
engine->addStrategiesNoInit("dps assist", "cure", nullptr);
break;
case CLASS_MAGE:
if (tab == 0) // Arcane
if (tab == MAGE_TAB_ARCANE)
engine->addStrategiesNoInit("arcane", nullptr);
else if (tab == 1) // Fire
else if (tab == MAGE_TAB_FIRE)
{
if (player->HasSpell(44614) /*Frostfire Bolt*/ && player->HasAura(15047) /*Ice Shards*/)
{
engine->addStrategiesNoInit("frostfire", nullptr);
}
else
{
engine->addStrategiesNoInit("fire", nullptr);
}
}
else // Frost
else
engine->addStrategiesNoInit("frost", nullptr);
engine->addStrategiesNoInit("dps", "dps assist", "cure", "aoe", nullptr);
break;
case CLASS_WARRIOR:
if (tab == 2)
if (tab == WARRIOR_TAB_PROTECTION)
engine->addStrategiesNoInit("tank", "tank assist", "aoe", nullptr);
else if (tab == 0 || !player->HasSpell(1680)) // Whirlwind
engine->addStrategiesNoInit("arms", "aoe", "dps assist", /*"behind",*/ nullptr);
else if (tab == WARRIOR_TAB_ARMS || !player->HasSpell(1680)) // Whirlwind
engine->addStrategiesNoInit("arms", "aoe", "dps assist", nullptr);
else
engine->addStrategiesNoInit("fury", "aoe", "dps assist", /*"behind",*/ nullptr);
engine->addStrategiesNoInit("fury", "aoe", "dps assist", nullptr);
break;
case CLASS_SHAMAN:
if (tab == 0) // Elemental
if (tab == SHAMAN_TAB_ELEMENTAL)
engine->addStrategiesNoInit("ele", "stoneskin", "wrath", "mana spring", "wrath of air", nullptr);
else if (tab == 2) // Restoration
else if (tab == SHAMAN_TAB_RESTORATION)
engine->addStrategiesNoInit("resto", "stoneskin", "flametongue", "mana spring", "wrath of air", nullptr);
else // Enhancement
else
engine->addStrategiesNoInit("enh", "strength of earth", "magma", "healing stream", "windfury", nullptr);
engine->addStrategiesNoInit("dps assist", "cure", "aoe", nullptr);
break;
case CLASS_PALADIN:
if (tab == 1)
if (tab == PALADIN_TAB_PROTECTION)
engine->addStrategiesNoInit("tank", "tank assist", "bthreat", "barmor", "cure", nullptr);
else if (tab == 0)
else if (tab == PALADIN_TAB_HOLY)
engine->addStrategiesNoInit("heal", "dps assist", "cure", "bcast", nullptr);
else
engine->addStrategiesNoInit("dps", "dps assist", "cure", "baoe", nullptr);
break;
case CLASS_DRUID:
if (tab == 0)
if (tab == DRUID_TAB_BALANCE)
{
engine->addStrategiesNoInit("caster", "cure", "caster aoe", "dps assist", nullptr);
engine->addStrategy("caster debuff", false);
}
else if (tab == 2)
else if (tab == DRUID_TAB_RESTORATION)
engine->addStrategiesNoInit("heal", "cure", "dps assist", nullptr);
else
{
if (player->HasSpell(768) /*cat form*/&& !player->HasAura(16931) /*thick hide*/)
{
if (player->HasSpell(768) /*cat form*/ && !player->HasAura(16931) /*thick hide*/)
engine->addStrategiesNoInit("cat", "dps assist", nullptr);
}
else
{
engine->addStrategiesNoInit("bear", "tank assist", nullptr);
}
}
break;
case CLASS_HUNTER:
if (tab == 0) // Beast Mastery
if (tab == HUNTER_TAB_BEAST_MASTERY)
engine->addStrategiesNoInit("bm", nullptr);
else if (tab == 1) // Marksmanship
else if (tab == HUNTER_TAB_MARKSMANSHIP)
engine->addStrategiesNoInit("mm", nullptr);
else if (tab == 2) // Survival
else
engine->addStrategiesNoInit("surv", nullptr);
engine->addStrategiesNoInit("cc", "dps assist", "aoe", nullptr);
break;
case CLASS_ROGUE:
if (tab == ROGUE_TAB_ASSASSINATION || tab == ROGUE_TAB_SUBTLETY)
{
engine->addStrategiesNoInit("melee", "dps assist", "aoe", nullptr);
}
else
{
engine->addStrategiesNoInit("dps", "dps assist", "aoe", nullptr);
}
break;
case CLASS_WARLOCK:
if (tab == 0) // Affliction
if (tab == WARLOCK_TAB_AFFLICTION)
engine->addStrategiesNoInit("affli", "curse of agony", nullptr);
else if (tab == 1) // Demonology
else if (tab == WARLOCK_TAB_DEMONOLOGY)
engine->addStrategiesNoInit("demo", "curse of agony", "meta melee", nullptr);
else if (tab == 2) // Destruction
else
engine->addStrategiesNoInit("destro", "curse of elements", nullptr);
engine->addStrategiesNoInit("cc", "dps assist", "aoe", nullptr);
break;
case CLASS_DEATH_KNIGHT:
if (tab == 0)
if (tab == DEATH_KNIGHT_TAB_BLOOD)
engine->addStrategiesNoInit("blood", "tank assist", nullptr);
else if (tab == 1)
else if (tab == DEATH_KNIGHT_TAB_FROST)
engine->addStrategiesNoInit("frost", "frost aoe", "dps assist", nullptr);
else
engine->addStrategiesNoInit("unholy", "unholy aoe", "dps assist", nullptr);
break;
}
if (PlayerbotAI::IsTank(player, true))
{
engine->addStrategy("tank face", false);
}
if (PlayerbotAI::IsMelee(player, true) && PlayerbotAI::IsDps(player, true))
{
engine->addStrategy("behind", false);
}
if (PlayerbotAI::IsHeal(player, true))
{
if (sPlayerbotAIConfig->autoSaveMana)
@@ -428,6 +404,7 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
if (!sPlayerbotAIConfig->IsRestrictedHealerDPSMap(player->GetMapId()))
engine->addStrategy("healer dps", false);
}
if (facade->IsRealPlayer() || sRandomPlayerbotMgr->IsRandomBot(player))
{
if (!player->GetGroup())
@@ -436,15 +413,13 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
engine->addStrategy("boost", false);
engine->addStrategy("dps assist", false);
engine->removeStrategy("threat", false);
// engine-
switch (player->getClass())
{
case CLASS_PRIEST:
{
if (tab != PRIEST_TAB_SHADOW)
{
engine->addStrategiesNoInit("holy dps", "shadow debuff", "shadow aoe", nullptr);
}
break;
}
case CLASS_DRUID:
@@ -459,17 +434,13 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
case CLASS_SHAMAN:
{
if (tab == SHAMAN_TAB_RESTORATION)
{
engine->addStrategiesNoInit("caster", "caster aoe", "bmana", nullptr);
}
break;
}
case CLASS_PALADIN:
{
if (tab == PALADIN_TAB_HOLY)
{
engine->addStrategiesNoInit("dps", "dps assist", "baoe", nullptr);
}
break;
}
default:
@@ -478,13 +449,9 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
}
}
if (sRandomPlayerbotMgr->IsRandomBot(player))
{
engine->ChangeStrategy(sPlayerbotAIConfig->randomBotCombatStrategies);
}
else
{
engine->ChangeStrategy(sPlayerbotAIConfig->combatStrategies);
}
// Battleground switch
if (player->InBattleground() && player->GetBattleground())
@@ -511,23 +478,15 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
if (player->InArena())
{
engine->addStrategy("arena", false);
engine->addStrategiesNoInit("boost", "racials", "chat", "default", "aoe", "cast time", "dps assist", nullptr);
}
else
engine->addStrategiesNoInit("boost", "racials", "chat", "default", "aoe", "potions", "cast time", "dps assist", nullptr);
engine->addStrategiesNoInit("boost", "racials", "chat", "default", "aoe", "potions", "cast time", "dps assist",
nullptr);
engine->removeStrategy("custom::say", false);
engine->removeStrategy("flee", false);
engine->removeStrategy("threat", false);
engine->addStrategy("boost", false);
// if ((player->getClass() == CLASS_DRUID && tab == 2) || (player->getClass() == CLASS_SHAMAN && tab == 2))
// engine->addStrategiesNoInit("caster", "caster aoe", nullptr);
// if (player->getClass() == CLASS_DRUID && tab == 1)
// engine->addStrategiesNoInit(/*"behind",*/ "dps", nullptr);
// if (player->getClass() == CLASS_ROGUE)
// engine->addStrategiesNoInit(/*"behind",*/ "stealth", nullptr);
}
}
@@ -549,19 +508,15 @@ void AiFactory::AddDefaultNonCombatStrategies(Player* player, PlayerbotAI* const
nonCombatEngine->addStrategiesNoInit("dps assist", "cure", nullptr);
break;
case CLASS_PALADIN:
if (tab == 1)
if (tab == PALADIN_TAB_PROTECTION)
{
nonCombatEngine->addStrategiesNoInit("bthreat", "tank assist", "barmor", nullptr);
if (player->GetLevel() >= 20)
{
nonCombatEngine->addStrategy("bhealth", false);
}
else
{
nonCombatEngine->addStrategy("bdps", false);
}
}
else if (tab == 0)
else if (tab == PALADIN_TAB_HOLY)
nonCombatEngine->addStrategiesNoInit("dps assist", "bmana", "bcast", nullptr);
else
nonCombatEngine->addStrategiesNoInit("dps assist", "bdps", "baoe", nullptr);
@@ -572,7 +527,7 @@ void AiFactory::AddDefaultNonCombatStrategies(Player* player, PlayerbotAI* const
nonCombatEngine->addStrategiesNoInit("bdps", "dps assist", "pet", nullptr);
break;
case CLASS_SHAMAN:
if (tab == 0 || tab == 2)
if (tab == SHAMAN_TAB_ELEMENTAL || tab == SHAMAN_TAB_RESTORATION)
nonCombatEngine->addStrategy("bmana", false);
else
nonCombatEngine->addStrategy("bdps", false);
@@ -588,43 +543,34 @@ void AiFactory::AddDefaultNonCombatStrategies(Player* player, PlayerbotAI* const
nonCombatEngine->addStrategiesNoInit("dps assist", "cure", nullptr);
break;
case CLASS_DRUID:
if (tab == 1)
if (tab == DRUID_TAB_FERAL)
{
if (player->GetLevel() >= 20 && !player->HasAura(16931) /*thick hide*/)
{
nonCombatEngine->addStrategy("dps assist", false);
}
else
{
nonCombatEngine->addStrategy("tank assist", false);
}
}
else
nonCombatEngine->addStrategiesNoInit("dps assist", "cure", nullptr);
break;
case CLASS_WARRIOR:
if (tab == 2)
if (tab == WARRIOR_TAB_PROTECTION)
nonCombatEngine->addStrategy("tank assist", false);
else
nonCombatEngine->addStrategy("dps assist", false);
break;
case CLASS_WARLOCK:
if (tab == WARLOCK_TAB_AFFLICTION)
{
nonCombatEngine->addStrategiesNoInit("felhunter", "spellstone", nullptr);
}
else if (tab == WARLOCK_TAB_DEMONOLOGY)
{
nonCombatEngine->addStrategiesNoInit("felguard", "spellstone", nullptr);
}
else if (tab == WARLOCK_TAB_DESTRUCTION)
{
nonCombatEngine->addStrategiesNoInit("imp", "firestone", nullptr);
}
nonCombatEngine->addStrategiesNoInit("dps assist", "ss self", nullptr);
break;
case CLASS_DEATH_KNIGHT:
if (tab == 0)
if (tab == DEATH_KNIGHT_TAB_BLOOD)
nonCombatEngine->addStrategy("tank assist", false);
else
nonCombatEngine->addStrategy("dps assist", false);
@@ -641,9 +587,7 @@ void AiFactory::AddDefaultNonCombatStrategies(Player* player, PlayerbotAI* const
}
if (sPlayerbotAIConfig->autoSaveMana && PlayerbotAI::IsHeal(player, true))
{
nonCombatEngine->addStrategy("save mana", false);
}
if ((sRandomPlayerbotMgr->IsRandomBot(player)) && !player->InBattleground())
{
@@ -669,18 +613,14 @@ void AiFactory::AddDefaultNonCombatStrategies(Player* player, PlayerbotAI* const
nonCombatEngine->addStrategy("grind", false);
if (sPlayerbotAIConfig->enableNewRpgStrategy)
{
nonCombatEngine->addStrategy("new rpg", false);
}
else if (sPlayerbotAIConfig->autoDoQuests)
{
// nonCombatEngine->addStrategy("travel");
nonCombatEngine->addStrategy("rpg", false);
}
else
{
nonCombatEngine->addStrategy("move random", false);
}
if (sPlayerbotAIConfig->randomBotJoinBG)
nonCombatEngine->addStrategy("bg", false);
@@ -729,11 +669,8 @@ void AiFactory::AddDefaultNonCombatStrategies(Player* player, PlayerbotAI* const
}
}
else
{
nonCombatEngine->ChangeStrategy(sPlayerbotAIConfig->nonCombatStrategies);
}
// nonCombatEngine->addStrategy("battleground");
// nonCombatEngine->addStrategy("warsong");
// Battleground switch
if (player->InBattleground() && player->GetBattleground())
{
@@ -790,9 +727,7 @@ void AiFactory::AddDefaultDeadStrategies(Player* player, PlayerbotAI* const faca
deadEngine->addStrategiesNoInit("dead", "stay", "chat", "default", "follow", nullptr);
if (sRandomPlayerbotMgr->IsRandomBot(player) && !player->GetGroup())
{
deadEngine->removeStrategy("follow", false);
}
}
Engine* AiFactory::createDeadEngine(Player* player, PlayerbotAI* const facade, AiObjectContext* AiObjectContext)

View File

@@ -43,6 +43,7 @@
#include "PlayerbotAIConfig.h"
#include "PlayerbotDbStore.h"
#include "PlayerbotMgr.h"
#include "PlayerbotGuildMgr.h"
#include "Playerbots.h"
#include "PointMovementGenerator.h"
#include "PositionValue.h"
@@ -436,7 +437,7 @@ void PlayerbotAI::UpdateAIGroupMaster()
void PlayerbotAI::UpdateAIInternal([[maybe_unused]] uint32 elapsed, bool minimal)
{
if (bot->IsBeingTeleported() || !bot->IsInWorld())
if (!bot || bot->IsBeingTeleported() || !bot->IsInWorld())
return;
std::string const mapString = WorldPosition(bot).isOverworld() ? std::to_string(bot->GetMapId()) : "I";
@@ -515,23 +516,37 @@ void PlayerbotAI::UpdateAIInternal([[maybe_unused]] uint32 elapsed, bool minimal
void PlayerbotAI::HandleCommands()
{
ExternalEventHelper helper(aiObjectContext);
for (auto it = chatCommands.begin(); it != chatCommands.end();)
{
time_t& checkTime = it->GetTime();
if (checkTime && time(0) < checkTime)
if (checkTime && time(nullptr) < checkTime)
{
++it;
continue;
}
const std::string& command = it->GetCommand();
Player* owner = it->GetOwner();
if (!owner)
{
it = chatCommands.erase(it);
continue;
}
const std::string& command = it->GetCommand();
if (command.empty())
{
it = chatCommands.erase(it);
continue;
}
if (!helper.ParseChatCommand(command, owner) && it->GetType() == CHAT_MSG_WHISPER)
{
// ostringstream out; out << "Unknown command " << command;
// TellPlayer(out);
// helper.ParseChatCommand("help");
}
it = chatCommands.erase(it);
}
}
@@ -539,6 +554,9 @@ void PlayerbotAI::HandleCommands()
std::map<std::string, ChatMsg> chatMap;
void PlayerbotAI::HandleCommand(uint32 type, const std::string& text, Player& fromPlayer, const uint32 lang)
{
if (!bot)
return;
std::string filtered = text;
if (!IsAllowedCommand(filtered) && !GetSecurity()->CheckLevelFor(PlayerbotSecurityLevel::PLAYERBOT_SECURITY_INVITE,
@@ -710,65 +728,82 @@ void PlayerbotAI::HandleCommand(uint32 type, const std::string& text, Player& fr
void PlayerbotAI::HandleTeleportAck()
{
if (!bot || !bot->GetSession())
return;
// only for bots
if (IsRealPlayer())
return;
// Clearing motion generators and stopping movement which prevents
// conflicts between teleport and any active motion (walk, run, swim, flight, etc.)
bot->GetMotionMaster()->Clear(true);
bot->StopMoving();
// Near teleport (within map/instance)
if (bot->IsBeingTeleportedNear())
{
// Previous versions manually added the bot to the map if it was not in the world.
// not needed: HandleMoveTeleportAckOpcode() safely attaches the player to the map
// and clears IsBeingTeleportedNear().
Player* plMover = bot->m_mover->ToPlayer();
if (!plMover)
return;
// Send the near teleport ACK packet
WorldPacket p(MSG_MOVE_TELEPORT_ACK, 20);
p << plMover->GetPackGUID();
p << uint32(0);
p << uint32(0);
bot->GetSession()->HandleMoveTeleportAck(p);
// Simulate teleport latency and prevent AI from running too early (used cmangos delays)
SetNextCheckDelay(urand(1000, 2000));
}
// Far teleport (worldport / different map)
/*
* FAR TELEPORT (worldport / map change)
* Player may NOT be in world or grid here.
* Handle this FIRST.
*/
if (bot->IsBeingTeleportedFar())
{
// Handle far teleport ACK:
// Moves the bot to the new map, clears IsBeingTeleportedFar(), updates session/map references
bot->GetSession()->HandleMoveWorldportAck();
// Ensure bot now has a valid map. If this fails, there is a core/session bug?
// after worldport ACK the player should be in a valid map
if (!bot->GetMap())
{
LOG_ERROR("playerbot", "Bot {} has no map after worldport ACK", bot->GetGUID().ToString());
return;
}
// Instance strategies after teleport
// apply instance-related strategies after map attach
if (sPlayerbotAIConfig->applyInstanceStrategies)
ApplyInstanceStrategies(bot->GetMapId(), true);
// healer DPS strategies if restrictions are enabled
if (sPlayerbotAIConfig->restrictHealerDPS)
EvaluateHealerDpsStrategy();
// Reset AI state to to before teleport conditions
// reset AI state after teleport
Reset(true);
// Slightly longer delay to simulate far teleport latency (used cmangos delays)
// clear movement only AFTER teleport is finalized and bot is in world
if (bot->IsInWorld() && bot->GetMotionMaster())
{
bot->GetMotionMaster()->Clear(true);
bot->StopMoving();
}
// simulate far teleport latency (cmangos-style)
SetNextCheckDelay(urand(2000, 5000));
return;
}
SetNextCheckDelay(sPlayerbotAIConfig->globalCoolDown);
/*
* NEAR TELEPORT (same map / instance)
* Player MUST be in world (and in grid).
*/
if (bot->IsBeingTeleportedNear())
{
if (!bot->IsInWorld())
return;
Player* plMover = bot->m_mover ? bot->m_mover->ToPlayer() : nullptr;
if (!plMover)
return;
WorldPacket p(MSG_MOVE_TELEPORT_ACK, 20);
p << plMover->GetPackGUID();
p << uint32(0); // flags
p << uint32(0); // time
bot->GetSession()->HandleMoveTeleportAck(p);
// clear movement after successful relocation
if (bot->GetMotionMaster())
{
bot->GetMotionMaster()->Clear(true);
bot->StopMoving();
}
// simulate near teleport latency
SetNextCheckDelay(urand(1000, 2000));
return;
}
}
void PlayerbotAI::Reset(bool full)
@@ -930,7 +965,6 @@ void PlayerbotAI::HandleCommand(uint32 type, std::string const text, Player* fro
fromPlayer->SendDirectMessage(&data);
return;
}
if (!IsAllowedCommand(filtered) &&
(!GetSecurity()->CheckLevelFor(PLAYERBOT_SECURITY_ALLOW_ALL, type != CHAT_MSG_WHISPER, fromPlayer)))
return;
@@ -1286,7 +1320,12 @@ void PlayerbotAI::SpellInterrupted(uint32 spellid)
Spell* spell = bot->GetCurrentSpell((CurrentSpellTypes)type);
if (!spell)
continue;
if (spell->GetSpellInfo()->Id == spellid)
SpellInfo const* spellInfo = spell->GetSpellInfo();
if (!spellInfo)
continue;
if (spellInfo->Id == spellid)
bot->InterruptSpell((CurrentSpellTypes)type);
}
// LastSpellCast& lastSpell = aiObjectContext->GetValue<LastSpellCast&>("last spell cast")->Get();
@@ -1387,8 +1426,8 @@ void PlayerbotAI::DoNextAction(bool min)
return;
}
// Change engine if just ressed
if (currentEngine == engines[BOT_STATE_DEAD] && isBotAlive)
// Change engine if just ressed (no movement update when rooted)
if (currentEngine == engines[BOT_STATE_DEAD] && isBotAlive && !bot->IsRooted())
{
bot->SendMovementFlagUpdate();
@@ -1504,9 +1543,6 @@ 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;
@@ -1731,6 +1767,7 @@ bool PlayerbotAI::IsRanged(Player* player, bool bySpec)
}
break;
}
return true;
}
@@ -1824,10 +1861,9 @@ bool PlayerbotAI::IsRangedDpsAssistantOfIndex(Player* player, int index)
bool PlayerbotAI::HasAggro(Unit* unit)
{
if (!unit)
{
if (!IsValidUnit(unit))
return false;
}
bool isMT = IsMainTank(bot);
Unit* victim = unit->GetVictim();
if (victim && (victim->GetGUID() == bot->GetGUID() || (!isMT && victim->ToPlayer() && IsTank(victim->ToPlayer()))))
@@ -2035,7 +2071,7 @@ bool PlayerbotAI::IsTank(Player* player, bool bySpec)
switch (player->getClass())
{
case CLASS_DEATH_KNIGHT:
if (tab == DEATHKNIGHT_TAB_BLOOD)
if (tab == DEATH_KNIGHT_TAB_BLOOD)
{
return true;
}
@@ -2143,7 +2179,7 @@ bool PlayerbotAI::IsDps(Player* player, bool bySpec)
}
break;
case CLASS_DEATH_KNIGHT:
if (tab != DEATHKNIGHT_TAB_BLOOD)
if (tab != DEATH_KNIGHT_TAB_BLOOD)
{
return true;
}
@@ -2803,7 +2839,12 @@ bool PlayerbotAI::TellMaster(std::ostringstream& stream, PlayerbotSecurityLevel
bool PlayerbotAI::TellMaster(std::string const text, PlayerbotSecurityLevel securityLevel)
{
if (!master || !TellMasterNoFacing(text, securityLevel))
if (!master)
{
if (sPlayerbotAIConfig->randomBotSayWithoutMaster)
return TellMasterNoFacing(text, securityLevel);
}
if (!TellMasterNoFacing(text, securityLevel))
return false;
if (!bot->isMoving() && !bot->IsInCombat() && bot->GetMapId() == master->GetMapId() &&
@@ -2820,6 +2861,9 @@ bool PlayerbotAI::TellMaster(std::string const text, PlayerbotSecurityLevel secu
bool IsRealAura(Player* bot, AuraEffect const* aurEff, Unit const* unit)
{
if (!unit || !unit->IsInWorld() || unit->IsDuringRemoveFromWorld())
return false;
if (!aurEff)
return false;
@@ -2827,6 +2871,8 @@ bool IsRealAura(Player* bot, AuraEffect const* aurEff, Unit const* unit)
return true;
SpellInfo const* spellInfo = aurEff->GetSpellInfo();
if (!spellInfo)
return false;
uint32 stacks = aurEff->GetBase()->GetStackAmount();
if (stacks >= spellInfo->StackAmount)
@@ -2842,7 +2888,7 @@ bool IsRealAura(Player* bot, AuraEffect const* aurEff, Unit const* unit)
bool PlayerbotAI::HasAura(std::string const name, Unit* unit, bool maxStack, bool checkIsOwner, int maxAuraAmount,
bool checkDuration)
{
if (!unit)
if (!IsValidUnit(unit))
return false;
std::wstring wnamepart;
@@ -2938,7 +2984,7 @@ bool PlayerbotAI::HasAura(uint32 spellId, Unit const* unit)
Aura* PlayerbotAI::GetAura(std::string const name, Unit* unit, bool checkIsOwner, bool checkDuration, int checkStack)
{
if (!unit)
if (!IsValidUnit(unit))
return nullptr;
std::wstring wnamepart;
@@ -2956,6 +3002,9 @@ Aura* PlayerbotAI::GetAura(std::string const name, Unit* unit, bool checkIsOwner
for (AuraEffect const* aurEff : auras)
{
SpellInfo const* spellInfo = aurEff->GetSpellInfo();
if (!spellInfo)
continue;
std::string const& auraName = spellInfo->SpellName[0];
// Directly skip if name mismatch (both length and content)
@@ -3036,6 +3085,9 @@ bool PlayerbotAI::CanCastSpell(uint32 spellid, Unit* target, bool checkHasSpell,
if (!target)
target = bot;
if (!IsValidUnit(target))
return false;
if (Pet* pet = bot->GetPet())
if (pet->HasSpell(spellid))
return true;
@@ -3297,6 +3349,9 @@ bool PlayerbotAI::CanCastSpell(uint32 spellid, float x, float y, float z, bool c
bool PlayerbotAI::CastSpell(std::string const name, Unit* target, Item* itemTarget)
{
if (!IsValidUnit(target))
return false;
bool result = CastSpell(aiObjectContext->GetValue<uint32>("spell id", name)->Get(), target, itemTarget);
if (result)
{
@@ -3309,15 +3364,19 @@ bool PlayerbotAI::CastSpell(std::string const name, Unit* target, Item* itemTarg
bool PlayerbotAI::CastSpell(uint32 spellId, Unit* target, Item* itemTarget)
{
if (!spellId)
{
return false;
}
if (!target)
target = bot;
Pet* pet = bot->GetPet();
if (!IsValidUnit(target))
return false;
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
if (!spellInfo)
return false;
Pet* pet = bot->GetPet();
if (pet && pet->HasSpell(spellId))
{
// List of spell IDs for which we do NOT want to toggle auto-cast or send message
@@ -3720,6 +3779,9 @@ bool PlayerbotAI::CanCastVehicleSpell(uint32 spellId, Unit* target)
if (!spellId)
return false;
if (!IsValidUnit(target))
return false;
Vehicle* vehicle = bot->GetVehicle();
if (!vehicle)
return false;
@@ -3730,12 +3792,12 @@ bool PlayerbotAI::CanCastVehicleSpell(uint32 spellId, Unit* target)
return false;
Unit* vehicleBase = vehicle->GetBase();
Unit* spellTarget = target;
if (!spellTarget)
spellTarget = vehicleBase;
if (!spellTarget)
if (!IsValidUnit(spellTarget))
return false;
if (vehicleBase->HasSpellCooldown(spellId))
@@ -3802,6 +3864,9 @@ bool PlayerbotAI::CastVehicleSpell(uint32 spellId, Unit* target)
if (!spellId)
return false;
if (!IsValidUnit(target))
return false;
Vehicle* vehicle = bot->GetVehicle();
if (!vehicle)
return false;
@@ -3812,12 +3877,12 @@ bool PlayerbotAI::CastVehicleSpell(uint32 spellId, Unit* target)
return false;
Unit* vehicleBase = vehicle->GetBase();
Unit* spellTarget = target;
if (!spellTarget)
spellTarget = vehicleBase;
if (!spellTarget)
if (!IsValidUnit(spellTarget))
return false;
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
@@ -3970,9 +4035,13 @@ bool PlayerbotAI::IsInVehicle(bool canControl, bool canCast, bool canAttack, boo
void PlayerbotAI::WaitForSpellCast(Spell* spell)
{
if (!spell)
return;
SpellInfo const* spellInfo = spell->GetSpellInfo();
uint32 castTime = spell->GetCastTime();
if (spellInfo->IsChanneled())
if (spellInfo && spellInfo->IsChanneled())
{
int32 duration = spellInfo->GetDuration();
bot->ApplySpellMod(spellInfo->Id, SPELLMOD_DURATION, duration);
@@ -4020,6 +4089,9 @@ void PlayerbotAI::RemoveAura(std::string const name)
bool PlayerbotAI::IsInterruptableSpellCasting(Unit* target, std::string const spell)
{
if (!IsValidUnit(target))
return false;
uint32 spellid = aiObjectContext->GetValue<uint32>("spell id", spell)->Get();
if (!spellid || !target->IsNonMeleeSpellCast(true))
return false;
@@ -4048,17 +4120,25 @@ bool PlayerbotAI::IsInterruptableSpellCasting(Unit* target, std::string const sp
bool PlayerbotAI::HasAuraToDispel(Unit* target, uint32 dispelType)
{
if (!target->IsInWorld())
{
if (!IsValidUnit(target) || !target->IsAlive())
return false;
}
if (!IsValidPlayer(bot))
return false;
bool isFriend = bot->IsFriendlyTo(target);
Unit::VisibleAuraMap const* visibleAuras = target->GetVisibleAuras();
if (!visibleAuras)
return false;
for (Unit::VisibleAuraMap::const_iterator itr = visibleAuras->begin(); itr != visibleAuras->end(); ++itr)
{
Aura* aura = itr->second->GetBase();
if (!itr->second)
continue;
if (aura->IsPassive())
Aura* aura = itr->second->GetBase();
if (!aura || aura->IsPassive() || aura->IsRemoved())
continue;
if (sPlayerbotAIConfig->dispelAuraDuration && aura->GetDuration() &&
@@ -4066,6 +4146,8 @@ bool PlayerbotAI::HasAuraToDispel(Unit* target, uint32 dispelType)
continue;
SpellInfo const* spellInfo = aura->GetSpellInfo();
if (!spellInfo)
continue;
bool isPositiveSpell = spellInfo->IsPositive();
if (isPositiveSpell && isFriend)
@@ -4077,6 +4159,7 @@ bool PlayerbotAI::HasAuraToDispel(Unit* target, uint32 dispelType)
if (canDispel(spellInfo, dispelType))
return true;
}
return false;
}
@@ -5707,7 +5790,7 @@ void PlayerbotAI::ImbueItem(Item* item) { ImbueItem(item, TARGET_FLAG_NONE, Obje
// item on unit
void PlayerbotAI::ImbueItem(Item* item, Unit* target)
{
if (!target)
if (!IsValidUnit(target))
return;
ImbueItem(item, TARGET_FLAG_UNIT, target->GetGUID());
@@ -5849,30 +5932,38 @@ int32 PlayerbotAI::GetNearGroupMemberCount(float dis)
bool PlayerbotAI::CanMove()
{
// do not allow if not vehicle driver
if (IsInVehicle() && !IsInVehicle(true))
// Most common checks: confused, stunned, fleeing, jumping, charging. All these
// states are set when handling certain aura effects. We don't check against
// UNIT_STATE_ROOT here, because this state is used by vehicles.
if (bot->HasUnitState(UNIT_STATE_LOST_CONTROL))
return false;
if (bot->isFrozen() || bot->IsPolymorphed() || (bot->isDead() && !bot->HasPlayerFlag(PLAYER_FLAGS_GHOST)) ||
bot->IsBeingTeleported() || bot->HasRootAura() || bot->HasSpiritOfRedemptionAura() || bot->HasConfuseAura() ||
bot->IsCharmed() || bot->HasStunAura() || bot->IsInFlight() || bot->HasUnitState(UNIT_STATE_LOST_CONTROL))
// Death state (w/o spirit release) and Spirit of Redemption aura (priest)
if ((bot->isDead() && !bot->HasPlayerFlag(PLAYER_FLAGS_GHOST)) || bot->HasSpiritOfRedemptionAura())
return false;
return bot->GetMotionMaster()->GetCurrentMovementGeneratorType() != FLIGHT_MOTION_TYPE;
}
bool PlayerbotAI::IsRealGuild(uint32 guildId)
{
Guild* guild = sGuildMgr->GetGuildById(guildId);
if (!guild)
{
// Common CC effects, ordered by frequency: rooted > charmed > frozen > polymorphed.
// NOTE: Can't find proper way to check if bot is rooted or charmed w/o additional
// vehicle check -- when a passenger is added, they become rooted and charmed.
if (!bot->GetVehicle() && (bot->IsRooted() || bot->IsCharmed()))
return false;
}
uint32 leaderAccount = sCharacterCache->GetCharacterAccountIdByGuid(guild->GetLeaderGUID());
if (!leaderAccount)
if (bot->isFrozen() || bot->IsPolymorphed())
return false;
return !(sPlayerbotAIConfig->IsInRandomAccountList(leaderAccount));
// Check for the MM controlled slot types: feared, confused, fleeing, etc.
if (bot->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_CONTROLLED) != NULL_MOTION_TYPE)
return false;
// Traveling state: taxi flight and being teleported (relatively rare)
if (bot->IsInFlight() || bot->GetMotionMaster()->GetCurrentMovementGeneratorType() == FLIGHT_MOTION_TYPE ||
bot->IsBeingTeleported())
return false;
// Vehicle state: is in the vehicle and can control it (rare, content-specific)
if ((bot->GetVehicle() && !IsInVehicle(true)))
return false;
return true;
}
bool PlayerbotAI::IsInRealGuild()
@@ -5880,7 +5971,7 @@ bool PlayerbotAI::IsInRealGuild()
if (!bot->GetGuildId())
return false;
return IsRealGuild(bot->GetGuildId());
return sPlayerbotGuildMgr->IsRealGuild(bot->GetGuildId());
}
void PlayerbotAI::QueueChatResponse(const ChatQueuedReply chatReply) { chatReplies.push_back(std::move(chatReply)); }

View File

@@ -276,7 +276,7 @@ enum BotRoles : uint8
enum HUNTER_TABS
{
HUNTER_TAB_BEASTMASTERY,
HUNTER_TAB_BEAST_MASTERY,
HUNTER_TAB_MARKSMANSHIP,
HUNTER_TAB_SURVIVAL,
};
@@ -295,11 +295,11 @@ enum PRIEST_TABS
PRIEST_TAB_SHADOW,
};
enum DEATHKNIGHT_TABS
enum DEATH_KNIGHT_TABS
{
DEATHKNIGHT_TAB_BLOOD,
DEATHKNIGHT_TAB_FROST,
DEATHKNIGHT_TAB_UNHOLY,
DEATH_KNIGHT_TAB_BLOOD,
DEATH_KNIGHT_TAB_FROST,
DEATH_KNIGHT_TAB_UNHOLY,
};
enum DRUID_TABS
@@ -579,7 +579,6 @@ public:
void ResetJumpDestination() { jumpDestination = Position(); }
bool CanMove();
static bool IsRealGuild(uint32 guildId);
bool IsInRealGuild();
static std::vector<std::string> dispel_whitelist;
bool EqualLowercaseName(std::string s1, std::string s2);
@@ -617,7 +616,15 @@ private:
void HandleCommands();
void HandleCommand(uint32 type, const std::string& text, Player& fromPlayer, const uint32 lang = LANG_UNIVERSAL);
bool _isBotInitializing = false;
inline bool IsValidUnit(const Unit* unit) const
{
return unit && unit->IsInWorld() && !unit->IsDuringRemoveFromWorld();
}
inline bool IsValidPlayer(const Player* player) const
{
return player && player->GetSession() && player->IsInWorld() && !player->IsDuringRemoveFromWorld() &&
!player->IsBeingTeleported();
}
protected:
Player* bot;
Player* master;

View File

@@ -10,6 +10,7 @@
#include "PlayerbotDungeonSuggestionMgr.h"
#include "PlayerbotFactory.h"
#include "Playerbots.h"
#include "PlayerbotGuildMgr.h"
#include "RandomItemMgr.h"
#include "RandomPlayerbotFactory.h"
#include "RandomPlayerbotMgr.h"
@@ -222,6 +223,11 @@ bool PlayerbotAIConfig::Initialize()
EnableICCBuffs = sConfigMgr->GetOption<bool>("AiPlayerbot.EnableICCBuffs", true);
//////////////////////////// Professions
fishingDistanceFromMaster = sConfigMgr->GetOption<float>("AiPlayerbot.FishingDistanceFromMaster", 10.0f);
endFishingWithMaster = sConfigMgr->GetOption<float>("AiPlayerbot.EndFishingWithMaster", 30.0f);
fishingDistance = sConfigMgr->GetOption<float>("AiPlayerbot.FishingDistance", 40.0f);
enableFishingWithMaster = sConfigMgr->GetOption<bool>("AiPlayerbot.EnableFishingWithMaster", true);
//////////////////////////// CHAT
enableBroadcasts = sConfigMgr->GetOption<bool>("AiPlayerbot.EnableBroadcasts", true);
randomBotTalk = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotTalk", false);
@@ -661,6 +667,7 @@ bool PlayerbotAIConfig::Initialize()
sRandomPlayerbotMgr->Init();
}
sPlayerbotGuildMgr->Init();
sRandomItemMgr->Init();
sRandomItemMgr->InitAfterAhBot();
sPlayerbotTextMgr->LoadBotTexts();

View File

@@ -145,6 +145,10 @@ public:
// Cooldown (seconds) between reagent-missing RP warnings, per bot & per buff. Default: 30
int32 rpWarningCooldown;
// Professions
bool enableFishingWithMaster;
float fishingDistanceFromMaster, fishingDistance, endFishingWithMaster;
// chat
bool randomBotTalk;
bool randomBotEmote;
@@ -269,7 +273,6 @@ public:
bool deleteRandomBotAccounts;
uint32 randomBotGuildCount, randomBotGuildSizeMax;
bool deleteRandomBotGuilds;
std::vector<uint32> randomBotGuilds;
std::vector<uint32> pvpProhibitedZoneIds;
std::vector<uint32> pvpProhibitedAreaIds;
bool fastReactInBG;

322
src/PlayerbotGuildMgr.cpp Normal file
View File

@@ -0,0 +1,322 @@
#include "PlayerbotGuildMgr.h"
#include "Player.h"
#include "PlayerbotAIConfig.h"
#include "DatabaseEnv.h"
#include "Guild.h"
#include "GuildMgr.h"
#include "RandomPlayerbotMgr.h"
#include "ScriptMgr.h"
PlayerbotGuildMgr::PlayerbotGuildMgr(){}
void PlayerbotGuildMgr::Init()
{
_guildCache.clear();
if (sPlayerbotAIConfig->deleteRandomBotGuilds)
DeleteBotGuilds();
LoadGuildNames();
ValidateGuildCache();
}
bool PlayerbotGuildMgr::CreateGuild(Player* player, std::string guildName)
{
Guild* guild = new Guild();
if (!guild->Create(player, guildName))
{
LOG_ERROR("playerbots", "Error creating guild [ {} ] with leader [ {} ]", guildName,
player->GetName());
delete guild;
return false;
}
sGuildMgr->AddGuild(guild);
LOG_DEBUG("playerbots", "Guild created: id={} name='{}'", guild->GetId(), guildName);
SetGuildEmblem(guild->GetId());
GuildCache entry;
entry.name = guildName;
entry.memberCount = 1;
entry.status = 1;
entry.maxMembers = sPlayerbotAIConfig->randomBotGuildSizeMax;
entry.faction = player->GetTeamId();
_guildCache[guild->GetId()] = entry;
return true;
}
bool PlayerbotGuildMgr::SetGuildEmblem(uint32 guildId)
{
Guild* guild = sGuildMgr->GetGuildById(guildId);
if (!guild)
return false;
// create random emblem
uint32 st, cl, br, bc, bg;
bg = urand(0, 51);
bc = urand(0, 17);
cl = urand(0, 17);
br = urand(0, 7);
st = urand(0, 180);
LOG_DEBUG("playerbots",
"[TABARD] new guild id={} random -> style={}, color={}, borderStyle={}, borderColor={}, bgColor={}",
guild->GetId(), st, cl, br, bc, bg);
// populate guild table with a random tabard design
CharacterDatabase.Execute(
"UPDATE guild SET EmblemStyle={}, EmblemColor={}, BorderStyle={}, BorderColor={}, BackgroundColor={} "
"WHERE guildid={}",
st, cl, br, bc, bg, guild->GetId());
LOG_DEBUG("playerbots", "[TABARD] UPDATE done for guild id={}", guild->GetId());
// Immediate reading for log
if (QueryResult qr = CharacterDatabase.Query(
"SELECT EmblemStyle,EmblemColor,BorderStyle,BorderColor,BackgroundColor FROM guild WHERE guildid={}",
guild->GetId()))
{
Field* f = qr->Fetch();
LOG_DEBUG("playerbots",
"[TABARD] DB check guild id={} => style={}, color={}, borderStyle={}, borderColor={}, bgColor={}",
guild->GetId(), f[0].Get<uint8>(), f[1].Get<uint8>(), f[2].Get<uint8>(), f[3].Get<uint8>(), f[4].Get<uint8>());
}
return true;
}
std::string PlayerbotGuildMgr::AssignToGuild(Player* player)
{
if (!player)
return "";
uint8_t playerFaction = player->GetTeamId();
std::vector<GuildCache*> partiallyfilledguilds;
partiallyfilledguilds.reserve(_guildCache.size());
for (auto& keyValue : _guildCache)
{
GuildCache& cached = keyValue.second;
if (!cached.hasRealPlayer && cached.status == 1 && cached.faction == playerFaction)
partiallyfilledguilds.push_back(&cached);
}
if (!partiallyfilledguilds.empty())
{
size_t idx = static_cast<size_t>(urand(0, static_cast<int>(partiallyfilledguilds.size()) - 1));
return (partiallyfilledguilds[idx]->name);
}
size_t count = std::count_if(
_guildCache.begin(), _guildCache.end(),
[](const std::pair<const uint32, GuildCache>& pair)
{
return !pair.second.hasRealPlayer;
}
);
if (count < sPlayerbotAIConfig->randomBotGuildCount)
{
for (auto& key : _shuffled_guild_keys)
{
if (_guildNames[key])
{
LOG_INFO("playerbots","Assigning player [{}] to guild [{}]", player->GetName(), key);
return key;
}
}
LOG_ERROR("playerbots","No available guild names left.");
}
return "";
}
void PlayerbotGuildMgr::OnGuildUpdate(Guild* guild)
{
auto it = _guildCache.find(guild->GetId());
if (it == _guildCache.end())
return;
GuildCache& entry = it->second;
entry.memberCount = guild->GetMemberCount();
if (entry.memberCount < entry.maxMembers)
entry.status = 1;
else if (entry.memberCount >= entry.maxMembers)
entry.status = 2; // Full
std::string guildName = guild->GetName();
for (auto& it : _guildNames)
{
if (it.first == guildName)
{
it.second = false;
break;
}
}
}
void PlayerbotGuildMgr::ResetGuildCache()
{
for (auto it = _guildCache.begin(); it != _guildCache.end();)
{
GuildCache& cached = it->second;
cached.memberCount = 0;
cached.faction = 2;
cached.status = 0;
}
}
void PlayerbotGuildMgr::LoadGuildNames()
{
LOG_INFO("playerbots", "Loading guild names from playerbots_guild_names...");
QueryResult result = CharacterDatabase.Query("SELECT name_id, name FROM playerbots_guild_names");
if (!result)
{
LOG_ERROR("playerbots", "No entries found in playerbots_guild_names. List is empty.");
return;
}
do
{
Field* fields = result->Fetch();
_guildNames[fields[1].Get<std::string>()] = true;
} while (result->NextRow());
for (auto& pair : _guildNames)
_shuffled_guild_keys.push_back(pair.first);
std::random_device rd;
std::mt19937 g(rd());
std::shuffle(_shuffled_guild_keys.begin(), _shuffled_guild_keys.end(), g);
LOG_INFO("playerbots", "Loaded {} guild entries from playerbots_guild_names table.", _guildNames.size());
}
void PlayerbotGuildMgr::ValidateGuildCache()
{
QueryResult result = CharacterDatabase.Query("SELECT guildid, name FROM guild");
if (!result)
{
LOG_ERROR("playerbots", "No guilds found in database, resetting guild cache");
ResetGuildCache();
return;
}
std::unordered_map<uint32, std::string> dbGuilds;
do
{
Field* fields = result->Fetch();
uint32 guildId = fields[0].Get<uint32>();
std::string guildName = fields[1].Get<std::string>();
dbGuilds[guildId] = guildName;
} while (result->NextRow());
for (auto it = dbGuilds.begin(); it != dbGuilds.end(); it++)
{
uint32 guildId = it->first;
GuildCache cache;
cache.name = it->second;
cache.maxMembers = sPlayerbotAIConfig->randomBotGuildSizeMax;
Guild* guild = sGuildMgr ->GetGuildById(guildId);
if (!guild)
continue;
cache.memberCount = guild->GetMemberCount();
ObjectGuid leaderGuid = guild->GetLeaderGUID();
CharacterCacheEntry const* leaderEntry = sCharacterCache->GetCharacterCacheByGuid(leaderGuid);
uint32 leaderAccount = leaderEntry->AccountId;
cache.hasRealPlayer = !(sPlayerbotAIConfig->IsInRandomAccountList(leaderAccount));
cache.faction = Player::TeamIdForRace(leaderEntry->Race);
if (cache.memberCount == 0)
cache.status = 0; // empty
else if (cache.memberCount < cache.maxMembers)
cache.status = 1; // partially filled
else
cache.status = 2; // full
_guildCache.insert_or_assign(guildId, cache);
for (auto& it : _guildNames)
{
if (it.first == cache.name)
{
it.second = false;
break;
}
}
}
}
void PlayerbotGuildMgr::DeleteBotGuilds()
{
LOG_INFO("playerbots", "Deleting random bot guilds...");
std::vector<uint32> randomBots;
PlayerbotsDatabasePreparedStatement* stmt = PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_SEL_RANDOM_BOTS_BOT);
stmt->SetData(0, "add");
if (PreparedQueryResult result = PlayerbotsDatabase.Query(stmt))
{
do
{
Field* fields = result->Fetch();
uint32 bot = fields[0].Get<uint32>();
randomBots.push_back(bot);
} while (result->NextRow());
}
for (std::vector<uint32>::iterator i = randomBots.begin(); i != randomBots.end(); ++i)
{
if (Guild* guild = sGuildMgr->GetGuildByLeader(ObjectGuid::Create<HighGuid::Player>(*i)))
guild->Disband();
}
LOG_INFO("playerbots", "Random bot guilds deleted");
}
bool PlayerbotGuildMgr::IsRealGuild(Player* bot)
{
if (!bot)
return false;
uint32 guildId = bot->GetGuildId();
if (!guildId)
return false;
return IsRealGuild(guildId);
}
bool PlayerbotGuildMgr::IsRealGuild(uint32 guildId)
{
if (!guildId)
return false;
auto it = _guildCache.find(guildId);
if (it == _guildCache.end())
return false;
return it->second.hasRealPlayer;
}
class BotGuildCacheWorldScript : public WorldScript
{
public:
BotGuildCacheWorldScript() : WorldScript("BotGuildCacheWorldScript"), _validateTimer(0){}
void OnUpdate(uint32 diff) override
{
_validateTimer += diff;
if (_validateTimer >= _validateInterval) // Validate every hour
{
_validateTimer = 0;
sPlayerbotGuildMgr->ValidateGuildCache();
LOG_INFO("playerbots", "Scheduled guild cache validation");
}
}
private:
uint32 _validateInterval = HOUR*IN_MILLISECONDS;
uint32 _validateTimer;
};
void PlayerBotsGuildValidationScript()
{
new BotGuildCacheWorldScript();
}

52
src/PlayerbotGuildMgr.h Normal file
View File

@@ -0,0 +1,52 @@
#ifndef _PLAYERBOT_PLAYERBOTGUILDMGR_H
#define _PLAYERBOT_PLAYERBOTGUILDMGR_H
#include "Guild.h"
#include "Player.h"
#include "PlayerbotAI.h"
class PlayerbotAI;
class PlayerbotGuildMgr
{
public:
static PlayerbotGuildMgr* instance()
{
static PlayerbotGuildMgr instance;
return &instance;
}
void Init();
std::string AssignToGuild(Player* player);
void LoadGuildNames();
void ValidateGuildCache();
void ResetGuildCache();
bool CreateGuild(Player* player, std::string guildName);
void OnGuildUpdate (Guild* guild);
bool SetGuildEmblem(uint32 guildId);
void DeleteBotGuilds();
bool IsRealGuild(uint32 guildId);
bool IsRealGuild(Player* bot);
private:
PlayerbotGuildMgr();
std::unordered_map<std::string, bool> _guildNames;
struct GuildCache
{
std::string name;
uint8 status;
uint32 maxMembers = 0;
uint32 memberCount = 0;
uint8 faction = 0;
bool hasRealPlayer = false;
};
std::unordered_map<uint32 , GuildCache> _guildCache;
std::vector<std::string> _shuffled_guild_keys;
};
void PlayerBotsGuildValidationScript();
#define sPlayerbotGuildMgr PlayerbotGuildMgr::instance()
#endif

View File

@@ -32,6 +32,7 @@
#include "PlayerbotSecurity.h"
#include "PlayerbotWorldThreadProcessor.h"
#include "Playerbots.h"
#include "PlayerbotGuildMgr.h"
#include "RandomPlayerbotMgr.h"
#include "SharedDefines.h"
#include "WorldSession.h"
@@ -1193,7 +1194,7 @@ std::vector<std::string> PlayerbotHolder::HandlePlayerbotCommand(char const* arg
if (ObjectAccessor::FindConnectedPlayer(guid))
continue;
uint32 guildId = sCharacterCache->GetCharacterGuildIdByGuid(guid);
if (guildId && PlayerbotAI::IsRealGuild(guildId))
if (guildId && sPlayerbotGuildMgr->IsRealGuild(guildId))
continue;
AddPlayerBot(guid, master->GetSession()->GetAccountId());
messages.push_back("Add class " + std::string(charname));

View File

@@ -25,6 +25,7 @@
#include "Metric.h"
#include "PlayerScript.h"
#include "PlayerbotAIConfig.h"
#include "PlayerbotGuildMgr.h"
#include "PlayerbotSpellCache.h"
#include "PlayerbotWorldThreadProcessor.h"
#include "RandomPlayerbotMgr.h"
@@ -502,6 +503,8 @@ public:
void OnBattlegroundEnd(Battleground* bg, TeamId /*winnerTeam*/) override { bgStrategies.erase(bg->GetInstanceID()); }
};
void AddPlayerbotsSecureLoginScripts();
void AddPlayerbotsScripts()
{
new PlayerbotsDatabaseScript();
@@ -511,6 +514,7 @@ void AddPlayerbotsScripts()
new PlayerbotsWorldScript();
new PlayerbotsScript();
new PlayerBotsBGScript();
AddPlayerbotsSecureLoginScripts();
AddSC_playerbots_commandscript();
PlayerBotsGuildValidationScript();
}

View File

@@ -0,0 +1,82 @@
#include "ScriptMgr.h"
#include "Opcodes.h"
#include "Player.h"
#include "ObjectAccessor.h"
#include "Playerbots.h"
namespace
{
static Player* FindOnlineAltbotByGuid(ObjectGuid guid)
{
if (!guid)
return nullptr;
Player* p = ObjectAccessor::FindPlayer(guid);
if (!p)
return nullptr;
PlayerbotAI* ai = GET_PLAYERBOT_AI(p);
if (!ai || ai->IsRealPlayer())
return nullptr;
return p;
}
static void ForceLogoutViaPlayerbotHolder(Player* target)
{
if (!target)
return;
PlayerbotAI* ai = GET_PLAYERBOT_AI(target);
if (!ai)
return;
if (Player* master = ai->GetMaster())
{
if (PlayerbotMgr* mgr = GET_PLAYERBOT_MGR(master))
{
mgr->LogoutPlayerBot(target->GetGUID());
return;
}
}
if (sRandomPlayerbotMgr)
{
sRandomPlayerbotMgr->LogoutPlayerBot(target->GetGUID());
return;
}
}
}
class PlayerbotsSecureLoginServerScript : public ServerScript
{
public:
PlayerbotsSecureLoginServerScript()
: ServerScript("PlayerbotsSecureLoginServerScript", { SERVERHOOK_CAN_PACKET_RECEIVE }) {}
bool CanPacketReceive(WorldSession* /*session*/, WorldPacket& packet) override
{
if (packet.GetOpcode() != CMSG_PLAYER_LOGIN)
return true;
auto const oldPos = packet.rpos();
ObjectGuid loginGuid;
packet >> loginGuid;
packet.rpos(oldPos);
if (!loginGuid)
return true;
Player* existingAltbot = FindOnlineAltbotByGuid(loginGuid);
if (existingAltbot)
ForceLogoutViaPlayerbotHolder(existingAltbot);
return true;
}
};
void AddPlayerbotsSecureLoginScripts()
{
new PlayerbotsSecureLoginServerScript();
}

View File

@@ -2834,22 +2834,20 @@ inline bool ContainsInternal(ItemTemplate const* proto, uint32 skillId)
CreatureTemplateContainer const* creatures = sObjectMgr->GetCreatureTemplates();
for (CreatureTemplateContainer::const_iterator itr = creatures->begin(); itr != creatures->end(); ++itr)
{
if (itr->second.trainer_type != TRAINER_TYPE_TRADESKILLS)
Trainer::Trainer* trainer = sObjectMgr->GetTrainer(itr->first);
if (!trainer)
continue;
uint32 trainerId = itr->second.Entry;
TrainerSpellData const* trainer_spells = sObjectMgr->GetNpcTrainerSpells(trainerId);
if (!trainer_spells)
if (trainer->GetTrainerType() != Trainer::Type::Tradeskill)
continue;
for (TrainerSpellMap::const_iterator iter = trainer_spells->spellList.begin();
iter != trainer_spells->spellList.end(); ++iter)
for (auto& spell : trainer->GetSpells())
{
TrainerSpell const* tSpell = &iter->second;
if (!tSpell || tSpell->reqSkill != skillId)
if (spell.ReqSkillLine != skillId)
continue;
if (IsCraftedBy(proto, tSpell->spell))
if (IsCraftedBy(proto, spell.SpellId))
return true;
}
}

View File

@@ -11,6 +11,7 @@
#include "GuildMgr.h"
#include "PlayerbotFactory.h"
#include "Playerbots.h"
#include "PlayerbotGuildMgr.h"
#include "ScriptMgr.h"
#include "SharedDefines.h"
#include "SocialMgr.h"
@@ -754,187 +755,6 @@ void RandomPlayerbotFactory::CreateRandomBots()
sPlayerbotAIConfig->randomBotAccounts.size(), totalRandomBotChars);
}
void RandomPlayerbotFactory::CreateRandomGuilds()
{
std::vector<uint32> randomBots;
PlayerbotsDatabasePreparedStatement* stmt = PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_SEL_RANDOM_BOTS_BOT);
stmt->SetData(0, "add");
if (PreparedQueryResult result = PlayerbotsDatabase.Query(stmt))
{
do
{
Field* fields = result->Fetch();
uint32 bot = fields[0].Get<uint32>();
randomBots.push_back(bot);
} while (result->NextRow());
}
if (sPlayerbotAIConfig->deleteRandomBotGuilds)
{
LOG_INFO("playerbots", "Deleting random bot guilds...");
for (std::vector<uint32>::iterator i = randomBots.begin(); i != randomBots.end(); ++i)
{
if (Guild* guild = sGuildMgr->GetGuildByLeader(ObjectGuid::Create<HighGuid::Player>(*i)))
guild->Disband();
}
LOG_INFO("playerbots", "Random bot guilds deleted");
}
std::unordered_set<uint32> botAccounts;
botAccounts.reserve(sPlayerbotAIConfig->randomBotAccounts.size());
for (uint32 acc : sPlayerbotAIConfig->randomBotAccounts)
botAccounts.insert(acc);
// Recount bot guilds directly from the database (does not depend on connected bots)
uint32 guildNumber = 0;
sPlayerbotAIConfig->randomBotGuilds.clear();
sPlayerbotAIConfig->randomBotGuilds.shrink_to_fit(); // avoids accumulating old capacity
if (!botAccounts.empty())
{
if (QueryResult res = CharacterDatabase.Query(
// We only retrieve what is necessary (guildid, leader account)
"SELECT g.guildid, c.account "
"FROM guild g JOIN characters c ON g.leaderguid = c.guid"))
{
do
{
Field* f = res->Fetch();
const uint32 guildId = f[0].Get<uint32>();
const uint32 accountId = f[1].Get<uint32>();
// Determine if guild leader's account is a bot account.
if (botAccounts.find(accountId) != botAccounts.end())
{
++guildNumber;
sPlayerbotAIConfig->randomBotGuilds.push_back(guildId);
}
} while (res->NextRow());
}
}
LOG_INFO("playerbots", "{}/{} random bot guilds exist in guild table",guildNumber, sPlayerbotAIConfig->randomBotGuildCount);
if (guildNumber >= sPlayerbotAIConfig->randomBotGuildCount)
{
LOG_DEBUG("playerbots", "No new random guilds required");
return;
}
// We list the available leaders (online bots, not in guilds)
GuidVector availableLeaders;
availableLeaders.reserve(randomBots.size()); // limit reallocs
for (const uint32 botLowGuid : randomBots)
{
ObjectGuid leader = ObjectGuid::Create<HighGuid::Player>(botLowGuid);
if (sGuildMgr->GetGuildByLeader(leader))
{
// already GuildLeader -> ignored
continue;
}
else
{
if (Player* player = ObjectAccessor::FindPlayer(leader))
{
if (!player->GetGuildId())
availableLeaders.push_back(leader);
}
}
}
LOG_DEBUG("playerbots", "{} available leaders for new guilds found", availableLeaders.size());
// Create up to randomBotGuildCount by counting only EFFECTIVE creations
uint32 createdThisRun = 0;
for (; guildNumber < sPlayerbotAIConfig->randomBotGuildCount; /* ++guildNumber -> done only if creation */)
{
std::string const guildName = CreateRandomGuildName();
if (guildName.empty())
break; // no more names available in playerbots_guild_names
if (sGuildMgr->GetGuildByName(guildName))
continue; // name already taken, skip
if (availableLeaders.empty())
{
LOG_ERROR("playerbots", "No leaders for random guilds available");
break; // no more leaders: we can no longer progress without distorting the counter
}
uint32 index = urand(0, availableLeaders.size() - 1);
ObjectGuid leader = availableLeaders[index];
availableLeaders.erase(availableLeaders.begin() + index); // Removes the chosen leader to avoid re-selecting it repeatedly
Player* player = ObjectAccessor::FindPlayer(leader);
if (!player)
{
LOG_ERROR("playerbots", "ObjectAccessor Cannot find player to set leader for guild {} . Skipped...",
guildName.c_str());
// we will try with other leaders in the next round (guildNumber is not incremented)
continue;
}
if (player->GetGuildId())
{
// leader already in guild -> we don't advance the counter, we move on to the next one
continue;
}
LOG_DEBUG("playerbots", "Creating guild name='{}' leader='{}'...", guildName.c_str(), player->GetName().c_str());
Guild* guild = new Guild();
if (!guild->Create(player, guildName))
{
LOG_ERROR("playerbots", "Error creating guild [ {} ] with leader [ {} ]", guildName.c_str(),
player->GetName().c_str());
delete guild;
continue;
}
sGuildMgr->AddGuild(guild);
LOG_DEBUG("playerbots", "Guild created: id={} name='{}'", guild->GetId(), guildName.c_str());
// create random emblem
uint32 st, cl, br, bc, bg;
bg = urand(0, 51);
bc = urand(0, 17);
cl = urand(0, 17);
br = urand(0, 7);
st = urand(0, 180);
LOG_DEBUG("playerbots",
"[TABARD] new guild id={} random -> style={}, color={}, borderStyle={}, borderColor={}, bgColor={}",
guild->GetId(), st, cl, br, bc, bg);
// populate guild table with a random tabard design
CharacterDatabase.Execute(
"UPDATE guild SET EmblemStyle={}, EmblemColor={}, BorderStyle={}, BorderColor={}, BackgroundColor={} "
"WHERE guildid={}",
st, cl, br, bc, bg, guild->GetId());
LOG_DEBUG("playerbots", "[TABARD] UPDATE done for guild id={}", guild->GetId());
// Immediate reading for log
if (QueryResult qr = CharacterDatabase.Query(
"SELECT EmblemStyle,EmblemColor,BorderStyle,BorderColor,BackgroundColor FROM guild WHERE guildid={}",
guild->GetId()))
{
Field* f = qr->Fetch();
LOG_DEBUG("playerbots",
"[TABARD] DB check guild id={} => style={}, color={}, borderStyle={}, borderColor={}, bgColor={}",
guild->GetId(), f[0].Get<uint8>(), f[1].Get<uint8>(), f[2].Get<uint8>(), f[3].Get<uint8>(), f[4].Get<uint8>());
}
sPlayerbotAIConfig->randomBotGuilds.push_back(guild->GetId());
// The guild is only counted if it is actually created
++guildNumber;
++createdThisRun;
}
// Shows the true total and how many were created during this run
LOG_INFO("playerbots", "{} random bot guilds created this run)", createdThisRun);
}
std::string const RandomPlayerbotFactory::CreateRandomGuildName()
{
std::string guildName = "";

View File

@@ -51,7 +51,6 @@ public:
Player* CreateRandomBot(WorldSession* session, uint8 cls, std::unordered_map<NameRaceAndGender, std::vector<std::string>>& names);
static void CreateRandomBots();
static void CreateRandomGuilds();
static void CreateRandomArenaTeams(ArenaType slot, uint32 count);
static std::string const CreateRandomGuildName();
static uint32 CalculateTotalAccountCount();

View File

@@ -1654,6 +1654,10 @@ void RandomPlayerbotMgr::RandomTeleport(Player* bot, std::vector<WorldLocation>&
if (bot->IsBeingTeleported() || !bot->IsInWorld())
return;
// no teleport / movement update when rooted.
if (bot->IsRooted())
return;
// ignore when in queue for battle grounds.
if (bot->InBattlegroundQueue())
return;
@@ -2593,14 +2597,17 @@ void RandomPlayerbotMgr::Refresh(Player* bot)
bool RandomPlayerbotMgr::IsRandomBot(Player* bot)
{
if (!bot)
return false;
if (bot && GET_PLAYERBOT_AI(bot))
{
if (GET_PLAYERBOT_AI(bot)->IsRealPlayer())
return false;
}
if (bot)
{
return IsRandomBot(bot->GetGUID().GetCounter());
}
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
if (!botAI || botAI->IsRealPlayer())
return false;
return IsRandomBot(bot->GetGUID().GetCounter());
return false;
}
bool RandomPlayerbotMgr::IsRandomBot(ObjectGuid::LowType bot)

View File

@@ -41,13 +41,17 @@ bool ServerFacade::IsDistanceLessOrEqualThan(float dist1, float dist2) { return
void ServerFacade::SetFacingTo(Player* bot, WorldObject* wo, bool force)
{
if (!bot)
return;
float angle = bot->GetAngle(wo);
// if (!force && bot->isMoving())
// bot->SetFacingTo(bot->GetAngle(wo));
// else
// {
bot->SetOrientation(angle);
bot->SendMovementFlagUpdate();
if (!bot->IsRooted())
bot->SendMovementFlagUpdate();
// }
}

View File

@@ -218,6 +218,11 @@ bool WorldPosition::isUnderWater()
: false;
};
bool WorldPosition::IsValid()
{
return !(GetMapId() == MAPID_INVALID && GetPositionX() == 0 && GetPositionY() == 0 && GetPositionZ() == 0);
}
WorldPosition WorldPosition::relPoint(WorldPosition* center)
{
return WorldPosition(GetMapId(), GetPositionX() - center->GetPositionX(), GetPositionY() - center->GetPositionY(),
@@ -3404,13 +3409,14 @@ void TravelMgr::LoadQuestTravelTable()
{
Strategy* strat = con->GetStrategy(stratName);
if (strat->getDefaultActions())
for (uint32 i = 0; i < NextAction::size(strat->getDefaultActions()); i++)
{
NextAction* nextAction = strat->getDefaultActions()[i];
const std::vector<NextAction> defaultActions = strat->getDefaultActions();
if (defaultActions.size() > 0)
{
for (NextAction nextAction : defaultActions)
{
std::ostringstream aout;
aout << nextAction->getRelevance() << "," << nextAction->getName()
aout << nextAction.getRelevance() << "," << nextAction.getName()
<< ",,S:" << stratName;
if (actions.find(aout.str().c_str()) != actions.end())
@@ -3422,27 +3428,24 @@ void TravelMgr::LoadQuestTravelTable()
actions.insert_or_assign(aout.str().c_str(), classSpecLevel);
}
}
std::vector<TriggerNode*> triggers;
strat->InitTriggers(triggers);
for (auto& triggerNode : triggers)
{
// out << " TN:" << triggerNode->getName();
for (TriggerNode*& triggerNode : triggers)
{
if (Trigger* trigger = con->GetTrigger(triggerNode->getName()))
{
triggerNode->setTrigger(trigger);
NextAction** nextActions = triggerNode->getHandlers();
std::vector<NextAction> nextActions = triggerNode->getHandlers();
for (uint32 i = 0; i < NextAction::size(nextActions); i++)
// for (uint32_t i = 0; i < nextActions.size(); ++i)
for (NextAction nextAction : nextActions)
{
NextAction* nextAction = nextActions[i];
// out << " A:" << nextAction->getName() << "(" <<
// nextAction->getRelevance() << ")";
std::ostringstream aout;
aout << nextAction->getRelevance() << "," << nextAction->getName()
aout << nextAction.getRelevance() << "," << nextAction.getName()
<< "," << triggerNode->getName() << "," << stratName;
if (actions.find(aout.str().c_str()) != actions.end())

View File

@@ -141,6 +141,7 @@ public:
bool isOverworld();
bool isInWater();
bool isUnderWater();
bool IsValid();
WorldPosition relPoint(WorldPosition* center);
WorldPosition offset(WorldPosition* center);

View File

@@ -30,6 +30,7 @@
#include "PlayerbotAI.h"
#include "PlayerbotAIConfig.h"
#include "PlayerbotDbStore.h"
#include "PlayerbotGuildMgr.h"
#include "Playerbots.h"
#include "QuestDef.h"
#include "RandomItemMgr.h"
@@ -1102,8 +1103,6 @@ void PlayerbotFactory::ResetQuests()
}
}
void PlayerbotFactory::InitSpells() { InitAvailableSpells(); }
void PlayerbotFactory::InitTalentsTree(bool increment /*false*/, bool use_template /*true*/, bool reset /*false*/)
{
uint32 specTab;
@@ -2525,66 +2524,35 @@ void PlayerbotFactory::InitAvailableSpells()
for (CreatureTemplateContainer::const_iterator i = creatureTemplateContainer->begin();
i != creatureTemplateContainer->end(); ++i)
{
CreatureTemplate const& co = i->second;
if (co.trainer_type != TRAINER_TYPE_TRADESKILLS && co.trainer_type != TRAINER_TYPE_CLASS)
Trainer::Trainer* trainer = sObjectMgr->GetTrainer(i->first);
if (!trainer)
continue;
if (co.trainer_type == TRAINER_TYPE_CLASS && co.trainer_class != bot->getClass())
if (trainer->GetTrainerType() != Trainer::Type::Tradeskill &&
trainer->GetTrainerType() != Trainer::Type::Class)
continue;
uint32 trainerId = co.Entry;
trainerIdCache[bot->getClass()].push_back(trainerId);
if (trainer->GetTrainerType() == Trainer::Type::Class &&
!trainer->IsTrainerValidForPlayer(bot))
continue;
trainerIdCache[bot->getClass()].push_back(i->first);
}
}
for (uint32 trainerId : trainerIdCache[bot->getClass()])
{
TrainerSpellData const* trainer_spells = sObjectMgr->GetNpcTrainerSpells(trainerId);
if (!trainer_spells)
trainer_spells = sObjectMgr->GetNpcTrainerSpells(trainerId);
Trainer::Trainer* trainer = sObjectMgr->GetTrainer(trainerId);
if (!trainer_spells)
continue;
for (TrainerSpellMap::const_iterator itr = trainer_spells->spellList.begin();
itr != trainer_spells->spellList.end(); ++itr)
for (auto& spell : trainer->GetSpells())
{
TrainerSpell const* tSpell = &itr->second;
if (!tSpell)
if (!trainer->CanTeachSpell(bot, trainer->GetSpell(spell.SpellId)))
continue;
if (tSpell->learnedSpell[0] && !bot->IsSpellFitByClassAndRace(tSpell->learnedSpell[0]))
continue;
TrainerSpellState state = bot->GetTrainerSpellState(tSpell);
if (state != TRAINER_SPELL_GREEN)
continue;
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(tSpell->spell);
bool learn = true;
for (uint8 j = 0; j < 3; ++j)
{
if (!tSpell->learnedSpell[j] && !bot->IsSpellFitByClassAndRace(tSpell->learnedSpell[j]))
continue;
if (spellInfo->Effects[j].Effect == SPELL_EFFECT_PROFICIENCY ||
(spellInfo->Effects[j].Effect == SPELL_EFFECT_SKILL_STEP &&
spellInfo->Effects[j].MiscValue != SKILL_RIDING) ||
spellInfo->Effects[j].Effect == SPELL_EFFECT_DUAL_WIELD)
{
learn = false;
break;
}
}
if (!learn)
{
continue;
}
if (tSpell->IsCastable())
bot->CastSpell(bot, tSpell->spell, true);
if (spell.IsCastable())
bot->CastSpell(bot, spell.SpellId, true);
else
bot->learnSpell(tSpell->learnedSpell[0], false);
bot->learnSpell(spell.SpellId, false);
}
}
}
@@ -3965,45 +3933,33 @@ void PlayerbotFactory::InitInventoryEquip()
void PlayerbotFactory::InitGuild()
{
if (bot->GetGuildId())
return;
// bot->SaveToDB(false, false);
// add guild tabard
if (bot->GetGuildId() && !bot->HasItemCount(5976, 1))
StoreItem(5976, 1);
if (sPlayerbotAIConfig->randomBotGuilds.empty())
RandomPlayerbotFactory::CreateRandomGuilds();
std::vector<uint32> guilds;
for (std::vector<uint32>::iterator i = sPlayerbotAIConfig->randomBotGuilds.begin();
i != sPlayerbotAIConfig->randomBotGuilds.end(); ++i)
guilds.push_back(*i);
if (guilds.empty())
{
LOG_ERROR("playerbots", "No random guilds available");
if (!bot->HasItemCount(5976, 1) && bot->GetLevel() > 9)
StoreItem(5976, 1);
return;
}
int index = urand(0, guilds.size() - 1);
uint32 guildId = guilds[index];
Guild* guild = sGuildMgr->GetGuildById(guildId);
std::string guildName = sPlayerbotGuildMgr->AssignToGuild(bot);
if (guildName.empty())
return;
Guild* guild = sGuildMgr->GetGuildByName(guildName);
if (!guild)
{
LOG_ERROR("playerbots", "Invalid guild {}", guildId);
if (!sPlayerbotGuildMgr->CreateGuild(bot, guildName))
LOG_ERROR("playerbots","Failed to create guild {} for bot {}", guildName, bot->GetName());
return;
}
if (guild->GetMemberSize() < urand(10, sPlayerbotAIConfig->randomBotGuildSizeMax))
guild->AddMember(bot->GetGUID(), urand(GR_OFFICER, GR_INITIATE));
else
{
if (guild->AddMember(bot->GetGUID(),urand(GR_OFFICER, GR_INITIATE)))
sPlayerbotGuildMgr->OnGuildUpdate(guild);
else
LOG_ERROR("playerbots","Bot {} failed to join guild {}.", bot->GetName(), guildName);
}
// add guild tabard
if (bot->GetGuildId() && bot->GetLevel() > 9 && urand(0, 4) && !bot->HasItemCount(5976, 1))
StoreItem(5976, 1);
// bot->SaveToDB(false, false);
}
void PlayerbotFactory::InitImmersive()

View File

@@ -95,7 +95,6 @@ private:
void InitTradeSkills();
void UpdateTradeSkills();
void SetRandomSkill(uint16 id);
void InitSpells();
void ClearSpells();
void ClearSkills();
void InitTalents(uint32 specNo);

View File

@@ -37,7 +37,7 @@ StatsWeightCalculator::StatsWeightCalculator(Player* player) : player_(player)
tab = AiFactory::GetPlayerSpecTab(player);
collector_ = std::make_unique<StatsCollector>(type_, cls);
if (cls == CLASS_DEATH_KNIGHT && tab == DEATHKNIGHT_TAB_UNHOLY)
if (cls == CLASS_DEATH_KNIGHT && tab == DEATH_KNIGHT_TAB_UNHOLY)
hitOverflowType_ = CollectorType::SPELL;
else if (cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT)
hitOverflowType_ = CollectorType::SPELL;
@@ -193,7 +193,7 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player)
stats_weights_[STATS_TYPE_MELEE_DPS] += 0.01f;
stats_weights_[STATS_TYPE_RANGED_DPS] += 0.01f;
if (cls == CLASS_HUNTER && (tab == HUNTER_TAB_BEASTMASTERY || tab == HUNTER_TAB_SURVIVAL))
if (cls == CLASS_HUNTER && (tab == HUNTER_TAB_BEAST_MASTERY || tab == HUNTER_TAB_SURVIVAL))
{
stats_weights_[STATS_TYPE_AGILITY] += 2.5f;
stats_weights_[STATS_TYPE_ATTACK_POWER] += 1.0f;
@@ -249,7 +249,7 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player)
stats_weights_[STATS_TYPE_EXPERTISE] += 2.1f;
stats_weights_[STATS_TYPE_MELEE_DPS] += 5.0f;
}
else if (cls == CLASS_WARRIOR && tab == WARRIOR_TAB_FURY) // fury
else if (cls == CLASS_WARRIOR && tab == WARRIOR_TAB_FURY)
{
stats_weights_[STATS_TYPE_AGILITY] += 1.8f;
stats_weights_[STATS_TYPE_STRENGTH] += 2.6f;
@@ -261,7 +261,7 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player)
stats_weights_[STATS_TYPE_EXPERTISE] += 2.5f;
stats_weights_[STATS_TYPE_MELEE_DPS] += 7.0f;
}
else if (cls == CLASS_WARRIOR && tab == WARRIOR_TAB_ARMS) // arm
else if (cls == CLASS_WARRIOR && tab == WARRIOR_TAB_ARMS)
{
stats_weights_[STATS_TYPE_AGILITY] += 1.6f;
stats_weights_[STATS_TYPE_STRENGTH] += 2.3f;
@@ -273,7 +273,7 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player)
stats_weights_[STATS_TYPE_EXPERTISE] += 1.4f;
stats_weights_[STATS_TYPE_MELEE_DPS] += 7.0f;
}
else if (cls == CLASS_DEATH_KNIGHT && tab == DEATHKNIGHT_TAB_FROST) // frost dk
else if (cls == CLASS_DEATH_KNIGHT && tab == DEATH_KNIGHT_TAB_FROST)
{
stats_weights_[STATS_TYPE_AGILITY] += 1.7f;
stats_weights_[STATS_TYPE_STRENGTH] += 2.8f;
@@ -285,7 +285,7 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player)
stats_weights_[STATS_TYPE_EXPERTISE] += 2.5f;
stats_weights_[STATS_TYPE_MELEE_DPS] += 7.0f;
}
else if (cls == CLASS_DEATH_KNIGHT && tab == DEATHKNIGHT_TAB_UNHOLY)
else if (cls == CLASS_DEATH_KNIGHT && tab == DEATH_KNIGHT_TAB_UNHOLY)
{
stats_weights_[STATS_TYPE_AGILITY] += 0.9f;
stats_weights_[STATS_TYPE_STRENGTH] += 2.5f;
@@ -297,7 +297,7 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player)
stats_weights_[STATS_TYPE_EXPERTISE] += 1.5f;
stats_weights_[STATS_TYPE_MELEE_DPS] += 5.0f;
}
else if (cls == CLASS_PALADIN && tab == PALADIN_TAB_RETRIBUTION) // retribution
else if (cls == CLASS_PALADIN && tab == PALADIN_TAB_RETRIBUTION)
{
stats_weights_[STATS_TYPE_AGILITY] += 1.6f;
stats_weights_[STATS_TYPE_STRENGTH] += 2.5f;
@@ -311,7 +311,7 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player)
stats_weights_[STATS_TYPE_EXPERTISE] += 2.0f;
stats_weights_[STATS_TYPE_MELEE_DPS] += 9.0f;
}
else if ((cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT)) // enhancement
else if ((cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT))
{
stats_weights_[STATS_TYPE_AGILITY] += 1.4f;
stats_weights_[STATS_TYPE_STRENGTH] += 1.1f;
@@ -325,9 +325,10 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player)
stats_weights_[STATS_TYPE_EXPERTISE] += 2.0f;
stats_weights_[STATS_TYPE_MELEE_DPS] += 8.5f;
}
else if (cls == CLASS_WARLOCK || (cls == CLASS_MAGE && tab != MAGE_TAB_FIRE) ||
(cls == CLASS_PRIEST && tab == PRIEST_TAB_SHADOW) || // shadow
(cls == CLASS_DRUID && tab == DRUID_TAB_BALANCE)) // balance
else if (cls == CLASS_WARLOCK ||
(cls == CLASS_MAGE && tab != MAGE_TAB_FIRE) ||
(cls == CLASS_PRIEST && tab == PRIEST_TAB_SHADOW) ||
(cls == CLASS_DRUID && tab == DRUID_TAB_BALANCE))
{
stats_weights_[STATS_TYPE_INTELLECT] += 0.3f;
stats_weights_[STATS_TYPE_SPIRIT] += 0.6f;
@@ -355,8 +356,8 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player)
stats_weights_[STATS_TYPE_CRIT] += 0.8f;
stats_weights_[STATS_TYPE_HASTE] += 1.0f;
}
else if ((cls == CLASS_PALADIN && tab == PALADIN_TAB_HOLY) || // holy
(cls == CLASS_SHAMAN && tab == SHAMAN_TAB_RESTORATION)) // heal
else if ((cls == CLASS_PALADIN && tab == PALADIN_TAB_HOLY) ||
(cls == CLASS_SHAMAN && tab == SHAMAN_TAB_RESTORATION))
{
stats_weights_[STATS_TYPE_INTELLECT] += 0.9f;
stats_weights_[STATS_TYPE_SPIRIT] += 0.15f;
@@ -365,7 +366,7 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player)
stats_weights_[STATS_TYPE_CRIT] += 0.6f;
stats_weights_[STATS_TYPE_HASTE] += 0.8f;
}
else if ((cls == CLASS_PRIEST && tab != PRIEST_TAB_SHADOW) || // discipline / holy
else if ((cls == CLASS_PRIEST && tab != PRIEST_TAB_SHADOW) ||
(cls == CLASS_DRUID && tab == DRUID_TAB_RESTORATION))
{
stats_weights_[STATS_TYPE_INTELLECT] += 0.8f;
@@ -396,7 +397,7 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player)
stats_weights_[STATS_TYPE_EXPERTISE] += 3.0f;
stats_weights_[STATS_TYPE_MELEE_DPS] += 2.0f;
}
else if (cls == CLASS_DEATH_KNIGHT && tab == DEATHKNIGHT_TAB_BLOOD)
else if (cls == CLASS_DEATH_KNIGHT && tab == DEATH_KNIGHT_TAB_BLOOD)
{
stats_weights_[STATS_TYPE_AGILITY] += 2.0f;
stats_weights_[STATS_TYPE_STRENGTH] += 1.0f;
@@ -539,7 +540,7 @@ void StatsWeightCalculator::CalculateItemTypePenalty(ItemTemplate const* proto)
// spec without double hand
// enhancement, rogue, ice dk, unholy dk, shield tank, fury warrior without titan's grip but with duel wield
if (((cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT && player_->CanDualWield()) ||
(cls == CLASS_ROGUE) || (cls == CLASS_DEATH_KNIGHT && tab == DEATHKNIGHT_TAB_FROST) ||
(cls == CLASS_ROGUE) || (cls == CLASS_DEATH_KNIGHT && tab == DEATH_KNIGHT_TAB_FROST) ||
(cls == CLASS_WARRIOR && tab == WARRIOR_TAB_FURY && !player_->CanTitanGrip() &&
player_->CanDualWield()) ||
(cls == CLASS_WARRIOR && tab == WARRIOR_TAB_PROTECTION) ||
@@ -556,7 +557,7 @@ void StatsWeightCalculator::CalculateItemTypePenalty(ItemTemplate const* proto)
(cls == CLASS_WARRIOR && tab == WARRIOR_TAB_FURY && !player_->CanDualWield()) ||
(cls == CLASS_WARRIOR && tab == WARRIOR_TAB_ARMS) || (cls == CLASS_DRUID && tab == DRUID_TAB_FERAL) ||
(cls == CLASS_PALADIN && tab == PALADIN_TAB_RETRIBUTION) ||
(cls == CLASS_DEATH_KNIGHT && tab == DEATHKNIGHT_TAB_BLOOD) ||
(cls == CLASS_DEATH_KNIGHT && tab == DEATH_KNIGHT_TAB_BLOOD) ||
(cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT && !player_->CanDualWield()))
{
weight_ *= 0.1;

View File

@@ -8,90 +8,6 @@
#include "Playerbots.h"
#include "Timer.h"
uint32 NextAction::size(NextAction** actions)
{
if (!actions)
return 0;
uint32 size = 0;
for (size = 0; actions[size];)
++size;
return size;
}
NextAction** NextAction::clone(NextAction** actions)
{
if (!actions)
return nullptr;
uint32 size = NextAction::size(actions);
NextAction** res = new NextAction*[size + 1];
for (uint32 i = 0; i < size; i++)
res[i] = new NextAction(*actions[i]);
res[size] = nullptr;
return res;
}
NextAction** NextAction::merge(NextAction** left, NextAction** right)
{
uint32 leftSize = NextAction::size(left);
uint32 rightSize = NextAction::size(right);
NextAction** res = new NextAction*[leftSize + rightSize + 1];
for (uint32 i = 0; i < leftSize; i++)
res[i] = new NextAction(*left[i]);
for (uint32 i = 0; i < rightSize; i++)
res[leftSize + i] = new NextAction(*right[i]);
res[leftSize + rightSize] = nullptr;
NextAction::destroy(left);
NextAction::destroy(right);
return res;
}
NextAction** NextAction::array(uint32 nil, ...)
{
va_list vl;
va_start(vl, nil);
uint32 size = 0;
NextAction* cur = nullptr;
do
{
cur = va_arg(vl, NextAction*);
++size;
} while (cur);
va_end(vl);
NextAction** res = new NextAction*[size];
va_start(vl, nil);
for (uint32 i = 0; i < size; i++)
res[i] = va_arg(vl, NextAction*);
va_end(vl);
return res;
}
void NextAction::destroy(NextAction** actions)
{
if (!actions)
return;
for (uint32 i = 0; actions[i]; i++)
delete actions[i];
delete[] actions;
}
Value<Unit*>* Action::GetTargetValue() { return context->GetValue<Unit*>(GetTargetName()); }
Unit* Action::GetTarget() { return GetTargetValue()->Get(); }
@@ -101,4 +17,4 @@ ActionBasket::ActionBasket(ActionNode* action, float relevance, bool skipPrerequ
{
}
bool ActionBasket::isExpired(uint32 msecs) { return getMSTime() - created >= msecs; }
bool ActionBasket::isExpired(uint32_t msecs) { return getMSTime() - created >= msecs; }

View File

@@ -3,8 +3,7 @@
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_ACTION_H
#define _PLAYERBOT_ACTION_H
#pragma once
#include "AiObject.h"
#include "Common.h"
@@ -24,15 +23,26 @@ public:
std::string const getName() { return name; }
float getRelevance() { return relevance; }
static uint32 size(NextAction** actions);
static NextAction** clone(NextAction** actions);
static NextAction** merge(NextAction** what, NextAction** with);
static NextAction** array(uint32 nil, ...);
static void destroy(NextAction** actions);
static std::vector<NextAction> merge(std::vector<NextAction> const& what, std::vector<NextAction> const& with)
{
std::vector<NextAction> result = {};
for (NextAction const& action : what)
{
result.push_back(action);
}
for (NextAction const& action : with)
{
result.push_back(action);
}
return result;
};
private:
float relevance;
std::string const name;
std::string name;
};
class Action : public AiNamedObject
@@ -52,9 +62,9 @@ public:
virtual bool Execute([[maybe_unused]] Event event) { return true; }
virtual bool isPossible() { return true; }
virtual bool isUseful() { return true; }
virtual NextAction** getPrerequisites() { return nullptr; }
virtual NextAction** getAlternatives() { return nullptr; }
virtual NextAction** getContinuers() { return nullptr; }
virtual std::vector<NextAction> getPrerequisites() { return {}; }
virtual std::vector<NextAction> getAlternatives() { return {}; }
virtual std::vector<NextAction> getContinuers() { return {}; }
virtual ActionThreatType getThreatType() { return ActionThreatType::None; }
void Update() {}
void Reset() {}
@@ -73,39 +83,44 @@ protected:
class ActionNode
{
public:
ActionNode(std::string const name, NextAction** prerequisites = nullptr, NextAction** alternatives = nullptr,
NextAction** continuers = nullptr)
: name(name), action(nullptr), continuers(continuers), alternatives(alternatives), prerequisites(prerequisites)
{
} // reorder arguments - whipowill
ActionNode(
std::string name,
std::vector<NextAction> prerequisites = {},
std::vector<NextAction> alternatives = {},
std::vector<NextAction> continuers = {}
) :
name(std::move(name)),
action(nullptr),
continuers(continuers),
alternatives(alternatives),
prerequisites(prerequisites)
{}
virtual ~ActionNode()
{
NextAction::destroy(prerequisites);
NextAction::destroy(alternatives);
NextAction::destroy(continuers);
}
virtual ~ActionNode() = default;
Action* getAction() { return action; }
void setAction(Action* action) { this->action = action; }
std::string const getName() { return name; }
const std::string getName() { return name; }
NextAction** getContinuers() { return NextAction::merge(NextAction::clone(continuers), action->getContinuers()); }
NextAction** getAlternatives()
std::vector<NextAction> getContinuers()
{
return NextAction::merge(NextAction::clone(alternatives), action->getAlternatives());
return NextAction::merge(this->continuers, action->getContinuers());
}
NextAction** getPrerequisites()
std::vector<NextAction> getAlternatives()
{
return NextAction::merge(NextAction::clone(prerequisites), action->getPrerequisites());
return NextAction::merge(this->alternatives, action->getAlternatives());
}
std::vector<NextAction> getPrerequisites()
{
return NextAction::merge(this->prerequisites, action->getPrerequisites());
}
private:
std::string const name;
const std::string name;
Action* action;
NextAction** continuers;
NextAction** alternatives;
NextAction** prerequisites;
std::vector<NextAction> continuers;
std::vector<NextAction> alternatives;
std::vector<NextAction> prerequisites;
};
class ActionBasket
@@ -121,14 +136,12 @@ public:
bool isSkipPrerequisites() { return skipPrerequisites; }
void AmendRelevance(float k) { relevance *= k; }
void setRelevance(float relevance) { this->relevance = relevance; }
bool isExpired(uint32 msecs);
bool isExpired(uint32_t msecs);
private:
ActionNode* action;
float relevance;
bool skipPrerequisites;
Event event;
uint32 created;
uint32_t created;
};
#endif

View File

@@ -42,9 +42,6 @@ protected:
// TRIGGERS
//
#define NEXT_TRIGGERS(name, relevance) \
virtual NextAction* getNextAction() { return new NextAction(name, relevance); }
#define BEGIN_TRIGGER(clazz, super) \
class clazz : public super \
{ \
@@ -78,14 +75,6 @@ protected:
clazz(PlayerbotAI* botAI) : BuffOnPartyTrigger(botAI, spell) {} \
}
#define BUFF_PARTY_TRIGGER_A(clazz, spell) \
class clazz : public BuffOnPartyTrigger \
{ \
public: \
clazz(PlayerbotAI* botAI) : BuffOnPartyTrigger(botAI, spell) {} \
bool IsActive() override; \
}
#define DEBUFF_TRIGGER(clazz, spell) \
class clazz : public DebuffTrigger \
{ \
@@ -296,14 +285,6 @@ protected:
clazz(PlayerbotAI* botAI) : CastHealingSpellAction(botAI, spell) {} \
}
#define HEAL_ACTION_U(clazz, spell, useful) \
class clazz : public CastHealingSpellAction \
{ \
public: \
clazz(PlayerbotAI* botAI) : CastHealingSpellAction(botAI, spell) {} \
bool isUseful() override { return useful; } \
}
#define HEAL_PARTY_ACTION(clazz, spell, estAmount, manaEfficiency) \
class clazz : public HealPartyMemberAction \
{ \
@@ -404,14 +385,6 @@ protected:
clazz(PlayerbotAI* botAI) : CastReachTargetSpellAction(botAI, spell, range) {} \
}
#define REACH_ACTION_U(clazz, spell, range, useful) \
class clazz : public CastReachTargetSpellAction \
{ \
public: \
clazz(PlayerbotAI* botAI) : CastReachTargetSpellAction(botAI, spell, range) {} \
bool isUseful() override { return useful; } \
}
#define ENEMY_HEALER_ACTION(clazz, spell) \
class clazz : public CastSpellOnEnemyHealerAction \
{ \
@@ -440,10 +413,6 @@ protected:
clazz(PlayerbotAI* botAI) : CastProtectSpellAction(botAI, spell) {} \
}
#define END_RANGED_SPELL_ACTION() \
} \
;
#define BEGIN_SPELL_ACTION(clazz, name) \
class clazz : public CastSpellAction \
{ \
@@ -472,42 +441,4 @@ protected:
public: \
clazz(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, name) {}
#define END_RANGED_SPELL_ACTION() \
} \
;
#define BEGIN_BUFF_ON_PARTY_ACTION(clazz, name) \
class clazz : public BuffOnPartyAction \
{ \
public: \
clazz(PlayerbotAI* botAI) : BuffOnPartyAction(botAI, name) {}
//
// Action node
//
// node_name , action, prerequisite
#define ACTION_NODE_P(name, spell, pre) \
static ActionNode* name([[maybe_unused]] PlayerbotAI* botAI) \
{ \
return new ActionNode(spell, /*P*/ NextAction::array(0, new NextAction(pre), nullptr), /*A*/ nullptr, \
/*C*/ nullptr); \
}
// node_name , action, alternative
#define ACTION_NODE_A(name, spell, alt) \
static ActionNode* name([[maybe_unused]] PlayerbotAI* botAI) \
{ \
return new ActionNode(spell, /*P*/ nullptr, /*A*/ NextAction::array(0, new NextAction(alt), nullptr), \
/*C*/ nullptr); \
}
// node_name , action, continuer
#define ACTION_NODE_C(name, spell, con) \
static ActionNode* name([[maybe_unused]] PlayerbotAI* botAI) \
{ \
return new ActionNode(spell, /*P*/ nullptr, /*A*/ nullptr, \
/*C*/ NextAction::array(0, new NextAction(con), nullptr)); \
}
#endif

View File

@@ -43,8 +43,6 @@
#include "raids/magtheridon/RaidMagtheridonTriggerContext.h"
#include "raids/gruulslair/RaidGruulsLairActionContext.h"
#include "raids/gruulslair/RaidGruulsLairTriggerContext.h"
#include "raids/naxxramas/RaidNaxxActionContext.h"
#include "raids/naxxramas/RaidNaxxTriggerContext.h"
#include "raids/eyeofeternity/RaidEoEActionContext.h"
#include "raids/eyeofeternity/RaidEoETriggerContext.h"
#include "raids/vaultofarchavon/RaidVoAActionContext.h"
@@ -117,7 +115,6 @@ void AiObjectContext::BuildSharedActionContexts(SharedNamedObjectContextList<Act
actionContexts.Add(new RaidKarazhanActionContext());
actionContexts.Add(new RaidMagtheridonActionContext());
actionContexts.Add(new RaidGruulsLairActionContext());
actionContexts.Add(new RaidNaxxActionContext());
actionContexts.Add(new RaidOsActionContext());
actionContexts.Add(new RaidEoEActionContext());
actionContexts.Add(new RaidVoAActionContext());
@@ -152,7 +149,6 @@ void AiObjectContext::BuildSharedTriggerContexts(SharedNamedObjectContextList<Tr
triggerContexts.Add(new RaidKarazhanTriggerContext());
triggerContexts.Add(new RaidMagtheridonTriggerContext());
triggerContexts.Add(new RaidGruulsLairTriggerContext());
triggerContexts.Add(new RaidNaxxTriggerContext());
triggerContexts.Add(new RaidOsTriggerContext());
triggerContexts.Add(new RaidEoETriggerContext());
triggerContexts.Add(new RaidVoATriggerContext());

View File

@@ -6,36 +6,40 @@
#include "CustomStrategy.h"
#include <regex>
#include <stdexcept>
#include "Playerbots.h"
std::map<std::string, std::string> CustomStrategy::actionLinesCache;
NextAction* toNextAction(std::string const action)
NextAction toNextAction(std::string const action)
{
std::vector<std::string> tokens = split(action, '!');
if (tokens.size() == 2 && !tokens[0].empty())
return new NextAction(tokens[0], atof(tokens[1].c_str()));
else if (tokens.size() == 1 && !tokens[0].empty())
return new NextAction(tokens[0], ACTION_NORMAL);
if (tokens[0].empty())
throw std::invalid_argument("Invalid action");
if (tokens.size() == 2)
return NextAction(tokens[0], atof(tokens[1].c_str()));
if (tokens.size() == 1)
return NextAction(tokens[0], ACTION_NORMAL);
LOG_ERROR("playerbots", "Invalid action {}", action.c_str());
return nullptr;
throw std::invalid_argument("Invalid action");
}
NextAction** toNextActionArray(std::string const actions)
std::vector<NextAction> toNextActionArray(const std::string actions)
{
std::vector<std::string> tokens = split(actions, ',');
NextAction** res = new NextAction*[tokens.size() + 1];
const std::vector<std::string> tokens = split(actions, ',');
std::vector<NextAction> res = {};
uint32 index = 0;
for (std::vector<std::string>::iterator i = tokens.begin(); i != tokens.end(); ++i)
for (const std::string token : tokens)
{
if (NextAction* na = toNextAction(*i))
res[index++] = na;
res.push_back(toNextAction(token));
}
res[index++] = nullptr;
return res;
}

View File

@@ -258,48 +258,45 @@ ActionNode* Engine::CreateActionNode(std::string const name)
return node;
return new ActionNode(name,
/*P*/ nullptr,
/*A*/ nullptr,
/*C*/ nullptr);
/*P*/ {},
/*A*/ {},
/*C*/ {});
}
bool Engine::MultiplyAndPush(NextAction** actions, float forceRelevance, bool skipPrerequisites, Event event,
char const* pushType)
bool Engine::MultiplyAndPush(
std::vector<NextAction> actions,
float forceRelevance,
bool skipPrerequisites,
Event event,
char const* pushType
)
{
bool pushed = false;
if (actions)
for (NextAction nextAction : actions)
{
for (uint32 j = 0; actions[j]; j++)
ActionNode* action = this->CreateActionNode(nextAction.getName());
this->InitializeAction(action);
float k = nextAction.getRelevance();
if (forceRelevance > 0.0f)
{
if (NextAction* nextAction = actions[j])
{
ActionNode* action = CreateActionNode(nextAction->getName());
InitializeAction(action);
float k = nextAction->getRelevance();
if (forceRelevance > 0.0f)
{
k = forceRelevance;
}
if (k > 0)
{
LogAction("PUSH:%s - %f (%s)", action->getName().c_str(), k, pushType);
queue.Push(new ActionBasket(action, k, skipPrerequisites, event));
pushed = true;
}
else
{
delete action;
}
delete nextAction;
}
else
break;
k = forceRelevance;
}
delete[] actions;
if (k > 0)
{
this->LogAction("PUSH:%s - %f (%s)", action->getName().c_str(), k, pushType);
queue.Push(new ActionBasket(action, k, skipPrerequisites, event));
pushed = true;
continue;
}
delete action;
}
return pushed;
@@ -530,10 +527,10 @@ std::vector<std::string> Engine::GetStrategies()
void Engine::PushAgain(ActionNode* actionNode, float relevance, Event event)
{
NextAction** nextAction = new NextAction*[2];
nextAction[0] = new NextAction(actionNode->getName(), relevance);
nextAction[1] = nullptr;
std::vector<NextAction> nextAction = { NextAction(actionNode->getName(), relevance) };
MultiplyAndPush(nextAction, relevance, true, event, "again");
delete actionNode;
}
@@ -563,6 +560,13 @@ bool Engine::ListenAndExecute(Action* action, Event event)
{
bool actionExecuted = false;
if (action == nullptr)
{
LOG_ERROR("playerbots", "Action is nullptr");
return actionExecuted;
}
if (actionExecutionListeners.Before(action, event))
{
actionExecuted = actionExecutionListeners.AllowExecution(action, event) ? action->Execute(event) : true;

View File

@@ -90,7 +90,7 @@ public:
bool testMode;
private:
bool MultiplyAndPush(NextAction** actions, float forceRelevance, bool skipPrerequisites, Event event,
bool MultiplyAndPush(std::vector<NextAction> actions, float forceRelevance, bool skipPrerequisites, Event event,
const char* pushType);
void Reset();
void ProcessTriggers(bool minimal);

View File

@@ -28,90 +28,112 @@ public:
private:
static ActionNode* melee([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("melee",
/*P*/ nullptr,
/*A*/ nullptr,
/*C*/ nullptr);
return new ActionNode(
"melee",
/*P*/ {},
/*A*/ {},
/*C*/ {}
);
}
static ActionNode* healthstone([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("healthstone",
/*P*/ nullptr,
/*A*/ NextAction::array(0, new NextAction("healing potion"), nullptr),
/*C*/ nullptr);
return new ActionNode(
"healthstone",
/*P*/ {},
/*A*/ { NextAction("healing potion") },
/*C*/ {}
);
}
static ActionNode* follow_master_random([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("be near",
/*P*/ nullptr,
/*A*/ NextAction::array(0, new NextAction("follow"), nullptr),
/*C*/ nullptr);
return new ActionNode(
"be near",
/*P*/ {},
/*A*/ { NextAction("follow") },
/*C*/ {}
);
}
static ActionNode* attack_anything([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("attack anything",
/*P*/ nullptr,
/*A*/ nullptr,
/*C*/ nullptr);
return new ActionNode(
"attack anything",
/*P*/ {},
/*A*/ {},
/*C*/ {}
);
}
static ActionNode* move_random([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("move random",
/*P*/ nullptr,
/*A*/ NextAction::array(0, new NextAction("stay line"), nullptr),
/*C*/ nullptr);
return new ActionNode(
"move random",
/*P*/ {},
/*A*/ { NextAction("stay line") },
/*C*/ {}
);
}
static ActionNode* move_to_loot([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("move to loot",
/*P*/ nullptr,
/*A*/ nullptr,
/*C*/ nullptr);
return new ActionNode(
"move to loot",
/*P*/ {},
/*A*/ {},
/*C*/ {}
);
}
static ActionNode* food([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("food",
/*P*/ nullptr,
/*A*/ nullptr,
/*C*/ nullptr);
return new ActionNode(
"food",
/*P*/ {},
/*A*/ {},
/*C*/ {}
);
}
static ActionNode* drink([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("drink",
/*P*/ nullptr,
/*A*/ nullptr,
/*C*/ nullptr);
return new ActionNode(
"drink",
/*P*/ {},
/*A*/ {},
/*C*/ {}
);
}
static ActionNode* mana_potion([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("mana potion",
/*P*/ nullptr,
/*A*/ nullptr,
/*C*/ nullptr);
return new ActionNode(
"mana potion",
/*P*/ {},
/*A*/ {},
/*C*/ {}
);
}
static ActionNode* healing_potion([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("healing potion",
/*P*/ nullptr,
/*A*/ NextAction::array(0, new NextAction("food"), nullptr),
/*C*/ nullptr);
return new ActionNode(
"healing potion",
/*P*/ {},
/*A*/ { NextAction("food") },
/*C*/ {}
);
}
static ActionNode* flee([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("flee",
/*P*/ nullptr,
/*A*/ nullptr,
/*C*/ nullptr);
return new ActionNode(
"flee",
/*P*/ {},
/*A*/ {},
/*C*/ {}
);
}
};

View File

@@ -60,7 +60,7 @@ public:
Strategy(PlayerbotAI* botAI);
virtual ~Strategy() {}
virtual NextAction** getDefaultActions() { return nullptr; }
virtual std::vector<NextAction> getDefaultActions() { return {}; }
virtual void InitTriggers([[maybe_unused]] std::vector<TriggerNode*>& triggers) {}
virtual void InitMultipliers([[maybe_unused]] std::vector<Multiplier*>& multipliers) {}
virtual std::string const getName() = 0;

View File

@@ -120,6 +120,8 @@ public:
creators["formation"] = &StrategyContext::combat_formation;
creators["move from group"] = &StrategyContext::move_from_group;
creators["worldbuff"] = &StrategyContext::world_buff;
creators["use bobber"] = &StrategyContext::bobber_strategy;
creators["master fishing"] = &StrategyContext::master_fishing;
}
private:
@@ -188,6 +190,8 @@ private:
static Strategy* combat_formation(PlayerbotAI* botAI) { return new CombatFormationStrategy(botAI); }
static Strategy* move_from_group(PlayerbotAI* botAI) { return new MoveFromGroupStrategy(botAI); }
static Strategy* world_buff(PlayerbotAI* botAI) { return new WorldBuffStrategy(botAI); }
static Strategy* bobber_strategy(PlayerbotAI* botAI) { return new UseBobberStrategy(botAI); }
static Strategy* master_fishing(PlayerbotAI* botAI) { return new MasterFishingStrategy(botAI); }
};
class MovementStrategyContext : public NamedObjectContext<Strategy>

View File

@@ -3,8 +3,7 @@
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_TRIGGER_H
#define _PLAYERBOT_TRIGGER_H
#pragma once
#include "Action.h"
#include "Common.h"
@@ -15,7 +14,11 @@ class Unit;
class Trigger : public AiNamedObject
{
public:
Trigger(PlayerbotAI* botAI, std::string const name = "trigger", int32 checkInterval = 1);
Trigger(
PlayerbotAI* botAI,
const std::string name = "trigger",
int32_t checkInterval = 1
);
virtual ~Trigger() {}
@@ -23,7 +26,7 @@ public:
virtual void ExternalEvent([[maybe_unused]] std::string const param, [[maybe_unused]] Player* owner = nullptr) {}
virtual void ExternalEvent([[maybe_unused]] WorldPacket& packet, [[maybe_unused]] Player* owner = nullptr) {}
virtual bool IsActive() { return false; }
virtual NextAction** getHandlers() { return nullptr; }
virtual std::vector<NextAction> getHandlers() { return {}; }
void Update() {}
virtual void Reset() {}
virtual Unit* GetTarget();
@@ -33,32 +36,49 @@ public:
bool needCheck(uint32 now);
protected:
int32 checkInterval;
uint32 lastCheckTime;
int32_t checkInterval;
uint32_t lastCheckTime;
};
class TriggerNode
{
public:
TriggerNode(std::string const name, NextAction** handlers = nullptr)
: trigger(nullptr), handlers(handlers), name(name)
{
} // reorder args - whipowill
virtual ~TriggerNode() { NextAction::destroy(handlers); }
TriggerNode(
const std::string& name,
std::vector<NextAction> handlers = {}
) :
trigger(nullptr),
handlers(std::move(handlers)),
name(name)
{}
Trigger* getTrigger() { return trigger; }
void setTrigger(Trigger* trigger) { this->trigger = trigger; }
std::string const getName() { return name; }
const std::string getName() { return name; }
NextAction** getHandlers() { return NextAction::merge(NextAction::clone(handlers), trigger->getHandlers()); }
std::vector<NextAction> getHandlers()
{
std::vector<NextAction> result = this->handlers;
float getFirstRelevance() { return handlers[0] ? handlers[0]->getRelevance() : -1; }
if (trigger != nullptr)
{
std::vector<NextAction> extra = trigger->getHandlers();
result.insert(result.end(), extra.begin(), extra.end());
}
return result;
}
float getFirstRelevance()
{
if (this->handlers.size() > 0)
return this->handlers[0].getRelevance();
return -1;
}
private:
Trigger* trigger;
NextAction** handlers;
std::string const name;
std::vector<NextAction> handlers;
const std::string name;
};
#endif

View File

@@ -59,7 +59,7 @@ bool AcceptInvitationAction::Execute(Event event)
if (sPlayerbotAIConfig->summonWhenGroup && bot->GetDistance(inviter) > sPlayerbotAIConfig->sightDistance)
{
Teleport(inviter, bot);
Teleport(inviter, bot, true);
}
return true;
}

View File

@@ -46,7 +46,6 @@
#include "OutfitAction.h"
#include "PositionAction.h"
#include "DropQuestAction.h"
#include "RaidNaxxActions.h"
#include "RandomBotUpdateAction.h"
#include "ReachTargetActions.h"
#include "ReleaseSpiritAction.h"
@@ -64,6 +63,7 @@
#include "WorldBuffAction.h"
#include "XpGainAction.h"
#include "NewRpgAction.h"
#include "FishingAction.h"
#include "CancelChannelAction.h"
class PlayerbotAI;
@@ -191,6 +191,11 @@ public:
creators["buy tabard"] = &ActionContext::buy_tabard;
creators["guild manage nearby"] = &ActionContext::guild_manage_nearby;
creators["clean quest log"] = &ActionContext::clean_quest_log;
creators["move near water"] = &ActionContext::move_near_water;
creators["go fishing"] = &ActionContext::go_fishing;
creators["use fishing bobber"] = &ActionContext::use_fishing_bobber;
creators["end master fishing"] = &ActionContext::end_master_fishing;
creators["remove bobber strategy"] = &ActionContext::remove_bobber_strategy;
creators["roll"] = &ActionContext::roll_action;
creators["cancel channel"] = &ActionContext::cancel_channel;
@@ -380,6 +385,11 @@ private:
static Action* buy_tabard(PlayerbotAI* botAI) { return new BuyTabardAction(botAI); }
static Action* guild_manage_nearby(PlayerbotAI* botAI) { return new GuildManageNearbyAction(botAI); }
static Action* clean_quest_log(PlayerbotAI* botAI) { return new CleanQuestLogAction(botAI); }
static Action* move_near_water(PlayerbotAI* botAI) { return new MoveNearWaterAction(botAI); }
static Action* go_fishing(PlayerbotAI* botAI) { return new FishingAction(botAI);}
static Action* use_fishing_bobber(PlayerbotAI* botAI) { return new UseBobberAction(botAI);}
static Action* end_master_fishing(PlayerbotAI* botAI) { return new EndMasterFishingAction(botAI); }
static Action* remove_bobber_strategy(PlayerbotAI* botAI) { return new RemoveBobberStrategyAction(botAI); }
static Action* roll_action(PlayerbotAI* botAI) { return new RollAction(botAI); }
// BG Tactics

View File

@@ -159,7 +159,7 @@ bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/)
bot->StopMoving();
}
if (IsMovingAllowed() && !bot->HasInArc(CAST_ANGLE_IN_FRONT, target))
if (botAI->CanMove() && !bot->HasInArc(CAST_ANGLE_IN_FRONT, target))
sServerFacade->SetFacingTo(bot, target);
botAI->ChangeEngine(BOT_STATE_COMBAT);

View File

@@ -185,7 +185,6 @@ public:
creators["guild remove"] = &ChatActionContext::guild_remove;
creators["guild leave"] = &ChatActionContext::guild_leave;
creators["rtsc"] = &ChatActionContext::rtsc;
creators["naxx chat shortcut"] = &ChatActionContext::naxx_chat_shortcut;
creators["bwl chat shortcut"] = &ChatActionContext::bwl_chat_shortcut;
creators["tell estimated dps"] = &ChatActionContext::tell_estimated_dps;
creators["join"] = &ChatActionContext::join;
@@ -298,7 +297,6 @@ 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

@@ -241,20 +241,6 @@ 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,13 +85,6 @@ 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

@@ -78,20 +78,17 @@ float ChooseRpgTargetAction::getMaxRelevance(GuidPosition guidP)
if (!trigger->IsActive())
continue;
NextAction** nextActions = triggerNode->getHandlers();
std::vector<NextAction> nextActions = triggerNode->getHandlers();
bool isRpg = false;
for (int32 i = 0; i < NextAction::size(nextActions); i++)
for (NextAction nextAction : nextActions)
{
NextAction* nextAction = nextActions[i];
Action* action = botAI->GetAiObjectContext()->GetAction(nextAction->getName());
Action* action = botAI->GetAiObjectContext()->GetAction(nextAction.getName());
if (dynamic_cast<RpgEnabled*>(action))
isRpg = true;
}
NextAction::destroy(nextActions);
if (isRpg)
{

View File

@@ -344,6 +344,27 @@ bool EquipUpgradesAction::Execute(Event event)
return false;
}
if (event.GetSource() == "item push result")
{
WorldPacket p(event.getPacket());
p.rpos(0);
ObjectGuid playerGuid;
uint32 received, created, sendChatMessage, itemSlot, itemId;
uint8 bagSlot;
p >> playerGuid;
p >> received;
p >> created;
p >> sendChatMessage;
p >> bagSlot;
p >> itemSlot;
p >> itemId;
ItemTemplate const* item = sObjectMgr->GetItemTemplate(itemId);
if (item->Class == ITEM_CLASS_TRADE_GOODS && item->SubClass == ITEM_SUBCLASS_MEAT)
return false;
}
CollectItemsVisitor visitor;
IterateItems(&visitor, ITERATE_ITEMS_IN_BAGS);

View File

@@ -0,0 +1,493 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "FishingAction.h"
#include "FishValues.h"
#include "Event.h"
#include "GridNotifiers.h"
#include "GridNotifiersImpl.h"
#include "ItemPackets.h"
#include "LastMovementValue.h"
#include "Map.h"
#include "MovementActions.h"
#include "Object.h"
#include "PlayerbotAI.h"
#include "PlayerbotTextMgr.h"
#include "Playerbots.h"
#include "Position.h"
uint32 const FISHING_SPELL = 7620;
uint32 const FISHING_POLE = 6256;
uint32 const FISHING_BOBBER = 35591;
float const MIN_DISTANCE_TO_WATER = 10.0f; // Minimum spell distance
float const MAX_DISTANCE_TO_WATER = 20.0f; // Maximum spell distance
float const HEIGHT_ABOVE_WATER_TOLERANCE = 1.0f; // Can stand in up to 1 unit of water and still fish.
float const SEARCH_INCREMENT = 2.5f;
float const HEIGHT_SEARCH_BUFFER = 10.0f; // Height buffer to prevent potentially missing the model the bot is standing on.
float const SEARCH_LAND_BUFFER = 0.5f;
uint32 const FISHING_LOCATION_TIMEOUT = 180000; //Three minutes
static bool IsFishingPole(Item* const item)
{
if (!item)
return false;
const ItemTemplate* proto = item->GetTemplate();
return proto && proto->Class == ITEM_CLASS_WEAPON &&
proto->SubClass == ITEM_SUBCLASS_WEAPON_FISHING_POLE;
}
float HasFishableWaterOrLand(float x, float y, float z, Map* map, uint32 phaseMask, bool checkForLand=false)
{
if (!map)
return INVALID_HEIGHT;
LiquidData const& liq = map->GetLiquidData(phaseMask, x, y, z+HEIGHT_ABOVE_WATER_TOLERANCE, DEFAULT_COLLISION_HEIGHT, MAP_ALL_LIQUIDS);
float ground = map->GetHeight(phaseMask, x, y, z + HEIGHT_SEARCH_BUFFER, true);
if (liq.Entry == MAP_LIQUID_TYPE_NO_WATER)
{
if (checkForLand)
return ground;
return INVALID_HEIGHT;
}
if (checkForLand)
{
if (ground > liq.Level - HEIGHT_ABOVE_WATER_TOLERANCE)
return ground;
return INVALID_HEIGHT;
}
if (liq.Level + HEIGHT_ABOVE_WATER_TOLERANCE > ground)
{
if (abs(liq.DepthLevel) < 0.5f) // too shallow to fish in.
return INVALID_HEIGHT;
return liq.Level;
}
return INVALID_HEIGHT;
}
bool HasLosToWater(Player* bot, float wx, float wy, float waterZ)
{
float z = bot->GetCollisionHeight() + bot->GetPositionZ();
return bot->GetMap()->isInLineOfSight(
bot->GetPositionX(), bot->GetPositionY(), z,
wx, wy, waterZ,
bot->GetPhaseMask(),
LINEOFSIGHT_ALL_CHECKS,
VMAP::ModelIgnoreFlags::Nothing);
}
WorldPosition FindLandFromPosition(PlayerbotAI* botAI, float startDistance, float endDistance, float increment, float orientation, WorldPosition targetPos, float fishingSearchWindow, bool checkLOS = true)
{
Player* bot = botAI->GetBot();
Map* map = bot->GetMap();
uint32 phaseMask = bot->GetPhaseMask();
Player* master = botAI->GetMaster();
float targetX = targetPos.GetPositionX();
float targetY = targetPos.GetPositionY();
float targetZ = targetPos.GetPositionZ();
for (float dist = startDistance; dist <= endDistance; dist += increment)
{
//step backwards from position to bot to find edge of shore.
float checkX = targetX - dist * cos(orientation);
float checkY = targetY - dist * sin(orientation);
float groundZ = map->GetHeight(phaseMask, checkX, checkY, targetZ + HEIGHT_SEARCH_BUFFER, true);
if (groundZ == INVALID_HEIGHT)
continue;
LiquidData const& liq = map->GetLiquidData(phaseMask, checkX, checkY, targetZ, DEFAULT_COLLISION_HEIGHT, MAP_ALL_LIQUIDS);
if (liq.Entry == MAP_LIQUID_TYPE_NO_WATER || groundZ > liq.DepthLevel + HEIGHT_ABOVE_WATER_TOLERANCE)
{
if (checkLOS)
{
bool hasLOS = map->isInLineOfSight(checkX, checkY, groundZ, targetX, targetY, targetZ, phaseMask, LINEOFSIGHT_ALL_CHECKS, VMAP::ModelIgnoreFlags::Nothing);
if (!hasLOS)
continue;
}
// Add a distance check for the position to prevent the bot from moving out of range to the master.
if (master && botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT) && master->GetDistance(checkX, checkY, groundZ) > fishingSearchWindow - SEARCH_LAND_BUFFER)
continue;
return WorldPosition(bot->GetMapId(), checkX, checkY, groundZ);
}
}
return WorldPosition();
}
WorldPosition FindLandRadialFromPosition (PlayerbotAI* botAI, WorldPosition targetPos, float startDistance, float endDistance, float increment, float fishingSearchWindow, int angles = 16)
{
Player* bot = botAI->GetBot();
const int numDirections = angles;
std::vector<WorldPosition> boundaryPoints;
Player* master = botAI->GetMaster();
if (!master)
return WorldPosition();
Map* map = bot->GetMap();
uint32 phaseMask = bot->GetPhaseMask();
float targetX = targetPos.GetPositionX();
float targetY = targetPos.GetPositionY();
float targetZ = targetPos.GetPositionZ();
for (float dist = startDistance; dist <= endDistance; dist += increment)
{
for (int i = 0; i < numDirections; ++i)
{
float angle = (2.0f * M_PI * i) / numDirections;
float checkX = targetX - cos(angle) * dist;
float checkY = targetY - sin(angle) * dist;
float groundZ = HasFishableWaterOrLand(checkX, checkY, targetZ, map, phaseMask, true);
if (groundZ == INVALID_HEIGHT)
continue;
if (map->isInLineOfSight(checkX, checkY, groundZ, targetX, targetY, targetZ, phaseMask, LINEOFSIGHT_ALL_CHECKS, VMAP::ModelIgnoreFlags::Nothing) && master->GetDistance(checkX, checkY, groundZ) > fishingSearchWindow - SEARCH_LAND_BUFFER)
continue;
boundaryPoints.emplace_back(WorldPosition(bot->GetMapId(), checkX, checkY, groundZ));
}
if (!boundaryPoints.empty())
break;
}
if (boundaryPoints.empty())
return WorldPosition();
if (boundaryPoints.size() == 1)
return boundaryPoints[0];
float minDistance = FLT_MAX;
WorldLocation closestPoint = WorldPosition();
for (auto const& pos : boundaryPoints)
{
float distance = bot->GetExactDist2d(&pos);
if (distance < minDistance)
{
minDistance = distance;
closestPoint = pos;
}
}
return closestPoint;
}
WorldPosition FindWaterRadial(Player* bot, float x, float y, float z, Map* map, uint32 phaseMask, float minDistance, float maxDistance, float increment, bool checkLOS, int numDirections)
{
std::vector<WorldPosition> boundaryPoints;
float dist = minDistance;
while (dist <= maxDistance)
{
for (int i = 0; i < numDirections; ++i)
{
float angle = (2.0f * M_PI * i) / numDirections;
float checkX = x + cos(angle) * dist;
float checkY = y + sin(angle) * dist;
float waterZ = HasFishableWaterOrLand(checkX, checkY, z, map, phaseMask);
if (waterZ == INVALID_HEIGHT)
continue;
if (checkLOS && !HasLosToWater(bot, checkX, checkY, waterZ))
continue;
boundaryPoints.emplace_back(WorldPosition(bot->GetMapId(), checkX, checkY, waterZ));
}
if (!boundaryPoints.empty())
break;
dist += increment;
}
if (boundaryPoints.empty())
return WorldPosition();
if (boundaryPoints.size() == 1)
return boundaryPoints[0];
// return the central point in the identified positions in to try to be perpendicular to the shore.
return boundaryPoints[boundaryPoints.size() / 2];
}
WorldPosition FindFishingHole(PlayerbotAI* botAI)
{
Player* player = botAI->GetBot();
GuidVector gos = PAI_VALUE(GuidVector, "nearest game objects no los");
GameObject* nearestFishingHole = nullptr;
float minDist = std::numeric_limits<float>::max();
for (auto const& guid : gos)
{
GameObject* go = botAI->GetGameObject(guid);
if (!go)
continue;
if (go->GetGoType() == GAMEOBJECT_TYPE_FISHINGHOLE)
{
float dist = player->GetDistance2d(go);
if (dist < minDist)
{
minDist = dist;
nearestFishingHole = go;
}
}
}
if (nearestFishingHole)
return WorldPosition(nearestFishingHole->GetMapId(), nearestFishingHole->GetPositionX(), nearestFishingHole->GetPositionY(), nearestFishingHole->GetPositionZ());
return WorldPosition();
}
bool MoveNearWaterAction::Execute(Event event)
{
WorldPosition landSpot = AI_VALUE(WorldPosition, "fishing spot");
if (landSpot.IsValid())
return MoveTo(landSpot.GetMapId(), landSpot.GetPositionX(), landSpot.GetPositionY(), landSpot.GetPositionZ());
return false;
}
bool MoveNearWaterAction::isUseful()
{
if (!AI_VALUE(bool, "can fish"))
return false;
FishingSpotValue* fishingSpotValueObject = (FishingSpotValue*)context->GetValue<WorldPosition>("fishing spot");
WorldPosition pos = fishingSpotValueObject->Get();
return !pos.IsValid() || fishingSpotValueObject->IsStale(FISHING_LOCATION_TIMEOUT) || pos != bot->GetPosition();
}
bool MoveNearWaterAction::isPossible()
{
Player* master = botAI->GetMaster();
float fishingSearchWindow;
if (master)
fishingSearchWindow = sPlayerbotAIConfig->fishingDistanceFromMaster;
else
fishingSearchWindow = sPlayerbotAIConfig->fishingDistance;
WorldPosition fishingHole = FindFishingHole(botAI);
if (fishingHole.IsValid())
{
float distance = bot->GetExactDist2d(&fishingHole);
bool hasLOS = bot->IsWithinLOS(fishingHole.GetPositionX(), fishingHole.GetPositionY(), fishingHole.GetPositionZ());
// Water spot is in range, and we have LOS to it. Set bot position to fishing spot and do not move
if (distance >= MIN_DISTANCE_TO_WATER &&
distance <= MAX_DISTANCE_TO_WATER && hasLOS)
{
SET_AI_VALUE(WorldPosition, "fishing spot", WorldPosition(WorldPosition(bot->GetMapId(), bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ())));
return false;
}
// Water spot is out of range, lets look for a spot to move to for the fishing hole.
if (distance > MAX_DISTANCE_TO_WATER || distance < MIN_DISTANCE_TO_WATER)
{
float angle = bot->GetAngle(fishingHole.GetPositionX(), fishingHole.GetPositionY());
WorldPosition landSpot = FindLandRadialFromPosition(botAI, fishingHole, MIN_DISTANCE_TO_WATER, MAX_DISTANCE_TO_WATER, SEARCH_INCREMENT, fishingSearchWindow, 32);
if (landSpot.IsValid())
{
SET_AI_VALUE(WorldPosition, "fishing spot", landSpot);
return true;
}
}
}
// Lets find some water where we can fish.
WorldPosition water = FindWaterRadial(
bot, bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(),
bot->GetMap(), bot->GetPhaseMask(),
MIN_DISTANCE_TO_WATER,
fishingSearchWindow + MAX_DISTANCE_TO_WATER,
SEARCH_INCREMENT, false);
if (!water.IsValid())
return false;
bool hasLOS = bot->IsWithinLOS(water.GetPositionX(), water.GetPositionY(), water.GetPositionZ());
float angle = bot->GetAngle(water.GetPositionX(), water.GetPositionY());
WorldPosition landSpot =
FindLandFromPosition(botAI, 0.0f, MAX_DISTANCE_TO_WATER, 1.0f, angle, water, fishingSearchWindow, false);
if (landSpot.IsValid())
{
SET_AI_VALUE(WorldPosition, "fishing spot", landSpot);
return true;
}
return false;
}
bool EquipFishingPoleAction::Execute(Event event)
{
if (!_pole)
return false;
WorldPacket eqPacket(CMSG_AUTOEQUIP_ITEM_SLOT, 2);
eqPacket << _pole->GetGUID() << uint8(EQUIPMENT_SLOT_MAINHAND);
WorldPackets::Item::AutoEquipItemSlot nicePacket(std::move(eqPacket));
nicePacket.Read();
bot->GetSession()->HandleAutoEquipItemSlotOpcode(nicePacket);
return true;
}
bool EquipFishingPoleAction::isUseful()
{
Item* mainHand = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND);
if (IsFishingPole(mainHand))
return false;
for (uint8 slot = INVENTORY_SLOT_ITEM_START; slot < INVENTORY_SLOT_ITEM_END; ++slot)
{
if (Item* item = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot))
{
if (IsFishingPole(item))
{
_pole = item;
return true;
}
}
}
for (uint8 bag = INVENTORY_SLOT_BAG_START; bag < INVENTORY_SLOT_BAG_END; ++bag)
{
if (Bag* pBag = bot->GetBagByPos(bag))
{
for (uint32 j = 0; j < pBag->GetBagSize(); ++j)
{
if (Item* item = pBag->GetItemByPos(j))
{
if (IsFishingPole(item))
{
_pole = item;
return true;
}
}
}
}
}
if (sRandomPlayerbotMgr->IsRandomBot(bot))
{
bot->StoreNewItemInBestSlots(FISHING_POLE, 1); // Try to get a fishing pole
return true;
}
Player* master = botAI->GetMaster();
if (!master)
return false;
std::string masterName = master->GetName();
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
"no_fishing_pole_error", "I don't have a Fishing Pole",{});
botAI->Whisper(text, masterName);
return false;
}
bool FishingAction::Execute(Event event)
{
WorldPosition target = WorldPosition();
WorldPosition fishingHole = FindFishingHole(botAI);
if (fishingHole.IsValid())
{
Position pos = fishingHole;
float distance = bot->GetExactDist2d(&pos);
bool hasLOS = bot->IsWithinLOS(fishingHole.GetPositionX(), fishingHole.GetPositionY(), fishingHole.GetPositionZ());
if (distance < MAX_DISTANCE_TO_WATER &&
distance > MIN_DISTANCE_TO_WATER && hasLOS)
target = fishingHole;
}
if (!target.IsValid())
{
target = FindWaterRadial(bot, bot->GetPositionX(), bot->GetPositionY(),
bot->GetPositionZ(), bot->GetMap(), bot->GetPhaseMask(),
MIN_DISTANCE_TO_WATER, MAX_DISTANCE_TO_WATER, SEARCH_INCREMENT, true, 32);
if (!target.IsValid())
return false;
}
Position pos = target;
if (!bot->HasInArc(1.0, &pos, 1.0))
{
float angle = bot->GetAngle(pos.GetPositionX(), pos.GetPositionY());
bot->SetOrientation(angle);
if (!bot->IsRooted())
bot->SendMovementFlagUpdate();
}
EquipFishingPoleAction equipAction(botAI);
if (equipAction.isUseful())
return equipAction.Execute(event);
botAI->CastSpell(FISHING_SPELL, bot);
botAI->ChangeStrategy("+use bobber", BOT_STATE_NON_COMBAT);
return true;
}
bool FishingAction::isUseful()
{
if (!AI_VALUE(bool, "can fish"))
return false;
FishingSpotValue* fishingSpotValueObject = (FishingSpotValue*)context->GetValue<WorldPosition>("fishing spot");
WorldPosition pos = fishingSpotValueObject->Get();
return pos.IsValid() && !fishingSpotValueObject->IsStale(FISHING_LOCATION_TIMEOUT) && pos == bot->GetPosition();
}
bool UseBobberAction::isUseful()
{
return AI_VALUE(bool, "can use fishing bobber");
}
bool UseBobberAction::Execute(Event event)
{
GuidVector gos = AI_VALUE(GuidVector, "nearest game objects no los");
for (auto const& guid : gos)
{
if (GameObject* go = botAI->GetGameObject(guid))
{
if (go->GetEntry() != FISHING_BOBBER)
continue;
if (go->GetOwnerGUID() != bot->GetGUID())
continue;
if (go->getLootState() == GO_READY)
{
go->Use(bot);
botAI->ChangeStrategy("-use bobber", BOT_STATE_NON_COMBAT);
return true;
}
}
}
return false;
}
bool EndMasterFishingAction::Execute(Event event)
{
botAI->ChangeStrategy("-master fishing", BOT_STATE_NON_COMBAT);
return true;
}
bool EndMasterFishingAction::isUseful()
{
FishingSpotValue* fishingSpotValueObject = (FishingSpotValue*)context->GetValue<WorldPosition>("fishing spot");
WorldPosition pos = fishingSpotValueObject->Get();
if (pos.IsValid() && !fishingSpotValueObject->IsStale(FISHING_LOCATION_TIMEOUT) && pos == bot->GetPosition())
return false;
WorldPosition nearWater = FindWaterRadial(bot, bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(),
bot->GetMap(), bot->GetPhaseMask(), MIN_DISTANCE_TO_WATER, sPlayerbotAIConfig->endFishingWithMaster, 10.0f);
return !nearWater.IsValid();
}
bool RemoveBobberStrategyAction::Execute(Event event)
{
botAI->ChangeStrategy("-use bobber", BOT_STATE_NON_COMBAT);
return true;
}

View File

@@ -0,0 +1,71 @@
/*
* 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_FISHINGACTION_H
#define _PLAYERBOT_FISHINGACTION_H
#include "Action.h"
#include "MovementActions.h"
#include "Event.h"
#include "Playerbots.h"
extern const uint32 FISHING_SPELL;
extern const uint32 FISHING_POLE;
extern const uint32 FISHING_BOBBER;
WorldPosition FindWaterRadial(Player* bot, float x, float y, float z, Map* map, uint32 phaseMask, float minDistance, float maxDistance, float increment, bool checkLOS=false, int numDirections = 16);
class PlayerbotAI;
class FishingAction : public Action
{
public:
FishingAction(PlayerbotAI* botAI) : Action(botAI, "go fishing"){}
bool Execute(Event event) override;
bool isUseful() override;
};
class EquipFishingPoleAction : public Action
{
public:
EquipFishingPoleAction(PlayerbotAI* botAI) : Action(botAI, "equip fishing pole") {}
bool Execute(Event event) override;
bool isUseful() override;
private:
Item* _pole = nullptr;
};
class MoveNearWaterAction : public MovementAction
{
public:
MoveNearWaterAction(PlayerbotAI* botAI): MovementAction(botAI, "move near water") {}
bool Execute(Event event) override;
bool isUseful() override;
bool isPossible() override;
};
class UseBobberAction : public Action
{
public:
UseBobberAction(PlayerbotAI* botAI) : Action(botAI, "use fishing bobber") {}
bool Execute(Event event) override;
bool isUseful() override;
};
class EndMasterFishingAction : public Action
{
public:
EndMasterFishingAction(PlayerbotAI* botAI) : Action(botAI, "end master fishing") {}
bool Execute(Event event) override;
bool isUseful() override;
};
class RemoveBobberStrategyAction : public Action
{
public:
RemoveBobberStrategyAction(PlayerbotAI* botAI) : Action(botAI, "remove bobber strategy") {}
bool Execute(Event event) override;
};
#endif

View File

@@ -97,6 +97,8 @@ bool FollowAction::isUseful()
distance = bot->GetDistance(loc.GetPositionX(), loc.GetPositionY(), loc.GetPositionZ());
}
if (botAI->HasStrategy("master fishing", BOT_STATE_NON_COMBAT))
return sServerFacade->IsDistanceGreaterThan(distance, sPlayerbotAIConfig->fishingDistanceFromMaster);
return sServerFacade->IsDistanceGreaterThan(distance, formation->GetMaxDistance());
}

View File

@@ -265,11 +265,6 @@ CastShootAction::CastShootAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "s
}
}
NextAction** CastSpellAction::getPrerequisites()
{
return nullptr;
}
Value<Unit*>* CastDebuffSpellOnAttackerAction::GetTargetValue()
{
return context->GetValue<Unit*>("attacker without aura", spell);

View File

@@ -27,7 +27,11 @@ public:
bool isUseful() override;
ActionThreatType getThreatType() override { return ActionThreatType::Single; }
NextAction** getPrerequisites() override;
std::vector<NextAction> getPrerequisites() override
{
return {};
}
std::string const getSpell() { return spell; }
protected:
@@ -193,10 +197,12 @@ public:
ResurrectPartyMemberAction(PlayerbotAI* botAI, std::string const spell) : CastSpellAction(botAI, spell) {}
std::string const GetTargetName() override { return "party member to resurrect"; }
NextAction** getPrerequisites() override
std::vector<NextAction> getPrerequisites() override
{
return NextAction::merge(NextAction::array(0, new NextAction("reach party member to resurrect"), NULL),
Action::getPrerequisites());
return NextAction::merge(
{ NextAction("reach party member to resurrect") },
Action::getPrerequisites()
);
}
};

View File

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

View File

@@ -68,17 +68,15 @@ bool RpgAction::SetNextRpgAction()
triggerNode->setTrigger(trigger);
NextAction** nextActions = triggerNode->getHandlers();
std::vector<NextAction> nextActions = triggerNode->getHandlers();
Trigger* trigger = triggerNode->getTrigger();
bool isChecked = false;
for (int32 i = 0; i < NextAction::size(nextActions); i++)
for (NextAction nextAction : nextActions)
{
NextAction* nextAction = nextActions[i];
if (nextAction->getRelevance() > 5.0f)
if (nextAction.getRelevance() > 5.0f)
continue;
if (!isChecked && !trigger->IsActive())
@@ -86,14 +84,13 @@ bool RpgAction::SetNextRpgAction()
isChecked = true;
Action* action = botAI->GetAiObjectContext()->GetAction(nextAction->getName());
Action* action = botAI->GetAiObjectContext()->GetAction(nextAction.getName());
if (!dynamic_cast<RpgEnabled*>(action) || !action->isPossible() || !action->isUseful())
continue;
actions.push_back(action);
relevances.push_back((nextAction->getRelevance() - 1) * 500);
relevances.push_back((nextAction.getRelevance() - 1) * 500);
}
NextAction::destroy(nextActions);
}
}

View File

@@ -14,6 +14,8 @@
#include "PositionValue.h"
#include "ByteBuffer.h"
std::set<uint32> const FISHING_SPELLS = {7620, 7731, 7732, 18248, 33095, 51294};
Creature* SeeSpellAction::CreateWps(Player* wpOwner, float x, float y, float z, float o, uint32 entry, Creature* lastWp,
bool important)
{
@@ -57,6 +59,16 @@ bool SeeSpellAction::Execute(Event event)
// if (!botAI->HasStrategy("RTSC", botAI->GetState()))
// return false;
if (FISHING_SPELLS.find(spellId) != FISHING_SPELLS.end())
{
if (AI_VALUE(bool, "can fish") && sPlayerbotAIConfig->enableFishingWithMaster)
{
botAI->ChangeStrategy("+master fishing", BOT_STATE_NON_COMBAT);
return true;
}
return false;
}
if (spellId != RTSC_MOVE_SPELL)
return false;

View File

@@ -10,7 +10,7 @@
#include "PlayerbotFactory.h"
#include "Playerbots.h"
void TrainerAction::Learn(uint32 cost, TrainerSpell const* tSpell, std::ostringstream& msg)
void TrainerAction::Learn(uint32 cost, const Trainer::Spell tSpell, std::ostringstream& msg)
{
if (sPlayerbotAIConfig->autoTrainSpells != "free" && !botAI->HasCheat(BotCheatMask::gold))
{
@@ -23,7 +23,7 @@ void TrainerAction::Learn(uint32 cost, TrainerSpell const* tSpell, std::ostrings
bot->ModifyMoney(-int32(cost));
}
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(tSpell->spell);
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(tSpell.SpellId);
if (!spellInfo)
return;
@@ -41,10 +41,8 @@ void TrainerAction::Learn(uint32 cost, TrainerSpell const* tSpell, std::ostrings
}
}
if (!learned && !bot->HasSpell(tSpell->spell))
{
bot->learnSpell(tSpell->spell);
}
if (!learned && !bot->HasSpell(tSpell.SpellId))
bot->learnSpell(tSpell.SpellId);
msg << " - learned";
}
@@ -53,37 +51,35 @@ void TrainerAction::Iterate(Creature* creature, TrainerSpellAction action, Spell
{
TellHeader(creature);
TrainerSpellData const* trainer_spells = creature->GetTrainerSpells();
Trainer::Trainer* trainer = sObjectMgr->GetTrainer(creature->GetEntry());
if (!trainer)
return;
float fDiscountMod = bot->GetReputationPriceDiscount(creature);
uint32 totalCost = 0;
for (TrainerSpellMap::const_iterator itr = trainer_spells->spellList.begin();
itr != trainer_spells->spellList.end(); ++itr)
for (auto& spell : trainer->GetSpells())
{
TrainerSpell const* tSpell = &itr->second;
if (!tSpell)
if (!trainer->CanTeachSpell(bot, trainer->GetSpell(spell.SpellId)))
continue;
TrainerSpellState state = bot->GetTrainerSpellState(tSpell);
if (state != TRAINER_SPELL_GREEN)
if (!spells.empty() && spells.find(spell.SpellId) == spells.end())
continue;
uint32 spellId = tSpell->spell;
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spell.SpellId);
if (!spellInfo)
continue;
if (!spells.empty() && spells.find(tSpell->spell) == spells.end())
continue;
uint32 cost = uint32(floor(tSpell->spellCost * fDiscountMod));
uint32 cost = uint32(floor(spell.MoneyCost * fDiscountMod));
totalCost += cost;
std::ostringstream out;
out << chat->FormatSpell(spellInfo) << chat->formatMoney(cost);
if (action)
(this->*action)(cost, tSpell, out);
(this->*action)(cost, spell, out);
botAI->TellMaster(out);
}
@@ -112,15 +108,14 @@ bool TrainerAction::Execute(Event event)
if (!creature || !creature->IsTrainer())
return false;
if (!creature->IsValidTrainerForPlayer(bot))
{
botAI->TellError("This trainer cannot teach me");
return false;
}
Trainer::Trainer* trainer = sObjectMgr->GetTrainer(creature->GetEntry());
// check present spell in trainer spell list
TrainerSpellData const* cSpells = creature->GetTrainerSpells();
if (!cSpells)
if (!trainer || !trainer->IsTrainerValidForPlayer(bot))
return false;
std::vector<Trainer::Spell> trainer_spells = trainer->GetSpells();
if (trainer_spells.empty())
{
botAI->TellError("No spells can be learned from this trainer");
return false;
@@ -133,7 +128,7 @@ bool TrainerAction::Execute(Event event)
if (text.find("learn") != std::string::npos || sRandomPlayerbotMgr->IsRandomBot(bot) ||
(sPlayerbotAIConfig->autoTrainSpells != "no" &&
(creature->GetCreatureTemplate()->trainer_type != TRAINER_TYPE_TRADESKILLS ||
(trainer->GetTrainerType() != Trainer::Type::Tradeskill ||
!botAI->HasActivePlayerMaster()))) // Todo rewrite to only exclude start primary profession skills and make
// config dependent.
Iterate(creature, &TrainerAction::Learn, spells);

View File

@@ -8,6 +8,7 @@
#include "Action.h"
#include "ChatHelper.h"
#include "Trainer.h"
class Creature;
class PlayerbotAI;
@@ -22,9 +23,9 @@ public:
bool Execute(Event event) override;
private:
typedef void (TrainerAction::*TrainerSpellAction)(uint32, TrainerSpell const*, std::ostringstream& msg);
typedef void (TrainerAction::*TrainerSpellAction)(uint32, const Trainer::Spell, std::ostringstream& msg);
void Iterate(Creature* creature, TrainerSpellAction action, SpellIds& spells);
void Learn(uint32 cost, TrainerSpell const* tSpell, std::ostringstream& msg);
void Learn(uint32 cost, const Trainer::Spell tSpell, std::ostringstream& msg);
void TellHeader(Creature* creature);
void TellFooter(uint32 totalCost);
};

View File

@@ -52,7 +52,7 @@ bool UseMeetingStoneAction::Execute(Event event)
if (!goInfo || goInfo->entry != 179944)
return false;
return Teleport(master, bot);
return Teleport(master, bot, false);
}
bool SummonAction::Execute(Event event)
@@ -70,16 +70,16 @@ bool SummonAction::Execute(Event event)
{
// botAI->GetAiObjectContext()->GetValue<GuidVector>("prioritized targets")->Set({});
AI_VALUE(std::list<FleeInfo>&, "recently flee info").clear();
return Teleport(master, bot);
return Teleport(master, bot, true);
}
if (SummonUsingGos(master, bot) || SummonUsingNpcs(master, bot))
if (SummonUsingGos(master, bot, true) || SummonUsingNpcs(master, bot, true))
{
botAI->TellMasterNoFacing("Hello!");
return true;
}
if (SummonUsingGos(bot, master) || SummonUsingNpcs(bot, master))
if (SummonUsingGos(bot, master, true) || SummonUsingNpcs(bot, master, true))
{
botAI->TellMasterNoFacing("Welcome!");
return true;
@@ -88,7 +88,7 @@ bool SummonAction::Execute(Event event)
return false;
}
bool SummonAction::SummonUsingGos(Player* summoner, Player* player)
bool SummonAction::SummonUsingGos(Player* summoner, Player* player, bool preserveAuras)
{
std::list<GameObject*> targets;
AnyGameObjectInObjectRangeCheck u_check(summoner, sPlayerbotAIConfig->sightDistance);
@@ -98,14 +98,14 @@ bool SummonAction::SummonUsingGos(Player* summoner, Player* player)
for (GameObject* go : targets)
{
if (go->isSpawned() && go->GetGoType() == GAMEOBJECT_TYPE_MEETINGSTONE)
return Teleport(summoner, player);
return Teleport(summoner, player, preserveAuras);
}
botAI->TellError(summoner == bot ? "There is no meeting stone nearby" : "There is no meeting stone near you");
return false;
}
bool SummonAction::SummonUsingNpcs(Player* summoner, Player* player)
bool SummonAction::SummonUsingNpcs(Player* summoner, Player* player, bool preserveAuras)
{
if (!sPlayerbotAIConfig->summonAtInnkeepersEnabled)
return false;
@@ -139,7 +139,7 @@ bool SummonAction::SummonUsingNpcs(Player* summoner, Player* player)
Spell spell(player, spellInfo, TRIGGERED_NONE);
spell.SendSpellCooldown();
return Teleport(summoner, player);
return Teleport(summoner, player, preserveAuras);
}
}
@@ -147,7 +147,7 @@ bool SummonAction::SummonUsingNpcs(Player* summoner, Player* player)
return false;
}
bool SummonAction::Teleport(Player* summoner, Player* player)
bool SummonAction::Teleport(Player* summoner, Player* player, bool preserveAuras)
{
// Player* master = GetMaster();
if (!summoner)
@@ -208,7 +208,11 @@ bool SummonAction::Teleport(Player* summoner, Player* player)
player->GetMotionMaster()->Clear();
AI_VALUE(LastMovement&, "last movement").clear();
player->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TELEPORTED | AURA_INTERRUPT_FLAG_CHANGE_MAP);
if (!preserveAuras)
player->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TELEPORTED |
AURA_INTERRUPT_FLAG_CHANGE_MAP);
player->TeleportTo(mapId, x, y, z, 0);
if (botAI->HasStrategy("stay", botAI->GetState()))

View File

@@ -19,9 +19,9 @@ public:
bool Execute(Event event) override;
protected:
bool Teleport(Player* summoner, Player* player);
bool SummonUsingGos(Player* summoner, Player* player);
bool SummonUsingNpcs(Player* summoner, Player* player);
bool Teleport(Player* summoner, Player* player, bool preserveAuras);
bool SummonUsingGos(Player* summoner, Player* player, bool preserveAuras);
bool SummonUsingNpcs(Player* summoner, Player* player, bool preserveAuras);
};
class UseMeetingStoneAction : public SummonAction

View File

@@ -12,25 +12,10 @@ class BloodDKStrategyActionNodeFactory : public NamedObjectFactory<ActionNode>
public:
BloodDKStrategyActionNodeFactory()
{
// creators["melee"] = &melee;
// creators["blood strike"] = &blood_strike;
creators["rune strike"] = &rune_strike;
creators["heart strike"] = &heart_strike;
creators["death strike"] = &death_strike;
// creators["death grip"] = &death_grip;
// creators["plague strike"] = &plague_strike;
// creators["pestilence"] = &pestilence;
creators["icy touch"] = &icy_touch;
// creators["obliterate"] = &obliterate;
// creators["blood boil"] = &blood_boil;
// creators["mark of_blood"] = &mark_of_blood;
// creators["blood presence"] = &blood_presence;
// creators["rune tap"] = &rune_tap;
// creators["vampiric blood"] = &vampiric_blood;
// creators["death pact"] = &death_pact;
// creators["death rune_mastery"] = &death_rune_mastery;
// creators["hysteria"] = &hysteria;
// creators["dancing weapon"] = &dancing_weapon;
creators["dark command"] = &dark_command;
creators["taunt spell"] = &dark_command;
}
@@ -38,39 +23,61 @@ public:
private:
static ActionNode* rune_strike([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("rune strike",
/*P*/ NextAction::array(0, new NextAction("frost presence"), nullptr),
/*A*/ nullptr,
/*C*/ nullptr);
return new ActionNode(
"rune strike",
{
NextAction("frost presence")
},
/*A*/ {},
/*C*/ {}
);
}
static ActionNode* icy_touch([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("icy touch",
/*P*/ NextAction::array(0, new NextAction("frost presence"), nullptr),
/*A*/ nullptr,
/*C*/ nullptr);
return new ActionNode(
"icy touch",
{
NextAction("frost presence")
},
/*A*/ {},
/*C*/ {}
);
}
static ActionNode* heart_strike([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("heart strike",
/*P*/ NextAction::array(0, new NextAction("frost presence"), nullptr),
/*A*/ nullptr,
/*C*/ nullptr);
return new ActionNode(
"heart strike",
{
NextAction("frost presence")
},
/*A*/ {},
/*C*/ {}
);
}
static ActionNode* death_strike([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("death strike",
/*P*/ NextAction::array(0, new NextAction("frost presence"), nullptr),
/*A*/ nullptr,
/*C*/ nullptr);
return new ActionNode(
"death strike",
{
NextAction("frost presence")
},
/*A*/ {},
/*C*/ {}
);
}
static ActionNode* dark_command([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("dark command",
/*P*/ NextAction::array(0, new NextAction("frost presence"), NULL),
/*A*/ NextAction::array(0, new NextAction("death grip"), NULL),
/*C*/ NULL);
return new ActionNode(
"dark command",
{
NextAction("frost presence")
},
/*A*/ {
NextAction("death grip")
},
/*C*/ {}
);
}
};
@@ -79,33 +86,80 @@ BloodDKStrategy::BloodDKStrategy(PlayerbotAI* botAI) : GenericDKStrategy(botAI)
actionNodeFactories.Add(new BloodDKStrategyActionNodeFactory());
}
NextAction** BloodDKStrategy::getDefaultActions()
std::vector<NextAction> BloodDKStrategy::getDefaultActions()
{
return NextAction::array(
0, new NextAction("rune strike", ACTION_DEFAULT + 0.8f), new NextAction("icy touch", ACTION_DEFAULT + 0.7f),
new NextAction("heart strike", ACTION_DEFAULT + 0.6f), new NextAction("blood strike", ACTION_DEFAULT + 0.5f),
new NextAction("dancing rune weapon", ACTION_DEFAULT + 0.4f),
new NextAction("death coil", ACTION_DEFAULT + 0.3f), new NextAction("plague strike", ACTION_DEFAULT + 0.2f),
new NextAction("horn of winter", ACTION_DEFAULT + 0.1f), new NextAction("melee", ACTION_DEFAULT), NULL);
return {
NextAction("rune strike", ACTION_DEFAULT + 0.8f),
NextAction("icy touch", ACTION_DEFAULT + 0.7f),
NextAction("heart strike", ACTION_DEFAULT + 0.6f),
NextAction("blood strike", ACTION_DEFAULT + 0.5f),
NextAction("dancing rune weapon", ACTION_DEFAULT + 0.4f),
NextAction("death coil", ACTION_DEFAULT + 0.3f),
NextAction("plague strike", ACTION_DEFAULT + 0.2f),
NextAction("horn of winter", ACTION_DEFAULT + 0.1f),
NextAction("melee", ACTION_DEFAULT)
};
}
void BloodDKStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
GenericDKStrategy::InitTriggers(triggers);
triggers.push_back(new TriggerNode(
"rune strike", NextAction::array(0, new NextAction("rune strike", ACTION_NORMAL + 3), nullptr)));
triggers.push_back(
new TriggerNode("blood tap", NextAction::array(0, new NextAction("blood tap", ACTION_HIGH + 5), nullptr)));
new TriggerNode(
"rune strike",
{
NextAction("rune strike", ACTION_NORMAL + 3)
}
)
);
triggers.push_back(
new TriggerNode("lose aggro", NextAction::array(0, new NextAction("dark command", ACTION_HIGH + 3), nullptr)));
new TriggerNode(
"blood tap",
{
NextAction("blood tap", ACTION_HIGH + 5)
}
)
);
triggers.push_back(
new TriggerNode("low health", NextAction::array(0, new NextAction("army of the dead", ACTION_HIGH + 4),
new NextAction("death strike", ACTION_HIGH + 3), nullptr)));
new TriggerNode(
"lose aggro",
{
NextAction("dark command", ACTION_HIGH + 3)
}
)
);
triggers.push_back(
new TriggerNode("critical health", NextAction::array(0, new NextAction("vampiric blood", ACTION_HIGH + 5), nullptr)));
new TriggerNode(
"low health",
{
NextAction("army of the dead", ACTION_HIGH + 4),
NextAction("death strike", ACTION_HIGH + 3)
}
)
);
triggers.push_back(
new TriggerNode("icy touch", NextAction::array(0, new NextAction("icy touch", ACTION_HIGH + 2), nullptr)));
triggers.push_back(new TriggerNode(
"plague strike", NextAction::array(0, new NextAction("plague strike", ACTION_HIGH + 2), nullptr)));
new TriggerNode(
"critical health",
{
NextAction("vampiric blood", ACTION_HIGH + 5)
}
)
);
triggers.push_back(
new TriggerNode(
"icy touch",
{
NextAction("icy touch", ACTION_HIGH + 2)
}
)
);
triggers.push_back(
new TriggerNode(
"plague strike",
{
NextAction("plague strike", ACTION_HIGH + 2)
}
)
);
}

View File

@@ -17,7 +17,7 @@ public:
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
std::string const getName() override { return "blood"; }
NextAction** getDefaultActions() override;
std::vector<NextAction> getDefaultActions() override;
uint32 GetType() const override { return STRATEGY_TYPE_TANK | STRATEGY_TYPE_MELEE; }
};

View File

@@ -11,39 +11,40 @@
#include "SpellInfo.h"
#include "SpellMgr.h"
NextAction** CastDeathchillAction::getPrerequisites()
std::vector<NextAction> CastDeathchillAction::getPrerequisites()
{
return NextAction::merge(NextAction::array(0, new NextAction("frost presence"), nullptr),
return NextAction::merge({ NextAction("frost presence") },
CastSpellAction::getPrerequisites());
}
NextAction** CastUnholyMeleeSpellAction::getPrerequisites()
std::vector<NextAction> CastUnholyMeleeSpellAction::getPrerequisites()
{
return NextAction::merge(NextAction::array(0, new NextAction("unholy presence"), nullptr),
return NextAction::merge({ NextAction("unholy presence") },
CastMeleeSpellAction::getPrerequisites());
}
NextAction** CastFrostMeleeSpellAction::getPrerequisites()
std::vector<NextAction> CastFrostMeleeSpellAction::getPrerequisites()
{
return NextAction::merge(NextAction::array(0, new NextAction("frost presence"), nullptr),
return NextAction::merge({ NextAction("frost presence") },
CastMeleeSpellAction::getPrerequisites());
}
NextAction** CastBloodMeleeSpellAction::getPrerequisites()
std::vector<NextAction> CastBloodMeleeSpellAction::getPrerequisites()
{
return NextAction::merge(NextAction::array(0, new NextAction("blood presence"), nullptr),
return NextAction::merge({ NextAction("blood presence") },
CastMeleeSpellAction::getPrerequisites());
}
bool CastRaiseDeadAction::Execute(Event event)
{
bool result = CastBuffSpellAction::Execute(event);
const bool result = CastBuffSpellAction::Execute(event);
if (!result)
{
return false;
}
uint32 spellId = AI_VALUE2(uint32, "spell id", spell);
// SpellInfo const *spellInfo = sSpellMgr->GetSpellInfo(spellId);
const uint32_t spellId = AI_VALUE2(uint32_t, "spell id", spell);
bot->AddSpellCooldown(spellId, 0, 3 * 60 * 1000);
return true;
}

View File

@@ -34,7 +34,7 @@ class CastDeathchillAction : public CastBuffSpellAction
public:
CastDeathchillAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "deathchill") {}
NextAction** getPrerequisites() override;
std::vector<NextAction> getPrerequisites() override;
};
class CastDarkCommandAction : public CastSpellAction
@@ -52,7 +52,7 @@ class CastUnholyMeleeSpellAction : public CastMeleeSpellAction
public:
CastUnholyMeleeSpellAction(PlayerbotAI* botAI, std::string const spell) : CastMeleeSpellAction(botAI, spell) {}
NextAction** getPrerequisites() override;
std::vector<NextAction> getPrerequisites() override;
};
// Frost presence
@@ -61,7 +61,7 @@ class CastFrostMeleeSpellAction : public CastMeleeSpellAction
public:
CastFrostMeleeSpellAction(PlayerbotAI* botAI, std::string const spell) : CastMeleeSpellAction(botAI, spell) {}
NextAction** getPrerequisites() override;
std::vector<NextAction> getPrerequisites() override;
};
// Blood presence
@@ -70,7 +70,7 @@ class CastBloodMeleeSpellAction : public CastMeleeSpellAction
public:
CastBloodMeleeSpellAction(PlayerbotAI* botAI, std::string const spell) : CastMeleeSpellAction(botAI, spell) {}
NextAction** getPrerequisites() override;
std::vector<NextAction> getPrerequisites() override;
};
class CastRuneStrikeAction : public CastMeleeSpellAction
@@ -79,10 +79,6 @@ public:
CastRuneStrikeAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "rune strike") {}
};
// debuff
// BEGIN_DEBUFF_ACTION(CastPestilenceAction, "pestilence")
// END_SPELL_ACTION()
class CastPestilenceAction : public CastSpellAction
{
public:
@@ -90,20 +86,12 @@ public:
ActionThreatType getThreatType() override { return ActionThreatType::None; }
};
// debuff
// BEGIN_DEBUFF_ACTION(CastHowlingBlastAction, "howling blast")
// END_SPELL_ACTION()
class CastHowlingBlastAction : public CastSpellAction
{
public:
CastHowlingBlastAction(PlayerbotAI* ai) : CastSpellAction(ai, "howling blast") {}
};
// debuff it
// BEGIN_DEBUFF_ACTION(CastIcyTouchAction, "icy touch")
// END_SPELL_ACTION()
class CastIcyTouchAction : public CastSpellAction
{
public:
@@ -126,8 +114,6 @@ class CastPlagueStrikeAction : public CastSpellAction
public:
CastPlagueStrikeAction(PlayerbotAI* ai) : CastSpellAction(ai, "plague strike") {}
};
// BEGIN_DEBUFF_ACTION(CastPlagueStrikeAction, "plague strike")
// END_SPELL_ACTION()
class CastPlagueStrikeOnAttackerAction : public CastDebuffSpellOnMeleeAttackerAction
{

View File

@@ -16,66 +16,68 @@ public:
creators["obliterate"] = &obliterate;
creators["howling blast"] = &howling_blast;
creators["frost strike"] = &frost_strike;
// creators["chains of ice"] = &chains_of_ice;
creators["rune strike"] = &rune_strike;
// creators["icy clutch"] = &icy_clutch;
// creators["horn of winter"] = &horn_of_winter;
// creators["killing machine"] = &killing_machine;
// creators["frost presence"] = &frost_presence;
// creators["deathchill"] = &deathchill;
// creators["icebound fortitude"] = &icebound_fortitude;
// creators["mind freeze"] = &mind_freeze;
// creators["hungering cold"] = &hungering_cold;
creators["unbreakable armor"] = &unbreakable_armor;
// creators["improved icy talons"] = &improved_icy_talons;
}
private:
static ActionNode* icy_touch([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("icy touch",
/*P*/ NextAction::array(0, new NextAction("blood presence"), nullptr),
/*A*/ nullptr,
/*C*/ nullptr);
return new ActionNode(
"icy touch",
/*P*/ { NextAction("blood presence") },
/*A*/ {},
/*C*/ {}
);
}
static ActionNode* obliterate([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("obliterate",
/*P*/ NextAction::array(0, new NextAction("blood presence"), nullptr),
/*A*/ nullptr,
/*C*/ nullptr);
return new ActionNode(
"obliterate",
/*P*/ { NextAction("blood presence") },
/*A*/ {},
/*C*/ {}
);
}
static ActionNode* rune_strike([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("rune strike",
/*P*/ NextAction::array(0, new NextAction("blood presence"), nullptr),
/*A*/ NextAction::array(0, new NextAction("melee"), nullptr),
/*C*/ nullptr);
return new ActionNode(
"rune strike",
/*P*/ { NextAction("blood presence") },
/*A*/ { NextAction("melee") },
/*C*/ {}
);
}
static ActionNode* frost_strike([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("frost strike",
/*P*/ NextAction::array(0, new NextAction("blood presence"), nullptr),
/*A*/ nullptr,
/*C*/ nullptr);
return new ActionNode(
"frost strike",
/*P*/ { NextAction("blood presence") },
/*A*/ {},
/*C*/ {}
);
}
static ActionNode* howling_blast([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("howling blast",
/*P*/ NextAction::array(0, new NextAction("blood presence"), nullptr),
/*A*/ nullptr,
/*C*/ nullptr);
return new ActionNode(
"howling blast",
/*P*/ { NextAction("blood presence") },
/*A*/ {},
/*C*/ {}
);
}
static ActionNode* unbreakable_armor([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("unbreakable armor",
/*P*/ NextAction::array(0, new NextAction("blood tap"), nullptr),
/*A*/ nullptr,
/*C*/ nullptr);
return new ActionNode(
"unbreakable armor",
/*P*/ { NextAction("blood tap") },
/*A*/ {},
/*C*/ {}
);
}
};
@@ -84,41 +86,84 @@ FrostDKStrategy::FrostDKStrategy(PlayerbotAI* botAI) : GenericDKStrategy(botAI)
actionNodeFactories.Add(new FrostDKStrategyActionNodeFactory());
}
NextAction** FrostDKStrategy::getDefaultActions()
std::vector<NextAction> FrostDKStrategy::getDefaultActions()
{
return NextAction::array(
0, new NextAction("obliterate", ACTION_DEFAULT + 0.7f),
new NextAction("frost strike", ACTION_DEFAULT + 0.4f),
new NextAction("empower rune weapon", ACTION_DEFAULT + 0.3f),
new NextAction("horn of winter", ACTION_DEFAULT + 0.1f), new NextAction("melee", ACTION_DEFAULT), NULL);
return {
NextAction("obliterate", ACTION_DEFAULT + 0.7f),
NextAction("frost strike", ACTION_DEFAULT + 0.4f),
NextAction("empower rune weapon", ACTION_DEFAULT + 0.3f),
NextAction("horn of winter", ACTION_DEFAULT + 0.1f),
NextAction("melee", ACTION_DEFAULT)
};
}
void FrostDKStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
GenericDKStrategy::InitTriggers(triggers);
triggers.push_back(new TriggerNode(
"unbreakable armor", NextAction::array(0, new NextAction("unbreakable armor", ACTION_DEFAULT + 0.6f), nullptr)));
triggers.push_back(new TriggerNode(
"freezing fog", NextAction::array(0, new NextAction("howling blast", ACTION_DEFAULT + 0.5f), nullptr)));
triggers.push_back(new TriggerNode(
"high blood rune", NextAction::array(0, new NextAction("blood strike", ACTION_DEFAULT + 0.2f), nullptr)));
triggers.push_back(new TriggerNode(
"army of the dead", NextAction::array(0, new NextAction("army of the dead", ACTION_HIGH + 6), nullptr)));
triggers.push_back(
new TriggerNode(
"unbreakable armor",
{
NextAction("unbreakable armor", ACTION_DEFAULT + 0.6f)
}
)
);
triggers.push_back(
new TriggerNode("icy touch", NextAction::array(0, new NextAction("icy touch", ACTION_HIGH + 2), nullptr)));
triggers.push_back(new TriggerNode(
"plague strike", NextAction::array(0, new NextAction("plague strike", ACTION_HIGH + 2), nullptr)));
// triggers.push_back(new TriggerNode("empower rune weapon", NextAction::array(0, new NextAction("empower rune
// weapon", ACTION_NORMAL + 4), nullptr)));
new TriggerNode(
"freezing fog",
{
NextAction("howling blast", ACTION_DEFAULT + 0.5f)
}
)
);
triggers.push_back(
new TriggerNode(
"high blood rune",
{
NextAction("blood strike", ACTION_DEFAULT + 0.2f)
}
)
);
triggers.push_back(
new TriggerNode(
"army of the dead",
{
NextAction("army of the dead", ACTION_HIGH + 6)
}
)
);
triggers.push_back(
new TriggerNode(
"icy touch",
{
NextAction("icy touch", ACTION_HIGH + 2)
}
)
);
triggers.push_back(
new TriggerNode(
"plague strike",
{
NextAction("plague strike", ACTION_HIGH + 2)
}
)
);
}
void FrostDKAoeStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(
new TriggerNode("medium aoe", NextAction::array(0, new NextAction("howling blast", ACTION_HIGH + 4), nullptr)));
new TriggerNode(
"medium aoe",
{
NextAction("howling blast", ACTION_HIGH + 4)
}
)
);
}

View File

@@ -17,7 +17,7 @@ public:
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
std::string const getName() override { return "frost"; }
NextAction** getDefaultActions() override;
std::vector<NextAction> getDefaultActions() override;
uint32 GetType() const override { return STRATEGY_TYPE_COMBAT | STRATEGY_TYPE_DPS | STRATEGY_TYPE_MELEE; }
};

View File

@@ -20,17 +20,17 @@ private:
static ActionNode* bone_shield([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("bone shield",
/*P*/ nullptr,
/*A*/ nullptr,
/*C*/ nullptr);
/*P*/ {},
/*A*/ {},
/*C*/ {});
}
static ActionNode* horn_of_winter([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("horn of winter",
/*P*/ nullptr,
/*A*/ nullptr,
/*C*/ nullptr);
/*P*/ {},
/*A*/ {},
/*C*/ {});
}
};
@@ -44,19 +44,18 @@ void GenericDKNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& trigger
NonCombatStrategy::InitTriggers(triggers);
triggers.push_back(
new TriggerNode("no pet", NextAction::array(0, new NextAction("raise dead", ACTION_NORMAL + 1), nullptr)));
new TriggerNode("no pet", { NextAction("raise dead", ACTION_NORMAL + 1) }));
triggers.push_back(
new TriggerNode("horn of winter", NextAction::array(0, new NextAction("horn of winter", 21.0f), nullptr)));
new TriggerNode("horn of winter", { NextAction("horn of winter", 21.0f) }));
triggers.push_back(
new TriggerNode("bone shield", NextAction::array(0, new NextAction("bone shield", 21.0f), nullptr)));
new TriggerNode("bone shield", { NextAction("bone shield", 21.0f) }));
triggers.push_back(
new TriggerNode("has pet", NextAction::array(0, new NextAction("toggle pet spell", 60.0f), NULL)));
new TriggerNode("has pet", { NextAction("toggle pet spell", 60.0f) }));
triggers.push_back(
new TriggerNode("new pet", NextAction::array(0, new NextAction("set pet stance", 60.0f), NULL)));
new TriggerNode("new pet", { NextAction("set pet stance", 60.0f) }));
}
void DKBuffDpsStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
// triggers.push_back(new TriggerNode("improved icy talons", NextAction::array(0, new NextAction("improved icy
// talons", 19.0f), nullptr)));
}

View File

@@ -54,105 +54,105 @@ private:
static ActionNode* death_coil([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("death coil",
/*P*/ nullptr,
/*A*/ nullptr,
/*C*/ nullptr);
/*P*/ {},
/*A*/ {},
/*C*/ {});
}
static ActionNode* death_grip([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("death grip",
/*P*/ nullptr,
/*A*/ NextAction::array(0, new NextAction("icy touch"), nullptr),
/*C*/ nullptr);
/*P*/ {},
/*A*/ { NextAction("icy touch") },
/*C*/ {});
}
static ActionNode* plague_strike([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("plague strike",
/*P*/ nullptr,
/*A*/ nullptr,
/*C*/ nullptr);
/*P*/ {},
/*A*/ {},
/*C*/ {});
}
static ActionNode* icy_touch([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("icy touch",
/*P*/ nullptr,
/*A*/ nullptr,
/*C*/ nullptr);
/*P*/ {},
/*A*/ {},
/*C*/ {});
}
static ActionNode* heart_strike([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("heart strike",
/*P*/ nullptr,
/*A*/ nullptr,
/*C*/ nullptr);
/*P*/ {},
/*A*/ {},
/*C*/ {});
}
static ActionNode* pestilence([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("pestilence",
/*P*/ nullptr,
/*A*/ nullptr,
/*C*/ nullptr);
/*P*/ {},
/*A*/ {},
/*C*/ {});
}
static ActionNode* horn_of_winter([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("horn of winter",
/*P*/ nullptr,
/*A*/ nullptr,
/*C*/ nullptr);
/*P*/ {},
/*A*/ {},
/*C*/ {});
}
static ActionNode* bone_shield([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("bone shield",
/*P*/ nullptr,
/*A*/ nullptr,
/*C*/ nullptr);
/*P*/ {},
/*A*/ {},
/*C*/ {});
}
static ActionNode* killing_machine([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("killing machine",
/*P*/ nullptr,
/*A*/ NextAction::array(0, new NextAction("improved icy talons"), nullptr),
/*C*/ nullptr);
/*P*/ {},
/*A*/ { NextAction("improved icy talons") },
/*C*/ {});
}
static ActionNode* corpse_explosion([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("corpse explosion",
/*P*/ nullptr,
/*A*/ nullptr,
/*C*/ nullptr);
/*P*/ {},
/*A*/ {},
/*C*/ {});
}
static ActionNode* death_and_decay([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("death and decay",
/*P*/ nullptr,
/*A*/ nullptr,
/*C*/ nullptr);
/*P*/ {},
/*A*/ {},
/*C*/ {});
}
static ActionNode* anti_magic_zone([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("anti magic zone",
/*P*/ nullptr,
/*A*/ NextAction::array(0, new NextAction("anti magic shell"), nullptr),
/*C*/ nullptr);
/*P*/ {},
/*A*/ { NextAction("anti magic shell") },
/*C*/ {});
}
static ActionNode* icebound_fortitude([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("icebound fortitude",
/*P*/ nullptr,
/*A*/ nullptr,
/*C*/ nullptr);
/*P*/ {},
/*A*/ {},
/*C*/ {});
}
};
@@ -165,36 +165,29 @@ void GenericDKStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
MeleeCombatStrategy::InitTriggers(triggers);
// triggers.push_back(new TriggerNode("high aoe", NextAction::array(0, new NextAction("anti magic shell",
// ACTION_NORMAL + 3), nullptr))); triggers.push_back(new TriggerNode("death coil", NextAction::array(0, new
// NextAction("death coil", ACTION_NORMAL + 3), nullptr))); triggers.push_back(new TriggerNode("critical aoe heal",
// NextAction::array(0, new NextAction("anti magic zone", ACTION_EMERGENCY + 1), nullptr)));
triggers.push_back(
new TriggerNode("no pet", NextAction::array(0, new NextAction("raise dead", ACTION_NORMAL + 5), nullptr)));
new TriggerNode("no pet", { NextAction("raise dead", ACTION_NORMAL + 5) }));
triggers.push_back(
new TriggerNode("has pet", NextAction::array(0, new NextAction("toggle pet spell", 60.0f), nullptr)));
new TriggerNode("has pet", { NextAction("toggle pet spell", 60.0f) }));
triggers.push_back(
new TriggerNode("new pet", NextAction::array(0, new NextAction("set pet stance", 60.0f), nullptr)));
new TriggerNode("new pet", { NextAction("set pet stance", 60.0f) }));
triggers.push_back(
new TriggerNode("mind freeze", NextAction::array(0, new NextAction("mind freeze", ACTION_HIGH + 1), nullptr)));
new TriggerNode("mind freeze", { NextAction("mind freeze", ACTION_HIGH + 1) }));
triggers.push_back(
new TriggerNode("mind freeze on enemy healer",
NextAction::array(0, new NextAction("mind freeze on enemy healer", ACTION_HIGH + 1), nullptr)));
{ NextAction("mind freeze on enemy healer", ACTION_HIGH + 1) }));
triggers.push_back(new TriggerNode(
"horn of winter", NextAction::array(0, new NextAction("horn of winter", ACTION_NORMAL + 1), nullptr)));
"horn of winter", { NextAction("horn of winter", ACTION_NORMAL + 1) }));
triggers.push_back(new TriggerNode("critical health",
NextAction::array(0, new NextAction("death pact", ACTION_HIGH + 5), nullptr)));
{ NextAction("death pact", ACTION_HIGH + 5) }));
triggers.push_back(
new TriggerNode("low health", NextAction::array(0, new NextAction("icebound fortitude", ACTION_HIGH + 5),
new NextAction("rune tap", ACTION_HIGH + 4), nullptr)));
new TriggerNode("low health", { NextAction("icebound fortitude", ACTION_HIGH + 5),
NextAction("rune tap", ACTION_HIGH + 4) }));
triggers.push_back(
new TriggerNode("medium aoe", NextAction::array(0, new NextAction("death and decay", ACTION_HIGH + 9),
new NextAction("pestilence", ACTION_NORMAL + 4),
new NextAction("blood boil", ACTION_NORMAL + 3), nullptr)));
// triggers.push_back(new TriggerNode("light aoe", NextAction::array(0,
// new NextAction("pestilence", ACTION_NORMAL + 4),
// nullptr)));
new TriggerNode("medium aoe", { NextAction("death and decay", ACTION_HIGH + 9),
NextAction("pestilence", ACTION_NORMAL + 4),
NextAction("blood boil", ACTION_NORMAL + 3) }));
triggers.push_back(
new TriggerNode("pestilence glyph", NextAction::array(0, new NextAction("pestilence", ACTION_HIGH + 9), NULL)));
new TriggerNode("pestilence glyph", { NextAction("pestilence", ACTION_HIGH + 9) }));
}

View File

@@ -1,4 +1,4 @@
#/*
/*
* 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.
*/
@@ -11,21 +11,8 @@ class UnholyDKStrategyActionNodeFactory : public NamedObjectFactory<ActionNode>
public:
UnholyDKStrategyActionNodeFactory()
{
// Unholy
// creators["bone shield"] = &bone_shield;
// creators["plague strike"] = &plague_strike;
// creators["death grip"] = &death_grip;
// creators["death coil"] = &death_coil;
creators["death strike"] = &death_strike;
// creators["unholy blight"] = &unholy_blight;
creators["scourge strike"] = &scourge_strike;
// creators["death and decay"] = &death_and_decay;
// creators["unholy pressence"] = &unholy_pressence;
// creators["raise dead"] = &raise_dead;
// creators["army of the dead"] = &army of the dead;
// creators["summon gargoyle"] = &army of the dead;
// creators["anti magic shell"] = &anti_magic_shell;
// creators["anti magic zone"] = &anti_magic_zone;
creators["ghoul frenzy"] = &ghoul_frenzy;
creators["corpse explosion"] = &corpse_explosion;
creators["icy touch"] = &icy_touch;
@@ -34,39 +21,49 @@ public:
private:
static ActionNode* death_strike([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("death strike",
/*P*/ NextAction::array(0, new NextAction("blood presence"), nullptr),
/*A*/ nullptr,
/*C*/ nullptr);
return new ActionNode(
"death strike",
/*P*/ { NextAction("blood presence") },
/*A*/ {},
/*C*/ {}
);
}
static ActionNode* ghoul_frenzy([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("ghoul frenzy",
/*P*/ NextAction::array(0, new NextAction("blood presence"), nullptr),
/*A*/ nullptr,
/*C*/ nullptr);
return new ActionNode(
"ghoul frenzy",
/*P*/ { NextAction("blood presence") },
/*A*/ {},
/*C*/ {}
);
}
static ActionNode* corpse_explosion([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("corpse explosion",
/*P*/ NextAction::array(0, new NextAction("blood presence"), nullptr),
/*A*/ nullptr,
/*C*/ nullptr);
return new ActionNode(
"corpse explosion",
/*P*/ { NextAction("blood presence") },
/*A*/ {},
/*C*/ {}
);
}
static ActionNode* scourge_strike([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("scourge strike",
/*P*/ NextAction::array(0, new NextAction("blood presence"), nullptr),
/*A*/ nullptr,
/*C*/ nullptr);
return new ActionNode(
"scourge strike",
/*P*/ { NextAction("blood presence") },
/*A*/ {},
/*C*/ {}
);
}
static ActionNode* icy_touch([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("icy touch",
/*P*/ NextAction::array(0, new NextAction("blood presence"), nullptr),
/*A*/ nullptr,
/*C*/ nullptr);
return new ActionNode(
"icy touch",
/*P*/ { NextAction("blood presence") },
/*A*/ {},
/*C*/ {}
);
}
};
@@ -75,69 +72,121 @@ UnholyDKStrategy::UnholyDKStrategy(PlayerbotAI* botAI) : GenericDKStrategy(botAI
actionNodeFactories.Add(new UnholyDKStrategyActionNodeFactory());
}
NextAction** UnholyDKStrategy::getDefaultActions()
std::vector<NextAction> UnholyDKStrategy::getDefaultActions()
{
return NextAction::array(
0, new NextAction("death and decay", ACTION_HIGH + 5),
new NextAction("summon gargoyle", ACTION_DEFAULT + 0.4f),
// new NextAction("empower rune weapon", ACTION_DEFAULT + 0.3f),
new NextAction("horn of winter", ACTION_DEFAULT + 0.2f),
new NextAction("death coil", ACTION_DEFAULT + 0.1f),
new NextAction("melee", ACTION_DEFAULT), nullptr);
return {
NextAction("death and decay", ACTION_HIGH + 5),
NextAction("summon gargoyle", ACTION_DEFAULT + 0.4f),
NextAction("horn of winter", ACTION_DEFAULT + 0.2f),
NextAction("death coil", ACTION_DEFAULT + 0.1f),
NextAction("melee", ACTION_DEFAULT)
};
}
void UnholyDKStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
GenericDKStrategy::InitTriggers(triggers);
triggers.push_back(new TriggerNode(
"death and decay cooldown", NextAction::array(0,
new NextAction("ghoul frenzy", ACTION_DEFAULT + 0.9f),
new NextAction("scourge strike", ACTION_DEFAULT + 0.8f),
new NextAction("icy touch", ACTION_DEFAULT + 0.7f),
new NextAction("blood strike", ACTION_DEFAULT + 0.6f),
new NextAction("plague strike", ACTION_DEFAULT + 0.5f),
nullptr)));
triggers.push_back(new TriggerNode("dd cd and no desolation",
NextAction::array(0, new NextAction("blood strike", ACTION_DEFAULT + 0.75f), nullptr)));
// triggers.push_back(
// new TriggerNode("icy touch", NextAction::array(0, new NextAction("icy touch", ACTION_HIGH + 2), nullptr)));
// triggers.push_back(new TriggerNode(
// "plague strike", NextAction::array(0, new NextAction("plague strike", ACTION_HIGH + 1), nullptr)));
triggers.push_back(new TriggerNode(
"high frost rune", NextAction::array(0,
new NextAction("icy touch", ACTION_NORMAL + 3), nullptr)));
triggers.push_back(new TriggerNode(
"high blood rune", NextAction::array(0, new NextAction("blood strike", ACTION_NORMAL + 2), nullptr)));
triggers.push_back(new TriggerNode(
"high unholy rune", NextAction::array(0,
new NextAction("plague strike", ACTION_NORMAL + 1), nullptr)));
triggers.push_back(
new TriggerNode("dd cd and plague strike 3s", NextAction::array(0, new NextAction("plague strike", ACTION_HIGH + 1), nullptr)));
new TriggerNode(
"death and decay cooldown",
{
NextAction("ghoul frenzy", ACTION_DEFAULT + 0.9f),
NextAction("scourge strike", ACTION_DEFAULT + 0.8f),
NextAction("icy touch", ACTION_DEFAULT + 0.7f),
NextAction("blood strike", ACTION_DEFAULT + 0.6f),
NextAction("plague strike", ACTION_DEFAULT + 0.5f),
}
)
);
triggers.push_back(
new TriggerNode("dd cd and icy touch 3s", NextAction::array(0, new NextAction("icy touch", ACTION_HIGH + 2), nullptr)));
new TriggerNode(
"dd cd and no desolation",
{
NextAction("blood strike", ACTION_DEFAULT + 0.75f)
}
)
);
triggers.push_back(
new TriggerNode("no rune", NextAction::array(0, new NextAction("empower rune weapon", ACTION_HIGH + 1), nullptr)));
// triggers.push_back(new TriggerNode("often", NextAction::array(0, new NextAction(, ACTION_NORMAL + 2), nullptr)));
triggers.push_back(new TriggerNode(
"army of the dead", NextAction::array(0, new NextAction("army of the dead", ACTION_HIGH + 6), nullptr)));
new TriggerNode(
"high frost rune",
{
NextAction("icy touch", ACTION_NORMAL + 3)
}
)
);
triggers.push_back(
new TriggerNode("bone shield", NextAction::array(0, new NextAction("bone shield", ACTION_HIGH + 3), nullptr)));
new TriggerNode(
"high blood rune",
{
NextAction("blood strike", ACTION_NORMAL + 2)
}
)
);
triggers.push_back(
new TriggerNode(
"high unholy rune",
{
NextAction("plague strike", ACTION_NORMAL + 1)
}
)
);
triggers.push_back(
new TriggerNode("dd cd and plague strike 3s",
{
NextAction("plague strike", ACTION_HIGH + 1)
}
)
);
triggers.push_back(
new TriggerNode("dd cd and icy touch 3s",
{
NextAction("icy touch", ACTION_HIGH + 2)
}
)
);
triggers.push_back(
new TriggerNode("no rune",
{
NextAction("empower rune weapon", ACTION_HIGH + 1)
}
)
);
triggers.push_back(
new TriggerNode(
"army of the dead",
{
NextAction("army of the dead", ACTION_HIGH + 6)
}
)
);
triggers.push_back(
new TriggerNode("bone shield",
{
NextAction("bone shield", ACTION_HIGH + 3)
}
)
);
}
void UnholyDKAoeStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(new TriggerNode(
"loot available", NextAction::array(0, new NextAction("corpse explosion", ACTION_NORMAL + 1), nullptr)));
triggers.push_back(new TriggerNode(
"medium aoe", NextAction::array(0, new NextAction("death and decay", ACTION_NORMAL + 3),
new NextAction("corpse explosion", ACTION_NORMAL + 3), nullptr)));
triggers.push_back(
new TriggerNode(
"loot available",
{
NextAction("corpse explosion", ACTION_NORMAL + 1)
}
)
);
triggers.push_back(
new TriggerNode(
"medium aoe",
{
NextAction("death and decay", ACTION_NORMAL + 3),
NextAction("corpse explosion", ACTION_NORMAL + 3)
}
)
);
}

View File

@@ -17,7 +17,7 @@ public:
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
std::string const getName() override { return "unholy"; }
NextAction** getDefaultActions() override;
std::vector<NextAction> getDefaultActions() override;
uint32 GetType() const override { return STRATEGY_TYPE_COMBAT | STRATEGY_TYPE_DPS | STRATEGY_TYPE_MELEE; }
};

View File

@@ -30,107 +30,132 @@ public:
private:
static ActionNode* melee([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("melee",
/*P*/ NextAction::array(0, new NextAction("feral charge - bear"), nullptr),
/*A*/ nullptr,
/*C*/ nullptr);
return new ActionNode(
"melee",
/*P*/ { NextAction("feral charge - bear") },
/*A*/ {},
/*C*/ {}
);
}
static ActionNode* feral_charge_bear([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("feral charge - bear",
/*P*/ nullptr,
/*A*/ NextAction::array(0, new NextAction("reach melee"), nullptr),
/*C*/ nullptr);
return new ActionNode(
"feral charge - bear",
/*P*/ {},
/*A*/ { NextAction("reach melee") },
/*C*/ {}
);
}
static ActionNode* swipe_bear([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("swipe (bear)",
/*P*/ nullptr,
/*A*/ nullptr,
/*C*/ nullptr);
return new ActionNode(
"swipe (bear)",
/*P*/ {},
/*A*/ {},
/*C*/ {}
);
}
static ActionNode* faerie_fire_feral([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("faerie fire (feral)",
/*P*/ NextAction::array(0, new NextAction("feral charge - bear"), nullptr),
/*A*/ nullptr,
/*C*/ nullptr);
return new ActionNode(
"faerie fire (feral)",
/*P*/ { NextAction("feral charge - bear") },
/*A*/ {},
/*C*/ {}
);
}
static ActionNode* bear_form([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("bear form",
/*P*/ nullptr,
/*A*/ nullptr,
/*C*/ nullptr);
return new ActionNode(
"bear form",
/*P*/ {},
/*A*/ {},
/*C*/ {}
);
}
static ActionNode* dire_bear_form([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("dire bear form",
/*P*/ NextAction::array(0, new NextAction("caster form"), nullptr),
/*A*/ NextAction::array(0, new NextAction("bear form"), nullptr),
/*C*/ nullptr);
return new ActionNode(
"dire bear form",
/*P*/ { NextAction("caster form") },
/*A*/ { NextAction("bear form") },
/*C*/ {}
);
}
static ActionNode* mangle_bear([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("mangle (bear)",
/*P*/ nullptr,
// /*A*/ NextAction::array(0, new NextAction("lacerate"), nullptr),
nullptr,
/*C*/ nullptr);
return new ActionNode(
"mangle (bear)",
/*P*/ {},
/*A*/ {},
/*C*/ {}
);
}
static ActionNode* maul([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("maul",
/*P*/ nullptr,
/*A*/ NextAction::array(0, new NextAction("melee"), nullptr),
/*C*/ nullptr);
return new ActionNode(
"maul",
/*P*/ {},
/*A*/ { NextAction("melee") },
/*C*/ {}
);
}
static ActionNode* bash([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("bash",
/*P*/ nullptr,
/*A*/ NextAction::array(0, new NextAction("melee"), nullptr),
/*C*/ nullptr);
return new ActionNode(
"bash",
/*P*/ {},
/*A*/ { NextAction("melee") },
/*C*/ {}
);
}
static ActionNode* swipe([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("swipe",
/*P*/ nullptr,
/*A*/ NextAction::array(0, new NextAction("melee"), nullptr),
/*C*/ nullptr);
return new ActionNode(
"swipe",
/*P*/ {},
/*A*/ { NextAction("melee") },
/*C*/ {}
);
}
static ActionNode* lacerate([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("lacerate",
/*P*/ nullptr,
/*A*/ NextAction::array(0, new NextAction("maul"), nullptr),
/*C*/ nullptr);
return new ActionNode(
"lacerate",
/*P*/ {},
/*A*/ { NextAction("maul") },
/*C*/ {}
);
}
static ActionNode* growl([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("growl",
/*P*/ nullptr,
/*A*/ nullptr,
/*C*/ nullptr);
return new ActionNode(
"growl",
/*P*/ {},
/*A*/ {},
/*C*/ {}
);
}
static ActionNode* demoralizing_roar([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("demoralizing roar",
/*P*/ nullptr,
/*A*/ nullptr,
/*C*/ nullptr);
return new ActionNode(
"demoralizing roar",
/*P*/ {},
/*A*/ {},
/*C*/ {}
);
}
};
@@ -139,38 +164,93 @@ BearTankDruidStrategy::BearTankDruidStrategy(PlayerbotAI* botAI) : FeralDruidStr
actionNodeFactories.Add(new BearTankDruidStrategyActionNodeFactory());
}
NextAction** BearTankDruidStrategy::getDefaultActions()
std::vector<NextAction> BearTankDruidStrategy::getDefaultActions()
{
return NextAction::array(
0, new NextAction("mangle (bear)", ACTION_DEFAULT + 0.5f),
new NextAction("faerie fire (feral)", ACTION_DEFAULT + 0.4f), new NextAction("lacerate", ACTION_DEFAULT + 0.3f),
new NextAction("maul", ACTION_DEFAULT + 0.2f), new NextAction("enrage", ACTION_DEFAULT + 0.1f),
new NextAction("melee", ACTION_DEFAULT), nullptr);
return {
NextAction("mangle (bear)", ACTION_DEFAULT + 0.5f),
NextAction("faerie fire (feral)", ACTION_DEFAULT + 0.4f),
NextAction("lacerate", ACTION_DEFAULT + 0.3f),
NextAction("maul", ACTION_DEFAULT + 0.2f),
NextAction("enrage", ACTION_DEFAULT + 0.1f),
NextAction("melee", ACTION_DEFAULT)
};
}
void BearTankDruidStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
FeralDruidStrategy::InitTriggers(triggers);
triggers.push_back(new TriggerNode(
"enemy out of melee", NextAction::array(0, new NextAction("feral charge - bear", ACTION_NORMAL + 8), nullptr)));
// triggers.push_back(new TriggerNode("thorns", NextAction::array(0, new NextAction("thorns", ACTION_HIGH + 9),
// nullptr)));
triggers.push_back(
new TriggerNode("bear form", NextAction::array(0, new NextAction("dire bear form", ACTION_HIGH + 8), nullptr)));
triggers.push_back(new TriggerNode(
"low health", NextAction::array(0, new NextAction("frenzied regeneration", ACTION_HIGH + 7), nullptr)));
triggers.push_back(new TriggerNode(
"faerie fire (feral)", NextAction::array(0, new NextAction("faerie fire (feral)", ACTION_HIGH + 7), nullptr)));
new TriggerNode(
"enemy out of melee",
{
NextAction("feral charge - bear", ACTION_NORMAL + 8)
}
)
);
triggers.push_back(
new TriggerNode("lose aggro", NextAction::array(0, new NextAction("growl", ACTION_HIGH + 8), nullptr)));
new TriggerNode(
"bear form",
{
NextAction("dire bear form", ACTION_HIGH + 8)
}
)
);
triggers.push_back(
new TriggerNode("medium aoe", NextAction::array(0, new NextAction("demoralizing roar", ACTION_HIGH + 6),
new NextAction("swipe (bear)", ACTION_HIGH + 6), nullptr)));
new TriggerNode(
"low health",
{
NextAction("frenzied regeneration", ACTION_HIGH + 7)
}
)
);
triggers.push_back(
new TriggerNode("light aoe", NextAction::array(0, new NextAction("swipe (bear)", ACTION_HIGH + 5), nullptr)));
new TriggerNode(
"faerie fire (feral)",
{
NextAction("faerie fire (feral)", ACTION_HIGH + 7)
}
)
);
triggers.push_back(
new TriggerNode("bash", NextAction::array(0, new NextAction("bash", ACTION_INTERRUPT + 2), nullptr)));
new TriggerNode(
"lose aggro",
{
NextAction("growl", ACTION_HIGH + 8)
}
)
);
triggers.push_back(
new TriggerNode("bash on enemy healer",
NextAction::array(0, new NextAction("bash on enemy healer", ACTION_INTERRUPT + 1), nullptr)));
new TriggerNode(
"medium aoe",
{
NextAction("demoralizing roar", ACTION_HIGH + 6),
NextAction("swipe (bear)", ACTION_HIGH + 6)
}
)
);
triggers.push_back(
new TriggerNode(
"light aoe",
{
NextAction("swipe (bear)", ACTION_HIGH + 5)
}
)
);
triggers.push_back(
new TriggerNode(
"bash",
{
NextAction("bash", ACTION_INTERRUPT + 2)
}
)
);
triggers.push_back(
new TriggerNode(
"bash on enemy healer",
{
NextAction("bash on enemy healer", ACTION_INTERRUPT + 1)
}
)
);
}

View File

@@ -17,7 +17,7 @@ public:
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
std::string const getName() override { return "bear"; }
NextAction** getDefaultActions() override;
std::vector<NextAction> getDefaultActions() override;
uint32 GetType() const override { return STRATEGY_TYPE_TANK | STRATEGY_TYPE_MELEE; }
};

View File

@@ -28,82 +28,102 @@ public:
private:
static ActionNode* faerie_fire([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("faerie fire",
/*P*/ NextAction::array(0, new NextAction("moonkin form"), nullptr),
/*A*/ nullptr,
/*C*/ nullptr);
return new ActionNode(
"faerie fire",
/*P*/ { NextAction("moonkin form") },
/*A*/ {},
/*C*/ {}
);
}
static ActionNode* hibernate([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("hibernate",
/*P*/ NextAction::array(0, new NextAction("moonkin form"), nullptr),
/*A*/ NextAction::array(0, new NextAction("entangling roots"), nullptr),
/*C*/ nullptr);
return new ActionNode(
"hibernate",
/*P*/ { NextAction("moonkin form") },
/*A*/ { NextAction("entangling roots") },
/*C*/ {}
);
}
static ActionNode* entangling_roots([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("entangling roots",
/*P*/ NextAction::array(0, new NextAction("moonkin form"), nullptr),
/*A*/ nullptr,
/*C*/ nullptr);
return new ActionNode(
"entangling roots",
/*P*/ { NextAction("moonkin form") },
/*A*/ {},
/*C*/ {}
);
}
static ActionNode* entangling_roots_on_cc([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("entangling roots on cc",
/*P*/ NextAction::array(0, new NextAction("moonkin form"), nullptr),
/*A*/ nullptr,
/*C*/ nullptr);
return new ActionNode(
"entangling roots on cc",
/*P*/ { NextAction("moonkin form") },
/*A*/ {},
/*C*/ {}
);
}
static ActionNode* wrath([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("wrath",
/*P*/ NextAction::array(0, new NextAction("moonkin form"), nullptr),
/*A*/ nullptr,
/*C*/ nullptr);
return new ActionNode(
"wrath",
/*P*/ { NextAction("moonkin form") },
/*A*/ {},
/*C*/ {}
);
}
static ActionNode* starfall([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("starfall",
/*P*/ NextAction::array(0, new NextAction("moonkin form"), nullptr),
/*A*/ nullptr,
/*C*/ nullptr);
return new ActionNode(
"starfall",
/*P*/ { NextAction("moonkin form") },
/*A*/ {},
/*C*/ {}
);
}
static ActionNode* insect_swarm([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("insect swarm",
/*P*/ NextAction::array(0, new NextAction("moonkin form"), nullptr),
/*A*/ nullptr,
/*C*/ nullptr);
return new ActionNode(
"insect swarm",
/*P*/ { NextAction("moonkin form") },
/*A*/ {},
/*C*/ {}
);
}
static ActionNode* moonfire([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("moonfire",
/*P*/ NextAction::array(0, new NextAction("moonkin form"), nullptr),
/*A*/ nullptr,
/*C*/ nullptr);
return new ActionNode(
"moonfire",
/*P*/ { NextAction("moonkin form") },
/*A*/ {},
/*C*/ {}
);
}
static ActionNode* starfire([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("starfire",
/*P*/ NextAction::array(0, new NextAction("moonkin form"), nullptr),
/*A*/ nullptr,
/*C*/ nullptr);
return new ActionNode(
"starfire",
/*P*/ { NextAction("moonkin form") },
/*A*/ {},
/*C*/ {}
);
}
static ActionNode* moonkin_form([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("moonkin form",
/*P*/ NextAction::array(0, new NextAction("caster form"), nullptr),
/*A*/ nullptr,
/*C*/ nullptr);
return new ActionNode(
"moonkin form",
/*P*/ { NextAction("caster form") },
/*A*/ {},
/*C*/ {}
);
}
};
@@ -113,55 +133,122 @@ CasterDruidStrategy::CasterDruidStrategy(PlayerbotAI* botAI) : GenericDruidStrat
actionNodeFactories.Add(new ShapeshiftDruidStrategyActionNodeFactory());
}
NextAction** CasterDruidStrategy::getDefaultActions()
std::vector<NextAction> CasterDruidStrategy::getDefaultActions()
{
return NextAction::array(0,
new NextAction("starfall", ACTION_HIGH + 1.0f),
new NextAction("force of nature", ACTION_DEFAULT + 1.0f),
new NextAction("wrath", ACTION_DEFAULT + 0.1f),
// new NextAction("starfire", ACTION_NORMAL),
nullptr);
return {
NextAction("starfall", ACTION_HIGH + 1.0f),
NextAction("force of nature", ACTION_DEFAULT + 1.0f),
NextAction("wrath", ACTION_DEFAULT + 0.1f),
};
}
void CasterDruidStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
GenericDruidStrategy::InitTriggers(triggers);
// triggers.push_back(new TriggerNode("enemy out of spell", NextAction::array(0, new NextAction("reach spell",
// ACTION_MOVE), nullptr)));
triggers.push_back(new TriggerNode("eclipse (lunar) cooldown",
NextAction::array(0, new NextAction("starfire", ACTION_DEFAULT + 0.2f), nullptr)));
triggers.push_back(new TriggerNode("eclipse (solar) cooldown",
NextAction::array(0, new NextAction("wrath", ACTION_DEFAULT + 0.2f), nullptr)));
triggers.push_back(new TriggerNode(
"insect swarm", NextAction::array(0, new NextAction("insect swarm", ACTION_NORMAL + 5), nullptr)));
triggers.push_back(
new TriggerNode("moonfire", NextAction::array(0, new NextAction("moonfire", ACTION_NORMAL + 4), nullptr)));
new TriggerNode(
"eclipse (lunar) cooldown",
{
NextAction("starfire", ACTION_DEFAULT + 0.2f)
}
)
);
triggers.push_back(
new TriggerNode("eclipse (solar)", NextAction::array(0, new NextAction("wrath", ACTION_NORMAL + 6), nullptr)));
triggers.push_back(new TriggerNode("eclipse (lunar)",
NextAction::array(0, new NextAction("starfire", ACTION_NORMAL + 6), nullptr)));
new TriggerNode(
"eclipse (solar) cooldown",
{
NextAction("wrath", ACTION_DEFAULT + 0.2f)
}
)
);
triggers.push_back(
new TriggerNode("medium mana", NextAction::array(0, new NextAction("innervate", ACTION_HIGH + 9), nullptr)));
triggers.push_back(new TriggerNode("enemy too close for spell",
NextAction::array(0, new NextAction("flee", ACTION_MOVE + 9), nullptr)));
new TriggerNode(
"insect swarm",
{
NextAction("insect swarm", ACTION_NORMAL + 5)
}
)
);
triggers.push_back(
new TriggerNode(
"moonfire",
{
NextAction("moonfire", ACTION_NORMAL + 4)
}
)
);
triggers.push_back(
new TriggerNode(
"eclipse (solar)",
{
NextAction("wrath", ACTION_NORMAL + 6)
}
)
);
triggers.push_back(
new TriggerNode(
"eclipse (lunar)",
{
NextAction("starfire", ACTION_NORMAL + 6)
}
)
);
triggers.push_back(
new TriggerNode(
"medium mana",
{
NextAction("innervate", ACTION_HIGH + 9)
}
)
);
triggers.push_back(
new TriggerNode(
"enemy too close for spell",
{
NextAction("flee", ACTION_MOVE + 9)
}
)
);
}
void CasterDruidAoeStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(
new TriggerNode("hurricane channel check", NextAction::array(0, new NextAction("cancel channel", ACTION_HIGH + 2), nullptr)));
new TriggerNode(
"hurricane channel check",
{
NextAction("cancel channel", ACTION_HIGH + 2)
}
)
);
triggers.push_back(
new TriggerNode("medium aoe", NextAction::array(0, new NextAction("hurricane", ACTION_HIGH + 1), nullptr)));
triggers.push_back(new TriggerNode(
"light aoe", NextAction::array(0, new NextAction("insect swarm on attacker", ACTION_NORMAL + 3),
new NextAction("moonfire on attacker", ACTION_NORMAL + 3), NULL)));
new TriggerNode(
"medium aoe",
{
NextAction("hurricane", ACTION_HIGH + 1)
}
)
);
triggers.push_back(
new TriggerNode(
"light aoe",
{
NextAction("insect swarm on attacker", ACTION_NORMAL + 3),
NextAction("moonfire on attacker", ACTION_NORMAL + 3)
}
)
);
}
void CasterDruidDebuffStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(
new TriggerNode("faerie fire", NextAction::array(0, new NextAction("faerie fire", ACTION_HIGH), nullptr)));
new TriggerNode(
"faerie fire",
{
NextAction("faerie fire", ACTION_HIGH)
}
)
);
}

View File

@@ -18,7 +18,7 @@ public:
public:
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
std::string const getName() override { return "caster"; }
NextAction** getDefaultActions() override;
std::vector<NextAction> getDefaultActions() override;
uint32 GetType() const override { return STRATEGY_TYPE_COMBAT | STRATEGY_TYPE_DPS | STRATEGY_TYPE_RANGED; }
};

View File

@@ -28,90 +28,112 @@ public:
private:
static ActionNode* faerie_fire_feral([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("faerie fire (feral)",
/*P*/ nullptr,
/*A*/ nullptr,
/*C*/ nullptr);
return new ActionNode(
"faerie fire (feral)",
/*P*/ {},
/*A*/ {},
/*C*/ {}
);
}
static ActionNode* melee([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("melee",
/*P*/ NextAction::array(0, new NextAction("feral charge - cat"), nullptr),
/*A*/ nullptr,
/*C*/ nullptr);
return new ActionNode(
"melee",
/*P*/ { NextAction("feral charge - cat") },
/*A*/ {},
/*C*/ {}
);
}
static ActionNode* feral_charge_cat([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("feral charge - cat",
/*P*/ nullptr,
/*A*/ NextAction::array(0, new NextAction("reach melee"), nullptr),
/*C*/ nullptr);
return new ActionNode(
"feral charge - cat",
/*P*/ {},
/*A*/ { NextAction("reach melee") },
/*C*/ {}
);
}
static ActionNode* cat_form([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("cat form",
/*P*/ NextAction::array(0, new NextAction("caster form"), nullptr),
/*A*/ nullptr,
/*C*/ nullptr);
return new ActionNode(
"cat form",
/*P*/ { NextAction("caster form") },
/*A*/ {},
/*C*/ {}
);
}
static ActionNode* claw([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("claw",
/*P*/ nullptr,
/*A*/ NextAction::array(0, new NextAction("melee"), nullptr),
/*C*/ nullptr);
return new ActionNode(
"claw",
/*P*/ {},
/*A*/ { NextAction("melee") },
/*C*/ {}
);
}
static ActionNode* mangle_cat([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("mangle (cat)",
/*P*/ nullptr,
/*A*/ nullptr,
/*C*/ nullptr);
return new ActionNode(
"mangle (cat)",
/*P*/ {},
/*A*/ {},
/*C*/ {}
);
}
static ActionNode* rake([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("rake",
/*P*/ nullptr,
/*A*/ nullptr,
/*C*/ nullptr);
return new ActionNode(
"rake",
/*P*/ {},
/*A*/ {},
/*C*/ {}
);
}
static ActionNode* ferocious_bite([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("ferocious bite",
/*P*/ nullptr,
/*A*/ NextAction::array(0, new NextAction("rip"), nullptr),
/*C*/ nullptr);
return new ActionNode(
"ferocious bite",
/*P*/ {},
/*A*/ { NextAction("rip") },
/*C*/ {}
);
}
static ActionNode* rip([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("rip",
/*P*/ nullptr,
/*A*/ nullptr,
/*C*/ nullptr);
return new ActionNode(
"rip",
/*P*/ {},
/*A*/ {},
/*C*/ {}
);
}
static ActionNode* pounce([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("pounce",
/*P*/ nullptr,
/*A*/ NextAction::array(0, new NextAction("ravage"), nullptr),
/*C*/ nullptr);
return new ActionNode(
"pounce",
/*P*/ {},
/*A*/ { NextAction("ravage") },
/*C*/ {}
);
}
static ActionNode* ravage([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("ravage",
/*P*/ nullptr,
/*A*/ NextAction::array(0, new NextAction("shred"), nullptr),
/*C*/ nullptr);
return new ActionNode(
"ravage",
/*P*/ {},
/*A*/ { NextAction("shred") },
/*C*/ {}
);
}
};
@@ -120,9 +142,11 @@ CatDpsDruidStrategy::CatDpsDruidStrategy(PlayerbotAI* botAI) : FeralDruidStrateg
actionNodeFactories.Add(new CatDpsDruidStrategyActionNodeFactory());
}
NextAction** CatDpsDruidStrategy::getDefaultActions()
std::vector<NextAction> CatDpsDruidStrategy::getDefaultActions()
{
return NextAction::array(0, new NextAction("tiger's fury", ACTION_DEFAULT + 0.1f), nullptr);
return {
NextAction("tiger's fury", ACTION_DEFAULT + 0.1f)
};
}
void CatDpsDruidStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
@@ -130,50 +154,161 @@ void CatDpsDruidStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
FeralDruidStrategy::InitTriggers(triggers);
// Default priority
triggers.push_back(new TriggerNode("almost full energy available",
NextAction::array(0, new NextAction("shred", ACTION_DEFAULT + 0.4f), nullptr)));
triggers.push_back(new TriggerNode("combo points not full",
NextAction::array(0, new NextAction("shred", ACTION_DEFAULT + 0.4f), nullptr)));
triggers.push_back(new TriggerNode("almost full energy available",
NextAction::array(0, new NextAction("mangle (cat)", ACTION_DEFAULT + 0.3f), nullptr)));
triggers.push_back(new TriggerNode("combo points not full and high energy",
NextAction::array(0, new NextAction("mangle (cat)", ACTION_DEFAULT + 0.3f), nullptr)));
triggers.push_back(new TriggerNode("almost full energy available",
NextAction::array(0, new NextAction("claw", ACTION_DEFAULT + 0.2f), nullptr)));
triggers.push_back(new TriggerNode("combo points not full and high energy",
NextAction::array(0, new NextAction("claw", ACTION_DEFAULT + 0.2f), nullptr)));
triggers.push_back(
new TriggerNode("faerie fire (feral)",
NextAction::array(0, new NextAction("faerie fire (feral)", ACTION_DEFAULT + 0.0f), nullptr)));
new TriggerNode(
"almost full energy available",
{
NextAction("shred", ACTION_DEFAULT + 0.4f)
}
)
);
triggers.push_back(
new TriggerNode(
"combo points not full",
{
NextAction("shred", ACTION_DEFAULT + 0.4f)
}
)
);
triggers.push_back(
new TriggerNode(
"almost full energy available",
{
NextAction("mangle (cat)", ACTION_DEFAULT + 0.3f)
}
)
);
triggers.push_back(
new TriggerNode(
"combo points not full and high energy",
{
NextAction("mangle (cat)", ACTION_DEFAULT + 0.3f)
}
)
);
triggers.push_back(
new TriggerNode(
"almost full energy available",
{
NextAction("claw", ACTION_DEFAULT + 0.2f)
}
)
);
triggers.push_back(
new TriggerNode(
"combo points not full and high energy",
{
NextAction("claw", ACTION_DEFAULT + 0.2f)
}
)
);
triggers.push_back(
new TriggerNode(
"faerie fire (feral)",
{
NextAction("faerie fire (feral)", ACTION_DEFAULT + 0.0f)
}
)
);
// Main spell
triggers.push_back(
new TriggerNode("cat form", NextAction::array(0, new NextAction("cat form", ACTION_HIGH + 8), nullptr)));
new TriggerNode(
"cat form", {
NextAction("cat form", ACTION_HIGH + 8)
}
)
);
triggers.push_back(
new TriggerNode("savage roar", NextAction::array(0, new NextAction("savage roar", ACTION_HIGH + 7), nullptr)));
triggers.push_back(new TriggerNode("combo points available",
NextAction::array(0, new NextAction("rip", ACTION_HIGH + 6), nullptr)));
triggers.push_back(new TriggerNode(
"ferocious bite time", NextAction::array(0, new NextAction("ferocious bite", ACTION_HIGH + 5), nullptr)));
new TriggerNode(
"savage roar", {
NextAction("savage roar", ACTION_HIGH + 7)
}
)
);
triggers.push_back(
new TriggerNode("target with combo points almost dead",
NextAction::array(0, new NextAction("ferocious bite", ACTION_HIGH + 4), nullptr)));
triggers.push_back(new TriggerNode("mangle (cat)",
NextAction::array(0, new NextAction("mangle (cat)", ACTION_HIGH + 3), nullptr)));
triggers.push_back(new TriggerNode("rake", NextAction::array(0, new NextAction("rake", ACTION_HIGH + 2), nullptr)));
new TriggerNode(
"combo points available",
{
NextAction("rip", ACTION_HIGH + 6)
}
)
);
triggers.push_back(
new TriggerNode("medium threat", NextAction::array(0, new NextAction("cower", ACTION_HIGH + 1), nullptr)));
new TriggerNode(
"ferocious bite time",
{
NextAction("ferocious bite", ACTION_HIGH + 5)
}
)
);
triggers.push_back(
new TriggerNode(
"target with combo points almost dead",
{
NextAction("ferocious bite", ACTION_HIGH + 4)
}
)
);
triggers.push_back(
new TriggerNode(
"mangle (cat)",
{
NextAction("mangle (cat)", ACTION_HIGH + 3)
}
)
);
triggers.push_back(
new TriggerNode(
"rake",
{
NextAction("rake", ACTION_HIGH + 2)
}
)
);
triggers.push_back(
new TriggerNode(
"medium threat",
{
NextAction("cower", ACTION_HIGH + 1)
}
)
);
// AOE
triggers.push_back(
new TriggerNode("medium aoe", NextAction::array(0, new NextAction("swipe (cat)", ACTION_HIGH + 3), nullptr)));
triggers.push_back(new TriggerNode(
"light aoe", NextAction::array(0, new NextAction("rake on attacker", ACTION_HIGH + 2), nullptr)));
// Reach target
triggers.push_back(new TriggerNode(
"enemy out of melee", NextAction::array(0, new NextAction("feral charge - cat", ACTION_HIGH + 9), nullptr)));
new TriggerNode(
"medium aoe",
{
NextAction("swipe (cat)", ACTION_HIGH + 3)
}
)
);
triggers.push_back(
new TriggerNode("enemy out of melee", NextAction::array(0, new NextAction("dash", ACTION_HIGH + 8), nullptr)));
new TriggerNode(
"light aoe",
{
NextAction("rake on attacker", ACTION_HIGH + 2)
}
)
);
// Reach target
triggers.push_back(
new TriggerNode(
"enemy out of melee",
{
NextAction("feral charge - cat", ACTION_HIGH + 9)
}
)
);
triggers.push_back(
new TriggerNode(
"enemy out of melee",
{
NextAction("dash", ACTION_HIGH + 8)
}
)
);
}
void CatAoeDruidStrategy::InitTriggers(std::vector<TriggerNode*>& triggers) {}

View File

@@ -18,7 +18,7 @@ public:
public:
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
std::string const getName() override { return "cat"; }
NextAction** getDefaultActions() override;
std::vector<NextAction> getDefaultActions() override;
uint32 GetType() const override { return STRATEGY_TYPE_COMBAT | STRATEGY_TYPE_MELEE; }
};

View File

@@ -11,15 +11,15 @@
#include "AoeValues.h"
#include "TargetValue.h"
NextAction** CastAbolishPoisonAction::getAlternatives()
std::vector<NextAction> CastAbolishPoisonAction::getAlternatives()
{
return NextAction::merge(NextAction::array(0, new NextAction("cure poison"), nullptr),
return NextAction::merge({ NextAction("cure poison") },
CastSpellAction::getPrerequisites());
}
NextAction** CastAbolishPoisonOnPartyAction::getAlternatives()
std::vector<NextAction> CastAbolishPoisonOnPartyAction::getAlternatives()
{
return NextAction::merge(NextAction::array(0, new NextAction("cure poison on party"), nullptr),
return NextAction::merge({ NextAction("cure poison on party") },
CastSpellAction::getPrerequisites());
}
@@ -60,15 +60,15 @@ bool CastStarfallAction::isUseful()
return true;
}
NextAction** CastReviveAction::getPrerequisites()
std::vector<NextAction> CastReviveAction::getPrerequisites()
{
return NextAction::merge(NextAction::array(0, new NextAction("caster form"), nullptr),
return NextAction::merge({ NextAction("caster form") },
ResurrectPartyMemberAction::getPrerequisites());
}
NextAction** CastRebirthAction::getPrerequisites()
std::vector<NextAction> CastRebirthAction::getPrerequisites()
{
return NextAction::merge(NextAction::array(0, new NextAction("caster form"), nullptr),
return NextAction::merge({ NextAction("caster form") },
ResurrectPartyMemberAction::getPrerequisites());
}

View File

@@ -74,7 +74,7 @@ class CastReviveAction : public ResurrectPartyMemberAction
public:
CastReviveAction(PlayerbotAI* botAI) : ResurrectPartyMemberAction(botAI, "revive") {}
NextAction** getPrerequisites() override;
std::vector<NextAction> getPrerequisites() override;
};
class CastRebirthAction : public ResurrectPartyMemberAction
@@ -82,7 +82,7 @@ class CastRebirthAction : public ResurrectPartyMemberAction
public:
CastRebirthAction(PlayerbotAI* botAI) : ResurrectPartyMemberAction(botAI, "rebirth") {}
NextAction** getPrerequisites() override;
std::vector<NextAction> getPrerequisites() override;
bool isUseful() override;
};
@@ -223,7 +223,7 @@ class CastAbolishPoisonAction : public CastCureSpellAction
{
public:
CastAbolishPoisonAction(PlayerbotAI* botAI) : CastCureSpellAction(botAI, "abolish poison") {}
NextAction** getAlternatives() override;
std::vector<NextAction> getAlternatives() override;
};
class CastAbolishPoisonOnPartyAction : public CurePartyMemberAction
@@ -233,7 +233,7 @@ public:
{
}
NextAction** getAlternatives() override;
std::vector<NextAction> getAlternatives() override;
};
class CastBarkskinAction : public CastBuffSpellAction

View File

@@ -17,9 +17,9 @@ bool CastBearFormAction::isUseful()
return CastBuffSpellAction::isUseful() && !botAI->HasAura("dire bear form", GetTarget());
}
NextAction** CastDireBearFormAction::getAlternatives()
std::vector<NextAction> CastDireBearFormAction::getAlternatives()
{
return NextAction::merge(NextAction::array(0, new NextAction("bear form"), nullptr),
return NextAction::merge({ NextAction("bear form") },
CastSpellAction::getAlternatives());
}

View File

@@ -24,7 +24,7 @@ class CastDireBearFormAction : public CastBuffSpellAction
public:
CastDireBearFormAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "dire bear form") {}
NextAction** getAlternatives() override;
std::vector<NextAction> getAlternatives() override;
};
class CastCatFormAction : public CastBuffSpellAction

View File

@@ -26,65 +26,65 @@ private:
static ActionNode* survival_instincts([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("survival instincts",
/*P*/ nullptr,
/*A*/ NextAction::array(0, new NextAction("barkskin"), nullptr),
/*C*/ nullptr);
/*P*/ {},
/*A*/ { NextAction("barkskin") },
/*C*/ {});
}
static ActionNode* thorns([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("thorns",
/*P*/ NextAction::array(0, new NextAction("caster form"), nullptr),
/*A*/ nullptr,
/*C*/ nullptr);
/*P*/ { NextAction("caster form") },
/*A*/ {},
/*C*/ {});
}
static ActionNode* omen_of_clarity([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("omen of clarity",
/*P*/ NextAction::array(0, new NextAction("caster form"), nullptr),
/*A*/ nullptr,
/*C*/ nullptr);
/*P*/ { NextAction("caster form") },
/*A*/ {},
/*C*/ {});
}
static ActionNode* cure_poison([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("cure poison",
/*P*/ NextAction::array(0, new NextAction("caster form"), nullptr),
/*A*/ nullptr,
/*C*/ nullptr);
/*P*/ { NextAction("caster form") },
/*A*/ {},
/*C*/ {});
}
static ActionNode* cure_poison_on_party([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("cure poison on party",
/*P*/ NextAction::array(0, new NextAction("caster form"), nullptr),
/*A*/ nullptr,
/*C*/ nullptr);
/*P*/ { NextAction("caster form") },
/*A*/ {},
/*C*/ {});
}
static ActionNode* abolish_poison([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("abolish poison",
/*P*/ NextAction::array(0, new NextAction("caster form"), nullptr),
/*A*/ nullptr,
/*C*/ nullptr);
/*P*/ { NextAction("caster form") },
/*A*/ {},
/*C*/ {});
}
static ActionNode* abolish_poison_on_party([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("abolish poison on party",
/*P*/ NextAction::array(0, new NextAction("caster form"), nullptr),
/*A*/ nullptr,
/*C*/ nullptr);
/*P*/ { NextAction("caster form") },
/*A*/ {},
/*C*/ {});
}
static ActionNode* prowl([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("prowl",
/*P*/ NextAction::array(0, new NextAction("cat form"), nullptr),
/*A*/ nullptr,
/*C*/ nullptr);
/*P*/ { NextAction("cat form") },
/*A*/ {},
/*C*/ {});
}
};
@@ -98,20 +98,16 @@ void FeralDruidStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
GenericDruidStrategy::InitTriggers(triggers);
// triggers.push_back(new TriggerNode("not facing target", NextAction::array(0, new NextAction("set facing",
// ACTION_NORMAL + 7), nullptr)));
triggers.push_back(new TriggerNode(
"enemy out of melee", NextAction::array(0, new NextAction("reach melee", ACTION_HIGH + 1), nullptr)));
// triggers.push_back(new TriggerNode("enemy too close for melee", NextAction::array(0, new NextAction("move out of
// enemy contact", ACTION_NORMAL + 8), nullptr)));
"enemy out of melee", { NextAction("reach melee", ACTION_HIGH + 1) }));
triggers.push_back(new TriggerNode(
"critical health", NextAction::array(0, new NextAction("survival instincts", ACTION_EMERGENCY + 1), nullptr)));
"critical health", { NextAction("survival instincts", ACTION_EMERGENCY + 1) }));
triggers.push_back(new TriggerNode(
"omen of clarity", NextAction::array(0, new NextAction("omen of clarity", ACTION_HIGH + 9), nullptr)));
"omen of clarity", { NextAction("omen of clarity", ACTION_HIGH + 9) }));
triggers.push_back(new TriggerNode("player has flag",
NextAction::array(0, new NextAction("dash", ACTION_EMERGENCY + 2), nullptr)));
{ NextAction("dash", ACTION_EMERGENCY + 2) }));
triggers.push_back(new TriggerNode("enemy flagcarrier near",
NextAction::array(0, new NextAction("dash", ACTION_EMERGENCY + 2), nullptr)));
{ NextAction("dash", ACTION_EMERGENCY + 2) }));
triggers.push_back(
new TriggerNode("berserk", NextAction::array(0, new NextAction("berserk", ACTION_HIGH + 6), nullptr)));
new TriggerNode("berserk", { NextAction("berserk", ACTION_HIGH + 6) }));
}

View File

@@ -27,49 +27,49 @@ private:
static ActionNode* regrowth([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("regrowth",
/*P*/ NextAction::array(0, new NextAction("caster form"), nullptr),
/*A*/ NextAction::array(0, new NextAction("healing touch"), nullptr),
/*C*/ NextAction::array(0, new NextAction("melee", 10.0f), nullptr));
/*P*/ { NextAction("caster form") },
/*A*/ { NextAction("healing touch") },
/*C*/ { NextAction("melee", 10.0f) });
}
static ActionNode* rejuvenation([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("rejuvenation",
/*P*/ NextAction::array(0, new NextAction("caster form"), nullptr),
/*A*/ nullptr,
/*C*/ nullptr);
/*P*/ { NextAction("caster form") },
/*A*/ {},
/*C*/ {});
}
static ActionNode* healing_touch([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("healing touch",
/*P*/ NextAction::array(0, new NextAction("caster form"), nullptr),
/*A*/ nullptr,
/*C*/ nullptr);
/*P*/ { NextAction("caster form") },
/*A*/ {},
/*C*/ {});
}
static ActionNode* regrowth_on_party([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("regrowth on party",
/*P*/ NextAction::array(0, new NextAction("caster form"), nullptr),
/*A*/ NextAction::array(0, new NextAction("healing touch on party"), nullptr),
/*C*/ NextAction::array(0, new NextAction("melee", 10.0f), nullptr));
/*P*/ { NextAction("caster form") },
/*A*/ { NextAction("healing touch on party") },
/*C*/ { NextAction("melee", 10.0f) });
}
static ActionNode* rejuvenation_on_party([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("rejuvenation on party",
/*P*/ NextAction::array(0, new NextAction("caster form"), nullptr),
/*A*/ nullptr,
/*C*/ nullptr);
/*P*/ { NextAction("caster form") },
/*A*/ {},
/*C*/ {});
}
static ActionNode* healing_touch_on_party([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("healing touch on party",
/*P*/ NextAction::array(0, new NextAction("caster form"), nullptr),
/*A*/ nullptr,
/*C*/ nullptr);
/*P*/ { NextAction("caster form") },
/*A*/ {},
/*C*/ {});
}
};

View File

@@ -29,76 +29,69 @@ private:
static ActionNode* thorns([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("thorns",
/*P*/ NextAction::array(0, new NextAction("caster form"), nullptr),
/*A*/ nullptr,
/*C*/ nullptr);
/*P*/ { NextAction("caster form") },
/*A*/ {},
/*C*/ {});
}
static ActionNode* thorns_on_party([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("thorns on party",
/*P*/ NextAction::array(0, new NextAction("caster form"), nullptr),
/*A*/ nullptr,
/*C*/ nullptr);
/*P*/ { NextAction("caster form") },
/*A*/ {},
/*C*/ {});
}
static ActionNode* mark_of_the_wild([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("mark of the wild",
/*P*/ NextAction::array(0, new NextAction("caster form"), nullptr),
/*A*/ nullptr,
/*C*/ nullptr);
/*P*/ { NextAction("caster form") },
/*A*/ {},
/*C*/ {});
}
static ActionNode* mark_of_the_wild_on_party([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("mark of the wild on party",
/*P*/ NextAction::array(0, new NextAction("caster form"), nullptr),
/*A*/ nullptr,
/*C*/ nullptr);
/*P*/ { NextAction("caster form") },
/*A*/ {},
/*C*/ {});
}
static ActionNode* regrowth_on_party([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("regrowth on party",
/*P*/ NextAction::array(0, new NextAction("caster form"), nullptr),
/*A*/ NULL,
/*C*/ NULL);
/*P*/ { NextAction("caster form") },
/*A*/ {},
/*C*/ {});
}
static ActionNode* rejuvenation_on_party([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("rejuvenation on party",
/*P*/ NextAction::array(0, new NextAction("caster form"), nullptr),
/*A*/ NULL,
/*C*/ NULL);
/*P*/ { NextAction("caster form") },
/*A*/ {},
/*C*/ {});
}
static ActionNode* remove_curse_on_party([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("remove curse on party",
/*P*/ NextAction::array(0, new NextAction("caster form"), nullptr),
/*A*/ NULL,
/*C*/ NULL);
/*P*/ { NextAction("caster form") },
/*A*/ {},
/*C*/ {});
}
static ActionNode* abolish_poison_on_party([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("abolish poison on party",
/*P*/ NextAction::array(0, new NextAction("caster form"), nullptr),
/*A*/ NULL,
/*C*/ NULL);
/*P*/ { NextAction("caster form") },
/*A*/ {},
/*C*/ {});
}
static ActionNode* revive([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("revive",
/*P*/ NextAction::array(0, new NextAction("caster form"), nullptr),
/*A*/ NULL,
/*C*/ NULL);
/*P*/ { NextAction("caster form") },
/*A*/ {},
/*C*/ {});
}
// static ActionNode* innervate([[maybe_unused]] PlayerbotAI* botAI)
// {
// return new ActionNode ("innervate",
// /*P*/ nullptr,
// /*A*/ NextAction::array(0, new NextAction("drink"), nullptr),
// /*C*/ nullptr);
// }
};
GenericDruidNonCombatStrategy::GenericDruidNonCombatStrategy(PlayerbotAI* botAI) : NonCombatStrategy(botAI)
@@ -110,79 +103,73 @@ void GenericDruidNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& trig
{
NonCombatStrategy::InitTriggers(triggers);
triggers.push_back(new TriggerNode("mark of the wild", NextAction::array(0, new NextAction("mark of the wild", 14.0f), nullptr)));
// triggers.push_back(new TriggerNode("thorns", NextAction::array(0, new NextAction("thorns", 12.0f), nullptr)));
// triggers.push_back(new TriggerNode("cure poison", NextAction::array(0, new NextAction("abolish poison", 21.0f),
// nullptr)));
triggers.push_back(new TriggerNode("party member cure poison", NextAction::array(0, new NextAction("abolish poison on party", 20.0f), nullptr)));
triggers.push_back(new TriggerNode("party member dead", NextAction::array(0, new NextAction("revive", ACTION_CRITICAL_HEAL + 10), nullptr)));
// triggers.push_back(new TriggerNode("low mana", NextAction::array(0, new NextAction("innervate", ACTION_EMERGENCY
// + 5), nullptr))); triggers.push_back(new TriggerNode("swimming", NextAction::array(0, new NextAction("aquatic
// form", 1.0f), nullptr)));
triggers.push_back(new TriggerNode("mark of the wild", { NextAction("mark of the wild", 14.0f) }));
triggers.push_back(new TriggerNode("party member cure poison", { NextAction("abolish poison on party", 20.0f) }));
triggers.push_back(new TriggerNode("party member dead", { NextAction("revive", ACTION_CRITICAL_HEAL + 10) }));
triggers.push_back(new TriggerNode("often", NextAction::array(0, new NextAction("apply oil", 1.0f), nullptr)));
triggers.push_back(new TriggerNode("often", { NextAction("apply oil", 1.0f) }));
triggers.push_back(
new TriggerNode("party member critical health",
NextAction::array(0,
new NextAction("wild growth on party", ACTION_MEDIUM_HEAL + 7),
new NextAction("regrowth on party", ACTION_MEDIUM_HEAL + 6),
new NextAction("rejuvenation on party", ACTION_MEDIUM_HEAL + 5),
nullptr)));
{
NextAction("wild growth on party", ACTION_MEDIUM_HEAL + 7),
NextAction("regrowth on party", ACTION_MEDIUM_HEAL + 6),
NextAction("rejuvenation on party", ACTION_MEDIUM_HEAL + 5),
}));
triggers.push_back(
new TriggerNode("party member low health",
NextAction::array(0,
new NextAction("wild growth on party", ACTION_MEDIUM_HEAL + 5),
new NextAction("regrowth on party", ACTION_MEDIUM_HEAL + 4),
new NextAction("rejuvenation on party", ACTION_MEDIUM_HEAL + 3),
nullptr)));
{
NextAction("wild growth on party", ACTION_MEDIUM_HEAL + 5),
NextAction("regrowth on party", ACTION_MEDIUM_HEAL + 4),
NextAction("rejuvenation on party", ACTION_MEDIUM_HEAL + 3),
}));
triggers.push_back(
new TriggerNode("party member medium health",
NextAction::array(0, new NextAction("wild growth on party", ACTION_MEDIUM_HEAL + 3),
new NextAction("regrowth on party", ACTION_MEDIUM_HEAL + 2),
new NextAction("rejuvenation on party", ACTION_MEDIUM_HEAL + 1),
nullptr)));
{ NextAction("wild growth on party", ACTION_MEDIUM_HEAL + 3),
NextAction("regrowth on party", ACTION_MEDIUM_HEAL + 2),
NextAction("rejuvenation on party", ACTION_MEDIUM_HEAL + 1),
}));
triggers.push_back(
new TriggerNode("party member almost full health",
NextAction::array(0, new NextAction("wild growth on party", ACTION_LIGHT_HEAL + 3), new NextAction("rejuvenation on party", ACTION_LIGHT_HEAL + 2), NULL)));
{ NextAction("wild growth on party", ACTION_LIGHT_HEAL + 3), NextAction("rejuvenation on party", ACTION_LIGHT_HEAL + 2) }));
triggers.push_back(
new TriggerNode("party member remove curse",
NextAction::array(0, new NextAction("remove curse on party", ACTION_DISPEL + 7), nullptr)));
{ NextAction("remove curse on party", ACTION_DISPEL + 7) }));
triggers.push_back(
new TriggerNode("new pet", NextAction::array(0, new NextAction("set pet stance", 60.0f), nullptr)));
new TriggerNode("new pet", { NextAction("set pet stance", 60.0f) }));
triggers.push_back(new TriggerNode("party member critical health", NextAction::array(0,
new NextAction("wild growth on party", ACTION_MEDIUM_HEAL + 7),
new NextAction("regrowth on party", ACTION_MEDIUM_HEAL + 6),
new NextAction("rejuvenation on party", ACTION_MEDIUM_HEAL + 5),
nullptr)));
triggers.push_back(new TriggerNode("party member low health", NextAction::array(0,
new NextAction("wild growth on party", ACTION_MEDIUM_HEAL + 5),
new NextAction("regrowth on party", ACTION_MEDIUM_HEAL + 4),
new NextAction("rejuvenation on party", ACTION_MEDIUM_HEAL + 3),
nullptr)));
triggers.push_back(new TriggerNode("party member medium health", NextAction::array(0,
new NextAction("wild growth on party", ACTION_MEDIUM_HEAL + 3),
new NextAction("regrowth on party", ACTION_MEDIUM_HEAL + 2),
new NextAction("rejuvenation on party", ACTION_MEDIUM_HEAL + 1),
nullptr)));
triggers.push_back(new TriggerNode("party member almost full health", NextAction::array(0,
new NextAction("wild growth on party", ACTION_LIGHT_HEAL + 3),
new NextAction("rejuvenation on party", ACTION_LIGHT_HEAL + 2),
nullptr)));
triggers.push_back(new TriggerNode("party member remove curse", NextAction::array(0,
new NextAction("remove curse on party", ACTION_DISPEL + 7),
nullptr)));
triggers.push_back(new TriggerNode("party member critical health", {
NextAction("wild growth on party", ACTION_MEDIUM_HEAL + 7),
NextAction("regrowth on party", ACTION_MEDIUM_HEAL + 6),
NextAction("rejuvenation on party", ACTION_MEDIUM_HEAL + 5),
}));
triggers.push_back(new TriggerNode("party member low health", {
NextAction("wild growth on party", ACTION_MEDIUM_HEAL + 5),
NextAction("regrowth on party", ACTION_MEDIUM_HEAL + 4),
NextAction("rejuvenation on party", ACTION_MEDIUM_HEAL + 3),
}));
triggers.push_back(new TriggerNode("party member medium health", {
NextAction("wild growth on party", ACTION_MEDIUM_HEAL + 3),
NextAction("regrowth on party", ACTION_MEDIUM_HEAL + 2),
NextAction("rejuvenation on party", ACTION_MEDIUM_HEAL + 1),
}));
triggers.push_back(new TriggerNode("party member almost full health", {
NextAction("wild growth on party", ACTION_LIGHT_HEAL + 3),
NextAction("rejuvenation on party", ACTION_LIGHT_HEAL + 2),
}));
triggers.push_back(new TriggerNode("party member remove curse", {
NextAction("remove curse on party", ACTION_DISPEL + 7),
}));
int specTab = AiFactory::GetPlayerSpecTab(botAI->GetBot());
if (specTab == 0 || specTab == 2) // Balance or Restoration
triggers.push_back(new TriggerNode("often", NextAction::array(0, new NextAction("apply oil", 1.0f), nullptr)));
triggers.push_back(new TriggerNode("often", { NextAction("apply oil", 1.0f) }));
if (specTab == 1) // Feral
triggers.push_back(new TriggerNode("often", NextAction::array(0, new NextAction("apply stone", 1.0f), nullptr)));
triggers.push_back(new TriggerNode("often", { NextAction("apply stone", 1.0f) }));
}
@@ -195,13 +182,13 @@ void GenericDruidBuffStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
NonCombatStrategy::InitTriggers(triggers);
triggers.push_back(new TriggerNode("mark of the wild on party", NextAction::array(0,
new NextAction("mark of the wild on party", 13.0f),
nullptr)));
triggers.push_back(new TriggerNode("thorns on main tank", NextAction::array(0,
new NextAction("thorns on main tank", 11.0f),
nullptr)));
triggers.push_back(new TriggerNode("thorns", NextAction::array(0,
new NextAction("thorns", 10.0f),
nullptr)));
triggers.push_back(new TriggerNode("mark of the wild on party", {
NextAction("mark of the wild on party", 13.0f),
}));
triggers.push_back(new TriggerNode("thorns on main tank", {
NextAction("thorns on main tank", 11.0f),
}));
triggers.push_back(new TriggerNode("thorns", {
NextAction("thorns", 10.0f),
}));
}

View File

@@ -27,73 +27,73 @@ private:
static ActionNode* melee([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("melee",
/*P*/ nullptr,
/*A*/ nullptr,
/*C*/ nullptr);
/*P*/ {},
/*A*/ {},
/*C*/ {});
}
static ActionNode* caster_form([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("caster form",
/*P*/ nullptr,
/*A*/ nullptr,
/*C*/ nullptr);
/*P*/ {},
/*A*/ {},
/*C*/ {});
}
static ActionNode* cure_poison([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("cure poison",
/*P*/ nullptr,
/*A*/ nullptr,
/*C*/ nullptr);
/*P*/ {},
/*A*/ {},
/*C*/ {});
}
static ActionNode* cure_poison_on_party([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("cure poison on party",
/*P*/ nullptr,
/*A*/ nullptr,
/*C*/ nullptr);
/*P*/ {},
/*A*/ {},
/*C*/ {});
}
static ActionNode* abolish_poison([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("abolish poison",
/*P*/ nullptr,
/*A*/ nullptr,
/*C*/ nullptr);
/*P*/ {},
/*A*/ {},
/*C*/ {});
}
static ActionNode* abolish_poison_on_party([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("abolish poison on party",
/*P*/ nullptr,
/*A*/ nullptr,
/*C*/ nullptr);
/*P*/ {},
/*A*/ {},
/*C*/ {});
}
static ActionNode* rebirth([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("rebirth",
/*P*/ nullptr,
/*A*/ nullptr,
/*C*/ nullptr);
/*P*/ {},
/*A*/ {},
/*C*/ {});
}
static ActionNode* entangling_roots_on_cc([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("entangling roots on cc",
/*P*/ NextAction::array(0, new NextAction("caster form"), nullptr),
/*A*/ nullptr,
/*C*/ nullptr);
/*P*/ { NextAction("caster form") },
/*A*/ {},
/*C*/ {});
}
static ActionNode* innervate([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("innervate",
/*P*/ nullptr,
/*A*/ NextAction::array(0, new NextAction("mana potion"), nullptr),
/*C*/ nullptr);
/*P*/ {},
/*A*/ { NextAction("mana potion") },
/*C*/ {});
}
};
@@ -107,70 +107,52 @@ void GenericDruidStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
CombatStrategy::InitTriggers(triggers);
triggers.push_back(
new TriggerNode("low health", NextAction::array(0, new NextAction("barkskin", ACTION_HIGH + 7), nullptr)));
new TriggerNode("low health", { NextAction("barkskin", ACTION_HIGH + 7) }));
// triggers.push_back(new TriggerNode("low health", NextAction::array(0, new NextAction("regrowth",
// ACTION_MEDIUM_HEAL + 2), nullptr))); triggers.push_back(new TriggerNode("party member low health",
// NextAction::array(0, new NextAction("regrowth on party", ACTION_MEDIUM_HEAL + 1), nullptr)));
// triggers.push_back(new TriggerNode("critical health", NextAction::array(0, new NextAction("regrowth",
// ACTION_CRITICAL_HEAL + 2), new NextAction("healing touch", ACTION_CRITICAL_HEAL + 2), nullptr)));
// triggers.push_back(new TriggerNode("party member critical health", NextAction::array(0, new NextAction("regrowth
// on party", ACTION_CRITICAL_HEAL + 1), new NextAction("healing touch on party", ACTION_CRITICAL_HEAL + 1),
// nullptr))); triggers.push_back(new TriggerNode("party member dead", NextAction::array(0, new
// NextAction("rebirth", ACTION_HIGH + 1), nullptr))); triggers.push_back(new TriggerNode("low mana",
// NextAction::array(0, new NextAction("innervate", ACTION_EMERGENCY + 5), nullptr)));
triggers.push_back(new TriggerNode("combat party member dead",
NextAction::array(0, new NextAction("rebirth", ACTION_HIGH + 9), NULL)));
{ NextAction("rebirth", ACTION_HIGH + 9) }));
triggers.push_back(new TriggerNode("being attacked",
NextAction::array(0, new NextAction("nature's grasp", ACTION_HIGH + 1), nullptr)));
triggers.push_back(new TriggerNode("new pet", NextAction::array(0, new NextAction("set pet stance", 60.0f), nullptr)));
{ NextAction("nature's grasp", ACTION_HIGH + 1) }));
triggers.push_back(new TriggerNode("new pet", { NextAction("set pet stance", 60.0f) }));
}
void DruidCureStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
// triggers.push_back(new TriggerNode("cure poison", NextAction::array(0, new NextAction("abolish poison",
// ACTION_DISPEL + 2), nullptr)));
triggers.push_back(
new TriggerNode("party member cure poison",
NextAction::array(0, new NextAction("abolish poison on party", ACTION_DISPEL + 1), nullptr)));
{ NextAction("abolish poison on party", ACTION_DISPEL + 1) }));
triggers.push_back(
new TriggerNode("party member remove curse",
NextAction::array(0, new NextAction("remove curse on party", ACTION_DISPEL + 7), NULL)));
{ NextAction("remove curse on party", ACTION_DISPEL + 7) }));
}
void DruidBoostStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(new TriggerNode(
"nature's swiftness", NextAction::array(0, new NextAction("nature's swiftness", ACTION_HIGH + 9), nullptr)));
"nature's swiftness", { NextAction("nature's swiftness", ACTION_HIGH + 9) }));
}
void DruidCcStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(new TriggerNode(
"entangling roots", NextAction::array(0, new NextAction("entangling roots on cc", ACTION_HIGH + 2), nullptr)));
"entangling roots", { NextAction("entangling roots on cc", ACTION_HIGH + 2) }));
triggers.push_back(new TriggerNode(
"entangling roots kite", NextAction::array(0, new NextAction("entangling roots", ACTION_HIGH + 2), nullptr)));
"entangling roots kite", { NextAction("entangling roots", ACTION_HIGH + 2) }));
triggers.push_back(new TriggerNode(
"hibernate", NextAction::array(0, new NextAction("hibernate on cc", ACTION_HIGH + 3), nullptr)));
"hibernate", { NextAction("hibernate on cc", ACTION_HIGH + 3) }));
}
void DruidHealerDpsStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(
new TriggerNode("healer should attack",
NextAction::array(0,
new NextAction("cancel tree form", ACTION_DEFAULT + 0.3f),
new NextAction("moonfire", ACTION_DEFAULT + 0.2f),
new NextAction("wrath", ACTION_DEFAULT + 0.1f),
new NextAction("starfire", ACTION_DEFAULT),
nullptr)));
{
NextAction("cancel tree form", ACTION_DEFAULT + 0.3f),
NextAction("moonfire", ACTION_DEFAULT + 0.2f),
NextAction("wrath", ACTION_DEFAULT + 0.1f),
NextAction("starfire", ACTION_DEFAULT),
}));
// long cast time
// triggers.push_back(
// new TriggerNode("medium aoe and healer should attack",
// NextAction::array(0,
// new NextAction("hurricane", ACTION_DEFAULT + 0.7f),
// nullptr)));
}

View File

@@ -12,40 +12,16 @@ class HealDruidStrategyActionNodeFactory : public NamedObjectFactory<ActionNode>
public:
HealDruidStrategyActionNodeFactory() {
creators["nourish on party"] = &nourtish_on_party;
// creators["wild growth on party"] = &wild_growth_on_party;
// creators["rejuvenation on party"] = &rejuvenation_on_party;
// creators["regrowth on party"] = &regrowth_on_party;
}
private:
static ActionNode* nourtish_on_party([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("nourish on party",
/*P*/ nullptr,
/*A*/ NextAction::array(0, new NextAction("healing touch on party"), nullptr),
/*C*/ nullptr);
/*P*/ {},
/*A*/ { NextAction("healing touch on party") },
/*C*/ {});
}
// static ActionNode* wild_growth_on_party([[maybe_unused]] PlayerbotAI* botAI)
// {
// return new ActionNode("wild growth on party",
// /*P*/ NextAction::array(0, new NextAction("tree form"), nullptr),
// /*A*/ nullptr,
// /*C*/ nullptr);
// }
// static ActionNode* rejuvenation_on_party([[maybe_unused]] PlayerbotAI* botAI)
// {
// return new ActionNode("rejuvenation on party",
// /*P*/ NextAction::array(0, new NextAction("tree form"), nullptr),
// /*A*/ nullptr,
// /*C*/ nullptr);
// }
// static ActionNode* regrowth_on_party([[maybe_unused]] PlayerbotAI* botAI)
// {
// return new ActionNode("regrowth on party",
// /*P*/ NextAction::array(0, new NextAction("tree form"), nullptr),
// /*A*/ nullptr,
// /*C*/ nullptr);
// }
};
HealDruidStrategy::HealDruidStrategy(PlayerbotAI* botAI) : GenericDruidStrategy(botAI)
@@ -57,73 +33,69 @@ void HealDruidStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
GenericDruidStrategy::InitTriggers(triggers);
// triggers.push_back(
// new TriggerNode("tree form", NextAction::array(0, new NextAction("tree form", ACTION_HIGH + 1), nullptr)));
triggers.push_back(new TriggerNode(
"party member to heal out of spell range",
NextAction::array(0, new NextAction("reach party member to heal", ACTION_CRITICAL_HEAL + 9), nullptr)));
{ NextAction("reach party member to heal", ACTION_CRITICAL_HEAL + 9) }));
// CRITICAL
triggers.push_back(
new TriggerNode("party member critical health",
NextAction::array(0,
new NextAction("tree form", ACTION_CRITICAL_HEAL + 4.1f),
new NextAction("swiftmend on party", ACTION_CRITICAL_HEAL + 4),
new NextAction("regrowth on party", ACTION_CRITICAL_HEAL + 3),
new NextAction("wild growth on party", ACTION_CRITICAL_HEAL + 2),
new NextAction("nourish on party", ACTION_CRITICAL_HEAL + 1),
// new NextAction("healing touch on party", ACTION_CRITICAL_HEAL + 0),
nullptr)));
{
NextAction("tree form", ACTION_CRITICAL_HEAL + 4.1f),
NextAction("swiftmend on party", ACTION_CRITICAL_HEAL + 4),
NextAction("regrowth on party", ACTION_CRITICAL_HEAL + 3),
NextAction("wild growth on party", ACTION_CRITICAL_HEAL + 2),
NextAction("nourish on party", ACTION_CRITICAL_HEAL + 1),
}));
triggers.push_back(
new TriggerNode("party member critical health",
NextAction::array(0, new NextAction("nature's swiftness", ACTION_CRITICAL_HEAL + 4), nullptr)));
{ NextAction("nature's swiftness", ACTION_CRITICAL_HEAL + 4) }));
triggers.push_back(new TriggerNode(
"group heal setting",
NextAction::array(0,
new NextAction("tree form", ACTION_MEDIUM_HEAL + 2.3f),
new NextAction("wild growth on party", ACTION_MEDIUM_HEAL + 2.2f),
new NextAction("rejuvenation on not full", ACTION_MEDIUM_HEAL + 2.1f),
nullptr)));
{
NextAction("tree form", ACTION_MEDIUM_HEAL + 2.3f),
NextAction("wild growth on party", ACTION_MEDIUM_HEAL + 2.2f),
NextAction("rejuvenation on not full", ACTION_MEDIUM_HEAL + 2.1f),
}));
triggers.push_back(
new TriggerNode("medium group heal setting",
NextAction::array(0,
new NextAction("tree form", ACTION_CRITICAL_HEAL + 0.6f),
new NextAction("tranquility", ACTION_CRITICAL_HEAL + 0.5f), nullptr)));
{
NextAction("tree form", ACTION_CRITICAL_HEAL + 0.6f),
NextAction("tranquility", ACTION_CRITICAL_HEAL + 0.5f) }));
// LOW
triggers.push_back(
new TriggerNode("party member low health",
NextAction::array(0, new NextAction("tree form", ACTION_MEDIUM_HEAL + 1.5f),
new NextAction("wild growth on party", ACTION_MEDIUM_HEAL + 1.4f),
new NextAction("regrowth on party", ACTION_MEDIUM_HEAL + 1.3f),
new NextAction("swiftmend on party", ACTION_MEDIUM_HEAL + 1.2),
new NextAction("nourish on party", ACTION_MEDIUM_HEAL + 1.1f),
nullptr)));
{ NextAction("tree form", ACTION_MEDIUM_HEAL + 1.5f),
NextAction("wild growth on party", ACTION_MEDIUM_HEAL + 1.4f),
NextAction("regrowth on party", ACTION_MEDIUM_HEAL + 1.3f),
NextAction("swiftmend on party", ACTION_MEDIUM_HEAL + 1.2),
NextAction("nourish on party", ACTION_MEDIUM_HEAL + 1.1f),
}));
// MEDIUM
triggers.push_back(
new TriggerNode("party member medium health",
NextAction::array(0,
new NextAction("tree form", ACTION_MEDIUM_HEAL + 0.5f),
new NextAction("wild growth on party", ACTION_MEDIUM_HEAL + 0.4f),
new NextAction("rejuvenation on party", ACTION_MEDIUM_HEAL + 0.3f),
new NextAction("regrowth on party", ACTION_MEDIUM_HEAL + 0.2f),
new NextAction("nourish on party", ACTION_MEDIUM_HEAL + 0.1f), nullptr)));
{
NextAction("tree form", ACTION_MEDIUM_HEAL + 0.5f),
NextAction("wild growth on party", ACTION_MEDIUM_HEAL + 0.4f),
NextAction("rejuvenation on party", ACTION_MEDIUM_HEAL + 0.3f),
NextAction("regrowth on party", ACTION_MEDIUM_HEAL + 0.2f),
NextAction("nourish on party", ACTION_MEDIUM_HEAL + 0.1f) }));
// almost full
triggers.push_back(
new TriggerNode("party member almost full health",
NextAction::array(0, new NextAction("wild growth on party", ACTION_LIGHT_HEAL + 0.3f),
new NextAction("rejuvenation on party", ACTION_LIGHT_HEAL + 0.2f),
new NextAction("regrowth on party", ACTION_LIGHT_HEAL + 0.1f), nullptr)));
{ NextAction("wild growth on party", ACTION_LIGHT_HEAL + 0.3f),
NextAction("rejuvenation on party", ACTION_LIGHT_HEAL + 0.2f),
NextAction("regrowth on party", ACTION_LIGHT_HEAL + 0.1f) }));
triggers.push_back(
new TriggerNode("medium mana", NextAction::array(0, new NextAction("innervate", ACTION_HIGH + 5), nullptr)));
new TriggerNode("medium mana", { NextAction("innervate", ACTION_HIGH + 5) }));
triggers.push_back(new TriggerNode("enemy too close for spell",
NextAction::array(0, new NextAction("flee", ACTION_MOVE + 9), nullptr)));
{ NextAction("flee", ACTION_MOVE + 9) }));
}

View File

@@ -9,16 +9,24 @@
MeleeDruidStrategy::MeleeDruidStrategy(PlayerbotAI* botAI) : CombatStrategy(botAI) {}
NextAction** MeleeDruidStrategy::getDefaultActions()
std::vector<NextAction> MeleeDruidStrategy::getDefaultActions()
{
return NextAction::array(0, new NextAction("faerie fire", ACTION_DEFAULT + 0.1f),
new NextAction("melee", ACTION_DEFAULT), nullptr);
return {
NextAction("faerie fire", ACTION_DEFAULT + 0.1f),
NextAction("melee", ACTION_DEFAULT)
};
}
void MeleeDruidStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(new TriggerNode(
"omen of clarity", NextAction::array(0, new NextAction("omen of clarity", ACTION_HIGH + 9), nullptr)));
triggers.push_back(
new TriggerNode(
"omen of clarity",
{
NextAction("omen of clarity", ACTION_HIGH + 9)
}
)
);
CombatStrategy::InitTriggers(triggers);
}

View File

@@ -15,7 +15,7 @@ public:
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
std::string const getName() override { return "melee"; }
NextAction** getDefaultActions() override;
std::vector<NextAction> getDefaultActions() override;
};
#endif

View File

@@ -29,90 +29,112 @@ public:
private:
static ActionNode* cat_form([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("cat form",
/*P*/ nullptr,
/*A*/ nullptr,
/*C*/ nullptr);
return new ActionNode(
"cat form",
/*P*/ {},
/*A*/ {},
/*C*/ {}
);
}
static ActionNode* mangle_cat([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("mangle (cat)",
/*P*/ nullptr,
/*A*/ nullptr,
/*C*/ nullptr);
return new ActionNode(
"mangle (cat)",
/*P*/ {},
/*A*/ {},
/*C*/ {}
);
}
static ActionNode* shred([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("shred",
/*P*/ nullptr,
/*A*/ NextAction::array(0, new NextAction("claw"), nullptr),
/*C*/ nullptr);
return new ActionNode(
"shred",
/*P*/ {},
/*A*/ { NextAction("claw") },
/*C*/ {}
);
}
static ActionNode* rake([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("rake",
/*P*/ nullptr,
/*A*/ nullptr,
/*C*/ nullptr);
return new ActionNode(
"rake",
/*P*/ {},
/*A*/ {},
/*C*/ {}
);
}
static ActionNode* rip([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("rip",
/*P*/ nullptr,
/*A*/ nullptr,
/*C*/ nullptr);
return new ActionNode(
"rip",
/*P*/ {},
/*A*/ {},
/*C*/ {}
);
}
static ActionNode* ferocious_bite([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("ferocious bite",
/*P*/ nullptr,
/*A*/ NextAction::array(0, new NextAction("rip"), nullptr),
/*C*/ nullptr);
return new ActionNode(
"ferocious bite",
/*P*/ {},
/*A*/ { NextAction("rip") },
/*C*/ {}
);
}
static ActionNode* savage_roar([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("savage roar",
/*P*/ nullptr,
/*A*/ nullptr,
/*C*/ nullptr);
return new ActionNode(
"savage roar",
/*P*/ {},
/*A*/ {},
/*C*/ {}
);
}
static ActionNode* faerie_fire_feral([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("faerie fire (feral)",
/*P*/ nullptr,
/*A*/ nullptr,
/*C*/ nullptr);
return new ActionNode(
"faerie fire (feral)",
/*P*/ {},
/*A*/ {},
/*C*/ {}
);
}
static ActionNode* healing_touch_on_party([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("healing touch on party",
/*P*/ NextAction::array(0, new NextAction("caster form"), nullptr),
/*A*/ nullptr,
/*C*/ NextAction::array(0, new NextAction("cat form"), nullptr));
return new ActionNode(
"healing touch on party",
/*P*/ { NextAction("caster form") },
/*A*/ {},
/*C*/ { NextAction("cat form") }
);
}
static ActionNode* regrowth_on_party([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("regrowth on party",
/*P*/ NextAction::array(0, new NextAction("caster form"), nullptr),
/*A*/ nullptr,
/*C*/ NextAction::array(0, new NextAction("cat form"), nullptr));
return new ActionNode(
"regrowth on party",
/*P*/ { NextAction("caster form") },
/*A*/ {},
/*C*/ { NextAction("cat form") }
);
}
static ActionNode* rejuvenation_on_party([[maybe_unused]] PlayerbotAI* botAI)
{
return new ActionNode("rejuvenation on party",
/*P*/ NextAction::array(0, new NextAction("caster form"), nullptr),
/*A*/ nullptr,
/*C*/ NextAction::array(0, new NextAction("cat form"), nullptr));
return new ActionNode(
"rejuvenation on party",
/*P*/ { NextAction("caster form") },
/*A*/ {},
/*C*/ { NextAction("cat form") }
);
}
};
@@ -121,12 +143,15 @@ OffhealDruidCatStrategy::OffhealDruidCatStrategy(PlayerbotAI* botAI) : FeralDrui
actionNodeFactories.Add(new OffhealDruidCatStrategyActionNodeFactory());
}
NextAction** OffhealDruidCatStrategy::getDefaultActions()
std::vector<NextAction> OffhealDruidCatStrategy::getDefaultActions()
{
return NextAction::array(0, new NextAction("mangle (cat)", ACTION_DEFAULT + 0.5f),
new NextAction("shred", ACTION_DEFAULT + 0.4f),
new NextAction("rake", ACTION_DEFAULT + 0.3f), new NextAction("melee", ACTION_DEFAULT),
new NextAction("cat form", ACTION_DEFAULT - 0.1f), nullptr);
return {
NextAction("mangle (cat)", ACTION_DEFAULT + 0.5f),
NextAction("shred", ACTION_DEFAULT + 0.4f),
NextAction("rake", ACTION_DEFAULT + 0.3f),
NextAction("melee", ACTION_DEFAULT),
NextAction("cat form", ACTION_DEFAULT - 0.1f)
};
}
void OffhealDruidCatStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
@@ -134,46 +159,149 @@ void OffhealDruidCatStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
FeralDruidStrategy::InitTriggers(triggers);
triggers.push_back(
new TriggerNode("cat form", NextAction::array(0, new NextAction("cat form", ACTION_HIGH + 8), nullptr)));
new TriggerNode(
"cat form",
{
NextAction("cat form", ACTION_HIGH + 8)
}
)
);
triggers.push_back(
new TriggerNode("savage roar", NextAction::array(0, new NextAction("savage roar", ACTION_HIGH + 7), nullptr)));
triggers.push_back(new TriggerNode("combo points available",
NextAction::array(0, new NextAction("rip", ACTION_HIGH + 6), nullptr)));
triggers.push_back(new TriggerNode(
"ferocious bite time", NextAction::array(0, new NextAction("ferocious bite", ACTION_HIGH + 5), nullptr)));
new TriggerNode(
"savage roar",
{
NextAction("savage roar", ACTION_HIGH + 7)
}
)
);
triggers.push_back(
new TriggerNode("target with combo points almost dead",
NextAction::array(0, new NextAction("ferocious bite", ACTION_HIGH + 4), nullptr)));
triggers.push_back(new TriggerNode("mangle (cat)",
NextAction::array(0, new NextAction("mangle (cat)", ACTION_HIGH + 3), nullptr)));
triggers.push_back(new TriggerNode("rake", NextAction::array(0, new NextAction("rake", ACTION_HIGH + 2), nullptr)));
triggers.push_back(new TriggerNode("almost full energy available",
NextAction::array(0, new NextAction("shred", ACTION_DEFAULT + 0.4f), nullptr)));
triggers.push_back(new TriggerNode("combo points not full",
NextAction::array(0, new NextAction("shred", ACTION_DEFAULT + 0.4f), nullptr)));
triggers.push_back(new TriggerNode(
"faerie fire (feral)", NextAction::array(0, new NextAction("faerie fire (feral)", ACTION_NORMAL), nullptr)));
triggers.push_back(new TriggerNode("enemy out of melee",
NextAction::array(0, new NextAction("feral charge - cat", ACTION_HIGH + 9),
new NextAction("dash", ACTION_HIGH + 8), nullptr)));
new TriggerNode(
"combo points available",
{
NextAction("rip", ACTION_HIGH + 6)
}
)
);
triggers.push_back(
new TriggerNode("medium aoe", NextAction::array(0, new NextAction("swipe (cat)", ACTION_HIGH + 3), nullptr)));
triggers.push_back(new TriggerNode(
"low energy", NextAction::array(0, new NextAction("tiger's fury", ACTION_NORMAL + 1), nullptr)));
triggers.push_back(new TriggerNode(
"party member critical health",
NextAction::array(0, new NextAction("regrowth on party", ACTION_CRITICAL_HEAL + 6),
new NextAction("healing touch on party", ACTION_CRITICAL_HEAL + 5), nullptr)));
triggers.push_back(new TriggerNode(
"party member low health",
NextAction::array(0, new NextAction("healing touch on party", ACTION_MEDIUM_HEAL + 5), nullptr)));
new TriggerNode(
"ferocious bite time",
{
NextAction("ferocious bite", ACTION_HIGH + 5)
}
)
);
triggers.push_back(
new TriggerNode("party member medium health",
NextAction::array(0, new NextAction("rejuvenation on party", ACTION_LIGHT_HEAL + 8), nullptr)));
triggers.push_back(new TriggerNode(
"party member to heal out of spell range",
NextAction::array(0, new NextAction("reach party member to heal", ACTION_EMERGENCY + 3), nullptr)));
new TriggerNode(
"target with combo points almost dead",
{
NextAction("ferocious bite", ACTION_HIGH + 4)
}
)
);
triggers.push_back(
new TriggerNode("low mana", NextAction::array(0, new NextAction("innervate", ACTION_HIGH + 4), nullptr)));
new TriggerNode(
"mangle (cat)",
{
NextAction("mangle (cat)", ACTION_HIGH + 3)
}
)
);
triggers.push_back(
new TriggerNode(
"rake",
{
NextAction("rake", ACTION_HIGH + 2)
}
)
);
triggers.push_back(
new TriggerNode(
"almost full energy available",
{
NextAction("shred", ACTION_DEFAULT + 0.4f)
}
)
);
triggers.push_back(
new TriggerNode(
"combo points not full",
{
NextAction("shred", ACTION_DEFAULT + 0.4f)
}
)
);
triggers.push_back(
new TriggerNode(
"faerie fire (feral)",
{
NextAction("faerie fire (feral)", ACTION_NORMAL)
}
)
);
triggers.push_back(
new TriggerNode(
"enemy out of melee",
{
NextAction("feral charge - cat", ACTION_HIGH + 9),
NextAction("dash", ACTION_HIGH + 8)
}
)
);
triggers.push_back(
new TriggerNode(
"medium aoe",
{
NextAction("swipe (cat)", ACTION_HIGH + 3)
}
)
);
triggers.push_back(
new TriggerNode(
"low energy",
{
NextAction("tiger's fury", ACTION_NORMAL + 1)
}
)
);
triggers.push_back(
new TriggerNode(
"party member critical health",
{
NextAction("regrowth on party", ACTION_CRITICAL_HEAL + 6),
NextAction("healing touch on party", ACTION_CRITICAL_HEAL + 5)
}
)
);
triggers.push_back(
new TriggerNode(
"party member low health",
{
NextAction("healing touch on party", ACTION_MEDIUM_HEAL + 5)
}
)
);
triggers.push_back(
new TriggerNode(
"party member medium health",
{
NextAction("rejuvenation on party", ACTION_LIGHT_HEAL + 8)
}
)
);
triggers.push_back(
new TriggerNode(
"party member to heal out of spell range",
{
NextAction("reach party member to heal", ACTION_EMERGENCY + 3)
}
)
);
triggers.push_back(
new TriggerNode(
"low mana",
{
NextAction("innervate", ACTION_HIGH + 4)
}
)
);
}

View File

@@ -17,7 +17,7 @@
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
std::string const getName() override { return "offheal"; }
NextAction** getDefaultActions() override;
std::vector<NextAction> getDefaultActions() override;
uint32 GetType() const override
{
return STRATEGY_TYPE_COMBAT | STRATEGY_TYPE_DPS | STRATEGY_TYPE_HEAL | STRATEGY_TYPE_MELEE;

View File

@@ -7,9 +7,9 @@ void WotlkDungeonANStrategy::InitTriggers(std::vector<TriggerNode*> &triggers)
// TODO: Add CC trigger while web wraps are casting?
// TODO: Bring healer closer than ranged dps to avoid fixates?
triggers.push_back(new TriggerNode("krik'thir web wrap",
NextAction::array(0, new NextAction("attack web wrap", ACTION_RAID + 5), nullptr)));
{ NextAction("attack web wrap", ACTION_RAID + 5) }));
triggers.push_back(new TriggerNode("krik'thir watchers",
NextAction::array(0, new NextAction("krik'thir priority", ACTION_RAID + 4), nullptr)));
{ NextAction("krik'thir priority", ACTION_RAID + 4) }));
// Hadronox
// The core AC triggers are very buggy with this boss, but default strat seems to play correctly
@@ -19,9 +19,9 @@ void WotlkDungeonANStrategy::InitTriggers(std::vector<TriggerNode*> &triggers)
// and cast time is instant so no way to check currently casting location.
// May need to hook boss AI.. might be able to just heal through it for now.
// triggers.push_back(new TriggerNode("anub'arak impale",
// NextAction::array(0, new NextAction("TODO", ACTION_MOVE + 5), nullptr)));
// { NextAction("TODO", ACTION_MOVE + 5) }));
triggers.push_back(new TriggerNode("anub'arak pound",
NextAction::array(0, new NextAction("dodge pound", ACTION_MOVE + 5), nullptr)));
{ NextAction("dodge pound", ACTION_MOVE + 5) }));
}
void WotlkDungeonANStrategy::InitMultipliers(std::vector<Multiplier*> &multipliers)

View File

@@ -8,12 +8,12 @@ void WotlkDungeonCoSStrategy::InitTriggers(std::vector<TriggerNode*> &triggers)
// Salramm the Fleshcrafter
triggers.push_back(new TriggerNode("explode ghoul",
NextAction::array(0, new NextAction("explode ghoul spread", ACTION_MOVE + 5), nullptr)));
{ NextAction("explode ghoul spread", ACTION_MOVE + 5) }));
// Chrono-Lord Epoch
// Not sure if this actually works, I think I've seen him charge melee characters..?
triggers.push_back(new TriggerNode("epoch ranged",
NextAction::array(0, new NextAction("epoch stack", ACTION_MOVE + 5), nullptr)));
{ NextAction("epoch stack", ACTION_MOVE + 5) }));
// Mal'Ganis

View File

@@ -5,32 +5,32 @@ void WotlkDungeonDTKStrategy::InitTriggers(std::vector<TriggerNode*> &triggers)
{
// Trollgore
triggers.push_back(new TriggerNode("corpse explode",
NextAction::array(0, new NextAction("corpse explode spread", ACTION_MOVE + 5), nullptr)));
{ NextAction("corpse explode spread", ACTION_MOVE + 5) }));
// Novos the Summoner
// TODO: Can be improved - it's a pretty easy fight but complex to program, revisit if needed
triggers.push_back(new TriggerNode("arcane field",
NextAction::array(0, new NextAction("avoid arcane field", ACTION_MOVE + 5), nullptr)));
{ NextAction("avoid arcane field", ACTION_MOVE + 5) }));
triggers.push_back(new TriggerNode("arcane field",
NextAction::array(0, new NextAction("novos positioning", ACTION_MOVE + 4), nullptr)));
{ NextAction("novos positioning", ACTION_MOVE + 4) }));
triggers.push_back(new TriggerNode("arcane field",
NextAction::array(0, new NextAction("novos target priority", ACTION_NORMAL + 1), nullptr)));
{ NextAction("novos target priority", ACTION_NORMAL + 1) }));
// King Dred
// TODO: Fear ward / tremor totem, or general anti-fear strat development
//The Prophet Tharon'ja
triggers.push_back(new TriggerNode("gift of tharon'ja",
NextAction::array(0, new NextAction("touch of life", ACTION_NORMAL + 5), nullptr)));
{ NextAction("touch of life", ACTION_NORMAL + 5) }));
triggers.push_back(new TriggerNode("gift of tharon'ja",
NextAction::array(0, new NextAction("bone armor", ACTION_NORMAL + 4), nullptr)));
{ NextAction("bone armor", ACTION_NORMAL + 4) }));
// Run ranged chars (who would normally stand at range) into melee, to dps in skeleton form
triggers.push_back(new TriggerNode("tharon'ja out of melee",
NextAction::array(0, new NextAction("reach melee", ACTION_NORMAL + 3), nullptr)));
{ NextAction("reach melee", ACTION_NORMAL + 3) }));
triggers.push_back(new TriggerNode("gift of tharon'ja",
NextAction::array(0, new NextAction("taunt", ACTION_NORMAL + 2), nullptr)));
{ NextAction("taunt", ACTION_NORMAL + 2) }));
triggers.push_back(new TriggerNode("gift of tharon'ja",
NextAction::array(0, new NextAction("slaying strike", ACTION_NORMAL + 2), nullptr)));
{ NextAction("slaying strike", ACTION_NORMAL + 2) }));
}
void WotlkDungeonDTKStrategy::InitMultipliers(std::vector<Multiplier*> &multipliers)

View File

@@ -4,17 +4,16 @@
void WotlkDungeonFoSStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(new TriggerNode("move from bronjahm",
NextAction::array(0, new NextAction("move from bronjahm", ACTION_MOVE + 5), nullptr)));
{ NextAction("move from bronjahm", ACTION_MOVE + 5) }));
triggers.push_back(new TriggerNode("switch to soul fragment",
NextAction::array(0, new NextAction("attack corrupted soul fragment", ACTION_RAID + 2), nullptr)));
{ NextAction("attack corrupted soul fragment", ACTION_RAID + 2) }));
triggers.push_back(new TriggerNode("bronjahm position",
NextAction::array(0, new NextAction("bronjahm group position", ACTION_RAID + 1), nullptr)));
{ NextAction("bronjahm group position", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("devourer of souls",
NextAction::array(0, new NextAction("devourer of souls", ACTION_RAID + 1), nullptr)));
{ NextAction("devourer of souls", ACTION_RAID + 1) }));
}
void WotlkDungeonFoSStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers)
{
multipliers.push_back(new BronjahmMultiplier(botAI));
//multipliers.push_back(new AttackFragmentMultiplier(botAI));
}

View File

@@ -11,13 +11,13 @@ void WotlkDungeonGDStrategy::InitTriggers(std::vector<TriggerNode*> &triggers)
// TODO: Might need to add target priority for heroic on the snakes or to burn down boss.
// Will re-test in heroic, decent dps groups should be able to blast him down with no funky strats.
triggers.push_back(new TriggerNode("poison nova",
NextAction::array(0, new NextAction("avoid poison nova", ACTION_RAID + 5), nullptr)));
{ NextAction("avoid poison nova", ACTION_RAID + 5) }));
triggers.push_back(new TriggerNode("snake wrap",
NextAction::array(0, new NextAction("attack snake wrap", ACTION_RAID + 4), nullptr)));
{ NextAction("attack snake wrap", ACTION_RAID + 4) }));
// Gal'darah
triggers.push_back(new TriggerNode("whirling slash",
NextAction::array(0, new NextAction("avoid whirling slash", ACTION_RAID + 5), nullptr)));
{ NextAction("avoid whirling slash", ACTION_RAID + 5) }));
// Eck the Ferocious (Heroic only)
}

View File

@@ -5,30 +5,30 @@ void WotlkDungeonHoLStrategy::InitTriggers(std::vector<TriggerNode*> &triggers)
{
// General Bjarngrim
triggers.push_back(new TriggerNode("stormforged lieutenant",
NextAction::array(0, new NextAction("bjarngrim target", ACTION_RAID + 5), nullptr)));
{ NextAction("bjarngrim target", ACTION_RAID + 5) }));
triggers.push_back(new TriggerNode("whirlwind",
NextAction::array(0, new NextAction("avoid whirlwind", ACTION_RAID + 4), nullptr)));
{ NextAction("avoid whirlwind", ACTION_RAID + 4) }));
// Volkhan
triggers.push_back(new TriggerNode("volkhan",
NextAction::array(0, new NextAction("volkhan target", ACTION_RAID + 5), nullptr)));
{ NextAction("volkhan target", ACTION_RAID + 5) }));
// Ionar
triggers.push_back(new TriggerNode("ionar disperse",
NextAction::array(0, new NextAction("disperse position", ACTION_MOVE + 5), nullptr)));
{ NextAction("disperse position", ACTION_MOVE + 5) }));
triggers.push_back(new TriggerNode("ionar tank aggro",
NextAction::array(0, new NextAction("ionar tank position", ACTION_MOVE + 4), nullptr)));
{ NextAction("ionar tank position", ACTION_MOVE + 4) }));
triggers.push_back(new TriggerNode("static overload",
NextAction::array(0, new NextAction("static overload spread", ACTION_MOVE + 3), nullptr)));
{ NextAction("static overload spread", ACTION_MOVE + 3) }));
// TODO: Targeted player can dodge the ball, but a single player soaking it isn't too bad to heal
triggers.push_back(new TriggerNode("ball lightning",
NextAction::array(0, new NextAction("ball lightning spread", ACTION_MOVE + 2), nullptr)));
{ NextAction("ball lightning spread", ACTION_MOVE + 2) }));
// Loken
triggers.push_back(new TriggerNode("lightning nova",
NextAction::array(0, new NextAction("avoid lightning nova", ACTION_MOVE + 5), nullptr)));
{ NextAction("avoid lightning nova", ACTION_MOVE + 5) }));
triggers.push_back(new TriggerNode("loken ranged",
NextAction::array(0, new NextAction("loken stack", ACTION_MOVE + 4), nullptr)));
{ NextAction("loken stack", ACTION_MOVE + 4) }));
}
void WotlkDungeonHoLStrategy::InitMultipliers(std::vector<Multiplier*> &multipliers)

View File

@@ -9,7 +9,7 @@ void WotlkDungeonHoSStrategy::InitTriggers(std::vector<TriggerNode*> &triggers)
// Krystallus
// TODO: I think bots need to dismiss pets on this, or they nuke players they are standing close to
triggers.push_back(new TriggerNode("ground slam",
NextAction::array(0, new NextAction("shatter spread", ACTION_RAID + 5), nullptr)));
{ NextAction("shatter spread", ACTION_RAID + 5) }));
// Tribunal of Ages
// Seems fine, maybe add focus targeting strat if needed on heroic.
@@ -19,7 +19,7 @@ void WotlkDungeonHoSStrategy::InitTriggers(std::vector<TriggerNode*> &triggers)
// Sjonnir The Ironshaper
// Possibly tank in place in the middle of the room, assign a dps to adds?
triggers.push_back(new TriggerNode("lightning ring",
NextAction::array(0, new NextAction("avoid lightning ring", ACTION_RAID + 5), nullptr)));
{ NextAction("avoid lightning ring", ACTION_RAID + 5) }));
}
void WotlkDungeonHoSStrategy::InitMultipliers(std::vector<Multiplier*> &multipliers)

View File

@@ -7,39 +7,39 @@ void WotlkDungeonNexStrategy::InitTriggers(std::vector<TriggerNode*> &triggers)
// or
// Alliance Commander (Horde N)/Commander Stoutbeard (Horde H)
triggers.push_back(new TriggerNode("faction commander whirlwind",
NextAction::array(0, new NextAction("move from whirlwind", ACTION_MOVE + 5), nullptr)));
{ NextAction("move from whirlwind", ACTION_MOVE + 5) }));
// TODO: Handle fear? (tremor totems, fear ward etc.)
// Grand Magus Telestra
triggers.push_back(new TriggerNode("telestra firebomb",
NextAction::array(0, new NextAction("firebomb spread", ACTION_MOVE + 5), nullptr)));
{ NextAction("firebomb spread", ACTION_MOVE + 5) }));
triggers.push_back(new TriggerNode("telestra split phase",
NextAction::array(0, new NextAction("telestra split target", ACTION_RAID + 1), nullptr)));
{ NextAction("telestra split target", ACTION_RAID + 1) }));
// TODO: Add priority interrupt on the frost split's Blizzard casts
// Anomalus
triggers.push_back(new TriggerNode("chaotic rift",
NextAction::array(0, new NextAction("chaotic rift target", ACTION_RAID + 1), nullptr)));
{ NextAction("chaotic rift target", ACTION_RAID + 1) }));
// Ormorok the Tree-Shaper
// Tank trigger to stack inside boss. Can also add return action to prevent boss repositioning
// if it becomes too much of a problem. He usually dies before he's up against a wall though
triggers.push_back(new TriggerNode("ormorok spikes",
NextAction::array(0, new NextAction("dodge spikes", ACTION_MOVE + 5), nullptr)));
{ NextAction("dodge spikes", ACTION_MOVE + 5) }));
// Non-tank trigger to stack. Avoiding the spikes at range is.. harder than it seems.
// TODO: This turns hunters into melee marshmallows, have not come up with a better solution yet
triggers.push_back(new TriggerNode("ormorok stack",
NextAction::array(0, new NextAction("dodge spikes", ACTION_MOVE + 5), nullptr)));
{ NextAction("dodge spikes", ACTION_MOVE + 5) }));
// TODO: Add handling for spell reflect... best to spam low level/weak spells but don't want
// to hardcode spells per class, might be difficult to dynamically generate this.
// Will revisit if I find my altbots killing themselves in heroic, just heal through it for now
// Keristrasza
triggers.push_back(new TriggerNode("intense cold",
NextAction::array(0, new NextAction("intense cold jump", ACTION_MOVE + 5), nullptr)));
{ NextAction("intense cold jump", ACTION_MOVE + 5) }));
// Flank dragon positioning
triggers.push_back(new TriggerNode("keristrasza positioning",
NextAction::array(0, new NextAction("rear flank", ACTION_MOVE + 4), nullptr)));
{ NextAction("rear flank", ACTION_MOVE + 4) }));
// TODO: Add frost resist aura for paladins?
}

View File

@@ -6,28 +6,28 @@ void WotlkDungeonOccStrategy::InitTriggers(std::vector<TriggerNode*> &triggers)
// Drakos the Interrogator
// TODO: May need work, TBA.
triggers.push_back(new TriggerNode("unstable sphere",
NextAction::array(0, new NextAction("avoid unstable sphere", ACTION_MOVE + 5), nullptr)));
{ NextAction("avoid unstable sphere", ACTION_MOVE + 5) }));
// DRAKES
triggers.push_back(new TriggerNode("drake mount",
NextAction::array(0, new NextAction("mount drake", ACTION_RAID + 5), nullptr)));
{ NextAction("mount drake", ACTION_RAID + 5) }));
triggers.push_back(new TriggerNode("drake dismount",
NextAction::array(0, new NextAction("dismount drake", ACTION_RAID + 5), nullptr)));
{ NextAction("dismount drake", ACTION_RAID + 5) }));
triggers.push_back(new TriggerNode("group flying",
NextAction::array(0, new NextAction("occ fly drake", ACTION_NORMAL + 1), nullptr)));
{ NextAction("occ fly drake", ACTION_NORMAL + 1) }));
triggers.push_back(new TriggerNode("drake combat",
NextAction::array(0, new NextAction("occ drake attack", ACTION_NORMAL + 5), nullptr)));
{ NextAction("occ drake attack", ACTION_NORMAL + 5) }));
// Varos Cloudstrider
// Seems to be no way to identify the marked cores, may need to hook boss AI..
// triggers.push_back(new TriggerNode("varos cloudstrider",
// NextAction::array(0, new NextAction("avoid energize cores", ACTION_RAID + 5), nullptr)));
// { NextAction("avoid energize cores", ACTION_RAID + 5) }));
// Mage-Lord Urom
triggers.push_back(new TriggerNode("arcane explosion",
NextAction::array(0, new NextAction("avoid arcane explosion", ACTION_MOVE + 5), nullptr)));
{ NextAction("avoid arcane explosion", ACTION_MOVE + 5) }));
triggers.push_back(new TriggerNode("time bomb",
NextAction::array(0, new NextAction("time bomb spread", ACTION_MOVE + 4), nullptr)));
{ NextAction("time bomb spread", ACTION_MOVE + 4) }));
// Ley-Guardian Eregos
}

View File

@@ -5,7 +5,7 @@ void WotlkDungeonOKStrategy::InitTriggers(std::vector<TriggerNode*> &triggers)
{
// Elder Nadox
triggers.push_back(new TriggerNode("nadox guardian",
NextAction::array(0, new NextAction("attack nadox guardian", ACTION_RAID + 5), nullptr)));
{ NextAction("attack nadox guardian", ACTION_RAID + 5) }));
// Prince Taldaram
// Flame Orb spawns in melee, doesn't have a clear direction until it starts moving.
@@ -14,13 +14,13 @@ void WotlkDungeonOKStrategy::InitTriggers(std::vector<TriggerNode*> &triggers)
// Jedoga Shadowseeker
triggers.push_back(new TriggerNode("jedoga volunteer",
NextAction::array(0, new NextAction("attack jedoga volunteer", ACTION_RAID + 5), nullptr)));
{ NextAction("attack jedoga volunteer", ACTION_RAID + 5) }));
// Herald Volazj
// Trash mobs before him have a big telegraphed shadow crash spell,
// this can be avoided and is intended to be dodged
triggers.push_back(new TriggerNode("shadow crash",
NextAction::array(0, new NextAction("avoid shadow crash", ACTION_MOVE + 5), nullptr)));
{ NextAction("avoid shadow crash", ACTION_MOVE + 5) }));
// Volazj is not implemented properly in AC, insanity phase does nothing.
// Amanitar (Heroic Only)

View File

@@ -4,14 +4,13 @@
void WotlkDungeonPoSStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(new TriggerNode("ick and krick",
NextAction::array(0, new NextAction("ick and krick", ACTION_RAID + 5), nullptr)));
{ NextAction("ick and krick", ACTION_RAID + 5) }));
triggers.push_back(new TriggerNode("tyrannus",
NextAction::array(0, new NextAction("tyrannus", ACTION_RAID + 5), nullptr)));
{ NextAction("tyrannus", ACTION_RAID + 5) }));
}
void WotlkDungeonPoSStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers)
{
multipliers.push_back(new IckAndKrickMultiplier(botAI));
//multipliers.push_back(new AttackFragmentMultiplier(botAI));
}

View File

@@ -4,19 +4,18 @@
void WotlkDungeonToCStrategy::InitTriggers(std::vector<TriggerNode*> &triggers)
{
triggers.push_back(new TriggerNode("toc lance",
NextAction::array(0, new NextAction("toc lance", ACTION_RAID + 5), nullptr)));
{ NextAction("toc lance", ACTION_RAID + 5) }));
triggers.push_back(new TriggerNode("toc ue lance",
NextAction::array(0, new NextAction("toc ue lance", ACTION_RAID + 2), nullptr)));
{ NextAction("toc ue lance", ACTION_RAID + 2) }));
triggers.push_back(new TriggerNode("toc mount near",
NextAction::array(0, new NextAction("toc mount", ACTION_RAID + 4), nullptr)));
{ NextAction("toc mount", ACTION_RAID + 4) }));
triggers.push_back(new TriggerNode("toc mounted",
NextAction::array(0, new NextAction("toc mounted", ACTION_RAID + 6), nullptr)));
{ NextAction("toc mounted", ACTION_RAID + 6) }));
triggers.push_back(new TriggerNode("toc eadric",
NextAction::array(0, new NextAction("toc eadric", ACTION_RAID + 3), nullptr)));
{ NextAction("toc eadric", ACTION_RAID + 3) }));
}
void WotlkDungeonToCStrategy::InitMultipliers(std::vector<Multiplier*> &multipliers)
{
//multipliers.push_back(new toc...); if needed in the future
}

View File

@@ -5,32 +5,32 @@ void WotlkDungeonUKStrategy::InitTriggers(std::vector<TriggerNode*> &triggers)
{
// Prince Keleseth
triggers.push_back(new TriggerNode("keleseth frost tomb",
NextAction::array(0, new NextAction("attack frost tomb", ACTION_RAID + 1), nullptr)));
{ NextAction("attack frost tomb", ACTION_RAID + 1) }));
// Skarvald the Constructor & Dalronn the Controller
triggers.push_back(new TriggerNode("dalronn priority",
NextAction::array(0, new NextAction("attack dalronn", ACTION_RAID + 1), nullptr)));
{ NextAction("attack dalronn", ACTION_RAID + 1) }));
// Ingvar the Plunderer
// Doesn't work yet, this action doesn't get processed until the existing cast finishes
// triggers.push_back(new TriggerNode("ingvar staggering roar",
// NextAction::array(0, new NextAction("ingvar stop casting", ACTION_RAID + 1), nullptr)));
// { NextAction("ingvar stop casting", ACTION_RAID + 1) }));
// No easy way to check LoS here, the pillars do not seem to count as gameobjects.
// Not implemented for now, unsure if this is needed as a good group can probably burst through the boss
// and just eat the debuff.
// triggers.push_back(new TriggerNode("ingvar dreadful roar",
// NextAction::array(0, new NextAction("ingvar hide los", ACTION_RAID + 1), nullptr)));
// { NextAction("ingvar hide los", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("ingvar smash tank",
NextAction::array(0, new NextAction("ingvar dodge smash", ACTION_MOVE + 5), nullptr)));
{ NextAction("ingvar dodge smash", ACTION_MOVE + 5) }));
triggers.push_back(new TriggerNode("ingvar smash tank return",
NextAction::array(0, new NextAction("ingvar smash return", ACTION_MOVE + 5), nullptr)));
{ NextAction("ingvar smash return", ACTION_MOVE + 5) }));
// Buggy... if not behind target, ai can get stuck running towards and away from target.
// I think for ranged chars, a custom action should be added that doesn't attempt to run into melee.
// This is a bandaid for now, needs to be improved.
triggers.push_back(new TriggerNode("not behind ingvar",
NextAction::array(0, new NextAction("set behind", ACTION_MOVE + 1), nullptr)));
{ NextAction("set behind", ACTION_MOVE + 1) }));
}

View File

@@ -10,14 +10,14 @@ void WotlkDungeonUPStrategy::InitTriggers(std::vector<TriggerNode*> &triggers)
// Skadi the Ruthless
// TODO: Harpoons launchable via GameObject. For now players should do them
triggers.push_back(new TriggerNode("freezing cloud",
NextAction::array(0, new NextAction("avoid freezing cloud", ACTION_RAID + 5), nullptr)));
{ NextAction("avoid freezing cloud", ACTION_RAID + 5) }));
triggers.push_back(new TriggerNode("skadi whirlwind",
NextAction::array(0, new NextAction("avoid skadi whirlwind", ACTION_RAID + 4), nullptr)));
{ NextAction("avoid skadi whirlwind", ACTION_RAID + 4) }));
// King Ymiron
// May need to avoid orb.. unclear if the generic avoid AoE does this well
triggers.push_back(new TriggerNode("ymiron bane",
NextAction::array(0, new NextAction("stop attack", ACTION_RAID + 5), nullptr)));
{ NextAction("stop attack", ACTION_RAID + 5) }));
}
void WotlkDungeonUPStrategy::InitMultipliers(std::vector<Multiplier*> &multipliers)

View File

@@ -6,14 +6,14 @@ void WotlkDungeonVHStrategy::InitTriggers(std::vector<TriggerNode*> &triggers)
// Erekem
// This boss has many purgable buffs, purging/dispels could be merged into generic strats though
triggers.push_back(new TriggerNode("erekem target",
NextAction::array(0, new NextAction("attack erekem", ACTION_RAID + 1), nullptr)));
{ NextAction("attack erekem", ACTION_RAID + 1) }));
// Moragg
// TODO: This guy has Optic Link which may require moving, add if needed
// Ichoron
triggers.push_back(new TriggerNode("ichoron target",
NextAction::array(0, new NextAction("attack ichor globule", ACTION_RAID + 1), nullptr)));
{ NextAction("attack ichor globule", ACTION_RAID + 1) }));
// Xevozz
// TODO: Revisit in heroics, waypoints back and forth on stairs. Need to test with double beacon spawn
@@ -23,13 +23,13 @@ void WotlkDungeonVHStrategy::InitTriggers(std::vector<TriggerNode*> &triggers)
// Zuramat the Obliterator
triggers.push_back(new TriggerNode("shroud of darkness",
NextAction::array(0, new NextAction("stop attack", ACTION_HIGH + 5), nullptr)));
{ NextAction("stop attack", ACTION_HIGH + 5) }));
triggers.push_back(new TriggerNode("void shift",
NextAction::array(0, new NextAction("attack void sentry", ACTION_RAID + 1), nullptr)));
{ NextAction("attack void sentry", ACTION_RAID + 1) }));
// Cyanigosa
triggers.push_back(new TriggerNode("cyanigosa positioning",
NextAction::array(0, new NextAction("rear flank", ACTION_MOVE + 5), nullptr)));
{ NextAction("rear flank", ACTION_MOVE + 5) }));
}
void WotlkDungeonVHStrategy::InitMultipliers(std::vector<Multiplier*> &multipliers)

View File

@@ -10,5 +10,5 @@
void AttackEnemyPlayersStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(new TriggerNode("enemy player near",
NextAction::array(0, new NextAction("attack enemy player", 55.0f), nullptr)));
{ NextAction("attack enemy player", 55.0f) }));
}

View File

@@ -9,83 +9,79 @@
void BGStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(new TriggerNode("often", NextAction::array(0, new NextAction("bg join", relevance), nullptr)));
triggers.push_back(new TriggerNode("bg invite active", NextAction::array(0, new NextAction("bg status check", relevance), nullptr)));
triggers.push_back(new TriggerNode("timer", NextAction::array(0, new NextAction("bg strategy check", relevance), nullptr)));
triggers.push_back(new TriggerNode("often", { NextAction("bg join", relevance)}));
triggers.push_back(new TriggerNode("bg invite active", { NextAction("bg status check", relevance)}));
triggers.push_back(new TriggerNode("timer", { NextAction("bg strategy check", relevance)}));
}
BGStrategy::BGStrategy(PlayerbotAI* botAI) : PassTroughStrategy(botAI) {}
void BattlegroundStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(new TriggerNode("bg waiting", NextAction::array(0, new NextAction("bg move to start", ACTION_BG), nullptr)));
triggers.push_back(new TriggerNode("bg active", NextAction::array(0, new NextAction("bg move to objective", ACTION_BG), nullptr)));
triggers.push_back(new TriggerNode("often", NextAction::array(0, new NextAction("bg check objective", ACTION_BG + 1), nullptr)));
triggers.push_back(new TriggerNode("dead", NextAction::array(0, new NextAction("bg reset objective force", ACTION_EMERGENCY), nullptr)));
// triggers.push_back(new TriggerNode("enemy flagcarrier near", NextAction::array(0, new NextAction("attack enemy
// flag carrier", 80.0f), nullptr))); triggers.push_back(new TriggerNode("team flagcarrier near",
// NextAction::array(0, new NextAction("bg protect fc", 40.0f), nullptr))); triggers.push_back(new
// TriggerNode("player has flag", NextAction::array(0, new NextAction("bg move to objective", 90.0f), nullptr)));
triggers.push_back(new TriggerNode("bg waiting", { NextAction("bg move to start", ACTION_BG)}));
triggers.push_back(new TriggerNode("bg active", { NextAction("bg move to objective", ACTION_BG)}));
triggers.push_back(new TriggerNode("often", { NextAction("bg check objective", ACTION_BG + 1)}));
triggers.push_back(new TriggerNode("dead", { NextAction("bg reset objective force", ACTION_EMERGENCY)}));
}
void WarsongStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(new TriggerNode("bg active", NextAction::array(0, new NextAction("bg check flag", ACTION_EMERGENCY ), nullptr)));
triggers.push_back(new TriggerNode("enemy flagcarrier near", NextAction::array(0, new NextAction("attack enemy flag carrier", ACTION_RAID + 1.0f), nullptr)));
triggers.push_back(new TriggerNode("team flagcarrier near", NextAction::array(0, new NextAction("bg protect fc", ACTION_RAID), nullptr)));
triggers.push_back(new TriggerNode("often", NextAction::array(0, new NextAction("bg use buff", ACTION_BG), nullptr)));
triggers.push_back(new TriggerNode("low health", NextAction::array(0, new NextAction("bg use buff", ACTION_MOVE), nullptr)));
triggers.push_back(new TriggerNode("low mana", NextAction::array(0, new NextAction("bg use buff", ACTION_MOVE), nullptr)));
triggers.push_back(new TriggerNode("player has flag", NextAction::array(0, new NextAction("bg move to objective", ACTION_EMERGENCY), nullptr)));
triggers.push_back(new TriggerNode("timer bg", NextAction::array(0, new NextAction("bg reset objective force", ACTION_EMERGENCY), nullptr)));
triggers.push_back(new TriggerNode("bg active", { NextAction("bg check flag", ACTION_EMERGENCY )}));
triggers.push_back(new TriggerNode("enemy flagcarrier near", { NextAction("attack enemy flag carrier", ACTION_RAID + 1.0f)}));
triggers.push_back(new TriggerNode("team flagcarrier near", { NextAction("bg protect fc", ACTION_RAID)}));
triggers.push_back(new TriggerNode("often", { NextAction("bg use buff", ACTION_BG)}));
triggers.push_back(new TriggerNode("low health", { NextAction("bg use buff", ACTION_MOVE)}));
triggers.push_back(new TriggerNode("low mana", { NextAction("bg use buff", ACTION_MOVE)}));
triggers.push_back(new TriggerNode("player has flag", { NextAction("bg move to objective", ACTION_EMERGENCY)}));
triggers.push_back(new TriggerNode("timer bg", { NextAction("bg reset objective force", ACTION_EMERGENCY)}));
}
void AlteracStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(new TriggerNode("alliance no snowfall gy", NextAction::array(0, new NextAction("bg move to objective", ACTION_EMERGENCY), nullptr)));
triggers.push_back(new TriggerNode("timer bg", NextAction::array(0, new NextAction("bg reset objective force", ACTION_EMERGENCY), nullptr)));
triggers.push_back(new TriggerNode("alliance no snowfall gy", { NextAction("bg move to objective", ACTION_EMERGENCY)}));
triggers.push_back(new TriggerNode("timer bg", { NextAction("bg reset objective force", ACTION_EMERGENCY)}));
}
void ArathiStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(new TriggerNode("bg active", NextAction::array(0, new NextAction("bg check flag", ACTION_EMERGENCY), nullptr)));
triggers.push_back(new TriggerNode("often", NextAction::array(0, new NextAction("bg use buff", ACTION_BG), nullptr)));
triggers.push_back(new TriggerNode("low health", NextAction::array(0, new NextAction("bg use buff", ACTION_MOVE), nullptr)));
triggers.push_back(new TriggerNode("low mana", NextAction::array(0, new NextAction("bg use buff", ACTION_MOVE), nullptr)));
triggers.push_back(new TriggerNode("bg active", { NextAction("bg check flag", ACTION_EMERGENCY)}));
triggers.push_back(new TriggerNode("often", { NextAction("bg use buff", ACTION_BG)}));
triggers.push_back(new TriggerNode("low health", { NextAction("bg use buff", ACTION_MOVE)}));
triggers.push_back(new TriggerNode("low mana", { NextAction("bg use buff", ACTION_MOVE)}));
}
void EyeStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(new TriggerNode("bg active", NextAction::array(0, new NextAction("bg check flag", ACTION_EMERGENCY), nullptr)));
triggers.push_back(new TriggerNode("often", NextAction::array(0, new NextAction("bg use buff", ACTION_BG), nullptr)));
triggers.push_back(new TriggerNode("low health", NextAction::array(0, new NextAction("bg use buff", ACTION_MOVE), nullptr)));
triggers.push_back(new TriggerNode("low mana", NextAction::array(0, new NextAction("bg use buff", ACTION_MOVE), nullptr)));
triggers.push_back(new TriggerNode("enemy flagcarrier near", NextAction::array(0, new NextAction("attack enemy flag carrier", ACTION_RAID), nullptr)));
triggers.push_back(new TriggerNode("player has flag",NextAction::array(0, new NextAction("bg move to objective", ACTION_EMERGENCY), nullptr)));
triggers.push_back(new TriggerNode("bg active", { NextAction("bg check flag", ACTION_EMERGENCY)}));
triggers.push_back(new TriggerNode("often", { NextAction("bg use buff", ACTION_BG)}));
triggers.push_back(new TriggerNode("low health", { NextAction("bg use buff", ACTION_MOVE)}));
triggers.push_back(new TriggerNode("low mana", { NextAction("bg use buff", ACTION_MOVE)}));
triggers.push_back(new TriggerNode("enemy flagcarrier near", { NextAction("attack enemy flag carrier", ACTION_RAID)}));
triggers.push_back(new TriggerNode("player has flag",{ NextAction("bg move to objective", ACTION_EMERGENCY)}));
}
//TODO: Do Priorities
void IsleStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(new TriggerNode("bg active", NextAction::array(0, new NextAction("bg check flag", ACTION_MOVE), nullptr)));
triggers.push_back(new TriggerNode("timer", NextAction::array(0, new NextAction("enter vehicle", ACTION_MOVE + 8.0f), nullptr)));
triggers.push_back(new TriggerNode("random", NextAction::array(0, new NextAction("leave vehicle", ACTION_MOVE + 7.0f), nullptr)));
triggers.push_back(new TriggerNode("in vehicle", NextAction::array(0, new NextAction("hurl boulder", ACTION_MOVE + 9.0f), nullptr)));
triggers.push_back(new TriggerNode("in vehicle", NextAction::array(0, new NextAction("fire cannon", ACTION_MOVE + 9.0f), nullptr)));
triggers.push_back(new TriggerNode("in vehicle", NextAction::array(0, new NextAction("napalm", ACTION_MOVE + 9.0f), nullptr)));
triggers.push_back(new TriggerNode("enemy is close", NextAction::array(0, new NextAction("steam blast", ACTION_MOVE + 9.0f), nullptr)));
triggers.push_back(new TriggerNode("in vehicle", NextAction::array(0, new NextAction("ram", ACTION_MOVE + 9.0f), nullptr)));
triggers.push_back(new TriggerNode("enemy is close", NextAction::array(0, new NextAction("ram", ACTION_MOVE + 9.1f), nullptr)));
triggers.push_back(new TriggerNode("enemy out of melee", NextAction::array(0, new NextAction("steam rush", ACTION_MOVE + 9.2f), nullptr)));
triggers.push_back(new TriggerNode("in vehicle", NextAction::array(0, new NextAction("incendiary rocket", ACTION_MOVE + 9.0f), nullptr)));
triggers.push_back(new TriggerNode("in vehicle", NextAction::array(0, new NextAction("rocket blast", ACTION_MOVE + 9.0f), nullptr)));
triggers.push_back(new TriggerNode("bg active", { NextAction("bg check flag", ACTION_MOVE)}));
triggers.push_back(new TriggerNode("timer", { NextAction("enter vehicle", ACTION_MOVE + 8.0f)}));
triggers.push_back(new TriggerNode("random", { NextAction("leave vehicle", ACTION_MOVE + 7.0f)}));
triggers.push_back(new TriggerNode("in vehicle", { NextAction("hurl boulder", ACTION_MOVE + 9.0f)}));
triggers.push_back(new TriggerNode("in vehicle", { NextAction("fire cannon", ACTION_MOVE + 9.0f)}));
triggers.push_back(new TriggerNode("in vehicle", { NextAction("napalm", ACTION_MOVE + 9.0f)}));
triggers.push_back(new TriggerNode("enemy is close", { NextAction("steam blast", ACTION_MOVE + 9.0f)}));
triggers.push_back(new TriggerNode("in vehicle", { NextAction("ram", ACTION_MOVE + 9.0f)}));
triggers.push_back(new TriggerNode("enemy is close", { NextAction("ram", ACTION_MOVE + 9.1f)}));
triggers.push_back(new TriggerNode("enemy out of melee", { NextAction("steam rush", ACTION_MOVE + 9.2f)}));
triggers.push_back(new TriggerNode("in vehicle", { NextAction("incendiary rocket", ACTION_MOVE + 9.0f)}));
triggers.push_back(new TriggerNode("in vehicle", { NextAction("rocket blast", ACTION_MOVE + 9.0f)}));
// this is bugged: it doesn't work, and stops glaive throw working (which is needed to take down gate)
// triggers.push_back(new TriggerNode("in vehicle", NextAction::array(0, new NextAction("blade salvo", ACTION_MOVE + 9.0f), nullptr)));
triggers.push_back(new TriggerNode("in vehicle", NextAction::array(0, new NextAction("glaive throw", ACTION_MOVE + 9.0f), nullptr)));
// triggers.push_back(new TriggerNode("in vehicle", { NextAction("blade salvo", ACTION_MOVE + 9.0f)}));
triggers.push_back(new TriggerNode("in vehicle", { NextAction("glaive throw", ACTION_MOVE + 9.0f)}));
}
void ArenaStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(
new TriggerNode("no possible targets", NextAction::array(0, new NextAction("arena tactics", ACTION_BG), nullptr)));
new TriggerNode("no possible targets", { NextAction("arena tactics", ACTION_BG)}));
}

View File

@@ -16,9 +16,9 @@ private:
static ActionNode* tank_attack_chat_shortcut(PlayerbotAI* botAI)
{
return new ActionNode("tank attack chat shortcut",
/*P*/ nullptr,
/*A*/ nullptr,
/*C*/ NextAction::array(0, new NextAction("attack my target", 100.0f), nullptr));
/*P*/ {},
/*A*/ {},
/*C*/ { NextAction("attack my target", 100.0f) });
}
};
@@ -26,88 +26,86 @@ void ChatCommandHandlerStrategy::InitTriggers(std::vector<TriggerNode*>& trigger
{
PassTroughStrategy::InitTriggers(triggers);
triggers.push_back(new TriggerNode("rep", NextAction::array(0, new NextAction("reputation", relevance), nullptr)));
triggers.push_back(new TriggerNode("q", NextAction::array(0, new NextAction("query quest", relevance),
new NextAction("query item usage", relevance), nullptr)));
triggers.push_back(new TriggerNode("add all loot", NextAction::array(0, new NextAction("add all loot", relevance),
new NextAction("loot", relevance), nullptr)));
triggers.push_back(new TriggerNode("u", NextAction::array(0, new NextAction("use", relevance), nullptr)));
triggers.push_back(new TriggerNode("c", NextAction::array(0, new NextAction("item count", relevance), nullptr)));
triggers.push_back(new TriggerNode("rep", { NextAction("reputation", relevance) }));
triggers.push_back(new TriggerNode("q", { NextAction("query quest", relevance),
NextAction("query item usage", relevance) }));
triggers.push_back(new TriggerNode("add all loot", { NextAction("add all loot", relevance),
NextAction("loot", relevance) }));
triggers.push_back(new TriggerNode("u", { NextAction("use", relevance) }));
triggers.push_back(new TriggerNode("c", { NextAction("item count", relevance) }));
triggers.push_back(
new TriggerNode("items", NextAction::array(0, new NextAction("item count", relevance), nullptr)));
triggers.push_back(new TriggerNode("inv", NextAction::array(0, new NextAction("item count", relevance), nullptr)));
triggers.push_back(new TriggerNode("e", NextAction::array(0, new NextAction("equip", relevance), nullptr)));
triggers.push_back(new TriggerNode("ue", NextAction::array(0, new NextAction("unequip", relevance), nullptr)));
triggers.push_back(new TriggerNode("t", NextAction::array(0, new NextAction("trade", relevance), nullptr)));
triggers.push_back(new TriggerNode("nt", NextAction::array(0, new NextAction("trade", relevance), nullptr)));
triggers.push_back(new TriggerNode("s", NextAction::array(0, new NextAction("sell", relevance), nullptr)));
triggers.push_back(new TriggerNode("b", NextAction::array(0, new NextAction("buy", relevance), nullptr)));
triggers.push_back(new TriggerNode("r", NextAction::array(0, new NextAction("reward", relevance), nullptr)));
new TriggerNode("items", { NextAction("item count", relevance) }));
triggers.push_back(new TriggerNode("inv", { NextAction("item count", relevance) }));
triggers.push_back(new TriggerNode("e", { NextAction("equip", relevance) }));
triggers.push_back(new TriggerNode("ue", { NextAction("unequip", relevance) }));
triggers.push_back(new TriggerNode("t", { NextAction("trade", relevance) }));
triggers.push_back(new TriggerNode("nt", { NextAction("trade", relevance) }));
triggers.push_back(new TriggerNode("s", { NextAction("sell", relevance) }));
triggers.push_back(new TriggerNode("b", { NextAction("buy", relevance) }));
triggers.push_back(new TriggerNode("r", { NextAction("reward", relevance) }));
triggers.push_back(
new TriggerNode("attack", NextAction::array(0, new NextAction("attack my target", relevance), nullptr)));
new TriggerNode("attack", { NextAction("attack my target", relevance) }));
triggers.push_back(
new TriggerNode("accept", NextAction::array(0, new NextAction("accept quest", relevance), nullptr)));
new TriggerNode("accept", { NextAction("accept quest", relevance) }));
triggers.push_back(
new TriggerNode("follow", NextAction::array(0, new NextAction("follow chat shortcut", relevance), nullptr)));
new TriggerNode("follow", { NextAction("follow chat shortcut", relevance) }));
triggers.push_back(
new TriggerNode("stay", NextAction::array(0, new NextAction("stay chat shortcut", relevance), nullptr)));
new TriggerNode("stay", { NextAction("stay chat shortcut", relevance) }));
triggers.push_back(
new TriggerNode("move from group", NextAction::array(0, new NextAction("move from group chat shortcut", relevance), nullptr)));
new TriggerNode("move from group", { NextAction("move from group chat shortcut", relevance) }));
triggers.push_back(
new TriggerNode("flee", NextAction::array(0, new NextAction("flee chat shortcut", relevance), nullptr)));
new TriggerNode("flee", { NextAction("flee chat shortcut", relevance) }));
triggers.push_back(new TriggerNode(
"tank attack", NextAction::array(0, new NextAction("tank attack chat shortcut", relevance), nullptr)));
"tank attack", { NextAction("tank attack chat shortcut", relevance) }));
triggers.push_back(
new TriggerNode("grind", NextAction::array(0, new NextAction("grind chat shortcut", relevance), nullptr)));
new TriggerNode("grind", { NextAction("grind chat shortcut", relevance) }));
triggers.push_back(
new TriggerNode("talk", NextAction::array(0, new NextAction("gossip hello", relevance),
new NextAction("talk to quest giver", relevance), nullptr)));
new TriggerNode("talk", { NextAction("gossip hello", relevance),
NextAction("talk to quest giver", relevance) }));
triggers.push_back(
new TriggerNode("enter vehicle", NextAction::array(0, new NextAction("enter vehicle", relevance), nullptr)));
new TriggerNode("enter vehicle", { NextAction("enter vehicle", relevance) }));
triggers.push_back(
new TriggerNode("leave vehicle", NextAction::array(0, new NextAction("leave vehicle", relevance), nullptr)));
new TriggerNode("leave vehicle", { NextAction("leave vehicle", relevance) }));
triggers.push_back(
new TriggerNode("cast", NextAction::array(0, new NextAction("cast custom spell", relevance), nullptr)));
new TriggerNode("cast", { NextAction("cast custom spell", relevance) }));
triggers.push_back(
new TriggerNode("castnc", NextAction::array(0, new NextAction("cast custom nc spell", relevance), nullptr)));
new TriggerNode("castnc", { NextAction("cast custom nc spell", relevance) }));
triggers.push_back(
new TriggerNode("revive", NextAction::array(0, new NextAction("spirit healer", relevance), nullptr)));
new TriggerNode("revive", { NextAction("spirit healer", relevance) }));
triggers.push_back(
new TriggerNode("runaway", NextAction::array(0, new NextAction("runaway chat shortcut", relevance), nullptr)));
new TriggerNode("runaway", { NextAction("runaway chat shortcut", relevance) }));
triggers.push_back(
new TriggerNode("warning", NextAction::array(0, new NextAction("runaway chat shortcut", relevance), nullptr)));
new TriggerNode("warning", { NextAction("runaway chat shortcut", relevance) }));
triggers.push_back(
new TriggerNode("max dps", NextAction::array(0, new NextAction("max dps chat shortcut", relevance), nullptr)));
new TriggerNode("max dps", { NextAction("max dps chat shortcut", relevance) }));
triggers.push_back(
new TriggerNode("attackers", NextAction::array(0, new NextAction("tell attackers", relevance), nullptr)));
new TriggerNode("attackers", { NextAction("tell attackers", relevance) }));
triggers.push_back(
new TriggerNode("target", NextAction::array(0, new NextAction("tell target", relevance), nullptr)));
new TriggerNode("target", { NextAction("tell target", relevance) }));
triggers.push_back(
new TriggerNode("ready", NextAction::array(0, new NextAction("ready check", relevance), nullptr)));
new TriggerNode("ready", { NextAction("ready check", relevance) }));
triggers.push_back(
new TriggerNode("naxx", NextAction::array(0, new NextAction("naxx chat shortcut", relevance), NULL)));
new TriggerNode("bwl", { NextAction("bwl chat shortcut", relevance) }));
triggers.push_back(
new TriggerNode("bwl", NextAction::array(0, new NextAction("bwl chat shortcut", relevance), NULL)));
new TriggerNode("dps", { NextAction("tell estimated dps", relevance) }));
triggers.push_back(
new TriggerNode("dps", NextAction::array(0, new NextAction("tell estimated dps", relevance), NULL)));
new TriggerNode("disperse", { NextAction("disperse set", relevance) }));
triggers.push_back(
new TriggerNode("disperse", NextAction::array(0, new NextAction("disperse set", relevance), NULL)));
new TriggerNode("open items", { NextAction("open items", relevance) }));
triggers.push_back(
new TriggerNode("open items", NextAction::array(0, new NextAction("open items", relevance), nullptr)));
new TriggerNode("qi", { NextAction("query item usage", relevance) }));
triggers.push_back(
new TriggerNode("qi", NextAction::array(0, new NextAction("query item usage", relevance), nullptr)));
new TriggerNode("unlock items", { NextAction("unlock items", relevance) }));
triggers.push_back(
new TriggerNode("unlock items", NextAction::array(0, new NextAction("unlock items", relevance), nullptr)));
new TriggerNode("unlock traded item", { NextAction("unlock traded item", relevance) }));
triggers.push_back(
new TriggerNode("unlock traded item", NextAction::array(0, new NextAction("unlock traded item", relevance), nullptr)));
triggers.push_back(
new TriggerNode("wipe", NextAction::array(0, new NextAction("wipe", relevance), nullptr)));
triggers.push_back(new TriggerNode("tame", NextAction::array(0, new NextAction("tame", relevance), nullptr)));
triggers.push_back(new TriggerNode("glyphs", NextAction::array(0, new NextAction("glyphs", relevance), nullptr))); // Added for custom Glyphs
triggers.push_back(new TriggerNode("glyph equip", NextAction::array(0, new NextAction("glyph equip", relevance), nullptr))); // Added for custom Glyphs
triggers.push_back(new TriggerNode("pet", NextAction::array(0, new NextAction("pet", relevance), nullptr)));
triggers.push_back(new TriggerNode("pet attack", NextAction::array(0, new NextAction("pet attack", relevance), nullptr)));
triggers.push_back(new TriggerNode("roll", NextAction::array(0, new NextAction("roll", relevance), nullptr)));
new TriggerNode("wipe", { NextAction("wipe", relevance) }));
triggers.push_back(new TriggerNode("tame", { NextAction("tame", relevance) }));
triggers.push_back(new TriggerNode("glyphs", { NextAction("glyphs", relevance) })); // Added for custom Glyphs
triggers.push_back(new TriggerNode("glyph equip", { NextAction("glyph equip", relevance) })); // Added for custom Glyphs
triggers.push_back(new TriggerNode("pet", { NextAction("pet", relevance) }));
triggers.push_back(new TriggerNode("pet attack", { NextAction("pet attack", relevance) }));
triggers.push_back(new TriggerNode("roll", { NextAction("roll", relevance) }));
}
ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : PassTroughStrategy(botAI)

Some files were not shown because too many files have changed in this diff Show More