Merge branch 'liyunfan1223:master' into clean_quest_log

This commit is contained in:
avirar
2024-10-13 10:50:33 +11:00
committed by GitHub
125 changed files with 3525 additions and 385 deletions

View File

@@ -728,7 +728,7 @@ AiPlayerbot.FastReactInBG = 1
# #
# All In seconds # All In seconds
AiPlayerbot.RandomBotUpdateInterval = 10 AiPlayerbot.RandomBotUpdateInterval = 20
AiPlayerbot.RandomBotCountChangeMinInterval = 1800 AiPlayerbot.RandomBotCountChangeMinInterval = 1800
AiPlayerbot.RandomBotCountChangeMaxInterval = 7200 AiPlayerbot.RandomBotCountChangeMaxInterval = 7200
AiPlayerbot.MinRandomBotInWorldTime = 3600 AiPlayerbot.MinRandomBotInWorldTime = 3600
@@ -1002,16 +1002,16 @@ AiPlayerbot.PremadeSpecLink.9.2.80 = -03310030003-05203205210331051335230351
AiPlayerbot.PremadeSpecName.11.0 = balance pve AiPlayerbot.PremadeSpecName.11.0 = balance pve
AiPlayerbot.PremadeSpecGlyph.11.0 = 40916,43331,40921,43335,44922,40919 AiPlayerbot.PremadeSpecGlyph.11.0 = 40916,43331,40921,43335,44922,40919
AiPlayerbot.PremadeSpecLink.11.0.60 = 5012203115331003213302301231 AiPlayerbot.PremadeSpecLink.11.0.60 = 5022203105331003213005301231
AiPlayerbot.PremadeSpecLink.11.0.80 = 5012203125331103213305301231--205003212 AiPlayerbot.PremadeSpecLink.11.0.80 = 5032203105331303213305301231--205003012
AiPlayerbot.PremadeSpecName.11.1 = bear pve AiPlayerbot.PremadeSpecName.11.1 = bear pve
AiPlayerbot.PremadeSpecGlyph.11.1 = 40897,43331,46372,43335,43332,40899 AiPlayerbot.PremadeSpecGlyph.11.1 = 40897,43331,46372,43335,43332,40899
AiPlayerbot.PremadeSpecLink.11.1.60 = -500232130322110353100301310501 AiPlayerbot.PremadeSpecLink.11.1.60 = -500232130322110353100301310501
AiPlayerbot.PremadeSpecLink.11.1.80 = -501232130322110353120303313511-20350001 AiPlayerbot.PremadeSpecLink.11.1.80 = -501232130322110353120303313511-20350001
AiPlayerbot.PremadeSpecName.11.2 = resto pve AiPlayerbot.PremadeSpecName.11.2 = resto pve
AiPlayerbot.PremadeSpecGlyph.11.2 = 40913,43331,40906,43335,44922,45602 AiPlayerbot.PremadeSpecGlyph.11.2 = 40913,43331,40906,43335,44922,45602
AiPlayerbot.PremadeSpecLink.11.2.60 = --230033312031501531050013051 AiPlayerbot.PremadeSpecLink.11.2.60 = --230033312031500531050113051
AiPlayerbot.PremadeSpecLink.11.2.80 = 05320001--230033312031512531153313051 AiPlayerbot.PremadeSpecLink.11.2.80 = 05320031--230033312031501531053313051
AiPlayerbot.PremadeSpecName.11.3 = cat pve AiPlayerbot.PremadeSpecName.11.3 = cat pve
AiPlayerbot.PremadeSpecGlyph.11.3 = 40902,43331,40901,43335,44922,45604 AiPlayerbot.PremadeSpecGlyph.11.3 = 40902,43331,40901,43335,44922,45604
AiPlayerbot.PremadeSpecLink.11.3.60 = -552202032322010053100030310501 AiPlayerbot.PremadeSpecLink.11.3.60 = -552202032322010053100030310501
@@ -1263,11 +1263,6 @@ Playerbots.Updates.EnableDatabases = 1
# Command server port, 0 - disabled # Command server port, 0 - disabled
AiPlayerbot.CommandServerPort = 8888 AiPlayerbot.CommandServerPort = 8888
# Diff with/without player in server. The server will tune bot activity to reach the desired server tick speed (in ms).# PLAYERBOT SYSTEM SETTINGS #
AiPlayerbot.EnablePrototypePerformanceDiff = 0
AiPlayerbot.DiffWithPlayer = 100
AiPlayerbot.DiffEmpty = 200
# #
# #
# #
@@ -1462,9 +1457,16 @@ AiPlayerbot.RandombotsWalkingRPG.InDoors = 0
# The default is 10. With 10% of all bots going active or inactive each minute. # The default is 10. With 10% of all bots going active or inactive each minute.
AiPlayerbot.BotActiveAlone = 100 AiPlayerbot.BotActiveAlone = 100
# Specify 1 for enabled, 0 for disabled. # Specify smart scaling is enabled or not.
# The default is 1. Automatically adjusts 'BotActiveAlone' percentage based on server latency. # The default is 1. When enabled (smart) scales the 'BotActiveAlone' value.
AiPlayerbot.botActiveAloneAutoScale = 1 AiPlayerbot.botActiveAloneSmartScale = 1
# Only when botLevel is between WhenMinLevel and WhenMaxLevel.
AiPlayerbot.botActiveAloneSmartScaleWhenMinLevel = 1
AiPlayerbot.botActiveAloneSmartScaleWhenMaxLevel = 80
# The server will tune bot activity to reach the desired server tick speed (in ms)
# bots will only join battleground when there is no lag based on latency diffs below
AiPlayerbot.botActiveAloneSmartScaleDiffWithPlayer = 100
AiPlayerbot.botActiveAloneSmartScaleDiffEmpty = 200
# Premade spell to avoid (undetected spells) # Premade spell to avoid (undetected spells)
# spellid-radius, ... # spellid-radius, ...

View File

@@ -277,10 +277,6 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
{ {
engine->addStrategiesNoInit("racials", "chat", "default", "cast time", "duel", "boost", nullptr); engine->addStrategiesNoInit("racials", "chat", "default", "cast time", "duel", "boost", nullptr);
} }
if (sPlayerbotAIConfig->autoSaveMana)
{
engine->addStrategy("save mana", false);
}
if (sPlayerbotAIConfig->autoAvoidAoe && facade->HasRealPlayerMaster()) if (sPlayerbotAIConfig->autoAvoidAoe && facade->HasRealPlayerMaster())
{ {
engine->addStrategy("avoid aoe", false); engine->addStrategy("avoid aoe", false);
@@ -394,7 +390,12 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
if (PlayerbotAI::IsMelee(player, true) && PlayerbotAI::IsDps(player, true)) { if (PlayerbotAI::IsMelee(player, true) && PlayerbotAI::IsDps(player, true)) {
engine->addStrategy("behind", false); engine->addStrategy("behind", false);
} }
if (PlayerbotAI::IsHeal(player, true))
{
if (sPlayerbotAIConfig->autoSaveMana)
engine->addStrategy("save mana", false);
engine->addStrategy("healer dps", false);
}
if (facade->IsRealPlayer() || sRandomPlayerbotMgr->IsRandomBot(player)) if (facade->IsRealPlayer() || sRandomPlayerbotMgr->IsRandomBot(player))
{ {
if (!player->GetGroup()) if (!player->GetGroup())
@@ -483,8 +484,8 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
engine->removeStrategy("threat", false); engine->removeStrategy("threat", false);
engine->addStrategy("boost", false); engine->addStrategy("boost", false);
if ((player->getClass() == CLASS_DRUID && tab == 2) || (player->getClass() == CLASS_SHAMAN && tab == 2)) // if ((player->getClass() == CLASS_DRUID && tab == 2) || (player->getClass() == CLASS_SHAMAN && tab == 2))
engine->addStrategiesNoInit("caster", "caster aoe", nullptr); // engine->addStrategiesNoInit("caster", "caster aoe", nullptr);
// if (player->getClass() == CLASS_DRUID && tab == 1) // if (player->getClass() == CLASS_DRUID && tab == 1)
// engine->addStrategiesNoInit(/*"behind",*/ "dps", nullptr); // engine->addStrategiesNoInit(/*"behind",*/ "dps", nullptr);

View File

@@ -239,6 +239,7 @@ void PlayerbotAI::UpdateAI(uint32 elapsed, bool minimal)
{ {
return; return;
} }
// if (!GetMaster() || !GetMaster()->IsInWorld() || !GetMaster()->GetSession() || // if (!GetMaster() || !GetMaster()->IsInWorld() || !GetMaster()->GetSession() ||
// GetMaster()->GetSession()->isLogingOut()) { // GetMaster()->GetSession()->isLogingOut()) {
// return; // return;
@@ -301,6 +302,7 @@ void PlayerbotAI::UpdateAI(uint32 elapsed, bool minimal)
// bot->GetMotionMaster()->Clear(); // bot->GetMotionMaster()->Clear();
// bot->GetMotionMaster()->MoveIdle(); // bot->GetMotionMaster()->MoveIdle();
// } // }
// cheat options // cheat options
if (bot->IsAlive() && ((uint32)GetCheat() > 0 || (uint32)sPlayerbotAIConfig->botCheatMask > 0)) if (bot->IsAlive() && ((uint32)GetCheat() > 0 || (uint32)sPlayerbotAIConfig->botCheatMask > 0))
{ {
@@ -314,16 +316,20 @@ void PlayerbotAI::UpdateAI(uint32 elapsed, bool minimal)
AllowActivity(); AllowActivity();
if (!CanUpdateAI())
return;
Spell* currentSpell = bot->GetCurrentSpell(CURRENT_GENERIC_SPELL); Spell* currentSpell = bot->GetCurrentSpell(CURRENT_GENERIC_SPELL);
if (!currentSpell) if (!currentSpell)
currentSpell = bot->GetCurrentSpell(CURRENT_CHANNELED_SPELL); currentSpell = bot->GetCurrentSpell(CURRENT_CHANNELED_SPELL);
if (currentSpell && currentSpell->getState() == SPELL_STATE_PREPARING) if (currentSpell && currentSpell->GetSpellInfo() && currentSpell->getState() == SPELL_STATE_PREPARING)
{ {
const SpellInfo* spellInfo = currentSpell->GetSpellInfo(); const SpellInfo* spellInfo = currentSpell->GetSpellInfo();
// interrupt if target is dead // interrupt if target is dead
if (currentSpell->m_targets.GetUnitTarget() && !currentSpell->m_targets.GetUnitTarget()->IsAlive() && if (currentSpell->m_targets.GetUnitTarget() && !currentSpell->m_targets.GetUnitTarget()->IsAlive() &&
spellInfo && !spellInfo->IsAllowingDeadTarget()) !spellInfo->IsAllowingDeadTarget())
{ {
InterruptSpell(); InterruptSpell();
SetNextCheckDelay(sPlayerbotAIConfig->reactDelay); SetNextCheckDelay(sPlayerbotAIConfig->reactDelay);
@@ -356,6 +362,7 @@ void PlayerbotAI::UpdateAI(uint32 elapsed, bool minimal)
} }
// wait for spell cast // wait for spell cast
YieldThread(GetReactDelay());
return; return;
} }
@@ -383,9 +390,6 @@ void PlayerbotAI::UpdateAI(uint32 elapsed, bool minimal)
} }
} }
if (!CanUpdateAI())
return;
if (!bot->InBattleground() && !bot->inRandomLfgDungeon() && bot->GetGroup()) if (!bot->InBattleground() && !bot->inRandomLfgDungeon() && bot->GetGroup())
{ {
Player* leader = bot->GetGroup()->GetLeader(); Player* leader = bot->GetGroup()->GetLeader();
@@ -703,8 +707,7 @@ void PlayerbotAI::HandleTeleportAck()
p << (uint32)0; // supposed to be flags? not used currently p << (uint32)0; // supposed to be flags? not used currently
p << (uint32)0; // time - not currently used p << (uint32)0; // time - not currently used
bot->GetSession()->HandleMoveTeleportAck(p); bot->GetSession()->HandleMoveTeleportAck(p);
} };
SetNextCheckDelay(urand(1000, 3000));
} }
if (bot->IsBeingTeleportedFar()) if (bot->IsBeingTeleportedFar())
{ {
@@ -712,13 +715,13 @@ void PlayerbotAI::HandleTeleportAck()
{ {
bot->GetSession()->HandleMoveWorldportAck(); bot->GetSession()->HandleMoveWorldportAck();
} }
SetNextCheckDelay(urand(2000, 5000)); // SetNextCheckDelay(urand(2000, 5000));
if (sPlayerbotAIConfig->applyInstanceStrategies) if (sPlayerbotAIConfig->applyInstanceStrategies)
ApplyInstanceStrategies(bot->GetMapId(), true); ApplyInstanceStrategies(bot->GetMapId(), true);
Reset();
} }
SetNextCheckDelay(sPlayerbotAIConfig->globalCoolDown); SetNextCheckDelay(sPlayerbotAIConfig->globalCoolDown);
Reset();
} }
void PlayerbotAI::Reset(bool full) void PlayerbotAI::Reset(bool full)
@@ -1198,7 +1201,7 @@ void PlayerbotAI::HandleBotOutgoingPacket(WorldPacket const& packet)
void PlayerbotAI::SpellInterrupted(uint32 spellid) void PlayerbotAI::SpellInterrupted(uint32 spellid)
{ {
for (uint8 type = CURRENT_MELEE_SPELL; type < CURRENT_CHANNELED_SPELL; type++) for (uint8 type = CURRENT_MELEE_SPELL; type <= CURRENT_CHANNELED_SPELL; type++)
{ {
Spell* spell = bot->GetCurrentSpell((CurrentSpellTypes)type); Spell* spell = bot->GetCurrentSpell((CurrentSpellTypes)type);
if (!spell) if (!spell)
@@ -1206,8 +1209,8 @@ void PlayerbotAI::SpellInterrupted(uint32 spellid)
if (spell->GetSpellInfo()->Id == spellid) if (spell->GetSpellInfo()->Id == spellid)
bot->InterruptSpell((CurrentSpellTypes)type); bot->InterruptSpell((CurrentSpellTypes)type);
} }
LastSpellCast& lastSpell = aiObjectContext->GetValue<LastSpellCast&>("last spell cast")->Get(); // LastSpellCast& lastSpell = aiObjectContext->GetValue<LastSpellCast&>("last spell cast")->Get();
lastSpell.id = 0; // lastSpell.id = 0;
} }
int32 PlayerbotAI::CalculateGlobalCooldown(uint32 spellid) int32 PlayerbotAI::CalculateGlobalCooldown(uint32 spellid)
@@ -1516,7 +1519,7 @@ void PlayerbotAI::DoNextAction(bool min)
void PlayerbotAI::ReInitCurrentEngine() void PlayerbotAI::ReInitCurrentEngine()
{ {
InterruptSpell(); // InterruptSpell();
currentEngine->Init(); currentEngine->Init();
} }
@@ -3339,11 +3342,11 @@ bool PlayerbotAI::CastSpell(uint32 spellId, Unit* target, Item* itemTarget)
// } // }
// } // }
WaitForSpellCast(spell); // WaitForSpellCast(spell);
if (spell->GetCastTime())
aiObjectContext->GetValue<LastSpellCast&>("last spell cast") aiObjectContext->GetValue<LastSpellCast&>("last spell cast")
->Get() ->Get()
.Set(spellId, target->GetGUID(), time(nullptr)); .Set(spellId, target->GetGUID(), time(nullptr));
aiObjectContext->GetValue<PositionMap&>("position")->Get()["random"].Reset(); aiObjectContext->GetValue<PositionMap&>("position")->Get()["random"].Reset();
@@ -3473,7 +3476,7 @@ bool PlayerbotAI::CastSpell(uint32 spellId, float x, float y, float z, Item* ite
} }
} }
WaitForSpellCast(spell); // WaitForSpellCast(spell);
aiObjectContext->GetValue<LastSpellCast&>("last spell cast")->Get().Set(spellId, bot->GetGUID(), time(nullptr)); aiObjectContext->GetValue<LastSpellCast&>("last spell cast")->Get().Set(spellId, bot->GetGUID(), time(nullptr));
aiObjectContext->GetValue<PositionMap&>("position")->Get()["random"].Reset(); aiObjectContext->GetValue<PositionMap&>("position")->Get()["random"].Reset();
@@ -3688,7 +3691,7 @@ bool PlayerbotAI::CastVehicleSpell(uint32 spellId, Unit* target)
return false; return false;
} }
WaitForSpellCast(spell); // WaitForSpellCast(spell);
// aiObjectContext->GetValue<LastSpellCast&>("last spell cast")->Get().Set(spellId, target->GetGUID(), time(0)); // aiObjectContext->GetValue<LastSpellCast&>("last spell cast")->Get().Set(spellId, target->GetGUID(), time(0));
// aiObjectContext->GetValue<botAI::PositionMap&>("position")->Get()["random"].Reset(); // aiObjectContext->GetValue<botAI::PositionMap&>("position")->Get()["random"].Reset();
@@ -3745,23 +3748,15 @@ bool PlayerbotAI::IsInVehicle(bool canControl, bool canCast, bool canAttack, boo
void PlayerbotAI::WaitForSpellCast(Spell* spell) void PlayerbotAI::WaitForSpellCast(Spell* spell)
{ {
return;
SpellInfo const* spellInfo = spell->GetSpellInfo(); SpellInfo const* spellInfo = spell->GetSpellInfo();
uint32 castTime = spell->GetCastTime(); uint32 castTime = spell->GetCastTime();
// float castTime = spell->GetCastTime(); if (spellInfo->IsChanneled())
// if (spellInfo->IsChanneled()) {
// { int32 duration = spellInfo->GetDuration();
// int32 duration = spellInfo->GetDuration(); bot->ApplySpellMod(spellInfo->Id, SPELLMOD_DURATION, duration);
// bot->ApplySpellMod(spellInfo->Id, SPELLMOD_DURATION, duration); if (duration > 0)
// if (duration > 0) castTime += duration;
// castTime += duration; }
// }
// castTime = ceil(castTime);
// uint32 globalCooldown = CalculateGlobalCooldown(spellInfo->Id);
// if (castTime < globalCooldown)
// castTime = globalCooldown;
SetNextCheckDelay(castTime + sPlayerbotAIConfig->reactDelay); SetNextCheckDelay(castTime + sPlayerbotAIConfig->reactDelay);
} }
@@ -3933,7 +3928,10 @@ Player* PlayerbotAI::GetGroupMaster()
uint32 PlayerbotAI::GetFixedBotNumer(BotTypeNumber typeNumber, uint32 maxNum, float cyclePerMin) uint32 PlayerbotAI::GetFixedBotNumer(BotTypeNumber typeNumber, uint32 maxNum, float cyclePerMin)
{ {
uint32 randseed = rand32(); // Seed random number //deterministic seed
uint8 seedNumber = uint8(typeNumber);
std::mt19937 rng(seedNumber);
uint32 randseed = rng(); // Seed random number
uint32 randnum = bot->GetGUID().GetCounter() + randseed; // Semi-random but fixed number for each bot. uint32 randnum = bot->GetGUID().GetCounter() + randseed; // Semi-random but fixed number for each bot.
if (cyclePerMin > 0) if (cyclePerMin > 0)
@@ -3943,8 +3941,7 @@ uint32 PlayerbotAI::GetFixedBotNumer(BotTypeNumber typeNumber, uint32 maxNum, fl
randnum += cycle; // Make the random number cylce. randnum += cycle; // Make the random number cylce.
} }
randnum = randnum = (randnum % (maxNum + 1)); // Loops the randomnumber at maxNum. Bassically removes all the numbers above 99.
(randnum % (maxNum + 1)); // Loops the randomnumber at maxNum. Bassically removes all the numbers above 99.
return randnum; // Now we have a number unique for each bot between 0 and maxNum that increases by cyclePerMin. return randnum; // Now we have a number unique for each bot between 0 and maxNum that increases by cyclePerMin.
} }
@@ -4082,18 +4079,15 @@ inline bool HasRealPlayers(Map* map)
return false; return false;
} }
bool PlayerbotAI::AllowActive(ActivityType activityType) ActivePiorityType PlayerbotAI::GetPriorityType(ActivityType activityType)
{ {
// General exceptions // First priority - priorities disabled or has player master. Always active.
if (activityType == PACKET_ACTIVITY) if (HasRealPlayerMaster())
return true; return ActivePiorityType::HAS_REAL_PLAYER_MASTER;
if (GetMaster()) // Has player master. Always active. // Self bot in a group with a bot master.
{ if (IsRealPlayer())
PlayerbotAI* masterBotAI = GET_PLAYERBOT_AI(GetMaster()); return ActivePiorityType::IS_REAL_PLAYER;
if (!masterBotAI || masterBotAI->IsRealPlayer())
return true;
}
Group* group = bot->GetGroup(); Group* group = bot->GetGroup();
if (group) if (group)
@@ -4109,19 +4103,40 @@ bool PlayerbotAI::AllowActive(ActivityType activityType)
PlayerbotAI* memberBotAI = GET_PLAYERBOT_AI(member); PlayerbotAI* memberBotAI = GET_PLAYERBOT_AI(member);
if (!memberBotAI || memberBotAI->HasRealPlayerMaster()) if (!memberBotAI || memberBotAI->HasRealPlayerMaster())
return true; return ActivePiorityType::IN_GROUP_WITH_REAL_PLAYER;
if (group->IsLeader(member->GetGUID())) if (group->IsLeader(member->GetGUID()))
{
if (!memberBotAI->AllowActivity(PARTY_ACTIVITY)) if (!memberBotAI->AllowActivity(PARTY_ACTIVITY))
return false; return ActivePiorityType::IN_GROUP_WITH_REAL_PLAYER;
}
} }
} }
if (!WorldPosition(bot).isOverworld()) // bg, raid, dungeon if (bot->IsBeingTeleported()) // Allow activity while teleportation.
return true; return ActivePiorityType::IN_INSTANCE;
if (bot->InBattlegroundQueue()) // In bg queue. Speed up bg queue/join. if (!WorldPosition(bot).isOverworld())
return true; return ActivePiorityType::IN_INSTANCE;
if (HasPlayerNearby())
return ActivePiorityType::VISIBLE_FOR_PLAYER;
if (activityType != OUT_OF_PARTY_ACTIVITY && activityType != PACKET_ACTIVITY)
{
// Is in combat.Defend yourself.
if (bot->IsInCombat())
return ActivePiorityType::IN_COMBAT;
}
if (HasPlayerNearby(300.f))
return ActivePiorityType::NEARBY_PLAYER;
//if (sPlayerbotAIConfig->IsFreeAltBot(bot) || HasStrategy("travel once", BotState::BOT_STATE_NON_COMBAT))
// return ActivePiorityType::IS_ALWAYS_ACTIVE;
if (bot->InBattlegroundQueue())
return ActivePiorityType::IN_BG_QUEUE;
bool isLFG = false; bool isLFG = false;
if (group) if (group)
@@ -4131,62 +4146,167 @@ bool PlayerbotAI::AllowActive(ActivityType activityType)
isLFG = true; isLFG = true;
} }
} }
if (sLFGMgr->GetState(bot->GetGUID()) != lfg::LFG_STATE_NONE) if (sLFGMgr->GetState(bot->GetGUID()) != lfg::LFG_STATE_NONE)
{ {
isLFG = true; isLFG = true;
} }
if (isLFG) if (isLFG)
return true; return ActivePiorityType::IN_LFG;
if (activityType != OUT_OF_PARTY_ACTIVITY && activityType != PACKET_ACTIVITY) // Is in combat. Defend yourself. // If has real players - slow down continents without player
if (bot->IsInCombat()) // This means we first disable bots in a different continent/area.
return true; if (sRandomPlayerbotMgr->GetPlayers().empty())
return ActivePiorityType::IN_EMPTY_SERVER;
if (HasPlayerNearby(300.f)) // Player is near. Always active.
return true;
// friends always active // friends always active
// for (auto& player : sRandomPlayerbotMgr->GetPlayers())
// HasFriend sometimes cause crash, disable // {
// for (auto& player : sRandomPlayerbotMgr->GetPlayers()) // if (!player || !player->IsInWorld())
// { // continue;
// if (!player || !player->IsInWorld())
// continue;
// if (player->GetSocial()->HasFriend(bot->GetGUID())) // if (player->GetSocial()->HasFriend(bot->GetGUID()))
// return true; // return ActivePiorityType::PLAYER_FRIEND;
// } // }
if (activityType == OUT_OF_PARTY_ACTIVITY || // real guild always active if member+
activityType == GRIND_ACTIVITY) // Many bots nearby. Do not do heavy area checks. if (IsInRealGuild())
if (HasManyPlayersNearby()) return ActivePiorityType::PLAYER_GUILD;
return false;
// Bots don't need to move using PathGenerator. if (bot->IsBeingTeleported() || !bot->IsInWorld() || !HasRealPlayers(bot->GetMap()))
if (activityType == DETAILED_MOVE_ACTIVITY) return ActivePiorityType::IN_INACTIVE_MAP;
return false;
// All exceptions are now done. // IN_ACTIVE_AREA
// Below is code to have a specified % of bots active at all times. if (activityType == OUT_OF_PARTY_ACTIVITY || activityType == GRIND_ACTIVITY)
// The default is 10%. With 0.1% of all bots going active or inactive each minute.
if (sPlayerbotAIConfig->botActiveAlone <= 0)
return false;
uint32 mod = sPlayerbotAIConfig->botActiveAlone > 100 ? 100 : sPlayerbotAIConfig->botActiveAlone;
if (sPlayerbotAIConfig->botActiveAloneAutoScale)
{ {
mod = AutoScaleActivity(mod); // Many bots nearby. Do not do heavy area checks.
if (HasManyPlayersNearby())
return ActivePiorityType::IN_ACTIVE_AREA;
} }
uint32 ActivityNumber = return ActivePiorityType::IN_ACTIVE_AREA;
GetFixedBotNumer(BotTypeNumber::ACTIVITY_TYPE_NUMBER, 100, }
sPlayerbotAIConfig->botActiveAlone * static_cast<float>(mod) / 100 * 0.01f);
return ActivityNumber <= // Returns the lower and upper bracket for bots to be active.
(sPlayerbotAIConfig->botActiveAlone * mod) / // Ie. { 10, 20 } means all bots in this bracket will be inactive below 10% activityMod,
100; // The given percentage of bots should be active and rotate 1% of those active bots each minute. // and will be active above 20% activityMod and scale between those values.
std::pair<uint32, uint32> PlayerbotAI::GetPriorityBracket(ActivePiorityType type)
{
switch (type)
{
case ActivePiorityType::HAS_REAL_PLAYER_MASTER:
case ActivePiorityType::IS_REAL_PLAYER:
case ActivePiorityType::IN_GROUP_WITH_REAL_PLAYER:
case ActivePiorityType::IN_INSTANCE:
case ActivePiorityType::VISIBLE_FOR_PLAYER:
return {0, 0};
case ActivePiorityType::IS_ALWAYS_ACTIVE:
case ActivePiorityType::IN_COMBAT:
return {0, 10};
case ActivePiorityType::IN_BG_QUEUE:
return {0, 20};
case ActivePiorityType::IN_LFG:
return {0, 30};
case ActivePiorityType::NEARBY_PLAYER:
return {0, 40};
case ActivePiorityType::PLAYER_FRIEND:
case ActivePiorityType::PLAYER_GUILD:
return {0, 50};
case ActivePiorityType::IN_ACTIVE_AREA:
return {30, 100};
case ActivePiorityType::IN_ACTIVE_MAP:
return {50, 100};
case ActivePiorityType::IN_INACTIVE_MAP:
return {70, 100};
case ActivePiorityType::IN_EMPTY_SERVER:
return {80, 100};
default:
return {90, 100};
}
return {90, 100};
}
bool PlayerbotAI::AllowActive(ActivityType activityType)
{
// General exceptions
if (activityType == PACKET_ACTIVITY)
return true;
ActivePiorityType type = GetPriorityType(activityType);
if (activityType == DETAILED_MOVE_ACTIVITY)
{
switch (type)
{
case ActivePiorityType::HAS_REAL_PLAYER_MASTER:
case ActivePiorityType::IS_REAL_PLAYER:
case ActivePiorityType::IN_GROUP_WITH_REAL_PLAYER:
case ActivePiorityType::IN_INSTANCE:
case ActivePiorityType::VISIBLE_FOR_PLAYER:
case ActivePiorityType::IN_COMBAT:
case ActivePiorityType::NEARBY_PLAYER:
return true;
break;
case ActivePiorityType::IS_ALWAYS_ACTIVE:
case ActivePiorityType::IN_BG_QUEUE:
case ActivePiorityType::IN_LFG:
case ActivePiorityType::PLAYER_FRIEND:
case ActivePiorityType::PLAYER_GUILD:
case ActivePiorityType::IN_ACTIVE_AREA:
case ActivePiorityType::IN_EMPTY_SERVER:
case ActivePiorityType::IN_ACTIVE_MAP:
case ActivePiorityType::IN_INACTIVE_MAP:
default:
break;
}
}
else if (activityType == REACT_ACTIVITY)
{
switch (type)
{
case ActivePiorityType::HAS_REAL_PLAYER_MASTER:
case ActivePiorityType::IS_REAL_PLAYER:
case ActivePiorityType::IN_GROUP_WITH_REAL_PLAYER:
case ActivePiorityType::IN_INSTANCE:
case ActivePiorityType::VISIBLE_FOR_PLAYER:
case ActivePiorityType::IS_ALWAYS_ACTIVE:
case ActivePiorityType::IN_COMBAT:
return true;
break;
case ActivePiorityType::NEARBY_PLAYER:
case ActivePiorityType::IN_BG_QUEUE:
case ActivePiorityType::IN_LFG:
case ActivePiorityType::PLAYER_FRIEND:
case ActivePiorityType::PLAYER_GUILD:
case ActivePiorityType::IN_ACTIVE_AREA:
case ActivePiorityType::IN_EMPTY_SERVER:
case ActivePiorityType::IN_ACTIVE_MAP:
case ActivePiorityType::IN_INACTIVE_MAP:
default:
return false;
break;
}
}
// GetPriorityBracket acitivity
float activePerc = 100;
if (sPlayerbotAIConfig->botActiveAloneSmartScale &&
bot->GetLevel() >= sPlayerbotAIConfig->botActiveAloneSmartScaleWhenMinLevel &&
bot->GetLevel() <= sPlayerbotAIConfig->botActiveAloneSmartScaleWhenMaxLevel)
{
std::pair<uint8, uint8> priorityBracket = GetPriorityBracket(type);
if (!priorityBracket.second) return true;
float activityPercentage = sRandomPlayerbotMgr->getActivityPercentage();
if (priorityBracket.first >= activityPercentage) return false;
if (priorityBracket.second <= activityPercentage && priorityBracket.second < 100) return true;
activePerc = (activityPercentage - priorityBracket.first) / (priorityBracket.second - priorityBracket.first);
activePerc *= (priorityBracket.second == 100) ? sPlayerbotAIConfig->botActiveAlone : 100;
}
// The last number if the amount it cycles per min. Currently set to 1% of the active bots.
uint32 ActivityNumber = GetFixedBotNumer(BotTypeNumber::ACTIVITY_TYPE_NUMBER, 100, activePerc * 0.01f);
// The given percentage of bots should be active and rotate 1% of those active bots each minute.
return ActivityNumber <= (activePerc);
} }
bool PlayerbotAI::AllowActivity(ActivityType activityType, bool checkNow) bool PlayerbotAI::AllowActivity(ActivityType activityType, bool checkNow)
@@ -4203,31 +4323,6 @@ bool PlayerbotAI::AllowActivity(ActivityType activityType, bool checkNow)
return allowed; return allowed;
} }
uint32 PlayerbotAI::AutoScaleActivity(uint32 mod)
{
uint32 maxDiff = sWorldUpdateTime.GetAverageUpdateTime();
if (maxDiff > 500) return 0;
if (maxDiff > 250)
{
if (Map* map = bot->GetMap())
{
if (map->GetEntry()->IsWorldMap() &&
(!HasRealPlayers(map) ||
!map->IsGridLoaded(bot->GetPositionX(), bot->GetPositionY())))
return 0;
}
return (mod * 1) / 10;
}
if (maxDiff > 200) return (mod * 3) / 10;
if (maxDiff > 150) return (mod * 5) / 10;
if (maxDiff > 100) return (mod * 6) / 10;
if (maxDiff > 80) return (mod * 9) / 10;
return mod;
}
bool PlayerbotAI::IsOpposing(Player* player) { return IsOpposing(player->getRace(), bot->getRace()); } bool PlayerbotAI::IsOpposing(Player* player) { return IsOpposing(player->getRace(), bot->getRace()); }
bool PlayerbotAI::IsOpposing(uint8 race1, uint8 race2) bool PlayerbotAI::IsOpposing(uint8 race1, uint8 race2)
@@ -5335,15 +5430,29 @@ bool PlayerbotAI::CanMove()
if (IsInVehicle() && !IsInVehicle(true)) if (IsInVehicle() && !IsInVehicle(true))
return false; return false;
if (bot->isFrozen() || bot->IsPolymorphed() || (bot->isDead() && !bot->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST)) || if (bot->isFrozen() ||
bot->IsBeingTeleported() || bot->isInRoots() || bot->HasAuraType(SPELL_AURA_SPIRIT_OF_REDEMPTION) || bot->IsPolymorphed() ||
bot->HasAuraType(SPELL_AURA_MOD_CONFUSE) || bot->IsCharmed() || bot->HasAuraType(SPELL_AURA_MOD_STUN) || (bot->isDead() && !bot->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST)) ||
bot->HasUnitState(UNIT_STATE_IN_FLIGHT) || bot->HasUnitState(UNIT_STATE_LOST_CONTROL)) bot->IsBeingTeleported() ||
bot->isInRoots() ||
bot->HasAuraType(SPELL_AURA_SPIRIT_OF_REDEMPTION) ||
bot->HasAuraType(SPELL_AURA_MOD_CONFUSE) ||
bot->IsCharmed() ||
bot->HasAuraType(SPELL_AURA_MOD_STUN) ||
bot->HasUnitState(UNIT_STATE_IN_FLIGHT) ||
bot->HasUnitState(UNIT_STATE_LOST_CONTROL))
return false; return false;
return bot->GetMotionMaster()->GetCurrentMovementGeneratorType() != FLIGHT_MOTION_TYPE; return bot->GetMotionMaster()->GetCurrentMovementGeneratorType() != FLIGHT_MOTION_TYPE;
} }
bool PlayerbotAI::IsTaxiFlying()
{
return bot->HasUnitMovementFlag(MOVEMENTFLAG_ONTRANSPORT) &&
bot->HasUnitState(UNIT_STATE_IGNORE_PATHFINDING);
}
bool PlayerbotAI::IsInRealGuild() bool PlayerbotAI::IsInRealGuild()
{ {
if (!bot->GetGuildId()) if (!bot->GetGuildId())

View File

@@ -241,6 +241,27 @@ enum class GuilderType : uint8
VERY_LARGE = 250 VERY_LARGE = 250
}; };
enum class ActivePiorityType : uint8
{
IS_REAL_PLAYER = 0,
HAS_REAL_PLAYER_MASTER = 1,
IN_GROUP_WITH_REAL_PLAYER = 2,
IN_INSTANCE = 3,
VISIBLE_FOR_PLAYER = 4,
IS_ALWAYS_ACTIVE = 5,
IN_COMBAT = 6,
IN_BG_QUEUE = 7,
IN_LFG = 8,
NEARBY_PLAYER = 9,
PLAYER_FRIEND = 10,
PLAYER_GUILD = 11,
IN_ACTIVE_AREA = 12,
IN_ACTIVE_MAP = 13,
IN_INACTIVE_MAP = 14,
IN_EMPTY_SERVER = 15,
MAX_TYPE
};
enum ActivityType enum ActivityType
{ {
GRIND_ACTIVITY = 1, GRIND_ACTIVITY = 1,
@@ -250,8 +271,8 @@ enum ActivityType
PACKET_ACTIVITY = 5, PACKET_ACTIVITY = 5,
DETAILED_MOVE_ACTIVITY = 6, DETAILED_MOVE_ACTIVITY = 6,
PARTY_ACTIVITY = 7, PARTY_ACTIVITY = 7,
ALL_ACTIVITY = 8, REACT_ACTIVITY = 8,
ALL_ACTIVITY = 9,
MAX_ACTIVITY_TYPE MAX_ACTIVITY_TYPE
}; };
@@ -525,9 +546,10 @@ public:
bool HasPlayerNearby(WorldPosition* pos, float range = sPlayerbotAIConfig->reactDistance); bool HasPlayerNearby(WorldPosition* pos, float range = sPlayerbotAIConfig->reactDistance);
bool HasPlayerNearby(float range = sPlayerbotAIConfig->reactDistance); bool HasPlayerNearby(float range = sPlayerbotAIConfig->reactDistance);
bool HasManyPlayersNearby(uint32 trigerrValue = 20, float range = sPlayerbotAIConfig->sightDistance); bool HasManyPlayersNearby(uint32 trigerrValue = 20, float range = sPlayerbotAIConfig->sightDistance);
ActivePiorityType GetPriorityType(ActivityType activityType);
std::pair<uint32, uint32> GetPriorityBracket(ActivePiorityType type);
bool AllowActive(ActivityType activityType); bool AllowActive(ActivityType activityType);
bool AllowActivity(ActivityType activityType = ALL_ACTIVITY, bool checkNow = false); bool AllowActivity(ActivityType activityType = ALL_ACTIVITY, bool checkNow = false);
uint32 AutoScaleActivity(uint32 mod);
// Check if player is safe to use. // Check if player is safe to use.
bool IsSafe(Player* player); bool IsSafe(Player* player);
@@ -554,6 +576,7 @@ public:
void ResetJumpDestination() { jumpDestination = Position(); } void ResetJumpDestination() { jumpDestination = Position(); }
bool CanMove(); bool CanMove();
bool IsTaxiFlying();
bool IsInRealGuild(); bool IsInRealGuild();
static std::vector<std::string> dispel_whitelist; static std::vector<std::string> dispel_whitelist;
bool EqualLowercaseName(std::string s1, std::string s2); bool EqualLowercaseName(std::string s1, std::string s2);

