diff --git a/conf/playerbots.conf.dist b/conf/playerbots.conf.dist index 6186e5dc..da7b83fe 100644 --- a/conf/playerbots.conf.dist +++ b/conf/playerbots.conf.dist @@ -67,7 +67,7 @@ ################################### # # -# GENERAL SETTINGS # +# GENERAL SETTINGS # # # ################################### @@ -368,6 +368,10 @@ AiPlayerbot.SyncQuestWithPlayer = 1 # Default: 0 (disabled) AiPlayerbot.SyncQuestForPlayer = 0 +# Bots will drop obsolete quests +# Default: 1 (enabled) +AiPlayerbot.DropObsoleteQuests = 1 + # # # @@ -1027,6 +1031,56 @@ AiPlayerbot.PremadeSpecLink.11.3.80 = -553202032322010053100030310511-205503012 # ################################################################################################### +################################### +# # +# WORLD BUFFS # +# # +################################### + +#################################################################################################### +# +# +# + +# Applies a permanent buff to all bots when not in combat simulating flasks, food, rune etc. +# WorldBuff.Faction.Class.Spec.MinLevel.MaxLevel + +AiPlayerbot.WorldBuff.0.1.0.80.80 = 53760,57358 #WARRIOR ARMS +AiPlayerbot.WorldBuff.0.1.1.80.80 = 53760,57358 #WARRIOR FURY +AiPlayerbot.WorldBuff.0.1.2.80.80 = 53758,57356 #WARRIOR PROTECTION +AiPlayerbot.WorldBuff.0.2.0.80.80 = 60347,53749,57332 #PALADIN HOLY +AiPlayerbot.WorldBuff.0.2.1.80.80 = 53758,57356 #PALADIN PROTECTION +AiPlayerbot.WorldBuff.0.2.2.80.80 = 53760,57371 #PALADIN RETRIBUTION +AiPlayerbot.WorldBuff.0.3.0.80.80 = 53760,57325 #HUNTER BEAST +AiPlayerbot.WorldBuff.0.3.1.80.80 = 53760,57358 #HUNTER MARKSMANSHIP +AiPlayerbot.WorldBuff.0.3.2.80.80 = 53760,57367 #HUNTER SURVIVAL +AiPlayerbot.WorldBuff.0.4.0.80.80 = 53760,57325 #ROGUE ASSASINATION +AiPlayerbot.WorldBuff.0.4.1.80.80 = 53760,57358 #ROGUE COMBAT +AiPlayerbot.WorldBuff.0.4.2.80.80 = 53760,57367 #ROGUE SUBTLETY +AiPlayerbot.WorldBuff.0.5.0.80.80 = 53755,57327 #PRIEST DISCIPLINE +AiPlayerbot.WorldBuff.0.5.1.80.80 = 53755,57327 #PRIEST HOLY +AiPlayerbot.WorldBuff.0.5.2.80.80 = 53755,57327 #PRIEST SHADOW +AiPlayerbot.WorldBuff.0.6.0.80.80 = 53758,57356 #DEATH KNIGHT BLOOD +AiPlayerbot.WorldBuff.0.6.1.80.80 = 53760,57358 #DEATH KNIGHT FROST +AiPlayerbot.WorldBuff.0.6.2.80.80 = 53760,57358 #DEATH KNIGHT UNHOLY +AiPlayerbot.WorldBuff.0.7.0.80.80 = 53755,57327 #SHAMAN ELEMENTAL +AiPlayerbot.WorldBuff.0.7.1.80.80 = 53760,57325 #SHAMAN ENHANCEMENT +AiPlayerbot.WorldBuff.0.7.2.80.80 = 53755,57327 #SHAMAN RESTORATION +AiPlayerbot.WorldBuff.0.8.0.80.80 = 53755,57327 #MAGE ARCANE +AiPlayerbot.WorldBuff.0.8.1.80.80 = 53755,57327 #MAGE FIRE +AiPlayerbot.WorldBuff.0.8.2.80.80 = 53755,57327 #MAGE FROST +AiPlayerbot.WorldBuff.0.9.0.80.80 = 53755,57327 #WARLOCK AFFLICTION +AiPlayerbot.WorldBuff.0.9.1.80.80 = 53755,57327 #WARLOCK DEMONOLOGY +AiPlayerbot.WorldBuff.0.9.2.80.80 = 53755,57327 #WARLOCK DESTRUCTION +AiPlayerbot.WorldBuff.0.11.0.80.80 = 53755,57327 #DRUID BALANCE +AiPlayerbot.WorldBuff.0.11.1.80.80 = 53749,53763,57367 #DRUID FERAL +AiPlayerbot.WorldBuff.0.11.2.80.80 = 54212,57334 #DRUID RESTORATION + +# +# +# +################################################################################################### + ################################### # # # RANDOM BOT DEFAULT TALENT SPEC # @@ -1416,9 +1470,6 @@ AiPlayerbot.BroadcastChanceGuildManagement = 30000 # Example: AiPlayerbot.AllowedLogFiles = travelNodes.csv,travelPaths.csv,TravelNodeStore.h,bot_movement.csv,bot_location.csv AiPlayerbot.AllowedLogFiles = "" -Appender.Playerbots=2,5,0,Playerbots.log,w -Logger.playerbots=5,Console Playerbots - # # # diff --git a/src/PlayerbotAI.cpp b/src/PlayerbotAI.cpp index f67738bc..4abb5b1b 100644 --- a/src/PlayerbotAI.cpp +++ b/src/PlayerbotAI.cpp @@ -1606,6 +1606,9 @@ void PlayerbotAI::ApplyInstanceStrategies(uint32 mapId, bool tellMaster) case 608: strategyName = "wotlk-vh"; // Violet Hold break; + case 615: + strategyName = "wotlk-os"; // Obsidian Sanctum + break; case 619: strategyName = "wotlk-ok"; // Ahn'kahet: The Old Kingdom break; @@ -5466,10 +5469,10 @@ bool PlayerbotAI::CanMove() if (IsInVehicle() && !IsInVehicle(true)) return false; - if (bot->isFrozen() || bot->IsPolymorphed() || (bot->isDead() && !bot->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST)) || - 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)) + if (bot->isFrozen() || bot->IsPolymorphed() || (bot->isDead() && !bot->HasPlayerFlag(PLAYER_FLAGS_GHOST)) || + bot->IsBeingTeleported() || bot->HasRootAura() || bot->HasSpiritOfRedemptionAura() || + bot->HasConfuseAura() || bot->IsCharmed() || bot->HasStunAura() || + bot->IsInFlight() || bot->HasUnitState(UNIT_STATE_LOST_CONTROL)) return false; return bot->GetMotionMaster()->GetCurrentMovementGeneratorType() != FLIGHT_MOTION_TYPE; diff --git a/src/PlayerbotAIConfig.cpp b/src/PlayerbotAIConfig.cpp index cb26477a..fa21ad5c 100644 --- a/src/PlayerbotAIConfig.cpp +++ b/src/PlayerbotAIConfig.cpp @@ -403,11 +403,14 @@ bool PlayerbotAIConfig::Initialize() { for (uint32 classId = 0; classId < MAX_CLASSES; classId++) { - for (uint32 minLevel = 0; minLevel < MAX_LEVEL; minLevel++) + for (uint32 specId = 0; specId < MAX_SPECNO; specId++) { - for (uint32 maxLevel = 0; maxLevel < MAX_LEVEL; maxLevel++) + for (uint32 minLevel = 0; minLevel < MAX_LEVEL; minLevel++) { - loadWorldBuf(factionId, classId, minLevel, maxLevel); + for (uint32 maxLevel = 0; maxLevel < MAX_LEVEL; maxLevel++) + { + loadWorldBuf(factionId, classId, specId, minLevel, maxLevel); + } } } } @@ -491,6 +494,7 @@ bool PlayerbotAIConfig::Initialize() twoRoundsGearInit = sConfigMgr->GetOption("AiPlayerbot.TwoRoundsGearInit", false); syncQuestWithPlayer = sConfigMgr->GetOption("AiPlayerbot.SyncQuestWithPlayer", true); syncQuestForPlayer = sConfigMgr->GetOption("AiPlayerbot.SyncQuestForPlayer", false); + dropObsoleteQuests = sConfigMgr->GetOption("AiPlayerbot.DropObsoleteQuests", true); autoTrainSpells = sConfigMgr->GetOption("AiPlayerbot.AutoTrainSpells", "yes"); autoPickTalents = sConfigMgr->GetOption("AiPlayerbot.AutoPickTalents", true); autoUpgradeEquip = sConfigMgr->GetOption("AiPlayerbot.AutoUpgradeEquip", false); @@ -645,36 +649,50 @@ void PlayerbotAIConfig::log(std::string const fileName, char const* str, ...) fflush(stdout); } -void PlayerbotAIConfig::loadWorldBuf(uint32 factionId1, uint32 classId1, uint32 minLevel1, uint32 maxLevel1) +void PlayerbotAIConfig::loadWorldBuf(uint32 factionId1, uint32 classId1, uint32 specId1, uint32 minLevel1, uint32 maxLevel1) { std::vector buffs; std::ostringstream os; - os << "AiPlayerbot.WorldBuff." << factionId1 << "." << classId1 << "." << minLevel1 << "." << maxLevel1; + os << "AiPlayerbot.WorldBuff." << factionId1 << "." << classId1 << "." << specId1 << "." << minLevel1 << "." << maxLevel1; LoadList>(sConfigMgr->GetOption(os.str().c_str(), "", false), buffs); for (auto buff : buffs) { - worldBuff wb = {buff, factionId1, classId1, minLevel1, maxLevel1}; + worldBuff wb = {buff, factionId1, classId1, specId1, minLevel1, maxLevel1}; worldBuffs.push_back(wb); } if (maxLevel1 == 0) { std::ostringstream os; - os << "AiPlayerbot.WorldBuff." << factionId1 << "." << classId1 << "." << minLevel1; + os << "AiPlayerbot.WorldBuff." << factionId1 << "." << classId1 << "." << specId1 << "." << minLevel1; LoadList>(sConfigMgr->GetOption(os.str().c_str(), "", false), buffs); for (auto buff : buffs) { - worldBuff wb = {buff, factionId1, classId1, minLevel1, maxLevel1}; + worldBuff wb = {buff, factionId1, classId1, specId1, minLevel1, maxLevel1}; worldBuffs.push_back(wb); } } if (maxLevel1 == 0 && minLevel1 == 0) + { + std::ostringstream os; + os << "AiPlayerbot.WorldBuff." << factionId1 << "." << factionId1 << "." << classId1 << "." << specId1; + + LoadList>(sConfigMgr->GetOption(os.str().c_str(), "", false), buffs); + + for (auto buff : buffs) + { + worldBuff wb = {buff, factionId1, classId1, specId1, minLevel1, maxLevel1}; + worldBuffs.push_back(wb); + } + } + + if (maxLevel1 == 0 && minLevel1 == 0 && specId1 == 0) { std::ostringstream os; os << "AiPlayerbot.WorldBuff." << factionId1 << "." << factionId1 << "." << classId1; @@ -683,12 +701,12 @@ void PlayerbotAIConfig::loadWorldBuf(uint32 factionId1, uint32 classId1, uint32 for (auto buff : buffs) { - worldBuff wb = {buff, factionId1, classId1, minLevel1, maxLevel1}; + worldBuff wb = {buff, factionId1, classId1, specId1, minLevel1, maxLevel1}; worldBuffs.push_back(wb); } } - if (classId1 == 0 && maxLevel1 == 0 && minLevel1 == 0) + if (classId1 == 0 && maxLevel1 == 0 && minLevel1 == 0 && specId1 == 0) { std::ostringstream os; os << "AiPlayerbot.WorldBuff." << factionId1; @@ -697,12 +715,12 @@ void PlayerbotAIConfig::loadWorldBuf(uint32 factionId1, uint32 classId1, uint32 for (auto buff : buffs) { - worldBuff wb = {buff, factionId1, classId1, minLevel1, maxLevel1}; + worldBuff wb = {buff, factionId1, classId1, specId1, minLevel1, maxLevel1}; worldBuffs.push_back(wb); } } - if (factionId1 == 0 && classId1 == 0 && maxLevel1 == 0 && minLevel1 == 0) + if (factionId1 == 0 && classId1 == 0 && maxLevel1 == 0 && minLevel1 == 0 && specId1 == 0) { std::ostringstream os; os << "AiPlayerbot.WorldBuff"; @@ -711,7 +729,7 @@ void PlayerbotAIConfig::loadWorldBuf(uint32 factionId1, uint32 classId1, uint32 for (auto buff : buffs) { - worldBuff wb = {buff, factionId1, classId1, minLevel1, maxLevel1}; + worldBuff wb = {buff, factionId1, classId1, specId1, minLevel1, maxLevel1}; worldBuffs.push_back(wb); } } diff --git a/src/PlayerbotAIConfig.h b/src/PlayerbotAIConfig.h index be31da62..c2476a76 100644 --- a/src/PlayerbotAIConfig.h +++ b/src/PlayerbotAIConfig.h @@ -247,6 +247,7 @@ public: uint32 spellId; uint32 factionId = 0; uint32 classId = 0; + uint32 specId = 0; uint32 minLevel = 0; uint32 maxLevel = 0; }; @@ -275,6 +276,7 @@ public: bool twoRoundsGearInit; bool syncQuestWithPlayer; bool syncQuestForPlayer; + bool dropObsoleteQuests; std::string autoTrainSpells; bool autoPickTalents; bool autoUpgradeEquip; @@ -328,7 +330,7 @@ public: } void log(std::string const fileName, const char* str, ...); - void loadWorldBuf(uint32 factionId, uint32 classId, uint32 minLevel, uint32 maxLevel); + void loadWorldBuf(uint32 factionId, uint32 classId, uint32 specId, uint32 minLevel, uint32 maxLevel); static std::vector> ParseTempTalentsOrder(uint32 cls, std::string temp_talents_order); static std::vector> ParseTempPetTalentsOrder(uint32 spec, std::string temp_talents_order); }; diff --git a/src/Playerbots.cpp b/src/Playerbots.cpp index 94b6b806..5386d9b2 100644 --- a/src/Playerbots.cpp +++ b/src/Playerbots.cpp @@ -52,7 +52,7 @@ public: void OnDatabaseSelectIndexLogout(Player* player, uint32& statementIndex, uint32& statementParam) override { - statementIndex = CHAR_UPD_CHAR_ONLINE; + statementIndex = CHAR_UPD_CHAR_OFFLINE; statementParam = player->GetGUID().GetCounter(); } diff --git a/src/strategy/AiObjectContext.cpp b/src/strategy/AiObjectContext.cpp index fc791244..10e9e187 100644 --- a/src/strategy/AiObjectContext.cpp +++ b/src/strategy/AiObjectContext.cpp @@ -24,6 +24,8 @@ #include "raids/blackwinglair/RaidBwlTriggerContext.h" #include "raids/naxxramas/RaidNaxxActionContext.h" #include "raids/naxxramas/RaidNaxxTriggerContext.h" +#include "raids/obsidiansanctum/RaidOsActionContext.h" +#include "raids/obsidiansanctum/RaidOsTriggerContext.h" #include "raids/moltencore/RaidMcActionContext.h" #include "raids/moltencore/RaidMcTriggerContext.h" #include "raids/aq20/RaidAq20ActionContext.h" @@ -48,6 +50,7 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI) actionContexts.Add(new RaidBwlActionContext()); actionContexts.Add(new RaidAq20ActionContext()); actionContexts.Add(new RaidNaxxActionContext()); + actionContexts.Add(new RaidOsActionContext()); actionContexts.Add(new RaidUlduarActionContext()); actionContexts.Add(new RaidIccActionContext()); actionContexts.Add(new WotlkDungeonUKActionContext()); @@ -71,6 +74,7 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI) triggerContexts.Add(new RaidBwlTriggerContext()); triggerContexts.Add(new RaidAq20TriggerContext()); triggerContexts.Add(new RaidNaxxTriggerContext()); + triggerContexts.Add(new RaidOsTriggerContext()); triggerContexts.Add(new RaidUlduarTriggerContext()); triggerContexts.Add(new RaidIccTriggerContext()); triggerContexts.Add(new WotlkDungeonUKTriggerContext()); diff --git a/src/strategy/actions/ChooseTargetActions.cpp b/src/strategy/actions/ChooseTargetActions.cpp index c06f675d..9d94c2cf 100644 --- a/src/strategy/actions/ChooseTargetActions.cpp +++ b/src/strategy/actions/ChooseTargetActions.cpp @@ -10,8 +10,8 @@ #include "LootObjectStack.h" #include "Playerbots.h" #include "PossibleRpgTargetsValue.h" -#include "ServerFacade.h" #include "PvpTriggers.h" +#include "ServerFacade.h" bool AttackEnemyPlayerAction::isUseful() { @@ -129,3 +129,33 @@ bool DpsAssistAction::isUseful() return true; } + +bool AttackRtiTargetAction::Execute(Event event) +{ + Unit* rtiTarget = AI_VALUE(Unit*, "rti target"); + + if (rtiTarget && rtiTarget->IsInWorld() && rtiTarget->GetMapId() == bot->GetMapId()) + { + botAI->GetAiObjectContext()->GetValue("prioritized targets")->Set({rtiTarget->GetGUID()}); + bool result = Attack(botAI->GetUnit(rtiTarget->GetGUID())); + if (result) + { + context->GetValue("pull target")->Set(rtiTarget->GetGUID()); + return true; + } + } + else + { + botAI->TellError("I dont see my rti attack target"); + } + + return false; +} + +bool AttackRtiTargetAction::isUseful() +{ + if (botAI->ContainsStrategy(STRATEGY_TYPE_HEAL)) + return false; + + return true; +} diff --git a/src/strategy/actions/ChooseTargetActions.h b/src/strategy/actions/ChooseTargetActions.h index 6b801a72..38307e09 100644 --- a/src/strategy/actions/ChooseTargetActions.h +++ b/src/strategy/actions/ChooseTargetActions.h @@ -69,6 +69,8 @@ public: AttackRtiTargetAction(PlayerbotAI* botAI) : AttackAction(botAI, "attack rti target") {} std::string const GetTargetName() override { return "rti target"; } + bool Execute(Event event) override; + bool isUseful() override; }; class AttackEnemyFlagCarrierAction : public AttackAction diff --git a/src/strategy/actions/DropQuestAction.cpp b/src/strategy/actions/DropQuestAction.cpp index ffdbb942..eea9337f 100644 --- a/src/strategy/actions/DropQuestAction.cpp +++ b/src/strategy/actions/DropQuestAction.cpp @@ -68,6 +68,11 @@ bool CleanQuestLogAction::Execute(Event event) return false; } + if (!sPlayerbotAIConfig->dropObsoleteQuests) + { + return false; + } + // Only output this message if "debug rpg" strategy is enabled if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT)) { diff --git a/src/strategy/actions/MovementActions.cpp b/src/strategy/actions/MovementActions.cpp index f4e2be24..086afea3 100644 --- a/src/strategy/actions/MovementActions.cpp +++ b/src/strategy/actions/MovementActions.cpp @@ -935,10 +935,10 @@ bool MovementAction::IsMovingAllowed() if (botAI->IsInVehicle() && !botAI->IsInVehicle(true)) return false; - if (bot->isFrozen() || bot->IsPolymorphed() || (bot->isDead() && !bot->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST)) || - 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)) + if (bot->isFrozen() || bot->IsPolymorphed() || (bot->isDead() && !bot->HasPlayerFlag(PLAYER_FLAGS_GHOST)) || + bot->IsBeingTeleported() || bot->HasRootAura() || bot->HasSpiritOfRedemptionAura() || + bot->HasConfuseAura() || bot->IsCharmed() || bot->HasStunAura() || + bot->IsInFlight() || bot->HasUnitState(UNIT_STATE_LOST_CONTROL)) return false; if (bot->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_CONTROLLED) != NULL_MOTION_TYPE) @@ -2564,9 +2564,9 @@ bool SetFacingTargetAction::isUseful() { return !AI_VALUE2(bool, "facing", "curr bool SetFacingTargetAction::isPossible() { - if (bot->isFrozen() || bot->IsPolymorphed() || (bot->isDead() && !bot->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST)) || - bot->IsBeingTeleported() || bot->HasAuraType(SPELL_AURA_MOD_CONFUSE) || bot->IsCharmed() || - bot->HasAuraType(SPELL_AURA_MOD_STUN) || bot->HasUnitState(UNIT_STATE_IN_FLIGHT) || + if (bot->isFrozen() || bot->IsPolymorphed() || (bot->isDead() && !bot->HasPlayerFlag(PLAYER_FLAGS_GHOST)) || + bot->IsBeingTeleported() || bot->HasConfuseAura() || bot->IsCharmed() || + bot->HasStunAura() || bot->IsInFlight() || bot->HasUnitState(UNIT_STATE_LOST_CONTROL)) return false; diff --git a/src/strategy/actions/ReleaseSpiritAction.cpp b/src/strategy/actions/ReleaseSpiritAction.cpp index d15c7487..6f49574d 100644 --- a/src/strategy/actions/ReleaseSpiritAction.cpp +++ b/src/strategy/actions/ReleaseSpiritAction.cpp @@ -22,7 +22,7 @@ bool ReleaseSpiritAction::Execute(Event event) return false; } - if (bot->GetCorpse() && bot->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST)) + if (bot->GetCorpse() && bot->HasPlayerFlag(PLAYER_FLAGS_GHOST)) { botAI->TellMasterNoFacing("I am already a spirit"); return false; @@ -149,7 +149,7 @@ bool AutoReleaseSpiritAction::isUseful() if (bot->InBattleground()) return true; - if (bot->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST)) + if (bot->HasPlayerFlag(PLAYER_FLAGS_GHOST)) return false; if (!bot->GetGroup()) diff --git a/src/strategy/actions/ReviveFromCorpseAction.cpp b/src/strategy/actions/ReviveFromCorpseAction.cpp index 6cab4206..960f438f 100644 --- a/src/strategy/actions/ReviveFromCorpseAction.cpp +++ b/src/strategy/actions/ReviveFromCorpseAction.cpp @@ -360,4 +360,4 @@ bool SpiritHealerAction::Execute(Event event) return false; } -bool SpiritHealerAction::isUseful() { return bot->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST); } +bool SpiritHealerAction::isUseful() { return bot->HasPlayerFlag(PLAYER_FLAGS_GHOST); } diff --git a/src/strategy/actions/RtiAction.cpp b/src/strategy/actions/RtiAction.cpp index b28b9585..cf4a3d40 100644 --- a/src/strategy/actions/RtiAction.cpp +++ b/src/strategy/actions/RtiAction.cpp @@ -80,7 +80,7 @@ bool MarkRtiAction::Execute(Event event) for (uint8 i = 0; i < 8; i++) { ObjectGuid iconGUID = group->GetTargetIcon(i); - if (guid == unit->GetGUID()) + if (iconGUID == unit->GetGUID()) { marked = true; break; diff --git a/src/strategy/actions/WorldBuffAction.cpp b/src/strategy/actions/WorldBuffAction.cpp index 577731bc..a21dfc56 100644 --- a/src/strategy/actions/WorldBuffAction.cpp +++ b/src/strategy/actions/WorldBuffAction.cpp @@ -5,6 +5,7 @@ #include "WorldBuffAction.h" +#include "AiFactory.h" #include "Event.h" #include "Playerbots.h" @@ -39,6 +40,11 @@ std::vector WorldBuffAction::NeedWorldBuffs(Unit* unit) if (wb.classId != 0 && wb.classId != unit->getClass()) continue; + uint8 tab = AiFactory::GetPlayerSpecTab(unit->ToPlayer()); + + if (wb.specId != tab) + continue; + if (wb.minLevel != 0 && wb.minLevel > unit->GetLevel()) continue; diff --git a/src/strategy/raids/RaidStrategyContext.h b/src/strategy/raids/RaidStrategyContext.h index af30ff14..31675f7f 100644 --- a/src/strategy/raids/RaidStrategyContext.h +++ b/src/strategy/raids/RaidStrategyContext.h @@ -5,6 +5,7 @@ #include "Strategy.h" #include "RaidBwlStrategy.h" #include "RaidNaxxStrategy.h" +#include "RaidOsStrategy.h" #include "RaidMcStrategy.h" #include "RaidAq20Strategy.h" #include "RaidIccStrategy.h" @@ -21,6 +22,7 @@ public: creators["bwl"] = &RaidStrategyContext::bwl; creators["aq20"] = &RaidStrategyContext::aq20; creators["naxx"] = &RaidStrategyContext::naxx; + creators["wotlk-os"] = &RaidStrategyContext::wotlk_os; creators["uld"] = &RaidStrategyContext::uld; creators["icc"] = &RaidStrategyContext::icc; } @@ -30,6 +32,7 @@ private: static Strategy* bwl(PlayerbotAI* botAI) { return new RaidBwlStrategy(botAI); } static Strategy* aq20(PlayerbotAI* botAI) { return new RaidAq20Strategy(botAI); } static Strategy* naxx(PlayerbotAI* botAI) { return new RaidNaxxStrategy(botAI); } + static Strategy* wotlk_os(PlayerbotAI* botAI) { return new RaidOsStrategy(botAI); } static Strategy* uld(PlayerbotAI* botAI) { return new RaidUlduarStrategy(botAI); } static Strategy* icc(PlayerbotAI* botAI) { return new RaidIccStrategy(botAI); } }; diff --git a/src/strategy/raids/obsidiansanctum/RaidOsActionContext.h b/src/strategy/raids/obsidiansanctum/RaidOsActionContext.h new file mode 100644 index 00000000..55afe526 --- /dev/null +++ b/src/strategy/raids/obsidiansanctum/RaidOsActionContext.h @@ -0,0 +1,30 @@ +#ifndef _PLAYERBOT_RAIDOSACTIONCONTEXT_H +#define _PLAYERBOT_RAIDOSACTIONCONTEXT_H + +#include "Action.h" +#include "NamedObjectContext.h" +#include "RaidOsActions.h" + +class RaidOsActionContext : public NamedObjectContext +{ +public: + RaidOsActionContext() + { + creators["sartharion tank position"] = &RaidOsActionContext::tank_position; + creators["avoid twilight fissure"] = &RaidOsActionContext::avoid_twilight_fissure; + creators["avoid flame tsunami"] = &RaidOsActionContext::avoid_flame_tsunami; + creators["sartharion attack priority"] = &RaidOsActionContext::attack_priority; + creators["enter twilight portal"] = &RaidOsActionContext::enter_twilight_portal; + creators["exit twilight portal"] = &RaidOsActionContext::exit_twilight_portal; + } + +private: + static Action* tank_position(PlayerbotAI* ai) { return new SartharionTankPositionAction(ai); } + static Action* avoid_twilight_fissure(PlayerbotAI* ai) { return new AvoidTwilightFissureAction(ai); } + static Action* avoid_flame_tsunami(PlayerbotAI* ai) { return new AvoidFlameTsunamiAction(ai); } + static Action* attack_priority(PlayerbotAI* ai) { return new SartharionAttackPriorityAction(ai); } + static Action* enter_twilight_portal(PlayerbotAI* ai) { return new EnterTwilightPortalAction(ai); } + static Action* exit_twilight_portal(PlayerbotAI* ai) { return new ExitTwilightPortalAction(ai); } +}; + +#endif diff --git a/src/strategy/raids/obsidiansanctum/RaidOsActions.cpp b/src/strategy/raids/obsidiansanctum/RaidOsActions.cpp new file mode 100644 index 00000000..2ee68bca --- /dev/null +++ b/src/strategy/raids/obsidiansanctum/RaidOsActions.cpp @@ -0,0 +1,246 @@ +#include "RaidOsActions.h" +#include "RaidOsTriggers.h" + +#include "Playerbots.h" + +bool SartharionTankPositionAction::Execute(Event event) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "sartharion"); + if (!boss) { return false; } + + // Unit* shadron = AI_VALUE2(Unit*, "find target", "shadron"); + // Unit* tenebron = AI_VALUE2(Unit*, "find target", "tenebron"); + // Unit* vesperon = AI_VALUE2(Unit*, "find target", "vesperon"); + Unit* shadron = nullptr; + Unit* tenebron = nullptr; + Unit* vesperon = nullptr; + + // Detect incoming drakes before they are on aggro table + GuidVector targets = AI_VALUE(GuidVector, "possible targets no los"); + for (auto& target : targets) + { + Unit* unit = botAI->GetUnit(target); + if (!unit) { continue; } + + switch (unit->GetEntry()) + { + case NPC_SHADRON: + shadron = unit; + continue; + case NPC_TENEBRON: + tenebron = unit; + continue; + case NPC_VESPERON: + vesperon = unit; + continue; + default: + continue; + } + } + + Position currentPos = bot->GetPosition(); + // Adjustable, this is the acceptable distance to stack point that will be accepted as "safe" + float looseDistance = 12.0f; + + if (botAI->IsMainTank(bot)) + { + if (bot->GetExactDist2d(SARTHARION_MAINTANK_POSITION.first, SARTHARION_MAINTANK_POSITION.second) > looseDistance) + { + return MoveTo(OS_MAP_ID, SARTHARION_MAINTANK_POSITION.first, SARTHARION_MAINTANK_POSITION.second, currentPos.GetPositionZ(), + false, false, false, false, MovementPriority::MOVEMENT_COMBAT); + } + } + // Offtank grab drakes + else if (shadron || tenebron || vesperon) + { + float triggerDistance = 100.0f; + // Prioritise threat before positioning + if (tenebron && bot->GetExactDist2d(tenebron) < triggerDistance && + tenebron->GetTarget() != bot->GetGUID() && AI_VALUE(Unit*, "current target") != tenebron) + { + return Attack(tenebron); + } + if (shadron && bot->GetExactDist2d(shadron) < triggerDistance && + shadron->GetTarget() != bot->GetGUID() && AI_VALUE(Unit*, "current target") != shadron) + { + return Attack(shadron); + } + if (vesperon && bot->GetExactDist2d(vesperon) < triggerDistance && + vesperon->GetTarget() != bot->GetGUID() && AI_VALUE(Unit*, "current target") != vesperon) + { + return Attack(vesperon); + } + + bool drakeInCombat = (tenebron && bot->GetExactDist2d(tenebron) < triggerDistance) || + (shadron && bot->GetExactDist2d(shadron) < triggerDistance) || + (vesperon && bot->GetExactDist2d(vesperon) < triggerDistance); + // Offtank has threat on drakes, check positioning + if (drakeInCombat && bot->GetExactDist2d(SARTHARION_OFFTANK_POSITION.first, SARTHARION_OFFTANK_POSITION.second) > looseDistance) + { + return MoveTo(OS_MAP_ID, SARTHARION_OFFTANK_POSITION.first, SARTHARION_OFFTANK_POSITION.second, currentPos.GetPositionZ(), + false, false, false, false, MovementPriority::MOVEMENT_COMBAT); + } + } + return false; +} + +bool AvoidTwilightFissureAction::Execute(Event event) +{ + const float radius = 5.0f; + + GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + for (auto& npc : npcs) + { + Unit* unit = botAI->GetUnit(npc); + if (unit && unit->GetEntry() == NPC_TWILIGHT_FISSURE) + { + float currentDistance = bot->GetDistance2d(unit); + if (currentDistance < radius) + { + return MoveAway(unit, radius - currentDistance); + } + } + } + return false; +} + +bool AvoidFlameTsunamiAction::Execute(Event event) +{ + // Adjustable, this is the acceptable distance to stack point that will be accepted as "safe" + float looseDistance = 4.0f; + + GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + for (auto& npc : npcs) + { + Unit* unit = botAI->GetUnit(npc); + if (unit && unit->GetEntry() == NPC_FLAME_TSUNAMI) + { + Position currentPos = bot->GetPosition(); + + // I think these are centrepoints for the wave segments. Either way they uniquely identify the wave + // direction as they have different coords for the left and right waves + // int casting is not a mistake, need to avoid FP errors somehow. + // I always saw these accurate to around 6 decimal places, but if there are issues, + // can switch this to abs comparison of floats which would technically be more robust. + int posY = (int) unit->GetPositionY(); + if (posY == 505 || posY == 555) // RIGHT WAVE + { + bool wavePassed = currentPos.GetPositionX() > unit->GetPositionX(); + if (wavePassed) + { + return false; + } + + if (bot->GetExactDist2d(currentPos.GetPositionX(), TSUNAMI_RIGHT_SAFE_ALL) > looseDistance) + { + return MoveTo(OS_MAP_ID, currentPos.GetPositionX(), TSUNAMI_RIGHT_SAFE_ALL, currentPos.GetPositionZ(), + false, false, false, false, MovementPriority::MOVEMENT_COMBAT); + } + } + else // LEFT WAVE + { + bool wavePassed = currentPos.GetPositionX() < unit->GetPositionX(); + if (wavePassed) + { + return false; + } + + if (botAI->IsMelee(bot)) + { + if (bot->GetExactDist2d(currentPos.GetPositionX(), TSUNAMI_LEFT_SAFE_MELEE) > looseDistance) + { + return MoveTo(OS_MAP_ID, currentPos.GetPositionX(), TSUNAMI_LEFT_SAFE_MELEE, currentPos.GetPositionZ(), + false, false, false, false, MovementPriority::MOVEMENT_COMBAT); + } + } + else // Ranged/healers + { + if (bot->GetExactDist2d(currentPos.GetPositionX(), TSUNAMI_LEFT_SAFE_RANGED) > looseDistance) + { + return MoveTo(OS_MAP_ID, currentPos.GetPositionX(), TSUNAMI_LEFT_SAFE_RANGED, currentPos.GetPositionZ(), + false, false, false, false, MovementPriority::MOVEMENT_COMBAT); + } + } + } + } + } + return false; +} + +bool SartharionAttackPriorityAction::Execute(Event event) +{ + Unit* sartharion = AI_VALUE2(Unit*, "find target", "sartharion"); + Unit* shadron = AI_VALUE2(Unit*, "find target", "shadron"); + Unit* tenebron = AI_VALUE2(Unit*, "find target", "tenebron"); + Unit* vesperon = AI_VALUE2(Unit*, "find target", "vesperon"); + Unit* acolyte = AI_VALUE2(Unit*, "find target", "acolyte of shadron"); + + Unit* target = nullptr; + + if (acolyte) + { + target = acolyte; + } + else if (vesperon) + { + target = vesperon; + } + else if (tenebron) + { + target = tenebron; + } + else if (shadron) + { + target = shadron; + } + else if (sartharion) + { + target = sartharion; + } + + if (target && AI_VALUE(Unit*, "current target") != target) + { + return Attack(target); + } + + return false; +} + +bool EnterTwilightPortalAction::Execute(Event event) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "sartharion"); + if (!boss || !boss->HasAura(SPELL_GIFT_OF_TWILIGHT_FIRE)) { return false; } + + GameObject* portal = bot->FindNearestGameObject(GO_TWILIGHT_PORTAL, 100.0f); + if (!portal) { return false; } + + if (!portal->IsAtInteractDistance(bot)) + { + return MoveTo(portal, fmaxf(portal->GetInteractionDistance() - 1.0f, 0.0f)); + } + + // Go through portal + WorldPacket data1(CMSG_GAMEOBJ_USE); + data1 << portal->GetGUID(); + bot->GetSession()->HandleGameObjectUseOpcode(data1); + + return true; +} + +bool ExitTwilightPortalAction::Execute(Event event) +{ + GameObject* portal = bot->FindNearestGameObject(GO_NORMAL_PORTAL, 100.0f); + if (!portal) { return false; } + + if (!portal->IsAtInteractDistance(bot)) + { + return MoveTo(portal, fmaxf(portal->GetInteractionDistance() - 1.0f, 0.0f)); + } + + // Go through portal + WorldPacket data1(CMSG_GAMEOBJ_USE); + data1 << portal->GetGUID(); + bot->GetSession()->HandleGameObjectUseOpcode(data1); + + return true; +} diff --git a/src/strategy/raids/obsidiansanctum/RaidOsActions.h b/src/strategy/raids/obsidiansanctum/RaidOsActions.h new file mode 100644 index 00000000..eba7c11d --- /dev/null +++ b/src/strategy/raids/obsidiansanctum/RaidOsActions.h @@ -0,0 +1,64 @@ +#ifndef _PLAYERBOT_RAIDOSACTIONS_H +#define _PLAYERBOT_RAIDOSACTIONS_H + +#include "MovementActions.h" +#include "AttackAction.h" +#include "PlayerbotAI.h" +#include "Playerbots.h" + +const float TSUNAMI_LEFT_SAFE_MELEE = 552.0f; +const float TSUNAMI_LEFT_SAFE_RANGED = 504.0f; +const float TSUNAMI_RIGHT_SAFE_ALL = 529.0f; +const std::pair SARTHARION_MAINTANK_POSITION = {3258.5f, 532.5f}; +const std::pair SARTHARION_OFFTANK_POSITION = {3230.0f, 526.0f}; +const std::pair SARTHARION_RANGED_POSITION = {3248.0f, 507.0f}; + +class SartharionTankPositionAction : public AttackAction +{ +public: + SartharionTankPositionAction(PlayerbotAI* botAI, std::string const name = "sartharion tank position") + : AttackAction(botAI, name) {} + bool Execute(Event event) override; +}; + +class AvoidTwilightFissureAction : public MovementAction +{ +public: + AvoidTwilightFissureAction(PlayerbotAI* botAI, std::string const name = "avoid twilight fissure") + : MovementAction(botAI, name) {} + bool Execute(Event event) override; +}; + +class AvoidFlameTsunamiAction : public MovementAction +{ +public: + AvoidFlameTsunamiAction(PlayerbotAI* botAI, std::string const name = "avoid flame tsunami") + : MovementAction(botAI, name) {} + bool Execute(Event event) override; +}; + +class SartharionAttackPriorityAction : public AttackAction +{ +public: + SartharionAttackPriorityAction(PlayerbotAI* botAI, std::string const name = "sartharion attack priority") + : AttackAction(botAI, name) {} + bool Execute(Event event) override; +}; + +class EnterTwilightPortalAction : public MovementAction +{ +public: + EnterTwilightPortalAction(PlayerbotAI* botAI, std::string const name = "enter twilight portal") + : MovementAction(botAI, name) {} + bool Execute(Event event) override; +}; + +class ExitTwilightPortalAction : public MovementAction +{ +public: + ExitTwilightPortalAction(PlayerbotAI* botAI, std::string const name = "exit twilight portal") + : MovementAction(botAI, name) {} + bool Execute(Event event) override; +}; + +#endif diff --git a/src/strategy/raids/obsidiansanctum/RaidOsMultipliers.cpp b/src/strategy/raids/obsidiansanctum/RaidOsMultipliers.cpp new file mode 100644 index 00000000..bc63f967 --- /dev/null +++ b/src/strategy/raids/obsidiansanctum/RaidOsMultipliers.cpp @@ -0,0 +1,49 @@ +#include "RaidOsMultipliers.h" + +#include "ChooseTargetActions.h" +#include "DKActions.h" +#include "DruidActions.h" +#include "DruidBearActions.h" +#include "FollowActions.h" +#include "GenericActions.h" +#include "GenericSpellActions.h" +#include "MovementActions.h" +#include "PaladinActions.h" +#include "RaidOsActions.h" +#include "RaidOsTriggers.h" +#include "ReachTargetActions.h" +#include "ScriptedCreature.h" +#include "WarriorActions.h" + +float SartharionMultiplier::GetValue(Action* action) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "sartharion"); + if (!boss) { return 1.0f; } + + Unit* target = action->GetTarget(); + + if (botAI->IsMainTank(bot) && dynamic_cast(action)) + { + // return 0.0f; + } + + if (botAI->IsDps(bot) && dynamic_cast(action)) + { + return 0.0f; + } + + if (botAI->IsMainTank(bot) && target && target != boss && + (dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action))) + { + return 0.0f; + } + + if (botAI->IsAssistTank(bot) && target && target == boss && + (dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action))) + { + return 0.0f; + } + return 1.0f; +} diff --git a/src/strategy/raids/obsidiansanctum/RaidOsMultipliers.h b/src/strategy/raids/obsidiansanctum/RaidOsMultipliers.h new file mode 100644 index 00000000..802335e1 --- /dev/null +++ b/src/strategy/raids/obsidiansanctum/RaidOsMultipliers.h @@ -0,0 +1,16 @@ + +#ifndef _PLAYERRBOT_RAIDOSMULTIPLIERS_H +#define _PLAYERRBOT_RAIDOSMULTIPLIERS_H + +#include "Multiplier.h" + +class SartharionMultiplier : public Multiplier +{ +public: + SartharionMultiplier(PlayerbotAI* ai) : Multiplier(ai, "sartharion") {} + +public: + virtual float GetValue(Action* action); +}; + +#endif diff --git a/src/strategy/raids/obsidiansanctum/RaidOsStrategy.cpp b/src/strategy/raids/obsidiansanctum/RaidOsStrategy.cpp new file mode 100644 index 00000000..508f7609 --- /dev/null +++ b/src/strategy/raids/obsidiansanctum/RaidOsStrategy.cpp @@ -0,0 +1,32 @@ +#include "RaidOsStrategy.h" +#include "RaidOsMultipliers.h" +#include "Strategy.h" + +void RaidOsStrategy::InitTriggers(std::vector& triggers) +{ + triggers.push_back( + new TriggerNode("sartharion tank", + NextAction::array(0, new NextAction("sartharion tank position", ACTION_MOVE), nullptr))); + triggers.push_back( + new TriggerNode("twilight fissure", + NextAction::array(0, new NextAction("avoid twilight fissure", ACTION_RAID + 2), nullptr))); + triggers.push_back( + new TriggerNode("flame tsunami", + NextAction::array(0, new NextAction("avoid flame tsunami", ACTION_RAID + 1), nullptr))); + triggers.push_back( + new TriggerNode("sartharion dps", + NextAction::array(0, new NextAction("sartharion attack priority", ACTION_RAID), nullptr))); + // Flank dragon positioning + triggers.push_back(new TriggerNode("sartharion melee positioning", + NextAction::array(0, new NextAction("rear flank", ACTION_MOVE + 4), nullptr))); + + triggers.push_back(new TriggerNode("twilight portal enter", + NextAction::array(0, new NextAction("enter twilight portal", ACTION_RAID + 1), nullptr))); + triggers.push_back(new TriggerNode("twilight portal exit", + NextAction::array(0, new NextAction("exit twilight portal", ACTION_RAID + 1), nullptr))); +} + +void RaidOsStrategy::InitMultipliers(std::vector &multipliers) +{ + multipliers.push_back(new SartharionMultiplier(botAI)); +} diff --git a/src/strategy/raids/obsidiansanctum/RaidOsStrategy.h b/src/strategy/raids/obsidiansanctum/RaidOsStrategy.h new file mode 100644 index 00000000..44983f1f --- /dev/null +++ b/src/strategy/raids/obsidiansanctum/RaidOsStrategy.h @@ -0,0 +1,17 @@ +#ifndef _PLAYERBOT_RAIDOSSTRATEGY_H +#define _PLAYERBOT_RAIDOSSTRATEGY_H + +#include "AiObjectContext.h" +#include "Multiplier.h" +#include "Strategy.h" + +class RaidOsStrategy : public Strategy +{ +public: + RaidOsStrategy(PlayerbotAI* ai) : Strategy(ai) {} + virtual std::string const getName() override { return "wotlk-os"; } + virtual void InitTriggers(std::vector &triggers) override; + virtual void InitMultipliers(std::vector &multipliers) override; +}; + +#endif diff --git a/src/strategy/raids/obsidiansanctum/RaidOsTriggerContext.h b/src/strategy/raids/obsidiansanctum/RaidOsTriggerContext.h new file mode 100644 index 00000000..b8a1f4b3 --- /dev/null +++ b/src/strategy/raids/obsidiansanctum/RaidOsTriggerContext.h @@ -0,0 +1,32 @@ +#ifndef _PLAYERBOT_RAIDOSTRIGGERCONTEXT_H +#define _PLAYERBOT_RAIDOSTRIGGERCONTEXT_H + +#include "AiObjectContext.h" +#include "NamedObjectContext.h" +#include "RaidOsTriggers.h" + +class RaidOsTriggerContext : public NamedObjectContext +{ +public: + RaidOsTriggerContext() + { + creators["sartharion tank"] = &RaidOsTriggerContext::sartharion_tank; + creators["flame tsunami"] = &RaidOsTriggerContext::flame_tsunami; + creators["twilight fissure"] = &RaidOsTriggerContext::twilight_fissure; + creators["sartharion dps"] = &RaidOsTriggerContext::sartharion_dps; + creators["sartharion melee positioning"] = &RaidOsTriggerContext::sartharion_melee; + creators["twilight portal enter"] = &RaidOsTriggerContext::twilight_portal_enter; + creators["twilight portal exit"] = &RaidOsTriggerContext::twilight_portal_exit; + } + +private: + static Trigger* sartharion_tank(PlayerbotAI* ai) { return new SartharionTankTrigger(ai); } + static Trigger* flame_tsunami(PlayerbotAI* ai) { return new FlameTsunamiTrigger(ai); } + static Trigger* twilight_fissure(PlayerbotAI* ai) { return new TwilightFissureTrigger(ai); } + static Trigger* sartharion_dps(PlayerbotAI* ai) { return new SartharionDpsTrigger(ai); } + static Trigger* sartharion_melee(PlayerbotAI* ai) { return new SartharionMeleePositioningTrigger(ai); } + static Trigger* twilight_portal_enter(PlayerbotAI* ai) { return new TwilightPortalEnterTrigger(ai); } + static Trigger* twilight_portal_exit(PlayerbotAI* ai) { return new TwilightPortalExitTrigger(ai); } +}; + +#endif diff --git a/src/strategy/raids/obsidiansanctum/RaidOsTriggers.cpp b/src/strategy/raids/obsidiansanctum/RaidOsTriggers.cpp new file mode 100644 index 00000000..45b3b12f --- /dev/null +++ b/src/strategy/raids/obsidiansanctum/RaidOsTriggers.cpp @@ -0,0 +1,128 @@ +#include "RaidOsTriggers.h" + +#include "SharedDefines.h" + +bool SartharionTankTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "sartharion"); + if (!boss) { return false; } + + return botAI->IsTank(bot); +} + +bool FlameTsunamiTrigger::IsActive() +{ + if (botAI->IsTank(bot)) { return false; } + + Unit* boss = AI_VALUE2(Unit*, "find target", "sartharion"); + if (!boss) { return false; } + + + GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + for (auto& npc : npcs) + { + Unit* unit = botAI->GetUnit(npc); + if (unit) + { + if (unit->GetEntry() == NPC_FLAME_TSUNAMI) + { + return true; + } + } + } + + return false; +} + +bool TwilightFissureTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "sartharion"); + if (!boss) { return false; } + + + GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + for (auto& npc : npcs) + { + Unit* unit = botAI->GetUnit(npc); + if (unit) + { + if (unit->GetEntry() == NPC_TWILIGHT_FISSURE) + { + return true; + } + } + } + + return false; +} + +bool SartharionDpsTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "sartharion"); + if (!boss) { return false; } + + return botAI->IsDps(bot); +} + +bool SartharionMeleePositioningTrigger::IsActive() +{ + if (!botAI->IsMelee(bot) || !botAI->IsDps(bot)) { return false; } + + Unit* boss = AI_VALUE2(Unit*, "find target", "sartharion"); + if (!boss) { return false; } + + Unit* shadron = AI_VALUE2(Unit*, "find target", "shadron"); + Unit* tenebron = AI_VALUE2(Unit*, "find target", "tenebron"); + Unit* vesperon = AI_VALUE2(Unit*, "find target", "vesperon"); + + return !(shadron || tenebron || vesperon); +} + +bool TwilightPortalEnterTrigger::IsActive() +{ + if (botAI->IsMainTank(bot) || botAI->IsHealAssistantOfIndex(bot, 0)) { return false; } + + // In 25-man, take two healers in. Otherwise just take one + // if (bot->GetRaidDifficulty() == RAID_DIFFICULTY_25MAN_NORMAL) + // { + // if (botAI->IsHealAssistantOfIndex(bot, 0) || botAI->IsHealAssistantOfIndex(bot, 1)) + // { + // return false; + // } + // } + // else + // { + // if (botAI->IsHealAssistantOfIndex(bot, 0)) + // { + // return false; + // } + // } + + + // Don't enter portal until drakes are dead + if (bot->HasAura(SPELL_POWER_OF_SHADRON) || + bot->HasAura(SPELL_POWER_OF_TENEBRON) || + bot->HasAura(SPELL_POWER_OF_VESPERON)) + { + return false; + } + + Unit* boss = AI_VALUE2(Unit*, "find target", "sartharion"); + if (!boss) { return false; } + + // GuidVector objects = AI_VALUE(GuidVector, "nearest game objects no los"); + // for (auto& object : objects) + // { + // GameObject* go = botAI->GetGameObject(object); + // if (go && go->GetEntry() == GO_TWILIGHT_PORTAL) + // { + // return true; + // } + // } + return bool(bot->FindNearestGameObject(GO_TWILIGHT_PORTAL, 100.0f)); +} + +bool TwilightPortalExitTrigger::IsActive() +{ + return bot->HasAura(SPELL_TWILIGHT_SHIFT) && !AI_VALUE2(Unit*, "find target", "acolyte of shadron"); +} \ No newline at end of file diff --git a/src/strategy/raids/obsidiansanctum/RaidOsTriggers.h b/src/strategy/raids/obsidiansanctum/RaidOsTriggers.h new file mode 100644 index 00000000..b7a45703 --- /dev/null +++ b/src/strategy/raids/obsidiansanctum/RaidOsTriggers.h @@ -0,0 +1,120 @@ +#ifndef _PLAYERBOT_RAIDOSTRIGGERS_H +#define _PLAYERBOT_RAIDOSTRIGGERS_H + +#include "PlayerbotAI.h" +#include "Playerbots.h" +#include "Trigger.h" + +enum ObsidianSanctumIDs +{ + // Bosses + NPC_SARTHARION = 28860, + NPC_SHADRON = 30451, + NPC_TENEBRON = 30452, + NPC_VESPERON = 30449, + + // Mini-boss shared + SPELL_SHADOW_BREATH = 57570, + SPELL_SHADOW_FISSURE = 57579, + SPELL_SUMMON_TWILIGHT_WHELP = 58035, + SPELL_GIFT_OF_TWILIGHT_SHADOW = 57835, + SPELL_TWILIGHT_TORMENT_VESPERON = 57935, + + // Sartharion + SPELL_SARTHARION_CLEAVE = 56909, + SPELL_SARTHARION_FLAME_BREATH = 56908, + SPELL_SARTHARION_TAIL_LASH = 56910, + SPELL_CYCLONE_AURA_PERIODIC = 57598, + SPELL_LAVA_STRIKE_DUMMY = 57578, + SPELL_LAVA_STRIKE_DUMMY_TRIGGER = 57697, + SPELL_LAVA_STRIKE_SUMMON = 57572, + SPELL_SARTHARION_PYROBUFFET = 56916, + SPELL_SARTHARION_BERSERK = 61632, + SPELL_SARTHARION_TWILIGHT_REVENGE = 60639, + + // Sartharion with drakes + SPELL_WILL_OF_SARTHARION = 61254, + SPELL_POWER_OF_TENEBRON = 61248, + SPELL_POWER_OF_VESPERON = 61251, + SPELL_POWER_OF_SHADRON = 58105, + SPELL_GIFT_OF_TWILIGHT_FIRE = 58766, + + // Visuals + SPELL_EGG_MARKER_VISUAL = 58547, + SPELL_FLAME_TSUNAMI_VISUAL = 57494, + + // Misc + SPELL_FADE_ARMOR = 60708, + SPELL_FLAME_TSUNAMI_DAMAGE_AURA = 57492, + SPELL_FLAME_TSUNAMI_LEAP = 60241, + SPELL_SARTHARION_PYROBUFFET_TRIGGER = 57557, + + NPC_TWILIGHT_EGG = 30882, + NPC_TWILIGHT_WHELP = 30890, + NPC_DISCIPLE_OF_SHADRON = 30688, + NPC_DISCIPLE_OF_VESPERON = 30858, + NPC_ACOLYTE_OF_SHADRON = 31218, + NPC_ACOLYTE_OF_VESPERON = 31219, + + // Sartharion fight + NPC_LAVA_BLAZE = 30643, + NPC_FLAME_TSUNAMI = 30616, + NPC_SAFE_AREA_TRIGGER = 30494, + NPC_TWILIGHT_FISSURE = 30641, + GO_TWILIGHT_PORTAL = 193988, + GO_NORMAL_PORTAL = 193989, + SPELL_TWILIGHT_SHIFT = 57874, +}; + +const uint32 OS_MAP_ID = 615; + +class SartharionTankTrigger : public Trigger +{ +public: + SartharionTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "sartharion tank") {} + bool IsActive() override; +}; + +class FlameTsunamiTrigger : public Trigger +{ +public: + FlameTsunamiTrigger(PlayerbotAI* botAI) : Trigger(botAI, "flame tsunami") {} + bool IsActive() override; +}; + +class TwilightFissureTrigger : public Trigger +{ +public: + TwilightFissureTrigger(PlayerbotAI* botAI) : Trigger(botAI, "twilight fissure") {} + bool IsActive() override; +}; + +class SartharionDpsTrigger : public Trigger +{ +public: + SartharionDpsTrigger(PlayerbotAI* botAI) : Trigger(botAI, "sartharion dps") {} + bool IsActive() override; +}; + +class SartharionMeleePositioningTrigger : public Trigger +{ +public: + SartharionMeleePositioningTrigger(PlayerbotAI* botAI) : Trigger(botAI, "sartharion melee positioning") {} + bool IsActive() override; +}; + +class TwilightPortalEnterTrigger : public Trigger +{ +public: + TwilightPortalEnterTrigger(PlayerbotAI* botAI) : Trigger(botAI, "twilight portal enter") {} + bool IsActive() override; +}; + +class TwilightPortalExitTrigger : public Trigger +{ +public: + TwilightPortalExitTrigger(PlayerbotAI* botAI) : Trigger(botAI, "twilight portal exit") {} + bool IsActive() override; +}; + +#endif diff --git a/src/strategy/triggers/CureTriggers.cpp b/src/strategy/triggers/CureTriggers.cpp index abd23022..60aa29f6 100644 --- a/src/strategy/triggers/CureTriggers.cpp +++ b/src/strategy/triggers/CureTriggers.cpp @@ -25,4 +25,8 @@ bool PartyMemberNeedCureTrigger::IsActive() return target && target->IsInWorld(); } -bool NeedWorldBuffTrigger::IsActive() { return !WorldBuffAction::NeedWorldBuffs(bot).empty(); } +bool NeedWorldBuffTrigger::IsActive() +{ + std::any_of(WorldBuffAction::NeedWorldBuffs(bot).begin(), WorldBuffAction::NeedWorldBuffs(bot).end(), + [](const auto& wb) { return true; }); +} diff --git a/src/strategy/values/AttackersValue.cpp b/src/strategy/values/AttackersValue.cpp index b7d5c91f..f94823a9 100644 --- a/src/strategy/values/AttackersValue.cpp +++ b/src/strategy/values/AttackersValue.cpp @@ -175,7 +175,7 @@ bool AttackersValue::IsPossibleTarget(Unit* attacker, Player* bot, float range) // !((attacker->IsPolymorphed() || botAI->HasAura("sap", attacker) || /*attacker->IsCharmed() ||*/ // attacker->isFeared()) && !rti) && /*!sServerFacade->IsInRoots(attacker) &&*/ - !attacker->IsFriendlyTo(bot) && !attacker->HasAuraType(SPELL_AURA_SPIRIT_OF_REDEMPTION) && + !attacker->IsFriendlyTo(bot) && !attacker->HasSpiritOfRedemptionAura() && // !(attacker->GetGUID().IsPet() && enemy) && !(attacker->GetCreatureType() == CREATURE_TYPE_CRITTER && !attacker->IsInCombat()) && !attacker->HasUnitFlag(UNIT_FLAG_IMMUNE_TO_PC) && !attacker->HasUnitFlag(UNIT_FLAG_NOT_SELECTABLE) && diff --git a/src/strategy/values/EnemyPlayerValue.cpp b/src/strategy/values/EnemyPlayerValue.cpp index 5302bde2..3bc1b28b 100644 --- a/src/strategy/values/EnemyPlayerValue.cpp +++ b/src/strategy/values/EnemyPlayerValue.cpp @@ -18,7 +18,7 @@ bool NearestEnemyPlayersValue::AcceptUnit(Unit* unit) !enemy->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NON_ATTACKABLE_2) && ((inCannon || !enemy->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE))) && /*!enemy->HasStealthAura() && !enemy->HasInvisibilityAura()*/ enemy->CanSeeOrDetect(bot) && - !(enemy->HasAuraType(SPELL_AURA_SPIRIT_OF_REDEMPTION))) + !(enemy->HasSpiritOfRedemptionAura())) return true; return false; diff --git a/src/strategy/values/InvalidTargetValue.cpp b/src/strategy/values/InvalidTargetValue.cpp index 63bf10c3..b6f943a1 100644 --- a/src/strategy/values/InvalidTargetValue.cpp +++ b/src/strategy/values/InvalidTargetValue.cpp @@ -21,7 +21,7 @@ bool InvalidTargetValue::Calculate() return target->GetMapId() != bot->GetMapId() || target->HasUnitFlag(UNIT_FLAG_NOT_SELECTABLE) || target->HasUnitFlag(UNIT_FLAG_NON_ATTACKABLE) || target->HasUnitFlag(UNIT_FLAG_NON_ATTACKABLE_2) || !target->IsVisible() || !target->IsAlive() || target->IsPolymorphed() || target->IsCharmed() || - target->isFeared() || target->HasUnitState(UNIT_STATE_ISOLATED) || target->IsFriendlyTo(bot) || + target->HasFearAura() || target->HasUnitState(UNIT_STATE_ISOLATED) || target->IsFriendlyTo(bot) || !AttackersValue::IsValidTarget(target, bot); } diff --git a/src/strategy/values/RtiTargetValue.cpp b/src/strategy/values/RtiTargetValue.cpp index 99279f9d..6ce8d1d0 100644 --- a/src/strategy/values/RtiTargetValue.cpp +++ b/src/strategy/values/RtiTargetValue.cpp @@ -48,9 +48,6 @@ Unit* RtiTargetValue::Calculate() if (!guid) return nullptr; - if (!bot->IsInCombat()) - return nullptr; - //////////////////////////////////////////////////////begin: delete below check // Some units that need to be killed in battle are not on the list of attackers, // such as the Kor'kron Battle-Mage in Icecrown Citadel. @@ -62,7 +59,7 @@ Unit* RtiTargetValue::Calculate() //////////////////////////////////////////////////////end: delete below check Unit* unit = botAI->GetUnit(guid); - if (!unit || unit->isDead() || !bot->IsWithinLOSInMap(unit) || !AttackersValue::IsValidTarget(unit, bot) || + if (!unit || unit->isDead() || !bot->IsWithinLOSInMap(unit) || !AttackersValue::IsValidTarget(unit, bot) || sServerFacade->IsDistanceGreaterThan(sServerFacade->GetDistance2d(bot, unit), sPlayerbotAIConfig->sightDistance)) return nullptr; diff --git a/src/strategy/warlock/WarlockActions.cpp b/src/strategy/warlock/WarlockActions.cpp index 005e268e..627b042a 100644 --- a/src/strategy/warlock/WarlockActions.cpp +++ b/src/strategy/warlock/WarlockActions.cpp @@ -23,3 +23,5 @@ bool CastFearOnCcAction::isPossible() { return botAI->CanCastSpell("fear", GetTa bool CastFearOnCcAction::isUseful() { return true; } bool CastLifeTapAction::isUseful() { return AI_VALUE2(uint8, "health", "self target") > sPlayerbotAIConfig->lowHealth; } + +Unit* UseSoulstoneAction::GetTarget() { return botAI->GetMaster(); } diff --git a/src/strategy/warlock/WarlockActions.h b/src/strategy/warlock/WarlockActions.h index 8f41d0b1..4b29c59b 100644 --- a/src/strategy/warlock/WarlockActions.h +++ b/src/strategy/warlock/WarlockActions.h @@ -7,6 +7,7 @@ #define _PLAYERBOT_WARLOCKACTIONS_H #include "GenericSpellActions.h" +#include "UseItemAction.h" class PlayerbotAI; class Unit; @@ -302,4 +303,12 @@ class CastIncinerateAction : public CastSpellAction public: CastIncinerateAction(PlayerbotAI* ai) : CastSpellAction(ai, "incinerate") {} }; + +class UseSoulstoneAction : public UseSpellItemAction +{ +public: + UseSoulstoneAction(PlayerbotAI* ai) : UseSpellItemAction(ai, "soulstone") {} + + Unit* GetTarget() override; +}; #endif diff --git a/src/strategy/warlock/WarlockAiObjectContext.cpp b/src/strategy/warlock/WarlockAiObjectContext.cpp index 81f888f3..909e874d 100644 --- a/src/strategy/warlock/WarlockAiObjectContext.cpp +++ b/src/strategy/warlock/WarlockAiObjectContext.cpp @@ -184,6 +184,7 @@ public: creators["metamorphosis"] = &WarlockAiObjectContextInternal::metamorphosis; creators["soul fire"] = &WarlockAiObjectContextInternal::soul_fire; creators["incinerate"] = &WarlockAiObjectContextInternal::incinerate; + creators["soulstone"] = &WarlockAiObjectContextInternal::soulstone; } private: @@ -239,6 +240,7 @@ private: static Action* metamorphosis(PlayerbotAI* ai) { return new CastMetamorphosisAction(ai); } static Action* soul_fire(PlayerbotAI* ai) { return new CastSoulFireAction(ai); } static Action* incinerate(PlayerbotAI* ai) { return new CastIncinerateAction(ai); } + static Action* soulstone(PlayerbotAI* ai) { return new UseSoulstoneAction(ai); } }; WarlockAiObjectContext::WarlockAiObjectContext(PlayerbotAI* botAI) : AiObjectContext(botAI)