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

namespace SoulHarvest.UI;

internal class DeathAltarUIState : UIState
{
    private const float BasePanelWidth = 1180f;
    private const float BasePanelHeight = 700f;
    private const float TreeLeft = 228f;
    private readonly List<AltarEquipmentEntry> availableEquipment = [];
    private DeathAltarFramePanel? panel;
    private UIPanel? selectorPanel;
    private UIList? itemList;
    private UIScrollbar? itemScrollbar;
    private UIText? titleText;
    private UIText? selectorTitleText;
    private UIText? itemText;
    private UIText? resourceText;
    private AltarTextButton? closeButton;
    private AltarTextButton? recipeHelpButton;
    private ReaperProgressionPanel? characterProgressionPanel;
    private AltarEquipmentProgressionPanel? equipmentProgressionPanel;
    private UIPanel? recipeHelpPanel;
    private UIList? recipeList;
    private UIScrollbar? recipeScrollbar;
    private UIText? recipeHelpTitle;
    private UIText? noRecipesText;
    private bool recipeHelpVisible;
    private AltarEquipmentEntry? selectedEquipment;
    private int tileX;
    private int tileY;
    private int itemSignature;
    private float layoutScale = 1f;
    private int layoutWidth = -1;
    private int layoutHeight = -1;

    public override void OnInitialize()
    {
        panel = new DeathAltarFramePanel();
        panel.BackgroundColor = new Color(10, 9, 11) * 0.985f;
        panel.BorderColor = new Color(116, 34, 43);
        Append(panel);

        titleText = new UIText(string.Empty, 1.08f, true)
        {
            DynamicallyScaleDownToWidth = true,
            TextOriginX = 0.5f,
            TextColor = new Color(232, 219, 204)
        };
        panel.Append(titleText);

        closeButton = new AltarTextButton(
            "×",
            1f,
            new Color(90, 35, 55),
            new Color(142, 49, 72),
            new Color(180, 82, 105));
        closeButton.OnLeftClick += (_, _) =>
        {
            SoundEngine.PlaySound(SoundID.MenuClose);
            ModContent.GetInstance<DeathAltarUISystem>().Hide();
        };
        panel.Append(closeButton);

        recipeHelpButton = new AltarTextButton(
            string.Empty,
            0.72f,
            new Color(42, 35, 39),
            new Color(70, 42, 46),
            new Color(151, 54, 63));
        recipeHelpButton.OnLeftClick += (_, _) => ToggleRecipeHelp();
        panel.Append(recipeHelpButton);

        selectorTitleText = new UIText(string.Empty, 0.86f, true)
        {
            DynamicallyScaleDownToWidth = true,
            TextColor = new Color(218, 203, 190)
        };
        panel.Append(selectorTitleText);

        selectorPanel = new UIPanel();
        selectorPanel.BackgroundColor = new Color(15, 14, 17) * 0.94f;
        selectorPanel.BorderColor = new Color(83, 48, 51);
        panel.Append(selectorPanel);

        itemList = new UIList();
        selectorPanel.Append(itemList);
        itemScrollbar = new UIScrollbar();
        selectorPanel.Append(itemScrollbar);
        itemList.SetScrollbar(itemScrollbar);

        itemText = new UIText(string.Empty, 0.85f, true)
        {
            DynamicallyScaleDownToWidth = true,
            TextColor = new Color(237, 222, 208)
        };
        panel.Append(itemText);
        resourceText = new UIText(string.Empty, 0.78f)
        {
            DynamicallyScaleDownToWidth = true,
            TextColor = new Color(202, 96, 104)
        };
        panel.Append(resourceText);

        BuildRecipeHelpPanel();
        BuildCharacterProgressionPanel();
        BuildEquipmentProgressionPanel();
        ApplyResponsiveLayout(UserInterface.ActiveInstance.GetDimensions());
    }

    public override void OnDeactivate()
    {
        CancelTransientInteraction();
        base.OnDeactivate();
    }

    public override void Recalculate()
    {
        ApplyResponsiveLayout(UserInterface.ActiveInstance.GetDimensions());
        base.Recalculate();
    }

