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));
}
}
}
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));
}
}
}