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

DeathMod

【Terraria】DeathMod (now aka Soul Harvest)

公开
关注 0 Fork 0 Star 0
UTF-8
using SoulHarvest.Common;
using SoulHarvest.Items;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using ReLogic.Content;
using System;
using System.Collections.Generic;
using System.Text;
using Terraria;
using Terraria.Audio;
using Terraria.GameContent;
using Terraria.GameContent.UI.Elements;
using Terraria.GameInput;
using Terraria.ID;
using Terraria.Localization;
using Terraria.ModLoader;
using Terraria.UI;

namespace SoulHarvest.UI;

internal sealed class ReaperTreeCanvas : UIElement
{
    private readonly ReaperProgressionPanel owner;
    private readonly ReaperTreeViewport viewport;
    private readonly IReadOnlyList<ReaperTreeNodeModel> nodes;

    public ReaperTreeCanvas(ReaperProgressionPanel owner, ReaperTreeViewport viewport, IReadOnlyList<ReaperTreeNodeModel> nodes)
    {
        this.owner = owner;
        this.viewport = viewport;
        this.nodes = nodes;
    }

    protected override void DrawSelf(SpriteBatch spriteBatch)
    {
        Texture2D pixel = TextureAssets.MagicPixel.Value;
        float scale = viewport.ContentScale;
        DrawRunicBackdrop(spriteBatch, pixel, scale);
        DrawTotem(spriteBatch, scale);
        DrawConnections(spriteBatch, pixel, scale);
        foreach (ReaperTreeNodeModel node in nodes)
            DrawNode(spriteBatch, pixel, node, scale);
        if (viewport.HoveredNode is ReaperTreeNodeModel hovered)
            Main.instance.MouseText(hovered.Title);
    }

    private void DrawRunicBackdrop(SpriteBatch batch, Texture2D pixel, float scale)
    {
        Color grid = new Color(72, 71, 68) * 0.09f;
        for (float x = 30f; x < ReaperTreeViewport.ContentWidth; x += 82f)
            ReaperTreeDraw.DrawLine(batch, pixel, viewport.DesignToScreen(new Vector2(x, 0f)), viewport.DesignToScreen(new Vector2(x, ReaperTreeViewport.ContentHeight)), grid, Math.Max(1f, scale));
        for (float y = 26f; y < ReaperTreeViewport.ContentHeight; y += 82f)
            ReaperTreeDraw.DrawLine(batch, pixel, viewport.DesignToScreen(new Vector2(0f, y)), viewport.DesignToScreen(new Vector2(ReaperTreeViewport.ContentWidth, y)), grid, Math.Max(1f, scale));
        Vector2 center = viewport.DesignToScreen(ReaperTreeViewport.ContentCenter);
        ReaperTreeDraw.DrawRing(batch, pixel, center, 123f * scale, new Color(140, 43, 51) * 0.19f, Math.Max(1f, scale), 48);
        ReaperTreeDraw.DrawRing(batch, pixel, center, 454f * scale, new Color(120, 109, 103) * 0.12f, Math.Max(1f, scale), 72);
        for (int index = 0; index < 12; index++)
        {
            float angle = MathHelper.TwoPi * index / 12f;
            Vector2 rune = center + new Vector2(MathF.Cos(angle), MathF.Sin(angle)) * 123f * scale;
            ReaperTreeDraw.DrawDiamond(batch, pixel, rune, 3.5f * scale, index % 2 == 0 ? new Color(185, 51, 60) * 0.42f : new Color(130, 119, 111) * 0.28f);
        }
    }

    private void DrawTotem(SpriteBatch batch, float scale)
    {
        Texture2D texture = ReaperUIData.GetFormTexture(ReaperFormId.Base);
        Rectangle frame = texture.Frame();
        Vector2 center = viewport.DesignToScreen(ReaperTreeViewport.ContentCenter + new Vector2(0f, -16f));
        float drawScale = Math.Min(1f, 360f / Math.Max(frame.Width, frame.Height)) * scale;
        batch.Draw(texture, center, frame, new Color(180, 187, 179) * 0.11f, -0.55f, frame.Size() * 0.5f, drawScale, SpriteEffects.None, 0f);
    }

