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)
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)

View File

@ -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<LootObject>("loot target")->Set(lootObject);
std::ostringstream out;
out << "Opened item: " << item->GetTemplate()->Name1;
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";
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);

View File

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

View File

@ -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<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
{
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);
}
}

View File

@ -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();

View File

@ -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); }
};

View File

@ -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()

View File

@ -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:

View File

@ -46,25 +46,38 @@ void RaidUlduarStrategy::InitTriggers(std::vector<TriggerNode*>& 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<Multiplier*>& multipliers)

View File

@ -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); }

View File

@ -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()

View File

@ -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:

View File

@ -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 bots 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)

View File

@ -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());