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.
169 lines
5.2 KiB
169 lines
5.2 KiB
using Godot.NativeInterop; |
|
|
|
public partial class Item : RigidBody3D |
|
{ |
|
public MultiplayerSynchronizer Sync { get; internal set; } |
|
public ItemManager Manager { get; internal set; } = null; // TODO: Replace with Owner? |
|
public uint TrackingId { get; internal set; } |
|
|
|
/// <summary> Child node representing the 3D model of this item. </summary> |
|
public virtual Node3D Model => GetNode<Node3D>("Model"); |
|
|
|
/// <summary> Whether this item is attached to a grid. </summary> |
|
public bool IsAttached => GetParent() is Grid; |
|
|
|
/// <summary> Size of the item in grid spaces. </summary> |
|
[Export] public Vector3I Size { get; set; } |
|
|
|
public override void _Ready() |
|
{ |
|
// Set the collision properties here so we don't have to specify them in each item scene separately. |
|
CollisionLayer = (uint)(PhysicsLayer.Item | PhysicsLayer.Pickup); |
|
CollisionMask = (uint)(PhysicsLayer.Static | PhysicsLayer.Dynamic | PhysicsLayer.Player | PhysicsLayer.Item); |
|
|
|
// TODO: Find a better way to better import models with colliders. |
|
// TODO: Import items dynamically at runtime? |
|
// TODO: Use PostImport tool script? |
|
foreach (var body in FindChildren("*", "StaticBody3D").Cast<StaticBody3D>()) { |
|
foreach (var shape in body.GetChildren().OfType<CollisionShape3D>()) |
|
shape.Reparent(this); |
|
body.GetParent().RemoveChild(body); |
|
} |
|
|
|
// Set up syncronization for this item when its physics are enabled. |
|
// Sync = new() { RootPath = ".." }; |
|
// var config = Sync.ReplicationConfig = new(); |
|
// config.AddProperty(":position"); |
|
// config.AddProperty(":rotation"); |
|
// config.AddProperty(":linear_velocity"); |
|
// config.AddProperty(":angular_velocity"); |
|
// AddChild(Sync); |
|
|
|
UpdatePhysicsState(); |
|
} |
|
|
|
public override void _Notification(int what) |
|
{ |
|
switch ((long)what) { |
|
case NotificationParented: |
|
if (IsInsideTree()) |
|
UpdatePhysicsState(); |
|
break; |
|
} |
|
} |
|
|
|
void UpdatePhysicsState() |
|
{ |
|
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); |
|
} |
|
}
|
|
|