~ruther/NosSmooth

6387c54d508d29708e8c81ec1c2fa03bfe7c7d7c — Rutherther 3 years ago c5dffa0
feat(localclient): add pet walk handler
M Local/NosSmooth.LocalBinding/Structs/ControlManager.cs => Local/NosSmooth.LocalBinding/Structs/ControlManager.cs +5 -0
@@ 24,6 24,11 @@ public abstract class ControlManager : NostaleObject
    }

    /// <summary>
    /// Gets the entity this control manager is for.
    /// </summary>
    public abstract MapBaseObj Entity { get; }

    /// <summary>
    /// Gets the current player position x coordinate.
    /// </summary>
    public int X

M Local/NosSmooth.LocalBinding/Structs/PetManager.cs => Local/NosSmooth.LocalBinding/Structs/PetManager.cs +3 -0
@@ 45,4 45,7 @@ public class PetManager : ControlManager
            return new MapNpcObj(Memory, (IntPtr)playerAddress);
        }
    }

    /// <inheritdoc />
    public override MapBaseObj Entity => Pet;
}
\ No newline at end of file

M Local/NosSmooth.LocalBinding/Structs/PlayerManager.cs => Local/NosSmooth.LocalBinding/Structs/PlayerManager.cs +3 -0
@@ 88,4 88,7 @@ public class PlayerManager : ControlManager
            return playerId;
        }
    }

    /// <inheritdoc />
    public override MapBaseObj Entity => Player;
}
\ No newline at end of file

A Local/NosSmooth.LocalClient/CommandHandlers/Walk/ControlCommandWalkHandler.cs => Local/NosSmooth.LocalClient/CommandHandlers/Walk/ControlCommandWalkHandler.cs +145 -0
@@ 0,0 1,145 @@
//
//  ControlCommandWalkHandler.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.Core.Client;
using NosSmooth.Core.Commands.Control;
using NosSmooth.Core.Commands.Walking;
using NosSmooth.Core.Extensions;
using NosSmooth.LocalBinding.Structs;
using NosSmooth.LocalClient.CommandHandlers.Walk.Errors;
using Remora.Results;

namespace NosSmooth.LocalClient.CommandHandlers.Walk;

/// <summary>
/// Handler for control manager walk command.
/// </summary>
internal class ControlCommandWalkHandler
{
    private readonly INostaleClient _nostaleClient;
    private readonly Func<ushort, ushort, Result<bool>> _walkFunction;
    private readonly ControlManager _controlManager;
    private readonly WalkCommandHandlerOptions _options;

    private ushort _x;
    private ushort _y;

    /// <summary>
    /// Initializes a new instance of the <see cref="ControlCommandWalkHandler"/> class.
    /// </summary>
    /// <param name="nostaleClient">The nostale client.</param>
    /// <param name="walkFunction">The walk function.</param>
    /// <param name="controlManager">The control manager.</param>
    /// <param name="options">The options.</param>
    public ControlCommandWalkHandler
    (
        INostaleClient nostaleClient,
        Func<ushort, ushort, Result<bool>> walkFunction,
        ControlManager controlManager,
        WalkCommandHandlerOptions options
    )
    {
        _nostaleClient = nostaleClient;
        _walkFunction = walkFunction;
        _controlManager = controlManager;
        _options = options;
    }

    /// <summary>
    /// Handle walk take control command.
    /// </summary>
    /// <param name="x">The x coordinate.</param>
    /// <param name="y">The y coordinate.</param>
    /// <param name="command">The take control command.</param>
    /// <param name="groupName">The name of the take control group.</param>
    /// <param name="ct">The cancellation token for cancelling the operation.</param>
    /// <returns>A <see cref="Task{TResult}"/> representing the result of the asynchronous operation.</returns>
    public async Task<Result> HandleCommand(ushort x, ushort y, ITakeControlCommand command, string groupName, CancellationToken ct = default)
    {
        _x = x;
        _y = y;

        using CancellationTokenSource linked = CancellationTokenSource.CreateLinkedTokenSource(ct);
        WalkUnfinishedReason? reason = null;
        var takeControlCommand = command.CreateTakeControl
        (
            groupName,
            WalkGrantedCallback,
            (r) =>
            {
                reason = r switch
                {
                    ControlCancelReason.AnotherTask => WalkUnfinishedReason.AnotherTask,
                    ControlCancelReason.UserAction => WalkUnfinishedReason.UserAction,
                    _ => WalkUnfinishedReason.Unknown
                };
                return Task.FromResult(Result.FromSuccess());
            }
        );

        var commandResult = await _nostaleClient.SendCommandAsync(takeControlCommand, ct);
        if (!commandResult.IsSuccess)
        {
            return commandResult;
        }

        if (reason is null && !IsAt(x, y))
        {
            reason = WalkUnfinishedReason.PathNotFound;
        }

        if (reason is null)
        {
            return Result.FromSuccess();
        }

        return new WalkNotFinishedError
        (
            _controlManager.X,
            _controlManager.Y,
            (WalkUnfinishedReason)reason
        );
    }

