~ruther/NosSmooth

2cebbe162d835dc098237a3ce08bccb4173a7c06 — František Boháček 3 years ago db9c595
feat(samples): add sample with loading data from a file
M NosSmooth.sln => NosSmooth.sln +15 -0
@@ 44,6 44,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataBrowser", "Samples\Data
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NosSmooth.Game", "Core\NosSmooth.Game\NosSmooth.Game.csproj", "{7C9C7375-6FC0-4704-9332-1F74CDF41D11}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileClient", "Samples\FileClient\FileClient.csproj", "{D33E1AC5-8946-4D6F-A6D3-D81F98E4F86B}"
EndProject
Global
	GlobalSection(SolutionConfigurationPlatforms) = preSolution
		Debug|Any CPU = Debug|Any CPU


@@ 210,6 212,18 @@ Global
		{055C66A7-640C-49BB-81A7-28E630F51C37}.Release|x64.Build.0 = Release|Any CPU
		{055C66A7-640C-49BB-81A7-28E630F51C37}.Release|x86.ActiveCfg = Release|Any CPU
		{055C66A7-640C-49BB-81A7-28E630F51C37}.Release|x86.Build.0 = Release|Any CPU
		{D33E1AC5-8946-4D6F-A6D3-D81F98E4F86B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{D33E1AC5-8946-4D6F-A6D3-D81F98E4F86B}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{D33E1AC5-8946-4D6F-A6D3-D81F98E4F86B}.Debug|x64.ActiveCfg = Debug|Any CPU
		{D33E1AC5-8946-4D6F-A6D3-D81F98E4F86B}.Debug|x64.Build.0 = Debug|Any CPU
		{D33E1AC5-8946-4D6F-A6D3-D81F98E4F86B}.Debug|x86.ActiveCfg = Debug|Any CPU
		{D33E1AC5-8946-4D6F-A6D3-D81F98E4F86B}.Debug|x86.Build.0 = Debug|Any CPU
		{D33E1AC5-8946-4D6F-A6D3-D81F98E4F86B}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{D33E1AC5-8946-4D6F-A6D3-D81F98E4F86B}.Release|Any CPU.Build.0 = Release|Any CPU
		{D33E1AC5-8946-4D6F-A6D3-D81F98E4F86B}.Release|x64.ActiveCfg = Release|Any CPU
		{D33E1AC5-8946-4D6F-A6D3-D81F98E4F86B}.Release|x64.Build.0 = Release|Any CPU
		{D33E1AC5-8946-4D6F-A6D3-D81F98E4F86B}.Release|x86.ActiveCfg = Release|Any CPU
		{D33E1AC5-8946-4D6F-A6D3-D81F98E4F86B}.Release|x86.Build.0 = Release|Any CPU
	EndGlobalSection
	GlobalSection(SolutionProperties) = preSolution
		HideSolutionNode = FALSE


@@ 228,6 242,7 @@ Global
		{F1884ADF-6412-4E9B-81FD-357DC5761ADF} = {1C785A74-19B9-42D2-93B1-F4EC9D6A8CFD}
		{7C9C7375-6FC0-4704-9332-1F74CDF41D11} = {01B5E872-271F-4D30-A1AA-AD48D81840C5}
		{055C66A7-640C-49BB-81A7-28E630F51C37} = {99E72557-BCE9-496A-B49C-79537B0E6063}
		{D33E1AC5-8946-4D6F-A6D3-D81F98E4F86B} = {99E72557-BCE9-496A-B49C-79537B0E6063}
	EndGlobalSection
	GlobalSection(ExtensibilityGlobals) = postSolution
		SolutionGuid = {C5F46653-4DEC-429B-8580-4ED18ED9B4CA}

M Packets/NosSmooth.Packets/Server/Maps/OutPacket.cs => Packets/NosSmooth.Packets/Server/Maps/OutPacket.cs +1 -1
@@ 14,7 14,7 @@ namespace NosSmooth.Packets.Server.Maps;
/// </summary>
/// <param name="EntityType">The entity type.</param>
/// <param name="EntityId">The entity id.</param>
[PacketHeader("c_map", PacketSource.Server)]
[PacketHeader("out", PacketSource.Server)]
[GenerateSerializer(true)]
public record OutPacket
(

A Samples/FileClient/App.cs => Samples/FileClient/App.cs +77 -0
@@ 0,0 1,77 @@
//
//  App.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 Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using NosSmooth.Core.Client;
using NosSmooth.Core.Extensions;
using NosSmooth.Data.NOSFiles;
using NosSmooth.Packets.Extensions;
using NosSmooth.Packets.Packets;
using Remora.Results;

namespace FileClient;

/// <summary>
/// The application.
/// </summary>
public class App : BackgroundService
{
    private readonly INostaleClient _client;
    private readonly IPacketTypesRepository _packetRepository;
    private readonly NostaleDataFilesManager _filesManager;
    private readonly ILogger<App> _logger;
    private readonly IHostLifetime _lifetime;

    /// <summary>
    /// Initializes a new instance of the <see cref="App"/> class.
    /// </summary>
    /// <param name="client">The client.</param>
    /// <param name="packetRepository">The packet repository.</param>
    /// <param name="filesManager">The file manager.</param>
    /// <param name="logger">The logger.</param>
    /// <param name="lifetime">The lifetime.</param>
    public App
    (
        INostaleClient client,
        IPacketTypesRepository packetRepository,
        NostaleDataFilesManager filesManager,
        ILogger<App> logger,
        IHostLifetime lifetime
    )
    {
        _client = client;
        _packetRepository = packetRepository;
        _filesManager = filesManager;
        _logger = logger;
        _lifetime = lifetime;
    }

    /// <inheritdoc />
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        var packetResult = _packetRepository.AddDefaultPackets();
        if (!packetResult.IsSuccess)
        {
            _logger.LogResultError(packetResult);
            return;
        }

        var filesResult = _filesManager.Initialize();
        if (!filesResult.IsSuccess)
        {
            _logger.LogResultError(filesResult);
            return;
        }

        var runResult = await _client.RunAsync(stoppingToken);
        if (!runResult.IsSuccess)
        {
            _logger.LogResultError(runResult);
            await _lifetime.StopAsync(default);
        }
    }
}
\ No newline at end of file

A Samples/FileClient/Client.cs => Samples/FileClient/Client.cs +126 -0
@@ 0,0 1,126 @@
//
//  Client.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.Text.RegularExpressions;
using Microsoft.Extensions.Logging;
using NosSmooth.Core.Client;
using NosSmooth.Core.Commands;
using NosSmooth.Core.Extensions;
using NosSmooth.Core.Packets;
using NosSmooth.Packets;
using NosSmooth.Packets.Errors;
using NosSmooth.PacketSerializer.Abstractions.Attributes;
using Remora.Results;

namespace FileClient;

/// <summary>
/// A NosTale client using stream to read lines.
/// </summary>
public class Client : BaseNostaleClient
{
    private const string LineRegex = ".*\\[(Recv|Send)\\]\t(.*)";
    private readonly IPacketHandler _packetHandler;
    private readonly IPacketSerializer _packetSerializer;
    private readonly ILogger<Client> _logger;
    private readonly Stream _stream;

    /// <summary>
    /// Initializes a new instance of the <see cref="Client"/> class.
    /// </summary>
    /// <param name="stream">The stream with packets.</param>
    /// <param name="packetHandler">The packet handler.</param>
    /// <param name="commandProcessor">The command processor.</param>
    /// <param name="packetSerializer">The packet serializer.</param>
    /// <param name="logger">The logger.</param>
    public Client(
        Stream stream,
        IPacketHandler packetHandler,
        CommandProcessor commandProcessor,
        IPacketSerializer packetSerializer,
        ILogger<Client> logger
    )
        : base(commandProcessor, packetSerializer)
    {
        _stream = stream;
        _packetHandler = packetHandler;
        _packetSerializer = packetSerializer;
        _logger = logger;
    }

    /// <inheritdoc />
    public override async Task<Result> RunAsync(CancellationToken stopRequested = default)
    {
        using var reader = new StreamReader(_stream);
        var regex = new Regex(LineRegex);
        while (!reader.EndOfStream)
        {
            stopRequested.ThrowIfCancellationRequested();
            var line = await reader.ReadLineAsync();
            if (line is null)
            {
                continue;
            }

            var match = regex.Match(line);
            if (!match.Success)
            {
                _logger.LogError("Could not find match on line {Line}", line);
                continue;
            }

            var type = match.Groups[1].Value;
            var packetStr = match.Groups[2].Value;

            var source = type == "Recv" ? PacketSource.Server : PacketSource.Client;
            var packet = CreatePacket(packetStr, source);
            Result result;
            if (source == PacketSource.Client)
            {
                result = await _packetHandler.HandleSentPacketAsync(packet, packetStr, stopRequested);
            }
            else
            {
                result = await _packetHandler.HandleReceivedPacketAsync(packet, packetStr, stopRequested);
            }

            if (!result.IsSuccess)
            {
                _logger.LogResultError(result);
            }
        }

        return Result.FromSuccess();
    }

    /// <inheritdoc/>
    public override async Task<Result> SendPacketAsync(string packetString, CancellationToken ct = default)
    {
        return await _packetHandler.HandleReceivedPacketAsync(CreatePacket(packetString, PacketSource.Client), packetString, ct);
    }

    /// <inheritdoc/>
    public override async Task<Result> ReceivePacketAsync(string packetString, CancellationToken ct = default)
    {
        return await _packetHandler.HandleReceivedPacketAsync(CreatePacket(packetString, PacketSource.Server), packetString, ct);
    }

    private IPacket CreatePacket(string packetStr, PacketSource source)
    {
        var packetResult = _packetSerializer.Deserialize(packetStr, source);
        if (!packetResult.IsSuccess)
        {
            if (packetResult.Error is PacketConverterNotFoundError err)
            {
                return new UnresolvedPacket(err.Header, packetStr);
            }

            return new ParsingFailedPacket(packetResult, packetStr);
        }

        return packetResult.Entity;
    }
}
\ No newline at end of file

A Samples/FileClient/FileClient.csproj => Samples/FileClient/FileClient.csproj +27 -0
@@ 0,0 1,27 @@
<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net6.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
    </PropertyGroup>

    <ItemGroup>
      <Folder Include="Handlers" />
    </ItemGroup>

    <ItemGroup>
      <ProjectReference Include="..\..\Core\NosSmooth.Core\NosSmooth.Core.csproj" />
      <ProjectReference Include="..\..\Core\NosSmooth.Game\NosSmooth.Game.csproj" />
      <ProjectReference Include="..\..\Data\NosSmooth.Data.Abstractions\NosSmooth.Data.Abstractions.csproj" />
      <ProjectReference Include="..\..\Data\NosSmooth.Data.NOSFiles\NosSmooth.Data.NOSFiles.csproj" />
    </ItemGroup>

    <ItemGroup>
      <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
      <PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.0" />
      <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0" />
    </ItemGroup>

</Project>

A Samples/FileClient/Program.cs => Samples/FileClient/Program.cs +66 -0
@@ 0,0 1,66 @@
//
//  Program.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 Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using NosSmooth.Core.Client;
using NosSmooth.Core.Commands;
using NosSmooth.Core.Extensions;
using NosSmooth.Core.Packets;
using NosSmooth.Data.Abstractions.Language;
using NosSmooth.Data.NOSFiles.Extensions;
using NosSmooth.Data.NOSFiles.Options;
using NosSmooth.Game.Extensions;
using NosSmooth.Packets;

namespace FileClient;

/// <summary>
/// An entrypoint class.
/// </summary>
public static class Program
{
    // TODO: create console hosting.

    /// <summary>
    /// An entrypoint method.
    /// </summary>
    /// <param name="args">The command line arguments.</param>
    /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
    public static async Task Main(string[] args)
    {
        await using FileStream stream = File.OpenRead(string.Join(' ', args));
        await CreateHost(stream).StartAsync();
    }

    private static IHost CreateHost(Stream fileStream)
    {
        return Host.CreateDefaultBuilder()
            .ConfigureServices(coll =>
            {
                coll.AddHostedService<App>();

                coll.AddNostaleCore()
                    .AddNostaleGame()
                    .AddNostaleDataFiles()
                    .Configure<LanguageServiceOptions>(o => o.Language = Language.Cz)
                    .Configure<NostaleDataOptions>(o => o.SupportedLanguages = new[]
                    {
                        Language.Cz
                    });
                coll.AddSingleton<INostaleClient>(p => new Client(
                    fileStream,
                    p.GetRequiredService<IPacketHandler>(),
                    p.GetRequiredService<CommandProcessor>(),
                    p.GetRequiredService<IPacketSerializer>(),
                    p.GetRequiredService<ILogger<Client>>()
                ));
            })
            .UseConsoleLifetime()
            .Build();
    }
}
\ No newline at end of file

M Tests/NosSmooth.Packets.Tests/Converters/Packets/InPacketConverterTests.cs => Tests/NosSmooth.Packets.Tests/Converters/Packets/InPacketConverterTests.cs +3 -2
@@ 11,6 11,7 @@ using NosSmooth.Packets.Enums.Entities;
using NosSmooth.Packets.Enums.Players;
using NosSmooth.Packets.Extensions;
using NosSmooth.Packets.Server.Entities;
using NosSmooth.Packets.Server.Maps;
using NosSmooth.Packets.Server.Players;
using NosSmooth.Packets.Server.Weapons;
using NosSmooth.PacketSerializer.Abstractions.Attributes;


@@ 90,7 91,7 @@ public class InPacketConverterTests
                new UpgradeRareSubPacket(10, 8),
                new FamilySubPacket(null, null),
                null,
                "26",
                26,
                false,
                0,
                0,


@@ 161,7 162,7 @@ public class InPacketConverterTests
                new UpgradeRareSubPacket(10, 8),
                new FamilySubPacket("-1", null),
                null,
                "26",
                26,
                false,
                0,
                0,

Do not follow this link