using SoulHarvest.Common;
using Microsoft.Xna.Framework;
using System;
using System.IO;
using Terraria;
using Terraria.ID;
using Terraria.ModLoader;
namespace SoulHarvest.Projectiles;
/// <summary>A continuous molten route left by Infernal movement and dash attacks.</summary>
public sealed class ReaperInfernalTrailProjectile : ModProjectile
{
private SickleCombatSnapshot snapshot;
private float routeLength;
private float routeWidth;
private int lifetime;
private int age;
private int seed;
private bool configured;
private bool serverAuthorized;
public override string Texture => "Terraria/Images/Projectile_0";
internal static int Spawn(Terraria.DataStructures.IEntitySource source, int owner,
in SickleCombatSnapshot snapshot, Vector2 start, Vector2 end, float width,
int phase, int actionId)
{
if (Main.netMode == NetmodeID.MultiplayerClient)
return -1;
Vector2 path = end - start;
float length = path.Length();
if (length < 8f)
return -1;
Vector2 axis = path / length;
int damage = Math.Max(1, (int)Math.Round(snapshot.Damage * 0.25f));
int index = Projectile.NewProjectile(source, (start + end) * 0.5f, axis,
ModContent.ProjectileType<ReaperInfernalTrailProjectile>(), damage,
snapshot.Knockback * 0.10f, owner);
if (index < 0 || index >= Main.maxProjectiles)
return -1;
Projectile projectile = Main.projectile[index];
projectile.originalDamage = snapshot.Damage;
int skillLevel = Math.Clamp(Math.Max(1, (int)snapshot.Skill1), 1, 3);
int activeLifetime = skillLevel switch { 1 => 90, 2 => 120, _ => 150 };
int stableSeed = unchecked(actionId * 486187739 + phase * 16777619 + owner * 71);
if (projectile.ModProjectile is ReaperInfernalTrailProjectile trail)
trail.Configure(snapshot, length, width, activeLifetime, stableSeed);
projectile.GetGlobalProjectile<MyGlobalProjectile>().ConfigureReaperProjectile(
projectile, snapshot, ReaperHitKind.DamageOverTime, phase,
axis.ToRotation(), actionId);
projectile.localNPCHitCooldown = 30;
ReaperProjectileHelper.SyncNewProjectile(projectile);
return index;
}
public override void SetDefaults()
{
Projectile.width = 4;
Projectile.height = 4;
Projectile.friendly = true;
Projectile.hostile = false;
Projectile.tileCollide = false;
Projectile.ignoreWater = true;
Projectile.penetrate = -1;
Projectile.timeLeft = 150;
Projectile.netImportant = true;
Projectile.DamageType = DamageClass.Melee;
Projectile.usesLocalNPCImmunity = true;
Projectile.localNPCHitCooldown = 30;
}
public override bool ShouldUpdatePosition() => false;
public override bool? CanDamage()
=> configured && age >= 4 && Projectile.timeLeft > 15 ? null : false;
public override void AI()
{
if (Main.netMode == NetmodeID.Server && !serverAuthorized)
{
Projectile.Kill();
return;
}
if (!configured)
return;
age++;
Projectile.rotation = Projectile.velocity.ToRotation();
}
public override bool? Colliding(Rectangle projHitbox, Rectangle targetHitbox)
{
Vector2 axis = Projectile.velocity.SafeNormalize(Vector2.UnitX);
Vector2 start = Projectile.Center - axis * routeLength * 0.5f;
Vector2 end = Projectile.Center + axis * routeLength * 0.5f;
float collisionPoint = 0f;
return Collision.CheckAABBvLineCollision(targetHitbox.TopLeft(), targetHitbox.Size(),
start, end, routeWidth * 0.62f, ref collisionPoint);
}
public override bool PreDraw(ref Color lightColor)
{
if (!configured || Main.dedServ)
return false;
float reveal = Smooth01(age / 9f);
float fade = Smooth01(Projectile.timeLeft / 30f);
float pulse = 0.94f + (float)Math.Sin(age * 0.11f + seed * 0.001f) * 0.06f;
Vector2 center = Projectile.Center - Main.screenPosition;
DeathDomainPrimitiveTextureSystem.DrawBladeCentered(center, Projectile.rotation,
routeLength, routeWidth * 1.55f, new Color(8, 2, 0, 225) * fade, reveal);
DeathDomainPrimitiveTextureSystem.DrawBladeCentered(center, Projectile.rotation,
routeLength, routeWidth * pulse, new Color(245, 48, 3, 0) * (fade * 0.86f), reveal);
DeathDomainPrimitiveTextureSystem.DrawBladeCentered(center, Projectile.rotation,
routeLength, Math.Max(1.5f, routeWidth * 0.24f),
new Color(255, 225, 94, 0) * fade, reveal);
Vector2 axis = Projectile.velocity.SafeNormalize(Vector2.UnitX);
Vector2 start = Projectile.Center - axis * routeLength * 0.5f;
for (int mote = 0; mote < 5; mote++)
{
float amount = (age * 0.019f + mote / 5f + seed * 0.00013f) % 1f;
Vector2 point = start + axis * routeLength * amount;
float radius = 1.8f + mote % 2 * 0.7f;
DeathDomainPrimitiveTextureSystem.DrawSmoothDisc(Main.spriteBatch,
point - Main.screenPosition, radius + 2f,
new Color(255, 52, 5, 0) * (fade * 0.42f));
DeathDomainPrimitiveTextureSystem.DrawSmoothDisc(Main.spriteBatch,
point - Main.screenPosition, radius,
new Color(255, 231, 120, 0) * (fade * 0.92f));
}
return false;
}
public override void SendExtraAI(BinaryWriter writer)
{
writer.Write(configured);
if (!configured)
return;
snapshot.Write(writer);
writer.Write(routeLength);
writer.Write(routeWidth);
writer.Write((short)lifetime);
writer.Write(seed);
writer.Write((short)Math.Clamp(age, 0, short.MaxValue));
}
public override void ReceiveExtraAI(BinaryReader reader)
{
bool incoming = reader.ReadBoolean();
if (!incoming)
{
if (Main.netMode != NetmodeID.Server)
configured = false;
return;
}
SickleCombatSnapshot incomingSnapshot = SickleCombatSnapshot.Read(reader);
float incomingLength = reader.ReadSingle();
float incomingWidth = reader.ReadSingle();
int incomingLifetime = reader.ReadInt16();
int incomingSeed = reader.ReadInt32();
int incomingAge = reader.ReadInt16();
if (Main.netMode == NetmodeID.Server)
return;
snapshot = incomingSnapshot;
routeLength = incomingLength;
routeWidth = incomingWidth;
lifetime = incomingLifetime;
seed = incomingSeed;
age = incomingAge;
configured = true;
}
private void Configure(in SickleCombatSnapshot value, float length, float width,
int activeLifetime, int shapeSeed)
{
snapshot = value;
routeLength = MathHelper.Clamp(length, 8f, 1600f);
routeWidth = MathHelper.Clamp(width, 6f, 80f);
lifetime = Math.Clamp(activeLifetime, 45, 240);
seed = shapeSeed;
age = 0;
configured = true;
serverAuthorized = Main.netMode != NetmodeID.MultiplayerClient;
Projectile.timeLeft = lifetime;
}
private static float Smooth01(float value)
{
value = MathHelper.Clamp(value, 0f, 1f);
return value * value * (3f - 2f * value);
}
}
using SoulHarvest.Common;
using Microsoft.Xna.Framework;
using System;
using System.IO;
using Terraria;
using Terraria.ID;
using Terraria.ModLoader;
namespace SoulHarvest.Projectiles;
/// <summary>A continuous molten route left by Infernal movement and dash attacks.</summary>
public sealed class ReaperInfernalTrailProjectile : ModProjectile
{
private SickleCombatSnapshot snapshot;
private float routeLength;
private float routeWidth;
private int lifetime;
private int age;
private int seed;
private bool configured;
private bool serverAuthorized;
public override string Texture => "Terraria/Images/Projectile_0";
internal static int Spawn(Terraria.DataStructures.IEntitySource source, int owner,
in SickleCombatSnapshot snapshot, Vector2 start, Vector2 end, float width,
int phase, int actionId)
{
if (Main.netMode == NetmodeID.MultiplayerClient)
return -1;
Vector2 path = end - start;
float length = path.Length();
if (length < 8f)
return -1;
Vector2 axis = path / length;
int damage = Math.Max(1, (int)Math.Round(snapshot.Damage * 0.25f));
int index = Projectile.NewProjectile(source, (start + end) * 0.5f, axis,
ModContent.ProjectileType<ReaperInfernalTrailProjectile>(), damage,
snapshot.Knockback * 0.10f, owner);
if (index < 0 || index >= Main.maxProjectiles)
return -1;
Projectile projectile = Main.projectile[index];
projectile.originalDamage = snapshot.Damage;
int skillLevel = Math.Clamp(Math.Max(1, (int)snapshot.Skill1), 1, 3);
int activeLifetime = skillLevel switch { 1 => 90, 2 => 120, _ => 150 };
int stableSeed = unchecked(actionId * 486187739 + phase * 16777619 + owner * 71);
if (projectile.ModProjectile is ReaperInfernalTrailProjectile trail)
trail.Configure(snapshot, length, width, activeLifetime, stableSeed);
projectile.GetGlobalProjectile<MyGlobalProjectile>().ConfigureReaperProjectile(
projectile, snapshot, ReaperHitKind.DamageOverTime, phase,
axis.ToRotation(), actionId);
projectile.localNPCHitCooldown = 30;
ReaperProjectileHelper.SyncNewProjectile(projectile);
return index;
}
public override void SetDefaults()
{
Projectile.width = 4;
Projectile.height = 4;
Projectile.friendly = true;
Projectile.hostile = false;
Projectile.tileCollide = false;
Projectile.ignoreWater = true;
Projectile.penetrate = -1;
Projectile.timeLeft = 150;
Projectile.netImportant = true;
Projectile.DamageType = DamageClass.Melee;
Projectile.usesLocalNPCImmunity = true;
Projectile.localNPCHitCooldown = 30;
}
public override bool ShouldUpdatePosition() => false;
public override bool? CanDamage()
=> configured && age >= 4 && Projectile.timeLeft > 15 ? null : false;
public override void AI()
{
if (Main.netMode == NetmodeID.Server && !serverAuthorized)
{
Projectile.Kill();
return;
}
if (!configured)
return;
age++;
Projectile.rotation = Projectile.velocity.ToRotation();
}
public override bool? Colliding(Rectangle projHitbox, Rectangle targetHitbox)
{
Vector2 axis = Projectile.velocity.SafeNormalize(Vector2.UnitX);
Vector2 start = Projectile.Center - axis * routeLength * 0.5f;
Vector2 end = Projectile.Center + axis * routeLength * 0.5f;
float collisionPoint = 0f;
return Collision.CheckAABBvLineCollision(targetHitbox.TopLeft(), targetHitbox.Size(),
start, end, routeWidth * 0.62f, ref collisionPoint);
}
public override bool PreDraw(ref Color lightColor)
{
if (!configured || Main.dedServ)
return false;
float reveal = Smooth01(age / 9f);
float fade = Smooth01(Projectile.timeLeft / 30f);
float pulse = 0.94f + (float)Math.Sin(age * 0.11f + seed * 0.001f) * 0.06f;
Vector2 center = Projectile.Center - Main.screenPosition;
DeathDomainPrimitiveTextureSystem.DrawBladeCentered(center, Projectile.rotation,
routeLength, routeWidth * 1.55f, new Color(8, 2, 0, 225) * fade, reveal);
DeathDomainPrimitiveTextureSystem.DrawBladeCentered(center, Projectile.rotation,
routeLength, routeWidth * pulse, new Color(245, 48, 3, 0) * (fade * 0.86f), reveal);
DeathDomainPrimitiveTextureSystem.DrawBladeCentered(center, Projectile.rotation,
routeLength, Math.Max(1.5f, routeWidth * 0.24f),
new Color(255, 225, 94, 0) * fade, reveal);
Vector2 axis = Projectile.velocity.SafeNormalize(Vector2.UnitX);
Vector2 start = Projectile.Center - axis * routeLength * 0.5f;
for (int mote = 0; mote < 5; mote++)
{
float amount = (age * 0.019f + mote / 5f + seed * 0.00013f) % 1f;
Vector2 point = start + axis * routeLength * amount;
float radius = 1.8f + mote % 2 * 0.7f;
DeathDomainPrimitiveTextureSystem.DrawSmoothDisc(Main.spriteBatch,
point - Main.screenPosition, radius + 2f,
new Color(255, 52, 5, 0) * (fade * 0.42f));
DeathDomainPrimitiveTextureSystem.DrawSmoothDisc(Main.spriteBatch,
point - Main.screenPosition, radius,
new Color(255, 231, 120, 0) * (fade * 0.92f));
}
return false;
}
public override void SendExtraAI(BinaryWriter writer)
{
writer.Write(configured);
if (!configured)
return;
snapshot.Write(writer);
writer.Write(routeLength);
writer.Write(routeWidth);
writer.Write((short)lifetime);
writer.Write(seed);
writer.Write((short)Math.Clamp(age, 0, short.MaxValue));
}
public override void ReceiveExtraAI(BinaryReader reader)
{
bool incoming = reader.ReadBoolean();
if (!incoming)
{
if (Main.netMode != NetmodeID.Server)
configured = false;
return;
}
SickleCombatSnapshot incomingSnapshot = SickleCombatSnapshot.Read(reader);
float incomingLength = reader.ReadSingle();
float incomingWidth = reader.ReadSingle();
int incomingLifetime = reader.ReadInt16();
int incomingSeed = reader.ReadInt32();
int incomingAge = reader.ReadInt16();
if (Main.netMode == NetmodeID.Server)
return;
snapshot = incomingSnapshot;
routeLength = incomingLength;
routeWidth = incomingWidth;
lifetime = incomingLifetime;
seed = incomingSeed;
age = incomingAge;
configured = true;
}
private void Configure(in SickleCombatSnapshot value, float length, float width,
int activeLifetime, int shapeSeed)
{
snapshot = value;
routeLength = MathHelper.Clamp(length, 8f, 1600f);
routeWidth = MathHelper.Clamp(width, 6f, 80f);
lifetime = Math.Clamp(activeLifetime, 45, 240);
seed = shapeSeed;
age = 0;
configured = true;
serverAuthorized = Main.netMode != NetmodeID.MultiplayerClient;
Projectile.timeLeft = lifetime;
}
private static float Smooth01(float value)
{
value = MathHelper.Clamp(value, 0f, 1f);
return value * value * (3f - 2f * value);
}
}