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 Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using Terraria;
using Terraria.ModLoader;

namespace SoulHarvest.Common;

/// <summary>Client-only one-second scene tears left by the Void blade tip.</summary>
[Autoload(Side = ModSide.Client)]
internal sealed class ReaperVoidTrailVisualSystem : ModSystem
{
    private const int PointLifetime = 60;
    private const int MaximumTrails = 24;
    private const int MaximumPointsPerTrail = 72;
    private static readonly Dictionary<TrailKey, ReaperVoidTrail> Trails = [];

    private readonly record struct TrailKey(int Owner, int Identity);
    internal static void RecordTip(int owner, int identity, ReaperStage stage,
        Vector2 position)
    {
        if (Main.dedServ || Main.gameMenu || !float.IsFinite(position.X) || !float.IsFinite(position.Y))
            return;
        TrailKey key = new(owner, identity);
        if (!Trails.TryGetValue(key, out ReaperVoidTrail? trail))
        {
            if (Trails.Count >= MaximumTrails)
            {
                TrailKey keyToRemove = default;
                foreach (TrailKey candidate in Trails.Keys)
                {
                    keyToRemove = candidate;
                    break;
                }
                Trails.Remove(keyToRemove);
            }
            trail = new ReaperVoidTrail
            {
                Stage = stage,
                Seed = unchecked(owner * 73856093 ^ identity * 19349663)
            };
            Trails[key] = trail;
        }
        trail.Stage = stage;
        if (trail.Points.Count > 0)
        {
            float distanceSquared = Vector2.DistanceSquared(trail.Points[^1].Position, position);
            if (distanceSquared < 2.5f * 2.5f)
            {
                trail.Points[^1].Position = position;
                trail.Points[^1].Age = 0;
                return;
            }
            if (distanceSquared > 180f * 180f)
                trail.Points.Clear();
        }
        trail.Points.Add(new ReaperVoidTrailPoint { Position = position });
        if (trail.Points.Count > MaximumPointsPerTrail)
            trail.Points.RemoveAt(0);
    }

    public override void PostUpdateEverything()
    {
        if (Main.gameMenu)
        {
            Trails.Clear();
            return;
        }
        List<TrailKey>? empty = null;
        foreach ((TrailKey key, ReaperVoidTrail trail) in Trails)
        {
            for (int index = trail.Points.Count - 1; index >= 0; index--)
            {
                if (++trail.Points[index].Age > PointLifetime)
                    trail.Points.RemoveAt(index);
            }
            if (trail.Points.Count == 0)
                (empty ??= []).Add(key);
        }
        if (empty is not null)
            foreach (TrailKey key in empty)
                Trails.Remove(key);
    }

    public override void PostDrawTiles()
    {
        if (Main.dedServ || Main.gameMenu || Trails.Count == 0)
            return;
        SpriteBatch batch = Main.spriteBatch;
        batch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp,
            DepthStencilState.None, RasterizerState.CullNone, null,
            Main.GameViewMatrix.TransformationMatrix);
        foreach (ReaperVoidTrail trail in Trails.Values)
            DrawTrail(batch, trail);
        batch.End();
    }

    private static void DrawTrail(SpriteBatch batch, ReaperVoidTrail trail)
    {
        float width = trail.Stage switch
        {
            ReaperStage.StageI => 7f,
            ReaperStage.StageII => 10f,
            // Let the fully evolved Void Reaper leave a tear twice as wide as
            // its previous Stage III presentation.
            _ => 26f
        };
        for (int index = 1; index < trail.Points.Count; index++)
        {
            ReaperVoidTrailPoint older = trail.Points[index - 1];
            ReaperVoidTrailPoint newer = trail.Points[index];
            float age = Math.Max(older.Age, newer.Age);
            float opacity = 1f - Smooth01((age - 34f) / 26f);
            if (opacity <= 0.001f || Vector2.DistanceSquared(older.Position, newer.Position) > 90f * 90f)
                continue;
            float progress = (index - 0.5f) / Math.Max(1f, trail.Points.Count - 1f);
            // Both ends collapse continuously to a blade point. This removes the
            // blunt caps while preserving the same curved, tip-following path.
            float taper = (float)Math.Pow(Math.Max(0f,
                Math.Sin(progress * MathHelper.Pi)), 0.46f);
            ReaperVoidBackdropRenderer.DrawTrailStrip(batch, older.Position, newer.Position,
                Math.Max(0.55f, width * taper), opacity * 0.92f, drawStars: true,
                branchSeed: trail.Seed, branchIndex: index);
        }
    }

    public override void OnWorldUnload() => Trails.Clear();
    public override void Unload() => Trails.Clear();

    private static float Smooth01(float value)
    {
        value = MathHelper.Clamp(value, 0f, 1f);
        return value * value * (3f - 2f * value);
    }
}