M Extensions/NosSmooth.Extensions.Combat/CombatManager.cs => Extensions/NosSmooth.Extensions.Combat/CombatManager.cs +5 -3
@@ 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)
{
A Extensions/NosSmooth.Extensions.Combat/Errors/CharacterCannotAttackError.cs => Extensions/NosSmooth.Extensions.Combat/Errors/CharacterCannotAttackError.cs +12 -0
@@ 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
A Extensions/NosSmooth.Extensions.Combat/Errors/CharacterCannotMoveError.cs => Extensions/NosSmooth.Extensions.Combat/Errors/CharacterCannotMoveError.cs +12 -0
@@ 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
A Extensions/NosSmooth.Extensions.Combat/Errors/NotEnoughManaError.cs => Extensions/NosSmooth.Extensions.Combat/Errors/NotEnoughManaError.cs +12 -0
@@ 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
A Extensions/NosSmooth.Extensions.Combat/Errors/TargetDeadError.cs => Extensions/NosSmooth.Extensions.Combat/Errors/TargetDeadError.cs +12 -0
@@ 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
A Extensions/NosSmooth.Extensions.Combat/Operations/CannotBeUsedError.cs => Extensions/NosSmooth.Extensions.Combat/Operations/CannotBeUsedError.cs +12 -0
@@ 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
M Extensions/NosSmooth.Extensions.Combat/Operations/CompoundOperation.cs => Extensions/NosSmooth.Extensions.Combat/Operations/CompoundOperation.cs +14 -7
@@ 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;
/// </summary>
public class CompoundOperation : ICombatOperation
{
+ private readonly ICombatTechnique _technique;
private readonly ICombatOperation[] _operations;
private readonly OperationQueueType _queueType;
private Task<Result>? _compoundOperation;
@@ 22,16 24,18 @@ public class CompoundOperation : ICombatOperation
/// <summary>
/// Initializes a new instance of the <see cref="CompoundOperation"/> class.
/// </summary>
- /// <param name="operations">The operations to execute.</param>
+ /// <param name="technique">The combat technique used for calling HandleWaiting.</param>
/// <param name="queueType">The queue type.</param>
+ /// <param name="operations">The operations to execute.</param>
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;
/// <inheritdoc />
- public Result<CanBeUsedResponse> CanBeUsed(ICombatState combatState)
+ public Result CanBeUsed(ICombatState combatState)
=> _operations[0].CanBeUsed(combatState);
private async Task<Result> 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);
M Extensions/NosSmooth.Extensions.Combat/Operations/ICombatOperation.cs => Extensions/NosSmooth.Extensions.Combat/Operations/ICombatOperation.cs +1 -9
@@ 15,14 15,6 @@ namespace NosSmooth.Extensions.Combat.Operations;
/// </summary>
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
-
/// <summary>
/// Gets the queue type the operation belongs to.
/// </summary>
@@ 70,5 62,5 @@ public interface ICombatOperation : IDisposable
/// </remarks>
/// <param name="combatState">The combat state.</param>
/// <returns>Whether the operation can be used right away.</returns>
- public Result<CanBeUsedResponse> CanBeUsed(ICombatState combatState);
+ public Result CanBeUsed(ICombatState combatState);
}=
\ No newline at end of file
M Extensions/NosSmooth.Extensions.Combat/Operations/UseItemOperation.cs => Extensions/NosSmooth.Extensions.Combat/Operations/UseItemOperation.cs +2 -2
@@ 66,8 66,8 @@ public record UseItemOperation(InventoryItem Item) : ICombatOperation
=> _useItemOperation?.IsCompleted ?? false;
/// <inheritdoc />
- public Result<CanBeUsedResponse> CanBeUsed(ICombatState combatState)
- => CanBeUsedResponse.CanBeUsed;
+ public Result CanBeUsed(ICombatState combatState)
+ => Result.FromSuccess();
/// <inheritdoc />
public void Dispose()
M Extensions/NosSmooth.Extensions.Combat/Operations/UseSkillOperation.cs => Extensions/NosSmooth.Extensions.Combat/Operations/UseSkillOperation.cs +24 -8
@@ 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;
/// <inheritdoc />
- public Result<CanBeUsedResponse> 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<IContract<SkillUsedEvent, UseSkillStates>> ContractSkill(ISkillInfo info)
M Extensions/NosSmooth.Extensions.Combat/Operations/WalkInRangeOperation.cs => Extensions/NosSmooth.Extensions.Combat/Operations/WalkInRangeOperation.cs +7 -2
@@ 76,7 76,7 @@ public record WalkInRangeOperation
=> _walkInRangeOperation?.IsCompleted ?? false;
/// <inheritdoc />
- public Result<CanBeUsedResponse> 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<Result> UseAsync(ICombatState combatState, CancellationToken ct = default)
M Extensions/NosSmooth.Extensions.Combat/Operations/WalkOperation.cs => Extensions/NosSmooth.Extensions.Combat/Operations/WalkOperation.cs +7 -2
@@ 66,7 66,7 @@ public record WalkOperation(WalkManager WalkManager, short X, short Y) : ICombat
=> _walkOperation?.IsCompleted ?? false;
/// <inheritdoc />
- public Result<CanBeUsedResponse> 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<Result> UseAsync(ICombatState combatState, CancellationToken ct = default)
M Extensions/NosSmooth.Extensions.Combat/Techniques/ICombatTechnique.cs => Extensions/NosSmooth.Extensions.Combat/Techniques/ICombatTechnique.cs +2 -1
@@ 51,8 51,9 @@ public interface ICombatTechnique
/// <param name="queueType">The type of the operation.</param>
/// <param name="state">The combat state.</param>
/// <param name="operation">The operation that needs waiting.</param>
+ /// <param name="error">The error received from the operation.</param>
/// <returns>A result that may or may not have succeeded. In case of an error, <see cref="HandleError"/> will be called with the error.</returns>
- public Result HandleWaiting(OperationQueueType queueType, ICombatState state, ICombatOperation operation);
+ public Result HandleWaiting(OperationQueueType queueType, ICombatState state, ICombatOperation operation, CannotBeUsedError error);
/// <summary>
/// Handles an arbitrary error.
M Extensions/NosSmooth.Extensions.Combat/Techniques/SimpleAttackTechnique.cs => Extensions/NosSmooth.Extensions.Combat/Techniques/SimpleAttackTechnique.cs +14 -5
@@ 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
}
/// <inheritdoc />
- 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)