feat(Core/Movement): Improved pathfinding, collisions and movements (#4220)

Npc positioning
Implemented slope check to avoid unwanted climbing for some kind of movements (backwards, repositioning etc.)
Implemented backwards movement
Re-implemented circle repositioning algorithm (smartest than retail, but with the same feeling)
Fixed random position of summoned minions
Improved pet following movement. Also, they attack NPC from behind now. Thanks to @Footman

Swimming creatures
Fixed max_z coordinate for swimming creatures. Now only part of their body is allowed to be out of the water level
Fixed pathfinder for swimming creatures creating shortcuts for specific segments, now they swim underwater to reach the seashore instead of flying above the water level.
Creatures with water InhabitType but no swimming flag now, when not in combat, will walk on water depth instead of swimming. Thanks @jackpoz for the original code
UNIT_FLAG_SWIMMING in UpdateEnvironmentIfNeeded to show the swimming animation correctly when underwater
Implemented HasEnoughWater check to avoid swimming creatures to go where the water level is too low but also to properly enable swimming animation only when a creature has enough water to swim.

Walking creatures
Extended the DetourNavMeshQuery adding area cost based on walkability (slope angle + source height) to find better paths at runtime instead of completely remove them from mmaps
improve Z height in certain conditions (see #4205, #4203, #4247 )

Flying creatures
Rewriting of the hover system
Removed hacks and improved the UpdateEnvironmentIfNeeded. Now creatures can properly switch from flying to walk etc.
Spells
LOS on spell effect must be calculated on CollisionHeight and HitSpherePoint instead of position coords.
Improved position for object/creature spawned via spells
Improved checks for Fleeing movements (fear spells)

Other improvements
Implemented method to calculate the CollisionWidth from dbc (used by repositioning algorithm etc.)
Improved raycast and collision checks

Co-authored-by: Footman <p.alexej@freenet.de>
Co-authored-by: Helias <stefanoborzi32@gmail.com>
Co-authored-by: Francesco Borzì <borzifrancesco@gmail.com>
Co-authored-by: Kitzunu <24550914+Kitzunu@users.noreply.github.com>
This commit is contained in:
Yehonal
2021-02-01 01:34:27 +01:00
committed by GitHub
parent fcad2b56ae
commit c8f43d8584
79 changed files with 3025 additions and 2199 deletions

View File

@@ -90,7 +90,7 @@ void ConfusedMovementGenerator<T>::DoInitialize(T* unit)
template<>
void ConfusedMovementGenerator<Creature>::_InitSpecific(Creature* creature, bool& is_water_ok, bool& is_land_ok)
{
is_water_ok = creature->CanSwim();
is_water_ok = creature->CanEnterWater();
is_land_ok = creature->CanWalk();
}

View File

@@ -22,7 +22,7 @@ void EscortMovementGenerator<T>::DoInitialize(T* unit)
Movement::MoveSplineInit init(unit);
if (m_precomputedPath.size() == 2) // xinef: simple case, just call move to
init.MoveTo(m_precomputedPath[1].x, m_precomputedPath[1].y, m_precomputedPath[1].z);
init.MoveTo(m_precomputedPath[1].x, m_precomputedPath[1].y, m_precomputedPath[1].z, true);
else if (m_precomputedPath.size())
init.MovebyPath(m_precomputedPath);
@@ -66,7 +66,7 @@ bool EscortMovementGenerator<T>::DoUpdate(T* unit, uint32 /*diff*/)
if (m_precomputedPath.size() > 2)
init.MovebyPath(m_precomputedPath);
else if (m_precomputedPath.size() == 2)
init.MoveTo(m_precomputedPath[1].x, m_precomputedPath[1].y, m_precomputedPath[1].z);
init.MoveTo(m_precomputedPath[1].x, m_precomputedPath[1].y, m_precomputedPath[1].z, true);
}
init.Launch();

View File

@@ -30,18 +30,7 @@ void FleeingMovementGenerator<T>::_setTargetLocation(T* owner)
return;
float x, y, z;
if (!_getPoint(owner, x, y, z))
return;
// Add LOS check for target point
bool isInLOS = VMAP::VMapFactory::createOrGetVMapManager()->isInLineOfSight(owner->GetMapId(),
owner->GetPositionX(),
owner->GetPositionY(),
owner->GetPositionZ() + 2.0f,
x, y, z + 2.0f);
if (!isInLOS)
{
if (!_getPoint(owner, x, y, z)) {
i_nextCheckTime.Reset(100);
return;
}
@@ -60,12 +49,13 @@ bool FleeingMovementGenerator<T>::_getPoint(T* owner, float& x, float& y, float&
if (!owner)
return false;
const Map* _map = owner->GetBaseMap();
x = owner->GetPositionX();
y = owner->GetPositionY();
z = owner->GetPositionZ();
float temp_x, temp_y, angle;
const Map* _map = owner->GetBaseMap();
// primitive path-finding
for (uint8 i = 0; i < 18; ++i)
{
@@ -151,40 +141,25 @@ bool FleeingMovementGenerator<T>::_getPoint(T* owner, float& x, float& y, float&
temp_x = x + distance * cos(angle);
temp_y = y + distance * sin(angle);
acore::NormalizeMapCoord(temp_x);
acore::NormalizeMapCoord(temp_y);
if (owner->IsWithinLOS(temp_x, temp_y, z))
{
bool is_water_now = _map->IsInWater(x, y, z);
float temp_z = z;
if (is_water_now && _map->IsInWater(temp_x, temp_y, z))
if (!_map->CanReachPositionAndGetValidCoords(owner, temp_x, temp_y, temp_z, true, true))
{
break;
}
if (!(temp_z - z) || distance / fabs(temp_z - z) > 1.0f)
{
float temp_z_left = _map->GetHeight(owner->GetPhaseMask(), temp_x + 1.0f * cos(angle + static_cast<float>(M_PI / 2)), temp_y + 1.0f * sin(angle + static_cast<float>(M_PI / 2)), z, true);
float temp_z_right = _map->GetHeight(owner->GetPhaseMask(), temp_x + 1.0f * cos(angle - static_cast<float>(M_PI / 2)), temp_y + 1.0f * sin(angle - static_cast<float>(M_PI / 2)), z, true);
if (fabs(temp_z_left - temp_z) < 1.2f && fabs(temp_z_right - temp_z) < 1.2f)
{
// use new values
x = temp_x;
y = temp_y;
z = temp_z;
return true;
}
float new_z = _map->GetHeight(owner->GetPhaseMask(), temp_x, temp_y, z, true);
if (new_z <= INVALID_HEIGHT || fabs(z - new_z) > 3.0f)
continue;
bool is_water_next = _map->IsInWater(temp_x, temp_y, new_z);
if ((is_water_now && !is_water_next && !is_land_ok) || (!is_water_now && is_water_next && !is_water_ok))
continue;
if (!(new_z - z) || distance / fabs(new_z - z) > 1.0f)
{
float new_z_left = _map->GetHeight(owner->GetPhaseMask(), temp_x + 1.0f * cos(angle + static_cast<float>(M_PI / 2)), temp_y + 1.0f * sin(angle + static_cast<float>(M_PI / 2)), z, true);
float new_z_right = _map->GetHeight(owner->GetPhaseMask(), temp_x + 1.0f * cos(angle - static_cast<float>(M_PI / 2)), temp_y + 1.0f * sin(angle - static_cast<float>(M_PI / 2)), z, true);
if (fabs(new_z_left - new_z) < 1.2f && fabs(new_z_right - new_z) < 1.2f)
{
x = temp_x;
y = temp_y;
z = new_z;
return true;
}
}
}
}
i_to_distance_from_caster = 0.0f;
@@ -322,7 +297,7 @@ void FleeingMovementGenerator<Creature>::_Init(Creature* owner)
return;
//owner->SetTargetGuid(ObjectGuid());
is_water_ok = owner->CanSwim();
is_water_ok = owner->CanEnterWater();
is_land_ok = owner->CanWalk();
}

View File

@@ -26,7 +26,10 @@ void HomeMovementGenerator<Creature>::DoFinalize(Creature* owner)
owner->LoadCreaturesAddon(true);
owner->AI()->JustReachedHome();
}
owner->m_targetsNotAcceptable.clear();
if (!owner->HasSwimmingFlagOutOfCombat())
owner->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SWIMMING);
owner->UpdateEnvironmentIfNeeded(2);
}
@@ -51,6 +54,7 @@ void HomeMovementGenerator<Creature>::_setTargetLocation(Creature* owner)
init.SetFacing(o);
}
owner->UpdateAllowedPositionZ(x, y, z);
init.MoveTo(x, y, z, MMAP::MMapFactory::IsPathfindingEnabled(owner->FindMap()), true);
init.SetWalk(false);
init.Launch();

