using SoulHarvest.Common; using Microsoft.Xna.Framework; using System; using System.IO; using Terraria; using Terraria.DataStructures; using Terraria.ID; using Terraria.ModLoader; namespace SoulHarvest.Projectiles; /// /// The thin crimson cut lines placed on every enemy awaiting the next domain harvest. /// Their deterministic rotations are reused by the actual blades so each execution /// lands exactly on its own warning line. /// public class DeathDomainHarvestTelegraphProjectile : ModProjectile { private int TargetIndex => (int)Projectile.ai[0]; private int TelegraphFrames => Math.Clamp((int)Projectile.ai[1], 1, 180); private int SlashCount => Math.Clamp((int)Projectile.ai[2], 1, 5); private float Mastery => MathHelper.Clamp((Projectile.ai[2] - SlashCount) * 10f, 0f, 1f); public override string Texture => "Terraria/Images/Projectile_0"; public override void SetStaticDefaults() { ProjectileID.Sets.DrawScreenCheckFluff[Type] = 3600; } public override void SetDefaults() { Projectile.width = 2; Projectile.height = 2; Projectile.friendly = false; Projectile.hostile = false; Projectile.tileCollide = false; Projectile.ignoreWater = true; Projectile.penetrate = -1; Projectile.timeLeft = 120; Projectile.netImportant = false; } public override void OnSpawn(IEntitySource source) { Projectile.timeLeft = TelegraphFrames + 6; } public override bool? CanDamage() => false; public override void SendExtraAI(BinaryWriter writer) { writer.Write(Projectile.rotation); writer.Write(Projectile.scale); } public override void ReceiveExtraAI(BinaryReader reader) { Projectile.rotation = reader.ReadSingle(); Projectile.scale = reader.ReadSingle(); } public override void AI() { if (TargetIndex >= 0 && TargetIndex < Main.maxNPCs && Main.npc[TargetIndex].active) Projectile.Center = Main.npc[TargetIndex].Center; if (Main.dedServ || !Main.rand.NextBool(18)) return; int slash = Main.rand.Next(SlashCount); float rotation = DeathDomainHarvestSlashProjectile.GetSlashRotation(Projectile.rotation, slash); float halfLength = DeathDomainHarvestSlashProjectile.GetSlashLength(Mastery, Projectile.scale, slash) * 0.5f; Vector2 direction = rotation.ToRotationVector2(); Vector2 end = Projectile.Center + direction * (Main.rand.NextBool() ? halfLength : -halfLength); Dust dust = Dust.NewDustPerfect( end, DustID.RedTorch, direction.RotatedByRandom(0.45f) * Main.rand.NextFloat(0.25f, 1.2f), 95, new Color(255, 15, 50), Main.rand.NextFloat(0.45f, 0.8f)); dust.noGravity = true; } public override bool PreDraw(ref Color lightColor) { float remaining = MathHelper.Clamp((Projectile.timeLeft - 5f) / Math.Max(1f, TelegraphFrames), 0f, 1f); float urgency = 1f - remaining; float pulse = 0.72f + (float)Math.Sin(Main.GlobalTimeWrappedHourly * (7f + urgency * 9f) + Projectile.identity) * 0.20f; Vector2 center = Projectile.Center - Main.screenPosition; for (int slash = 0; slash < SlashCount; slash++) { float rotation = DeathDomainHarvestSlashProjectile.GetSlashRotation(Projectile.rotation, slash); float length = DeathDomainHarvestSlashProjectile.GetSlashLength(Mastery, Projectile.scale, slash); float linePulse = pulse * (0.90f + 0.10f * (float)Math.Sin(slash * 1.9f + Main.GlobalTimeWrappedHourly * 3f)); DrawTaperedLine(center, rotation, length, (6f + Mastery * 2f) * Projectile.scale, new Color(120, 0, 25, 0) * (0.24f + urgency * 0.22f)); DrawTaperedLine(center, rotation, length, (1.35f + urgency * 0.85f) * Projectile.scale, new Color(255, 8, 48, 0) * linePulse); } return false; } private static void DrawTaperedLine(Vector2 center, float rotation, float length, float maximumWidth, Color color) { Vector2 start = center - rotation.ToRotationVector2() * length * 0.5f; DeathDomainPrimitiveTextureSystem.DrawBlade(start, rotation, length, maximumWidth, color); } }