mirror of
https://github.com/mod-playerbots/mod-playerbots.git
synced 2026-01-13 00:58:33 +00:00
Closer the original solution, i dont wanna drift to much away without really good reason. At this point i still see NPC and bots moving through the levels or even falling out the levels here and there. I verified whether the MovePoint signature changes and related params itself in playerbots has anything todo with it, even when params are hardcoded the behavior remains. It could be deeper problem, but for now it seems core problem. Therefore i dont wanna change to much until the dust has settled a bit in core itself. I havent implemented moveTakeOff or moveLand which are basically responsible for the transitions phases between ground and air visa versa. I have version where i use takeOff for the animation, which means moving vertical. For now i am gonna leave for what it is. PS: also includes additional movement fix for AreaTriggerAction which we missed first time after the core update on movements. @Wishmaster117 Been testing and trying a lot in the background on find solutions and causes. The general solutions remains removed some tweaks, altered code here and there. With the general idea to keep closer to the original code for now at least. Testing: - Class abilities: Slow fall (priest), Flight Form (druid) : Green - BG: Green - Swimming and walking shallow waters: Green - Takeoff and land when following master: Green - Boat and zeppelins: Green - Flymount and ground walking: Green - Water Walking (shaman), Path of Frost (DK): Green - Water Walking (shaman), Path of Frost (DK) transisions; flying, swimming, water walking: Green Skipping pets when group water walking, path of frost, once core pathing changes has settled more i will add it. Which is easy but i dont wanna add more edge cases and code branches for now.
3013 lines
104 KiB
C++
3013 lines
104 KiB
C++
/*
|
||
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||
*/
|
||
|
||
#include "MovementActions.h"
|
||
|
||
#include <cmath>
|
||
#include <cstdlib>
|
||
#include <iomanip>
|
||
#include <string>
|
||
|
||
#include "Corpse.h"
|
||
#include "Event.h"
|
||
#include "FleeManager.h"
|
||
#include "G3D/Vector3.h"
|
||
#include "GameObject.h"
|
||
#include "Geometry.h"
|
||
#include "LastMovementValue.h"
|
||
#include "LootObjectStack.h"
|
||
#include "Map.h"
|
||
#include "MotionMaster.h"
|
||
#include "MoveSplineInitArgs.h"
|
||
#include "MovementGenerator.h"
|
||
#include "ObjectDefines.h"
|
||
#include "ObjectGuid.h"
|
||
#include "PathGenerator.h"
|
||
#include "PlayerbotAI.h"
|
||
#include "PlayerbotAIConfig.h"
|
||
#include "Playerbots.h"
|
||
#include "Position.h"
|
||
#include "PositionValue.h"
|
||
#include "Random.h"
|
||
#include "ServerFacade.h"
|
||
#include "SharedDefines.h"
|
||
#include "SpellAuraEffects.h"
|
||
#include "SpellInfo.h"
|
||
#include "Stances.h"
|
||
#include "TargetedMovementGenerator.h"
|
||
#include "Timer.h"
|
||
#include "Transport.h"
|
||
#include "Unit.h"
|
||
#include "Vehicle.h"
|
||
#include "WaypointMovementGenerator.h"
|
||
|
||
MovementAction::MovementAction(PlayerbotAI* botAI, std::string const name) : Action(botAI, name)
|
||
{
|
||
bot = botAI->GetBot();
|
||
}
|
||
|
||
void MovementAction::CreateWp(Player* wpOwner, float x, float y, float z, float o, uint32 entry, bool important)
|
||
{
|
||
float dist = wpOwner->GetDistance(x, y, z);
|
||
float delay = 1000.0f * dist / wpOwner->GetSpeed(MOVE_RUN) + sPlayerbotAIConfig->reactDelay;
|
||
|
||
// if (!important)
|
||
// delay *= 0.25;
|
||
|
||
Creature* wpCreature = wpOwner->SummonCreature(entry, x, y, z - 1, o, TEMPSUMMON_TIMED_DESPAWN, delay);
|
||
botAI->GetBot()->AddAura(246, wpCreature);
|
||
|
||
if (!important)
|
||
wpCreature->SetObjectScale(0.5f);
|
||
}
|
||
|
||
bool MovementAction::JumpTo(uint32 mapId, float x, float y, float z, MovementPriority priority)
|
||
{
|
||
UpdateMovementState();
|
||
if (!IsMovingAllowed(mapId, x, y, z))
|
||
{
|
||
return false;
|
||
}
|
||
if (IsDuplicateMove(mapId, x, y, z))
|
||
{
|
||
return false;
|
||
}
|
||
if (IsWaitingForLastMove(priority))
|
||
{
|
||
return false;
|
||
}
|
||
float botZ = bot->GetPositionZ();
|
||
float speed = bot->GetSpeed(MOVE_RUN);
|
||
MotionMaster& mm = *bot->GetMotionMaster();
|
||
mm.Clear();
|
||
mm.MoveJump(x, y, z, speed, speed, 1);
|
||
AI_VALUE(LastMovement&, "last movement").Set(mapId, x, y, z, bot->GetOrientation(), 1000, priority);
|
||
return true;
|
||
}
|
||
|
||
bool MovementAction::MoveNear(uint32 mapId, float x, float y, float z, float distance, MovementPriority priority)
|
||
{
|
||
float angle = GetFollowAngle();
|
||
return MoveTo(mapId, x + cos(angle) * distance, y + sin(angle) * distance, z, false, false, false, false, priority);
|
||
}
|
||
|
||
bool MovementAction::MoveNear(WorldObject* target, float distance, MovementPriority priority)
|
||
{
|
||
if (!target)
|
||
return false;
|
||
|
||
distance += target->GetCombatReach();
|
||
|
||
float x = target->GetPositionX();
|
||
float y = target->GetPositionY();
|
||
float z = target->GetPositionZ();
|
||
float followAngle = GetFollowAngle();
|
||
|
||
for (float angle = followAngle; angle <= followAngle + static_cast<float>(2 * M_PI);
|
||
angle += static_cast<float>(M_PI / 4.f))
|
||
{
|
||
float x = target->GetPositionX() + cos(angle) * distance;
|
||
float y = target->GetPositionY() + sin(angle) * distance;
|
||
float z = target->GetPositionZ();
|
||
|
||
if (!bot->IsWithinLOS(x, y, z))
|
||
continue;
|
||
|
||
bool moved = MoveTo(target->GetMapId(), x, y, z, false, false, false, false, priority);
|
||
if (moved)
|
||
return true;
|
||
}
|
||
|
||
// botAI->TellError("All paths not in LOS");
|
||
return false;
|
||
}
|
||
|
||
bool MovementAction::MoveToLOS(WorldObject* target, bool ranged)
|
||
{
|
||
if (!target)
|
||
return false;
|
||
|
||
// std::ostringstream out; out << "Moving to LOS!";
|
||
// bot->Say(out.str(), LANG_UNIVERSAL);
|
||
|
||
float x = target->GetPositionX();
|
||
float y = target->GetPositionY();
|
||
float z = target->GetPositionZ();
|
||
|
||
// Use standard PathGenerator to find a route.
|
||
PathGenerator path(bot);
|
||
path.CalculatePath(x, y, z, false);
|
||
PathType type = path.GetPathType();
|
||
if (type != PATHFIND_NORMAL && type != PATHFIND_INCOMPLETE)
|
||
return false;
|
||
|
||
if (!ranged)
|
||
return MoveTo((Unit*)target, target->GetCombatReach());
|
||
|
||
float dist = FLT_MAX;
|
||
PositionInfo dest;
|
||
|
||
if (!path.GetPath().empty())
|
||
{
|
||
for (auto& point : path.GetPath())
|
||
{
|
||
if (botAI->HasStrategy("debug move", BOT_STATE_NON_COMBAT))
|
||
CreateWp(bot, point.x, point.y, point.z, 0.0, 2334);
|
||
|
||
float distPoint = target->GetDistance(point.x, point.y, point.z);
|
||
if (distPoint < dist && target->IsWithinLOS(point.x, point.y, point.z + bot->GetCollisionHeight()))
|
||
{
|
||
dist = distPoint;
|
||
dest.Set(point.x, point.y, point.z, target->GetMapId());
|
||
|
||
if (ranged)
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (dest.isSet())
|
||
return MoveTo(dest.mapId, dest.x, dest.y, dest.z);
|
||
else
|
||
botAI->TellError("All paths not in LOS");
|
||
|
||
return false;
|
||
}
|
||
|
||
bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle, bool react, bool normal_only,
|
||
bool exact_waypoint, MovementPriority priority, bool lessDelay, bool backwards)
|
||
{
|
||
UpdateMovementState();
|
||
if (!IsMovingAllowed(mapId, x, y, z))
|
||
{
|
||
return false;
|
||
}
|
||
if (IsDuplicateMove(mapId, x, y, z))
|
||
{
|
||
return false;
|
||
}
|
||
if (IsWaitingForLastMove(priority))
|
||
{
|
||
return false;
|
||
}
|
||
|
||
bool generatePath = !bot->IsFlying() && !bot->isSwimming();
|
||
bool disableMoveSplinePath =
|
||
sPlayerbotAIConfig->disableMoveSplinePath >= 2 ||
|
||
(sPlayerbotAIConfig->disableMoveSplinePath == 1 && bot->InBattleground());
|
||
if (Vehicle* vehicle = bot->GetVehicle())
|
||
{
|
||
VehicleSeatEntry const* seat = vehicle->GetSeatForPassenger(bot);
|
||
Unit* vehicleBase = vehicle->GetBase();
|
||
generatePath = !vehicleBase || !vehicleBase->CanFly();
|
||
if (!vehicleBase || !seat || !seat->CanControl()) // is passenger and cant move anyway
|
||
return false;
|
||
|
||
float distance = vehicleBase->GetExactDist(x, y, z); // use vehicle distance, not bot
|
||
if (distance > 0.01f)
|
||
{
|
||
DoMovePoint(vehicleBase, x, y, z, generatePath, backwards);
|
||
float speed = backwards ? vehicleBase->GetSpeed(MOVE_RUN_BACK) : vehicleBase->GetSpeed(MOVE_RUN);
|
||
float delay = 1000.0f * (distance / speed);
|
||
if (lessDelay)
|
||
{
|
||
delay -= botAI->GetReactDelay();
|
||
}
|
||
delay = std::max(.0f, delay);
|
||
delay = std::min((float)sPlayerbotAIConfig->maxWaitForMove, delay);
|
||
AI_VALUE(LastMovement&, "last movement").Set(mapId, x, y, z, bot->GetOrientation(), delay, priority);
|
||
return true;
|
||
}
|
||
}
|
||
else if (exact_waypoint || disableMoveSplinePath || !generatePath)
|
||
{
|
||
float distance = bot->GetExactDist(x, y, z);
|
||
if (distance > 0.01f)
|
||
{
|
||
if (bot->IsSitState())
|
||
bot->SetStandState(UNIT_STAND_STATE_STAND);
|
||
|
||
// if (bot->IsNonMeleeSpellCast(true))
|
||
// {
|
||
// bot->CastStop();
|
||
// botAI->InterruptSpell();
|
||
// }
|
||
DoMovePoint(bot, x, y, z, generatePath, backwards);
|
||
float delay = 1000.0f * MoveDelay(distance, backwards);
|
||
if (lessDelay)
|
||
{
|
||
delay -= botAI->GetReactDelay();
|
||
}
|
||
delay = std::max(.0f, delay);
|
||
delay = std::min((float)sPlayerbotAIConfig->maxWaitForMove, delay);
|
||
AI_VALUE(LastMovement&, "last movement").Set(mapId, x, y, z, bot->GetOrientation(), delay, priority);
|
||
return true;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
float modifiedZ;
|
||
Movement::PointsArray path =
|
||
SearchForBestPath(x, y, z, modifiedZ, sPlayerbotAIConfig->maxMovementSearchTime, normal_only);
|
||
if (modifiedZ == INVALID_HEIGHT)
|
||
return false;
|
||
float distance = bot->GetExactDist(x, y, modifiedZ);
|
||
if (distance > 0.01f)
|
||
{
|
||
if (bot->IsSitState())
|
||
bot->SetStandState(UNIT_STAND_STATE_STAND);
|
||
|
||
// if (bot->IsNonMeleeSpellCast(true))
|
||
// {
|
||
// bot->CastStop();
|
||
// botAI->InterruptSpell();
|
||
// }
|
||
G3D::Vector3 endP = path.back();
|
||
DoMovePoint(bot, x, y, z, generatePath, backwards);
|
||
float delay = 1000.0f * MoveDelay(distance, backwards);
|
||
if (lessDelay)
|
||
{
|
||
delay -= botAI->GetReactDelay();
|
||
}
|
||
delay = std::max(.0f, delay);
|
||
delay = std::min((float)sPlayerbotAIConfig->maxWaitForMove, delay);
|
||
AI_VALUE(LastMovement&, "last movement").Set(mapId, x, y, z, bot->GetOrientation(), delay, priority);
|
||
return true;
|
||
}
|
||
}
|
||
|
||
return false;
|
||
//
|
||
// // LOG_DEBUG("playerbots", "IsMovingAllowed {}", IsMovingAllowed());
|
||
// bot->AddUnitMovementFlag()
|
||
|
||
// bool isVehicle = false;
|
||
// Unit* mover = bot;
|
||
// if (Vehicle* vehicle = bot->GetVehicle())
|
||
// {
|
||
// VehicleSeatEntry const* seat = vehicle->GetSeatForPassenger(bot);
|
||
// LOG_DEBUG("playerbots", "!seat || !seat->CanControl() {}", !seat || !seat->CanControl());
|
||
// if (!seat || !seat->CanControl())
|
||
// return false;
|
||
|
||
// isVehicle = true;
|
||
// mover = vehicle->GetBase();
|
||
// }
|
||
|
||
// bool detailedMove = botAI->AllowActivity(DETAILED_MOVE_ACTIVITY);
|
||
// if (!detailedMove)
|
||
// {
|
||
// time_t now = time(nullptr);
|
||
// if (AI_VALUE(LastMovement&, "last movement").nextTeleport > now) // We can not teleport yet. Wait.
|
||
// {
|
||
// LOG_DEBUG("playerbots", "AI_VALUE(LastMovement&, \"last movement\").nextTeleport > now");
|
||
// botAI->SetNextCheckDelay((AI_VALUE(LastMovement&, "last movement").nextTeleport - now) * 1000);
|
||
// return true;
|
||
// }
|
||
// }
|
||
|
||
// float minDist = sPlayerbotAIConfig->targetPosRecalcDistance; //Minium distance a bot should move.
|
||
// float maxDist = sPlayerbotAIConfig->reactDistance; //Maxium distance a bot can move in one single
|
||
// action. float originalZ = z; // save original destination height to check
|
||
// if bot needs to fly up
|
||
|
||
// bool generatePath = !bot->IsFlying() && !bot->HasUnitMovementFlag(MOVEMENTFLAG_SWIMMING) && !bot->IsInWater() &&
|
||
// !bot->IsUnderWater(); if (generatePath)
|
||
// {
|
||
// z += CONTACT_DISTANCE;
|
||
// mover->UpdateAllowedPositionZ(x, y, z);
|
||
// }
|
||
|
||
// if (!isVehicle && !IsMovingAllowed() && bot->isDead())
|
||
// {
|
||
// bot->StopMoving();
|
||
// LOG_DEBUG("playerbots", "!isVehicle && !IsMovingAllowed() && bot->isDead()");
|
||
// return false;
|
||
// }
|
||
|
||
// if (!isVehicle && bot->isMoving() && !IsMovingAllowed())
|
||
// {
|
||
// if (!bot->HasUnitState(UNIT_STATE_IN_FLIGHT))
|
||
// bot->StopMoving();
|
||
// LOG_DEBUG("playerbots", "!isVehicle && bot->isMoving() && !IsMovingAllowed()");
|
||
// return false;
|
||
// }
|
||
|
||
// LastMovement& lastMove = *context->GetValue<LastMovement&>("last movement");
|
||
|
||
// WorldPosition startPosition = WorldPosition(bot); //Current location of the bot
|
||
// WorldPosition endPosition = WorldPosition(mapId, x, y, z, 0); //The requested end location
|
||
// WorldPosition movePosition; //The actual end location
|
||
|
||
// float totalDistance = startPosition.distance(endPosition); //Total distance to where we want to go
|
||
// float maxDistChange = totalDistance * 0.1; //Maximum change between previous destination
|
||
// before needing a recalulation
|
||
|
||
// if (totalDistance < minDist)
|
||
// {
|
||
// if (lastMove.lastMoveShort.distance(endPosition) < maxDistChange)
|
||
// AI_VALUE(LastMovement&, "last movement").clear();
|
||
|
||
// mover->StopMoving();
|
||
// LOG_DEBUG("playerbots", "totalDistance < minDist");
|
||
// return false;
|
||
// }
|
||
|
||
// TravelPath movePath;
|
||
|
||
// if (lastMove.lastMoveShort.distance(endPosition) < maxDistChange &&
|
||
// startPosition.distance(lastMove.lastMoveShort) < maxDist) //The last short movement was to the same place we want
|
||
// to move now.
|
||
// movePosition = endPosition;
|
||
// else if (!lastMove.lastPath.empty() && lastMove.lastPath.getBack().distance(endPosition) < maxDistChange) //The
|
||
// last long movement was to the same place we want to move now.
|
||
// {
|
||
// movePath = lastMove.lastPath;
|
||
// }
|
||
// else
|
||
// {
|
||
// movePosition = endPosition;
|
||
|
||
// if (startPosition.getMapId() != endPosition.getMapId() || totalDistance > maxDist)
|
||
// {
|
||
// if (!sTravelNodeMap->getNodes().empty() && !bot->InBattleground())
|
||
// {
|
||
// if (sPlayerbotAIConfig->tweakValue)
|
||
// {
|
||
// if (lastMove.future.valid())
|
||
// {
|
||
// movePath = lastMove.future.get();
|
||
// }
|
||
// else
|
||
// {
|
||
// lastMove.future = std::async(&TravelNodeMap::getFullPath, startPosition, endPosition, bot);
|
||
// LOG_DEBUG("playerbots", "lastMove.future = std::async(&TravelNodeMap::getFullPath,
|
||
// startPosition, endPosition, bot);"); return true;
|
||
// }
|
||
// }
|
||
// else
|
||
// movePath = sTravelNodeMap->getFullPath(startPosition, endPosition, bot);
|
||
|
||
// if (movePath.empty())
|
||
// {
|
||
// //We have no path. Beyond 450yd the standard PathGenerator will probably move the wrong way.
|
||
// if (sServerFacade->IsDistanceGreaterThan(totalDistance, maxDist * 3))
|
||
// {
|
||
// movePath.clear();
|
||
// movePath.addPoint(endPosition);
|
||
// AI_VALUE(LastMovement&, "last movement").setPath(movePath);
|
||
|
||
// bot->StopMoving();
|
||
// if (botAI->HasStrategy("debug move", BOT_STATE_NON_COMBAT))
|
||
// botAI->TellMasterNoFacing("I have no path");
|
||
// LOG_DEBUG("playerbots", "sServerFacade->IsDistanceGreaterThan(totalDistance, maxDist * 3)");
|
||
// return false;
|
||
// }
|
||
|
||
// movePosition = endPosition;
|
||
// }
|
||
// }
|
||
// else
|
||
// {
|
||
// //Use standard PathGenerator to find a route.
|
||
// movePosition = endPosition;
|
||
// }
|
||
// }
|
||
// }
|
||
|
||
// if (movePath.empty() && movePosition.distance(startPosition) > maxDist)
|
||
// {
|
||
// //Use standard PathGenerator to find a route.
|
||
// PathGenerator path(mover);
|
||
// path.CalculatePath(movePosition.getX(), movePosition.getY(), movePosition.getZ(), false);
|
||
// PathType type = path.GetPathType();
|
||
// Movement::PointsArray const& points = path.GetPath();
|
||
// movePath.addPath(startPosition.fromPointsArray(points));
|
||
// }
|
||
|
||
// if (!movePath.empty())
|
||
// {
|
||
// if (movePath.makeShortCut(startPosition, maxDist))
|
||
// if (botAI->HasStrategy("debug move", BOT_STATE_NON_COMBAT))
|
||
// botAI->TellMasterNoFacing("Found a shortcut.");
|
||
|
||
// if (movePath.empty())
|
||
// {
|
||
// AI_VALUE(LastMovement&, "last movement").setPath(movePath);
|
||
|
||
// if (botAI->HasStrategy("debug move", BOT_STATE_NON_COMBAT))
|
||
// botAI->TellMasterNoFacing("Too far from path. Rebuilding.");
|
||
// LOG_DEBUG("playerbots", "movePath.empty()");
|
||
// return true;
|
||
// }
|
||
|
||
// TravelNodePathType pathType;
|
||
// uint32 entry;
|
||
// movePosition = movePath.getNextPoint(startPosition, maxDist, pathType, entry);
|
||
|
||
// if (pathType == TravelNodePathType::portal) // && !botAI->isRealPlayer())
|
||
// {
|
||
// //Log bot movement
|
||
// if (sPlayerbotAIConfig->hasLog("bot_movement.csv"))
|
||
// {
|
||
// WorldPosition telePos;
|
||
// if (entry)
|
||
// {
|
||
// if (AreaTriggerTeleport const* at = sObjectMgr->GetAreaTriggerTeleport(entry))
|
||
// telePos = WorldPosition(at->target_mapId, at->target_X, at->target_Y, at->target_Z,
|
||
// at->target_Orientation);
|
||
// }
|
||
// else
|
||
// telePos = movePosition;
|
||
|
||
// std::ostringstream out;
|
||
// out << sPlayerbotAIConfig->GetTimestampStr() << "+00,";
|
||
// out << bot->GetName() << ",";
|
||
// if (telePos && telePos.GetExactDist(movePosition) > 0.001)
|
||
// startPosition.printWKT({ startPosition, movePosition, telePos }, out, 1);
|
||
// else
|
||
// startPosition.printWKT({ startPosition, movePosition }, out, 1);
|
||
|
||
// out << std::to_string(bot->getRace()) << ",";
|
||
// out << std::to_string(bot->getClass()) << ",";
|
||
// out << bot->GetLevel() << ",";
|
||
// out << (entry ? -1 : entry);
|
||
|
||
// sPlayerbotAIConfig->log("bot_movement.csv", out.str().c_str());
|
||
// }
|
||
|
||
// if (entry)
|
||
// {
|
||
// AI_VALUE(LastMovement&, "last area trigger").lastAreaTrigger = entry;
|
||
// }
|
||
// else
|
||
// {
|
||
// LOG_DEBUG("playerbots", "!entry");
|
||
// return bot->TeleportTo(movePosition.getMapId(), movePosition.getX(), movePosition.getY(),
|
||
// movePosition.getZ(), movePosition.getO(), 0);
|
||
// }
|
||
// }
|
||
|
||
// if (pathType == TravelNodePathType::transport && entry)
|
||
// {
|
||
// if (!bot->GetTransport())
|
||
// {
|
||
// for (auto& transport : movePosition.getTransports(entry))
|
||
// if (movePosition.sqDistance2d(WorldPosition((WorldObject*)transport)) < 5 * 5)
|
||
// transport->AddPassenger(bot, true);
|
||
// }
|
||
// WaitForReach(100.0f);
|
||
// LOG_DEBUG("playerbots", "pathType == TravelNodePathType::transport && entry");
|
||
// return true;
|
||
// }
|
||
|
||
// if (pathType == TravelNodePathType::flightPath && entry)
|
||
// {
|
||
// if (TaxiPathEntry const* tEntry = sTaxiPathStore.LookupEntry(entry))
|
||
// {
|
||
// Creature* unit = nullptr;
|
||
|
||
// if (!bot->m_taxi.IsTaximaskNodeKnown(tEntry->from))
|
||
// {
|
||
// GuidVector npcs = AI_VALUE(GuidVector, "nearest npcs");
|
||
// for (GuidVector::iterator i = npcs.begin(); i != npcs.end(); i++)
|
||
// {
|
||
// Creature* unit = bot->GetNPCIfCanInteractWith(*i, UNIT_NPC_FLAG_FLIGHTMASTER);
|
||
// if (!unit)
|
||
// continue;
|
||
|
||
// bot->GetSession()->SendLearnNewTaxiNode(unit);
|
||
|
||
// unit->SetFacingTo(unit->GetAngle(bot));
|
||
// }
|
||
// }
|
||
|
||
// uint32 botMoney = bot->GetMoney();
|
||
// if (botAI->HasCheat(BotCheatMask::gold))
|
||
// {
|
||
// bot->SetMoney(10000000);
|
||
// }
|
||
|
||
// bool goTaxi = bot->ActivateTaxiPathTo({ tEntry->from, tEntry->to }, unit, 1);
|
||
|
||
// if (botAI->HasCheat(BotCheatMask::gold))
|
||
// bot->SetMoney(botMoney);
|
||
// LOG_DEBUG("playerbots", "goTaxi");
|
||
// return goTaxi;
|
||
// }
|
||
// }
|
||
|
||
// // if (pathType == TravelNodePathType::teleportSpell && entry)
|
||
// // {
|
||
// // if (entry == 8690)
|
||
// // {
|
||
// // if (!bot->HasSpellCooldown(8690))
|
||
// // {
|
||
// // return botAI->DoSpecificAction("hearthstone", Event("move action"));
|
||
// // }
|
||
// // else
|
||
// // {
|
||
// // movePath.clear();
|
||
// // AI_VALUE(LastMovement&, "last movement").setPath(movePath);
|
||
// // LOG_DEBUG("playerbots", "bot->HasSpellCooldown(8690)");
|
||
// // return false;
|
||
// // }
|
||
// // }
|
||
// // }
|
||
|
||
// //if (!isTransport && bot->GetTransport())
|
||
// // bot->GetTransport()->RemovePassenger(bot);
|
||
// }
|
||
|
||
// AI_VALUE(LastMovement&, "last movement").setPath(movePath);
|
||
|
||
// if (!movePosition || movePosition.getMapId() != bot->GetMapId())
|
||
// {
|
||
// movePath.clear();
|
||
// AI_VALUE(LastMovement&, "last movement").setPath(movePath);
|
||
|
||
// if (botAI->HasStrategy("debug move", BOT_STATE_NON_COMBAT))
|
||
// botAI->TellMasterNoFacing("No point. Rebuilding.");
|
||
// LOG_DEBUG("playerbots", "!movePosition || movePosition.getMapId() != bot->GetMapId()");
|
||
// return false;
|
||
// }
|
||
|
||
// if (movePosition.distance(startPosition) > maxDist)
|
||
// {
|
||
// //Use standard pathfinder to find a route.
|
||
// PathGenerator path(mover);
|
||
// path.CalculatePath(movePosition.getX(), movePosition.getY(), movePosition.getZ(), false);
|
||
// PathType type = path.GetPathType();
|
||
// Movement::PointsArray const& points = path.GetPath();
|
||
// movePath.addPath(startPosition.fromPointsArray(points));
|
||
// TravelNodePathType pathType;
|
||
// uint32 entry;
|
||
// movePosition = movePath.getNextPoint(startPosition, maxDist, pathType, entry);
|
||
// }
|
||
|
||
// if (movePosition == WorldPosition())
|
||
// {
|
||
// movePath.clear();
|
||
|
||
// AI_VALUE(LastMovement&, "last movement").setPath(movePath);
|
||
|
||
// if (botAI->HasStrategy("debug move", BOT_STATE_NON_COMBAT))
|
||
// botAI->TellMasterNoFacing("No point. Rebuilding.");
|
||
|
||
// return false;
|
||
// }
|
||
|
||
// //Visual waypoints
|
||
// if (botAI->HasStrategy("debug move", BOT_STATE_NON_COMBAT))
|
||
// {
|
||
// if (!movePath.empty())
|
||
// {
|
||
// float cx = x;
|
||
// float cy = y;
|
||
// float cz = z;
|
||
// for (auto i : movePath.getPath())
|
||
// {
|
||
// CreateWp(bot, i.point.getX(), i.point.getY(), i.point.getZ(), 0.f, 2334);
|
||
|
||
// cx = i.point.getX();
|
||
// cy = i.point.getY();
|
||
// cz = i.point.getZ();
|
||
// }
|
||
// }
|
||
// else
|
||
// CreateWp(bot, movePosition.getX(), movePosition.getY(), movePosition.getZ(), 0, 2334, true);
|
||
// }
|
||
|
||
// //Log bot movement
|
||
// if (sPlayerbotAIConfig->hasLog("bot_movement.csv") && lastMove.lastMoveShort.GetExactDist(movePosition) > 0.001)
|
||
// {
|
||
// std::ostringstream out;
|
||
// out << sPlayerbotAIConfig->GetTimestampStr() << "+00,";
|
||
// out << bot->GetName() << ",";
|
||
// startPosition.printWKT({ startPosition, movePosition }, out, 1);
|
||
// out << std::to_string(bot->getRace()) << ",";
|
||
// out << std::to_string(bot->getClass()) << ",";
|
||
// out << bot->GetLevel();
|
||
// out << 0;
|
||
|
||
// sPlayerbotAIConfig->log("bot_movement.csv", out.str().c_str());
|
||
// }
|
||
// // LOG_DEBUG("playerbots", "({}, {}) -> ({}, {})", startPosition.getX(), startPosition.getY(),
|
||
// movePosition.getX(), movePosition.getY()); if (!react)
|
||
// if (totalDistance > maxDist)
|
||
// WaitForReach(startPosition.distance(movePosition) - 10.0f);
|
||
// else
|
||
// WaitForReach(startPosition.distance(movePosition));
|
||
|
||
// if (!isVehicle)
|
||
// {
|
||
// bot->HandleEmoteCommand(0);
|
||
// if (bot->IsSitState())
|
||
// bot->SetStandState(UNIT_STAND_STATE_STAND);
|
||
|
||
// if (bot->IsNonMeleeSpellCast(true))
|
||
// {
|
||
// bot->CastStop();
|
||
// botAI->InterruptSpell();
|
||
// }
|
||
// }
|
||
|
||
// /* Why do we do this?
|
||
// if (lastMove.lastMoveShort.distance(movePosition) < minDist)
|
||
// {
|
||
// bot->StopMoving();
|
||
// bot->GetMotionMaster()->Clear();
|
||
// }
|
||
// */
|
||
|
||
// // Clean movement if not already moving the same way.
|
||
// // if (mover->GetMotionMaster()->GetCurrentMovementGeneratorType() != POINT_MOTION_TYPE)
|
||
// // {
|
||
// // mover->StopMoving();
|
||
// // mover->GetMotionMaster()->Clear();
|
||
// // }
|
||
// // else
|
||
// // {
|
||
// // mover->GetMotionMaster()->GetDestination(x, y, z);
|
||
// // if (movePosition.distance(WorldPosition(movePosition.getMapId(), x, y, z, 0)) > minDist)
|
||
// // {
|
||
// // mover->StopMoving();
|
||
// // mover->GetMotionMaster()->Clear();
|
||
// // }
|
||
// // }
|
||
|
||
// if (totalDistance > maxDist && !detailedMove && !botAI->HasPlayerNearby(&movePosition)) // Why walk if you can
|
||
// fly?
|
||
// {
|
||
// time_t now = time(nullptr);
|
||
|
||
// AI_VALUE(LastMovement&, "last movement").nextTeleport = now +
|
||
// (time_t)MoveDelay(startPosition.distance(movePosition)); LOG_DEBUG("playerbots", "totalDistance > maxDist &&
|
||
// !detailedMove && !botAI->HasPlayerNearby(&movePosition)"); return bot->TeleportTo(movePosition.getMapId(),
|
||
// movePosition.getX(), movePosition.getY(), movePosition.getZ(), startPosition.getAngleTo(movePosition));
|
||
// }
|
||
|
||
// // walk if master walks and is close
|
||
// bool masterWalking = false;
|
||
// if (botAI->GetMaster())
|
||
// {
|
||
// if (botAI->GetMaster()->m_movementInfo.HasMovementFlag(MOVEMENTFLAG_WALKING) &&
|
||
// sServerFacade->GetDistance2d(bot, botAI->GetMaster()) < 20.0f)
|
||
// masterWalking = true;
|
||
// }
|
||
|
||
// if (masterWalking)
|
||
// bot->SetWalk(true);
|
||
|
||
// bot->SendMovementFlagUpdate();
|
||
// // LOG_DEBUG("playerbots", "normal move? {} {} {}",
|
||
// !bot->HasAuraType(SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED) && !bot->HasAuraType(SPELL_AURA_FLY),
|
||
// // bot->HasUnitFlag(UNIT_FLAG_DISABLE_MOVE), bot->getStandState());
|
||
// if (!bot->HasAuraType(SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED) && !bot->HasAuraType(SPELL_AURA_FLY))
|
||
// {
|
||
// bot->SetWalk(masterWalking);
|
||
// bot->GetMotionMaster()->MovePoint(movePosition.getMapId(), movePosition.getX(), movePosition.getY(),
|
||
// movePosition.getZ(), generatePath); WaitForReach(startPosition.distance(movePosition));
|
||
// // LOG_DEBUG("playerbots", "Movepoint to ({}, {})", movePosition.getX(), movePosition.getY());
|
||
// }
|
||
// else
|
||
// {
|
||
// bool needFly = false;
|
||
// bool needLand = false;
|
||
// bool isFly = bot->IsFlying();
|
||
|
||
// if (!isFly && originalZ > bot->GetPositionZ() && (originalZ - bot->GetPositionZ()) > 5.0f)
|
||
// needFly = true;
|
||
|
||
// if (needFly && !isFly)
|
||
// {
|
||
// WorldPacket data(SMSG_SPLINE_MOVE_SET_FLYING, 9);
|
||
// data << bot->GetPackGUID();
|
||
// bot->SendMessageToSet(&data, true);
|
||
|
||
// if (!bot->m_movementInfo.HasMovementFlag(MOVEMENTFLAG_FLYING))
|
||
// bot->m_movementInfo.AddMovementFlag(MOVEMENTFLAG_FLYING);
|
||
|
||
// if (!bot->m_movementInfo.HasMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY))
|
||
// bot->m_movementInfo.AddMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY);
|
||
// }
|
||
|
||
// if (isFly)
|
||
// {
|
||
// float ground = bot->GetPositionZ();
|
||
// float height = bot->GetMap()->GetWaterOrGroundLevel(bot->GetPositionX(), bot->GetPositionY(),
|
||
// bot->GetPositionZ(), ground); if (bot->GetPositionZ() > originalZ && (bot->GetPositionZ() - originalZ
|
||
// < 5.0f) && (fabs(originalZ - ground) < 5.0f))
|
||
// needLand = true;
|
||
|
||
// if (needLand)
|
||
// {
|
||
// WorldPacket data(SMSG_SPLINE_MOVE_UNSET_FLYING, 9);
|
||
// data << bot->GetPackGUID();
|
||
// bot->SendMessageToSet(&data, true);
|
||
|
||
// if (bot->m_movementInfo.HasMovementFlag(MOVEMENTFLAG_FLYING))
|
||
// bot->m_movementInfo.RemoveMovementFlag(MOVEMENTFLAG_FLYING);
|
||
|
||
// if (bot->m_movementInfo.HasMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY))
|
||
// bot->m_movementInfo.RemoveMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY);
|
||
// }
|
||
// }
|
||
|
||
// bot->GetMotionMaster()->MovePoint(movePosition.getMapId(), Position(movePosition.getX(), movePosition.getY(),
|
||
// movePosition.getZ(), 0.f)); WaitForReach(startPosition.distance(movePosition)); LOG_DEBUG("playerbots",
|
||
// "Movepoint to ({}, {})", movePosition.getX(), movePosition.getY());
|
||
// }
|
||
|
||
// AI_VALUE(LastMovement&, "last movement").setShort(movePosition);
|
||
|
||
// if (!idle)
|
||
// ClearIdleState();
|
||
|
||
// LOG_DEBUG("playerbots", "return true in the end");
|
||
// return true;
|
||
}
|
||
|
||
bool MovementAction::MoveTo(WorldObject* target, float distance, MovementPriority priority)
|
||
{
|
||
if (!IsMovingAllowed(target))
|
||
return false;
|
||
|
||
float bx = bot->GetPositionX();
|
||
float by = bot->GetPositionY();
|
||
float bz = bot->GetPositionZ();
|
||
|
||
float tx = target->GetPositionX();
|
||
float ty = target->GetPositionY();
|
||
float tz = target->GetPositionZ();
|
||
|
||
float distanceToTarget = bot->GetDistance(target);
|
||
float angle = bot->GetAngle(target);
|
||
float needToGo = distanceToTarget - distance;
|
||
|
||
float maxDistance = sPlayerbotAIConfig->spellDistance;
|
||
if (needToGo > 0 && needToGo > maxDistance)
|
||
needToGo = maxDistance;
|
||
else if (needToGo < 0 && needToGo < -maxDistance)
|
||
needToGo = -maxDistance;
|
||
|
||
float dx = cos(angle) * needToGo + bx;
|
||
float dy = sin(angle) * needToGo + by;
|
||
float dz; // = std::max(bz, tz); // calc accurate z position to avoid stuck
|
||
if (distanceToTarget > CONTACT_DISTANCE)
|
||
{
|
||
dz = bz + (tz - bz) * (needToGo / distanceToTarget);
|
||
}
|
||
else
|
||
{
|
||
dz = tz;
|
||
}
|
||
return MoveTo(target->GetMapId(), dx, dy, dz, false, false, false, false, priority);
|
||
}
|
||
|
||
bool MovementAction::ReachCombatTo(Unit* target, float distance)
|
||
{
|
||
if (!IsMovingAllowed(target))
|
||
return false;
|
||
|
||
float bx = bot->GetPositionX();
|
||
float by = bot->GetPositionY();
|
||
float bz = bot->GetPositionZ();
|
||
|
||
float tx = target->GetPositionX();
|
||
float ty = target->GetPositionY();
|
||
float tz = target->GetPositionZ();
|
||
|
||
float targetOrientation = target->GetOrientation();
|
||
|
||
float deltaAngle = Position::NormalizeOrientation(targetOrientation - target->GetAngle(bot));
|
||
if (deltaAngle > M_PI)
|
||
deltaAngle -= 2.0f * M_PI; // -PI..PI
|
||
// if target is moving forward and moving far away, predict the position
|
||
bool behind = fabs(deltaAngle) > M_PI_2;
|
||
if (target->HasUnitMovementFlag(MOVEMENTFLAG_FORWARD) && behind)
|
||
{
|
||
float predictDis = std::min(3.0f, target->GetObjectSize() * 2);
|
||
tx += cos(target->GetOrientation()) * predictDis;
|
||
ty += sin(target->GetOrientation()) * predictDis;
|
||
if (!target->GetMap()->CheckCollisionAndGetValidCoords(target, target->GetPositionX(), target->GetPositionY(),
|
||
target->GetPositionZ(), tx, ty, tz))
|
||
{
|
||
tx = target->GetPositionX();
|
||
ty = target->GetPositionY();
|
||
tz = target->GetPositionZ();
|
||
}
|
||
}
|
||
float combatDistance = bot->GetCombatReach() + target->GetCombatReach();
|
||
distance += combatDistance;
|
||
|
||
if (bot->GetExactDist(tx, ty, tz) <= distance)
|
||
return false;
|
||
|
||
PathGenerator path(bot);
|
||
path.CalculatePath(tx, ty, tz, false);
|
||
PathType type = path.GetPathType();
|
||
int typeOk = PATHFIND_NORMAL | PATHFIND_INCOMPLETE | PATHFIND_SHORTCUT;
|
||
if (!(type & typeOk))
|
||
return false;
|
||
float shortenTo = distance;
|
||
|
||
// Avoid walking too far when moving towards each other
|
||
float disToGo = bot->GetExactDist(tx, ty, tz) - distance;
|
||
if (disToGo >= 6.0f)
|
||
shortenTo = disToGo / 2 + distance;
|
||
|
||
// if (bot->GetExactDist(tx, ty, tz) <= shortenTo)
|
||
// return false;
|
||
|
||
path.ShortenPathUntilDist(G3D::Vector3(tx, ty, tz), shortenTo);
|
||
G3D::Vector3 endPos = path.GetPath().back();
|
||
return MoveTo(target->GetMapId(), endPos.x, endPos.y, endPos.z, false, false, false, false,
|
||
MovementPriority::MOVEMENT_COMBAT, true);
|
||
}
|
||
|
||
float MovementAction::GetFollowAngle()
|
||
{
|
||
Player* master = GetMaster();
|
||
Group* group = master ? master->GetGroup() : bot->GetGroup();
|
||
if (!group)
|
||
return 0.0f;
|
||
|
||
uint32 index = 1;
|
||
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
||
{
|
||
if (ref->GetSource() == master)
|
||
continue;
|
||
|
||
if (ref->GetSource() == bot)
|
||
return 2 * M_PI / (group->GetMembersCount() - 1) * index;
|
||
|
||
++index;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
bool MovementAction::IsMovingAllowed(WorldObject* target)
|
||
{
|
||
if (!target)
|
||
return false;
|
||
|
||
if (bot->GetMapId() != target->GetMapId())
|
||
return false;
|
||
|
||
// float distance = sServerFacade->GetDistance2d(bot, target);
|
||
// if (!bot->InBattleground() && distance > sPlayerbotAIConfig->reactDistance)
|
||
// return false;
|
||
|
||
return IsMovingAllowed();
|
||
}
|
||
|
||
bool MovementAction::IsMovingAllowed(uint32 mapId, float x, float y, float z)
|
||
{
|
||
// removed sqrt as means distance limit was effectively 22500 (ReactDistance<63>)
|
||
// leaving it commented incase we find ReactDistance limit causes problems
|
||
// float distance = sqrt(bot->GetDistance(x, y, z));
|
||
|
||
// Remove react distance limit
|
||
// if (!bot->InBattleground())
|
||
// return false;
|
||
|
||
return IsMovingAllowed();
|
||
}
|
||
|
||
bool MovementAction::IsDuplicateMove(uint32 mapId, float x, float y, float z)
|
||
{
|
||
LastMovement& lastMove = *context->GetValue<LastMovement&>("last movement");
|
||
|
||
// heuristic 5s
|
||
if (lastMove.msTime + sPlayerbotAIConfig->maxWaitForMove < getMSTime() ||
|
||
lastMove.lastMoveShort.GetExactDist(x, y, z) > 0.01f)
|
||
return false;
|
||
|
||
return true;
|
||
}
|
||
|
||
bool MovementAction::IsWaitingForLastMove(MovementPriority priority)
|
||
{
|
||
LastMovement& lastMove = *context->GetValue<LastMovement&>("last movement");
|
||
|
||
if (priority > lastMove.priority)
|
||
return false;
|
||
|
||
// heuristic 5s
|
||
if (lastMove.lastdelayTime + lastMove.msTime > getMSTime())
|
||
return true;
|
||
|
||
return false;
|
||
}
|
||
|
||
bool MovementAction::IsMovingAllowed()
|
||
{
|
||
// do not allow if not vehicle driver
|
||
if (botAI->IsInVehicle() && !botAI->IsInVehicle(true))
|
||
return false;
|
||
|
||
if (bot->isFrozen() || bot->IsPolymorphed() || (bot->isDead() && !bot->HasPlayerFlag(PLAYER_FLAGS_GHOST)) ||
|
||
bot->IsBeingTeleported() || bot->HasRootAura() || bot->HasSpiritOfRedemptionAura() || bot->HasConfuseAura() ||
|
||
bot->IsCharmed() || bot->HasStunAura() || bot->IsInFlight() || bot->HasUnitState(UNIT_STATE_LOST_CONTROL))
|
||
return false;
|
||
|
||
if (bot->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_CONTROLLED) != NULL_MOTION_TYPE)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
// if (bot->HasUnitMovementFlag(MOVEMENTFLAG_FALLING))
|
||
// {
|
||
// return false;
|
||
// }
|
||
return bot->GetMotionMaster()->GetCurrentMovementGeneratorType() != FLIGHT_MOTION_TYPE;
|
||
}
|
||
|
||
bool MovementAction::Follow(Unit* target, float distance) { return Follow(target, distance, GetFollowAngle()); }
|
||
|
||
void MovementAction::UpdateMovementState()
|
||
{
|
||
// state flags
|
||
const float gLvlZ = bot->GetMapWaterOrGroundLevel(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ());
|
||
const bool onGround = bot->GetPositionZ() < gLvlZ + 1.f;
|
||
const bool wantsToFly = bot->HasIncreaseMountedFlightSpeedAura() || bot->HasFlyAura();
|
||
const auto master = botAI ? botAI->GetMaster() : nullptr; // real or not
|
||
const bool masterIsFlying = master && master->HasUnitMovementFlag(MOVEMENTFLAG_FLYING);
|
||
const bool isFlying = bot->HasUnitMovementFlag(MOVEMENTFLAG_FLYING);
|
||
const auto liquidState = bot->GetLiquidData().Status; // default LIQUID_MAP_NO_WATER
|
||
const bool isWaterArea = liquidState != LIQUID_MAP_NO_WATER;
|
||
const bool isUnderWater = liquidState == LIQUID_MAP_UNDER_WATER;
|
||
const bool isInWater = liquidState == LIQUID_MAP_IN_WATER;
|
||
const bool isWaterWalking = bot->HasUnitMovementFlag(MOVEMENTFLAG_WATERWALKING);
|
||
const bool isSwimming = bot->HasUnitMovementFlag(MOVEMENTFLAG_SWIMMING);
|
||
const bool wantsToWaterWalk = bot->HasWaterWalkAura();
|
||
const bool wantsToSwim = isInWater || isUnderWater;
|
||
|
||
// handle water state
|
||
if (isWaterArea)
|
||
{
|
||
// water walking
|
||
if (wantsToWaterWalk && !isWaterWalking && !isUnderWater && !isFlying)
|
||
{
|
||
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_SWIMMING);
|
||
bot->AddUnitMovementFlag(MOVEMENTFLAG_WATERWALKING);
|
||
bot->SendMovementFlagUpdate();
|
||
}
|
||
// swimming
|
||
else if (wantsToSwim && !isSwimming && !wantsToWaterWalk && !isFlying)
|
||
{
|
||
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_WATERWALKING);
|
||
bot->AddUnitMovementFlag(MOVEMENTFLAG_SWIMMING);
|
||
bot->SendMovementFlagUpdate();
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// reset flags, if not will inherit incorrect walk speed here and there
|
||
// when transistions between land and water.
|
||
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_SWIMMING);
|
||
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_WATERWALKING);
|
||
bot->SendMovementFlagUpdate();
|
||
}
|
||
|
||
// handle flying state
|
||
if (wantsToFly && !isFlying && masterIsFlying)
|
||
{
|
||
bot->AddUnitMovementFlag(MOVEMENTFLAG_FLYING);
|
||
bot->SendMovementFlagUpdate();
|
||
}
|
||
else if ((!wantsToFly || onGround) && isFlying)
|
||
{
|
||
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_FLYING);
|
||
bot->SendMovementFlagUpdate();
|
||
}
|
||
|
||
// See if the bot is currently slowed, rooted, or otherwise unable to move
|
||
bool isCurrentlyRestricted = bot->isFrozen() || bot->IsPolymorphed() || bot->HasRootAura() || bot->HasStunAura() ||
|
||
bot->HasConfuseAura() || bot->HasUnitState(UNIT_STATE_LOST_CONTROL);
|
||
|
||
// Detect if movement restrictions have been lifted
|
||
if (wasMovementRestricted && !isCurrentlyRestricted && bot->IsAlive())
|
||
{
|
||
// CC just ended - refresh movement state to ensure animations play correctly
|
||
bot->SendMovementFlagUpdate();
|
||
}
|
||
|
||
// Save current state for the next check
|
||
wasMovementRestricted = isCurrentlyRestricted;
|
||
|
||
// Temporary speed increase in group
|
||
// if (botAI->HasRealPlayerMaster())
|
||
// {
|
||
// bot->SetSpeedRate(MOVE_RUN, 1.1f);
|
||
// }
|
||
// else
|
||
// {
|
||
// bot->SetSpeedRate(MOVE_RUN, 1.0f);
|
||
// }
|
||
// check if target is not reachable (from Vmangos)
|
||
// if (bot->GetMotionMaster()->GetCurrentMovementGeneratorType() == CHASE_MOTION_TYPE && bot->CanNotReachTarget() &&
|
||
// !bot->InBattleground())
|
||
// {
|
||
// if (Unit* pTarget = sServerFacade->GetChaseTarget(bot))
|
||
// {
|
||
// if (!bot->IsWithinMeleeRange(pTarget) && pTarget->IsCreature())
|
||
// {
|
||
// float angle = bot->GetAngle(pTarget);
|
||
// float distance = 5.0f;
|
||
// float x = bot->GetPositionX() + cos(angle) * distance;
|
||
// float y = bot->GetPositionY() + sin(angle) * distance;
|
||
// float z = bot->GetPositionZ();
|
||
|
||
// z += CONTACT_DISTANCE;
|
||
// bot->UpdateAllowedPositionZ(x, y, z);
|
||
|
||
// bot->StopMoving();
|
||
// bot->GetMotionMaster()->Clear();
|
||
// bot->NearTeleportTo(x, y, z, bot->GetOrientation());
|
||
// //bot->GetMotionMaster()->MovePoint(bot->GetMapId(), x, y, z, FORCED_MOVEMENT_RUN, false);
|
||
// return;
|
||
// /*if (pTarget->IsCreature() && !bot->isMoving() && bot->IsWithinDist(pTarget, 10.0f))
|
||
// {
|
||
// // Cheating to prevent getting stuck because of bad mmaps.
|
||
// bot->StopMoving();
|
||
// bot->GetMotionMaster()->Clear();
|
||
// bot->GetMotionMaster()->MovePoint(bot->GetMapId(), pTarget->GetPosition(), FORCED_MOVEMENT_RUN,
|
||
// false); return;
|
||
// }*/
|
||
// }
|
||
// }
|
||
// }
|
||
|
||
// if ((bot->GetMotionMaster()->GetCurrentMovementGeneratorType() == FOLLOW_MOTION_TYPE ||
|
||
// bot->GetMotionMaster()->GetCurrentMovementGeneratorType() == POINT_MOTION_TYPE) && bot->CanNotReachTarget()
|
||
// && !bot->InBattleground())
|
||
// {
|
||
// if (Unit* pTarget = sServerFacade->GetChaseTarget(bot))
|
||
// {
|
||
// if (pTarget != botAI->GetGroupMaster())
|
||
// return;
|
||
|
||
// if (!bot->IsWithinMeleeRange(pTarget))
|
||
// {
|
||
// if (!bot->isMoving() && bot->IsWithinDist(pTarget, 10.0f))
|
||
// {
|
||
// // Cheating to prevent getting stuck because of bad mmaps.
|
||
// float angle = bot->GetAngle(pTarget);
|
||
// float distance = 5.0f;
|
||
// float x = bot->GetPositionX() + cos(angle) * distance;
|
||
// float y = bot->GetPositionY() + sin(angle) * distance;
|
||
// float z = bot->GetPositionZ();
|
||
|
||
// z += CONTACT_DISTANCE;
|
||
// bot->UpdateAllowedPositionZ(x, y, z);
|
||
|
||
// bot->StopMoving();
|
||
// bot->GetMotionMaster()->Clear();
|
||
// bot->NearTeleportTo(x, y, z, bot->GetOrientation());
|
||
// //bot->GetMotionMaster()->MovePoint(bot->GetMapId(), x, y, z, FORCED_MOVEMENT_RUN, false);
|
||
// return;
|
||
// }
|
||
// }
|
||
// }
|
||
// }
|
||
}
|
||
|
||
bool MovementAction::Follow(Unit* target, float distance, float angle)
|
||
{
|
||
UpdateMovementState();
|
||
|
||
if (!target)
|
||
return false;
|
||
|
||
if (!bot->InBattleground() && sServerFacade->IsDistanceLessOrEqualThan(sServerFacade->GetDistance2d(bot, target),
|
||
sPlayerbotAIConfig->followDistance))
|
||
{
|
||
// botAI->TellError("No need to follow");
|
||
return false;
|
||
}
|
||
|
||
/*
|
||
if (!bot->InBattleground()
|
||
&& sServerFacade->IsDistanceLessOrEqualThan(sServerFacade->GetDistance2d(bot, target->GetPositionX(),
|
||
target->GetPositionY()), sPlayerbotAIConfig->sightDistance)
|
||
&& abs(bot->GetPositionZ() - target->GetPositionZ()) >= sPlayerbotAIConfig->spellDistance &&
|
||
botAI->HasRealPlayerMaster()
|
||
&& (target->GetMapId() && bot->GetMapId() != target->GetMapId()))
|
||
{
|
||
bot->StopMoving();
|
||
bot->GetMotionMaster()->Clear();
|
||
|
||
float x = bot->GetPositionX();
|
||
float y = bot->GetPositionY();
|
||
float z = target->GetPositionZ();
|
||
if (target->GetMapId() && bot->GetMapId() != target->GetMapId())
|
||
{
|
||
if ((target->GetMap() && target->GetMap()->IsBattlegroundOrArena()) || (bot->GetMap() &&
|
||
bot->GetMap()->IsBattlegroundOrArena())) return false;
|
||
|
||
bot->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TELEPORTED | AURA_INTERRUPT_FLAG_CHANGE_MAP);
|
||
bot->TeleportTo(target->GetMapId(), x, y, z, bot->GetOrientation());
|
||
}
|
||
else
|
||
{
|
||
bot->Relocate(x, y, z, bot->GetOrientation());
|
||
}
|
||
|
||
AI_VALUE(LastMovement&, "last movement").Set(target);
|
||
ClearIdleState();
|
||
return true;
|
||
}
|
||
|
||
if (!IsMovingAllowed(target) && botAI->HasRealPlayerMaster())
|
||
{
|
||
if ((target->GetMap() && target->GetMap()->IsBattlegroundOrArena()) || (bot->GetMap() &&
|
||
bot->GetMap()->IsBattlegroundOrArena())) return false;
|
||
|
||
if (bot->isDead() && botAI->GetMaster()->IsAlive())
|
||
{
|
||
bot->ResurrectPlayer(1.0f, false);
|
||
botAI->TellMasterNoFacing("I live, again!");
|
||
}
|
||
else
|
||
botAI->TellError("I am stuck while following");
|
||
|
||
bot->CombatStop(true);
|
||
botAI->TellMasterNoFacing("I will there soon.");
|
||
bot->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TELEPORTED | AURA_INTERRUPT_FLAG_CHANGE_MAP);
|
||
bot->TeleportTo(target->GetMapId(), target->GetPositionX(), target->GetPositionY(), target->GetPositionZ(),
|
||
target->GetOrientation()); return false;
|
||
}
|
||
*/
|
||
|
||
// Move to target corpse if alive.
|
||
if (!target->IsAlive() && bot->IsAlive() && target->GetGUID().IsPlayer())
|
||
{
|
||
Player* pTarget = (Player*)target;
|
||
|
||
Corpse* corpse = pTarget->GetCorpse();
|
||
|
||
if (corpse)
|
||
{
|
||
WorldPosition botPos(bot);
|
||
WorldPosition cPos(corpse);
|
||
|
||
if (botPos.fDist(cPos) > sPlayerbotAIConfig->spellDistance)
|
||
return MoveTo(cPos.getMapId(), cPos.getX(), cPos.getY(), cPos.getZ());
|
||
}
|
||
}
|
||
|
||
if (sServerFacade->IsDistanceGreaterOrEqualThan(sServerFacade->GetDistance2d(bot, target),
|
||
sPlayerbotAIConfig->sightDistance))
|
||
{
|
||
if (target->GetGUID().IsPlayer())
|
||
{
|
||
Player* pTarget = (Player*)target;
|
||
|
||
PlayerbotAI* targetBotAI = GET_PLAYERBOT_AI(pTarget);
|
||
if (targetBotAI) // Try to move to where the bot is going if it is closer and in the same direction.
|
||
{
|
||
WorldPosition botPos(bot);
|
||
WorldPosition tarPos(target);
|
||
WorldPosition longMove =
|
||
targetBotAI->GetAiObjectContext()->GetValue<WorldPosition>("last long move")->Get();
|
||
|
||
if (longMove)
|
||
{
|
||
float lDist = botPos.fDist(longMove);
|
||
float tDist = botPos.fDist(tarPos);
|
||
float ang = botPos.getAngleBetween(tarPos, longMove);
|
||
if ((lDist * 1.5 < tDist && ang < static_cast<float>(M_PI) / 2) ||
|
||
target->HasUnitState(UNIT_STATE_IN_FLIGHT))
|
||
{
|
||
return MoveTo(longMove.getMapId(), longMove.getX(), longMove.getY(), longMove.getZ());
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (pTarget->HasUnitState(UNIT_STATE_IN_FLIGHT)) // Move to where the player is flying to.
|
||
{
|
||
TaxiPathNodeList const& tMap =
|
||
static_cast<FlightPathMovementGenerator*>(pTarget->GetMotionMaster()->top())->GetPath();
|
||
if (!tMap.empty())
|
||
{
|
||
auto tEnd = tMap.back();
|
||
if (tEnd)
|
||
return MoveTo(tEnd->mapid, tEnd->x, tEnd->y, tEnd->z);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if (!target->HasUnitState(UNIT_STATE_IN_FLIGHT))
|
||
return MoveTo(target, sPlayerbotAIConfig->followDistance);
|
||
}
|
||
|
||
if (sServerFacade->IsDistanceLessOrEqualThan(sServerFacade->GetDistance2d(bot, target),
|
||
sPlayerbotAIConfig->followDistance))
|
||
{
|
||
// botAI->TellError("No need to follow");
|
||
return false;
|
||
}
|
||
|
||
if (target->IsFriendlyTo(bot) && bot->IsMounted() && AI_VALUE(GuidVector, "all targets").empty())
|
||
distance += angle;
|
||
|
||
if (!bot->InBattleground() && sServerFacade->IsDistanceLessOrEqualThan(sServerFacade->GetDistance2d(bot, target),
|
||
sPlayerbotAIConfig->followDistance))
|
||
{
|
||
// botAI->TellError("No need to follow");
|
||
return false;
|
||
}
|
||
|
||
bot->HandleEmoteCommand(0);
|
||
|
||
if (bot->IsSitState())
|
||
bot->SetStandState(UNIT_STAND_STATE_STAND);
|
||
|
||
if (bot->IsNonMeleeSpellCast(true))
|
||
{
|
||
bot->CastStop();
|
||
botAI->InterruptSpell();
|
||
}
|
||
|
||
// AI_VALUE(LastMovement&, "last movement").Set(target);
|
||
ClearIdleState();
|
||
|
||
if (bot->GetMotionMaster()->GetCurrentMovementGeneratorType() == FOLLOW_MOTION_TYPE)
|
||
{
|
||
Unit* currentTarget = sServerFacade->GetChaseTarget(bot);
|
||
if (currentTarget && currentTarget->GetGUID() == target->GetGUID())
|
||
return false;
|
||
}
|
||
|
||
if (bot->GetMotionMaster()->GetCurrentMovementGeneratorType() != FOLLOW_MOTION_TYPE)
|
||
bot->GetMotionMaster()->Clear();
|
||
|
||
bot->GetMotionMaster()->MoveFollow(target, distance, angle);
|
||
return true;
|
||
}
|
||
|
||
bool MovementAction::ChaseTo(WorldObject* obj, float distance, float angle)
|
||
{
|
||
if (!IsMovingAllowed())
|
||
{
|
||
return false;
|
||
}
|
||
|
||
if (Vehicle* vehicle = bot->GetVehicle())
|
||
{
|
||
VehicleSeatEntry const* seat = vehicle->GetSeatForPassenger(bot);
|
||
if (!seat || !seat->CanControl())
|
||
return false;
|
||
|
||
// vehicle->GetMotionMaster()->Clear();
|
||
vehicle->GetBase()->GetMotionMaster()->MoveChase((Unit*)obj, 30.0f);
|
||
return true;
|
||
}
|
||
|
||
UpdateMovementState();
|
||
|
||
if (!bot->IsStandState())
|
||
bot->SetStandState(UNIT_STAND_STATE_STAND);
|
||
|
||
if (bot->IsNonMeleeSpellCast(true))
|
||
{
|
||
bot->CastStop();
|
||
botAI->InterruptSpell();
|
||
}
|
||
|
||
// bot->GetMotionMaster()->Clear();
|
||
bot->GetMotionMaster()->MoveChase((Unit*)obj, distance);
|
||
|
||
// TODO shouldnt this use "last movement" value?
|
||
WaitForReach(bot->GetExactDist2d(obj) - distance);
|
||
return true;
|
||
}
|
||
|
||
float MovementAction::MoveDelay(float distance, bool backwards)
|
||
{
|
||
float speed;
|
||
if (bot->isSwimming())
|
||
{
|
||
speed = backwards ? bot->GetSpeed(MOVE_SWIM_BACK) : bot->GetSpeed(MOVE_SWIM);
|
||
}
|
||
else if (bot->IsFlying())
|
||
{
|
||
speed = backwards ? bot->GetSpeed(MOVE_FLIGHT_BACK) : bot->GetSpeed(MOVE_FLIGHT);
|
||
}
|
||
else
|
||
{
|
||
speed = backwards ? bot->GetSpeed(MOVE_RUN_BACK) : bot->GetSpeed(MOVE_RUN);
|
||
}
|
||
float delay = distance / speed;
|
||
return delay;
|
||
}
|
||
|
||
// TODO should this be removed? (or modified to use "last movement" value?)
|
||
void MovementAction::WaitForReach(float distance)
|
||
{
|
||
float delay = 1000.0f * MoveDelay(distance);
|
||
|
||
if (delay > sPlayerbotAIConfig->maxWaitForMove)
|
||
delay = sPlayerbotAIConfig->maxWaitForMove;
|
||
|
||
Unit* target = *botAI->GetAiObjectContext()->GetValue<Unit*>("current target");
|
||
Unit* player = *botAI->GetAiObjectContext()->GetValue<Unit*>("enemy player target");
|
||
if ((player || target) && delay > sPlayerbotAIConfig->globalCoolDown)
|
||
delay = sPlayerbotAIConfig->globalCoolDown;
|
||
|
||
if (delay < 0)
|
||
delay = 0;
|
||
|
||
botAI->SetNextCheckDelay((uint32)delay);
|
||
}
|
||
|
||
// similiar to botAI->SetNextCheckDelay() but only stops movement
|
||
void MovementAction::SetNextMovementDelay(float delayMillis)
|
||
{
|
||
AI_VALUE(LastMovement&, "last movement")
|
||
.Set(bot->GetMapId(), bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), bot->GetOrientation(),
|
||
delayMillis, MovementPriority::MOVEMENT_FORCED);
|
||
}
|
||
|
||
bool MovementAction::Flee(Unit* target)
|
||
{
|
||
Player* master = GetMaster();
|
||
if (!target)
|
||
target = master;
|
||
|
||
if (!target)
|
||
return false;
|
||
|
||
if (!sPlayerbotAIConfig->fleeingEnabled)
|
||
return false;
|
||
|
||
if (!IsMovingAllowed())
|
||
{
|
||
botAI->TellError("I am stuck while fleeing");
|
||
return false;
|
||
}
|
||
|
||
bool foundFlee = false;
|
||
time_t lastFlee = AI_VALUE(LastMovement&, "last movement").lastFlee;
|
||
time_t now = time(0);
|
||
uint32 fleeDelay = urand(2, sPlayerbotAIConfig->returnDelay / 1000);
|
||
|
||
if (lastFlee)
|
||
{
|
||
if ((now - lastFlee) <= fleeDelay)
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
|
||
HostileReference* ref = target->GetThreatMgr().getCurrentVictim();
|
||
if (ref && ref->getTarget() == bot) // bot is target - try to flee to tank or master
|
||
{
|
||
if (Group* group = bot->GetGroup())
|
||
{
|
||
Unit* fleeTarget = nullptr;
|
||
float fleeDistance = sPlayerbotAIConfig->sightDistance;
|
||
|
||
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
|
||
{
|
||
Player* player = gref->GetSource();
|
||
if (!player || player == bot || !player->IsAlive())
|
||
continue;
|
||
|
||
if (botAI->IsTank(player))
|
||
{
|
||
float distanceToTank = sServerFacade->GetDistance2d(bot, player);
|
||
float distanceToTarget = sServerFacade->GetDistance2d(bot, target);
|
||
if (distanceToTank < fleeDistance)
|
||
{
|
||
fleeTarget = player;
|
||
fleeDistance = distanceToTank;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (fleeTarget)
|
||
foundFlee = MoveNear(fleeTarget);
|
||
|
||
if ((!fleeTarget || !foundFlee) && master)
|
||
{
|
||
foundFlee = MoveNear(master);
|
||
}
|
||
}
|
||
}
|
||
else // bot is not targeted, try to flee dps/healers
|
||
{
|
||
bool isHealer = botAI->IsHeal(bot);
|
||
bool isDps = !isHealer && !botAI->IsTank(bot);
|
||
bool isTank = botAI->IsTank(bot);
|
||
bool needHealer = !isHealer && AI_VALUE2(uint8, "health", "self target") < 50;
|
||
bool isRanged = botAI->IsRanged(bot);
|
||
|
||
Group* group = bot->GetGroup();
|
||
if (group)
|
||
{
|
||
Unit* fleeTarget = nullptr;
|
||
float fleeDistance = botAI->GetRange("shoot") * 1.5f;
|
||
Unit* spareTarget = nullptr;
|
||
float spareDistance = botAI->GetRange("shoot") * 2.0f;
|
||
std::vector<Unit*> possibleTargets;
|
||
|
||
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
|
||
{
|
||
Player* player = gref->GetSource();
|
||
if (!player || player == bot || !player->IsAlive())
|
||
continue;
|
||
|
||
if ((isHealer && botAI->IsHeal(player)) || needHealer)
|
||
{
|
||
float distanceToHealer = sServerFacade->GetDistance2d(bot, player);
|
||
float distanceToTarget = sServerFacade->GetDistance2d(player, target);
|
||
if (distanceToHealer < fleeDistance &&
|
||
distanceToTarget > (botAI->GetRange("shoot") / 2 + sPlayerbotAIConfig->followDistance) &&
|
||
(needHealer || player->IsWithinLOSInMap(target)))
|
||
{
|
||
fleeTarget = player;
|
||
fleeDistance = distanceToHealer;
|
||
possibleTargets.push_back(fleeTarget);
|
||
}
|
||
}
|
||
else if (isRanged && botAI->IsRanged(player))
|
||
{
|
||
float distanceToRanged = sServerFacade->GetDistance2d(bot, player);
|
||
float distanceToTarget = sServerFacade->GetDistance2d(player, target);
|
||
if (distanceToRanged < fleeDistance &&
|
||
distanceToTarget > (botAI->GetRange("shoot") / 2 + sPlayerbotAIConfig->followDistance) &&
|
||
player->IsWithinLOSInMap(target))
|
||
{
|
||
fleeTarget = player;
|
||
fleeDistance = distanceToRanged;
|
||
possibleTargets.push_back(fleeTarget);
|
||
}
|
||
}
|
||
// remember any group member in case no one else found
|
||
float distanceToFlee = sServerFacade->GetDistance2d(bot, player);
|
||
float distanceToTarget = sServerFacade->GetDistance2d(player, target);
|
||
if (distanceToFlee < spareDistance &&
|
||
distanceToTarget > (botAI->GetRange("shoot") / 2 + sPlayerbotAIConfig->followDistance) &&
|
||
player->IsWithinLOSInMap(target))
|
||
{
|
||
spareTarget = player;
|
||
spareDistance = distanceToFlee;
|
||
possibleTargets.push_back(fleeTarget);
|
||
}
|
||
}
|
||
|
||
if (!possibleTargets.empty())
|
||
fleeTarget = possibleTargets[urand(0, possibleTargets.size() - 1)];
|
||
|
||
if (!fleeTarget)
|
||
fleeTarget = spareTarget;
|
||
|
||
if (fleeTarget)
|
||
foundFlee = MoveNear(fleeTarget);
|
||
|
||
if ((!fleeTarget || !foundFlee) && master && master->IsAlive() && master->IsWithinLOSInMap(target))
|
||
{
|
||
float distanceToTarget = sServerFacade->GetDistance2d(master, target);
|
||
if (distanceToTarget > (botAI->GetRange("shoot") / 2 + sPlayerbotAIConfig->followDistance))
|
||
foundFlee = MoveNear(master);
|
||
}
|
||
}
|
||
}
|
||
|
||
if ((foundFlee || lastFlee) && bot->GetGroup())
|
||
{
|
||
if (!lastFlee)
|
||
{
|
||
AI_VALUE(LastMovement&, "last movement").lastFlee = now;
|
||
}
|
||
else
|
||
{
|
||
if ((now - lastFlee) > fleeDelay)
|
||
{
|
||
AI_VALUE(LastMovement&, "last movement").lastFlee = 0;
|
||
}
|
||
else
|
||
return false;
|
||
}
|
||
}
|
||
|
||
FleeManager manager(bot, botAI->GetRange("flee"), bot->GetAngle(target) + M_PI);
|
||
if (!manager.isUseful())
|
||
return false;
|
||
|
||
float rx, ry, rz;
|
||
if (!manager.CalculateDestination(&rx, &ry, &rz))
|
||
{
|
||
botAI->TellError("Nowhere to flee");
|
||
return false;
|
||
}
|
||
|
||
bool result = MoveTo(target->GetMapId(), rx, ry, rz);
|
||
|
||
if (result)
|
||
AI_VALUE(LastMovement&, "last movement").lastFlee = time(nullptr);
|
||
|
||
return result;
|
||
}
|
||
|
||
void MovementAction::ClearIdleState()
|
||
{
|
||
context->GetValue<time_t>("stay time")->Set(0);
|
||
context->GetValue<PositionMap&>("position")->Get()["random"].Reset();
|
||
}
|
||
|
||
bool MovementAction::MoveAway(Unit* target, float distance, bool backwards)
|
||
{
|
||
if (!target)
|
||
{
|
||
return false;
|
||
}
|
||
float init_angle = target->GetAngle(bot);
|
||
for (float delta = 0; delta <= M_PI / 2; delta += M_PI / 8)
|
||
{
|
||
float angle = init_angle + delta;
|
||
float dx = bot->GetPositionX() + cos(angle) * distance;
|
||
float dy = bot->GetPositionY() + sin(angle) * distance;
|
||
float dz = bot->GetPositionZ();
|
||
bool exact = true;
|
||
if (!bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(),
|
||
bot->GetPositionZ(), dx, dy, dz))
|
||
{
|
||
// disable prediction if position is invalid
|
||
dx = bot->GetPositionX() + cos(angle) * distance;
|
||
dy = bot->GetPositionY() + sin(angle) * distance;
|
||
dz = bot->GetPositionZ();
|
||
exact = false;
|
||
}
|
||
if (MoveTo(target->GetMapId(), dx, dy, dz, false, false, true, exact, MovementPriority::MOVEMENT_COMBAT, false,
|
||
backwards))
|
||
{
|
||
return true;
|
||
}
|
||
if (delta == 0)
|
||
{
|
||
continue;
|
||
}
|
||
exact = true;
|
||
angle = init_angle - delta;
|
||
dx = bot->GetPositionX() + cos(angle) * distance;
|
||
dy = bot->GetPositionY() + sin(angle) * distance;
|
||
dz = bot->GetPositionZ();
|
||
if (!bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(),
|
||
bot->GetPositionZ(), dx, dy, dz))
|
||
{
|
||
// disable prediction if position is invalid
|
||
dx = bot->GetPositionX() + cos(angle) * distance;
|
||
dy = bot->GetPositionY() + sin(angle) * distance;
|
||
dz = bot->GetPositionZ();
|
||
exact = false;
|
||
}
|
||
if (MoveTo(target->GetMapId(), dx, dy, dz, false, false, true, exact, MovementPriority::MOVEMENT_COMBAT, false,
|
||
backwards))
|
||
{
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
// just calculates average position of group and runs away from that position
|
||
bool MovementAction::MoveFromGroup(float distance)
|
||
{
|
||
if (Group* group = bot->GetGroup())
|
||
{
|
||
uint32 mapId = bot->GetMapId();
|
||
float closestDist = FLT_MAX;
|
||
float x = 0.0f;
|
||
float y = 0.0f;
|
||
uint32 count = 0;
|
||
|
||
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
|
||
{
|
||
Player* player = gref->GetSource();
|
||
if (!player || player == bot || !player->IsAlive() || player->GetMapId() != mapId)
|
||
continue;
|
||
float dist = bot->GetDistance2d(player);
|
||
if (closestDist > dist)
|
||
closestDist = dist;
|
||
x += player->GetPositionX();
|
||
y += player->GetPositionY();
|
||
count++;
|
||
}
|
||
|
||
if (count && closestDist < distance)
|
||
{
|
||
x /= count;
|
||
y /= count;
|
||
// x and y are now average position of the group members
|
||
float angle = bot->GetAngle(x, y) + M_PI;
|
||
return Move(angle, distance - closestDist);
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
bool MovementAction::Move(float angle, float distance)
|
||
{
|
||
float x = bot->GetPositionX() + cos(angle) * distance;
|
||
float y = bot->GetPositionY() + sin(angle) * distance;
|
||
|
||
// TODO do we need GetMapWaterOrGroundLevel() if we're using CheckCollisionAndGetValidCoords() ?
|
||
float z = bot->GetMapWaterOrGroundLevel(x, y, bot->GetPositionZ());
|
||
if (z == -100000.0f || z == -200000.0f)
|
||
z = bot->GetPositionZ();
|
||
if (!bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(),
|
||
bot->GetPositionZ(), x, y, z, false))
|
||
return false;
|
||
|
||
return MoveTo(bot->GetMapId(), x, y, z);
|
||
}
|
||
|
||
bool MovementAction::MoveInside(uint32 mapId, float x, float y, float z, float distance, MovementPriority priority)
|
||
{
|
||
if (bot->GetDistance2d(x, y) <= distance)
|
||
{
|
||
return false;
|
||
}
|
||
return MoveNear(mapId, x, y, z, distance, priority);
|
||
}
|
||
|
||
// float MovementAction::SearchBestGroundZForPath(float x, float y, float z, bool generatePath, float range, bool
|
||
// normal_only, float step)
|
||
// {
|
||
// if (!generatePath)
|
||
// {
|
||
// return z;
|
||
// }
|
||
// float min_length = 100000.0f;
|
||
// float current_z = INVALID_HEIGHT;
|
||
// float modified_z;
|
||
// float delta;
|
||
// for (delta = 0.0f; delta <= range / 2; delta += step) {
|
||
// modified_z = bot->GetMapWaterOrGroundLevel(x, y, z + delta);
|
||
// PathGenerator gen(bot);
|
||
// gen.CalculatePath(x, y, modified_z);
|
||
// if (gen.GetPathType() == PATHFIND_NORMAL && gen.getPathLength() < min_length)
|
||
// {
|
||
// min_length = gen.getPathLength();
|
||
// current_z = modified_z;
|
||
// if (abs(current_z - z) < 0.5f)
|
||
// {
|
||
// return current_z;
|
||
// }
|
||
// }
|
||
// }
|
||
// for (delta = -step; delta >= -range / 2; delta -= step) {
|
||
// modified_z = bot->GetMapWaterOrGroundLevel(x, y, z + delta);
|
||
// PathGenerator gen(bot);
|
||
// gen.CalculatePath(x, y, modified_z);
|
||
// if (gen.GetPathType() == PATHFIND_NORMAL && gen.getPathLength() < min_length)
|
||
// {
|
||
// min_length = gen.getPathLength();
|
||
// current_z = modified_z;
|
||
// if (abs(current_z - z) < 0.5f)
|
||
// return current_z;
|
||
// }
|
||
// }
|
||
// for (delta = range / 2 + step; delta <= range; delta += 2) {
|
||
// modified_z = bot->GetMapWaterOrGroundLevel(x, y, z + delta);
|
||
// PathGenerator gen(bot);
|
||
// gen.CalculatePath(x, y, modified_z);
|
||
// if (gen.GetPathType() == PATHFIND_NORMAL && gen.getPathLength() < min_length)
|
||
// {
|
||
// min_length = gen.getPathLength();
|
||
// current_z = modified_z;
|
||
// if (abs(current_z - z) < 0.5f)
|
||
// {
|
||
// return current_z;
|
||
// }
|
||
// }
|
||
// }
|
||
// if (current_z == INVALID_HEIGHT && normal_only)
|
||
// {
|
||
// return INVALID_HEIGHT;
|
||
// }
|
||
// if (current_z == INVALID_HEIGHT && !normal_only)
|
||
// {
|
||
// return z;
|
||
// }
|
||
// return current_z;
|
||
// }
|
||
|
||
const Movement::PointsArray MovementAction::SearchForBestPath(float x, float y, float z, float& modified_z,
|
||
int maxSearchCount, bool normal_only, float step)
|
||
{
|
||
bool found = false;
|
||
modified_z = INVALID_HEIGHT;
|
||
float tempZ = bot->GetMapHeight(x, y, z);
|
||
PathGenerator gen(bot);
|
||
gen.CalculatePath(x, y, tempZ);
|
||
Movement::PointsArray result = gen.GetPath();
|
||
float min_length = gen.getPathLength();
|
||
int typeOk = PATHFIND_NORMAL | PATHFIND_INCOMPLETE;
|
||
if ((gen.GetPathType() & typeOk) && abs(tempZ - z) < 0.5f)
|
||
{
|
||
modified_z = tempZ;
|
||
return result;
|
||
}
|
||
// Start searching
|
||
if (gen.GetPathType() & typeOk)
|
||
{
|
||
modified_z = tempZ;
|
||
found = true;
|
||
}
|
||
int count = 1;
|
||
for (float delta = step; count < maxSearchCount / 2 + 1; count++, delta += step)
|
||
{
|
||
tempZ = bot->GetMapHeight(x, y, z + delta);
|
||
if (tempZ == INVALID_HEIGHT)
|
||
{
|
||
continue;
|
||
}
|
||
PathGenerator gen(bot);
|
||
gen.CalculatePath(x, y, tempZ);
|
||
if ((gen.GetPathType() & typeOk) && gen.getPathLength() < min_length)
|
||
{
|
||
found = true;
|
||
min_length = gen.getPathLength();
|
||
result = gen.GetPath();
|
||
modified_z = tempZ;
|
||
}
|
||
}
|
||
for (float delta = -step; count < maxSearchCount; count++, delta -= step)
|
||
{
|
||
tempZ = bot->GetMapHeight(x, y, z + delta);
|
||
if (tempZ == INVALID_HEIGHT)
|
||
{
|
||
continue;
|
||
}
|
||
PathGenerator gen(bot);
|
||
gen.CalculatePath(x, y, tempZ);
|
||
if ((gen.GetPathType() & typeOk) && gen.getPathLength() < min_length)
|
||
{
|
||
found = true;
|
||
min_length = gen.getPathLength();
|
||
result = gen.GetPath();
|
||
modified_z = tempZ;
|
||
}
|
||
}
|
||
if (!found && normal_only)
|
||
{
|
||
modified_z = INVALID_HEIGHT;
|
||
return Movement::PointsArray{};
|
||
}
|
||
if (!found && !normal_only)
|
||
{
|
||
return result;
|
||
}
|
||
return result;
|
||
}
|
||
|
||
void MovementAction::DoMovePoint(Unit* unit, float x, float y, float z, bool generatePath, bool backwards)
|
||
{
|
||
if (!unit)
|
||
return;
|
||
|
||
MotionMaster* mm = unit->GetMotionMaster();
|
||
if (!mm)
|
||
return;
|
||
|
||
// enable flying
|
||
if (unit->HasUnitMovementFlag(MOVEMENTFLAG_FLYING))
|
||
{
|
||
unit->AddUnitMovementFlag(MOVEMENTFLAG_CAN_FLY);
|
||
unit->AddUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY);
|
||
}
|
||
else
|
||
{
|
||
unit->RemoveUnitMovementFlag(MOVEMENTFLAG_CAN_FLY);
|
||
unit->RemoveUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY);
|
||
}
|
||
|
||
// enable water walking
|
||
if (unit->HasUnitMovementFlag(MOVEMENTFLAG_WATERWALKING))
|
||
{
|
||
float gLvlZ = unit->GetMapWaterOrGroundLevel(unit->GetPositionX(), unit->GetPositionY(), unit->GetPositionZ());
|
||
unit->UpdatePosition(unit->GetPositionX(), unit->GetPositionY(), gLvlZ, false);
|
||
// z = gLvlZ; do not overwrite Z axex, otherwise you wont be able to steer the bots into swimming when water
|
||
// walking.
|
||
}
|
||
|
||
mm->Clear();
|
||
if (backwards)
|
||
{
|
||
mm->MovePointBackwards(
|
||
/*id*/ 0,
|
||
/*coords*/ x, y, z,
|
||
/*generatePath*/ generatePath,
|
||
/*forceDestination*/ false);
|
||
return;
|
||
}
|
||
else
|
||
{
|
||
mm->MovePoint(
|
||
/*id*/ 0,
|
||
/*coords*/ x, y, z,
|
||
/*forcedMovement*/ FORCED_MOVEMENT_NONE,
|
||
/*speed*/ 0.f,
|
||
/*orientation*/ 0.f,
|
||
/*generatePath*/ generatePath, // true => terrain path (2d mmap); false => straight spline (3d vmap)
|
||
/*forceDestination*/ false);
|
||
}
|
||
}
|
||
|
||
bool FleeAction::Execute(Event event)
|
||
{
|
||
return MoveAway(AI_VALUE(Unit*, "current target"), sPlayerbotAIConfig->fleeDistance, true);
|
||
}
|
||
|
||
bool FleeAction::isUseful()
|
||
{
|
||
if (bot->GetCurrentSpell(CURRENT_CHANNELED_SPELL) != nullptr)
|
||
{
|
||
return false;
|
||
}
|
||
Unit* target = AI_VALUE(Unit*, "current target");
|
||
if (target && target->IsInWorld() && !bot->IsWithinMeleeRange(target))
|
||
return false;
|
||
|
||
return true;
|
||
}
|
||
|
||
bool FleeWithPetAction::Execute(Event event)
|
||
{
|
||
if (Pet* pet = bot->GetPet())
|
||
{
|
||
botAI->PetFollow();
|
||
}
|
||
|
||
return Flee(AI_VALUE(Unit*, "current target"));
|
||
}
|
||
|
||
bool AvoidAoeAction::isUseful()
|
||
{
|
||
if (getMSTime() - moveInterval < lastMoveTimer)
|
||
{
|
||
return false;
|
||
}
|
||
GuidVector traps = AI_VALUE(GuidVector, "nearest trap with damage");
|
||
GuidVector triggers = AI_VALUE(GuidVector, "possible triggers");
|
||
return AI_VALUE(Aura*, "area debuff") || !traps.empty() || !triggers.empty();
|
||
}
|
||
|
||
bool AvoidAoeAction::Execute(Event event)
|
||
{
|
||
// Case #1: Aura with dynamic object (e.g. rain of fire)
|
||
if (AvoidAuraWithDynamicObj())
|
||
{
|
||
return true;
|
||
}
|
||
// Case #2: Trap game object with spell (e.g. lava bomb)
|
||
if (AvoidGameObjectWithDamage())
|
||
{
|
||
return true;
|
||
}
|
||
// Case #3: Trigger npc (e.g. Lesser shadow fissure)
|
||
if (AvoidUnitWithDamageAura())
|
||
{
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
bool AvoidAoeAction::AvoidAuraWithDynamicObj()
|
||
{
|
||
Aura* aura = AI_VALUE(Aura*, "area debuff");
|
||
if (!aura || aura->IsRemoved() || aura->IsExpired())
|
||
{
|
||
return false;
|
||
}
|
||
if (!aura->GetOwner() || !aura->GetOwner()->IsInWorld())
|
||
{
|
||
return false;
|
||
}
|
||
// Crash fix: maybe change owner due to check interval
|
||
if (aura->GetType() != DYNOBJ_AURA_TYPE)
|
||
{
|
||
return false;
|
||
}
|
||
const SpellInfo* spellInfo = aura->GetSpellInfo();
|
||
if (!spellInfo)
|
||
{
|
||
return false;
|
||
}
|
||
if (sPlayerbotAIConfig->aoeAvoidSpellWhitelist.find(spellInfo->Id) !=
|
||
sPlayerbotAIConfig->aoeAvoidSpellWhitelist.end())
|
||
return false;
|
||
|
||
DynamicObject* dynOwner = aura->GetDynobjOwner();
|
||
if (!dynOwner || !dynOwner->IsInWorld())
|
||
{
|
||
return false;
|
||
}
|
||
float radius = dynOwner->GetRadius();
|
||
if (!radius || radius > sPlayerbotAIConfig->maxAoeAvoidRadius)
|
||
return false;
|
||
if (bot->GetDistance(dynOwner) > radius)
|
||
{
|
||
return false;
|
||
}
|
||
std::ostringstream name;
|
||
name << spellInfo->SpellName[LOCALE_enUS]; // << "] (aura)";
|
||
if (FleePosition(dynOwner->GetPosition(), radius))
|
||
{
|
||
if (sPlayerbotAIConfig->tellWhenAvoidAoe && lastTellTimer < time(NULL) - 10)
|
||
{
|
||
lastTellTimer = time(NULL);
|
||
lastMoveTimer = getMSTime();
|
||
std::ostringstream out;
|
||
out << "I'm avoiding " << name.str() << " (" << spellInfo->Id << ")" << " Radius " << radius << " - [Aura]";
|
||
bot->Say(out.str(), LANG_UNIVERSAL);
|
||
}
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
bool AvoidAoeAction::AvoidGameObjectWithDamage()
|
||
{
|
||
GuidVector traps = AI_VALUE(GuidVector, "nearest trap with damage");
|
||
if (traps.empty())
|
||
{
|
||
return false;
|
||
}
|
||
for (ObjectGuid& guid : traps)
|
||
{
|
||
GameObject* go = botAI->GetGameObject(guid);
|
||
if (!go || !go->IsInWorld())
|
||
{
|
||
continue;
|
||
}
|
||
if (go->GetGoType() != GAMEOBJECT_TYPE_TRAP)
|
||
{
|
||
continue;
|
||
}
|
||
const GameObjectTemplate* goInfo = go->GetGOInfo();
|
||
if (!goInfo)
|
||
{
|
||
continue;
|
||
}
|
||
// 0 trap with no despawn after cast. 1 trap despawns after cast. 2 bomb casts on spawn.
|
||
if (goInfo->trap.type != 0)
|
||
continue;
|
||
|
||
uint32 spellId = goInfo->trap.spellId;
|
||
if (!spellId)
|
||
{
|
||
continue;
|
||
}
|
||
|
||
if (sPlayerbotAIConfig->aoeAvoidSpellWhitelist.find(spellId) !=
|
||
sPlayerbotAIConfig->aoeAvoidSpellWhitelist.end())
|
||
continue;
|
||
|
||
const SpellInfo* spellInfo = sSpellMgr->GetSpellInfo(spellId);
|
||
if (!spellInfo || spellInfo->IsPositive())
|
||
{
|
||
continue;
|
||
}
|
||
|
||
float radius = (float)goInfo->trap.diameter / 2 + go->GetCombatReach();
|
||
if (!radius || radius > sPlayerbotAIConfig->maxAoeAvoidRadius)
|
||
continue;
|
||
|
||
if (bot->GetDistance(go) > radius)
|
||
{
|
||
continue;
|
||
}
|
||
std::ostringstream name;
|
||
name << spellInfo->SpellName[LOCALE_enUS]; // << "] (object)";
|
||
if (FleePosition(go->GetPosition(), radius))
|
||
{
|
||
if (sPlayerbotAIConfig->tellWhenAvoidAoe && lastTellTimer < time(NULL) - 10)
|
||
{
|
||
lastTellTimer = time(NULL);
|
||
lastMoveTimer = getMSTime();
|
||
std::ostringstream out;
|
||
out << "I'm avoiding " << name.str() << " (" << spellInfo->Id << ")" << " Radius " << radius
|
||
<< " - [Trap]";
|
||
bot->Say(out.str(), LANG_UNIVERSAL);
|
||
}
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
bool AvoidAoeAction::AvoidUnitWithDamageAura()
|
||
{
|
||
GuidVector triggers = AI_VALUE(GuidVector, "possible triggers");
|
||
if (triggers.empty())
|
||
{
|
||
return false;
|
||
}
|
||
for (ObjectGuid& guid : triggers)
|
||
{
|
||
Unit* unit = botAI->GetUnit(guid);
|
||
if (!unit || !unit->IsInWorld())
|
||
{
|
||
continue;
|
||
}
|
||
if (!unit->HasUnitFlag(UNIT_FLAG_NOT_SELECTABLE))
|
||
{
|
||
return false;
|
||
}
|
||
Unit::AuraEffectList const& aurasPeriodicTriggerSpell =
|
||
unit->GetAuraEffectsByType(SPELL_AURA_PERIODIC_TRIGGER_SPELL);
|
||
Unit::AuraEffectList const& aurasPeriodicTriggerWithValueSpell =
|
||
unit->GetAuraEffectsByType(SPELL_AURA_PERIODIC_TRIGGER_SPELL_WITH_VALUE);
|
||
for (const Unit::AuraEffectList& list : {aurasPeriodicTriggerSpell, aurasPeriodicTriggerWithValueSpell})
|
||
{
|
||
for (auto i = list.begin(); i != list.end(); ++i)
|
||
{
|
||
AuraEffect* aurEff = *i;
|
||
const SpellInfo* spellInfo = aurEff->GetSpellInfo();
|
||
if (!spellInfo)
|
||
continue;
|
||
const SpellInfo* triggerSpellInfo =
|
||
sSpellMgr->GetSpellInfo(spellInfo->Effects[aurEff->GetEffIndex()].TriggerSpell);
|
||
if (!triggerSpellInfo)
|
||
continue;
|
||
if (sPlayerbotAIConfig->aoeAvoidSpellWhitelist.find(triggerSpellInfo->Id) !=
|
||
sPlayerbotAIConfig->aoeAvoidSpellWhitelist.end())
|
||
return false;
|
||
for (int j = 0; j < MAX_SPELL_EFFECTS; j++)
|
||
{
|
||
if (triggerSpellInfo->Effects[j].Effect == SPELL_EFFECT_SCHOOL_DAMAGE)
|
||
{
|
||
float radius = triggerSpellInfo->Effects[j].CalcRadius();
|
||
if (bot->GetDistance(unit) > radius)
|
||
{
|
||
break;
|
||
}
|
||
if (!radius || radius > sPlayerbotAIConfig->maxAoeAvoidRadius)
|
||
continue;
|
||
std::ostringstream name;
|
||
name << triggerSpellInfo->SpellName[LOCALE_enUS]; //<< "] (unit)";
|
||
if (FleePosition(unit->GetPosition(), radius))
|
||
{
|
||
if (sPlayerbotAIConfig->tellWhenAvoidAoe && lastTellTimer < time(NULL) - 10)
|
||
{
|
||
lastTellTimer = time(NULL);
|
||
lastMoveTimer = getMSTime();
|
||
std::ostringstream out;
|
||
out << "I'm avoiding " << name.str() << " (" << triggerSpellInfo->Id << ")"
|
||
<< " Radius " << radius << " - [Unit Trigger]";
|
||
bot->Say(out.str(), LANG_UNIVERSAL);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
Position MovementAction::BestPositionForMeleeToFlee(Position pos, float radius)
|
||
{
|
||
Unit* currentTarget = AI_VALUE(Unit*, "current target");
|
||
std::vector<CheckAngle> possibleAngles;
|
||
if (currentTarget)
|
||
{
|
||
// Normally, move to left or right is the best position
|
||
bool isTanking = (!currentTarget->isFrozen()
|
||
&& !currentTarget->HasRootAura()) && (currentTarget->GetVictim() == bot);
|
||
float angle = bot->GetAngle(currentTarget);
|
||
float angleLeft = angle + (float)M_PI / 2;
|
||
float angleRight = angle - (float)M_PI / 2;
|
||
possibleAngles.push_back({angleLeft, false});
|
||
possibleAngles.push_back({angleRight, false});
|
||
possibleAngles.push_back({angle, true});
|
||
if (isTanking)
|
||
{
|
||
possibleAngles.push_back({angle + (float)M_PI, false});
|
||
possibleAngles.push_back({bot->GetAngle(&pos) - (float)M_PI, false});
|
||
}
|
||
}
|
||
else
|
||
{
|
||
float angleTo = bot->GetAngle(&pos) - (float)M_PI;
|
||
possibleAngles.push_back({angleTo, false});
|
||
}
|
||
float farestDis = 0.0f;
|
||
Position bestPos;
|
||
for (CheckAngle& checkAngle : possibleAngles)
|
||
{
|
||
float angle = checkAngle.angle;
|
||
std::list<FleeInfo>& infoList = AI_VALUE(std::list<FleeInfo>&, "recently flee info");
|
||
if (!CheckLastFlee(angle, infoList))
|
||
{
|
||
continue;
|
||
}
|
||
bool strict = checkAngle.strict;
|
||
float fleeDis = std::min(radius + 1.0f, sPlayerbotAIConfig->fleeDistance);
|
||
float dx = bot->GetPositionX() + cos(angle) * fleeDis;
|
||
float dy = bot->GetPositionY() + sin(angle) * fleeDis;
|
||
float dz = bot->GetPositionZ();
|
||
if (!bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(),
|
||
bot->GetPositionZ(), dx, dy, dz))
|
||
{
|
||
continue;
|
||
}
|
||
Position fleePos{dx, dy, dz};
|
||
if (strict && currentTarget &&
|
||
fleePos.GetExactDist(currentTarget) - currentTarget->GetCombatReach() >
|
||
sPlayerbotAIConfig->tooCloseDistance &&
|
||
bot->IsWithinMeleeRange(currentTarget))
|
||
{
|
||
continue;
|
||
}
|
||
if (pos.GetExactDist(fleePos) > farestDis)
|
||
{
|
||
farestDis = pos.GetExactDist(fleePos);
|
||
bestPos = fleePos;
|
||
}
|
||
}
|
||
if (farestDis > 0.0f)
|
||
{
|
||
return bestPos;
|
||
}
|
||
return Position();
|
||
}
|
||
|
||
Position MovementAction::BestPositionForRangedToFlee(Position pos, float radius)
|
||
{
|
||
Unit* currentTarget = AI_VALUE(Unit*, "current target");
|
||
std::vector<CheckAngle> possibleAngles;
|
||
float angleToTarget = 0.0f;
|
||
float angleFleeFromCenter = bot->GetAngle(&pos) - (float)M_PI;
|
||
if (currentTarget)
|
||
{
|
||
// Normally, move to left or right is the best position
|
||
angleToTarget = bot->GetAngle(currentTarget);
|
||
float angleLeft = angleToTarget + (float)M_PI / 2;
|
||
float angleRight = angleToTarget - (float)M_PI / 2;
|
||
possibleAngles.push_back({angleLeft, false});
|
||
possibleAngles.push_back({angleRight, false});
|
||
possibleAngles.push_back({angleToTarget + (float)M_PI, true});
|
||
possibleAngles.push_back({angleToTarget, true});
|
||
possibleAngles.push_back({angleFleeFromCenter, true});
|
||
}
|
||
else
|
||
{
|
||
possibleAngles.push_back({angleFleeFromCenter, false});
|
||
}
|
||
float farestDis = 0.0f;
|
||
Position bestPos;
|
||
for (CheckAngle& checkAngle : possibleAngles)
|
||
{
|
||
float angle = checkAngle.angle;
|
||
std::list<FleeInfo>& infoList = AI_VALUE(std::list<FleeInfo>&, "recently flee info");
|
||
if (!CheckLastFlee(angle, infoList))
|
||
{
|
||
continue;
|
||
}
|
||
bool strict = checkAngle.strict;
|
||
float fleeDis = std::min(radius + 1.0f, sPlayerbotAIConfig->fleeDistance);
|
||
float dx = bot->GetPositionX() + cos(angle) * fleeDis;
|
||
float dy = bot->GetPositionY() + sin(angle) * fleeDis;
|
||
float dz = bot->GetPositionZ();
|
||
if (!bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(),
|
||
bot->GetPositionZ(), dx, dy, dz))
|
||
{
|
||
continue;
|
||
}
|
||
Position fleePos{dx, dy, dz};
|
||
if (strict && currentTarget &&
|
||
fleePos.GetExactDist(currentTarget) - currentTarget->GetCombatReach() > sPlayerbotAIConfig->spellDistance)
|
||
{
|
||
continue;
|
||
}
|
||
if (strict && currentTarget &&
|
||
fleePos.GetExactDist(currentTarget) - currentTarget->GetCombatReach() <
|
||
(sPlayerbotAIConfig->tooCloseDistance))
|
||
{
|
||
continue;
|
||
}
|
||
|
||
if (pos.GetExactDist(fleePos) > farestDis)
|
||
{
|
||
farestDis = pos.GetExactDist(fleePos);
|
||
bestPos = fleePos;
|
||
}
|
||
}
|
||
if (farestDis > 0.0f)
|
||
{
|
||
return bestPos;
|
||
}
|
||
return Position();
|
||
}
|
||
|
||
bool MovementAction::FleePosition(Position pos, float radius, uint32 minInterval)
|
||
{
|
||
std::list<FleeInfo>& infoList = AI_VALUE(std::list<FleeInfo>&, "recently flee info");
|
||
|
||
if (!infoList.empty() && infoList.back().timestamp + minInterval > getMSTime())
|
||
return false;
|
||
|
||
Position bestPos;
|
||
if (botAI->IsMelee(bot))
|
||
{
|
||
bestPos = BestPositionForMeleeToFlee(pos, radius);
|
||
}
|
||
else
|
||
{
|
||
bestPos = BestPositionForRangedToFlee(pos, radius);
|
||
}
|
||
if (bestPos != Position())
|
||
{
|
||
if (MoveTo(bot->GetMapId(), bestPos.GetPositionX(), bestPos.GetPositionY(), bestPos.GetPositionZ(), false,
|
||
false, true, false, MovementPriority::MOVEMENT_COMBAT))
|
||
{
|
||
uint32 curTS = getMSTime();
|
||
while (!infoList.empty())
|
||
{
|
||
if (infoList.size() > 10 || infoList.front().timestamp + 5000 < curTS)
|
||
{
|
||
infoList.pop_front();
|
||
}
|
||
else
|
||
{
|
||
break;
|
||
}
|
||
}
|
||
infoList.push_back({pos, radius, bot->GetAngle(&bestPos), curTS});
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
bool MovementAction::CheckLastFlee(float curAngle, std::list<FleeInfo>& infoList)
|
||
{
|
||
uint32 curTS = getMSTime();
|
||
curAngle = Position::NormalizeOrientation(curAngle);
|
||
while (!infoList.empty())
|
||
{
|
||
if (infoList.size() > 10 || infoList.front().timestamp + 5000 < curTS)
|
||
{
|
||
infoList.pop_front();
|
||
}
|
||
else
|
||
{
|
||
break;
|
||
}
|
||
}
|
||
for (FleeInfo& info : infoList)
|
||
{
|
||
// more than 5 sec
|
||
if (info.timestamp + 5000 < curTS)
|
||
{
|
||
continue;
|
||
}
|
||
float revAngle = Position::NormalizeOrientation(info.angle + M_PI);
|
||
// angle too close
|
||
if (fabs(revAngle - curAngle) < M_PI / 4)
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
bool CombatFormationMoveAction::isUseful()
|
||
{
|
||
if (getMSTime() - moveInterval < lastMoveTimer)
|
||
{
|
||
return false;
|
||
}
|
||
if (bot->GetCurrentSpell(CURRENT_CHANNELED_SPELL) != nullptr)
|
||
{
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
bool CombatFormationMoveAction::Execute(Event event)
|
||
{
|
||
float dis = AI_VALUE(float, "disperse distance");
|
||
if (dis <= 0.0f || (!bot->IsInCombat() && botAI->HasStrategy("stay", BotState::BOT_STATE_NON_COMBAT)) ||
|
||
(bot->IsInCombat() && botAI->HasStrategy("stay", BotState::BOT_STATE_COMBAT)))
|
||
return false;
|
||
Player* playerToLeave = NearestGroupMember(dis);
|
||
if (playerToLeave && bot->GetExactDist(playerToLeave) < dis)
|
||
{
|
||
if (FleePosition(playerToLeave->GetPosition(), dis))
|
||
{
|
||
lastMoveTimer = getMSTime();
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
Position CombatFormationMoveAction::AverageGroupPos(float dis, bool ranged, bool self)
|
||
{
|
||
float averageX = 0, averageY = 0, averageZ = 0;
|
||
int cnt = 0;
|
||
Group* group = bot->GetGroup();
|
||
if (!group)
|
||
{
|
||
return Position();
|
||
}
|
||
Group::MemberSlotList const& groupSlot = group->GetMemberSlots();
|
||
for (Group::member_citerator itr = groupSlot.begin(); itr != groupSlot.end(); itr++)
|
||
{
|
||
Player* member = ObjectAccessor::FindPlayer(itr->guid);
|
||
if (!member)
|
||
continue;
|
||
|
||
if (!self && member == bot)
|
||
continue;
|
||
|
||
if (ranged && !PlayerbotAI::IsRanged(member))
|
||
continue;
|
||
|
||
if (!member->IsAlive() || member->GetMapId() != bot->GetMapId() || member->IsCharmed() ||
|
||
sServerFacade->GetDistance2d(bot, member) > dis)
|
||
continue;
|
||
|
||
averageX += member->GetPositionX();
|
||
averageY += member->GetPositionY();
|
||
averageZ += member->GetPositionZ();
|
||
}
|
||
averageX /= cnt;
|
||
averageY /= cnt;
|
||
averageZ /= cnt;
|
||
return Position(averageX, averageY, averageZ);
|
||
}
|
||
|
||
float CombatFormationMoveAction::AverageGroupAngle(Unit* from, bool ranged, bool self)
|
||
{
|
||
Group* group = bot->GetGroup();
|
||
if (!from || !group)
|
||
{
|
||
return 0.0f;
|
||
}
|
||
// float average = 0.0f;
|
||
float sumX = 0.0f;
|
||
float sumY = 0.0f;
|
||
int cnt = 0;
|
||
Group::MemberSlotList const& groupSlot = group->GetMemberSlots();
|
||
for (Group::member_citerator itr = groupSlot.begin(); itr != groupSlot.end(); itr++)
|
||
{
|
||
Player* member = ObjectAccessor::FindPlayer(itr->guid);
|
||
if (!member)
|
||
continue;
|
||
|
||
if (!self && member == bot)
|
||
continue;
|
||
|
||
if (ranged && !PlayerbotAI::IsRanged(member))
|
||
continue;
|
||
|
||
if (!member->IsAlive() || member->GetMapId() != bot->GetMapId() || member->IsCharmed() ||
|
||
sServerFacade->GetDistance2d(bot, member) > sPlayerbotAIConfig->sightDistance)
|
||
continue;
|
||
|
||
cnt++;
|
||
sumX += member->GetPositionX() - from->GetPositionX();
|
||
sumY += member->GetPositionY() - from->GetPositionY();
|
||
}
|
||
if (cnt == 0)
|
||
return 0.0f;
|
||
|
||
// unnecessary division
|
||
// sumX /= cnt;
|
||
// sumY /= cnt;
|
||
|
||
return atan2(sumY, sumX);
|
||
}
|
||
|
||
Position CombatFormationMoveAction::GetNearestPosition(const std::vector<Position>& positions)
|
||
{
|
||
Position result;
|
||
for (const Position& pos : positions)
|
||
{
|
||
if (bot->GetExactDist(pos) < bot->GetExactDist(result))
|
||
result = pos;
|
||
}
|
||
return result;
|
||
}
|
||
|
||
Player* CombatFormationMoveAction::NearestGroupMember(float dis)
|
||
{
|
||
float nearestDis = 10000.0f;
|
||
Player* result = nullptr;
|
||
Group* group = bot->GetGroup();
|
||
if (!group)
|
||
{
|
||
return result;
|
||
}
|
||
Group::MemberSlotList const& groupSlot = group->GetMemberSlots();
|
||
for (Group::member_citerator itr = groupSlot.begin(); itr != groupSlot.end(); itr++)
|
||
{
|
||
Player* member = ObjectAccessor::FindPlayer(itr->guid);
|
||
if (!member || !member->IsAlive() || member == bot || member->GetMapId() != bot->GetMapId() ||
|
||
member->IsCharmed() || sServerFacade->GetDistance2d(bot, member) > dis)
|
||
continue;
|
||
if (nearestDis > bot->GetExactDist(member))
|
||
{
|
||
result = member;
|
||
nearestDis = bot->GetExactDist(member);
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
|
||
bool TankFaceAction::Execute(Event event)
|
||
{
|
||
Unit* target = AI_VALUE(Unit*, "current target");
|
||
if (!target)
|
||
return false;
|
||
|
||
if (!bot->GetGroup())
|
||
return false;
|
||
|
||
if (!bot->IsWithinMeleeRange(target) || target->isMoving())
|
||
return false;
|
||
|
||
if (!AI_VALUE2(bool, "has aggro", "current target"))
|
||
return false;
|
||
|
||
float averageAngle = AverageGroupAngle(target, true);
|
||
|
||
if (averageAngle == 0.0f)
|
||
return false;
|
||
|
||
float deltaAngle = Position::NormalizeOrientation(averageAngle - target->GetAngle(bot));
|
||
if (deltaAngle > M_PI)
|
||
deltaAngle -= 2.0f * M_PI; // -PI..PI
|
||
|
||
float tolerable = M_PI_2;
|
||
|
||
if (fabs(deltaAngle) > tolerable)
|
||
return false;
|
||
|
||
float goodAngle1 = Position::NormalizeOrientation(averageAngle + M_PI * 3 / 5);
|
||
float goodAngle2 = Position::NormalizeOrientation(averageAngle - M_PI * 3 / 5);
|
||
|
||
// if dist < bot->GetMeleeRange(target) / 2, target will move backward
|
||
float dist = std::max(bot->GetExactDist(target), bot->GetMeleeRange(target) / 2) - bot->GetCombatReach() -
|
||
target->GetCombatReach();
|
||
std::vector<Position> availablePos;
|
||
float x, y, z;
|
||
target->GetNearPoint(bot, x, y, z, 0.0f, dist, goodAngle1);
|
||
if (bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(),
|
||
bot->GetPositionZ(), x, y, z))
|
||
{
|
||
/// @todo: movement control now is a mess, prepare to rewrite
|
||
std::list<FleeInfo>& infoList = AI_VALUE(std::list<FleeInfo>&, "recently flee info");
|
||
Position pos(x, y, z);
|
||
float angle = bot->GetAngle(&pos);
|
||
if (CheckLastFlee(angle, infoList))
|
||
{
|
||
availablePos.push_back(Position(x, y, z));
|
||
}
|
||
}
|
||
target->GetNearPoint(bot, x, y, z, 0.0f, dist, goodAngle2);
|
||
if (bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(),
|
||
bot->GetPositionZ(), x, y, z))
|
||
{
|
||
std::list<FleeInfo>& infoList = AI_VALUE(std::list<FleeInfo>&, "recently flee info");
|
||
Position pos(x, y, z);
|
||
float angle = bot->GetAngle(&pos);
|
||
if (CheckLastFlee(angle, infoList))
|
||
{
|
||
availablePos.push_back(Position(x, y, z));
|
||
}
|
||
}
|
||
if (availablePos.empty())
|
||
return false;
|
||
Position nearest = GetNearestPosition(availablePos);
|
||
return MoveTo(bot->GetMapId(), nearest.GetPositionX(), nearest.GetPositionY(), nearest.GetPositionZ(), false, false,
|
||
false, true, MovementPriority::MOVEMENT_COMBAT);
|
||
}
|
||
|
||
bool RearFlankAction::isUseful()
|
||
{
|
||
Unit* target = AI_VALUE(Unit*, "current target");
|
||
if (!target)
|
||
return false;
|
||
|
||
// Need to double the front angle check to account for mirrored angle.
|
||
bool inFront = target->HasInArc(2.f * minAngle, bot);
|
||
// Rear check does not need to double this angle as the logic is inverted
|
||
// and we are subtracting from 2pi.
|
||
bool inRear = !target->HasInArc((2.f * M_PI) - maxAngle, bot);
|
||
|
||
return inFront || inRear;
|
||
}
|
||
|
||
bool RearFlankAction::Execute(Event event)
|
||
{
|
||
Unit* target = AI_VALUE(Unit*, "current target");
|
||
if (!target)
|
||
return false;
|
||
|
||
float angle = frand(minAngle, maxAngle);
|
||
float baseDistance = bot->GetMeleeRange(target) * 0.5f;
|
||
Position leftFlank = target->GetPosition();
|
||
Position rightFlank = target->GetPosition();
|
||
Position* destination = nullptr;
|
||
leftFlank.RelocatePolarOffset(angle, baseDistance + distance);
|
||
rightFlank.RelocatePolarOffset(-angle, baseDistance + distance);
|
||
|
||
if (bot->GetExactDist2d(leftFlank) < bot->GetExactDist2d(rightFlank))
|
||
{
|
||
destination = &leftFlank;
|
||
}
|
||
else
|
||
{
|
||
destination = &rightFlank;
|
||
}
|
||
|
||
return MoveTo(bot->GetMapId(), destination->GetPositionX(), destination->GetPositionY(),
|
||
destination->GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_COMBAT);
|
||
}
|
||
|
||
bool DisperseSetAction::Execute(Event event)
|
||
{
|
||
std::string const text = event.getParam();
|
||
if (text == "disable")
|
||
{
|
||
RESET_AI_VALUE(float, "disperse distance");
|
||
botAI->TellMasterNoFacing("Disable disperse");
|
||
return true;
|
||
}
|
||
if (text == "enable" || text == "reset")
|
||
{
|
||
if (botAI->IsMelee(bot))
|
||
{
|
||
SET_AI_VALUE(float, "disperse distance", DEFAULT_DISPERSE_DISTANCE_MELEE);
|
||
}
|
||
else
|
||
{
|
||
SET_AI_VALUE(float, "disperse distance", DEFAULT_DISPERSE_DISTANCE_RANGED);
|
||
}
|
||
float dis = AI_VALUE(float, "disperse distance");
|
||
std::ostringstream out;
|
||
out << "Enable disperse distance " << std::setprecision(2) << dis;
|
||
botAI->TellMasterNoFacing(out.str());
|
||
return true;
|
||
}
|
||
if (text == "increase")
|
||
{
|
||
float dis = AI_VALUE(float, "disperse distance");
|
||
std::ostringstream out;
|
||
if (dis <= 0.0f)
|
||
{
|
||
out << "Enable disperse first";
|
||
botAI->TellMasterNoFacing(out.str());
|
||
return true;
|
||
}
|
||
dis += 1.0f;
|
||
SET_AI_VALUE(float, "disperse distance", dis);
|
||
out << "Increase disperse distance to " << std::setprecision(2) << dis;
|
||
botAI->TellMasterNoFacing(out.str());
|
||
return true;
|
||
}
|
||
if (text == "decrease")
|
||
{
|
||
float dis = AI_VALUE(float, "disperse distance");
|
||
dis -= 1.0f;
|
||
if (dis <= 0.0f)
|
||
{
|
||
dis += 1.0f;
|
||
}
|
||
SET_AI_VALUE(float, "disperse distance", dis);
|
||
std::ostringstream out;
|
||
out << "Increase disperse distance to " << std::setprecision(2) << dis;
|
||
botAI->TellMasterNoFacing(out.str());
|
||
return true;
|
||
}
|
||
if (text.starts_with("set"))
|
||
{
|
||
float dis = -1.0f;
|
||
;
|
||
sscanf(text.c_str(), "set %f", &dis);
|
||
std::ostringstream out;
|
||
if (dis < 0 || dis > 100.0f)
|
||
{
|
||
out << "Invalid disperse distance " << std::setprecision(2) << dis;
|
||
}
|
||
else
|
||
{
|
||
SET_AI_VALUE(float, "disperse distance", dis);
|
||
out << "Set disperse distance to " << std::setprecision(2) << dis;
|
||
}
|
||
botAI->TellMasterNoFacing(out.str());
|
||
return true;
|
||
}
|
||
std::ostringstream out;
|
||
out << "Usage: disperse [enable | disable | increase | decrease | set {distance}]";
|
||
float dis = AI_VALUE(float, "disperse distance");
|
||
if (dis > 0.0f)
|
||
{
|
||
out << "(Current disperse distance: " << std::setprecision(2) << dis << ")";
|
||
}
|
||
botAI->TellMasterNoFacing(out.str());
|
||
return true;
|
||
}
|
||
|
||
bool RunAwayAction::Execute(Event event) { return Flee(AI_VALUE(Unit*, "master target")); }
|
||
|
||
bool MoveToLootAction::Execute(Event event)
|
||
{
|
||
LootObject loot = AI_VALUE(LootObject, "loot target");
|
||
if (!loot.IsLootPossible(bot))
|
||
return false;
|
||
|
||
return MoveNear(loot.GetWorldObject(bot), sPlayerbotAIConfig->contactDistance);
|
||
}
|
||
|
||
bool MoveOutOfEnemyContactAction::Execute(Event event)
|
||
{
|
||
Unit* target = AI_VALUE(Unit*, "current target");
|
||
if (!target)
|
||
return false;
|
||
|
||
return MoveTo(target, sPlayerbotAIConfig->contactDistance);
|
||
}
|
||
|
||
bool MoveOutOfEnemyContactAction::isUseful() { return AI_VALUE2(bool, "inside target", "current target"); }
|
||
|
||
bool SetFacingTargetAction::Execute(Event event)
|
||
{
|
||
Unit* target = AI_VALUE(Unit*, "current target");
|
||
if (!target)
|
||
return false;
|
||
|
||
if (bot->HasUnitState(UNIT_STATE_IN_FLIGHT))
|
||
return true;
|
||
|
||
sServerFacade->SetFacingTo(bot, target);
|
||
botAI->SetNextCheckDelay(sPlayerbotAIConfig->reactDelay);
|
||
return true;
|
||
}
|
||
|
||
bool SetFacingTargetAction::isUseful() { return !AI_VALUE2(bool, "facing", "current target"); }
|
||
|
||
bool SetFacingTargetAction::isPossible()
|
||
{
|
||
if (bot->isFrozen() || bot->IsPolymorphed() || (bot->isDead() && !bot->HasPlayerFlag(PLAYER_FLAGS_GHOST)) ||
|
||
bot->IsBeingTeleported() || bot->HasConfuseAura() || bot->IsCharmed() || bot->HasStunAura() ||
|
||
bot->IsInFlight() || bot->HasUnitState(UNIT_STATE_LOST_CONTROL))
|
||
return false;
|
||
|
||
return true;
|
||
}
|
||
|
||
bool SetBehindTargetAction::Execute(Event event)
|
||
{
|
||
Unit* target = AI_VALUE(Unit*, "current target");
|
||
if (!target)
|
||
return false;
|
||
|
||
if (target->GetVictim() == bot)
|
||
return false;
|
||
|
||
if (!bot->IsWithinMeleeRange(target) || target->isMoving())
|
||
return false;
|
||
|
||
float deltaAngle = Position::NormalizeOrientation(target->GetOrientation() - target->GetAngle(bot));
|
||
if (deltaAngle > M_PI)
|
||
deltaAngle -= 2.0f * M_PI; // -PI..PI
|
||
|
||
float tolerable = M_PI_2;
|
||
|
||
if (fabs(deltaAngle) > tolerable)
|
||
return false;
|
||
|
||
float goodAngle1 = Position::NormalizeOrientation(target->GetOrientation() + M_PI * 3 / 5);
|
||
float goodAngle2 = Position::NormalizeOrientation(target->GetOrientation() - M_PI * 3 / 5);
|
||
|
||
float dist = std::max(bot->GetExactDist(target), bot->GetMeleeRange(target) / 2) - bot->GetCombatReach() -
|
||
target->GetCombatReach();
|
||
std::vector<Position> availablePos;
|
||
float x, y, z;
|
||
target->GetNearPoint(bot, x, y, z, 0.0f, dist, goodAngle1);
|
||
if (bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(),
|
||
bot->GetPositionZ(), x, y, z))
|
||
{
|
||
/// @todo: movement control now is a mess, prepare to rewrite
|
||
std::list<FleeInfo>& infoList = AI_VALUE(std::list<FleeInfo>&, "recently flee info");
|
||
Position pos(x, y, z);
|
||
float angle = bot->GetAngle(&pos);
|
||
if (CheckLastFlee(angle, infoList))
|
||
{
|
||
availablePos.push_back(Position(x, y, z));
|
||
}
|
||
}
|
||
target->GetNearPoint(bot, x, y, z, 0.0f, dist, goodAngle2);
|
||
if (bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(),
|
||
bot->GetPositionZ(), x, y, z))
|
||
{
|
||
std::list<FleeInfo>& infoList = AI_VALUE(std::list<FleeInfo>&, "recently flee info");
|
||
Position pos(x, y, z);
|
||
float angle = bot->GetAngle(&pos);
|
||
if (CheckLastFlee(angle, infoList))
|
||
{
|
||
availablePos.push_back(Position(x, y, z));
|
||
}
|
||
}
|
||
if (availablePos.empty())
|
||
return false;
|
||
Position nearest = GetNearestPosition(availablePos);
|
||
return MoveTo(bot->GetMapId(), nearest.GetPositionX(), nearest.GetPositionY(), nearest.GetPositionZ(), false, false,
|
||
false, true, MovementPriority::MOVEMENT_COMBAT);
|
||
}
|
||
|
||
bool MoveOutOfCollisionAction::Execute(Event event)
|
||
{
|
||
float angle = M_PI * 2000 / frand(1.f, 1000.f);
|
||
float distance = sPlayerbotAIConfig->followDistance;
|
||
return MoveTo(bot->GetMapId(), bot->GetPositionX() + cos(angle) * distance,
|
||
bot->GetPositionY() + sin(angle) * distance, bot->GetPositionZ());
|
||
}
|
||
|
||
bool MoveOutOfCollisionAction::isUseful()
|
||
{
|
||
// do not avoid collision on vehicle
|
||
if (botAI->IsInVehicle())
|
||
return false;
|
||
|
||
return AI_VALUE2(bool, "collision", "self target") &&
|
||
botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest friendly players")->Get().size() < 15;
|
||
}
|
||
|
||
bool MoveRandomAction::Execute(Event event)
|
||
{
|
||
float distance = sPlayerbotAIConfig->tooCloseDistance + urand(10, 30);
|
||
|
||
Map* map = bot->GetMap();
|
||
for (int i = 0; i < 3; ++i)
|
||
{
|
||
float x = bot->GetPositionX();
|
||
float y = bot->GetPositionY();
|
||
float z = bot->GetPositionZ();
|
||
float angle = (float)rand_norm() * static_cast<float>(M_PI);
|
||
x += urand(0, distance) * cos(angle);
|
||
y += urand(0, distance) * sin(angle);
|
||
float ox = x;
|
||
float oy = y;
|
||
float oz = z;
|
||
if (!bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(),
|
||
bot->GetPositionZ(), x, y, z))
|
||
continue;
|
||
if (map->IsInWater(bot->GetPhaseMask(), x, y, z, bot->GetCollisionHeight()))
|
||
continue;
|
||
|
||
bool moved = MoveTo(bot->GetMapId(), x, y, z, false, false, false, true);
|
||
if (moved)
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
bool MoveRandomAction::isUseful() { return !AI_VALUE(GuidPosition, "rpg target"); }
|
||
|
||
bool MoveInsideAction::Execute(Event event) { return MoveInside(bot->GetMapId(), x, y, bot->GetPositionZ(), distance); }
|
||
|
||
bool RotateAroundTheCenterPointAction::Execute(Event event)
|
||
{
|
||
uint32 next_point = GetCurrWaypoint();
|
||
if (MoveTo(bot->GetMapId(), waypoints[next_point].first, waypoints[next_point].second, bot->GetPositionZ(), false,
|
||
false, false, false, MovementPriority::MOVEMENT_COMBAT))
|
||
{
|
||
call_counters += 1;
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
bool MoveFromGroupAction::Execute(Event event)
|
||
{
|
||
float distance = atoi(event.getParam().c_str());
|
||
if (!distance)
|
||
distance = 20.0f; // flee distance from config is too small for this
|
||
return MoveFromGroup(distance);
|
||
}
|
||
|
||
bool MoveAwayFromCreatureAction::Execute(Event event)
|
||
{
|
||
GuidVector targets = AI_VALUE(GuidVector, "nearest npcs");
|
||
Creature* nearestCreature = bot->FindNearestCreature(creatureId, range, alive);
|
||
|
||
// Find all creatures with the specified Id
|
||
std::vector<Unit*> creatures;
|
||
for (auto const& guid : targets)
|
||
{
|
||
Unit* unit = botAI->GetUnit(guid);
|
||
if (unit && (alive && unit->IsAlive()) && unit->GetEntry() == creatureId)
|
||
{
|
||
creatures.push_back(unit);
|
||
}
|
||
}
|
||
|
||
if (creatures.empty())
|
||
{
|
||
return false;
|
||
}
|
||
|
||
// Search for a safe position
|
||
const int directions = 8;
|
||
const float increment = 3.0f;
|
||
float bestX = bot->GetPositionX();
|
||
float bestY = bot->GetPositionY();
|
||
float bestZ = bot->GetPositionZ();
|
||
float maxSafetyScore = -1.0f;
|
||
|
||
for (int i = 0; i < directions; ++i)
|
||
{
|
||
float angle = (i * 2 * M_PI) / directions;
|
||
for (float distance = increment; distance <= 30.0f; distance += increment)
|
||
{
|
||
float moveX = bot->GetPositionX() + distance * cos(angle);
|
||
float moveY = bot->GetPositionY() + distance * sin(angle);
|
||
float moveZ = bot->GetPositionZ();
|
||
|
||
// Check creature distance constraints
|
||
bool isSafeFromCreatures = true;
|
||
float minCreatureDist = std::numeric_limits<float>::max();
|
||
for (Unit* creature : creatures)
|
||
{
|
||
float dist = creature->GetExactDist2d(moveX, moveY);
|
||
if (dist < range)
|
||
{
|
||
isSafeFromCreatures = false;
|
||
break;
|
||
}
|
||
if (dist < minCreatureDist)
|
||
{
|
||
minCreatureDist = dist;
|
||
}
|
||
}
|
||
|
||
if (isSafeFromCreatures && bot->IsWithinLOS(moveX, moveY, moveZ))
|
||
{
|
||
// A simple safety score: the minimum distance to any creature. Higher is better.
|
||
if (minCreatureDist > maxSafetyScore)
|
||
{
|
||
maxSafetyScore = minCreatureDist;
|
||
bestX = moveX;
|
||
bestY = moveY;
|
||
bestZ = moveZ;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Move to the best position found
|
||
if (maxSafetyScore > 0.0f)
|
||
{
|
||
return MoveTo(bot->GetMapId(), bestX, bestY, bestZ, false, false, false, false,
|
||
MovementPriority::MOVEMENT_COMBAT);
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
bool MoveAwayFromCreatureAction::isPossible() { return bot->CanFreeMove(); }
|
||
|
||
bool MoveAwayFromPlayerWithDebuffAction::Execute(Event event)
|
||
{
|
||
Player* closestPlayer = nullptr;
|
||
float minDistance = 0.0f;
|
||
|
||
Group* group = bot->GetGroup();
|
||
if (!group)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
std::vector<Player*> debuffedPlayers;
|
||
|
||
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
|
||
{
|
||
Player* player = gref->GetSource();
|
||
if (player && player->IsAlive() && player->HasAura(spellId))
|
||
{
|
||
debuffedPlayers.push_back(player);
|
||
}
|
||
}
|
||
|
||
if (debuffedPlayers.empty())
|
||
{
|
||
return false;
|
||
}
|
||
|
||
// Search for a safe position
|
||
const int directions = 8;
|
||
const float increment = 3.0f;
|
||
float bestX = bot->GetPositionX();
|
||
float bestY = bot->GetPositionY();
|
||
float bestZ = bot->GetPositionZ();
|
||
float maxSafetyScore = -1.0f;
|
||
|
||
for (int i = 0; i < directions; ++i)
|
||
{
|
||
float angle = (i * 2 * M_PI) / directions;
|
||
for (float distance = increment; distance <= (range + 5.0f); distance += increment)
|
||
{
|
||
float moveX = bot->GetPositionX() + distance * cos(angle);
|
||
float moveY = bot->GetPositionY() + distance * sin(angle);
|
||
float moveZ = bot->GetPositionZ();
|
||
|
||
// Check creature distance constraints
|
||
bool isSafeFromDebuffedPlayer = true;
|
||
float minDebuffedPlayerDistance = std::numeric_limits<float>::max();
|
||
for (Unit* debuffedPlayer : debuffedPlayers)
|
||
{
|
||
float dist = debuffedPlayer->GetExactDist2d(moveX, moveY);
|
||
if (dist < range)
|
||
{
|
||
isSafeFromDebuffedPlayer = false;
|
||
break;
|
||
}
|
||
if (dist < minDebuffedPlayerDistance)
|
||
{
|
||
minDebuffedPlayerDistance = dist;
|
||
}
|
||
}
|
||
|
||
if (isSafeFromDebuffedPlayer && bot->IsWithinLOS(moveX, moveY, moveZ))
|
||
{
|
||
// A simple safety score: the minimum distance to any debuffed player. Higher is better.
|
||
if (minDebuffedPlayerDistance > maxSafetyScore)
|
||
{
|
||
maxSafetyScore = minDebuffedPlayerDistance;
|
||
bestX = moveX;
|
||
bestY = moveY;
|
||
bestZ = moveZ;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Move to the best position found
|
||
if (maxSafetyScore > 0.0f)
|
||
{
|
||
return MoveTo(bot->GetMapId(), bestX, bestY, bestZ, false, false, false, false,
|
||
MovementPriority::MOVEMENT_COMBAT, true);
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
bool MoveAwayFromPlayerWithDebuffAction::isPossible() { return bot->CanFreeMove(); }
|