// // 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 NosSmooth.Extensions.Combat.Errors; using NosSmooth.Extensions.Pathfinding; using NosSmooth.Extensions.Pathfinding.Errors; using NosSmooth.Game.Data.Entities; using NosSmooth.Game.Data.Info; 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 { /// public Result CanBeUsed(ICombatState combatState) { var character = combatState.Game.Character; if (character is null) { return new CharacterNotInitializedError(); } return character.CantMove ? CanBeUsedResponse.MustWait : CanBeUsedResponse.CanBeUsed; } /// public 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 GenericError("Entity's position is not initialized."); } 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 == position) { 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); } }