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>());