using SoulHarvest.Common;
using Microsoft.Xna.Framework;
using System;
using System.IO;
using Terraria;
using Terraria.DataStructures;
using Terraria.ID;
using Terraria.ModLoader;
namespace SoulHarvest.Projectiles;
///
/// A tiny, zero-damage server-authored presentation event. Skill mechanics stay
/// authoritative on the server while every receiving client gets the same skill,
/// level, target point and deterministic seed for its local storyboard.
///
public sealed class ReaperSkillCueProjectile : ModProjectile
{
private ReaperFormId form;
private ReaperStage stage;
private byte skillIndex;
private byte skillLevel;
private byte variant;
private int actionId;
private int seed;
private Vector2 direction;
private bool configured;
private bool presented;
public override string Texture => "Terraria/Images/Projectile_0";
public static int Spawn(
IEntitySource source,
int owner,
in SickleCombatSnapshot snapshot,
int skillIndex,
Vector2 center,
Vector2 direction,
int variant = 0,
int actionId = -1)
{
return Spawn(source, owner, snapshot.Form, snapshot.Stage, skillIndex,
snapshot.GetSkillLevel(skillIndex), center, direction, variant, actionId);
}
public static int Spawn(
IEntitySource source,
int owner,
ReaperFormId form,
ReaperStage stage,
int skillIndex,
int skillLevel,
Vector2 center,
Vector2 direction,
int variant = 0,
int actionId = -1)
{
if (Main.netMode == NetmodeID.MultiplayerClient
|| owner < 0 || owner >= Main.maxPlayers
|| !ReaperDefinitions.IsBranchForm(form)
|| skillIndex is < 0 or > 2
|| skillLevel <= 0)
{
return -1;
}
direction = direction.SafeNormalize(Vector2.UnitY * -1f);
int projectileIndex = Projectile.NewProjectile(source, center, direction,
ModContent.ProjectileType(), 0, 0f, owner);
if (projectileIndex < 0 || projectileIndex >= Main.maxProjectiles)
return -1;
Projectile projectile = Main.projectile[projectileIndex];
if (projectile.ModProjectile is not ReaperSkillCueProjectile cue)
{
projectile.Kill();
return -1;
}
cue.form = form;
cue.stage = stage;
cue.skillIndex = (byte)skillIndex;
cue.skillLevel = (byte)Math.Clamp(skillLevel, 1, 3);
cue.variant = (byte)Math.Clamp(variant, 0, byte.MaxValue);
cue.actionId = actionId >= 0 ? actionId : projectile.identity;
cue.seed = StableSeed(owner, projectile.identity, cue.actionId, (int)form, skillIndex);
cue.direction = direction;
cue.configured = true;
projectile.timeLeft = 30;
ReaperProjectileHelper.SyncNewProjectile(projectile);
return projectileIndex;
}
public override void SetDefaults()
{
Projectile.width = 2;
Projectile.height = 2;
Projectile.friendly = false;
Projectile.hostile = false;
Projectile.tileCollide = false;
Projectile.ignoreWater = true;
Projectile.penetrate = -1;
Projectile.timeLeft = 30;
Projectile.hide = true;
Projectile.netImportant = true;
}
public override bool ShouldUpdatePosition() => false;
public override bool? CanDamage() => false;
public override void AI()
{
if (!configured || presented || Main.netMode == NetmodeID.Server)
return;
presented = true;
ReaperVfxDirector.Submit(new ReaperVisualEvent(
Projectile.owner,
form,
stage,
ReaperVisualActionKind.Skill,
skillIndex,
skillLevel,
actionId,
Target: -1,
Origin: Projectile.Center,
Direction: direction,
Seed: seed,
TimelinePhase: variant,
NormalizedTime: 0f));
}
public override bool PreDraw(ref Color lightColor) => false;
public override void SendExtraAI(BinaryWriter writer)
{
writer.Write(configured);
if (!configured)
return;
writer.Write((byte)form);
writer.Write((byte)stage);
writer.Write(skillIndex);
writer.Write(skillLevel);
writer.Write(variant);
writer.Write(actionId);
writer.Write(seed);
writer.WriteVector2(direction);
}
public override void ReceiveExtraAI(BinaryReader reader)
{
bool incomingConfigured = reader.ReadBoolean();
if (!incomingConfigured)
{
if (Main.netMode != NetmodeID.Server)
configured = false;
return;
}
ReaperFormId incomingForm = (ReaperFormId)reader.ReadByte();
ReaperStage incomingStage = (ReaperStage)reader.ReadByte();
byte incomingSkillIndex = reader.ReadByte();
byte incomingSkillLevel = reader.ReadByte();
byte incomingVariant = reader.ReadByte();
int incomingActionId = reader.ReadInt32();
int incomingSeed = reader.ReadInt32();
Vector2 incomingDirection = reader.ReadVector2().SafeNormalize(Vector2.UnitY * -1f);
if (Main.netMode == NetmodeID.Server)
return;
if (!ReaperDefinitions.IsBranchForm(incomingForm)
|| incomingStage is < ReaperStage.StageI or > ReaperStage.StageIII
|| incomingSkillIndex > 2
|| incomingSkillLevel is < 1 or > 3)
{
configured = false;
return;
}
form = incomingForm;
stage = incomingStage;
skillIndex = incomingSkillIndex;
skillLevel = incomingSkillLevel;
variant = incomingVariant;
actionId = incomingActionId;
seed = incomingSeed;
direction = incomingDirection;
configured = true;
}
internal static int StableSeed(int first, int second, int third, int fourth, int fifth = 0)
{
unchecked
{
uint hash = 2166136261u;
hash = (hash ^ (uint)first) * 16777619u;
hash = (hash ^ (uint)second) * 16777619u;
hash = (hash ^ (uint)third) * 16777619u;
hash = (hash ^ (uint)fourth) * 16777619u;
hash = (hash ^ (uint)fifth) * 16777619u;
return (int)hash;
}
}
}