forked from liza/Questionable
Attempt to handle Revisit triggering
This commit is contained in:
parent
606b9d52ba
commit
9d8c67155f
@ -16,12 +16,15 @@
|
|||||||
"Z": 405.1829
|
"Z": 405.1829
|
||||||
},
|
},
|
||||||
"MinimumAngle": 100,
|
"MinimumAngle": 100,
|
||||||
"MaximumAngle": 250
|
"MaximumAngle": 250,
|
||||||
|
"MinimumDistance": 1.5,
|
||||||
|
"MaximumDistance": 3
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"DataId": 31345,
|
"DataId": 31345,
|
||||||
|
"Fly": false,
|
||||||
"Locations": [
|
"Locations": [
|
||||||
{
|
{
|
||||||
"Position": {
|
"Position": {
|
||||||
@ -29,8 +32,10 @@
|
|||||||
"Y": 216.5585,
|
"Y": 216.5585,
|
||||||
"Z": 412.4353
|
"Z": 412.4353
|
||||||
},
|
},
|
||||||
"MinimumAngle": 50,
|
"MinimumAngle": 75,
|
||||||
"MaximumAngle": 165
|
"MaximumAngle": 145,
|
||||||
|
"MinimumDistance": 1.5,
|
||||||
|
"MaximumDistance": 3
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Position": {
|
"Position": {
|
||||||
@ -39,7 +44,9 @@
|
|||||||
"Z": 421.5481
|
"Z": 421.5481
|
||||||
},
|
},
|
||||||
"MinimumAngle": 0,
|
"MinimumAngle": 0,
|
||||||
"MaximumAngle": 145
|
"MaximumAngle": 145,
|
||||||
|
"MinimumDistance": 1.5,
|
||||||
|
"MaximumDistance": 3
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Position": {
|
"Position": {
|
||||||
@ -48,7 +55,9 @@
|
|||||||
"Z": 408.2164
|
"Z": 408.2164
|
||||||
},
|
},
|
||||||
"MinimumAngle": 155,
|
"MinimumAngle": 155,
|
||||||
"MaximumAngle": 225
|
"MaximumAngle": 225,
|
||||||
|
"MinimumDistance": 1.5,
|
||||||
|
"MaximumDistance": 3
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -155,4 +164,4 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
@ -5,7 +5,7 @@ namespace Questionable.Model.Gathering;
|
|||||||
public sealed class GatheringNode
|
public sealed class GatheringNode
|
||||||
{
|
{
|
||||||
public uint DataId { get; set; }
|
public uint DataId { get; set; }
|
||||||
public bool Fly { get; set; }
|
public bool? Fly { get; set; }
|
||||||
|
|
||||||
public List<GatheringLocation> Locations { get; set; } = [];
|
public List<GatheringLocation> Locations { get; set; } = [];
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,6 @@ public sealed class GatheringRoot
|
|||||||
public EAetheryteLocation? AetheryteShortcut { get; set; }
|
public EAetheryteLocation? AetheryteShortcut { get; set; }
|
||||||
|
|
||||||
public AethernetShortcut? AethernetShortcut { get; set; }
|
public AethernetShortcut? AethernetShortcut { get; set; }
|
||||||
public bool FlyBetweenNodes { get; set; } = true;
|
public bool? FlyBetweenNodes { get; set; }
|
||||||
public List<GatheringNodeGroup> Groups { get; set; } = [];
|
public List<GatheringNodeGroup> Groups { get; set; } = [];
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,18 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using Dalamud.Game.ClientState.Conditions;
|
using Dalamud.Game.ClientState.Conditions;
|
||||||
using Dalamud.Game.ClientState.Objects.Enums;
|
using Dalamud.Game.ClientState.Objects.Enums;
|
||||||
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Event;
|
using FFXIVClientStructs.FFXIV.Client.Game.Event;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||||
|
using LLib;
|
||||||
|
using Lumina.Excel.GeneratedSheets;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Questionable.Controller.Steps;
|
using Questionable.Controller.Steps;
|
||||||
@ -32,6 +37,7 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
|
|||||||
private readonly IObjectTable _objectTable;
|
private readonly IObjectTable _objectTable;
|
||||||
private readonly IServiceProvider _serviceProvider;
|
private readonly IServiceProvider _serviceProvider;
|
||||||
private readonly ICondition _condition;
|
private readonly ICondition _condition;
|
||||||
|
private readonly Regex _revisitRegex;
|
||||||
|
|
||||||
private CurrentRequest? _currentRequest;
|
private CurrentRequest? _currentRequest;
|
||||||
|
|
||||||
@ -44,7 +50,9 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
|
|||||||
IChatGui chatGui,
|
IChatGui chatGui,
|
||||||
ILogger<GatheringController> logger,
|
ILogger<GatheringController> logger,
|
||||||
IServiceProvider serviceProvider,
|
IServiceProvider serviceProvider,
|
||||||
ICondition condition)
|
ICondition condition,
|
||||||
|
IDataManager dataManager,
|
||||||
|
IPluginLog pluginLog)
|
||||||
: base(chatGui, logger)
|
: base(chatGui, logger)
|
||||||
{
|
{
|
||||||
_movementController = movementController;
|
_movementController = movementController;
|
||||||
@ -54,6 +62,9 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
|
|||||||
_objectTable = objectTable;
|
_objectTable = objectTable;
|
||||||
_serviceProvider = serviceProvider;
|
_serviceProvider = serviceProvider;
|
||||||
_condition = condition;
|
_condition = condition;
|
||||||
|
|
||||||
|
_revisitRegex = dataManager.GetRegex<LogMessage>(5574, x => x.Text, pluginLog)
|
||||||
|
?? throw new InvalidDataException("No regex found for revisit message");
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Start(GatheringRequest gatheringRequest)
|
public bool Start(GatheringRequest gatheringRequest)
|
||||||
@ -88,13 +99,19 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
|
|||||||
public EStatus Update()
|
public EStatus Update()
|
||||||
{
|
{
|
||||||
if (_currentRequest == null)
|
if (_currentRequest == null)
|
||||||
|
{
|
||||||
|
Stop("No request");
|
||||||
return EStatus.Complete;
|
return EStatus.Complete;
|
||||||
|
}
|
||||||
|
|
||||||
if (_movementController.IsPathfinding || _movementController.IsPathfinding)
|
if (_movementController.IsPathfinding || _movementController.IsPathfinding)
|
||||||
return EStatus.Moving;
|
return EStatus.Moving;
|
||||||
|
|
||||||
if (HasRequestedItems() && !_condition[ConditionFlag.Gathering])
|
if (HasRequestedItems() && !_condition[ConditionFlag.Gathering])
|
||||||
|
{
|
||||||
|
Stop("Has all items");
|
||||||
return EStatus.Complete;
|
return EStatus.Complete;
|
||||||
|
}
|
||||||
|
|
||||||
if (_currentTask == null && _taskQueue.Count == 0)
|
if (_currentTask == null && _taskQueue.Count == 0)
|
||||||
GoToNextNode();
|
GoToNextNode();
|
||||||
@ -136,6 +153,9 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
|
|||||||
|
|
||||||
_taskQueue.Enqueue(_serviceProvider.GetRequiredService<MountTask>()
|
_taskQueue.Enqueue(_serviceProvider.GetRequiredService<MountTask>()
|
||||||
.With(_currentRequest.Root.TerritoryId, MountTask.EMountIf.Always));
|
.With(_currentRequest.Root.TerritoryId, MountTask.EMountIf.Always));
|
||||||
|
|
||||||
|
bool fly = currentNode.Fly.GetValueOrDefault(_currentRequest.Root.FlyBetweenNodes.GetValueOrDefault(true)) &&
|
||||||
|
_gameFunctions.IsFlyingUnlocked(_currentRequest.Root.TerritoryId);
|
||||||
if (currentNode.Locations.Count > 1)
|
if (currentNode.Locations.Count > 1)
|
||||||
{
|
{
|
||||||
Vector3 averagePosition = new Vector3
|
Vector3 averagePosition = new Vector3
|
||||||
@ -144,8 +164,7 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
|
|||||||
Y = currentNode.Locations.Select(x => x.Position.Y).Max() + 5f,
|
Y = currentNode.Locations.Select(x => x.Position.Y).Max() + 5f,
|
||||||
Z = currentNode.Locations.Sum(x => x.Position.Z) / currentNode.Locations.Count,
|
Z = currentNode.Locations.Sum(x => x.Position.Z) / currentNode.Locations.Count,
|
||||||
};
|
};
|
||||||
bool fly = (currentNode.Fly || _currentRequest.Root.FlyBetweenNodes)
|
|
||||||
&& _gameFunctions.IsFlyingUnlocked(_currentRequest.Root.TerritoryId);
|
|
||||||
Vector3? pointOnFloor = _navmeshIpc.GetPointOnFloor(averagePosition, true);
|
Vector3? pointOnFloor = _navmeshIpc.GetPointOnFloor(averagePosition, true);
|
||||||
if (pointOnFloor != null)
|
if (pointOnFloor != null)
|
||||||
pointOnFloor = pointOnFloor.Value with { Y = pointOnFloor.Value.Y + (fly ? 3f : 0f) };
|
pointOnFloor = pointOnFloor.Value with { Y = pointOnFloor.Value.Y + (fly ? 3f : 0f) };
|
||||||
@ -156,17 +175,19 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
|
|||||||
}
|
}
|
||||||
|
|
||||||
_taskQueue.Enqueue(_serviceProvider.GetRequiredService<MoveToLandingLocation>()
|
_taskQueue.Enqueue(_serviceProvider.GetRequiredService<MoveToLandingLocation>()
|
||||||
.With(_currentRequest.Root.TerritoryId,
|
.With(_currentRequest.Root.TerritoryId, fly, currentNode));
|
||||||
currentNode.Fly || _currentRequest.Root.FlyBetweenNodes,
|
|
||||||
currentNode));
|
|
||||||
_taskQueue.Enqueue(_serviceProvider.GetRequiredService<Interact.DoInteract>()
|
_taskQueue.Enqueue(_serviceProvider.GetRequiredService<Interact.DoInteract>()
|
||||||
.With(currentNode.DataId, null, EInteractionType.InternalGather, true));
|
.With(currentNode.DataId, null, EInteractionType.InternalGather, true));
|
||||||
_taskQueue.Enqueue(_serviceProvider.GetRequiredService<DoGather>()
|
|
||||||
.With(_currentRequest.Data, currentNode));
|
foreach (bool revisitRequired in new[] { false, true })
|
||||||
if (_currentRequest.Data.Collectability > 0)
|
|
||||||
{
|
{
|
||||||
_taskQueue.Enqueue(_serviceProvider.GetRequiredService<DoGatherCollectable>()
|
_taskQueue.Enqueue(_serviceProvider.GetRequiredService<DoGather>()
|
||||||
.With(_currentRequest.Data, currentNode));
|
.With(_currentRequest.Data, currentNode, revisitRequired));
|
||||||
|
if (_currentRequest.Data.Collectability > 0)
|
||||||
|
{
|
||||||
|
_taskQueue.Enqueue(_serviceProvider.GetRequiredService<DoGatherCollectable>()
|
||||||
|
.With(_currentRequest.Data, currentNode, revisitRequired));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -232,6 +253,21 @@ internal sealed unsafe class GatheringController : MiniTaskController<GatheringC
|
|||||||
return base.GetRemainingTaskNames();
|
return base.GetRemainingTaskNames();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void OnNormalToast(SeString message)
|
||||||
|
{
|
||||||
|
if (_revisitRegex.IsMatch(message.TextValue))
|
||||||
|
{
|
||||||
|
if (_currentTask is IRevisitAware currentTaskRevisitAware)
|
||||||
|
currentTaskRevisitAware.OnRevisit();
|
||||||
|
|
||||||
|
foreach (ITask task in _taskQueue)
|
||||||
|
{
|
||||||
|
if (task is IRevisitAware taskRevisitAware)
|
||||||
|
taskRevisitAware.OnRevisit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private sealed class CurrentRequest
|
private sealed class CurrentRequest
|
||||||
{
|
{
|
||||||
public required GatheringRequest Data { get; init; }
|
public required GatheringRequest Data { get; init; }
|
||||||
|
@ -4,6 +4,7 @@ using System.Diagnostics.CodeAnalysis;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Dalamud.Game.ClientState.Conditions;
|
using Dalamud.Game.ClientState.Conditions;
|
||||||
using Dalamud.Game.ClientState.Keys;
|
using Dalamud.Game.ClientState.Keys;
|
||||||
|
using Dalamud.Game.Gui.Toast;
|
||||||
using Dalamud.Game.Text.SeStringHandling;
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
@ -90,6 +91,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>, IDi
|
|||||||
_taskFactories = taskFactories.ToList().AsReadOnly();
|
_taskFactories = taskFactories.ToList().AsReadOnly();
|
||||||
|
|
||||||
_condition.ConditionChange += OnConditionChange;
|
_condition.ConditionChange += OnConditionChange;
|
||||||
|
_toastGui.Toast += OnNormalToast;
|
||||||
_toastGui.ErrorToast += OnErrorToast;
|
_toastGui.ErrorToast += OnErrorToast;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -786,6 +788,11 @@ internal sealed class QuestController : MiniTaskController<QuestController>, IDi
|
|||||||
conditionChangeAware.OnConditionChange(flag, value);
|
conditionChangeAware.OnConditionChange(flag, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnNormalToast(ref SeString message, ref ToastOptions options, ref bool ishandled)
|
||||||
|
{
|
||||||
|
_gatheringController.OnNormalToast(message);
|
||||||
|
}
|
||||||
|
|
||||||
private void OnErrorToast(ref SeString message, ref bool ishandled)
|
private void OnErrorToast(ref SeString message, ref bool ishandled)
|
||||||
{
|
{
|
||||||
if (_currentTask is IToastAware toastAware)
|
if (_currentTask is IToastAware toastAware)
|
||||||
@ -795,6 +802,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>, IDi
|
|||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_toastGui.ErrorToast -= OnErrorToast;
|
_toastGui.ErrorToast -= OnErrorToast;
|
||||||
|
_toastGui.Toast -= OnNormalToast;
|
||||||
_condition.ConditionChange -= OnConditionChange;
|
_condition.ConditionChange -= OnConditionChange;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,20 +22,24 @@ internal sealed class DoGather(
|
|||||||
IGameGui gameGui,
|
IGameGui gameGui,
|
||||||
IClientState clientState,
|
IClientState clientState,
|
||||||
ICondition condition,
|
ICondition condition,
|
||||||
ILogger<DoGather> logger) : ITask
|
ILogger<DoGather> logger) : ITask, IRevisitAware
|
||||||
{
|
{
|
||||||
private const uint StatusGatheringRateUp = 218;
|
private const uint StatusGatheringRateUp = 218;
|
||||||
|
|
||||||
private GatheringController.GatheringRequest _currentRequest = null!;
|
private GatheringController.GatheringRequest _currentRequest = null!;
|
||||||
private GatheringNode _currentNode = null!;
|
private GatheringNode _currentNode = null!;
|
||||||
|
private bool _revisitRequired;
|
||||||
|
private bool _revisitTriggered;
|
||||||
private bool _wasGathering;
|
private bool _wasGathering;
|
||||||
private SlotInfo? _slotToGather;
|
private SlotInfo? _slotToGather;
|
||||||
private Queue<EAction>? _actionQueue;
|
private Queue<EAction>? _actionQueue;
|
||||||
|
|
||||||
public ITask With(GatheringController.GatheringRequest currentRequest, GatheringNode currentNode)
|
public ITask With(GatheringController.GatheringRequest currentRequest, GatheringNode currentNode,
|
||||||
|
bool revisitRequired)
|
||||||
{
|
{
|
||||||
_currentRequest = currentRequest;
|
_currentRequest = currentRequest;
|
||||||
_currentNode = currentNode;
|
_currentNode = currentNode;
|
||||||
|
_revisitRequired = revisitRequired;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,8 +47,17 @@ internal sealed class DoGather(
|
|||||||
|
|
||||||
public unsafe ETaskResult Update()
|
public unsafe ETaskResult Update()
|
||||||
{
|
{
|
||||||
if (gatheringController.HasNodeDisappeared(_currentNode))
|
if (_revisitRequired && !_revisitTriggered)
|
||||||
|
{
|
||||||
|
logger.LogInformation("No revisit");
|
||||||
return ETaskResult.TaskComplete;
|
return ETaskResult.TaskComplete;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gatheringController.HasNodeDisappeared(_currentNode))
|
||||||
|
{
|
||||||
|
logger.LogInformation("Node disappeared");
|
||||||
|
return ETaskResult.TaskComplete;
|
||||||
|
}
|
||||||
|
|
||||||
if (gameFunctions.GetFreeInventorySlots() == 0)
|
if (gameFunctions.GetFreeInventorySlots() == 0)
|
||||||
throw new TaskException("Inventory full");
|
throw new TaskException("Inventory full");
|
||||||
@ -225,7 +238,12 @@ internal sealed class DoGather(
|
|||||||
return ActionManager.Instance()->GetActionStatus(ActionType.Action, (uint)action) == 0;
|
return ActionManager.Instance()->GetActionStatus(ActionType.Action, (uint)action) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => "DoGather";
|
public void OnRevisit()
|
||||||
|
{
|
||||||
|
_revisitTriggered = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() => $"DoGather{(_revisitRequired ? " if revist" : "")}";
|
||||||
|
|
||||||
private sealed record SlotInfo(int Index, uint ItemId, int GatheringChance, int BoonChance, int Quantity);
|
private sealed record SlotInfo(int Index, uint ItemId, int GatheringChance, int BoonChance, int Quantity);
|
||||||
|
|
||||||
|
@ -17,16 +17,19 @@ internal sealed class DoGatherCollectable(
|
|||||||
GameFunctions gameFunctions,
|
GameFunctions gameFunctions,
|
||||||
IClientState clientState,
|
IClientState clientState,
|
||||||
IGameGui gameGui,
|
IGameGui gameGui,
|
||||||
ILogger<DoGatherCollectable> logger) : ITask
|
ILogger<DoGatherCollectable> logger) : ITask, IRevisitAware
|
||||||
{
|
{
|
||||||
private GatheringController.GatheringRequest _currentRequest = null!;
|
private GatheringController.GatheringRequest _currentRequest = null!;
|
||||||
private GatheringNode _currentNode = null!;
|
private GatheringNode _currentNode = null!;
|
||||||
|
private bool _revisitRequired;
|
||||||
|
private bool _revisitTriggered;
|
||||||
private Queue<EAction>? _actionQueue;
|
private Queue<EAction>? _actionQueue;
|
||||||
|
|
||||||
public ITask With(GatheringController.GatheringRequest currentRequest, GatheringNode currentNode)
|
public ITask With(GatheringController.GatheringRequest currentRequest, GatheringNode currentNode, bool revisitRequired)
|
||||||
{
|
{
|
||||||
_currentRequest = currentRequest;
|
_currentRequest = currentRequest;
|
||||||
_currentNode = currentNode;
|
_currentNode = currentNode;
|
||||||
|
_revisitRequired = revisitRequired;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,8 +37,17 @@ internal sealed class DoGatherCollectable(
|
|||||||
|
|
||||||
public unsafe ETaskResult Update()
|
public unsafe ETaskResult Update()
|
||||||
{
|
{
|
||||||
if (gatheringController.HasNodeDisappeared(_currentNode))
|
if (_revisitRequired && !_revisitTriggered)
|
||||||
|
{
|
||||||
|
logger.LogInformation("No revisit");
|
||||||
return ETaskResult.TaskComplete;
|
return ETaskResult.TaskComplete;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gatheringController.HasNodeDisappeared(_currentNode))
|
||||||
|
{
|
||||||
|
logger.LogInformation("Node disappeared");
|
||||||
|
return ETaskResult.TaskComplete;
|
||||||
|
}
|
||||||
|
|
||||||
if (gatheringController.HasRequestedItems())
|
if (gatheringController.HasRequestedItems())
|
||||||
{
|
{
|
||||||
@ -150,8 +162,13 @@ internal sealed class DoGatherCollectable(
|
|||||||
return botanistAction;
|
return botanistAction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void OnRevisit()
|
||||||
|
{
|
||||||
|
_revisitTriggered = true;
|
||||||
|
}
|
||||||
|
|
||||||
public override string ToString() =>
|
public override string ToString() =>
|
||||||
$"DoGatherCollectable({SeIconChar.Collectible.ToIconString()} {_currentRequest.Collectability})";
|
$"DoGatherCollectable({SeIconChar.Collectible.ToIconString()} {_currentRequest.Collectability}){(_revisitRequired ? " if revist" : "")}";
|
||||||
|
|
||||||
[SuppressMessage("ReSharper", "NotAccessedPositionalProperty.Local")]
|
[SuppressMessage("ReSharper", "NotAccessedPositionalProperty.Local")]
|
||||||
private sealed record NodeCondition(
|
private sealed record NodeCondition(
|
||||||
|
6
Questionable/Controller/Steps/IRevisitAware.cs
Normal file
6
Questionable/Controller/Steps/IRevisitAware.cs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
namespace Questionable.Controller.Steps;
|
||||||
|
|
||||||
|
public interface IRevisitAware
|
||||||
|
{
|
||||||
|
void OnRevisit();
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user