Core/Movement: Replace old TargetedMovementGenerator into ChaseMovementGenerator and FollowMovementGenerator, full rewrite for both.

Port From (https://github.com/TrinityCore/TrinityCore/commit/14939204955d1a24fe465cdfcbf307daf3ce4f09)
This commit is contained in:
hondacrx
2021-09-26 10:29:27 -04:00
parent 4004f583a9
commit ad1975b277
22 changed files with 712 additions and 629 deletions
+1 -1
View File
@@ -208,7 +208,7 @@ namespace Framework.Constants
/// </summary>
public const int MaxPetStables = 4;
public const float PetFollowDist = 1.0f;
public const float PetFollowAngle = MathFunctions.PiOver2;
public const float PetFollowAngle = MathF.PI;
public const int MaxSpellCharm = 4;
public const int ActionBarIndexStart = 0;
public const byte ActionBarIndexPetSpellStart = 3;
+1 -1
View File
@@ -474,7 +474,7 @@ namespace Game.AI
ClearCharmInfoFlags();
me.GetCharmInfo().SetIsCommandAttack(oldCmdAttack); // For passive pets commanded to attack so they will use spells
me.GetMotionMaster().Clear();
me.GetMotionMaster().MoveChase(target, me.GetPetChaseDistance());
me.GetMotionMaster().MoveChase(target, me.GetPetChaseDistance(), MathF.PI);
}
else
{
+2 -10
View File
@@ -1245,11 +1245,7 @@ namespace Game.Chat
break;
case MovementGeneratorType.Chase:
{
Unit target;
if (unit.IsTypeId(TypeId.Player))
target = ((ChaseMovementGenerator<Player>)movementGenerator).GetTarget();
else
target = ((ChaseMovementGenerator<Creature>)movementGenerator).GetTarget();
Unit target = ((ChaseMovementGenerator)movementGenerator).GetTarget();
if (!target)
handler.SendSysMessage(CypherStrings.MovegensChaseNull);
@@ -1261,11 +1257,7 @@ namespace Game.Chat
}
case MovementGeneratorType.Follow:
{
Unit target;
if (unit.IsTypeId(TypeId.Player))
target = ((FollowMovementGenerator<Player>)movementGenerator).GetTarget();
else
target = ((FollowMovementGenerator<Creature>)movementGenerator).GetTarget();
Unit target = ((FollowMovementGenerator)movementGenerator).GetTarget();
if (!target)
handler.SendSysMessage(CypherStrings.MovegensFollowNull);
+1 -2
View File
@@ -982,8 +982,7 @@ namespace Game.Chat
return false;
}
FollowMovementGenerator<Creature> mgen = (FollowMovementGenerator<Creature>)creature.GetMotionMaster().Top();
FollowMovementGenerator mgen = (FollowMovementGenerator)creature.GetMotionMaster().Top();
if (mgen.GetTarget() != player)
{
handler.SendSysMessage(CypherStrings.CreatureNotFollowYou, creature.GetName());
+1 -1
View File
@@ -235,7 +235,7 @@ namespace Game.Entities
return GetAbsoluteAngle(pos.GetPositionX(), pos.GetPositionY());
}
float ToAbsoluteAngle(float relAngle)
public float ToAbsoluteAngle(float relAngle)
{
return NormalizeOrientation(relAngle + Orientation);
}
+6 -4
View File
@@ -1471,14 +1471,16 @@ namespace Game.Entities
}
}
public bool IsWithinMeleeRange(Unit obj)
public bool IsWithinMeleeRange(Unit obj) { return IsWithinMeleeRangeAt(GetPosition(), obj); }
public bool IsWithinMeleeRangeAt(Position pos, Unit obj)
{
if (!obj || !IsInMap(obj) || !IsInPhase(obj))
return false;
float dx = GetPositionX() - obj.GetPositionX();
float dy = GetPositionY() - obj.GetPositionY();
float dz = GetPositionZ() - obj.GetPositionZ();
float dx = pos.GetPositionX() - obj.GetPositionX();
float dy = pos.GetPositionY() - obj.GetPositionY();
float dz = pos.GetPositionZ() - obj.GetPositionZ();
float distsq = (dx * dx) + (dy * dy) + (dz * dz);
float maxdist = GetMeleeRange(obj) + GetTotalAuraModifier(AuraType.ModAutoAttackRange);
+1 -1
View File
@@ -41,7 +41,7 @@ namespace Game.Entities
//Movement
protected float[] m_speed_rate = new float[(int)UnitMoveType.Max];
RefManager<Unit, ITargetedMovementGeneratorBase> m_FollowingRefManager;
List<AbstractFollower> m_followingMe = new();
public MoveSpline MoveSpline { get; set; }
MotionMaster i_motionMaster;
public uint m_movementCounter; //< Incrementing counter used in movement packets
+94 -95
View File
@@ -406,57 +406,57 @@ namespace Game.Entities
case UnitMoveType.Walk:
return;
case UnitMoveType.Run:
{
if (IsMounted()) // Use on mount auras
{
if (IsMounted()) // Use on mount auras
{
main_speed_mod = GetMaxPositiveAuraModifier(AuraType.ModIncreaseMountedSpeed);
stack_bonus = GetTotalAuraMultiplier(AuraType.ModMountedSpeedAlways);
non_stack_bonus += GetMaxPositiveAuraModifier(AuraType.ModMountedSpeedNotStack) / 100.0f;
}
else
{
main_speed_mod = GetMaxPositiveAuraModifier(AuraType.ModIncreaseSpeed);
stack_bonus = GetTotalAuraMultiplier(AuraType.ModSpeedAlways);
non_stack_bonus += GetMaxPositiveAuraModifier(AuraType.ModSpeedNotStack) / 100.0f;
}
break;
main_speed_mod = GetMaxPositiveAuraModifier(AuraType.ModIncreaseMountedSpeed);
stack_bonus = GetTotalAuraMultiplier(AuraType.ModMountedSpeedAlways);
non_stack_bonus += GetMaxPositiveAuraModifier(AuraType.ModMountedSpeedNotStack) / 100.0f;
}
else
{
main_speed_mod = GetMaxPositiveAuraModifier(AuraType.ModIncreaseSpeed);
stack_bonus = GetTotalAuraMultiplier(AuraType.ModSpeedAlways);
non_stack_bonus += GetMaxPositiveAuraModifier(AuraType.ModSpeedNotStack) / 100.0f;
}
break;
}
case UnitMoveType.Swim:
{
main_speed_mod = GetMaxPositiveAuraModifier(AuraType.ModIncreaseSwimSpeed);
break;
}
{
main_speed_mod = GetMaxPositiveAuraModifier(AuraType.ModIncreaseSwimSpeed);
break;
}
case UnitMoveType.Flight:
{
if (IsTypeId(TypeId.Unit) && IsControlledByPlayer()) // not sure if good for pet
{
if (IsTypeId(TypeId.Unit) && IsControlledByPlayer()) // not sure if good for pet
{
main_speed_mod = GetMaxPositiveAuraModifier(AuraType.ModIncreaseVehicleFlightSpeed);
stack_bonus = GetTotalAuraMultiplier(AuraType.ModVehicleSpeedAlways);
main_speed_mod = GetMaxPositiveAuraModifier(AuraType.ModIncreaseVehicleFlightSpeed);
stack_bonus = GetTotalAuraMultiplier(AuraType.ModVehicleSpeedAlways);
// for some spells this mod is applied on vehicle owner
int owner_speed_mod = 0;
// for some spells this mod is applied on vehicle owner
int owner_speed_mod = 0;
Unit owner = GetCharmer();
if (owner != null)
owner_speed_mod = owner.GetMaxPositiveAuraModifier(AuraType.ModIncreaseVehicleFlightSpeed);
Unit owner = GetCharmer();
if (owner != null)
owner_speed_mod = owner.GetMaxPositiveAuraModifier(AuraType.ModIncreaseVehicleFlightSpeed);
main_speed_mod = Math.Max(main_speed_mod, owner_speed_mod);
}
else if (IsMounted())
{
main_speed_mod = GetMaxPositiveAuraModifier(AuraType.ModIncreaseMountedFlightSpeed);
stack_bonus = GetTotalAuraMultiplier(AuraType.ModMountedFlightSpeedAlways);
}
else // Use not mount (shapeshift for example) auras (should stack)
main_speed_mod = GetTotalAuraModifier(AuraType.ModIncreaseFlightSpeed) + GetTotalAuraModifier(AuraType.ModIncreaseVehicleFlightSpeed);
non_stack_bonus += GetMaxPositiveAuraModifier(AuraType.ModFlightSpeedNotStack) / 100.0f;
// Update speed for vehicle if available
if (IsTypeId(TypeId.Player) && GetVehicle() != null)
GetVehicleBase().UpdateSpeed(UnitMoveType.Flight);
break;
main_speed_mod = Math.Max(main_speed_mod, owner_speed_mod);
}
else if (IsMounted())
{
main_speed_mod = GetMaxPositiveAuraModifier(AuraType.ModIncreaseMountedFlightSpeed);
stack_bonus = GetTotalAuraMultiplier(AuraType.ModMountedFlightSpeedAlways);
}
else // Use not mount (shapeshift for example) auras (should stack)
main_speed_mod = GetTotalAuraModifier(AuraType.ModIncreaseFlightSpeed) + GetTotalAuraModifier(AuraType.ModIncreaseVehicleFlightSpeed);
non_stack_bonus += GetMaxPositiveAuraModifier(AuraType.ModFlightSpeedNotStack) / 100.0f;
// Update speed for vehicle if available
if (IsTypeId(TypeId.Player) && GetVehicle() != null)
GetVehicleBase().UpdateSpeed(UnitMoveType.Flight);
break;
}
default:
Log.outError(LogFilter.Unit, "Unit.UpdateSpeed: Unsupported move type ({0})", mtype);
return;
@@ -472,71 +472,70 @@ namespace Game.Entities
case UnitMoveType.Run:
case UnitMoveType.Swim:
case UnitMoveType.Flight:
{
// Set creature speed rate
if (IsTypeId(TypeId.Unit))
speed *= ToCreature().GetCreatureTemplate().SpeedRun; // at this point, MOVE_WALK is never reached
// Normalize speed by 191 aura SPELL_AURA_USE_NORMAL_MOVEMENT_SPEED if need
// @todo possible affect only on MOVE_RUN
int normalization = GetMaxPositiveAuraModifier(AuraType.UseNormalMovementSpeed);
if (normalization != 0)
{
// Set creature speed rate
if (IsTypeId(TypeId.Unit))
Creature creature1 = ToCreature();
if (creature1)
{
Unit pOwner = GetCharmerOrOwner();
if ((IsPet() || IsGuardian()) && !IsInCombat() && pOwner != null) // Must check for owner or crash on "Tame Beast"
{
// For every yard over 5, increase speed by 0.01
// to help prevent pet from lagging behind and despawning
float dist = GetDistance(pOwner);
float base_rate = 1.00f; // base speed is 100% of owner speed
if (dist < 5)
dist = 5;
float mult = base_rate + ((dist - 5) * 0.01f);
speed *= pOwner.GetSpeedRate(mtype) * mult; // pets derive speed from owner when not in combat
}
else
speed *= ToCreature().GetCreatureTemplate().SpeedRun; // at this point, MOVE_WALK is never reached
uint immuneMask = creature1.GetCreatureTemplate().MechanicImmuneMask;
if (Convert.ToBoolean(immuneMask & (1 << ((int)Mechanics.Snare - 1))) || Convert.ToBoolean(immuneMask & (1 << ((int)Mechanics.Daze - 1))))
break;
}
// Normalize speed by 191 aura SPELL_AURA_USE_NORMAL_MOVEMENT_SPEED if need
// @todo possible affect only on MOVE_RUN
int normalization = GetMaxPositiveAuraModifier(AuraType.UseNormalMovementSpeed);
if (normalization != 0)
{
Creature creature = ToCreature();
if (creature)
{
uint immuneMask = creature.GetCreatureTemplate().MechanicImmuneMask;
if (Convert.ToBoolean(immuneMask & (1 << ((int)Mechanics.Snare - 1))) || Convert.ToBoolean(immuneMask & (1 << ((int)Mechanics.Daze - 1))))
break;
}
// Use speed from aura
float max_speed = normalization / (IsControlledByPlayer() ? SharedConst.playerBaseMoveSpeed[(int)mtype] : SharedConst.baseMoveSpeed[(int)mtype]);
if (speed > max_speed)
speed = max_speed;
}
if (mtype == UnitMoveType.Run)
{
// force minimum speed rate @ aura 437 SPELL_AURA_MOD_MINIMUM_SPEED_RATE
int minSpeedMod1 = GetMaxPositiveAuraModifier(AuraType.ModMinimumSpeedRate);
if (minSpeedMod1 != 0)
{
float minSpeed = minSpeedMod1 / (IsControlledByPlayer() ? SharedConst.playerBaseMoveSpeed[(int)mtype] : SharedConst.baseMoveSpeed[(int)mtype]);
if (speed < minSpeed)
speed = minSpeed;
}
}
break;
// Use speed from aura
float max_speed = normalization / (IsControlledByPlayer() ? SharedConst.playerBaseMoveSpeed[(int)mtype] : SharedConst.baseMoveSpeed[(int)mtype]);
if (speed > max_speed)
speed = max_speed;
}
if (mtype == UnitMoveType.Run)
{
// force minimum speed rate @ aura 437 SPELL_AURA_MOD_MINIMUM_SPEED_RATE
int minSpeedMod1 = GetMaxPositiveAuraModifier(AuraType.ModMinimumSpeedRate);
if (minSpeedMod1 != 0)
{
float minSpeed = minSpeedMod1 / (IsControlledByPlayer() ? SharedConst.playerBaseMoveSpeed[(int)mtype] : SharedConst.baseMoveSpeed[(int)mtype]);
if (speed < minSpeed)
speed = minSpeed;
}
}
break;
}
default:
break;
}
// for creature case, we check explicit if mob searched for assistance
if (IsTypeId(TypeId.Unit))
Creature creature = ToCreature();
if (creature != null)
{
// for creature case, we check explicit if mob searched for assistance
if (ToCreature().HasSearchedAssistance())
speed *= 0.66f; // best guessed value, so this will be 33% reduction. Based off initial speed, mob can then "run", "walk fast" or "walk".
if (creature.HasUnitTypeMask(UnitTypeMask.Minion) && !creature.IsInCombat())
{
var top = creature.GetMotionMaster().Top();
if (top != null && top.GetMovementGeneratorType() == MovementGeneratorType.Follow)
{
Unit followed = ((AbstractFollower)top).GetTarget();
if (followed != null && followed.GetGUID() == GetOwnerGUID() && !followed.IsInCombat())
{
float ownerSpeed = followed.GetSpeedRate(mtype);
if (speed < ownerSpeed || creature.IsWithinDist3d(followed, 10.0f))
speed = ownerSpeed;
speed *= Math.Min(Math.Max(1.0f, 0.75f + (GetDistance(followed) - SharedConst.PetFollowDist) * 0.05f), 1.3f);
}
}
}
}
// Apply strongest slow aura mod to speed
+11 -6
View File
@@ -44,7 +44,6 @@ namespace Game.Entities
m_combatManager = new CombatManager(this);
m_threatManager = new ThreatManager(this);
_spellHistory = new SpellHistory(this);
m_FollowingRefManager = new RefManager<Unit, ITargetedMovementGeneratorBase>();
ObjectTypeId = TypeId.Unit;
ObjectTypeMask |= TypeMask.Unit;
@@ -454,6 +453,8 @@ namespace Game.Entities
RemoveAreaAurasDueToLeaveWorld();
RemoveAllFollowers();
if (!GetCharmerGUID().IsEmpty())
{
Log.outFatal(LogFilter.Unit, "Unit {0} has charmer guid when removed from world", GetEntry());
@@ -1312,6 +1313,13 @@ namespace Game.Entities
public Totem ToTotem() { return IsTotem() ? (this as Totem) : null; }
public TempSummon ToTempSummon() { return IsSummon() ? (this as TempSummon) : null; }
void RemoveAllFollowers()
{
while (!m_followingMe.Empty())
m_followingMe[0].SetTarget(null);
}
public virtual void SetDeathState(DeathState s)
{
// Death state needs to be updated before RemoveAllAurasOnDeath() is called, to prevent entering combat
@@ -1563,11 +1571,8 @@ namespace Game.Entities
public bool IsCharmedOwnedByPlayerOrPlayer() { return GetCharmerOrOwnerOrOwnGUID().IsPlayer(); }
public void AddFollower(FollowerReference pRef)
{
m_FollowingRefManager.InsertFirst(pRef);
}
public void RemoveFollower(FollowerReference pRef) { } //nothing to do yet
public void FollowerAdded(AbstractFollower f) { m_followingMe.Add(f); }
public void FollowerRemoved(AbstractFollower f) { m_followingMe.Remove(f); }
public uint GetCreatureTypeMask()
{
+49
View File
@@ -0,0 +1,49 @@
/*
* Copyright (C) 2012-2020 CypherCore <http://github.com/CypherCore>
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Game.Entities;
namespace Game.Movement
{
public class AbstractFollower
{
Unit _target;
public AbstractFollower(Unit target = null) { SetTarget(target); }
public void SetTarget(Unit unit)
{
if (unit == _target)
return;
if (_target)
_target.FollowerRemoved(this);
_target = unit;
if (_target)
_target.FollowerAdded(this);
}
public Unit GetTarget() { return _target; }
}
}
@@ -0,0 +1,214 @@
/*
* Copyright (C) 2012-2020 CypherCore <http://github.com/CypherCore>
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
using Framework.Constants;
using Game.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Framework.GameMath;
namespace Game.Movement
{
class ChaseMovementGenerator : AbstractFollower, IMovementGenerator
{
static uint RANGE_CHECK_INTERVAL = 100; // time (ms) until we attempt to recalculate
ChaseRange? _range;
ChaseAngle? _angle;
PathGenerator _path;
Position _lastTargetPosition;
uint _rangeCheckTimer = RANGE_CHECK_INTERVAL;
bool _movingTowards = true;
bool _mutualChase = true;
public ChaseMovementGenerator(Unit target, ChaseRange? range, ChaseAngle? angle) : base(target)
{
_range = range;
_angle = angle;
}
public void Initialize(Unit owner)
{
owner.AddUnitState(UnitState.Chase);
owner.SetWalk(false);
}
public bool Update(Unit owner, uint diff)
{
// owner might be dead or gone (can we even get nullptr here?)
if (!owner || !owner.IsAlive())
return false;
// our target might have gone away
Unit target = GetTarget();
if (!target)
return false;
// the owner might've selected a different target (feels like we shouldn't check this here...)
if (owner.GetVictim() != target)
return false;
// the owner might be unable to move (rooted or casting), pause movement
if (owner.HasUnitState(UnitState.NotMove) || owner.IsMovementPreventedByCasting())
{
owner.StopMoving();
return true;
}
bool mutualChase = IsMutualChase(owner, target);
float hitboxSum = owner.GetCombatReach() + target.GetCombatReach();
float minRange = _range.HasValue ? _range.Value.MinRange + hitboxSum : SharedConst.ContactDistance;
float minTarget = (_range.HasValue ? _range.Value.MinTolerance : 0.0f) + hitboxSum;
float maxRange = _range.HasValue ? _range.Value.MaxRange + hitboxSum : owner.GetMeleeRange(target); // melee range already includes hitboxes
float maxTarget = _range.HasValue ? _range.Value.MaxTolerance + hitboxSum : SharedConst.ContactDistance + hitboxSum;
ChaseAngle? angle = mutualChase ? null : _angle;
// if we're already moving, periodically check if we're already in the expected range...
if (owner.HasUnitState(UnitState.ChaseMove))
{
if (_rangeCheckTimer > diff)
_rangeCheckTimer -= diff;
else
{
_rangeCheckTimer = RANGE_CHECK_INTERVAL;
if (PositionOkay(owner, target, _movingTowards ? null : minTarget, _movingTowards ? maxTarget : null, angle))
{
_path = null;
owner.StopMoving();
owner.SetInFront(target);
return true;
}
}
}
// if we're done moving, we want to clean up
if (owner.HasUnitState(UnitState.ChaseMove) && owner.MoveSpline.Finalized())
{
_path = null;
owner.ClearUnitState(UnitState.ChaseMove);
owner.SetInFront(target);
}
// if the target moved, we have to consider whether to adjust
if (_lastTargetPosition != target.GetPosition() || mutualChase != _mutualChase)
{
_lastTargetPosition = target.GetPosition();
_mutualChase = mutualChase;
if (owner.HasUnitState(UnitState.ChaseMove) || !PositionOkay(owner, target, minRange, maxRange, angle))
{
Creature cOwner = owner.ToCreature();
// can we get to the target?
if (cOwner != null && !target.IsInAccessiblePlaceFor(cOwner))
{
cOwner.SetCannotReachTarget(true);
cOwner.StopMoving();
_path = null;
return true;
}
// figure out which way we want to move
bool moveToward = !owner.IsInDist(target, maxRange);
// make a new path if we have to...
if (_path == null || moveToward != _movingTowards)
_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.HasValue)
{
// ...we'll pathfind to the center, then shorten the path
target.GetPosition(out x, out y, out z);
shortenPath = true;
}
else
{
// otherwise, we fall back to nearpoint finding
target.GetNearPoint(owner, out x, out y, out z, (moveToward ? maxTarget : minTarget) - hitboxSum, angle.HasValue ? target.ToAbsoluteAngle(angle.Value.RelativeAngle) : target.GetAbsoluteAngle(owner));
shortenPath = false;
}
if (owner.IsHovering())
owner.UpdateAllowedPositionZ(x, y, ref z);
bool success = _path.CalculatePath(x, y, z);
if (!success || _path.GetPathType().HasFlag(PathType.NoPath))
{
if (cOwner)
cOwner.SetCannotReachTarget(true);
owner.StopMoving();
return true;
}
if (shortenPath)
_path.ShortenPathUntilDist(target, maxTarget);
if (cOwner)
cOwner.SetCannotReachTarget(false);
owner.AddUnitState(UnitState.ChaseMove);
MoveSplineInit init = new(owner);
init.MovebyPath(_path.GetPath());
init.SetWalk(false);
init.SetFacing(target);
init.Launch();
}
}
// and then, finally, we're done for the tick
return true;
}
public void Finalize(Unit owner)
{
owner.ClearUnitState(UnitState.Chase | UnitState.ChaseMove);
Creature cOwner = owner.ToCreature();
if (cOwner != null)
cOwner.SetCannotReachTarget(false);
}
public MovementGeneratorType GetMovementGeneratorType() { return MovementGeneratorType.Chase; }
public void Reset(Unit owner) { Initialize(owner); }
public void UnitSpeedChanged() { _lastTargetPosition.Relocate(0.0f, 0.0f, 0.0f); }
static bool IsMutualChase(Unit owner, Unit target)
{
IMovementGenerator gen = target.GetMotionMaster().Top();
if (gen == null || gen.GetMovementGeneratorType() != MovementGeneratorType.Chase)
return false;
return ((ChaseMovementGenerator)gen).GetTarget() == owner;
}
static bool PositionOkay(Unit owner, Unit target, float? minDistance, float? maxDistance, ChaseAngle? angle)
{
float distSq = owner.GetExactDistSq(target);
if (minDistance.HasValue && distSq < MathF.Sqrt(minDistance.Value))
return false;
if (maxDistance.HasValue && distSq > MathF.Sqrt(maxDistance.Value))
return false;
return !angle.HasValue || angle.Value.IsAngleOkay(target.GetRelativeAngle(owner));
}
}
}
@@ -0,0 +1,178 @@
/*
* Copyright (C) 2012-2020 CypherCore <http://github.com/CypherCore>
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
using Framework.Constants;
using Framework.GameMath;
using Game.Entities;
using System;
namespace Game.Movement
{
public class FollowMovementGenerator : AbstractFollower, IMovementGenerator
{
static uint CHECK_INTERVAL = 500;
static float FOLLOW_RANGE_TOLERANCE = 1.0f;
float _range;
ChaseAngle _angle;
uint _checkTimer = CHECK_INTERVAL;
PathGenerator _path;
Position _lastTargetPosition;
public FollowMovementGenerator(Unit target, float range, ChaseAngle angle) : base(target)
{
_range = range;
_angle = angle;
}
public void Initialize(Unit owner)
{
owner.AddUnitState(UnitState.Follow);
UpdatePetSpeed(owner);
}
public bool Update(Unit owner, uint diff)
{
// owner might be dead or gone
if (!owner.IsAlive())
return false;
// our target might have gone away
Unit target = GetTarget();
if (target == null)
return false;
if (owner.HasUnitState(UnitState.NotMove) || owner.IsMovementPreventedByCasting())
{
owner.StopMoving();
return true;
}
if (owner.HasUnitState(UnitState.FollowMove))
{
if (_checkTimer > diff)
_checkTimer -= diff;
else
{
_checkTimer = CHECK_INTERVAL;
if (PositionOkay(owner, target, _range, _angle))
{
_path = null;
owner.StopMoving();
return true;
}
}
}
if (owner.HasUnitState(UnitState.FollowMove) && owner.MoveSpline.Finalized())
{
_path = null;
owner.ClearUnitState(UnitState.FollowMove);
}
if (_lastTargetPosition.GetExactDistSq(target.GetPosition()) > 0.0f)
{
_lastTargetPosition = target.GetPosition();
if (owner.HasUnitState(UnitState.FollowMove) || !PositionOkay(owner, target, _range + FOLLOW_RANGE_TOLERANCE))
{
if (_path == null)
_path = new PathGenerator(owner);
float x, y, z;
// select angle
float tAngle;
float curAngle = target.GetRelativeAngle(owner);
if (_angle.IsAngleOkay(curAngle))
tAngle = curAngle;
else
{
float diffUpper = Position.NormalizeOrientation(curAngle - _angle.UpperBound());
float diffLower = Position.NormalizeOrientation(_angle.LowerBound() - curAngle);
if (diffUpper < diffLower)
tAngle = _angle.UpperBound();
else
tAngle = _angle.LowerBound();
}
target.GetNearPoint(owner, out x, out y, out z, _range, target.ToAbsoluteAngle(tAngle));
if (owner.IsHovering())
owner.UpdateAllowedPositionZ(x, y, ref z);
// pets are allowed to "cheat" on pathfinding when following their master
bool allowShortcut = false;
Pet oPet = owner.ToPet();
if (oPet != null)
if (target.GetGUID() == oPet.GetOwnerGUID())
allowShortcut = true;
bool success = _path.CalculatePath(x, y, z, allowShortcut);
if (!success || _path.GetPathType().HasFlag(PathType.NoPath))
{
owner.StopMoving();
return true;
}
owner.AddUnitState(UnitState.FollowMove);
MoveSplineInit init = new(owner);
init.MovebyPath(_path.GetPath());
init.SetWalk(target.IsWalking());
init.SetFacing(target.GetOrientation());
init.Launch();
}
}
return true;
}
public void Finalize(Unit owner)
{
owner.ClearUnitState(UnitState.Follow | UnitState.FollowMove);
UpdatePetSpeed(owner);
}
void UpdatePetSpeed(Unit owner)
{
Pet oPet = owner.ToPet();
if (oPet != null)
{
if (!GetTarget() || GetTarget().GetGUID() == owner.GetOwnerGUID())
{
oPet.UpdateSpeed(UnitMoveType.Run);
oPet.UpdateSpeed(UnitMoveType.Walk);
oPet.UpdateSpeed(UnitMoveType.Swim);
}
}
}
public void Reset(Unit owner) { }
public MovementGeneratorType GetMovementGeneratorType() { return MovementGeneratorType.Follow; }
public void UnitSpeedChanged() { _lastTargetPosition.Relocate(0.0f, 0.0f, 0.0f); }
static bool PositionOkay(Unit owner, Unit target, float range, ChaseAngle? angle = null)
{
if (owner.GetExactDistSq(target) > MathF.Sqrt(owner.GetCombatReach() + target.GetCombatReach() + range))
return false;
return !angle.HasValue || angle.Value.IsAngleOkay(target.GetRelativeAngle(owner));
}
}
}
@@ -133,7 +133,7 @@ namespace Game.Movement
MovementInform(owner);
}
public override void UnitSpeedChanged()
public void UnitSpeedChanged()
{
_recalculateSpeed = true;
}
+15 -15
View File
@@ -22,27 +22,27 @@ namespace Game.Movement
{
public class IdleMovementGenerator : IMovementGenerator
{
public override void Initialize(Unit owner)
public void Initialize(Unit owner)
{
Reset(owner);
}
public override void Reset(Unit owner)
public void Reset(Unit owner)
{
if (!owner.IsStopped())
owner.StopMoving();
}
public override bool Update(Unit owner, uint diff)
public bool Update(Unit owner, uint diff)
{
return true;
}
public override void Finalize(Unit owner)
public void Finalize(Unit owner)
{
}
public override MovementGeneratorType GetMovementGeneratorType()
public MovementGeneratorType GetMovementGeneratorType()
{
return MovementGeneratorType.Idle;
}
@@ -59,7 +59,7 @@ namespace Game.Movement
_direction = direction;
}
public override void Initialize(Unit owner)
public void Initialize(Unit owner)
{
if (!owner.IsStopped())
owner.StopMoving();
@@ -71,9 +71,9 @@ namespace Game.Movement
owner.AttackStop();
}
public override void Reset(Unit owner) { }
public void Reset(Unit owner) { }
public override bool Update(Unit owner, uint diff)
public bool Update(Unit owner, uint diff)
{
float angle = owner.GetOrientation();
angle += diff * MathFunctions.TwoPi / _maxDuration * (_direction == RotateDirection.Left ? 1.0f : -1.0f);
@@ -90,14 +90,14 @@ namespace Game.Movement
return true;
}
public override void Finalize(Unit owner)
public void Finalize(Unit owner)
{
owner.ClearUnitState(UnitState.Rotating);
if (owner.IsTypeId(TypeId.Unit))
owner.ToCreature().GetAI().MovementInform(MovementGeneratorType.Rotate, 0);
}
public override MovementGeneratorType GetMovementGeneratorType() { return MovementGeneratorType.Rotate; }
public MovementGeneratorType GetMovementGeneratorType() { return MovementGeneratorType.Rotate; }
uint _duration;
uint _maxDuration;
@@ -111,7 +111,7 @@ namespace Game.Movement
_timer = timer;
}
public override void Initialize(Unit owner)
public void Initialize(Unit owner)
{
// Distracted creatures stand up if not standing
if (!owner.IsStandState())
@@ -120,9 +120,9 @@ namespace Game.Movement
owner.AddUnitState(UnitState.Distracted);
}
public override void Reset(Unit owner) { }
public void Reset(Unit owner) { }
public override bool Update(Unit owner, uint diff)
public bool Update(Unit owner, uint diff)
{
if (diff > _timer)
return false;
@@ -131,7 +131,7 @@ namespace Game.Movement
return true;
}
public override void Finalize(Unit owner)
public virtual void Finalize(Unit owner)
{
owner.ClearUnitState(UnitState.Distracted);
@@ -143,7 +143,7 @@ namespace Game.Movement
}
}
public override MovementGeneratorType GetMovementGeneratorType() { return MovementGeneratorType.Distract; }
public virtual MovementGeneratorType GetMovementGeneratorType() { return MovementGeneratorType.Distract; }
uint _timer;
}
@@ -18,51 +18,52 @@
using Framework.Constants;
using Framework.Dynamic;
using Game.Entities;
using Framework.GameMath;
namespace Game.Movement
{
public abstract class IMovementGenerator
public interface IMovementGenerator
{
public abstract void Finalize(Unit owner);
public void Finalize(Unit owner);
public abstract void Initialize(Unit owner);
public void Initialize(Unit owner);
public abstract void Reset(Unit owner);
public void Reset(Unit owner);
public abstract bool Update(Unit owner, uint time_diff);
public bool Update(Unit owner, uint time_diff);
public abstract MovementGeneratorType GetMovementGeneratorType();
public MovementGeneratorType GetMovementGeneratorType();
public virtual void UnitSpeedChanged() { }
public void UnitSpeedChanged() { }
public virtual void Pause(uint timer = 0) { }
public void Pause(uint timer = 0) { }
public virtual void Resume(uint overrideTimer = 0) { }
public void Resume(uint overrideTimer = 0) { }
// used by Evade code for select point to evade with expected restart default movement
public virtual bool GetResetPosition(Unit u, out float x, out float y, out float z)
public bool GetResetPosition(Unit u, out float x, out float y, out float z)
{
x = y = z = 0.0f;
x = y = z = 0.0f;
return false;
}
}
public abstract class MovementGeneratorMedium<T> : IMovementGenerator where T : Unit
{
public override void Initialize(Unit owner)
public virtual void Initialize(Unit owner)
{
DoInitialize((T)owner);
IsActive = true;
}
public override void Finalize(Unit owner)
public virtual void Finalize(Unit owner)
{
DoFinalize((T)owner);
}
public override void Reset(Unit owner)
public virtual void Reset(Unit owner)
{
DoReset((T)owner);
}
public override bool Update(Unit owner, uint diff)
public virtual bool Update(Unit owner, uint diff)
{
return DoUpdate((T)owner, diff);
}
@@ -73,23 +74,7 @@ namespace Game.Movement
public abstract void DoFinalize(T owner);
public abstract void DoReset(T owner);
public abstract bool DoUpdate(T owner, uint diff);
}
public class FollowerReference : Reference<Unit, ITargetedMovementGeneratorBase>
{
public override void TargetObjectBuildLink()
{
GetTarget().AddFollower(this);
}
public override void TargetObjectDestroyLink()
{
GetTarget().RemoveFollower(this);
}
public override void SourceObjectDestroyLink()
{
GetSource().StopFollowing();
}
public virtual MovementGeneratorType GetMovementGeneratorType() { return MovementGeneratorType.Max; }
}
}
@@ -864,43 +864,52 @@ namespace Game.Movement
return (p1 - p2).GetLengthSquared();
}
public void ReducePathLenghtByDist(float dist)
public void ShortenPathUntilDist(Position pos, float dist) { ShortenPathUntilDist(new Vector3(pos.posX, pos.posY, pos.posZ), dist); }
public void ShortenPathUntilDist(Vector3 target, float dist)
{
if (GetPathType() == PathType.Blank)
if (GetPathType() == PathType.Blank || _pathPoints.Length < 2)
{
Log.outError(LogFilter.Maps, "PathGenerator.ReducePathLenghtByDist called before path was built");
Log.outError(LogFilter.Maps, "PathGenerator.ReducePathLengthByDist called before path was successfully built");
return;
}
if (_pathPoints.Length < 2) // path building failure
float distSq = dist * dist;
// the first point of the path must be outside the specified range
// (this should have really been checked by the caller...)
if ((_pathPoints[0] - target).GetLengthSquared() < distSq)
return;
int i = _pathPoints.Length;
Vector3 nextVec = _pathPoints[--i];
while (i > 0)
// check if we even need to do anything
if ((_pathPoints[0] - target).GetLengthSquared() >= distSq)
return;
int i = _pathPoints.Length - 1;
// find the first i s.t.:
// - _pathPoints[i] is still too close
// - _pathPoints[i-1] is too far away
// => the end point is somewhere on the line between the two
while (true)
{
Vector3 currVec = _pathPoints[--i];
Vector3 diffVec = (nextVec - currVec);
float len = diffVec.GetLength();
if (len > dist)
// we know that pathPoints[i] is too close already (from the previous iteration)
if ((_pathPoints[i - 1] - target).GetLengthSquared() >= distSq)
break; // bingo!
if (--i == 0)
{
float step = dist / len;
// same as nextVec
_pathPoints[i + 1] -= diffVec * step;
_sourceUnit.UpdateAllowedPositionZ(_pathPoints[i + 1].X, _pathPoints[i + 1].Y, ref _pathPoints[i + 1].Z);
Array.Resize(ref _pathPoints, i + 2);
break;
}
else if (i == 0) // at second point
{
_pathPoints[1] = _pathPoints[0];
// no point found that fulfills the condition
_pathPoints[0] = _pathPoints[1];
Array.Resize(ref _pathPoints, 2);
break;
return;
}
dist -= len;
nextVec = currVec; // we're going backwards
}
// ok, _pathPoints[i] is too close, _pathPoints[i-1] is not, so our target point is somewhere between the two...
// ... settle for a guesstimate since i'm not confident in doing trig on every chase motion tick...
// (@todo review this)
_pathPoints[i] += (_pathPoints[i - 1] - _pathPoints[i]).directionOrZero() * (dist - (_pathPoints[i] - target).GetLength());
Array.Resize(ref _pathPoints, i + 1);
}
public bool IsInvalidDestinationZ(Unit target)
@@ -135,7 +135,7 @@ namespace Game.Movement
}
}
public override void UnitSpeedChanged()
public void UnitSpeedChanged()
{
_recalculateSpeed = true;
}
@@ -183,16 +183,16 @@ namespace Game.Movement
_arrivalSpellTargetGuid = arrivalSpellTargetGuid;
}
public override void Initialize(Unit owner) { }
public void Initialize(Unit owner) { }
public override void Reset(Unit owner) { }
public void Reset(Unit owner) { }
public override bool Update(Unit owner, uint diff)
public bool Update(Unit owner, uint diff)
{
return !owner.MoveSpline.Finalized();
}
public override void Finalize(Unit owner)
public void Finalize(Unit owner)
{
MovementInform(owner);
}
@@ -206,7 +206,7 @@ namespace Game.Movement
owner.ToCreature().GetAI().MovementInform(MovementGeneratorType.Effect, _pointId);
}
public override MovementGeneratorType GetMovementGeneratorType() { return MovementGeneratorType.Effect; }
public MovementGeneratorType GetMovementGeneratorType() { return MovementGeneratorType.Effect; }
uint _pointId;
uint _arrivalSpellId;
@@ -75,7 +75,7 @@ namespace Game.Movement
}
}
public override void Initialize(Unit me)
public void Initialize(Unit me)
{
if (_chainSize != 0)
{
@@ -114,7 +114,7 @@ namespace Game.Movement
}
}
public override void Finalize(Unit me)
public void Finalize(Unit me)
{
if (!finished)
return;
@@ -124,7 +124,7 @@ namespace Game.Movement
cMe.GetAI().MovementInform(MovementGeneratorType.SplineChain, _id);
}
public override bool Update(Unit me, uint diff)
public bool Update(Unit me, uint diff)
{
if (finished)
return false;
@@ -169,9 +169,9 @@ namespace Game.Movement
return new SplineChainResumeInfo(_id, _chain, _walk, (byte)(_nextIndex - 1), (byte)(me.MoveSpline.CurrentSplineIdx()), _msToNext);
}
public override void Reset(Unit owner) { }
public void Reset(Unit owner) { }
public override MovementGeneratorType GetMovementGeneratorType() { return MovementGeneratorType.SplineChain; }
public MovementGeneratorType GetMovementGeneratorType() { return MovementGeneratorType.SplineChain; }
uint _id;
List<SplineChainLink> _chain = new();
@@ -1,406 +0,0 @@
/*
* Copyright (C) 2012-2020 CypherCore <http://github.com/CypherCore>
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
using Framework.Constants;
using Framework.GameMath;
using Game.Entities;
using System;
namespace Game.Movement
{
public interface ITargetedMovementGeneratorBase
{
bool IsTargetValid();
Unit GetTarget();
void StopFollowing();
}
public abstract class TargetedMovementGenerator<T> : MovementGeneratorMedium<T>, ITargetedMovementGeneratorBase where T : Unit
{
protected static float MOVE_FOLLOW_REPOSITIONING_DISTANCE = 1.5f;
protected TargetedMovementGenerator(Unit target, float offset = 0, float angle = 0)
{
_target = new FollowerReference();
_target.Link(target, this);
_timer = new TimeTracker();
_offset = offset;
_angle = angle;
_recalculateTravel = false;
_targetReached = false;
}
public override bool DoUpdate(T owner, uint diff)
{
if (!IsTargetValid() || !GetTarget().IsInWorld)
return false;
if (owner == null || !owner.IsAlive())
return false;
if (owner.HasUnitState(UnitState.NotMove) || owner.IsMovementPreventedByCasting() || HasLostTarget(owner))
{
_interrupt = true;
owner.StopMoving();
return true;
}
if (_interrupt || _recalculateTravel)
{
_interrupt = false;
SetTargetLocation(owner, true);
return true;
}
bool targetMoved = false;
_timer.Update((int)diff);
if (_timer.Passed())
{
_timer.Reset(100);
// the allowed distance between target and mover before the mover needs to reposition in order to keep attacking
float distance = GetMaxDistanceBeforeRepositioning(owner);
if (owner.IsPet() && (owner.GetCharmerOrOwnerGUID() == GetTarget().GetGUID()))
distance = 1.0f + GetTarget().GetCombatReach(); // pet following owner
Vector3 destination = owner.MoveSpline.FinalDestination();
if (owner.MoveSpline.onTransport)
{
float o = 0;
ITransport transport = owner.GetDirectTransport();
if (transport != null)
transport.CalculatePassengerPosition(ref destination.X, ref destination.Y, ref destination.Z, ref o);
}
// First check distance
targetMoved = !GetTarget().IsInDist(destination.X, destination.Y, destination.Z, distance);
// then, if the target is in range, check also Line of Sight.
if (!targetMoved)
targetMoved = !GetTarget().IsWithinLOSInMap(owner);
}
if (targetMoved)
SetTargetLocation(owner, true);
else if (_speedChanged)
SetTargetLocation(owner, false);
if (!_targetReached && owner.MoveSpline.Finalized())
{
MovementInform(owner);
if (_angle == 0.0f && !owner.HasInArc(0.01f, GetTarget()))
owner.SetInFront(GetTarget());
if (!_targetReached)
{
_targetReached = true;
ReachTarget(owner);
}
}
return true;
}
public void SetTargetLocation(T owner, bool updateDestination)
{
if (!IsTargetValid() || !GetTarget().IsInWorld)
return;
if (!owner || !owner.IsAlive())
return;
if (owner.HasUnitState(UnitState.NotMove) || owner.IsMovementPreventedByCasting() || HasLostTarget(owner))
{
_interrupt = true;
owner.StopMoving();
return;
}
if (owner.IsTypeId(TypeId.Unit) && !GetTarget().IsInAccessiblePlaceFor(owner.ToCreature()))
{
owner.ToCreature().SetCannotReachTarget(true);
return;
}
float x, y, z;
if (updateDestination || _path == null)
{
if (_offset == 0)
{
if (GetTarget().IsWithinDistInMap(owner, SharedConst.ContactDistance))
return;
GetTarget().GetNearPoint(owner, out x, out y, out z, SharedConst.ContactDistance, GetTarget().GetAbsoluteAngle(owner));
}
else
{
float distance = _offset + 1.0f;
if (owner.IsPet() && GetTarget().GetTypeId() == TypeId.Player)
distance = 1.0f;
if (GetTarget().IsWithinDistInMap(owner, distance))
return;
GetTarget().GetClosePoint(out x, out y, out z, _offset, _angle);
}
if (owner.IsHovering())
owner.UpdateAllowedPositionZ(x, y, ref z);
}
else
{
// the destination has not changed, we just need to refresh the path (usually speed change)
var end = _path.GetEndPosition();
x = end.X;
y = end.Y;
z = end.Z;
}
if (_path == null)
_path = new PathGenerator(owner);
// allow pets to use shortcut if no path found when following their master
bool forceDest = owner.IsTypeId(TypeId.Unit) && owner.IsPet() && owner.HasUnitState(UnitState.Follow);
bool result = _path.CalculatePath(x, y, z, forceDest);
if (!result && Convert.ToBoolean(_path.GetPathType() & PathType.NoPath))
{
// Can't reach target
_recalculateTravel = true;
if (owner.IsTypeId(TypeId.Unit))
owner.ToCreature().SetCannotReachTarget(true);
return;
}
_targetReached = false;
_recalculateTravel = false;
_speedChanged = false;
AddUnitStateMove(owner);
if (owner.IsTypeId(TypeId.Unit))
owner.ToCreature().SetCannotReachTarget(false);
MoveSplineInit init = new(owner);
init.MovebyPath(_path.GetPath());
init.SetWalk(EnableWalking());
// Using the same condition for facing target as the one that is used for SetInFront on movement end
// - applies to ChaseMovementGenerator mostly
if (_angle == 0.0f)
init.SetFacing(GetTarget());
init.Launch();
}
bool IsReachable()
{
return _path != null ? _path.GetPathType().HasAnyFlag(PathType.Normal) : true;
}
public override void UnitSpeedChanged()
{
_speedChanged = true;
}
public abstract void ClearUnitStateMove(T owner);
public abstract void AddUnitStateMove(T owner);
public virtual bool HasLostTarget(T owner) { return false; }
public abstract void ReachTarget(T owner);
public virtual bool EnableWalking() { return false; }
public abstract void MovementInform(T owner);
public virtual float GetMaxDistanceBeforeRepositioning(T owner) { return 0.0f; }
public void StopFollowing() { }
public bool IsTargetValid() { return _target.IsValid(); }
public Unit GetTarget() { return _target.GetTarget(); }
FollowerReference _target;
PathGenerator _path;
TimeTracker _timer;
float _offset;
float _angle;
bool _recalculateTravel;
bool _speedChanged;
bool _targetReached;
bool _interrupt;
}
public class ChaseMovementGenerator<T> : TargetedMovementGenerator<T> where T : Unit
{
public ChaseMovementGenerator(Unit target)
: base(target)
{
}
public ChaseMovementGenerator(Unit target, float offset, float angle)
: base(target, offset, angle)
{
}
public override void DoInitialize(T owner)
{
if (owner.IsTypeId(TypeId.Unit))
owner.SetWalk(false);
owner.AddUnitState(UnitState.Chase);
SetTargetLocation(owner, true);
}
public override void DoReset(T owner)
{
DoInitialize(owner);
}
public override void DoFinalize(T owner)
{
owner.ClearUnitState(UnitState.Chase | UnitState.ChaseMove);
}
public override void ClearUnitStateMove(T owner)
{
owner.ClearUnitState(UnitState.ChaseMove);
}
public override void AddUnitStateMove(T owner)
{
owner.AddUnitState(UnitState.ChaseMove);
}
public override bool HasLostTarget(T owner)
{
return owner.GetVictim() != GetTarget();
}
public override void ReachTarget(T owner)
{
ClearUnitStateMove(owner);
if (owner.IsWithinMeleeRange(GetTarget()))
owner.Attack(GetTarget(), true);
if (owner.IsTypeId(TypeId.Unit))
owner.ToCreature().SetCannotReachTarget(false);
}
public override float GetMaxDistanceBeforeRepositioning(T owner)
{
// the notion of melee range and melee attack is clearly separated from the notion of the movement.
// As a seperation of concern refactoring, this value should be passed into parameter by the caller (during creation of ChaseMoveGen) instead of defining it
// here (or a callback should be used since this value depends on dynamic fields (combat reach).
return owner.GetMeleeRange(base.GetTarget());
}
public override void MovementInform(T owner)
{
if (owner.IsTypeId(TypeId.Unit))
{
// Pass back the GUIDLow of the target. If it is pet's owner then PetAI will handle
if (owner.ToCreature().GetAI() != null)
owner.ToCreature().GetAI().MovementInform(MovementGeneratorType.Chase, (uint)GetTarget().GetGUID().GetCounter());
}
}
public override MovementGeneratorType GetMovementGeneratorType() { return MovementGeneratorType.Chase; }
}
public class FollowMovementGenerator<T> : TargetedMovementGenerator<T> where T : Unit
{
public FollowMovementGenerator(Unit target) : base(target) { }
public FollowMovementGenerator(Unit target, float offset, float angle) : base(target, offset, angle) { }
public override void DoInitialize(T owner)
{
owner.AddUnitState(UnitState.Follow);
UpdateSpeed(owner);
SetTargetLocation(owner, true);
}
public override void DoReset(T owner)
{
DoInitialize(owner);
}
public override void DoFinalize(T owner)
{
owner.ClearUnitState(UnitState.Follow | UnitState.FollowMove);
UpdateSpeed(owner);
}
public override void ClearUnitStateMove(T owner)
{
owner.ClearUnitState(UnitState.FollowMove);
}
public override void AddUnitStateMove(T owner)
{
owner.AddUnitState(UnitState.FollowMove);
}
public override void ReachTarget(T owner)
{
ClearUnitStateMove(owner);
}
public override bool EnableWalking()
{
if (typeof(T) == typeof(Player))
return false;
else
return IsTargetValid() && GetTarget().IsWalking();
}
public override float GetMaxDistanceBeforeRepositioning(T owner)
{
return owner.GetCombatReach() + base.GetTarget().GetCombatReach() + MOVE_FOLLOW_REPOSITIONING_DISTANCE;
}
public override void MovementInform(T owner)
{
if (owner.IsTypeId(TypeId.Player))
return;
// Pass back the GUIDLow of the target. If it is pet's owner then PetAI will handle
if (owner.ToCreature().GetAI() != null)
owner.ToCreature().GetAI().MovementInform(MovementGeneratorType.Follow, (uint)GetTarget().GetGUID().GetCounter());
}
public override bool HasLostTarget(T u) { return false; }
public override MovementGeneratorType GetMovementGeneratorType()
{
return MovementGeneratorType.Follow;
}
void UpdateSpeed(T owner)
{
if (owner.IsTypeId(TypeId.Player))
return;
if (!owner.IsPet() || !owner.IsInWorld || !IsTargetValid() && GetTarget().GetGUID() != owner.GetOwnerGUID())
return;
owner.UpdateSpeed(UnitMoveType.Run);
owner.UpdateSpeed(UnitMoveType.Walk);
owner.UpdateSpeed(UnitMoveType.Swim);
}
}
}
@@ -284,7 +284,7 @@ namespace Game.Movement
creature.GetAI().MovementInform(MovementGeneratorType.Waypoint, (uint)_currentNode);
}
public override bool GetResetPosition(Unit u, out float x, out float y, out float z)
public bool GetResetPosition(Unit u, out float x, out float y, out float z)
{
x = y = z = 0;
// prevent a crash at empty waypoint path.
@@ -301,13 +301,13 @@ namespace Game.Movement
return true;
}
public override void Pause(uint timer = 0)
public void Pause(uint timer = 0)
{
_stalled = timer == 0;
_nextMoveTime.Reset(timer != 0 ? (int)timer : 1);
}
public override void Resume(uint overrideTimer = 0)
public void Resume(uint overrideTimer = 0)
{
_stalled = false;
if (overrideTimer != 0)
@@ -321,7 +321,7 @@ namespace Game.Movement
public override MovementGeneratorType GetMovementGeneratorType() { return MovementGeneratorType.Waypoint; }
public override void UnitSpeedChanged() { _recalculateSpeed = true; }
public void UnitSpeedChanged() { _recalculateSpeed = true; }
TimeTrackerSmall _nextMoveTime;
bool _recalculateSpeed;
+70 -13
View File
@@ -28,7 +28,7 @@ namespace Game.Movement
public class MotionMaster
{
public const double gravity = 19.29110527038574;
public const float SPEED_CHARGE = 42.0f;
public const float SPEED_CHARGE = 42.0f;
IdleMovementGenerator staticIdleMovement = new();
public MotionMaster(Unit me)
@@ -139,7 +139,7 @@ namespace Game.Movement
{
if (Empty() || slot >= MovementSlot.Max || _slot[(int)slot] == null)
return MovementGeneratorType.Max;
return _slot[(int)slot].GetMovementGeneratorType();
}
@@ -202,7 +202,7 @@ namespace Game.Movement
{
Unit target = _owner.ToCreature().GetCharmerOrOwner();
if (target)
StartMovement(new FollowMovementGenerator<Creature>(target, SharedConst.PetFollowDist, SharedConst.PetFollowAngle), MovementSlot.Active);
StartMovement(new FollowMovementGenerator(target, SharedConst.PetFollowDist, new ChaseAngle(SharedConst.PetFollowAngle)), MovementSlot.Active);
}
}
@@ -212,27 +212,29 @@ namespace Game.Movement
StartMovement(new RandomMovementGenerator(spawndist), MovementSlot.Idle);
}
public void MoveFollow(Unit target, float dist = 0.0f, float angle = 0.0f, MovementSlot slot = MovementSlot.Idle)
public void MoveFollow(Unit target, float dist, float angle = 0.0f, MovementSlot slot = MovementSlot.Idle) { MoveFollow(target, dist, new ChaseAngle(angle), slot); }
public void MoveFollow(Unit target, float dist, ChaseAngle angle, MovementSlot slot = MovementSlot.Idle)
{
// ignore movement request if target not exist
if (!target || target == _owner)
return;
if (_owner.IsTypeId(TypeId.Player))
StartMovement(new FollowMovementGenerator<Player>(target, dist, angle), slot);
else
StartMovement(new FollowMovementGenerator<Creature>(target, dist, angle), slot);
Log.outDebug(LogFilter.Misc, $"{(_owner.IsPlayer() ? "Player" : "Creature")} (Entry: {_owner.GetEntry()}, {_owner.GetGUID()}) follows {(target.IsPlayer() ? "player" : "creature")} (Entry {target.GetEntry()}, {target.GetGUID()}).");
StartMovement(new FollowMovementGenerator(target, dist, angle), slot);
}
public void MoveChase(Unit target, float dist = 0.0f, float angle = 0.0f)
public void MoveChase(Unit target, float dist, float angle = 0.0f) { MoveChase(target, new ChaseRange(dist), new ChaseAngle(angle)); }
public void MoveChase(Unit target, ChaseRange? dist = null, ChaseAngle? angle = null)
{
if (!target || target == _owner)
return;
if (_owner.IsTypeId(TypeId.Player))
StartMovement(new ChaseMovementGenerator<Player>(target, dist, angle), MovementSlot.Active);
else
StartMovement(new ChaseMovementGenerator<Creature>(target, dist, angle), MovementSlot.Active);
Log.outDebug(LogFilter.Misc, $"{(_owner.IsPlayer() ? "Player" : "Creature")} (Entry: {_owner.GetEntry()}, {_owner.GetGUID()}) chase to {(target.IsPlayer() ? "player" : "creature")} (Entry: {target.GetEntry()}, {target.GetGUID()})");
StartMovement(new ChaseMovementGenerator(target, dist, angle), MovementSlot.Active);
}
public void MoveConfused()
@@ -840,4 +842,59 @@ namespace Game.Movement
Update = 1, // Clear or Expire called from update
Reset = 2 // Flag if need top().Reset()
}
public struct ChaseRange
{
// this contains info that informs how we should path!
public float MinRange; // we have to move if we are within this range... (min. attack range)
public float MinTolerance; // ...and if we are, we will move this far away
public float MaxRange; // we have to move if we are outside this range... (max. attack range)
public float MaxTolerance; // ...and if we are, we will move into this range
public ChaseRange(float range)
{
MinRange = range > SharedConst.ContactDistance ? 0 : range - SharedConst.ContactDistance;
MinTolerance = range;
MaxRange = range + SharedConst.ContactDistance;
MaxTolerance = range;
}
public ChaseRange(float min, float max)
{
MinRange = min;
MinTolerance = Math.Min(min + SharedConst.ContactDistance, (min + max) / 2);
MaxRange = max;
MaxTolerance = Math.Max(max - SharedConst.ContactDistance, MinTolerance);
}
public ChaseRange(float min, float tMin, float tMax, float max)
{
MinRange = min;
MinTolerance = tMin;
MaxRange = max;
MaxTolerance = tMax;
}
}
public struct ChaseAngle
{
public float RelativeAngle; // we want to be at this angle relative to the target (0 = front, M_PI = back)
public float Tolerance; // but we'll tolerate anything within +- this much
public ChaseAngle(float angle, float tol = MathFunctions.PiOver4)
{
RelativeAngle = Position.NormalizeOrientation(angle);
Tolerance = tol;
}
public float UpperBound() { return Position.NormalizeOrientation(RelativeAngle + Tolerance); }
public float LowerBound() { return Position.NormalizeOrientation(RelativeAngle - Tolerance); }
public bool IsAngleOkay(float relAngle)
{
float diff = Math.Abs(relAngle - RelativeAngle);
return (Math.Min(diff, (2 * MathF.PI) - diff) <= Tolerance);
}
}
}
+1 -1
View File
@@ -4887,7 +4887,7 @@ namespace Game.Spells
else if (m_preGeneratedPath.IsInvalidDestinationZ(target)) // Check position z, if in a straight line
return SpellCastResult.NoPath;
m_preGeneratedPath.ReducePathLenghtByDist(objSize); //move back
m_preGeneratedPath.ShortenPathUntilDist(target, objSize); //move back
}
break;
}