using SoulHarvest.Common; using Microsoft.Xna.Framework; using System; using System.IO; using Terraria; using Terraria.ID; using Terraria.ModLoader; namespace SoulHarvest.Projectiles; /// A continuous molten route left by Infernal movement and dash attacks. public sealed class ReaperInfernalTrailProjectile : ModProjectile { private SickleCombatSnapshot snapshot; private float routeLength; private float routeWidth; private int lifetime; private int age; private int seed; private bool configured; private bool serverAuthorized; public override string Texture => "Terraria/Images/Projectile_0"; internal static int Spawn(Terraria.DataStructures.IEntitySource source, int owner, in SickleCombatSnapshot snapshot, Vector2 start, Vector2 end, float width, int phase, int actionId) { if (Main.netMode == NetmodeID.MultiplayerClient) return -1; Vector2 path = end - start; float length = path.Length(); if (length < 8f) return -1; Vector2 axis = path / length; int damage = Math.Max(1, (int)Math.Round(snapshot.Damage * 0.25f)); int index = Projectile.NewProjectile(source, (start + end) * 0.5f, axis, ModContent.ProjectileType(), damage, snapshot.Knockback * 0.10f, owner); if (index < 0 || index >= Main.maxProjectiles) return -1; Projectile projectile = Main.projectile[index]; projectile.originalDamage = snapshot.Damage; int skillLevel = Math.Clamp(Math.Max(1, (int)snapshot.Skill1), 1, 3); int activeLifetime = skillLevel switch { 1 => 90, 2 => 120, _ => 150 }; int stableSeed = unchecked(actionId * 486187739 + phase * 16777619 + owner * 71); if (projectile.ModProjectile is ReaperInfernalTrailProjectile trail) trail.Configure(snapshot, length, width, activeLifetime, stableSeed); projectile.GetGlobalProjectile().ConfigureReaperProjectile( projectile, snapshot, ReaperHitKind.DamageOverTime, phase, axis.ToRotation(), actionId); projectile.localNPCHitCooldown = 30; ReaperProjectileHelper.SyncNewProjectile(projectile); return index; } public override void SetDefaults() { Projectile.width = 4; Projectile.height = 4; Projectile.friendly = true; Projectile.hostile = false; Projectile.tileCollide = false; Projectile.ignoreWater = true; Projectile.penetrate = -1; Projectile.timeLeft = 150; Projectile.netImportant = true; Projectile.DamageType = DamageClass.Melee; Projectile.usesLocalNPCImmunity = true; Projectile.localNPCHitCooldown = 30; } public override bool ShouldUpdatePosition() => false; public override bool? CanDamage() => configured && age >= 4 && Projectile.timeLeft > 15 ? null : false; public override void AI() { if (Main.netMode == NetmodeID.Server && !serverAuthorized) { Projectile.Kill(); return; } if (!configured) return; age++; Projectile.rotation = Projectile.velocity.ToRotation(); } public override bool? Colliding(Rectangle projHitbox, Rectangle targetHitbox) { Vector2 axis = Projectile.velocity.SafeNormalize(Vector2.UnitX); Vector2 start = Projectile.Center - axis * routeLength * 0.5f; Vector2 end = Projectile.Center + axis * routeLength * 0.5f; float collisionPoint = 0f; return Collision.CheckAABBvLineCollision(targetHitbox.TopLeft(), targetHitbox.Size(), start, end, routeWidth * 0.62f, ref collisionPoint); } public override bool PreDraw(ref Color lightColor) { if (!configured || Main.dedServ) return false; float reveal = Smooth01(age / 9f); float fade = Smooth01(Projectile.timeLeft / 30f); float pulse = 0.94f + (float)Math.Sin(age * 0.11f + seed * 0.001f) * 0.06f; Vector2 center = Projectile.Center - Main.screenPosition; DeathDomainPrimitiveTextureSystem.DrawBladeCentered(center, Projectile.rotation, routeLength, routeWidth * 1.55f, new Color(8, 2, 0, 225) * fade, reveal); DeathDomainPrimitiveTextureSystem.DrawBladeCentered(center, Projectile.rotation, routeLength, routeWidth * pulse, new Color(245, 48, 3, 0) * (fade * 0.86f), reveal); DeathDomainPrimitiveTextureSystem.DrawBladeCentered(center, Projectile.rotation, routeLength, Math.Max(1.5f, routeWidth * 0.24f), new Color(255, 225, 94, 0) * fade, reveal); Vector2 axis = Projectile.velocity.SafeNormalize(Vector2.UnitX); Vector2 start = Projectile.Center - axis * routeLength * 0.5f; for (int mote = 0; mote < 5; mote++) { float amount = (age * 0.019f + mote / 5f + seed * 0.00013f) % 1f; Vector2 point = start + axis * routeLength * amount; float radius = 1.8f + mote % 2 * 0.7f; DeathDomainPrimitiveTextureSystem.DrawSmoothDisc(Main.spriteBatch, point - Main.screenPosition, radius + 2f, new Color(255, 52, 5, 0) * (fade * 0.42f)); DeathDomainPrimitiveTextureSystem.DrawSmoothDisc(Main.spriteBatch, point - Main.screenPosition, radius, new Color(255, 231, 120, 0) * (fade * 0.92f)); } return false; } public override void SendExtraAI(BinaryWriter writer) { writer.Write(configured); if (!configured) return; snapshot.Write(writer); writer.Write(routeLength); writer.Write(routeWidth); writer.Write((short)lifetime); writer.Write(seed); writer.Write((short)Math.Clamp(age, 0, short.MaxValue)); } public override void ReceiveExtraAI(BinaryReader reader) { bool incoming = reader.ReadBoolean(); if (!incoming) { if (Main.netMode != NetmodeID.Server) configured = false; return; } SickleCombatSnapshot incomingSnapshot = SickleCombatSnapshot.Read(reader); float incomingLength = reader.ReadSingle(); float incomingWidth = reader.ReadSingle(); int incomingLifetime = reader.ReadInt16(); int incomingSeed = reader.ReadInt32(); int incomingAge = reader.ReadInt16(); if (Main.netMode == NetmodeID.Server) return; snapshot = incomingSnapshot; routeLength = incomingLength; routeWidth = incomingWidth; lifetime = incomingLifetime; seed = incomingSeed; age = incomingAge; configured = true; } private void Configure(in SickleCombatSnapshot value, float length, float width, int activeLifetime, int shapeSeed) { snapshot = value; routeLength = MathHelper.Clamp(length, 8f, 1600f); routeWidth = MathHelper.Clamp(width, 6f, 80f); lifetime = Math.Clamp(activeLifetime, 45, 240); seed = shapeSeed; age = 0; configured = true; serverAuthorized = Main.netMode != NetmodeID.MultiplayerClient; Projectile.timeLeft = lifetime; } private static float Smooth01(float value) { value = MathHelper.Clamp(value, 0f, 1f); return value * value * (3f - 2f * value); } }