Inventory management focused game written in Godot / C#
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.

186 lines
5.8 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()
{
if (this.IsAuthority())
RPC.ToAll().Send(Manager.RelayAccept, GetPath(),
ItemManager.AcceptFunc.AcceptPickup,
new Godot.Collections.Array { Multiplayer.GetUniqueId() });
// RPC.ToAll().Send(AcceptPickup, Multiplayer.GetUniqueId());
else
RPC.To(GetMultiplayerAuthority()).Send(Manager.RelayRequest, GetPath(),
ItemManager.RequestFunc.RequestPickup,
new Godot.Collections.Array());
// RPC.To(GetMultiplayerAuthority()).Send(RequestPickup);
}
[Rpc(RpcMode.AnyPeer)]
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.
RPC.ToAll().Send(Manager.RelayAccept, GetPath(),
ItemManager.AcceptFunc.AcceptPickup,
new Godot.Collections.Array { player.PeerId });
// RPC.ToAll().Send(AcceptPickup, player.PeerId);
}
[Rpc(CallLocal = true)]
void AcceptPickup(int peerId)
{
var player = Game.Players.ByPeerId(peerId);
Reparent(player.Pickup);
Transform = Transform3D.Identity; // TODO: Rotate item?
}
public void TryPlace(Grid grid, Transform3D localTransform)
{
if (this.IsAuthority())
RPC.ToAll().Send(Manager.RelayAccept, GetPath(),
ItemManager.AcceptFunc.AcceptPlace,
new Godot.Collections.Array { grid.GetPath(), localTransform });
// RPC.ToAll().Send(AcceptPlace, grid.GetPath(), localTransform);
else
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)]
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()
{
if (this.IsAuthority())
RPC.ToAll().Send(Manager.RelayAccept, GetPath(),
ItemManager.AcceptFunc.AcceptThrow,
new Godot.Collections.Array());
// RPC.ToAll().Send(AcceptThrow);
else
RPC.To(GetMultiplayerAuthority()).Send(Manager.RelayRequest, GetPath(),
ItemManager.RequestFunc.RequestThrow,
new Godot.Collections.Array());
// RPC.To(GetMultiplayerAuthority()).Send(RequestThrow);
}
[Rpc(RpcMode.AnyPeer)]
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);
}
}