File diff suppressed because it is too large Load Diff

View File

@@ -1,18 +1,7 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license: https://github.com/azerothcore/azerothcore-wotlk/blob/master/LICENSE-GPL2
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
* Copyright (C) 2008-2016 TrinityCore <http://www.trinitycore.org/>
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
*/
#ifndef _PATH_GENERATOR_H
@@ -22,138 +11,164 @@
#include "DetourNavMesh.h"
#include "DetourNavMeshQuery.h"
#include "MoveSplineInitArgs.h"
#include <G3D/Vector3.h>
#include "MMapFactory.h"
#include "MMapManager.h"
class Unit;
class WorldObject;
// 74*4.0f=296y number_of_points*interval = max_path_len
// 74*4.0f=296y number_of_points*interval = max_path_len
// this is way more than actual evade range
// I think we can safely cut those down even more
#define MAX_PATH_LENGTH 74
#define MAX_POINT_PATH_LENGTH 74
#define MAX_PATH_LENGTH 74
#define MAX_POINT_PATH_LENGTH 74
#define SMOOTH_PATH_STEP_SIZE 4.0f
#define SMOOTH_PATH_SLOP 0.3f
#define ALLOWED_DIST_FROM_POLY 2.5f
#define ADDED_Z_FOR_POLY_LOOKUP 0.3f
#define SMOOTH_PATH_STEP_SIZE 4.0f
#define SMOOTH_PATH_SLOP 0.3f
#define DISALLOW_TIME_AFTER_FAIL 3 // secs
#define MAX_FIXABLE_Z_ERROR 7.0f
#define VERTEX_SIZE 3
#define INVALID_POLYREF 0
enum PathType
{
PATHFIND_BLANK = 0x00, // path not built yet
PATHFIND_NORMAL = 0x01, // normal path
PATHFIND_SHORTCUT = 0x02, // travel through obstacles, terrain, air, etc (old behavior)
PATHFIND_INCOMPLETE = 0x04, // we have partial path to follow - getting closer to target
PATHFIND_NOPATH = 0x08, // no valid path at all or error in generating one
PATHFIND_NOT_USING_PATH = 0x10, // used when we are either flying/swiming or on map w/o mmaps
PATHFIND_SHORT = 0x20, // path is longer or equal to its limited path length
PATHFIND_BLANK = 0x00, // path not built yet
PATHFIND_NORMAL = 0x01, // normal path
PATHFIND_SHORTCUT = 0x02, // travel through obstacles, terrain, air, etc (old behavior)
PATHFIND_INCOMPLETE = 0x04, // we have partial path to follow - getting closer to target
PATHFIND_NOPATH = 0x08, // no valid path at all or error in generating one
PATHFIND_NOT_USING_PATH = 0x10, // used when we are either flying/swiming or on map w/o mmaps
PATHFIND_SHORT = 0x20, // path is longer or equal to its limited path length
PATHFIND_FARFROMPOLY_START = 0x40, // start position is far from the mmap poligon
PATHFIND_FARFROMPOLY_END = 0x80, // end positions is far from the mmap poligon
PATHFIND_FARFROMPOLY = PATHFIND_FARFROMPOLY_START | PATHFIND_FARFROMPOLY_END, // start or end positions are far from the mmap poligon
};
class PathGenerator
{
public:
explicit PathGenerator(Unit const* owner);
~PathGenerator();
public:
explicit PathGenerator(WorldObject const* owner);
~PathGenerator();
// Calculate the path from owner to given destination
// return: true if new path was calculated, false otherwise (no change needed)
bool CalculatePath(float destX, float destY, float destZ, bool forceDest = false);
// Calculate the path from owner to given destination
// return: true if new path was calculated, false otherwise (no change needed)
bool CalculatePath(float destX, float destY, float destZ, bool forceDest = false);
bool CalculatePath(float x, float y, float z, float destX, float destY, float destZ, bool forceDest);
[[nodiscard]] bool IsInvalidDestinationZ(Unit const* target) const;
[[nodiscard]] bool IsWalkableClimb(float const* v1, float const* v2) const;
[[nodiscard]] bool IsWalkableClimb(float x, float y, float z, float destX, float destY, float destZ) const;
[[nodiscard]] static bool IsWalkableClimb(float x, float y, float z, float destX, float destY, float destZ, float sourceHeight);
[[nodiscard]] bool IsWaterPath(Movement::PointsArray _pathPoints) const;
[[nodiscard]] bool IsSwimmableSegment(float const* v1, float const* v2, bool checkSwim = true) const;
[[nodiscard]] bool IsSwimmableSegment(float x, float y, float z, float destX, float destY, float destZ, bool checkSwim = true) const;
[[nodiscard]] static float GetRequiredHeightToClimb(float x, float y, float z, float destX, float destY, float destZ, float sourceHeight);
// option setters - use optional
void SetUseStraightPath(bool useStraightPath) { _useStraightPath = useStraightPath; }
void SetPathLengthLimit(float distance) { _pointPathLimit = std::min<uint32>(uint32(distance / SMOOTH_PATH_STEP_SIZE), MAX_POINT_PATH_LENGTH); }
// option setters - use optional
// result getters
G3D::Vector3 const& GetStartPosition() const { return _startPosition; }
G3D::Vector3 const& GetEndPosition() const { return _endPosition; }
G3D::Vector3 const& GetActualEndPosition() const { return _actualEndPosition; }
// when set, it skips paths with too high slopes (doesn't work with StraightPath enabled)
void SetSlopeCheck(bool checkSlope) { _slopeCheck = checkSlope; }
void SetUseStraightPath(bool useStraightPath) { _useStraightPath = useStraightPath; }
void SetPathLengthLimit(float distance) { _pointPathLimit = std::min<uint32>(uint32(distance/SMOOTH_PATH_STEP_SIZE), MAX_POINT_PATH_LENGTH); }
void SetUseRaycast(bool useRaycast) { _useRaycast = useRaycast; }
Movement::PointsArray const& GetPath() const { return _pathPoints; }
// result getters
G3D::Vector3 const& GetStartPosition() const { return _startPosition; }
G3D::Vector3 const& GetEndPosition() const { return _endPosition; }
G3D::Vector3 const& GetActualEndPosition() const { return _actualEndPosition; }
PathType GetPathType() const { return _type; }
float getPathLength() const
{
float len = 0.0f;
float dx, dy, dz;
uint32 size = _pathPoints.size();
if (size)
Movement::PointsArray const& GetPath() const { return _pathPoints; }
PathType GetPathType() const { return _type; }
// shortens the path until the destination is the specified distance from the target point
void ShortenPathUntilDist(G3D::Vector3 const& point, float dist);
float getPathLength() const
{
dx = _pathPoints[0].x - _startPosition.x;
dy = _pathPoints[0].y - _startPosition.y;
dz = _pathPoints[0].z - _startPosition.z;
len += sqrt( dx * dx + dy * dy + dz * dz );
}
else
float len = 0.0f;
float dx, dy, dz;
uint32 size = _pathPoints.size();
if (size)
{
dx = _pathPoints[0].x - _startPosition.x;
dy = _pathPoints[0].y - _startPosition.y;
dz = _pathPoints[0].z - _startPosition.z;
len += sqrt( dx * dx + dy * dy + dz * dz );
}
else
{
return len;
}
for (uint32 i = 1; i < size; ++i)
{
dx = _pathPoints[i].x - _pathPoints[i - 1].x;
dy = _pathPoints[i].y - _pathPoints[i - 1].y;
dz = _pathPoints[i].z - _pathPoints[i - 1].z;
len += sqrt( dx * dx + dy * dy + dz * dz );
}
return len;
for (uint32 i = 1; i < size; ++i)
{
dx = _pathPoints[i].x - _pathPoints[i - 1].x;
dy = _pathPoints[i].y - _pathPoints[i - 1].y;
dz = _pathPoints[i].z - _pathPoints[i - 1].z;
len += sqrt( dx * dx + dy * dy + dz * dz );
}
return len;
}
private:
dtPolyRef _pathPolyRefs[MAX_PATH_LENGTH]; // array of detour polygon references
uint32 _polyLength; // number of polygons in the path
private:
dtPolyRef _pathPolyRefs[MAX_PATH_LENGTH]; // array of detour polygon references
uint32 _polyLength; // number of polygons in the path
Movement::PointsArray _pathPoints; // our actual (x,y,z) path to the target
PathType _type; // tells what kind of path this is
Movement::PointsArray _pathPoints; // our actual (x,y,z) path to the target
PathType _type; // tells what kind of path this is
bool _useStraightPath; // type of path will be generated
bool _forceDestination; // when set, we will always arrive at given point
uint32 _pointPathLimit; // limit point path size; min(this, MAX_POINT_PATH_LENGTH)
bool _useStraightPath; // type of path will be generated (do not use it for movement paths)
bool _forceDestination; // when set, we will always arrive at given point
bool _slopeCheck; // when set, it skips paths with too high slopes (doesn't work with _useStraightPath)
uint32 _pointPathLimit; // limit point path size; min(this, MAX_POINT_PATH_LENGTH)
bool _useRaycast; // use raycast if true for a straight line path
G3D::Vector3 _startPosition; // {x, y, z} of current location
G3D::Vector3 _endPosition; // {x, y, z} of the destination
G3D::Vector3 _actualEndPosition; // {x, y, z} of the closest possible point to given destination
G3D::Vector3 _startPosition; // {x, y, z} of current location
G3D::Vector3 _endPosition; // {x, y, z} of the destination
G3D::Vector3 _actualEndPosition; // {x, y, z} of the closest possible point to given destination
Unit const* const _sourceUnit; // the unit that is moving
dtNavMesh const* _navMesh; // the nav mesh
dtNavMeshQuery const* _navMeshQuery; // the nav mesh query used to find the path
WorldObject const* const _source; // the object that is moving
dtNavMesh const* _navMesh; // the nav mesh
dtNavMeshQuery const* _navMeshQuery; // the nav mesh query used to find the path
dtQueryFilter _filter; // use single filter for all movements, update it when needed
dtQueryFilterExt _filter; // use single filter for all movements, update it when needed
void SetStartPosition(G3D::Vector3 const& point) { _startPosition = point; }
void SetEndPosition(G3D::Vector3 const& point) { _actualEndPosition = point; _endPosition = point; }
void SetActualEndPosition(G3D::Vector3 const& point) { _actualEndPosition = point; }
void SetStartPosition(G3D::Vector3 const& point) { _startPosition = point; }
void SetEndPosition(G3D::Vector3 const& point) { _actualEndPosition = point; _endPosition = point; }
void SetActualEndPosition(G3D::Vector3 const& point) { _actualEndPosition = point; }
void NormalizePath();
void Clear()
{
_polyLength = 0;
_pathPoints.clear();
}
void Clear()
{
_polyLength = 0;
_pathPoints.clear();
}
bool InRange(G3D::Vector3 const& p1, G3D::Vector3 const& p2, float r, float h) const;
float Dist3DSqr(G3D::Vector3 const& p1, G3D::Vector3 const& p2) const;
bool InRangeYZX(float const* v1, float const* v2, float r, float h) const;
bool InRange(G3D::Vector3 const& p1, G3D::Vector3 const& p2, float r, float h) const;
float Dist3DSqr(G3D::Vector3 const& p1, G3D::Vector3 const& p2) const;
bool InRangeYZX(float const* v1, float const* v2, float r, float h) const;
dtPolyRef GetPathPolyByPosition(dtPolyRef const* polyPath, uint32 polyPathSize, float const* Point, float* Distance = nullptr) const;
dtPolyRef GetPolyByLocation(float* Point, float* Distance) const;
bool HaveTile(G3D::Vector3 const& p) const;
dtPolyRef GetPathPolyByPosition(dtPolyRef const* polyPath, uint32 polyPathSize, float const* Point, float* Distance = nullptr) const;
dtPolyRef GetPolyByLocation(float const* Point, float* Distance) const;
bool HaveTile(G3D::Vector3 const& p) const;
void BuildPolyPath(G3D::Vector3 const& startPos, G3D::Vector3 const& endPos, ACE_RW_Thread_Mutex& lock);
void BuildPointPath(float const* startPoint, float const* endPoint);
void BuildShortcut();
void BuildPolyPath(G3D::Vector3 const& startPos, G3D::Vector3 const& endPos);
void BuildPointPath(float const* startPoint, float const* endPoint);
void BuildShortcut();
NavTerrain GetNavTerrain(float x, float y, float z);
void CreateFilter();
void UpdateFilter();
NavTerrain GetNavTerrain(float x, float y, float z) const;
void CreateFilter();
void UpdateFilter();
// smooth path aux functions
uint32 FixupCorridor(dtPolyRef* path, uint32 npath, uint32 maxPath, dtPolyRef const* visited, uint32 nvisited);
bool GetSteerTarget(float const* startPos, float const* endPos, float minTargetDist, dtPolyRef const* path, uint32 pathSize, float* steerPos,
unsigned char& steerPosFlag, dtPolyRef& steerPosRef);
dtStatus FindSmoothPath(float const* startPos, float const* endPos,
dtPolyRef const* polyPath, uint32 polyPathSize,
float* smoothPath, int* smoothPathSize, uint32 smoothPathMaxSize);
// smooth path aux functions
uint32 FixupCorridor(dtPolyRef* path, uint32 npath, uint32 maxPath, dtPolyRef const* visited, uint32 nvisited);
bool GetSteerTarget(float const* startPos, float const* endPos, float minTargetDist, dtPolyRef const* path, uint32 pathSize, float* steerPos,
unsigned char& steerPosFlag, dtPolyRef& steerPosRef);
dtStatus FindSmoothPath(float const* startPos, float const* endPos,
dtPolyRef const* polyPath, uint32 polyPathSize,
float* smoothPath, int* smoothPathSize, uint32 smoothPathMaxSize);
void AddFarFromPolyFlags(bool startFarFromPoly, bool endFarFromPoly);
};
#endif