    private void ApplyResponsiveLayout(CalculatedStyle viewport)
    {
        if (panel is null)
            return;

        int viewportWidth = Math.Max(1, (int)MathF.Round(viewport.Width));
        int viewportHeight = Math.Max(1, (int)MathF.Round(viewport.Height));
        if (viewportWidth == layoutWidth && viewportHeight == layoutHeight)
            return;

        layoutWidth = viewportWidth;
        layoutHeight = viewportHeight;
        float widthScale = Math.Max(0.01f, (viewport.Width - 24f) / BasePanelWidth);
        float heightScale = Math.Max(0.01f, (viewport.Height - 24f) / BasePanelHeight);
        // Active UI dimensions can be as small as 400x300 when the game is run at
        // 800x600 with 200% UI scale. Scale against both axes and preserve a small
        // safety margin instead of forcing a minimum that can leave the frame off-screen.
        layoutScale = Math.Clamp(Math.Min(1f, Math.Min(widthScale, heightScale)), 0.25f, 1f);

        float panelWidth = BasePanelWidth * layoutScale;
        float panelHeight = BasePanelHeight * layoutScale;
        panel.Left.Set(-panelWidth * 0.5f, 0.5f);
        panel.Top.Set(-panelHeight * 0.5f, 0.5f);
        panel.Width.Set(panelWidth, 0f);
        panel.Height.Set(panelHeight, 0f);
        panel.SetPadding(14f * layoutScale);
        panel.LayoutScale = layoutScale;
        panel.SidebarDividerX = 218f * layoutScale;

        if (titleText is not null)
        {
            titleText.Left.Set(240f * layoutScale, 0f);
            titleText.Top.Set(4f * layoutScale, 0f);
            titleText.Width.Set(-480f * layoutScale, 1f);
            titleText.Height.Set(30f * layoutScale, 0f);
            SetConstrainedText(titleText, titleText.Text, 1.08f, large: true);
        }

        LayoutButton(closeButton, -34f, 1f, 0f, 30f, 30f, 1f);
        LayoutButton(recipeHelpButton, -156f, 1f, 0f, 116f, 30f, 0.72f);

        if (selectorTitleText is not null)
        {
            selectorTitleText.Left.Set(10f * layoutScale, 0f);
            selectorTitleText.Top.Set(49f * layoutScale, 0f);
            selectorTitleText.Width.Set(198f * layoutScale, 0f);
            selectorTitleText.Height.Set(24f * layoutScale, 0f);
            SetConstrainedText(selectorTitleText, selectorTitleText.Text, 0.86f, large: true);
        }

        if (selectorPanel is not null)
        {
            selectorPanel.Left.Set(4f * layoutScale, 0f);
            selectorPanel.Top.Set(78f * layoutScale, 0f);
            selectorPanel.Width.Set(206f * layoutScale, 0f);
            selectorPanel.Height.Set(564f * layoutScale, 0f);
            selectorPanel.SetPadding(6f * layoutScale);
        }

        if (itemList is not null)
        {
            itemList.Width.Set(-18f * layoutScale, 1f);
            itemList.Height.Set(0f, 1f);
            itemList.ListPadding = 5f * layoutScale;
        }

        if (itemScrollbar is not null)
        {
            itemScrollbar.Left.Set(-14f * layoutScale, 1f);
            itemScrollbar.Width.Set(14f * layoutScale, 0f);
            itemScrollbar.Height.Set(0f, 1f);
        }

        LayoutTreeHeaderText(itemText, 48f, 0.85f, large: true);
        LayoutTreeHeaderText(resourceText, 75f, 0.78f, large: false);

        LayoutRecipeHelpPanel();
        LayoutCharacterProgressionPanel();
        LayoutEquipmentProgressionPanel();
        if (availableEquipment.Count > 0)
            RebuildSelectors();
        if (recipeHelpVisible)
            RefreshRecipeHelp();
        SyncSelectedContentLayer();
    }

    private void LayoutTreeHeaderText(UIText? text, float top, float textScale, bool large)
    {
        if (text is null)
            return;
        text.Left.Set(TreeLeft * layoutScale, 0f);
        text.Top.Set(top * layoutScale, 0f);
        text.Width.Set(-(TreeLeft + 8f) * layoutScale, 1f);
        text.Height.Set(24f * layoutScale, 0f);
        SetConstrainedText(text, text.Text, textScale, large);
    }

    private void LayoutButton(AltarTextButton? button, float left, float leftPercent, float top, float width, float height, float textScale)
    {
        if (button is null)
            return;
        button.Left.Set(left * layoutScale, leftPercent);
        button.Top.Set(top * layoutScale, 0f);
        button.Width.Set(width * layoutScale, 0f);
        button.Height.Set(height * layoutScale, 0f);
        button.SetPadding(0f);
        button.TextScale = textScale * layoutScale;
    }

