working on charging system
This commit is contained in:
53
Assets/jelycho/Code/Items/EggChargeAction.cs
Normal file
53
Assets/jelycho/Code/Items/EggChargeAction.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using RebootKit.Engine.Foundation;
|
||||
using RebootKit.Engine.Simulation;
|
||||
using RebootReality.jelycho.Player;
|
||||
|
||||
namespace RebootReality.jelycho.Items {
|
||||
public class EggChargeAction : IItemChargeAction {
|
||||
static readonly Logger s_Logger = new Logger(nameof(EggChargeAction));
|
||||
|
||||
public bool OnChargeStart(Actor user, ItemActor itemActor) {
|
||||
PlayerActor player = user as PlayerActor;
|
||||
if (player == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (itemActor.Config.itemType != ItemType.Egg) {
|
||||
s_Logger.Error($"Item {itemActor.name} is not an egg, cannot charge.");
|
||||
return false;
|
||||
}
|
||||
|
||||
s_Logger.Info($"Begin charging egg: {itemActor.name} by {user.name}");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void OnChargeUpdate(Actor user, ItemActor itemActor, float chargeProgress) {
|
||||
PlayerActor player = user as PlayerActor;
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Here you can implement the logic for updating the charge progress, e.g. visual effects
|
||||
s_Logger.Info($"Charging egg: {itemActor.name} by {user.name}, progress: {chargeProgress * 100}%");
|
||||
}
|
||||
|
||||
public void OnChargeEnd(Actor user, ItemActor itemActor, float chargeProgress) {
|
||||
PlayerActor player = user as PlayerActor;
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
s_Logger.Info($"Finished charging egg: {itemActor.name} by {user.name}, final progress: {chargeProgress * 100}%");
|
||||
}
|
||||
|
||||
public void OnChargeCancel(Actor user, ItemActor itemActor) {
|
||||
PlayerActor player = user as PlayerActor;
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
s_Logger.Info($"Charging egg: {itemActor.name} by {user.name} was cancelled.");
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/jelycho/Code/Items/EggChargeAction.cs.meta
Normal file
3
Assets/jelycho/Code/Items/EggChargeAction.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4db1d041795f44c3bbecefd644337773
|
||||
timeCreated: 1753850307
|
||||
@@ -6,11 +6,17 @@ namespace RebootReality.jelycho.Items {
|
||||
public class Inventory {
|
||||
static readonly Logger s_Logger = new Logger(nameof(Inventory));
|
||||
|
||||
class ItemState {
|
||||
public List<ItemActor> Actors = new List<ItemActor>();
|
||||
class SlotState {
|
||||
public ushort ActorID;
|
||||
public ItemActor Actor;
|
||||
|
||||
public void EmptySlot() {
|
||||
ActorID = 0;
|
||||
Actor = null;
|
||||
}
|
||||
}
|
||||
|
||||
readonly ItemState[] m_Items;
|
||||
|
||||
readonly SlotState[] m_Slots;
|
||||
|
||||
public event Action<ItemActor> OnItemPickedUp = delegate { };
|
||||
public event Action<ItemActor> OnItemDropped = delegate { };
|
||||
@@ -18,103 +24,114 @@ namespace RebootReality.jelycho.Items {
|
||||
|
||||
public int SlotsCount {
|
||||
get {
|
||||
return m_Items.Length;
|
||||
return m_Slots.Length;
|
||||
}
|
||||
}
|
||||
|
||||
public Inventory(int slotsCount) {
|
||||
m_Items = new ItemState[slotsCount];
|
||||
m_Slots = new SlotState[slotsCount];
|
||||
|
||||
for (int i = 0; i < slotsCount; i++) {
|
||||
m_Items[i] = new ItemState();
|
||||
m_Slots[i] = new SlotState();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// @MARK: Server API
|
||||
//
|
||||
public bool TryPickup(ItemActor actor) {
|
||||
if (Contains(actor)) {
|
||||
s_Logger.Error($"Item {actor.name} is already in the inventory.");
|
||||
return false;
|
||||
}
|
||||
|
||||
(int slotIndex, ItemState freeItemState) = FindFreeItemState();
|
||||
(int slotIndex, SlotState freeItemState) = FindFreeItemState();
|
||||
if (freeItemState == null) {
|
||||
s_Logger.Error("Inventory is full, cannot pick up item.");
|
||||
return false;
|
||||
}
|
||||
|
||||
freeItemState.Actors.Add(actor);
|
||||
freeItemState.Actor = actor;
|
||||
OnItemPickedUp?.Invoke(actor);
|
||||
OnSlotUpdated?.Invoke(slotIndex);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryDrop(ItemActor actor) {
|
||||
for (int i = 0; i < m_Items.Length; i++) {
|
||||
if (m_Items[i].Actors.Remove(actor)) {
|
||||
OnItemDropped?.Invoke(actor);
|
||||
OnSlotUpdated?.Invoke(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
s_Logger.Error($"Item {actor.name} is not in the inventory.");
|
||||
return false;
|
||||
}
|
||||
|
||||
public ItemActor TryDropOne(int slotIndex) {
|
||||
if (slotIndex < 0 || slotIndex >= m_Items.Length) {
|
||||
public bool TryDrop(int slotIndex, out ItemActor actor) {
|
||||
if (slotIndex < 0 || slotIndex >= m_Slots.Length) {
|
||||
s_Logger.Error($"Slot index {slotIndex} is out of range.");
|
||||
return null;
|
||||
actor = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_Items[slotIndex].Actors.Count == 0) {
|
||||
if (m_Slots[slotIndex].Actor == null) {
|
||||
s_Logger.Error($"No items in slot {slotIndex} to drop.");
|
||||
return null;
|
||||
actor = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
ItemActor actor = m_Items[slotIndex].Actors[0];
|
||||
m_Items[slotIndex].Actors.RemoveAt(0);
|
||||
actor = m_Slots[slotIndex].Actor;
|
||||
m_Slots[slotIndex].Actor = null;
|
||||
|
||||
OnItemDropped?.Invoke(actor);
|
||||
OnSlotUpdated?.Invoke(slotIndex);
|
||||
return actor;
|
||||
}
|
||||
|
||||
public int GetQuantity(int slotIndex) {
|
||||
if (slotIndex < 0 || slotIndex >= m_Items.Length) {
|
||||
//
|
||||
// @MARK: Common API
|
||||
//
|
||||
public void SetItem(int slotIndex, ItemActor actor) {
|
||||
if (slotIndex < 0 || slotIndex >= m_Slots.Length) {
|
||||
throw new ArgumentOutOfRangeException(nameof(slotIndex), "Slot index is out of range.");
|
||||
}
|
||||
|
||||
return m_Items[slotIndex].Actors.Count;
|
||||
if (actor == null) {
|
||||
m_Slots[slotIndex].EmptySlot();
|
||||
OnSlotUpdated?.Invoke(slotIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Contains(actor)) {
|
||||
s_Logger.Error($"Item {actor.name} is already in the inventory.");
|
||||
return;
|
||||
}
|
||||
|
||||
m_Slots[slotIndex].Actor = actor;
|
||||
m_Slots[slotIndex].ActorID = actor.ActorID;
|
||||
OnSlotUpdated?.Invoke(slotIndex);
|
||||
}
|
||||
|
||||
public ItemActor GetFirstItem(int slotIndex) {
|
||||
if (slotIndex < 0 || slotIndex >= m_Items.Length) {
|
||||
public ItemActor GetItem(int slotIndex) {
|
||||
if (slotIndex < 0 || slotIndex >= m_Slots.Length) {
|
||||
throw new ArgumentOutOfRangeException(nameof(slotIndex), "Slot index is out of range.");
|
||||
}
|
||||
|
||||
if (m_Items[slotIndex].Actors.Count > 0) {
|
||||
return m_Items[slotIndex].Actors[0];
|
||||
}
|
||||
|
||||
return null;
|
||||
return m_Slots[slotIndex].Actor;
|
||||
}
|
||||
|
||||
public bool Contains(ItemActor actor) {
|
||||
for (int i = 0; i < m_Items.Length; i++) {
|
||||
if (m_Items[i].Actors.Contains(actor)) {
|
||||
for (int i = 0; i < m_Slots.Length; i++) {
|
||||
if (m_Slots[i].Actor == actor) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
(int, ItemState) FindFreeItemState() {
|
||||
for (int i = 0; i < m_Items.Length; i++) {
|
||||
if (m_Items[i].Actors.Count == 0) {
|
||||
return (i, m_Items[i]);
|
||||
|
||||
(int slot, SlotState state) FindFreeItemState() {
|
||||
for (int i = 0; i < m_Slots.Length; i++) {
|
||||
if (m_Slots[i].Actor == null) {
|
||||
return (i, m_Slots[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return (-1, null);
|
||||
}
|
||||
|
||||
//
|
||||
// @MARK: Client API
|
||||
//
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using RebootKit.Engine.Simulation;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RebootReality.jelycho.Items {
|
||||
@@ -9,6 +10,13 @@ namespace RebootReality.jelycho.Items {
|
||||
Sword = 2,
|
||||
}
|
||||
|
||||
public interface IItemChargeAction {
|
||||
bool OnChargeStart(Actor user, ItemActor itemActor);
|
||||
void OnChargeUpdate(Actor user, ItemActor itemActor, float chargeProgress);
|
||||
void OnChargeEnd(Actor user, ItemActor itemActor, float chargeProgress);
|
||||
void OnChargeCancel(Actor user, ItemActor itemActor);
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class ItemConfig {
|
||||
public ItemType itemType = ItemType.Neutral;
|
||||
@@ -18,7 +26,9 @@ namespace RebootReality.jelycho.Items {
|
||||
|
||||
[Header("Chargeable")]
|
||||
public bool isChargeable = false;
|
||||
public float chargeDuration = 0.5f;
|
||||
public float minChargeDuration = 0.1f;
|
||||
public float maxChargeDuration = 1.0f;
|
||||
public float chargeCooldown = 1.0f;
|
||||
[SerializeReference] public IItemChargeAction chargeAction;
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,6 @@ namespace RebootReality.jelycho.Player.HUD {
|
||||
public VisualElement Root;
|
||||
public VisualElement Slot;
|
||||
public VisualElement Icon;
|
||||
public Label QuantityLabel;
|
||||
}
|
||||
|
||||
readonly List<InventorySlot> m_InventorySlots = new List<InventorySlot>();
|
||||
@@ -43,13 +42,11 @@ namespace RebootReality.jelycho.Player.HUD {
|
||||
InventorySlot slot = new InventorySlot {
|
||||
Root = slotRoot,
|
||||
Slot = slotRoot.Q<VisualElement>("player-hud__inventory-slot"),
|
||||
Icon = slotRoot.Q<VisualElement>("player-hud__inventory-slot-icon"),
|
||||
QuantityLabel = slotRoot.Q<Label>("player-hud__inventory-slot-quantity"),
|
||||
Icon = slotRoot.Q<VisualElement>("player-hud__inventory-slot-icon")
|
||||
};
|
||||
Assert.IsNotNull(slot.Root, "Slot root cannot be null");
|
||||
Assert.IsNotNull(slot.Slot, "Slot element cannot be null");
|
||||
Assert.IsNotNull(slot.Icon, "Slot icon cannot be null");
|
||||
Assert.IsNotNull(slot.QuantityLabel, "Slot quantity label cannot be null");
|
||||
|
||||
m_InventorySlots.Add(slot);
|
||||
OnSlotUpdated(i);
|
||||
@@ -86,25 +83,15 @@ namespace RebootReality.jelycho.Player.HUD {
|
||||
s_Logger.Error($"Invalid slot index: {slotIndex}. Inventory has {m_InventorySlots.Count} slots.");
|
||||
return;
|
||||
}
|
||||
|
||||
int quantity = m_Inventory.GetQuantity(slotIndex);
|
||||
|
||||
if (quantity <= 0) {
|
||||
m_InventorySlots[slotIndex].QuantityLabel.style.display = DisplayStyle.None;
|
||||
ItemActor itemActor = m_Inventory.GetItem(slotIndex);
|
||||
|
||||
if (itemActor == null) {
|
||||
m_InventorySlots[slotIndex].Icon.style.backgroundImage = null;
|
||||
m_InventorySlots[slotIndex].Icon.style.display = DisplayStyle.None;
|
||||
} else {
|
||||
m_InventorySlots[slotIndex].QuantityLabel.style.display = DisplayStyle.Flex;
|
||||
m_InventorySlots[slotIndex].QuantityLabel.text = quantity.ToString();
|
||||
|
||||
ItemActor itemActor = m_Inventory.GetFirstItem(slotIndex);
|
||||
if (itemActor != null) {
|
||||
m_InventorySlots[slotIndex].Icon.style.backgroundImage = new StyleBackground(itemActor.Config.icon);
|
||||
m_InventorySlots[slotIndex].Icon.style.display = DisplayStyle.Flex;
|
||||
} else {
|
||||
m_InventorySlots[slotIndex].Icon.style.backgroundImage = null;
|
||||
m_InventorySlots[slotIndex].Icon.style.display = DisplayStyle.None;
|
||||
}
|
||||
m_InventorySlots[slotIndex].Icon.style.backgroundImage = new StyleBackground(itemActor.Config.icon);
|
||||
m_InventorySlots[slotIndex].Icon.style.display = DisplayStyle.Flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using R3;
|
||||
using System;
|
||||
using R3;
|
||||
using RebootKit.Engine.Extensions;
|
||||
using RebootKit.Engine.Foundation;
|
||||
using RebootKit.Engine.Main;
|
||||
@@ -11,6 +12,16 @@ using UnityEngine;
|
||||
using Logger = RebootKit.Engine.Foundation.Logger;
|
||||
|
||||
namespace RebootReality.jelycho.Player {
|
||||
[Serializable]
|
||||
struct PlayerItemTypeHandsAnimationsConfig {
|
||||
public ItemType itemType;
|
||||
|
||||
// @TODO: Cache hashes for state names
|
||||
public string idle;
|
||||
public string charging;
|
||||
public string chargedUse;
|
||||
}
|
||||
|
||||
public class PlayerActor : Actor {
|
||||
static readonly Logger s_Logger = new Logger(nameof(PlayerActor));
|
||||
|
||||
@@ -48,6 +59,13 @@ namespace RebootReality.jelycho.Player {
|
||||
|
||||
float m_CharacterTurnVelocity = 0.0f;
|
||||
|
||||
[Header("Animations")]
|
||||
[SerializeField] int m_HandsLayerIndex;
|
||||
|
||||
[SerializeField] PlayerItemTypeHandsAnimationsConfig[] m_ItemTypeHandsAnimations;
|
||||
[SerializeField] PlayerItemTypeHandsAnimationsConfig m_DefaultItemHandsAnimations;
|
||||
[SerializeField] string m_HandsIdleStateName = "Hands Locomotion";
|
||||
|
||||
[Header("Dragging")]
|
||||
[SerializeField] Transform m_DragGutStartPosition;
|
||||
[SerializeField] PhysicsObjectDragger m_PhysicsDragger;
|
||||
@@ -73,10 +91,12 @@ namespace RebootReality.jelycho.Player {
|
||||
[SerializeField] int m_InventorySize = 10;
|
||||
|
||||
public Inventory Inventory { get; private set; }
|
||||
public ReactiveProperty<int> SelectedInventorySlot { get; private set; } = new ReactiveProperty<int>(0);
|
||||
|
||||
ItemActor m_EquippedItem;
|
||||
|
||||
public ReactiveProperty<int> SelectedInventorySlot { get; private set; } = new ReactiveProperty<int>(0);
|
||||
bool m_IsCharging;
|
||||
float m_ChargeTimer;
|
||||
|
||||
public float3 LookDirection {
|
||||
get {
|
||||
@@ -120,142 +140,6 @@ namespace RebootReality.jelycho.Player {
|
||||
Inventory.OnItemDropped -= OnItemDropped;
|
||||
}
|
||||
|
||||
//
|
||||
// @MARK: Actor
|
||||
//
|
||||
public override void OnServerTick(float deltaTime) {
|
||||
base.OnServerTick(deltaTime);
|
||||
|
||||
NativeArray<byte> remoteStateData = DataSerializationUtils.Serialize(m_RemoteState);
|
||||
SendActorEvent((byte) 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((byte) PlayerActorCommands.UpdateRemoteState, data);
|
||||
}
|
||||
} else {
|
||||
InterpolateActorState(deltaTime);
|
||||
}
|
||||
|
||||
TickCharacterRotation();
|
||||
}
|
||||
|
||||
protected override void OnActorCommandServer(ulong senderID, ActorCommand actorCommand) {
|
||||
switch ((PlayerActorCommands)actorCommand.CommandID) {
|
||||
case PlayerActorCommands.UpdateRemoteState: {
|
||||
RemotePlayerActorState remoteState = new RemotePlayerActorState();
|
||||
DataSerializationUtils.Deserialize(actorCommand.Data, ref remoteState);
|
||||
m_RemoteState = remoteState;
|
||||
break;
|
||||
}
|
||||
|
||||
case 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.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 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();
|
||||
break;
|
||||
}
|
||||
|
||||
case 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.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnActorEventClient(ActorEvent actorEvent) {
|
||||
base.OnActorEventClient(actorEvent);
|
||||
|
||||
switch ((PlayerActorEvents)actorEvent.EventID) {
|
||||
case PlayerActorEvents.UpdatedRemoteState: {
|
||||
RemotePlayerActorState remoteState = new RemotePlayerActorState();
|
||||
DataSerializationUtils.Deserialize(actorEvent.Data, ref remoteState);
|
||||
m_RemoteState = remoteState;
|
||||
break;
|
||||
}
|
||||
case 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.");
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
s_Logger.Error("Invalid actor event received: " + actorEvent.EventID);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// @MARK: Controller API
|
||||
//
|
||||
@@ -314,7 +198,7 @@ namespace RebootReality.jelycho.Player {
|
||||
|
||||
m_PhysicsDragger.Drop();
|
||||
}
|
||||
|
||||
|
||||
public void DropItem() {
|
||||
if (!m_IsSetupAsOwner) {
|
||||
s_Logger.Error("Cannot drop item when not set up as owner.");
|
||||
@@ -323,30 +207,98 @@ namespace RebootReality.jelycho.Player {
|
||||
|
||||
if (m_EquippedItem != null) {
|
||||
PlayerActorDropItemCommand command = new PlayerActorDropItemCommand {
|
||||
InventorySlotIndex = SelectedInventorySlot.Value,
|
||||
Count = 1
|
||||
InventorySlotIndex = (byte) SelectedInventorySlot.Value
|
||||
};
|
||||
SendActorCommand((byte) PlayerActorCommands.DropItem, ref command);
|
||||
}
|
||||
}
|
||||
|
||||
public void PrimaryAction() {
|
||||
PlayerItemTypeHandsAnimationsConfig GetHandsAnimationsConfig(ItemType itemType) {
|
||||
foreach (PlayerItemTypeHandsAnimationsConfig config in m_ItemTypeHandsAnimations) {
|
||||
if (config.itemType == itemType) {
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
return m_DefaultItemHandsAnimations;
|
||||
}
|
||||
|
||||
void SetHandsIdleAnimation() {
|
||||
if (m_EquippedItem != null) {
|
||||
PlayerItemTypeHandsAnimationsConfig animationsConfig =
|
||||
GetHandsAnimationsConfig(m_EquippedItem.Config.itemType);
|
||||
m_Animator.CrossFade(animationsConfig.idle, 0.0f, m_HandsLayerIndex);
|
||||
} else {
|
||||
m_Animator.CrossFade(m_HandsIdleStateName, 0.0f, m_HandsLayerIndex);
|
||||
}
|
||||
}
|
||||
|
||||
void SetChargingAnimation() {
|
||||
if (m_EquippedItem != null) {
|
||||
PlayerItemTypeHandsAnimationsConfig animationsConfig =
|
||||
GetHandsAnimationsConfig(m_EquippedItem.Config.itemType);
|
||||
m_Animator.CrossFade(animationsConfig.charging, 0.0f, m_HandsLayerIndex);
|
||||
}
|
||||
}
|
||||
|
||||
void SetChargedUseAnimation() {
|
||||
if (m_EquippedItem != null) {
|
||||
PlayerItemTypeHandsAnimationsConfig animationsConfig =
|
||||
GetHandsAnimationsConfig(m_EquippedItem.Config.itemType);
|
||||
m_Animator.CrossFade(animationsConfig.chargedUse, 0.0f, m_HandsLayerIndex);
|
||||
}
|
||||
}
|
||||
|
||||
public void BeginPrimaryAction() {
|
||||
if (!m_IsSetupAsOwner) {
|
||||
s_Logger.Error("Cannot perform primary action when not set up as owner.");
|
||||
s_Logger.Error("Cannot begin 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);
|
||||
// }
|
||||
// }
|
||||
if (m_EquippedItem == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_EquippedItem.Config.chargeAction != null && m_EquippedItem.Config.isChargeable) {
|
||||
if (m_EquippedItem.Config.chargeAction.OnChargeStart(this, m_EquippedItem)) {
|
||||
m_IsCharging = true;
|
||||
m_ChargeTimer = 0.0f;
|
||||
SetChargingAnimation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float GetChargeProgress() {
|
||||
if (m_EquippedItem == null || !m_EquippedItem.Config.isChargeable) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
float chargeProgress = Mathf.InverseLerp(m_EquippedItem.Config.minChargeDuration,
|
||||
m_EquippedItem.Config.maxChargeDuration,
|
||||
m_ChargeTimer);
|
||||
return Mathf.Clamp01(chargeProgress);
|
||||
}
|
||||
|
||||
public void EndPrimaryAction() {
|
||||
if (!m_IsSetupAsOwner) {
|
||||
s_Logger.Error("Cannot end primary action when not set up as owner.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_IsCharging) {
|
||||
ItemConfig itemConfig = m_EquippedItem.Config;
|
||||
|
||||
if (m_ChargeTimer >= itemConfig.minChargeDuration) {
|
||||
float chargeProgress = GetChargeProgress();
|
||||
itemConfig.chargeAction.OnChargeEnd(this, m_EquippedItem, chargeProgress);
|
||||
SetChargedUseAnimation();
|
||||
} else {
|
||||
itemConfig.chargeAction.OnChargeCancel(this, m_EquippedItem);
|
||||
SetHandsIdleAnimation();
|
||||
}
|
||||
|
||||
m_IsCharging = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void SecondaryAction() {
|
||||
@@ -355,7 +307,13 @@ namespace RebootReality.jelycho.Player {
|
||||
return;
|
||||
}
|
||||
|
||||
m_Animator.SetTrigger(AnimatorParamHashes.Block);
|
||||
if (m_IsCharging) {
|
||||
m_EquippedItem.Config.chargeAction.OnChargeCancel(this, m_EquippedItem);
|
||||
m_IsCharging = false;
|
||||
m_ChargeTimer = 0.0f;
|
||||
|
||||
SetHandsIdleAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
public void Interact() {
|
||||
@@ -372,6 +330,171 @@ namespace RebootReality.jelycho.Player {
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// @MARK: Actor
|
||||
//
|
||||
public override void OnServerTick(float deltaTime) {
|
||||
base.OnServerTick(deltaTime);
|
||||
|
||||
NativeArray<byte> remoteStateData = DataSerializationUtils.Serialize(m_RemoteState);
|
||||
SendActorEvent((byte) PlayerActorEvents.UpdatedRemoteState, remoteStateData);
|
||||
}
|
||||
|
||||
public override void OnClientTick(float deltaTime) {
|
||||
base.OnClientTick(deltaTime);
|
||||
|
||||
if (m_IsSetupAsOwner) {
|
||||
TickCamera();
|
||||
UpdateAnimator(m_Locomotion.Velocity);
|
||||
SenseInteractable();
|
||||
|
||||
if (m_IsCharging) {
|
||||
m_ChargeTimer += deltaTime;
|
||||
m_EquippedItem.Config.chargeAction.OnChargeUpdate(this, m_EquippedItem, GetChargeProgress());
|
||||
}
|
||||
|
||||
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((byte) PlayerActorCommands.UpdateRemoteState, data);
|
||||
}
|
||||
} else {
|
||||
InterpolateActorState(deltaTime);
|
||||
}
|
||||
|
||||
TickCharacterRotation();
|
||||
}
|
||||
|
||||
protected override void OnActorCommandServer(ulong senderID, ActorCommand actorCommand) {
|
||||
switch ((PlayerActorCommands) actorCommand.CommandID) {
|
||||
case PlayerActorCommands.UpdateRemoteState: {
|
||||
RemotePlayerActorState remoteState = new RemotePlayerActorState();
|
||||
DataSerializationUtils.Deserialize(actorCommand.Data, ref remoteState);
|
||||
m_RemoteState = remoteState;
|
||||
break;
|
||||
}
|
||||
|
||||
case 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.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 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();
|
||||
break;
|
||||
}
|
||||
|
||||
case PlayerActorCommands.DropItem: {
|
||||
PlayerActorDropItemCommand command = new PlayerActorDropItemCommand();
|
||||
DataSerializationUtils.Deserialize(actorCommand.Data, ref command);
|
||||
|
||||
Inventory.TryDrop(command.InventorySlotIndex, out _);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnActorEventClient(ActorEvent actorEvent) {
|
||||
base.OnActorEventClient(actorEvent);
|
||||
|
||||
switch ((PlayerActorEvents) actorEvent.EventID) {
|
||||
case PlayerActorEvents.UpdatedRemoteState: {
|
||||
RemotePlayerActorState remoteState = new RemotePlayerActorState();
|
||||
DataSerializationUtils.Deserialize(actorEvent.Data, ref remoteState);
|
||||
m_RemoteState = remoteState;
|
||||
break;
|
||||
}
|
||||
|
||||
case 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.");
|
||||
}
|
||||
}
|
||||
|
||||
SetHandsIdleAnimation();
|
||||
break;
|
||||
}
|
||||
|
||||
case PlayerActorEvents.UpdateInventory: {
|
||||
if (RR.IsServer()) {
|
||||
break;
|
||||
}
|
||||
|
||||
PlayerUpdateInventoryEvent updateInventoryEvent = new PlayerUpdateInventoryEvent();
|
||||
DataSerializationUtils.Deserialize(actorEvent.Data, ref updateInventoryEvent);
|
||||
|
||||
for (int i = 0; i < Inventory.SlotsCount; i++) {
|
||||
ushort actorID = updateInventoryEvent.SlotsActorIDs[i];
|
||||
if (actorID == 0) {
|
||||
Inventory.SetItem(i, null);
|
||||
} else {
|
||||
Actor itemActor = RR.FindSpawnedActor(actorID);
|
||||
if (itemActor is ItemActor item) {
|
||||
Inventory.SetItem(i, item);
|
||||
} else {
|
||||
s_Logger.Error($"Item actor with ID {actorID} not found for inventory slot {i}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
s_Logger.Error("Invalid actor event received: " + actorEvent.EventID);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// @MARK: Owner
|
||||
//
|
||||
@@ -456,13 +579,28 @@ namespace RebootReality.jelycho.Player {
|
||||
//
|
||||
// @MARK: Server
|
||||
//
|
||||
void SendInventoryState() {
|
||||
if (!RR.IsServer()) {
|
||||
s_Logger.Error("Only the server can send inventory state.");
|
||||
return;
|
||||
}
|
||||
|
||||
PlayerUpdateInventoryEvent updateInventoryEvent = new PlayerUpdateInventoryEvent();
|
||||
updateInventoryEvent.SlotsActorIDs = new NativeArray<ushort>(Inventory.SlotsCount, Allocator.Temp);
|
||||
for (int i = 0; i < Inventory.SlotsCount; i++) {
|
||||
updateInventoryEvent.SlotsActorIDs[i] = Inventory.GetItem(i)?.ActorID ?? (ushort) 0;
|
||||
}
|
||||
|
||||
SendActorEvent((byte) PlayerActorEvents.UpdateInventory, ref updateInventoryEvent);
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -470,6 +608,8 @@ namespace RebootReality.jelycho.Player {
|
||||
|
||||
item.transform.position = m_HeadBoneTransform.position + m_HeadBoneTransform.forward * 1.0f;
|
||||
item.transform.rotation = Quaternion.LookRotation(m_HeadBoneTransform.forward, Vector3.up);
|
||||
|
||||
SendInventoryState();
|
||||
}
|
||||
|
||||
void OnItemPickedUp(ItemActor item) {
|
||||
@@ -478,6 +618,7 @@ namespace RebootReality.jelycho.Player {
|
||||
}
|
||||
|
||||
item.SetHidden(true);
|
||||
SendInventoryState();
|
||||
}
|
||||
|
||||
void UpdateEquippedItem() {
|
||||
@@ -486,7 +627,7 @@ namespace RebootReality.jelycho.Player {
|
||||
return;
|
||||
}
|
||||
|
||||
ItemActor itemActor = Inventory.GetFirstItem(SelectedInventorySlot.Value);
|
||||
ItemActor itemActor = Inventory.GetItem(SelectedInventorySlot.Value);
|
||||
if (itemActor == m_EquippedItem) {
|
||||
return;
|
||||
}
|
||||
@@ -504,9 +645,11 @@ namespace RebootReality.jelycho.Player {
|
||||
|
||||
PlayerActorPrimaryEquippedItemChangedEvent itemChangedEvent =
|
||||
new PlayerActorPrimaryEquippedItemChangedEvent {
|
||||
ItemActorID = m_EquippedItem != null ? m_EquippedItem.ActorID : (ushort)0
|
||||
ItemActorID = m_EquippedItem != null ? m_EquippedItem.ActorID : (ushort) 0
|
||||
};
|
||||
SendActorEvent((byte) PlayerActorEvents.PrimaryEquippedItemChanged, ref itemChangedEvent);
|
||||
|
||||
SetHandsIdleAnimation();
|
||||
}
|
||||
|
||||
public void WarpTo(Vector3 position) {
|
||||
@@ -682,12 +825,12 @@ namespace RebootReality.jelycho.Player {
|
||||
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;
|
||||
}
|
||||
@@ -720,7 +863,7 @@ namespace RebootReality.jelycho.Player {
|
||||
return sizeof(float) * 3 + // Position;
|
||||
sizeof(float) * 3 + // Velocity
|
||||
sizeof(float) * 2 + // LookPitch, LookYaw
|
||||
sizeof(bool); // IsGrounded
|
||||
sizeof(bool); // IsGrounded
|
||||
}
|
||||
}
|
||||
|
||||
@@ -751,21 +894,18 @@ namespace RebootReality.jelycho.Player {
|
||||
}
|
||||
|
||||
struct PlayerActorDropItemCommand : IActorData {
|
||||
public int InventorySlotIndex;
|
||||
public int Count;
|
||||
public byte InventorySlotIndex;
|
||||
|
||||
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
|
||||
return sizeof(byte);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -795,7 +935,7 @@ namespace RebootReality.jelycho.Player {
|
||||
public void Deserialize(NetworkBufferReader reader) {
|
||||
reader.Read(out SlotIndex);
|
||||
}
|
||||
|
||||
|
||||
public int GetMaxBytes() {
|
||||
return sizeof(int); // SlotIndex
|
||||
}
|
||||
@@ -806,6 +946,7 @@ namespace RebootReality.jelycho.Player {
|
||||
None = 0x00,
|
||||
PrimaryEquippedItemChanged = 0x01,
|
||||
UpdatedRemoteState = 0x02,
|
||||
UpdateInventory = 0x03,
|
||||
}
|
||||
|
||||
struct PlayerActorPrimaryEquippedItemChangedEvent : IActorData {
|
||||
@@ -823,4 +964,28 @@ namespace RebootReality.jelycho.Player {
|
||||
return sizeof(ushort); // ItemActorID
|
||||
}
|
||||
}
|
||||
|
||||
struct PlayerUpdateInventoryEvent : IActorData {
|
||||
public NativeArray<ushort> SlotsActorIDs;
|
||||
|
||||
public void Serialize(NetworkBufferWriter writer) {
|
||||
writer.Write((byte) SlotsActorIDs.Length);
|
||||
for (int i = 0; i < SlotsActorIDs.Length; i++) {
|
||||
writer.Write(SlotsActorIDs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public void Deserialize(NetworkBufferReader reader) {
|
||||
reader.Read(out byte slotsCount);
|
||||
SlotsActorIDs = new NativeArray<ushort>(slotsCount, Allocator.Temp);
|
||||
for (int i = 0; i < slotsCount; i++) {
|
||||
reader.Read(out ushort actorID);
|
||||
SlotsActorIDs[i] = actorID;
|
||||
}
|
||||
}
|
||||
|
||||
public int GetMaxBytes() {
|
||||
return sizeof(byte) + SlotsActorIDs.Length * sizeof(ushort);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -128,8 +128,12 @@ namespace RebootReality.jelycho.Player {
|
||||
m_Actor.DropItem();
|
||||
}
|
||||
|
||||
if (m_Config.primaryActionReference.action.WasPressedThisFrame()) {
|
||||
m_Actor.BeginPrimaryAction();
|
||||
}
|
||||
|
||||
if (m_Config.primaryActionReference.action.WasReleasedThisFrame()) {
|
||||
m_Actor.PrimaryAction();
|
||||
m_Actor.EndPrimaryAction();
|
||||
}
|
||||
|
||||
if (m_Config.secondaryActionReference.action.WasReleasedThisFrame()) {
|
||||
|
||||
Reference in New Issue
Block a user