Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d8cc1d0859 |
@@ -3,5 +3,7 @@
|
|||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<EnableDynamicLoading>true</EnableDynamicLoading>
|
<EnableDynamicLoading>true</EnableDynamicLoading>
|
||||||
<RootNamespace>Game</RootNamespace>
|
<RootNamespace>Game</RootNamespace>
|
||||||
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
|
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
</Project>
|
</Project>
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using Game.Features.Interactions.Contracts;
|
||||||
|
using Game.Features.Players.Contracts;
|
||||||
|
using Godot;
|
||||||
|
|
||||||
|
namespace Game.Features.Items.Contracts;
|
||||||
|
|
||||||
|
public interface IPlayerInteractionController : IItemSpawnPoint
|
||||||
|
{
|
||||||
|
void SetActive(bool isActive);
|
||||||
|
|
||||||
|
void InitializeController(
|
||||||
|
PlayerId playerId,
|
||||||
|
IItemInteractionService items,
|
||||||
|
IInteractionService interactions
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://cvfdyxpc3xkfd
|
||||||
@@ -9,15 +9,22 @@ using Godot;
|
|||||||
|
|
||||||
namespace Game.Features.Interactions.Godot;
|
namespace Game.Features.Interactions.Godot;
|
||||||
|
|
||||||
public partial class PlayerInteractionController : Node, IItemSpawnPoint
|
public partial class PlayerInteractionController : Node, IPlayerInteractionController
|
||||||
{
|
{
|
||||||
[Export] public Camera3D Camera;
|
[Export]
|
||||||
[Export] public CharacterBody3D Body;
|
public Camera3D Camera;
|
||||||
[Export] public InteractionRaycaster InteractionRaycaster;
|
|
||||||
[Export] public RayCast3D DropRaycaster;
|
[Export]
|
||||||
[Export] public InteractionPromptController PromptController;
|
public CharacterBody3D Body;
|
||||||
[Export] public Node3D LeftHandAnchor;
|
|
||||||
[Export] public Node3D RightHandAnchor;
|
[Export]
|
||||||
|
public InteractionRaycaster InteractionRaycaster;
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public RayCast3D DropRaycaster;
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public InteractionPromptController PromptController;
|
||||||
|
|
||||||
private IItemInteractionService _items;
|
private IItemInteractionService _items;
|
||||||
private IInteractionService _interactions;
|
private IInteractionService _interactions;
|
||||||
@@ -30,7 +37,13 @@ public partial class PlayerInteractionController : Node, IItemSpawnPoint
|
|||||||
this.AssertRequiredExports();
|
this.AssertRequiredExports();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Initialize(
|
public void SetActive(bool isActive)
|
||||||
|
{
|
||||||
|
SetProcess(isActive);
|
||||||
|
SetProcessUnhandledInput(isActive);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InitializeController(
|
||||||
PlayerId playerId,
|
PlayerId playerId,
|
||||||
IItemInteractionService items,
|
IItemInteractionService items,
|
||||||
IInteractionService interactions
|
IInteractionService interactions
|
||||||
@@ -43,7 +56,12 @@ public partial class PlayerInteractionController : Node, IItemSpawnPoint
|
|||||||
|
|
||||||
public override void _Process(double delta)
|
public override void _Process(double delta)
|
||||||
{
|
{
|
||||||
if (_items is null || _interactions is null || GamePause.IsPaused || !MouseCapture.IsCaptured)
|
if (
|
||||||
|
_items is null
|
||||||
|
|| _interactions is null
|
||||||
|
|| GamePause.IsPaused
|
||||||
|
|| !MouseCapture.IsCaptured
|
||||||
|
)
|
||||||
{
|
{
|
||||||
PromptController.Clear();
|
PromptController.Clear();
|
||||||
return;
|
return;
|
||||||
@@ -54,7 +72,12 @@ public partial class PlayerInteractionController : Node, IItemSpawnPoint
|
|||||||
|
|
||||||
public override void _UnhandledInput(InputEvent @event)
|
public override void _UnhandledInput(InputEvent @event)
|
||||||
{
|
{
|
||||||
if (_items is null || _interactions is null || GamePause.IsPaused || !MouseCapture.IsCaptured)
|
if (
|
||||||
|
_items is null
|
||||||
|
|| _interactions is null
|
||||||
|
|| GamePause.IsPaused
|
||||||
|
|| !MouseCapture.IsCaptured
|
||||||
|
)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -107,12 +130,9 @@ public partial class PlayerInteractionController : Node, IItemSpawnPoint
|
|||||||
{
|
{
|
||||||
if (_activeUseTargets.Remove(hand, out InteractionTargetId? activeTargetId))
|
if (_activeUseTargets.Remove(hand, out InteractionTargetId? activeTargetId))
|
||||||
{
|
{
|
||||||
_items.TryUse(new ItemUseRequest(
|
_items.TryUse(
|
||||||
_playerId,
|
new ItemUseRequest(_playerId, hand, ItemActionPhase.Cancelled, activeTargetId)
|
||||||
hand,
|
);
|
||||||
ItemActionPhase.Cancelled,
|
|
||||||
activeTargetId
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_items.TryDrop(hand, CalculateDropPosition());
|
_items.TryDrop(hand, CalculateDropPosition());
|
||||||
@@ -168,7 +188,10 @@ public partial class PlayerInteractionController : Node, IItemSpawnPoint
|
|||||||
|
|
||||||
if (DropRaycaster.IsColliding())
|
if (DropRaycaster.IsColliding())
|
||||||
{
|
{
|
||||||
distance = Mathf.Max(0.25f, origin.DistanceTo(DropRaycaster.GetCollisionPoint()) - 0.2f);
|
distance = Mathf.Max(
|
||||||
|
0.25f,
|
||||||
|
origin.DistanceTo(DropRaycaster.GetCollisionPoint()) - 0.2f
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return origin + direction * distance;
|
return origin + direction * distance;
|
||||||
@@ -198,9 +221,11 @@ public partial class PlayerInteractionController : Node, IItemSpawnPoint
|
|||||||
if (itemInfo.HandRequirement == HandRequirement.Both)
|
if (itemInfo.HandRequirement == HandRequirement.Both)
|
||||||
{
|
{
|
||||||
bool bothHandsFree = _items.IsHandFree(Hand.Left) && _items.IsHandFree(Hand.Right);
|
bool bothHandsFree = _items.IsHandFree(Hand.Left) && _items.IsHandFree(Hand.Right);
|
||||||
PromptController.ShowPrompt(bothHandsFree
|
PromptController.ShowPrompt(
|
||||||
? $"[LPM lub PPM] Podnieś {itemInfo.DisplayName} — obie ręce"
|
bothHandsFree
|
||||||
: "Potrzebujesz obu wolnych rąk");
|
? $"[LPM lub PPM] Podnieś {itemInfo.DisplayName} — obie ręce"
|
||||||
|
: "Potrzebujesz obu wolnych rąk"
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,8 +241,8 @@ public partial class PlayerInteractionController : Node, IItemSpawnPoint
|
|||||||
actions.Add($"[PPM] Podnieś {itemInfo.DisplayName} — prawa ręka");
|
actions.Add($"[PPM] Podnieś {itemInfo.DisplayName} — prawa ręka");
|
||||||
}
|
}
|
||||||
|
|
||||||
PromptController.ShowPrompt(actions.Count > 0
|
PromptController.ShowPrompt(
|
||||||
? string.Join("\n", actions)
|
actions.Count > 0 ? string.Join("\n", actions) : "Brak wolnej ręki"
|
||||||
: "Brak wolnej ręki");
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ public interface IMultiplayerPeer
|
|||||||
event Action Connected;
|
event Action Connected;
|
||||||
event Action ConnectionFailed;
|
event Action ConnectionFailed;
|
||||||
event Action Disconnected;
|
event Action Disconnected;
|
||||||
|
event Action<PeerId> HostingStarted;
|
||||||
event Action<PeerId> PeerJoined;
|
event Action<PeerId> PeerJoined;
|
||||||
event Action<PeerId> PeerLeft;
|
event Action<PeerId> PeerLeft;
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ public partial class MultiplayerController : Node, IMultiplayerPeer
|
|||||||
public event Action Connected;
|
public event Action Connected;
|
||||||
public event Action ConnectionFailed;
|
public event Action ConnectionFailed;
|
||||||
public event Action Disconnected;
|
public event Action Disconnected;
|
||||||
|
public event Action<PeerId> HostingStarted;
|
||||||
public event Action<PeerId> PeerJoined;
|
public event Action<PeerId> PeerJoined;
|
||||||
public event Action<PeerId> PeerLeft;
|
public event Action<PeerId> PeerLeft;
|
||||||
|
|
||||||
@@ -91,7 +92,10 @@ public partial class MultiplayerController : Node, IMultiplayerPeer
|
|||||||
AssertNoActiveSession();
|
AssertNoActiveSession();
|
||||||
|
|
||||||
Multiplayer.MultiplayerPeer = _multiplayerPeerFactory.CreateHost(port, MultiplayerSession.MaxPlayers - 1);
|
Multiplayer.MultiplayerPeer = _multiplayerPeerFactory.CreateHost(port, MultiplayerSession.MaxPlayers - 1);
|
||||||
_session.StartHosting(new PeerId(Multiplayer.GetUniqueId()));
|
|
||||||
|
PeerId hostPeerId = new(Multiplayer.GetUniqueId());
|
||||||
|
_session.StartHosting(hostPeerId);
|
||||||
|
HostingStarted?.Invoke(hostPeerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Join(string address, int port)
|
public void Join(string address, int port)
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
using Game.Features.Items.Contracts;
|
||||||
|
using Game.Features.Players.Contracts;
|
||||||
|
|
||||||
|
public interface IPlayerController : IMovementModeSwitcher, IItemSpawnPoint { }
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://1i0wvlumjjiu
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using Game.Features.Items.Contracts;
|
||||||
|
using Game.Features.Players.Contracts;
|
||||||
|
using Godot;
|
||||||
|
|
||||||
|
namespace Game.Features.Players.Godot;
|
||||||
|
|
||||||
|
public sealed class LocalPlayerController : IPlayerController
|
||||||
|
{
|
||||||
|
public PlayerController Current { get; private set; }
|
||||||
|
|
||||||
|
public void SetCurrent(PlayerController player)
|
||||||
|
{
|
||||||
|
Current = player;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MovementMode ActiveMovementMode => Current?.ActiveMovementMode ?? MovementMode.Ground;
|
||||||
|
|
||||||
|
public void SetMovementMode(MovementMode movementMode)
|
||||||
|
{
|
||||||
|
Current?.SetMovementMode(movementMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector3 GetItemSpawnPosition() => Current?.GetItemSpawnPosition() ?? Vector3.Zero;
|
||||||
|
}
|
||||||
@@ -1,25 +1,53 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Game.Features.Items.Contracts;
|
||||||
using Game.Features.Players.Contracts;
|
using Game.Features.Players.Contracts;
|
||||||
using Game.Features.Players.Domain;
|
using Game.Features.Players.Domain;
|
||||||
using Game.Features.Players.Domain.Movement;
|
using Game.Features.Players.Domain.Movement;
|
||||||
using Game.Godot;
|
using Game.Godot;
|
||||||
|
using Game.Godot.Attributes;
|
||||||
using Game.Godot.Extensions;
|
using Game.Godot.Extensions;
|
||||||
using Game.Godot.Mappings;
|
using Game.Godot.Mappings;
|
||||||
using Godot;
|
using Godot;
|
||||||
|
|
||||||
namespace Game.Features.Players.Godot;
|
namespace Game.Features.Players.Godot;
|
||||||
|
|
||||||
public partial class PlayerController : Node3D, IMovementModeSwitcher
|
public partial class PlayerController : Node3D, IPlayerController
|
||||||
{
|
{
|
||||||
[Export] public CharacterBody3D Body;
|
[Export]
|
||||||
[Export] public CollisionShape3D Collision;
|
public CharacterBody3D Body;
|
||||||
[Export] public Camera3D Camera;
|
|
||||||
|
|
||||||
[Export] public float MoveSpeed = 4.0f;
|
[Export]
|
||||||
[Export] public float JumpVelocity = 4.5f;
|
public CollisionShape3D Collision;
|
||||||
[Export] public float NoclipFlySpeed = 8.0f;
|
|
||||||
[Export] public float MouseSensitivity = 0.003f;
|
[Export]
|
||||||
[Export] public float MaxLookAngleDegrees = 89.0f;
|
public Camera3D Camera;
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public Node3D LeftHandAnchor;
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public Node3D RightHandAnchor;
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public float MoveSpeed = 4.0f;
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public float JumpVelocity = 4.5f;
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public float NoclipFlySpeed = 8.0f;
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public float MouseSensitivity = 0.003f;
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public float MaxLookAngleDegrees = 89.0f;
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
[RequireInterface(typeof(IPlayerInteractionController))]
|
||||||
|
private Node InteractionController;
|
||||||
|
public IPlayerInteractionController Interaction =>
|
||||||
|
(IPlayerInteractionController)InteractionController;
|
||||||
|
|
||||||
private Dictionary<MovementMode, IMovementStrategy> _movementStrategies;
|
private Dictionary<MovementMode, IMovementStrategy> _movementStrategies;
|
||||||
private IMovementStrategy _currentMovementStrategy;
|
private IMovementStrategy _currentMovementStrategy;
|
||||||
@@ -34,6 +62,13 @@ public partial class PlayerController : Node3D, IMovementModeSwitcher
|
|||||||
Collision.Disabled = movementMode == MovementMode.Noclip;
|
Collision.Disabled = movementMode == MovementMode.Noclip;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Vector3 GetItemSpawnPosition() => Interaction.GetItemSpawnPosition();
|
||||||
|
|
||||||
|
public override void _EnterTree()
|
||||||
|
{
|
||||||
|
SetMultiplayerAuthority(int.Parse(Name.ToString()));
|
||||||
|
}
|
||||||
|
|
||||||
public override void _Ready()
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
this.AssertRequiredExports();
|
this.AssertRequiredExports();
|
||||||
@@ -44,14 +79,21 @@ public partial class PlayerController : Node3D, IMovementModeSwitcher
|
|||||||
_movementStrategies = new Dictionary<MovementMode, IMovementStrategy>
|
_movementStrategies = new Dictionary<MovementMode, IMovementStrategy>
|
||||||
{
|
{
|
||||||
[MovementMode.Ground] = new GroundMovement(MoveSpeed, JumpVelocity, gravity),
|
[MovementMode.Ground] = new GroundMovement(MoveSpeed, JumpVelocity, gravity),
|
||||||
[MovementMode.Noclip] = new NoclipMovement(NoclipFlySpeed)
|
[MovementMode.Noclip] = new NoclipMovement(NoclipFlySpeed),
|
||||||
};
|
};
|
||||||
_currentMovementStrategy = _movementStrategies[ActiveMovementMode];
|
_currentMovementStrategy = _movementStrategies[ActiveMovementMode];
|
||||||
|
|
||||||
|
bool isLocal = IsMultiplayerAuthority();
|
||||||
|
Camera.Current = isLocal;
|
||||||
|
SetPhysicsProcess(isLocal);
|
||||||
|
SetProcessUnhandledInput(isLocal);
|
||||||
|
Interaction.SetActive(isLocal);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void _UnhandledInput(InputEvent @event)
|
public override void _UnhandledInput(InputEvent @event)
|
||||||
{
|
{
|
||||||
if (@event is not InputEventMouseMotion mouseMotion || !MouseCapture.IsCaptured) return;
|
if (@event is not InputEventMouseMotion mouseMotion || !MouseCapture.IsCaptured)
|
||||||
|
return;
|
||||||
|
|
||||||
Body.RotateY(_look.CalculateYawDelta(mouseMotion.Relative.X));
|
Body.RotateY(_look.CalculateYawDelta(mouseMotion.Relative.X));
|
||||||
_look.UpdatePitch(mouseMotion.Relative.Y);
|
_look.UpdatePitch(mouseMotion.Relative.Y);
|
||||||
|
|||||||
@@ -293,14 +293,28 @@ _surfaces = [{
|
|||||||
}]
|
}]
|
||||||
blend_shape_mode = 0
|
blend_shape_mode = 0
|
||||||
|
|
||||||
[node name="Player" type="Node3D" unique_id=26823676 node_paths=PackedStringArray("Body", "Collision", "Camera")]
|
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_player_transform"]
|
||||||
|
properties/0/path = NodePath(".:position")
|
||||||
|
properties/0/spawn = true
|
||||||
|
properties/0/replication_mode = 1
|
||||||
|
properties/1/path = NodePath(".:rotation")
|
||||||
|
properties/1/spawn = true
|
||||||
|
properties/1/replication_mode = 1
|
||||||
|
|
||||||
|
[node name="Player" type="Node3D" unique_id=26823676 node_paths=PackedStringArray("Body", "Collision", "Camera", "LeftHandAnchor", "RightHandAnchor", "InteractionController")]
|
||||||
script = ExtResource("1_gkqfc")
|
script = ExtResource("1_gkqfc")
|
||||||
Body = NodePath("CharacterBody3D")
|
Body = NodePath("CharacterBody3D")
|
||||||
Collision = NodePath("CharacterBody3D/CollisionShape3D")
|
Collision = NodePath("CharacterBody3D/CollisionShape3D")
|
||||||
Camera = NodePath("CharacterBody3D/Camera3D")
|
Camera = NodePath("CharacterBody3D/Camera3D")
|
||||||
|
LeftHandAnchor = NodePath("CharacterBody3D/Camera3D/LeftHandAnchor")
|
||||||
|
RightHandAnchor = NodePath("CharacterBody3D/Camera3D/RightHandAnchor")
|
||||||
|
InteractionController = NodePath("PlayerInteraction")
|
||||||
|
|
||||||
[node name="CharacterBody3D" type="CharacterBody3D" parent="." unique_id=1327672780]
|
[node name="CharacterBody3D" type="CharacterBody3D" parent="." unique_id=1327672780]
|
||||||
|
|
||||||
|
[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="CharacterBody3D"]
|
||||||
|
replication_config = SubResource("SceneReplicationConfig_player_transform")
|
||||||
|
|
||||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="CharacterBody3D" unique_id=1006347064]
|
[node name="CollisionShape3D" type="CollisionShape3D" parent="CharacterBody3D" unique_id=1006347064]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
|
||||||
shape = SubResource("CapsuleShape3D_130ks")
|
shape = SubResource("CapsuleShape3D_130ks")
|
||||||
@@ -796,15 +810,13 @@ enabled = true
|
|||||||
target_position = Vector3(0, 0, -1)
|
target_position = Vector3(0, 0, -1)
|
||||||
collide_with_areas = true
|
collide_with_areas = true
|
||||||
|
|
||||||
[node name="PlayerInteraction" type="Node" parent="." node_paths=PackedStringArray("Camera", "Body", "InteractionRaycaster", "DropRaycaster", "PromptController", "LeftHandAnchor", "RightHandAnchor")]
|
[node name="PlayerInteraction" type="Node" parent="." node_paths=PackedStringArray("Camera", "Body", "InteractionRaycaster", "DropRaycaster", "PromptController")]
|
||||||
script = ExtResource("3_interaction")
|
script = ExtResource("3_interaction")
|
||||||
Camera = NodePath("../CharacterBody3D/Camera3D")
|
Camera = NodePath("../CharacterBody3D/Camera3D")
|
||||||
Body = NodePath("../CharacterBody3D")
|
Body = NodePath("../CharacterBody3D")
|
||||||
InteractionRaycaster = NodePath("../CharacterBody3D/Camera3D/InteractionRaycaster")
|
InteractionRaycaster = NodePath("../CharacterBody3D/Camera3D/InteractionRaycaster")
|
||||||
DropRaycaster = NodePath("../CharacterBody3D/Camera3D/DropRaycaster")
|
DropRaycaster = NodePath("../CharacterBody3D/Camera3D/DropRaycaster")
|
||||||
PromptController = NodePath("InteractionUi")
|
PromptController = NodePath("InteractionUi")
|
||||||
LeftHandAnchor = NodePath("../CharacterBody3D/Camera3D/LeftHandAnchor")
|
|
||||||
RightHandAnchor = NodePath("../CharacterBody3D/Camera3D/RightHandAnchor")
|
|
||||||
|
|
||||||
[node name="InteractionUi" type="CanvasLayer" parent="PlayerInteraction" node_paths=PackedStringArray("PromptLabel")]
|
[node name="InteractionUi" type="CanvasLayer" parent="PlayerInteraction" node_paths=PackedStringArray("PromptLabel")]
|
||||||
layer = 50
|
layer = 50
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
using System;
|
||||||
|
using Game.Features.Multiplayer.Contracts;
|
||||||
|
using Game.Godot.Extensions;
|
||||||
|
using Godot;
|
||||||
|
|
||||||
|
namespace Game.Features.Players.Godot;
|
||||||
|
|
||||||
|
public partial class PlayerSpawnerController : MultiplayerSpawner
|
||||||
|
{
|
||||||
|
/// <summary>Container that spawned players are added to.</summary>
|
||||||
|
[Export]
|
||||||
|
public Node3D PlayersRoot;
|
||||||
|
|
||||||
|
/// <summary>Scene instantiated for each peer.</summary>
|
||||||
|
[Export]
|
||||||
|
public PackedScene PlayerScene;
|
||||||
|
|
||||||
|
public event Action<PlayerController> LocalPlayerSpawned;
|
||||||
|
|
||||||
|
private void Spawn(PeerId peerId)
|
||||||
|
{
|
||||||
|
if (!IsMultiplayerAuthority())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerController player = PlayerScene.Instantiate<PlayerController>();
|
||||||
|
player.Name = peerId.ToString();
|
||||||
|
PlayersRoot.AddChild(player);
|
||||||
|
|
||||||
|
if (peerId.Value == Multiplayer.GetUniqueId())
|
||||||
|
{
|
||||||
|
LocalPlayerSpawned?.Invoke(player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Despawn(PeerId peerId)
|
||||||
|
{
|
||||||
|
if (!IsMultiplayerAuthority())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayersRoot.GetNodeOrNull(peerId.ToString())?.QueueFree();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UseMultiplayerPeer(IMultiplayerPeer multiplayer)
|
||||||
|
{
|
||||||
|
multiplayer.HostingStarted += Spawn;
|
||||||
|
multiplayer.PeerJoined += Spawn;
|
||||||
|
multiplayer.PeerLeft += Despawn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void _Ready()
|
||||||
|
{
|
||||||
|
this.AssertRequiredExports();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://5j5el8n12uxm
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Game.Godot.Attributes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Marks an [Export]ed Node field/property as required to implement the given interface.
|
||||||
|
/// Validated by <see cref="Game.Godot.Extensions.NodeExportValidationExtensions.AssertRequiredExports"/>.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)]
|
||||||
|
public sealed class RequireInterfaceAttribute(Type interfaceType) : Attribute
|
||||||
|
{
|
||||||
|
public Type InterfaceType { get; } = interfaceType;
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://g0fcf6gcpkcm
|
||||||
@@ -2,6 +2,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using Game.Godot.Attributes;
|
||||||
using Game.Godot.Exceptions;
|
using Game.Godot.Exceptions;
|
||||||
using Godot;
|
using Godot;
|
||||||
|
|
||||||
@@ -44,11 +45,33 @@ public static class NodeExportValidationExtensions
|
|||||||
return missingFields.Concat(missingProperties).ToArray();
|
return missingFields.Concat(missingProperties).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string[] GetInterfaceMismatchWarnings(Node node)
|
||||||
|
{
|
||||||
|
Type nodeType = node.GetType();
|
||||||
|
|
||||||
|
IEnumerable<string> fieldWarnings = nodeType
|
||||||
|
.GetFields(MemberFlags)
|
||||||
|
.SelectMany(field => field.GetCustomAttributes<RequireInterfaceAttribute>()
|
||||||
|
.Select(require => (field.Name, Require: require, Value: field.GetValue(node))))
|
||||||
|
.Where(member => member.Value is not null && !member.Require.InterfaceType.IsInstanceOfType(member.Value))
|
||||||
|
.Select(member => $"{member.Name} ({member.Value.GetType().Name}) does not implement {member.Require.InterfaceType.Name}.");
|
||||||
|
|
||||||
|
IEnumerable<string> propertyWarnings = nodeType
|
||||||
|
.GetProperties(MemberFlags)
|
||||||
|
.SelectMany(property => property.GetCustomAttributes<RequireInterfaceAttribute>()
|
||||||
|
.Select(require => (property.Name, Require: require, Value: property.GetValue(node))))
|
||||||
|
.Where(member => member.Value is not null && !member.Require.InterfaceType.IsInstanceOfType(member.Value))
|
||||||
|
.Select(member => $"{member.Name} ({member.Value.GetType().Name}) does not implement {member.Require.InterfaceType.Name}.");
|
||||||
|
|
||||||
|
return fieldWarnings.Concat(propertyWarnings).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Throws if any [Export]ed Node field on this node is unassigned.
|
/// Throws if any [Export]ed Node field on this node is unassigned, or assigned to a Node
|
||||||
|
/// that does not implement the interface declared via [RequireInterface].
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="node">Node to check.</param>
|
/// <param name="node">Node to check.</param>
|
||||||
/// <exception cref="MissingRequiredExportException">One or more exports are missing.</exception>
|
/// <exception cref="MissingRequiredExportException">One or more exports are missing or invalid.</exception>
|
||||||
/// <example>
|
/// <example>
|
||||||
/// <code>
|
/// <code>
|
||||||
/// public override void _Ready()
|
/// public override void _Ready()
|
||||||
@@ -60,7 +83,10 @@ public static class NodeExportValidationExtensions
|
|||||||
/// </example>
|
/// </example>
|
||||||
public static void AssertRequiredExports(this Node node)
|
public static void AssertRequiredExports(this Node node)
|
||||||
{
|
{
|
||||||
List<string> missing = GetMissingExportWarnings(node).ToList();
|
List<string> missing = GetMissingExportWarnings(node)
|
||||||
|
.Concat(GetInterfaceMismatchWarnings(node))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
if (missing.Count == 0)
|
if (missing.Count == 0)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
|||||||
+49
-18
@@ -2,13 +2,14 @@ using System;
|
|||||||
using Game.Features.Console.Application.Commands;
|
using Game.Features.Console.Application.Commands;
|
||||||
using Game.Features.Console.Godot;
|
using Game.Features.Console.Godot;
|
||||||
using Game.Features.Console.Infrastructure;
|
using Game.Features.Console.Infrastructure;
|
||||||
using Game.Features.Logging.Godot;
|
|
||||||
using Game.Features.Logging.Infrastructure;
|
|
||||||
using Game.Features.Interactions.Application;
|
using Game.Features.Interactions.Application;
|
||||||
using Game.Features.Interactions.Godot;
|
using Game.Features.Interactions.Godot;
|
||||||
using Game.Features.Items.Application;
|
using Game.Features.Items.Application;
|
||||||
|
using Game.Features.Items.Contracts;
|
||||||
using Game.Features.Items.Domain;
|
using Game.Features.Items.Domain;
|
||||||
using Game.Features.Items.Godot;
|
using Game.Features.Items.Godot;
|
||||||
|
using Game.Features.Logging.Godot;
|
||||||
|
using Game.Features.Logging.Infrastructure;
|
||||||
using Game.Features.Multiplayer.Godot;
|
using Game.Features.Multiplayer.Godot;
|
||||||
using Game.Features.Players.Contracts;
|
using Game.Features.Players.Contracts;
|
||||||
using Game.Features.Players.Godot;
|
using Game.Features.Players.Godot;
|
||||||
@@ -20,13 +21,25 @@ namespace Game.Godot;
|
|||||||
|
|
||||||
public partial class GameBootstrap : Node
|
public partial class GameBootstrap : Node
|
||||||
{
|
{
|
||||||
[Export] public ConsoleController Console;
|
[Export]
|
||||||
[Export] public PlayerController Player;
|
public ConsoleController Console;
|
||||||
[Export] public PlayerInteractionController PlayerInteraction;
|
|
||||||
[Export] public ItemWorldController ItemWorld;
|
[Export]
|
||||||
[Export] public ItemSpawnerController ItemSpawner;
|
public PlayerSpawnerController PlayerSpawner;
|
||||||
[Export] public InteractionWorldController InteractionWorld;
|
|
||||||
[Export] public MultiplayerController MultiplayerController;
|
[Export]
|
||||||
|
public ItemWorldController ItemWorld;
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public ItemSpawnerController ItemSpawner;
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public InteractionWorldController InteractionWorld;
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public MultiplayerController MultiplayerController;
|
||||||
|
|
||||||
|
private readonly LocalPlayerController _localPlayerController = new();
|
||||||
|
|
||||||
private FileLogger _fileLogger;
|
private FileLogger _fileLogger;
|
||||||
private CompositeLogger _logger;
|
private CompositeLogger _logger;
|
||||||
@@ -37,7 +50,7 @@ public partial class GameBootstrap : Node
|
|||||||
_logger = new CompositeLogger([
|
_logger = new CompositeLogger([
|
||||||
new GodotConsoleLogger(),
|
new GodotConsoleLogger(),
|
||||||
new LogTargetLogger(Console),
|
new LogTargetLogger(Console),
|
||||||
_fileLogger
|
_fileLogger,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,8 +60,8 @@ public partial class GameBootstrap : Node
|
|||||||
registry.Register(new HelpCommand(registry));
|
registry.Register(new HelpCommand(registry));
|
||||||
registry.Register(new EchoCommand());
|
registry.Register(new EchoCommand());
|
||||||
registry.Register(new ClearCommand());
|
registry.Register(new ClearCommand());
|
||||||
registry.Register(new NoclipCommand(Player));
|
registry.Register(new NoclipCommand(_localPlayerController));
|
||||||
registry.Register(new SpawnItemCommand(ItemSpawner, PlayerInteraction));
|
registry.Register(new SpawnItemCommand(ItemSpawner, _localPlayerController));
|
||||||
registry.Register(new HostCommand(MultiplayerController));
|
registry.Register(new HostCommand(MultiplayerController));
|
||||||
registry.Register(new JoinCommand(MultiplayerController));
|
registry.Register(new JoinCommand(MultiplayerController));
|
||||||
|
|
||||||
@@ -58,11 +71,17 @@ public partial class GameBootstrap : Node
|
|||||||
private void InitializeMultiplayer()
|
private void InitializeMultiplayer()
|
||||||
{
|
{
|
||||||
MultiplayerController.UsePeerFactory(new EnetMultiplayerPeerFactory());
|
MultiplayerController.UsePeerFactory(new EnetMultiplayerPeerFactory());
|
||||||
MultiplayerController.Connected += () => _logger.Info("Multiplayer", "Connected successfully.");
|
MultiplayerController.Connected += () =>
|
||||||
MultiplayerController.ConnectionFailed += () => _logger.Warning("Multiplayer", "Connection failed.");
|
_logger.Info("Multiplayer", "Connected successfully.");
|
||||||
MultiplayerController.Disconnected += () => _logger.Warning("Multiplayer", "Server disconnected.");
|
MultiplayerController.ConnectionFailed += () =>
|
||||||
|
_logger.Warning("Multiplayer", "Connection failed.");
|
||||||
|
MultiplayerController.Disconnected += () =>
|
||||||
|
_logger.Warning("Multiplayer", "Server disconnected.");
|
||||||
MultiplayerController.PeerJoined += id => _logger.Info("Multiplayer", $"Peer {id} joined.");
|
MultiplayerController.PeerJoined += id => _logger.Info("Multiplayer", $"Peer {id} joined.");
|
||||||
MultiplayerController.PeerLeft += id => _logger.Info("Multiplayer", $"Peer {id} left.");
|
MultiplayerController.PeerLeft += id => _logger.Info("Multiplayer", $"Peer {id} left.");
|
||||||
|
|
||||||
|
PlayerSpawner.UseMultiplayerPeer(MultiplayerController);
|
||||||
|
PlayerSpawner.LocalPlayerSpawned += OnLocalPlayerSpawned;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitializeItems()
|
private void InitializeItems()
|
||||||
@@ -70,15 +89,27 @@ public partial class GameBootstrap : Node
|
|||||||
ItemWorld.RegisterSceneItems();
|
ItemWorld.RegisterSceneItems();
|
||||||
ItemSpawner.Initialize();
|
ItemSpawner.Initialize();
|
||||||
InteractionWorld.RegisterSceneTargets();
|
InteractionWorld.RegisterSceneTargets();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnLocalPlayerSpawned(PlayerController player)
|
||||||
|
{
|
||||||
|
_localPlayerController.SetCurrent(player);
|
||||||
|
|
||||||
|
IPlayerInteractionController interaction = player.Interaction;
|
||||||
|
|
||||||
PlayerHeldItemPresenter presenter = new(
|
PlayerHeldItemPresenter presenter = new(
|
||||||
ItemWorld,
|
ItemWorld,
|
||||||
PlayerInteraction.LeftHandAnchor,
|
player.LeftHandAnchor,
|
||||||
PlayerInteraction.RightHandAnchor
|
player.RightHandAnchor
|
||||||
);
|
);
|
||||||
ItemInteractionService itemInteractions = new(new HandsInventory(), ItemWorld, presenter);
|
ItemInteractionService itemInteractions = new(new HandsInventory(), ItemWorld, presenter);
|
||||||
InteractionService interactions = new(InteractionWorld);
|
InteractionService interactions = new(InteractionWorld);
|
||||||
PlayerInteraction.Initialize(new PlayerId(Guid.NewGuid()), itemInteractions, interactions);
|
|
||||||
|
interaction.InitializeController(
|
||||||
|
new PlayerId(Guid.NewGuid()),
|
||||||
|
itemInteractions,
|
||||||
|
interactions
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void _Ready()
|
public override void _Ready()
|
||||||
|
|||||||
@@ -6,10 +6,11 @@
|
|||||||
[ext_resource type="Script" uid="uid://drctu5y21ey2y" path="res://src/Godot/GameBootstrap.cs" id="3_bootstrap"]
|
[ext_resource type="Script" uid="uid://drctu5y21ey2y" path="res://src/Godot/GameBootstrap.cs" id="3_bootstrap"]
|
||||||
[ext_resource type="PackedScene" uid="uid://bk0771dvonxh8" path="res://src/Features/Console/Godot/ConsoleController.tscn" id="4_console"]
|
[ext_resource type="PackedScene" uid="uid://bk0771dvonxh8" path="res://src/Features/Console/Godot/ConsoleController.tscn" id="4_console"]
|
||||||
[ext_resource type="Script" uid="uid://drqx0wiw0sbj1" path="res://src/Features/Multiplayer/Godot/MultiplayerController.cs" id="5_multiplayer"]
|
[ext_resource type="Script" uid="uid://drqx0wiw0sbj1" path="res://src/Features/Multiplayer/Godot/MultiplayerController.cs" id="5_multiplayer"]
|
||||||
[ext_resource type="Script" uid="uid://f8n3b6w1k5r9m" path="res://src/Features/Items/Godot/ItemWorldController.cs" id="6_items"]
|
[ext_resource type="Script" uid="uid://cd87p40r0xwiu" path="res://src/Features/Items/Godot/ItemWorldController.cs" id="6_items"]
|
||||||
[ext_resource type="PackedScene" uid="uid://cv8m3q1y6r4nf" path="res://src/Features/Items/Godot/Flashlight/FlashlightItem.tscn" id="7_flashlight"]
|
[ext_resource type="PackedScene" uid="uid://cv8m3q1y6r4nf" path="res://src/Features/Items/Godot/Flashlight/FlashlightItem.tscn" id="7_flashlight"]
|
||||||
[ext_resource type="Script" uid="uid://c3x9m6w2q8r1b" path="res://src/Features/Items/Godot/ItemSpawnerController.cs" id="8_spawner"]
|
[ext_resource type="Script" uid="uid://c3yam6w2q8r1b" path="res://src/Features/Items/Godot/ItemSpawnerController.cs" id="8_spawner"]
|
||||||
[ext_resource type="Script" uid="uid://f6q1c9m4w7v2n" path="res://src/Features/Interactions/Godot/InteractionWorldController.cs" id="9_interactions"]
|
[ext_resource type="Script" uid="uid://ccc5q7pvd01bv" path="res://src/Features/Interactions/Godot/InteractionWorldController.cs" id="9_interactions"]
|
||||||
|
[ext_resource type="Script" uid="uid://5j5el8n12uxm" path="res://src/Features/Players/Godot/PlayerSpawnerController.cs" id="10_player_spawner"]
|
||||||
|
|
||||||
[sub_resource type="PanoramaSkyMaterial" id="PanoramaSkyMaterial_epykj"]
|
[sub_resource type="PanoramaSkyMaterial" id="PanoramaSkyMaterial_epykj"]
|
||||||
panorama = ExtResource("1_g4n7o")
|
panorama = ExtResource("1_g4n7o")
|
||||||
@@ -22,11 +23,10 @@ background_mode = 2
|
|||||||
sky = SubResource("Sky_1kpcd")
|
sky = SubResource("Sky_1kpcd")
|
||||||
glow_enabled = true
|
glow_enabled = true
|
||||||
|
|
||||||
[node name="GameWorld" type="Node" unique_id=222644234 node_paths=PackedStringArray("Console", "Player", "PlayerInteraction", "ItemWorld", "ItemSpawner", "InteractionWorld", "MultiplayerController")]
|
[node name="GameWorld" type="Node" unique_id=222644234 node_paths=PackedStringArray("Console", "PlayerSpawner", "ItemWorld", "ItemSpawner", "InteractionWorld", "MultiplayerController")]
|
||||||
script = ExtResource("3_bootstrap")
|
script = ExtResource("3_bootstrap")
|
||||||
Console = NodePath("Console")
|
Console = NodePath("Console")
|
||||||
Player = NodePath("Player")
|
PlayerSpawner = NodePath("PlayerSpawner")
|
||||||
PlayerInteraction = NodePath("Player/PlayerInteraction")
|
|
||||||
ItemWorld = NodePath("ItemWorld")
|
ItemWorld = NodePath("ItemWorld")
|
||||||
ItemSpawner = NodePath("ItemSpawner")
|
ItemSpawner = NodePath("ItemSpawner")
|
||||||
InteractionWorld = NodePath("InteractionWorld")
|
InteractionWorld = NodePath("InteractionWorld")
|
||||||
@@ -39,24 +39,31 @@ shadow_enabled = true
|
|||||||
[node name="WorldEnvironment" type="WorldEnvironment" parent="." unique_id=847581298]
|
[node name="WorldEnvironment" type="WorldEnvironment" parent="." unique_id=847581298]
|
||||||
environment = SubResource("Environment_mvtit")
|
environment = SubResource("Environment_mvtit")
|
||||||
|
|
||||||
[node name="Player" parent="." unique_id=26823676 instance=ExtResource("2_mvtit")]
|
[node name="PlayersRoot" type="Node3D" parent="." unique_id=26823676]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
|
||||||
|
|
||||||
|
[node name="PlayerSpawner" type="MultiplayerSpawner" parent="." unique_id=1573506152 node_paths=PackedStringArray("PlayersRoot")]
|
||||||
|
_spawnable_scenes = PackedStringArray("uid://bka1dmgdi5btd")
|
||||||
|
spawn_path = NodePath("../PlayersRoot")
|
||||||
|
script = ExtResource("10_player_spawner")
|
||||||
|
PlayersRoot = NodePath("../PlayersRoot")
|
||||||
|
PlayerScene = ExtResource("2_mvtit")
|
||||||
|
|
||||||
[node name="Console" parent="." unique_id=1947043894 instance=ExtResource("4_console")]
|
[node name="Console" parent="." unique_id=1947043894 instance=ExtResource("4_console")]
|
||||||
|
|
||||||
[node name="Multiplayer" type="Node" parent="." unique_id=1209131013]
|
[node name="Multiplayer" type="Node" parent="." unique_id=1209131013]
|
||||||
script = ExtResource("5_multiplayer")
|
script = ExtResource("5_multiplayer")
|
||||||
|
|
||||||
[node name="ItemWorld" type="Node" parent="."]
|
[node name="ItemWorld" type="Node" parent="." unique_id=1821063866]
|
||||||
script = ExtResource("6_items")
|
script = ExtResource("6_items")
|
||||||
|
|
||||||
[node name="ItemSpawner" type="Node" parent="." node_paths=PackedStringArray("ItemsRoot", "ItemWorld")]
|
[node name="ItemSpawner" type="Node" parent="." unique_id=483552217 node_paths=PackedStringArray("ItemsRoot", "ItemWorld")]
|
||||||
script = ExtResource("8_spawner")
|
script = ExtResource("8_spawner")
|
||||||
ItemsRoot = NodePath("../Entities")
|
ItemsRoot = NodePath("../Entities")
|
||||||
ItemWorld = NodePath("../ItemWorld")
|
ItemWorld = NodePath("../ItemWorld")
|
||||||
ItemScenes = Array[PackedScene]([ExtResource("7_flashlight")])
|
ItemScenes = Array[PackedScene]([ExtResource("7_flashlight")])
|
||||||
|
|
||||||
[node name="InteractionWorld" type="Node" parent="."]
|
[node name="InteractionWorld" type="Node" parent="." unique_id=556371192]
|
||||||
script = ExtResource("9_interactions")
|
script = ExtResource("9_interactions")
|
||||||
|
|
||||||
[node name="Entities" type="Node3D" parent="." unique_id=851492709]
|
[node name="Entities" type="Node3D" parent="." unique_id=851492709]
|
||||||
@@ -87,10 +94,10 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -4, 0, -4)
|
|||||||
[node name="floor_ceiling_hr_50" parent="Entities" unique_id=1804933431 instance=ExtResource("2_1kpcd")]
|
[node name="floor_ceiling_hr_50" parent="Entities" unique_id=1804933431 instance=ExtResource("2_1kpcd")]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -4, 0, 0)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -4, 0, 0)
|
||||||
|
|
||||||
[node name="FlashlightLeft" parent="Entities" instance=ExtResource("7_flashlight")]
|
[node name="FlashlightLeft" parent="Entities" unique_id=2018294910 instance=ExtResource("7_flashlight")]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.25, 0.65, -0.7)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.25, 0.65, -0.7)
|
||||||
InstanceId = "c08790de-b2de-44e6-a73c-a7502ca27e34"
|
InstanceId = "c08790de-b2de-44e6-a73c-a7502ca27e34"
|
||||||
|
|
||||||
[node name="FlashlightRight" parent="Entities" instance=ExtResource("7_flashlight")]
|
[node name="FlashlightRight" parent="Entities" unique_id=2042120202 instance=ExtResource("7_flashlight")]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.25, 0.65, -0.7)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.25, 0.65, -0.7)
|
||||||
InstanceId = "f71e67cb-786b-49eb-bd56-cb42ad19a754"
|
InstanceId = "f71e67cb-786b-49eb-bd56-cb42ad19a754"
|
||||||
|
|||||||
Reference in New Issue
Block a user