~ruther/NosSmooth

c5234a389e51f0fca6b49cbaf04aef9d91c1c5d4 — Rutherther 2 years ago 4168acb
feat(combat): add compound operation to simplify using skills
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;
    }


Do not follow this link