    private void SetConstrainedText(UIText text, string value, float textScale, bool large)
    {
        text.SetText(value, textScale * layoutScale, large);
        text.MinWidth.Set(0f, 0f);
    }

    public override void Update(GameTime gameTime)
    {
        base.Update(gameTime);
        if (panel?.ContainsPoint(Main.MouseScreen) == true)
            Main.LocalPlayer.mouseInterface = true;
        Player player = Main.LocalPlayer;
        RefreshAvailableEquipment(player);

        IDeathAltarUpgradeable? upgradeable = null;
        Item? selected = null;
        bool characterSickleSelected = selectedEquipment is AltarEquipmentEntry selection && selection.IsCharacterSickle;
        if (selectedEquipment is AltarEquipmentEntry selectedEntry && selectedEntry.TryGetDisplayItem(player, out Item resolved))
        {
            selected = resolved;
            if (selectedEntry.IsCharacterDeathDomain)
                upgradeable = player.GetModPlayer<MyPlayer>().DeathDomainProgression;
            else if (!selectedEntry.IsCharacterSickle)
                upgradeable = resolved.ModItem as IDeathAltarUpgradeable;
        }
        int essenceCount = player.CountItem(ModContent.ItemType<Soul>());
        int souls = player.GetModPlayer<MyPlayer>().Souls;
        if (!characterSickleSelected
            && selectedEquipment is AltarEquipmentEntry equipmentEntry
            && selected is not null
            && upgradeable is not null
            && equipmentProgressionPanel is not null)
        {
            equipmentProgressionPanel.Bind(equipmentEntry, selected, upgradeable);
            equipmentProgressionPanel.Refresh(selected, upgradeable, essenceCount, souls);
        }

        if (titleText is not null)
            SetConstrainedText(titleText, Language.GetTextValue("Mods.SoulHarvest.UI.AltarTitle"), 1.08f, large: true);
        if (selectorTitleText is not null)
            SetConstrainedText(selectorTitleText, Language.GetTextValue("Mods.SoulHarvest.UI.SelectEquipment"), 0.86f, large: true);
        if (itemText is not null)
            SetConstrainedText(itemText, selected is null
                ? Language.GetTextValue("Mods.SoulHarvest.UI.NoUpgradeableItems")
                : Language.GetTextValue("Mods.SoulHarvest.UI.CurrentItem", selected.Name), 0.85f, large: true);
        if (resourceText is not null)
            SetConstrainedText(resourceText, Language.GetTextValue("Mods.SoulHarvest.UI.AltarResources", essenceCount, souls), 0.78f, large: false);
        recipeHelpButton?.SetText(Language.GetTextValue(recipeHelpVisible
            ? "Mods.SoulHarvest.UI.BackToSkillTree"
            : "Mods.SoulHarvest.UI.RecipeHelpButton"));
        if (characterSickleSelected)
            characterProgressionPanel?.Refresh(player, essenceCount, souls);
        SyncSelectedContentLayer();
    }

    public void SetAltarPosition(int x, int y)
    {
        CancelTransientInteraction();
        tileX = x;
        tileY = y;
        itemSignature = 0;
        selectedEquipment = AltarEquipmentEntry.CreateCharacterSickle();
        SetRecipeHelpVisible(false, playSound: false);
        SyncSelectedContentLayer();
    }

    internal void CancelTransientInteraction()
    {
        characterProgressionPanel?.CancelInteraction();
        equipmentProgressionPanel?.DeactivateInteraction();
    }

    public bool IsPlayerNearAltar()
    {
        Tile tile = Framing.GetTileSafely(tileX, tileY);
        if (!tile.HasTile || tile.TileType != ModContent.TileType<DeathAltarTile>())
            return false;
        Vector2 altar = new((tileX + 0.5f) * 16f, (tileY + 0.5f) * 16f);
        return Vector2.Distance(Main.LocalPlayer.Center, altar) <= 12f * 16f;
    }

    internal void SelectEquipment(AltarEquipmentEntry entry)
    {
        if (selectedEquipment is AltarEquipmentEntry current && IsSameEquipmentEntry(current, entry))
            return;

        CancelTransientInteraction();
        selectedEquipment = entry;
        SetRecipeHelpVisible(false, playSound: false);
        RebuildSelectors();
        SyncSelectedContentLayer();
        SoundEngine.PlaySound(SoundID.MenuTick);
    }

