Compare commits
3
Commits
11fedb7f7b
...
988f1102f8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
988f1102f8 | ||
|
|
5ee80df4c1 | ||
|
|
fc14cfe0c3 |
@@ -0,0 +1,29 @@
|
||||
using System.Collections.Generic;
|
||||
using Game.Features.Console.Contracts;
|
||||
using Game.Features.Multiplayer.Contracts;
|
||||
using Game.Features.Multiplayer.Contracts.Exceptions;
|
||||
|
||||
namespace Game.Features.Console.Application.Commands;
|
||||
|
||||
public sealed class HostCommand(IMultiplayerPeer multiplayer) : ICommand
|
||||
{
|
||||
public string Name => "host";
|
||||
public string Description => "Starts hosting a multiplayer session on the given port.";
|
||||
public IReadOnlyList<CommandArgSpec> Args { get; } = new CommandArgsBuilder().AddIntArg("port").Build();
|
||||
|
||||
public CommandResult Execute(CommandArgValues args)
|
||||
{
|
||||
int port = args.GetInt(0);
|
||||
|
||||
try
|
||||
{
|
||||
multiplayer.Host(port);
|
||||
}
|
||||
catch (MultiplayerException ex)
|
||||
{
|
||||
return CommandResult.Fail(ex.Message);
|
||||
}
|
||||
|
||||
return CommandResult.Ok($"Hosting on port {port}.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://crmnspnxcut3y
|
||||
@@ -0,0 +1,35 @@
|
||||
using System.Collections.Generic;
|
||||
using Game.Features.Console.Contracts;
|
||||
using Game.Features.Multiplayer.Contracts;
|
||||
using Game.Features.Multiplayer.Contracts.Exceptions;
|
||||
|
||||
namespace Game.Features.Console.Application.Commands;
|
||||
|
||||
public sealed class JoinCommand(IMultiplayerPeer multiplayer) : ICommand
|
||||
{
|
||||
private const string AddressPattern =
|
||||
@"^(localhost|((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))$";
|
||||
|
||||
public string Name => "join";
|
||||
public string Description => "Joins a multiplayer session at the given address and port.";
|
||||
|
||||
public IReadOnlyList<CommandArgSpec> Args { get; } =
|
||||
new CommandArgsBuilder().AddRegexArg("address", AddressPattern).AddIntArg("port").Build();
|
||||
|
||||
public CommandResult Execute(CommandArgValues args)
|
||||
{
|
||||
string address = args.GetString(0);
|
||||
int port = args.GetInt(1);
|
||||
|
||||
try
|
||||
{
|
||||
multiplayer.Join(address, port);
|
||||
}
|
||||
catch (MultiplayerException ex)
|
||||
{
|
||||
return CommandResult.Fail(ex.Message);
|
||||
}
|
||||
|
||||
return CommandResult.Ok($"Connecting to {address}:{port}...");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://c80rskv3u7su7
|
||||
@@ -8,13 +8,20 @@ public enum CommandArgType
|
||||
Int,
|
||||
Float,
|
||||
Bool,
|
||||
Choice
|
||||
Choice,
|
||||
Regex
|
||||
}
|
||||
|
||||
public sealed record CommandArgSpec(string Name, CommandArgType Type, IReadOnlyList<string> Choices = null)
|
||||
{
|
||||
public override string ToString() =>
|
||||
Type == CommandArgType.Choice && Choices is not null
|
||||
? string.Join("|", Choices)
|
||||
: Type.ToString().ToLowerInvariant();
|
||||
public sealed record CommandArgSpec(
|
||||
string Name,
|
||||
CommandArgType Type,
|
||||
IReadOnlyList<string> Choices = null,
|
||||
string Pattern = null
|
||||
) {
|
||||
public override string ToString() => Type switch
|
||||
{
|
||||
CommandArgType.Choice when Choices is not null => string.Join("|", Choices),
|
||||
CommandArgType.Regex when Pattern is not null => "regex",
|
||||
_ => Type.ToString().ToLowerInvariant()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -36,5 +36,11 @@ public sealed class CommandArgsBuilder
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandArgsBuilder AddRegexArg(string name, string pattern)
|
||||
{
|
||||
_specs.Add(new CommandArgSpec(name, CommandArgType.Regex, Pattern: pattern));
|
||||
return this;
|
||||
}
|
||||
|
||||
public IReadOnlyList<CommandArgSpec> Build() => _specs.ToArray();
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Game.Features.Console.Contracts;
|
||||
|
||||
namespace Game.Features.Console.Domain;
|
||||
@@ -18,6 +19,7 @@ public static class CommandArgValidator
|
||||
CommandArgType.Float => float.TryParse(rawValue, NumberStyles.Float, CultureInfo.InvariantCulture, out _),
|
||||
CommandArgType.Bool => bool.TryParse(rawValue, out _),
|
||||
CommandArgType.Choice => spec.Choices is not null && ChoicesContain(spec.Choices, rawValue),
|
||||
CommandArgType.Regex => spec.Pattern is not null && Regex.IsMatch(rawValue, spec.Pattern),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(spec), spec.Type, null)
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Game.Features.Console.Application;
|
||||
using Game.Features.Console.Contracts;
|
||||
using Game.Features.Console.Infrastructure;
|
||||
using Game.Features.Logging.Contracts;
|
||||
using Game.Godot;
|
||||
using Game.Godot.Extensions;
|
||||
using Game.Godot.Mappings;
|
||||
@@ -8,7 +9,7 @@ using Godot;
|
||||
|
||||
namespace Game.Features.Console.Godot;
|
||||
|
||||
public partial class ConsoleController : CanvasLayer
|
||||
public partial class ConsoleController : CanvasLayer, ILogTarget
|
||||
{
|
||||
[Export] public LineEdit CommandInput;
|
||||
[Export] public RichTextLabel OutputLog;
|
||||
@@ -32,16 +33,6 @@ public partial class ConsoleController : CanvasLayer
|
||||
_mouseMemento.RestoreCaptureMode();
|
||||
}
|
||||
}
|
||||
|
||||
private void AppendLine(string line)
|
||||
{
|
||||
OutputLog.AppendText(line + "\n");
|
||||
}
|
||||
|
||||
public void UseCommandRegistry(ICommandRegistry registry)
|
||||
{
|
||||
_commandExecute = new CommandExecute(registry);
|
||||
}
|
||||
|
||||
private void OnInputSubmitted(string text)
|
||||
{
|
||||
@@ -64,6 +55,21 @@ public partial class ConsoleController : CanvasLayer
|
||||
}
|
||||
}
|
||||
|
||||
private void AppendLine(string line)
|
||||
{
|
||||
OutputLog.AppendText(line + "\n");
|
||||
}
|
||||
|
||||
public void UseCommandRegistry(ICommandRegistry registry)
|
||||
{
|
||||
_commandExecute = new CommandExecute(registry);
|
||||
}
|
||||
|
||||
public void WriteLogLine(string logLine)
|
||||
{
|
||||
AppendLine(logLine);
|
||||
}
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
this.AssertRequiredExports();
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Game.Features.Logging.Contracts;
|
||||
|
||||
public interface ILogTarget
|
||||
{
|
||||
void WriteLogLine(string logLine);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://cm0ugu0l8td0e
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Game.Features.Logging.Contracts;
|
||||
|
||||
public interface ILogger
|
||||
{
|
||||
void Debug(string category, string message);
|
||||
void Info(string category, string message);
|
||||
void Warning(string category, string message);
|
||||
void Error(string category, string message);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://0hjq84kko3td
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Game.Features.Logging.Contracts;
|
||||
|
||||
public enum LogLevel
|
||||
{
|
||||
Debug,
|
||||
Info,
|
||||
Warning,
|
||||
Error
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://2ee58okj156n
|
||||
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using Game.Features.Logging.Contracts;
|
||||
using Godot;
|
||||
|
||||
namespace Game.Features.Logging.Godot;
|
||||
|
||||
public sealed class FileLogger : ILogger, IDisposable
|
||||
{
|
||||
private const string LogPath = "user://logs/latest.log";
|
||||
|
||||
private readonly FileAccess _file;
|
||||
|
||||
public FileLogger()
|
||||
{
|
||||
DirAccess.MakeDirRecursiveAbsolute(LogPath.GetBaseDir());
|
||||
_file = FileAccess.Open(LogPath, FileAccess.ModeFlags.Write);
|
||||
|
||||
if (_file is null) return;
|
||||
|
||||
_file.StoreLine($"=== Log started at {DateTime.Now:yyyy-MM-dd HH:mm:ss} ===");
|
||||
_file.Flush();
|
||||
}
|
||||
|
||||
private void Write(LogLevel level, string category, string message)
|
||||
{
|
||||
if (_file is null) return;
|
||||
|
||||
_file.StoreLine($"{DateTime.Now:HH:mm:ss} [{level}] [{category}] {message}");
|
||||
_file.Flush();
|
||||
}
|
||||
|
||||
public void Debug(string category, string message)
|
||||
=> Write(LogLevel.Debug, category, message);
|
||||
|
||||
public void Info(string category, string message)
|
||||
=> Write(LogLevel.Info, category, message);
|
||||
|
||||
public void Warning(string category, string message)
|
||||
=> Write(LogLevel.Warning, category, message);
|
||||
|
||||
public void Error(string category, string message)
|
||||
=> Write(LogLevel.Error, category, message);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_file?.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://f45qb03i1dua
|
||||
@@ -0,0 +1,22 @@
|
||||
using Game.Features.Logging.Contracts;
|
||||
using Godot;
|
||||
|
||||
namespace Game.Features.Logging.Godot;
|
||||
|
||||
public sealed class GodotConsoleLogger : ILogger
|
||||
{
|
||||
private static string Format(LogLevel level, string category, string message)
|
||||
=> $"[{level}] [{category}] {message}";
|
||||
|
||||
public void Debug(string category, string message)
|
||||
=> GD.Print(Format(LogLevel.Debug, category, message));
|
||||
|
||||
public void Info(string category, string message)
|
||||
=> GD.Print(Format(LogLevel.Info, category, message));
|
||||
|
||||
public void Warning(string category, string message)
|
||||
=> GD.PushWarning(Format(LogLevel.Warning, category, message));
|
||||
|
||||
public void Error(string category, string message)
|
||||
=> GD.PushError(Format(LogLevel.Error, category, message));
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://jyq0in2wtq02
|
||||
@@ -0,0 +1,39 @@
|
||||
using System.Collections.Generic;
|
||||
using Game.Features.Logging.Contracts;
|
||||
|
||||
namespace Game.Features.Logging.Infrastructure;
|
||||
|
||||
public sealed class CompositeLogger(IReadOnlyList<ILogger> loggers) : ILogger
|
||||
{
|
||||
public void Debug(string category, string message)
|
||||
{
|
||||
foreach (ILogger logger in loggers)
|
||||
{
|
||||
logger.Debug(category, message);
|
||||
}
|
||||
}
|
||||
|
||||
public void Info(string category, string message)
|
||||
{
|
||||
foreach (ILogger logger in loggers)
|
||||
{
|
||||
logger.Info(category, message);
|
||||
}
|
||||
}
|
||||
|
||||
public void Warning(string category, string message)
|
||||
{
|
||||
foreach (ILogger logger in loggers)
|
||||
{
|
||||
logger.Warning(category, message);
|
||||
}
|
||||
}
|
||||
|
||||
public void Error(string category, string message)
|
||||
{
|
||||
foreach (ILogger logger in loggers)
|
||||
{
|
||||
logger.Error(category, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://cumunwgy6i71s
|
||||
@@ -0,0 +1,21 @@
|
||||
using Game.Features.Logging.Contracts;
|
||||
|
||||
namespace Game.Features.Logging.Infrastructure;
|
||||
|
||||
/// <summary>
|
||||
/// Forwards log entries to an ILogTarget.
|
||||
/// </summary>
|
||||
public sealed class LogTargetLogger(ILogTarget target) : ILogger
|
||||
{
|
||||
public void Debug(string category, string message)
|
||||
=> target.WriteLogLine(message);
|
||||
|
||||
public void Info(string category, string message)
|
||||
=> target.WriteLogLine(message);
|
||||
|
||||
public void Warning(string category, string message)
|
||||
=> target.WriteLogLine(message);
|
||||
|
||||
public void Error(string category, string message)
|
||||
=> target.WriteLogLine(message);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://bxnu6wg0qe03r
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Game.Features.Multiplayer.Contracts.Exceptions;
|
||||
|
||||
/// <summary>
|
||||
/// Thrown when trying to host or join while already connected to a session.
|
||||
/// </summary>
|
||||
public sealed class MultiplayerAlreadyConnectedException() : MultiplayerException("Already connected to a session.");
|
||||
+1
@@ -0,0 +1 @@
|
||||
uid://dr6f5t6nhh60w
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Game.Features.Multiplayer.Contracts.Exceptions;
|
||||
|
||||
/// <summary>
|
||||
/// Thrown when trying to host or join while already hosting a session.
|
||||
/// </summary>
|
||||
public sealed class MultiplayerAlreadyHostingException() : MultiplayerException("Already hosting a session.");
|
||||
+1
@@ -0,0 +1 @@
|
||||
uid://c41cffks8ymh0
|
||||
@@ -0,0 +1,8 @@
|
||||
using System;
|
||||
|
||||
namespace Game.Features.Multiplayer.Contracts.Exceptions;
|
||||
|
||||
/// <summary>
|
||||
/// Base type for multiplayer connection failures.
|
||||
/// </summary>
|
||||
public abstract class MultiplayerException(string message) : Exception(message);
|
||||
@@ -0,0 +1 @@
|
||||
uid://ct5t3i77ovfx1
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Game.Features.Multiplayer.Contracts.Exceptions;
|
||||
|
||||
/// <summary>
|
||||
/// Thrown when starting a host fails at the transport level.
|
||||
/// </summary>
|
||||
public sealed class MultiplayerHostException(int port, string reason)
|
||||
: MultiplayerException($"Failed to start host on port {port}: {reason}.")
|
||||
{
|
||||
public int Port { get; } = port;
|
||||
public string Reason { get; } = reason;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://scqi785gvntu
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Game.Features.Multiplayer.Contracts.Exceptions;
|
||||
|
||||
/// <summary>
|
||||
/// Thrown when connecting to a host fails at the transport level.
|
||||
/// </summary>
|
||||
public sealed class MultiplayerJoinException(string address, int port, string reason)
|
||||
: MultiplayerException($"Failed to connect to {address}:{port}: {reason}.")
|
||||
{
|
||||
public string Address { get; } = address;
|
||||
public int Port { get; } = port;
|
||||
public string Reason { get; } = reason;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://q8oyea00yrxy
|
||||
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
|
||||
namespace Game.Features.Multiplayer.Contracts;
|
||||
|
||||
public interface IMultiplayerPeer
|
||||
{
|
||||
event Action Connected;
|
||||
event Action ConnectionFailed;
|
||||
event Action Disconnected;
|
||||
event Action<PeerId> PeerJoined;
|
||||
event Action<PeerId> PeerLeft;
|
||||
|
||||
void Host(int port);
|
||||
|
||||
void Join(string address, int port);
|
||||
|
||||
void Disconnect();
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://bs401ksgc6qdn
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Game.Features.Multiplayer.Contracts;
|
||||
|
||||
public readonly record struct PeerId(long Value)
|
||||
{
|
||||
public override string ToString() => Value.ToString();
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://bix11ttul7aco
|
||||
@@ -0,0 +1,26 @@
|
||||
using System.Collections.Generic;
|
||||
using Game.Features.Multiplayer.Contracts;
|
||||
|
||||
namespace Game.Features.Multiplayer.Domain;
|
||||
|
||||
public sealed class MultiplayerSession
|
||||
{
|
||||
public const int MaxPlayers = 4;
|
||||
public IReadOnlyCollection<PeerId> PeerIds => _peerIds;
|
||||
public bool IsFull => _peerIds.Count >= MaxPlayers;
|
||||
public bool IsHosting { get; set; }
|
||||
public bool IsConnected { get; set; }
|
||||
|
||||
private readonly HashSet<PeerId> _peerIds = [];
|
||||
|
||||
public void AddPeer(PeerId peerId) => _peerIds.Add(peerId);
|
||||
|
||||
public void RemovePeer(PeerId peerId) => _peerIds.Remove(peerId);
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_peerIds.Clear();
|
||||
IsHosting = false;
|
||||
IsConnected = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://dbpm807l4bmfk
|
||||
@@ -0,0 +1,21 @@
|
||||
using Game.Features.Multiplayer.Contracts.Exceptions;
|
||||
using Godot;
|
||||
|
||||
namespace Game.Features.Multiplayer.Godot;
|
||||
|
||||
public sealed class EnetMultiplayerPeerFactory : IMultiplayerPeerFactory
|
||||
{
|
||||
public MultiplayerPeer CreateHost(int port, int maxPeers)
|
||||
{
|
||||
ENetMultiplayerPeer peer = new();
|
||||
Error error = peer.CreateServer(port, maxPeers);
|
||||
return error != Error.Ok ? throw new MultiplayerHostException(port, error.ToString()) : peer;
|
||||
}
|
||||
|
||||
public MultiplayerPeer CreateClient(string address, int port)
|
||||
{
|
||||
ENetMultiplayerPeer peer = new();
|
||||
Error error = peer.CreateClient(address, port);
|
||||
return error != Error.Ok ? throw new MultiplayerJoinException(address, port, error.ToString()) : peer;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://dkymguty84h4v
|
||||
@@ -0,0 +1,10 @@
|
||||
using Godot;
|
||||
|
||||
namespace Game.Features.Multiplayer.Godot;
|
||||
|
||||
public interface IMultiplayerPeerFactory
|
||||
{
|
||||
MultiplayerPeer CreateHost(int port, int maxPeers);
|
||||
|
||||
MultiplayerPeer CreateClient(string address, int port);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://csk14887ww6q6
|
||||
@@ -0,0 +1,101 @@
|
||||
using System;
|
||||
using Game.Features.Multiplayer.Contracts;
|
||||
using Game.Features.Multiplayer.Contracts.Exceptions;
|
||||
using Game.Features.Multiplayer.Domain;
|
||||
using Godot;
|
||||
|
||||
namespace Game.Features.Multiplayer.Godot;
|
||||
|
||||
public partial class MultiplayerController : Node, IMultiplayerPeer
|
||||
{
|
||||
private readonly MultiplayerSession _session = new();
|
||||
private IMultiplayerPeerFactory _multiplayerPeerFactory;
|
||||
|
||||
public event Action Connected;
|
||||
public event Action ConnectionFailed;
|
||||
public event Action Disconnected;
|
||||
public event Action<PeerId> PeerJoined;
|
||||
public event Action<PeerId> PeerLeft;
|
||||
|
||||
private void OnPeerConnected(long id)
|
||||
{
|
||||
PeerId peerId = new(id);
|
||||
_session.AddPeer(peerId);
|
||||
PeerJoined?.Invoke(peerId);
|
||||
}
|
||||
|
||||
private void OnPeerDisconnected(long id)
|
||||
{
|
||||
PeerId peerId = new(id);
|
||||
_session.RemovePeer(peerId);
|
||||
PeerLeft?.Invoke(peerId);
|
||||
}
|
||||
|
||||
private void OnConnectedToServer()
|
||||
{
|
||||
Connected?.Invoke();
|
||||
}
|
||||
|
||||
private void OnConnectionFailed()
|
||||
{
|
||||
_session.Reset();
|
||||
ConnectionFailed?.Invoke();
|
||||
}
|
||||
|
||||
private void OnServerDisconnected()
|
||||
{
|
||||
_session.Reset();
|
||||
Disconnected?.Invoke();
|
||||
}
|
||||
|
||||
private void AssertNoActiveSession()
|
||||
{
|
||||
if (_session.IsHosting)
|
||||
{
|
||||
throw new MultiplayerAlreadyHostingException();
|
||||
}
|
||||
|
||||
if (_session.IsConnected)
|
||||
{
|
||||
throw new MultiplayerAlreadyConnectedException();
|
||||
}
|
||||
}
|
||||
|
||||
public void UsePeerFactory(IMultiplayerPeerFactory multiplayerPeerFactory)
|
||||
{
|
||||
_multiplayerPeerFactory = multiplayerPeerFactory;
|
||||
}
|
||||
|
||||
public void Host(int port)
|
||||
{
|
||||
AssertNoActiveSession();
|
||||
|
||||
Multiplayer.MultiplayerPeer = _multiplayerPeerFactory.CreateHost(port, MultiplayerSession.MaxPlayers - 1);
|
||||
_session.AddPeer(new PeerId(Multiplayer.GetUniqueId()));
|
||||
_session.IsHosting = true;
|
||||
}
|
||||
|
||||
public void Join(string address, int port)
|
||||
{
|
||||
AssertNoActiveSession();
|
||||
|
||||
Multiplayer.MultiplayerPeer = _multiplayerPeerFactory.CreateClient(address, port);
|
||||
_session.IsConnected = true;
|
||||
}
|
||||
|
||||
public void Disconnect()
|
||||
{
|
||||
Multiplayer.MultiplayerPeer?.Close();
|
||||
Multiplayer.MultiplayerPeer = null;
|
||||
_session.Reset();
|
||||
}
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
Multiplayer.PeerConnected += OnPeerConnected;
|
||||
Multiplayer.PeerDisconnected += OnPeerDisconnected;
|
||||
Multiplayer.ConnectedToServer += OnConnectedToServer;
|
||||
Multiplayer.ConnectionFailed += OnConnectionFailed;
|
||||
Multiplayer.ServerDisconnected += OnServerDisconnected;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://drqx0wiw0sbj1
|
||||
@@ -1,6 +1,9 @@
|
||||
using Game.Features.Console.Application.Commands;
|
||||
using Game.Features.Console.Godot;
|
||||
using Game.Features.Console.Infrastructure;
|
||||
using Game.Features.Logging.Godot;
|
||||
using Game.Features.Logging.Infrastructure;
|
||||
using Game.Features.Multiplayer.Godot;
|
||||
using Game.Features.Players.Godot;
|
||||
using Game.Godot.Extensions;
|
||||
using Game.Godot.Mappings;
|
||||
@@ -12,6 +15,20 @@ public partial class GameBootstrap : Node
|
||||
{
|
||||
[Export] public ConsoleController Console;
|
||||
[Export] public PlayerController Player;
|
||||
[Export] public MultiplayerController MultiplayerController;
|
||||
|
||||
private FileLogger _fileLogger;
|
||||
private CompositeLogger _logger;
|
||||
|
||||
private void InitializeLogging()
|
||||
{
|
||||
_fileLogger = new FileLogger();
|
||||
_logger = new CompositeLogger([
|
||||
new GodotConsoleLogger(),
|
||||
new LogTargetLogger(Console),
|
||||
_fileLogger
|
||||
]);
|
||||
}
|
||||
|
||||
private void RegisterConsoleCommands()
|
||||
{
|
||||
@@ -20,15 +37,30 @@ public partial class GameBootstrap : Node
|
||||
registry.Register(new EchoCommand());
|
||||
registry.Register(new ClearCommand());
|
||||
registry.Register(new NoclipCommand(Player));
|
||||
registry.Register(new HostCommand(MultiplayerController));
|
||||
registry.Register(new JoinCommand(MultiplayerController));
|
||||
|
||||
Console.UseCommandRegistry(registry);
|
||||
}
|
||||
|
||||
private void InitializeMultiplayer()
|
||||
{
|
||||
MultiplayerController.UsePeerFactory(new EnetMultiplayerPeerFactory());
|
||||
MultiplayerController.Connected += () => _logger.Info("Multiplayer", "Connected successfully.");
|
||||
MultiplayerController.ConnectionFailed += () => _logger.Warning("Multiplayer", "Connection failed.");
|
||||
MultiplayerController.Disconnected += () => _logger.Warning("Multiplayer", "Server disconnected.");
|
||||
MultiplayerController.PeerJoined += id => _logger.Info("Multiplayer", $"Peer {id} joined.");
|
||||
MultiplayerController.PeerLeft += id => _logger.Info("Multiplayer", $"Peer {id} left.");
|
||||
}
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
this.AssertRequiredExports();
|
||||
|
||||
MouseCapture.Capture();
|
||||
|
||||
InitializeLogging();
|
||||
InitializeMultiplayer();
|
||||
RegisterConsoleCommands();
|
||||
}
|
||||
|
||||
@@ -39,4 +71,9 @@ public partial class GameBootstrap : Node
|
||||
MouseCapture.Toggle();
|
||||
}
|
||||
}
|
||||
|
||||
public override void _ExitTree()
|
||||
{
|
||||
_fileLogger?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
|
||||
[ext_resource type="Texture2D" uid="uid://b5i827vmueyei" path="res://assets/textures/skyboxes/skybox_emx_19.png" id="1_g4n7o"]
|
||||
[ext_resource type="PackedScene" uid="uid://curuqmbndymi6" path="res://src/Godot/Scenes/FloorCeilingHr42.tscn" id="2_1kpcd"]
|
||||
[ext_resource type="PackedScene" uid="uid://bka1dmgdi5btd" path="res://src/Features/Player/Godot/PlayerNode.tscn" id="2_mvtit"]
|
||||
[ext_resource type="PackedScene" uid="uid://bka1dmgdi5btd" path="res://src/Features/Players/Godot/PlayerNode.tscn" id="2_mvtit"]
|
||||
[ext_resource type="Script" uid="uid://drctu5y21ey2y" path="res://src/Godot/GameBootstrap.cs" id="3_bootstrap"]
|
||||
[ext_resource type="PackedScene" 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"]
|
||||
|
||||
[sub_resource type="PanoramaSkyMaterial" id="PanoramaSkyMaterial_epykj"]
|
||||
panorama = ExtResource("1_g4n7o")
|
||||
@@ -17,10 +18,11 @@ background_mode = 2
|
||||
sky = SubResource("Sky_1kpcd")
|
||||
glow_enabled = true
|
||||
|
||||
[node name="GameWorld" type="Node" unique_id=222644234 node_paths=PackedStringArray("Console", "Player")]
|
||||
[node name="GameWorld" type="Node" unique_id=222644234 node_paths=PackedStringArray("Console", "Player", "MultiplayerController")]
|
||||
script = ExtResource("3_bootstrap")
|
||||
Console = NodePath("Console")
|
||||
Player = NodePath("Player")
|
||||
MultiplayerController = NodePath("Multiplayer")
|
||||
|
||||
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="." unique_id=1669211403]
|
||||
transform = Transform3D(-0.8660254, -0.43301278, 0.25, 0, 0.49999997, 0.86602545, -0.50000006, 0.75, -0.43301266, 0, 0, 0)
|
||||
@@ -32,7 +34,10 @@ environment = SubResource("Environment_mvtit")
|
||||
[node name="Player" parent="." unique_id=26823676 instance=ExtResource("2_mvtit")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
|
||||
|
||||
[node name="Console" parent="." instance=ExtResource("4_console")]
|
||||
[node name="Console" parent="." unique_id=1947043894 instance=ExtResource("4_console")]
|
||||
|
||||
[node name="Multiplayer" type="Node" parent="." unique_id=1209131013]
|
||||
script = ExtResource("5_multiplayer")
|
||||
|
||||
[node name="Entities" type="Node3D" parent="." unique_id=851492709]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user