using System;
using Unity.Netcode;
using UnityEngine;
namespace Unity.BossRoom.Gameplay.Actions
{
///
/// Comprehensive class that contains information needed to play back any action on the server. This is what gets sent client->server when
/// the Action gets played, and also what gets sent server->client to broadcast the action event. Note that the OUTCOMES of the action effect
/// don't ride along with this object when it is broadcast to clients; that information is sync'd separately, usually by NetworkVariables.
///
public struct ActionRequestData : INetworkSerializable
{
public ActionID ActionID; //index of the action in the list of all actions in the game - a way to recover the reference to the instance at runtime
public Vector3 Position; //center position of skill, e.g. "ground zero" of a fireball skill.
public Vector3 Direction; //direction of skill, if not inferrable from the character's current facing.
public ulong[] TargetIds; //NetworkObjectIds of targets, or null if untargeted.
public float Amount; //can mean different things depending on the Action. For a ChaseAction, it will be target range the ChaseAction is trying to achieve.
public bool ShouldQueue; //if true, this action should queue. If false, it should clear all current actions and play immediately.
public bool ShouldClose; //if true, the server should synthesize a ChaseAction to close to within range of the target before playing the Action. Ignored for untargeted actions.
public bool CancelMovement; // if true, movement is cancelled before playing this action
//O__O Hey, are you adding something? Be sure to update ActionLogicInfo, as well as the methods below.
[Flags]
private enum PackFlags
{
None = 0,
HasPosition = 1,
HasDirection = 1 << 1,
HasTargetIds = 1 << 2,
HasAmount = 1 << 3,
ShouldQueue = 1 << 4,
ShouldClose = 1 << 5,
CancelMovement = 1 << 6,
//currently serialized with a byte. Change Read/Write if you add more than 8 fields.
}
public static ActionRequestData Create(Action action) =>
new()
{
ActionID = action.ActionID
};
///
/// Returns true if the ActionRequestDatas are "functionally equivalent" (not including their Queueing or Closing properties).
///
public bool Compare(ref ActionRequestData rhs)
{
bool scalarParamsEqual = (ActionID, Position, Direction, Amount) == (rhs.ActionID, rhs.Position, rhs.Direction, rhs.Amount);
if (!scalarParamsEqual) { return false; }
if (TargetIds == rhs.TargetIds) { return true; } //covers case of both being null.
if (TargetIds == null || rhs.TargetIds == null || TargetIds.Length != rhs.TargetIds.Length) { return false; }
for (int i = 0; i < TargetIds.Length; i++)
{
if (TargetIds[i] != rhs.TargetIds[i]) { return false; }
}
return true;
}
private PackFlags GetPackFlags()
{
PackFlags flags = PackFlags.None;
if (Position != Vector3.zero) { flags |= PackFlags.HasPosition; }
if (Direction != Vector3.zero) { flags |= PackFlags.HasDirection; }
if (TargetIds != null) { flags |= PackFlags.HasTargetIds; }
if (Amount != 0) { flags |= PackFlags.HasAmount; }
if (ShouldQueue) { flags |= PackFlags.ShouldQueue; }
if (ShouldClose) { flags |= PackFlags.ShouldClose; }
if (CancelMovement) { flags |= PackFlags.CancelMovement; }
return flags;
}
public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter
{
PackFlags flags = PackFlags.None;
if (!serializer.IsReader)
{
flags = GetPackFlags();
}
serializer.SerializeValue(ref ActionID);
serializer.SerializeValue(ref flags);
if (serializer.IsReader)
{
ShouldQueue = (flags & PackFlags.ShouldQueue) != 0;
CancelMovement = (flags & PackFlags.CancelMovement) != 0;
ShouldClose = (flags & PackFlags.ShouldClose) != 0;
}
if ((flags & PackFlags.HasPosition) != 0)
{
serializer.SerializeValue(ref Position);
}
if ((flags & PackFlags.HasDirection) != 0)
{
serializer.SerializeValue(ref Direction);
}
if ((flags & PackFlags.HasTargetIds) != 0)
{
serializer.SerializeValue(ref TargetIds);
}
if ((flags & PackFlags.HasAmount) != 0)
{
serializer.SerializeValue(ref Amount);
}
}
}
}