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