// // WalkInRangeOperation.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.Diagnostics; using NosSmooth.Extensions.Combat.Errors; using NosSmooth.Extensions.Pathfinding; using NosSmooth.Extensions.Pathfinding.Errors; using NosSmooth.Game.Data.Entities; using NosSmooth.Game.Data.Info; using NosSmooth.Game.Errors; using NosSmooth.Packets.Client.Inventory; using Remora.Results; namespace NosSmooth.Extensions.Combat.Operations; /// /// A combat operation that walks into a given range of an entity. /// /// The walk manager. /// The entity to walk to. /// The maximal distance from the entity. public record WalkInRangeOperation ( WalkManager WalkManager, IEntity Entity, float Distance ) : ICombatOperation { private Task? _walkInRangeOperation; private CancellationTokenSource? _ct; private bool _disposed; /// public OperationQueueType QueueType => OperationQueueType.TotalControl; /// public bool MayBeCancelled => true; /// public Task BeginExecution(ICombatState combatState, CancellationToken ct = default) { if (_walkInRangeOperation is not null) { return Task.FromResult(Result.FromSuccess()); } _ct = new CancellationTokenSource(); _walkInRangeOperation = Task.Run ( () => UseAsync(combatState, _ct.Token), _ct.Token ); return Task.FromResult(Result.FromSuccess()); } /// public async Task WaitForFinishedAsync(ICombatState combatState, CancellationToken ct = default) { if (IsFinished()) { return Result.FromSuccess(); } await BeginExecution(combatState, ct); if (_walkInRangeOperation is null) { throw new UnreachableException(); } try { return await _walkInRangeOperation; } catch (OperationCanceledException) { return Result.FromSuccess(); } catch (Exception e) { return e; } } /// public bool IsExecuting() => _walkInRangeOperation is not null && !IsFinished(); /// public bool IsFinished() => _walkInRangeOperation?.IsCompleted ?? false; /// public Result CanBeUsed(ICombatState combatState) { var character = combatState.Game.Character; if (character is null) { return new CharacterNotInitializedError(); } if (character.CantMove) { return new CannotBeUsedError(CanBeUsedResponse.MustWait, new CharacterCannotMoveError()); } return Result.FromSuccess(); } /// public void Cancel() { _ct?.Cancel(); } private async Task UseAsync(ICombatState combatState, CancellationToken ct = default) { var character = combatState.Game.Character; if (character is null) { return new CharacterNotInitializedError(); } var distance = Distance; while (distance >= 0) { var position = Entity.Position; if (position is null) { return new NotInitializedError("entity's position"); } var currentPosition = character.Position; if (currentPosition is null) { return new CharacterNotInitializedError("Position"); } if (Entity.Position?.DistanceSquared(currentPosition.Value) <= Distance * Distance) { return Result.FromSuccess(); } var closePosition = GetClosePosition(currentPosition.Value, position.Value, distance); if (closePosition == currentPosition) { return Result.FromSuccess(); } using var goToCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(ct); var walkResultTask = WalkManager.GoToAsync (closePosition.X, closePosition.Y, true, goToCancellationTokenSource.Token); while (!walkResultTask.IsCompleted) { await Task.Delay(5, ct); if (Entity.Position != position) { goToCancellationTokenSource.Cancel(); await walkResultTask; } } if (Entity.Position != position) { continue; } var walkResult = await walkResultTask; if ((character.Position - Entity.Position)?.DistanceSquared(Position.Zero) <= Distance * Distance) { return Result.FromSuccess(); } if (!walkResult.IsSuccess && walkResult.Error is PathNotFoundError) { if (distance - 1 > 0) { distance--; } else { distance = 0; } continue; } return walkResult; } return Result.FromSuccess(); } private Position GetClosePosition(Position start, Position target, double distance) { var diff = start - target; if (diff.DistanceSquared(Position.Zero) < distance * distance) { return start; } var diffLength = Math.Sqrt(diff.DistanceSquared(Position.Zero)); return target + ((distance / diffLength) * diff); } /// public void Dispose() { if (_disposed) { return; } _disposed = true; _ct?.Cancel(); _walkInRangeOperation?.Dispose(); _ct?.Dispose(); } }