~ruther/NosSmooth

792efeba255aec3fede9ead710ec67dce00dea3b — Rutherther 3 years ago 9326822
feat(combat): add combat manager
A Extensions/NosSmooth.Extensions.Combat/CombatManager.cs => Extensions/NosSmooth.Extensions.Combat/CombatManager.cs +149 -0
@@ 0,0 1,149 @@
//
//  CombatManager.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.Core.Client;
using NosSmooth.Core.Stateful;
using NosSmooth.Extensions.Combat.Errors;
using NosSmooth.Extensions.Combat.Operations;
using NosSmooth.Extensions.Combat.Techniques;
using Remora.Results;

namespace NosSmooth.Extensions.Combat;

/// <summary>
/// The combat manager that uses techniques to attack enemies.
/// </summary>
public class CombatManager : IStatefulEntity
{
    private readonly List<CancellationTokenSource> _tokenSource;
    private readonly Semaphore _semaphore;
    private readonly INostaleClient _client;
    private readonly Game.Game _game;

    /// <summary>
    /// Initializes a new instance of the <see cref="CombatManager"/> class.
    /// </summary>
    /// <param name="client">The NosTale client.</param>
    /// <param name="game">The game.</param>
    public CombatManager(INostaleClient client, Game.Game game)
    {
        _semaphore = new Semaphore(1, 1);
        _tokenSource = new List<CancellationTokenSource>();
        _client = client;
        _game = game;
    }

    /// <summary>
    /// Enter into a combat state using the given technique.
    /// </summary>
    /// <param name="technique">The technique to use.</param>
    /// <returns>A result that may or may not succeed.</returns>
    public async Task<Result> EnterCombat(ICombatTechnique technique)
    {
        var combatState = new CombatState(_client, _game, this);

        while (!combatState.ShouldQuit)
        {
            if (!technique.ShouldContinue(combatState))
            {
                combatState.QuitCombat();
                continue;
            }

            var operation = combatState.NextOperation();

            if (operation is null)
            { // The operation is null and the step has to be obtained from the technique.
                var stepResult = technique.HandleCombatStep(combatState);
                if (!stepResult.IsSuccess)
                {
                    return stepResult;
                }

                operation = combatState.NextOperation();
            }

            if (operation is null)
            { // The operation could be null just because there is currently not a skill to be used etc.
                await Task.Delay(5);
                continue;
            }

            Result<CanBeUsedResponse> responseResult;
            while ((responseResult = operation.CanBeUsed(combatState)).IsSuccess
                && responseResult.Entity == CanBeUsedResponse.MustWait)
            { // TODO: wait for just some amount of time
                await Task.Delay(5);
            }

            if (!responseResult.IsSuccess)
            {
                return Result.FromError(responseResult);
            }

            if (responseResult.Entity == CanBeUsedResponse.WontBeUsable)
            {
                return new UnusableOperationError(operation);
            }

            var usageResult = await operation.UseAsync(combatState);
            if (!usageResult.IsSuccess)
            {
                var errorHandleResult = technique.HandleError(combatState, operation, usageResult);
                if (!errorHandleResult.IsSuccess)
                {
                    return errorHandleResult;
                }
            }
        }

        return Result.FromSuccess();
    }

    /// <summary>
    /// Register the given cancellation token source to be cancelled on skill use/cancel.
    /// </summary>
    /// <param name="tokenSource">The token source to register.</param>
    public void RegisterSkillCancellationToken(CancellationTokenSource tokenSource)
    {
        _semaphore.WaitOne();
        _tokenSource.Add(tokenSource);
        _semaphore.Release();
    }

    /// <summary>
    /// Unregister the given cancellation token registered using <see cref="RegisterSkillCancellationToken"/>.
    /// </summary>
    /// <param name="tokenSource">The token source to unregister.</param>
    public void UnregisterSkillCancellationToken(CancellationTokenSource tokenSource)
    {
        _semaphore.WaitOne();
        _tokenSource.Remove(tokenSource);
        _semaphore.Release();
    }

