Move Item RPCs to ItemManager

main
copygirl 3 months ago
parent fe5fd72898
commit 49f7f66db4
  1. 109
      objects/Item.cs
  2. 7
      player/PickupController.cs
  3. 155
      scenes/ItemManager.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>() != 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<Player>();
var world = Manager.GetParent<Node3D>();
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);
}
}

@ -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();
}
}

@ -1,5 +1,6 @@
public partial class ItemManager : Node
{
uint _trackingIdCounter = 0;
public Dictionary<uint, Item> 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<Item>(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>() != 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<Item>(itemPath);
var item = VerifyAuthority(TrackedItems[itemTrackingId], "Item");
var grid = VerifyAuthority(this.GetNodeOrThrow<Grid>(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>() != player) return;
RPC.ToAll().Send(AcceptThrow, itemTrackingId);
}
[Rpc(CallLocal = true)]
void AcceptThrow(uint itemTrackingId)
{
var item = VerifyAuthority(TrackedItems[itemTrackingId], "Item");
var player = item.FindParentOrThrow<Player>();
var world = GetParent<Node3D>();
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);
}
/// <summary>
/// Verifies that this item is managed by this
/// Manager instance, throwing an exception if not.
/// </summary>
void VerifyManager(Item item)
{
if (item.Manager != this) throw new InvalidOperationException(
$"Item manager ({item.Manager?.GetPath().ToString() ?? "null"}) is not this manager ({GetPath()})");
}
/// <summary>
/// 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.
/// </summary>
T VerifyAuthority<T>(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;
}
}

Loading…
Cancel
Save