From 792efeba255aec3fede9ead710ec67dce00dea3b Mon Sep 17 00:00:00 2001 From: Rutherther Date: Fri, 18 Feb 2022 23:33:40 +0100 Subject: [PATCH] feat(combat): add combat manager --- .../CombatManager.cs | 149 ++++++++++++++++++ .../CombatState.cs | 107 +++++++++++++ .../Extensions/ServiceCollectionExtensions.cs | 30 ++++ 3 files changed, 286 insertions(+) create mode 100644 Extensions/NosSmooth.Extensions.Combat/CombatManager.cs create mode 100644 Extensions/NosSmooth.Extensions.Combat/CombatState.cs create mode 100644 Extensions/NosSmooth.Extensions.Combat/Extensions/ServiceCollectionExtensions.cs diff --git a/Extensions/NosSmooth.Extensions.Combat/CombatManager.cs b/Extensions/NosSmooth.Extensions.Combat/CombatManager.cs new file mode 100644 index 0000000..2121d96 --- /dev/null +++ b/Extensions/NosSmooth.Extensions.Combat/CombatManager.cs @@ -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; + +/// +/// The combat manager that uses techniques to attack enemies. +/// +public class CombatManager : IStatefulEntity +{ + private readonly List _tokenSource; + private readonly Semaphore _semaphore; + private readonly INostaleClient _client; + private readonly Game.Game _game; + + /// + /// Initializes a new instance of the class. + /// + /// The NosTale client. + /// The game. + public CombatManager(INostaleClient client, Game.Game game) + { + _semaphore = new Semaphore(1, 1); + _tokenSource = new List(); + _client = client; + _game = game; + } + + /// + /// Enter into a combat state using the given technique. + /// + /// The technique to use. + /// A result that may or may not succeed. + public async Task 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 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(); + } + + /// + /// Register the given cancellation token source to be cancelled on skill use/cancel. + /// + /// The token source to register. + public void RegisterSkillCancellationToken(CancellationTokenSource tokenSource) + { + _semaphore.WaitOne(); + _tokenSource.Add(tokenSource); + _semaphore.Release(); + } + + /// + /// Unregister the given cancellation token registered using . + /// + /// The token source to unregister. + public void UnregisterSkillCancellationToken(CancellationTokenSource tokenSource) + { + _semaphore.WaitOne(); + _tokenSource.Remove(tokenSource); + _semaphore.Release(); + } + + /// + /// Cancel all of the skill tokens. + /// + 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 diff --git a/Extensions/NosSmooth.Extensions.Combat/CombatState.cs b/Extensions/NosSmooth.Extensions.Combat/CombatState.cs new file mode 100644 index 0000000..a12b314 --- /dev/null +++ b/Extensions/NosSmooth.Extensions.Combat/CombatState.cs @@ -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; + +/// +internal class CombatState : ICombatState +{ + private readonly LinkedList _operations; + private ICombatOperation? _currentOperation; + + /// + /// Initializes a new instance of the class. + /// + /// The NosTale client. + /// The game. + /// The combat manager. + public CombatState(INostaleClient client, Game.Game game, CombatManager combatManager) + { + Client = client; + Game = game; + CombatManager = combatManager; + _operations = new LinkedList(); + } + + /// + /// Gets whether the combat state should be quit. + /// + public bool ShouldQuit { get; private set; } + + /// + public CombatManager CombatManager { get; } + + /// + public Game.Game Game { get; } + + /// + public INostaleClient Client { get; } + + /// + public void QuitCombat() + { + ShouldQuit = true; + } + + /// + /// Make a step in the queue. + /// + /// The current operation, if any. + public ICombatOperation? NextOperation() + { + var operation = _currentOperation = _operations.First?.Value; + if (operation is not null) + { + _operations.RemoveFirst(); + } + + return operation; + } + + /// + 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); + } + } + + /// + public void EnqueueOperation(ICombatOperation operation) + { + _operations.AddLast(operation); + } + + /// + public void RemoveOperations(Func 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 diff --git a/Extensions/NosSmooth.Extensions.Combat/Extensions/ServiceCollectionExtensions.cs b/Extensions/NosSmooth.Extensions.Combat/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..eebcf9d --- /dev/null +++ b/Extensions/NosSmooth.Extensions.Combat/Extensions/ServiceCollectionExtensions.cs @@ -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; + +/// +/// Extension methods for . +/// +public static class ServiceCollectionExtensions +{ + /// + /// Adds a NosTale combat extension. . + /// + /// The service collection. + /// The collection. + public static IServiceCollection AddNostaleCombat(this IServiceCollection serviceCollection) + { + return serviceCollection + .AddPacketResponder() + .AddPacketResponder() + .AddSingleton(); + } +} \ No newline at end of file -- 2.49.0