using SoulHarvest.Common; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System; using Terraria; using Terraria.GameContent; using Terraria.ID; using Terraria.ModLoader; namespace SoulHarvest.Projectiles; /// /// A non-damaging, server-authoritative Soul energy pickup. Energy is awarded only /// after the shard reaches its owner; changing form before then destroys it. /// public sealed class ReaperSoulShardProjectile : ModProjectile { private bool serverAuthorized; private Vector2 authoritativeCenter; private Vector2 authoritativeVelocity; private int timer; public override string Texture => "Terraria/Images/Projectile_0"; public static bool Spawn(Terraria.DataStructures.IEntitySource source, Player owner, Vector2 origin) { if (Main.netMode == NetmodeID.MultiplayerClient || !owner.active || owner.dead) return false; int index = Projectile.NewProjectile(source, origin, Vector2.Zero, ModContent.ProjectileType(), 0, 0f, owner.whoAmI); if (index < 0 || index >= Main.maxProjectiles) return false; Projectile projectile = Main.projectile[index]; if (projectile.ModProjectile is not ReaperSoulShardProjectile shard) { projectile.Kill(); return false; } shard.serverAuthorized = true; shard.authoritativeCenter = projectile.Center; shard.authoritativeVelocity = Vector2.Zero; projectile.GetGlobalProjectile().AuthorizeServerReaperProjectile(projectile); ReaperProjectileHelper.SyncNewProjectile(projectile); return true; } public override void SetDefaults() { Projectile.width = 12; Projectile.height = 12; Projectile.friendly = false; Projectile.hostile = false; Projectile.tileCollide = false; Projectile.ignoreWater = true; Projectile.penetrate = -1; Projectile.timeLeft = 120; // PreDraw supplies the orb itself; keeping the projectile hidden prevented // Terraria from ever placing it in a draw cache. Projectile.hide = false; Projectile.netImportant = true; } public override bool ShouldUpdatePosition() => false; public override bool? CanDamage() => false; public override void AI() { if (Main.netMode == NetmodeID.Server) { if (!serverAuthorized) { Projectile.Kill(); return; } Projectile.Center = authoritativeCenter; Projectile.velocity = authoritativeVelocity; } if (Projectile.owner < 0 || Projectile.owner >= Main.maxPlayers) { Projectile.Kill(); return; } Player owner = Main.player[Projectile.owner]; if (!owner.active || owner.dead) { Projectile.Kill(); return; } if (Main.netMode != NetmodeID.MultiplayerClient && ReaperCombatRegistry.ResolveUsableForm(owner.GetModPlayer().ReaperProgression) != ReaperFormId.Soul) { Projectile.Kill(); return; } timer++; Vector2 toOwner = owner.MountedCenter - Projectile.Center; if (Main.netMode != NetmodeID.MultiplayerClient && toOwner.LengthSquared() <= 24f * 24f) { Projectile.Kill(); return; } float speed = MathHelper.Lerp(7f, 18f, MathHelper.Clamp(timer / 45f, 0f, 1f)); Vector2 desired = toOwner.SafeNormalize(Vector2.UnitY * -1f) * speed; Projectile.velocity = Vector2.Lerp(Projectile.velocity, desired, 0.16f); Projectile.position += Projectile.velocity; Projectile.rotation = Projectile.velocity.ToRotation(); if (Main.netMode == NetmodeID.Server) { authoritativeCenter = Projectile.Center; authoritativeVelocity = Projectile.velocity; if (timer % 10 == 0) Projectile.netUpdate = true; } else { Lighting.AddLight(Projectile.Center, new Vector3(0.12f, 0.32f, 0.58f)); if (Main.rand.NextBool(2)) { Dust dust = Dust.NewDustPerfect(Projectile.Center, DustID.AncientLight, -Projectile.velocity * 0.08f, 60, Main.rand.NextBool() ? new Color(86, 229, 255) : new Color(173, 91, 255), 0.8f); dust.noGravity = true; } } } public override bool PreDraw(ref Color lightColor) { Color outer = new Color(116, 78, 255, 0); Color core = new Color(130, 245, 255, 0); float pulse = 0.82f + (float)Math.Sin(Main.GlobalTimeWrappedHourly * 8f + Projectile.identity) * 0.15f; Vector2 center = Projectile.Center - Main.screenPosition; Vector2 axis = Projectile.rotation.ToRotationVector2(); DeathDomainPrimitiveTextureSystem.DrawSmoothLine(Main.spriteBatch, center - axis * 6.5f, center + axis * 6.5f, outer * pulse, 7f); DeathDomainPrimitiveTextureSystem.DrawSmoothLine(Main.spriteBatch, center - axis * 3f, center + axis * 3f, core * pulse, 3f); return false; } }