XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

DeathMod

【Terraria】DeathMod (now aka Soul Harvest)

公开
关注 0 Fork 0 Star 0
UTF-8
using Microsoft.Xna.Framework;
using System;
using Terraria;
using Terraria.ID;

namespace SoulHarvest.Common;


/// <summary>
/// Deterministic, terrain-swept movement used by Reaper attacks. Multiplayer
/// clients only consume the server's authoritative destination broadcast.
/// </summary>
internal static class ReaperMovementHelper
{
    private const float SweepStep = 4f;
    private const float MaximumAttackMovement = 960f;

    /// <summary>
    /// 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.
    /// </summary>
    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<ReaperMovementAuthorityPlayer>()
                .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;
    }

    /// <summary>
    /// 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.
    /// </summary>
    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<ReaperMovementAuthorityPlayer>()
                .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;
    }
}