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

namespace SoulHarvest.UI;

internal sealed class AltarEquipmentProgressionPanel : UIElement
{
    private const float TopBarHeight = 52f;
    private const float DetailWidth = 246f;
    private readonly DeathAltarUIState owner;
    private readonly List<AltarEquipmentTreeNode> nodes = [];
    private readonly AltarEquipmentTreeDetailPanel detailPanel;
    private AltarEquipmentTreeViewport? viewport;
    private AltarEquipmentTreeNode? selectedNode;
    private DeathAltarItemReference? boundReference;
    private IDeathAltarUpgradeable? upgradeable;
    private bool boundCharacterDeathDomain;
    private int itemType;
    private string itemName = string.Empty;
    private int essenceCount;
    private int souls;
    private float layoutScale = 1f;

    public AltarEquipmentProgressionPanel(DeathAltarUIState owner)
    {
        this.owner = owner;
        SetPadding(0f);
        detailPanel = new AltarEquipmentTreeDetailPanel(this);
        Append(detailPanel);
    }

    public bool IsBoundTo(AltarEquipmentEntry entry)
    {
        return !entry.IsCharacterSickle
            && entry.ItemReference is DeathAltarItemReference reference
            && boundReference is DeathAltarItemReference current
            && current.Storage == reference.Storage
            && current.Slot == reference.Slot
            && current.ExpectedItemType == reference.ExpectedItemType;
    }

    public void Bind(AltarEquipmentEntry entry, Item item, IDeathAltarUpgradeable target)
    {
        if (entry.ItemReference is not DeathAltarItemReference reference)
            return;

        upgradeable = target;
        boundCharacterDeathDomain = entry.IsCharacterDeathDomain;
        itemType = item.type;
        itemName = item.Name;
        if (IsBoundTo(entry))
            return;

        DeactivateInteraction();
        boundReference = reference;
        if (viewport is not null && viewport.Parent is not null)
            RemoveChild(viewport);
        nodes.Clear();

        BuildTree(target.AltarUpgradeTypes, out float contentWidth, out float contentHeight);
        viewport = new AltarEquipmentTreeViewport(this, nodes, contentWidth, contentHeight);
        Append(viewport);
        if (detailPanel.Parent is not null)
            RemoveChild(detailPanel);
        Append(detailPanel);
        selectedNode = nodes.Count > 0 ? nodes[0] : null;
        viewport.SetSelectedNode(selectedNode);
        detailPanel.SetNode(selectedNode);
        ApplyLayoutScale(layoutScale);
    }

    public void Refresh(Item item, IDeathAltarUpgradeable target, int essenceCount, int souls)
    {
        itemType = item.type;
        itemName = item.Name;
        upgradeable = target;
        this.essenceCount = essenceCount;
        this.souls = souls;
        foreach (AltarEquipmentTreeNode node in nodes)
            node.Refresh(itemName, target, essenceCount, souls);
        detailPanel.SetNode(selectedNode);
        detailPanel.RefreshActionState();
    }

