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 release hit for the Blood special. Its authoritative damage is
/// the total life drained during the charge; the persistent scar is a separate
/// actor worth half of that amount on every damage pulse.
/// </summary>
public sealed class ReaperBloodExecutionProjectile : ModProjectile
{
private SickleCombatSnapshot snapshot;
private int targetIndex = -1;
private int phase;
private int actionId;
private float length;
private float width;
private int age;
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, NPC target, Vector2 direction,
float length, float width, int damage, int phase, int actionId)
{
if (Main.netMode == NetmodeID.MultiplayerClient || damage <= 0)
return -1;
direction = direction.SafeNormalize(Vector2.UnitX);
int index = Projectile.NewProjectile(source, target.Center, direction,
ModContent.ProjectileType<ReaperBloodExecutionProjectile>(), damage,
snapshot.Knockback * 0.35f, owner);
if (index < 0 || index >= Main.maxProjectiles)
return -1;
Projectile projectile = Main.projectile[index];
projectile.originalDamage = damage;
projectile.ArmorPenetration = 1000;
projectile.CritChance = snapshot.CritChance;
if (projectile.ModProjectile is ReaperBloodExecutionProjectile execution)
execution.Configure(snapshot, target.whoAmI, phase, actionId, length, width);
projectile.GetGlobalProjectile<MyGlobalProjectile>().ConfigureReaperProjectile(
projectile, snapshot, ReaperHitKind.Special, phase,
direction.ToRotation(), actionId);
ReaperProjectileHelper.SyncNewProjectile(projectile);
return index;
}
public override void SetDefaults()
{
Projectile.width = 6;
Projectile.height = 6;
Projectile.friendly = true;
Projectile.hostile = false;
Projectile.tileCollide = false;
Projectile.ignoreWater = true;
Projectile.penetrate = 1;
Projectile.timeLeft = 16;
Projectile.netImportant = true;
Projectile.DamageType = DamageClass.Melee;
}
public override bool ShouldUpdatePosition() => false;
public override bool? CanDamage() => configured && age is >= 4 and <= 6 ? 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;
}
}
public override bool? Colliding(Rectangle projHitbox, Rectangle targetHitbox)
=> configured && targetIndex >= 0 && targetIndex < Main.maxNPCs
&& Main.npc[targetIndex].active
&& Main.npc[targetIndex].Hitbox.Intersects(targetHitbox);
public override bool PreDraw(ref Color lightColor)
{
if (!configured || Main.dedServ)
return false;
float reveal = MathHelper.Clamp(age / 5f, 0f, 1f);
float fade = 1f - MathHelper.Clamp((age - 7f) / 8f, 0f, 1f);
DeathDomainPrimitiveTextureSystem.DrawBloodScar(Projectile.Center,
Projectile.velocity.ToRotation(), length, width, fade, reveal);
return false;
}
public override void SendExtraAI(BinaryWriter writer)
{
writer.Write(configured);
if (!configured)
return;
snapshot.Write(writer);
writer.Write((short)targetIndex);
writer.Write((byte)Math.Clamp(phase, 0, byte.MaxValue));
writer.Write(actionId);
writer.Write(length);
writer.Write(width);
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 incomingPhase = reader.ReadByte();
int incomingAction = reader.ReadInt32();
float incomingLength = reader.ReadSingle();
float incomingWidth = reader.ReadSingle();
int incomingAge = reader.ReadByte();
if (Main.netMode == NetmodeID.Server)
return;
snapshot = incomingSnapshot;
targetIndex = incomingTarget;
phase = incomingPhase;
actionId = incomingAction;
length = incomingLength;
width = incomingWidth;
age = incomingAge;
configured = true;
}
private void Configure(in SickleCombatSnapshot value, int target, int hitPhase,
int parentActionId, float slashLength, float slashWidth)
{
snapshot = value;
targetIndex = target;
phase = hitPhase;
actionId = parentActionId;
length = MathHelper.Clamp(slashLength, 48f, 560f);
width = MathHelper.Clamp(slashWidth, 7f, 96f);
age = 0;
configured = true;
serverAuthorized = Main.netMode != NetmodeID.MultiplayerClient;
}
}
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 release hit for the Blood special. Its authoritative damage is
/// the total life drained during the charge; the persistent scar is a separate
/// actor worth half of that amount on every damage pulse.
/// </summary>
public sealed class ReaperBloodExecutionProjectile : ModProjectile
{
private SickleCombatSnapshot snapshot;
private int targetIndex = -1;
private int phase;
private int actionId;
private float length;
private float width;
private int age;
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, NPC target, Vector2 direction,
float length, float width, int damage, int phase, int actionId)
{
if (Main.netMode == NetmodeID.MultiplayerClient || damage <= 0)
return -1;
direction = direction.SafeNormalize(Vector2.UnitX);
int index = Projectile.NewProjectile(source, target.Center, direction,
ModContent.ProjectileType<ReaperBloodExecutionProjectile>(), damage,
snapshot.Knockback * 0.35f, owner);
if (index < 0 || index >= Main.maxProjectiles)
return -1;
Projectile projectile = Main.projectile[index];
projectile.originalDamage = damage;
projectile.ArmorPenetration = 1000;
projectile.CritChance = snapshot.CritChance;
if (projectile.ModProjectile is ReaperBloodExecutionProjectile execution)
execution.Configure(snapshot, target.whoAmI, phase, actionId, length, width);
projectile.GetGlobalProjectile<MyGlobalProjectile>().ConfigureReaperProjectile(
projectile, snapshot, ReaperHitKind.Special, phase,
direction.ToRotation(), actionId);
ReaperProjectileHelper.SyncNewProjectile(projectile);
return index;
}
public override void SetDefaults()
{
Projectile.width = 6;
Projectile.height = 6;
Projectile.friendly = true;
Projectile.hostile = false;
Projectile.tileCollide = false;
Projectile.ignoreWater = true;
Projectile.penetrate = 1;
Projectile.timeLeft = 16;
Projectile.netImportant = true;
Projectile.DamageType = DamageClass.Melee;
}
public override bool ShouldUpdatePosition() => false;
public override bool? CanDamage() => configured && age is >= 4 and <= 6 ? 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;
}
}
public override bool? Colliding(Rectangle projHitbox, Rectangle targetHitbox)
=> configured && targetIndex >= 0 && targetIndex < Main.maxNPCs
&& Main.npc[targetIndex].active
&& Main.npc[targetIndex].Hitbox.Intersects(targetHitbox);
public override bool PreDraw(ref Color lightColor)
{
if (!configured || Main.dedServ)
return false;
float reveal = MathHelper.Clamp(age / 5f, 0f, 1f);
float fade = 1f - MathHelper.Clamp((age - 7f) / 8f, 0f, 1f);
DeathDomainPrimitiveTextureSystem.DrawBloodScar(Projectile.Center,
Projectile.velocity.ToRotation(), length, width, fade, reveal);
return false;
}
public override void SendExtraAI(BinaryWriter writer)
{
writer.Write(configured);
if (!configured)
return;
snapshot.Write(writer);
writer.Write((short)targetIndex);
writer.Write((byte)Math.Clamp(phase, 0, byte.MaxValue));
writer.Write(actionId);
writer.Write(length);
writer.Write(width);
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 incomingPhase = reader.ReadByte();
int incomingAction = reader.ReadInt32();
float incomingLength = reader.ReadSingle();
float incomingWidth = reader.ReadSingle();
int incomingAge = reader.ReadByte();
if (Main.netMode == NetmodeID.Server)
return;
snapshot = incomingSnapshot;
targetIndex = incomingTarget;
phase = incomingPhase;
actionId = incomingAction;
length = incomingLength;
width = incomingWidth;
age = incomingAge;
configured = true;
}
private void Configure(in SickleCombatSnapshot value, int target, int hitPhase,
int parentActionId, float slashLength, float slashWidth)
{
snapshot = value;
targetIndex = target;
phase = hitPhase;
actionId = parentActionId;
length = MathHelper.Clamp(slashLength, 48f, 560f);
width = MathHelper.Clamp(slashWidth, 7f, 96f);
age = 0;
configured = true;
serverAuthorized = Main.netMode != NetmodeID.MultiplayerClient;
}
}