using SoulHarvest.Common; using Microsoft.Xna.Framework; using System; using System.IO; using Terraria; using Terraria.ID; using Terraria.ModLoader; namespace SoulHarvest.Projectiles; /// /// Target-locked release hit for the Blood special. Its authoritative damage is /// the total life drained during the charge; the persistent scar is a separate /// actor worth half of that amount on every damage pulse. /// public sealed class ReaperBloodExecutionProjectile : ModProjectile { private SickleCombatSnapshot snapshot; private int targetIndex = -1; private int phase; private int actionId; private float length; private float width; 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, NPC target, Vector2 direction, float length, float width, int damage, int phase, int actionId) { if (Main.netMode == NetmodeID.MultiplayerClient || damage <= 0) return -1; direction = direction.SafeNormalize(Vector2.UnitX); int index = Projectile.NewProjectile(source, target.Center, direction, ModContent.ProjectileType(), damage, snapshot.Knockback * 0.35f, owner); if (index < 0 || index >= Main.maxProjectiles) return -1; Projectile projectile = Main.projectile[index]; projectile.originalDamage = damage; projectile.ArmorPenetration = 1000; projectile.CritChance = snapshot.CritChance; if (projectile.ModProjectile is ReaperBloodExecutionProjectile execution) execution.Configure(snapshot, target.whoAmI, phase, actionId, length, width); projectile.GetGlobalProjectile().ConfigureReaperProjectile( projectile, snapshot, ReaperHitKind.Special, phase, direction.ToRotation(), actionId); ReaperProjectileHelper.SyncNewProjectile(projectile); return index; } public override void SetDefaults() { Projectile.width = 6; Projectile.height = 6; Projectile.friendly = true; Projectile.hostile = false; Projectile.tileCollide = false; Projectile.ignoreWater = true; Projectile.penetrate = 1; Projectile.timeLeft = 16; Projectile.netImportant = true; Projectile.DamageType = DamageClass.Melee; } public override bool ShouldUpdatePosition() => false; public override bool? CanDamage() => configured && age is >= 4 and <= 6 ? null : false; public override bool? CanHitNPC(NPC target) => configured && target.whoAmI == targetIndex ? null : false; public override void AI() { if (Main.netMode == NetmodeID.Server && !serverAuthorized) { Projectile.Kill(); return; } if (!configured) return; age++; if (targetIndex >= 0 && targetIndex < Main.maxNPCs && Main.npc[targetIndex].active) { Projectile.Center = Main.npc[targetIndex].Center; } } public override bool? Colliding(Rectangle projHitbox, Rectangle targetHitbox) => configured && targetIndex >= 0 && targetIndex < Main.maxNPCs && Main.npc[targetIndex].active && Main.npc[targetIndex].Hitbox.Intersects(targetHitbox); public override bool PreDraw(ref Color lightColor) { if (!configured || Main.dedServ) return false; float reveal = MathHelper.Clamp(age / 5f, 0f, 1f); float fade = 1f - MathHelper.Clamp((age - 7f) / 8f, 0f, 1f); DeathDomainPrimitiveTextureSystem.DrawBloodScar(Projectile.Center, Projectile.velocity.ToRotation(), length, width, fade, reveal); return false; } public override void SendExtraAI(BinaryWriter writer) { writer.Write(configured); if (!configured) return; snapshot.Write(writer); writer.Write((short)targetIndex); writer.Write((byte)Math.Clamp(phase, 0, byte.MaxValue)); writer.Write(actionId); writer.Write(length); writer.Write(width); writer.Write((byte)Math.Clamp(age, 0, byte.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); int incomingTarget = reader.ReadInt16(); int incomingPhase = reader.ReadByte(); int incomingAction = reader.ReadInt32(); float incomingLength = reader.ReadSingle(); float incomingWidth = reader.ReadSingle(); int incomingAge = reader.ReadByte(); if (Main.netMode == NetmodeID.Server) return; snapshot = incomingSnapshot; targetIndex = incomingTarget; phase = incomingPhase; actionId = incomingAction; length = incomingLength; width = incomingWidth; age = incomingAge; configured = true; } private void Configure(in SickleCombatSnapshot value, int target, int hitPhase, int parentActionId, float slashLength, float slashWidth) { snapshot = value; targetIndex = target; phase = hitPhase; actionId = parentActionId; length = MathHelper.Clamp(slashLength, 48f, 560f); width = MathHelper.Clamp(slashWidth, 7f, 96f); age = 0; configured = true; serverAuthorized = Main.netMode != NetmodeID.MultiplayerClient; } }