717 lines
21 KiB
C#
717 lines
21 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using Dalamud.Game.ClientState.Conditions;
|
|
using Dalamud.Game.ClientState.Objects.SubKinds;
|
|
using Dalamud.Plugin.Services;
|
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
|
using FFXIVClientStructs.FFXIV.Client.Game.Group;
|
|
using LLib.Gear;
|
|
using Microsoft.Extensions.Logging;
|
|
using Questionable.Controller.CombatModules;
|
|
using Questionable.Controller.Steps.Common;
|
|
using Questionable.Controller.Steps.Shared;
|
|
using Questionable.Controller.Utils;
|
|
using Questionable.Data;
|
|
using Questionable.External;
|
|
using Questionable.Functions;
|
|
using Questionable.Model;
|
|
using Questionable.Model.Questing;
|
|
using Questionable.Notifications;
|
|
|
|
namespace Questionable.Controller.Steps.Interactions;
|
|
|
|
internal static class Duty
|
|
{
|
|
internal sealed class Factory(AutoDutyIpc autoDutyIpc, WrathComboModule wrathComboModule, RotationSolverRebornModule rsrModule, BossModIpc bossModIpc, Configuration configuration, TerritoryData territoryData, GearStatsCalculator gearStatsCalculator, IObjectTable objectTable, ILogger<Factory> logger) : ITaskFactory
|
|
{
|
|
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
|
{
|
|
if (step.InteractionType != EInteractionType.Duty)
|
|
{
|
|
yield break;
|
|
}
|
|
ArgumentNullException.ThrowIfNull(step.DutyOptions, "step.DutyOptions");
|
|
uint cfcId = step.DutyOptions.ContentFinderConditionId;
|
|
DutyRegistry.Duties.TryGetValue(cfcId, out DutyEntry value);
|
|
EDutyMode? registryDutyMode = value?.DutyMode;
|
|
if (autoDutyIpc.IsConfiguredToRunContent(step.DutyOptions))
|
|
{
|
|
EDutyMode effectiveEDutyMode = GetEffectiveEDutyMode(cfcId, registryDutyMode);
|
|
AutoDutyIpc.DutyMode dutyMode = ConvertToAutoDutyMode(effectiveEDutyMode);
|
|
if (dutyMode == AutoDutyIpc.DutyMode.UnsyncRegular && effectiveEDutyMode == EDutyMode.UnsyncParty)
|
|
{
|
|
yield return new WaitForPartyTask();
|
|
}
|
|
if (!autoDutyIpc.ManagesRotationPluginState())
|
|
{
|
|
if (wrathComboModule.IsAvailable())
|
|
{
|
|
yield return new EnableWrathForDutyTask();
|
|
}
|
|
else if (rsrModule.IsAvailable())
|
|
{
|
|
yield return new EnableRsrForDutyTask();
|
|
}
|
|
}
|
|
if (!autoDutyIpc.ManagesBossModAiSettings())
|
|
{
|
|
if (configuration.General.CombatModule == Configuration.ECombatModule.BossMod && bossModIpc.IsSupported())
|
|
{
|
|
yield return new EnableBossModForDutyTask();
|
|
}
|
|
else if (bossModIpc.IsSupported())
|
|
{
|
|
yield return new EnableBossModPassiveForDutyTask();
|
|
}
|
|
}
|
|
yield return new StartAutoDutyTask(cfcId, dutyMode);
|
|
yield return new WaitAutoDutyTask(cfcId);
|
|
yield return new DisableCombatPluginsForDutyTask();
|
|
if (!QuestWorkUtils.HasCompletionFlags(step.CompletionQuestVariablesFlags))
|
|
{
|
|
yield return new WaitAtEnd.WaitNextStepOrSequence();
|
|
}
|
|
}
|
|
else if ((object)value == null || !value.LowPriority)
|
|
{
|
|
yield return new OpenDutyFinderTask(cfcId);
|
|
}
|
|
}
|
|
|
|
private EDutyMode GetEffectiveEDutyMode(uint cfcId, EDutyMode? registryDutyMode)
|
|
{
|
|
if (registryDutyMode.HasValue)
|
|
{
|
|
return registryDutyMode.Value;
|
|
}
|
|
if (configuration.Duties.DutyModeOverrides.TryGetValue(cfcId, out var value))
|
|
{
|
|
return value;
|
|
}
|
|
EDutyMode defaultDutyMode = configuration.Duties.DefaultDutyMode;
|
|
if (defaultDutyMode == EDutyMode.Support && configuration.Duties.AutoUnsyncOverleveled && IsSafelyOverleveled(cfcId))
|
|
{
|
|
logger.LogInformation("Running duty {CfcId} unsynced, as the player is safely overleveled", cfcId);
|
|
return EDutyMode.UnsyncSolo;
|
|
}
|
|
return defaultDutyMode;
|
|
}
|
|
|
|
private unsafe bool IsSafelyOverleveled(uint cfcId)
|
|
{
|
|
if (!territoryData.TryGetContentFinderCondition(cfcId, out TerritoryData.ContentFinderConditionData contentFinderConditionData))
|
|
{
|
|
return false;
|
|
}
|
|
if (!contentFinderConditionData.IsDungeon || !contentFinderConditionData.AllowUndersized || contentFinderConditionData.ClassJobLevelSync == 0)
|
|
{
|
|
return false;
|
|
}
|
|
int num = (objectTable.LocalPlayer?.Level ?? 0) - contentFinderConditionData.ClassJobLevelSync;
|
|
if (num >= 15)
|
|
{
|
|
return true;
|
|
}
|
|
if (num < 10)
|
|
{
|
|
return false;
|
|
}
|
|
InventoryManager* ptr = InventoryManager.Instance();
|
|
if (ptr == null)
|
|
{
|
|
return false;
|
|
}
|
|
InventoryContainer* inventoryContainer = ptr->GetInventoryContainer(InventoryType.EquippedItems);
|
|
if (inventoryContainer == null)
|
|
{
|
|
return false;
|
|
}
|
|
return gearStatsCalculator.CalculateAverageItemLevel(inventoryContainer) - contentFinderConditionData.RequiredItemLevel >= 100;
|
|
}
|
|
|
|
private static AutoDutyIpc.DutyMode ConvertToAutoDutyMode(EDutyMode mode)
|
|
{
|
|
return mode switch
|
|
{
|
|
EDutyMode.Support => AutoDutyIpc.DutyMode.Support,
|
|
EDutyMode.UnsyncSolo => AutoDutyIpc.DutyMode.UnsyncRegular,
|
|
EDutyMode.UnsyncParty => AutoDutyIpc.DutyMode.UnsyncRegular,
|
|
_ => AutoDutyIpc.DutyMode.Support,
|
|
};
|
|
}
|
|
}
|
|
|
|
internal sealed record WaitForPartyTask : ITask
|
|
{
|
|
public override string ToString()
|
|
{
|
|
return "WaitForParty";
|
|
}
|
|
}
|
|
|
|
internal sealed class WaitForPartyExecutor(IChatGui chatGui, ILogger<WaitForPartyExecutor> logger) : TaskExecutor<WaitForPartyTask>()
|
|
{
|
|
private long _warnNextMs;
|
|
|
|
private long _startMs;
|
|
|
|
protected override bool Start()
|
|
{
|
|
logger.LogDebug("Waiting for party members before starting duty...");
|
|
_startMs = Environment.TickCount64;
|
|
return true;
|
|
}
|
|
|
|
public unsafe override ETaskResult Update()
|
|
{
|
|
GroupManager* ptr = GroupManager.Instance();
|
|
if (ptr == null)
|
|
{
|
|
return ETaskResult.StillRunning;
|
|
}
|
|
byte memberCount = ptr->MainGroup.MemberCount;
|
|
bool isAlliance = ptr->MainGroup.IsAlliance;
|
|
if (memberCount > 1 || isAlliance)
|
|
{
|
|
logger.LogDebug("Party detected with {MemberCount} members, proceeding with duty", memberCount);
|
|
return ETaskResult.TaskComplete;
|
|
}
|
|
long tickCount = Environment.TickCount64;
|
|
if (tickCount - _startMs > 300000)
|
|
{
|
|
logger.LogError("No party members joined within 5 minutes, stopping");
|
|
chatGui.PrintError("No party members joined within 5 minutes, stopping.", "Questionable", 576);
|
|
return ETaskResult.End;
|
|
}
|
|
if (tickCount >= _warnNextMs)
|
|
{
|
|
chatGui.Print("[Questionable] Waiting for party members before starting duty (Unsync Party mode)...", "Questionable", 576);
|
|
_warnNextMs = tickCount + 10000;
|
|
}
|
|
return ETaskResult.StillRunning;
|
|
}
|
|
|
|
public override bool ShouldInterruptOnDamage()
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
internal sealed record StartAutoDutyTask(uint ContentFinderConditionId, AutoDutyIpc.DutyMode DutyMode) : ITask
|
|
{
|
|
public override string ToString()
|
|
{
|
|
return $"StartAutoDuty({ContentFinderConditionId}, {DutyMode})";
|
|
}
|
|
}
|
|
|
|
internal sealed class StartAutoDutyExecutor(GearStatsCalculator gearStatsCalculator, AutoDutyIpc autoDutyIpc, TerritoryData territoryData, IClientState clientState, IChatGui chatGui, ILogger<StartAutoDutyExecutor> logger, SendNotification.Executor sendNotificationExecutor) : TaskExecutor<StartAutoDutyTask>(), IStoppableTaskExecutor, ITaskExecutor
|
|
{
|
|
private long _startMs;
|
|
|
|
private bool _runAccepted;
|
|
|
|
protected unsafe override bool Start()
|
|
{
|
|
if (!territoryData.TryGetContentFinderCondition(base.Task.ContentFinderConditionId, out TerritoryData.ContentFinderConditionData contentFinderConditionData))
|
|
{
|
|
throw new TaskException("Failed to get territory ID for content finder condition");
|
|
}
|
|
if (base.Task.DutyMode != AutoDutyIpc.DutyMode.UnsyncRegular)
|
|
{
|
|
InventoryManager* intPtr = InventoryManager.Instance();
|
|
if (intPtr == null)
|
|
{
|
|
throw new TaskException("Inventory unavailable");
|
|
}
|
|
InventoryContainer* inventoryContainer = intPtr->GetInventoryContainer(InventoryType.EquippedItems);
|
|
if (inventoryContainer == null)
|
|
{
|
|
throw new TaskException("Equipped items unavailable");
|
|
}
|
|
short num = gearStatsCalculator.CalculateAverageItemLevel(inventoryContainer);
|
|
if (contentFinderConditionData.RequiredItemLevel > num)
|
|
{
|
|
string text = $"Could not use AutoDuty to queue for {contentFinderConditionData.Name}, required item level: {contentFinderConditionData.RequiredItemLevel}, current item level: {num}.";
|
|
if (!sendNotificationExecutor.Start(new SendNotification.Task(EInteractionType.Duty, text)))
|
|
{
|
|
chatGui.PrintError(text, "Questionable", 576);
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
autoDutyIpc.StartInstance(base.Task.ContentFinderConditionId, base.Task.DutyMode);
|
|
_startMs = Environment.TickCount64;
|
|
_runAccepted = false;
|
|
return true;
|
|
}
|
|
|
|
public override ETaskResult Update()
|
|
{
|
|
if (!territoryData.TryGetContentFinderCondition(base.Task.ContentFinderConditionId, out TerritoryData.ContentFinderConditionData contentFinderConditionData))
|
|
{
|
|
throw new TaskException("Failed to get territory ID for content finder condition");
|
|
}
|
|
if (clientState.TerritoryType == contentFinderConditionData.TerritoryId)
|
|
{
|
|
return ETaskResult.TaskComplete;
|
|
}
|
|
if (!autoDutyIpc.IsStopped())
|
|
{
|
|
_runAccepted = true;
|
|
return ETaskResult.StillRunning;
|
|
}
|
|
if (!_runAccepted)
|
|
{
|
|
logger.LogError("AutoDuty did not start duty {CfcId} (territory {TerritoryId}) - Run was rejected, check the AutoDuty log [{Installation}]", base.Task.ContentFinderConditionId, contentFinderConditionData.TerritoryId, autoDutyIpc.DescribeInstallation());
|
|
chatGui.PrintError("AutoDuty did not start the duty (rejected the run), check the AutoDuty log.", "Questionable", 576);
|
|
return ETaskResult.End;
|
|
}
|
|
if (Environment.TickCount64 - _startMs < 10000)
|
|
{
|
|
return ETaskResult.StillRunning;
|
|
}
|
|
logger.LogError("AutoDuty stopped without entering duty {CfcId} after 10 seconds", base.Task.ContentFinderConditionId);
|
|
chatGui.PrintError("AutoDuty failed to start the duty, stopping.", "Questionable", 576);
|
|
return ETaskResult.End;
|
|
}
|
|
|
|
public void StopNow()
|
|
{
|
|
autoDutyIpc.Stop();
|
|
}
|
|
|
|
public override bool ShouldInterruptOnDamage()
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
internal sealed record WaitAutoDutyTask(uint ContentFinderConditionId) : IDutyTask, ITask
|
|
{
|
|
public override string ToString()
|
|
{
|
|
return $"Wait(AutoDuty, left instance {ContentFinderConditionId})";
|
|
}
|
|
}
|
|
|
|
internal sealed class WaitAutoDutyExecutor(AutoDutyIpc autoDutyIpc, TerritoryData territoryData, IClientState clientState, IChatGui chatGui, ILogger<WaitAutoDutyExecutor> logger, NotificationService notificationService) : TaskExecutor<WaitAutoDutyTask>(), IStoppableTaskExecutor, ITaskExecutor
|
|
{
|
|
private long _stoppedInsideMs = -1L;
|
|
|
|
protected override bool Start()
|
|
{
|
|
_stoppedInsideMs = -1L;
|
|
return true;
|
|
}
|
|
|
|
public override ETaskResult Update()
|
|
{
|
|
if (!territoryData.TryGetContentFinderCondition(base.Task.ContentFinderConditionId, out TerritoryData.ContentFinderConditionData contentFinderConditionData))
|
|
{
|
|
throw new TaskException("Failed to get territory ID for content finder condition");
|
|
}
|
|
bool flag = clientState.TerritoryType != contentFinderConditionData.TerritoryId;
|
|
if (flag && autoDutyIpc.IsStopped())
|
|
{
|
|
notificationService.Notify(EInteractionType.Duty, "Duty complete - " + contentFinderConditionData.Name);
|
|
return ETaskResult.TaskComplete;
|
|
}
|
|
if (!flag && autoDutyIpc.IsStopped())
|
|
{
|
|
long tickCount = Environment.TickCount64;
|
|
if (_stoppedInsideMs < 0)
|
|
{
|
|
_stoppedInsideMs = tickCount;
|
|
}
|
|
if (tickCount - _stoppedInsideMs > 60000)
|
|
{
|
|
logger.LogError("AutoDuty stopped inside duty {CfcId} and did not progress within 60s", base.Task.ContentFinderConditionId);
|
|
chatGui.PrintError("AutoDuty stopped inside the duty, stopping.", "Questionable", 576);
|
|
return ETaskResult.End;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_stoppedInsideMs = -1L;
|
|
}
|
|
return ETaskResult.StillRunning;
|
|
}
|
|
|
|
public void StopNow()
|
|
{
|
|
autoDutyIpc.Stop();
|
|
}
|
|
|
|
public override bool ShouldInterruptOnDamage()
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
internal sealed record OpenDutyFinderTask(uint ContentFinderConditionId) : ITask
|
|
{
|
|
public override string ToString()
|
|
{
|
|
return $"OpenDutyFinder({ContentFinderConditionId})";
|
|
}
|
|
}
|
|
|
|
internal sealed class OpenDutyFinderExecutor(GameFunctions gameFunctions, ICondition condition) : TaskExecutor<OpenDutyFinderTask>()
|
|
{
|
|
protected override bool Start()
|
|
{
|
|
if (condition[ConditionFlag.InDutyQueue])
|
|
{
|
|
return false;
|
|
}
|
|
gameFunctions.OpenDutyFinder(base.Task.ContentFinderConditionId);
|
|
return true;
|
|
}
|
|
|
|
public override ETaskResult Update()
|
|
{
|
|
return ETaskResult.TaskComplete;
|
|
}
|
|
|
|
public override bool ShouldInterruptOnDamage()
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
internal sealed record EnableBossModForDutyTask : ITask
|
|
{
|
|
public override string ToString()
|
|
{
|
|
return "EnableBossModForDuty";
|
|
}
|
|
}
|
|
|
|
internal sealed class EnableBossModForDutyExecutor(BossModIpc bossModIpc, IObjectTable objectTable, ILogger<EnableBossModForDutyExecutor> logger) : TaskExecutor<EnableBossModForDutyTask>()
|
|
{
|
|
protected override bool Start()
|
|
{
|
|
bossModIpc.EnableAi(BossModIpc.EPreset.Active);
|
|
IPlayerCharacter localPlayer = objectTable.LocalPlayer;
|
|
bool flag;
|
|
if (localPlayer != null)
|
|
{
|
|
byte? b = localPlayer.ClassJob.ValueNullable?.Role;
|
|
if (b.HasValue)
|
|
{
|
|
byte valueOrDefault = b.GetValueOrDefault();
|
|
if ((uint)(valueOrDefault - 3) <= 1u)
|
|
{
|
|
flag = true;
|
|
goto IL_0077;
|
|
}
|
|
}
|
|
flag = false;
|
|
goto IL_0077;
|
|
}
|
|
goto IL_0087;
|
|
IL_0077:
|
|
bool isRanged = flag;
|
|
bossModIpc.SetRangeStrategy(BossModIpc.EPreset.Active, isRanged);
|
|
goto IL_0087;
|
|
IL_0087:
|
|
logger.LogDebug("Enabled BossMod Active preset for AutoDuty run");
|
|
return true;
|
|
}
|
|
|
|
public override ETaskResult Update()
|
|
{
|
|
return ETaskResult.TaskComplete;
|
|
}
|
|
|
|
public override bool ShouldInterruptOnDamage()
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
internal sealed record EnableRsrForDutyTask : ITask
|
|
{
|
|
public override string ToString()
|
|
{
|
|
return "EnableRsrForDuty";
|
|
}
|
|
}
|
|
|
|
internal sealed class EnableRsrForDutyExecutor(RotationSolverRebornModule rsrModule, ILogger<EnableRsrForDutyExecutor> logger) : TaskExecutor<EnableRsrForDutyTask>()
|
|
{
|
|
protected override bool Start()
|
|
{
|
|
if (rsrModule.StartForDuty())
|
|
{
|
|
logger.LogDebug("Enabled RSR Henched mode for AutoDuty run");
|
|
return true;
|
|
}
|
|
logger.LogWarning("Failed to enable RSR for AutoDuty run");
|
|
return true;
|
|
}
|
|
|
|
public override ETaskResult Update()
|
|
{
|
|
return ETaskResult.TaskComplete;
|
|
}
|
|
|
|
public override bool ShouldInterruptOnDamage()
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
internal sealed record EnableBossModPassiveForDutyTask : ITask
|
|
{
|
|
public override string ToString()
|
|
{
|
|
return "EnableBossModPassiveForDuty";
|
|
}
|
|
}
|
|
|
|
internal sealed class EnableBossModPassiveForDutyExecutor(BossModIpc bossModIpc, ILogger<EnableBossModPassiveForDutyExecutor> logger) : TaskExecutor<EnableBossModPassiveForDutyTask>()
|
|
{
|
|
protected override bool Start()
|
|
{
|
|
bossModIpc.EnableAi(BossModIpc.EPreset.Passive);
|
|
logger.LogDebug("Enabled BossMod Passive preset for AutoDuty run (hazard avoidance)");
|
|
return true;
|
|
}
|
|
|
|
public override ETaskResult Update()
|
|
{
|
|
return ETaskResult.TaskComplete;
|
|
}
|
|
|
|
public override bool ShouldInterruptOnDamage()
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
internal sealed record EnableWrathForDutyTask : ITask
|
|
{
|
|
public override string ToString()
|
|
{
|
|
return "EnableWrathForDuty";
|
|
}
|
|
}
|
|
|
|
internal sealed class EnableWrathForDutyExecutor(WrathComboModule wrathComboModule, ILogger<EnableWrathForDutyExecutor> logger) : TaskExecutor<EnableWrathForDutyTask>()
|
|
{
|
|
protected override bool Start()
|
|
{
|
|
CombatController.CombatData combatData = new CombatController.CombatData
|
|
{
|
|
ElementId = null,
|
|
Sequence = 0,
|
|
CompletionQuestVariablesFlags = new List<QuestWorkValue>(),
|
|
SpawnType = EEnemySpawnType.None,
|
|
KillEnemyDataIds = new List<uint>(),
|
|
ComplexCombatDatas = new List<ComplexCombatData>(),
|
|
CombatItemUse = null
|
|
};
|
|
if (wrathComboModule.Start(combatData))
|
|
{
|
|
logger.LogDebug("Enabled WrathCombo for AutoDuty run");
|
|
return true;
|
|
}
|
|
logger.LogWarning("Failed to enable WrathCombo for AutoDuty run");
|
|
return true;
|
|
}
|
|
|
|
public override ETaskResult Update()
|
|
{
|
|
return ETaskResult.TaskComplete;
|
|
}
|
|
|
|
public override bool ShouldInterruptOnDamage()
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
internal sealed record DisableCombatPluginsForDutyTask : ITask
|
|
{
|
|
public override string ToString()
|
|
{
|
|
return "DisableCombatPluginsForDuty";
|
|
}
|
|
}
|
|
|
|
internal sealed class DisableCombatPluginsForDutyExecutor(WrathComboModule wrathComboModule, RotationSolverRebornModule rsrModule, BossModIpc bossModIpc, ILogger<DisableCombatPluginsForDutyExecutor> logger) : TaskExecutor<DisableCombatPluginsForDutyTask>()
|
|
{
|
|
protected override bool Start()
|
|
{
|
|
wrathComboModule.Stop();
|
|
rsrModule.Stop();
|
|
bossModIpc.DisableAi();
|
|
logger.LogDebug("Disabled combat plugins after AutoDuty run");
|
|
return true;
|
|
}
|
|
|
|
public override ETaskResult Update()
|
|
{
|
|
return ETaskResult.TaskComplete;
|
|
}
|
|
|
|
public override bool ShouldInterruptOnDamage()
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
internal sealed record StartLevelingModeTask(int RequiredLevel, string? QuestName) : ITask
|
|
{
|
|
public override string ToString()
|
|
{
|
|
return $"StartLevelingMode(target: Lv{RequiredLevel} for '{QuestName}')";
|
|
}
|
|
}
|
|
|
|
internal sealed class StartLevelingModeExecutor(AutoDutyIpc autoDutyIpc, ICondition condition, IChatGui chatGui, ILogger<StartLevelingModeExecutor> logger) : TaskExecutor<StartLevelingModeTask>(), IStoppableTaskExecutor, ITaskExecutor
|
|
{
|
|
private long _startMs;
|
|
|
|
protected override bool Start()
|
|
{
|
|
logger.LogInformation("Starting AutoDuty Leveling mode to reach level {RequiredLevel} for quest '{QuestName}'", base.Task.RequiredLevel, base.Task.QuestName);
|
|
if (condition[ConditionFlag.BoundByDuty] || condition[ConditionFlag.InDutyQueue])
|
|
{
|
|
logger.LogDebug("Already in duty or queue, skipping start");
|
|
return true;
|
|
}
|
|
if (!autoDutyIpc.IsStopped())
|
|
{
|
|
logger.LogDebug("AutoDuty is already running, waiting for it");
|
|
_startMs = Environment.TickCount64;
|
|
return true;
|
|
}
|
|
autoDutyIpc.StartLevelingMode();
|
|
_startMs = Environment.TickCount64;
|
|
return true;
|
|
}
|
|
|
|
public override ETaskResult Update()
|
|
{
|
|
bool flag = condition[ConditionFlag.BoundByDuty];
|
|
bool flag2 = condition[ConditionFlag.InDutyQueue];
|
|
if (flag || flag2)
|
|
{
|
|
logger.LogDebug("AutoDuty started successfully (inDuty={InDuty}, inQueue={InQueue})", flag, flag2);
|
|
return ETaskResult.TaskComplete;
|
|
}
|
|
if (!autoDutyIpc.IsStopped())
|
|
{
|
|
return ETaskResult.StillRunning;
|
|
}
|
|
if (Environment.TickCount64 - _startMs < 10000)
|
|
{
|
|
return ETaskResult.StillRunning;
|
|
}
|
|
logger.LogError("AutoDuty failed to start leveling mode after 10 seconds");
|
|
chatGui.PrintError("AutoDuty failed to start leveling mode, stopping.", "Questionable", 576);
|
|
return ETaskResult.End;
|
|
}
|
|
|
|
public void StopNow()
|
|
{
|
|
autoDutyIpc.Stop();
|
|
}
|
|
|
|
public override bool ShouldInterruptOnDamage()
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
internal sealed record WaitLevelingModeTask(int RequiredLevel) : ITask
|
|
{
|
|
public override string ToString()
|
|
{
|
|
return $"WaitLevelingMode(until Lv{RequiredLevel})";
|
|
}
|
|
}
|
|
|
|
internal sealed class WaitLevelingModeExecutor(AutoDutyIpc autoDutyIpc, IObjectTable objectTable, ICondition condition, IChatGui chatGui, ILogger<WaitLevelingModeExecutor> logger) : TaskExecutor<WaitLevelingModeTask>(), IStoppableTaskExecutor, ITaskExecutor
|
|
{
|
|
private bool _wasInDuty;
|
|
|
|
private long _statusLogNextMs;
|
|
|
|
private long _idleSinceMs = -1L;
|
|
|
|
private long _reissueNextMs;
|
|
|
|
protected override bool Start()
|
|
{
|
|
_wasInDuty = false;
|
|
_statusLogNextMs = 0L;
|
|
_idleSinceMs = -1L;
|
|
_reissueNextMs = 0L;
|
|
return true;
|
|
}
|
|
|
|
public override ETaskResult Update()
|
|
{
|
|
bool flag = condition[ConditionFlag.BoundByDuty];
|
|
bool flag2 = condition[ConditionFlag.InDutyQueue];
|
|
if (flag && !_wasInDuty)
|
|
{
|
|
logger.LogDebug("Entered duty for leveling");
|
|
_wasInDuty = true;
|
|
}
|
|
byte b = objectTable.LocalPlayer?.Level ?? 0;
|
|
if (b >= base.Task.RequiredLevel)
|
|
{
|
|
logger.LogInformation("Reached required level {RequiredLevel} (current: {CurrentLevel})", base.Task.RequiredLevel, b);
|
|
chatGui.Print($"Reached level {b}, can now continue MSQ.", "Questionable", 576);
|
|
autoDutyIpc.Stop();
|
|
return ETaskResult.TaskComplete;
|
|
}
|
|
if (autoDutyIpc.IsStopped() && !flag && !flag2 && _wasInDuty)
|
|
{
|
|
long tickCount = Environment.TickCount64;
|
|
if (_idleSinceMs < 0)
|
|
{
|
|
_idleSinceMs = tickCount;
|
|
}
|
|
if (tickCount >= _statusLogNextMs)
|
|
{
|
|
int num = base.Task.RequiredLevel - b;
|
|
logger.LogDebug("Leveling between dungeons (current: {CurrentLevel}, need: {RequiredLevel}, {LevelsNeeded} more levels needed)", b, base.Task.RequiredLevel, num);
|
|
_statusLogNextMs = tickCount + 30000;
|
|
}
|
|
if (tickCount - _idleSinceMs > 90000)
|
|
{
|
|
logger.LogError("AutoDuty leveling stalled at level {CurrentLevel} (need {RequiredLevel}) and did not resume within 90s", b, base.Task.RequiredLevel);
|
|
chatGui.PrintError("AutoDuty leveling stalled and didn't resume, stopping.", "Questionable", 576);
|
|
return ETaskResult.End;
|
|
}
|
|
if (!condition[ConditionFlag.BetweenAreas] && !condition[ConditionFlag.BetweenAreas51] && tickCount >= _reissueNextMs)
|
|
{
|
|
logger.LogDebug("Starting next leveling dungeon");
|
|
autoDutyIpc.StartLevelingMode();
|
|
_reissueNextMs = tickCount + 5000;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_idleSinceMs = -1L;
|
|
}
|
|
return ETaskResult.StillRunning;
|
|
}
|
|
|
|
public void StopNow()
|
|
{
|
|
autoDutyIpc.Stop();
|
|
}
|
|
|
|
public override bool ShouldInterruptOnDamage()
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|