A Extensions/NosSmooth.Extensions.Combat/Operations/CompoundOperation.cs => Extensions/NosSmooth.Extensions.Combat/Operations/CompoundOperation.cs +133 -0
@@ 0,0 1,133 @@
+//
+// CompoundOperation.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 Remora.Results;
+
+namespace NosSmooth.Extensions.Combat.Operations;
+
+/// <summary>
+/// An operation made from multiple operations
+/// that should execute right after each other.
+/// </summary>
+public class CompoundOperation : ICombatOperation
+{
+ private readonly ICombatOperation[] _operations;
+ private readonly OperationQueueType _queueType;
+ private Task<Result>? _compoundOperation;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CompoundOperation"/> class.
+ /// </summary>
+ /// <param name="operations">The operations to execute.</param>
+ /// <param name="queueType">The queue type.</param>
+ public CompoundOperation
+ (OperationQueueType queueType = OperationQueueType.TotalControl, params ICombatOperation[] operations)
+ {
+ if (operations.Length == 0)
+ {
+ throw new ArgumentNullException(nameof(operations), "The compound operation needs at least one operation.");
+ }
+
+ _operations = operations;
+ _queueType = queueType;
+ }
+
+ /// <inheritdoc />
+ public void Dispose()
+ {
+ foreach (var operation in _operations)
+ {
+ operation.Dispose();
+ }
+ }
+
+ /// <inheritdoc />
+ public OperationQueueType QueueType { get; }
+
+ /// <inheritdoc />
+ public Task<Result> BeginExecution(ICombatState combatState, CancellationToken ct = default)
+ {
+ if (_compoundOperation is not null)
+ {
+ return Task.FromResult(Result.FromSuccess());
+ }
+
+ _compoundOperation = Task.Run
+ (
+ () => UseAsync(combatState, ct),
+ ct
+ );
+ return Task.FromResult(Result.FromSuccess());
+ }
+
+ /// <inheritdoc />
+ public async Task<Result> WaitForFinishedAsync(ICombatState combatState, CancellationToken ct = default)
+ {
+ if (IsFinished())
+ {
+ return Result.FromSuccess();
+ }
+
+ await BeginExecution(combatState, ct);
+ if (_compoundOperation is null)
+ {
+ throw new UnreachableException();
+ }
+
+ return await _compoundOperation;
+ }
+
+ /// <inheritdoc />
+ public bool IsExecuting()
+ => _compoundOperation is not null && !IsFinished();
+
+ /// <inheritdoc />
+ public bool IsFinished()
+ => _compoundOperation?.IsCompleted ?? false;
+
+ /// <inheritdoc />
+ public Result<CanBeUsedResponse> CanBeUsed(ICombatState combatState)
+ => _operations[0].CanBeUsed(combatState);
+
+ private async Task<Result> UseAsync(ICombatState combatState, CancellationToken ct)
+ {
+ foreach (var operation in _operations)
+ {
+ CanBeUsedResponse canBeUsed = CanBeUsedResponse.MustWait;
+
+ while (canBeUsed != CanBeUsedResponse.CanBeUsed)
+ {
+ var canBeUsedResult = operation.CanBeUsed(combatState);
+ if (!canBeUsedResult.IsDefined(out canBeUsed))
+ {
+ return Result.FromError(canBeUsedResult);
+ }
+
+ if (canBeUsed == CanBeUsedResponse.WontBeUsable)
+ {
+ return new GenericError("Won't be usable.");
+ }
+
+ await Task.Delay(10, ct);
+ }
+
+ var result = await operation.BeginExecution(combatState, ct);
+ if (!result.IsSuccess)
+ {
+ return result;
+ }
+
+ result = await operation.WaitForFinishedAsync(combatState, ct);
+ if (!result.IsSuccess)
+ {
+ return result;
+ }
+ }
+
+ return Result.FromSuccess();
+ }
+}<
\ No newline at end of file
M Extensions/NosSmooth.Extensions.Combat/Techniques/SimpleAttackTechnique.cs => Extensions/NosSmooth.Extensions.Combat/Techniques/SimpleAttackTechnique.cs +33 -42
@@ 35,7 35,6 @@ public class SimpleAttackTechnique : ICombatTechnique
private readonly ISkillSelector _skillSelector;
private readonly IItemSelector _itemSelector;
- private Skill? _currentSkill;
private ILivingEntity? _target;
/// <summary>
@@ 119,41 118,34 @@ public class SimpleAttackTechnique : ICombatTechnique
return new CharacterNotInitializedError();
}
- if (_currentSkill is null)
+ var skills = state.Game.Skills;
+ if (skills is null)
{
- var skills = state.Game.Skills;
- if (skills is null)
- {
- return new CharacterNotInitializedError("Skills");
- }
+ return new CharacterNotInitializedError("Skills");
+ }
- var characterMp = character.Mp?.Amount ?? 0;
- var usableSkills = skills.AllSkills
- .Where
- (
- x => x.Info is not null && x.Info.HitType != HitType.AlliesInZone
- && x.Info.SkillType == SkillType.Player
- )
- .Where(x => !x.IsOnCooldown && characterMp >= (x.Info?.MpCost ?? long.MaxValue));
-
- var skillResult = _skillSelector.GetSelectedSkill(usableSkills);
- if (!skillResult.IsSuccess)
- {
- if (skillResult.Error is SkillNotFoundError)
- {
- return _target.Id;
- }
+ var characterMp = character.Mp?.Amount ?? 0;
+ var usableSkills = skills.AllSkills
+ .Where
+ (
+ x => x.Info is not null && x.Info.HitType != HitType.AlliesInZone
+ && x.Info.SkillType == SkillType.Player
+ )
+ .Where(x => !x.IsOnCooldown && characterMp >= (x.Info?.MpCost ?? long.MaxValue));
- return Result<long?>.FromError(skillResult);
+ var skillResult = _skillSelector.GetSelectedSkill(usableSkills);
+ if (!skillResult.IsDefined(out var currentSkill))
+ {
+ if (skillResult.Error is SkillNotFoundError)
+ {
+ return _target.Id;
}
- _currentSkill = skillResult.Entity;
+ return Result<long?>.FromError(skillResult);
}
- if (_currentSkill.Info is null)
+ if (currentSkill.Info is null)
{
- var currentSkill = _currentSkill;
- _currentSkill = null;
return new MissingInfoError("skill", currentSkill.SkillVNum);
}
@@ 167,23 159,22 @@ public class SimpleAttackTechnique : ICombatTechnique
return new EntityNotFoundError();
}
- var range = _currentSkill.Info.Range;
- if (_currentSkill.Info.TargetType == TargetType.Self && _currentSkill.Info.HitType == HitType.EnemiesInZone
- && _currentSkill.Info.AttackType != AttackType.Dash)
- {
- range = _currentSkill.Info.ZoneRange;
- }
-
- if (!character.Position.Value.IsInRange(_target.Position.Value, range))
- {
- state.WalkInRange(_walkManager, _target, range);
- }
- else
+ var range = currentSkill.Info.Range;
+ if (currentSkill.Info.TargetType == TargetType.Self && currentSkill.Info.HitType == HitType.EnemiesInZone
+ && currentSkill.Info.AttackType != AttackType.Dash)
{
- state.UseSkill(_skillsApi, _currentSkill, _target);
- _currentSkill = null;
+ range = currentSkill.Info.ZoneRange;
}
+ state.EnqueueOperation
+ (
+ new CompoundOperation
+ (
+ OperationQueueType.TotalControl,
+ new WalkInRangeOperation(_walkManager, _target, range),
+ new UseSkillOperation(_skillsApi, currentSkill, character, _target)
+ )
+ );
return _target.Id;
}