//
// 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.LocalBinding.Objects;
namespace NosSmooth.LocalClient.CommandHandlers.Walk;
///
/// The status for .
///
public class WalkStatus
{
private readonly CharacterBinding _characterBinding;
private readonly SemaphoreSlim _semaphore;
private CancellationTokenSource? _walkingCancellation;
private bool _userCanCancel;
private bool _walkHooked;
///
/// Initializes a new instance of the class.
///
/// The character binding.
public WalkStatus(CharacterBinding characterBinding)
{
_characterBinding = characterBinding;
_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)
{
_characterBinding.WalkCall += 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(ushort x, ushort y)
{
if (IsWalking)
{
if (!_userCanCancel)
{
return false;
}
CancelWalkingAsync(WalkCancelReason.UserWalked)
.GetAwaiter()
.GetResult();
}
return true;
}
}