View File

@@ -156,7 +156,7 @@ bool PlayerbotAIConfig::Initialize()
randomBotAutologin = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotAutologin", true); randomBotAutologin = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotAutologin", true);
minRandomBots = sConfigMgr->GetOption<int32>("AiPlayerbot.MinRandomBots", 50); minRandomBots = sConfigMgr->GetOption<int32>("AiPlayerbot.MinRandomBots", 50);
maxRandomBots = sConfigMgr->GetOption<int32>("AiPlayerbot.MaxRandomBots", 200); maxRandomBots = sConfigMgr->GetOption<int32>("AiPlayerbot.MaxRandomBots", 200);
randomBotUpdateInterval = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotUpdateInterval", 10); randomBotUpdateInterval = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotUpdateInterval", 20);
randomBotCountChangeMinInterval = randomBotCountChangeMinInterval =
sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotCountChangeMinInterval", 30 * MINUTE); sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotCountChangeMinInterval", 30 * MINUTE);
randomBotCountChangeMaxInterval = randomBotCountChangeMaxInterval =
@@ -465,11 +465,15 @@ bool PlayerbotAIConfig::Initialize()
playerbotsXPrate = sConfigMgr->GetOption<int32>("AiPlayerbot.KillXPRate", 1); playerbotsXPrate = sConfigMgr->GetOption<int32>("AiPlayerbot.KillXPRate", 1);
disableDeathKnightLogin = sConfigMgr->GetOption<bool>("AiPlayerbot.DisableDeathKnightLogin", 0); disableDeathKnightLogin = sConfigMgr->GetOption<bool>("AiPlayerbot.DisableDeathKnightLogin", 0);
botActiveAlone = sConfigMgr->GetOption<int32>("AiPlayerbot.BotActiveAlone", 100); botActiveAlone = sConfigMgr->GetOption<int32>("AiPlayerbot.BotActiveAlone", 100);
botActiveAloneAutoScale = sConfigMgr->GetOption<bool>("AiPlayerbot.botActiveAloneAutoScale", true); botActiveAloneSmartScale = sConfigMgr->GetOption<bool>("AiPlayerbot.botActiveAloneSmartScale", 1);
botActiveAloneSmartScaleWhenMinLevel =
enablePrototypePerformanceDiff = sConfigMgr->GetOption<bool>("AiPlayerbot.EnablePrototypePerformanceDiff", false); sConfigMgr->GetOption<uint32>("AiPlayerbot.botActiveAloneSmartScaleWhenMinLevel", 1);
diffWithPlayer = sConfigMgr->GetOption<int32>("AiPlayerbot.DiffWithPlayer", 100); botActiveAloneSmartScaleWhenMaxLevel =
diffEmpty = sConfigMgr->GetOption<int32>("AiPlayerbot.DiffEmpty", 200); sConfigMgr->GetOption<uint32>("AiPlayerbot.botActiveAloneSmartScaleWhenMaxLevel", 80);
botActiveAloneSmartScaleDiffWithPlayer =
sConfigMgr->GetOption<uint32>("AiPlayerbot.botActiveAloneSmartScaleDiffWithPlayer", 100);
botActiveAloneSmartScaleDiffEmpty =
sConfigMgr->GetOption<uint32>("AiPlayerbot.botActiveAloneSmartScaleDiffEmpty", 200);
randombotsWalkingRPG = sConfigMgr->GetOption<bool>("AiPlayerbot.RandombotsWalkingRPG", false); randombotsWalkingRPG = sConfigMgr->GetOption<bool>("AiPlayerbot.RandombotsWalkingRPG", false);
randombotsWalkingRPGInDoors = sConfigMgr->GetOption<bool>("AiPlayerbot.RandombotsWalkingRPG.InDoors", false); randombotsWalkingRPGInDoors = sConfigMgr->GetOption<bool>("AiPlayerbot.RandombotsWalkingRPG.InDoors", false);

View File

@@ -263,11 +263,11 @@ public:
uint32 playerbotsXPrate; uint32 playerbotsXPrate;
bool disableDeathKnightLogin; bool disableDeathKnightLogin;
uint32 botActiveAlone; uint32 botActiveAlone;
bool botActiveAloneAutoScale; bool botActiveAloneSmartScale;
uint32 botActiveAloneSmartScaleWhenMinLevel;
uint32 enablePrototypePerformanceDiff; uint32 botActiveAloneSmartScaleWhenMaxLevel;
uint32 diffWithPlayer; uint32 botActiveAloneSmartScaleDiffWithPlayer;
uint32 diffEmpty; uint32 botActiveAloneSmartScaleDiffEmpty;
bool freeMethodLoot; bool freeMethodLoot;
int32 lootRollLevel; int32 lootRollLevel;

View File

@@ -165,6 +165,13 @@ Player* RandomPlayerbotFactory::CreateRandomBot(WorldSession* session, uint8 cls
raceOptions.push_back(race); raceOptions.push_back(race);
} }
} }
if (raceOptions.empty())
{
LOG_ERROR("playerbots", "No races available for class: {}", cls);
return nullptr;
}
uint8 race = raceOptions[urand(0, raceOptions.size() - 1)]; uint8 race = raceOptions[urand(0, raceOptions.size() - 1)];
const auto raceAndGender = CombineRaceAndGender(gender, race); const auto raceAndGender = CombineRaceAndGender(gender, race);

View File