View File

@@ -62,7 +62,7 @@ void RandomMovementGenerator<Creature>::_setRandomLocation(Creature* creature)
}
float ground = INVALID_HEIGHT;
float levelZ = map->GetWaterOrGroundLevel(creature->GetPhaseMask(), x, y, z + 4.0f, &ground);
float levelZ = creature->GetMapWaterOrGroundLevel(x, y, z, &ground);
float newZ = INVALID_HEIGHT;
// flying creature
@@ -71,15 +71,11 @@ void RandomMovementGenerator<Creature>::_setRandomLocation(Creature* creature)
// point underwater
else if (ground < levelZ)
{
if (!creature->CanSwim())
if (!creature->CanEnterWater())
{
if (ground < levelZ - 1.5f)
{
_validPointsVector[_currentPoint].erase(randomIter);
_preComputedPaths.erase(pathIdx);
return;
}
levelZ = ground;
_validPointsVector[_currentPoint].erase(randomIter);
_preComputedPaths.erase(pathIdx);
return;
}
else
{
@@ -99,6 +95,8 @@ void RandomMovementGenerator<Creature>::_setRandomLocation(Creature* creature)
}
}
creature->UpdateAllowedPositionZ(x, y, newZ);
if (newZ > INVALID_HEIGHT)
{
// flying / swiming creature - dest not in los

View File

@@ -4,373 +4,212 @@
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
*/
#include "ByteBuffer.h"
#include "TargetedMovementGenerator.h"
#include "Errors.h"
#include "Creature.h"
#include "CreatureAI.h"
#include "World.h"
#include "MoveSplineInit.h"
#include "MoveSpline.h"
#include "Player.h"
#include "Spell.h"
#include "BattlegroundRV.h"
#include "VehicleDefines.h"
#include "Transport.h"
#include "MapManager.h"
#include "Pet.h"
#include <cmath>
template<class T, typename D>
void TargetedMovementGeneratorMedium<T, D>::_setTargetLocation(T* owner, bool initial)
static bool IsMutualChase(Unit* owner, Unit* target)
{
if (!i_target.isValid() || !i_target->IsInWorld() || !owner->IsInMap(i_target.getTarget()))
return;
if (target->GetMotionMaster()->GetCurrentMovementGeneratorType() != CHASE_MOTION_TYPE)
return false;
if (owner->HasUnitState(UNIT_STATE_NOT_MOVE))
return;
if (owner->HasUnitState(UNIT_STATE_CASTING) && !owner->CanMoveDuringChannel())
return;
float x, y, z;
bool isPlayerPet = owner->IsPet() && IS_PLAYER_GUID(owner->GetOwnerGUID());
bool sameTransport = owner->GetTransport() && owner->GetTransport() == i_target->GetTransport();
if (owner->GetMapId() == 631 && owner->GetTransport() && owner->GetTransport()->IsMotionTransport() && i_target->GetTransport() && i_target->GetTransport()->IsMotionTransport()) // for ICC, if both on a motion transport => don't use mmaps
sameTransport = owner->GetTypeId() == TYPEID_UNIT && i_target->isInAccessiblePlaceFor(owner->ToCreature());
bool useMMaps = MMAP::MMapFactory::IsPathfindingEnabled(owner->FindMap()) && !sameTransport;
bool forceDest =
// (owner->FindMap() && owner->FindMap()->IsDungeon() && !isPlayerPet) || // force in instances to prevent exploiting
(owner->GetTypeId() == TYPEID_UNIT && ((owner->IsPet() && owner->HasUnitState(UNIT_STATE_FOLLOW)))) || // allow pets following their master to cheat while generating paths
// ((Creature*)owner)->isWorldBoss() || ((Creature*)owner)->IsDungeonBoss())) || // force for all bosses, even not in instances
(owner->GetMapId() == 572 && (owner->GetPositionX() < 1275.0f || i_target->GetPositionX() < 1275.0f)) || // pussywizard: Ruins of Lordaeron - special case (acid)
sameTransport || // nothing to comment, can't find path on transports so allow it
(i_target->GetTypeId() == TYPEID_PLAYER && i_target->ToPlayer()->IsGameMaster()) // for .npc follow*/
; // closes "bool forceDest", that way it is more appropriate, so we can comment out crap whenever we need to
bool forcePoint = ((!isPlayerPet || owner->GetMapId() == 618) && (forceDest || !useMMaps)) || sameTransport;
if (owner->GetTypeId() == TYPEID_UNIT && !i_target->isInAccessiblePlaceFor(owner->ToCreature()) && !sameTransport && !forceDest && !forcePoint)
{
owner->ToCreature()->SetCannotReachTarget(true);
return;
}
lastOwnerXYZ.Relocate(owner->GetPositionX(), owner->GetPositionY(), owner->GetPositionZ());
lastTargetXYZ.Relocate(i_target->GetPositionX(), i_target->GetPositionY(), i_target->GetPositionZ());
if (!i_offset)
{
if (i_target->IsWithinDistInMap(owner, CONTACT_DISTANCE))
return;
float allowedRange = MELEE_RANGE;
if ((!initial || (owner->movespline->Finalized() && this->GetMovementGeneratorType() == CHASE_MOTION_TYPE)) && i_target->IsWithinMeleeRange(owner, allowedRange) && i_target->IsWithinLOS(owner->GetPositionX(), owner->GetPositionY(), owner->GetPositionZ()))
return;
bool inRange = i_target->GetRandomContactPoint(owner, x, y, z, forcePoint);
if (useMMaps && !inRange && (!isPlayerPet || i_target->GetPositionZ() - z > 50.0f))
{
//useMMaps = false;
owner->m_targetsNotAcceptable[i_target->GetGUID()] = MMapTargetData(sWorld->GetGameTime() + DISALLOW_TIME_AFTER_FAIL, owner, i_target.getTarget());
return;
}
// to nearest contact position
i_target->GetContactPoint(owner, x, y, z);
}
else
{
float dist;
float size;
// Pets need special handling.
// We need to subtract GetObjectSize() because it gets added back further down the chain
// and that makes pets too far away. Subtracting it allows pets to properly
// be (GetCombatReach() + i_offset) away.
// Only applies when i_target is pet's owner otherwise pets and mobs end up
// doing a "dance" while fighting
if (owner->IsPet() && i_target->GetTypeId() == TYPEID_PLAYER)
{
dist = i_target->GetCombatReach();
size = i_target->GetCombatReach() - i_target->GetObjectSize();
}
else
{
dist = i_offset;
size = owner->GetObjectSize();
}
if ((!initial || (owner->movespline->Finalized() && this->GetMovementGeneratorType() == CHASE_MOTION_TYPE)) && i_target->IsWithinDistInMap(owner, dist) && i_target->IsWithinLOS(owner->GetPositionX(), owner->GetPositionY(), owner->GetPositionZ()))
return;
float angle = i_angle;
if (i_target->GetTypeId() == TYPEID_PLAYER)
{
Creature* creature = owner->ToCreature();
if (creature && creature->GetCreatureType() == CREATURE_TYPE_NON_COMBAT_PET)
{
// fix distance and angle for vanity pets
dist = 0.3f;
angle = PET_FOLLOW_ANGLE + M_PI * 0.2f;
size = i_target->GetCombatReach() - i_target->GetObjectSize();
}
}
// Xinef: Fix follow angle for hostile units
if (angle == 0.0f && owner->GetVictim() && owner->GetVictim()->GetGUID() == i_target->GetGUID())
angle = MapManager::NormalizeOrientation(i_target->GetAngle(owner) - i_target->GetOrientation());
// to at i_offset distance from target and i_angle from target facing
bool inRange = i_target->GetClosePoint(x, y, z, size, dist, angle, owner, forcePoint);
if (!inRange && (forceDest || !useMMaps) && owner->HasUnitState(UNIT_STATE_FOLLOW) && fabs(i_target->GetPositionZ() - z) > 25.0f)
{
x = i_target->GetPositionX();
y = i_target->GetPositionY();
z = i_target->GetPositionZ();
}
}
D::_addUnitStateMove(owner);
i_targetReached = false;
i_recalculateTravel = false;
Movement::MoveSplineInit init(owner);
if (useMMaps) // pussywizard
{
if (!i_path)
i_path = new PathGenerator(owner);
if (!forceDest)
{
if (owner->GetMapId() == 618) // pussywizard: 618 Ring of Valor
{
if (Map* map = owner->FindMap())
if (Battleground* bg = ((BattlegroundMap*)map)->GetBG())
{
Position dest = {x, y, z, 0.0f};
if (GameObject* pillar = ((BattlegroundRV*)bg)->GetPillarAtPosition(&dest))
{
if ((pillar->GetGoState() == GO_STATE_READY && pillar->ToTransport()->GetPathProgress() == 0) || owner->GetPositionZ() > 31.0f || owner->GetTransGUID() == pillar->GetGUID())
{
if (pillar->GetGoState() == GO_STATE_READY && pillar->ToTransport()->GetPathProgress() == 0)
z = std::max(z, 28.28f);
else
z = i_target->GetPositionZ();
init.MoveTo(x, y, z);
if (i_angle == 0.f)
init.SetFacing(i_target.getTarget());
init.SetWalk(((D*)this)->EnableWalking());
init.Launch();
return;
}
if (pillar->GetGoState() == GO_STATE_ACTIVE || (pillar->GetGoState() == GO_STATE_READY && pillar->ToTransport()->GetPathProgress() > 0))
{
Position pos;
owner->GetFirstCollisionPositionForTotem(pos, owner->GetExactDist2d(i_target.getTarget()), owner->GetAngle(i_target.getTarget()) - owner->GetOrientation(), false);
x = pos.GetPositionX();
y = pos.GetPositionY();
z = 28.28f;
}
}
}
}
}
if (!forceDest && getMSTimeDiff(lastPathingFailMSTime, World::GetGameTimeMS()) < 1000)
{
lastOwnerXYZ.Relocate(-5000.0f, -5000.0f, -5000.0f);
return;
}
bool result = i_path->CalculatePath(x, y, z, forceDest);
if (result)
{
float maxDist = MELEE_RANGE + owner->GetMeleeReach() + i_target->GetMeleeReach();
if (!forceDest && (i_path->GetPathType() & PATHFIND_NOPATH || (!i_offset && !isPlayerPet && i_target->GetExactDistSq(i_path->GetActualEndPosition().x, i_path->GetActualEndPosition().y, i_path->GetActualEndPosition().z) > maxDist * maxDist)))
{
if (owner->GetTypeId() == TYPEID_UNIT)
{
owner->ToCreature()->SetCannotReachTarget(false);
}
lastPathingFailMSTime = World::GetGameTimeMS();
owner->m_targetsNotAcceptable[i_target->GetGUID()] = MMapTargetData(sWorld->GetGameTime() + DISALLOW_TIME_AFTER_FAIL, owner, i_target.getTarget());
return;
}
else
{
owner->m_targetsNotAcceptable.erase(i_target->GetGUID());
owner->AddUnitState(UNIT_STATE_CHASE);
init.MovebyPath(i_path->GetPath());
if (i_angle == 0.f)
init.SetFacing(i_target.getTarget());
init.SetWalk(((D*)this)->EnableWalking());
init.Launch();
return;
}
}
else
{
// evade first
if (owner->GetTypeId() == TYPEID_UNIT)
{
owner->ToCreature()->SetCannotReachTarget(true);
}
// then use normal MoveTo - if we have to
}
}
owner->AddUnitState(UNIT_STATE_CHASE);
init.MoveTo(x, y, z);
// Using the same condition for facing target as the one that is used for SetInFront on movement end
// - applies to ChaseMovementGenerator mostly
if (i_angle == 0.f)
init.SetFacing(i_target.getTarget());
init.SetWalk(((D*)this)->EnableWalking());
init.Launch();
return target->GetVictim() == owner->GetVictim();
}
template<class T, typename D>
bool TargetedMovementGeneratorMedium<T, D>::DoUpdate(T* owner, uint32 time_diff)
template<class T>
bool ChaseMovementGenerator<T>::PositionOkay(T* owner, Unit* target, std::optional<float> maxDistance, std::optional<ChaseAngle> angle)
{
if (!i_target.isValid() || !i_target->IsInWorld())
float const distSq = owner->GetExactDistSq(target);
if (maxDistance && distSq > G3D::square(*maxDistance))
return false;
if (angle && !angle->IsAngleOkay(target->GetRelativeAngle(owner)))
return false;
if (!owner->IsWithinLOSInMap(target))
return false;
return true;
}
template<class T>
bool ChaseMovementGenerator<T>::DoUpdate(T* owner, uint32 time_diff)
{
if (!i_target.isValid() || !i_target->IsInWorld() || !owner->IsInMap(i_target.getTarget()))
return false;
if (!owner || !owner->IsAlive())
return false;
if (owner->HasUnitState(UNIT_STATE_NOT_MOVE))
// the owner might be unable to move (rooted or casting), or we have lost the target, pause movement
if (owner->HasUnitState(UNIT_STATE_NOT_MOVE) || HasLostTarget(owner) || (owner->GetTypeId() == TYPEID_UNIT && owner->ToCreature()->IsMovementPreventedByCasting()))
{
D::_clearUnitStateMove(owner);
return true;
}
// prevent movement while casting spells with cast time or channel time
if (owner->HasUnitState(UNIT_STATE_CASTING) && !owner->CanMoveDuringChannel())
{
bool stop = true;
if (Spell* spell = owner->GetCurrentSpell(CURRENT_CHANNELED_SPELL))
if (!(spell->GetSpellInfo()->ChannelInterruptFlags & (AURA_INTERRUPT_FLAG_MOVE | AURA_INTERRUPT_FLAG_TURNING)) && !(spell->GetSpellInfo()->InterruptFlags & SPELL_INTERRUPT_FLAG_MOVEMENT))
stop = false;
if (stop)
i_path = nullptr;
owner->StopMoving();
_lastTargetPosition.reset();
if (Creature* cOwner = owner->ToCreature())
{
// Xinef: delay distance recheck in case of succeeding casts
i_recheckDistance.Reset(300);
if (!owner->IsStopped())
owner->StopMoving();
return true;
cOwner->SetCannotReachTarget(false);
}
}
// prevent crash after creature killed pet
if (static_cast<D*>(this)->_lostTarget(owner))
{
D::_clearUnitStateMove(owner);
return true;
}
i_recheckDistanceForced.Update(time_diff);
if (i_recheckDistanceForced.Passed())
{
i_recheckDistanceForced.Reset(2500);
lastOwnerXYZ.Relocate(-5000.0f, -5000.0f, -5000.0f);
}
Creature* cOwner = owner->ToCreature();
bool forceDest =
//(cOwner && (cOwner->isWorldBoss() || cOwner->IsDungeonBoss())) || // force for all bosses, even not in instances
(i_target->GetTypeId() == TYPEID_PLAYER && i_target->ToPlayer()->IsGameMaster()) || // for .npc follow
(owner->CanFly())
; // closes "bool forceDest", that way it is more appropriate, so we can comment out crap whenever we need to
Unit* target = i_target.getTarget();
bool const mutualChase = IsMutualChase(owner, target);
float const hitboxSum = owner->GetCombatReach() + target->GetCombatReach();
float const minTarget = (_range ? _range->MinTolerance : 0.0f) + hitboxSum;
float const maxRange = _range ? _range->MaxRange + hitboxSum : owner->GetMeleeRange(target); // melee range already includes hitboxes
float const maxTarget = _range ? _range->MaxTolerance + hitboxSum : CONTACT_DISTANCE + hitboxSum;
std::optional<ChaseAngle> angle = mutualChase ? std::optional<ChaseAngle>() : _angle;
i_recheckDistance.Update(time_diff);
if (i_recheckDistance.Passed())
{
i_recheckDistance.Reset(50);
//More distance let have better performance, less distance let have more sensitive reaction at target move.
float allowed_dist_sq = i_target->GetObjectSize() + owner->GetObjectSize() + MELEE_RANGE - 0.5f;
i_recheckDistance.Reset(100);
// xinef: if offset is negative (follow distance is smaller than just object sizes), reduce minimum allowed distance which is based purely on object sizes
if (i_offset < 0.0f)
if (i_recalculateTravel && PositionOkay(owner, target, _movingTowards ? maxTarget : std::optional<float>(), angle))
{
allowed_dist_sq += i_offset;
allowed_dist_sq = std::max<float>(0.0f, allowed_dist_sq);
i_recalculateTravel = false;
i_path = nullptr;
if (Creature* cOwner = owner->ToCreature())
{
cOwner->SetCannotReachTarget(false);
}
owner->StopMoving();
owner->SetInFront(target);
MovementInform(owner);
return true;
}
allowed_dist_sq *= allowed_dist_sq;
G3D::Vector3 dest = owner->movespline->FinalDestination();
if (owner->movespline->onTransport)
if (TransportBase* transport = owner->GetDirectTransport())
transport->CalculatePassengerPosition(dest.x, dest.y, dest.z);
float dist = (dest - G3D::Vector3(i_target->GetPositionX(), i_target->GetPositionY(), i_target->GetPositionZ())).squaredLength();
float targetMoveDistSq = i_target->GetExactDistSq(&lastTargetXYZ);
if (dist >= allowed_dist_sq || (!i_offset && targetMoveDistSq >= 1.5f * 1.5f))
if (targetMoveDistSq >= 0.1f * 0.1f || owner->GetExactDistSq(&lastOwnerXYZ) >= 0.1f * 0.1f)
_setTargetLocation(owner, false);
}
if (owner->movespline->Finalized())
bool hasMoveState = owner->HasUnitState(UNIT_STATE_CHASE_MOVE) || owner->HasUnitState(UNIT_STATE_FOLLOW_MOVE);
if (hasMoveState && owner->movespline->Finalized())
{
static_cast<D*>(this)->MovementInform(owner);
if (i_angle == 0.f && !owner->HasInArc(0.01f, i_target.getTarget()))
owner->SetInFront(i_target.getTarget());
i_recalculateTravel = false;
i_path = nullptr;
owner->ClearUnitState(UNIT_STATE_CHASE_MOVE);
owner->SetInFront(target);
MovementInform(owner);
if (!i_targetReached)
if (owner->IsWithinMeleeRange(this->i_target.getTarget()))
owner->Attack(this->i_target.getTarget(), true);
}
if (_lastTargetPosition && i_target->GetPosition() == _lastTargetPosition.value() && mutualChase == _mutualChase)
return true;
_lastTargetPosition = i_target->GetPosition();
if (PositionOkay(owner, target, maxRange, angle) && !hasMoveState)
return true;
bool moveToward = !owner->IsInDist(target, maxRange);
_mutualChase = mutualChase;
if (owner->HasUnitState(UNIT_STATE_CHASE_MOVE))
{
// can we get to the target?
if (cOwner && !target->isInAccessiblePlaceFor(cOwner))
{
i_targetReached = true;
static_cast<D*>(this)->_reachTarget(owner);
cOwner->SetCannotReachTarget(true);
cOwner->StopMoving();
return true;
}
}
if (!i_path || moveToward != _movingTowards)
i_path = new PathGenerator(owner);
float x, y, z;
bool shortenPath;
// if we want to move toward the target and there's no fixed angle...
if (moveToward && !angle)
{
// ...we'll pathfind to the center, then shorten the path
target->GetPosition(x, y, z);
shortenPath = true;
}
else
{
if (i_recalculateTravel)
_setTargetLocation(owner, false);
if (target->GetTypeId() == TYPEID_PLAYER)
shortenPath = false;
// otherwise, we fall back to nearpoint finding
target->GetNearPoint(owner, x, y, z, (moveToward ? maxTarget : minTarget) - hitboxSum, 0, angle ? target->ToAbsoluteAngle(angle->RelativeAngle) : target->GetAngle(owner));
shortenPath = false;
}
Unit* pOwner = owner->GetCharmerOrOwner();
if (owner->IsHovering())
owner->UpdateAllowedPositionZ(x, y, z);
if (pOwner && pOwner->GetTypeId() == TYPEID_PLAYER)
bool success = i_path->CalculatePath(x, y, z, forceDest);
if (!success || i_path->GetPathType() & PATHFIND_NOPATH)
{
// Update pet speed for players in order to avoid stuttering
if (pOwner->IsFlying())
owner->UpdateSpeed(MOVE_FLIGHT, true);
else
owner->UpdateSpeed(MOVE_RUN, true);
if (cOwner)
cOwner->SetCannotReachTarget(true);
owner->StopMoving();
return true;
}
if (shortenPath)
i_path->ShortenPathUntilDist(G3D::Vector3(target->GetPositionX(), target->GetPositionY(), target->GetPositionZ()), maxTarget);
if (cOwner)
cOwner->SetCannotReachTarget(false);
bool walk = false;
if (cOwner && !cOwner->IsPet())
{
walk = owner->IsWalking();
}
owner->AddUnitState(UNIT_STATE_CHASE_MOVE);
i_recalculateTravel = true;
Movement::MoveSplineInit init(owner);
init.MovebyPath(i_path->GetPath());
init.SetFacing(target);
init.SetWalk(walk);
init.Launch();
return true;
}
//-----------------------------------------------//
template<class T>
void ChaseMovementGenerator<T>::_reachTarget(T* owner)
{
if (owner->IsWithinMeleeRange(this->i_target.getTarget()))
owner->Attack(this->i_target.getTarget(), true);
}
template<>
void ChaseMovementGenerator<Player>::DoInitialize(Player* owner)
{
owner->AddUnitState(UNIT_STATE_CHASE | UNIT_STATE_CHASE_MOVE);
_setTargetLocation(owner, true);
_lastTargetPosition.reset();
owner->AddUnitState(UNIT_STATE_CHASE);
}
template<>
void ChaseMovementGenerator<Creature>::DoInitialize(Creature* owner)
{
_lastTargetPosition.reset();
owner->SetWalk(false);
owner->AddUnitState(UNIT_STATE_CHASE | UNIT_STATE_CHASE_MOVE);
_setTargetLocation(owner, true);
owner->AddUnitState(UNIT_STATE_CHASE);
}
template<class T>
void ChaseMovementGenerator<T>::DoFinalize(T* owner)
{
owner->ClearUnitState(UNIT_STATE_CHASE | UNIT_STATE_CHASE_MOVE);
if (Creature* cOwner = owner->ToCreature())
cOwner->SetCannotReachTarget(false);
}
template<class T>
@@ -380,29 +219,143 @@ void ChaseMovementGenerator<T>::DoReset(T* owner)
}
template<class T>
void ChaseMovementGenerator<T>::MovementInform(T* /*unit*/)
void ChaseMovementGenerator<T>::MovementInform(T* owner)
{
}
if (owner->GetTypeId() != TYPEID_UNIT)
return;
template<>
void ChaseMovementGenerator<Creature>::MovementInform(Creature* unit)
{
// Pass back the GUIDLow of the target. If it is pet's owner then PetAI will handle
if (unit->AI())
unit->AI()->MovementInform(CHASE_MOTION_TYPE, i_target.getTarget()->GetGUIDLow());
if (CreatureAI* AI = owner->ToCreature()->AI())
AI->MovementInform(CHASE_MOTION_TYPE, i_target.getTarget()->GetGUIDLow());
}
//-----------------------------------------------//
template<>
bool FollowMovementGenerator<Creature>::EnableWalking() const
template<class T>
bool FollowMovementGenerator<T>::PositionOkay(T* owner, Unit* target, float range, std::optional<ChaseAngle> angle)
{
return i_target.isValid() && (i_target->IsWalking() || i_target->movespline->isWalking());
if (owner->GetExactDistSq(target) > G3D::square(owner->GetCombatReach() + target->GetCombatReach() + range))
return false;
return !owner->IsPet() || !angle || angle->IsAngleOkay(target->GetRelativeAngle(owner));
}
template<>
bool FollowMovementGenerator<Player>::EnableWalking() const
template<class T>
bool FollowMovementGenerator<T>::DoUpdate(T* owner, uint32 time_diff)
{
return false;
if (!i_target.isValid() || !i_target->IsInWorld() || !owner->IsInMap(i_target.getTarget()))
return false;
if (!owner || !owner->IsAlive())
return false;
Creature* cOwner = owner->ToCreature();
Unit* target = i_target.getTarget();
// the owner might be unable to move (rooted or casting), or we have lost the target, pause movement
if (owner->HasUnitState(UNIT_STATE_NOT_MOVE) || (cOwner && owner->ToCreature()->IsMovementPreventedByCasting()))
{
i_path = nullptr;
owner->StopMoving();
_lastTargetPosition.reset();
return true;
}
bool followingMaster = false;
Pet* oPet = owner->ToPet();
if (oPet)
{
if (target->GetGUID() == oPet->GetOwnerGUID())
followingMaster = true;
}
bool forceDest =
(followingMaster) || // allow pets following their master to cheat while generating paths
(i_target->GetTypeId() == TYPEID_PLAYER && i_target->ToPlayer()->IsGameMaster()) // for .npc follow
; // closes "bool forceDest", that way it is more appropriate, so we can comment out crap whenever we need to
i_recheckDistance.Update(time_diff);
if (i_recheckDistance.Passed())
{
i_recheckDistance.Reset(100);
if (i_recalculateTravel && PositionOkay(owner, target, _range, _angle))
{
i_recalculateTravel = false;
i_path = nullptr;
owner->StopMoving();
_lastTargetPosition.reset();
MovementInform(owner);
return true;
}
}
if (owner->HasUnitState(UNIT_STATE_FOLLOW_MOVE) && owner->movespline->Finalized())
{
i_recalculateTravel = false;
i_path = nullptr;
owner->ClearUnitState(UNIT_STATE_FOLLOW_MOVE);
MovementInform(owner);
}
Position targetPosition = i_target->GetPosition();
if (_lastTargetPosition && _lastTargetPosition->GetExactDistSq(&targetPosition) == 0.0f)
return true;
_lastTargetPosition = targetPosition;
if (PositionOkay(owner, target, _range + PET_FOLLOW_DIST) && !owner->HasUnitState(UNIT_STATE_FOLLOW_MOVE))
return true;
if (!i_path)
i_path = new PathGenerator(owner);
float x, y, z;
// select angle
float tAngle;
float const curAngle = target->GetRelativeAngle(owner);
if (!oPet)
{
// for non pets, keep the relative angle
// decided during the summon
tAngle = _angle.RelativeAngle;
}
else if (_angle.IsAngleOkay(curAngle))
{
tAngle = curAngle;
}
else
{
float const diffUpper = Position::NormalizeOrientation(curAngle - _angle.UpperBound());
float const diffLower = Position::NormalizeOrientation(_angle.LowerBound() - curAngle);
if (diffUpper < diffLower)
tAngle = _angle.UpperBound();
else
tAngle = _angle.LowerBound();
}
target->GetNearPoint(owner, x, y, z, _range, 0.f, target->ToAbsoluteAngle(tAngle));
bool success = i_path->CalculatePath(x, y, z, forceDest);
if (!success || i_path->GetPathType() & PATHFIND_NOPATH)
{
owner->StopMoving();
return true;
}
owner->AddUnitState(UNIT_STATE_FOLLOW_MOVE);
i_recalculateTravel = true;
Movement::MoveSplineInit init(owner);
init.MovebyPath(i_path->GetPath());
init.SetFacing(target->GetOrientation());
init.SetWalk(target->IsWalking());
init.Launch();
return true;
}
template<>
@@ -424,20 +377,12 @@ void FollowMovementGenerator<Creature>::_updateSpeed(Creature* owner)
owner->UpdateSpeed(MOVE_SWIM, true);
}
template<>
void FollowMovementGenerator<Player>::DoInitialize(Player* owner)
template<class T>
void FollowMovementGenerator<T>::DoInitialize(T* owner)
{
owner->AddUnitState(UNIT_STATE_FOLLOW | UNIT_STATE_FOLLOW_MOVE);
_lastTargetPosition.reset();
owner->AddUnitState(UNIT_STATE_FOLLOW);
_updateSpeed(owner);
_setTargetLocation(owner, true);
}
template<>
void FollowMovementGenerator<Creature>::DoInitialize(Creature* owner)
{
owner->AddUnitState(UNIT_STATE_FOLLOW | UNIT_STATE_FOLLOW_MOVE);
_updateSpeed(owner);
_setTargetLocation(owner, true);
}
template<class T>
@@ -454,38 +399,31 @@ void FollowMovementGenerator<T>::DoReset(T* owner)
}
template<class T>
void FollowMovementGenerator<T>::MovementInform(T* /*unit*/)
void FollowMovementGenerator<T>::MovementInform(T* owner)
{
}
if (owner->GetTypeId() != TYPEID_UNIT)
return;
template<>
void FollowMovementGenerator<Creature>::MovementInform(Creature* unit)
{
// Pass back the GUIDLow of the target. If it is pet's owner then PetAI will handle
if (unit->AI())
unit->AI()->MovementInform(FOLLOW_MOTION_TYPE, i_target.getTarget()->GetGUIDLow());
if (CreatureAI* AI = owner->ToCreature()->AI())
AI->MovementInform(FOLLOW_MOTION_TYPE, i_target.getTarget()->GetGUIDLow());
}
//-----------------------------------------------//
template void TargetedMovementGeneratorMedium<Player, ChaseMovementGenerator<Player> >::_setTargetLocation(Player*, bool initial);
template void TargetedMovementGeneratorMedium<Player, FollowMovementGenerator<Player> >::_setTargetLocation(Player*, bool initial);
template void TargetedMovementGeneratorMedium<Creature, ChaseMovementGenerator<Creature> >::_setTargetLocation(Creature*, bool initial);
template void TargetedMovementGeneratorMedium<Creature, FollowMovementGenerator<Creature> >::_setTargetLocation(Creature*, bool initial);
template bool TargetedMovementGeneratorMedium<Player, ChaseMovementGenerator<Player> >::DoUpdate(Player*, uint32);
template bool TargetedMovementGeneratorMedium<Player, FollowMovementGenerator<Player> >::DoUpdate(Player*, uint32);
template bool TargetedMovementGeneratorMedium<Creature, ChaseMovementGenerator<Creature> >::DoUpdate(Creature*, uint32);
template bool TargetedMovementGeneratorMedium<Creature, FollowMovementGenerator<Creature> >::DoUpdate(Creature*, uint32);
template void ChaseMovementGenerator<Player>::_reachTarget(Player*);
template void ChaseMovementGenerator<Creature>::_reachTarget(Creature*);
template void ChaseMovementGenerator<Player>::DoFinalize(Player*);
template void ChaseMovementGenerator<Creature>::DoFinalize(Creature*);
template void ChaseMovementGenerator<Player>::DoReset(Player*);
template void ChaseMovementGenerator<Creature>::DoReset(Creature*);
template void ChaseMovementGenerator<Player>::MovementInform(Player*);
template bool ChaseMovementGenerator<Player>::DoUpdate(Player*, uint32);
template bool ChaseMovementGenerator<Creature>::DoUpdate(Creature*, uint32);
template void ChaseMovementGenerator<Unit>::MovementInform(Unit*);
template void FollowMovementGenerator<Player>::DoInitialize(Player*);
template void FollowMovementGenerator<Creature>::DoInitialize(Creature*);
template void FollowMovementGenerator<Player>::DoFinalize(Player*);
template void FollowMovementGenerator<Creature>::DoFinalize(Creature*);
template void FollowMovementGenerator<Player>::DoReset(Player*);
template void FollowMovementGenerator<Creature>::DoReset(Creature*);
template void FollowMovementGenerator<Player>::MovementInform(Player*);
template bool FollowMovementGenerator<Player>::DoUpdate(Player*, uint32);
template bool FollowMovementGenerator<Creature>::DoUpdate(Creature*, uint32);

