latest playerbots module

This commit is contained in:
mikx 2025-03-25 04:39:38 -04:00
parent 6c74f37aee
commit 98f7a2ccfe
15 changed files with 431 additions and 68 deletions

View File

@ -237,6 +237,13 @@ bool CheckMountStateAction::TryForms(Player* master, int32 masterMountType, int3
if (!master) if (!master)
return false; 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 // Check if master is in Travel Form and bot can do the same
if (botAI->CanCastSpell(SPELL_TRAVEL_FORM, bot, true) && if (botAI->CanCastSpell(SPELL_TRAVEL_FORM, bot, true) &&
masterInShapeshiftForm == FORM_TRAVEL && botInShapeshiftForm != FORM_TRAVEL) masterInShapeshiftForm == FORM_TRAVEL && botInShapeshiftForm != FORM_TRAVEL)

View File

@ -4,6 +4,9 @@
#include "WorldPacket.h" #include "WorldPacket.h"
#include "Player.h" #include "Player.h"
#include "ObjectMgr.h" #include "ObjectMgr.h"
#include "LootObjectStack.h"
#include "AiObjectContext.h"
bool OpenItemAction::Execute(Event event) bool OpenItemAction::Execute(Event event)
{ {
bool foundOpenable = false; bool foundOpenable = false;
@ -27,6 +30,11 @@ void OpenItemAction::OpenItem(Item* item, uint8 bag, uint8 slot)
packet << bag << slot; packet << bag << slot;
bot->GetSession()->HandleOpenItemOpcode(packet); bot->GetSession()->HandleOpenItemOpcode(packet);
// Store the item GUID as the loot target
LootObject lootObject;
lootObject.guid = item->GetGUID();
botAI->GetAiObjectContext()->GetValue<LootObject>("loot target")->Set(lootObject);
std::ostringstream out; std::ostringstream out;
out << "Opened item: " << item->GetTemplate()->Name1; out << "Opened item: " << item->GetTemplate()->Name1;
botAI->TellMaster(out.str()); botAI->TellMaster(out.str());

View File

@ -969,7 +969,7 @@ std::string ChatReplyAction::GenerateReplyMessage(Player* bot, std::string& inco
msg = "ya %s but thats in the past"; msg = "ya %s but thats in the past";
break; break;
case 2: 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; break;
} }
msg = std::regex_replace(msg, std::regex("%s"), name); 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?"; msg = "%s, what will happen %s?";
break; break;
case 2: 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; break;
} }
msg = std::regex_replace(msg, std::regex("%s"), name); msg = std::regex_replace(msg, std::regex("%s"), name);

View File

