mirror of
https://github.com/mod-playerbots/mod-playerbots.git
synced 2026-01-23 13:36:23 +00:00
https://github.com/mod-playerbots/mod-playerbots/issues/2046 https://github.com/mod-playerbots/mod-playerbots/issues/2030
363 lines
11 KiB
C++
363 lines
11 KiB
C++
#include "Playerbots.h"
|
|
#include "OculusActions.h"
|
|
#include "OculusStrategy.h"
|
|
#include "LastSpellCastValue.h"
|
|
|
|
bool AvoidUnstableSphereAction::Execute(Event event)
|
|
{
|
|
Unit* boss = AI_VALUE2(Unit*, "find target", "drakos the interrogator");
|
|
if (!boss) { return false; }
|
|
|
|
float radius = 12.0f;
|
|
float extraDistance = 1.0f;
|
|
Unit* closestSphere = nullptr;
|
|
|
|
GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs");
|
|
for (auto& npc : npcs)
|
|
{
|
|
Unit* unit = botAI->GetUnit(npc);
|
|
if (unit && unit->GetEntry() == NPC_UNSTABLE_SPHERE && !unit->isMoving())
|
|
{
|
|
if (!closestSphere || bot->GetExactDist2d(unit) < bot->GetExactDist2d(closestSphere))
|
|
{
|
|
closestSphere = unit;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (closestSphere && bot->GetExactDist2d(closestSphere) < radius + extraDistance)
|
|
{
|
|
return MoveAway(closestSphere, fmin(3.0f, bot->GetExactDist2d(closestSphere) - radius + extraDistance));
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool MountDrakeAction::isPossible() { return bot->GetMapId() == OCULUS_MAP_ID; }
|
|
bool MountDrakeAction::Execute(Event event)
|
|
{
|
|
std::map<int32, int32> drakeAssignments;
|
|
// Composition can be adjusted - both 3/1/1 and 2/2/1 are good default comps
|
|
// {Amber, Emerald, Ruby}
|
|
std::vector<uint8> composition = {2, 2, 1};
|
|
// std::vector<uint8> composition = {3, 1, 1};
|
|
int32 myIndex = botAI->GetGroupSlotIndex(bot);
|
|
|
|
Player* master = botAI->GetMaster();
|
|
if (!master) { return false; }
|
|
Unit* vehicle = master->GetVehicleBase();
|
|
if (!vehicle) { return false; }
|
|
|
|
// Subtract the player's chosen mount type from the composition so player can play whichever they prefer
|
|
switch (vehicle->GetEntry())
|
|
{
|
|
case NPC_AMBER_DRAKE:
|
|
composition[0]--;
|
|
break;
|
|
case NPC_EMERALD_DRAKE:
|
|
composition[1]--;
|
|
break;
|
|
case NPC_RUBY_DRAKE:
|
|
composition[2]--;
|
|
break;
|
|
}
|
|
|
|
std::vector<Player*> players = botAI->GetPlayersInGroup();
|
|
for (Player* player : players)
|
|
{
|
|
if (!player || !player->IsInWorld() || player->IsDuringRemoveFromWorld())
|
|
continue;
|
|
|
|
WorldSession* session = player->GetSession();
|
|
if (!session || !session->IsBot())
|
|
continue;
|
|
|
|
int slot = botAI->GetGroupSlotIndex(player);
|
|
if (slot < 0)
|
|
continue;
|
|
|
|
for (uint8 i = 0; i < composition.size(); ++i)
|
|
{
|
|
if (composition[i] > 0)
|
|
{
|
|
drakeAssignments[slot] = DRAKE_ITEMS[i];
|
|
composition[i]--;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Correct/update the drake items in inventories incase assignments have changed
|
|
for (uint32 itemId : DRAKE_ITEMS)
|
|
{
|
|
Item* item = bot->GetItemByEntry(itemId);
|
|
if (!item) { continue; }
|
|
|
|
if (itemId == drakeAssignments[myIndex])
|
|
{
|
|
// Use our assigned drake
|
|
return UseItemAuto(item);
|
|
}
|
|
// Else assigned drake is different, destroy old drake
|
|
uint32 count = 1;
|
|
bot->DestroyItemCount(item, count, true);
|
|
break;
|
|
}
|
|
|
|
// Bot does not have the correct drake item
|
|
bot->AddItem(drakeAssignments[myIndex], 1);
|
|
return false;
|
|
}
|
|
|
|
bool DismountDrakeAction::Execute(Event event)
|
|
{
|
|
if (bot->GetVehicle())
|
|
{
|
|
bot->ExitVehicle();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool OccFlyDrakeAction::Execute(Event event)
|
|
{
|
|
Player* master = botAI->GetMaster();
|
|
if (!master) { return false; }
|
|
Unit* masterVehicle = master->GetVehicleBase();
|
|
Unit* vehicleBase = bot->GetVehicleBase();
|
|
if (!vehicleBase || !masterVehicle) { return false; }
|
|
|
|
MotionMaster* mm = vehicleBase->GetMotionMaster();
|
|
Unit* boss = AI_VALUE2(Unit*, "find target", "ley-guardian eregos");
|
|
if (boss && !boss->HasAura(SPELL_PLANAR_SHIFT))
|
|
{
|
|
// Handle as boss encounter instead of formation flight
|
|
mm->Clear(false);
|
|
float distance = vehicleBase->GetExactDist(boss);
|
|
float range = 55.0f; // Drake range is 60yd
|
|
if (distance > range)
|
|
{
|
|
mm->MoveForwards(boss, range - distance);
|
|
vehicleBase->SendMovementFlagUpdate();
|
|
return true;
|
|
}
|
|
|
|
vehicleBase->SetFacingToObject(boss);
|
|
mm->MoveIdle();
|
|
vehicleBase->SendMovementFlagUpdate();
|
|
return false;
|
|
}
|
|
|
|
if (vehicleBase->GetExactDist(masterVehicle) > 20.0f)
|
|
{
|
|
// 3/4 of a circle, with frontal cone 90 deg unobstructed
|
|
float angle = botAI->GetGroupSlotIndex(bot) * (2*M_PI - M_PI_2)/5 + M_PI_2;
|
|
vehicleBase->SetCanFly(true);
|
|
mm->MoveFollow(masterVehicle, 15.0f, angle);
|
|
vehicleBase->SendMovementFlagUpdate();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool OccDrakeAttackAction::Execute(Event event)
|
|
{
|
|
vehicleBase = bot->GetVehicleBase();
|
|
if (!vehicleBase) { return false; }
|
|
|
|
Unit* target = AI_VALUE(Unit*, "current target");
|
|
|
|
if (!target)
|
|
{
|
|
GuidVector npcs = AI_VALUE(GuidVector, "possible targets");
|
|
for (auto& npc : npcs)
|
|
{
|
|
Unit* unit = botAI->GetUnit(npc);
|
|
if (!unit || !unit->IsInCombat()) { continue; }
|
|
|
|
target = unit;
|
|
break;
|
|
}
|
|
}
|
|
// Check this again to see if a target was assigned
|
|
if (!target) { return false; }
|
|
|
|
switch (vehicleBase->GetEntry())
|
|
{
|
|
case NPC_AMBER_DRAKE:
|
|
return AmberDrakeAction(target);
|
|
case NPC_EMERALD_DRAKE:
|
|
return EmeraldDrakeAction(target);
|
|
case NPC_RUBY_DRAKE:
|
|
return RubyDrakeAction(target);
|
|
default:
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool OccDrakeAttackAction::CastDrakeSpellAction(Unit* target, uint32 spellId, uint32 cooldown)
|
|
{
|
|
if (botAI->CanCastVehicleSpell(spellId, target))
|
|
if (botAI->CastVehicleSpell(spellId, target))
|
|
{
|
|
vehicleBase->AddSpellCooldown(spellId, 0, cooldown);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool OccDrakeAttackAction::AmberDrakeAction(Unit* target)
|
|
{
|
|
Aura* shockCharges = target->GetAura(SPELL_SHOCK_CHARGE, vehicleBase->GetGUID());
|
|
if (shockCharges && shockCharges->GetStackAmount() > 8)
|
|
{
|
|
// At 9 charges, better to detonate and re-channel rather than stacking the last charge due to gcd
|
|
// If stacking Amber drakes, may need to drop this even lower as the charges stack so fast
|
|
return CastDrakeSpellAction(target, SPELL_SHOCK_LANCE, 0);
|
|
}
|
|
|
|
// Deal with enrage after shock charges, as Stop Time adds 5 charges and they may get wasted
|
|
if (target->HasAura(SPELL_ENRAGED_ASSAULT) &&
|
|
!target->HasAura(SPELL_STOP_TIME) &&
|
|
!vehicleBase->HasSpellCooldown(SPELL_STOP_TIME))
|
|
{
|
|
return CastDrakeSpellAction(target, SPELL_STOP_TIME, 60000);
|
|
}
|
|
|
|
if (!vehicleBase->FindCurrentSpellBySpellId(SPELL_TEMPORAL_RIFT))
|
|
{
|
|
return CastDrakeSpellAction(target, SPELL_TEMPORAL_RIFT, 0);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool OccDrakeAttackAction::EmeraldDrakeAction(Unit* target)
|
|
{
|
|
Aura* poisonStacks = target->GetAura(SPELL_LEECHING_POISON, vehicleBase->GetGUID());
|
|
if (!poisonStacks || (poisonStacks->GetStackAmount() < 3 ||
|
|
poisonStacks->GetDuration() < 4000))
|
|
{
|
|
return CastDrakeSpellAction(target, SPELL_LEECHING_POISON, 0);
|
|
}
|
|
|
|
if (!vehicleBase->HasSpellCooldown(SPELL_TOUCH_THE_NIGHTMARE) &&
|
|
(!target->HasAura(SPELL_TOUCH_THE_NIGHTMARE) || vehicleBase->HealthAbovePct(90)))
|
|
{
|
|
return CastDrakeSpellAction(target, SPELL_TOUCH_THE_NIGHTMARE, 10000);
|
|
}
|
|
|
|
Unit* healingTarget = nullptr;
|
|
GuidVector members = AI_VALUE(GuidVector, "group members");
|
|
for (auto& member : members)
|
|
{
|
|
Unit* unit = botAI->GetUnit(member);
|
|
if (!unit || bot->GetGUID() == member)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Unit* drake = unit->GetVehicleBase();
|
|
if (!drake || drake->IsFullHealth()) { continue; }
|
|
|
|
if (!healingTarget || drake->GetHealthPct() < healingTarget->GetHealthPct() - 15.0f)
|
|
{
|
|
healingTarget = drake;
|
|
}
|
|
}
|
|
|
|
Spell* currentSpell = vehicleBase->FindCurrentSpellBySpellId(SPELL_DREAM_FUNNEL);
|
|
if (healingTarget)
|
|
{
|
|
if (!currentSpell || currentSpell->m_targets.GetUnitTarget() != healingTarget)
|
|
{
|
|
float distance = vehicleBase->GetExactDist(healingTarget);
|
|
float range = 55.0f;
|
|
if (distance > range)
|
|
{
|
|
MotionMaster* mm = vehicleBase->GetMotionMaster();
|
|
mm->Clear(false);
|
|
mm->MoveForwards(healingTarget, distance - range - 10.0f);
|
|
vehicleBase->SendMovementFlagUpdate();
|
|
return false;
|
|
}
|
|
return CastDrakeSpellAction(healingTarget, SPELL_DREAM_FUNNEL, 0);
|
|
}
|
|
}
|
|
// Fill GCDs with Leeching Poison to refresh timer, rather than idling
|
|
if (!currentSpell)
|
|
{
|
|
return CastDrakeSpellAction(target, SPELL_LEECHING_POISON, 0);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool OccDrakeAttackAction::RubyDrakeAction(Unit* target)
|
|
{
|
|
Aura* evasiveCharges = vehicleBase->GetAura(SPELL_EVASIVE_CHARGES);
|
|
Aura* evasiveManeuvers = vehicleBase->GetAura(SPELL_EVASIVE_MANEUVERS);
|
|
|
|
if (evasiveCharges)
|
|
{
|
|
if (evasiveManeuvers &&
|
|
!vehicleBase->HasSpellCooldown(SPELL_MARTYR) &&
|
|
evasiveManeuvers->GetDuration() > 10000 &&
|
|
evasiveCharges->GetStackAmount() >= 5)
|
|
{
|
|
return CastDrakeSpellAction(vehicleBase, SPELL_MARTYR, 10000);
|
|
}
|
|
|
|
if (!vehicleBase->HasSpellCooldown(SPELL_EVASIVE_MANEUVERS) &&
|
|
evasiveCharges->GetStackAmount() >= 10)
|
|
{
|
|
return CastDrakeSpellAction(vehicleBase, SPELL_EVASIVE_MANEUVERS, 5000);
|
|
}
|
|
}
|
|
|
|
return CastDrakeSpellAction(target, SPELL_SEARING_WRATH, 0);
|
|
}
|
|
|
|
bool AvoidArcaneExplosionAction::Execute(Event event)
|
|
{
|
|
Unit* boss = AI_VALUE2(Unit*, "find target", "mage-lord urom");
|
|
if (!boss) { return false; }
|
|
|
|
const Position* closestPos = nullptr;
|
|
|
|
for (auto& position : uromSafePositions)
|
|
{
|
|
if (!closestPos || bot->GetExactDist(position) < bot->GetExactDist(closestPos))
|
|
{
|
|
closestPos = &position;
|
|
}
|
|
}
|
|
|
|
if (!closestPos) { return false; }
|
|
|
|
return MoveNear(bot->GetMapId(), closestPos->GetPositionX(), closestPos->GetPositionY(), closestPos->GetPositionZ(), 2.0f, MovementPriority::MOVEMENT_COMBAT);
|
|
}
|
|
|
|
bool TimeBombSpreadAction::Execute(Event event)
|
|
{
|
|
float radius = 10.0f;
|
|
float distanceExtra = 2.0f;
|
|
|
|
GuidVector members = AI_VALUE(GuidVector, "group members");
|
|
for (auto& member : members)
|
|
{
|
|
if (bot->GetGUID() == member)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Unit* unit = botAI->GetUnit(member);
|
|
if (unit && bot->GetExactDist2d(unit) < radius)
|
|
{
|
|
return MoveAway(unit, radius + distanceExtra - bot->GetExactDist2d(unit));
|
|
}
|
|
}
|
|
return false;
|
|
}
|