M src/Core/NosSmooth.Comms.Core/ConnectionHandler.cs => src/Core/NosSmooth.Comms.Core/ConnectionHandler.cs +21 -7
@@ 133,11 133,14 @@ public class ConnectionHandler
/// Create a contract for sending a message,
/// <see cref="ResponseResult"/> will be returned back.
/// </summary>
- /// <param name="handshake">The handshake request.</param>
+ /// <param name="message">The request message.</param>
+ /// <param name="filter">The filter to check if the response is meant for the request.</param>
/// <typeparam name="TMessage">The type of the message.</typeparam>
+ /// <typeparam name="TResponse">The type of the response.</typeparam>
/// <returns>A contract representing send message operation.</returns>
/// <exception cref="InvalidOperationException">Thrown in case contract is created on the server. Clients do not send responses.</exception>
- public IContract<HandshakeResponse, DefaultStates> ContractHanshake(HandshakeRequest handshake)
+ public IContract<TResponse, DefaultStates> ContractCustomResponse<TMessage, TResponse>(TMessage message, Func<TResponse, bool> filter)
+ where TResponse : notnull
{
if (_contractor is null)
{
@@ 147,13 150,13 @@ public class ConnectionHandler
);
}
- return new ContractBuilder<HandshakeResponse, DefaultStates, NoErrors>(_contractor, DefaultStates.None)
+ return new ContractBuilder<TResponse, DefaultStates, NoErrors>(_contractor, DefaultStates.None)
.SetMoveAction
(
DefaultStates.None,
async (a, ct) =>
{
- var result = await SendMessageAsync(handshake, ct);
+ var result = await SendMessageAsync(message, ct);
if (!result.IsDefined(out _))
{
return Result<bool>.FromError(result);
@@ 163,9 166,9 @@ public class ConnectionHandler
},
DefaultStates.Requested
)
- .SetMoveFilter<HandshakeResponse>
- (DefaultStates.Requested, DefaultStates.ResponseObtained)
- .SetFillData<HandshakeResponse>(DefaultStates.ResponseObtained, r => r)
+ .SetMoveFilter<TResponse>
+ (DefaultStates.Requested, filter, DefaultStates.ResponseObtained)
+ .SetFillData<TResponse>(DefaultStates.ResponseObtained, r => r)
.Build();
}
@@ 173,6 176,17 @@ public class ConnectionHandler
/// Create a contract for sending a message,
/// <see cref="ResponseResult"/> will be returned back.
/// </summary>
+ /// <param name="handshake">The handshake request.</param>
+ /// <typeparam name="TMessage">The type of the message.</typeparam>
+ /// <returns>A contract representing send message operation.</returns>
+ /// <exception cref="InvalidOperationException">Thrown in case contract is created on the server. Clients do not send responses.</exception>
+ public IContract<HandshakeResponse, DefaultStates> ContractHanshake(HandshakeRequest handshake)
+ => ContractCustomResponse<HandshakeRequest, HandshakeResponse>(handshake, _ => true);
+
+ /// <summary>
+ /// Create a contract for sending a message,
+ /// <see cref="ResponseResult"/> will be returned back.
+ /// </summary>
/// <param name="message">The message.</param>
/// <typeparam name="TMessage">The type of the message.</typeparam>
/// <returns>A contract representing send message operation.</returns>
M src/Local/NosSmooth.Comms.Inject/ClientState.cs => src/Local/NosSmooth.Comms.Inject/ClientState.cs +53 -0
@@ 4,6 4,9 @@
// Copyright (c) František Boháček. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+using NosSmooth.Comms.Inject.Messages;
+using NosSmooth.LocalBinding;
+using NosSmooth.LocalBinding.Options;
using Remora.Results;
namespace NosSmooth.Comms.Inject;
@@ 14,6 17,16 @@ namespace NosSmooth.Comms.Inject;
public class ClientState
{
/// <summary>
+ /// Gets the token cancelled upon starting the client (<see cref="RunClientRequest"/> received).
+ /// </summary>
+ public CancellationTokenSource Starting { get; } = new CancellationTokenSource();
+
+ /// <summary>
+ /// Gets the token cancelled when client has started (inside of <see cref="NosSmoothService"/>).
+ /// </summary>
+ public CancellationTokenSource Started { get; } = new CancellationTokenSource();
+
+ /// <summary>
/// Gets or sets whether the client is running.
/// </summary>
public bool IsRunning { get; internal set; }
@@ 22,4 35,44 @@ public class ClientState
/// Gets or sets the result that was obtained upon initialization of the client.
/// </summary>
public Result? InitResult { get; internal set; }
+
+ /// <summary>
+ /// Gets or sets the result that was obtained from initialization of <see cref="NosBindingManager"/>.
+ /// </summary>
+ public Result? BindingResult { get; internal set; }
+
+ /// <summary>
+ /// Gets or sets hook manager options.
+ /// </summary>
+ public HookManagerOptions? HookOptions { get; internal set; }
+
+ /// <summary>
+ /// Gets or sets unit manager options.
+ /// </summary>
+ public UnitManagerOptions? UnitManagerOptions { get; internal set; }
+
+ /// <summary>
+ /// Gets or sets scene manager options.
+ /// </summary>
+ public SceneManagerOptions? SceneManagerOptions { get; internal set; }
+
+ /// <summary>
+ /// Gets or sets network manager options.
+ /// </summary>
+ public NetworkManagerOptions? NetworkManagerOptions { get; internal set; }
+
+ /// <summary>
+ /// Gets or sets player manager options.
+ /// </summary>
+ public PlayerManagerOptions? PlayerManagerOptions { get; internal set; }
+
+ /// <summary>
+ /// Gets or sets pet manager options.
+ /// </summary>
+ public PetManagerOptions? PetManagerOptions { get; internal set; }
+
+ /// <summary>
+ /// Gets or sets nt client options.
+ /// </summary>
+ public NtClientOptions? NtClientOptions { get; internal set; }
}=
\ No newline at end of file
M src/Local/NosSmooth.Comms.Inject/DllMain.cs => src/Local/NosSmooth.Comms.Inject/DllMain.cs +13 -2
@@ 12,10 12,12 @@ using Microsoft.Extensions.Logging;
using NosSmooth.Comms.Core;
using NosSmooth.Comms.Core.Extensions;
using NosSmooth.Comms.Data;
+using NosSmooth.Comms.Inject.Extensions;
using NosSmooth.Comms.Inject.MessageResponders;
using NosSmooth.Comms.Inject.PacketResponders;
using NosSmooth.Core.Extensions;
using NosSmooth.Extensions.SharedBinding.Extensions;
+using NosSmooth.LocalBinding.Options;
using NosSmooth.LocalClient.Extensions;
using Remora.Results;
@@ 98,6 100,7 @@ public class DllMain
return;
}
+ var clientState = new ClientState();
_host = Host.CreateDefaultBuilder()
.UseConsoleLifetime()
.ConfigureLogging
@@ 114,7 117,7 @@ public class DllMain
s =>
{
s
- .AddSingleton<ClientState>()
+ .AddSingleton<ClientState>(_ => clientState)
.AddSingleton<CallbackConfigRepository>()
.AddManagedNostaleCore()
.AddLocalClient()
@@ 127,7 130,15 @@ public class DllMain
.AddMessageResponder<FollowResponder>()
.AddMessageResponder<HandshakeResponder>()
.AddMessageResponder<ConsoleResponder>()
- .AddMessageResponder<PacketResponder>();
+ .AddMessageResponder<PacketResponder>()
+ .AddMessageResponder<RunClientResponder>()
+ .Configure<HookManagerOptions>(clientState.HookOptions.CopyProperties)
+ .Configure<PlayerManagerOptions>(clientState.PlayerManagerOptions.CopyProperties)
+ .Configure<NetworkManagerOptions>(opts => clientState.NetworkManagerOptions.CopyProperties(opts))
+ .Configure<SceneManagerOptions>(clientState.SceneManagerOptions.CopyProperties)
+ .Configure<UnitManagerOptions>(clientState.UnitManagerOptions.CopyProperties)
+ .Configure<NtClientOptions>(clientState.NtClientOptions.CopyProperties)
+ .Configure<PetManagerOptions>(clientState.PetManagerOptions.CopyProperties);
s.AddHostedService<NosSmoothService>();
}
).Build();
A src/Local/NosSmooth.Comms.Inject/Extensions/ObjectExtensions.cs => src/Local/NosSmooth.Comms.Inject/Extensions/ObjectExtensions.cs +39 -0
@@ 0,0 1,39 @@
+//
+// ObjectExtensions.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Diagnostics;
+using System.Reflection;
+
+namespace NosSmooth.Comms.Inject.Extensions;
+
+/// <summary>
+/// Extension methods for object.
+/// </summary>
+public static class ObjectExtensions
+{
+ /// <summary>
+ /// Extension for 'Object' that copies the properties to a destination object.
+ /// </summary>
+ /// <param name="source">The source.</param>
+ /// <param name="destination">The destination.</param>
+ /// <typeparam name="T">The type.</typeparam>
+ public static void CopyProperties<T>(this T? source, T destination)
+ where T : notnull
+ {
+ if (source is null)
+ {
+ return;
+ }
+
+ var properties = source.GetType().GetProperties();
+
+ foreach (var p in properties.Where(prop => prop.CanRead && prop.CanWrite))
+ {
+ object? copyValue = p.GetValue(source);
+ p.SetValue(destination, copyValue);
+ }
+ }
+}<
\ No newline at end of file
M src/Local/NosSmooth.Comms.Inject/MessageResponders/HandshakeResponder.cs => src/Local/NosSmooth.Comms.Inject/MessageResponders/HandshakeResponder.cs +6 -15
@@ 19,7 19,6 @@ namespace NosSmooth.Comms.Inject.MessageResponders;
public class HandshakeResponder : IMessageResponder<HandshakeRequest>
{
private readonly ClientState _state;
- private readonly NosBrowserManager _browserManager;
private readonly ConnectionHandler _connectionHandler;
private readonly CallbackConfigRepository _config;
private readonly ILogger<HandshakeResponder> _logger;
@@ 35,14 34,12 @@ public class HandshakeResponder : IMessageResponder<HandshakeRequest>
public HandshakeResponder
(
ClientState state,
- NosBrowserManager browserManager,
ConnectionHandler connectionHandler,
CallbackConfigRepository config,
ILogger<HandshakeResponder> logger
)
{
_state = state;
- _browserManager = browserManager;
_connectionHandler = connectionHandler;
_config = config;
_logger = logger;
@@ 54,20 51,14 @@ public class HandshakeResponder : IMessageResponder<HandshakeRequest>
var config = new CallbackConfig(message.SendRawPackets, message.SendDeserializedPackets);
_config.SetConfig(_connectionHandler, config);
- string? playerName = null;
- long? playerId = null;
-
- if (_browserManager.PlayerManager.TryGet(out var playerManager) &&
- _browserManager.IsInGame.TryGet(out var isInGame) &&
- isInGame)
- {
- playerName = playerManager.Player.Name;
- playerId = playerManager.PlayerId;
- }
-
var result = await _connectionHandler.SendMessageAsync
(
- new HandshakeResponse(_state.IsRunning, _state.InitResult, playerId, playerName),
+ new HandshakeResponse
+ (
+ _state.IsRunning,
+ _state.InitResult,
+ _state.BindingResult
+ ),
ct
);
A src/Local/NosSmooth.Comms.Inject/MessageResponders/RunClientResponder.cs => src/Local/NosSmooth.Comms.Inject/MessageResponders/RunClientResponder.cs +71 -0
@@ 0,0 1,71 @@
+//
+// RunClientResponder.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using NosSmooth.Comms.Core;
+using NosSmooth.Comms.Data.Responders;
+using NosSmooth.Comms.Inject.Messages;
+using Remora.Results;
+
+namespace NosSmooth.Comms.Inject.MessageResponders;
+
+/// <summary>
+/// A responder to <see cref="RunClientRequest"/>.
+/// </summary>
+public class RunClientResponder : IMessageResponder<RunClientRequest>
+{
+ private readonly ClientState _state;
+ private readonly ConnectionHandler _connectionHandler;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="RunClientResponder"/> class.
+ /// </summary>
+ /// <param name="state">The client state.</param>
+ /// <param name="connectionHandler">The connection handler wrapper.</param>
+ public RunClientResponder
+ (
+ ClientState state,
+ ConnectionHandler connectionHandler
+ )
+ {
+ _state = state;
+ _connectionHandler = connectionHandler;
+ }
+
+ /// <inheritdoc />
+ public async Task<Result> Respond(RunClientRequest request, CancellationToken ct = default)
+ {
+ _state.HookOptions = request.HookOptions;
+ _state.NetworkManagerOptions = request.NetworkManagerOptions;
+ _state.NtClientOptions = request.NtClientOptions;
+ _state.PetManagerOptions = request.PetManagerOptions;
+ _state.PlayerManagerOptions = request.PlayerManagerOptions;
+ _state.SceneManagerOptions = request.SceneManagerOptions;
+ _state.UnitManagerOptions = request.UnitManagerOptions;
+ _state.UnitManagerOptions = request.UnitManagerOptions;
+
+ if (!_state.Starting.IsCancellationRequested)
+ { // start the client.
+ _state.Starting.Cancel();
+ }
+
+ if (!_state.Started.IsCancellationRequested)
+ { // wait until the client has started.
+ try
+ {
+ await Task.Delay(-1, _state.Started.Token);
+ }
+ catch
+ {
+ // ignored
+ }
+ }
+
+ var sendMessageResult = await _connectionHandler.SendMessageAsync
+ (new RunClientResponse(_state.InitResult, _state.BindingResult), ct);
+
+ return sendMessageResult.IsSuccess ? Result.FromSuccess() : Result.FromError(sendMessageResult);
+ }
+}<
\ No newline at end of file
A src/Local/NosSmooth.Comms.Inject/Messages/RunClientRequest.cs => src/Local/NosSmooth.Comms.Inject/Messages/RunClientRequest.cs +35 -0
@@ 0,0 1,35 @@
+//
+// RunClientRequest.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using NosSmooth.Comms.Data.Messages;
+using NosSmooth.LocalBinding.Options;
+
+namespace NosSmooth.Comms.Inject.Messages;
+
+/// <summary>
+/// Request to run NosTale client.
+/// </summary>
+/// <remarks>
+/// Should be used if <see cref="HandshakeResponse"/>
+/// contained not started .
+/// </remarks>
+/// <param name="HookOptions"></param>
+/// <param name="UnitManagerOptions"></param>
+/// <param name="SceneManagerOptions"></param>
+/// <param name="NetworkManagerOptions"></param>
+/// <param name="PlayerManagerOptions"></param>
+/// <param name="PetManagerOptions"></param>
+/// <param name="NtClientOptions"></param>
+public record RunClientRequest
+(
+ HookManagerOptions? HookOptions = default,
+ UnitManagerOptions? UnitManagerOptions = default,
+ SceneManagerOptions? SceneManagerOptions = default,
+ NetworkManagerOptions? NetworkManagerOptions = default,
+ PlayerManagerOptions? PlayerManagerOptions = default,
+ PetManagerOptions? PetManagerOptions = default,
+ NtClientOptions? NtClientOptions = default
+);<
\ No newline at end of file
A src/Local/NosSmooth.Comms.Inject/Messages/RunClientResponse.cs => src/Local/NosSmooth.Comms.Inject/Messages/RunClientResponse.cs +20 -0
@@ 0,0 1,20 @@
+//
+// RunClientResponse.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Remora.Results;
+
+namespace NosSmooth.Comms.Inject.Messages;
+
+/// <summary>
+/// A response to run client.
+/// </summary>
+/// <param name="InitializationResult">The run result. If error, that means the client is not running.</param>
+/// <param name="BindingManagerResult">The result from binding manager initialization.</param>
+public record RunClientResponse
+(
+ Result? InitializationResult,
+ Result? BindingManagerResult
+);<
\ No newline at end of file
M src/Local/NosSmooth.Comms.Inject/NosSmoothService.cs => src/Local/NosSmooth.Comms.Inject/NosSmoothService.cs +41 -11
@@ 5,6 5,7 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Diagnostics;
+using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
@@ 25,7 26,6 @@ public class NosSmoothService : BackgroundService
private readonly IServiceProvider _services;
private readonly ClientState _state;
private readonly IPacketTypesRepository _packetTypesRepository;
- private readonly NosBindingManager _bindingManager;
private readonly ILogger<NosSmoothService> _logger;
/// <summary>
@@ 34,27 34,38 @@ public class NosSmoothService : BackgroundService
/// <param name="services">The services.</param>
/// <param name="state">The state of the application.</param>
/// <param name="packetTypesRepository">The packet types repository.</param>
- /// <param name="bindingManager">The binding manager.</param>
/// <param name="logger">The logger.</param>
public NosSmoothService
(
IServiceProvider services,
ClientState state,
IPacketTypesRepository packetTypesRepository,
- NosBindingManager bindingManager,
ILogger<NosSmoothService> logger
)
{
_services = services;
_state = state;
_packetTypesRepository = packetTypesRepository;
- _bindingManager = bindingManager;
_logger = logger;
}
/// <inheritdoc />
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
+ var ctSource = CancellationTokenSource.CreateLinkedTokenSource(stoppingToken, _state.Starting.Token);
+ try
+ {
+ await Task.Delay(-1, ctSource.Token);
+ }
+ catch (Exception e)
+ {
+ }
+
+ if (stoppingToken.IsCancellationRequested)
+ {
+ return;
+ }
+
var packetResult = _packetTypesRepository.AddDefaultPackets();
if (!packetResult.IsSuccess)
{
@@ 64,20 75,39 @@ public class NosSmoothService : BackgroundService
_logger.LogResultError(packetResult);
}
- var bindingResult = _bindingManager.Initialize();
+ var bindingResult = _services.GetRequiredService<NosBindingManager>().Initialize();
if (!bindingResult.IsSuccess)
{
_state.InitResult = Result.FromError(bindingResult.Error);
- _logger.LogError("Could not initialize the binding manager");
+ _logger.LogError("Could not initialize the binding manager.");
_logger.LogResultError(bindingResult);
- await Task.Delay(-1, stoppingToken);
- return;
+ _state.BindingResult = Result.FromError
+ (new GenericError("Binding manager not initialized correctly."), bindingResult);
+ }
+ else
+ {
+ _state.BindingResult = Result.FromSuccess();
}
- _state.IsRunning = true;
var nostaleClient = _services.GetRequiredService<INostaleClient>();
- var runResult = await nostaleClient.RunAsync(stoppingToken);
- _state.IsRunning = false;
+ var runTask = nostaleClient.RunAsync(stoppingToken);
+ await Task.Delay(10, stoppingToken);
+ Result runResult;
+ if (runTask.IsCompleted)
+ {
+ runResult = await runTask;
+ _state.InitResult = runResult;
+ _state.Started.Cancel();
+ }
+ else
+ {
+ _state.IsRunning = true;
+ _state.InitResult = Result.FromSuccess();
+ _state.Started.Cancel();
+
+ runResult = await runTask;
+ }
+
if (!runResult.IsSuccess)
{
_state.InitResult = runResult;
A src/Local/NosSmooth.Comms.Local/Extensions/ConnectionHandlerExtensions.cs => src/Local/NosSmooth.Comms.Local/Extensions/ConnectionHandlerExtensions.cs +27 -0
@@ 0,0 1,27 @@
+//
+// ConnectionHandlerExtensions.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using NosSmooth.Comms.Core;
+using NosSmooth.Comms.Inject.Messages;
+using NosSmooth.Core.Contracts;
+
+namespace NosSmooth.Comms.Local.Extensions;
+
+/// <summary>
+/// Extension methods for <see cref="ConnectionHandler"/>.
+/// </summary>
+public static class ConnectionHandlerExtensions
+{
+ /// <summary>
+ /// Contract <see cref="RunClientRequest"/>, <see cref="RunClientResponse"/>.
+ /// </summary>
+ /// <param name="connectionHandler">The connection handler.</param>
+ /// <param name="message">The request.</param>
+ /// <returns>The contract.</returns>
+ public static IContract<RunClientResponse, DefaultStates> ContractRunClient
+ (this ConnectionHandler connectionHandler, RunClientRequest message)
+ => connectionHandler.ContractCustomResponse<RunClientRequest, RunClientResponse>(message, _ => true);
+}<
\ No newline at end of file
M src/Samples/ConsolePacketLogger/ClientService.cs => src/Samples/ConsolePacketLogger/ClientService.cs +40 -9
@@ 10,9 10,13 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using NosSmooth.Comms.Core;
using NosSmooth.Comms.Data.Messages;
+using NosSmooth.Comms.Inject.Messages;
using NosSmooth.Comms.Local;
+using NosSmooth.Comms.Local.Extensions;
using NosSmooth.Core.Contracts;
using NosSmooth.Core.Extensions;
+using NosSmooth.LocalBinding.Options;
+using NosSmooth.PacketSerializer.Abstractions.Attributes;
using NosSmooth.PacketSerializer.Extensions;
using NosSmooth.PacketSerializer.Packets;
@@ 84,20 88,47 @@ public class ClientService : BackgroundService
if (!handshakeResponse.ClientRunning)
{
- _logger.LogError("The client is not running?");
+ _logger.LogInformation("The client is not running, going to start it");
+ }
+ else
+ {
+ if (handshakeResponse.InitializationResult is not null
+ && !handshakeResponse.InitializationResult.Value.IsSuccess)
+ {
+ _logger.LogError("Received an error from the Inject assembly that failed on initialization.");
+ _logger.LogResultError(handshakeResponse.InitializationResult);
+ }
+ }
+
+ // should be run only if !handshakeResponse.ClientRunning
+ // but will work correctly even if client is already running.
+ var clientRunResult = await connection.Connection.ContractRunClient(new RunClientRequest())
+ .WaitForAsync(DefaultStates.ResponseObtained, ct: stoppingToken);
+
+ if (!clientRunResult.IsDefined(out var clientRun))
+ {
+ _logger.LogResultError(clientRunResult);
+ _lifetime.StopApplication();
+ return;
+ }
+
+ if (clientRun.InitializationResult is null)
+ {
+ _logger.LogError("Huh, the client did not return a result?");
+ }
+
+ if (!(clientRun.BindingManagerResult?.IsSuccess ?? true))
+ {
+ _logger.LogError("Binding manager threw an error.");
+ _logger.LogResultError(clientRun.BindingManagerResult);
}
- if (handshakeResponse.InitializationErrorfulResult is not null
- && !handshakeResponse.InitializationErrorfulResult.Value.IsSuccess)
+ if (!(clientRun.InitializationResult?.IsSuccess ?? true))
{
- _logger.LogError("Received an error from the Inject assembly that failed on initialization.");
- _logger.LogResultError(handshakeResponse.InitializationErrorfulResult);
+ _logger.LogResultError(clientRun.InitializationResult);
}
- _logger.LogInformation
- (
- $"Connected to {handshakeResponse.CharacterName ?? "Not in game"} ({handshakeResponse.CharacterId?.ToString() ?? "Not in game"})"
- );
+ _logger.LogInformation($"Connected to NosTale");
var runResult = await connection.Connection.RunHandlerAsync(stoppingToken);
if (!runResult.IsSuccess)