mirror of
https://github.com/mod-playerbots/mod-playerbots
synced 2025-11-29 15:58:20 +08:00
Merge branch 'liyunfan1223:master' into clean_quest_log
This commit is contained in:
@@ -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, ...
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -382,9 +389,6 @@ void PlayerbotAI::UpdateAI(uint32 elapsed, bool minimal)
|
|||||||
bot->StopMovingOnCurrentPos();
|
bot->StopMovingOnCurrentPos();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!CanUpdateAI())
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!bot->InBattleground() && !bot->inRandomLfgDungeon() && bot->GetGroup())
|
if (!bot->InBattleground() && !bot->inRandomLfgDungeon() && bot->GetGroup())
|
||||||
{
|
{
|
||||||
@@ -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())
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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); }
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -47,4 +47,35 @@ 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();
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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); }
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)));
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"] = ®rowth_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)));
|
||||||
|
|||||||
@@ -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); }
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
|
||||||
110
src/strategy/dungeons/wotlk/azjolnerub/AzjolNerubActions.cpp
Normal file
110
src/strategy/dungeons/wotlk/azjolnerub/AzjolNerubActions.cpp
Normal 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);
|
||||||
|
}
|
||||||
34
src/strategy/dungeons/wotlk/azjolnerub/AzjolNerubActions.h
Normal file
34
src/strategy/dungeons/wotlk/azjolnerub/AzjolNerubActions.h
Normal 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
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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));
|
||||||
|
}
|
||||||
18
src/strategy/dungeons/wotlk/azjolnerub/AzjolNerubStrategy.h
Normal file
18
src/strategy/dungeons/wotlk/azjolnerub/AzjolNerubStrategy.h
Normal 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
|
||||||
@@ -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
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
61
src/strategy/dungeons/wotlk/azjolnerub/AzjolNerubTriggers.h
Normal file
61
src/strategy/dungeons/wotlk/azjolnerub/AzjolNerubTriggers.h
Normal 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
|
||||||
@@ -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
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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));
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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
|
||||||
28
src/strategy/dungeons/wotlk/nexus/NexusActionContext.h
Normal file
28
src/strategy/dungeons/wotlk/nexus/NexusActionContext.h
Normal 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
|
||||||
159
src/strategy/dungeons/wotlk/nexus/NexusActions.cpp
Normal file
159
src/strategy/dungeons/wotlk/nexus/NexusActions.cpp
Normal 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();
|
||||||
|
}
|
||||||
71
src/strategy/dungeons/wotlk/nexus/NexusActions.h
Normal file
71
src/strategy/dungeons/wotlk/nexus/NexusActions.h
Normal 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
|
||||||
99
src/strategy/dungeons/wotlk/nexus/NexusMultipliers.cpp
Normal file
99
src/strategy/dungeons/wotlk/nexus/NexusMultipliers.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
51
src/strategy/dungeons/wotlk/nexus/NexusMultipliers.h
Normal file
51
src/strategy/dungeons/wotlk/nexus/NexusMultipliers.h
Normal 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
|
||||||
54
src/strategy/dungeons/wotlk/nexus/NexusStrategy.cpp
Normal file
54
src/strategy/dungeons/wotlk/nexus/NexusStrategy.cpp
Normal 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));
|
||||||
|
}
|
||||||
18
src/strategy/dungeons/wotlk/nexus/NexusStrategy.h
Normal file
18
src/strategy/dungeons/wotlk/nexus/NexusStrategy.h
Normal 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
|
||||||
33
src/strategy/dungeons/wotlk/nexus/NexusTriggerContext.h
Normal file
33
src/strategy/dungeons/wotlk/nexus/NexusTriggerContext.h
Normal 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
|
||||||
107
src/strategy/dungeons/wotlk/nexus/NexusTriggers.cpp
Normal file
107
src/strategy/dungeons/wotlk/nexus/NexusTriggers.cpp
Normal 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);
|
||||||
|
}
|
||||||
89
src/strategy/dungeons/wotlk/nexus/NexusTriggers.h
Normal file
89
src/strategy/dungeons/wotlk/nexus/NexusTriggers.h
Normal 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
|
||||||
@@ -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
|
||||||
81
src/strategy/dungeons/wotlk/oldkingdom/OldKingdomActions.cpp
Normal file
81
src/strategy/dungeons/wotlk/oldkingdom/OldKingdomActions.cpp
Normal 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;
|
||||||
|
}
|
||||||
31
src/strategy/dungeons/wotlk/oldkingdom/OldKingdomActions.h
Normal file
31
src/strategy/dungeons/wotlk/oldkingdom/OldKingdomActions.h
Normal 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
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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));
|
||||||
|
}
|
||||||
18
src/strategy/dungeons/wotlk/oldkingdom/OldKingdomStrategy.h
Normal file
18
src/strategy/dungeons/wotlk/oldkingdom/OldKingdomStrategy.h
Normal 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
|
||||||
@@ -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
|
||||||
@@ -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");
|
||||||
|
}
|
||||||
45
src/strategy/dungeons/wotlk/oldkingdom/OldKingdomTriggers.h
Normal file
45
src/strategy/dungeons/wotlk/oldkingdom/OldKingdomTriggers.h
Normal 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
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
99
src/strategy/dungeons/wotlk/violethold/VioletHoldActions.cpp
Normal file
99
src/strategy/dungeons/wotlk/violethold/VioletHoldActions.cpp
Normal 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();
|
||||||
|
}
|
||||||
48
src/strategy/dungeons/wotlk/violethold/VioletHoldActions.h
Normal file
48
src/strategy/dungeons/wotlk/violethold/VioletHoldActions.h
Normal 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
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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));
|
||||||
|
}
|
||||||
18
src/strategy/dungeons/wotlk/violethold/VioletHoldStrategy.h
Normal file
18
src/strategy/dungeons/wotlk/violethold/VioletHoldStrategy.h
Normal 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
|
||||||
@@ -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
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
59
src/strategy/dungeons/wotlk/violethold/VioletHoldTriggers.h
Normal file
59
src/strategy/dungeons/wotlk/violethold/VioletHoldTriggers.h
Normal 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
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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)));
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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
Reference in New Issue
Block a user