diff --git a/modules/mod-playerbots/src/strategy/actions/CheckMountStateAction.cpp b/modules/mod-playerbots/src/strategy/actions/CheckMountStateAction.cpp index c054d61..b83018c 100644 --- a/modules/mod-playerbots/src/strategy/actions/CheckMountStateAction.cpp +++ b/modules/mod-playerbots/src/strategy/actions/CheckMountStateAction.cpp @@ -237,6 +237,13 @@ bool CheckMountStateAction::TryForms(Player* master, int32 masterMountType, int3 if (!master) return false; + // If both master and bot are in matching forms or master is mounted with corresponding speed, nothing to do + else if + ((masterInShapeshiftForm == FORM_TRAVEL && botInShapeshiftForm == FORM_TRAVEL) || + ((masterInShapeshiftForm == FORM_FLIGHT || (masterMountType == 1 && masterSpeed == 149)) && botInShapeshiftForm == FORM_FLIGHT) || + ((masterInShapeshiftForm == FORM_FLIGHT_EPIC || (masterMountType == 1 && masterSpeed == 279)) && botInShapeshiftForm == FORM_FLIGHT_EPIC)) + return true; + // Check if master is in Travel Form and bot can do the same if (botAI->CanCastSpell(SPELL_TRAVEL_FORM, bot, true) && masterInShapeshiftForm == FORM_TRAVEL && botInShapeshiftForm != FORM_TRAVEL) diff --git a/modules/mod-playerbots/src/strategy/actions/OpenItemAction.cpp b/modules/mod-playerbots/src/strategy/actions/OpenItemAction.cpp index ff8407c..d1ebcba 100644 --- a/modules/mod-playerbots/src/strategy/actions/OpenItemAction.cpp +++ b/modules/mod-playerbots/src/strategy/actions/OpenItemAction.cpp @@ -4,6 +4,9 @@ #include "WorldPacket.h" #include "Player.h" #include "ObjectMgr.h" +#include "LootObjectStack.h" +#include "AiObjectContext.h" + bool OpenItemAction::Execute(Event event) { bool foundOpenable = false; @@ -27,6 +30,11 @@ void OpenItemAction::OpenItem(Item* item, uint8 bag, uint8 slot) packet << bag << slot; bot->GetSession()->HandleOpenItemOpcode(packet); + // Store the item GUID as the loot target + LootObject lootObject; + lootObject.guid = item->GetGUID(); + botAI->GetAiObjectContext()->GetValue("loot target")->Set(lootObject); + std::ostringstream out; out << "Opened item: " << item->GetTemplate()->Name1; botAI->TellMaster(out.str()); diff --git a/modules/mod-playerbots/src/strategy/actions/SayAction.cpp b/modules/mod-playerbots/src/strategy/actions/SayAction.cpp index 93d36a0..f6a6c31 100644 --- a/modules/mod-playerbots/src/strategy/actions/SayAction.cpp +++ b/modules/mod-playerbots/src/strategy/actions/SayAction.cpp @@ -969,7 +969,7 @@ std::string ChatReplyAction::GenerateReplyMessage(Player* bot, std::string& inco msg = "ya %s but thats in the past"; break; case 2: - msg = word[verb_pos - 1] + " will " + word[verb_pos + 1] + " again though %s"; + msg = word[verb_pos ? verb_pos - 1 : verb_pos + 1] + " will " + word[verb_pos + 1] + " again though %s"; break; } msg = std::regex_replace(msg, std::regex("%s"), name); @@ -1013,7 +1013,7 @@ std::string ChatReplyAction::GenerateReplyMessage(Player* bot, std::string& inco msg = "%s, what will happen %s?"; break; case 2: - msg = "are you saying " + word[verb_pos - 1] + " will " + word[verb_pos + 1] + " " + word[verb_pos + 2] + " %s?"; + msg = "are you saying " + word[verb_pos ? verb_pos - 1 : verb_pos + 1] + " will " + word[verb_pos + 1] + " " + word[verb_pos + 2] + " %s?"; break; } msg = std::regex_replace(msg, std::regex("%s"), name); diff --git a/modules/mod-playerbots/src/strategy/mage/MageActions.cpp b/modules/mod-playerbots/src/strategy/mage/MageActions.cpp index 1060319..b32c77f 100644 --- a/modules/mod-playerbots/src/strategy/mage/MageActions.cpp +++ b/modules/mod-playerbots/src/strategy/mage/MageActions.cpp @@ -16,7 +16,10 @@ Value* CastPolymorphAction::GetTargetValue() { return context->GetValueToCreature() && target->ToCreature()->HasMechanicTemplateImmunity(1 << (MECHANIC_FREEZE - 1))) + if (!target || !target->IsInWorld()) + return false; + + if (target->ToCreature() && target->ToCreature()->HasMechanicTemplateImmunity(1 << (MECHANIC_FREEZE - 1))) return false; if (target->isFrozen()) diff --git a/modules/mod-playerbots/src/strategy/raids/eyeofeternity/RaidEoEActions.cpp b/modules/mod-playerbots/src/strategy/raids/eyeofeternity/RaidEoEActions.cpp index 001733d..6582aac 100644 --- a/modules/mod-playerbots/src/strategy/raids/eyeofeternity/RaidEoEActions.cpp +++ b/modules/mod-playerbots/src/strategy/raids/eyeofeternity/RaidEoEActions.cpp @@ -1,9 +1,7 @@ +#include "Playerbots.h" #include "RaidEoEActions.h" #include "RaidEoETriggers.h" -#include "Playerbots.h" - - bool MalygosPositionAction::Execute(Event event) { Unit* boss = AI_VALUE2(Unit*, "find target", "malygos"); @@ -280,10 +278,14 @@ bool EoEDrakeAttackAction::isPossible() Unit* vehicleBase = bot->GetVehicleBase(); return (vehicleBase && vehicleBase->GetEntry() == NPC_WYRMREST_SKYTALON); } + bool EoEDrakeAttackAction::Execute(Event event) { vehicleBase = bot->GetVehicleBase(); - if (!vehicleBase) { return false; } + if (!vehicleBase) + { + return false; + } // Unit* target = AI_VALUE(Unit*, "current target"); Unit* boss = AI_VALUE2(Unit*, "find target", "malygos"); @@ -295,22 +297,55 @@ bool EoEDrakeAttackAction::Execute(Event event) for (auto& npc : npcs) { Unit* unit = botAI->GetUnit(npc); - if (!unit || unit->GetEntry() != NPC_MALYGOS) { continue; } + if (!unit || unit->GetEntry() != NPC_MALYGOS) + { + continue; + } boss = unit; break; } } // Check this again to see if a target was assigned - if (!boss) { return false; } - - if (botAI->IsHeal(bot)) + if (!boss) { - return DrakeHealAction(); + return false; + } + + uint8 numHealers; + bot->GetRaidDifficulty() == RAID_DIFFICULTY_25MAN_NORMAL ? numHealers = 10 : numHealers = 4; + + Group* group = bot->GetGroup(); + if (!group) + return false; + std::vector> sortedMembers; + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* member = itr->GetSource(); + sortedMembers.push_back(std::make_pair(member->GetGUID(), member)); + } + std::sort(sortedMembers.begin(), sortedMembers.end()); + + int botIndex = -1; + for (size_t i = 0; i < sortedMembers.size(); ++i) + { + if (sortedMembers[i].first == bot->GetGUID()) + { + botIndex = i; + break; + } + } + + if (botIndex == -1) + return false; + + if (botIndex > numHealers) + { + return DrakeDpsAction(boss); } else { - return DrakeDpsAction(boss); + return DrakeHealAction(); } return false; @@ -348,37 +383,15 @@ bool EoEDrakeAttackAction::DrakeDpsAction(Unit* target) bool EoEDrakeAttackAction::DrakeHealAction() { Unit* vehicleBase = bot->GetVehicleBase(); - if (!vehicleBase) { return false; } - - Unit* target = vehicleBase->GetComboTarget(); - if (!target) + if (!vehicleBase) { - // Unit* newTarget = nullptr; - Unit* newTarget = vehicleBase; - GuidVector members = AI_VALUE(GuidVector, "group members"); - for (auto& member : members) - { - Unit* unit = botAI->GetUnit(member); - if (!unit) - { - continue; - } - - Unit* drake = unit->GetVehicleBase(); - if (!drake || drake->IsFullHealth()) { continue; } - - if (!newTarget || drake->GetHealthPct() < newTarget->GetHealthPct() - 5.0f) - { - newTarget = drake; - } - } - target = newTarget; + return false; } - uint8 comboPoints = vehicleBase->GetComboPoints(target); + uint8 comboPoints = vehicleBase->GetComboPoints(vehicleBase); if (comboPoints >= 5) { - return CastDrakeSpellAction(target, SPELL_LIFE_BURST, 0); + return CastDrakeSpellAction(vehicleBase, SPELL_LIFE_BURST, 0); } else { @@ -386,6 +399,6 @@ bool EoEDrakeAttackAction::DrakeHealAction() // "botAI->CanCastVehicleSpell()" returns SPELL_FAILED_BAD_TARGETS when targeting drakes. // Forcing the cast attempt seems to succeed, not sure what's going on here. // return CastDrakeSpellAction(target, SPELL_REVIVIFY, 0); - return botAI->CastVehicleSpell(SPELL_REVIVIFY, target); + return botAI->CastVehicleSpell(SPELL_REVIVIFY, vehicleBase); } } diff --git a/modules/mod-playerbots/src/strategy/raids/icecrown/RaidIccActions.cpp b/modules/mod-playerbots/src/strategy/raids/icecrown/RaidIccActions.cpp index 35aebb6..9d553fe 100644 --- a/modules/mod-playerbots/src/strategy/raids/icecrown/RaidIccActions.cpp +++ b/modules/mod-playerbots/src/strategy/raids/icecrown/RaidIccActions.cpp @@ -3206,13 +3206,45 @@ bool IccLichKingWinterAction::Execute(Event event) bool IccLichKingAddsAction::Execute(Event event) { + if (bot->HasAura(68985) || !bot->IsAlive()) // Don't process actions if bot is picked up by Val'kyr or is dead + return false; + Unit* boss = AI_VALUE2(Unit*, "find target", "the lich king"); Unit* spiritWarden = AI_VALUE2(Unit*, "find target", "spirit warden"); + + if (bot->HasAura(30440)) // Random aura tracking whether bot has fallen off edge / been thrown by Val'kyr + { + if(bot->GetPositionZ() > 779.0f) + return JumpTo(bot->GetMapId(), bot->GetPositionX(), bot->GetPositionY(), 740.01f); + else + { + bot->KillPlayer(); // If bot has jumped past the kill Z (780), kill + return true; + } + } + + bool hasWinterAura = boss->HasAura(72259) || boss->HasAura(74273) || boss->HasAura(74274) || boss->HasAura(74275); + bool hasWinter2Aura = boss->HasAura(68981) || boss->HasAura(74270) || boss->HasAura(74271) || boss->HasAura(74272); + + if (boss && boss->GetHealthPct() < 70 && boss->GetHealthPct() > 40 && !hasWinterAura && !hasWinter2Aura) // If boss is in p2, check if bot has been thrown off platform + { + float dx = bot->GetPositionX() - 503.0f; + float dy = bot->GetPositionY() - (-2124.0f); + float distance = sqrt(dx*dx + dy*dy); // Calculate distance from the center of the platform + + if (distance > 52.0f && distance < 70.0f && bot->GetPositionZ() > 844) // If bot has fallen off edge, distance is over 52 + { + bot->AddAura(30440, bot); // Apply random 30 sec aura to track that we've initiated a jump + return JumpTo(bot->GetMapId(), bot->GetPositionX(), bot->GetPositionY(), 740.01f); // Start jumping to the abyss + } + } + /* //temp solution for bots going underground due to buggy ice platfroms and adds that go underground if (abs(bot->GetPositionZ() - 840.857f) > 1.0f) return bot->TeleportTo(bot->GetMapId(), bot->GetPositionX(), bot->GetPositionY(), 840.857f, bot->GetOrientation()); + */ //temp soultion for bots when they get teleport far away into another dimension (they are unable to attack spirit warden) in heroic they will be in safe spot while player is surviving vile spirits Difficulty diff = bot->GetRaidDifficulty(); diff --git a/modules/mod-playerbots/src/strategy/raids/ulduar/RaidUlduarActionContext.h b/modules/mod-playerbots/src/strategy/raids/ulduar/RaidUlduarActionContext.h index e3dcd97..a801735 100644 --- a/modules/mod-playerbots/src/strategy/raids/ulduar/RaidUlduarActionContext.h +++ b/modules/mod-playerbots/src/strategy/raids/ulduar/RaidUlduarActionContext.h @@ -24,7 +24,10 @@ public: creators["razorscale grounded"] = &RaidUlduarActionContext::razorscale_grounded; creators["razorscale harpoon action"] = &RaidUlduarActionContext::razorscale_harpoon_action; creators["razorscale fuse armor action"] = &RaidUlduarActionContext::razorscale_fuse_armor_action; + creators["iron assembly lightning tendrils action"] = &RaidUlduarActionContext::iron_assembly_lightning_tendrils_action; + creators["iron assembly overload action"] = &RaidUlduarActionContext::iron_assembly_overload_action; creators["hodir move snowpacked icicle"] = &RaidUlduarActionContext::hodir_move_snowpacked_icicle; + creators["hodir biting cold jump"] = &RaidUlduarActionContext::hodir_biting_cold_jump; creators["freya move away nature bomb"] = &RaidUlduarActionContext::freya_move_away_nature_bomb; creators["freya mark eonars gift"] = &RaidUlduarActionContext::freya_mark_eonars_gift; } @@ -39,7 +42,10 @@ private: static Action* razorscale_grounded(PlayerbotAI* ai) { return new RazorscaleGroundedAction(ai); } static Action* razorscale_harpoon_action(PlayerbotAI* ai) { return new RazorscaleHarpoonAction(ai); } static Action* razorscale_fuse_armor_action(PlayerbotAI* ai) { return new RazorscaleFuseArmorAction(ai); } + static Action* iron_assembly_lightning_tendrils_action(PlayerbotAI* ai) { return new IronAssemblyLightningTendrilsAction(ai); } + static Action* iron_assembly_overload_action(PlayerbotAI* ai) { return new IronAssemblyOverloadAction(ai); } static Action* hodir_move_snowpacked_icicle(PlayerbotAI* ai) { return new HodirMoveSnowpackedIcicleAction(ai); } + static Action* hodir_biting_cold_jump(PlayerbotAI* ai) { return new HodirBitingColdJumpAction(ai); } static Action* freya_move_away_nature_bomb(PlayerbotAI* ai) { return new FreyaMoveAwayNatureBombAction(ai); } static Action* freya_mark_eonars_gift(PlayerbotAI* ai) { return new FreyaMarkEonarsGiftAction(ai); } }; diff --git a/modules/mod-playerbots/src/strategy/raids/ulduar/RaidUlduarActions.cpp b/modules/mod-playerbots/src/strategy/raids/ulduar/RaidUlduarActions.cpp index b326502..de7a60e 100644 --- a/modules/mod-playerbots/src/strategy/raids/ulduar/RaidUlduarActions.cpp +++ b/modules/mod-playerbots/src/strategy/raids/ulduar/RaidUlduarActions.cpp @@ -1166,6 +1166,54 @@ bool RazorscaleFuseArmorAction::Execute(Event event) return true; } +bool IronAssemblyLightningTendrilsAction::isUseful() +{ + IronAssemblyLightningTendrilsTrigger ironAssemblyLightningTendrilsTrigger(botAI); + return ironAssemblyLightningTendrilsTrigger.IsActive(); +} + +bool IronAssemblyLightningTendrilsAction::Execute(Event event) +{ + const float radius = 18.0f + 10.0f; // 18 yards + 10 yards for safety + + Unit* boss = AI_VALUE2(Unit*, "find target", "stormcaller brundir"); + if (!boss) + return false; + + float currentDistance = bot->GetDistance2d(boss); + + if (currentDistance < radius) + { + return MoveAway(boss, radius - currentDistance); + } + + return false; +} + +bool IronAssemblyOverloadAction::isUseful() +{ + IronAssemblyOverloadTrigger ironAssemblyOverloadTrigger(botAI); + return ironAssemblyOverloadTrigger.IsActive(); +} + +bool IronAssemblyOverloadAction::Execute(Event event) +{ + const float radius = 20.0f + 5.0f; // 20 yards + 5 yards for safety + + Unit* boss = AI_VALUE2(Unit*, "find target", "stormcaller brundir"); + if (!boss) + return false; + + float currentDistance = bot->GetDistance2d(boss); + + if (currentDistance < radius) + { + return MoveAway(boss, radius - currentDistance); + } + + return false; +} + bool HodirMoveSnowpackedIcicleAction::isUseful() { // Check boss and it is alive @@ -1175,13 +1223,19 @@ bool HodirMoveSnowpackedIcicleAction::isUseful() return false; } + // Check if boss is casting Flash Freeze + if (!boss->HasUnitState(UNIT_STATE_CASTING) || !boss->FindCurrentSpellBySpellId(SPELL_FLASH_FREEZE)) + { + return false; + } + // Find the nearest Snowpacked Icicle Target - Creature* target = bot->FindNearestCreature(33174, 100.0f); + Creature* target = bot->FindNearestCreature(NPC_SNOWPACKED_ICICLE, 100.0f); if (!target) return false; - // Check that boss is stacked on Snowpacked Icicle - if (bot->GetDistance2d(target->GetPositionX(), target->GetPositionY()) <= 3.0f) + // Check that bot is stacked on Snowpacked Icicle + if (bot->GetDistance2d(target->GetPositionX(), target->GetPositionY()) <= 5.0f) { return false; } @@ -1191,12 +1245,42 @@ bool HodirMoveSnowpackedIcicleAction::isUseful() bool HodirMoveSnowpackedIcicleAction::Execute(Event event) { - Creature* target = bot->FindNearestCreature(33174, 100.0f); + Creature* target = bot->FindNearestCreature(NPC_SNOWPACKED_ICICLE, 100.0f); if (!target) return false; return MoveTo(target->GetMapId(), target->GetPositionX(), target->GetPositionY(), target->GetPositionZ(), false, - false, false, true, MovementPriority::MOVEMENT_NORMAL); + false, false, true, MovementPriority::MOVEMENT_NORMAL, true); +} + +bool HodirBitingColdJumpAction::Execute(Event event) +{ + // This needs improving but maybe it should be done in the playerbot core. + + int mapId = bot->GetMap()->GetId(); + int x = bot->GetPositionX(); + int y = bot->GetPositionY(); + int z = bot->GetPositionZ() + 3.98f; + float speed = 7.96f; + + UpdateMovementState(); + if (!IsMovingAllowed(mapId, x, y, z)) + { + return false; + } + MovementPriority priority; + if (IsWaitingForLastMove(priority)) + { + return false; + } + + MotionMaster& mm = *bot->GetMotionMaster(); + mm.Clear(); + mm.MoveJump(x, y, z, speed, speed, 1, AI_VALUE(Unit*, "current target")); + mm.MoveFall(0, true); + AI_VALUE(LastMovement&, "last movement").Set(mapId, x, y, z, bot->GetOrientation(), 1000, priority); + + return true; } bool FreyaMoveAwayNatureBombAction::isUseful() diff --git a/modules/mod-playerbots/src/strategy/raids/ulduar/RaidUlduarActions.h b/modules/mod-playerbots/src/strategy/raids/ulduar/RaidUlduarActions.h index 3c1a650..d0a362e 100644 --- a/modules/mod-playerbots/src/strategy/raids/ulduar/RaidUlduarActions.h +++ b/modules/mod-playerbots/src/strategy/raids/ulduar/RaidUlduarActions.h @@ -114,6 +114,29 @@ public: bool isUseful() override; }; +class IronAssemblyLightningTendrilsAction : public MovementAction +{ +public: + IronAssemblyLightningTendrilsAction(PlayerbotAI* botAI) : MovementAction(botAI, "iron assembly lightning tendrils action") {} + bool Execute(Event event) override; + bool isUseful() override; +}; + +class IronAssemblyOverloadAction : public MovementAction +{ +public: + IronAssemblyOverloadAction(PlayerbotAI* botAI) : MovementAction(botAI, "iron assembly overload action") {} + bool Execute(Event event) override; + bool isUseful() override; +}; + +class HodirBitingColdJumpAction : public MovementAction +{ +public: + HodirBitingColdJumpAction(PlayerbotAI* ai) : MovementAction(ai, "hodir biting cold jump") {} + bool Execute(Event event) override; +}; + class FreyaMoveAwayNatureBombAction : public MovementAction { public: diff --git a/modules/mod-playerbots/src/strategy/raids/ulduar/RaidUlduarStrategy.cpp b/modules/mod-playerbots/src/strategy/raids/ulduar/RaidUlduarStrategy.cpp index abbd521..a873768 100644 --- a/modules/mod-playerbots/src/strategy/raids/ulduar/RaidUlduarStrategy.cpp +++ b/modules/mod-playerbots/src/strategy/raids/ulduar/RaidUlduarStrategy.cpp @@ -46,25 +46,38 @@ void RaidUlduarStrategy::InitTriggers(std::vector& triggers) "razorscale fuse armor trigger", NextAction::array(0, new NextAction("razorscale fuse armor action", ACTION_RAID + 2), nullptr))); + // + // Iron Assembly + // + triggers.push_back(new TriggerNode( + "iron assembly lightning tendrils trigger", + NextAction::array(0, new NextAction("iron assembly lightning tendrils action", ACTION_RAID), nullptr))); + + triggers.push_back(new TriggerNode( + "iron assembly overload trigger", + NextAction::array(0, new NextAction("iron assembly overload action", ACTION_RAID), nullptr))); + // // Hodir // triggers.push_back(new TriggerNode( "hodir near snowpacked icicle", - NextAction::array(0, new NextAction("hodir move snowpacked icicle", ACTION_RAID + 5), nullptr))); + NextAction::array(0, new NextAction("hodir move snowpacked icicle", ACTION_RAID + 1), nullptr))); + triggers.push_back(new TriggerNode( - "hodir biting cold", NextAction::array(0, new NextAction("intense cold jump", ACTION_RAID + 4), nullptr))); + "hodir biting cold", + NextAction::array(0, new NextAction("hodir biting cold jump", ACTION_RAID), nullptr))); // // Freya // - triggers.push_back( - new TriggerNode("freya tank near eonars gift", - NextAction::array(0, new NextAction("freya mark eonars gift", ACTION_RAID + 1), nullptr))); + triggers.push_back(new TriggerNode( + "freya tank near eonars gift", + NextAction::array(0, new NextAction("freya mark eonars gift", ACTION_RAID + 1), nullptr))); - triggers.push_back( - new TriggerNode("freya near nature bomb", - NextAction::array(0, new NextAction("freya move away nature bomb", ACTION_RAID), nullptr))); + triggers.push_back(new TriggerNode( + "freya near nature bomb", + NextAction::array(0, new NextAction("freya move away nature bomb", ACTION_RAID), nullptr))); } void RaidUlduarStrategy::InitMultipliers(std::vector& multipliers) diff --git a/modules/mod-playerbots/src/strategy/raids/ulduar/RaidUlduarTriggerContext.h b/modules/mod-playerbots/src/strategy/raids/ulduar/RaidUlduarTriggerContext.h index 6976b35..953d5ed 100644 --- a/modules/mod-playerbots/src/strategy/raids/ulduar/RaidUlduarTriggerContext.h +++ b/modules/mod-playerbots/src/strategy/raids/ulduar/RaidUlduarTriggerContext.h @@ -24,6 +24,8 @@ public: creators["razorscale grounded"] = &RaidUlduarTriggerContext::razorscale_grounded; creators["razorscale harpoon trigger"] = &RaidUlduarTriggerContext::razorscale_harpoon_trigger; creators["razorscale fuse armor trigger"] = &RaidUlduarTriggerContext::razorscale_fuse_armor_trigger; + creators["iron assembly lightning tendrils trigger"] = &RaidUlduarTriggerContext::iron_assembly_lightning_tendrils_trigger; + creators["iron assembly overload trigger"] = &RaidUlduarTriggerContext::iron_assembly_overload_trigger; creators["hodir biting cold"] = &RaidUlduarTriggerContext::hodir_biting_cold; creators["hodir near snowpacked icicle"] = &RaidUlduarTriggerContext::hodir_near_snowpacked_icicle; creators["freya near nature bomb"] = &RaidUlduarTriggerContext::freya_near_nature_bomb; @@ -40,6 +42,8 @@ private: static Trigger* razorscale_grounded(PlayerbotAI* ai) { return new RazorscaleGroundedTrigger(ai); } static Trigger* razorscale_harpoon_trigger(PlayerbotAI* ai) { return new RazorscaleHarpoonAvailableTrigger(ai); } static Trigger* razorscale_fuse_armor_trigger(PlayerbotAI* ai) { return new RazorscaleFuseArmorTrigger(ai); } + static Trigger* iron_assembly_lightning_tendrils_trigger(PlayerbotAI* ai) { return new IronAssemblyLightningTendrilsTrigger(ai); } + static Trigger* iron_assembly_overload_trigger(PlayerbotAI* ai) { return new IronAssemblyOverloadTrigger(ai); } static Trigger* hodir_biting_cold(PlayerbotAI* ai) { return new HodirBitingColdTrigger(ai); } static Trigger* hodir_near_snowpacked_icicle(PlayerbotAI* ai) { return new HodirNearSnowpackedIcicleTrigger(ai); } static Trigger* freya_near_nature_bomb(PlayerbotAI* ai) { return new FreyaNearNatureBombTrigger(ai); } diff --git a/modules/mod-playerbots/src/strategy/raids/ulduar/RaidUlduarTriggers.cpp b/modules/mod-playerbots/src/strategy/raids/ulduar/RaidUlduarTriggers.cpp index b358f0e..5ae7b19 100644 --- a/modules/mod-playerbots/src/strategy/raids/ulduar/RaidUlduarTriggers.cpp +++ b/modules/mod-playerbots/src/strategy/raids/ulduar/RaidUlduarTriggers.cpp @@ -242,10 +242,58 @@ bool RazorscaleFuseArmorTrigger::IsActive() return false; } +bool IronAssemblyLightningTendrilsTrigger::IsActive() +{ + // Check boss and it is alive + Unit* boss = AI_VALUE2(Unit*, "find target", "stormcaller brundir"); + if (!boss || !boss->IsAlive()) + return false; + + // Check if bot is within 35 yards of the boss + if (boss->GetDistance(bot) > 35.0f) + return false; + + // Check if the boss has the Lightning Tendrils aura + return boss->HasAura(SPELL_LIGHTNING_TENDRILS_10_MAN) || boss->HasAura(SPELL_LIGHTNING_TENDRILS_25_MAN); +} + +bool IronAssemblyOverloadTrigger::IsActive() +{ + // Check if bot is tank + if (botAI->IsTank(bot)) + return false; + + // Check boss and it is alive + Unit* boss = AI_VALUE2(Unit*, "find target", "stormcaller brundir"); + if (!boss || !boss->IsAlive()) + return false; + + // Check if bot is within 35 yards of the boss + if (boss->GetDistance(bot) > 35.0f) + return false; + + // Check if the boss has the Overload aura + return boss->HasAura(SPELL_OVERLOAD_10_MAN) || boss->HasAura(SPELL_OVERLOAD_25_MAN) || + boss->HasAura(SPELL_OVERLOAD_10_MAN_2) || boss->HasAura(SPELL_OVERLOAD_25_MAN_2); +} + bool HodirBitingColdTrigger::IsActive() { Unit* boss = AI_VALUE2(Unit*, "find target", "hodir"); - return boss && botAI->GetAura("biting cold", bot, false, false); + + // Check boss and it is alive + if (!boss || !boss->IsAlive()) + { + return false; + } + + // Override if boss is casting Flash Freeze + if (!boss->HasUnitState(UNIT_STATE_CASTING) || !boss->FindCurrentSpellBySpellId(SPELL_FLASH_FREEZE)) + { + return true; + } + + return boss && botAI->GetAura("biting cold", bot, false, false, 2); } //Snowpacked Icicle Target @@ -258,9 +306,24 @@ bool HodirNearSnowpackedIcicleTrigger::IsActive() return false; } + // Check if boss is casting Flash Freeze + if (!boss->HasUnitState(UNIT_STATE_CASTING) || !boss->FindCurrentSpellBySpellId(SPELL_FLASH_FREEZE)) + { + return false; + } + // Find the nearest Snowpacked Icicle Target - Creature* target = bot->FindNearestCreature(33174, 100.0f); - return target != nullptr; + Creature* target = bot->FindNearestCreature(NPC_SNOWPACKED_ICICLE, 100.0f); + if (!target) + return false; + + // Check that bot is stacked on Snowpacked Icicle + if (bot->GetDistance2d(target->GetPositionX(), target->GetPositionY()) <= 5.0f) + { + return false; + } + + return true; } bool FreyaNearNatureBombTrigger::IsActive() diff --git a/modules/mod-playerbots/src/strategy/raids/ulduar/RaidUlduarTriggers.h b/modules/mod-playerbots/src/strategy/raids/ulduar/RaidUlduarTriggers.h index 2d03187..1782eb7 100644 --- a/modules/mod-playerbots/src/strategy/raids/ulduar/RaidUlduarTriggers.h +++ b/modules/mod-playerbots/src/strategy/raids/ulduar/RaidUlduarTriggers.h @@ -9,9 +9,21 @@ enum UlduarIDs { + // Iron Assembly + SPELL_LIGHTNING_TENDRILS_10_MAN = 61887, + SPELL_LIGHTNING_TENDRILS_25_MAN = 63486, + SPELL_OVERLOAD_10_MAN = 61869, + SPELL_OVERLOAD_25_MAN = 63481, + SPELL_OVERLOAD_10_MAN_2 = 63485, + SPELL_OVERLOAD_25_MAN_2 = 61886, + + // Hodir + NPC_SNOWPACKED_ICICLE = 33174, + NPC_TOASTY_FIRE = 33342, + SPELL_FLASH_FREEZE = 61968, + // Freya NPC_EONARS_GIFT = 33228, - GOBJECT_NATURE_BOMB = 194902, }; @@ -84,6 +96,26 @@ public: bool IsActive() override; }; +// +// Iron Assembly +// +class IronAssemblyLightningTendrilsTrigger : public Trigger +{ +public: + IronAssemblyLightningTendrilsTrigger(PlayerbotAI* ai) : Trigger(ai, "iron assembly lightning tendrils trigger") {} + bool IsActive() override; +}; + +class IronAssemblyOverloadTrigger : public Trigger +{ +public: + IronAssemblyOverloadTrigger(PlayerbotAI* ai) : Trigger(ai, "iron assembly overload trigger") {} + bool IsActive() override; +}; + +// +// Hodir +// class HodirBitingColdTrigger : public Trigger { public: @@ -98,6 +130,9 @@ public: bool IsActive() override; }; +// +// Freya +// class FreyaNearNatureBombTrigger : public Trigger { public: diff --git a/modules/mod-playerbots/src/strategy/values/ItemUsageValue.cpp b/modules/mod-playerbots/src/strategy/values/ItemUsageValue.cpp index a85e49f..65ddb75 100644 --- a/modules/mod-playerbots/src/strategy/values/ItemUsageValue.cpp +++ b/modules/mod-playerbots/src/strategy/values/ItemUsageValue.cpp @@ -8,6 +8,7 @@ #include "AiFactory.h" #include "ChatHelper.h" #include "GuildTaskMgr.h" +#include "LootObjectStack.h" #include "PlayerbotAIConfig.h" #include "PlayerbotFactory.h" #include "Playerbots.h" @@ -112,12 +113,38 @@ ItemUsage ItemUsageValue::Calculate() return ITEM_USAGE_DISENCHANT; } - // While sync is on, do not loot quest items that are also Useful for master. Master - if (!botAI->GetMaster() || !sPlayerbotAIConfig->syncQuestWithPlayer || - !IsItemUsefulForQuest(botAI->GetMaster(), proto)) - if (IsItemUsefulForQuest(bot, proto)) - return ITEM_USAGE_QUEST; - + Player* master = botAI->GetMaster(); + bool isSelfBot = (master == bot); + bool botNeedsItemForQuest = IsItemUsefulForQuest(bot, proto); + bool masterNeedsItemForQuest = master && sPlayerbotAIConfig->syncQuestWithPlayer && IsItemUsefulForQuest(master, proto); + + // Identify the source of loot + LootObject lootObject = AI_VALUE(LootObject, "loot target"); + + // Get GUID of loot source + ObjectGuid lootGuid = lootObject.guid; + + // Check if loot source is an item + bool isLootFromItem = lootGuid.IsItem(); + + // If the loot is from an item in the bot’s bags, ignore syncQuestWithPlayer + if (isLootFromItem && botNeedsItemForQuest) + { + return ITEM_USAGE_QUEST; + } + + // If the bot is NOT acting alone and the master needs this quest item, defer to the master + if (!isSelfBot && masterNeedsItemForQuest) + { + return ITEM_USAGE_NONE; + } + + // If the bot itself needs the item for a quest, allow looting + if (botNeedsItemForQuest) + { + return ITEM_USAGE_QUEST; + } + if (proto->Class == ITEM_CLASS_PROJECTILE && bot->CanUseItem(proto) == EQUIP_ERR_OK) { if (bot->getClass() == CLASS_HUNTER || bot->getClass() == CLASS_ROGUE || bot->getClass() == CLASS_WARRIOR) @@ -464,6 +491,10 @@ uint32 ItemUsageValue::GetSmallestBagSize() bool ItemUsageValue::IsItemUsefulForQuest(Player* player, ItemTemplate const* proto) { + PlayerbotAI* botAI = GET_PLAYERBOT_AI(player); + if (!botAI) + return false; + for (uint8 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot) { uint32 entry = player->GetQuestSlotQuestId(slot); @@ -471,20 +502,52 @@ bool ItemUsageValue::IsItemUsefulForQuest(Player* player, ItemTemplate const* pr if (!quest) continue; + // Check if the item itself is needed for the quest for (uint8 i = 0; i < 4; i++) { - if (quest->RequiredItemId[i] != proto->ItemId) + if (quest->RequiredItemId[i] == proto->ItemId) + { + if (AI_VALUE2(uint32, "item count", proto->Name1) >= quest->RequiredItemCount[i]) + continue; + + return true; // Item is directly required for a quest + } + } + + // Check if the item has spells that create a required quest item + for (uint8 i = 0; i < MAX_ITEM_SPELLS; i++) + { + uint32 spellId = proto->Spells[i].SpellId; + if (!spellId) continue; - if (GET_PLAYERBOT_AI(player) && - AI_VALUE2(uint32, "item count", proto->Name1) >= quest->RequiredItemCount[i]) + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); + if (!spellInfo) continue; - return true; + for (uint8 effectIndex = 0; effectIndex < MAX_SPELL_EFFECTS; effectIndex++) + { + if (spellInfo->Effects[effectIndex].Effect == SPELL_EFFECT_CREATE_ITEM) + { + uint32 createdItemId = spellInfo->Effects[effectIndex].ItemType; + + // Check if the created item is required for a quest + for (uint8 j = 0; j < 4; j++) + { + if (quest->RequiredItemId[j] == createdItemId) + { + if (AI_VALUE2(uint32, "item count", createdItemId) >= quest->RequiredItemCount[j]) + continue; + + return true; // Item is useful because it creates a required quest item + } + } + } + } } } - return false; + return false; // Item is not useful for any active quests } bool ItemUsageValue::IsItemNeededForSkill(ItemTemplate const* proto) diff --git a/modules/mod-playerbots/src/strategy/values/LootStrategyValue.cpp b/modules/mod-playerbots/src/strategy/values/LootStrategyValue.cpp index 6d98efb..22355d4 100644 --- a/modules/mod-playerbots/src/strategy/values/LootStrategyValue.cpp +++ b/modules/mod-playerbots/src/strategy/values/LootStrategyValue.cpp @@ -15,6 +15,15 @@ class NormalLootStrategy : public LootStrategy public: bool CanLoot(ItemTemplate const* proto, AiObjectContext* context) override { + // Identify the source of loot, loot it if the source is an item in the bots inventory + LootObject lootObject = AI_VALUE(LootObject, "loot target"); + ObjectGuid lootGuid = lootObject.guid; + if (lootGuid.IsItem()) + { + return true; + } + + // Otherwise, continue with the normal loot logic std::ostringstream out; out << proto->ItemId; ItemUsage usage = AI_VALUE2(ItemUsage, "item usage", out.str());