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 static class ReaperTreeDraw { public static void DrawLine(SpriteBatch batch, Texture2D pixel, Vector2 start, Vector2 end, Color color, float widthInPixels) { Vector2 delta = end - start; if (delta.LengthSquared() < 0.01f) return; batch.Draw(pixel, start, null, color, delta.ToRotation(), Vector2.Zero, new Vector2(delta.Length() / pixel.Width, widthInPixels / pixel.Height), SpriteEffects.None, 0f); } public static void DrawRing(SpriteBatch batch, Texture2D pixel, Vector2 center, float radius, Color color, float width, int segments) { if (radius <= 0f) return; Vector2 previous = center + new Vector2(radius, 0f); for (int index = 1; index <= segments; index++) { float angle = MathHelper.TwoPi * index / segments; Vector2 current = center + new Vector2(MathF.Cos(angle), MathF.Sin(angle)) * radius; DrawLine(batch, pixel, previous, current, color, width); previous = current; } } public static void DrawDiamond(SpriteBatch batch, Texture2D pixel, Vector2 center, float sideLengthInPixels, Color color) => batch.Draw(pixel, center, null, color, MathHelper.PiOver4, pixel.Size() * 0.5f, new Vector2(sideLengthInPixels / pixel.Width, sideLengthInPixels / pixel.Height), SpriteEffects.None, 0f); public static void DrawDiamondOutline(SpriteBatch batch, Texture2D pixel, Vector2 center, float radius, Color color, float width) { Vector2 top = center + new Vector2(0f, -radius); Vector2 right = center + new Vector2(radius, 0f); Vector2 bottom = center + new Vector2(0f, radius); Vector2 left = center + new Vector2(-radius, 0f); DrawLine(batch, pixel, top, right, color, width); DrawLine(batch, pixel, right, bottom, color, width); DrawLine(batch, pixel, bottom, left, color, width); DrawLine(batch, pixel, left, top, color, width); } public static void DrawSkillRune( SpriteBatch batch, Texture2D pixel, Vector2 center, float radius, ReaperFormId form, int skillIndex, Color color, float scale) { float runeRadius = Math.Max(5f * scale, radius * 0.62f); float stroke = Math.Max(1f, 1.65f * scale); float rotation = -MathHelper.PiOver4 + ((int)form - 1) * MathHelper.Pi / 6f; Vector2 axis = Rotate(Vector2.UnitX, rotation); Vector2 normal = new(-axis.Y, axis.X); switch (skillIndex) { case 0: // Twin echo blades: the second cut follows the first along a // parallel soul-track, matching the first skill's echo motif. DrawLine(batch, pixel, center - axis * runeRadius * 0.72f - normal * runeRadius * 0.27f, center + axis * runeRadius * 0.72f + normal * runeRadius * 0.12f, color, stroke); DrawLine(batch, pixel, center - axis * runeRadius * 0.52f + normal * runeRadius * 0.30f, center + axis * runeRadius * 0.57f + normal * runeRadius * 0.48f, color * 0.78f, stroke); DrawDiamond(batch, pixel, center + axis * runeRadius * 0.74f + normal * runeRadius * 0.13f, 2.7f * scale, color); break; case 1: { // Interlocked soul links represent the branch's second-stage // pact, restraint, refraction or chained movement mechanic. Vector2 offset = axis * runeRadius * 0.27f; float linkRadius = runeRadius * 0.34f; DrawRing(batch, pixel, center - offset, linkRadius, color, stroke, 12); DrawRing(batch, pixel, center + offset, linkRadius, color * 0.82f, stroke, 12); DrawLine(batch, pixel, center - axis * runeRadius * 0.12f, center + axis * runeRadius * 0.12f, color, stroke); break; } default: // Six-point execution seal: an inner core opens into the final // branch-specific mastery node without relying on text glyphs. DrawRing(batch, pixel, center, runeRadius * 0.36f, color * 0.86f, stroke, 14); DrawDiamond(batch, pixel, center, 3.2f * scale, color); for (int ray = 0; ray < 6; ray++) { float angle = rotation + MathHelper.TwoPi * ray / 6f; Vector2 direction = new(MathF.Cos(angle), MathF.Sin(angle)); DrawLine(batch, pixel, center + direction * runeRadius * 0.48f, center + direction * runeRadius * 0.82f, color, stroke); } break; } } public static void DrawCommonRune( SpriteBatch batch, Texture2D pixel, Vector2 center, float radius, ReaperCommonNode node, Color color, float scale) { float runeRadius = Math.Max(5f * scale, radius * 0.62f); float stroke = Math.Max(1f, 1.55f * scale); switch (node) { case ReaperCommonNode.Damage: DrawLine(batch, pixel, center + new Vector2(-runeRadius * 0.65f, runeRadius * 0.58f), center + new Vector2(runeRadius * 0.58f, -runeRadius * 0.65f), color, stroke); DrawLine(batch, pixel, center + new Vector2(-runeRadius * 0.58f, -runeRadius * 0.65f), center + new Vector2(runeRadius * 0.65f, runeRadius * 0.58f), color * 0.86f, stroke); DrawDiamond(batch, pixel, center, 3f * scale, color); break; case ReaperCommonNode.AttackSpeed: for (int chevron = 0; chevron < 3; chevron++) { float x = -runeRadius * 0.55f + chevron * runeRadius * 0.43f; Vector2 point = center + new Vector2(x + runeRadius * 0.30f, 0f); DrawLine(batch, pixel, center + new Vector2(x - runeRadius * 0.18f, -runeRadius * 0.38f), point, color, stroke); DrawLine(batch, pixel, center + new Vector2(x - runeRadius * 0.18f, runeRadius * 0.38f), point, color, stroke); } break; case ReaperCommonNode.Crit: DrawRing(batch, pixel, center, runeRadius * 0.43f, color, stroke, 14); DrawDiamond(batch, pixel, center, 2.7f * scale, color); DrawLine(batch, pixel, center + new Vector2(-runeRadius * 0.82f, 0f), center + new Vector2(-runeRadius * 0.52f, 0f), color, stroke); DrawLine(batch, pixel, center + new Vector2(runeRadius * 0.52f, 0f), center + new Vector2(runeRadius * 0.82f, 0f), color, stroke); DrawLine(batch, pixel, center + new Vector2(0f, -runeRadius * 0.82f), center + new Vector2(0f, -runeRadius * 0.52f), color, stroke); DrawLine(batch, pixel, center + new Vector2(0f, runeRadius * 0.52f), center + new Vector2(0f, runeRadius * 0.82f), color, stroke); break; case ReaperCommonNode.ArmorPenetration: DrawDiamondOutline(batch, pixel, center, runeRadius * 0.58f, color * 0.74f, stroke); DrawLine(batch, pixel, center + new Vector2(-runeRadius * 0.72f, runeRadius * 0.72f), center + new Vector2(runeRadius * 0.72f, -runeRadius * 0.72f), color, stroke); DrawDiamond(batch, pixel, center + new Vector2(runeRadius * 0.68f, -runeRadius * 0.68f), 2.5f * scale, color); break; case ReaperCommonNode.SpecialCooldown: DrawLine(batch, pixel, center + new Vector2(-runeRadius * 0.58f, -runeRadius * 0.65f), center + new Vector2(runeRadius * 0.58f, -runeRadius * 0.65f), color, stroke); DrawLine(batch, pixel, center + new Vector2(-runeRadius * 0.58f, runeRadius * 0.65f), center + new Vector2(runeRadius * 0.58f, runeRadius * 0.65f), color, stroke); DrawLine(batch, pixel, center + new Vector2(-runeRadius * 0.50f, -runeRadius * 0.55f), center + new Vector2(runeRadius * 0.50f, runeRadius * 0.55f), color, stroke); DrawLine(batch, pixel, center + new Vector2(runeRadius * 0.50f, -runeRadius * 0.55f), center + new Vector2(-runeRadius * 0.50f, runeRadius * 0.55f), color, stroke); DrawDiamond(batch, pixel, center, 2.5f * scale, color); break; case ReaperCommonNode.EnergyGain: { Vector2[] bolt = [ center + new Vector2(runeRadius * 0.18f, -runeRadius * 0.82f), center + new Vector2(-runeRadius * 0.38f, -runeRadius * 0.05f), center + new Vector2(runeRadius * 0.03f, -runeRadius * 0.05f), center + new Vector2(-runeRadius * 0.18f, runeRadius * 0.82f), center + new Vector2(runeRadius * 0.48f, runeRadius * 0.08f), center + new Vector2(runeRadius * 0.08f, runeRadius * 0.08f) ]; for (int index = 1; index < bolt.Length; index++) DrawLine(batch, pixel, bolt[index - 1], bolt[index], color, stroke); break; } case ReaperCommonNode.AssemblySpeed: DrawRing(batch, pixel, center, runeRadius * 0.62f, color * 0.72f, stroke, 12); DrawLine(batch, pixel, center + new Vector2(-runeRadius * 0.48f, runeRadius * 0.28f), center + new Vector2(0f, -runeRadius * 0.42f), color, stroke); DrawLine(batch, pixel, center + new Vector2(runeRadius * 0.48f, runeRadius * 0.28f), center + new Vector2(0f, -runeRadius * 0.42f), color, stroke); DrawDiamond(batch, pixel, center + new Vector2(0f, runeRadius * 0.35f), 2.6f * scale, color); break; case ReaperCommonNode.DeathDamage: DrawLine(batch, pixel, center + new Vector2(-runeRadius * 0.72f, runeRadius * 0.62f), center + new Vector2(runeRadius * 0.72f, -runeRadius * 0.62f), color, stroke * 1.35f); DrawDiamond(batch, pixel, center, 4f * scale, color); break; case ReaperCommonNode.DeathInvocationSlashes: for (int slash = -1; slash <= 1; slash++) { float offset = slash * runeRadius * 0.34f; DrawLine(batch, pixel, center + new Vector2(-runeRadius * 0.62f, offset + runeRadius * 0.32f), center + new Vector2(runeRadius * 0.62f, offset - runeRadius * 0.32f), color * (0.82f + (slash == 0 ? 0.18f : 0f)), stroke); } break; case ReaperCommonNode.DeathTempoCap: for (int ray = 0; ray < 5; ray++) { float angle = -0.8f + ray * 0.4f; Vector2 direction = new(MathF.Cos(angle), MathF.Sin(angle)); DrawLine(batch, pixel, center - direction * runeRadius * 0.15f, center + direction * runeRadius * 0.82f, color, stroke); } break; case ReaperCommonNode.DeathRange: DrawRing(batch, pixel, center, runeRadius * 0.38f, color, stroke, 16); DrawRing(batch, pixel, center, runeRadius * 0.76f, color * 0.78f, stroke, 20); break; case ReaperCommonNode.DeathSpaceBreak: DrawLine(batch, pixel, center + new Vector2(-runeRadius * 0.72f, runeRadius * 0.70f), center + new Vector2(-runeRadius * 0.12f, -runeRadius * 0.08f), color, stroke); DrawLine(batch, pixel, center + new Vector2(-runeRadius * 0.12f, -runeRadius * 0.08f), center + new Vector2(runeRadius * 0.18f, runeRadius * 0.18f), color, stroke); DrawLine(batch, pixel, center + new Vector2(runeRadius * 0.18f, runeRadius * 0.18f), center + new Vector2(runeRadius * 0.72f, -runeRadius * 0.70f), color, stroke); break; case ReaperCommonNode.DeathRiftWidth: DrawLine(batch, pixel, center + new Vector2(-runeRadius * 0.72f, -runeRadius * 0.42f), center + new Vector2(runeRadius * 0.72f, -runeRadius * 0.42f), color, stroke); DrawLine(batch, pixel, center + new Vector2(-runeRadius * 0.72f, runeRadius * 0.42f), center + new Vector2(runeRadius * 0.72f, runeRadius * 0.42f), color, stroke); DrawDiamond(batch, pixel, center, 3f * scale, color); break; } } private static Vector2 Rotate(Vector2 value, float radians) { float cosine = MathF.Cos(radians); float sine = MathF.Sin(radians); return new Vector2(value.X * cosine - value.Y * sine, value.X * sine + value.Y * cosine); } public static void DrawBorder(SpriteBatch batch, Texture2D pixel, Rectangle bounds, Color color, int thickness) { thickness = Math.Clamp(thickness, 1, Math.Max(1, Math.Min(bounds.Width, bounds.Height) / 2)); batch.Draw(pixel, new Rectangle(bounds.X, bounds.Y, bounds.Width, thickness), color); batch.Draw(pixel, new Rectangle(bounds.X, bounds.Bottom - thickness, bounds.Width, thickness), color); batch.Draw(pixel, new Rectangle(bounds.X, bounds.Y, thickness, bounds.Height), color); batch.Draw(pixel, new Rectangle(bounds.Right - thickness, bounds.Y, thickness, bounds.Height), color); } public static void DrawInsetBorder(SpriteBatch batch, Texture2D pixel, Rectangle bounds, int inset, Color color, int thickness) { if (bounds.Width > inset * 2 && bounds.Height > inset * 2) DrawBorder(batch, pixel, new Rectangle(bounds.X + inset, bounds.Y + inset, bounds.Width - inset * 2, bounds.Height - inset * 2), color, thickness); } public static float DrawWrappedText(SpriteBatch batch, string text, Vector2 position, float maximumWidth, float scale, Color color, int maximumLines) { List lines = WrapText(text, maximumWidth, scale, maximumLines); float lineHeight = 18f * scale; for (int index = 0; index < lines.Count; index++) Utils.DrawBorderString(batch, lines[index], position + new Vector2(0f, lineHeight * index), color, scale); return position.Y + lineHeight * lines.Count; } private static List WrapText(string text, float maximumWidth, float scale, int maximumLines) { List lines = []; if (string.IsNullOrEmpty(text) || maximumLines <= 0) return lines; StringBuilder line = new(); foreach (char character in text) { if (character == '\r') continue; if (character == '\n') { lines.Add(line.ToString().TrimEnd()); line.Clear(); if (lines.Count >= maximumLines) break; continue; } line.Append(character); if (FontAssets.MouseText.Value.MeasureString(line.ToString()).X * scale <= maximumWidth) continue; line.Length--; if (line.Length > 0) lines.Add(line.ToString().TrimEnd()); line.Clear(); if (character != ' ') line.Append(character); if (lines.Count >= maximumLines) break; } if (lines.Count < maximumLines && line.Length > 0) lines.Add(line.ToString().TrimEnd()); if (lines.Count == maximumLines && lines[^1].Length > 0 && FontAssets.MouseText.Value.MeasureString(lines[^1] + "…").X * scale <= maximumWidth) lines[^1] += "…"; return lines; } public static string Roman(int value) => value switch { 1 => "I", 2 => "II", 3 => "III", _ => value.ToString() }; }