using Microsoft.Xna.Framework; using System; using Terraria; namespace SoulHarvest.Common; /// /// Shared client-side presentation for every Death-domain summoning portal. /// The continuous tapered tear forms the opening, while restrained broken rims, /// hairline fractures, and inward-flowing embers keep it readable as a portal /// without covering the weapon that is passing through it. /// internal static class DeathDomainPortalRenderer { private const int RimSegments = 22; internal static void Draw(Vector2 center, Vector2 travelDirection, float scale, float aperture, int seed) { if (Main.dedServ || scale <= 0.05f || aperture <= 0.001f) return; aperture = Smooth01(aperture); Vector2 travel = travelDirection.SafeNormalize(Vector2.UnitX); Vector2 axis = travel.RotatedBy(MathHelper.PiOver2); Vector2 normal = axis.RotatedBy(MathHelper.PiOver2); float rotation = axis.ToRotation(); float pulse = 1f + (float)Math.Sin(Main.GlobalTimeWrappedHourly * 5.2f + seed * 0.173f) * 0.035f; float length = (51f + Hash01(seed, 17) * 8f) * aperture * scale * pulse; float width = (15f + Hash01(seed, 31) * 3f) * aperture * scale; const float opacity = 1f; // A single continuous mask produces a clean tapered tear. It replaces // the old chain of thick, sharply joined line segments. DeathDomainPrimitiveTextureSystem.DrawDeathPortalRift(center, rotation, length, width, opacity); DrawBrokenRim(center, axis, normal, length, width, opacity, seed); DrawFractures(center, axis, normal, length, width, opacity, seed); DrawInwardEmbers(center, axis, normal, length, width, opacity, seed); } private static void DrawBrokenRim(Vector2 center, Vector2 axis, Vector2 normal, float length, float width, float opacity, int seed) { Vector2 previous = GetRimPoint(center, axis, normal, length, width, seed, 0f); for (int segment = 0; segment < RimSegments; segment++) { float start = segment / (float)RimSegments; float end = (segment + 1f) / RimSegments; Vector2 current = GetRimPoint(center, axis, normal, length, width, seed, end); bool gap = Hash01(seed + segment * 97, 71) < 0.27f || segment is 5 or 16; if (!gap) { float segmentOpacity = opacity * MathHelper.Lerp(0.52f, 0.88f, Hash01(seed, segment * 43 + 103)); DeathDomainPrimitiveTextureSystem.DrawSmoothWorldLine(previous, current, new Color(126, 0, 25, 0) * (segmentOpacity * 0.62f), 3.2f); DeathDomainPrimitiveTextureSystem.DrawSmoothWorldLine(previous, current, new Color(242, 27, 55, 205) * segmentOpacity, 1.25f); if ((segment + seed & 3) == 0) { DeathDomainPrimitiveTextureSystem.DrawSmoothWorldLine(previous, current, new Color(255, 214, 205, 215) * (segmentOpacity * 0.52f), 0.48f); } } previous = current; } } private static Vector2 GetRimPoint(Vector2 center, Vector2 axis, Vector2 normal, float length, float width, int seed, float progress) { float angle = progress * MathHelper.TwoPi; float roughness = 1f + (float)Math.Sin(angle * 3f + seed * 0.37f) * 0.075f + (float)Math.Sin(angle * 7f - seed * 0.19f) * 0.035f; return center + axis * ((float)Math.Cos(angle) * length * 0.57f * roughness) + normal * ((float)Math.Sin(angle) * width * 0.88f * roughness); } private static void DrawFractures(Vector2 center, Vector2 axis, Vector2 normal, float length, float width, float opacity, int seed) { for (int branch = 0; branch < 4; branch++) { float side = (branch & 1) == 0 ? -1f : 1f; float along = MathHelper.Lerp(-0.34f, 0.34f, Hash01(seed, branch * 59 + 211)); Vector2 root = center + axis * length * along + normal * width * side * MathHelper.Lerp(0.32f, 0.48f, Hash01(seed, branch * 61 + 229)); Vector2 elbow = root + axis * length * (Hash01(seed, branch * 67 + 251) * 0.13f - 0.065f) + normal * width * side * 0.43f; Vector2 tip = elbow + axis * length * (Hash01(seed, branch * 71 + 277) * 0.17f - 0.085f) + normal * width * side * 0.34f; float alpha = opacity * MathHelper.Lerp(0.34f, 0.62f, Hash01(seed, branch * 73 + 293)); DrawHairline(root, elbow, alpha); DrawHairline(elbow, tip, alpha * 0.72f); } } private static void DrawHairline(Vector2 start, Vector2 end, float opacity) { DeathDomainPrimitiveTextureSystem.DrawSmoothWorldLine(start, end, new Color(100, 0, 22, 0) * (opacity * 0.55f), 2.1f); DeathDomainPrimitiveTextureSystem.DrawSmoothWorldLine(start, end, new Color(235, 20, 50, 205) * opacity, 0.72f); } private static void DrawInwardEmbers(Vector2 center, Vector2 axis, Vector2 normal, float length, float width, float opacity, int seed) { float time = Main.GlobalTimeWrappedHourly * 1.45f; for (int ember = 0; ember < 7; ember++) { float flow = Frac(time + Hash01(seed, ember * 89 + 337)); float angle = Hash01(seed, ember * 101 + 359) * MathHelper.TwoPi; Vector2 outer = axis * ((float)Math.Cos(angle) * length * 0.78f) + normal * ((float)Math.Sin(angle) * width * 1.65f); Vector2 position = center + outer * (1f - Smooth01(flow)); Vector2 inward = (center - position).SafeNormalize(-normal); float alpha = opacity * (float)Math.Sin(flow * MathHelper.Pi) * 0.64f; float size = MathHelper.Lerp(1.55f, 0.45f, flow); DeathDomainPrimitiveTextureSystem.DrawSmoothWorldLine(position, position - inward * (3.5f + 4f * (1f - flow)), new Color(249, 37, 62, 0) * alpha, size); DeathDomainPrimitiveTextureSystem.DrawSmoothDisc(Main.spriteBatch, position - Main.screenPosition, size * 0.62f, new Color(255, 221, 205, 220) * (alpha * 0.72f)); } } private static float Hash01(int a, int b) { uint value = unchecked((uint)a * 0x9E3779B9u ^ (uint)b * 0x85EBCA6Bu ^ 0xC2B2AE35u); value ^= value >> 16; value *= 0x7FEB352Du; value ^= value >> 15; value *= 0x846CA68Bu; value ^= value >> 16; return (value & 0x00FFFFFFu) / 16777215f; } private static float Frac(float value) => value - (float)Math.Floor(value); private static float Smooth01(float value) { value = MathHelper.Clamp(value, 0f, 1f); return value * value * (3f - 2f * value); } }