using SoulHarvest.Common; using Microsoft.Xna.Framework; using System; using System.IO; using Terraria; using Terraria.ID; using Terraria.ModLoader; namespace SoulHarvest.Projectiles; /// A persistent black wound with a smooth red rim. public sealed class ReaperBloodScarProjectile : ModProjectile { private SickleCombatSnapshot snapshot; private float scarLength; private float scarWidth; private int lifetime; private int seed; private int age; 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 center, Vector2 direction, float length, float width, int lifetime, float damageMultiplier, int phase, int actionId, int exactDamage = 0) { if (Main.netMode == NetmodeID.MultiplayerClient) return -1; direction = direction.SafeNormalize(Vector2.UnitX); int damage = exactDamage > 0 ? exactDamage : Math.Max(1, (int)Math.Round(snapshot.Damage * damageMultiplier)); int index = Projectile.NewProjectile(source, center, direction, ModContent.ProjectileType(), damage, snapshot.Knockback * 0.12f, owner); if (index < 0 || index >= Main.maxProjectiles) return -1; Projectile projectile = Main.projectile[index]; projectile.originalDamage = snapshot.Damage; if (exactDamage > 0) projectile.ArmorPenetration = 1000; int stableSeed = unchecked(actionId * 16777619 + phase * 486187739 + owner * 97); if (projectile.ModProjectile is ReaperBloodScarProjectile scar) scar.Configure(snapshot, length, width, lifetime, stableSeed); projectile.GetGlobalProjectile().ConfigureReaperProjectile( projectile, snapshot, ReaperHitKind.DamageOverTime, phase, direction.ToRotation(), actionId); projectile.localNPCHitCooldown = 18; 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 = 18; } 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 * scarLength * 0.5f; Vector2 end = Projectile.Center + axis * scarLength * 0.5f; float collisionPoint = 0f; return Collision.CheckAABBvLineCollision(targetHitbox.TopLeft(), targetHitbox.Size(), start, end, Math.Max(3f, scarWidth * 0.64f), 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 / 34f); DeathDomainPrimitiveTextureSystem.DrawBloodScar(Projectile.Center, Projectile.rotation, scarLength, scarWidth, fade, reveal); return false; } public override void SendExtraAI(BinaryWriter writer) { writer.Write(configured); if (!configured) return; snapshot.Write(writer); writer.Write(scarLength); writer.Write(scarWidth); writer.Write((short)Math.Clamp(lifetime, 1, short.MaxValue)); 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; scarLength = incomingLength; scarWidth = 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; scarLength = MathHelper.Clamp(length, 32f, 2400f); scarWidth = MathHelper.Clamp(width, 5f, 180f); lifetime = Math.Clamp(activeLifetime, 30, 600); 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); } }