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 Terraria;

namespace SoulHarvest.Common;

/// <summary>
/// One restrained Void surface shared by blade-tip tears and hit rifts. The star
/// field is sampled in world-sized cells, so both effects keep identical scale and
/// parallax instead of each inventing a differently zoomed background.
/// </summary>
internal static class ReaperVoidBackdropRenderer
{
    private const float StarCellSize = 42f;

    internal static void DrawTrailStrip(SpriteBatch batch, Vector2 start, Vector2 end,
        float width, float opacity, bool drawStars = true, int branchSeed = 0,
        int branchIndex = -1)
    {
        if (opacity <= 0.001f || width <= 0.5f)
            return;

        DrawCoreStrip(batch, start, end, width, opacity);
        if (branchIndex >= 0 && branchIndex % 6 == 3)
            DrawBranchCrack(batch, start, end, width, opacity, branchSeed, branchIndex);
        if (drawStars)
            DrawStarsInStrip(batch, start, end, width, opacity);
    }

    private static void DrawCoreStrip(SpriteBatch batch, Vector2 start, Vector2 end,
        float width, float opacity)
    {
        Vector2 screenStart = start - Main.screenPosition;
        Vector2 screenEnd = end - Main.screenPosition;
        DeathDomainPrimitiveTextureSystem.DrawSmoothStrip(batch, screenStart, screenEnd,
            new Color(58, 4, 92, 0) * (opacity * 0.26f), width + 4f);
        DeathDomainPrimitiveTextureSystem.DrawSmoothStrip(batch, screenStart, screenEnd,
            new Color(112, 26, 181, 220) * (opacity * 0.68f), width + 1.7f);
        DeathDomainPrimitiveTextureSystem.DrawSmoothStrip(batch, screenStart, screenEnd,
            new Color(2, 0, 6, 252) * opacity, width);
        DeathDomainPrimitiveTextureSystem.DrawSmoothStrip(batch, screenStart, screenEnd,
            new Color(9, 2, 16, 220) * (opacity * 0.72f), Math.Max(1f, width * 0.46f));
    }

    internal static void DrawHitRift(SpriteBatch batch, Vector2 center, Vector2 axis,
        float length, float width, int seed, float reveal, float opacity)
    {
        axis = axis.SafeNormalize(Vector2.UnitX);
        Vector2 normal = axis.RotatedBy(MathHelper.PiOver2);
        reveal = MathHelper.Clamp(reveal, 0f, 1f);
        float visibleEnd = MathHelper.Lerp(-1f, 1f, reveal);

        // The entire opening, including its continuous noisy silhouette and rim,
        // is one high-resolution mask just like the Blood ultimate's wound.
        DrawContinuousRiftBody(center, axis, length, width, reveal, opacity);

        DrawRiftBranches(batch, center, axis, normal, length, width, seed,
            visibleEnd, opacity);
        DrawStarsInRift(batch, center, axis, normal, length, width, seed,
            visibleEnd, opacity);
    }

    private static void DrawContinuousRiftBody(Vector2 center, Vector2 axis,
        float length, float width, float reveal, float opacity)
    {
        DeathDomainPrimitiveTextureSystem.DrawVoidRift(center, axis.ToRotation(),
            length, width, opacity, reveal);
    }

    internal static void DrawPersistentRift(SpriteBatch batch, Vector2 center,
        Vector2 axis, float length, float width, int seed, float reveal,
        float opacity)
    {
        DrawHitRift(batch, center, axis, length, width, seed, reveal, opacity);
    }

    private static Vector2 GetRiftCenter(Vector2 center, Vector2 axis, Vector2 normal,
        float u, float width, int seed)
    {
        // A stable multi-frequency wave keeps both sides of the opening
        // continuous. No per-segment random offsets are used, so even a giant
        // rift reads as one torn surface rather than disconnected polygons.
        float bend = ((float)Math.Sin(u * 11.7f + seed * 0.031f)
            + (float)Math.Sin(u * 23.3f - seed * 0.017f) * 0.44f
            + (float)Math.Sin(u * 47.1f + seed * 0.009f) * 0.17f) * width * 0.052f;
        return center + normal * bend;
    }