    public void ApplyLayoutScale(float scale)
    {
        layoutScale = Math.Clamp(scale, 0.25f, 1f);
        SetPadding(0f);
        if (viewport is not null)
        {
            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 override void Recalculate()
    {
        base.Recalculate();
        viewport?.EnsureCameraIsValid();
    }

    public void DeactivateInteraction() => viewport?.CancelInteraction();

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

    internal void RequestSelectedUpgrade()
    {
        if (selectedNode?.UpgradeType is not DeathAltarUpgradeType type
            || !selectedNode.CanRequest
            || !selectedNode.Affordable)
        {
            return;
        }
        owner.RequestUpgrade(type);
    }

    protected override void DrawSelf(SpriteBatch spriteBatch)
    {
        Rectangle bounds = GetDimensions().ToRectangle();
        Texture2D pixel = TextureAssets.MagicPixel.Value;
        spriteBatch.Draw(pixel, bounds, new Color(12, 11, 13) * 0.995f);
        ReaperTreeDraw.DrawBorder(spriteBatch, pixel, bounds, new Color(91, 62, 61), Math.Max(1, (int)(2f * layoutScale)));
        ReaperTreeDraw.DrawInsetBorder(spriteBatch, pixel, bounds, Math.Max(3, (int)(7f * layoutScale)), new Color(137, 42, 50) * 0.72f, 1);
        spriteBatch.Draw(pixel, new Rectangle(bounds.X + Math.Max(1, (int)(8f * layoutScale)), bounds.Y + Math.Max(1, (int)(TopBarHeight * layoutScale)), Math.Max(1, bounds.Width - Math.Max(2, (int)(16f * layoutScale))), Math.Max(1, (int)(2f * layoutScale))), new Color(139, 42, 50) * 0.72f);

        if (itemType > ItemID.None && itemType < ItemLoader.ItemCount)
        {
            Item display = new();
            display.SetDefaults(itemType);
            Vector2 iconCenter = new(bounds.X + 31f * layoutScale, bounds.Y + 27f * layoutScale);
            AltarDrawHelpers.DrawItemSlot(spriteBatch, pixel, iconCenter, 38f * layoutScale, new Color(28, 23, 25), new Color(143, 52, 57));
            if (boundCharacterDeathDomain)
            {
                int iconSize = Math.Max(1, (int)MathF.Round(29f * layoutScale));
                DeathDomainBackdropTextureSystem.DrawUiIcon(
                    spriteBatch,
                    new Rectangle(
                        (int)MathF.Round(iconCenter.X - iconSize * 0.5f),
                        (int)MathF.Round(iconCenter.Y - iconSize * 0.5f),
                        iconSize,
                        iconSize),
                    Main.MouseScreen);
            }
            else
            {
                AltarDrawHelpers.DrawItemIcon(spriteBatch, display, iconCenter, 31f * layoutScale);
            }
        }
        float titleScale = 0.76f * layoutScale;
        string title = Language.GetTextValue("Mods.SoulHarvest.UI.EquipmentTree.ProgressionTitle", itemName);
        title = AltarDrawHelpers.FitText(title, 255f * layoutScale, titleScale);
        Utils.DrawBorderString(spriteBatch, title, new Vector2(bounds.X + 56f * layoutScale, bounds.Y + 15f * layoutScale), new Color(232, 219, 202), titleScale);

        float resourcesLeft = bounds.X + 326f * layoutScale;
        float resourcesRight = bounds.Right - 12f * layoutScale;
        float cellWidth = Math.Max(34f * layoutScale, (resourcesRight - resourcesLeft) * 0.5f);
        DrawResourceCell(spriteBatch, resourcesLeft, bounds.Y + 9f * layoutScale, cellWidth, new Color(201, 68, 76), Language.GetTextValue("Mods.SoulHarvest.UI.ReaperTree.ResourceEssence", essenceCount));
        DrawResourceCell(spriteBatch, resourcesLeft + cellWidth, bounds.Y + 9f * layoutScale, cellWidth, new Color(196, 151, 113), Language.GetTextValue("Mods.SoulHarvest.UI.ReaperTree.ResourceSouls", souls));
    }

    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 - 5f * layoutScale)), Math.Max(1, (int)(34f * layoutScale)));
        batch.Draw(pixel, cell, new Color(21, 19, 22) * 0.94f);
        ReaperTreeDraw.DrawBorder(batch, pixel, cell, new Color(72, 57, 57), 1);
        Vector2 rune = new(cell.X + 13f * layoutScale, cell.Center.Y);
        ReaperTreeDraw.DrawDiamond(batch, pixel, rune, 6f * layoutScale, color * 0.86f);
        float textScale = 0.55f * layoutScale;
        string fitted = AltarDrawHelpers.FitText(text, Math.Max(6f, cell.Width - 27f * layoutScale), textScale);
        Utils.DrawBorderString(batch, fitted, new Vector2(cell.X + 24f * layoutScale, cell.Y + 9f * layoutScale), color, textScale);
    }

    private void BuildTree(IReadOnlyList<DeathAltarUpgradeType> types, out float contentWidth, out float contentHeight)
    {
        contentWidth = 1320f;
        Vector2 rootPosition = new(contentWidth * 0.5f, 112f);
        nodes.Add(AltarEquipmentTreeNode.Root(rootPosition));

        List<DeathAltarUpgradeType> regular = [];
        List<DeathAltarUpgradeType> convergence = [];
        foreach (DeathAltarUpgradeType type in types)
        {
            if (IsConvergenceNode(type))
                convergence.Add(type);
            else
                regular.Add(type);
        }

        int columns = Math.Clamp(regular.Count, 1, 5);
        const float spacingX = 214f;
        const float spacingY = 158f;
        const float firstRowY = 310f;
        int rows = Math.Max(1, (regular.Count + columns - 1) / columns);
        for (int index = 0; index < regular.Count; index++)
        {
            int row = index / columns;
            int rowStart = row * columns;
            int rowCount = Math.Min(columns, regular.Count - rowStart);
            int column = index - rowStart;
            float startX = contentWidth * 0.5f - (rowCount - 1) * spacingX * 0.5f;
            nodes.Add(AltarEquipmentTreeNode.Upgrade(regular[index], new Vector2(startX + column * spacingX, firstRowY + row * spacingY), convergence: false));
        }

        float convergenceY = firstRowY + rows * spacingY + 42f;
        for (int index = 0; index < convergence.Count; index++)
        {
            float startX = contentWidth * 0.5f - (convergence.Count - 1) * spacingX * 0.5f;
            nodes.Add(AltarEquipmentTreeNode.Upgrade(convergence[index], new Vector2(startX + index * spacingX, convergenceY), convergence: true));
        }
        float lastY = convergence.Count > 0 ? convergenceY : firstRowY + (rows - 1) * spacingY;
        contentHeight = Math.Max(760f, lastY + 190f);
    }

    internal static IReadOnlyList<DeathAltarUpgradeType> GetPrerequisites(DeathAltarUpgradeType type)
    {
        return type switch
        {
            DeathAltarUpgradeType.FatedMaxStacks or DeathAltarUpgradeType.FatedStacksPerHit or DeathAltarUpgradeType.FatedDuration
                => [DeathAltarUpgradeType.FatedUnlock],
            DeathAltarUpgradeType.WingInfiniteFlight
                => [DeathAltarUpgradeType.WingFlightTime, DeathAltarUpgradeType.WingHorizontalSpeed, DeathAltarUpgradeType.WingAcceleration, DeathAltarUpgradeType.WingVerticalPower],
            DeathAltarUpgradeType.NecklaceSovereignty
                => [DeathAltarUpgradeType.NecklaceRadius, DeathAltarUpgradeType.NecklaceAbsorbAll],
            _ => Array.Empty<DeathAltarUpgradeType>()
        };
    }

    private static bool IsConvergenceNode(DeathAltarUpgradeType type)
    {
        return type is DeathAltarUpgradeType.WingInfiniteFlight
            or DeathAltarUpgradeType.NecklaceSovereignty;
    }
}