View File

@@ -12,6 +12,7 @@
#include "Timer.h"
#include "Unit.h"
#include "PathGenerator.h"
#include <optional>
class TargetedMovementGeneratorBase
{
@@ -20,90 +21,80 @@ public:
void stopFollowing() { }
protected:
FollowerReference i_target;
Position lastOwnerXYZ;
Position lastTargetXYZ;
};
template<class T, typename D>
class TargetedMovementGeneratorMedium : public MovementGeneratorMedium< T, D >, public TargetedMovementGeneratorBase
{
protected:
TargetedMovementGeneratorMedium(Unit* target, float offset, float angle) :
TargetedMovementGeneratorBase(target), i_path(nullptr), lastPathingFailMSTime(0),
i_recheckDistance(0), i_recheckDistanceForced(2500), i_offset(offset), i_angle(angle),
i_recalculateTravel(false), i_targetReached(false)
{
}
~TargetedMovementGeneratorMedium() { delete i_path; }
public:
bool DoUpdate(T*, uint32);
Unit* GetTarget() const { return i_target.getTarget(); }
void unitSpeedChanged() { i_recalculateTravel = true; }
bool IsReachable() const { return (i_path) ? (i_path->GetPathType() & PATHFIND_NORMAL) : true; }
protected:
void _setTargetLocation(T* owner, bool initial);
PathGenerator* i_path;
uint32 lastPathingFailMSTime;
TimeTrackerSmall i_recheckDistance;
TimeTrackerSmall i_recheckDistanceForced;
float i_offset;
float i_angle;
bool i_recalculateTravel : 1;
bool i_targetReached : 1;
};
template<class T>
class ChaseMovementGenerator : public TargetedMovementGeneratorMedium<T, ChaseMovementGenerator<T> >
class ChaseMovementGenerator : public MovementGeneratorMedium<T, ChaseMovementGenerator<T>>, public TargetedMovementGeneratorBase
{
public:
ChaseMovementGenerator(Unit* target)
: TargetedMovementGeneratorMedium<T, ChaseMovementGenerator<T> >(target) {}
ChaseMovementGenerator(Unit* target, float offset, float angle)
: TargetedMovementGeneratorMedium<T, ChaseMovementGenerator<T> >(target, offset, angle) {}
ChaseMovementGenerator(Unit* target, std::optional<ChaseRange> range = {}, std::optional<ChaseAngle> angle = {})
: TargetedMovementGeneratorBase(target), i_path(nullptr), i_recheckDistance(0), i_recalculateTravel(true), _range(range), _angle(angle) {}
~ChaseMovementGenerator() {}
MovementGeneratorType GetMovementGeneratorType() { return CHASE_MOTION_TYPE; }
bool DoUpdate(T*, uint32);
void DoInitialize(T*);
void DoFinalize(T*);
void DoReset(T*);
void MovementInform(T*);
static void _clearUnitStateMove(T* u) { u->ClearUnitState(UNIT_STATE_CHASE_MOVE); }
static void _addUnitStateMove(T* u) { u->AddUnitState(UNIT_STATE_CHASE_MOVE); }
bool PositionOkay(T* owner, Unit* target, std::optional<float> maxDistance, std::optional<ChaseAngle> angle);
void unitSpeedChanged() { _lastTargetPosition.reset(); }
Unit* GetTarget() const { return i_target.getTarget(); }
bool EnableWalking() const { return false;}
bool _lostTarget(T* u) const { return u->GetVictim() != this->GetTarget(); }
void _reachTarget(T*);
bool HasLostTarget(Unit* unit) const { return unit->GetVictim() != this->GetTarget(); }
private:
PathGenerator* i_path;
TimeTrackerSmall i_recheckDistance;
bool i_recalculateTravel;
std::optional<Position> _lastTargetPosition;
std::optional<ChaseRange> const _range;
std::optional<ChaseAngle> const _angle;
bool _movingTowards = true;
bool _mutualChase = true;
};
template<class T>
class FollowMovementGenerator : public TargetedMovementGeneratorMedium<T, FollowMovementGenerator<T> >
class FollowMovementGenerator : public MovementGeneratorMedium<T, FollowMovementGenerator<T>>, public TargetedMovementGeneratorBase
{
public:
FollowMovementGenerator(Unit* target)
: TargetedMovementGeneratorMedium<T, FollowMovementGenerator<T> >(target) {}
FollowMovementGenerator(Unit* target, float offset, float angle)
: TargetedMovementGeneratorMedium<T, FollowMovementGenerator<T> >(target, offset, angle) {}
FollowMovementGenerator(Unit* target, float range, ChaseAngle angle)
: TargetedMovementGeneratorBase(target), i_path(nullptr), i_recheckDistance(0), i_recalculateTravel(true), _range(range), _angle(angle) {}
~FollowMovementGenerator() {}
MovementGeneratorType GetMovementGeneratorType() { return FOLLOW_MOTION_TYPE; }
bool DoUpdate(T*, uint32);
void DoInitialize(T*);
void DoFinalize(T*);
void DoReset(T*);
void MovementInform(T*);
Unit* GetTarget() const { return i_target.getTarget(); }
void unitSpeedChanged() { _lastTargetPosition.reset(); }
bool PositionOkay(T* owner, Unit* target, float range, std::optional<ChaseAngle> angle = {});
static void _clearUnitStateMove(T* u) { u->ClearUnitState(UNIT_STATE_FOLLOW_MOVE); }
static void _addUnitStateMove(T* u) { u->AddUnitState(UNIT_STATE_FOLLOW_MOVE); }
bool EnableWalking() const;
bool _lostTarget(T*) const { return false; }
void _reachTarget(T*) {}
private:
void _updateSpeed(T* owner);
private:
PathGenerator* i_path;
TimeTrackerSmall i_recheckDistance;
bool i_recalculateTravel;
std::optional<Position> _lastTargetPosition;
float _range;
ChaseAngle _angle;
};
#endif

