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; public class LifeStealWispProjectile : ModProjectile { private const int TrailHistoryLength = 32; private int LifeStealLevel => (int)Projectile.ai[0]; public override void SetStaticDefaults() { ProjectileID.Sets.TrailCacheLength[Type] = TrailHistoryLength; ProjectileID.Sets.TrailingMode[Type] = 2; } public override void SetDefaults() { Projectile.width = 8; Projectile.height = 8; Projectile.friendly = false; Projectile.hostile = false; Projectile.tileCollide = false; Projectile.ignoreWater = true; Projectile.timeLeft = 90; Projectile.penetrate = -1; // Modern Blood Covenant healing is resolved on the server. Keep the // short-lived feedback projectile important so its spawn is not skipped // before the owning client can render the return path. Projectile.netImportant = true; Projectile.extraUpdates = 1; Projectile.hide = false; } public override void AI() { Player player = Main.player[Projectile.owner]; if (!player.active || player.dead) { Projectile.Kill(); return; } Projectile.localAI[0]++; float age = Projectile.localAI[0]; Vector2 toPlayer = player.Center - Projectile.Center; float approach = MathHelper.Clamp(age / 58f, 0f, 1f); float speed = MathHelper.Lerp(3.5f, 13f + LifeStealLevel, approach); Vector2 radialDirection = toPlayer.SafeNormalize(Vector2.UnitY); Vector2 tangent = radialDirection.RotatedBy(MathHelper.PiOver2) * (float)System.Math.Sin(age * 0.2f + Projectile.ai[1]) * MathHelper.Lerp(5.5f + LifeStealLevel, 0.7f, approach); Vector2 desired = radialDirection * speed + tangent; Projectile.velocity = Vector2.Lerp(Projectile.velocity, desired, 0.085f + LifeStealLevel * 0.01f); Projectile.rotation = Projectile.velocity.ToRotation(); Color color = Color.Lerp(new Color(215, 20, 45), new Color(255, 105, 195), LifeStealLevel / 5f); if (!Main.dedServ) { Lighting.AddLight(Projectile.Center, color.ToVector3() * (0.18f + LifeStealLevel * 0.06f)); int particleCount = LifeStealLevel >= 4 ? 2 : 1; for (int index = 0; index < particleCount; index++) { if (!Main.rand.NextBool(System.Math.Max(1, 4 - LifeStealLevel / 2))) continue; Vector2 offset = Main.rand.NextVector2Circular(2.5f + LifeStealLevel * 0.35f, 2.5f + LifeStealLevel * 0.35f); Dust dust = Dust.NewDustPerfect(Projectile.Center + offset, DustID.Blood, -Projectile.velocity * Main.rand.NextFloat(0.025f, 0.075f), 80, color, 0.65f + LifeStealLevel * 0.07f + Main.rand.NextFloat(0.12f)); dust.noGravity = true; if (LifeStealLevel >= 3 && Main.rand.NextBool(3)) { Dust core = Dust.NewDustPerfect(Projectile.Center - offset * 0.3f, DustID.AncientLight, -Projectile.velocity * 0.018f, 110, Color.Lerp(color, Color.White, 0.45f), 0.42f + LifeStealLevel * 0.04f); core.noGravity = true; } } } if (toPlayer.LengthSquared() > 16f * 16f) return; if (!Main.dedServ) { for (int index = 0; index < 3 + LifeStealLevel * 2; index++) { Dust dust = Dust.NewDustPerfect(player.Center, DustID.Blood, Main.rand.NextVector2Circular(2.5f, 2.5f), 60, color, 0.8f + Main.rand.NextFloat(0.4f)); dust.noGravity = true; } } Projectile.Kill(); } public override bool PreDraw(ref Color lightColor) { Texture2D texture = TextureAssets.Projectile[Type].Value; Vector2 origin = texture.Size() * 0.5f; float rotation = Projectile.velocity.ToRotation() - MathHelper.PiOver2; float baseScale = 0.34f + LifeStealLevel * 0.045f; int trailPointCount = System.Math.Min(Projectile.oldPos.Length, 14 + LifeStealLevel * 3); float levelRatio = MathHelper.Clamp(LifeStealLevel / 5f, 0f, 1f); Color outerColor = Color.Lerp(new Color(125, 0, 28), new Color(245, 38, 155), levelRatio) with { A = 0 }; Color coreColor = Color.Lerp(new Color(235, 22, 48), new Color(255, 155, 220), levelRatio) with { A = 0 }; for (int index = trailPointCount - 1; index >= 0; index--) { Vector2 older = Projectile.oldPos[index] + Projectile.Size * 0.5f; Vector2 newer = index == 0 ? Projectile.Center : Projectile.oldPos[index - 1] + Projectile.Size * 0.5f; if (Projectile.oldPos[index] == Vector2.Zero || index > 0 && Projectile.oldPos[index - 1] == Vector2.Zero) continue; Vector2 segment = newer - older; float length = segment.Length(); if (length is < 0.35f or > 40f) continue; float strength = 1f - index / (float)trailPointCount; float taper = (float)System.Math.Pow(strength, 0.72f); float outerWidth = (2.25f + LifeStealLevel * 0.58f) * taper; float coreWidth = (0.72f + LifeStealLevel * 0.2f) * taper; DrawTrailSegment(older, segment, outerColor * strength * 0.34f, outerWidth); DrawTrailSegment(older, segment, coreColor * strength * 0.82f, coreWidth); } int afterimageCount = System.Math.Min(trailPointCount, 5 + LifeStealLevel); for (int index = afterimageCount - 1; index >= 1; index--) { if (Projectile.oldPos[index] == Vector2.Zero) continue; float strength = 1f - index / (float)afterimageCount; Vector2 position = Projectile.oldPos[index] + Projectile.Size * 0.5f - Main.screenPosition; Main.EntitySpriteDraw(texture, position, null, coreColor * strength * 0.26f, rotation, origin, baseScale * (0.58f + strength * 0.24f), SpriteEffects.None); } Main.EntitySpriteDraw(texture, Projectile.Center - Main.screenPosition, null, Color.White, rotation, origin, baseScale, SpriteEffects.None); return false; } private static void DrawTrailSegment(Vector2 start, Vector2 segment, Color color, float width) { DeathDomainPrimitiveTextureSystem.DrawSmoothWorldLine(start, start + segment, color, width); } }