@@ -292,12 +292,8 @@ void RandomPlayerbotMgr::UpdateAIInternal(uint32 elapsed, bool /*minimal*/)
if (!sPlayerbotAIConfig->randomBotAutologin || !sPlayerbotAIConfig->enabled) if (!sPlayerbotAIConfig->randomBotAutologin || !sPlayerbotAIConfig->enabled)
return; return;
if (sPlayerbotAIConfig->enablePrototypePerformanceDiff) if (sPlayerbotAIConfig->botActiveAloneSmartScale)
{ {
LOG_INFO("playerbots", "---------------------------------------");
LOG_INFO("playerbots",
"PROTOTYPE: Playerbot performance enhancements are active. Issues and instability may occur.");
LOG_INFO("playerbots", "---------------------------------------");
ScaleBotActivity(); ScaleBotActivity();
} }
@@ -414,8 +410,9 @@ void RandomPlayerbotMgr::ScaleBotActivity()
// max/min activity // max/min activity
// % increase/decrease wanted diff , avg diff // % increase/decrease wanted diff , avg diff
float activityPercentageMod = pid.calculate( float activityPercentageMod = pid.calculate(sRandomPlayerbotMgr->GetPlayers().empty() ?
sRandomPlayerbotMgr->GetPlayers().empty() ? sPlayerbotAIConfig->diffEmpty : sPlayerbotAIConfig->diffWithPlayer, sPlayerbotAIConfig->botActiveAloneSmartScaleDiffEmpty :
sPlayerbotAIConfig->botActiveAloneSmartScaleDiffWithPlayer,
sWorldUpdateTime.GetAverageUpdateTime()); sWorldUpdateTime.GetAverageUpdateTime());
activityPercentage = activityPercentageMod + 50; activityPercentage = activityPercentageMod + 50;
@@ -1108,6 +1105,9 @@ bool RandomPlayerbotMgr::ProcessBot(uint32 bot)
bool RandomPlayerbotMgr::ProcessBot(Player* player) bool RandomPlayerbotMgr::ProcessBot(Player* player)
{ {
if (!player || !player->IsInWorld() || player->IsBeingTeleported() || player->GetSession()->isLogingOut())
return false;
uint32 bot = player->GetGUID().GetCounter(); uint32 bot = player->GetGUID().GetCounter();
if (player->InBattleground()) if (player->InBattleground())

View File

@@ -51,6 +51,11 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI)
actionContexts.Add(new RaidUlduarActionContext()); actionContexts.Add(new RaidUlduarActionContext());
actionContexts.Add(new RaidIccActionContext()); actionContexts.Add(new RaidIccActionContext());
actionContexts.Add(new WotlkDungeonUKActionContext()); actionContexts.Add(new WotlkDungeonUKActionContext());
actionContexts.Add(new WotlkDungeonNexActionContext());
actionContexts.Add(new WotlkDungeonANActionContext());
actionContexts.Add(new WotlkDungeonOKActionContext());
actionContexts.Add(new WotlkDungeonDTKActionContext());
actionContexts.Add(new WotlkDungeonVHActionContext());
triggerContexts.Add(new TriggerContext()); triggerContexts.Add(new TriggerContext());
triggerContexts.Add(new ChatTriggerContext()); triggerContexts.Add(new ChatTriggerContext());
@@ -62,6 +67,11 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI)
triggerContexts.Add(new RaidUlduarTriggerContext()); triggerContexts.Add(new RaidUlduarTriggerContext());
triggerContexts.Add(new RaidIccTriggerContext()); triggerContexts.Add(new RaidIccTriggerContext());
triggerContexts.Add(new WotlkDungeonUKTriggerContext()); triggerContexts.Add(new WotlkDungeonUKTriggerContext());
triggerContexts.Add(new WotlkDungeonNexTriggerContext());
triggerContexts.Add(new WotlkDungeonANTriggerContext());
triggerContexts.Add(new WotlkDungeonOKTriggerContext());
triggerContexts.Add(new WotlkDungeonDTKTriggerContext());
triggerContexts.Add(new WotlkDungeonVHTriggerContext());
valueContexts.Add(new ValueContext()); valueContexts.Add(new ValueContext());

View File

@@ -10,6 +10,7 @@
#include <string> #include <string>
#include "Common.h" #include "Common.h"
#include "DynamicObject.h"
#include "NamedObjectContext.h" #include "NamedObjectContext.h"
#include "PlayerbotAIAware.h" #include "PlayerbotAIAware.h"
#include "Strategy.h" #include "Strategy.h"

View File

@@ -202,9 +202,9 @@ bool Engine::DoNextAction(Unit* unit, uint32 depth, bool minimal)
{ {
LogAction("A:%s - PREREQ", action->getName().c_str()); LogAction("A:%s - PREREQ", action->getName().c_str());
if (MultiplyAndPush(actionNode->getPrerequisites(), relevance + 0.02, false, event, "prereq")) if (MultiplyAndPush(actionNode->getPrerequisites(), relevance + 0.002f, false, event, "prereq"))
{ {
PushAgain(actionNode, relevance + 0.01, event); PushAgain(actionNode, relevance + 0.001f, event);
continue; continue;
} }
} }
@@ -226,7 +226,7 @@ bool Engine::DoNextAction(Unit* unit, uint32 depth, bool minimal)
else else
{ {
LogAction("A:%s - FAILED", action->getName().c_str()); LogAction("A:%s - FAILED", action->getName().c_str());
MultiplyAndPush(actionNode->getAlternatives(), relevance + 0.03, false, event, "alt"); MultiplyAndPush(actionNode->getAlternatives(), relevance + 0.003f, false, event, "alt");
} }
} }
else else
@@ -246,7 +246,7 @@ bool Engine::DoNextAction(Unit* unit, uint32 depth, bool minimal)
botAI->TellMasterNoFacing(out); botAI->TellMasterNoFacing(out);
} }
LogAction("A:%s - IMPOSSIBLE", action->getName().c_str()); LogAction("A:%s - IMPOSSIBLE", action->getName().c_str());
MultiplyAndPush(actionNode->getAlternatives(), relevance + 0.03, false, event, "alt"); MultiplyAndPush(actionNode->getAlternatives(), relevance + 0.003f, false, event, "alt");
} }
} }
else else

View File

@@ -94,6 +94,7 @@ public:
creators["avoid aoe"] = &ActionContext::avoid_aoe; creators["avoid aoe"] = &ActionContext::avoid_aoe;
creators["combat formation move"] = &ActionContext::combat_formation_move; creators["combat formation move"] = &ActionContext::combat_formation_move;
creators["tank face"] = &ActionContext::tank_face; creators["tank face"] = &ActionContext::tank_face;
creators["rear flank"] = &ActionContext::rear_flank;
creators["disperse set"] = &ActionContext::disperse_set; creators["disperse set"] = &ActionContext::disperse_set;
creators["gift of the naaru"] = &ActionContext::gift_of_the_naaru; creators["gift of the naaru"] = &ActionContext::gift_of_the_naaru;
creators["shoot"] = &ActionContext::shoot; creators["shoot"] = &ActionContext::shoot;
@@ -278,6 +279,7 @@ private:
static Action* avoid_aoe(PlayerbotAI* botAI) { return new AvoidAoeAction(botAI); } static Action* avoid_aoe(PlayerbotAI* botAI) { return new AvoidAoeAction(botAI); }
static Action* combat_formation_move(PlayerbotAI* botAI) { return new CombatFormationMoveAction(botAI); } static Action* combat_formation_move(PlayerbotAI* botAI) { return new CombatFormationMoveAction(botAI); }
static Action* tank_face(PlayerbotAI* botAI) { return new TankFaceAction(botAI); } static Action* tank_face(PlayerbotAI* botAI) { return new TankFaceAction(botAI); }
static Action* rear_flank(PlayerbotAI* botAI) { return new RearFlankAction(botAI); }
static Action* disperse_set(PlayerbotAI* botAI) { return new DisperseSetAction(botAI); } static Action* disperse_set(PlayerbotAI* botAI) { return new DisperseSetAction(botAI); }
static Action* gift_of_the_naaru(PlayerbotAI* botAI) { return new CastGiftOfTheNaaruAction(botAI); } static Action* gift_of_the_naaru(PlayerbotAI* botAI) { return new CastGiftOfTheNaaruAction(botAI); }
static Action* lifeblood(PlayerbotAI* botAI) { return new CastLifeBloodAction(botAI); } static Action* lifeblood(PlayerbotAI* botAI) { return new CastLifeBloodAction(botAI); }

View File

@@ -234,16 +234,15 @@ bool BGJoinAction::shouldJoinBg(BattlegroundQueueTypeId queueTypeId, Battlegroun
return false; return false;
TeamId teamId = bot->GetTeamId(); TeamId teamId = bot->GetTeamId();
bool noLag = sWorldUpdateTime.GetAverageUpdateTime() < (sRandomPlayerbotMgr->GetPlayers().empty() bool noLag = sWorldUpdateTime.GetAverageUpdateTime() < (sRandomPlayerbotMgr->GetPlayers().empty() ?
? sPlayerbotAIConfig->diffEmpty sPlayerbotAIConfig->botActiveAloneSmartScaleDiffEmpty :
: sPlayerbotAIConfig->diffWithPlayer) * sPlayerbotAIConfig->botActiveAloneSmartScaleDiffWithPlayer) * 1.1;
1.1;
uint32 BracketSize = bg->GetMaxPlayersPerTeam() * 2; uint32 BracketSize = bg->GetMaxPlayersPerTeam() * 2;
uint32 TeamSize = bg->GetMaxPlayersPerTeam(); uint32 TeamSize = bg->GetMaxPlayersPerTeam();
// If performance diff is enabled, only queue if there is no lag // If performance diff is enabled, only queue if there is no lag
if (sPlayerbotAIConfig->enablePrototypePerformanceDiff && !noLag) if (sPlayerbotAIConfig->botActiveAloneSmartScale && !noLag)
return false; return false;
// If the bot is in a group, only the leader can queue // If the bot is in a group, only the leader can queue
@@ -578,16 +577,15 @@ bool FreeBGJoinAction::shouldJoinBg(BattlegroundQueueTypeId queueTypeId, Battleg
return false; return false;
TeamId teamId = bot->GetTeamId(); TeamId teamId = bot->GetTeamId();
bool noLag = sWorldUpdateTime.GetAverageUpdateTime() < (sRandomPlayerbotMgr->GetPlayers().empty() bool noLag = sWorldUpdateTime.GetAverageUpdateTime() < (sRandomPlayerbotMgr->GetPlayers().empty() ?
? sPlayerbotAIConfig->diffEmpty sPlayerbotAIConfig->botActiveAloneSmartScaleDiffEmpty :
: sPlayerbotAIConfig->diffWithPlayer) * sPlayerbotAIConfig->botActiveAloneSmartScaleDiffWithPlayer) * 1.1;
1.1;
uint32 BracketSize = bg->GetMaxPlayersPerTeam() * 2; uint32 BracketSize = bg->GetMaxPlayersPerTeam() * 2;
uint32 TeamSize = bg->GetMaxPlayersPerTeam(); uint32 TeamSize = bg->GetMaxPlayersPerTeam();
// If performance diff is enabled, only queue if there is no lag // If performance diff is enabled, only queue if there is no lag
if (sPlayerbotAIConfig->enablePrototypePerformanceDiff && !noLag) if (sPlayerbotAIConfig->botActiveAloneSmartScale && !noLag)
return false; return false;
// If the bot is in a group, only the leader can queue // If the bot is in a group, only the leader can queue

View File

@@ -82,7 +82,7 @@ bool DropTargetAction::Execute(Event event)
bot->SetTarget(ObjectGuid::Empty); bot->SetTarget(ObjectGuid::Empty);
bot->SetSelection(ObjectGuid()); bot->SetSelection(ObjectGuid());
botAI->ChangeEngine(BOT_STATE_NON_COMBAT); botAI->ChangeEngine(BOT_STATE_NON_COMBAT);
botAI->InterruptSpell(); // botAI->InterruptSpell();
bot->AttackStop(); bot->AttackStop();
// if (Pet* pet = bot->GetPet()) // if (Pet* pet = bot->GetPet())

View File

@@ -38,7 +38,7 @@ bool TogglePetSpellAutoCastAction::Execute(Event event)
bool shouldApply = true; bool shouldApply = true;
// imp's spell, felhunte's intelligence, cat stealth // imp's spell, felhunte's intelligence, cat stealth
if (spellId == 4511 || spellId == 1742 || spellId == 54424 || spellId == 57564 || spellId == 57565 || if (spellId == 4511 || spellId == 1742 || spellId == 54424 || spellId == 57564 || spellId == 57565 ||
spellId == 57566 || spellId == 57567 || spellId == 24450 || spellId == 53477) spellId == 57566 || spellId == 57567 || spellId == 24450)
{ {
shouldApply = false; shouldApply = false;
} }

View File

@@ -196,8 +196,8 @@ bool CastEnchantItemAction::isPossible()
} }
CastHealingSpellAction::CastHealingSpellAction(PlayerbotAI* botAI, std::string const spell, uint8 estAmount, CastHealingSpellAction::CastHealingSpellAction(PlayerbotAI* botAI, std::string const spell, uint8 estAmount,
HealingManaEfficiency manaEfficiency) HealingManaEfficiency manaEfficiency, bool isOwner)
: CastAuraSpellAction(botAI, spell, true), estAmount(estAmount), manaEfficiency(manaEfficiency) : CastAuraSpellAction(botAI, spell, isOwner), estAmount(estAmount), manaEfficiency(manaEfficiency)
{ {
range = botAI->GetRange("heal"); range = botAI->GetRange("heal");
} }

View File

@@ -129,7 +129,7 @@ class CastHealingSpellAction : public CastAuraSpellAction
{ {
public: public:
CastHealingSpellAction(PlayerbotAI* botAI, std::string const spell, uint8 estAmount = 15.0f, CastHealingSpellAction(PlayerbotAI* botAI, std::string const spell, uint8 estAmount = 15.0f,
HealingManaEfficiency manaEfficiency = HealingManaEfficiency::MEDIUM); HealingManaEfficiency manaEfficiency = HealingManaEfficiency::MEDIUM, bool isOwner = true);
std::string const GetTargetName() override { return "self target"; } std::string const GetTargetName() override { return "self target"; }
bool isUseful() override; bool isUseful() override;
@@ -177,8 +177,8 @@ class HealPartyMemberAction : public CastHealingSpellAction, public PartyMemberA
{ {
public: public:
HealPartyMemberAction(PlayerbotAI* botAI, std::string const spell, uint8 estAmount = 15.0f, HealPartyMemberAction(PlayerbotAI* botAI, std::string const spell, uint8 estAmount = 15.0f,
HealingManaEfficiency manaEfficiency = HealingManaEfficiency::MEDIUM) HealingManaEfficiency manaEfficiency = HealingManaEfficiency::MEDIUM, bool isOwner = true)
: CastHealingSpellAction(botAI, spell, estAmount, manaEfficiency), PartyMemberActionNameSupport(spell) : CastHealingSpellAction(botAI, spell, estAmount, manaEfficiency, isOwner), PartyMemberActionNameSupport(spell)
{ {
} }

View File

@@ -827,11 +827,12 @@ bool MovementAction::ReachCombatTo(Unit* target, float distance)
float shortenTo = distance; float shortenTo = distance;
// Avoid walking too far when moving towards each other // Avoid walking too far when moving towards each other
if (bot->GetDistance(tx, ty, tz) >= 10.0f) float disToGo = bot->GetExactDist(tx, ty, tz) - distance;
shortenTo = std::max(distance, bot->GetDistance(tx, ty, tz) / 2); if (disToGo >= 10.0f)
shortenTo = disToGo / 2 + distance;
if (bot->GetExactDist(tx, ty, tz) <= shortenTo) // if (bot->GetExactDist(tx, ty, tz) <= shortenTo)
return false; // return false;
path.ShortenPathUntilDist(G3D::Vector3(tx, ty, tz), shortenTo); path.ShortenPathUntilDist(G3D::Vector3(tx, ty, tz), shortenTo);
G3D::Vector3 endPos = path.GetPath().back(); G3D::Vector3 endPos = path.GetPath().back();
@@ -2313,7 +2314,7 @@ bool TankFaceAction::Execute(Event event)
if (!bot->GetGroup()) if (!bot->GetGroup())
return false; return false;
if (!bot->IsWithinMeleeRange(target)) if (!bot->IsWithinMeleeRange(target) || target->isMoving())
return false; return false;
if (!AI_VALUE2(bool, "has aggro", "current target")) if (!AI_VALUE2(bool, "has aggro", "current target"))
@@ -2371,6 +2372,46 @@ bool TankFaceAction::Execute(Event event)
return MoveTo(bot->GetMapId(), nearest.GetPositionX(), nearest.GetPositionY(), nearest.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_COMBAT); return MoveTo(bot->GetMapId(), nearest.GetPositionX(), nearest.GetPositionY(), nearest.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_COMBAT);
} }
bool RearFlankAction::isUseful()
{
Unit* target = AI_VALUE(Unit*, "current target");
if (!target) { return false; }
// Need to double the front angle check to account for mirrored angle.
bool inFront = target->HasInArc(2.f * minAngle, bot);
// Rear check does not need to double this angle as the logic is inverted
// and we are subtracting from 2pi.
bool inRear = !target->HasInArc((2.f * M_PI) - maxAngle, bot);
return inFront || inRear;
}
bool RearFlankAction::Execute(Event event)
{
Unit* target = AI_VALUE(Unit*, "current target");
if (!target) { return false; }
float angle = frand(minAngle, maxAngle);
float baseDistance = bot->GetMeleeRange(target) * 0.5f;
Position leftFlank = target->GetPosition();
Position rightFlank = target->GetPosition();
Position* destination = nullptr;
leftFlank.RelocatePolarOffset(angle, baseDistance + distance);
rightFlank.RelocatePolarOffset(-angle, baseDistance + distance);
if (bot->GetExactDist2d(leftFlank) < bot->GetExactDist2d(rightFlank))
{
destination = &leftFlank;
}
else
{
destination = &rightFlank;
}
return MoveTo(bot->GetMapId(), destination->GetPositionX(), destination->GetPositionY(), destination->GetPositionZ(),
false, false, false, true, MovementPriority::MOVEMENT_COMBAT);
}
bool DisperseSetAction::Execute(Event event) bool DisperseSetAction::Execute(Event event)
{ {
std::string const text = event.getParam(); std::string const text = event.getParam();
@@ -2513,7 +2554,7 @@ bool SetBehindTargetAction::Execute(Event event)
if (target->GetVictim() == bot) if (target->GetVictim() == bot)
return false; return false;
if (!bot->IsWithinMeleeRange(target)) if (!bot->IsWithinMeleeRange(target) || target->isMoving())
return false; return false;
float deltaAngle = Position::NormalizeOrientation(target->GetOrientation() - target->GetAngle(bot)); float deltaAngle = Position::NormalizeOrientation(target->GetOrientation() - target->GetAngle(bot));

View File

@@ -18,6 +18,9 @@ class Unit;
class WorldObject; class WorldObject;
class Position; class Position;
#define ANGLE_45_DEG (static_cast<float>(M_PI) / 4.f)
#define ANGLE_90_DEG M_PI_2
#define ANGLE_120_DEG (2.f * static_cast<float>(M_PI) / 3.f)
class MovementAction : public Action class MovementAction : public Action
{ {
@@ -144,6 +147,27 @@ public:
bool Execute(Event event) override; bool Execute(Event event) override;
}; };
class RearFlankAction : public MovementAction
{
// 90 degree minimum angle prevents any frontal cleaves/breaths and avoids parry-hasting the boss.
// 120 degree maximum angle leaves a 120 degree symmetrical cone at the tail end which is usually enough to avoid tail swipes.
// Some dragons or mobs may have different danger zone angles, override if needed.
public:
RearFlankAction(PlayerbotAI* botAI, float distance = 0.0f, float minAngle = ANGLE_90_DEG, float maxAngle = ANGLE_120_DEG)
: MovementAction(botAI, "rear flank")
{
this->distance = distance;
this->minAngle = minAngle;
this->maxAngle = maxAngle;
}
bool Execute(Event event) override;
bool isUseful() override;
protected:
float distance, minAngle, maxAngle;
};
class DisperseSetAction : public Action class DisperseSetAction : public Action
{ {
public: public:
@@ -268,4 +292,5 @@ public:
bool Execute(Event event) override; bool Execute(Event event) override;
}; };
#endif #endif

View File

@@ -145,15 +145,12 @@ void CasterDruidStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
new TriggerNode("medium mana", NextAction::array(0, new NextAction("innervate", ACTION_HIGH + 9), NULL))); new TriggerNode("medium mana", NextAction::array(0, new NextAction("innervate", ACTION_HIGH + 9), NULL)));
triggers.push_back(new TriggerNode("enemy too close for spell", triggers.push_back(new TriggerNode("enemy too close for spell",
NextAction::array(0, new NextAction("flee", ACTION_MOVE + 9), nullptr))); NextAction::array(0, new NextAction("flee", ACTION_MOVE + 9), nullptr)));
triggers.push_back(
new TriggerNode("party member remove curse",
NextAction::array(0, new NextAction("remove curse on party", ACTION_DISPEL + 7), NULL)));
} }
void CasterDruidAoeStrategy::InitTriggers(std::vector<TriggerNode*>& triggers) void CasterDruidAoeStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{ {
triggers.push_back( triggers.push_back(
new TriggerNode("high aoe", NextAction::array(0, new NextAction("hurricane", ACTION_HIGH + 1), nullptr))); new TriggerNode("medium aoe", NextAction::array(0, new NextAction("hurricane", ACTION_HIGH + 1), nullptr)));
triggers.push_back(new TriggerNode( triggers.push_back(new TriggerNode(
"light aoe", NextAction::array(0, new NextAction("starfall", ACTION_NORMAL + 5), "light aoe", NextAction::array(0, new NextAction("starfall", ACTION_NORMAL + 5),
new NextAction("insect swarm on attacker", ACTION_NORMAL + 3), new NextAction("insect swarm on attacker", ACTION_NORMAL + 3),

View File

@@ -48,3 +48,34 @@ bool CastRebirthAction::isUseful()
return CastSpellAction::isUseful() && return CastSpellAction::isUseful() &&
AI_VALUE2(float, "distance", GetTargetName()) <= sPlayerbotAIConfig->spellDistance; AI_VALUE2(float, "distance", GetTargetName()) <= sPlayerbotAIConfig->spellDistance;
} }
Unit* CastRejuvenationOnNotFullAction::GetTarget()
{
Group* group = bot->GetGroup();
MinValueCalculator calc(100);
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
{
Player* player = gref->GetSource();
if (!player)
continue;
if (player->isDead() || player->IsFullHealth())
{
continue;
}
if (player->GetDistance2d(bot) > sPlayerbotAIConfig->spellDistance)
{
continue;
}
if (botAI->HasAura("rejuvenation", player))
{
continue;
}
calc.probe(player->GetHealthPct(), player);
}
return (Unit*)calc.param;
}
bool CastRejuvenationOnNotFullAction::isUseful()
{
return GetTarget();
}

View File

@@ -311,4 +311,16 @@ public:
CastEnrageAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "enrage") {} CastEnrageAction(PlayerbotAI* ai) : CastBuffSpellAction(ai, "enrage") {}
}; };
class CastRejuvenationOnNotFullAction : public HealPartyMemberAction
{
public:
CastRejuvenationOnNotFullAction(PlayerbotAI* ai)
: HealPartyMemberAction(ai, "rejuvenation", 5.0f, HealingManaEfficiency::VERY_HIGH)
{
}
bool isUseful() override;
Unit* GetTarget() override;
};
#endif #endif

View File

@@ -14,6 +14,7 @@
#include "DruidShapeshiftActions.h" #include "DruidShapeshiftActions.h"
#include "DruidTriggers.h" #include "DruidTriggers.h"
#include "GenericDruidNonCombatStrategy.h" #include "GenericDruidNonCombatStrategy.h"
#include "GenericDruidStrategy.h"
#include "HealDruidStrategy.h" #include "HealDruidStrategy.h"
#include "MeleeDruidStrategy.h" #include "MeleeDruidStrategy.h"
#include "Playerbots.h" #include "Playerbots.h"
@@ -33,6 +34,7 @@ public:
creators["buff"] = &DruidStrategyFactoryInternal::buff; creators["buff"] = &DruidStrategyFactoryInternal::buff;
creators["boost"] = &DruidStrategyFactoryInternal::boost; creators["boost"] = &DruidStrategyFactoryInternal::boost;
creators["cc"] = &DruidStrategyFactoryInternal::cc; creators["cc"] = &DruidStrategyFactoryInternal::cc;
creators["healer dps"] = &DruidStrategyFactoryInternal::healer_dps;
} }
private: private:
@@ -45,6 +47,7 @@ private:
static Strategy* buff(PlayerbotAI* botAI) { return new GenericDruidBuffStrategy(botAI); } static Strategy* buff(PlayerbotAI* botAI) { return new GenericDruidBuffStrategy(botAI); }
static Strategy* boost(PlayerbotAI* botAI) { return new DruidBoostStrategy(botAI); } static Strategy* boost(PlayerbotAI* botAI) { return new DruidBoostStrategy(botAI); }
static Strategy* cc(PlayerbotAI* botAI) { return new DruidCcStrategy(botAI); } static Strategy* cc(PlayerbotAI* botAI) { return new DruidCcStrategy(botAI); }
static Strategy* healer_dps(PlayerbotAI* botAI) { return new DruidHealerDpsStrategy(botAI); }
}; };
class DruidDruidStrategyFactoryInternal : public NamedObjectContext<Strategy> class DruidDruidStrategyFactoryInternal : public NamedObjectContext<Strategy>
@@ -161,6 +164,7 @@ public:
creators["travel form"] = &DruidAiObjectContextInternal::travel_form; creators["travel form"] = &DruidAiObjectContextInternal::travel_form;
creators["aquatic form"] = &DruidAiObjectContextInternal::aquatic_form; creators["aquatic form"] = &DruidAiObjectContextInternal::aquatic_form;
creators["caster form"] = &DruidAiObjectContextInternal::caster_form; creators["caster form"] = &DruidAiObjectContextInternal::caster_form;
creators["cancel tree form"] = &DruidAiObjectContextInternal::cancel_tree_form;
creators["mangle (bear)"] = &DruidAiObjectContextInternal::mangle_bear; creators["mangle (bear)"] = &DruidAiObjectContextInternal::mangle_bear;
creators["maul"] = &DruidAiObjectContextInternal::maul; creators["maul"] = &DruidAiObjectContextInternal::maul;
creators["bash"] = &DruidAiObjectContextInternal::bash; creators["bash"] = &DruidAiObjectContextInternal::bash;
@@ -205,6 +209,7 @@ public:
creators["healing touch"] = &DruidAiObjectContextInternal::healing_touch; creators["healing touch"] = &DruidAiObjectContextInternal::healing_touch;
creators["regrowth on party"] = &DruidAiObjectContextInternal::regrowth_on_party; creators["regrowth on party"] = &DruidAiObjectContextInternal::regrowth_on_party;
creators["rejuvenation on party"] = &DruidAiObjectContextInternal::rejuvenation_on_party; creators["rejuvenation on party"] = &DruidAiObjectContextInternal::rejuvenation_on_party;
creators["rejuvenation on not full"] = &DruidAiObjectContextInternal::rejuvenation_on_not_full;
creators["healing touch on party"] = &DruidAiObjectContextInternal::healing_touch_on_party; creators["healing touch on party"] = &DruidAiObjectContextInternal::healing_touch_on_party;
creators["rebirth"] = &DruidAiObjectContextInternal::rebirth; creators["rebirth"] = &DruidAiObjectContextInternal::rebirth;
creators["revive"] = &DruidAiObjectContextInternal::revive; creators["revive"] = &DruidAiObjectContextInternal::revive;
@@ -246,6 +251,7 @@ private:
static Action* travel_form(PlayerbotAI* botAI) { return new CastTravelFormAction(botAI); } static Action* travel_form(PlayerbotAI* botAI) { return new CastTravelFormAction(botAI); }
static Action* aquatic_form(PlayerbotAI* botAI) { return new CastAquaticFormAction(botAI); } static Action* aquatic_form(PlayerbotAI* botAI) { return new CastAquaticFormAction(botAI); }
static Action* caster_form(PlayerbotAI* botAI) { return new CastCasterFormAction(botAI); } static Action* caster_form(PlayerbotAI* botAI) { return new CastCasterFormAction(botAI); }
static Action* cancel_tree_form(PlayerbotAI* botAI) { return new CastCancelTreeFormAction(botAI); }
static Action* mangle_bear(PlayerbotAI* botAI) { return new CastMangleBearAction(botAI); } static Action* mangle_bear(PlayerbotAI* botAI) { return new CastMangleBearAction(botAI); }
static Action* maul(PlayerbotAI* botAI) { return new CastMaulAction(botAI); } static Action* maul(PlayerbotAI* botAI) { return new CastMaulAction(botAI); }
static Action* bash(PlayerbotAI* botAI) { return new CastBashAction(botAI); } static Action* bash(PlayerbotAI* botAI) { return new CastBashAction(botAI); }
@@ -290,6 +296,7 @@ private:
static Action* healing_touch(PlayerbotAI* botAI) { return new CastHealingTouchAction(botAI); } static Action* healing_touch(PlayerbotAI* botAI) { return new CastHealingTouchAction(botAI); }
static Action* regrowth_on_party(PlayerbotAI* botAI) { return new CastRegrowthOnPartyAction(botAI); } static Action* regrowth_on_party(PlayerbotAI* botAI) { return new CastRegrowthOnPartyAction(botAI); }
static Action* rejuvenation_on_party(PlayerbotAI* botAI) { return new CastRejuvenationOnPartyAction(botAI); } static Action* rejuvenation_on_party(PlayerbotAI* botAI) { return new CastRejuvenationOnPartyAction(botAI); }
static Action* rejuvenation_on_not_full(PlayerbotAI* botAI) { return new CastRejuvenationOnNotFullAction(botAI); }
static Action* healing_touch_on_party(PlayerbotAI* botAI) { return new CastHealingTouchOnPartyAction(botAI); } static Action* healing_touch_on_party(PlayerbotAI* botAI) { return new CastHealingTouchOnPartyAction(botAI); }
static Action* rebirth(PlayerbotAI* botAI) { return new CastRebirthAction(botAI); } static Action* rebirth(PlayerbotAI* botAI) { return new CastRebirthAction(botAI); }
static Action* revive(PlayerbotAI* botAI) { return new CastReviveAction(botAI); } static Action* revive(PlayerbotAI* botAI) { return new CastReviveAction(botAI); }