    internal void RequestUpgrade(DeathAltarUpgradeType type)
    {
        if (selectedEquipment?.ItemReference is not DeathAltarItemReference reference)
            return;
        SoundEngine.PlaySound(SoundID.MenuTick);
        ModContent.GetInstance<DeathAltarUISystem>().RequestUpgrade(type, reference, tileX, tileY);
    }

    private void RefreshAvailableEquipment(Player player)
    {
        AltarEquipmentEntry characterSickle = AltarEquipmentEntry.CreateCharacterSickle();
        AltarEquipmentEntry characterDeathDomain = AltarEquipmentEntry.CreateCharacterDeathDomain();
        List<AltarEquipmentEntry> found = [characterSickle, characterDeathDomain];
        int signature = HashCode.Combine(17, (int)characterSickle.Kind, characterSickle.ItemType);
        signature = HashCode.Combine(signature, (int)characterDeathDomain.Kind, characterDeathDomain.ItemType);
        ScanContainer(player.inventory, DeathAltarItemStorage.Inventory, Math.Min(58, player.inventory.Length), found, ref signature);
        ScanContainer(player.armor, DeathAltarItemStorage.Armor, player.armor.Length, found, ref signature);
        if (signature == itemSignature && found.Count == availableEquipment.Count)
            return;
        CancelTransientInteraction();
        AltarEquipmentEntry? previous = selectedEquipment;
        availableEquipment.Clear();
        availableEquipment.AddRange(found);
        selectedEquipment = previous is AltarEquipmentEntry old ? FindSameEquipmentEntry(old) : null;
        selectedEquipment ??= characterSickle;
        itemSignature = signature;
        RebuildSelectors();
        SyncSelectedContentLayer();
    }

    private static void ScanContainer(Item[] container, DeathAltarItemStorage storage, int accessibleSlots, List<AltarEquipmentEntry> found, ref int signature)
    {
        for (int slot = 0; slot < accessibleSlots && slot <= byte.MaxValue; slot++)
        {
            Item item = container[slot];
            if (item.IsAir || item.ModItem is not IDeathAltarUpgradeable)
                continue;
            DeathAltarItemReference reference = new(storage, (byte)slot, item.type);
            found.Add(AltarEquipmentEntry.CreateItem(reference));
            signature = HashCode.Combine(signature, (int)storage, slot, item.type);
        }
    }

    private AltarEquipmentEntry? FindSameEquipmentEntry(AltarEquipmentEntry old)
    {
        foreach (AltarEquipmentEntry candidate in availableEquipment)
            if (IsSameEquipmentEntry(candidate, old))
                return candidate;
        return null;
    }

    private static bool IsSameEquipmentEntry(AltarEquipmentEntry left, AltarEquipmentEntry right)
    {
        if (left.Kind != right.Kind)
            return false;
        if (left.IsCharacterProgression)
            return true;
        return left.ItemReference is DeathAltarItemReference leftReference
            && right.ItemReference is DeathAltarItemReference rightReference
            && leftReference.Storage == rightReference.Storage
            && leftReference.Slot == rightReference.Slot;
    }

    private void RebuildSelectors()
    {
        if (itemList is null)
            return;
        itemList.Clear();
        foreach (AltarEquipmentEntry entry in availableEquipment)
        {
            if (!entry.TryGetDisplayItem(Main.LocalPlayer, out Item item))
                continue;
            bool isSelected = selectedEquipment is AltarEquipmentEntry current && IsSameEquipmentEntry(current, entry);
            itemList.Add(new AltarItemSelector(this, entry, item, isSelected, layoutScale));
        }
    }

    private void BuildRecipeHelpPanel()
    {
        recipeHelpPanel = new UIPanel();
        recipeHelpPanel.BackgroundColor = new Color(13, 12, 14) * 0.995f;
        recipeHelpPanel.BorderColor = new Color(111, 42, 49);

        recipeHelpTitle = new UIText(string.Empty, 0.9f, true) { HAlign = 0.5f };
        recipeHelpTitle.DynamicallyScaleDownToWidth = true;
        recipeHelpPanel.Append(recipeHelpTitle);

        recipeList = new UIList();
        recipeHelpPanel.Append(recipeList);

        recipeScrollbar = new UIScrollbar();
        recipeHelpPanel.Append(recipeScrollbar);
        recipeList.SetScrollbar(recipeScrollbar);

        noRecipesText = new UIText(string.Empty, 0.72f) { HAlign = 0.5f };
        noRecipesText.DynamicallyScaleDownToWidth = true;
        LayoutRecipeHelpPanel();
    }

