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 AltarEquipmentTreeCanvas : UIElement
{
private readonly AltarEquipmentTreeViewport viewport;
private readonly IReadOnlyList<AltarEquipmentTreeNode> nodes;
private readonly float contentWidth;
private readonly float contentHeight;
public AltarEquipmentTreeCanvas(AltarEquipmentTreeViewport viewport, IReadOnlyList<AltarEquipmentTreeNode> nodes, float contentWidth, float contentHeight)
{
this.viewport = viewport;
this.nodes = nodes;
this.contentWidth = contentWidth;
this.contentHeight = contentHeight;
}
protected override void DrawSelf(SpriteBatch spriteBatch)
{
Texture2D pixel = TextureAssets.MagicPixel.Value;
float scale = viewport.LayoutScale;
DrawBackdrop(spriteBatch, pixel, scale);
DrawConnections(spriteBatch, pixel, scale);
foreach (AltarEquipmentTreeNode node in nodes)
DrawNode(spriteBatch, pixel, node, scale);
if (viewport.HoveredNode is AltarEquipmentTreeNode hovered)
Main.instance.MouseText(hovered.Title);
}
private void DrawBackdrop(SpriteBatch batch, Texture2D pixel, float scale)
{
Color grid = new Color(84, 71, 70) * 0.08f;
for (float x = 34f; x < contentWidth; x += 88f)
ReaperTreeDraw.DrawLine(batch, pixel, viewport.DesignToScreen(new Vector2(x, 0f)), viewport.DesignToScreen(new Vector2(x, contentHeight)), grid, Math.Max(1f, scale));
for (float y = 30f; y < contentHeight; y += 88f)
ReaperTreeDraw.DrawLine(batch, pixel, viewport.DesignToScreen(new Vector2(0f, y)), viewport.DesignToScreen(new Vector2(contentWidth, y)), grid, Math.Max(1f, scale));
}
private void DrawConnections(SpriteBatch batch, Texture2D pixel, float scale)
{
if (nodes.Count == 0)
return;
AltarEquipmentTreeNode root = nodes[0];
foreach (AltarEquipmentTreeNode node in nodes)
{
if (node.IsRoot || node.UpgradeType is not DeathAltarUpgradeType type)
continue;
IReadOnlyList<DeathAltarUpgradeType> prerequisites = AltarEquipmentProgressionPanel.GetPrerequisites(type);
if (prerequisites.Count == 0)
{
DrawPath(batch, pixel, root, node, node.Completed, scale);
continue;
}
foreach (DeathAltarUpgradeType prerequisite in prerequisites)
{
AltarEquipmentTreeNode? parent = Find(prerequisite);
if (parent is not null)
DrawPath(batch, pixel, parent, node, parent.Completed, scale);
}
}
}
private void DrawPath(SpriteBatch batch, Texture2D pixel, AltarEquipmentTreeNode start, AltarEquipmentTreeNode end, bool lit, float scale)
{
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 color = lit ? new Color(184, 54, 63) : new Color(64, 57, 58);
ReaperTreeDraw.DrawLine(batch, pixel, a, b, new Color(6, 6, 8) * 0.9f, Math.Max(2f, 5f * scale));
ReaperTreeDraw.DrawLine(batch, pixel, a, b, color * 0.78f, Math.Max(1f, (lit ? 2.3f : 1.2f) * scale));
ReaperTreeDraw.DrawDiamond(batch, pixel, Vector2.Lerp(a, b, 0.5f), (lit ? 4f : 3f) * scale, color * 0.92f);
}
private void DrawNode(SpriteBatch batch, Texture2D pixel, AltarEquipmentTreeNode node, float scale)
{
Vector2 center = viewport.DesignToScreen(node.Position);
float radius = node.Radius * scale;
bool selected = ReferenceEquals(viewport.SelectedNode, node);
bool hovered = ReferenceEquals(viewport.HoveredNode, node);
Color color = node.Completed ? new Color(207, 161, 116) : node.CanRequest && node.Affordable ? new Color(207, 57, 68) : node.CanRequest ? new Color(145, 58, 61) : new Color(79, 71, 70);
ReaperTreeDraw.DrawDiamond(batch, pixel, center, radius * 0.88f, new Color(8, 8, 10) * 0.99f);
if (node.IsRoot)
ReaperTreeDraw.DrawRing(batch, pixel, center, radius, new Color(212, 60, 69), Math.Max(1f, 2.8f * scale), 30);
else
ReaperTreeDraw.DrawDiamondOutline(batch, pixel, center, radius, color, Math.Max(1f, 2.2f * scale));
if (selected || hovered)
ReaperTreeDraw.DrawRing(batch, pixel, center, radius + (selected ? 7f : 4f) * scale, selected ? Color.White * 0.76f : color * 0.62f, Math.Max(1f, (selected ? 2f : 1f) * scale), 26);
Color runeColor = node.Lit ? new Color(239, 219, 199) : new Color(121, 111, 108);
if (node.IsRoot)
{
ReaperTreeDraw.DrawDiamondOutline(batch, pixel, center, radius * 0.34f, runeColor, Math.Max(1f, 1.7f * scale));
ReaperTreeDraw.DrawRing(batch, pixel, center, radius * 0.16f, runeColor * 0.84f, Math.Max(1f, 1.2f * scale), 16);
}
else
{
DrawUpgradeRune(batch, pixel, node.UpgradeType!.Value, center, radius, runeColor, scale);
}
if (!node.IsRoot)
{
float labelScale = 0.47f * scale;
string label = AltarDrawHelpers.FitText(node.Title, 166f * scale, labelScale);
Vector2 size = FontAssets.MouseText.Value.MeasureString(label) * labelScale;
Utils.DrawBorderString(batch, label, center + new Vector2(-size.X * 0.5f, radius + 13f * scale), node.Lit ? new Color(221, 203, 188) : new Color(128, 117, 114), labelScale);
}
}
private AltarEquipmentTreeNode? Find(DeathAltarUpgradeType type)
{
foreach (AltarEquipmentTreeNode node in nodes)
if (node.UpgradeType == type)
return node;
return null;
}
private static void DrawUpgradeRune(
SpriteBatch batch,
Texture2D pixel,
DeathAltarUpgradeType type,
Vector2 center,
float radius,
Color color,
float scale)
{
string name = type.ToString();
float lineWidth = Math.Max(1f, 1.65f * scale);
float arm = radius * 0.45f;
if (name.StartsWith("Domain", StringComparison.Ordinal))
{
ReaperTreeDraw.DrawRing(batch, pixel, center, arm * 0.72f, color, lineWidth, 22);
ReaperTreeDraw.DrawLine(batch, pixel, center + new Vector2(-arm, 0f), center + new Vector2(arm, 0f), color * 0.86f, lineWidth);
ReaperTreeDraw.DrawLine(batch, pixel, center + new Vector2(-arm * 0.62f, arm * 0.48f), center + new Vector2(arm * 0.62f, -arm * 0.48f), color, lineWidth);
}
else if (name.StartsWith("Wing", StringComparison.Ordinal))
{
Vector2 left = center + new Vector2(-arm, arm * 0.52f);
Vector2 right = center + new Vector2(arm, arm * 0.52f);
ReaperTreeDraw.DrawLine(batch, pixel, center, left, color, lineWidth);
ReaperTreeDraw.DrawLine(batch, pixel, center, right, color, lineWidth);
ReaperTreeDraw.DrawLine(batch, pixel, center + new Vector2(-arm * 0.18f, 0f), center + new Vector2(-arm * 0.72f, -arm * 0.38f), color * 0.8f, lineWidth);
ReaperTreeDraw.DrawLine(batch, pixel, center + new Vector2(arm * 0.18f, 0f), center + new Vector2(arm * 0.72f, -arm * 0.38f), color * 0.8f, lineWidth);
}
else if (name.StartsWith("Robe", StringComparison.Ordinal))
{
ReaperTreeDraw.DrawLine(batch, pixel, center + new Vector2(0f, -arm), center + new Vector2(0f, arm), color, lineWidth);
ReaperTreeDraw.DrawLine(batch, pixel, center + new Vector2(-arm * 0.72f, -arm * 0.28f), center + new Vector2(arm * 0.72f, -arm * 0.28f), color, lineWidth);
ReaperTreeDraw.DrawDiamondOutline(batch, pixel, center + new Vector2(0f, arm * 0.55f), arm * 0.30f, color * 0.82f, lineWidth);
}
else if (name.StartsWith("Necklace", StringComparison.Ordinal))
{
ReaperTreeDraw.DrawRing(batch, pixel, center + new Vector2(0f, -arm * 0.18f), arm * 0.62f, color, lineWidth, 20);
ReaperTreeDraw.DrawLine(batch, pixel, center + new Vector2(0f, arm * 0.43f), center + new Vector2(0f, arm), color, lineWidth);
ReaperTreeDraw.DrawDiamondOutline(batch, pixel, center + new Vector2(0f, arm), arm * 0.22f, color * 0.88f, lineWidth);
}
else
{
ReaperTreeDraw.DrawLine(batch, pixel, center + new Vector2(-arm, -arm), center + new Vector2(arm, arm), color, lineWidth);
ReaperTreeDraw.DrawLine(batch, pixel, center + new Vector2(arm, -arm), center + new Vector2(-arm, arm), color, lineWidth);
}
// Small orbital marks distinguish siblings without falling back to language-
// dependent abbreviations that read like placeholder text in localized UIs.
int marks = 1 + Math.Abs((int)type) % 3;
for (int index = 0; index < marks; index++)
{
float angle = -MathHelper.PiOver2 + MathHelper.TwoPi * index / marks;
Vector2 mark = center + angle.ToRotationVector2() * radius * 0.70f;
ReaperTreeDraw.DrawDiamond(batch, pixel, mark, Math.Max(1.8f * scale, radius * 0.075f), color * 0.84f);
}
}
}
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 AltarEquipmentTreeCanvas : UIElement
{
private readonly AltarEquipmentTreeViewport viewport;
private readonly IReadOnlyList<AltarEquipmentTreeNode> nodes;
private readonly float contentWidth;
private readonly float contentHeight;
public AltarEquipmentTreeCanvas(AltarEquipmentTreeViewport viewport, IReadOnlyList<AltarEquipmentTreeNode> nodes, float contentWidth, float contentHeight)
{
this.viewport = viewport;
this.nodes = nodes;
this.contentWidth = contentWidth;
this.contentHeight = contentHeight;
}
protected override void DrawSelf(SpriteBatch spriteBatch)
{
Texture2D pixel = TextureAssets.MagicPixel.Value;
float scale = viewport.LayoutScale;
DrawBackdrop(spriteBatch, pixel, scale);
DrawConnections(spriteBatch, pixel, scale);
foreach (AltarEquipmentTreeNode node in nodes)
DrawNode(spriteBatch, pixel, node, scale);
if (viewport.HoveredNode is AltarEquipmentTreeNode hovered)
Main.instance.MouseText(hovered.Title);
}
private void DrawBackdrop(SpriteBatch batch, Texture2D pixel, float scale)
{
Color grid = new Color(84, 71, 70) * 0.08f;
for (float x = 34f; x < contentWidth; x += 88f)
ReaperTreeDraw.DrawLine(batch, pixel, viewport.DesignToScreen(new Vector2(x, 0f)), viewport.DesignToScreen(new Vector2(x, contentHeight)), grid, Math.Max(1f, scale));
for (float y = 30f; y < contentHeight; y += 88f)
ReaperTreeDraw.DrawLine(batch, pixel, viewport.DesignToScreen(new Vector2(0f, y)), viewport.DesignToScreen(new Vector2(contentWidth, y)), grid, Math.Max(1f, scale));
}
private void DrawConnections(SpriteBatch batch, Texture2D pixel, float scale)
{
if (nodes.Count == 0)
return;
AltarEquipmentTreeNode root = nodes[0];
foreach (AltarEquipmentTreeNode node in nodes)
{
if (node.IsRoot || node.UpgradeType is not DeathAltarUpgradeType type)
continue;
IReadOnlyList<DeathAltarUpgradeType> prerequisites = AltarEquipmentProgressionPanel.GetPrerequisites(type);
if (prerequisites.Count == 0)
{
DrawPath(batch, pixel, root, node, node.Completed, scale);
continue;
}
foreach (DeathAltarUpgradeType prerequisite in prerequisites)
{
AltarEquipmentTreeNode? parent = Find(prerequisite);
if (parent is not null)
DrawPath(batch, pixel, parent, node, parent.Completed, scale);
}
}
}
private void DrawPath(SpriteBatch batch, Texture2D pixel, AltarEquipmentTreeNode start, AltarEquipmentTreeNode end, bool lit, float scale)
{
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 color = lit ? new Color(184, 54, 63) : new Color(64, 57, 58);
ReaperTreeDraw.DrawLine(batch, pixel, a, b, new Color(6, 6, 8) * 0.9f, Math.Max(2f, 5f * scale));
ReaperTreeDraw.DrawLine(batch, pixel, a, b, color * 0.78f, Math.Max(1f, (lit ? 2.3f : 1.2f) * scale));
ReaperTreeDraw.DrawDiamond(batch, pixel, Vector2.Lerp(a, b, 0.5f), (lit ? 4f : 3f) * scale, color * 0.92f);
}
private void DrawNode(SpriteBatch batch, Texture2D pixel, AltarEquipmentTreeNode node, float scale)
{
Vector2 center = viewport.DesignToScreen(node.Position);
float radius = node.Radius * scale;
bool selected = ReferenceEquals(viewport.SelectedNode, node);
bool hovered = ReferenceEquals(viewport.HoveredNode, node);
Color color = node.Completed ? new Color(207, 161, 116) : node.CanRequest && node.Affordable ? new Color(207, 57, 68) : node.CanRequest ? new Color(145, 58, 61) : new Color(79, 71, 70);
ReaperTreeDraw.DrawDiamond(batch, pixel, center, radius * 0.88f, new Color(8, 8, 10) * 0.99f);
if (node.IsRoot)
ReaperTreeDraw.DrawRing(batch, pixel, center, radius, new Color(212, 60, 69), Math.Max(1f, 2.8f * scale), 30);
else
ReaperTreeDraw.DrawDiamondOutline(batch, pixel, center, radius, color, Math.Max(1f, 2.2f * scale));
if (selected || hovered)
ReaperTreeDraw.DrawRing(batch, pixel, center, radius + (selected ? 7f : 4f) * scale, selected ? Color.White * 0.76f : color * 0.62f, Math.Max(1f, (selected ? 2f : 1f) * scale), 26);
Color runeColor = node.Lit ? new Color(239, 219, 199) : new Color(121, 111, 108);
if (node.IsRoot)
{
ReaperTreeDraw.DrawDiamondOutline(batch, pixel, center, radius * 0.34f, runeColor, Math.Max(1f, 1.7f * scale));
ReaperTreeDraw.DrawRing(batch, pixel, center, radius * 0.16f, runeColor * 0.84f, Math.Max(1f, 1.2f * scale), 16);
}
else
{
DrawUpgradeRune(batch, pixel, node.UpgradeType!.Value, center, radius, runeColor, scale);
}
if (!node.IsRoot)
{
float labelScale = 0.47f * scale;
string label = AltarDrawHelpers.FitText(node.Title, 166f * scale, labelScale);
Vector2 size = FontAssets.MouseText.Value.MeasureString(label) * labelScale;
Utils.DrawBorderString(batch, label, center + new Vector2(-size.X * 0.5f, radius + 13f * scale), node.Lit ? new Color(221, 203, 188) : new Color(128, 117, 114), labelScale);
}
}
private AltarEquipmentTreeNode? Find(DeathAltarUpgradeType type)
{
foreach (AltarEquipmentTreeNode node in nodes)
if (node.UpgradeType == type)
return node;
return null;
}
private static void DrawUpgradeRune(
SpriteBatch batch,
Texture2D pixel,
DeathAltarUpgradeType type,
Vector2 center,
float radius,
Color color,
float scale)
{
string name = type.ToString();
float lineWidth = Math.Max(1f, 1.65f * scale);
float arm = radius * 0.45f;
if (name.StartsWith("Domain", StringComparison.Ordinal))
{
ReaperTreeDraw.DrawRing(batch, pixel, center, arm * 0.72f, color, lineWidth, 22);
ReaperTreeDraw.DrawLine(batch, pixel, center + new Vector2(-arm, 0f), center + new Vector2(arm, 0f), color * 0.86f, lineWidth);
ReaperTreeDraw.DrawLine(batch, pixel, center + new Vector2(-arm * 0.62f, arm * 0.48f), center + new Vector2(arm * 0.62f, -arm * 0.48f), color, lineWidth);
}
else if (name.StartsWith("Wing", StringComparison.Ordinal))
{
Vector2 left = center + new Vector2(-arm, arm * 0.52f);
Vector2 right = center + new Vector2(arm, arm * 0.52f);
ReaperTreeDraw.DrawLine(batch, pixel, center, left, color, lineWidth);
ReaperTreeDraw.DrawLine(batch, pixel, center, right, color, lineWidth);
ReaperTreeDraw.DrawLine(batch, pixel, center + new Vector2(-arm * 0.18f, 0f), center + new Vector2(-arm * 0.72f, -arm * 0.38f), color * 0.8f, lineWidth);
ReaperTreeDraw.DrawLine(batch, pixel, center + new Vector2(arm * 0.18f, 0f), center + new Vector2(arm * 0.72f, -arm * 0.38f), color * 0.8f, lineWidth);
}
else if (name.StartsWith("Robe", StringComparison.Ordinal))
{
ReaperTreeDraw.DrawLine(batch, pixel, center + new Vector2(0f, -arm), center + new Vector2(0f, arm), color, lineWidth);
ReaperTreeDraw.DrawLine(batch, pixel, center + new Vector2(-arm * 0.72f, -arm * 0.28f), center + new Vector2(arm * 0.72f, -arm * 0.28f), color, lineWidth);
ReaperTreeDraw.DrawDiamondOutline(batch, pixel, center + new Vector2(0f, arm * 0.55f), arm * 0.30f, color * 0.82f, lineWidth);
}
else if (name.StartsWith("Necklace", StringComparison.Ordinal))
{
ReaperTreeDraw.DrawRing(batch, pixel, center + new Vector2(0f, -arm * 0.18f), arm * 0.62f, color, lineWidth, 20);
ReaperTreeDraw.DrawLine(batch, pixel, center + new Vector2(0f, arm * 0.43f), center + new Vector2(0f, arm), color, lineWidth);
ReaperTreeDraw.DrawDiamondOutline(batch, pixel, center + new Vector2(0f, arm), arm * 0.22f, color * 0.88f, lineWidth);
}
else
{
ReaperTreeDraw.DrawLine(batch, pixel, center + new Vector2(-arm, -arm), center + new Vector2(arm, arm), color, lineWidth);
ReaperTreeDraw.DrawLine(batch, pixel, center + new Vector2(arm, -arm), center + new Vector2(-arm, arm), color, lineWidth);
}
// Small orbital marks distinguish siblings without falling back to language-
// dependent abbreviations that read like placeholder text in localized UIs.
int marks = 1 + Math.Abs((int)type) % 3;
for (int index = 0; index < marks; index++)
{
float angle = -MathHelper.PiOver2 + MathHelper.TwoPi * index / marks;
Vector2 mark = center + angle.ToRotationVector2() * radius * 0.70f;
ReaperTreeDraw.DrawDiamond(batch, pixel, mark, Math.Max(1.8f * scale, radius * 0.075f), color * 0.84f);
}
}
}