From 49f7f66db494dee5cc236cedb6bfeda24935d8da Mon Sep 17 00:00:00 2001 From: copygirl Date: Fri, 26 Jan 2024 16:21:58 +0100 Subject: [PATCH] Move Item RPCs to ItemManager --- objects/Item.cs | 109 -------------------------- player/PickupController.cs | 7 +- scenes/ItemManager.cs | 155 ++++++++++++++++++++++++++++++++----- 3 files changed, 141 insertions(+), 130 deletions(-) diff --git a/objects/Item.cs b/objects/Item.cs index d45e47e..f5a4280 100644 --- a/objects/Item.cs +++ b/objects/Item.cs @@ -57,113 +57,4 @@ public partial class Item : RigidBody3D Freeze = IsAttached; // Sync.PublicVisibility = !IsAttached; } - - - public void TryPickup() - { - RPC.To(GetMultiplayerAuthority()).Send(Manager.RelayRequest, GetPath(), - ItemManager.RequestFunc.RequestPickup, - new Godot.Collections.Array()); - // RPC.To(GetMultiplayerAuthority()).Send(RequestPickup); - } - - [Rpc(RpcMode.AnyPeer, CallLocal = true)] - void RequestPickup() - { - // Pickup will be requested by any player to the owner of the item. - // If this message is received by anyone else, ignore it (for now). - if (!IsMultiplayerAuthority()) return; - - var player = Multiplayer.GetRemoteSenderPlayer(); - if (player.Pickup.HasItemsHeld) return; - // TODO: Check if player is in range. - - var localTransform = player.Pickup.GlobalBasis.Inverse() * GlobalBasis; - RPC.ToAll().Send(Manager.RelayAccept, GetPath(), - ItemManager.AcceptFunc.AcceptPickup, - new Godot.Collections.Array { player.PeerId, localTransform }); - // RPC.ToAll().Send(AcceptPickup, player.PeerId, localTransform); - } - - [Rpc(CallLocal = true)] - void AcceptPickup(int peerId, Transform3D localTransform) - { - var player = Game.Players.ByPeerId(peerId); - Reparent(player.Pickup); - Transform = localTransform; - } - - - public void TryPlace(Grid grid, Transform3D localTransform) - { - RPC.To(GetMultiplayerAuthority()).Send(Manager.RelayRequest, GetPath(), - ItemManager.RequestFunc.RequestPlace, - new Godot.Collections.Array { grid.GetPath(), localTransform }); - // RPC.To(GetMultiplayerAuthority()).Send(RequestPlace, grid.GetPath(), localTransform); - } - - [Rpc(RpcMode.AnyPeer, CallLocal = true)] - void RequestPlace(NodePath gridPath, Transform3D localTransform) - { - // Must be received by the owner of this item. - if (!IsMultiplayerAuthority()) return; - - if (GetNodeOrNull(gridPath) is not Grid grid) return; - // Item and Grid must be owned by the same peer. - if (!grid.IsMultiplayerAuthority()) return; - if (!grid.CanPlaceAt(this, localTransform)) return; - - RPC.ToAll().Send(Manager.RelayAccept, GetPath(), - ItemManager.AcceptFunc.AcceptPlace, - new Godot.Collections.Array { gridPath, localTransform }); - // RPC.ToAll().Send(AcceptPlace, gridPath, localTransform); - } - - [Rpc(CallLocal = true)] - void AcceptPlace(NodePath gridPath, Transform3D localTransform) - { - if (GetNodeOrNull(gridPath) is not Grid grid) return; - // Item and Grid must be owned by the same peer. - if (grid.GetMultiplayerAuthority() != GetMultiplayerAuthority()) return; - - Reparent(grid); - Transform = localTransform; - } - - - public void TryThrow() - { - RPC.To(GetMultiplayerAuthority()).Send(Manager.RelayRequest, GetPath(), - ItemManager.RequestFunc.RequestThrow, - new Godot.Collections.Array()); - // RPC.To(GetMultiplayerAuthority()).Send(RequestThrow); - } - - [Rpc(RpcMode.AnyPeer, CallLocal = true)] - void RequestThrow() - { - if (!IsMultiplayerAuthority()) return; - - var player = Multiplayer.GetRemoteSenderPlayer(); - if (this.FindParentOrNull() != player) return; - - RPC.ToAll().Send(Manager.RelayAccept, GetPath(), - ItemManager.AcceptFunc.AcceptThrow, - new Godot.Collections.Array()); - // RPC.ToAll().Send(AcceptThrow); - } - - [Rpc(CallLocal = true)] - void AcceptThrow() - { - var player = this.FindParentOrThrow(); - var world = Manager.GetParent(); - - Reparent(world, true); - - // Throw item forward and up a bit. - var basis = player.Camera.Camera.GlobalBasis; - var direction = -basis.Z + basis.Y; - ApplyImpulse(direction * 2); - } } diff --git a/player/PickupController.cs b/player/PickupController.cs index e9246ad..00f87a2 100644 --- a/player/PickupController.cs +++ b/player/PickupController.cs @@ -39,7 +39,7 @@ public partial class PickupController : Node3D if (@event.IsActionPressed("interact_pickup") && (TargetedItem != null) && !HasItemsHeld) { - TargetedItem.TryPickup(); + TargetedItem.Manager.TryPickup(TargetedItem); GetViewport().SetInputAsHandled(); } @@ -48,9 +48,10 @@ public partial class PickupController : Node3D { if (_preview.Visible && (_targetedGrid != null)) { var inverse = _targetedGrid.GlobalTransform.AffineInverse(); - HeldItem.TryPlace(_targetedGrid, inverse * _preview.GlobalTransform); + HeldItem.Manager.TryPlace(HeldItem, + _targetedGrid, inverse * _preview.GlobalTransform); } else - HeldItem.TryThrow(); + HeldItem.Manager.TryThrow(HeldItem); GetViewport().SetInputAsHandled(); } } diff --git a/scenes/ItemManager.cs b/scenes/ItemManager.cs index fc8639c..77b45cd 100644 --- a/scenes/ItemManager.cs +++ b/scenes/ItemManager.cs @@ -1,5 +1,6 @@ public partial class ItemManager : Node { + uint _trackingIdCounter = 0; public Dictionary TrackedItems { get; } = []; public override void _Ready() @@ -13,8 +14,10 @@ public partial class ItemManager : Node if (item.Manager != null) throw new ArgumentException( $"Item '{item.GetPath()}' is already part of another scene"); - // Generate a random, unused id for this item. - uint id; do { id = Randi(); } while (TrackedItems.ContainsKey(id)); + // FIXME: Synchronize items so we can get rid of this temporary fix. + // // Generate a random, unused id for this item. + // uint id; do { id = Randi(); } while (TrackedItems.ContainsKey(id)); + var id = _trackingIdCounter++; TrackedItems.Add(id, item); item.Manager = this; @@ -33,31 +36,147 @@ public partial class ItemManager : Node } - // FIXME: This should be fixed in 4.3 and thus not required anymore? - // FIXME: Don't actually rely on item path, use its TrackingId. - // https://github.com/godotengine/godot/issues/85883 + public void TryPickup(Item item) + { + VerifyManager(item); + VerifyAuthority(item, "Item"); + RPC.To(item.GetMultiplayerAuthority()).Send(RequestPickup, item.TrackingId); + } + + [Rpc(RpcMode.AnyPeer, CallLocal = true)] + void RequestPickup(uint itemTrackingId) + { + // TODO: Print debug messages instead of just returning? + + // Pickup will be requested by any player to the owner of the item. + // If this message is received by anyone else, ignore it. + if (!IsMultiplayerAuthority()) return; - internal enum RequestFunc { RequestPickup, RequestPlace, RequestThrow } - internal enum AcceptFunc { AcceptPickup, AcceptPlace, AcceptThrow } + // Find the item being requested to be picked up, return if not found. + if (!TrackedItems.TryGetValue(itemTrackingId, out var item)) return; + // Make sure that the item shares its authority with this Manager. + if (!item.IsMultiplayerAuthority()) return; + + var player = Multiplayer.GetRemoteSenderPlayer(); + if (player.Pickup.HasItemsHeld) return; + // TODO: Check if player is in range. + + // Have the item be rotated in the player's hands depending on how it's currently rotated. + var localBasis = player.Pickup.GlobalBasis.Inverse() * item.GlobalBasis; + var localTransform = new Transform3D(localBasis, Vector3.Zero); + + RPC.ToAll().Send(AcceptPickup, itemTrackingId, player.PeerId, localTransform); + } + + [Rpc(CallLocal = true)] + void AcceptPickup(uint itemTrackingId, int playerPeerId, Transform3D localTransform) + { + var item = VerifyAuthority(TrackedItems[itemTrackingId], "Item"); + var player = Game.Players.ByPeerId(playerPeerId); + item.Reparent(player.Pickup); + item.Transform = localTransform; + } + + + public void TryPlace(Item item, Grid grid, Transform3D localTransform) + { + VerifyManager(item); + VerifyAuthority(item, "Item"); + RPC.To(item.GetMultiplayerAuthority()).Send(RequestPlace, + item.TrackingId, grid.GetPath(), localTransform); + } [Rpc(RpcMode.AnyPeer, CallLocal = true)] - internal void RelayRequest(NodePath itemPath, RequestFunc func, Godot.Collections.Array args) + void RequestPlace(uint itemTrackingId, NodePath gridPath, Transform3D localTransform) { - var item = this.GetNodeOrThrow(itemPath); - var callable = new Callable(item, func.ToString()); - callable.Call(args.ToArray()); + if (!IsMultiplayerAuthority()) return; + + if (!TrackedItems.TryGetValue(itemTrackingId, out var item)) return; + if (!item.IsMultiplayerAuthority()) return; + + var player = Multiplayer.GetRemoteSenderPlayer(); + // Ensure that the item is currently being held by the player. + if (item.FindParentOrNull() != player) return; + + if (GetNodeOrNull(gridPath) is not Grid grid) return; + if (!grid.IsMultiplayerAuthority()) return; + // TODO: Further verify localTransform. + if (!grid.CanPlaceAt(item, localTransform)) return; + + RPC.ToAll().Send(AcceptPlace, itemTrackingId, gridPath, localTransform); } [Rpc(CallLocal = true)] - internal void RelayAccept(NodePath itemPath, AcceptFunc func, Godot.Collections.Array args) + void AcceptPlace(uint itemTrackingId, NodePath gridPath, Transform3D localTransform) { - var item = this.GetNodeOrThrow(itemPath); + var item = VerifyAuthority(TrackedItems[itemTrackingId], "Item"); + var grid = VerifyAuthority(this.GetNodeOrThrow(gridPath), "Grid"); - if (GetMultiplayerAuthority() != item.GetMultiplayerAuthority()) - throw new InvalidOperationException( - $"Item {item.GetPath()} not owned by correct player"); + item.Reparent(grid); + item.Transform = localTransform; + } - var callable = new Callable(item, func.ToString()); - callable.Call(args.ToArray()); + + public void TryThrow(Item item) + { + VerifyManager(item); + VerifyAuthority(item, "Item"); + RPC.To(item.GetMultiplayerAuthority()).Send(RequestThrow, item.TrackingId); + } + + [Rpc(RpcMode.AnyPeer, CallLocal = true)] + void RequestThrow(uint itemTrackingId) + { + if (!IsMultiplayerAuthority()) return; + + if (!TrackedItems.TryGetValue(itemTrackingId, out var item)) return; + if (!item.IsMultiplayerAuthority()) return; + + var player = Multiplayer.GetRemoteSenderPlayer(); + // Ensure that the item is currently being held by the player. + if (item.FindParentOrNull() != player) return; + + RPC.ToAll().Send(AcceptThrow, itemTrackingId); + } + + [Rpc(CallLocal = true)] + void AcceptThrow(uint itemTrackingId) + { + var item = VerifyAuthority(TrackedItems[itemTrackingId], "Item"); + var player = item.FindParentOrThrow(); + var world = GetParent(); + + item.Reparent(world, true); + + // Throw item forward and up a bit. + var basis = player.Camera.Camera.GlobalBasis; + var direction = -basis.Z + basis.Y; + item.ApplyImpulse(direction * 2); + } + + + /// + /// Verifies that this item is managed by this + /// Manager instance, throwing an exception if not. + /// + void VerifyManager(Item item) + { + if (item.Manager != this) throw new InvalidOperationException( + $"Item manager ({item.Manager?.GetPath().ToString() ?? "null"}) is not this manager ({GetPath()})"); + } + + /// + /// Verifies that this node has the same multiplayer authority as this + /// Manager instance, throwing an exception if not. Returns the node + /// that was passed in. + /// + T VerifyAuthority(T node, string name) + where T : Node + { + var expected = GetMultiplayerAuthority(); + var actual = node.GetMultiplayerAuthority(); + if (actual != expected) throw new InvalidOperationException( + $"{name} authority ({actual}) is not manager authority ({expected})"); + return node; } }