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 ReaperProgressionPanel : UIPanel
{
    private const float TopBarHeight = 52f;
    private const float DetailWidth = 246f;
    private readonly List<ReaperTreeNodeModel> nodes = [];
    private readonly ReaperTreeViewport viewport;
    private readonly ReaperTreeDetailPanel detailPanel;
    private ReaperTreeNodeModel selectedNode = null!;
    private ReaperProgressionState? progression;
    private int essenceCount;
    private int souls;
    private float layoutScale = 1f;

    public ReaperProgressionPanel()
    {
        SetPadding(0f);
        BuildPanorama();
        viewport = new ReaperTreeViewport(this, nodes);
        Append(viewport);
        detailPanel = new ReaperTreeDetailPanel(this);
        Append(detailPanel);
        selectedNode = nodes.Find(node => node.Kind == ReaperTreeNodeKind.Base)!;
        detailPanel.SetNode(selectedNode);
        ApplyLayoutScale(1f);
    }

    public void ApplyLayoutScale(float scale)
    {
        layoutScale = Math.Clamp(scale, 0.4f, 1f);
        SetPadding(0f);
        viewport.Left.Set(8f * layoutScale, 0f);
        viewport.Top.Set((TopBarHeight + 6f) * layoutScale, 0f);
        viewport.Width.Set(-(DetailWidth + 22f) * layoutScale, 1f);
        viewport.Height.Set(-(TopBarHeight + 14f) * layoutScale, 1f);
        viewport.ApplyLayoutScale(layoutScale);
        detailPanel.Left.Set(-(DetailWidth + 8f) * layoutScale, 1f);
        detailPanel.Top.Set((TopBarHeight + 6f) * layoutScale, 0f);
        detailPanel.Width.Set(DetailWidth * layoutScale, 0f);
        detailPanel.Height.Set(-(TopBarHeight + 14f) * layoutScale, 1f);
        detailPanel.ApplyLayoutScale(layoutScale);
    }

    public void Refresh(Player player, int essenceCount, int souls)
    {
        progression = player.GetModPlayer<MyPlayer>().ReaperProgression;
        this.essenceCount = essenceCount;
        this.souls = souls;
        foreach (ReaperTreeNodeModel node in nodes)
            node.Refresh(player, progression, essenceCount, souls);
        detailPanel.SetNode(selectedNode);
        detailPanel.RefreshActionState();
    }

    public override void Recalculate()
    {
        base.Recalculate();
        viewport.EnsureCameraIsValid();
    }

    internal void CancelInteraction()
    {
        viewport.CancelInteraction();
    }

    internal void SelectNode(ReaperTreeNodeModel node)
    {
        if (selectedNode == node)
            return;
        selectedNode = node;
        detailPanel.SetNode(node);
        detailPanel.RefreshActionState();
        SoundEngine.PlaySound(SoundID.MenuTick);
    }

    internal void RequestSelectedUpgrade()
    {
        if (!selectedNode.CanRequest || !selectedNode.Affordable)
            return;
        SoundEngine.PlaySound(SoundID.MenuTick);
        selectedNode.RequestUpgrade();
    }

    internal bool IsBranchMastered(ReaperFormId form)
    {
        if (progression is null || progression.GetStage(form) < ReaperStage.StageIII)
            return false;
        for (int skill = 0; skill < ReaperDefinitions.SkillsPerBranch; skill++)
            if (progression.GetSkillLevel(form, skill) < ReaperDefinitions.MaximumSkillLevel)
                return false;
        return true;
    }

    protected override void DrawSelf(SpriteBatch spriteBatch)
    {
        base.DrawSelf(spriteBatch);
        Rectangle bounds = GetDimensions().ToRectangle();
        Texture2D pixel = TextureAssets.MagicPixel.Value;
        int scaleOne = Math.Max(1, (int)MathF.Round(layoutScale));
        spriteBatch.Draw(pixel, new Rectangle(bounds.X + scaleOne, bounds.Y + scaleOne, Math.Max(1, bounds.Width - scaleOne * 2), Math.Max(1, (int)(TopBarHeight * layoutScale))), new Color(10, 9, 12) * 0.98f);
        spriteBatch.Draw(pixel, new Rectangle(bounds.X + (int)(8f * layoutScale), bounds.Y + (int)(TopBarHeight * layoutScale), bounds.Width - (int)(16f * layoutScale), Math.Max(1, (int)(2f * layoutScale))), new Color(140, 39, 49) * 0.72f);
        ReaperTreeDraw.DrawInsetBorder(spriteBatch, pixel, bounds, Math.Max(2, (int)(4f * layoutScale)), new Color(55, 51, 47), Math.Max(1, (int)(2f * layoutScale)));
        ReaperTreeDraw.DrawInsetBorder(spriteBatch, pixel, bounds, Math.Max(5, (int)(8f * layoutScale)), new Color(128, 43, 50) * 0.7f, 1);

        float titleScale = 0.82f * layoutScale;
        string title = Language.GetTextValue("Mods.SoulHarvest.UI.ReaperTree.EquipmentProgressionTitle");
        float titleMaximumWidth = 176f * layoutScale;
        float measuredTitleWidth = FontAssets.MouseText.Value.MeasureString(title).X * titleScale;
        if (measuredTitleWidth > titleMaximumWidth && measuredTitleWidth > 0f)
            titleScale *= titleMaximumWidth / measuredTitleWidth;
        Utils.DrawBorderString(spriteBatch, title, new Vector2(bounds.X + 18f * layoutScale, bounds.Y + 15f * layoutScale), new Color(229, 218, 203), titleScale);

        int sealI = progression?.GetSoulSealAvailable(ReaperStage.StageI) ?? 0;
        int sealII = progression?.GetSoulSealAvailable(ReaperStage.StageII) ?? 0;
        int sealIII = progression?.GetSoulSealAvailable(ReaperStage.StageIII) ?? 0;
        float resourcesLeft = bounds.X + 205f * layoutScale;
        float resourcesRight = bounds.Right - 12f * layoutScale;
        float resourceWidth = Math.Max(28f * layoutScale, (resourcesRight - resourcesLeft) / 5f);
        DrawResourceCell(spriteBatch, resourcesLeft + resourceWidth * 0f, bounds.Y + 9f * layoutScale, resourceWidth, new Color(177, 61, 225), Language.GetTextValue("Mods.SoulHarvest.UI.ReaperTree.ResourceEssence", essenceCount));
        DrawResourceCell(spriteBatch, resourcesLeft + resourceWidth * 1f, bounds.Y + 9f * layoutScale, resourceWidth, new Color(72, 214, 235), Language.GetTextValue("Mods.SoulHarvest.UI.ReaperTree.ResourceSouls", souls));
        DrawResourceCell(spriteBatch, resourcesLeft + resourceWidth * 2f, bounds.Y + 9f * layoutScale, resourceWidth, new Color(116, 220, 177), Language.GetTextValue("Mods.SoulHarvest.UI.ReaperTree.ResourceSealI", sealI));
        DrawResourceCell(spriteBatch, resourcesLeft + resourceWidth * 3f, bounds.Y + 9f * layoutScale, resourceWidth, new Color(241, 126, 62), Language.GetTextValue("Mods.SoulHarvest.UI.ReaperTree.ResourceSealII", sealII));
        DrawResourceCell(spriteBatch, resourcesLeft + resourceWidth * 4f, bounds.Y + 9f * layoutScale, resourceWidth, new Color(229, 48, 67), Language.GetTextValue("Mods.SoulHarvest.UI.ReaperTree.ResourceSealIII", sealIII));
    }

    private void DrawResourceCell(SpriteBatch batch, float x, float y, float width, Color color, string text)
    {
        Texture2D pixel = TextureAssets.MagicPixel.Value;
        Rectangle cell = new((int)x, (int)y, Math.Max(1, (int)(width - 4f * layoutScale)), Math.Max(1, (int)(34f * layoutScale)));
        batch.Draw(pixel, cell, new Color(21, 19, 23) * 0.92f);
        ReaperTreeDraw.DrawBorder(batch, pixel, cell, new Color(67, 58, 58), 1);
        Vector2 runeCenter = new(cell.X + 12f * layoutScale, cell.Center.Y);
        ReaperTreeDraw.DrawDiamond(batch, pixel, runeCenter, 6f * layoutScale, color * 0.85f);
        ReaperTreeDraw.DrawDiamond(batch, pixel, runeCenter, 3f * layoutScale, new Color(244, 230, 214));
        float textScale = 0.54f * layoutScale;
        string fitted = AltarDrawHelpers.FitText(text, Math.Max(6f, cell.Width - 25f * layoutScale), textScale);
        Utils.DrawBorderString(batch, fitted, new Vector2(cell.X + 22f * layoutScale, cell.Y + 9f * layoutScale), color, textScale);
    }

    private void BuildPanorama()
    {
        Vector2 center = ReaperTreeViewport.ContentCenter;
        nodes.Add(ReaperTreeNodeModel.Base(center));
        ReaperCommonNode[] commonNodes = Array.FindAll(
            Enum.GetValues<ReaperCommonNode>(),
            node => !ReaperDefinitions.IsDeathNode(node));
        for (int index = 0; index < commonNodes.Length; index++)
        {
            float angle = -MathHelper.PiOver2 + MathHelper.TwoPi * index / commonNodes.Length;
            nodes.Add(ReaperTreeNodeModel.Common(commonNodes[index], center + new Vector2(MathF.Cos(angle), MathF.Sin(angle)) * 91f));
        }

        float[] angles = [-160f, -105f, -45f, 15f, 75f, 135f];
        for (int branch = 0; branch < ReaperDefinitions.BranchForms.Count; branch++)
        {
            ReaperFormId form = ReaperDefinitions.BranchForms[branch];
            float angle = MathHelper.ToRadians(angles[branch]);
            Vector2 direction = new(MathF.Cos(angle), MathF.Sin(angle));
            Vector2 tangent = new(-direction.Y, direction.X);
            for (int stage = 1; stage <= 3; stage++)
            {
                float radius = 174f + (stage - 1) * 132f;
                Vector2 stagePosition = center + direction * radius;
                Vector2 skillPosition = stagePosition + tangent * (72f * (stage == 2 ? -1f : 1f));
                nodes.Add(ReaperTreeNodeModel.Stage(form, stage, stagePosition));
                nodes.Add(ReaperTreeNodeModel.Skill(form, stage - 1, skillPosition));
            }
        }
        // The final form is the trunk for its own ascension tree. Keep all six
        // Death upgrades visibly above it so the hierarchy reads from the
        // branches into Death, then upward into its uncapped progression.
        Vector2 deathRoot = center + new Vector2(70f, -438f);
        nodes.Add(ReaperTreeNodeModel.Death(deathRoot));
        ReaperCommonNode[] deathNodes = Array.FindAll(
            Enum.GetValues<ReaperCommonNode>(), ReaperDefinitions.IsDeathNode);
        for (int index = 0; index < deathNodes.Length; index++)
        {
            int column = index % 3;
            int row = index / 3;
            Vector2 position = deathRoot + new Vector2((column - 1) * 154f,
                -105f - row * 91f);
            nodes.Add(ReaperTreeNodeModel.DeathUpgrade(deathNodes[index],
                position));
        }
    }
}