    private static float GetRiftHalfWidth(float u, float width, int seed)
    {
        float envelope = (float)Math.Pow(Math.Max(0f,
            Math.Sin((u * 0.5f + 0.5f) * MathHelper.Pi)), 0.58f);
        float noise = 1f
            + (float)Math.Sin(u * 27f + seed * 0.071f) * 0.09f
            + (float)Math.Sin(u * 61f - seed * 0.037f) * 0.042f
            + (float)Math.Sin(u * 113f + seed * 0.013f) * 0.016f;
        return width * 0.5f * envelope * MathHelper.Clamp(noise, 0.80f, 1.16f);
    }

    private static void DrawRiftBranches(SpriteBatch batch, Vector2 center,
        Vector2 axis, Vector2 normal, float length, float width, int seed,
        float visibleEnd, float opacity)
    {
        int branchCount = Math.Clamp((int)Math.Ceiling(length / 150f), 3, 11);
        for (int branch = 0; branch < branchCount; branch++)
        {
            float jitter = Hash01(seed + branch * 97 + 17) * 0.08f - 0.04f;
            float u = MathHelper.Lerp(-0.82f, 0.82f,
                (branch + 0.5f) / branchCount) + jitter;
            if (u > visibleEnd)
                continue;

            float side = ((branch + seed) & 1) == 0 ? 1f : -1f;
            float halfWidth = GetRiftHalfWidth(u, width, seed);
            Vector2 root = GetRiftCenter(center, axis, normal, u, width, seed)
                + axis * (u * length * 0.5f)
                + normal * side * halfWidth * 0.82f;
            float branchLength = MathHelper.Clamp(width
                * (0.30f + Hash01(seed + branch * 31) * 0.20f), 5f, 28f);
            Vector2 direction = (normal * side + axis * (Hash01(seed + branch * 43) - 0.5f) * 0.85f)
                .SafeNormalize(normal * side);
            DrawBranchPath(batch, root, direction, branchLength,
                MathHelper.Clamp(width * 0.045f, 0.7f, 3.2f), opacity * 0.58f,
                seed + branch * 173);
        }
    }

    private static void DrawBranchCrack(SpriteBatch batch, Vector2 start, Vector2 end,
        float width, float opacity, int seed, int index)
    {
        Vector2 delta = end - start;
        if (delta.LengthSquared() < 9f)
            return;
        Vector2 axis = delta.SafeNormalize(Vector2.UnitX);
        Vector2 normal = axis.RotatedBy(MathHelper.PiOver2);
        float side = ((seed + index) & 1) == 0 ? 1f : -1f;
        Vector2 root = Vector2.Lerp(start, end, 0.58f)
            + normal * side * width * 0.47f;
        Vector2 direction = axis.RotatedBy(side * MathHelper.Lerp(0.55f, 0.92f,
            Hash01(seed + index * 79)));
        float length = MathHelper.Clamp(width * 1.7f, 7f, 29f);
        DrawBranchPath(batch, root, direction, length,
            Math.Max(0.65f, width * 0.13f), opacity * 0.50f,
            seed + index * 211);
    }

    private static void DrawBranchPath(SpriteBatch batch, Vector2 root,
        Vector2 direction, float length, float width, float opacity, int seed)
    {
        direction = direction.SafeNormalize(Vector2.UnitY);
        Vector2 normal = direction.RotatedBy(MathHelper.PiOver2);
        int samples = Math.Clamp((int)Math.Ceiling(length / 5f), 5, 15);
        float wavePhase = Hash01(seed + 19) * MathHelper.TwoPi;
        Vector2 previous = root;
        for (int sample = 1; sample <= samples; sample++)
        {
            float progress = sample / (float)samples;
            float envelope = (float)Math.Sin(progress * MathHelper.Pi);
            float wave = ((float)Math.Sin(progress * 5.4f + wavePhase)
                + (float)Math.Sin(progress * 11.1f - wavePhase * 0.7f) * 0.26f)
                * length * 0.055f * envelope;
            Vector2 current = root + direction * (length * progress) + normal * wave;
            float localWidth = Math.Max(0.45f,
                width * (float)Math.Pow(1f - progress * 0.86f, 0.72f));
            DrawCoreStrip(batch, previous, current, localWidth, opacity * (1f - progress * 0.32f));
            DrawStarsInStrip(batch, previous, current, localWidth,
                opacity * (1f - progress * 0.42f));
            previous = current;
        }
    }

    private static float Hash01(int seed)
    {
        uint value = (uint)seed;
        value ^= value >> 16;
        value *= 0x7FEB352Du;
        value ^= value >> 15;
        value *= 0x846CA68Bu;
        value ^= value >> 16;
        return (value & 0x00FFFFFFu) / 16777215f;
    }