View File

@@ -158,9 +158,11 @@ bool WaypointMovementGenerator<Creature>::StartMove(Creature* creature)
trans->CalculatePassengerPosition(formationDest.x, formationDest.y, formationDest.z, &formationDest.orientation);
}
float z = node->z;
creature->UpdateAllowedPositionZ(node->x, node->y, z);
//! Do not use formationDest here, MoveTo requires transport offsets due to DisableTransportPathTransformations() call
//! but formationDest contains global coordinates
init.MoveTo(node->x, node->y, node->z);
init.MoveTo(node->x, node->y, z, true, true);
//! Accepts angles such as 0.00001 and -0.00001, 0 must be ignored, default value in waypoint table
if (node->orientation && node->delay)
@@ -236,11 +238,11 @@ bool WaypointMovementGenerator<Creature>::DoUpdate(Creature* creature, uint32 di
Stop(sWorld->getIntConfig(CONFIG_WAYPOINT_MOVEMENT_STOP_TIME_FOR_PLAYER) * IN_MILLISECONDS);
else
{
bool finished = creature->movespline->Finalized();
// xinef: code to detect pre-empetively if we should start movement to next waypoint
// xinef: do not start pre-empetive movement if current node has delay or we are ending waypoint movement
bool finished = creature->movespline->Finalized();
if (!finished && !i_path->at(i_currentNode)->delay && ((i_currentNode != i_path->size() - 1) || repeating))
finished = (creature->movespline->_Spline().length(creature->movespline->_currentSplineIdx() + 1) - creature->movespline->timePassed()) < 200;
//if (!finished && !i_path->at(i_currentNode)->delay && ((i_currentNode != i_path->size() - 1) || repeating))
// finished = (creature->movespline->_Spline().length(creature->movespline->_currentSplineIdx() + 1) - creature->movespline->timePassed()) < 200;
if (finished)
{