From aa41cfec89339a8103d5b7dd38e25776c8da3e03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franti=C5=A1ek=20Boh=C3=A1=C4=8Dek?= Date: Sat, 21 Jan 2023 12:52:51 +0100 Subject: [PATCH] feat(combat): make waiting return an error with information about waiting information --- .../CombatManager.cs | 8 +++-- .../Errors/CharacterCannotAttackError.cs | 12 +++++++ .../Errors/CharacterCannotMoveError.cs | 12 +++++++ .../Errors/NotEnoughManaError.cs | 12 +++++++ .../Errors/TargetDeadError.cs | 12 +++++++ .../Operations/CannotBeUsedError.cs | 12 +++++++ .../Operations/CompoundOperation.cs | 21 ++++++++---- .../Operations/ICombatOperation.cs | 10 +----- .../Operations/UseItemOperation.cs | 4 +-- .../Operations/UseSkillOperation.cs | 32 ++++++++++++++----- .../Operations/WalkInRangeOperation.cs | 9 ++++-- .../Operations/WalkOperation.cs | 9 ++++-- .../Techniques/ICombatTechnique.cs | 3 +- .../Techniques/SimpleAttackTechnique.cs | 19 ++++++++--- 14 files changed, 136 insertions(+), 39 deletions(-) create mode 100644 Extensions/NosSmooth.Extensions.Combat/Errors/CharacterCannotAttackError.cs create mode 100644 Extensions/NosSmooth.Extensions.Combat/Errors/CharacterCannotMoveError.cs create mode 100644 Extensions/NosSmooth.Extensions.Combat/Errors/NotEnoughManaError.cs create mode 100644 Extensions/NosSmooth.Extensions.Combat/Errors/TargetDeadError.cs create mode 100644 Extensions/NosSmooth.Extensions.Combat/Operations/CannotBeUsedError.cs diff --git a/Extensions/NosSmooth.Extensions.Combat/CombatManager.cs b/Extensions/NosSmooth.Extensions.Combat/CombatManager.cs index e3f6150302c8fa29814b32a5214f0e3bb371b9e9..7becb911a0b0a85f49c3b0de94b28512dce4941f 100644 --- a/Extensions/NosSmooth.Extensions.Combat/CombatManager.cs +++ b/Extensions/NosSmooth.Extensions.Combat/CombatManager.cs @@ -169,17 +169,19 @@ public class CombatManager : IStatefulEntity if (!currentOperation.IsExecuting()) { // not executing, check can be used, execute if can. var canBeUsedResult = currentOperation.CanBeUsed(combatState); - if (!canBeUsedResult.IsDefined(out var canBeUsed)) + if (canBeUsedResult is { IsSuccess: false, Error: not CannotBeUsedError }) { return Result<(bool, long?)>.FromError(canBeUsedResult); } + var canBeUsedError = canBeUsedResult.Error as CannotBeUsedError; + var canBeUsed = canBeUsedError?.Response ?? CanBeUsedResponse.CanBeUsed; + switch (canBeUsed) { case CanBeUsedResponse.WontBeUsable: - return new UnusableOperationError(currentOperation); case CanBeUsedResponse.MustWait: - var waitingResult = technique.HandleWaiting(queueType, combatState, currentOperation); + var waitingResult = technique.HandleWaiting(queueType, combatState, currentOperation, canBeUsedError!); if (!waitingResult.IsSuccess) { diff --git a/Extensions/NosSmooth.Extensions.Combat/Errors/CharacterCannotAttackError.cs b/Extensions/NosSmooth.Extensions.Combat/Errors/CharacterCannotAttackError.cs new file mode 100644 index 0000000000000000000000000000000000000000..c5415b6379af7fac871fcd087e9efec481b87394 --- /dev/null +++ b/Extensions/NosSmooth.Extensions.Combat/Errors/CharacterCannotAttackError.cs @@ -0,0 +1,12 @@ +// +// CharacterCannotAttackError.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 Remora.Results; + +namespace NosSmooth.Extensions.Combat.Errors; + +public record CharacterCannotAttackError() + : ResultError("The character cannot currently attack (is stunned, under debuff)"); \ No newline at end of file diff --git a/Extensions/NosSmooth.Extensions.Combat/Errors/CharacterCannotMoveError.cs b/Extensions/NosSmooth.Extensions.Combat/Errors/CharacterCannotMoveError.cs new file mode 100644 index 0000000000000000000000000000000000000000..70dc59f76c1e9cd57970cc9a0635b6bb8e68c614 --- /dev/null +++ b/Extensions/NosSmooth.Extensions.Combat/Errors/CharacterCannotMoveError.cs @@ -0,0 +1,12 @@ +// +// CharacterCannotMoveError.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 Remora.Results; + +namespace NosSmooth.Extensions.Combat.Errors; + +public record CharacterCannotMoveError() + : ResultError("The character cannot currently move (is stunned, under debuff)"); \ No newline at end of file diff --git a/Extensions/NosSmooth.Extensions.Combat/Errors/NotEnoughManaError.cs b/Extensions/NosSmooth.Extensions.Combat/Errors/NotEnoughManaError.cs new file mode 100644 index 0000000000000000000000000000000000000000..9d05a0db91b06634306112d341f4abe0c5aec1a3 --- /dev/null +++ b/Extensions/NosSmooth.Extensions.Combat/Errors/NotEnoughManaError.cs @@ -0,0 +1,12 @@ +// +// NotEnoughManaError.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 Remora.Results; + +namespace NosSmooth.Extensions.Combat.Errors; + +public record NotEnoughManaError(long CurrentMana, long NeededMana) + : ResultError($"The character (with {CurrentMana} mp) does not have enough mana ({NeededMana} mp) for the given operation."); \ No newline at end of file diff --git a/Extensions/NosSmooth.Extensions.Combat/Errors/TargetDeadError.cs b/Extensions/NosSmooth.Extensions.Combat/Errors/TargetDeadError.cs new file mode 100644 index 0000000000000000000000000000000000000000..cf17a7dc933404b65ea539a385aae044bceea86c --- /dev/null +++ b/Extensions/NosSmooth.Extensions.Combat/Errors/TargetDeadError.cs @@ -0,0 +1,12 @@ +// +// TargetDeadError.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 Remora.Results; + +namespace NosSmooth.Extensions.Combat.Errors; + +public record TargetDeadError() + : ResultError("The target is already dead."); \ No newline at end of file diff --git a/Extensions/NosSmooth.Extensions.Combat/Operations/CannotBeUsedError.cs b/Extensions/NosSmooth.Extensions.Combat/Operations/CannotBeUsedError.cs new file mode 100644 index 0000000000000000000000000000000000000000..553daccef2b89053839a51881517d4952edfbd04 --- /dev/null +++ b/Extensions/NosSmooth.Extensions.Combat/Operations/CannotBeUsedError.cs @@ -0,0 +1,12 @@ +// +// CannotBeUsedError.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 Remora.Results; + +namespace NosSmooth.Extensions.Combat.Operations; + +public record CannotBeUsedError(CanBeUsedResponse Response, IResultError? UnderlyingError) + : ResultError($"The given operation cannot move forward ({Response}). {UnderlyingError?.Message}"); \ No newline at end of file diff --git a/Extensions/NosSmooth.Extensions.Combat/Operations/CompoundOperation.cs b/Extensions/NosSmooth.Extensions.Combat/Operations/CompoundOperation.cs index e0fefc0b8f4043060b04b55d5c82f82b4d61807f..eeb97b7fa82c0f2f117145c6d586a2a7cff2277c 100644 --- a/Extensions/NosSmooth.Extensions.Combat/Operations/CompoundOperation.cs +++ b/Extensions/NosSmooth.Extensions.Combat/Operations/CompoundOperation.cs @@ -5,6 +5,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Diagnostics; +using NosSmooth.Extensions.Combat.Techniques; using Remora.Results; namespace NosSmooth.Extensions.Combat.Operations; @@ -15,6 +16,7 @@ namespace NosSmooth.Extensions.Combat.Operations; /// public class CompoundOperation : ICombatOperation { + private readonly ICombatTechnique _technique; private readonly ICombatOperation[] _operations; private readonly OperationQueueType _queueType; private Task? _compoundOperation; @@ -22,16 +24,18 @@ public class CompoundOperation : ICombatOperation /// /// Initializes a new instance of the class. /// - /// The operations to execute. + /// The combat technique used for calling HandleWaiting. /// The queue type. + /// The operations to execute. public CompoundOperation - (OperationQueueType queueType = OperationQueueType.TotalControl, params ICombatOperation[] operations) + (ICombatTechnique technique, OperationQueueType queueType = OperationQueueType.TotalControl, params ICombatOperation[] operations) { if (operations.Length == 0) { throw new ArgumentNullException(nameof(operations), "The compound operation needs at least one operation."); } + _technique = technique; _operations = operations; _queueType = queueType; } @@ -90,7 +94,7 @@ public class CompoundOperation : ICombatOperation => _compoundOperation?.IsCompleted ?? false; /// - public Result CanBeUsed(ICombatState combatState) + public Result CanBeUsed(ICombatState combatState) => _operations[0].CanBeUsed(combatState); private async Task UseAsync(ICombatState combatState, CancellationToken ct) @@ -102,14 +106,17 @@ public class CompoundOperation : ICombatOperation while (canBeUsed != CanBeUsedResponse.CanBeUsed) { var canBeUsedResult = operation.CanBeUsed(combatState); - if (!canBeUsedResult.IsDefined(out canBeUsed)) + if (canBeUsedResult is { IsSuccess: false, Error: not CannotBeUsedError }) { - return Result.FromError(canBeUsedResult); + return canBeUsedResult; } - if (canBeUsed == CanBeUsedResponse.WontBeUsable) + var error = canBeUsedResult.Error as CannotBeUsedError; + canBeUsed = error?.Response ?? CanBeUsedResponse.CanBeUsed; + + if (canBeUsed != CanBeUsedResponse.CanBeUsed) { - return new GenericError("Won't be usable."); + _technique.HandleWaiting(QueueType, combatState, this, error!); } await Task.Delay(10, ct); diff --git a/Extensions/NosSmooth.Extensions.Combat/Operations/ICombatOperation.cs b/Extensions/NosSmooth.Extensions.Combat/Operations/ICombatOperation.cs index 412f1847b75c17a48814e2600e1c749b2406334e..b9aabf03be0e050e1fb0beff9e40c663ac006be8 100644 --- a/Extensions/NosSmooth.Extensions.Combat/Operations/ICombatOperation.cs +++ b/Extensions/NosSmooth.Extensions.Combat/Operations/ICombatOperation.cs @@ -15,14 +15,6 @@ namespace NosSmooth.Extensions.Combat.Operations; /// public interface ICombatOperation : IDisposable { - // 1. wait for CanBeUsed - // 2. use OnlyExecute - // 3. periodically check IsFinished - // 4. Finished - // 5. Call Dispose - // 6. Go to next operation in queue - // go to step 1 - /// /// Gets the queue type the operation belongs to. /// @@ -70,5 +62,5 @@ public interface ICombatOperation : IDisposable /// /// The combat state. /// Whether the operation can be used right away. - public Result CanBeUsed(ICombatState combatState); + public Result CanBeUsed(ICombatState combatState); } \ No newline at end of file diff --git a/Extensions/NosSmooth.Extensions.Combat/Operations/UseItemOperation.cs b/Extensions/NosSmooth.Extensions.Combat/Operations/UseItemOperation.cs index 6d468e8c44af013b2b7995721e1537400b0e6ad5..6d87c124086227184c1f3a29c953d377452950e1 100644 --- a/Extensions/NosSmooth.Extensions.Combat/Operations/UseItemOperation.cs +++ b/Extensions/NosSmooth.Extensions.Combat/Operations/UseItemOperation.cs @@ -66,8 +66,8 @@ public record UseItemOperation(InventoryItem Item) : ICombatOperation => _useItemOperation?.IsCompleted ?? false; /// - public Result CanBeUsed(ICombatState combatState) - => CanBeUsedResponse.CanBeUsed; + public Result CanBeUsed(ICombatState combatState) + => Result.FromSuccess(); /// public void Dispose() diff --git a/Extensions/NosSmooth.Extensions.Combat/Operations/UseSkillOperation.cs b/Extensions/NosSmooth.Extensions.Combat/Operations/UseSkillOperation.cs index c2c51c69b7628d2b56eed875da8c1ab17481d42d..14734af539b0821d7a85c7f7ee35ab77f62266c4 100644 --- a/Extensions/NosSmooth.Extensions.Combat/Operations/UseSkillOperation.cs +++ b/Extensions/NosSmooth.Extensions.Combat/Operations/UseSkillOperation.cs @@ -5,7 +5,6 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Diagnostics; -using System.Xml.XPath; using NosSmooth.Core.Contracts; using NosSmooth.Data.Abstractions.Enums; using NosSmooth.Data.Abstractions.Infos; @@ -16,8 +15,6 @@ using NosSmooth.Game.Data.Characters; using NosSmooth.Game.Data.Entities; using NosSmooth.Game.Errors; using NosSmooth.Game.Events.Battle; -using NosSmooth.Packets; -using NosSmooth.Packets.Client.Battle; using Remora.Results; namespace NosSmooth.Extensions.Combat.Operations; @@ -103,7 +100,7 @@ public record UseSkillOperation => _contract?.HasReachedState(UseSkillStates.CharacterRestored) ?? false; /// - public Result CanBeUsed(ICombatState combatState) + public Result CanBeUsed(ICombatState combatState) { if (Skill.Info is null) { @@ -113,18 +110,37 @@ public record UseSkillOperation var character = combatState.Game.Character; if (Target.Hp is not null && Target.Hp.Amount is not null && Target.Hp.Amount == 0) { - return CanBeUsedResponse.WontBeUsable; + return new CannotBeUsedError(CanBeUsedResponse.WontBeUsable, new TargetDeadError()); } - if (character is not null && character.Mp is not null && character.Mp.Amount is not null) + if (character is null) + { + return new CharacterNotInitializedError(); + } + + if (character.CantAttack) + { + return new CannotBeUsedError(CanBeUsedResponse.MustWait, new CharacterCannotAttackError()); + } + + if (character.Mp is not null && character.Mp.Amount is not null) { if (character.Mp.Amount < Skill.Info.MpCost) { // The character is in combat, mp won't restore. - return CanBeUsedResponse.WontBeUsable; + return new CannotBeUsedError + ( + CanBeUsedResponse.WontBeUsable, + new NotEnoughManaError(character.Mp.Amount.Value, Skill.Info.MpCost) + ); } } - return Skill.IsOnCooldown ? CanBeUsedResponse.MustWait : CanBeUsedResponse.CanBeUsed; + if (Skill.IsOnCooldown) + { + return new CannotBeUsedError(CanBeUsedResponse.MustWait, new SkillOnCooldownError(Skill)); + } + + return Result.FromSuccess(); } private Result> ContractSkill(ISkillInfo info) diff --git a/Extensions/NosSmooth.Extensions.Combat/Operations/WalkInRangeOperation.cs b/Extensions/NosSmooth.Extensions.Combat/Operations/WalkInRangeOperation.cs index 4ac553010178cc58d220d260aa0d0e3ed22267c1..7162ad2c869230f960cd691c59f9ae519ed71f0f 100644 --- a/Extensions/NosSmooth.Extensions.Combat/Operations/WalkInRangeOperation.cs +++ b/Extensions/NosSmooth.Extensions.Combat/Operations/WalkInRangeOperation.cs @@ -76,7 +76,7 @@ public record WalkInRangeOperation => _walkInRangeOperation?.IsCompleted ?? false; /// - public Result CanBeUsed(ICombatState combatState) + public Result CanBeUsed(ICombatState combatState) { var character = combatState.Game.Character; if (character is null) @@ -84,7 +84,12 @@ public record WalkInRangeOperation return new CharacterNotInitializedError(); } - return character.CantMove ? CanBeUsedResponse.MustWait : CanBeUsedResponse.CanBeUsed; + if (character.CantMove) + { + return new CannotBeUsedError(CanBeUsedResponse.MustWait, new CharacterCannotMoveError()); + } + + return Result.FromSuccess(); } private async Task UseAsync(ICombatState combatState, CancellationToken ct = default) diff --git a/Extensions/NosSmooth.Extensions.Combat/Operations/WalkOperation.cs b/Extensions/NosSmooth.Extensions.Combat/Operations/WalkOperation.cs index 10a61798eb0d85dc1eac7216f7d405943db0a070..3461d60e01cf43738370c25a27cf36229af9d48e 100644 --- a/Extensions/NosSmooth.Extensions.Combat/Operations/WalkOperation.cs +++ b/Extensions/NosSmooth.Extensions.Combat/Operations/WalkOperation.cs @@ -66,7 +66,7 @@ public record WalkOperation(WalkManager WalkManager, short X, short Y) : ICombat => _walkOperation?.IsCompleted ?? false; /// - public Result CanBeUsed(ICombatState combatState) + public Result CanBeUsed(ICombatState combatState) { var character = combatState.Game.Character; if (character is null) @@ -74,7 +74,12 @@ public record WalkOperation(WalkManager WalkManager, short X, short Y) : ICombat return new CharacterNotInitializedError(); } - return character.CantMove ? CanBeUsedResponse.MustWait : CanBeUsedResponse.CanBeUsed; + if (character.CantMove) + { + return new CannotBeUsedError(CanBeUsedResponse.MustWait, new CharacterCannotMoveError()); + } + + return Result.FromSuccess(); } private Task UseAsync(ICombatState combatState, CancellationToken ct = default) diff --git a/Extensions/NosSmooth.Extensions.Combat/Techniques/ICombatTechnique.cs b/Extensions/NosSmooth.Extensions.Combat/Techniques/ICombatTechnique.cs index 1c7ebdc14579103683d388984174d789682a2435..85229e1ab964b4b75deae16788f7ed2e350cead3 100644 --- a/Extensions/NosSmooth.Extensions.Combat/Techniques/ICombatTechnique.cs +++ b/Extensions/NosSmooth.Extensions.Combat/Techniques/ICombatTechnique.cs @@ -51,8 +51,9 @@ public interface ICombatTechnique /// The type of the operation. /// The combat state. /// The operation that needs waiting. + /// The error received from the operation. /// A result that may or may not have succeeded. In case of an error, will be called with the error. - public Result HandleWaiting(OperationQueueType queueType, ICombatState state, ICombatOperation operation); + public Result HandleWaiting(OperationQueueType queueType, ICombatState state, ICombatOperation operation, CannotBeUsedError error); /// /// Handles an arbitrary error. diff --git a/Extensions/NosSmooth.Extensions.Combat/Techniques/SimpleAttackTechnique.cs b/Extensions/NosSmooth.Extensions.Combat/Techniques/SimpleAttackTechnique.cs index 995b6bfce860e837ee741c676bc21ec121f06c98..f59eae143ce9a5e4a2c289cb1b00586680c3c62f 100644 --- a/Extensions/NosSmooth.Extensions.Combat/Techniques/SimpleAttackTechnique.cs +++ b/Extensions/NosSmooth.Extensions.Combat/Techniques/SimpleAttackTechnique.cs @@ -4,7 +4,6 @@ // 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 NosSmooth.Data.Abstractions.Enums; using NosSmooth.Extensions.Combat.Errors; using NosSmooth.Extensions.Combat.Extensions; @@ -12,9 +11,7 @@ using NosSmooth.Extensions.Combat.Operations; using NosSmooth.Extensions.Combat.Selectors; using NosSmooth.Extensions.Pathfinding; using NosSmooth.Game.Apis.Safe; -using NosSmooth.Game.Data.Characters; using NosSmooth.Game.Data.Entities; -using NosSmooth.Game.Data.Inventory; using NosSmooth.Game.Extensions; using Remora.Results; @@ -83,8 +80,19 @@ public class SimpleAttackTechnique : ICombatTechnique } /// - public Result HandleWaiting(OperationQueueType queueType, ICombatState state, ICombatOperation operation) - { // does not do anything, just wait. + public Result HandleWaiting(OperationQueueType queueType, ICombatState state, ICombatOperation operation, CannotBeUsedError cannotBeUsedError) + { + if (cannotBeUsedError.UnderlyingError is TargetDeadError) + { + state.RemoveCurrentOperation(queueType, true); + return Result.FromSuccess(); + } + + if (cannotBeUsedError.Response == CanBeUsedResponse.WontBeUsable) + { + return cannotBeUsedError; + } + return Result.FromSuccess(); } @@ -172,6 +180,7 @@ public class SimpleAttackTechnique : ICombatTechnique ( new CompoundOperation ( + this, OperationQueueType.TotalControl, new WalkInRangeOperation(_walkManager, _target, range), new UseSkillOperation(_skillsApi, currentSkill, character, _target)