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