forked from aly/qstbak
273 lines
6.5 KiB
C#
273 lines
6.5 KiB
C#
using System;
|
|
using Dalamud.Plugin.Services;
|
|
using LLib.Gear;
|
|
using Microsoft.Extensions.Logging;
|
|
using Questionable.Data;
|
|
using Questionable.Functions;
|
|
|
|
namespace Questionable.Controller.Steps.Common;
|
|
|
|
internal static class RepairGear
|
|
{
|
|
internal sealed record SelfRepairTask : ITask
|
|
{
|
|
public override string ToString()
|
|
{
|
|
return "SelfRepair";
|
|
}
|
|
}
|
|
|
|
internal sealed record MenderRepairTask(MenderNpc Npc) : ITask
|
|
{
|
|
public override string ToString()
|
|
{
|
|
return $"MenderRepair({Npc.DataId})";
|
|
}
|
|
}
|
|
|
|
internal enum EState
|
|
{
|
|
Interacting,
|
|
SelectingMenu,
|
|
OpeningWindow,
|
|
WaitingForAddon,
|
|
FiringRepair,
|
|
Confirming,
|
|
Settling,
|
|
Closing,
|
|
WaitingForClose
|
|
}
|
|
|
|
internal abstract class RepairExecutorBase<T>(IGameGui gameGui, ILogger logger) : TaskExecutor<T>(), IStoppableTaskExecutor, ITaskExecutor where T : class, ITask
|
|
{
|
|
private const long StateTimeoutMs = 5000L;
|
|
|
|
private const long SettleMs = 1000L;
|
|
|
|
private const long PollIntervalMs = 250L;
|
|
|
|
private const int MaxAttempts = 3;
|
|
|
|
private EState _state;
|
|
|
|
private long _stateDeadline;
|
|
|
|
private long _nextPollAt;
|
|
|
|
private int? _conditionBefore;
|
|
|
|
private int _attempt;
|
|
|
|
protected IGameGui GameGui { get; } = gameGui;
|
|
|
|
protected ILogger Logger { get; } = logger;
|
|
|
|
protected abstract EState InitialState { get; }
|
|
|
|
protected abstract bool UpdateCustomState(EState state);
|
|
|
|
protected sealed override bool Start()
|
|
{
|
|
_conditionBefore = GearConditionHelper.GetLowestConditionPercent();
|
|
_attempt = 1;
|
|
EnterState(InitialState);
|
|
_nextPollAt = 0L;
|
|
return true;
|
|
}
|
|
|
|
public sealed override ETaskResult Update()
|
|
{
|
|
long tickCount = Environment.TickCount64;
|
|
if (tickCount < _nextPollAt)
|
|
{
|
|
return ETaskResult.StillRunning;
|
|
}
|
|
_nextPollAt = tickCount + 250;
|
|
if (tickCount > _stateDeadline)
|
|
{
|
|
if (_state != EState.Settling)
|
|
{
|
|
Logger.LogWarning("Repair timed out in state {State} ({Diagnostic}), giving up", _state, RepairActions.DescribeRepairAddon(GameGui));
|
|
RepairActions.TryCloseRepairWindow(GameGui);
|
|
return Finish();
|
|
}
|
|
EnterState(EState.Closing);
|
|
}
|
|
switch (_state)
|
|
{
|
|
case EState.OpeningWindow:
|
|
if (RepairActions.TryOpenRepairWindow())
|
|
{
|
|
EnterState(EState.WaitingForAddon);
|
|
}
|
|
break;
|
|
case EState.WaitingForAddon:
|
|
if (RepairActions.IsRepairAddonReady(GameGui))
|
|
{
|
|
EnterState(EState.FiringRepair);
|
|
}
|
|
break;
|
|
case EState.FiringRepair:
|
|
if (RepairActions.TryRepairAll(GameGui))
|
|
{
|
|
EnterState(EState.Confirming);
|
|
}
|
|
break;
|
|
case EState.Confirming:
|
|
if (RepairActions.TryConfirmDialog(GameGui))
|
|
{
|
|
EnterState(EState.Settling);
|
|
}
|
|
else if (HasConditionImproved())
|
|
{
|
|
EnterState(EState.Settling);
|
|
}
|
|
else
|
|
{
|
|
RepairActions.TryRepairAll(GameGui);
|
|
}
|
|
break;
|
|
case EState.Closing:
|
|
if (RepairActions.TryCloseRepairWindow(GameGui))
|
|
{
|
|
EnterState(EState.WaitingForClose);
|
|
}
|
|
break;
|
|
case EState.WaitingForClose:
|
|
if (!RepairActions.IsRepairAddonReady(GameGui))
|
|
{
|
|
return Finish();
|
|
}
|
|
break;
|
|
default:
|
|
if (!UpdateCustomState(_state))
|
|
{
|
|
throw new TaskException($"Unhandled repair state {_state}");
|
|
}
|
|
break;
|
|
case EState.Settling:
|
|
break;
|
|
}
|
|
return ETaskResult.StillRunning;
|
|
}
|
|
|
|
protected void EnterState(EState state)
|
|
{
|
|
_state = state;
|
|
_stateDeadline = Environment.TickCount64 + ((state == EState.Settling) ? 1000 : 5000);
|
|
}
|
|
|
|
private bool HasConditionImproved()
|
|
{
|
|
int? conditionBefore = _conditionBefore;
|
|
if (conditionBefore.HasValue)
|
|
{
|
|
int valueOrDefault = conditionBefore.GetValueOrDefault();
|
|
conditionBefore = GearConditionHelper.GetLowestConditionPercent();
|
|
if (conditionBefore.HasValue)
|
|
{
|
|
int valueOrDefault2 = conditionBefore.GetValueOrDefault();
|
|
return valueOrDefault2 > valueOrDefault;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private ETaskResult Finish()
|
|
{
|
|
int? lowestConditionPercent = GearConditionHelper.GetLowestConditionPercent();
|
|
if (HasConditionImproved())
|
|
{
|
|
Logger.LogInformation("Repair complete, lowest condition {Before}% -> {After}%", _conditionBefore, lowestConditionPercent);
|
|
return ETaskResult.TaskComplete;
|
|
}
|
|
if (_attempt < 3)
|
|
{
|
|
_attempt++;
|
|
Logger.LogWarning("Repair pass ended without improving condition (still {After}%), retrying ({Attempt}/{Max})", lowestConditionPercent, _attempt, 3);
|
|
RepairActions.TryCloseRepairWindow(GameGui);
|
|
EnterState(InitialState);
|
|
return ETaskResult.StillRunning;
|
|
}
|
|
Logger.LogWarning("Repair finished without improving condition (still {After}%)", lowestConditionPercent);
|
|
return ETaskResult.TaskComplete;
|
|
}
|
|
|
|
public void StopNow()
|
|
{
|
|
RepairActions.TryCloseRepairWindow(GameGui);
|
|
}
|
|
}
|
|
|
|
internal sealed class SelfRepairExecutor : RepairExecutorBase<SelfRepairTask>
|
|
{
|
|
protected override EState InitialState => EState.OpeningWindow;
|
|
|
|
public SelfRepairExecutor(IGameGui gameGui, ILogger<SelfRepairExecutor> logger)
|
|
: base(gameGui, (ILogger)logger)
|
|
{
|
|
}
|
|
|
|
protected override bool UpdateCustomState(EState state)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public override bool ShouldInterruptOnDamage()
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
internal sealed class MenderRepairExecutor : RepairExecutorBase<MenderRepairTask>
|
|
{
|
|
protected override EState InitialState => EState.Interacting;
|
|
|
|
public MenderRepairExecutor(IGameGui gameGui, GameFunctions gameFunctions, IDataManager dataManager, ILogger<MenderRepairExecutor> logger)
|
|
{
|
|
_003CgameFunctions_003EP = gameFunctions;
|
|
_003CdataManager_003EP = dataManager;
|
|
base._002Ector(gameGui, (ILogger)logger);
|
|
}
|
|
|
|
protected override bool UpdateCustomState(EState state)
|
|
{
|
|
switch (state)
|
|
{
|
|
case EState.Interacting:
|
|
if (_003CgameFunctions_003EP.InteractWith(base.Task.Npc.DataId))
|
|
{
|
|
EnterState(EState.SelectingMenu);
|
|
}
|
|
return true;
|
|
case EState.SelectingMenu:
|
|
{
|
|
if (RepairActions.IsRepairAddonReady(base.GameGui))
|
|
{
|
|
EnterState(EState.FiringRepair);
|
|
return true;
|
|
}
|
|
int? num = RepairActions.FindRepairIndex(_003CdataManager_003EP, base.Task.Npc.DataId);
|
|
if (!num.HasValue)
|
|
{
|
|
base.Logger.LogWarning("NPC {DataId} has no repair menu entry", base.Task.Npc.DataId);
|
|
EnterState(EState.Closing);
|
|
return true;
|
|
}
|
|
if (RepairActions.TrySelectRepairFromMenu(base.GameGui, num.Value))
|
|
{
|
|
EnterState(EState.WaitingForAddon);
|
|
}
|
|
return true;
|
|
}
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public override bool ShouldInterruptOnDamage()
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|