    private void BuildCharacterProgressionPanel()
    {
        characterProgressionPanel = new ReaperProgressionPanel();
        characterProgressionPanel.BackgroundColor = new Color(13, 11, 14) * 0.995f;
        characterProgressionPanel.BorderColor = new Color(126, 39, 49);
        LayoutCharacterProgressionPanel();
    }

    private void BuildEquipmentProgressionPanel()
    {
        equipmentProgressionPanel = new AltarEquipmentProgressionPanel(this);
        LayoutEquipmentProgressionPanel();
    }

    private void LayoutCharacterProgressionPanel()
    {
        if (characterProgressionPanel is null)
            return;

        characterProgressionPanel.Left.Set((TreeLeft - 6f) * layoutScale, 0f);
        characterProgressionPanel.Top.Set(42f * layoutScale, 0f);
        characterProgressionPanel.Width.Set(-(TreeLeft - 2f) * layoutScale, 1f);
        characterProgressionPanel.Height.Set(-46f * layoutScale, 1f);
        characterProgressionPanel.SetPadding(10f * layoutScale);
        characterProgressionPanel.ApplyLayoutScale(layoutScale);
    }

    private void LayoutEquipmentProgressionPanel()
    {
        if (equipmentProgressionPanel is null)
            return;

        equipmentProgressionPanel.Left.Set((TreeLeft - 6f) * layoutScale, 0f);
        equipmentProgressionPanel.Top.Set(42f * layoutScale, 0f);
        equipmentProgressionPanel.Width.Set(-(TreeLeft - 2f) * layoutScale, 1f);
        equipmentProgressionPanel.Height.Set(-46f * layoutScale, 1f);
        equipmentProgressionPanel.SetPadding(0f);
        equipmentProgressionPanel.ApplyLayoutScale(layoutScale);
    }

    private void LayoutRecipeHelpPanel()
    {
        if (recipeHelpPanel is null)
            return;

        recipeHelpPanel.Left.Set((TreeLeft - 6f) * layoutScale, 0f);
        recipeHelpPanel.Top.Set(42f * layoutScale, 0f);
        recipeHelpPanel.Width.Set(-(TreeLeft - 2f) * layoutScale, 1f);
        recipeHelpPanel.Height.Set(-42f * layoutScale, 1f);
        recipeHelpPanel.SetPadding(10f * layoutScale);

        if (recipeHelpTitle is not null)
        {
            recipeHelpTitle.HAlign = 0f;
            recipeHelpTitle.Top.Set(2f * layoutScale, 0f);
            recipeHelpTitle.Width.Set(0f, 1f);
            recipeHelpTitle.Height.Set(28f * layoutScale, 0f);
            recipeHelpTitle.TextOriginX = 0.5f;
            SetConstrainedText(recipeHelpTitle, recipeHelpTitle.Text, 0.9f, large: true);
        }

        if (recipeList is not null)
        {
            recipeList.Top.Set(38f * layoutScale, 0f);
            recipeList.Width.Set(-20f * layoutScale, 1f);
            recipeList.Height.Set(-42f * layoutScale, 1f);
            recipeList.ListPadding = 8f * layoutScale;
        }

        if (recipeScrollbar is not null)
        {
            recipeScrollbar.Left.Set(-15f * layoutScale, 1f);
            recipeScrollbar.Top.Set(38f * layoutScale, 0f);
            recipeScrollbar.Width.Set(14f * layoutScale, 0f);
            recipeScrollbar.Height.Set(-42f * layoutScale, 1f);
        }

        if (noRecipesText is not null)
        {
            noRecipesText.HAlign = 0f;
            noRecipesText.Left.Set(10f * layoutScale, 0f);
            noRecipesText.Top.Set(82f * layoutScale, 0f);
            noRecipesText.Width.Set(-20f * layoutScale, 1f);
            noRecipesText.Height.Set(22f * layoutScale, 0f);
            noRecipesText.TextOriginX = 0.5f;
            SetConstrainedText(noRecipesText, noRecipesText.Text, 0.72f, large: false);
        }
    }