View File

@@ -45,6 +45,17 @@ bool CastCasterFormAction::Execute(Event event)
return true; return true;
} }
bool CastCancelTreeFormAction::isUseful()
{
return botAI->HasAura(33891, bot);
}
bool CastCancelTreeFormAction::Execute(Event event)
{
botAI->RemoveAura("tree of life");
return true;
}
bool CastTreeFormAction::isUseful() bool CastTreeFormAction::isUseful()
{ {
return GetTarget() && CastSpellAction::isUseful() && !botAI->HasAura(33891, bot); return GetTarget() && CastSpellAction::isUseful() && !botAI->HasAura(33891, bot);

View File

@@ -70,4 +70,14 @@ public:
bool Execute(Event event) override; bool Execute(Event event) override;
}; };
class CastCancelTreeFormAction : public CastBuffSpellAction
{
public:
CastCancelTreeFormAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "cancel tree form") {}
bool isUseful() override;
bool isPossible() override { return true; }
bool Execute(Event event) override;
};
#endif #endif

View File

@@ -125,26 +125,34 @@ void GenericDruidNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& trig
triggers.push_back( triggers.push_back(
new TriggerNode("party member critical health", new TriggerNode("party member critical health",
NextAction::array(0, new NextAction("rejuvenation on party", ACTION_MEDIUM_HEAL + 5), NextAction::array(0,
new NextAction("regrowth on party", ACTION_MEDIUM_HEAL + 6), NULL))); 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( triggers.push_back(
new TriggerNode("party member low health", new TriggerNode("party member low health",
NextAction::array(0, new NextAction("rejuvenation on party", ACTION_MEDIUM_HEAL + 3), NextAction::array(0,
new NextAction("regrowth on party", ACTION_MEDIUM_HEAL + 4), NULL))); 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( triggers.push_back(
new TriggerNode("party member medium health", new TriggerNode("party member medium health",
NextAction::array(0, new NextAction("rejuvenation on party", ACTION_MEDIUM_HEAL + 1), NextAction::array(0, new NextAction("wild growth on party", ACTION_MEDIUM_HEAL + 3),
new NextAction("regrowth on party", ACTION_MEDIUM_HEAL + 2), NULL))); new NextAction("regrowth on party", ACTION_MEDIUM_HEAL + 2),
new NextAction("rejuvenation on party", ACTION_MEDIUM_HEAL + 1),
nullptr)));
triggers.push_back( triggers.push_back(
new TriggerNode("party member almost full health", new TriggerNode("party member almost full health",
NextAction::array(0, new NextAction("rejuvenation on party", ACTION_LIGHT_HEAL + 2), NULL))); NextAction::array(0, new NextAction("wild growth on party", ACTION_LIGHT_HEAL + 3), new NextAction("rejuvenation on party", ACTION_LIGHT_HEAL + 2), NULL)));
triggers.push_back( triggers.push_back(
new TriggerNode("party member remove curse", new TriggerNode("party member remove curse",
NextAction::array(0, new NextAction("remove curse on party", ACTION_DISPEL + 7), NULL))); NextAction::array(0, new NextAction("remove curse on party", ACTION_DISPEL + 7), nullptr)));
} }
GenericDruidBuffStrategy::GenericDruidBuffStrategy(PlayerbotAI* botAI) : NonCombatStrategy(botAI) GenericDruidBuffStrategy::GenericDruidBuffStrategy(PlayerbotAI* botAI) : NonCombatStrategy(botAI)

View File

@@ -130,6 +130,11 @@ void DruidCureStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
triggers.push_back( triggers.push_back(
new TriggerNode("party member cure poison", new TriggerNode("party member cure poison",
NextAction::array(0, new NextAction("abolish poison on party", ACTION_DISPEL + 1), nullptr))); NextAction::array(0, new NextAction("abolish poison on party", ACTION_DISPEL + 1), nullptr)));
triggers.push_back(
new TriggerNode("party member remove curse",
NextAction::array(0, new NextAction("remove curse on party", ACTION_DISPEL + 7), NULL)));
} }
void DruidBoostStrategy::InitTriggers(std::vector<TriggerNode*>& triggers) void DruidBoostStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
@@ -147,3 +152,22 @@ void DruidCcStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
triggers.push_back(new TriggerNode( triggers.push_back(new TriggerNode(
"hibernate", NextAction::array(0, new NextAction("hibernate on cc", ACTION_HIGH + 3), nullptr))); "hibernate", NextAction::array(0, new NextAction("hibernate on cc", ACTION_HIGH + 3), nullptr)));
} }
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)));
// 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

@@ -46,4 +46,13 @@ public:
std::string const getName() override { return "cc"; } std::string const getName() override { return "cc"; }
}; };
class DruidHealerDpsStrategy : public Strategy
{
public:
DruidHealerDpsStrategy(PlayerbotAI* botAI) : Strategy(botAI) {}
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
std::string const getName() override { return "healer dps"; }
};
#endif #endif

View File

@@ -10,7 +10,12 @@
class HealDruidStrategyActionNodeFactory : public NamedObjectFactory<ActionNode> class HealDruidStrategyActionNodeFactory : public NamedObjectFactory<ActionNode>
{ {
public: public:
HealDruidStrategyActionNodeFactory() { creators["nourish on party"] = &nourtish_on_party; } 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: private:
static ActionNode* nourtish_on_party([[maybe_unused]] PlayerbotAI* botAI) static ActionNode* nourtish_on_party([[maybe_unused]] PlayerbotAI* botAI)
@@ -20,6 +25,27 @@ private:
/*A*/ NextAction::array(0, new NextAction("healing touch on party"), nullptr), /*A*/ NextAction::array(0, new NextAction("healing touch on party"), nullptr),
/*C*/ nullptr); /*C*/ nullptr);
} }
// 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) HealDruidStrategy::HealDruidStrategy(PlayerbotAI* botAI) : GenericDruidStrategy(botAI)
@@ -31,61 +57,72 @@ void HealDruidStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{ {
GenericDruidStrategy::InitTriggers(triggers); GenericDruidStrategy::InitTriggers(triggers);
// triggers.push_back(new TriggerNode("enemy out of spell", NextAction::array(0, new NextAction("reach spell", // triggers.push_back(
// ACTION_NORMAL + 9), nullptr))); // new TriggerNode("tree form", NextAction::array(0, new NextAction("tree form", ACTION_HIGH + 1), nullptr)));
triggers.push_back(
new TriggerNode("tree form", NextAction::array(0, new NextAction("tree form", ACTION_HIGH + 1), nullptr)));
triggers.push_back(new TriggerNode( triggers.push_back(new TriggerNode(
"party member to heal out of spell range", "party member to heal out of spell range",
NextAction::array(0, new NextAction("reach party member to heal", ACTION_CRITICAL_HEAL + 9), nullptr))); NextAction::array(0, new NextAction("reach party member to heal", ACTION_CRITICAL_HEAL + 9), nullptr)));
triggers.push_back(
new TriggerNode("party member remove curse",
NextAction::array(0, new NextAction("remove curse on party", ACTION_DISPEL + 7), NULL)));
// CRITICAL // CRITICAL
triggers.push_back( triggers.push_back(
new TriggerNode("party member critical health", new TriggerNode("party member critical health",
NextAction::array(0, new NextAction("swiftmend on party", ACTION_CRITICAL_HEAL + 4), NextAction::array(0,
new NextAction("wild growth", ACTION_CRITICAL_HEAL + 3), new NextAction("tree form", ACTION_CRITICAL_HEAL + 4.1f),
new NextAction("regrowth on party", ACTION_CRITICAL_HEAL + 2), 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("nourish on party", ACTION_CRITICAL_HEAL + 1),
// new NextAction("healing touch on party", ACTION_CRITICAL_HEAL + 0), // new NextAction("healing touch on party", ACTION_CRITICAL_HEAL + 0),
NULL))); nullptr)));
triggers.push_back( triggers.push_back(
new TriggerNode("party member critical health", new TriggerNode("party member critical health",
NextAction::array(0, new NextAction("nature's swiftness", ACTION_CRITICAL_HEAL + 4), NULL))); NextAction::array(0, new NextAction("nature's swiftness", ACTION_CRITICAL_HEAL + 4), nullptr)));
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)));
triggers.push_back( triggers.push_back(
new TriggerNode("medium group heal occasion", new TriggerNode("medium group heal setting",
NextAction::array(0, new NextAction("tranquility", ACTION_CRITICAL_HEAL + 5), NULL))); NextAction::array(0,
new NextAction("tree form", ACTION_CRITICAL_HEAL + 0.6f),
new NextAction("tranquility", ACTION_CRITICAL_HEAL + 0.5f), nullptr)));
// LOW // LOW
triggers.push_back( triggers.push_back(
new TriggerNode("party member low health", new TriggerNode("party member low health",
NextAction::array(0, new NextAction("wild growth on party", ACTION_MEDIUM_HEAL + 9), NextAction::array(0, new NextAction("tree form", ACTION_MEDIUM_HEAL + 1.5f),
new NextAction("regrowth on party", ACTION_MEDIUM_HEAL + 8), new NextAction("wild growth on party", ACTION_MEDIUM_HEAL + 1.4f),
new NextAction("swiftmend on party", ACTION_MEDIUM_HEAL + 7), new NextAction("regrowth on party", ACTION_MEDIUM_HEAL + 1.3f),
new NextAction("nourish on party", ACTION_MEDIUM_HEAL + 6), new NextAction("swiftmend on party", ACTION_MEDIUM_HEAL + 1.2),
// new NextAction("healing touch on party", ACTION_MEDIUM_HEAL + 5), new NextAction("nourish on party", ACTION_MEDIUM_HEAL + 1.1f),
NULL))); nullptr)));
// MEDIUM // MEDIUM
triggers.push_back( triggers.push_back(
new TriggerNode("party member medium health", new TriggerNode("party member medium health",
NextAction::array(0, new NextAction("wild growth on party", ACTION_MEDIUM_HEAL + 4), NextAction::array(0,
new NextAction("rejuvenation on party", ACTION_MEDIUM_HEAL + 3), new NextAction("tree form", ACTION_MEDIUM_HEAL + 0.5f),
new NextAction("regrowth on party", ACTION_MEDIUM_HEAL + 2), new NextAction("wild growth on party", ACTION_MEDIUM_HEAL + 0.4f),
new NextAction("nourish on party", ACTION_MEDIUM_HEAL + 1), NULL))); 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)));
// almost full // almost full
triggers.push_back( triggers.push_back(
new TriggerNode("party member almost full health", new TriggerNode("party member almost full health",
NextAction::array(0, new NextAction("wild growth on party", ACTION_LIGHT_HEAL + 3), NextAction::array(0, new NextAction("wild growth on party", ACTION_LIGHT_HEAL + 0.3f),
new NextAction("rejuvenation on party", ACTION_LIGHT_HEAL + 2), new NextAction("rejuvenation on party", ACTION_LIGHT_HEAL + 0.2f),
new NextAction("regrowth on party", ACTION_LIGHT_HEAL + 1), NULL))); new NextAction("regrowth on party", ACTION_LIGHT_HEAL + 0.1f), nullptr)));
triggers.push_back( triggers.push_back(
new TriggerNode("medium mana", NextAction::array(0, new NextAction("innervate", ACTION_HIGH + 5), NULL))); new TriggerNode("medium mana", NextAction::array(0, new NextAction("innervate", ACTION_HIGH + 5), nullptr)));
triggers.push_back(new TriggerNode("enemy too close for spell", triggers.push_back(new TriggerNode("enemy too close for spell",
NextAction::array(0, new NextAction("flee", ACTION_MOVE + 9), nullptr))); NextAction::array(0, new NextAction("flee", ACTION_MOVE + 9), nullptr)));

View File

