using Microsoft.Xna.Framework;
using System;
using Terraria;
using Terraria.ID;
namespace SoulHarvest.Common;
///
/// Deterministic, terrain-swept movement used by Reaper attacks. Multiplayer
/// clients only consume the server's authoritative destination broadcast.
///
internal static class ReaperMovementHelper
{
private const float SweepStep = 4f;
private const float MaximumAttackMovement = 960f;
///
/// Resolves a terrain-limited attack path without changing player position.
/// This is retained for read-only previews; committed Reaper dashes use the
/// same resolver through MoveAlong on the authoritative server.
///
internal static float TraceAlong(
Player player,
Vector2 direction,
float requestedDistance,
out Vector2 startCenter,
out Vector2 endCenter)
{
return ResolvePath(player, direction, requestedDistance,
out _, out startCenter, out endCenter);
}
internal static float MoveAlong(
Player player,
Vector2 direction,
float requestedDistance,
out Vector2 startCenter,
out Vector2 endCenter)
{
startCenter = player.Center;
endCenter = startCenter;
if (requestedDistance <= 0f
|| !float.IsFinite(requestedDistance)
|| Main.netMode == NetmodeID.MultiplayerClient)
{
return 0f;
}
float acceptedDistance = ResolvePath(player, direction, requestedDistance,
out Vector2 position, out startCenter, out endCenter);
if (acceptedDistance <= 0f)
return 0f;
// ResolvePath checks every four pixels and includes the full player
// rectangle. Recheck the final rectangle before committing so a tile
// mutation in the same tick cannot place an authoritative dash in solid
// terrain.
if (Collision.SolidCollision(position, player.width, player.height))
{
endCenter = startCenter;
return 0f;
}
player.position = position;
player.velocity = Vector2.Zero;
endCenter = player.Center;
if (Main.netMode == NetmodeID.Server)
player.GetModPlayer()
.BeginAuthoritativeMovement(player.position, player.velocity);
return acceptedDistance;
}
internal static bool ReturnToSafePosition(Player player, Vector2 safePosition)
{
if (!float.IsFinite(safePosition.X) || !float.IsFinite(safePosition.Y)
|| Collision.SolidCollision(safePosition, player.width, player.height))
{
return false;
}
Vector2 displacement = safePosition - player.position;
float distance = displacement.Length();
if (distance <= 0.5f)
return true;
float accepted = MoveAlong(player, displacement, distance, out _, out _);
return accepted >= distance - SweepStep;
}
///
/// Performs a true target-point teleport. Unlike a dash it may cross solid
/// terrain, but the destination rectangle itself must be safe. If the exact
/// cursor point is occupied, the nearest safe point on the cursor-to-player
/// line is selected so the authoritative player is never embedded in tiles.
///
internal static bool TeleportToTarget(Player player, Vector2 requestedCenter,
float maximumDistance, out Vector2 startCenter, out Vector2 endCenter)
{
startCenter = player.Center;
endCenter = startCenter;
if (Main.netMode == NetmodeID.MultiplayerClient
|| !float.IsFinite(requestedCenter.X)
|| !float.IsFinite(requestedCenter.Y)
|| !float.IsFinite(maximumDistance)
|| maximumDistance <= 0f)
{
return false;
}
Vector2 displacement = requestedCenter - startCenter;
float requestedDistance = displacement.Length();
if (requestedDistance <= 1f)
return false;
Vector2 direction = displacement / requestedDistance;
float acceptedDistance = Math.Min(maximumDistance, requestedDistance);
Vector2 acceptedPosition = startCenter + direction * acceptedDistance
- player.Size * 0.5f;
while (acceptedDistance > 4f)
{
Point tile = (acceptedPosition + player.Size * 0.5f).ToTileCoordinates();
if (WorldGen.InWorld(tile.X, tile.Y, 2)
&& !Collision.SolidCollision(acceptedPosition, player.width, player.height))
{
break;
}
acceptedDistance -= SweepStep;
acceptedPosition = startCenter + direction * acceptedDistance
- player.Size * 0.5f;
}
if (acceptedDistance <= 4f
|| Collision.SolidCollision(acceptedPosition, player.width, player.height))
{
return false;
}
player.position = acceptedPosition;
player.velocity = Vector2.Zero;
endCenter = player.Center;
if (Main.netMode == NetmodeID.Server)
{
player.GetModPlayer()
.BeginAuthoritativeMovement(player.position, player.velocity);
}
return true;
}
private static float ResolvePath(
Player player,
Vector2 direction,
float requestedDistance,
out Vector2 endPosition,
out Vector2 startCenter,
out Vector2 endCenter)
{
startCenter = player.Center;
endCenter = startCenter;
endPosition = player.position;
if (requestedDistance <= 0f
|| !float.IsFinite(requestedDistance)
|| !float.IsFinite(direction.X)
|| !float.IsFinite(direction.Y))
{
return 0f;
}
direction = direction.SafeNormalize(Vector2.UnitX * (player.direction == 0 ? 1 : player.direction));
float acceptedDistance = 0f;
float maximumDistance = Math.Min(MaximumAttackMovement, requestedDistance);
while (acceptedDistance + 0.01f < maximumDistance)
{
float stepLength = Math.Min(SweepStep, maximumDistance - acceptedDistance);
Vector2 candidate = endPosition + direction * stepLength;
Point centerTile = (candidate + player.Size * 0.5f).ToTileCoordinates();
if (!WorldGen.InWorld(centerTile.X, centerTile.Y, 2)
|| Collision.SolidCollision(candidate, player.width, player.height))
{
break;
}
endPosition = candidate;
acceptedDistance += stepLength;
}
endCenter = endPosition + player.Size * 0.5f;
return acceptedDistance;
}
}