    private void ToggleRecipeHelp()
    {
        SetRecipeHelpVisible(!recipeHelpVisible, playSound: true);
    }

    private bool IsCharacterSickleSelected()
    {
        return selectedEquipment is AltarEquipmentEntry entry && entry.IsCharacterSickle;
    }

    private void SyncSelectedContentLayer()
    {
        if (panel is null || characterProgressionPanel is null || equipmentProgressionPanel is null)
            return;

        bool showCharacterTree = IsCharacterSickleSelected() && !recipeHelpVisible;
        bool showEquipmentTree = !IsCharacterSickleSelected()
            && selectedEquipment is not null
            && !recipeHelpVisible;
        if (showCharacterTree)
        {
            if (characterProgressionPanel.Parent is null)
            {
                characterProgressionPanel.CancelInteraction();
                panel.Append(characterProgressionPanel);
            }
        }
        else if (characterProgressionPanel.Parent is not null)
        {
            characterProgressionPanel.CancelInteraction();
            panel.RemoveChild(characterProgressionPanel);
        }

        if (showEquipmentTree)
        {
            if (equipmentProgressionPanel.Parent is null)
            {
                equipmentProgressionPanel.DeactivateInteraction();
                panel.Append(equipmentProgressionPanel);
            }
        }
        else if (equipmentProgressionPanel.Parent is not null)
        {
            equipmentProgressionPanel.DeactivateInteraction();
            panel.RemoveChild(equipmentProgressionPanel);
        }
    }

    private void SetRecipeHelpVisible(bool visible, bool playSound)
    {
        if (visible)
            CancelTransientInteraction();
        recipeHelpVisible = visible;
        if (panel is null || recipeHelpPanel is null)
            return;

        if (visible)
        {
            SyncSelectedContentLayer();
            RefreshRecipeHelp();
            BringRecipeHelpToFront();
        }
        else if (recipeHelpPanel.Parent is not null)
        {
            panel.RemoveChild(recipeHelpPanel);
        }

        if (!visible)
            SyncSelectedContentLayer();

        recipeHelpButton?.SetText(Language.GetTextValue(visible
            ? "Mods.SoulHarvest.UI.BackToSkillTree"
            : "Mods.SoulHarvest.UI.RecipeHelpButton"));
        if (playSound)
            SoundEngine.PlaySound(SoundID.MenuTick);
    }

    private void BringRecipeHelpToFront()
    {
        if (!recipeHelpVisible || panel is null || recipeHelpPanel is null)
            return;
        if (recipeHelpPanel.Parent is not null)
            panel.RemoveChild(recipeHelpPanel);
        panel.Append(recipeHelpPanel);
    }

    private void RefreshRecipeHelp()
    {
        if (recipeList is null || recipeHelpPanel is null)
            return;

        recipeList.Clear();
        int recipeCount = 0;
        Mod deathMod = ModContent.GetInstance<SoulHarvest>();
        for (int index = 0; index < Main.recipe.Length; index++)
        {
            Recipe recipe = Main.recipe[index];
            if (recipe is null
                || recipe.Disabled
                || recipe.createItem.IsAir
                || recipe.createItem.ModItem?.Mod != deathMod)
            {
                continue;
            }

            recipeList.Add(new AltarRecipeEntry(recipe, layoutScale));
            recipeCount++;
        }

        if (recipeHelpTitle is not null)
            SetConstrainedText(recipeHelpTitle, Language.GetTextValue("Mods.SoulHarvest.UI.RecipeHelpTitle", recipeCount), 0.9f, large: true);
        if (noRecipesText is not null)
            SetConstrainedText(noRecipesText, Language.GetTextValue("Mods.SoulHarvest.UI.NoModRecipes"), 0.72f, large: false);
        if (noRecipesText is not null)
        {
            if (recipeCount == 0 && noRecipesText.Parent is null)
                recipeHelpPanel.Append(noRecipesText);
            else if (recipeCount > 0 && noRecipesText.Parent is not null)
                recipeHelpPanel.RemoveChild(noRecipesText);
        }
    }

    internal static string FormatPrice(DeathAltarPrice price)
    {
        string key = price.Currency switch
        {
            DeathAltarCurrency.Souls => "Souls",
            DeathAltarCurrency.SoulEssenceRefund => "SoulEssenceRefund",
            _ => "SoulEssence"
        };
        return Language.GetTextValue($"Mods.SoulHarvest.UI.UpgradeCosts.{key}", price.Amount);
    }
}