@ -16,7 +16,10 @@ Value<Unit*>* CastPolymorphAction::GetTargetValue() { return context->GetValue<U
bool CastFrostNovaAction::isUseful() bool CastFrostNovaAction::isUseful()
{ {
Unit* target = AI_VALUE(Unit*, "current target"); Unit* target = AI_VALUE(Unit*, "current target");
if (target && target->ToCreature() && 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; return false;
if (target->isFrozen()) if (target->isFrozen())

View File

@ -1,9 +1,7 @@
#include "Playerbots.h"
#include "RaidEoEActions.h" #include "RaidEoEActions.h"
#include "RaidEoETriggers.h" #include "RaidEoETriggers.h"
#include "Playerbots.h"
bool MalygosPositionAction::Execute(Event event) bool MalygosPositionAction::Execute(Event event)
{ {
Unit* boss = AI_VALUE2(Unit*, "find target", "malygos"); Unit* boss = AI_VALUE2(Unit*, "find target", "malygos");
@ -280,10 +278,14 @@ bool EoEDrakeAttackAction::isPossible()
Unit* vehicleBase = bot->GetVehicleBase(); Unit* vehicleBase = bot->GetVehicleBase();
return (vehicleBase && vehicleBase->GetEntry() == NPC_WYRMREST_SKYTALON); return (vehicleBase && vehicleBase->GetEntry() == NPC_WYRMREST_SKYTALON);
} }
bool EoEDrakeAttackAction::Execute(Event event) bool EoEDrakeAttackAction::Execute(Event event)
{ {
vehicleBase = bot->GetVehicleBase(); vehicleBase = bot->GetVehicleBase();
if (!vehicleBase) { return false; } if (!vehicleBase)
{
return false;
}
// Unit* target = AI_VALUE(Unit*, "current target"); // Unit* target = AI_VALUE(Unit*, "current target");
Unit* boss = AI_VALUE2(Unit*, "find target", "malygos"); Unit* boss = AI_VALUE2(Unit*, "find target", "malygos");
@ -295,22 +297,55 @@ bool EoEDrakeAttackAction::Execute(Event event)
for (auto& npc : npcs) for (auto& npc : npcs)
{ {
Unit* unit = botAI->GetUnit(npc); Unit* unit = botAI->GetUnit(npc);
if (!unit || unit->GetEntry() != NPC_MALYGOS) { continue; } if (!unit || unit->GetEntry() != NPC_MALYGOS)
{
continue;
}
boss = unit; boss = unit;
break; break;
} }
} }
// Check this again to see if a target was assigned // Check this again to see if a target was assigned
if (!boss) { return false; } if (!boss)
if (botAI->IsHeal(bot))
{ {
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<std::pair<ObjectGuid, Player*>> 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 else
{ {
return DrakeDpsAction(boss); return DrakeHealAction();
} }
return false; return false;
@ -348,37 +383,15 @@ bool EoEDrakeAttackAction::DrakeDpsAction(Unit* target)
bool EoEDrakeAttackAction::DrakeHealAction() bool EoEDrakeAttackAction::DrakeHealAction()
{ {
Unit* vehicleBase = bot->GetVehicleBase(); Unit* vehicleBase = bot->GetVehicleBase();
if (!vehicleBase) { return false; } if (!vehicleBase)
Unit* target = vehicleBase->GetComboTarget();
if (!target)
{ {
// Unit* newTarget = nullptr; return false;
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(); uint8 comboPoints = vehicleBase->GetComboPoints(vehicleBase);
if (!drake || drake->IsFullHealth()) { continue; }
if (!newTarget || drake->GetHealthPct() < newTarget->GetHealthPct() - 5.0f)
{
newTarget = drake;
}
}
target = newTarget;
}
uint8 comboPoints = vehicleBase->GetComboPoints(target);
if (comboPoints >= 5) if (comboPoints >= 5)
{ {
return CastDrakeSpellAction(target, SPELL_LIFE_BURST, 0); return CastDrakeSpellAction(vehicleBase, SPELL_LIFE_BURST, 0);
} }
else else
{ {
@ -386,6 +399,6 @@ bool EoEDrakeAttackAction::DrakeHealAction()
// "botAI->CanCastVehicleSpell()" returns SPELL_FAILED_BAD_TARGETS when targeting drakes. // "botAI->CanCastVehicleSpell()" returns SPELL_FAILED_BAD_TARGETS when targeting drakes.
// Forcing the cast attempt seems to succeed, not sure what's going on here. // Forcing the cast attempt seems to succeed, not sure what's going on here.
// return CastDrakeSpellAction(target, SPELL_REVIVIFY, 0); // return CastDrakeSpellAction(target, SPELL_REVIVIFY, 0);
return botAI->CastVehicleSpell(SPELL_REVIVIFY, target); return botAI->CastVehicleSpell(SPELL_REVIVIFY, vehicleBase);
} }
} }

View File

@ -3206,13 +3206,45 @@ bool IccLichKingWinterAction::Execute(Event event)
bool IccLichKingAddsAction::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* boss = AI_VALUE2(Unit*, "find target", "the lich king");
Unit* spiritWarden = AI_VALUE2(Unit*, "find target", "spirit warden"); 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 //temp solution for bots going underground due to buggy ice platfroms and adds that go underground
if (abs(bot->GetPositionZ() - 840.857f) > 1.0f) if (abs(bot->GetPositionZ() - 840.857f) > 1.0f)
return bot->TeleportTo(bot->GetMapId(), bot->GetPositionX(), return bot->TeleportTo(bot->GetMapId(), bot->GetPositionX(),
bot->GetPositionY(), 840.857f, bot->GetOrientation()); 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 //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(); Difficulty diff = bot->GetRaidDifficulty();

View File

@ -24,7 +24,10 @@ public:
creators["razorscale grounded"] = &RaidUlduarActionContext::razorscale_grounded; creators["razorscale grounded"] = &RaidUlduarActionContext::razorscale_grounded;
creators["razorscale harpoon action"] = &RaidUlduarActionContext::razorscale_harpoon_action; creators["razorscale harpoon action"] = &RaidUlduarActionContext::razorscale_harpoon_action;
creators["razorscale fuse armor action"] = &RaidUlduarActionContext::razorscale_fuse_armor_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 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 move away nature bomb"] = &RaidUlduarActionContext::freya_move_away_nature_bomb;
creators["freya mark eonars gift"] = &RaidUlduarActionContext::freya_mark_eonars_gift; 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_grounded(PlayerbotAI* ai) { return new RazorscaleGroundedAction(ai); }
static Action* razorscale_harpoon_action(PlayerbotAI* ai) { return new RazorscaleHarpoonAction(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* 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_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_move_away_nature_bomb(PlayerbotAI* ai) { return new FreyaMoveAwayNatureBombAction(ai); }
static Action* freya_mark_eonars_gift(PlayerbotAI* ai) { return new FreyaMarkEonarsGiftAction(ai); } static Action* freya_mark_eonars_gift(PlayerbotAI* ai) { return new FreyaMarkEonarsGiftAction(ai); }
}; };

View File

@ -1166,6 +1166,54 @@ bool RazorscaleFuseArmorAction::Execute(Event event)
return true; 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() bool HodirMoveSnowpackedIcicleAction::isUseful()
{ {
// Check boss and it is alive // Check boss and it is alive
@ -1175,13 +1223,19 @@ bool HodirMoveSnowpackedIcicleAction::isUseful()
return false; 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 // Find the nearest Snowpacked Icicle Target
Creature* target = bot->FindNearestCreature(33174, 100.0f); Creature* target = bot->FindNearestCreature(NPC_SNOWPACKED_ICICLE, 100.0f);
if (!target) if (!target)
return false; return false;
// Check that boss is stacked on Snowpacked Icicle // Check that bot is stacked on Snowpacked Icicle
if (bot->GetDistance2d(target->GetPositionX(), target->GetPositionY()) <= 3.0f) if (bot->GetDistance2d(target->GetPositionX(), target->GetPositionY()) <= 5.0f)
{ {
return false; return false;
} }
@ -1191,12 +1245,42 @@ bool HodirMoveSnowpackedIcicleAction::isUseful()
bool HodirMoveSnowpackedIcicleAction::Execute(Event event) bool HodirMoveSnowpackedIcicleAction::Execute(Event event)
{ {
Creature* target = bot->FindNearestCreature(33174, 100.0f); Creature* target = bot->FindNearestCreature(NPC_SNOWPACKED_ICICLE, 100.0f);
if (!target) if (!target)
return false; return false;
return MoveTo(target->GetMapId(), target->GetPositionX(), target->GetPositionY(), target->GetPositionZ(), 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() bool FreyaMoveAwayNatureBombAction::isUseful()

View File

@ -114,6 +114,29 @@ public:
bool isUseful() override; 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 class FreyaMoveAwayNatureBombAction : public MovementAction
{ {
public: public:

View File

@ -46,24 +46,37 @@ void RaidUlduarStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
"razorscale fuse armor trigger", "razorscale fuse armor trigger",
NextAction::array(0, new NextAction("razorscale fuse armor action", ACTION_RAID + 2), nullptr))); 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 // Hodir
// //
triggers.push_back(new TriggerNode( triggers.push_back(new TriggerNode(
"hodir near snowpacked icicle", "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( 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 // Freya
// //
triggers.push_back( triggers.push_back(new TriggerNode(
new TriggerNode("freya tank near eonars gift", "freya tank near eonars gift",
NextAction::array(0, new NextAction("freya mark eonars gift", ACTION_RAID + 1), nullptr))); NextAction::array(0, new NextAction("freya mark eonars gift", ACTION_RAID + 1), nullptr)));
triggers.push_back( triggers.push_back(new TriggerNode(
new TriggerNode("freya near nature bomb", "freya near nature bomb",
NextAction::array(0, new NextAction("freya move away nature bomb", ACTION_RAID), nullptr))); NextAction::array(0, new NextAction("freya move away nature bomb", ACTION_RAID), nullptr)));
} }

View File

@ -24,6 +24,8 @@ public:
creators["razorscale grounded"] = &RaidUlduarTriggerContext::razorscale_grounded; creators["razorscale grounded"] = &RaidUlduarTriggerContext::razorscale_grounded;
creators["razorscale harpoon trigger"] = &RaidUlduarTriggerContext::razorscale_harpoon_trigger; creators["razorscale harpoon trigger"] = &RaidUlduarTriggerContext::razorscale_harpoon_trigger;
creators["razorscale fuse armor trigger"] = &RaidUlduarTriggerContext::razorscale_fuse_armor_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 biting cold"] = &RaidUlduarTriggerContext::hodir_biting_cold;
creators["hodir near snowpacked icicle"] = &RaidUlduarTriggerContext::hodir_near_snowpacked_icicle; creators["hodir near snowpacked icicle"] = &RaidUlduarTriggerContext::hodir_near_snowpacked_icicle;
creators["freya near nature bomb"] = &RaidUlduarTriggerContext::freya_near_nature_bomb; 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_grounded(PlayerbotAI* ai) { return new RazorscaleGroundedTrigger(ai); }
static Trigger* razorscale_harpoon_trigger(PlayerbotAI* ai) { return new RazorscaleHarpoonAvailableTrigger(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* 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_biting_cold(PlayerbotAI* ai) { return new HodirBitingColdTrigger(ai); }
static Trigger* hodir_near_snowpacked_icicle(PlayerbotAI* ai) { return new HodirNearSnowpackedIcicleTrigger(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); } static Trigger* freya_near_nature_bomb(PlayerbotAI* ai) { return new FreyaNearNatureBombTrigger(ai); }

View File

@ -242,10 +242,58 @@ bool RazorscaleFuseArmorTrigger::IsActive()
return false; 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() bool HodirBitingColdTrigger::IsActive()
{ {
Unit* boss = AI_VALUE2(Unit*, "find target", "hodir"); 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 //Snowpacked Icicle Target
@ -258,9 +306,24 @@ bool HodirNearSnowpackedIcicleTrigger::IsActive()
return false; 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 // Find the nearest Snowpacked Icicle Target
Creature* target = bot->FindNearestCreature(33174, 100.0f); Creature* target = bot->FindNearestCreature(NPC_SNOWPACKED_ICICLE, 100.0f);
return target != nullptr; 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() bool FreyaNearNatureBombTrigger::IsActive()

View File

@ -9,9 +9,21 @@
enum UlduarIDs 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 // Freya
NPC_EONARS_GIFT = 33228, NPC_EONARS_GIFT = 33228,
GOBJECT_NATURE_BOMB = 194902, GOBJECT_NATURE_BOMB = 194902,
}; };
@ -84,6 +96,26 @@ public:
bool IsActive() override; 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 class HodirBitingColdTrigger : public Trigger
{ {
public: public:
@ -98,6 +130,9 @@ public:
bool IsActive() override; bool IsActive() override;
}; };
//
// Freya
//
class FreyaNearNatureBombTrigger : public Trigger class FreyaNearNatureBombTrigger : public Trigger
{ {
public: public:

View File

@ -8,6 +8,7 @@
#include "AiFactory.h" #include "AiFactory.h"
#include "ChatHelper.h" #include "ChatHelper.h"
#include "GuildTaskMgr.h" #include "GuildTaskMgr.h"
#include "LootObjectStack.h"
#include "PlayerbotAIConfig.h" #include "PlayerbotAIConfig.h"
#include "PlayerbotFactory.h" #include "PlayerbotFactory.h"
#include "Playerbots.h" #include "Playerbots.h"
@ -112,11 +113,37 @@ ItemUsage ItemUsageValue::Calculate()
return ITEM_USAGE_DISENCHANT; return ITEM_USAGE_DISENCHANT;
} }
// While sync is on, do not loot quest items that are also Useful for master. Master Player* master = botAI->GetMaster();
if (!botAI->GetMaster() || !sPlayerbotAIConfig->syncQuestWithPlayer || bool isSelfBot = (master == bot);
!IsItemUsefulForQuest(botAI->GetMaster(), proto)) bool botNeedsItemForQuest = IsItemUsefulForQuest(bot, proto);
if (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 bots bags, ignore syncQuestWithPlayer
if (isLootFromItem && botNeedsItemForQuest)
{
return ITEM_USAGE_QUEST; 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 (proto->Class == ITEM_CLASS_PROJECTILE && bot->CanUseItem(proto) == EQUIP_ERR_OK)
{ {
@ -464,6 +491,10 @@ uint32 ItemUsageValue::GetSmallestBagSize()
bool ItemUsageValue::IsItemUsefulForQuest(Player* player, ItemTemplate const* proto) 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) for (uint8 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot)
{ {
uint32 entry = player->GetQuestSlotQuestId(slot); uint32 entry = player->GetQuestSlotQuestId(slot);
@ -471,20 +502,52 @@ bool ItemUsageValue::IsItemUsefulForQuest(Player* player, ItemTemplate const* pr
if (!quest) if (!quest)
continue; continue;
// Check if the item itself is needed for the quest
for (uint8 i = 0; i < 4; i++) 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; continue;
if (GET_PLAYERBOT_AI(player) && return true; // Item is directly required for a quest
AI_VALUE2(uint32, "item count", proto->Name1) >= quest->RequiredItemCount[i])
continue;
return true;
} }
} }
return false; // 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;
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
if (!spellInfo)
continue;
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; // Item is not useful for any active quests
} }
bool ItemUsageValue::IsItemNeededForSkill(ItemTemplate const* proto) bool ItemUsageValue::IsItemNeededForSkill(ItemTemplate const* proto)

View File

@ -15,6 +15,15 @@ class NormalLootStrategy : public LootStrategy
public: public:
bool CanLoot(ItemTemplate const* proto, AiObjectContext* context) override 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; std::ostringstream out;
out << proto->ItemId; out << proto->ItemId;
ItemUsage usage = AI_VALUE2(ItemUsage, "item usage", out.str()); ItemUsage usage = AI_VALUE2(ItemUsage, "item usage", out.str());