/*
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
*
* 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, see .
*/
#include "MoveSplineInit.h"
#include "Creature.h"
#include "MoveSpline.h"
#include "MovementPackets.h"
#include "PathGenerator.h"
#include "Unit.h"
#include "VehicleDefines.h"
namespace Movement
{
// Transforms coordinates from global to transport offsets
class TransportPathTransform
{
public:
TransportPathTransform(Unit const* owner, bool transformForTransport)
: _transport(transformForTransport ? owner->GetDirectTransport() : nullptr) { }
Vector3 operator()(Vector3 input) const
{
if (_transport)
_transport->CalculatePassengerOffset(input.x, input.y, input.z);
return input;
}
float operator()(float input) const
{
if (_transport)
input -= _transport->GetTransportOrientation();
return input;
}
private:
TransportBase* _transport;
};
UnitMoveType SelectSpeedType(uint32 moveFlags)
{
if (moveFlags & MOVEMENTFLAG_FLYING)
{
if (moveFlags & MOVEMENTFLAG_BACKWARD /*&& speed_obj.flight >= speed_obj.flight_back*/)
return MOVE_FLIGHT_BACK;
else
return MOVE_FLIGHT;
}
else if (moveFlags & MOVEMENTFLAG_SWIMMING)
{
if (moveFlags & MOVEMENTFLAG_BACKWARD /*&& speed_obj.swim >= speed_obj.swim_back*/)
return MOVE_SWIM_BACK;
else
return MOVE_SWIM;
}
else if (moveFlags & MOVEMENTFLAG_WALKING)
{
//if (speed_obj.run > speed_obj.walk)
return MOVE_WALK;
}
else if (moveFlags & MOVEMENTFLAG_BACKWARD /*&& speed_obj.run >= speed_obj.run_back*/)
return MOVE_RUN_BACK;
// Flying creatures use MOVEMENTFLAG_CAN_FLY or MOVEMENTFLAG_DISABLE_GRAVITY
// Run speed is their default flight speed.
return MOVE_RUN;
}
int32 MoveSplineInit::Launch()
{
MoveSpline& move_spline = *unit->movespline;
bool transport = !unit->GetTransGUID().IsEmpty();
Location real_position;
// there is a big chance that current position is unknown if current state is not finalized, need compute it
// this also allows calculate spline position and update map position in much greater intervals
// Don't compute for transport movement if the unit is in a motion between two transports
if (!move_spline.Finalized() && move_spline.onTransport == transport)
real_position = move_spline.ComputePosition();
else
{
Position const* pos;
if (!transport)
pos = unit;
else
pos = &unit->m_movementInfo.transport.pos;
real_position.x = pos->GetPositionX();
real_position.y = pos->GetPositionY();
real_position.z = pos->GetPositionZ();
real_position.orientation = unit->GetOrientation();
}
// should i do the things that user should do? - no.
if (args.path.empty())
return 0;
// correct first vertex
args.path[0] = real_position;
args.initialOrientation = real_position.orientation;
args.flags.Enter_Cycle = args.flags.Cyclic;
move_spline.onTransport = transport;
uint32 moveFlags = unit->m_movementInfo.GetMovementFlags();
if (!args.flags.Backward)
moveFlags = (moveFlags & ~MOVEMENTFLAG_BACKWARD) | MOVEMENTFLAG_FORWARD;
else
moveFlags = (moveFlags & ~MOVEMENTFLAG_FORWARD) | MOVEMENTFLAG_BACKWARD;
if (moveFlags & MOVEMENTFLAG_ROOT)
moveFlags &= ~MOVEMENTFLAG_MASK_MOVING;
if (!args.HasVelocity)
{
// If spline is initialized with SetWalk method it only means we need to select
// walk move speed for it but not add walk flag to unit
uint32 moveFlagsForSpeed = moveFlags;
if (args.walk)
moveFlagsForSpeed |= MOVEMENTFLAG_WALKING;
else
moveFlagsForSpeed &= ~MOVEMENTFLAG_WALKING;
args.velocity = unit->GetSpeed(SelectSpeedType(moveFlagsForSpeed));
if (Creature* creature = unit->ToCreature())
if (creature->HasSearchedAssistance())
args.velocity *= 0.66f;
}
// limit the speed in the same way the client does
float speedLimit = [&]()
{
if (args.flags.UnlimitedSpeed)
return std::numeric_limits::max();
if (args.flags.Falling || args.flags.Catmullrom || args.flags.Flying || args.flags.Parabolic)
return 50.0f;
return std::max(28.0f, unit->GetSpeed(MOVE_RUN) * 4.0f);
}();
args.velocity = std::min(args.velocity, speedLimit);
if (!args.Validate(unit))
return 0;
unit->m_movementInfo.SetMovementFlags(moveFlags);
move_spline.Initialize(args);
WorldPackets::Movement::MonsterMove packet;
packet.MoverGUID = unit->GetGUID();
packet.Pos = Position(real_position.x, real_position.y, real_position.z, real_position.orientation);
packet.InitializeSplineData(move_spline);
if (transport)
{
packet.SplineData.Move.TransportGUID = unit->GetTransGUID();
packet.SplineData.Move.VehicleSeat = unit->GetTransSeat();
}
unit->SendMessageToSet(packet.Write(), true);
return move_spline.Duration();
}
void MoveSplineInit::Stop()
{
MoveSpline& move_spline = *unit->movespline;
// No need to stop if we are not moving
if (move_spline.Finalized())
return;
bool transport = !unit->GetTransGUID().IsEmpty();
Location loc;
if (move_spline.onTransport == transport)
loc = move_spline.ComputePosition();
else
{
Position const* pos;
if (!transport)
pos = unit;
else
pos = &unit->m_movementInfo.transport.pos;
loc.x = pos->GetPositionX();
loc.y = pos->GetPositionY();
loc.z = pos->GetPositionZ();
loc.orientation = unit->GetOrientation();
}
if (move_spline.isTurning())
SetFacing(loc.orientation);
args.flags = MoveSplineFlagEnum::Done;
unit->m_movementInfo.RemoveMovementFlag(MOVEMENTFLAG_FORWARD);
move_spline.onTransport = transport;
move_spline.Initialize(args);
WorldPackets::Movement::MonsterMove packet;
packet.MoverGUID = unit->GetGUID();
packet.Pos = Position(loc.x, loc.y, loc.z, loc.orientation);
packet.SplineData.StopSplineStyle = 2;
packet.SplineData.ID = move_spline.GetId();
packet.SplineData.StopUseFaceDirection = args.facing.type == MONSTER_MOVE_FACING_ANGLE;
packet.SplineData.Move.Face = args.facing.type;
packet.SplineData.Move.FaceDirection = args.facing.angle;
if (transport)
{
packet.SplineData.Move.TransportGUID = unit->GetTransGUID();
packet.SplineData.Move.VehicleSeat = unit->GetTransSeat();
}
unit->SendMessageToSet(packet.Write(), true);
}
MoveSplineInit::MoveSplineInit(Unit* m) : unit(m)
{
args.splineId = splineIdGen.NewId();
// Elevators also use MOVEMENTFLAG_ONTRANSPORT but we do not keep track of their position changes
args.TransformForTransport = !unit->GetTransGUID().IsEmpty();
// mix existing state into new
args.walk = unit->HasUnitMovementFlag(MOVEMENTFLAG_WALKING);
args.flags.CanSwim = unit->CanSwim();
args.flags.Flying = unit->HasUnitMovementFlag(MovementFlags(MOVEMENTFLAG_CAN_FLY | MOVEMENTFLAG_DISABLE_GRAVITY));
args.flags.FastSteering = true;
args.flags.Steering = unit->HasNpcFlag2(UNIT_NPC_FLAG_2_STEERING) || !unit->IsInCombat();
}
MoveSplineInit::~MoveSplineInit() = default;
void MoveSplineInit::SetFacing(Vector3 const& spot)
{
TransportPathTransform transform(unit, args.TransformForTransport);
Vector3 finalSpot = transform(spot);
args.facing.f.x = finalSpot.x;
args.facing.f.y = finalSpot.y;
args.facing.f.z = finalSpot.z;
args.facing.type = MONSTER_MOVE_FACING_SPOT;
}
void MoveSplineInit::SetFacing(float x, float y, float z)
{
SetFacing({ x, y, z });
}
void MoveSplineInit::SetFacing(Unit const* target)
{
args.facing.angle = unit->GetAbsoluteAngle(target);
args.facing.target = target->GetGUID();
args.facing.type = MONSTER_MOVE_FACING_TARGET;
}
void MoveSplineInit::SetFacing(float angle)
{
TransportPathTransform transform(unit, args.TransformForTransport);
args.facing.angle = Position::NormalizeOrientation(transform(angle));
args.facing.type = MONSTER_MOVE_FACING_ANGLE;
}
void MoveSplineInit::MovebyPath(PointsArray const& path, int32 pointId)
{
args.path_Idx_offset = pointId;
args.path.resize(path.size());
std::ranges::transform(path, args.path.begin(), TransportPathTransform(unit, args.TransformForTransport));
}
void MoveSplineInit::MoveTo(float x, float y, float z, bool generatePath, bool forceDestination)
{
MoveTo(G3D::Vector3(x, y, z), generatePath, forceDestination);
}
void MoveSplineInit::MoveTo(Vector3 const& dest, bool generatePath, bool forceDestination)
{
if (generatePath)
{
PathGenerator path(unit);
bool result = path.CalculatePath(dest.x, dest.y, dest.z, forceDestination);
if (result && !(path.GetPathType() & PATHFIND_NOPATH))
{
MovebyPath(path.GetPath());
return;
}
}
args.path_Idx_offset = 0;
args.path.resize(2);
TransportPathTransform transform(unit, args.TransformForTransport);
args.path[1] = transform(dest);
}
void MoveSplineInit::SetFall()
{
args.flags.Falling = true;
args.flags.FallingSlow = unit->HasUnitMovementFlag(MOVEMENTFLAG_FALLING_SLOW);
}
void MoveSplineInitFacingVisitor::operator()(Position const& point) const
{
_init.SetFacing(point.GetPositionX(), point.GetPositionY(), point.GetPositionZ());
}
}