    private static void DrawStarsInStrip(SpriteBatch batch, Vector2 start, Vector2 end,
        float width, float opacity)
    {
        Vector2 delta = end - start;
        float lengthSquared = delta.LengthSquared();
        if (lengthSquared <= 0.1f)
            return;

        float margin = width + StarCellSize;
        EnumerateStars(Vector2.Min(start, end) - new Vector2(margin),
            Vector2.Max(start, end) + new Vector2(margin), (point, size) =>
            {
                float amount = MathHelper.Clamp(Vector2.Dot(point - start, delta) / lengthSquared, 0f, 1f);
                Vector2 closest = Vector2.Lerp(start, end, amount);
                if (Vector2.DistanceSquared(point, closest) > width * width * 0.12f)
                    return;
                DrawStar(batch, point, size, opacity);
            });
    }

    private static void DrawStarsInRift(SpriteBatch batch, Vector2 center, Vector2 axis,
        Vector2 normal, float length, float width, int seed, float visibleEnd, float opacity)
    {
        float margin = length * 0.55f + width + StarCellSize;
        EnumerateStars(center - new Vector2(margin), center + new Vector2(margin),
            (point, size) =>
            {
                Vector2 local = point - center;
                float u = Vector2.Dot(local, axis) / Math.Max(1f, length * 0.5f);
                if (u is < -1f or > 1f || u > visibleEnd)
                    return;
                float centerOffset = Vector2.Dot(
                    GetRiftCenter(center, axis, normal, u, width, seed) - center, normal);
                float v = Vector2.Dot(local, normal) - centerOffset;
                if (Math.Abs(v) > GetRiftHalfWidth(u, width, seed) * 0.62f)
                    return;
                DrawStar(batch, point, size, opacity);
            });
    }

    private static void EnumerateStars(Vector2 minimum, Vector2 maximum,
        Action<Vector2, float> draw)
    {
        Vector2 drift = Main.screenPosition * 0.018f + new Vector2(
            (float)Math.Sin(Main.GlobalTimeWrappedHourly * 0.31f) * 3.5f,
            (float)Math.Cos(Main.GlobalTimeWrappedHourly * 0.27f) * 2.4f);
        int minX = (int)Math.Floor((minimum.X - drift.X) / StarCellSize);
        int maxX = (int)Math.Ceiling((maximum.X - drift.X) / StarCellSize);
        int minY = (int)Math.Floor((minimum.Y - drift.Y) / StarCellSize);
        int maxY = (int)Math.Ceiling((maximum.Y - drift.Y) / StarCellSize);

        for (int cellY = minY; cellY <= maxY; cellY++)
        for (int cellX = minX; cellX <= maxX; cellX++)
        {
            // One global world-space star field is clipped by every Void surface.
            // A hit opening over an existing trail therefore reveals the same
            // points at the same scale instead of a second randomized texture.
            uint hash = Hash((uint)cellX, (uint)cellY, 0x564F4944u);
            if ((hash & 0xFFFFu) < 0xD900u)
                continue;
            float offsetX = ((hash >> 8) & 0xFFu) / 255f;
            float offsetY = ((hash >> 16) & 0xFFu) / 255f;
            Vector2 point = new Vector2((cellX + offsetX) * StarCellSize,
                (cellY + offsetY) * StarCellSize) + drift;
            draw(point, 0.75f + ((hash >> 24) & 0xFFu) / 255f * 0.9f);
        }
    }

    private static void DrawStar(SpriteBatch batch, Vector2 worldPoint, float size, float opacity)
    {
        Vector2 point = worldPoint - Main.screenPosition;
        Color color = new Color(232, 222, 255, 0) * (opacity * 0.82f);
        DeathDomainPrimitiveTextureSystem.DrawSmoothLine(batch,
            point - Vector2.UnitX * size, point + Vector2.UnitX * size, color, Math.Max(0.7f, size * 0.62f));
        if (size > 1.15f)
            DeathDomainPrimitiveTextureSystem.DrawSmoothLine(batch,
                point - Vector2.UnitY * size, point + Vector2.UnitY * size, color * 0.65f, 0.7f);
    }

    private static uint Hash(uint x, uint y, uint seed)
    {
        uint value = x * 374761393u + y * 668265263u + seed;
        value = (value ^ (value >> 13)) * 1274126177u;
        return value ^ (value >> 16);
    }
}