    private void DrawConnections(SpriteBatch batch, Texture2D pixel, float scale)
    {
        ReaperTreeNodeModel root = Find(ReaperTreeNodeKind.Base, ReaperFormId.Base, 0)!;
        ReaperTreeNodeModel death = Find(ReaperTreeNodeKind.Death, ReaperFormId.Death, 0)!;
        foreach (ReaperCommonNode common in Enum.GetValues<ReaperCommonNode>())
        {
            if (ReaperDefinitions.IsDeathNode(common))
                continue;
            ReaperTreeNodeModel commonNode = FindCommon(common)!;
            DrawPath(batch, pixel, root, commonNode, commonNode.Lit, new Color(190, 188, 174), scale, 0.62f);
        }
        foreach (ReaperCommonNode deathUpgrade in Enum.GetValues<ReaperCommonNode>())
        {
            if (!ReaperDefinitions.IsDeathNode(deathUpgrade))
                continue;
            ReaperTreeNodeModel upgradeNode = FindCommon(deathUpgrade)!;
            DrawPath(batch, pixel, death, upgradeNode, upgradeNode.Lit,
                ReaperUIData.GetPrimaryColor(ReaperFormId.Death), scale, 0.64f);
        }
        foreach (ReaperFormId form in ReaperDefinitions.BranchForms)
        {
            Color color = ReaperUIData.GetPrimaryColor(form);
            ReaperTreeNodeModel previousStage = root;
            ReaperTreeNodeModel? previousSkill = null;
            for (int stage = 1; stage <= 3; stage++)
            {
                ReaperTreeNodeModel stageNode = Find(ReaperTreeNodeKind.Stage, form, stage)!;
                ReaperTreeNodeModel skillNode = Find(ReaperTreeNodeKind.Skill, form, stage - 1)!;
                DrawPath(batch, pixel, previousStage, stageNode, stageNode.Lit, color, scale, 1f);
                DrawPath(batch, pixel, stageNode, skillNode, skillNode.Lit, color, scale, 0.78f);
                if (previousSkill is not null)
                    DrawPath(batch, pixel, previousSkill, skillNode, skillNode.CurrentLevel >= ReaperDefinitions.MaximumSkillLevel, color, scale, 0.48f);
                previousStage = stageNode;
                previousSkill = skillNode;
            }
            bool mastered = owner.IsBranchMastered(form);
            DrawPath(batch, pixel, previousStage, death, mastered, color, scale, 0.62f);
            if (previousSkill is not null)
                DrawPath(batch, pixel, previousSkill, death, mastered, color, scale, 0.42f);
        }
    }

    private void DrawPath(SpriteBatch batch, Texture2D pixel, ReaperTreeNodeModel start, ReaperTreeNodeModel end, bool lit, Color color, float scale, float opacity)
    {
        Vector2 a = viewport.DesignToScreen(start.Position);
        Vector2 b = viewport.DesignToScreen(end.Position);
        Vector2 delta = b - a;
        if (delta.LengthSquared() < 1f)
            return;
        Vector2 direction = Vector2.Normalize(delta);
        a += direction * start.Radius * scale;
        b -= direction * end.Radius * scale;
        Color line = lit ? color * opacity : new Color(65, 61, 62) * (opacity * 0.58f);
        ReaperTreeDraw.DrawLine(batch, pixel, a, b, new Color(7, 7, 9) * Math.Min(0.94f, opacity + 0.2f), Math.Max(2f, 5f * scale));
        ReaperTreeDraw.DrawLine(batch, pixel, a, b, line, Math.Max(1f, (lit ? 2.3f : 1.2f) * scale));
        ReaperTreeDraw.DrawDiamond(batch, pixel, Vector2.Lerp(a, b, 0.5f), (lit ? 4.4f : 3f) * scale, line * 1.18f);
    }

