using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Terraria; using Terraria.GameContent; using Terraria.ID; using Terraria.ModLoader; namespace SoulHarvest.Projectiles; public class VoidRiftProjectile : ModProjectile { public override void SetStaticDefaults() { ProjectileID.Sets.TrailCacheLength[Type] = 20; ProjectileID.Sets.TrailingMode[Type] = 2; } public override void SetDefaults() { Projectile.width = 40; Projectile.height = 40; Projectile.friendly = true; Projectile.tileCollide = false; Projectile.ignoreWater = true; Projectile.penetrate = -1; Projectile.timeLeft = 210; Projectile.DamageType = DamageClass.Melee; } public override void AI() { Projectile.localAI[0]++; float timer = Projectile.localAI[0]; Projectile.rotation += 0.15f + Projectile.ai[1] * 0.08f; if (timer < 34f) { float curveDirection = System.Math.Sign(Projectile.ai[2] == 0f ? 1f : Projectile.ai[2]); Projectile.velocity = Projectile.velocity.RotatedBy(curveDirection * 0.012f) * 0.965f; Projectile.scale = MathHelper.Lerp(0.65f, 1.05f + Projectile.ai[1] * 0.45f, timer / 34f); } else { Projectile.velocity *= 0.93f; Projectile.scale = 1.05f + Projectile.ai[1] * 0.45f + (float)System.Math.Sin(timer * 0.15f) * 0.08f; NPC? target = ReaperProjectileHelper.FindTarget(Projectile, 620f + Projectile.ai[1] * 220f); if (target is not null) { Vector2 desired = (target.Center - Projectile.Center).SafeNormalize(Vector2.UnitX) * (3.5f + Projectile.ai[1] * 2.5f); Projectile.velocity = Vector2.Lerp(Projectile.velocity, desired, 0.045f); } PullEnemiesIntoRift(timer); } Lighting.AddLight(Projectile.Center, new Vector3(0.48f, 0.05f, 0.72f) * Projectile.scale); int dustCount = Projectile.ai[0] > 0f ? 3 : 1; for (int index = 0; index < dustCount; index++) { Vector2 offset = Main.rand.NextVector2Circular(28f * Projectile.scale, 28f * Projectile.scale); Vector2 velocity = offset.SafeNormalize(Vector2.UnitX).RotatedBy(MathHelper.PiOver2) * 1.5f; Dust dust = Dust.NewDustPerfect(Projectile.Center + offset, DustID.Shadowflame, velocity, 40, new Color(195, 45, 255), 1f); dust.noGravity = true; } } private void PullEnemiesIntoRift(float timer) { float empoweredBonus = Projectile.ai[0] > 0f ? 48f : 0f; float pullRadius = 240f + Projectile.ai[1] * 160f + empoweredBonus; float coreRadius = 18f + Projectile.scale * 12f; foreach (NPC npc in Main.ActiveNPCs) { if (!npc.CanBeChasedBy(Projectile) || IsBossOrBossPart(npc)) continue; // Pull the head/root of segmented non-boss enemies and let vanilla segment AI follow it. if (npc.realLife >= 0 && npc.realLife != npc.whoAmI) continue; Vector2 pull = Projectile.Center - npc.Center; float distance = pull.Length(); if (distance <= 0.01f || distance >= pullRadius) continue; Vector2 direction = pull / distance; float proximity = 1f - distance / pullRadius; if (Main.netMode != NetmodeID.Server && Main.rand.NextFloat() < 0.22f + proximity * 0.35f) { Dust draggedSoul = Dust.NewDustPerfect( npc.Center + Main.rand.NextVector2Circular(npc.width * 0.45f, npc.height * 0.45f), DustID.Shadowflame, direction * (1.4f + proximity * 2.8f), 70, new Color(205, 55, 255), 0.7f + proximity * 0.45f); draggedSoul.noGravity = true; } if (Main.netMode == NetmodeID.MultiplayerClient) continue; // Direct positional displacement is intentional. Velocity-only pulls are overwritten by // many stationary or scripted NPC AIs, which made the old rift appear to accelerate them. float displacement = (2.8f + Projectile.ai[1] * 2.6f) * MathHelper.Lerp(0.42f, 1.35f, proximity); if (Projectile.ai[0] > 0f) displacement *= 1.3f; if (distance > coreRadius) { float step = System.Math.Min(displacement, distance - coreRadius); npc.position += direction * step; float forcedSpeed = 4.5f + proximity * 5.5f + Projectile.ai[1] * 2f; npc.velocity = Vector2.Lerp(npc.velocity, direction * forcedSpeed, 0.72f); } else { // Enemies that reach the eye are pinned there even if their own AI normally never moves. npc.Center = Vector2.Lerp(npc.Center, Projectile.Center, 0.58f); npc.velocity *= 0.12f; } if (((int)timer + npc.whoAmI + Projectile.identity) % 4 == 0) npc.netUpdate = true; } } private static bool IsBossOrBossPart(NPC npc) { if (npc.boss) return true; return npc.realLife >= 0 && npc.realLife < Main.maxNPCs && Main.npc[npc.realLife].active && Main.npc[npc.realLife].boss; } public override bool PreDraw(ref Color lightColor) { ReaperProjectileHelper.DrawTrail(Projectile, new Color(185, 45, 255), copies: 14); Texture2D texture = TextureAssets.Projectile[Type].Value; Vector2 origin = texture.Size() / 2f; Color aura = new Color(210, 55, 255, 0); for (int index = 0; index < 4; index++) { Vector2 offset = (Main.GlobalTimeWrappedHourly * 3f + index * MathHelper.PiOver2).ToRotationVector2() * (3f + Projectile.ai[1] * 4f); Main.EntitySpriteDraw(texture, Projectile.Center + offset - Main.screenPosition, null, aura * 0.24f, -Projectile.rotation, origin, Projectile.scale * 1.08f, SpriteEffects.None); } return false; } }