    private bool IsAtTarget()
    {
        return _controlManager.TargetX == _controlManager.Entity.X
            && _controlManager.TargetY == _controlManager.Entity.Y;
    }

    private bool IsAt(ushort x, ushort y)
    {
        return _controlManager.Entity.X == x && _controlManager.Entity.Y == y;
    }

    private async Task<Result> WalkGrantedCallback(CancellationToken ct)
    {
        var result = _walkFunction(_x, _y);
        if (!result.IsSuccess)
        {
            return Result.FromError(result);
        }

        while (!ct.IsCancellationRequested)
        {
            try
            {
                await Task.Delay(_options.CheckDelay, ct);
            }
            catch
            {
                // ignored
            }

            if (IsAtTarget() || IsAt(_x, _y))
            {
                return Result.FromSuccess();
            }
        }

        return Result.FromSuccess(); // cancellation is handled in cancellation callback.
    }
}
\ No newline at end of file

A Local/NosSmooth.LocalClient/CommandHandlers/Walk/PetWalkCommandHandler.cs => Local/NosSmooth.LocalClient/CommandHandlers/Walk/PetWalkCommandHandler.cs +75 -0
@@ 0,0 1,75 @@
//
//  PetWalkCommandHandler.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.Options;
using NosSmooth.Core.Client;
using NosSmooth.Core.Commands;
using NosSmooth.Core.Commands.Control;
using NosSmooth.Core.Commands.Walking;
using NosSmooth.LocalBinding.Objects;
using Remora.Results;

namespace NosSmooth.LocalClient.CommandHandlers.Walk;

/// <summary>
/// Handles <see cref="PetWalkCommand"/>.
/// </summary>
public class PetWalkCommandHandler : ICommandHandler<PetWalkCommand>
{
    /// <summary>
    /// Group that is used for <see cref="TakeControlCommand"/>.
    /// </summary>
    public const string PetWalkControlGroup = "PetWalk";

    private readonly PetManagerBinding _petManagerBinding;
    private readonly INostaleClient _nostaleClient;
    private readonly WalkCommandHandlerOptions _options;

    /// <summary>
    /// Initializes a new instance of the <see cref="PetWalkCommandHandler"/> class.
    /// </summary>
    /// <param name="petManagerBinding">The character object binding.</param>
    /// <param name="nostaleClient">The nostale client.</param>
    /// <param name="options">The options.</param>
    public PetWalkCommandHandler
    (
        PetManagerBinding petManagerBinding,
        INostaleClient nostaleClient,
        IOptions<WalkCommandHandlerOptions> options
    )
    {
        _options = options.Value;
        _petManagerBinding = petManagerBinding;
        _nostaleClient = nostaleClient;
    }

    /// <inheritdoc/>
    public async Task<Result> HandleCommand(PetWalkCommand command, CancellationToken ct = default)
    {
        if (_petManagerBinding.PetManagerList.Length < command.PetSelector + 1)
        {
            return new NotFoundError("Could not find the pet using the given selector.");
        }
        var petManager = _petManagerBinding.PetManagerList[command.PetSelector];

        var handler = new ControlCommandWalkHandler
        (
            _nostaleClient,
            (x, y) => _petManagerBinding.PetWalk(command.PetSelector, x, y),
            petManager,
            _options
        );

        return await handler.HandleCommand
        (
            command.TargetX,
            command.TargetY,
            command,
            PetWalkControlGroup + "_" + command.PetSelector,
            ct
        );
    }
}
\ No newline at end of file

