Files
jelito/Assets/jelycho/Code/Player/PlayerActor.cs
2025-07-21 19:58:20 +02:00

813 lines
30 KiB
C#
Executable File

using R3;
using RebootKit.Engine.Extensions;
using RebootKit.Engine.Foundation;
using RebootKit.Engine.Main;
using RebootKit.Engine.Network;
using RebootKit.Engine.Simulation;
using RebootKit.Engine.Simulation.Sensors;
using RebootReality.jelycho.Items;
using Unity.Collections;
using Unity.Mathematics;
using Unity.Netcode;
using UnityEngine;
using Logger = RebootKit.Engine.Foundation.Logger;
namespace RebootReality.jelycho.Player {
public class PlayerActor : Actor {
static readonly Logger s_Logger = new Logger(nameof(PlayerActor));
[SerializeField] Animator m_Animator;
[Header("Movement")]
[SerializeField] PlayerFPPLocomotion m_Locomotion;
[Header("Camera")]
[SerializeField] FPPCamera m_Camera;
[SerializeField] CameraSpring m_CameraSpring;
[SerializeField, Range(0.0f, 1.0f), Tooltip("Percentage of run speed")]
float m_EnableCameraBobbingPercentThreshold = 0.5f;
[SerializeField] float m_SprintCameraBobbing = 1.0f;
[SerializeField] float m_RunCameraBobbing = 0.5f;
[SerializeField] float m_IdleCameraBobbing = 0.0f;
[SerializeField] float m_CameraBobbingTransitionSpeed = 5.0f;
float m_TargetCameraBobbing = 0.0f;
float m_CurrentCameraBobbing = 0.0f;
[Header("Character")]
[SerializeField] Transform m_CharacterRootTransform;
[SerializeField] Transform m_HeadBoneTransform;
[SerializeField] Transform m_HeadAimTargetTransform;
[SerializeField] Transform m_CharacterForwardTransform;
[SerializeField, Range(0.0f, 90.0f)] float m_CharacterRotateDeadAngle = 5.0f;
[SerializeField, Range(0.0f, 90.0f)] float m_CharacterRotateSoftAngle = 90.0f;
[SerializeField] float m_CharacterRotateSpeed = 180.0f;
[SerializeField] float m_CharacterRotateFastSpeed = 720.0f;
float m_CharacterTurnVelocity = 0.0f;
[Header("Dragging")]
[SerializeField] Transform m_DragGutStartPosition;
[SerializeField] PhysicsObjectDragger m_PhysicsDragger;
[SerializeField] FloatRange m_DragDistanceRange = new FloatRange(1.0f, 5.0f);
[Header("Beacon location picking")]
[SerializeField] LayerMask m_BeaconPlacementLayerMask = 0;
[SerializeField] float m_BeaconPlacementMaxDistance = 15.0f;
[SerializeField] float m_NormalDotUpThreshold = 0.5f;
[Header("Sensors")]
[SerializeField] SingleRaySensor<IInteractable> m_InteractablesSensor;
[Header("Network")]
[SerializeField] float m_MinTeleportDistance = 0.5f;
[SerializeField] float m_ItemPickupDistance = 2.0f;
bool m_IsSetupAsOwner = false;
float m_SyncRemoteStateTimer = 0.0f;
RemotePlayerActorState m_RemoteState;
[Header("Inventory")]
[SerializeField] int m_InventorySize = 10;
public Inventory Inventory { get; private set; }
ItemActor m_EquippedItem;
public ReactiveProperty<int> SelectedInventorySlot { get; private set; } = new ReactiveProperty<int>(0);
public float3 LookDirection {
get {
float pitchRad = math.radians(-m_Camera.Pitch);
float yawRad = math.radians(m_Camera.Yaw);
return new float3(math.sin(yawRad) * math.cos(pitchRad),
math.sin(pitchRad),
math.cos(yawRad) * math.cos(pitchRad));
}
}
readonly ReactiveProperty<IInteractable> m_TargetInteractable = new ReactiveProperty<IInteractable>(null);
public ReadOnlyReactiveProperty<IInteractable> TargetInteractable {
get {
return m_TargetInteractable;
}
}
protected override IActorData CreateActorData() {
return new PlayerActorData { };
}
void Awake() {
Inventory = new Inventory(m_InventorySize);
// @NOTE: By default player actor should be set up as remote
SetupAsRemote();
}
void Start() {
m_CameraSpring.Initialize();
}
void OnEnable() {
Inventory.OnItemPickedUp += OnItemPickedUp;
Inventory.OnItemDropped += OnItemDropped;
}
void OnDisable() {
Inventory.OnItemPickedUp -= OnItemPickedUp;
Inventory.OnItemDropped -= OnItemDropped;
}
//
// @MARK: Actor
//
public override void OnServerTick(float deltaTime) {
base.OnServerTick(deltaTime);
// Update actor data
// PlayerActorData data = DataAs<PlayerActorData>();
// IsDataDirty = true;
NativeArray<byte> remoteStateData = DataSerializationUtils.Serialize(m_RemoteState);
SendActorEvent((ushort) PlayerActorEvents.UpdatedRemoteState, remoteStateData);
}
public override void OnClientTick(float deltaTime) {
base.OnClientTick(deltaTime);
if (m_IsSetupAsOwner) {
TickCamera();
UpdateAnimator(m_Locomotion.Velocity);
SenseInteractable();
m_SyncRemoteStateTimer -= deltaTime;
if (m_SyncRemoteStateTimer <= 0.0f) {
m_SyncRemoteStateTimer = 1.0f / NetworkSystem.TickRate.IndexValue;
RemotePlayerActorState remoteState = new RemotePlayerActorState {
Position = transform.position,
Velocity = m_Locomotion.Velocity,
LookPitch = m_Camera.Pitch,
LookYaw = m_Camera.Yaw,
IsGrounded = m_Locomotion.IsGrounded
};
NativeArray<byte> data = DataSerializationUtils.Serialize(remoteState);
SendActorCommand((ushort) PlayerActorCommands.UpdateRemoteState, data);
}
} else {
InterpolateActorState(deltaTime);
}
TickCharacterRotation();
}
protected override void OnActorCommandServer(ActorCommand actorCommand) {
base.OnActorCommandServer(actorCommand);
if (actorCommand.CommandID == (ushort) PlayerActorCommands.UpdateRemoteState) {
RemotePlayerActorState remoteState = new RemotePlayerActorState();
DataSerializationUtils.Deserialize(actorCommand.Data, ref remoteState);
m_RemoteState = remoteState;
} else if (actorCommand.CommandID == (ushort) PlayerActorCommands.PickupItem) {
PlayerActorPickupItemCommand command = new PlayerActorPickupItemCommand();
DataSerializationUtils.Deserialize(actorCommand.Data, ref command);
Actor itemActor = RR.FindSpawnedActor(command.ItemActorID);
if (itemActor is ItemActor item) {
if (math.distancesq(itemActor.transform.position, m_HeadBoneTransform.position) <=
m_ItemPickupDistance * m_ItemPickupDistance) {
if (Inventory.TryPickup(item)) {
s_Logger.Info($"Item {item.name} picked up successfully by player {name}.");
UpdateEquippedItem();
} else {
s_Logger.Info($"Failed to pick up item {item.name}. Inventory is full.");
}
} else {
s_Logger.Info($"Item actor {item.name} is too far away to pick up.");
}
} else {
s_Logger.Error($"Item actor with ID {command.ItemActorID} not found.");
}
} else if (actorCommand.CommandID == (ushort) PlayerActorCommands.SelectItemSlot) {
PlayerActorSelectItemSlotCommand command = new PlayerActorSelectItemSlotCommand();
DataSerializationUtils.Deserialize(actorCommand.Data, ref command);
if (command.SlotIndex < 0 || command.SlotIndex >= Inventory.SlotsCount) {
s_Logger.Error($"Invalid slot index {command.SlotIndex}. Must be between 0 and {Inventory.SlotsCount - 1}.");
return;
}
SelectedInventorySlot.Value = command.SlotIndex;
UpdateEquippedItem();
} else if (actorCommand.CommandID == (ushort) PlayerActorCommands.DropItem) {
PlayerActorDropItemCommand command = new PlayerActorDropItemCommand();
DataSerializationUtils.Deserialize(actorCommand.Data, ref command);
if (command.Count == 1) {
Inventory.TryDropOne(command.InventorySlotIndex);
} else {
s_Logger.Error("DropItem command with count != 1 is not supported yet.");
}
}
}
protected override void OnActorEventClient(ActorEvent actorEvent) {
base.OnActorEventClient(actorEvent);
if (actorEvent.EventID == (ushort) PlayerActorEvents.UpdatedRemoteState) {
RemotePlayerActorState remoteState = new RemotePlayerActorState();
DataSerializationUtils.Deserialize(actorEvent.Data, ref remoteState);
m_RemoteState = remoteState;
} else if (actorEvent.EventID == (ushort) PlayerActorEvents.PrimaryEquippedItemChanged) {
PlayerActorPrimaryEquippedItemChangedEvent itemChangedEvent =
new PlayerActorPrimaryEquippedItemChangedEvent();
DataSerializationUtils.Deserialize(actorEvent.Data, ref itemChangedEvent);
if (itemChangedEvent.ItemActorID == 0) {
m_EquippedItem = null;
} else {
Actor itemActor = RR.FindSpawnedActor(itemChangedEvent.ItemActorID);
if (itemActor is ItemActor item) {
s_Logger.Info($"Primary equipped item changed to {item.name} for player {name}.");
m_EquippedItem = item;
} else {
s_Logger.Error($"Primary equipped item with ID {itemChangedEvent.ItemActorID} not found.");
}
}
}
}
//
// @MARK: Controller API
//
public void SetSprint(bool isSprinting) {
if (!m_IsSetupAsOwner) {
s_Logger.Error("Cannot set sprint state when not set up as owner.");
return;
}
m_Locomotion.SetSprint(isSprinting);
}
public void Jump() {
if (!m_IsSetupAsOwner) {
s_Logger.Error("Cannot jump when not set up as owner.");
return;
}
m_Locomotion.Jump();
}
public void Look(Vector2 input) {
if (!m_IsSetupAsOwner) {
s_Logger.Error("Cannot look when not set up as owner.");
return;
}
m_Camera.Rotate(input.x, input.y);
}
public void SetMoveInput(Vector2 input) {
if (!m_IsSetupAsOwner) {
s_Logger.Error("Cannot set move input when not set up as owner.");
return;
}
float3 direction = Quaternion.AngleAxis(m_Camera.Yaw, Vector3.up) *
new float3(input.x, 0.0f, input.y);
m_Locomotion.SetWishDirection(direction);
}
public void StartDrag() {
if (!m_IsSetupAsOwner) {
s_Logger.Error("Cannot start dragging when not set up as owner.");
return;
}
GameObject pickedGameObject = m_Camera.Sensor.Sense();
if (pickedGameObject != null && pickedGameObject.TryGetComponent(out Rigidbody rigidbody)) {
m_PhysicsDragger.Grab(rigidbody);
}
}
public void StopDrag() {
if (!m_IsSetupAsOwner) {
s_Logger.Error("Cannot stop dragging when not set up as owner.");
return;
}
m_PhysicsDragger.Drop();
}
public void DropItem() {
if (!m_IsSetupAsOwner) {
s_Logger.Error("Cannot drop item when not set up as owner.");
return;
}
if (m_EquippedItem != null) {
PlayerActorDropItemCommand command = new PlayerActorDropItemCommand {
InventorySlotIndex = SelectedInventorySlot.Value,
Count = 1
};
SendActorCommand((ushort) PlayerActorCommands.DropItem, ref command);
}
}
public void PrimaryAction() {
if (!m_IsSetupAsOwner) {
s_Logger.Error("Cannot perform primary action when not set up as owner.");
return;
}
// if (!IsOwner()) {
// s_Logger.Error("Only the owner can perform primary actions.");
// return;
// }
// if (TryGetBeaconPosition(out Vector3 beaconPosition)) {
// SetAnimatorTriggerRpc(AnimatorParamHashes.Throw);
//
// if (RR.World.Context is WorldContext worldContext) {
// worldContext.BaseManager.TrySpawnBeacon(beaconPosition);
// }
// }
}
public void SecondaryAction() {
if (!m_IsSetupAsOwner) {
s_Logger.Error("Cannot perform secondary action when not set up as owner.");
return;
}
m_Animator.SetTrigger(AnimatorParamHashes.Block);
}
public void Interact() {
if (!m_IsSetupAsOwner) {
s_Logger.Error("Cannot perform interaction when not set up as owner.");
return;
}
if (m_TargetInteractable.Value is ItemActor itemActor) {
Pickup(itemActor);
} else if (m_TargetInteractable.Value is not null) {
m_TargetInteractable.Value.Interact();
// SetAnimatorTriggerRpc(AnimatorParamHashes.Throw);
}
}
//
// @MARK: Owner
//
public void SetupAsOwner() {
m_Camera.enabled = true;
m_Camera.Camera.enabled = true;
m_Locomotion.enabled = true;
if (TryGetComponent(out Rigidbody rbody)) {
rbody.isKinematic = false;
}
m_IsSetupAsOwner = true;
}
void TickCamera() {
// Camera Stuff
m_Camera.Tick();
if (m_Locomotion.IsGrounded &&
m_Locomotion.SpeedXZ >= m_Locomotion.runSpeed * m_EnableCameraBobbingPercentThreshold) {
if (m_Locomotion.IsSprinting) {
m_TargetCameraBobbing = m_SprintCameraBobbing;
} else {
m_TargetCameraBobbing = m_RunCameraBobbing;
}
} else {
m_TargetCameraBobbing = m_IdleCameraBobbing;
}
m_CurrentCameraBobbing = Mathf.MoveTowards(m_CurrentCameraBobbing,
m_TargetCameraBobbing,
m_CameraBobbingTransitionSpeed * Time.deltaTime);
m_Camera.SetBobbing(m_CurrentCameraBobbing);
m_CameraSpring.UpdateSpring(Time.deltaTime,
m_CharacterForwardTransform.up,
m_CharacterForwardTransform.right,
m_CharacterForwardTransform.forward);
}
void SenseInteractable() {
IInteractable interactable = m_InteractablesSensor.Sense();
if (interactable != m_TargetInteractable.Value) {
m_TargetInteractable.Value = interactable;
}
}
//
// @MARK: Remote
//
void SetupAsRemote() {
m_Camera.enabled = false;
m_Camera.Camera.enabled = false;
m_Locomotion.enabled = false;
if (TryGetComponent(out Rigidbody rbody)) {
rbody.isKinematic = true;
}
m_IsSetupAsOwner = false;
}
void InterpolateActorState(float deltaTime) {
Vector3 targetPosition = m_RemoteState.Position;
if ((transform.position - m_RemoteState.Position).sqrMagnitude <
m_MinTeleportDistance * m_MinTeleportDistance) {
targetPosition = Vector3.MoveTowards(transform.position,
m_RemoteState.Position,
m_Locomotion.runSpeed * deltaTime);
}
m_Locomotion.WarpTo(targetPosition);
m_Camera.Pitch = m_RemoteState.LookPitch;
m_Camera.Yaw = m_RemoteState.LookYaw;
UpdateAnimator(m_RemoteState.Velocity);
}
//
// @MARK: Server
//
void OnItemDropped(ItemActor item) {
if (!RR.IsServer()) {
return;
}
// @BUG: Sometimes the item will not update it's physics state and will keep floating in the air. It's rare?
UpdateEquippedItem();
item.SetHidden(false);
item.UnMount();
item.transform.position = m_HeadBoneTransform.position + m_HeadBoneTransform.forward * 1.0f;
item.transform.rotation = Quaternion.LookRotation(m_HeadBoneTransform.forward, Vector3.up);
}
void OnItemPickedUp(ItemActor item) {
if (!RR.IsServer()) {
return;
}
item.SetHidden(true);
}
void UpdateEquippedItem() {
if (!RR.IsServer()) {
s_Logger.Error("Only the server can update selected item.");
return;
}
ItemActor itemActor = Inventory.GetFirstItem(SelectedInventorySlot.Value);
if (itemActor == m_EquippedItem) {
return;
}
if (m_EquippedItem != null) {
m_EquippedItem.SetHidden(true);
}
m_EquippedItem = itemActor;
if (m_EquippedItem != null) {
m_EquippedItem.SetHidden(false);
m_EquippedItem.MountTo(this, "hand_right");
}
PlayerActorPrimaryEquippedItemChangedEvent itemChangedEvent =
new PlayerActorPrimaryEquippedItemChangedEvent {
ItemActorID = m_EquippedItem != null ? m_EquippedItem.ActorID : 0
};
SendActorEvent((ushort) PlayerActorEvents.PrimaryEquippedItemChanged, ref itemChangedEvent);
}
public void WarpTo(Vector3 position) {
if (!RR.IsServer()) {
s_Logger.Error("Only the server can warp players.");
return;
}
m_Locomotion.WarpTo(position);
}
//
// @MARK: Common
//
void TickCharacterRotation() {
float3 targetCharacterForward = math.normalize(LookDirection.With(y: 0.0f));
float3 currentCharacterForward = math.normalize(m_CharacterForwardTransform.forward.With(y: 0.0f));
float angleRad =
math.acos(math.clamp(math.dot(targetCharacterForward, currentCharacterForward) / (math.length(targetCharacterForward) * math.length(currentCharacterForward)),
-1f, 1f));
float angleDeg = math.degrees(angleRad);
bool rotateCharacter = false;
float rotateCharacterSpeed = m_CharacterRotateSpeed;
m_CharacterTurnVelocity = 0.0f;
if (math.abs(angleDeg) > m_CharacterRotateDeadAngle) {
if (math.abs(angleDeg) < m_CharacterRotateSoftAngle) {
rotateCharacter = true;
} else {
rotateCharacter = true;
rotateCharacterSpeed = m_CharacterRotateFastSpeed;
}
}
float velocityForward = m_Locomotion.Velocity.z;
if (!rotateCharacter && math.abs(velocityForward) > 0.01f) {
rotateCharacter = true;
}
if (rotateCharacter) {
m_CharacterTurnVelocity = rotateCharacterSpeed * Time.deltaTime;
Vector3 newForward = Vector3.RotateTowards(currentCharacterForward,
targetCharacterForward,
math.radians(m_CharacterTurnVelocity),
0.0f);
m_CharacterForwardTransform.forward = newForward;
}
// Aim Target adjustment
m_HeadAimTargetTransform.position = (float3) m_HeadBoneTransform.position + LookDirection * 5.0f;
}
//
// @MARK: Sensors
//
bool TryGetBeaconPosition(out Vector3 position) {
Ray ray = new Ray(m_Camera.Camera.transform.position, m_Camera.Camera.transform.forward);
if (Physics.Raycast(ray, out RaycastHit hit, m_BeaconPlacementMaxDistance, m_BeaconPlacementLayerMask) &&
Vector3.Dot(hit.normal, Vector3.up) >= m_NormalDotUpThreshold) {
position = hit.point;
return true;
}
position = Vector3.zero;
return false;
}
//
// @MARK: Inventory
//
void Pickup(ItemActor actor) {
if (!m_IsSetupAsOwner) {
s_Logger.Error("Cannot pick up items when not set up as owner.");
return;
}
PlayerActorPickupItemCommand command = new PlayerActorPickupItemCommand {
ItemActorID = actor.ActorID
};
SendActorCommand((ushort) PlayerActorCommands.PickupItem, ref command);
}
public void SelectPreviousItemSlot() {
if (!m_IsSetupAsOwner) {
s_Logger.Error("Only the owner can change inventory selection.");
return;
}
if (SelectedInventorySlot.Value > 0) {
SelectedInventorySlot.Value--;
} else {
SelectedInventorySlot.Value = Inventory.SlotsCount - 1;
}
PlayerActorSelectItemSlotCommand command = new PlayerActorSelectItemSlotCommand();
command.SlotIndex = SelectedInventorySlot.Value;
SendActorCommand((ushort) PlayerActorCommands.SelectItemSlot, ref command);
}
public void SelectNextItemSlot() {
if (!m_IsSetupAsOwner) {
s_Logger.Error("Only the owner can change inventory selection.");
return;
}
if (SelectedInventorySlot.Value < Inventory.SlotsCount - 1) {
SelectedInventorySlot.Value++;
} else {
SelectedInventorySlot.Value = 0;
}
PlayerActorSelectItemSlotCommand command = new PlayerActorSelectItemSlotCommand();
command.SlotIndex = SelectedInventorySlot.Value;
SendActorCommand((ushort) PlayerActorCommands.SelectItemSlot, ref command);
}
public void SelectItemSlot(int slotIndex) {
if (!m_IsSetupAsOwner) {
s_Logger.Error("Only the owner can change inventory selection.");
return;
}
if (slotIndex < 0 || slotIndex >= Inventory.SlotsCount) {
s_Logger.Error($"Invalid slot index {slotIndex}. Must be between 0 and {Inventory.SlotsCount - 1}.");
return;
}
SelectedInventorySlot.Value = slotIndex;
PlayerActorSelectItemSlotCommand command = new PlayerActorSelectItemSlotCommand();
command.SlotIndex = SelectedInventorySlot.Value;
SendActorCommand((ushort) PlayerActorCommands.SelectItemSlot, ref command);
}
//
// @MARK: Animations
//
struct AnimatorParamHashes {
public static readonly int VelocityForwardNormalized = Animator.StringToHash("VelocityForwardNormalized");
public static readonly int VelocityRightNormalized = Animator.StringToHash("VelocityRightNormalized");
public static readonly int TurnVelocity = Animator.StringToHash("TurnVelocity");
public static readonly int IsGrounded = Animator.StringToHash("IsGrounded");
public static readonly int Attack = Animator.StringToHash("Attack");
public static readonly int Block = Animator.StringToHash("Block");
public static readonly int Throw = Animator.StringToHash("Throw");
public static readonly int Holding = Animator.StringToHash("Holding");
}
void UpdateAnimator(Vector3 velocity) {
Vector3 localVelocity = m_CharacterForwardTransform.InverseTransformDirection(velocity);
float forwardNormalized = localVelocity.z / m_Locomotion.runSpeed;
float rightNormalized = localVelocity.x / m_Locomotion.runSpeed;
float turnVelocity = m_CharacterTurnVelocity;
if (math.abs(forwardNormalized) > 0.01f ||
math.abs(rightNormalized) > 0.01f ||
!m_Locomotion.IsGrounded) {
turnVelocity = 0.0f;
}
m_Animator.SetFloat(AnimatorParamHashes.VelocityForwardNormalized, forwardNormalized);
m_Animator.SetFloat(AnimatorParamHashes.VelocityRightNormalized, rightNormalized);
m_Animator.SetFloat(AnimatorParamHashes.TurnVelocity, turnVelocity);
m_Animator.SetBool(AnimatorParamHashes.IsGrounded, m_Locomotion.IsGrounded);
m_Animator.SetInteger(AnimatorParamHashes.Holding, 1);
}
}
public class PlayerActorData : IActorData {
public void Serialize(NetworkBufferWriter writer) { }
public void Deserialize(NetworkBufferReader reader) { }
public int GetMaxBytes() {
return 0;
}
}
public struct RemotePlayerActorState : IActorData {
public Vector3 Position;
public Vector3 Velocity;
public float LookPitch;
public float LookYaw;
public bool IsGrounded;
public void Serialize(NetworkBufferWriter writer) {
writer.Write(Position);
writer.Write(Velocity);
writer.Write(LookPitch);
writer.Write(LookYaw);
writer.Write(IsGrounded);
}
public void Deserialize(NetworkBufferReader reader) {
reader.Read(out Position);
reader.Read(out Velocity);
reader.Read(out LookPitch);
reader.Read(out LookYaw);
reader.Read(out IsGrounded);
}
public int GetMaxBytes() {
return sizeof(float) * 3 + // Position;
sizeof(float) * 3 + // Velocity
sizeof(float) * 2 + // LookPitch, LookYaw
sizeof(bool); // IsGrounded
}
}
// @MARK: Player Actor Commands
enum PlayerActorCommands : ushort {
None = 0x0000,
UpdateRemoteState = 0x0001,
PickupItem = 0x0002,
DropItem = 0x0003,
EquipItem = 0x0004,
SelectItemSlot = 0x0005,
}
struct PlayerActorPickupItemCommand : IActorData {
public ulong ItemActorID;
public void Serialize(NetworkBufferWriter writer) {
writer.Write(ItemActorID);
}
public void Deserialize(NetworkBufferReader reader) {
reader.Read(out ItemActorID);
}
public int GetMaxBytes() {
return sizeof(ulong); // ItemActorID
}
}
struct PlayerActorDropItemCommand : IActorData {
public int InventorySlotIndex;
public int Count;
public void Serialize(NetworkBufferWriter writer) {
writer.Write(InventorySlotIndex);
writer.Write(Count);
}
public void Deserialize(NetworkBufferReader reader) {
reader.Read(out InventorySlotIndex);
reader.Read(out Count);
}
public int GetMaxBytes() {
return sizeof(int) * 2; // InventorySlotIndex, Count
}
}
struct PlayerActorEquipItemCommand : IActorData {
public int InventorySlotIndex;
public void Serialize(NetworkBufferWriter writer) {
writer.Write(InventorySlotIndex);
}
public void Deserialize(NetworkBufferReader reader) {
reader.Read(out InventorySlotIndex);
}
public int GetMaxBytes() {
return sizeof(int); // InventorySlotIndex
}
}
struct PlayerActorSelectItemSlotCommand : IActorData {
public int SlotIndex;
public void Serialize(NetworkBufferWriter writer) {
writer.Write(SlotIndex);
}
public void Deserialize(NetworkBufferReader reader) {
reader.Read(out SlotIndex);
}
public int GetMaxBytes() {
return sizeof(int); // SlotIndex
}
}
// @MARK: Player Actor Events
enum PlayerActorEvents : ushort {
None = 0x0000,
PrimaryEquippedItemChanged = 0x0001,
UpdatedRemoteState = 0x0002,
}
struct PlayerActorPrimaryEquippedItemChangedEvent : IActorData {
public ulong ItemActorID;
public void Serialize(NetworkBufferWriter writer) {
writer.Write(ItemActorID);
}
public void Deserialize(NetworkBufferReader reader) {
reader.Read(out ItemActorID);
}
public int GetMaxBytes() {
return sizeof(ulong); // ItemActorID
}
}
}