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;
}
}
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;
}
}