M Local/NosSmooth.LocalClient/CommandHandlers/Walk/PlayerWalkCommandHandler.cs => Local/NosSmooth.LocalClient/CommandHandlers/Walk/PlayerWalkCommandHandler.cs +11 -84
@@ 30,9 30,6 @@ public class PlayerWalkCommandHandler : ICommandHandler<PlayerWalkCommand>
    private readonly INostaleClient _nostaleClient;
    private readonly WalkCommandHandlerOptions _options;

    private ushort _x;
    private ushort _y;

    /// <summary>
    /// Initializes a new instance of the <see cref="PlayerWalkCommandHandler"/> class.
    /// </summary>


@@ 52,93 49,23 @@ public class PlayerWalkCommandHandler : ICommandHandler<PlayerWalkCommand>
    }

    /// <inheritdoc/>
    /// 1) If client called walk, cancel.
    /// 2) If another walk command requested, cancel.
    /// 3) If at the correct spot, cancel.
    /// 4) If not walking for over x ms, cancel.
    public async Task<Result> HandleCommand(PlayerWalkCommand command, CancellationToken ct = default)
    {
        _x = command.TargetX;
        _y = command.TargetY;

        using CancellationTokenSource linked = CancellationTokenSource.CreateLinkedTokenSource(ct);
        WalkUnfinishedReason? reason = null;
        var takeControlCommand = command.CreateTakeControl
        var handler = new ControlCommandWalkHandler
        (
            PlayerWalkControlGroup,
            WalkGrantedCallback,
            (r) =>
            {
                reason = r switch
                {
                    ControlCancelReason.AnotherTask => WalkUnfinishedReason.AnotherTask,
                    ControlCancelReason.UserAction => WalkUnfinishedReason.UserAction,
                    _ => WalkUnfinishedReason.Unknown
                };
                return Task.FromResult(Result.FromSuccess());
            }
            _nostaleClient,
            (x, y) => _playerManagerBinding.Walk(x, y),
            _playerManagerBinding.PlayerManager,
            _options
        );

        var commandResult = await _nostaleClient.SendCommandAsync(takeControlCommand, ct);
        if (!commandResult.IsSuccess)
        {
            return commandResult;
        }

        if (reason is null && !IsAt(command.TargetX, command.TargetY))
        {
            reason = WalkUnfinishedReason.PathNotFound;
        }

        if (reason is null)
        {
            return Result.FromSuccess();
        }

        return new WalkNotFinishedError
        return await handler.HandleCommand
        (
            _playerManagerBinding.PlayerManager.X,
            _playerManagerBinding.PlayerManager.Y,
            (WalkUnfinishedReason)reason
            command.TargetX,
            command.TargetY,
            command,
            PlayerWalkControlGroup,
            ct
        );
    }

    private bool IsAtTarget()
    {
        return _playerManagerBinding.PlayerManager.TargetX == _playerManagerBinding.PlayerManager.Player.X
            && _playerManagerBinding.PlayerManager.TargetY == _playerManagerBinding.PlayerManager.Player.Y;
    }

    private bool IsAt(ushort x, ushort y)
    {
        return _playerManagerBinding.PlayerManager.Player.X == x && _playerManagerBinding.PlayerManager.Player.Y == y;
    }

    private async Task<Result> WalkGrantedCallback(CancellationToken ct)
    {
        while (!ct.IsCancellationRequested)
        {
            var result = _playerManagerBinding.Walk(_x, _y);
            if (!result.IsSuccess)
            {
                return Result.FromError(result);
            }

            try
            {
                await Task.Delay(_options.CheckDelay, ct);
            }
            catch
            {
                // ignored
            }

            if (IsAtTarget() || IsAt(_x, _y))
            {
                return Result.FromSuccess();
            }
        }

        return Result.FromSuccess(); // cancellation is handled in cancellation callback.
    }
}
\ No newline at end of file

M Local/NosSmooth.LocalClient/Extensions/ServiceCollectionExtensions.cs => Local/NosSmooth.LocalClient/Extensions/ServiceCollectionExtensions.cs +2 -1
@@ 29,7 29,8 @@ public static class ServiceCollectionExtensions
        serviceCollection.AddNostaleBindings();
        serviceCollection
            .AddTakeControlCommand()
            .AddCommandHandler<PlayerWalkCommandHandler>();
            .AddCommandHandler<PlayerWalkCommandHandler>()
            .AddCommandHandler<PetWalkCommandHandler>();
        serviceCollection.TryAddSingleton<NostaleLocalClient>();
        serviceCollection.TryAddSingleton<INostaleClient>(p => p.GetRequiredService<NostaleLocalClient>());


Do not follow this link