    private void DrawNode(SpriteBatch batch, Texture2D pixel, ReaperTreeNodeModel node, float scale)
    {
        Vector2 center = viewport.DesignToScreen(node.Position);
        float radius = node.Radius * scale;
        Color primary = node.Kind == ReaperTreeNodeKind.Common
            ? new Color(196, 194, 177)
            : ReaperUIData.GetPrimaryColor(node.Form);
        bool selected = ReferenceEquals(viewport.SelectedNode, node) || viewport.SelectedNode is null && node.Kind == ReaperTreeNodeKind.Base;
        bool hovered = ReferenceEquals(viewport.HoveredNode, node);
        Color stateColor = node.Lit ? primary : node.CanRequest && node.Affordable ? primary * 0.82f : node.CanRequest ? new Color(180, 57, 65) : new Color(75, 70, 72);
        float pulse = 0.86f + MathF.Sin(Main.GlobalTimeWrappedHourly * 3.2f + node.Position.X * 0.01f) * 0.1f;
        ReaperTreeDraw.DrawDiamond(batch, pixel, center, radius * 0.88f, new Color(8, 8, 11) * 0.98f);
        if (node.Kind is ReaperTreeNodeKind.Base or ReaperTreeNodeKind.Death)
        {
            ReaperTreeDraw.DrawRing(batch, pixel, center, radius, stateColor * (node.Lit ? pulse : 0.52f), Math.Max(1f, 2.6f * scale), 30);
            ReaperTreeDraw.DrawRing(batch, pixel, center, radius - 6f * scale, new Color(104, 91, 84) * 0.56f, Math.Max(1f, scale), 24);
        }
        else if (node.Kind == ReaperTreeNodeKind.Skill)
            ReaperTreeDraw.DrawRing(batch, pixel, center, radius, stateColor * (node.Lit ? pulse : 0.58f), Math.Max(1f, 2f * scale), 20);
        else
            ReaperTreeDraw.DrawDiamondOutline(batch, pixel, center, radius, stateColor * (node.Lit ? pulse : 0.62f), Math.Max(1f, 2f * scale));
        if (selected || hovered)
            ReaperTreeDraw.DrawRing(batch, pixel, center, radius + (selected ? 7f : 4f) * scale, selected ? Color.White * 0.72f : stateColor * 0.62f, Math.Max(1f, (selected ? 2f : 1f) * scale), 28);
        DrawNodeContent(batch, node, center, radius, primary, scale);
        DrawNodeLevels(batch, pixel, node, center + new Vector2(0f, radius + 9f * scale), primary, scale);
        if (node.Kind == ReaperTreeNodeKind.Stage && node.Index == 1)
            DrawBranchMarker(batch, pixel, node, center, radius, primary, scale);
        if (node.Kind == ReaperTreeNodeKind.Stage && node.Index == 3)
        {
            float labelScale = 0.48f * scale;
            string label = AltarDrawHelpers.FitText(ReaperUIData.GetFormName(node.Form), 112f * scale, labelScale);
            Vector2 labelSize = FontAssets.MouseText.Value.MeasureString(label) * labelScale;
            Utils.DrawBorderString(batch, label, center + new Vector2(-labelSize.X * 0.5f, radius + 19f * scale), primary * (node.Lit ? 1f : 0.62f), labelScale);
        }
    }

    private void DrawNodeContent(SpriteBatch batch, ReaperTreeNodeModel node, Vector2 center, float radius, Color color, float scale)
    {
        if (node.Kind is ReaperTreeNodeKind.Base or ReaperTreeNodeKind.Death || node.Kind == ReaperTreeNodeKind.Stage && node.Index == 3)
        {
            Texture2D texture = ReaperUIData.GetFormTexture(node.Form);
            Rectangle frame = texture.Frame();
            float maximum = node.Kind is ReaperTreeNodeKind.Base or ReaperTreeNodeKind.Death ? radius * 1.3f : radius * 1.12f;
            float iconScale = Math.Min(1f, maximum / Math.Max(frame.Width, frame.Height));
            Color tint = node.Lit || node.Kind == ReaperTreeNodeKind.Base ? Color.White : new Color(92, 88, 91);
            batch.Draw(texture, center, frame, tint, 0f, frame.Size() * 0.5f, iconScale, SpriteEffects.None, 0f);
            return;
        }

        Color runeColor = node.Lit ? ReaperUIData.GetSecondaryColor(node.Form) : color * 0.68f;
        if (node.Kind == ReaperTreeNodeKind.Skill)
        {
            ReaperTreeDraw.DrawSkillRune(batch, TextureAssets.MagicPixel.Value, center, radius, node.Form, node.Index, runeColor, scale);
            return;
        }
        if (node.Kind is ReaperTreeNodeKind.Common
            or ReaperTreeNodeKind.DeathUpgrade)
        {
            ReaperTreeDraw.DrawCommonRune(batch, TextureAssets.MagicPixel.Value, center, radius, node.CommonNode, runeColor, scale);
            return;
        }

        string glyph = node.Kind == ReaperTreeNodeKind.Stage ? ReaperTreeDraw.Roman(node.Index) : string.Empty;
        float textScale = 0.62f * scale;
        Vector2 size = FontAssets.MouseText.Value.MeasureString(glyph) * textScale;
        Utils.DrawBorderString(batch, glyph, center - size * 0.5f, runeColor, textScale);
    }

