using SoulHarvest.Projectiles;
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.ModLoader;
namespace SoulHarvest.Common;
/// <summary>
/// Client-only storyboards for the eighteen branch skills. The authoritative
/// cue is synchronized by <see cref="ReaperSkillCueProjectile"/>; everything kept
/// here is disposable presentation state and can never deal damage or move an NPC.
/// </summary>
[Autoload(Side = ModSide.Client)]
internal sealed class ReaperSkillVisualSystem : ModSystem
{
private const int MaximumVisuals = 40;
private static readonly List<ReaperSkillVisual> Visuals = [];
public static void ReportSkill(
int owner,
ReaperFormId form,
ReaperStage stage,
int skill,
int level,
int variant,
Vector2 center,
Vector2 direction,
int actionId,
int seed)
{
if (Main.dedServ || Main.gameMenu
|| owner < 0 || owner >= Main.maxPlayers
|| !ReaperDefinitions.IsBranchForm(form)
|| skill is < 0 or > 2 || level is < 1 or > 3)
{
return;
}
direction = direction.SafeNormalize(Vector2.UnitY * -1f);
if (form == ReaperFormId.Blood && skill is 1 or 2 && variant == 1)
{
for (int index = Visuals.Count - 1; index >= 0; index--)
{
ReaperSkillVisual shield = Visuals[index];
if (shield.Owner == owner && shield.Form == form && shield.Skill == skill && shield.Variant == 0)
Visuals.RemoveAt(index);
}
}
for (int index = Visuals.Count - 1; index >= 0; index--)
{
ReaperSkillVisual existing = Visuals[index];
bool persistent = IsPersistentOwnerVisual(form, skill, variant);
if (existing.Owner != owner || existing.Form != form || existing.Skill != skill
|| existing.Variant != variant)
{
continue;
}
// A wide attack hitting several targets in the same update becomes one
// centered execution instead of several opaque copies of the same VFX.
bool sameAction = existing.ActionId == actionId;
bool nearbyBurst = (existing.Age <= 7 || persistent)
&& Vector2.DistanceSquared(existing.Center, center) <= 240f * 240f;
if (sameAction || nearbyBurst)
{
int samples = Math.Clamp(existing.CenterSamples, 1, 64);
float sampleWeight = 1f / (samples + 1f);
existing.Center = Vector2.Lerp(existing.Center, center, sampleWeight);
existing.Direction = Vector2.Lerp(existing.Direction, direction, sampleWeight)
.SafeNormalize(existing.Direction);
existing.CenterSamples = Math.Min(64, samples + 1);
existing.Level = Math.Max(existing.Level, level);
existing.Duration = Math.Max(existing.Duration, GetDuration(form, skill, existing.Level, variant));
existing.Age = persistent
? 0
: Math.Min(existing.Age, 3);
return;
}
}
if (Visuals.Count >= MaximumVisuals)
Visuals.RemoveAt(0);
Visuals.Add(new ReaperSkillVisual
{
Owner = owner,
Form = form,
Stage = stage,
Skill = skill,
Level = level,
Variant = variant,
ActionId = actionId,
Seed = seed,
Center = center,
Direction = direction,
CenterSamples = 1,
Duration = GetDuration(form, skill, level, variant)
});
if (IsNearScreen(center, 620f))
PlaySkillSound(form, skill, level, variant, center);
// Unique skills keep their restrained camera punctuation, but never tint
// the entire viewport. A three-frame pale impact used to present as a
// one-frame white screen when several automatic triggers were merged.
if (level >= 3 && IsNearScreen(center))
ReaperVfxDirector.TriggerGlobalImpact(direction, 2.8f, 6,
Color.Transparent, 0f, 0, 0.06f);
}
public override void OnWorldUnload() => Visuals.Clear();
public override void Unload() => Visuals.Clear();
public override void PostUpdateEverything()
{
if (Main.gameMenu)
{
Visuals.Clear();
return;
}
for (int index = Visuals.Count - 1; index >= 0; index--)
{
if (++Visuals[index].Age >= Visuals[index].Duration)
Visuals.RemoveAt(index);
}
}
public override void PostDrawTiles()
{
if (Main.dedServ || Main.gameMenu || Visuals.Count == 0)
return;
SpriteBatch batch = Main.spriteBatch;
batch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp,
DepthStencilState.None, RasterizerState.CullNone, null,
Main.GameViewMatrix.TransformationMatrix);
Texture2D pixel = TextureAssets.MagicPixel.Value;
foreach (ReaperSkillVisual visual in Visuals)
{
Vector2 center = ResolveCenter(visual);
if (!IsNearScreen(center, 440f))
continue;
DrawVisual(batch, pixel, visual, center);
}
batch.End();
}
private static int GetDuration(ReaperFormId form, int skill, int level, int variant)
{
if (form == ReaperFormId.Blood && skill == 2 && variant == 0)
return 300;
if (form == ReaperFormId.Blood && skill == 1 && variant == 0)
return 120;
if (form == ReaperFormId.Infernal && skill == 0 && variant == 0)
return 130;
return variant > 0 ? 28 + level * 3 : level switch { 1 => 30, 2 => 37, _ => 46 };
}
private static bool IsPersistentOwnerVisual(ReaperFormId form, int skill, int variant)
=> variant == 0 && (form == ReaperFormId.Blood && skill is 1 or 2
|| form == ReaperFormId.Infernal && skill == 0);
private static Vector2 ResolveCenter(ReaperSkillVisual visual)
{
bool followsOwner = visual.Form switch
{
ReaperFormId.Blood => visual.Skill is 1 or 2 && visual.Variant == 0,
ReaperFormId.Infernal => visual.Skill is 0 or 2,
ReaperFormId.Soul => visual.Skill == 1,
_ => false
};
if (!followsOwner || visual.Owner < 0 || visual.Owner >= Main.maxPlayers)
return visual.Center;
Player owner = Main.player[visual.Owner];
return owner.active ? owner.MountedCenter : visual.Center;
}
private static bool IsNearScreen(Vector2 center, float margin = 180f)
{
Rectangle area = new(
(int)(Main.screenPosition.X - margin),
(int)(Main.screenPosition.Y - margin),
Main.screenWidth + (int)(margin * 2f),
Main.screenHeight + (int)(margin * 2f));
return area.Contains(center.ToPoint());
}
private static void PlaySkillSound(
ReaperFormId form,
int skill,
int level,
int variant,
Vector2 center)
{
SoundStyle primary = form switch
{
ReaperFormId.Bone => skill == 2 ? SoundID.NPCDeath6 : SoundID.Item71,
ReaperFormId.Blood => skill == 0 ? SoundID.Item29 : SoundID.NPCDeath6,
ReaperFormId.Infernal => skill == 0 ? SoundID.Item74 : SoundID.Item14,
ReaperFormId.Frost => SoundID.Shatter,
ReaperFormId.Soul => skill == 1 ? SoundID.NPCDeath6 : SoundID.Item8,
ReaperFormId.Void => skill == 1 ? SoundID.Item122 : SoundID.Item8,
_ => SoundID.Item1
};
SoundEngine.PlaySound(primary with
{
Volume = MathHelper.Clamp(0.42f + level * 0.09f, 0f, 0.74f),
Pitch = MathHelper.Clamp(-0.22f + level * 0.08f + variant * 0.035f, -1f, 1f),
PitchVariance = 0.045f,
MaxInstances = 2,
SoundLimitBehavior = SoundLimitBehavior.IgnoreNew
}, center);
// Lv.3 executions get one restrained low accent. The shared instance cap
// prevents simultaneous multi-target triggers from producing audio peaks.
if (level >= 3 && variant == 0)
{
SoundEngine.PlaySound(SoundID.Item71 with
{
Volume = 0.36f,
Pitch = -0.52f,
PitchVariance = 0.03f,
MaxInstances = 2,
SoundLimitBehavior = SoundLimitBehavior.IgnoreNew
}, center);
}
}
private static void DrawVisual(SpriteBatch batch, Texture2D pixel, ReaperSkillVisual visual, Vector2 center)
{
float progress = visual.Age / (float)Math.Max(1, visual.Duration);
float reveal = MathHelper.SmoothStep(0f, 1f, MathHelper.Clamp(progress / 0.22f, 0f, 1f));
float fade = 1f - MathHelper.SmoothStep(0f, 1f, MathHelper.Clamp((progress - 0.66f) / 0.34f, 0f, 1f));
float opacity = reveal * fade;
float pulse = 1f + (float)Math.Sin(progress * MathHelper.TwoPi * 2f + visual.Seed * 0.013f) * 0.055f;
Color primary = ReaperCombatRegistry.GetPrimaryColor(visual.Form) with { A = 0 };
Color secondary = ReaperCombatRegistry.GetSecondaryColor(visual.Form) with { A = 0 };
switch (visual.Form)
{
case ReaperFormId.Bone:
if (visual.Skill == 0) DrawBoneShatter(batch, pixel, visual, center, progress, opacity, pulse, primary, secondary);
else if (visual.Skill == 1) DrawBoneRestraint(batch, pixel, visual, center, progress, opacity, primary, secondary);
else DrawBoneChorus(batch, pixel, visual, center, progress, opacity, primary, secondary);
break;
case ReaperFormId.Blood:
if (visual.Skill == 0) DrawBloodDrink(batch, pixel, visual, center, progress, opacity, primary, secondary);
else if (visual.Skill == 1) DrawBloodFrenzy(batch, pixel, visual, center, progress, opacity, primary, secondary);
else DrawBloodShield(batch, pixel, visual, center, progress, opacity, primary, secondary);
break;
case ReaperFormId.Infernal:
if (visual.Skill == 0) DrawInfernalMomentum(batch, pixel, visual, center, progress, opacity, primary, secondary);
else if (visual.Skill == 1) DrawInfernalScorch(batch, pixel, visual, center, progress, opacity, primary, secondary);
else DrawInfernalCircuit(batch, pixel, visual, center, progress, opacity, primary, secondary);
break;
case ReaperFormId.Frost:
if (visual.Skill == 0) DrawFrostMark(batch, pixel, visual, center, progress, opacity, primary, secondary);
else if (visual.Skill == 1) DrawFrostRefraction(batch, pixel, visual, center, progress, opacity, primary, secondary);
else DrawFrostExecution(batch, pixel, visual, center, progress, opacity, primary, secondary);
break;
case ReaperFormId.Soul:
if (visual.Skill == 0) DrawSoulEcho(batch, pixel, visual, center, progress, opacity, primary, secondary);
else if (visual.Skill == 1) DrawSoulFamiliar(batch, pixel, visual, center, progress, opacity, primary, secondary);
else DrawSoulFate(batch, pixel, visual, center, progress, opacity, primary, secondary);
break;
case ReaperFormId.Void:
if (visual.Skill == 0) DrawVoidIntersection(batch, pixel, visual, center, progress, opacity, primary, secondary);
else if (visual.Skill == 1) DrawVoidResidual(batch, pixel, visual, center, progress, opacity, primary, secondary);
else DrawVoidCoordinate(batch, pixel, visual, center, progress, opacity, primary, secondary);
break;
}
}
private static void DrawBoneShatter(SpriteBatch batch, Texture2D pixel, ReaperSkillVisual v, Vector2 center,
float p, float opacity, float pulse, Color primary, Color secondary)
{
int spikes = v.Level switch { 1 => 3, 2 => 6, _ => 10 };
float radius = (40f + v.Level * 15f) * pulse;
float collapse = 1f - MathHelper.SmoothStep(0f, 1f, MathHelper.Clamp((p - 0.08f) / 0.48f, 0f, 1f));
for (int i = 0; i < spikes; i++)
{
Vector2 direction = (i * MathHelper.TwoPi / spikes - MathHelper.PiOver2).ToRotationVector2();
Vector2 tangent = direction.RotatedBy(MathHelper.PiOver2);
Vector2 tip = center + direction * (10f + radius * collapse);
Vector2 root = center + direction * (radius + 22f) + tangent * ((i & 1) == 0 ? 5f : -5f);
DrawSegment(batch, pixel, root, tip, secondary * opacity, 3.4f + v.Level);
DrawSegment(batch, pixel, root + tangent * 3f, tip, primary * (opacity * 0.72f), 1.5f);
}
DrawRing(batch, pixel, center, radius * (0.56f + 0.2f * collapse), spikes * 2,
primary * (opacity * 0.58f), 2.5f, p * 0.8f);
if (v.Level >= 2)
{
for (int i = 0; i < 6; i++)
{
float x = (i - 2.5f) * 14f;
float bow = 18f - Math.Abs(i - 2.5f) * 3f;
DrawSegment(batch, pixel, center + new Vector2(x, 38f), center + new Vector2(x * 0.7f, -bow),
secondary * (opacity * 0.48f), 2.8f);
}
}
if (v.Level >= 3)
{
Vector2 skull = center + new Vector2(0f, -44f);
DrawArc(batch, pixel, skull, 34f, MathHelper.Pi, MathHelper.Pi, 16, secondary * (opacity * 0.7f), 4f);
DrawDiamond(batch, pixel, skull + new Vector2(-12f, 2f), 5f, primary * opacity, 2f, 0f);
DrawDiamond(batch, pixel, skull + new Vector2(12f, 2f), 5f, primary * opacity, 2f, 0f);
if (p > 0.42f)
DrawCross(batch, pixel, center, 72f, secondary * (opacity * 0.88f), 4.5f, v.Direction.ToRotation());
}
}
private static void DrawBoneRestraint(SpriteBatch batch, Texture2D pixel, ReaperSkillVisual v, Vector2 center,
float p, float opacity, Color primary, Color secondary)
{
float radius = 43f + v.Level * 13f;
DrawChain(batch, pixel, center + new Vector2(-radius, -radius * 0.45f), center + new Vector2(radius, radius * 0.45f),
primary * opacity, secondary * opacity, 8 + v.Level * 2);
DrawChain(batch, pixel, center + new Vector2(radius, -radius * 0.45f), center + new Vector2(-radius, radius * 0.45f),
primary * opacity, secondary * opacity, 8 + v.Level * 2);
if (v.Level >= 2)
{
for (int i = 0; i < 6; i++)
{
float angle = i * MathHelper.TwoPi / 6f;
Vector2 direction = angle.ToRotationVector2();
Vector2 basePoint = center + direction * radius;
DrawSegment(batch, pixel, basePoint - Vector2.UnitY * 42f, basePoint + Vector2.UnitY * 42f,
secondary * (opacity * 0.72f), 4f);
DrawSegment(batch, pixel, basePoint - Vector2.UnitY * 42f,
basePoint - Vector2.UnitY * 51f + direction * 8f, primary * opacity, 2.2f);
}
}
if (v.Level >= 3)
{
Vector2 palm = center + new Vector2(0f, 58f);
for (int finger = -2; finger <= 2; finger++)
DrawSegment(batch, pixel, palm + new Vector2(finger * 8f, 0f), center + new Vector2(finger * 14f, -42f),
secondary * (opacity * 0.58f), 6f - Math.Abs(finger) * 0.6f);
float fall = MathHelper.SmoothStep(0f, 1f, MathHelper.Clamp((p - 0.42f) / 0.25f, 0f, 1f));
float bladeY = MathHelper.Lerp(-120f, 20f, fall);
DrawSegment(batch, pixel, center + new Vector2(-82f, bladeY), center + new Vector2(82f, bladeY),
secondary * opacity, 8f);
DrawSegment(batch, pixel, center + new Vector2(-80f, bladeY - 4f), center + new Vector2(80f, bladeY - 4f),
primary * opacity, 2f);
}
}
private static void DrawBoneChorus(SpriteBatch batch, Texture2D pixel, ReaperSkillVisual v, Vector2 center,
float p, float opacity, Color primary, Color secondary)
{
int ghosts = v.Level switch { 1 => 2, 2 => 4, _ => 6 };
float radius = 54f + v.Level * 9f;
for (int i = 0; i < ghosts; i++)
{
float angle = i * MathHelper.TwoPi / ghosts + 0.35f;
Vector2 position = center + angle.ToRotationVector2() * radius;
DrawGhost(batch, pixel, position, center - position, primary * (opacity * 0.62f), secondary * (opacity * 0.82f), 0.8f);
Vector2 inward = (center - position).SafeNormalize(Vector2.UnitX);
float sweep = MathHelper.Lerp(-0.7f, 0.65f, MathHelper.SmoothStep(0f, 1f, MathHelper.Clamp(p * 1.7f, 0f, 1f)));
DrawSegment(batch, pixel, position - inward.RotatedBy(sweep) * 10f,
position + inward.RotatedBy(sweep) * 52f, secondary * opacity, 3f);
}
DrawRing(batch, pixel, center, radius * 0.72f, 30, primary * (opacity * 0.42f), 3f, -p * 2f);
if (v.Level >= 3 && p > 0.43f)
{
Vector2 reaper = center - Vector2.UnitY * 82f;
DrawGhost(batch, pixel, reaper, Vector2.UnitY, primary * (opacity * 0.54f), secondary * opacity, 1.8f);
DrawSegment(batch, pixel, reaper + new Vector2(-78f, 27f), center + new Vector2(78f, 25f),
secondary * opacity, 7f);
}
}
private static void DrawBloodDrink(SpriteBatch batch, Texture2D pixel, ReaperSkillVisual v, Vector2 center,
float p, float opacity, Color primary, Color secondary)
{
Vector2 owner = v.Owner >= 0 && v.Owner < Main.maxPlayers && Main.player[v.Owner].active
? Main.player[v.Owner].MountedCenter
: center + v.Direction * 110f;
Vector2 delta = owner - center;
Vector2 tangent = delta.SafeNormalize(Vector2.UnitX).RotatedBy(MathHelper.PiOver2);
int strands = v.Level == 1 ? 1 : 3;
for (int strand = 0; strand < strands; strand++)
{
Vector2 previous = center;
for (int point = 1; point <= 14; point++)
{
float t = point / 14f;
float wave = (float)Math.Sin(t * MathHelper.TwoPi * (1f + v.Level * 0.3f) + strand * 2.1f + p * 5f)
* (strand == 0 ? 5f : 10f) * (float)Math.Sin(t * MathHelper.Pi);
Vector2 next = center + delta * t + tangent * wave;
DrawSegment(batch, pixel, previous, next, (strand == 0 ? secondary : primary) * opacity,
strand == 0 ? 3.4f : 1.8f);
previous = next;
}
}
if (v.Level >= 2)
{
Vector2 rose = Vector2.Lerp(center, owner, 0.46f);
for (int petal = 0; petal < (v.Level >= 3 ? 8 : 5); petal++)
DrawArc(batch, pixel, rose, 17f + petal % 2 * 4f, petal * MathHelper.TwoPi / (v.Level >= 3 ? 8f : 5f),
1.7f, 7, primary * (opacity * 0.65f), 2.5f);
}
if (v.Level >= 3)
{
for (int petal = 0; petal < 6; petal++)
{
float angle = petal * MathHelper.TwoPi / 6f;
DrawDiamond(batch, pixel, center + angle.ToRotationVector2() * 29f, 18f,
secondary * (opacity * 0.72f), 3f, angle);
}
DrawArc(batch, pixel, center, 60f, -2.4f, 4.7f, 28, secondary * opacity, 7f);
}
}
private static void DrawBloodFrenzy(SpriteBatch batch, Texture2D pixel, ReaperSkillVisual v, Vector2 center,
float p, float opacity, Color primary, Color secondary)
{
if (v.Variant == 1)
{
float burst = MathHelper.SmoothStep(0f, 1f, MathHelper.Clamp(p * 1.7f, 0f, 1f));
DrawRing(batch, pixel, center, MathHelper.Lerp(28f, 112f, burst), 36,
secondary * opacity, 7f, -p);
for (int bead = 0; bead < 14; bead++)
{
Vector2 direction = (bead * MathHelper.TwoPi / 14f + 0.18f).ToRotationVector2();
Vector2 start = center + direction * MathHelper.Lerp(22f, 62f, burst);
DrawDiamond(batch, pixel, start + direction * burst * 58f, 7f,
(bead % 2 == 0 ? secondary : primary) * opacity, 2.4f, direction.ToRotation());
}
return;
}
if (v.Variant >= 2)
{
float facing = v.Direction.ToRotation();
int crescents = v.Level == 2 ? 2 : 1;
float radius = v.Level >= 3 ? 132f : v.Level == 2 ? 110f : 92f;
float reveal = MathHelper.SmoothStep(0f, 1f, MathHelper.Clamp(p * 2.2f, 0f, 1f));
for (int slash = 0; slash < crescents; slash++)
{
float side = crescents == 1 ? 0f : slash == 0 ? -0.22f : 0.22f;
Vector2 slashCenter = center + v.Direction * (radius * 0.18f)
+ v.Direction.RotatedBy(MathHelper.PiOver2) * side * 34f;
float start = facing - 1.9f + side;
DrawArc(batch, pixel, slashCenter, radius, start, 3.8f, 42,
new Color(65, 0, 13, 0) * (opacity * 0.82f), v.Level >= 3 ? 31f : 25f);
DrawArc(batch, pixel, slashCenter, radius, start, 3.8f, 42,
primary * (opacity * (0.62f + reveal * 0.2f)), v.Level >= 3 ? 20f : 15f);
DrawArc(batch, pixel, slashCenter, radius + 2f, start, 3.8f, 42,
secondary * opacity, v.Level >= 3 ? 4.5f : 3.5f);
}
return;
}
float beat = 1f + Math.Max(0f, (float)Math.Sin(p * MathHelper.TwoPi * 3f)) * 0.22f;
DrawRing(batch, pixel, center, 42f * beat, 28, primary * opacity, 5f, 0f);
DrawSegment(batch, pixel, center + new Vector2(-33f, 0f), center + new Vector2(-12f, 0f), secondary * opacity, 2.5f);
DrawSegment(batch, pixel, center + new Vector2(-12f, 0f), center + new Vector2(-5f, -11f), secondary * opacity, 2.5f);
DrawSegment(batch, pixel, center + new Vector2(-5f, -11f), center + new Vector2(4f, 13f), secondary * opacity, 2.5f);
DrawSegment(batch, pixel, center + new Vector2(4f, 13f), center + new Vector2(13f, 0f), secondary * opacity, 2.5f);
DrawSegment(batch, pixel, center + new Vector2(13f, 0f), center + new Vector2(34f, 0f), secondary * opacity, 2.5f);
if (v.Level >= 2)
{
for (int side = -1; side <= 1; side += 2)
{
Vector2 root = center + new Vector2(side * 10f, -3f);
Vector2 tip = center + new Vector2(side * 82f, -45f);
DrawArc(batch, pixel, root, 78f, side > 0 ? -1.05f : MathHelper.Pi + 0.05f,
side * 1.15f, 14, primary * (opacity * 0.62f), 7f);
DrawSegment(batch, pixel, root, tip, secondary * (opacity * 0.72f), 3f);
}
}
if (v.Level >= 3)
{
Vector2 moon = center + new Vector2(0f, -68f);
for (int layer = 0; layer < 5; layer++)
DrawRing(batch, pixel, moon, 26f - layer * 4f, 28, primary * (opacity * (0.13f + layer * 0.035f)), 5f, 0f);
DrawArc(batch, pixel, moon, 31f, -1.85f, 3.7f, 26, secondary * opacity, 4.5f);
}
}
private static void DrawBloodShield(SpriteBatch batch, Texture2D pixel, ReaperSkillVisual v, Vector2 center,
float p, float opacity, Color primary, Color secondary)
{
if (v.Variant == 1)
{
float radius = MathHelper.Lerp(24f, 88f, MathHelper.SmoothStep(0f, 1f, MathHelper.Clamp(p * 1.7f, 0f, 1f)));
DrawRing(batch, pixel, center, radius, 34, secondary * opacity, 8f, p);
for (int i = 0; i < 12; i++)
{
Vector2 direction = (i * MathHelper.TwoPi / 12f).ToRotationVector2();
DrawSegment(batch, pixel, center + direction * radius * 0.7f, center + direction * (radius + 25f),
primary * opacity, 4f);
}
return;
}
if (v.Variant >= 2)
{
float angle = v.Direction.ToRotation();
Vector2 strikeCenter = center + v.Direction * 30f;
DrawArc(batch, pixel, strikeCenter, v.Level >= 3 ? 101f : 86f,
angle - 1.7f, 3.4f, 34, primary * (opacity * 0.78f), v.Level >= 3 ? 20f : 16f);
DrawArc(batch, pixel, strikeCenter, v.Level >= 3 ? 104f : 89f,
angle - 1.7f, 3.4f, 34, secondary * opacity, 4f);
return;
}
int shards = v.Level switch { 1 => 4, 2 => 8, _ => 12 };
float radiusOrbit = 45f + v.Level * 5f;
for (int i = 0; i < shards; i++)
{
float angle = i * MathHelper.TwoPi / shards + p * 2.4f;
Vector2 position = center + angle.ToRotationVector2() * radiusOrbit;
DrawDiamond(batch, pixel, position, v.Level >= 2 ? 10f : 8f,
(i % 2 == 0 ? secondary : primary) * opacity, 3f, angle);
}
if (v.Level >= 2)
DrawRing(batch, pixel, center, radiusOrbit - 9f, 32, primary * (opacity * 0.34f), 5f, -p);
if (v.Level >= 3)
{
DrawArc(batch, pixel, center, 65f, -2.25f, 4.5f, 28, primary * (opacity * 0.54f), 9f);
Vector2 crown = center - Vector2.UnitY * 69f;
DrawSegment(batch, pixel, crown + new Vector2(-23f, 10f), crown + new Vector2(-15f, -8f), secondary * opacity, 3f);
DrawSegment(batch, pixel, crown + new Vector2(-15f, -8f), crown, secondary * opacity, 3f);
DrawSegment(batch, pixel, crown, crown + new Vector2(15f, -8f), secondary * opacity, 3f);
DrawSegment(batch, pixel, crown + new Vector2(15f, -8f), crown + new Vector2(23f, 10f), secondary * opacity, 3f);
}
}
private static void DrawInfernalMomentum(SpriteBatch batch, Texture2D pixel, ReaperSkillVisual v, Vector2 center,
float p, float opacity, Color primary, Color secondary)
{
Vector2 direction = v.Direction.SafeNormalize(Vector2.UnitX);
Vector2 tangent = direction.RotatedBy(MathHelper.PiOver2);
for (int foot = 0; foot < 4 + v.Level; foot++)
{
Vector2 position = center - direction * (18f + foot * 17f) + tangent * ((foot & 1) == 0 ? 7f : -7f);
DrawDiamond(batch, pixel, position, 7f + v.Level, primary * (opacity * (1f - foot * 0.1f)), 3f,
direction.ToRotation());
}
if (v.Level >= 2)
{
Vector2 ghost = center - direction * 66f;
DrawGhost(batch, pixel, ghost, direction, new Color(35, 20, 12, 0) * opacity,
secondary * (opacity * 0.66f), 1.25f);
DrawSegment(batch, pixel, ghost - tangent * 42f, ghost + direction * 90f + tangent * 38f,
secondary * opacity, 5f);
}
if (v.Level >= 3)
DrawStar(batch, pixel, center + direction * 25f, 67f, 27f, 5, primary * (opacity * 0.76f), 4f, p * 1.8f);
}
private static void DrawInfernalScorch(SpriteBatch batch, Texture2D pixel, ReaperSkillVisual v, Vector2 center,
float p, float opacity, Color primary, Color secondary)
{
if (v.Variant > 0)
{
float burst = MathHelper.SmoothStep(0f, 1f, MathHelper.Clamp(p * 1.8f, 0f, 1f));
DrawRing(batch, pixel, center, MathHelper.Lerp(34f, 102f, burst), 36,
secondary * opacity, 11f, p);
DrawStar(batch, pixel, center, 86f, 37f, 5, primary * (opacity * 0.86f), 7f, -p * 1.4f);
for (int rib = -1; rib <= 1; rib += 2)
{
DrawArc(batch, pixel, center + new Vector2(rib * 20f, 0f), 68f,
rib < 0 ? -1.35f : MathHelper.Pi - 1.35f, rib * 2.7f, 22,
secondary * opacity, 6f);
}
return;
}
float size = 45f + v.Level * 8f;
Vector2 head = center - Vector2.UnitY * size * 0.58f;
DrawRing(batch, pixel, head, 12f, 18, secondary * opacity, 3f, 0f);
DrawSegment(batch, pixel, head + Vector2.UnitY * 12f, center + Vector2.UnitY * 25f, secondary * opacity, 4f);
DrawSegment(batch, pixel, center - Vector2.UnitY * 9f, center + new Vector2(-size * 0.48f, 8f), primary * opacity, 3f);
DrawSegment(batch, pixel, center - Vector2.UnitY * 9f, center + new Vector2(size * 0.48f, 8f), primary * opacity, 3f);
DrawSegment(batch, pixel, center + Vector2.UnitY * 24f, center + new Vector2(-size * 0.33f, size * 0.68f), secondary * opacity, 3f);
DrawSegment(batch, pixel, center + Vector2.UnitY * 24f, center + new Vector2(size * 0.33f, size * 0.68f), secondary * opacity, 3f);
int ribs = 2 + v.Level;
for (int i = 0; i < ribs; i++)
{
float y = -2f + i * 9f;
DrawArc(batch, pixel, center + new Vector2(0f, y), 19f + i * 3f, MathHelper.Pi, MathHelper.Pi,
9, primary * (opacity * 0.7f), 3f);
}
if (v.Level >= 2)
{
for (int i = 0; i < 3; i++)
{
Vector2 hot = center + (i * MathHelper.TwoPi / 3f - MathHelper.PiOver2).ToRotationVector2() * (24f + i * 6f);
DrawRing(batch, pixel, hot, 7f + p * 9f, 14, secondary * opacity, 4f, 0f);
}
}
if (v.Level >= 3)
DrawRing(batch, pixel, center, MathHelper.Lerp(35f, 88f, p), 32, secondary * (opacity * 0.8f), 8f, 0f);
}
private static void DrawInfernalCircuit(SpriteBatch batch, Texture2D pixel, ReaperSkillVisual v, Vector2 center,
float p, float opacity, Color primary, Color secondary)
{
float radius = 51f + v.Level * 17f;
DrawRing(batch, pixel, center, radius * (0.75f + p * 0.25f), 36, secondary * opacity, 6f, p * 2f);
if (v.Level >= 2)
DrawStar(batch, pixel, center, radius, radius * 0.42f, 5, primary * opacity, 4f, -p * 1.5f);
if (v.Level >= 3)
{
Vector2 portal = center + v.Direction * 54f;
DrawArc(batch, pixel, portal, radius * 0.78f, 0f, MathHelper.TwoPi, 38, primary * (opacity * 0.72f), 12f);
DrawArc(batch, pixel, portal, radius * 0.62f, 0f, MathHelper.TwoPi, 34, secondary * opacity, 4f);
Vector2 tangent = v.Direction.RotatedBy(MathHelper.PiOver2);
float sweep = MathHelper.Lerp(-0.8f, 0.8f, MathHelper.SmoothStep(0f, 1f, p));
Vector2 slashDirection = v.Direction.RotatedBy(sweep);
DrawSegment(batch, pixel, portal - slashDirection * 112f - tangent * 18f,
portal + slashDirection * 112f + tangent * 18f, secondary * opacity, 11f);
}
}
private static void DrawFrostMark(SpriteBatch batch, Texture2D pixel, ReaperSkillVisual v, Vector2 center,
float p, float opacity, Color primary, Color secondary)
{
float radius = 44f + v.Level * 13f;
for (int arm = 0; arm < 6; arm++)
{
Vector2 direction = (arm * MathHelper.TwoPi / 6f).ToRotationVector2();
Vector2 tangent = direction.RotatedBy(MathHelper.PiOver2);
DrawSegment(batch, pixel, center, center + direction * radius, secondary * opacity, 3.5f);
DrawSegment(batch, pixel, center + direction * radius * 0.62f,
center + direction * radius * 0.78f + tangent * 10f, primary * opacity, 2f);
DrawSegment(batch, pixel, center + direction * radius * 0.62f,
center + direction * radius * 0.78f - tangent * 10f, primary * opacity, 2f);
}
if (v.Level >= 2)
{
for (int mirror = 0; mirror < 6; mirror++)
{
float angle = mirror * MathHelper.TwoPi / 6f + p * 0.4f;
DrawDiamond(batch, pixel, center + angle.ToRotationVector2() * (radius + 25f), 16f,
primary * (opacity * 0.66f), 3f, angle);
}
}
if (v.Level >= 3)
{
DrawPolygon(batch, pixel, center, radius * 1.28f, 8, secondary * (opacity * 0.8f), 6f, p * 0.2f);
DrawCross(batch, pixel, center, radius * 1.18f, Color.White with { A = 0 } * opacity, 4f, MathHelper.PiOver4);
}
}
private static void DrawFrostRefraction(SpriteBatch batch, Texture2D pixel, ReaperSkillVisual v, Vector2 center,
float p, float opacity, Color primary, Color secondary)
{
int mirrors = v.Level switch { 1 => 1, 2 => 3, _ => 6 };
for (int i = 0; i < mirrors; i++)
{
float angle = i * MathHelper.TwoPi / mirrors + p * 0.65f;
Vector2 mirror = center + angle.ToRotationVector2() * (42f + v.Level * 10f);
DrawPolygon(batch, pixel, mirror, 17f + v.Level * 2f, 6,
(i % 2 == 0 ? secondary : primary) * (opacity * 0.72f), 3f, angle);
DrawSegment(batch, pixel, mirror - v.Direction.RotatedBy(angle * 0.08f) * 28f,
mirror + v.Direction.RotatedBy(angle * 0.08f) * 28f, secondary * opacity, 3f);
}
Vector2 cometEnd = center + v.Direction * (70f + v.Level * 28f);
DrawSegment(batch, pixel, center - v.Direction * 45f, cometEnd, secondary * opacity, 7f + v.Level * 2f);
DrawSegment(batch, pixel, center - v.Direction * 58f, cometEnd, Color.White with { A = 0 } * opacity, 2f);
if (v.Level >= 3)
DrawRing(batch, pixel, cometEnd, 38f + p * 18f, 24, primary * opacity, 6f, p);
}
private static void DrawFrostExecution(SpriteBatch batch, Texture2D pixel, ReaperSkillVisual v, Vector2 center,
float p, float opacity, Color primary, Color secondary)
{
float length = 70f + v.Level * 22f;
DrawCross(batch, pixel, center, length, secondary * opacity, 7f + v.Level, MathHelper.PiOver4);
DrawCross(batch, pixel, center, length, Color.White with { A = 0 } * opacity, 2f, MathHelper.PiOver4);
if (v.Level >= 2)
{
float frame = 65f + v.Level * 14f;
DrawPolygon(batch, pixel, center, frame, 4, primary * (opacity * 0.72f), 5f, MathHelper.PiOver4);
DrawSegment(batch, pixel, center - Vector2.UnitX * frame, center + Vector2.UnitX * frame,
primary * (opacity * 0.46f), 3f);
DrawSegment(batch, pixel, center - Vector2.UnitY * frame, center + Vector2.UnitY * frame,
primary * (opacity * 0.46f), 3f);
}
if (v.Level >= 3)
{
DrawPolygon(batch, pixel, center, 82f, 6, secondary * (opacity * 0.64f), 8f, MathHelper.PiOver2);
for (int crack = 0; crack < 6; crack++)
{
Vector2 direction = (crack * MathHelper.TwoPi / 6f + 0.2f).ToRotationVector2();
DrawSegment(batch, pixel, center + direction * 62f,
center + direction * (108f + crack % 2 * 16f), Color.White with { A = 0 } * (opacity * 0.72f), 1.7f);
}
}
}
private static void DrawSoulEcho(SpriteBatch batch, Texture2D pixel, ReaperSkillVisual v, Vector2 center,
float p, float opacity, Color primary, Color secondary)
{
int ghosts = v.Level;
for (int i = 0; i < ghosts; i++)
{
Vector2 offset = -v.Direction * (28f + i * 29f) + v.Direction.RotatedBy(MathHelper.PiOver2) * (i - (ghosts - 1) * 0.5f) * 15f;
Vector2 ghost = center + offset;
DrawGhost(batch, pixel, ghost, v.Direction, primary * (opacity * (0.52f + i * 0.09f)),
secondary * (opacity * 0.82f), 1f + i * 0.12f);
DrawArc(batch, pixel, ghost, 45f + i * 6f, v.Direction.ToRotation() - 1.7f,
2.8f, 14, secondary * opacity, 4f);
}
if (v.Level >= 3 && p > 0.36f)
{
Vector2 giant = center - Vector2.UnitY * 64f;
DrawGhost(batch, pixel, giant, v.Direction, primary * (opacity * 0.45f), secondary * opacity, 1.7f);
DrawArc(batch, pixel, giant, 88f, v.Direction.ToRotation() - 1.75f, 2.9f, 24,
secondary * opacity, 7f);
}
}
private static void DrawSoulFamiliar(SpriteBatch batch, Texture2D pixel, ReaperSkillVisual v, Vector2 center,
float p, float opacity, Color primary, Color secondary)
{
float scale = v.Level switch { 1 => 1f, 2 => 1.28f, _ => 1.58f };
DrawGhost(batch, pixel, center - Vector2.UnitY * 32f, v.Direction,
primary * (opacity * 0.62f), secondary * opacity, scale);
int gates = v.Level == 1 ? 1 : v.Level == 2 ? 2 : 3;
for (int i = 0; i < gates; i++)
{
float angle = i * MathHelper.TwoPi / gates + p;
Vector2 gate = center + angle.ToRotationVector2() * (44f + v.Level * 13f);
DrawArc(batch, pixel, gate, 20f + v.Level * 4f, -MathHelper.PiOver2, MathHelper.Pi,
14, primary * (opacity * 0.64f), 5f);
}
int slashes = v.Level switch { 1 => 2, 2 => 3, _ => 4 };
for (int i = 0; i < slashes; i++)
{
float angle = v.Direction.ToRotation() + (i - (slashes - 1) * 0.5f) * 0.42f;
Vector2 direction = angle.ToRotationVector2();
DrawSegment(batch, pixel, center - direction * 18f, center + direction * (55f + v.Level * 17f),
secondary * (opacity * 0.76f), 4f + v.Level);
}
if (v.Level >= 3)
DrawRing(batch, pixel, center, 76f, 32, primary * (opacity * 0.55f), 6f, -p * 1.8f);
}
private static void DrawSoulFate(SpriteBatch batch, Texture2D pixel, ReaperSkillVisual v, Vector2 center,
float p, float opacity, Color primary, Color secondary)
{
if (v.Variant > 0)
{
float collapse = 1f - MathHelper.SmoothStep(0f, 1f,
MathHelper.Clamp((p - 0.18f) / 0.55f, 0f, 1f));
DrawRing(batch, pixel, center, (54f + v.Level * 15f) * collapse,
34, primary * (opacity * 0.74f), 8f, -p * 1.5f);
Vector2 slash = v.Direction.SafeNormalize(Vector2.UnitX);
DrawSegment(batch, pixel, center - slash * 84f, center + slash * 84f,
new Color(28, 8, 48, 0) * opacity, 18f);
DrawSegment(batch, pixel, center - slash * 88f, center + slash * 88f,
secondary * opacity, 5f);
if (v.Level >= 2)
{
Vector2 cross = slash.RotatedBy(MathHelper.PiOver2);
DrawSegment(batch, pixel, center - cross * 70f, center + cross * 70f,
primary * opacity, 4.5f);
}
return;
}
int runes = v.Level switch { 1 => 3, 2 => 5, _ => 7 };
float radius = 44f + v.Level * 11f;
for (int i = 0; i < runes; i++)
{
float angle = i * MathHelper.TwoPi / runes - p * 0.8f;
Vector2 rune = center + angle.ToRotationVector2() * radius;
Vector2 radial = angle.ToRotationVector2();
Vector2 tangent = radial.RotatedBy(MathHelper.PiOver2);
DrawSegment(batch, pixel, rune - tangent * 7f, rune + tangent * 7f, secondary * opacity, 2f);
DrawSegment(batch, pixel, rune - radial * 5f, rune + radial * 8f, primary * opacity, 3f);
if (v.Level >= 2)
DrawDiamond(batch, pixel, rune, 9f, primary * (opacity * 0.45f), 1.5f, angle);
}
if (v.Level >= 2)
{
Vector2 penStart = center + new Vector2(-55f, -55f);
Vector2 penEnd = center + new Vector2(48f, 44f);
DrawSegment(batch, pixel, penStart, penEnd, secondary * opacity, 3f);
DrawDiamond(batch, pixel, penEnd, 8f, primary * opacity, 2f, (penEnd - penStart).ToRotation());
}
if (v.Level >= 3)
{
Vector2 topLeft = center + new Vector2(-58f, -76f);
Vector2 topRight = center + new Vector2(58f, -76f);
Vector2 bottomLeft = center + new Vector2(-58f, 70f);
Vector2 bottomRight = center + new Vector2(58f, 70f);
DrawSegment(batch, pixel, topLeft, topRight, primary * (opacity * 0.62f), 7f);
DrawSegment(batch, pixel, topLeft, bottomLeft, primary * (opacity * 0.62f), 7f);
DrawSegment(batch, pixel, topRight, bottomRight, primary * (opacity * 0.62f), 7f);
DrawSegment(batch, pixel, bottomLeft, bottomRight, primary * (opacity * 0.62f), 7f);
DrawSegment(batch, pixel, center + new Vector2(-74f, 42f), center + new Vector2(76f, -35f),
secondary * opacity, 9f);
}
}
private static void DrawVoidIntersection(SpriteBatch batch, Texture2D pixel, ReaperSkillVisual v, Vector2 center,
float p, float opacity, Color primary, Color secondary)
{
float length = 92f + v.Level * 22f;
float width = 7f + v.Level * 2f;
float reveal = MathHelper.SmoothStep(0f, 1f, MathHelper.Clamp(p / 0.28f, 0f, 1f));
Vector2 facing = v.Direction.SafeNormalize(Vector2.UnitX);
ReaperVoidBackdropRenderer.DrawHitRift(batch, center, facing.RotatedBy(-0.54f),
length, width, v.Seed, reveal, opacity);
ReaperVoidBackdropRenderer.DrawHitRift(batch, center, facing.RotatedBy(0.57f),
length * (v.Level >= 2 ? 0.94f : 0.78f), width * 0.88f,
v.Seed ^ 0x2718, reveal, opacity * 0.92f);
}
private static void DrawVoidResidual(SpriteBatch batch, Texture2D pixel, ReaperSkillVisual v, Vector2 center,
float p, float opacity, Color primary, Color secondary)
{
int cuts = v.Level;
Vector2 tangent = v.Direction.RotatedBy(MathHelper.PiOver2);
for (int i = 0; i < cuts; i++)
{
float offset = (i - (cuts - 1) * 0.5f) * 12f;
Vector2 origin = center + tangent * offset + v.Direction * (i * 5f);
Vector2 cutDirection = v.Direction.RotatedBy((i - (cuts - 1) * 0.5f) * 0.14f);
float length = 104f + v.Level * 18f - i * 6f;
ReaperVoidBackdropRenderer.DrawHitRift(batch, origin, cutDirection,
length, 7f + v.Level * 1.6f, v.Seed + i * 977,
MathHelper.SmoothStep(0f, 1f, MathHelper.Clamp(p / (0.22f + i * 0.04f), 0f, 1f)),
opacity * (1f - i * 0.10f));
}
if (v.Level >= 3)
{
float collapse = 1f - MathHelper.SmoothStep(0f, 1f, MathHelper.Clamp((p - 0.42f) / 0.3f, 0f, 1f));
DrawSegment(batch, pixel, center - v.Direction * 94f * collapse, center + v.Direction * 94f * collapse,
secondary * (opacity * 0.72f), 1.4f);
}
}
private static void DrawVoidCoordinate(SpriteBatch batch, Texture2D pixel, ReaperSkillVisual v, Vector2 center,
float p, float opacity, Color primary, Color secondary)
{
float radius = v.Level switch { 1 => 40f, 2 => 58f, _ => 82f };
float collapse = v.Variant > 0
? 1f - MathHelper.SmoothStep(0f, 1f, MathHelper.Clamp(p * 1.5f, 0f, 1f))
: 1f;
radius *= Math.Max(0.08f, collapse);
DrawCross(batch, pixel, center, radius, primary * opacity, 3f, 0f);
DrawRing(batch, pixel, center, radius * 0.72f, 28, secondary * opacity, 2.5f, p);
if (v.Level >= 2)
{
DrawDiamond(batch, pixel, center, radius, primary * opacity, 3f, p * 0.7f);
DrawDiamond(batch, pixel, center + new Vector2(9f, -9f), radius, secondary * (opacity * 0.62f), 2f, -p * 0.7f);
DrawSegment(batch, pixel, center + new Vector2(-radius, -radius), center + new Vector2(-radius + 9f, -radius - 9f), primary * opacity, 2f);
DrawSegment(batch, pixel, center + new Vector2(radius, radius), center + new Vector2(radius + 9f, radius - 9f), primary * opacity, 2f);
}
if (v.Level >= 3)
{
for (int grid = -1; grid <= 1; grid++)
{
float offset = grid * radius * 0.42f;
DrawSegment(batch, pixel, center + new Vector2(-radius, offset), center + new Vector2(radius, offset),
primary * (opacity * 0.38f), 1.3f);
DrawSegment(batch, pixel, center + new Vector2(offset, -radius), center + new Vector2(offset, radius),
primary * (opacity * 0.38f), 1.3f);
}
if (v.Variant > 0 && p > 0.45f)
{
DrawSegment(batch, pixel, center - Vector2.UnitX * 100f, center + Vector2.UnitX * 100f, secondary * opacity, 3f);
DrawSegment(batch, pixel, center - Vector2.UnitY * 100f, center + Vector2.UnitY * 100f, secondary * opacity, 3f);
Vector2 depth = new Vector2(1f, -0.62f).SafeNormalize(Vector2.UnitX);
DrawSegment(batch, pixel, center - depth * 100f, center + depth * 100f, secondary * opacity, 3f);
}
}
}
private static void DrawGhost(SpriteBatch batch, Texture2D pixel, Vector2 center, Vector2 facing,
Color body, Color edge, float scale)
{
facing = facing.SafeNormalize(Vector2.UnitX);
Vector2 tangent = facing.RotatedBy(MathHelper.PiOver2);
DrawArc(batch, pixel, center, 14f * scale, MathHelper.Pi, MathHelper.Pi, 14, edge, 3f * scale);
DrawSegment(batch, pixel, center - tangent * 13f * scale, center + facing * 4f,
edge, 2.5f * scale);
DrawSegment(batch, pixel, center + tangent * 13f * scale, center + facing * 4f,
edge, 2.5f * scale);
DrawSegment(batch, pixel, center + facing * 3f, center + facing * 38f * scale,
body, 12f * scale);
DrawSegment(batch, pixel, center + facing * 25f * scale - tangent * 14f * scale,
center + facing * 25f * scale + tangent * 14f * scale, edge * 0.72f, 2f * scale);
}
private static void DrawChain(SpriteBatch batch, Texture2D pixel, Vector2 start, Vector2 end,
Color outer, Color core, int links)
{
Vector2 delta = end - start;
for (int i = 0; i < links; i++)
{
float t0 = i / (float)links;
float t1 = (i + 0.72f) / links;
Vector2 linkStart = start + delta * t0;
Vector2 linkEnd = start + delta * t1;
DrawSegment(batch, pixel, linkStart, linkEnd, outer, i % 2 == 0 ? 4f : 2.4f);
DrawSegment(batch, pixel, linkStart, linkEnd, core * 0.72f, 1.2f);
}
}
private static void DrawCross(SpriteBatch batch, Texture2D pixel, Vector2 center, float radius,
Color color, float width, float rotation)
{
Vector2 first = rotation.ToRotationVector2();
Vector2 second = first.RotatedBy(MathHelper.PiOver2);
DrawSegment(batch, pixel, center - first * radius, center + first * radius, color, width);
DrawSegment(batch, pixel, center - second * radius, center + second * radius, color, width);
}
private static void DrawDiamond(SpriteBatch batch, Texture2D pixel, Vector2 center, float radius,
Color color, float width, float rotation)
=> DrawPolygon(batch, pixel, center, radius, 4, color, width, rotation + MathHelper.PiOver4);
private static void DrawStar(SpriteBatch batch, Texture2D pixel, Vector2 center, float outerRadius,
float innerRadius, int points, Color color, float width, float rotation)
{
int vertices = points * 2;
Vector2 previous = center + rotation.ToRotationVector2() * outerRadius;
for (int i = 1; i <= vertices; i++)
{
float angle = rotation + i * MathHelper.TwoPi / vertices;
float radius = (i & 1) == 0 ? outerRadius : innerRadius;
Vector2 next = center + angle.ToRotationVector2() * radius;
DrawSegment(batch, pixel, previous, next, color, width);
previous = next;
}
}
private static void DrawPolygon(SpriteBatch batch, Texture2D pixel, Vector2 center, float radius,
int sides, Color color, float width, float rotation)
{
Vector2 previous = center + rotation.ToRotationVector2() * radius;
for (int i = 1; i <= sides; i++)
{
Vector2 next = center + (rotation + i * MathHelper.TwoPi / sides).ToRotationVector2() * radius;
DrawSegment(batch, pixel, previous, next, color, width);
previous = next;
}
}
private static void DrawRing(SpriteBatch batch, Texture2D pixel, Vector2 center, float radius,
int segments, Color color, float width, float rotation)
=> DrawArc(batch, pixel, center, radius, rotation, MathHelper.TwoPi, segments, color, width);
private static void DrawArc(SpriteBatch batch, Texture2D pixel, Vector2 center, float radius,
float startAngle, float sweep, int segments, Color color, float width)
{
if (radius <= 0.1f || segments <= 0)
return;
segments = Math.Max(segments,
Math.Min(160, (int)Math.Ceiling(Math.Abs(sweep) * radius / 18f)));
Vector2 previous = center + startAngle.ToRotationVector2() * radius;
for (int i = 1; i <= segments; i++)
{
Vector2 next = center + (startAngle + sweep * i / segments).ToRotationVector2() * radius;
DrawSegment(batch, pixel, previous, next, color, width);
previous = next;
}
}
private static void DrawSegment(SpriteBatch batch, Texture2D pixel, Vector2 start, Vector2 end,
Color color, float width)
{
DeathDomainPrimitiveTextureSystem.DrawSmoothLine(batch,
start - Main.screenPosition, end - Main.screenPosition, color, width);
}
}
using SoulHarvest.Projectiles;
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.ModLoader;
namespace SoulHarvest.Common;
/// <summary>
/// Client-only storyboards for the eighteen branch skills. The authoritative
/// cue is synchronized by <see cref="ReaperSkillCueProjectile"/>; everything kept
/// here is disposable presentation state and can never deal damage or move an NPC.
/// </summary>
[Autoload(Side = ModSide.Client)]
internal sealed class ReaperSkillVisualSystem : ModSystem
{
private const int MaximumVisuals = 40;
private static readonly List<ReaperSkillVisual> Visuals = [];
public static void ReportSkill(
int owner,
ReaperFormId form,
ReaperStage stage,
int skill,
int level,
int variant,
Vector2 center,
Vector2 direction,
int actionId,
int seed)
{
if (Main.dedServ || Main.gameMenu
|| owner < 0 || owner >= Main.maxPlayers
|| !ReaperDefinitions.IsBranchForm(form)
|| skill is < 0 or > 2 || level is < 1 or > 3)
{
return;
}
direction = direction.SafeNormalize(Vector2.UnitY * -1f);
if (form == ReaperFormId.Blood && skill is 1 or 2 && variant == 1)
{
for (int index = Visuals.Count - 1; index >= 0; index--)
{
ReaperSkillVisual shield = Visuals[index];
if (shield.Owner == owner && shield.Form == form && shield.Skill == skill && shield.Variant == 0)
Visuals.RemoveAt(index);
}
}
for (int index = Visuals.Count - 1; index >= 0; index--)
{
ReaperSkillVisual existing = Visuals[index];
bool persistent = IsPersistentOwnerVisual(form, skill, variant);
if (existing.Owner != owner || existing.Form != form || existing.Skill != skill
|| existing.Variant != variant)
{
continue;
}
// A wide attack hitting several targets in the same update becomes one
// centered execution instead of several opaque copies of the same VFX.
bool sameAction = existing.ActionId == actionId;
bool nearbyBurst = (existing.Age <= 7 || persistent)
&& Vector2.DistanceSquared(existing.Center, center) <= 240f * 240f;
if (sameAction || nearbyBurst)
{
int samples = Math.Clamp(existing.CenterSamples, 1, 64);
float sampleWeight = 1f / (samples + 1f);
existing.Center = Vector2.Lerp(existing.Center, center, sampleWeight);
existing.Direction = Vector2.Lerp(existing.Direction, direction, sampleWeight)
.SafeNormalize(existing.Direction);
existing.CenterSamples = Math.Min(64, samples + 1);
existing.Level = Math.Max(existing.Level, level);
existing.Duration = Math.Max(existing.Duration, GetDuration(form, skill, existing.Level, variant));
existing.Age = persistent
? 0
: Math.Min(existing.Age, 3);
return;
}
}
if (Visuals.Count >= MaximumVisuals)
Visuals.RemoveAt(0);
Visuals.Add(new ReaperSkillVisual
{
Owner = owner,
Form = form,
Stage = stage,
Skill = skill,
Level = level,
Variant = variant,
ActionId = actionId,
Seed = seed,
Center = center,
Direction = direction,
CenterSamples = 1,
Duration = GetDuration(form, skill, level, variant)
});
if (IsNearScreen(center, 620f))
PlaySkillSound(form, skill, level, variant, center);
// Unique skills keep their restrained camera punctuation, but never tint
// the entire viewport. A three-frame pale impact used to present as a
// one-frame white screen when several automatic triggers were merged.
if (level >= 3 && IsNearScreen(center))
ReaperVfxDirector.TriggerGlobalImpact(direction, 2.8f, 6,
Color.Transparent, 0f, 0, 0.06f);
}
public override void OnWorldUnload() => Visuals.Clear();
public override void Unload() => Visuals.Clear();
public override void PostUpdateEverything()
{
if (Main.gameMenu)
{
Visuals.Clear();
return;
}
for (int index = Visuals.Count - 1; index >= 0; index--)
{
if (++Visuals[index].Age >= Visuals[index].Duration)
Visuals.RemoveAt(index);
}
}
public override void PostDrawTiles()
{
if (Main.dedServ || Main.gameMenu || Visuals.Count == 0)
return;
SpriteBatch batch = Main.spriteBatch;
batch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp,
DepthStencilState.None, RasterizerState.CullNone, null,
Main.GameViewMatrix.TransformationMatrix);
Texture2D pixel = TextureAssets.MagicPixel.Value;
foreach (ReaperSkillVisual visual in Visuals)
{
Vector2 center = ResolveCenter(visual);
if (!IsNearScreen(center, 440f))
continue;
DrawVisual(batch, pixel, visual, center);
}
batch.End();
}
private static int GetDuration(ReaperFormId form, int skill, int level, int variant)
{
if (form == ReaperFormId.Blood && skill == 2 && variant == 0)
return 300;
if (form == ReaperFormId.Blood && skill == 1 && variant == 0)
return 120;
if (form == ReaperFormId.Infernal && skill == 0 && variant == 0)
return 130;
return variant > 0 ? 28 + level * 3 : level switch { 1 => 30, 2 => 37, _ => 46 };
}
private static bool IsPersistentOwnerVisual(ReaperFormId form, int skill, int variant)
=> variant == 0 && (form == ReaperFormId.Blood && skill is 1 or 2
|| form == ReaperFormId.Infernal && skill == 0);
private static Vector2 ResolveCenter(ReaperSkillVisual visual)
{
bool followsOwner = visual.Form switch
{
ReaperFormId.Blood => visual.Skill is 1 or 2 && visual.Variant == 0,
ReaperFormId.Infernal => visual.Skill is 0 or 2,
ReaperFormId.Soul => visual.Skill == 1,
_ => false
};
if (!followsOwner || visual.Owner < 0 || visual.Owner >= Main.maxPlayers)
return visual.Center;
Player owner = Main.player[visual.Owner];
return owner.active ? owner.MountedCenter : visual.Center;
}
private static bool IsNearScreen(Vector2 center, float margin = 180f)
{
Rectangle area = new(
(int)(Main.screenPosition.X - margin),
(int)(Main.screenPosition.Y - margin),
Main.screenWidth + (int)(margin * 2f),
Main.screenHeight + (int)(margin * 2f));
return area.Contains(center.ToPoint());
}
private static void PlaySkillSound(
ReaperFormId form,
int skill,
int level,
int variant,
Vector2 center)
{
SoundStyle primary = form switch
{
ReaperFormId.Bone => skill == 2 ? SoundID.NPCDeath6 : SoundID.Item71,
ReaperFormId.Blood => skill == 0 ? SoundID.Item29 : SoundID.NPCDeath6,
ReaperFormId.Infernal => skill == 0 ? SoundID.Item74 : SoundID.Item14,
ReaperFormId.Frost => SoundID.Shatter,
ReaperFormId.Soul => skill == 1 ? SoundID.NPCDeath6 : SoundID.Item8,
ReaperFormId.Void => skill == 1 ? SoundID.Item122 : SoundID.Item8,
_ => SoundID.Item1
};
SoundEngine.PlaySound(primary with
{
Volume = MathHelper.Clamp(0.42f + level * 0.09f, 0f, 0.74f),
Pitch = MathHelper.Clamp(-0.22f + level * 0.08f + variant * 0.035f, -1f, 1f),
PitchVariance = 0.045f,
MaxInstances = 2,
SoundLimitBehavior = SoundLimitBehavior.IgnoreNew
}, center);
// Lv.3 executions get one restrained low accent. The shared instance cap
// prevents simultaneous multi-target triggers from producing audio peaks.
if (level >= 3 && variant == 0)
{
SoundEngine.PlaySound(SoundID.Item71 with
{
Volume = 0.36f,
Pitch = -0.52f,
PitchVariance = 0.03f,
MaxInstances = 2,
SoundLimitBehavior = SoundLimitBehavior.IgnoreNew
}, center);
}
}
private static void DrawVisual(SpriteBatch batch, Texture2D pixel, ReaperSkillVisual visual, Vector2 center)
{
float progress = visual.Age / (float)Math.Max(1, visual.Duration);
float reveal = MathHelper.SmoothStep(0f, 1f, MathHelper.Clamp(progress / 0.22f, 0f, 1f));
float fade = 1f - MathHelper.SmoothStep(0f, 1f, MathHelper.Clamp((progress - 0.66f) / 0.34f, 0f, 1f));
float opacity = reveal * fade;
float pulse = 1f + (float)Math.Sin(progress * MathHelper.TwoPi * 2f + visual.Seed * 0.013f) * 0.055f;
Color primary = ReaperCombatRegistry.GetPrimaryColor(visual.Form) with { A = 0 };
Color secondary = ReaperCombatRegistry.GetSecondaryColor(visual.Form) with { A = 0 };
switch (visual.Form)
{
case ReaperFormId.Bone:
if (visual.Skill == 0) DrawBoneShatter(batch, pixel, visual, center, progress, opacity, pulse, primary, secondary);
else if (visual.Skill == 1) DrawBoneRestraint(batch, pixel, visual, center, progress, opacity, primary, secondary);
else DrawBoneChorus(batch, pixel, visual, center, progress, opacity, primary, secondary);
break;
case ReaperFormId.Blood:
if (visual.Skill == 0) DrawBloodDrink(batch, pixel, visual, center, progress, opacity, primary, secondary);
else if (visual.Skill == 1) DrawBloodFrenzy(batch, pixel, visual, center, progress, opacity, primary, secondary);
else DrawBloodShield(batch, pixel, visual, center, progress, opacity, primary, secondary);
break;
case ReaperFormId.Infernal:
if (visual.Skill == 0) DrawInfernalMomentum(batch, pixel, visual, center, progress, opacity, primary, secondary);
else if (visual.Skill == 1) DrawInfernalScorch(batch, pixel, visual, center, progress, opacity, primary, secondary);
else DrawInfernalCircuit(batch, pixel, visual, center, progress, opacity, primary, secondary);
break;
case ReaperFormId.Frost:
if (visual.Skill == 0) DrawFrostMark(batch, pixel, visual, center, progress, opacity, primary, secondary);
else if (visual.Skill == 1) DrawFrostRefraction(batch, pixel, visual, center, progress, opacity, primary, secondary);
else DrawFrostExecution(batch, pixel, visual, center, progress, opacity, primary, secondary);
break;
case ReaperFormId.Soul:
if (visual.Skill == 0) DrawSoulEcho(batch, pixel, visual, center, progress, opacity, primary, secondary);
else if (visual.Skill == 1) DrawSoulFamiliar(batch, pixel, visual, center, progress, opacity, primary, secondary);
else DrawSoulFate(batch, pixel, visual, center, progress, opacity, primary, secondary);
break;
case ReaperFormId.Void:
if (visual.Skill == 0) DrawVoidIntersection(batch, pixel, visual, center, progress, opacity, primary, secondary);
else if (visual.Skill == 1) DrawVoidResidual(batch, pixel, visual, center, progress, opacity, primary, secondary);
else DrawVoidCoordinate(batch, pixel, visual, center, progress, opacity, primary, secondary);
break;
}
}
private static void DrawBoneShatter(SpriteBatch batch, Texture2D pixel, ReaperSkillVisual v, Vector2 center,
float p, float opacity, float pulse, Color primary, Color secondary)
{
int spikes = v.Level switch { 1 => 3, 2 => 6, _ => 10 };
float radius = (40f + v.Level * 15f) * pulse;
float collapse = 1f - MathHelper.SmoothStep(0f, 1f, MathHelper.Clamp((p - 0.08f) / 0.48f, 0f, 1f));
for (int i = 0; i < spikes; i++)
{
Vector2 direction = (i * MathHelper.TwoPi / spikes - MathHelper.PiOver2).ToRotationVector2();
Vector2 tangent = direction.RotatedBy(MathHelper.PiOver2);
Vector2 tip = center + direction * (10f + radius * collapse);
Vector2 root = center + direction * (radius + 22f) + tangent * ((i & 1) == 0 ? 5f : -5f);
DrawSegment(batch, pixel, root, tip, secondary * opacity, 3.4f + v.Level);
DrawSegment(batch, pixel, root + tangent * 3f, tip, primary * (opacity * 0.72f), 1.5f);
}
DrawRing(batch, pixel, center, radius * (0.56f + 0.2f * collapse), spikes * 2,
primary * (opacity * 0.58f), 2.5f, p * 0.8f);
if (v.Level >= 2)
{
for (int i = 0; i < 6; i++)
{
float x = (i - 2.5f) * 14f;
float bow = 18f - Math.Abs(i - 2.5f) * 3f;
DrawSegment(batch, pixel, center + new Vector2(x, 38f), center + new Vector2(x * 0.7f, -bow),
secondary * (opacity * 0.48f), 2.8f);
}
}
if (v.Level >= 3)
{
Vector2 skull = center + new Vector2(0f, -44f);
DrawArc(batch, pixel, skull, 34f, MathHelper.Pi, MathHelper.Pi, 16, secondary * (opacity * 0.7f), 4f);
DrawDiamond(batch, pixel, skull + new Vector2(-12f, 2f), 5f, primary * opacity, 2f, 0f);
DrawDiamond(batch, pixel, skull + new Vector2(12f, 2f), 5f, primary * opacity, 2f, 0f);
if (p > 0.42f)
DrawCross(batch, pixel, center, 72f, secondary * (opacity * 0.88f), 4.5f, v.Direction.ToRotation());
}
}
private static void DrawBoneRestraint(SpriteBatch batch, Texture2D pixel, ReaperSkillVisual v, Vector2 center,
float p, float opacity, Color primary, Color secondary)
{
float radius = 43f + v.Level * 13f;
DrawChain(batch, pixel, center + new Vector2(-radius, -radius * 0.45f), center + new Vector2(radius, radius * 0.45f),
primary * opacity, secondary * opacity, 8 + v.Level * 2);
DrawChain(batch, pixel, center + new Vector2(radius, -radius * 0.45f), center + new Vector2(-radius, radius * 0.45f),
primary * opacity, secondary * opacity, 8 + v.Level * 2);
if (v.Level >= 2)
{
for (int i = 0; i < 6; i++)
{
float angle = i * MathHelper.TwoPi / 6f;
Vector2 direction = angle.ToRotationVector2();
Vector2 basePoint = center + direction * radius;
DrawSegment(batch, pixel, basePoint - Vector2.UnitY * 42f, basePoint + Vector2.UnitY * 42f,
secondary * (opacity * 0.72f), 4f);
DrawSegment(batch, pixel, basePoint - Vector2.UnitY * 42f,
basePoint - Vector2.UnitY * 51f + direction * 8f, primary * opacity, 2.2f);
}
}
if (v.Level >= 3)
{
Vector2 palm = center + new Vector2(0f, 58f);
for (int finger = -2; finger <= 2; finger++)
DrawSegment(batch, pixel, palm + new Vector2(finger * 8f, 0f), center + new Vector2(finger * 14f, -42f),
secondary * (opacity * 0.58f), 6f - Math.Abs(finger) * 0.6f);
float fall = MathHelper.SmoothStep(0f, 1f, MathHelper.Clamp((p - 0.42f) / 0.25f, 0f, 1f));
float bladeY = MathHelper.Lerp(-120f, 20f, fall);
DrawSegment(batch, pixel, center + new Vector2(-82f, bladeY), center + new Vector2(82f, bladeY),
secondary * opacity, 8f);
DrawSegment(batch, pixel, center + new Vector2(-80f, bladeY - 4f), center + new Vector2(80f, bladeY - 4f),
primary * opacity, 2f);
}
}
private static void DrawBoneChorus(SpriteBatch batch, Texture2D pixel, ReaperSkillVisual v, Vector2 center,
float p, float opacity, Color primary, Color secondary)
{
int ghosts = v.Level switch { 1 => 2, 2 => 4, _ => 6 };
float radius = 54f + v.Level * 9f;
for (int i = 0; i < ghosts; i++)
{
float angle = i * MathHelper.TwoPi / ghosts + 0.35f;
Vector2 position = center + angle.ToRotationVector2() * radius;
DrawGhost(batch, pixel, position, center - position, primary * (opacity * 0.62f), secondary * (opacity * 0.82f), 0.8f);
Vector2 inward = (center - position).SafeNormalize(Vector2.UnitX);
float sweep = MathHelper.Lerp(-0.7f, 0.65f, MathHelper.SmoothStep(0f, 1f, MathHelper.Clamp(p * 1.7f, 0f, 1f)));
DrawSegment(batch, pixel, position - inward.RotatedBy(sweep) * 10f,
position + inward.RotatedBy(sweep) * 52f, secondary * opacity, 3f);
}
DrawRing(batch, pixel, center, radius * 0.72f, 30, primary * (opacity * 0.42f), 3f, -p * 2f);
if (v.Level >= 3 && p > 0.43f)
{
Vector2 reaper = center - Vector2.UnitY * 82f;
DrawGhost(batch, pixel, reaper, Vector2.UnitY, primary * (opacity * 0.54f), secondary * opacity, 1.8f);
DrawSegment(batch, pixel, reaper + new Vector2(-78f, 27f), center + new Vector2(78f, 25f),
secondary * opacity, 7f);
}
}
private static void DrawBloodDrink(SpriteBatch batch, Texture2D pixel, ReaperSkillVisual v, Vector2 center,
float p, float opacity, Color primary, Color secondary)
{
Vector2 owner = v.Owner >= 0 && v.Owner < Main.maxPlayers && Main.player[v.Owner].active
? Main.player[v.Owner].MountedCenter
: center + v.Direction * 110f;
Vector2 delta = owner - center;
Vector2 tangent = delta.SafeNormalize(Vector2.UnitX).RotatedBy(MathHelper.PiOver2);
int strands = v.Level == 1 ? 1 : 3;
for (int strand = 0; strand < strands; strand++)
{
Vector2 previous = center;
for (int point = 1; point <= 14; point++)
{
float t = point / 14f;
float wave = (float)Math.Sin(t * MathHelper.TwoPi * (1f + v.Level * 0.3f) + strand * 2.1f + p * 5f)
* (strand == 0 ? 5f : 10f) * (float)Math.Sin(t * MathHelper.Pi);
Vector2 next = center + delta * t + tangent * wave;
DrawSegment(batch, pixel, previous, next, (strand == 0 ? secondary : primary) * opacity,
strand == 0 ? 3.4f : 1.8f);
previous = next;
}
}
if (v.Level >= 2)
{
Vector2 rose = Vector2.Lerp(center, owner, 0.46f);
for (int petal = 0; petal < (v.Level >= 3 ? 8 : 5); petal++)
DrawArc(batch, pixel, rose, 17f + petal % 2 * 4f, petal * MathHelper.TwoPi / (v.Level >= 3 ? 8f : 5f),
1.7f, 7, primary * (opacity * 0.65f), 2.5f);
}
if (v.Level >= 3)
{
for (int petal = 0; petal < 6; petal++)
{
float angle = petal * MathHelper.TwoPi / 6f;
DrawDiamond(batch, pixel, center + angle.ToRotationVector2() * 29f, 18f,
secondary * (opacity * 0.72f), 3f, angle);
}
DrawArc(batch, pixel, center, 60f, -2.4f, 4.7f, 28, secondary * opacity, 7f);
}
}
private static void DrawBloodFrenzy(SpriteBatch batch, Texture2D pixel, ReaperSkillVisual v, Vector2 center,
float p, float opacity, Color primary, Color secondary)
{
if (v.Variant == 1)
{
float burst = MathHelper.SmoothStep(0f, 1f, MathHelper.Clamp(p * 1.7f, 0f, 1f));
DrawRing(batch, pixel, center, MathHelper.Lerp(28f, 112f, burst), 36,
secondary * opacity, 7f, -p);
for (int bead = 0; bead < 14; bead++)
{
Vector2 direction = (bead * MathHelper.TwoPi / 14f + 0.18f).ToRotationVector2();
Vector2 start = center + direction * MathHelper.Lerp(22f, 62f, burst);
DrawDiamond(batch, pixel, start + direction * burst * 58f, 7f,
(bead % 2 == 0 ? secondary : primary) * opacity, 2.4f, direction.ToRotation());
}
return;
}
if (v.Variant >= 2)
{
float facing = v.Direction.ToRotation();
int crescents = v.Level == 2 ? 2 : 1;
float radius = v.Level >= 3 ? 132f : v.Level == 2 ? 110f : 92f;
float reveal = MathHelper.SmoothStep(0f, 1f, MathHelper.Clamp(p * 2.2f, 0f, 1f));
for (int slash = 0; slash < crescents; slash++)
{
float side = crescents == 1 ? 0f : slash == 0 ? -0.22f : 0.22f;
Vector2 slashCenter = center + v.Direction * (radius * 0.18f)
+ v.Direction.RotatedBy(MathHelper.PiOver2) * side * 34f;
float start = facing - 1.9f + side;
DrawArc(batch, pixel, slashCenter, radius, start, 3.8f, 42,
new Color(65, 0, 13, 0) * (opacity * 0.82f), v.Level >= 3 ? 31f : 25f);
DrawArc(batch, pixel, slashCenter, radius, start, 3.8f, 42,
primary * (opacity * (0.62f + reveal * 0.2f)), v.Level >= 3 ? 20f : 15f);
DrawArc(batch, pixel, slashCenter, radius + 2f, start, 3.8f, 42,
secondary * opacity, v.Level >= 3 ? 4.5f : 3.5f);
}
return;
}
float beat = 1f + Math.Max(0f, (float)Math.Sin(p * MathHelper.TwoPi * 3f)) * 0.22f;
DrawRing(batch, pixel, center, 42f * beat, 28, primary * opacity, 5f, 0f);
DrawSegment(batch, pixel, center + new Vector2(-33f, 0f), center + new Vector2(-12f, 0f), secondary * opacity, 2.5f);
DrawSegment(batch, pixel, center + new Vector2(-12f, 0f), center + new Vector2(-5f, -11f), secondary * opacity, 2.5f);
DrawSegment(batch, pixel, center + new Vector2(-5f, -11f), center + new Vector2(4f, 13f), secondary * opacity, 2.5f);
DrawSegment(batch, pixel, center + new Vector2(4f, 13f), center + new Vector2(13f, 0f), secondary * opacity, 2.5f);
DrawSegment(batch, pixel, center + new Vector2(13f, 0f), center + new Vector2(34f, 0f), secondary * opacity, 2.5f);
if (v.Level >= 2)
{
for (int side = -1; side <= 1; side += 2)
{
Vector2 root = center + new Vector2(side * 10f, -3f);
Vector2 tip = center + new Vector2(side * 82f, -45f);
DrawArc(batch, pixel, root, 78f, side > 0 ? -1.05f : MathHelper.Pi + 0.05f,
side * 1.15f, 14, primary * (opacity * 0.62f), 7f);
DrawSegment(batch, pixel, root, tip, secondary * (opacity * 0.72f), 3f);
}
}
if (v.Level >= 3)
{
Vector2 moon = center + new Vector2(0f, -68f);
for (int layer = 0; layer < 5; layer++)
DrawRing(batch, pixel, moon, 26f - layer * 4f, 28, primary * (opacity * (0.13f + layer * 0.035f)), 5f, 0f);
DrawArc(batch, pixel, moon, 31f, -1.85f, 3.7f, 26, secondary * opacity, 4.5f);
}
}
private static void DrawBloodShield(SpriteBatch batch, Texture2D pixel, ReaperSkillVisual v, Vector2 center,
float p, float opacity, Color primary, Color secondary)
{
if (v.Variant == 1)
{
float radius = MathHelper.Lerp(24f, 88f, MathHelper.SmoothStep(0f, 1f, MathHelper.Clamp(p * 1.7f, 0f, 1f)));
DrawRing(batch, pixel, center, radius, 34, secondary * opacity, 8f, p);
for (int i = 0; i < 12; i++)
{
Vector2 direction = (i * MathHelper.TwoPi / 12f).ToRotationVector2();
DrawSegment(batch, pixel, center + direction * radius * 0.7f, center + direction * (radius + 25f),
primary * opacity, 4f);
}
return;
}
if (v.Variant >= 2)
{
float angle = v.Direction.ToRotation();
Vector2 strikeCenter = center + v.Direction * 30f;
DrawArc(batch, pixel, strikeCenter, v.Level >= 3 ? 101f : 86f,
angle - 1.7f, 3.4f, 34, primary * (opacity * 0.78f), v.Level >= 3 ? 20f : 16f);
DrawArc(batch, pixel, strikeCenter, v.Level >= 3 ? 104f : 89f,
angle - 1.7f, 3.4f, 34, secondary * opacity, 4f);
return;
}
int shards = v.Level switch { 1 => 4, 2 => 8, _ => 12 };
float radiusOrbit = 45f + v.Level * 5f;
for (int i = 0; i < shards; i++)
{
float angle = i * MathHelper.TwoPi / shards + p * 2.4f;
Vector2 position = center + angle.ToRotationVector2() * radiusOrbit;
DrawDiamond(batch, pixel, position, v.Level >= 2 ? 10f : 8f,
(i % 2 == 0 ? secondary : primary) * opacity, 3f, angle);
}
if (v.Level >= 2)
DrawRing(batch, pixel, center, radiusOrbit - 9f, 32, primary * (opacity * 0.34f), 5f, -p);
if (v.Level >= 3)
{
DrawArc(batch, pixel, center, 65f, -2.25f, 4.5f, 28, primary * (opacity * 0.54f), 9f);
Vector2 crown = center - Vector2.UnitY * 69f;
DrawSegment(batch, pixel, crown + new Vector2(-23f, 10f), crown + new Vector2(-15f, -8f), secondary * opacity, 3f);
DrawSegment(batch, pixel, crown + new Vector2(-15f, -8f), crown, secondary * opacity, 3f);
DrawSegment(batch, pixel, crown, crown + new Vector2(15f, -8f), secondary * opacity, 3f);
DrawSegment(batch, pixel, crown + new Vector2(15f, -8f), crown + new Vector2(23f, 10f), secondary * opacity, 3f);
}
}
private static void DrawInfernalMomentum(SpriteBatch batch, Texture2D pixel, ReaperSkillVisual v, Vector2 center,
float p, float opacity, Color primary, Color secondary)
{
Vector2 direction = v.Direction.SafeNormalize(Vector2.UnitX);
Vector2 tangent = direction.RotatedBy(MathHelper.PiOver2);
for (int foot = 0; foot < 4 + v.Level; foot++)
{
Vector2 position = center - direction * (18f + foot * 17f) + tangent * ((foot & 1) == 0 ? 7f : -7f);
DrawDiamond(batch, pixel, position, 7f + v.Level, primary * (opacity * (1f - foot * 0.1f)), 3f,
direction.ToRotation());
}
if (v.Level >= 2)
{
Vector2 ghost = center - direction * 66f;
DrawGhost(batch, pixel, ghost, direction, new Color(35, 20, 12, 0) * opacity,
secondary * (opacity * 0.66f), 1.25f);
DrawSegment(batch, pixel, ghost - tangent * 42f, ghost + direction * 90f + tangent * 38f,
secondary * opacity, 5f);
}
if (v.Level >= 3)
DrawStar(batch, pixel, center + direction * 25f, 67f, 27f, 5, primary * (opacity * 0.76f), 4f, p * 1.8f);
}
private static void DrawInfernalScorch(SpriteBatch batch, Texture2D pixel, ReaperSkillVisual v, Vector2 center,
float p, float opacity, Color primary, Color secondary)
{
if (v.Variant > 0)
{
float burst = MathHelper.SmoothStep(0f, 1f, MathHelper.Clamp(p * 1.8f, 0f, 1f));
DrawRing(batch, pixel, center, MathHelper.Lerp(34f, 102f, burst), 36,
secondary * opacity, 11f, p);
DrawStar(batch, pixel, center, 86f, 37f, 5, primary * (opacity * 0.86f), 7f, -p * 1.4f);
for (int rib = -1; rib <= 1; rib += 2)
{
DrawArc(batch, pixel, center + new Vector2(rib * 20f, 0f), 68f,
rib < 0 ? -1.35f : MathHelper.Pi - 1.35f, rib * 2.7f, 22,
secondary * opacity, 6f);
}
return;
}
float size = 45f + v.Level * 8f;
Vector2 head = center - Vector2.UnitY * size * 0.58f;
DrawRing(batch, pixel, head, 12f, 18, secondary * opacity, 3f, 0f);
DrawSegment(batch, pixel, head + Vector2.UnitY * 12f, center + Vector2.UnitY * 25f, secondary * opacity, 4f);
DrawSegment(batch, pixel, center - Vector2.UnitY * 9f, center + new Vector2(-size * 0.48f, 8f), primary * opacity, 3f);
DrawSegment(batch, pixel, center - Vector2.UnitY * 9f, center + new Vector2(size * 0.48f, 8f), primary * opacity, 3f);
DrawSegment(batch, pixel, center + Vector2.UnitY * 24f, center + new Vector2(-size * 0.33f, size * 0.68f), secondary * opacity, 3f);
DrawSegment(batch, pixel, center + Vector2.UnitY * 24f, center + new Vector2(size * 0.33f, size * 0.68f), secondary * opacity, 3f);
int ribs = 2 + v.Level;
for (int i = 0; i < ribs; i++)
{
float y = -2f + i * 9f;
DrawArc(batch, pixel, center + new Vector2(0f, y), 19f + i * 3f, MathHelper.Pi, MathHelper.Pi,
9, primary * (opacity * 0.7f), 3f);
}
if (v.Level >= 2)
{
for (int i = 0; i < 3; i++)
{
Vector2 hot = center + (i * MathHelper.TwoPi / 3f - MathHelper.PiOver2).ToRotationVector2() * (24f + i * 6f);
DrawRing(batch, pixel, hot, 7f + p * 9f, 14, secondary * opacity, 4f, 0f);
}
}
if (v.Level >= 3)
DrawRing(batch, pixel, center, MathHelper.Lerp(35f, 88f, p), 32, secondary * (opacity * 0.8f), 8f, 0f);
}
private static void DrawInfernalCircuit(SpriteBatch batch, Texture2D pixel, ReaperSkillVisual v, Vector2 center,
float p, float opacity, Color primary, Color secondary)
{
float radius = 51f + v.Level * 17f;
DrawRing(batch, pixel, center, radius * (0.75f + p * 0.25f), 36, secondary * opacity, 6f, p * 2f);
if (v.Level >= 2)
DrawStar(batch, pixel, center, radius, radius * 0.42f, 5, primary * opacity, 4f, -p * 1.5f);
if (v.Level >= 3)
{
Vector2 portal = center + v.Direction * 54f;
DrawArc(batch, pixel, portal, radius * 0.78f, 0f, MathHelper.TwoPi, 38, primary * (opacity * 0.72f), 12f);
DrawArc(batch, pixel, portal, radius * 0.62f, 0f, MathHelper.TwoPi, 34, secondary * opacity, 4f);
Vector2 tangent = v.Direction.RotatedBy(MathHelper.PiOver2);
float sweep = MathHelper.Lerp(-0.8f, 0.8f, MathHelper.SmoothStep(0f, 1f, p));
Vector2 slashDirection = v.Direction.RotatedBy(sweep);
DrawSegment(batch, pixel, portal - slashDirection * 112f - tangent * 18f,
portal + slashDirection * 112f + tangent * 18f, secondary * opacity, 11f);
}
}
private static void DrawFrostMark(SpriteBatch batch, Texture2D pixel, ReaperSkillVisual v, Vector2 center,
float p, float opacity, Color primary, Color secondary)
{
float radius = 44f + v.Level * 13f;
for (int arm = 0; arm < 6; arm++)
{
Vector2 direction = (arm * MathHelper.TwoPi / 6f).ToRotationVector2();
Vector2 tangent = direction.RotatedBy(MathHelper.PiOver2);
DrawSegment(batch, pixel, center, center + direction * radius, secondary * opacity, 3.5f);
DrawSegment(batch, pixel, center + direction * radius * 0.62f,
center + direction * radius * 0.78f + tangent * 10f, primary * opacity, 2f);
DrawSegment(batch, pixel, center + direction * radius * 0.62f,
center + direction * radius * 0.78f - tangent * 10f, primary * opacity, 2f);
}
if (v.Level >= 2)
{
for (int mirror = 0; mirror < 6; mirror++)
{
float angle = mirror * MathHelper.TwoPi / 6f + p * 0.4f;
DrawDiamond(batch, pixel, center + angle.ToRotationVector2() * (radius + 25f), 16f,
primary * (opacity * 0.66f), 3f, angle);
}
}
if (v.Level >= 3)
{
DrawPolygon(batch, pixel, center, radius * 1.28f, 8, secondary * (opacity * 0.8f), 6f, p * 0.2f);
DrawCross(batch, pixel, center, radius * 1.18f, Color.White with { A = 0 } * opacity, 4f, MathHelper.PiOver4);
}
}
private static void DrawFrostRefraction(SpriteBatch batch, Texture2D pixel, ReaperSkillVisual v, Vector2 center,
float p, float opacity, Color primary, Color secondary)
{
int mirrors = v.Level switch { 1 => 1, 2 => 3, _ => 6 };
for (int i = 0; i < mirrors; i++)
{
float angle = i * MathHelper.TwoPi / mirrors + p * 0.65f;
Vector2 mirror = center + angle.ToRotationVector2() * (42f + v.Level * 10f);
DrawPolygon(batch, pixel, mirror, 17f + v.Level * 2f, 6,
(i % 2 == 0 ? secondary : primary) * (opacity * 0.72f), 3f, angle);
DrawSegment(batch, pixel, mirror - v.Direction.RotatedBy(angle * 0.08f) * 28f,
mirror + v.Direction.RotatedBy(angle * 0.08f) * 28f, secondary * opacity, 3f);
}
Vector2 cometEnd = center + v.Direction * (70f + v.Level * 28f);
DrawSegment(batch, pixel, center - v.Direction * 45f, cometEnd, secondary * opacity, 7f + v.Level * 2f);
DrawSegment(batch, pixel, center - v.Direction * 58f, cometEnd, Color.White with { A = 0 } * opacity, 2f);
if (v.Level >= 3)
DrawRing(batch, pixel, cometEnd, 38f + p * 18f, 24, primary * opacity, 6f, p);
}
private static void DrawFrostExecution(SpriteBatch batch, Texture2D pixel, ReaperSkillVisual v, Vector2 center,
float p, float opacity, Color primary, Color secondary)
{
float length = 70f + v.Level * 22f;
DrawCross(batch, pixel, center, length, secondary * opacity, 7f + v.Level, MathHelper.PiOver4);
DrawCross(batch, pixel, center, length, Color.White with { A = 0 } * opacity, 2f, MathHelper.PiOver4);
if (v.Level >= 2)
{
float frame = 65f + v.Level * 14f;
DrawPolygon(batch, pixel, center, frame, 4, primary * (opacity * 0.72f), 5f, MathHelper.PiOver4);
DrawSegment(batch, pixel, center - Vector2.UnitX * frame, center + Vector2.UnitX * frame,
primary * (opacity * 0.46f), 3f);
DrawSegment(batch, pixel, center - Vector2.UnitY * frame, center + Vector2.UnitY * frame,
primary * (opacity * 0.46f), 3f);
}
if (v.Level >= 3)
{
DrawPolygon(batch, pixel, center, 82f, 6, secondary * (opacity * 0.64f), 8f, MathHelper.PiOver2);
for (int crack = 0; crack < 6; crack++)
{
Vector2 direction = (crack * MathHelper.TwoPi / 6f + 0.2f).ToRotationVector2();
DrawSegment(batch, pixel, center + direction * 62f,
center + direction * (108f + crack % 2 * 16f), Color.White with { A = 0 } * (opacity * 0.72f), 1.7f);
}
}
}
private static void DrawSoulEcho(SpriteBatch batch, Texture2D pixel, ReaperSkillVisual v, Vector2 center,
float p, float opacity, Color primary, Color secondary)
{
int ghosts = v.Level;
for (int i = 0; i < ghosts; i++)
{
Vector2 offset = -v.Direction * (28f + i * 29f) + v.Direction.RotatedBy(MathHelper.PiOver2) * (i - (ghosts - 1) * 0.5f) * 15f;
Vector2 ghost = center + offset;
DrawGhost(batch, pixel, ghost, v.Direction, primary * (opacity * (0.52f + i * 0.09f)),
secondary * (opacity * 0.82f), 1f + i * 0.12f);
DrawArc(batch, pixel, ghost, 45f + i * 6f, v.Direction.ToRotation() - 1.7f,
2.8f, 14, secondary * opacity, 4f);
}
if (v.Level >= 3 && p > 0.36f)
{
Vector2 giant = center - Vector2.UnitY * 64f;
DrawGhost(batch, pixel, giant, v.Direction, primary * (opacity * 0.45f), secondary * opacity, 1.7f);
DrawArc(batch, pixel, giant, 88f, v.Direction.ToRotation() - 1.75f, 2.9f, 24,
secondary * opacity, 7f);
}
}
private static void DrawSoulFamiliar(SpriteBatch batch, Texture2D pixel, ReaperSkillVisual v, Vector2 center,
float p, float opacity, Color primary, Color secondary)
{
float scale = v.Level switch { 1 => 1f, 2 => 1.28f, _ => 1.58f };
DrawGhost(batch, pixel, center - Vector2.UnitY * 32f, v.Direction,
primary * (opacity * 0.62f), secondary * opacity, scale);
int gates = v.Level == 1 ? 1 : v.Level == 2 ? 2 : 3;
for (int i = 0; i < gates; i++)
{
float angle = i * MathHelper.TwoPi / gates + p;
Vector2 gate = center + angle.ToRotationVector2() * (44f + v.Level * 13f);
DrawArc(batch, pixel, gate, 20f + v.Level * 4f, -MathHelper.PiOver2, MathHelper.Pi,
14, primary * (opacity * 0.64f), 5f);
}
int slashes = v.Level switch { 1 => 2, 2 => 3, _ => 4 };
for (int i = 0; i < slashes; i++)
{
float angle = v.Direction.ToRotation() + (i - (slashes - 1) * 0.5f) * 0.42f;
Vector2 direction = angle.ToRotationVector2();
DrawSegment(batch, pixel, center - direction * 18f, center + direction * (55f + v.Level * 17f),
secondary * (opacity * 0.76f), 4f + v.Level);
}
if (v.Level >= 3)
DrawRing(batch, pixel, center, 76f, 32, primary * (opacity * 0.55f), 6f, -p * 1.8f);
}
private static void DrawSoulFate(SpriteBatch batch, Texture2D pixel, ReaperSkillVisual v, Vector2 center,
float p, float opacity, Color primary, Color secondary)
{
if (v.Variant > 0)
{
float collapse = 1f - MathHelper.SmoothStep(0f, 1f,
MathHelper.Clamp((p - 0.18f) / 0.55f, 0f, 1f));
DrawRing(batch, pixel, center, (54f + v.Level * 15f) * collapse,
34, primary * (opacity * 0.74f), 8f, -p * 1.5f);
Vector2 slash = v.Direction.SafeNormalize(Vector2.UnitX);
DrawSegment(batch, pixel, center - slash * 84f, center + slash * 84f,
new Color(28, 8, 48, 0) * opacity, 18f);
DrawSegment(batch, pixel, center - slash * 88f, center + slash * 88f,
secondary * opacity, 5f);
if (v.Level >= 2)
{
Vector2 cross = slash.RotatedBy(MathHelper.PiOver2);
DrawSegment(batch, pixel, center - cross * 70f, center + cross * 70f,
primary * opacity, 4.5f);
}
return;
}
int runes = v.Level switch { 1 => 3, 2 => 5, _ => 7 };
float radius = 44f + v.Level * 11f;
for (int i = 0; i < runes; i++)
{
float angle = i * MathHelper.TwoPi / runes - p * 0.8f;
Vector2 rune = center + angle.ToRotationVector2() * radius;
Vector2 radial = angle.ToRotationVector2();
Vector2 tangent = radial.RotatedBy(MathHelper.PiOver2);
DrawSegment(batch, pixel, rune - tangent * 7f, rune + tangent * 7f, secondary * opacity, 2f);
DrawSegment(batch, pixel, rune - radial * 5f, rune + radial * 8f, primary * opacity, 3f);
if (v.Level >= 2)
DrawDiamond(batch, pixel, rune, 9f, primary * (opacity * 0.45f), 1.5f, angle);
}
if (v.Level >= 2)
{
Vector2 penStart = center + new Vector2(-55f, -55f);
Vector2 penEnd = center + new Vector2(48f, 44f);
DrawSegment(batch, pixel, penStart, penEnd, secondary * opacity, 3f);
DrawDiamond(batch, pixel, penEnd, 8f, primary * opacity, 2f, (penEnd - penStart).ToRotation());
}
if (v.Level >= 3)
{
Vector2 topLeft = center + new Vector2(-58f, -76f);
Vector2 topRight = center + new Vector2(58f, -76f);
Vector2 bottomLeft = center + new Vector2(-58f, 70f);
Vector2 bottomRight = center + new Vector2(58f, 70f);
DrawSegment(batch, pixel, topLeft, topRight, primary * (opacity * 0.62f), 7f);
DrawSegment(batch, pixel, topLeft, bottomLeft, primary * (opacity * 0.62f), 7f);
DrawSegment(batch, pixel, topRight, bottomRight, primary * (opacity * 0.62f), 7f);
DrawSegment(batch, pixel, bottomLeft, bottomRight, primary * (opacity * 0.62f), 7f);
DrawSegment(batch, pixel, center + new Vector2(-74f, 42f), center + new Vector2(76f, -35f),
secondary * opacity, 9f);
}
}
private static void DrawVoidIntersection(SpriteBatch batch, Texture2D pixel, ReaperSkillVisual v, Vector2 center,
float p, float opacity, Color primary, Color secondary)
{
float length = 92f + v.Level * 22f;
float width = 7f + v.Level * 2f;
float reveal = MathHelper.SmoothStep(0f, 1f, MathHelper.Clamp(p / 0.28f, 0f, 1f));
Vector2 facing = v.Direction.SafeNormalize(Vector2.UnitX);
ReaperVoidBackdropRenderer.DrawHitRift(batch, center, facing.RotatedBy(-0.54f),
length, width, v.Seed, reveal, opacity);
ReaperVoidBackdropRenderer.DrawHitRift(batch, center, facing.RotatedBy(0.57f),
length * (v.Level >= 2 ? 0.94f : 0.78f), width * 0.88f,
v.Seed ^ 0x2718, reveal, opacity * 0.92f);
}
private static void DrawVoidResidual(SpriteBatch batch, Texture2D pixel, ReaperSkillVisual v, Vector2 center,
float p, float opacity, Color primary, Color secondary)
{
int cuts = v.Level;
Vector2 tangent = v.Direction.RotatedBy(MathHelper.PiOver2);
for (int i = 0; i < cuts; i++)
{
float offset = (i - (cuts - 1) * 0.5f) * 12f;
Vector2 origin = center + tangent * offset + v.Direction * (i * 5f);
Vector2 cutDirection = v.Direction.RotatedBy((i - (cuts - 1) * 0.5f) * 0.14f);
float length = 104f + v.Level * 18f - i * 6f;
ReaperVoidBackdropRenderer.DrawHitRift(batch, origin, cutDirection,
length, 7f + v.Level * 1.6f, v.Seed + i * 977,
MathHelper.SmoothStep(0f, 1f, MathHelper.Clamp(p / (0.22f + i * 0.04f), 0f, 1f)),
opacity * (1f - i * 0.10f));
}
if (v.Level >= 3)
{
float collapse = 1f - MathHelper.SmoothStep(0f, 1f, MathHelper.Clamp((p - 0.42f) / 0.3f, 0f, 1f));
DrawSegment(batch, pixel, center - v.Direction * 94f * collapse, center + v.Direction * 94f * collapse,
secondary * (opacity * 0.72f), 1.4f);
}
}
private static void DrawVoidCoordinate(SpriteBatch batch, Texture2D pixel, ReaperSkillVisual v, Vector2 center,
float p, float opacity, Color primary, Color secondary)
{
float radius = v.Level switch { 1 => 40f, 2 => 58f, _ => 82f };
float collapse = v.Variant > 0
? 1f - MathHelper.SmoothStep(0f, 1f, MathHelper.Clamp(p * 1.5f, 0f, 1f))
: 1f;
radius *= Math.Max(0.08f, collapse);
DrawCross(batch, pixel, center, radius, primary * opacity, 3f, 0f);
DrawRing(batch, pixel, center, radius * 0.72f, 28, secondary * opacity, 2.5f, p);
if (v.Level >= 2)
{
DrawDiamond(batch, pixel, center, radius, primary * opacity, 3f, p * 0.7f);
DrawDiamond(batch, pixel, center + new Vector2(9f, -9f), radius, secondary * (opacity * 0.62f), 2f, -p * 0.7f);
DrawSegment(batch, pixel, center + new Vector2(-radius, -radius), center + new Vector2(-radius + 9f, -radius - 9f), primary * opacity, 2f);
DrawSegment(batch, pixel, center + new Vector2(radius, radius), center + new Vector2(radius + 9f, radius - 9f), primary * opacity, 2f);
}
if (v.Level >= 3)
{
for (int grid = -1; grid <= 1; grid++)
{
float offset = grid * radius * 0.42f;
DrawSegment(batch, pixel, center + new Vector2(-radius, offset), center + new Vector2(radius, offset),
primary * (opacity * 0.38f), 1.3f);
DrawSegment(batch, pixel, center + new Vector2(offset, -radius), center + new Vector2(offset, radius),
primary * (opacity * 0.38f), 1.3f);
}
if (v.Variant > 0 && p > 0.45f)
{
DrawSegment(batch, pixel, center - Vector2.UnitX * 100f, center + Vector2.UnitX * 100f, secondary * opacity, 3f);
DrawSegment(batch, pixel, center - Vector2.UnitY * 100f, center + Vector2.UnitY * 100f, secondary * opacity, 3f);
Vector2 depth = new Vector2(1f, -0.62f).SafeNormalize(Vector2.UnitX);
DrawSegment(batch, pixel, center - depth * 100f, center + depth * 100f, secondary * opacity, 3f);
}
}
}
private static void DrawGhost(SpriteBatch batch, Texture2D pixel, Vector2 center, Vector2 facing,
Color body, Color edge, float scale)
{
facing = facing.SafeNormalize(Vector2.UnitX);
Vector2 tangent = facing.RotatedBy(MathHelper.PiOver2);
DrawArc(batch, pixel, center, 14f * scale, MathHelper.Pi, MathHelper.Pi, 14, edge, 3f * scale);
DrawSegment(batch, pixel, center - tangent * 13f * scale, center + facing * 4f,
edge, 2.5f * scale);
DrawSegment(batch, pixel, center + tangent * 13f * scale, center + facing * 4f,
edge, 2.5f * scale);
DrawSegment(batch, pixel, center + facing * 3f, center + facing * 38f * scale,
body, 12f * scale);
DrawSegment(batch, pixel, center + facing * 25f * scale - tangent * 14f * scale,
center + facing * 25f * scale + tangent * 14f * scale, edge * 0.72f, 2f * scale);
}
private static void DrawChain(SpriteBatch batch, Texture2D pixel, Vector2 start, Vector2 end,
Color outer, Color core, int links)
{
Vector2 delta = end - start;
for (int i = 0; i < links; i++)
{
float t0 = i / (float)links;
float t1 = (i + 0.72f) / links;
Vector2 linkStart = start + delta * t0;
Vector2 linkEnd = start + delta * t1;
DrawSegment(batch, pixel, linkStart, linkEnd, outer, i % 2 == 0 ? 4f : 2.4f);
DrawSegment(batch, pixel, linkStart, linkEnd, core * 0.72f, 1.2f);
}
}
private static void DrawCross(SpriteBatch batch, Texture2D pixel, Vector2 center, float radius,
Color color, float width, float rotation)
{
Vector2 first = rotation.ToRotationVector2();
Vector2 second = first.RotatedBy(MathHelper.PiOver2);
DrawSegment(batch, pixel, center - first * radius, center + first * radius, color, width);
DrawSegment(batch, pixel, center - second * radius, center + second * radius, color, width);
}
private static void DrawDiamond(SpriteBatch batch, Texture2D pixel, Vector2 center, float radius,
Color color, float width, float rotation)
=> DrawPolygon(batch, pixel, center, radius, 4, color, width, rotation + MathHelper.PiOver4);
private static void DrawStar(SpriteBatch batch, Texture2D pixel, Vector2 center, float outerRadius,
float innerRadius, int points, Color color, float width, float rotation)
{
int vertices = points * 2;
Vector2 previous = center + rotation.ToRotationVector2() * outerRadius;
for (int i = 1; i <= vertices; i++)
{
float angle = rotation + i * MathHelper.TwoPi / vertices;
float radius = (i & 1) == 0 ? outerRadius : innerRadius;
Vector2 next = center + angle.ToRotationVector2() * radius;
DrawSegment(batch, pixel, previous, next, color, width);
previous = next;
}
}
private static void DrawPolygon(SpriteBatch batch, Texture2D pixel, Vector2 center, float radius,
int sides, Color color, float width, float rotation)
{
Vector2 previous = center + rotation.ToRotationVector2() * radius;
for (int i = 1; i <= sides; i++)
{
Vector2 next = center + (rotation + i * MathHelper.TwoPi / sides).ToRotationVector2() * radius;
DrawSegment(batch, pixel, previous, next, color, width);
previous = next;
}
}
private static void DrawRing(SpriteBatch batch, Texture2D pixel, Vector2 center, float radius,
int segments, Color color, float width, float rotation)
=> DrawArc(batch, pixel, center, radius, rotation, MathHelper.TwoPi, segments, color, width);
private static void DrawArc(SpriteBatch batch, Texture2D pixel, Vector2 center, float radius,
float startAngle, float sweep, int segments, Color color, float width)
{
if (radius <= 0.1f || segments <= 0)
return;
segments = Math.Max(segments,
Math.Min(160, (int)Math.Ceiling(Math.Abs(sweep) * radius / 18f)));
Vector2 previous = center + startAngle.ToRotationVector2() * radius;
for (int i = 1; i <= segments; i++)
{
Vector2 next = center + (startAngle + sweep * i / segments).ToRotationVector2() * radius;
DrawSegment(batch, pixel, previous, next, color, width);
previous = next;
}
}
private static void DrawSegment(SpriteBatch batch, Texture2D pixel, Vector2 start, Vector2 end,
Color color, float width)
{
DeathDomainPrimitiveTextureSystem.DrawSmoothLine(batch,
start - Main.screenPosition, end - Main.screenPosition, color, width);
}
}