// // WalkStatus.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.LocalClient.Hooks; namespace NosSmooth.LocalClient.CommandHandlers.Walk; /// /// The status for . /// public class WalkStatus { private readonly NostaleHookManager _hookManager; private readonly SemaphoreSlim _semaphore; private CancellationTokenSource? _walkingCancellation; private bool _userCanCancel; private bool _walkHooked; /// /// Initializes a new instance of the class. /// /// The hooking manager. public WalkStatus(NostaleHookManager hookManager) { _hookManager = hookManager; _semaphore = new SemaphoreSlim(1, 1); } /// /// Gets if the walk command is in progress. /// public bool IsWalking => _walkingCancellation is not null; /// /// Gets if the current walk command has been finished. /// public bool IsFinished { get; private set; } /// /// Gets the last time of walk. /// public DateTimeOffset LastWalkTime { get; private set; } /// /// Gets the walk target x coordinate. /// public int TargetX { get; private set; } /// /// Gets the walk target y coordinate. /// public int TargetY { get; private set; } /// /// Gets the characters current x coordinate. /// public int? CurrentX { get; private set; } /// /// Gets the characters current y coordinate. /// public int? CurrentY { get; private set; } /// /// Gets the error cause of cancellation. /// public WalkCancelReason? Error { get; private set; } /// /// Update the time of last walk, called on WalkPacket. /// /// The current characters x coordinate. /// The current characters y coordinate. internal void UpdateWalkTime(int currentX, int currentY) { CurrentX = currentX; CurrentY = currentY; LastWalkTime = DateTimeOffset.Now; } /// /// Sets that the walk command handler is handling walk command. /// /// The cancellation token source for cancelling the operation. /// The walk target x coordinate. /// The walk target y coordinate. /// Whether the user can cancel the operation by moving elsewhere. /// A task that may or may not have succeeded. internal async Task SetWalking(CancellationTokenSource cancellationTokenSource, int targetX, int targetY, bool userCanCancel) { await _semaphore.WaitAsync(cancellationTokenSource.Token); if (IsWalking) { // Cannot call CancelWalkingAsync as that would result in a deadlock _walkingCancellation?.Cancel(); _walkingCancellation = null; } IsFinished = false; Error = null; TargetX = targetX; TargetY = targetY; CurrentX = CurrentY = null; _walkingCancellation = cancellationTokenSource; LastWalkTime = DateTime.Now; _userCanCancel = userCanCancel; if (!_walkHooked) { _hookManager.ClientWalked += OnCharacterWalked; _walkHooked = true; } _semaphore.Release(); } /// /// Cancel the walking token. /// /// The cause. /// The cancellation token for cancelling the operation. /// A task that may or may not have succeeded. internal async Task CancelWalkingAsync(WalkCancelReason? error = null, CancellationToken ct = default) { await _semaphore.WaitAsync(ct); if (!IsWalking) { _semaphore.Release(); return; } Error = error; try { _walkingCancellation?.Cancel(); } catch { // ignored } _walkingCancellation = null; _semaphore.Release(); } /// /// Finish the walk successfully. /// /// The cancellation token for cancelling the operation. /// A task that may or may not have succeeded. internal async Task FinishWalkingAsync(CancellationToken ct = default) { await _semaphore.WaitAsync(ct); IsFinished = true; _semaphore.Release(); await CancelWalkingAsync(ct: ct); } private bool OnCharacterWalked(WalkEventArgs walkEventArgs) { if (IsWalking) { if (!_userCanCancel) { return false; } CancelWalkingAsync(WalkCancelReason.UserWalked) .GetAwaiter() .GetResult(); } return true; } }