    /// <summary>
    /// Cancel all of the skill tokens.
    /// </summary>
    internal void CancelSkillTokens()
    {
        _semaphore.WaitOne();
        foreach (var tokenSource in _tokenSource)
        {
            try
            {
                tokenSource.Cancel();
            }
            catch
            {
                // ignored
            }
        }

        _tokenSource.Clear();
        _semaphore.Release();
    }
}
\ No newline at end of file

A Extensions/NosSmooth.Extensions.Combat/CombatState.cs => Extensions/NosSmooth.Extensions.Combat/CombatState.cs +107 -0
@@ 0,0 1,107 @@
//
//  CombatState.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.Xml;
using NosSmooth.Core.Client;
using NosSmooth.Extensions.Combat.Operations;
using NosSmooth.Game.Data.Entities;

namespace NosSmooth.Extensions.Combat;

/// <inheritdoc />
internal class CombatState : ICombatState
{
    private readonly LinkedList<ICombatOperation> _operations;
    private ICombatOperation? _currentOperation;

    /// <summary>
    /// Initializes a new instance of the <see cref="CombatState"/> class.
    /// </summary>
    /// <param name="client">The NosTale client.</param>
    /// <param name="game">The game.</param>
    /// <param name="combatManager">The combat manager.</param>
    public CombatState(INostaleClient client, Game.Game game, CombatManager combatManager)
    {
        Client = client;
        Game = game;
        CombatManager = combatManager;
        _operations = new LinkedList<ICombatOperation>();
    }

    /// <summary>
    /// Gets whether the combat state should be quit.
    /// </summary>
    public bool ShouldQuit { get; private set; }

    /// <inheritdoc/>
    public CombatManager CombatManager { get; }
    
    /// <inheritdoc/>
    public Game.Game Game { get; }

    /// <inheritdoc/>
    public INostaleClient Client { get; }

    /// <inheritdoc/>
    public void QuitCombat()
    {
        ShouldQuit = true;
    }

    /// <summary>
    /// Make a step in the queue.
    /// </summary>
    /// <returns>The current operation, if any.</returns>
    public ICombatOperation? NextOperation()
    {
        var operation = _currentOperation = _operations.First?.Value;
        if (operation is not null)
        {
            _operations.RemoveFirst();
        }

        return operation;
    }

    /// <inheritdoc/>
    public void SetCurrentOperation
        (ICombatOperation operation, bool emptyQueue = false, bool prependCurrentOperationToQueue = false)
    {
        var current = _currentOperation;
        _currentOperation = operation;

        if (emptyQueue)
        {
            _operations.Clear();
        }

        if (prependCurrentOperationToQueue && current is not null)
        {
            _operations.AddFirst(current);
        }
    }

    /// <inheritdoc/>
    public void EnqueueOperation(ICombatOperation operation)
    {
        _operations.AddLast(operation);
    }

    /// <inheritdoc/>
    public void RemoveOperations(Func<ICombatOperation, bool> filter)
    {
        var node = _operations.First;
        while (node != null)
        {
            var next = node.Next;
            if (filter(node.Value))
            {
                _operations.Remove(node);
            }
            node = next;
        }
    }
}
\ No newline at end of file

A Extensions/NosSmooth.Extensions.Combat/Extensions/ServiceCollectionExtensions.cs => Extensions/NosSmooth.Extensions.Combat/Extensions/ServiceCollectionExtensions.cs +30 -0
@@ 0,0 1,30 @@
//
//  ServiceCollectionExtensions.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 Microsoft.Extensions.DependencyInjection;
using NosSmooth.Core.Extensions;
using NosSmooth.Extensions.Combat.Responders;

namespace NosSmooth.Extensions.Combat.Extensions;

/// <summary>
/// Extension methods for <see cref="IServiceCollection"/>.
/// </summary>
public static class ServiceCollectionExtensions
{
    /// <summary>
    /// Adds a NosTale combat extension. <see cref="CombatManager"/>.
    /// </summary>
    /// <param name="serviceCollection">The service collection.</param>
    /// <returns>The collection.</returns>
    public static IServiceCollection AddNostaleCombat(this IServiceCollection serviceCollection)
    {
        return serviceCollection
            .AddPacketResponder<CancelResponder>()
            .AddPacketResponder<SuResponder>()
            .AddSingleton<CombatManager>();
    }
}
\ No newline at end of file

Do not follow this link