You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
185 lines
5.8 KiB
185 lines
5.8 KiB
public partial class ItemManager : Node |
|
{ |
|
uint _trackingIdCounter = 0; |
|
public Dictionary<uint, Item> TrackedItems { get; } = []; |
|
|
|
public override void _Ready() |
|
{ |
|
var items = GetParent().FindChildren("*", "res://objects/Item.cs", owned: false).Cast<Item>(); |
|
foreach (var item in items) Add(item); |
|
} |
|
|
|
public void Add(Item item) |
|
{ |
|
if (item.Manager != null) throw new ArgumentException( |
|
$"Item '{item.GetPath()}' is already part of another scene"); |
|
|
|
// 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; |
|
item.TrackingId = id; |
|
} |
|
|
|
public void Remove(Item item) |
|
{ |
|
if (item.Manager != this) throw new ArgumentException( |
|
$"Item '{item.GetPath()}' is not part of this scene"); |
|
|
|
TrackedItems.Remove(item.TrackingId); |
|
|
|
item.Manager = null; |
|
item.TrackingId = 0; |
|
} |
|
|
|
|
|
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; |
|
|
|
// 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)] |
|
void RequestPlace(uint itemTrackingId, NodePath gridPath, Transform3D localTransform) |
|
{ |
|
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)] |
|
void AcceptPlace(uint itemTrackingId, NodePath gridPath, Transform3D localTransform) |
|
{ |
|
var item = VerifyAuthority(TrackedItems[itemTrackingId], "Item"); |
|
var grid = VerifyAuthority(this.GetNodeOrThrow<Grid>(gridPath), "Grid"); |
|
|
|
item.Reparent(grid); |
|
item.Transform = localTransform; |
|
} |
|
|
|
|
|
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>(); |
|
|
|
// FIXME: Actually this doesn't work, the item won't have a Manager anymore. D: |
|
// Add item to workshop, if player is currently in one, otherwise add to world. |
|
var parent = player.EnteredWorkshop?.Objects ?? Game.Instance; |
|
|
|
item.Reparent(parent, 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; |
|
} |
|
}
|
|
|