    private static void DrawBranchMarker(SpriteBatch batch, Texture2D pixel, ReaperTreeNodeModel node, Vector2 center, float radius, Color color, float scale)
    {
        Vector2 outward = (node.Position - ReaperTreeViewport.ContentCenter).SafeNormalize(Vector2.UnitY);
        Vector2 tangent = new(-outward.Y, outward.X);
        Vector2 markerCenter = center + outward * (radius + 28f * scale);
        float markerRadius = 12f * scale;
        ReaperTreeDraw.DrawDiamond(batch, pixel, markerCenter, markerRadius * 0.82f, new Color(7, 7, 9) * 0.98f);
        ReaperTreeDraw.DrawRing(batch, pixel, markerCenter, markerRadius, color * (node.Lit ? 0.94f : 0.58f), Math.Max(1f, 1.7f * scale), 18);

        Texture2D texture = ReaperUIData.GetFormTexture(node.Form);
        Rectangle frame = texture.Frame();
        float iconScale = Math.Min(1f, 17f * scale / Math.Max(frame.Width, frame.Height));
        batch.Draw(texture, markerCenter, frame, node.Lit ? Color.White : new Color(116, 109, 109), 0f, frame.Size() * 0.5f, iconScale, SpriteEffects.None, 0f);

        string label = ReaperUIData.GetBranchLabel(node.Form);
        float labelScale = 0.46f * scale;
        Vector2 labelCenter = markerCenter - tangent * (28f * scale);
        Vector2 labelSize = FontAssets.MouseText.Value.MeasureString(label) * labelScale;
        Utils.DrawBorderString(batch, label, labelCenter - labelSize * 0.5f, color * (node.Lit ? 1f : 0.72f), labelScale);
    }

    private static void DrawNodeLevels(SpriteBatch batch, Texture2D pixel, ReaperTreeNodeModel node, Vector2 center, Color color, float scale)
    {
        if (node.Kind is ReaperTreeNodeKind.Base or ReaperTreeNodeKind.Death)
            return;
        bool unlimited = (node.Kind is ReaperTreeNodeKind.Common
                or ReaperTreeNodeKind.DeathUpgrade)
            && ReaperDefinitions.IsUnlimitedCommonNode(node.CommonNode);
        int maximum = unlimited ? 8 : Math.Min(node.MaximumLevel, 10);
        int filledLevels = unlimited && node.CurrentLevel > 0
            ? (node.CurrentLevel - 1) % maximum + 1
            : node.CurrentLevel;
        float spacing = maximum > 5 ? 8f * scale : 10f * scale;
        float startX = center.X - (maximum - 1) * spacing * 0.5f;
        for (int level = 0; level < maximum; level++)
            ReaperTreeDraw.DrawDiamond(batch, pixel, new Vector2(startX + level * spacing, center.Y), (level < filledLevels ? 3.2f : 2.7f) * scale, level < filledLevels ? color : new Color(55, 52, 55));
    }

    private ReaperTreeNodeModel? Find(ReaperTreeNodeKind kind, ReaperFormId form, int index)
    {
        foreach (ReaperTreeNodeModel node in nodes)
            if (node.Kind == kind && node.Form == form && node.Index == index)
                return node;
        return null;
    }

    private ReaperTreeNodeModel? FindCommon(ReaperCommonNode common)
    {
        foreach (ReaperTreeNodeModel node in nodes)
            if ((node.Kind is ReaperTreeNodeKind.Common
                    or ReaperTreeNodeKind.DeathUpgrade)
                && node.CommonNode == common)
                return node;
        return null;
    }
}