@@ -1,22 +1,17 @@
#ifndef _PLAYERBOT_DUNGEONSTRATEGYCONTEXT_H_ #ifndef _PLAYERBOT_DUNGEONSTRATEGYCONTEXT_H
#define _PLAYERBOT_DUNGEONSTRATEGYCONTEXT_H_ #define _PLAYERBOT_DUNGEONSTRATEGYCONTEXT_H
#include "Strategy.h" #include "Strategy.h"
#include "wotlk/utgardekeep/UtgardeKeepStrategy.h" #include "wotlk/utgardekeep/UtgardeKeepStrategy.h"
#include "wotlk/nexus/NexusStrategy.h"
#include "wotlk/azjolnerub/AzjolNerubStrategy.h"
#include "wotlk/oldkingdom/OldKingdomStrategy.h"
#include "wotlk/draktharonkeep/DrakTharonKeepStrategy.h"
#include "wotlk/violethold/VioletHoldStrategy.h"
/* /*
Full list/TODO: Full list/TODO:
The Nexus - Nex
Grand Magus Telestra, Anomalus, Ormorok the Tree-Shaper, Keristrasza, Commander Stoutbeard (Horde Heroic Only)/Commander Kolurg (Alliance Heroic Only)
Azjol-Nerub: Azjol-Nerub - AN
Krik'thir the Gatewatcher, Hadronox, Anub'arak
Ahn'kahet: The Old Kingdom - OK
Elder Nadox, Prince Taldaram, Jedoga Shadowseeker, Herald Volazj, Amanitar (Heroic Only)
Drak'Tharon Keep - DTK
Trollgore, Novos the Summoner, King Dred, The Prophet Tharon'ja
The Violet Hold - VH
Erekem, Moragg, Ichoron, Xevozz, Lavanthor, Zuramat the Obliterator, Cyanigosa
Gundrak - GD Gundrak - GD
Slad'ran, Drakkari Colossus, Moorabi, Gal'darah, Eck the Ferocious (Heroic only) Slad'ran, Drakkari Colossus, Moorabi, Gal'darah, Eck the Ferocious (Heroic only)
Halls of Stone - HoS Halls of Stone - HoS
@@ -76,11 +71,12 @@ class DungeonStrategyContext : public NamedObjectContext<Strategy>
} }
private: private:
static Strategy* wotlk_uk(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); } static Strategy* wotlk_uk(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); }
static Strategy* wotlk_nex(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); } static Strategy* wotlk_nex(PlayerbotAI* botAI) { return new WotlkDungeonNexStrategy(botAI); }
static Strategy* wotlk_an(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); } static Strategy* wotlk_an(PlayerbotAI* botAI) { return new WotlkDungeonANStrategy(botAI); }
static Strategy* wotlk_ok(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); } static Strategy* wotlk_ok(PlayerbotAI* botAI) { return new WotlkDungeonOKStrategy(botAI); }
static Strategy* wotlk_dtk(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); } static Strategy* wotlk_dtk(PlayerbotAI* botAI) { return new WotlkDungeonDTKStrategy(botAI); }
static Strategy* wotlk_vh(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); } static Strategy* wotlk_vh(PlayerbotAI* botAI) { return new WotlkDungeonVHStrategy(botAI); }
static Strategy* wotlk_gd(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); } static Strategy* wotlk_gd(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); }
static Strategy* wotlk_hos(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); } static Strategy* wotlk_hos(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); }
static Strategy* wotlk_hol(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); } static Strategy* wotlk_hol(PlayerbotAI* botAI) { return new WotlkDungeonUKStrategy(botAI); }

View File

@@ -1,5 +1,5 @@
#ifndef _PLAYERBOT_DUNGEONUTILS_H_ #ifndef _PLAYERBOT_DUNGEONUTILS_H
#define _PLAYERBOT_DUNGEONUTILS_H_ #define _PLAYERBOT_DUNGEONUTILS_H
template<class T> inline template<class T> inline

View File

@@ -1,12 +1,12 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONACTIONCONTEXT_H_ #ifndef _PLAYERBOT_WOTLKDUNGEONACTIONCONTEXT_H
#define _PLAYERBOT_WOTLKDUNGEONACTIONCONTEXT_H_ #define _PLAYERBOT_WOTLKDUNGEONACTIONCONTEXT_H
#include "utgardekeep/UtgardeKeepActionContext.h" #include "utgardekeep/UtgardeKeepActionContext.h"
// #include "nexus/NexusActionContext.h" #include "nexus/NexusActionContext.h"
// #include "azjolnerub/AzjolNerubActionContext.h" #include "azjolnerub/AzjolNerubActionContext.h"
// #include "oldkingdom/OldKingdomActionContext.h" #include "oldkingdom/OldKingdomActionContext.h"
// #include "draktharonkeep/DraktharonKeepActionContext.h" #include "draktharonkeep/DrakTharonKeepActionContext.h"
// #include "violethold/VioletHoldActionContext.h" #include "violethold/VioletHoldActionContext.h"
// #include "gundrak/GundrakActionContext.h" // #include "gundrak/GundrakActionContext.h"
// #include "hallsofstone/HallsOfStoneActionContext.h" // #include "hallsofstone/HallsOfStoneActionContext.h"
// #include "hallsoflightning/HallsOfLightningActionContext.h" // #include "hallsoflightning/HallsOfLightningActionContext.h"

View File

@@ -1,12 +1,12 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONTRIGGERCONTEXT_H_ #ifndef _PLAYERBOT_WOTLKDUNGEONTRIGGERCONTEXT_H
#define _PLAYERBOT_WOTLKDUNGEONTRIGGERCONTEXT_H_ #define _PLAYERBOT_WOTLKDUNGEONTRIGGERCONTEXT_H
#include "utgardekeep/UtgardeKeepTriggerContext.h" #include "utgardekeep/UtgardeKeepTriggerContext.h"
// #include "nexus/NexusTriggerContext.h" #include "nexus/NexusTriggerContext.h"
// #include "azjolnerub/AzjolNerubTriggerContext.h" #include "azjolnerub/AzjolNerubTriggerContext.h"
// #include "oldkingdom/OldKingdomTriggerContext.h" #include "oldkingdom/OldKingdomTriggerContext.h"
// #include "draktharonkeep/DraktharonKeepTriggerContext.h" #include "draktharonkeep/DrakTharonKeepTriggerContext.h"
// #include "violethold/VioletHoldTriggerContext.h" #include "violethold/VioletHoldTriggerContext.h"
// #include "gundrak/GundrakTriggerContext.h" // #include "gundrak/GundrakTriggerContext.h"
// #include "hallsofstone/HallsOfStoneTriggerContext.h" // #include "hallsofstone/HallsOfStoneTriggerContext.h"
// #include "hallsoflightning/HallsOfLightningTriggerContext.h" // #include "hallsoflightning/HallsOfLightningTriggerContext.h"

View File

@@ -0,0 +1,22 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONANACTIONCONTEXT_H
#define _PLAYERBOT_WOTLKDUNGEONANACTIONCONTEXT_H
#include "Action.h"
#include "NamedObjectContext.h"
#include "AzjolNerubActions.h"
class WotlkDungeonANActionContext : public NamedObjectContext<Action>
{
public:
WotlkDungeonANActionContext() {
creators["attack web wrap"] = &WotlkDungeonANActionContext::attack_web_wrap;
creators["krik'thir priority"] = &WotlkDungeonANActionContext::krikthir_priority;
creators["dodge pound"] = &WotlkDungeonANActionContext::dodge_pound;
}
private:
static Action* attack_web_wrap(PlayerbotAI* ai) { return new AttackWebWrapAction(ai); }
static Action* krikthir_priority(PlayerbotAI* ai) { return new WatchersTargetAction(ai); }
static Action* dodge_pound(PlayerbotAI* ai) { return new AnubarakDodgePoundAction(ai); }
};
#endif

View File

@@ -0,0 +1,110 @@
#include "Playerbots.h"
#include "AzjolNerubActions.h"
#include "AzjolNerubStrategy.h"
bool AttackWebWrapAction::isUseful() { return !botAI->IsHeal(bot); }
bool AttackWebWrapAction::Execute(Event event)
{
Unit* webWrap = nullptr;
// Target is not findable from threat table using AI_VALUE2(),
// therefore need to search manually for the unit name
GuidVector targets = AI_VALUE(GuidVector, "possible targets no los");
for (auto i = targets.begin(); i != targets.end(); ++i)
{
Unit* unit = botAI->GetUnit(*i);
if (unit && unit->GetEntry() == NPC_WEB_WRAP)
{
webWrap = unit;
break;
}
}
if (!webWrap || AI_VALUE(Unit*, "current target") == webWrap)
{
return false;
}
return Attack(webWrap);
}
bool WatchersTargetAction::isUseful() { return !botAI->IsHeal(bot); }
bool WatchersTargetAction::Execute(Event event)
{
// Always prioritise web wraps
Unit* currTarget = AI_VALUE(Unit*, "current target");
if (currTarget && currTarget->GetEntry() == NPC_WEB_WRAP) { return false; }
// Do not search all units in range!
// There are many adds we don't want to aggro in close proximity,
// only check in-combat adds now.
GuidVector attackers = AI_VALUE(GuidVector, "attackers");
Unit* priorityTargets[4] = {nullptr, nullptr, nullptr, nullptr};
for (auto& attacker : attackers)
{
Unit* npc = botAI->GetUnit(attacker);
if (!npc)
{
continue;
}
switch (npc->GetEntry())
{
// Focus skirmishers first
case NPC_WATCHER_SKIRMISHER:
priorityTargets[0] = npc;
break;
// Then shadowcaster. This doesn't work so well for the shadowcaster
// + skirmisher pack - ideally we would kill the watcher second.
// But don't want to make this unnecessarily complex and rigid...
// Will revisit if this causes problems in heroic.
case NPC_WATCHER_SHADOWCASTER:
priorityTargets[1] = npc;
break;
// Named watcher next
case NPC_WATCHER_SILTHIK:
case NPC_WATCHER_GASHRA:
case NPC_WATCHER_NARJIL:
priorityTargets[2] = npc;
break;
// Warrior last
case NPC_WATCHER_WARRIOR:
priorityTargets[3] = npc;
break;
}
}
for (Unit* target : priorityTargets)
{
// Attack the first valid split target in the priority list
if (target)
{
if (currTarget != target)
{
// bot->Yell("ATTACKING "+target->GetName(), LANG_UNIVERSAL);
return Attack(target);
}
// Don't continue loop here, the target exists so we don't
// want to move down the prio list. We just don't need to send attack
// command again, just return false and exit the loop that way
return false;
}
}
return false;
}
bool AnubarakDodgePoundAction::isUseful() { return !AI_VALUE2(bool, "behind", "current target"); }
bool AnubarakDodgePoundAction::Execute(Event event)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "anub'arak");
if (!boss) { return false; }
float distance = bot->GetExactDist2d(boss->GetPosition());
// Extra units to move into the boss, instead of being just 1 pixel past his midpoint.
// Can be adjusted - this value tends to mirror how a human would play,
// and visibly ensures you won't get hit while not creating excessive movements.
float distanceExtra = 2.0f;
return Move(bot->GetAngle(boss), distance + distanceExtra);
}

View File

@@ -0,0 +1,34 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONANACTIONS_H
#define _PLAYERBOT_WOTLKDUNGEONANACTIONS_H
#include "Action.h"
#include "AttackAction.h"
#include "PlayerbotAI.h"
#include "Playerbots.h"
#include "AzjolNerubTriggers.h"
class AttackWebWrapAction : public AttackAction
{
public:
AttackWebWrapAction(PlayerbotAI* ai) : AttackAction(ai, "attack web wrap") {}
bool Execute(Event event) override;
bool isUseful() override;
};
class WatchersTargetAction : public AttackAction
{
public:
WatchersTargetAction(PlayerbotAI* ai) : AttackAction(ai, "krik'thir priority") {}
bool Execute(Event event) override;
bool isUseful() override;
};
class AnubarakDodgePoundAction : public AttackAction
{
public:
AnubarakDodgePoundAction(PlayerbotAI* ai) : AttackAction(ai, "anub'arak dodge pound") {}
bool Execute(Event event) override;
bool isUseful() override;
};
#endif

View File

@@ -0,0 +1,56 @@
#include "AzjolNerubMultipliers.h"
#include "AzjolNerubActions.h"
#include "GenericSpellActions.h"
#include "ChooseTargetActions.h"
#include "MovementActions.h"
#include "AzjolNerubTriggers.h"
#include "Action.h"
float KrikthirMultiplier::GetValue(Action* action)
{
// Target is not findable from threat table using AI_VALUE2(),
// therefore need to search manually for the unit name
Unit* boss = nullptr;
Unit* watcher = nullptr;
GuidVector targets = AI_VALUE(GuidVector, "possible targets no los");
for (auto i = targets.begin(); i != targets.end(); ++i)
{
Unit* unit = botAI->GetUnit(*i);
if (!unit) { continue; }
switch (unit->GetEntry())
{
case NPC_KRIKTHIR:
boss = unit;
continue;
case NPC_WATCHER_SILTHIK:
case NPC_WATCHER_GASHRA:
case NPC_WATCHER_NARJIL:
case NPC_WATCHER_SKIRMISHER:
case NPC_WATCHER_SHADOWCASTER:
case NPC_WATCHER_WARRIOR:
watcher = unit;
continue;
}
}
if (boss && watcher)
{
// Do not target swap
// TODO: Need to suppress AoE actions but unsure how to identify them
// TODO: TEST AOE Avoid
if (dynamic_cast<DpsAssistAction*>(action)
|| dynamic_cast<DpsAoeAction*>(action))
{
return 0.0f;
}
// Doesn't seem to work
// if (action->getThreatType() == Action::ActionThreatType::Aoe)
// {
// bot->Yell("Suppressed AoE", LANG_UNIVERSAL);
// return 0.0f;
// }
}
return 1.0f;
}

View File

@@ -0,0 +1,15 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONANMULTIPLIERS_H
#define _PLAYERBOT_WOTLKDUNGEONANMULTIPLIERS_H
#include "Multiplier.h"
class KrikthirMultiplier : public Multiplier
{
public:
KrikthirMultiplier(PlayerbotAI* ai) : Multiplier(ai, "krik'thir the gatewatcher") {}
public:
virtual float GetValue(Action* action);
};
#endif

View File

@@ -0,0 +1,31 @@
#include "AzjolNerubStrategy.h"
#include "AzjolNerubMultipliers.h"
void WotlkDungeonANStrategy::InitTriggers(std::vector<TriggerNode*> &triggers)
{
// Krik'thir the Gatewatcher
// 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)));
triggers.push_back(new TriggerNode("krik'thir watchers",
NextAction::array(0, new NextAction("krik'thir priority", ACTION_RAID + 4), nullptr)));
// Hadronox
// The core AC triggers are very buggy with this boss, but default strat seems to play correctly
//Anub'arak
// TODO: No clear way to track these spikes. They don't seem to appear as gameobjects or 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)));
triggers.push_back(new TriggerNode("anub'arak pound",
NextAction::array(0, new NextAction("dodge pound", ACTION_MOVE + 5), nullptr)));
}
void WotlkDungeonANStrategy::InitMultipliers(std::vector<Multiplier*> &multipliers)
{
multipliers.push_back(new KrikthirMultiplier(botAI));
}

View File

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

View File

@@ -0,0 +1,25 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONANTRIGGERCONTEXT_H
#define _PLAYERBOT_WOTLKDUNGEONANTRIGGERCONTEXT_H
#include "NamedObjectContext.h"
#include "AiObjectContext.h"
#include "AzjolNerubTriggers.h"
class WotlkDungeonANTriggerContext : public NamedObjectContext<Trigger>
{
public:
WotlkDungeonANTriggerContext()
{
creators["krik'thir web wrap"] = &WotlkDungeonANTriggerContext::krikthir_web_wrap;
creators["krik'thir watchers"] = &WotlkDungeonANTriggerContext::krikthir_watchers;
// creators["anub'arak impale"] = &WotlkDungeonANTriggerContext::anubarak_impale;
creators["anub'arak pound"] = &WotlkDungeonANTriggerContext::anubarak_pound;
}
private:
static Trigger* krikthir_web_wrap(PlayerbotAI* ai) { return new KrikthirWebWrapTrigger(ai); }
static Trigger* krikthir_watchers(PlayerbotAI* ai) { return new KrikthirWatchersTrigger(ai); }
// static Trigger* anubarak_impale(PlayerbotAI* ai) { return new AnubarakImpaleTrigger(ai); }
static Trigger* anubarak_pound(PlayerbotAI* ai) { return new AnubarakPoundTrigger(ai); }
};
#endif

View File

@@ -0,0 +1,69 @@
#include "Playerbots.h"
#include "AzjolNerubTriggers.h"
#include "AiObject.h"
#include "AiObjectContext.h"
bool KrikthirWebWrapTrigger::IsActive()
{
if (!botAI->IsDps(bot)) { return false; }
// Target is not findable from threat table using AI_VALUE2(),
// therefore need to search manually for the unit name
GuidVector targets = AI_VALUE(GuidVector, "possible targets no los");
for (auto i = targets.begin(); i != targets.end(); ++i)
{
Unit* unit = botAI->GetUnit(*i);
if (unit && unit->GetEntry() == NPC_WEB_WRAP)
{
return true;
}
}
return false;
}
bool KrikthirWatchersTrigger::IsActive()
{
if (!botAI->IsDps(bot)) { return false; }
// Target is not findable from threat table using AI_VALUE2(),
// therefore need to search manually for the unit name
GuidVector targets = AI_VALUE(GuidVector, "possible targets no los");
for (auto i = targets.begin(); i != targets.end(); ++i)
{
Unit* unit = botAI->GetUnit(*i);
if (unit && unit->GetEntry() == NPC_KRIKTHIR)
{
return true;
}
}
return false;
}
// bool AnubarakImpaleTrigger::IsActive()
// {
// Unit* boss = AI_VALUE2(Unit*, "find target", "anub'arak");
// if (!boss) { return false; }
// GuidVector triggers = AI_VALUE(GuidVector, "possible triggers");
// for (auto i = triggers.begin(); i != triggers.end(); i++)
// {
// Unit* unit = botAI->GetUnit(*i);
// if (unit)
// {
// bot->Yell("TRIGGER="+unit->GetName(), LANG_UNIVERSAL);
// }
// }
// return false;
// }
bool AnubarakPoundTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "anub'arak");
if (!boss) { return false; }
return boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(SPELL_POUND);
}

View File

@@ -0,0 +1,61 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONANTRIGGERS_H
#define _PLAYERBOT_WOTLKDUNGEONANTRIGGERS_H
#include "Trigger.h"
#include "PlayerbotAIConfig.h"
#include "GenericTriggers.h"
#include "DungeonStrategyUtils.h"
enum AzjolNerubIDs
{
// Krik'thir the Gatewatcher
NPC_KRIKTHIR = 28684,
NPC_WATCHER_SILTHIK = 28731,
NPC_WATCHER_GASHRA = 28730,
NPC_WATCHER_NARJIL = 28729,
NPC_WATCHER_SKIRMISHER = 28734,
NPC_WATCHER_SHADOWCASTER = 28733,
NPC_WATCHER_WARRIOR = 28732,
DEBUFF_WEB_WRAP = 52086,
NPC_WEB_WRAP = 28619,
// Anub'arak
// Not sure how to track this - first one is cast as a buff on himself,
// which triggers periodic casts of the spikes spell.
SPELL_IMPALE_PERIODIC = 53456,
SPELL_IMPALE_SPIKES = 53457,
SPELL_POUND_N = 53472,
SPELL_POUND_H = 59433,
};
#define SPELL_POUND DUNGEON_MODE(bot, SPELL_POUND_N, SPELL_POUND_H)
class KrikthirWebWrapTrigger : public Trigger
{
public:
KrikthirWebWrapTrigger(PlayerbotAI* ai) : Trigger(ai, "krik'thir web wrap") {}
bool IsActive() override;
};
class KrikthirWatchersTrigger : public Trigger
{
public:
KrikthirWatchersTrigger(PlayerbotAI* ai) : Trigger(ai, "krik'thir watchers") {}
bool IsActive() override;
};
// class AnubarakImpaleTrigger : public Trigger
// {
// public:
// AnubarakImpaleTrigger(PlayerbotAI* ai) : Trigger(ai, "anub'arak impale") {}
// bool IsActive() override;
// };
class AnubarakPoundTrigger : public Trigger
{
public:
AnubarakPoundTrigger(PlayerbotAI* ai) : Trigger(ai, "anub'arak pound") {}
bool IsActive() override;
};
#endif

View File

@@ -0,0 +1,32 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONDTKACTIONCONTEXT_H
#define _PLAYERBOT_WOTLKDUNGEONDTKACTIONCONTEXT_H
#include "Action.h"
#include "NamedObjectContext.h"
#include "DrakTharonKeepActions.h"
class WotlkDungeonDTKActionContext : public NamedObjectContext<Action>
{
public:
WotlkDungeonDTKActionContext() {
creators["corpse explode spread"] = &WotlkDungeonDTKActionContext::corpse_explode_spread;
creators["avoid arcane field"] = &WotlkDungeonDTKActionContext::avoid_arcane_field;
creators["novos positioning"] = &WotlkDungeonDTKActionContext::novos_positioning;
creators["novos target priority"] = &WotlkDungeonDTKActionContext::novos_target_priority;
creators["slaying strike"] = &WotlkDungeonDTKActionContext::slaying_strike;
creators["tharonja taunt"] = &WotlkDungeonDTKActionContext::taunt;
creators["bone armor"] = &WotlkDungeonDTKActionContext::bone_armor;
creators["touch of life"] = &WotlkDungeonDTKActionContext::touch_of_life;
}
private:
static Action* corpse_explode_spread(PlayerbotAI* ai) { return new CorpseExplodeSpreadAction(ai); }
static Action* avoid_arcane_field(PlayerbotAI* ai) { return new AvoidArcaneFieldAction(ai); }
static Action* novos_positioning(PlayerbotAI* ai) { return new NovosDefaultPositionAction(ai); }
static Action* novos_target_priority(PlayerbotAI* ai) { return new NovosTargetPriorityAction(ai); }
static Action* slaying_strike(PlayerbotAI* ai) { return new CastSlayingStrikeAction(ai); }
static Action* taunt(PlayerbotAI* ai) { return new CastTauntAction(ai); }
static Action* bone_armor(PlayerbotAI* ai) { return new CastBoneArmorAction(ai); }
static Action* touch_of_life(PlayerbotAI* ai) { return new CastTouchOfLifeAction(ai); }
};
#endif

View File

@@ -0,0 +1,174 @@
#include "Playerbots.h"
#include "DrakTharonKeepActions.h"
#include "DrakTharonKeepStrategy.h"
bool CorpseExplodeSpreadAction::Execute(Event event)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "trollgore");
if (!boss) { return false; }
float distance = 6.0f; // 5 unit radius, 1 unit added as buffer
GuidVector corpses = AI_VALUE(GuidVector, "nearest corpses");
for (auto i = corpses.begin(); i != corpses.end(); ++i)
{
Unit* unit = botAI->GetUnit(*i);
if (unit && unit->GetEntry() == NPC_DRAKKARI_INVADER)
{
if (bot->GetExactDist2d(unit) < distance)
{
return MoveAway(unit, distance - bot->GetExactDist2d(unit));
}
}
}
return false;
}
bool AvoidArcaneFieldAction::Execute(Event event)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "novos the summoner");
if (!boss) { return false; }
float distance = 12.0f; // 11 unit radius, 1 unit added as buffer
if (bot->GetExactDist2d(boss) < distance)
{
return MoveAway(boss, distance - bot->GetExactDist2d(boss));
}
return false;
}
bool NovosDefaultPositionAction::isUseful()
{
// Distance to tether to centre of room
float threshold = 15.0f;
return bot->GetDistance(NOVOS_PARTY_POSITION) > threshold;
}
bool NovosDefaultPositionAction::Execute(Event event)
{
float clusterDistance = 4.0f;
// Only reposition if we're not killing anything
if (!bot->GetTarget())
{
return MoveNear(bot->GetMap()->GetId(),
NOVOS_PARTY_POSITION.GetPositionX(),
NOVOS_PARTY_POSITION.GetPositionY(),
NOVOS_PARTY_POSITION.GetPositionZ(),
clusterDistance, MovementPriority::MOVEMENT_NORMAL);
}
return false;
}
bool NovosTargetPriorityAction::Execute(Event event)
{
// TODO: This can be improved, some parts are still buggy.
// But it works for now and this fight is very easy
// Designate a dps char to handle the stairs adds.
// This is probably better as a melee, so just pick the first
// melee dps in the party. If none exist, pick the first ranged.
// TODO: Switch to botAI->Index instead, cleaner
Player* stairsDps = nullptr;
GuidVector members = AI_VALUE(GuidVector, "group members");
for (auto& member : members)
{
Player* groupMember = botAI->GetPlayer(member);
if (!groupMember) { continue; }
if (botAI->IsDps(groupMember))
{
if (botAI->IsMelee(groupMember))
{
// Found our first melee dps, grab handle and break
stairsDps = groupMember;
break;
}
else
{
// Ranged dps, only set if none already assigned.
// Don't break, we want to keep searching for a melee instead.
if (!stairsDps)
{
stairsDps = groupMember;
}
}
}
}
Unit* selectedTargets[2] = {nullptr, nullptr};
// Target is not findable from threat table using AI_VALUE2(),
// therefore need to search manually for the unit name
GuidVector targets = AI_VALUE(GuidVector, "possible targets no los");
for (auto i = targets.begin(); i != targets.end(); ++i)
{
Unit* unit = botAI->GetUnit(*i);
if (!unit) { continue; }
uint32 creatureId = unit->GetEntry();
// Tank priority:
// Hulking Corpse -> Crystal Handler
if (botAI->IsTank(bot))
{
if (creatureId == NPC_HULKING_CORPSE)
{
selectedTargets[0] = unit;
}
else if (creatureId == NPC_CRYSTAL_HANDLER)
{
selectedTargets[1] = unit;
}
}
// Dedicated stairs dps is assigned.
// Priority: Risen Shadowcaster -> Fetid Troll Corpse
else if (stairsDps && bot == stairsDps)
{
if (creatureId == NPC_RISEN_SHADOWCASTER)
{
if (!selectedTargets[0] || bot->GetDistance(unit) < bot->GetDistance(selectedTargets[0]) - 5.0f)
{
selectedTargets[0] = unit;
}
}
else if (creatureId == NPC_FETID_TROLL_CORPSE)
{
if (!selectedTargets[1] || bot->GetDistance(unit) < bot->GetDistance(selectedTargets[1]) - 5.0f)
{
selectedTargets[1] = unit;
}
}
}
// All other dps priority:
// Crystal Handler -> Hulking Corpse
else if (botAI->IsDps(bot))
{
if (creatureId == NPC_CRYSTAL_HANDLER)
{
selectedTargets[0] = unit;
}
else if (creatureId == NPC_HULKING_CORPSE)
{
selectedTargets[1] = unit;
}
}
}
for (Unit* primaryTarget : selectedTargets)
{
// Attack the first valid target in the priority list
if (primaryTarget)
{
if (AI_VALUE(Unit*, "current target") != primaryTarget)
{
// bot->Yell(primaryTarget->GetName(), LANG_UNIVERSAL);
return Attack(primaryTarget);
}
// Don't continue loop here, the target exists so we don't
// want to move down the prio list. We just don't need to send attack
// command again, just return false and exit the loop that way
return false;
}
}
return false;
}

View File

@@ -0,0 +1,67 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONDTKACTIONS_H
#define _PLAYERBOT_WOTLKDUNGEONDTKACTIONS_H
#include "Action.h"
#include "AttackAction.h"
#include "GenericSpellActions.h"
#include "PlayerbotAI.h"
#include "Playerbots.h"
#include "DrakTharonKeepTriggers.h"
const Position NOVOS_PARTY_POSITION = Position(-378.852f, -760.349f, 28.587f);
class CorpseExplodeSpreadAction : public MovementAction
{
public:
CorpseExplodeSpreadAction(PlayerbotAI* ai) : MovementAction(ai, "corpse explode spread") {}
bool Execute(Event event) override;
};
class AvoidArcaneFieldAction : public MovementAction
{
public:
AvoidArcaneFieldAction(PlayerbotAI* ai) : MovementAction(ai, "avoid arcane field") {}
bool Execute(Event event) override;
};
class NovosDefaultPositionAction : public MovementAction
{
public:
NovosDefaultPositionAction(PlayerbotAI* ai) : MovementAction(ai, "novos default position") {}
bool Execute(Event event) override;
bool isUseful() override;
};
class NovosTargetPriorityAction : public AttackAction
{
public:
NovosTargetPriorityAction(PlayerbotAI* ai) : AttackAction(ai, "novos target priority") {}
bool Execute(Event event) override;
// bool isUseful() override;
};
class CastSlayingStrikeAction : public CastMeleeSpellAction
{
public:
CastSlayingStrikeAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "slaying strike") {}
};
class CastTauntAction : public CastSpellAction
{
public:
CastTauntAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "taunt") {}
};
class CastBoneArmorAction : public CastSpellAction
{
public:
CastBoneArmorAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "bone armor") {}
};
class CastTouchOfLifeAction : public CastSpellAction
{
public:
CastTouchOfLifeAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "touch of life") {}
};
#endif

View File

@@ -0,0 +1,62 @@
#include "DrakTharonKeepMultipliers.h"
#include "DrakTharonKeepActions.h"
#include "GenericSpellActions.h"
#include "ChooseTargetActions.h"
#include "MovementActions.h"
#include "DrakTharonKeepTriggers.h"
#include "Action.h"
float NovosMultiplier::GetValue(Action* action)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "novos the summoner");
if (!boss) { return 1.0f; }
if (boss->FindCurrentSpellBySpellId(SPELL_ARCANE_FIELD) && bot->GetTarget())
{
if (dynamic_cast<DpsAssistAction*>(action)
|| dynamic_cast<TankAssistAction*>(action))
{
return 0.0f;
}
}
return 1.0f;
}
float TharonjaMultiplier::GetValue(Action* action)
{
if (!bot->HasAura(SPELL_GIFT_OF_THARONJA)) { return 1.0f; }
// Suppress all skills that are not enabled in skeleton form.
// Still allow non-ability actions such as movement
if (dynamic_cast<CastSpellAction*>(action)
&& !dynamic_cast<CastSlayingStrikeAction*>(action)
&& !dynamic_cast<CastTauntAction*>(action)
&& !dynamic_cast<CastBoneArmorAction*>(action)
&& !dynamic_cast<CastTouchOfLifeAction*>(action))
{
return 0.0f;
}
// Also suppress FleeAction to prevent ranged characters from avoiding melee range
if (dynamic_cast<FleeAction*>(action))
{
return 0.0f;
}
// Tanks should only taunt, no slaying strike
if (botAI->IsTank(bot))
{
if (dynamic_cast<CastSlayingStrikeAction*>(action))
{
return 0.0f;
}
}
// Dps & healer should not taunt
else
{
if (dynamic_cast<CastTauntAction*>(action))
{
return 0.0f;
}
}
return 1.0f;
}

View File

@@ -0,0 +1,24 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONDTKMULTIPLIERS_H
#define _PLAYERBOT_WOTLKDUNGEONDTKMULTIPLIERS_H
#include "Multiplier.h"
class NovosMultiplier : public Multiplier
{
public:
NovosMultiplier(PlayerbotAI* ai) : Multiplier(ai, "novos the summoner") {}
public:
virtual float GetValue(Action* action);
};
class TharonjaMultiplier : public Multiplier
{
public:
TharonjaMultiplier(PlayerbotAI* ai) : Multiplier(ai, "the prophet tharon'ja") {}
public:
virtual float GetValue(Action* action);
};
#endif

View File

@@ -0,0 +1,41 @@
#include "DrakTharonKeepStrategy.h"
#include "DrakTharonKeepMultipliers.h"
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)));
// 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)));
triggers.push_back(new TriggerNode("arcane field",
NextAction::array(0, new NextAction("novos positioning", ACTION_MOVE + 4), nullptr)));
triggers.push_back(new TriggerNode("arcane field",
NextAction::array(0, new NextAction("novos target priority", ACTION_NORMAL + 1), nullptr)));
// 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)));
triggers.push_back(new TriggerNode("gift of tharon'ja",
NextAction::array(0, new NextAction("bone armor", ACTION_NORMAL + 4), nullptr)));
// 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)));
triggers.push_back(new TriggerNode("gift of tharon'ja",
NextAction::array(0, new NextAction("taunt", ACTION_NORMAL + 2), nullptr)));
triggers.push_back(new TriggerNode("gift of tharon'ja",
NextAction::array(0, new NextAction("slaying strike", ACTION_NORMAL + 2), nullptr)));
}
void WotlkDungeonDTKStrategy::InitMultipliers(std::vector<Multiplier*> &multipliers)
{
multipliers.push_back(new NovosMultiplier(botAI));
multipliers.push_back(new TharonjaMultiplier(botAI));
}

View File

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

View File

@@ -0,0 +1,28 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONDTKTRIGGERCONTEXT_H
#define _PLAYERBOT_WOTLKDUNGEONDTKTRIGGERCONTEXT_H
#include "NamedObjectContext.h"
#include "AiObjectContext.h"
#include "DrakTharonKeepTriggers.h"
class WotlkDungeonDTKTriggerContext : public NamedObjectContext<Trigger>
{
public:
WotlkDungeonDTKTriggerContext()
{
creators["corpse explode"] = &WotlkDungeonDTKTriggerContext::corpse_explode;
creators["arcane field"] = &WotlkDungeonDTKTriggerContext::arcane_field;
// creators["crystal handler"] = &WotlkDungeonDTKTriggerContext::crystal_handler;
creators["gift of tharon'ja"] = &WotlkDungeonDTKTriggerContext::gift_of_tharonja;
creators["tharon'ja out of melee"] = &WotlkDungeonDTKTriggerContext::tharonja_out_of_melee;
}
private:
static Trigger* corpse_explode(PlayerbotAI* ai) { return new CorpseExplodeTrigger(ai); }
static Trigger* arcane_field(PlayerbotAI* ai) { return new ArcaneFieldTrigger(ai); }
// static Trigger* crystal_handler(PlayerbotAI* ai) { return new CrystalHandlerTrigger(ai); }
static Trigger* gift_of_tharonja(PlayerbotAI* ai) { return new GiftOfTharonjaTrigger(ai); }
static Trigger* tharonja_out_of_melee(PlayerbotAI* ai) { return new TwoTriggers(ai, "gift of tharon'ja", "enemy out of melee"); }
};
#endif

View File

@@ -0,0 +1,61 @@
#include "Playerbots.h"
#include "DrakTharonKeepTriggers.h"
#include "AiObject.h"
#include "AiObjectContext.h"
bool CorpseExplodeTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "trollgore");
if (!boss) { return false; }
float distance = 6.0f; // 5 unit radius, 1 unit added as buffer
GuidVector corpses = AI_VALUE(GuidVector, "nearest corpses");
for (auto i = corpses.begin(); i != corpses.end(); ++i)
{
Unit* unit = botAI->GetUnit(*i);
if (unit && unit->GetEntry() == NPC_DRAKKARI_INVADER)
{
if (bot->GetExactDist2d(unit) < distance)
{
return true;
}
}
}
return false;
}
bool ArcaneFieldTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "novos the summoner");
if (boss)
{
return boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(SPELL_ARCANE_FIELD);
}
return false;
}
// bool CrystalHandlerTrigger::IsActive()
// {
// Unit* boss = AI_VALUE2(Unit*, "find target", "novos the summoner");
// if (!boss) { return false; }
// // Target is not findable from threat table using AI_VALUE2(),
// // therefore need to search manually for the unit name
// GuidVector targets = AI_VALUE(GuidVector, "possible targets no los");
// for (auto i = targets.begin(); i != targets.end(); ++i)
// {
// Unit* unit = botAI->GetUnit(*i);
// if (unit && unit->GetEntry() == NPC_CRYSTAL_HANDLER)
// {
// return true;
// }
// }
// return false;
// }
bool GiftOfTharonjaTrigger::IsActive()
{
return bot->HasAura(SPELL_GIFT_OF_THARONJA);
}

View File

@@ -0,0 +1,54 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONDTKTRIGGERS_H
#define _PLAYERBOT_WOTLKDUNGEONDTKTRIGGERS_H
#include "Trigger.h"
#include "PlayerbotAIConfig.h"
#include "GenericTriggers.h"
#include "DungeonStrategyUtils.h"
enum DrakTharonIDs
{
// Trollgore
NPC_DRAKKARI_INVADER = 27709,
// Novos the Summoner
NPC_NOVOS = 26631,
SPELL_ARCANE_FIELD = 47346,
NPC_CRYSTAL_HANDLER = 26627,
NPC_HULKING_CORPSE = 27597,
NPC_RISEN_SHADOWCASTER = 27600,
NPC_FETID_TROLL_CORPSE = 27598,
// The Prophet Tharon'ja
SPELL_GIFT_OF_THARONJA = 52509,
};
class CorpseExplodeTrigger : public Trigger
{
public:
CorpseExplodeTrigger(PlayerbotAI* ai) : Trigger(ai, "corpse explode") {}
bool IsActive() override;
};
class ArcaneFieldTrigger : public Trigger
{
public:
ArcaneFieldTrigger(PlayerbotAI* ai) : Trigger(ai, "arcane field") {}
bool IsActive() override;
};
// class CrystalHandlerTrigger : public Trigger
// {
// public:
// CrystalHandlerTrigger(PlayerbotAI* ai) : Trigger(ai, "crystal handler") {}
// bool IsActive() override;
// };
class GiftOfTharonjaTrigger : public Trigger
{
public:
GiftOfTharonjaTrigger(PlayerbotAI* ai) : Trigger(ai, "gift of tharon'ja") {}
bool IsActive() override;
};
#endif

View File

@@ -0,0 +1,28 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONNEXACTIONCONTEXT_H
#define _PLAYERBOT_WOTLKDUNGEONNEXACTIONCONTEXT_H
#include "Action.h"
#include "NamedObjectContext.h"
#include "NexusActions.h"
class WotlkDungeonNexActionContext : public NamedObjectContext<Action>
{
public:
WotlkDungeonNexActionContext() {
creators["move from whirlwind"] = &WotlkDungeonNexActionContext::move_from_whirlwind;
creators["firebomb spread"] = &WotlkDungeonNexActionContext::firebomb_spread;
creators["telestra split target"] = &WotlkDungeonNexActionContext::telestra_split_target;
creators["chaotic rift target"] = &WotlkDungeonNexActionContext::chaotic_rift_target;
creators["dodge spikes"] = &WotlkDungeonNexActionContext::dodge_spikes;
creators["intense cold jump"] = &WotlkDungeonNexActionContext::intense_cold_jump;
}
private:
static Action* move_from_whirlwind(PlayerbotAI* ai) { return new MoveFromWhirlwindAction(ai); }
static Action* firebomb_spread(PlayerbotAI* ai) { return new FirebombSpreadAction(ai); }
static Action* telestra_split_target(PlayerbotAI* ai) { return new TelestraSplitTargetAction(ai); }
static Action* chaotic_rift_target(PlayerbotAI* ai) { return new ChaoticRiftTargetAction(ai); }
static Action* dodge_spikes(PlayerbotAI* ai) { return new DodgeSpikesAction(ai); }
static Action* intense_cold_jump(PlayerbotAI* ai) { return new IntenseColdJumpAction(ai); }
};
#endif

View File

@@ -0,0 +1,159 @@
#include "Playerbots.h"
#include "NexusActions.h"
#include "NexusStrategy.h"
bool MoveFromWhirlwindAction::Execute(Event event)
{
Unit* boss = nullptr;
uint8 faction = bot->GetTeamId();
float targetDist = 10.0f; // Whirlwind has range of 8, add a couple for safety buffer
switch (bot->GetMap()->GetDifficulty())
{
case DUNGEON_DIFFICULTY_NORMAL:
if (faction == TEAM_ALLIANCE)
{
boss = AI_VALUE2(Unit*, "find target", "horde commander");
}
else //if (faction == TEAM_HORDE)
{
boss = AI_VALUE2(Unit*, "find target", "alliance commander");
}
break;
case DUNGEON_DIFFICULTY_HEROIC:
if (faction == TEAM_ALLIANCE)
{
boss = AI_VALUE2(Unit*, "find target", "commander kolurg");
}
else //if (faction == TEAM_HORDE)
{
boss = AI_VALUE2(Unit*, "find target", "commander stoutbeard");
}
break;
default:
break;
}
if (!boss || bot->GetExactDist2d(boss->GetPosition()) > targetDist)
{
return false;
}
return MoveAway(boss, targetDist - bot->GetExactDist2d(boss->GetPosition()));
}
bool FirebombSpreadAction::Execute(Event event)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "grand magus telestra");
float radius = 5.0f;
float targetDist = radius + 1.0f;
if (!boss) { return false; }
GuidVector members = AI_VALUE(GuidVector, "group members");
for (auto& member : members)
{
if (bot->GetGUID() == member)
{
continue;
}
if (bot->GetExactDist2d(botAI->GetUnit(member)) < targetDist)
{
return MoveAway(botAI->GetUnit(member), targetDist);
}
}
return false;
}
bool TelestraSplitTargetAction::isUseful() { return !botAI->IsHeal(bot); }
bool TelestraSplitTargetAction::Execute(Event event)
{
GuidVector attackers = AI_VALUE(GuidVector, "attackers");
Unit* splitTargets[3] = {nullptr, nullptr, nullptr};
for (auto& attacker : attackers)
{
Unit* npc = botAI->GetUnit(attacker);
if (!npc)
{
continue;
}
switch (npc->GetEntry())
{
// Focus arcane clone first
case NPC_ARCANE_MAGUS:
splitTargets[0] = npc;
break;
// Then the frost clone
case NPC_FROST_MAGUS:
splitTargets[1] = npc;
break;
// Fire clone last
case NPC_FIRE_MAGUS:
splitTargets[2] = npc;
break;
}
}
for (Unit* target : splitTargets)
{
// Attack the first valid split target in the priority list
if (target)
{
if (AI_VALUE(Unit*, "current target") != target)
{
return Attack(target);
}
// Don't continue loop here, the target exists so we don't
// want to move down the prio list. We just don't need to send attack
// command again, just return false and exit the loop that way
return false;
}
}
return false;
}
bool ChaoticRiftTargetAction::isUseful() { return !botAI->IsHeal(bot); }
bool ChaoticRiftTargetAction::Execute(Event event)
{
Unit* chaoticRift = nullptr;
// Target is not findable from threat table using AI_VALUE2(),
// therefore need to search manually for the unit name
GuidVector targets = AI_VALUE(GuidVector, "possible targets no los");
for (auto i = targets.begin(); i != targets.end(); ++i)
{
Unit* unit = botAI->GetUnit(*i);
if (unit && unit->GetName() == "Chaotic Rift")
{
chaoticRift = unit;
break;
}
}
if (!chaoticRift || AI_VALUE(Unit*, "current target") == chaoticRift)
{
return false;
}
return Attack(chaoticRift);
}
bool DodgeSpikesAction::isUseful()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "ormorok the tree-shaper");
return bot->GetExactDist2d(boss) > 0.5f;
}
bool DodgeSpikesAction::Execute(Event event)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "ormorok the tree-shaper");
return Move(bot->GetAngle(boss), bot->GetExactDist2d(boss) - 0.3f);
}
bool IntenseColdJumpAction::Execute(Event event)
{
// This needs improving but maybe it should be done in the playerbot core.
// Jump doesn't seem to support zero offset (eg. jump on the spot) so need to add a tiny delta.
// This does a tiny bunnyhop that takes a couple of ms, it doesn't do a natural jump.
// Adding extra Z offset causes floating, and appears to scale the jump speed based on Z difference.
// Probably best to revisit once bot movement is improved
return JumpTo(bot->GetMap()->GetId(), bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ() + 0.01f);
// bot->GetMotionMaster()->MoveFall();
}

View File

@@ -0,0 +1,71 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONNEXACTIONS_H
#define _PLAYERBOT_WOTLKDUNGEONNEXACTIONS_H
#include "Action.h"
#include "AttackAction.h"
#include "PlayerbotAI.h"
#include "Playerbots.h"
#include "NexusTriggers.h"
#define ANGLE_45_DEG (static_cast<float>(M_PI) / 4.f)
#define ANGLE_90_DEG M_PI_2
#define ANGLE_120_DEG (2.f * static_cast<float>(M_PI) / 3.f)
// Slice of the circle that we want melee dps to attack from.
// Measured from boss orientation, on one side.
// Even though the breath cone is not the full 180 degrees,
// avoid melee dps from the front due to parry potential.
// Bots should learn good dps etiquette :)
#define DRAGON_MELEE_MIN_ANGLE ANGLE_90_DEG
// This leaves a danger zone of 60 degrees at the tail end on both sides.
// This is a total of 120 degrees tail arc that bots will avoid -
// number just happens to be the same in this case, but this is always measured from the front.
#define DRAGON_MELEE_MAX_ANGLE ANGLE_120_DEG
class MoveFromWhirlwindAction : public MovementAction
{
public:
MoveFromWhirlwindAction(PlayerbotAI* ai) : MovementAction(ai, "move from whirlwind") {}
bool Execute(Event event) override;
};
class FirebombSpreadAction : public MovementAction
{
public:
FirebombSpreadAction(PlayerbotAI* ai) : MovementAction(ai, "firebomb spread") {}
bool Execute(Event event) override;
};
class TelestraSplitTargetAction : public AttackAction
{
public:
TelestraSplitTargetAction(PlayerbotAI* ai) : AttackAction(ai, "telestra split target") {}
bool Execute(Event event) override;
bool isUseful() override;
};
class ChaoticRiftTargetAction : public AttackAction
{
public:
ChaoticRiftTargetAction(PlayerbotAI* ai) : AttackAction(ai, "chaotic rift target") {}
bool Execute(Event event) override;
bool isUseful() override;
};
class DodgeSpikesAction : public MovementAction
{
public:
DodgeSpikesAction(PlayerbotAI* ai) : MovementAction(ai, "dodge spikes") {}
bool Execute(Event event) override;
bool isUseful() override;
};
class IntenseColdJumpAction : public MovementAction
{
public:
IntenseColdJumpAction(PlayerbotAI* ai) : MovementAction(ai, "intense cold jump") {}
bool Execute(Event event) override;
};
#endif

View File

@@ -0,0 +1,99 @@
#include "NexusMultipliers.h"
#include "NexusActions.h"
#include "GenericSpellActions.h"
#include "ChooseTargetActions.h"
#include "MovementActions.h"
#include "NexusTriggers.h"
float FactionCommanderMultiplier::GetValue(Action* action)
{
Unit* boss = nullptr;
uint8 faction = bot->GetTeamId();
switch (bot->GetMap()->GetDifficulty())
{
case DUNGEON_DIFFICULTY_NORMAL:
if (faction == TEAM_ALLIANCE)
{
boss = AI_VALUE2(Unit*, "find target", "horde commander");
}
else //if (faction == TEAM_HORDE)
{
boss = AI_VALUE2(Unit*, "find target", "alliance commander");
}
break;
case DUNGEON_DIFFICULTY_HEROIC:
if (faction == TEAM_ALLIANCE)
{
boss = AI_VALUE2(Unit*, "find target", "commander kolurg");
}
else //if (faction == TEAM_HORDE)
{
boss = AI_VALUE2(Unit*, "find target", "commander stoutbeard");
}
break;
default:
break;
}
if (boss && boss->HasUnitState(UNIT_STATE_CASTING) &&
boss->FindCurrentSpellBySpellId(SPELL_WHIRLWIND))
{
// Prevent movement actions other than flee during a whirlwind, to prevent running back in early.
if (dynamic_cast<MovementAction*>(action) && !dynamic_cast<MoveFromWhirlwindAction*>(action))
{
return 0.0f;
}
}
return 1.0f;
}
float TelestraMultiplier::GetValue(Action* action)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "grand magus telestra");
if (boss && boss->GetEntry() != NPC_TELESTRA)
{
// boss is split into clones, do not auto acquire target
if (dynamic_cast<DpsAssistAction*>(action))
{
return 0.0f;
}
}
return 1.0f;
}
float AnomalusMultiplier::GetValue(Action* action)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "anomalus");
if (boss && boss->HasAura(BUFF_RIFT_SHIELD))
{
if (dynamic_cast<DpsAssistAction*>(action))
{
return 0.0f;
}
}
return 1.0f;
}
float OrmorokMultiplier::GetValue(Action* action)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "ormorok the tree-shaper");
if (!boss)
{
return 1.0f;
}
// These are used for auto ranged repositioning, need to suppress so ranged dps don't ping-pong
if (dynamic_cast<FleeAction*>(action))
{
return 0.0f;
}
// This boss is annoying and shuffles around a lot. Don't let tank move once fight has started.
// Extra checks are to allow the tank to close distance and engage the boss initially
if (dynamic_cast<MovementAction*>(action) && !dynamic_cast<DodgeSpikesAction*>(action)
&& botAI->IsTank(bot) && bot->IsWithinMeleeRange(boss)
&& AI_VALUE2(bool, "facing", "current target"))
{
return 0.0f;
}
return 1.0f;
}

View File

@@ -0,0 +1,51 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONNEXMULTIPLIERS_H
#define _PLAYERBOT_WOTLKDUNGEONNEXMULTIPLIERS_H
#include "Multiplier.h"
class FactionCommanderMultiplier : public Multiplier
{
public:
FactionCommanderMultiplier(PlayerbotAI* ai) : Multiplier(ai, "faction commander") {}
public:
virtual float GetValue(Action* action);
};
class TelestraMultiplier : public Multiplier
{
public:
TelestraMultiplier(PlayerbotAI* ai) : Multiplier(ai, "grand magus telestra") {}
public:
virtual float GetValue(Action* action);
};
class AnomalusMultiplier : public Multiplier
{
public:
AnomalusMultiplier(PlayerbotAI* ai) : Multiplier(ai, "anomalus") {}
public:
virtual float GetValue(Action* action);
};
class OrmorokMultiplier : public Multiplier
{
public:
OrmorokMultiplier(PlayerbotAI* ai) : Multiplier(ai, "ormorok the tree-shaper") {}
public:
virtual float GetValue(Action* action);
};
class KeristraszaMultiplier : public Multiplier
{
public:
KeristraszaMultiplier(PlayerbotAI* ai) : Multiplier(ai, "keristrasza") {}
public:
virtual float GetValue(Action* action);
};
#endif

View File

@@ -0,0 +1,54 @@
#include "NexusStrategy.h"
#include "NexusMultipliers.h"
void WotlkDungeonNexStrategy::InitTriggers(std::vector<TriggerNode*> &triggers)
{
// Horde Commander (Alliance N)/Commander Kolurg (Alliance H)
// 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)));
// 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)));
triggers.push_back(new TriggerNode("telestra split phase",
NextAction::array(0, new NextAction("telestra split target", ACTION_RAID + 1), nullptr)));
// 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)));
// 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)));
// 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)));
// 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)));
// Flank dragon positioning
triggers.push_back(new TriggerNode("keristrasza positioning",
NextAction::array(0, new NextAction("rear flank", ACTION_MOVE + 4), nullptr)));
// TODO: Add frost resist aura for paladins?
}
void WotlkDungeonNexStrategy::InitMultipliers(std::vector<Multiplier*> &multipliers)
{
multipliers.push_back(new FactionCommanderMultiplier(botAI));
multipliers.push_back(new TelestraMultiplier(botAI));
multipliers.push_back(new AnomalusMultiplier(botAI));
multipliers.push_back(new OrmorokMultiplier(botAI));
// multipliers.push_back(new KeristraszaMultiplier(botAI));
}

View File

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

View File

@@ -0,0 +1,33 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONNEXTRIGGERCONTEXT_H
#define _PLAYERBOT_WOTLKDUNGEONNEXTRIGGERCONTEXT_H
#include "NamedObjectContext.h"
#include "AiObjectContext.h"
#include "NexusTriggers.h"
class WotlkDungeonNexTriggerContext : public NamedObjectContext<Trigger>
{
public:
WotlkDungeonNexTriggerContext()
{
creators["faction commander whirlwind"] = &WotlkDungeonNexTriggerContext::faction_commander_whirlwind;
creators["telestra firebomb"] = &WotlkDungeonNexTriggerContext::telestra_firebomb;
creators["telestra split phase"] = &WotlkDungeonNexTriggerContext::telestra_split_phase;
creators["chaotic rift"] = &WotlkDungeonNexTriggerContext::chaotic_rift;
creators["ormorok spikes"] = &WotlkDungeonNexTriggerContext::ormorok_spikes;
creators["ormorok stack"] = &WotlkDungeonNexTriggerContext::ormorok_stack;
creators["intense cold"] = &WotlkDungeonNexTriggerContext::intense_cold;
creators["keristrasza positioning"] = &WotlkDungeonNexTriggerContext::keristrasza_positioning;
}
private:
static Trigger* faction_commander_whirlwind(PlayerbotAI* ai) { return new FactionCommanderWhirlwindTrigger(ai); }
static Trigger* telestra_firebomb(PlayerbotAI* ai) { return new TelestraFirebombTrigger(ai); }
static Trigger* telestra_split_phase(PlayerbotAI* ai) { return new TelestraSplitPhaseTrigger(ai); }
static Trigger* chaotic_rift(PlayerbotAI* ai) { return new ChaoticRiftTrigger(ai); }
static Trigger* ormorok_spikes(PlayerbotAI* ai) { return new OrmorokSpikesTrigger(ai); }
static Trigger* ormorok_stack(PlayerbotAI* ai) { return new OrmorokStackTrigger(ai); }
static Trigger* intense_cold(PlayerbotAI* ai) { return new IntenseColdTrigger(ai); }
static Trigger* keristrasza_positioning(PlayerbotAI* ai) { return new KeristraszaPositioningTrigger(ai); }
};
#endif

View File

@@ -0,0 +1,107 @@
#include "Playerbots.h"
#include "NexusTriggers.h"
#include "AiObject.h"
#include "AiObjectContext.h"
bool FactionCommanderWhirlwindTrigger::IsActive()
{
Unit* boss = nullptr;
uint8 faction = bot->GetTeamId();
switch (bot->GetMap()->GetDifficulty())
{
case DUNGEON_DIFFICULTY_NORMAL:
if (faction == TEAM_ALLIANCE)
{
boss = AI_VALUE2(Unit*, "find target", "horde commander");
}
else //if (faction == TEAM_HORDE)
{
boss = AI_VALUE2(Unit*, "find target", "alliance commander");
}
break;
case DUNGEON_DIFFICULTY_HEROIC:
if (faction == TEAM_ALLIANCE)
{
boss = AI_VALUE2(Unit*, "find target", "commander kolurg");
}
else //if (faction == TEAM_HORDE)
{
boss = AI_VALUE2(Unit*, "find target", "commander stoutbeard");
}
break;
default:
break;
}
if (boss && boss->HasUnitState(UNIT_STATE_CASTING))
{
if (boss->FindCurrentSpellBySpellId(SPELL_WHIRLWIND))
{
return true;
}
}
return false;
}
bool TelestraFirebombTrigger::IsActive()
{
if (botAI->IsMelee(bot)) { return false; }
Unit* boss = AI_VALUE2(Unit*, "find target", "grand magus telestra");
// Avoid split phase with the fake Telestra units, only match the true boss id
return boss && boss->GetEntry() == NPC_TELESTRA;
}
bool TelestraSplitPhaseTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "grand magus telestra");
// Only match split phase with the fake Telestra units
return boss && boss->GetEntry() != NPC_TELESTRA;
}
bool ChaoticRiftTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "anomalus");
return boss && boss->HasAura(BUFF_RIFT_SHIELD);
}
bool OrmorokSpikesTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "ormorok the tree-shaper");
if (!boss || !botAI->IsTank(bot)) { return false; }
GuidVector objects = AI_VALUE(GuidVector, "closest game objects");
for (auto i = objects.begin(); i != objects.end(); ++i)
{
GameObject* go = botAI->GetGameObject(*i);
if (go && go->GetEntry() == GO_CRYSTAL_SPIKE)
{
return true;
}
}
return false;
}
bool OrmorokStackTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "ormorok the tree-shaper");
return (boss && !botAI->IsTank(bot));
}
bool IntenseColdTrigger::IsActive()
{
// Adjust as needed - too much interrupting loses dps time,
// but too many stacks is deadly. Assuming 3-5 is a good number to clear
int stackThreshold = 5;
Unit* boss = AI_VALUE2(Unit*, "find target", "keristrasza");
return boss && botAI->GetAura("intense cold", bot, false, false, stackThreshold);
}
bool KeristraszaPositioningTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "keristrasza");
// Include healers here for now, otherwise they stand in things
return boss && !botAI->IsTank(bot) && !botAI->IsRangedDps(bot);
// return boss && botAI->IsMelee(bot) && !botAI->IsTank(bot);
}

View File

@@ -0,0 +1,89 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONNEXTRIGGERS_H
#define _PLAYERBOT_WOTLKDUNGEONNEXTRIGGERS_H
#include "Trigger.h"
#include "PlayerbotAIConfig.h"
#include "GenericTriggers.h"
#include "DungeonStrategyUtils.h"
enum NexusIDs
{
// Faction Commander
NPC_ALLIANCE_COMMANDER = 27949,
NPC_HORDE_COMMANDER = 27947,
NPC_COMMANDER_STOUTBEARD = 26796,
NPC_COMMANDER_KOLURG = 26798,
// SPELL_FRIGHTENING_SHOUT = 19134,
SPELL_WHIRLWIND = 38618,
// Grand Magus Telestra
NPC_TELESTRA = 26731,
NPC_FIRE_MAGUS = 26928,
NPC_FROST_MAGUS = 26930,
NPC_ARCANE_MAGUS = 26929,
// Anomalus
BUFF_RIFT_SHIELD = 47748,
// Ormorok the Tree Shaper
// NPC_CRYSTAL_SPIKE = 27099,
GO_CRYSTAL_SPIKE = 188537,
};
class FactionCommanderWhirlwindTrigger : public Trigger
{
public:
FactionCommanderWhirlwindTrigger(PlayerbotAI* ai) : Trigger(ai, "faction commander whirlwind") {}
bool IsActive() override;
};
class TelestraFirebombTrigger : public Trigger
{
public:
TelestraFirebombTrigger(PlayerbotAI* ai) : Trigger(ai, "telestra firebomb spread") {}
bool IsActive() override;
};
class TelestraSplitPhaseTrigger : public Trigger
{
public:
TelestraSplitPhaseTrigger(PlayerbotAI* ai) : Trigger(ai, "telestra split phase") {}
bool IsActive() override;
};
class ChaoticRiftTrigger : public Trigger
{
public:
ChaoticRiftTrigger(PlayerbotAI* ai) : Trigger(ai, "chaotic rift") {}
bool IsActive() override;
};
class OrmorokSpikesTrigger : public Trigger
{
public:
OrmorokSpikesTrigger(PlayerbotAI* ai) : Trigger(ai, "ormorok spikes") {}
bool IsActive() override;
};
class OrmorokStackTrigger : public Trigger
{
public:
OrmorokStackTrigger(PlayerbotAI* ai) : Trigger(ai, "ormorok stack") {}
bool IsActive() override;
};
class IntenseColdTrigger : public Trigger
{
public:
IntenseColdTrigger(PlayerbotAI* ai) : Trigger(ai, "intense cold") {}
bool IsActive() override;
};
class KeristraszaPositioningTrigger : public Trigger
{
public:
KeristraszaPositioningTrigger(PlayerbotAI* ai) : Trigger(ai, "keristrasza positioning") {}
bool IsActive() override;
};
#endif

View File

@@ -0,0 +1,22 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONOKACTIONCONTEXT_H
#define _PLAYERBOT_WOTLKDUNGEONOKACTIONCONTEXT_H
#include "Action.h"
#include "NamedObjectContext.h"
#include "OldKingdomActions.h"
class WotlkDungeonOKActionContext : public NamedObjectContext<Action>
{
public:
WotlkDungeonOKActionContext() {
creators["attack nadox guardian"] = &WotlkDungeonOKActionContext::attack_nadox_guardian;
creators["attack jedoga volunteer"] = &WotlkDungeonOKActionContext::attack_jedoga_volunteer;
creators["avoid shadow crash"] = &WotlkDungeonOKActionContext::avoid_shadow_crash;
}
private:
static Action* attack_nadox_guardian(PlayerbotAI* ai) { return new AttackNadoxGuardianAction(ai); }
static Action* attack_jedoga_volunteer(PlayerbotAI* ai) { return new AttackJedogaVolunteerAction(ai); }
static Action* avoid_shadow_crash(PlayerbotAI* ai) { return new AvoidShadowCrashAction(ai); }
};
#endif

View File

@@ -0,0 +1,81 @@
#include "Playerbots.h"
#include "OldKingdomActions.h"
#include "OldKingdomStrategy.h"
bool AttackNadoxGuardianAction::Execute(Event event)
{
Unit* target = AI_VALUE2(Unit*, "find target", "ahn'kahar guardian");
if (!target || AI_VALUE(Unit*, "current target") == target)
{
return false;
}
return Attack(target);
}
bool AttackJedogaVolunteerAction::Execute(Event event)
{
Unit* target = nullptr;
// Target is not findable from threat table using AI_VALUE2(),
// therefore need to search manually for the unit name
GuidVector targets = AI_VALUE(GuidVector, "possible targets no los");
for (auto i = targets.begin(); i != targets.end(); ++i)
{
Unit* unit = botAI->GetUnit(*i);
if (unit && unit->GetEntry() == NPC_TWILIGHT_VOLUNTEER)
{
target = unit;
break;
}
}
if (!target || AI_VALUE(Unit*, "current target") == target)
{
return false;
}
return Attack(target);
}
bool AvoidShadowCrashAction::Execute(Event event)
{
// Could check all enemy units in range as it's possible to pull multiple of these mobs.
// They should really be killed 1 by 1, multipulls are messy so we just handle singles for now
Unit* npc = AI_VALUE2(Unit*, "find target", "forgotten one");
Unit* victim = nullptr;
float radius = 10.0f;
float targetDist = radius + 2.0f;
if (!npc) { return false; }
// Actively move if targeted by a shadow crash.
// Spell check not needed, they don't have any other non-instant casts
if (npc->HasUnitState(UNIT_STATE_CASTING)) // && npc->FindCurrentSpellBySpellId(SPELL_SHADOW_CRASH))
{
// This doesn't seem to avoid casts very well, perhaps because this isn't checked while allies are casting.
// TODO: Revisit if this is an issue in heroics, otherwise ignore shadow crashes for the most part.
victim = botAI->GetUnit(npc->GetTarget());
if (victim && bot->GetExactDist2d(victim) < radius)
{
return MoveAway(victim, targetDist - bot->GetExactDist2d(victim));
}
}
// Otherwise ranged members passively spread, to avoid AoE overlap
if (botAI->IsMelee(bot)) { return false; }
GuidVector members = AI_VALUE(GuidVector, "group members");
for (auto& member : members)
{
if (bot->GetGUID() == member)
{
continue;
}
float currentDist = bot->GetExactDist2d(botAI->GetUnit(member));
if (currentDist < radius)
{
return MoveAway(botAI->GetUnit(member), targetDist - currentDist);
}
}
return false;
}

View File

@@ -0,0 +1,31 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONOKACTIONS_H
#define _PLAYERBOT_WOTLKDUNGEONOKACTIONS_H
#include "Action.h"
#include "AttackAction.h"
#include "PlayerbotAI.h"
#include "Playerbots.h"
#include "OldKingdomTriggers.h"
class AttackNadoxGuardianAction : public AttackAction
{
public:
AttackNadoxGuardianAction(PlayerbotAI* ai) : AttackAction(ai, "attack nadox guardian") {}
bool Execute(Event event) override;
};
class AttackJedogaVolunteerAction : public AttackAction
{
public:
AttackJedogaVolunteerAction(PlayerbotAI* ai) : AttackAction(ai, "attack jedoga volunteer") {}
bool Execute(Event event) override;
};
class AvoidShadowCrashAction : public MovementAction
{
public:
AvoidShadowCrashAction(PlayerbotAI* ai) : MovementAction(ai, "avoid shadow crash") {}
bool Execute(Event event) override;
};
#endif

View File

@@ -0,0 +1,66 @@
#include "OldKingdomMultipliers.h"
#include "OldKingdomActions.h"
#include "GenericSpellActions.h"
#include "ChooseTargetActions.h"
#include "MovementActions.h"
#include "OldKingdomTriggers.h"
#include "Action.h"
float ElderNadoxMultiplier::GetValue(Action* action)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "elder nadox");
Unit* guardian = AI_VALUE2(Unit*, "find target", "ahn'kahar guardian");
if (boss && guardian)
{
if (dynamic_cast<DpsAssistAction*>(action))
{
return 0.0f;
}
}
return 1.0f;
}
float JedogaShadowseekerMultiplier::GetValue(Action* action)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "jedoga shadowseeker");
// Unit* volunteer = AI_VALUE2(Unit*, "find target", "twilight volunteer");
Unit* volunteer = nullptr;
// Target is not findable from threat table using AI_VALUE2(),
// therefore need to search manually for the unit name
GuidVector targets = AI_VALUE(GuidVector, "possible targets no los");
for (auto i = targets.begin(); i != targets.end(); ++i)
{
Unit* unit = botAI->GetUnit(*i);
if (unit && unit->GetEntry() == NPC_TWILIGHT_VOLUNTEER)
{
volunteer = unit;
break;
}
}
if (boss && volunteer)
{
if (dynamic_cast<DpsAssistAction*>(action))
{
return 0.0f;
}
}
return 1.0f;
}
float ForgottenOneMultiplier::GetValue(Action* action)
{
Unit* npc = AI_VALUE2(Unit*, "find target", "forgotten one");
if (npc && bot->isMoving())
{
if (dynamic_cast<MovementAction*>(action))
{
return 0.0f;
}
}
return 1.0f;
}

View File

@@ -0,0 +1,33 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONOKMULTIPLIERS_H
#define _PLAYERBOT_WOTLKDUNGEONOKMULTIPLIERS_H
#include "Multiplier.h"
class ElderNadoxMultiplier : public Multiplier
{
public:
ElderNadoxMultiplier(PlayerbotAI* ai) : Multiplier(ai, "elder nadox") {}
public:
virtual float GetValue(Action* action);
};
class JedogaShadowseekerMultiplier : public Multiplier
{
public:
JedogaShadowseekerMultiplier(PlayerbotAI* ai) : Multiplier(ai, "jedoga shadowseeker") {}
public:
virtual float GetValue(Action* action);
};
class ForgottenOneMultiplier : public Multiplier
{
public:
ForgottenOneMultiplier(PlayerbotAI* ai) : Multiplier(ai, "forgotten one") {}
public:
virtual float GetValue(Action* action);
};
#endif

View File

@@ -0,0 +1,36 @@
#include "OldKingdomStrategy.h"
#include "OldKingdomMultipliers.h"
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)));
// Prince Taldaram
// Flame Orb spawns in melee, doesn't have a clear direction until it starts moving.
// Maybe not worth trying to avoid and just heal through. Only consideration is not to have ranged
// players anywhere near melee when it spawns
// Jedoga Shadowseeker
triggers.push_back(new TriggerNode("jedoga volunteer",
NextAction::array(0, new NextAction("attack jedoga volunteer", ACTION_RAID + 5), nullptr)));
// 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)));
// Volazj is not implemented properly in AC, insanity phase does nothing.
// Amanitar (Heroic Only)
// TODO: once I get to heroics
}
void WotlkDungeonOKStrategy::InitMultipliers(std::vector<Multiplier*> &multipliers)
{
multipliers.push_back(new ElderNadoxMultiplier(botAI));
multipliers.push_back(new JedogaShadowseekerMultiplier(botAI));
multipliers.push_back(new ForgottenOneMultiplier(botAI));
}

View File

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

View File

@@ -0,0 +1,23 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONOKTRIGGERCONTEXT_H
#define _PLAYERBOT_WOTLKDUNGEONOKTRIGGERCONTEXT_H
#include "NamedObjectContext.h"
#include "AiObjectContext.h"
#include "OldKingdomTriggers.h"
class WotlkDungeonOKTriggerContext : public NamedObjectContext<Trigger>
{
public:
WotlkDungeonOKTriggerContext()
{
creators["nadox guardian"] = &WotlkDungeonOKTriggerContext::nadox_guardian;
creators["jedoga volunteer"] = &WotlkDungeonOKTriggerContext::jedoga_volunteer;
creators["shadow crash"] = &WotlkDungeonOKTriggerContext::shadow_crash;
}
private:
static Trigger* nadox_guardian(PlayerbotAI* ai) { return new NadoxGuardianTrigger(ai); }
static Trigger* jedoga_volunteer(PlayerbotAI* ai) { return new JedogaVolunteerTrigger(ai); }
static Trigger* shadow_crash(PlayerbotAI* ai) { return new ShadowCrashTrigger(ai); }
};
#endif

View File

@@ -0,0 +1,43 @@
#include "Playerbots.h"
#include "OldKingdomTriggers.h"
#include "AiObject.h"
#include "AiObjectContext.h"
bool NadoxGuardianTrigger::IsActive()
{
if (botAI->IsHeal(bot)) { return false; }
Unit* boss = AI_VALUE2(Unit*, "find target", "elder nadox");
Unit* guardian = AI_VALUE2(Unit*, "find target", "ahn'kahar guardian");
return boss && guardian;
}
bool JedogaVolunteerTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "jedoga shadowseeker");
// Unit* volunteer = AI_VALUE2(Unit*, "find target", "twilight volunteer");
Unit* volunteer = nullptr;
// Target is not findable from threat table using AI_VALUE2(),
// therefore need to search manually for the unit name
GuidVector targets = AI_VALUE(GuidVector, "possible targets no los");
for (auto i = targets.begin(); i != targets.end(); ++i)
{
Unit* unit = botAI->GetUnit(*i);
if (unit && unit->GetEntry() == NPC_TWILIGHT_VOLUNTEER)
{
volunteer = unit;
break;
}
}
return boss && volunteer;
}
bool ShadowCrashTrigger::IsActive()
{
if (botAI->IsMelee(bot)) { return false; }
return !botAI->IsMelee(bot) && AI_VALUE2(Unit*, "find target", "forgotten one");
}

View File

@@ -0,0 +1,45 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONOKTRIGGERS_H
#define _PLAYERBOT_WOTLKDUNGEONOKTRIGGERS_H
#include "Trigger.h"
#include "PlayerbotAIConfig.h"
#include "GenericTriggers.h"
#include "DungeonStrategyUtils.h"
enum OldKingdomIDs
{
// Elder Nadox
BUFF_GUARDIAN_AURA = 56153,
// Jedoga Shadowseeker
NPC_TWILIGHT_VOLUNTEER = 30385,
// Forgotten One(s)
SPELL_SHADOW_CRASH_N = 60833,
SPELL_SHADOW_CRASH_H = 60848,
};
#define SPELL_SHADOW_CRASH DUNGEON_MODE(bot, SPELL_SHADOW_CRASH_N, SPELL_SHADOW_CRASH_H)
class NadoxGuardianTrigger : public Trigger
{
public:
NadoxGuardianTrigger(PlayerbotAI* ai) : Trigger(ai, "elder nadox guardian") {}
bool IsActive() override;
};
class JedogaVolunteerTrigger : public Trigger
{
public:
JedogaVolunteerTrigger(PlayerbotAI* ai) : Trigger(ai, "jedoga volunteer") {}
bool IsActive() override;
};
class ShadowCrashTrigger : public Trigger
{
public:
ShadowCrashTrigger(PlayerbotAI* ai) : Trigger(ai, "shadow crash") {}
bool IsActive() override;
};
#endif

View File

@@ -2,6 +2,7 @@
#include "UtgardeKeepActions.h" #include "UtgardeKeepActions.h"
#include "UtgardeKeepStrategy.h" #include "UtgardeKeepStrategy.h"
bool AttackFrostTombAction::isUseful() { return !botAI->IsHeal(bot); }
bool AttackFrostTombAction::Execute(Event event) bool AttackFrostTombAction::Execute(Event event)
{ {
Unit* frostTomb = nullptr; Unit* frostTomb = nullptr;

View File

@@ -12,6 +12,7 @@ class AttackFrostTombAction : public AttackAction
public: public:
AttackFrostTombAction(PlayerbotAI* ai) : AttackAction(ai, "attack frost tomb") {} AttackFrostTombAction(PlayerbotAI* ai) : AttackAction(ai, "attack frost tomb") {}
bool Execute(Event event) override; bool Execute(Event event) override;
bool isUseful() override;
}; };
class AttackDalronnAction : public AttackAction class AttackDalronnAction : public AttackAction
@@ -32,16 +33,16 @@ class IngvarDodgeSmashAction : public MovementAction
{ {
public: public:
IngvarDodgeSmashAction(PlayerbotAI* ai) : MovementAction(ai, "ingvar dodge smash") {} IngvarDodgeSmashAction(PlayerbotAI* ai) : MovementAction(ai, "ingvar dodge smash") {}
bool isUseful() override;
bool Execute(Event event) override; bool Execute(Event event) override;
bool isUseful() override;
}; };
class IngvarSmashReturnAction : public MovementAction class IngvarSmashReturnAction : public MovementAction
{ {
public: public:
IngvarSmashReturnAction(PlayerbotAI* ai) : MovementAction(ai, "ingvar smash return") {} IngvarSmashReturnAction(PlayerbotAI* ai) : MovementAction(ai, "ingvar smash return") {}
bool isUseful() override;
bool Execute(Event event) override; bool Execute(Event event) override;
bool isUseful() override;
}; };
#endif #endif

View File

@@ -6,7 +6,7 @@
float PrinceKelesethMultiplier::GetValue(Action* action) float PrinceKelesethMultiplier::GetValue(Action* action)
{ {
Unit* boss = AI_VALUE2(Unit*, "find target", "prince keleseth"); Unit* boss = AI_VALUE2(Unit*, "find target", "prince keleseth");
if (!boss) if (!boss)
{ {
return 1.0f; return 1.0f;
@@ -15,8 +15,9 @@ float PrinceKelesethMultiplier::GetValue(Action* action)
{ {
return 0.0f; return 0.0f;
} }
return 1.0f; return 1.0f;
} }
float SkarvaldAndDalronnMultiplier::GetValue(Action* action) float SkarvaldAndDalronnMultiplier::GetValue(Action* action)
{ {
// Unit* skarvald = AI_VALUE2(Unit*, "find target", "skarvald the constructor"); // Unit* skarvald = AI_VALUE2(Unit*, "find target", "skarvald the constructor");

View File

@@ -1,5 +1,5 @@
#ifndef _PLAYERRBOT_WOTLKDUNGEONUKMULTIPLIERS_H_ #ifndef _PLAYERBOT_WOTLKDUNGEONUKMULTIPLIERS_H
#define _PLAYERRBOT_WOTLKDUNGEONUKMULTIPLIERS_H_ #define _PLAYERBOT_WOTLKDUNGEONUKMULTIPLIERS_H
#include "Multiplier.h" #include "Multiplier.h"

View File

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

View File

@@ -1,15 +1,12 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONUKTRIGGERS_H #ifndef _PLAYERBOT_WOTLKDUNGEONUKTRIGGERS_H
#define _PLAYERBOT_WOTLKDUNGEONUKTRIGGERS_H #define _PLAYERBOT_WOTLKDUNGEONUKTRIGGERS_H
#include "EventMap.h"
#include "Trigger.h" #include "Trigger.h"
#include "PlayerbotAIConfig.h" #include "PlayerbotAIConfig.h"
#include "GenericTriggers.h" #include "GenericTriggers.h"
#include "DungeonStrategyUtils.h" #include "DungeonStrategyUtils.h"
// Taken from: enum UtgardeKeepIDs
// src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/boss_ingvar_the_plunderer.cpp
enum eSpells
{ {
SPELL_SUMMON_VALKYR = 42912, SPELL_SUMMON_VALKYR = 42912,
SPELL_RESURRECTION_BEAM = 42857, SPELL_RESURRECTION_BEAM = 42857,
@@ -32,7 +29,6 @@ enum eSpells
SPELL_DARK_SMASH = 42723, SPELL_DARK_SMASH = 42723,
SPELL_SHADOW_AXE = 42749, SPELL_SHADOW_AXE = 42749,
// Added
DEBUFF_FROST_TOMB = 48400, DEBUFF_FROST_TOMB = 48400,
}; };

View File

@@ -0,0 +1,24 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONVHACTIONCONTEXT_H
#define _PLAYERBOT_WOTLKDUNGEONVHACTIONCONTEXT_H
#include "Action.h"
#include "NamedObjectContext.h"
#include "VioletHoldActions.h"
class WotlkDungeonVHActionContext : public NamedObjectContext<Action>
{
public:
WotlkDungeonVHActionContext() {
creators["attack erekem"] = &WotlkDungeonVHActionContext::attack_erekem;
creators["attack ichor globule"] = &WotlkDungeonVHActionContext::attack_ichor_globule;
creators["attack void sentry"] = &WotlkDungeonVHActionContext::attack_void_sentry;
creators["stop attack"] = &WotlkDungeonVHActionContext::stop_attack;
}
private:
static Action* attack_erekem(PlayerbotAI* ai) { return new AttackErekemAction(ai); }
static Action* attack_ichor_globule(PlayerbotAI* ai) { return new AttackIchorGlobuleAction(ai); }
static Action* attack_void_sentry(PlayerbotAI* ai) { return new AttackVoidSentryAction(ai); }
static Action* stop_attack(PlayerbotAI* ai) { return new StopAttackAction(ai); }
};
#endif

View File

@@ -0,0 +1,99 @@
#include "Playerbots.h"
#include "VioletHoldActions.h"
#include "VioletHoldStrategy.h"
bool AttackErekemAction::Execute(Event event)
{
// Focus boss first, adds after
Unit* boss = AI_VALUE2(Unit*, "find target", "erekem");
if (AI_VALUE(Unit*, "current target") != boss)
{
return Attack(boss);
}
return false;
}
bool AttackIchorGlobuleAction::Execute(Event event)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "ichoron");
if (!boss) { return false; }
// Tank prioritise boss if it's up
if (botAI->IsTank(bot) && !boss->HasAura(SPELL_DRAINED))
{
if (AI_VALUE(Unit*, "current target") != boss)
{
return Attack(boss);
}
return false;
}
// Target is not findable from threat table using AI_VALUE2(),
// therefore need to search manually for the unit name
GuidVector targets = AI_VALUE(GuidVector, "possible targets");
for (auto i = targets.begin(); i != targets.end(); ++i)
{
Unit* unit = botAI->GetUnit(*i);
if (unit && unit->GetEntry() == NPC_ICHOR_GLOBULE)
{
Unit* currentTarget = AI_VALUE(Unit*, "current target");
// Check IDs here, NOT Unit* pointers:
// Don't keep swapping between sentries.
// If we're already attacking one, don't retarget another
if (currentTarget && currentTarget->GetEntry() == NPC_ICHOR_GLOBULE)
{
return false;
}
return Attack(unit);
}
}
// No ichor globules left alive, fall back to targeting boss
if (AI_VALUE(Unit*, "current target") != boss)
{
return Attack(boss);
}
return false;
}
bool AttackVoidSentryAction::Execute(Event event)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "zuramat the obliterator");
if (!boss) { return false; }
// Target is not findable from threat table using AI_VALUE2(),
// therefore need to search manually for the unit name
// GuidVector targets = AI_VALUE(GuidVector, "possible targets no los");
GuidVector targets = AI_VALUE(GuidVector, "possible targets no los");
for (auto i = targets.begin(); i != targets.end(); ++i)
{
Unit* unit = botAI->GetUnit(*i);
if (unit && unit->GetEntry() == NPC_VOID_SENTRY)
{
Unit* currentTarget = AI_VALUE(Unit*, "current target");
// Check IDs here, NOT Unit* pointers:
// Don't keep swapping between sentries.
// If we're already attacking one, don't retarget another
if (currentTarget && currentTarget->GetEntry() == NPC_VOID_SENTRY)
{
return false;
}
return Attack(unit);
}
}
// No void sentries left alive, fall back to targeting boss
if (AI_VALUE(Unit*, "current target") != boss)
{
return Attack(boss);
}
return false;
}
bool StopAttackAction::Execute(Event event)
{
return bot->AttackStop();
}

View File

@@ -0,0 +1,48 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONVHACTIONS_H
#define _PLAYERBOT_WOTLKDUNGEONVHACTIONS_H
#include "Action.h"
#include "AttackAction.h"
#include "GenericSpellActions.h"
#include "PlayerbotAI.h"
#include "Playerbots.h"
#include "VioletHoldTriggers.h"
// const Position NOVOS_PARTY_POSITION = Position(-378.852f, -760.349f, 28.587f);
class AttackErekemAction : public AttackAction
{
public:
AttackErekemAction(PlayerbotAI* ai) : AttackAction(ai, "attack erekem") {}
bool Execute(Event event) override;
};
class AttackIchoronElementalsAction : public AttackAction
{
public:
AttackIchoronElementalsAction(PlayerbotAI* ai) : AttackAction(ai, "attack ichoron elementals") {}
bool Execute(Event event) override;
};
class AttackIchorGlobuleAction : public AttackAction
{
public:
AttackIchorGlobuleAction(PlayerbotAI* ai) : AttackAction(ai, "attack ichor globule") {}
bool Execute(Event event) override;
};
class AttackVoidSentryAction : public AttackAction
{
public:
AttackVoidSentryAction(PlayerbotAI* ai) : AttackAction(ai, "attack void sentry") {}
bool Execute(Event event) override;
};
class StopAttackAction : public Action
{
public:
StopAttackAction(PlayerbotAI* ai) : Action(ai, "stop attack") {}
bool Execute(Event event) override;
};
#endif

View File

@@ -0,0 +1,57 @@
#include "VioletHoldMultipliers.h"
#include "VioletHoldActions.h"
#include "GenericSpellActions.h"
#include "ChooseTargetActions.h"
#include "MovementActions.h"
#include "VioletHoldTriggers.h"
#include "Action.h"
float ErekemMultiplier::GetValue(Action* action)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "erekem");
if (!boss || !botAI->IsDps(bot)) { return 1.0f; }
if (dynamic_cast<DpsAssistAction*>(action))
{
return 0.0f;
}
if (action->getThreatType() == Action::ActionThreatType::Aoe)
{
return 0.0f;
}
return 1.0f;
}
float IchoronMultiplier::GetValue(Action* action)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "ichoron");
if (!boss) { return 1.0f; }
if (dynamic_cast<DpsAssistAction*>(action)
|| dynamic_cast<TankAssistAction*>(action)
|| dynamic_cast<DropTargetAction*>(action))
{
return 0.0f;
}
return 1.0f;
}
float ZuramatMultiplier::GetValue(Action* action)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "zuramat the obliterator");
if (!boss) { return 1.0f; }
if (bot->HasAura(SPELL_VOID_SHIFTED))
{
if (dynamic_cast<DpsAssistAction*>(action) || dynamic_cast<TankAssistAction*>(action))
{
return 0.0f;
}
}
if (boss->HasAura(SPELL_SHROUD_OF_DARKNESS) && dynamic_cast<AttackAction*>(action))
{
return 0.0f;
}
return 1.0f;
}

View File

@@ -0,0 +1,33 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONVHMULTIPLIERS_H
#define _PLAYERBOT_WOTLKDUNGEONVHMULTIPLIERS_H
#include "Multiplier.h"
class ErekemMultiplier : public Multiplier
{
public:
ErekemMultiplier(PlayerbotAI* ai) : Multiplier(ai, "erekem") {}
public:
virtual float GetValue(Action* action);
};
class IchoronMultiplier : public Multiplier
{
public:
IchoronMultiplier(PlayerbotAI* ai) : Multiplier(ai, "ichoron") {}
public:
virtual float GetValue(Action* action);
};
class ZuramatMultiplier : public Multiplier
{
public:
ZuramatMultiplier(PlayerbotAI* ai) : Multiplier(ai, "zuramat the obliterator") {}
public:
virtual float GetValue(Action* action);
};
#endif

View File

@@ -0,0 +1,41 @@
#include "VioletHoldStrategy.h"
#include "VioletHoldMultipliers.h"
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)));
// 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)));
// Xevozz
// TODO: Revisit in heroics, waypoints back and forth on stairs. Need to test with double beacon spawn
// Lavanthor
// Tank & spank
// Zuramat the Obliterator
triggers.push_back(new TriggerNode("shroud of darkness",
NextAction::array(0, new NextAction("stop attack", ACTION_HIGH + 5), nullptr)));
triggers.push_back(new TriggerNode("void shift",
NextAction::array(0, new NextAction("attack void sentry", ACTION_RAID + 1), nullptr)));
// Cyanigosa
triggers.push_back(new TriggerNode("cyanigosa positioning",
NextAction::array(0, new NextAction("rear flank", ACTION_MOVE + 5), nullptr)));
}
void WotlkDungeonVHStrategy::InitMultipliers(std::vector<Multiplier*> &multipliers)
{
multipliers.push_back(new ErekemMultiplier(botAI));
multipliers.push_back(new IchoronMultiplier(botAI));
multipliers.push_back(new ZuramatMultiplier(botAI));
}

View File

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

View File

@@ -0,0 +1,27 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONVHTRIGGERCONTEXT_H
#define _PLAYERBOT_WOTLKDUNGEONVHTRIGGERCONTEXT_H
#include "NamedObjectContext.h"
#include "AiObjectContext.h"
#include "VioletHoldTriggers.h"
class WotlkDungeonVHTriggerContext : public NamedObjectContext<Trigger>
{
public:
WotlkDungeonVHTriggerContext()
{
creators["erekem target"] = &WotlkDungeonVHTriggerContext::erekem_target;
creators["ichoron target"] = &WotlkDungeonVHTriggerContext::ichoron_target;
creators["void shift"] = &WotlkDungeonVHTriggerContext::void_shift;
creators["shroud of darkness"] = &WotlkDungeonVHTriggerContext::shroud_of_darkness;
creators["cyanigosa positioning"] = &WotlkDungeonVHTriggerContext::cyanigosa_positioning;
}
private:
static Trigger* erekem_target(PlayerbotAI* ai) { return new ErekemTargetTrigger(ai); }
static Trigger* ichoron_target(PlayerbotAI* ai) { return new IchoronTargetTrigger(ai); }
static Trigger* void_shift(PlayerbotAI* ai) { return new VoidShiftTrigger(ai); }
static Trigger* shroud_of_darkness(PlayerbotAI* ai) { return new ShroudOfDarknessTrigger(ai); }
static Trigger* cyanigosa_positioning(PlayerbotAI* ai) { return new CyanigosaPositioningTrigger(ai); }
};
#endif

View File

@@ -0,0 +1,35 @@
#include "Playerbots.h"
#include "VioletHoldTriggers.h"
#include "AiObject.h"
#include "AiObjectContext.h"
bool ErekemTargetTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "erekem") && botAI->IsDps(bot);
}
bool IchoronTargetTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "ichoron") && !botAI->IsHeal(bot);
}
bool VoidShiftTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "zuramat the obliterator");
return boss && bot->HasAura(SPELL_VOID_SHIFTED) && !botAI->IsHeal(bot);
}
bool ShroudOfDarknessTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "zuramat the obliterator");
return boss && boss->HasAura(SPELL_SHROUD_OF_DARKNESS);
}
bool CyanigosaPositioningTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "cyanigosa");
// Include healers here for now, otherwise they stand in things
return boss && !botAI->IsTank(bot) && !botAI->IsRangedDps(bot);
// return boss && botAI->IsMelee(bot) && !botAI->IsTank(bot);
}

View File

@@ -0,0 +1,59 @@
#ifndef _PLAYERBOT_WOTLKDUNGEONVHTRIGGERS_H
#define _PLAYERBOT_WOTLKDUNGEONVHTRIGGERS_H
#include "Trigger.h"
#include "PlayerbotAIConfig.h"
#include "GenericTriggers.h"
#include "DungeonStrategyUtils.h"
enum VioletHoldIDs
{
// Ichoron
SPELL_DRAINED = 59820,
NPC_ICHOR_GLOBULE = 29321,
// Zuramat the Obliterator
SPELL_VOID_SHIFTED = 54343,
SPELL_SHROUD_OF_DARKNESS_N = 54524,
SPELL_SHROUD_OF_DARKNESS_H = 59745,
NPC_VOID_SENTRY = 29364,
};
#define SPELL_SHROUD_OF_DARKNESS DUNGEON_MODE(bot, SPELL_SHROUD_OF_DARKNESS_N, SPELL_SHROUD_OF_DARKNESS_H)
class ErekemTargetTrigger : public Trigger
{
public:
ErekemTargetTrigger(PlayerbotAI* ai) : Trigger(ai, "erekem target") {}
bool IsActive() override;
};
class IchoronTargetTrigger : public Trigger
{
public:
IchoronTargetTrigger(PlayerbotAI* ai) : Trigger(ai, "ichoron target") {}
bool IsActive() override;
};
class VoidShiftTrigger : public Trigger
{
public:
VoidShiftTrigger(PlayerbotAI* ai) : Trigger(ai, "void shift") {}
bool IsActive() override;
};
class ShroudOfDarknessTrigger : public Trigger
{
public:
ShroudOfDarknessTrigger(PlayerbotAI* ai) : Trigger(ai, "shroud of darkness") {}
bool IsActive() override;
};
class CyanigosaPositioningTrigger : public Trigger
{
public:
CyanigosaPositioningTrigger(PlayerbotAI* ai) : Trigger(ai, "cyanigosa positioning") {}
bool IsActive() override;
};
#endif

View File

@@ -29,7 +29,7 @@ bool FingersOfFrostSingleTrigger::IsActive()
{ {
// Fingers of Frost "stack" count is always 1. // Fingers of Frost "stack" count is always 1.
// The value is instead stored in the charges. // The value is instead stored in the charges.
Aura* aura = botAI->GetAura(getName(), GetTarget(), false, true, -1); Aura* aura = botAI->GetAura("fingers of frost", bot, false, true, -1);
return (aura && aura->GetCharges() == 1); return (aura && aura->GetCharges() == 1);
} }

View File

@@ -22,7 +22,7 @@ void GenericPaladinNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& tr
triggers.push_back(new TriggerNode("party member almost full health", triggers.push_back(new TriggerNode("party member almost full health",
NextAction::array(0, new NextAction("flash of light on party", 25.0f), NULL))); NextAction::array(0, new NextAction("flash of light on party", 25.0f), NULL)));
triggers.push_back(new TriggerNode("party member medium health", triggers.push_back(new TriggerNode("party member medium health",
NextAction::array(0, new NextAction("holy light on party", 26.0f), NULL))); NextAction::array(0, new NextAction("flash of light on party", 26.0f), NULL)));
triggers.push_back(new TriggerNode("party member low health", triggers.push_back(new TriggerNode("party member low health",
NextAction::array(0, new NextAction("holy light on party", 27.0f), NULL))); NextAction::array(0, new NextAction("holy light on party", 27.0f), NULL)));
triggers.push_back(new TriggerNode("party member critical health", triggers.push_back(new TriggerNode("party member critical health",

View File

@@ -71,3 +71,17 @@ void PaladinCcStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
triggers.push_back( triggers.push_back(
new TriggerNode("turn undead", NextAction::array(0, new NextAction("turn undead", ACTION_HIGH + 1), nullptr))); new TriggerNode("turn undead", NextAction::array(0, new NextAction("turn undead", ACTION_HIGH + 1), nullptr)));
} }
void PaladinHealerDpsStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(
new TriggerNode("healer should attack",
NextAction::array(0,
new NextAction("hammer of wrath", ACTION_DEFAULT + 0.6f),
new NextAction("holy shock", ACTION_DEFAULT + 0.5f),
new NextAction("shield of righteousness", ACTION_DEFAULT + 0.4f),
new NextAction("judgement of light", ACTION_DEFAULT + 0.3f),
new NextAction("consecration", ACTION_DEFAULT + 0.2f),
new NextAction("exorcism", ACTION_DEFAULT+ 0.1f),
nullptr)));
}

View File

@@ -46,4 +46,13 @@ public:
std::string const getName() override { return "cc"; } std::string const getName() override { return "cc"; }
}; };
class PaladinHealerDpsStrategy : public Strategy
{
public:
PaladinHealerDpsStrategy(PlayerbotAI* botAI) : Strategy(botAI) {}
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
std::string const getName() override { return "healer dps"; }
};
#endif #endif

View File

@@ -22,7 +22,7 @@ public:
creators["cleanse magic"] = &cleanse_magic; creators["cleanse magic"] = &cleanse_magic;
creators["cleanse poison on party"] = &cleanse_poison_on_party; creators["cleanse poison on party"] = &cleanse_poison_on_party;
creators["cleanse disease on party"] = &cleanse_disease_on_party; creators["cleanse disease on party"] = &cleanse_disease_on_party;
// creators["seal of wisdom"] = &seal_of_wisdom; creators["seal of wisdom"] = &seal_of_wisdom;
creators["seal of justice"] = &seal_of_justice; creators["seal of justice"] = &seal_of_justice;
creators["hand of reckoning"] = &hand_of_reckoning; creators["hand of reckoning"] = &hand_of_reckoning;
creators["judgement"] = &judgement; creators["judgement"] = &judgement;
@@ -147,13 +147,13 @@ private:
/*A*/ NextAction::array(0, new NextAction("purify disease on party"), nullptr), /*A*/ NextAction::array(0, new NextAction("purify disease on party"), nullptr),
/*C*/ nullptr); /*C*/ nullptr);
} }
// static ActionNode* seal_of_wisdom(PlayerbotAI* ai) static ActionNode* seal_of_wisdom(PlayerbotAI* ai)
// { {
// return new ActionNode ("seal of wisdom", return new ActionNode ("seal of wisdom",
// /*P*/ NULL, /*P*/ NULL,
// /*A*/ NextAction::array(0, new NextAction("seal of justice"), NULL), /*A*/ NextAction::array(0, new NextAction("seal of righteousness"), NULL),
// /*C*/ NULL); /*C*/ NULL);
// } }
static ActionNode* seal_of_justice(PlayerbotAI* ai) static ActionNode* seal_of_justice(PlayerbotAI* ai)
{ {
return new ActionNode("seal of justice", return new ActionNode("seal of justice",

View File

@@ -27,7 +27,7 @@ HealPaladinStrategy::HealPaladinStrategy(PlayerbotAI* botAI) : GenericPaladinStr
NextAction** HealPaladinStrategy::getDefaultActions() NextAction** HealPaladinStrategy::getDefaultActions()
{ {
return NextAction::array(0, new NextAction("judgement of light", ACTION_DEFAULT + 2), nullptr); return NextAction::array(0, new NextAction("judgement of light", ACTION_DEFAULT), nullptr);
} }
void HealPaladinStrategy::InitTriggers(std::vector<TriggerNode*>& triggers) void HealPaladinStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
@@ -49,7 +49,7 @@ void HealPaladinStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
NextAction::array(0, new NextAction("reach party member to heal", ACTION_EMERGENCY + 3), nullptr))); NextAction::array(0, new NextAction("reach party member to heal", ACTION_EMERGENCY + 3), nullptr)));
triggers.push_back( triggers.push_back(
new TriggerNode("medium group heal occasion", new TriggerNode("medium group heal setting",
NextAction::array(0, new NextAction("divine sacrifice", ACTION_CRITICAL_HEAL + 5), NextAction::array(0, new NextAction("divine sacrifice", ACTION_CRITICAL_HEAL + 5),
new NextAction("avenging wrath", ACTION_HIGH + 4), new NextAction("avenging wrath", ACTION_HIGH + 4),
nullptr))); nullptr)));

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