using SoulHarvest.Common;
using Microsoft.Xna.Framework;
using System;
using System.IO;
using Terraria;
using Terraria.ID;
using Terraria.ModLoader;
namespace SoulHarvest.Projectiles;
/// <summary>Target-locked shatter worth exactly the stored cut-and-domain total.</summary>
public sealed class ReaperDeathShatterProjectile : ModProjectile
{
private SickleCombatSnapshot snapshot;
private int targetIndex = -1;
private int actionId;
private int age;
private bool configured;
private Vector2 visualCenter;
private bool drawCenterVisual;
private bool serverAuthorized;
private bool serverDamageTriggered;
internal Vector2 VisualCenter => visualCenter;
internal bool DrawsCenterVisual => drawCenterVisual;
public override string Texture => "Terraria/Images/Projectile_0";
internal static int Spawn(Terraria.DataStructures.IEntitySource source, int owner,
in SickleCombatSnapshot snapshot, NPC target, int accumulatedDamage,
int actionId, Vector2 visualCenter, bool drawCenterVisual)
{
if (Main.netMode == NetmodeID.MultiplayerClient || accumulatedDamage <= 0)
return -1;
int damage = Math.Max(1, Math.Min(int.MaxValue / 4, accumulatedDamage));
int index = Projectile.NewProjectile(source, target.Center, Vector2.Zero,
ModContent.ProjectileType<ReaperDeathShatterProjectile>(), damage,
snapshot.Knockback * 0.2f, owner);
if (index < 0 || index >= Main.maxProjectiles)
return -1;
Projectile projectile = Main.projectile[index];
projectile.originalDamage = damage;
projectile.ArmorPenetration = Math.Max(0, target.defense);
if (projectile.ModProjectile is ReaperDeathShatterProjectile shatter)
shatter.Configure(snapshot, target.whoAmI, actionId,
visualCenter, drawCenterVisual);
projectile.GetGlobalProjectile<MyGlobalProjectile>().ConfigureReaperProjectile(
projectile, snapshot, ReaperHitKind.UltimateDerived, 63, 0f, actionId);
ReaperProjectileHelper.SyncNewProjectile(projectile);
return index;
}
public override void SetDefaults()
{
Projectile.width = 8;
Projectile.height = 8;
Projectile.friendly = true;
Projectile.hostile = false;
Projectile.tileCollide = false;
Projectile.ignoreWater = true;
Projectile.penetrate = 1;
Projectile.timeLeft = 12;
Projectile.netImportant = true;
Projectile.DamageType = DamageClass.Melee;
}
public override bool ShouldUpdatePosition() => false;
public override bool? CanDamage()
{
if (Main.netMode == NetmodeID.MultiplayerClient)
return false;
return configured && age is >= 2 and <= 4 ? null : false;
}
public override bool? CanHitNPC(NPC target)
=> configured && target.whoAmI == targetIndex ? null : false;
public override void AI()
{
if (Main.netMode == NetmodeID.Server && !serverAuthorized)
{
Projectile.Kill();
return;
}
if (!configured)
return;
age++;
if (targetIndex >= 0 && targetIndex < Main.maxNPCs && Main.npc[targetIndex].active)
Projectile.Center = Main.npc[targetIndex].Center;
if (Main.netMode == NetmodeID.Server && !serverDamageTriggered
&& age >= 2)
{
serverDamageTriggered = true;
ReaperProjectileHelper.DamageOnDedicatedServer(Projectile);
}
}
public override bool? Colliding(Rectangle projHitbox, Rectangle targetHitbox)
=> configured && targetIndex >= 0 && targetIndex < Main.maxNPCs
&& Main.npc[targetIndex].Hitbox.Intersects(targetHitbox);
public override bool PreDraw(ref Color lightColor)
{
if (!configured || !drawCenterVisual || Main.dedServ)
return false;
float opacity = 1f - MathHelper.Clamp(age / 11f, 0f, 1f);
for (int shard = 0; shard < 12; shard++)
{
float angle = shard * MathHelper.TwoPi / 12f + actionId * 0.013f;
Vector2 direction = angle.ToRotationVector2();
Vector2 start = visualCenter + direction * (8f + age * 1.2f);
Vector2 end = start + direction * (30f + age * 5f);
DeathDomainPrimitiveTextureSystem.DrawSmoothWorldLine(start, end,
new Color(225, 22, 62, 0) * opacity, 4.5f * opacity + 0.8f);
DeathDomainPrimitiveTextureSystem.DrawSmoothWorldLine(start, end,
Color.White * (opacity * 0.78f), 1.1f);
}
return false;
}
public override void SendExtraAI(BinaryWriter writer)
{
writer.Write(configured);
if (!configured)
return;
snapshot.Write(writer);
writer.Write((short)targetIndex);
writer.Write(actionId);
writer.WriteVector2(visualCenter);
writer.Write(drawCenterVisual);
writer.Write((byte)Math.Clamp(age, 0, byte.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);
int incomingTarget = reader.ReadInt16();
int incomingAction = reader.ReadInt32();
Vector2 incomingVisualCenter = reader.ReadVector2();
bool incomingDrawCenterVisual = reader.ReadBoolean();
int incomingAge = reader.ReadByte();
if (Main.netMode == NetmodeID.Server)
return;
snapshot = incomingSnapshot;
targetIndex = incomingTarget;
actionId = incomingAction;
visualCenter = IsFinite(incomingVisualCenter)
? incomingVisualCenter
: Projectile.Center;
drawCenterVisual = incomingDrawCenterVisual;
age = incomingAge;
configured = true;
}
private void Configure(in SickleCombatSnapshot value, int target,
int parentActionId, Vector2 center, bool rendersCenterVisual)
{
snapshot = value;
targetIndex = target;
actionId = parentActionId;
visualCenter = IsFinite(center) ? center : Projectile.Center;
drawCenterVisual = rendersCenterVisual;
age = 0;
configured = true;
serverAuthorized = Main.netMode != NetmodeID.MultiplayerClient;
serverDamageTriggered = false;
}
private static bool IsFinite(Vector2 value)
=> float.IsFinite(value.X) && float.IsFinite(value.Y);
}
using SoulHarvest.Common;
using Microsoft.Xna.Framework;
using System;
using System.IO;
using Terraria;
using Terraria.ID;
using Terraria.ModLoader;
namespace SoulHarvest.Projectiles;
/// <summary>Target-locked shatter worth exactly the stored cut-and-domain total.</summary>
public sealed class ReaperDeathShatterProjectile : ModProjectile
{
private SickleCombatSnapshot snapshot;
private int targetIndex = -1;
private int actionId;
private int age;
private bool configured;
private Vector2 visualCenter;
private bool drawCenterVisual;
private bool serverAuthorized;
private bool serverDamageTriggered;
internal Vector2 VisualCenter => visualCenter;
internal bool DrawsCenterVisual => drawCenterVisual;
public override string Texture => "Terraria/Images/Projectile_0";
internal static int Spawn(Terraria.DataStructures.IEntitySource source, int owner,
in SickleCombatSnapshot snapshot, NPC target, int accumulatedDamage,
int actionId, Vector2 visualCenter, bool drawCenterVisual)
{
if (Main.netMode == NetmodeID.MultiplayerClient || accumulatedDamage <= 0)
return -1;
int damage = Math.Max(1, Math.Min(int.MaxValue / 4, accumulatedDamage));
int index = Projectile.NewProjectile(source, target.Center, Vector2.Zero,
ModContent.ProjectileType<ReaperDeathShatterProjectile>(), damage,
snapshot.Knockback * 0.2f, owner);
if (index < 0 || index >= Main.maxProjectiles)
return -1;
Projectile projectile = Main.projectile[index];
projectile.originalDamage = damage;
projectile.ArmorPenetration = Math.Max(0, target.defense);
if (projectile.ModProjectile is ReaperDeathShatterProjectile shatter)
shatter.Configure(snapshot, target.whoAmI, actionId,
visualCenter, drawCenterVisual);
projectile.GetGlobalProjectile<MyGlobalProjectile>().ConfigureReaperProjectile(
projectile, snapshot, ReaperHitKind.UltimateDerived, 63, 0f, actionId);
ReaperProjectileHelper.SyncNewProjectile(projectile);
return index;
}
public override void SetDefaults()
{
Projectile.width = 8;
Projectile.height = 8;
Projectile.friendly = true;
Projectile.hostile = false;
Projectile.tileCollide = false;
Projectile.ignoreWater = true;
Projectile.penetrate = 1;
Projectile.timeLeft = 12;
Projectile.netImportant = true;
Projectile.DamageType = DamageClass.Melee;
}
public override bool ShouldUpdatePosition() => false;
public override bool? CanDamage()
{
if (Main.netMode == NetmodeID.MultiplayerClient)
return false;
return configured && age is >= 2 and <= 4 ? null : false;
}
public override bool? CanHitNPC(NPC target)
=> configured && target.whoAmI == targetIndex ? null : false;
public override void AI()
{
if (Main.netMode == NetmodeID.Server && !serverAuthorized)
{
Projectile.Kill();
return;
}
if (!configured)
return;
age++;
if (targetIndex >= 0 && targetIndex < Main.maxNPCs && Main.npc[targetIndex].active)
Projectile.Center = Main.npc[targetIndex].Center;
if (Main.netMode == NetmodeID.Server && !serverDamageTriggered
&& age >= 2)
{
serverDamageTriggered = true;
ReaperProjectileHelper.DamageOnDedicatedServer(Projectile);
}
}
public override bool? Colliding(Rectangle projHitbox, Rectangle targetHitbox)
=> configured && targetIndex >= 0 && targetIndex < Main.maxNPCs
&& Main.npc[targetIndex].Hitbox.Intersects(targetHitbox);
public override bool PreDraw(ref Color lightColor)
{
if (!configured || !drawCenterVisual || Main.dedServ)
return false;
float opacity = 1f - MathHelper.Clamp(age / 11f, 0f, 1f);
for (int shard = 0; shard < 12; shard++)
{
float angle = shard * MathHelper.TwoPi / 12f + actionId * 0.013f;
Vector2 direction = angle.ToRotationVector2();
Vector2 start = visualCenter + direction * (8f + age * 1.2f);
Vector2 end = start + direction * (30f + age * 5f);
DeathDomainPrimitiveTextureSystem.DrawSmoothWorldLine(start, end,
new Color(225, 22, 62, 0) * opacity, 4.5f * opacity + 0.8f);
DeathDomainPrimitiveTextureSystem.DrawSmoothWorldLine(start, end,
Color.White * (opacity * 0.78f), 1.1f);
}
return false;
}
public override void SendExtraAI(BinaryWriter writer)
{
writer.Write(configured);
if (!configured)
return;
snapshot.Write(writer);
writer.Write((short)targetIndex);
writer.Write(actionId);
writer.WriteVector2(visualCenter);
writer.Write(drawCenterVisual);
writer.Write((byte)Math.Clamp(age, 0, byte.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);
int incomingTarget = reader.ReadInt16();
int incomingAction = reader.ReadInt32();
Vector2 incomingVisualCenter = reader.ReadVector2();
bool incomingDrawCenterVisual = reader.ReadBoolean();
int incomingAge = reader.ReadByte();
if (Main.netMode == NetmodeID.Server)
return;
snapshot = incomingSnapshot;
targetIndex = incomingTarget;
actionId = incomingAction;
visualCenter = IsFinite(incomingVisualCenter)
? incomingVisualCenter
: Projectile.Center;
drawCenterVisual = incomingDrawCenterVisual;
age = incomingAge;
configured = true;
}
private void Configure(in SickleCombatSnapshot value, int target,
int parentActionId, Vector2 center, bool rendersCenterVisual)
{
snapshot = value;
targetIndex = target;
actionId = parentActionId;
visualCenter = IsFinite(center) ? center : Projectile.Center;
drawCenterVisual = rendersCenterVisual;
age = 0;
configured = true;
serverAuthorized = Main.netMode != NetmodeID.MultiplayerClient;
serverDamageTriggered = false;
}
private static bool IsFinite(Vector2 value)
=> float.IsFinite(value.X) && float.IsFinite(value.Y);
}