1
0
Fork 0
forked from aly/qstbak
qstbak/Questionable/Questionable.Controller.Steps.Interactions/Duty.cs
2026-01-01 09:34:51 +10:00

375 lines
12 KiB
C#

using System;
using System.Collections.Generic;
using Dalamud.Game.ClientState.Conditions;
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.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;
namespace Questionable.Controller.Steps.Interactions;
internal static class Duty
{
internal sealed class Factory(AutoDutyIpc autoDutyIpc, Configuration configuration) : ITaskFactory
{
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
{
if (step.InteractionType != EInteractionType.Duty)
{
yield break;
}
ArgumentNullException.ThrowIfNull(step.DutyOptions, "step.DutyOptions");
if (autoDutyIpc.IsConfiguredToRunContent(step.DutyOptions))
{
AutoDutyIpc.DutyMode dutyMode = GetDutyMode(step.DutyOptions.ContentFinderConditionId, step.DutyOptions.DutyMode);
if (dutyMode == AutoDutyIpc.DutyMode.UnsyncRegular && (step.DutyOptions.DutyMode == EDutyMode.UnsyncParty || (!step.DutyOptions.DutyMode.HasValue && configuration.Duties.DutyModeOverrides.TryGetValue(step.DutyOptions.ContentFinderConditionId, out var value) && value == EDutyMode.UnsyncParty) || (!step.DutyOptions.DutyMode.HasValue && !configuration.Duties.DutyModeOverrides.ContainsKey(step.DutyOptions.ContentFinderConditionId) && configuration.Duties.DefaultDutyMode == EDutyMode.UnsyncParty)))
{
yield return new WaitForPartyTask();
}
yield return new StartAutoDutyTask(step.DutyOptions.ContentFinderConditionId, dutyMode);
yield return new WaitAutoDutyTask(step.DutyOptions.ContentFinderConditionId);
if (!QuestWorkUtils.HasCompletionFlags(step.CompletionQuestVariablesFlags))
{
yield return new WaitAtEnd.WaitNextStepOrSequence();
}
}
else if (!step.DutyOptions.LowPriority)
{
yield return new OpenDutyFinderTask(step.DutyOptions.ContentFinderConditionId);
}
}
private AutoDutyIpc.DutyMode GetDutyMode(uint cfcId, EDutyMode? stepDutyMode)
{
if (stepDutyMode.HasValue)
{
return ConvertToAutoDutyMode(stepDutyMode.Value);
}
if (configuration.Duties.DutyModeOverrides.TryGetValue(cfcId, out var value))
{
return ConvertToAutoDutyMode(value);
}
return ConvertToAutoDutyMode(configuration.Duties.DefaultDutyMode);
}
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 DateTime _lastWarningTime = DateTime.MinValue;
protected override bool Start()
{
logger.LogInformation("Waiting for party members before starting duty...");
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.LogInformation("Party detected with {MemberCount} members, proceeding with duty", memberCount);
return ETaskResult.TaskComplete;
}
if (DateTime.Now - _lastWarningTime > TimeSpan.FromSeconds(10L))
{
chatGui.Print("[Questionable] Waiting for party members before starting duty (Unsync Party mode)...", "Questionable", 576);
_lastWarningTime = DateTime.Now;
}
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, SendNotification.Executor sendNotificationExecutor) : TaskExecutor<StartAutoDutyTask>(), IStoppableTaskExecutor, ITaskExecutor
{
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");
}
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);
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.StillRunning;
}
return ETaskResult.TaskComplete;
}
public void StopNow()
{
autoDutyIpc.Stop();
}
public override bool ShouldInterruptOnDamage()
{
return false;
}
}
internal sealed record WaitAutoDutyTask(uint ContentFinderConditionId) : ITask
{
public override string ToString()
{
return $"Wait(AutoDuty, left instance {ContentFinderConditionId})";
}
}
internal sealed class WaitAutoDutyExecutor(AutoDutyIpc autoDutyIpc, TerritoryData territoryData, IClientState clientState) : TaskExecutor<WaitAutoDutyTask>(), IStoppableTaskExecutor, ITaskExecutor
{
protected override bool Start()
{
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 || !autoDutyIpc.IsStopped())
{
return ETaskResult.StillRunning;
}
return ETaskResult.TaskComplete;
}
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 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, ILogger<StartLevelingModeExecutor> logger) : TaskExecutor<StartLevelingModeTask>(), IStoppableTaskExecutor, ITaskExecutor
{
private DateTime _startTime;
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.LogInformation("Already in duty or queue, skipping start");
return true;
}
if (!autoDutyIpc.IsStopped())
{
logger.LogInformation("AutoDuty is already running, waiting for it");
_startTime = DateTime.Now;
return true;
}
autoDutyIpc.StartLevelingMode();
_startTime = DateTime.Now;
return true;
}
public override ETaskResult Update()
{
bool flag = condition[ConditionFlag.BoundByDuty];
bool flag2 = condition[ConditionFlag.InDutyQueue];
if (flag || flag2)
{
logger.LogInformation("AutoDuty started successfully (inDuty={InDuty}, inQueue={InQueue})", flag, flag2);
return ETaskResult.TaskComplete;
}
if (!autoDutyIpc.IsStopped())
{
return ETaskResult.StillRunning;
}
if (DateTime.Now - _startTime < TimeSpan.FromSeconds(10L))
{
return ETaskResult.StillRunning;
}
logger.LogError("AutoDuty failed to start leveling mode after 10 seconds");
return ETaskResult.TaskComplete;
}
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 DateTime _lastStatusMessage = DateTime.MinValue;
protected override bool Start()
{
_wasInDuty = false;
return true;
}
public override ETaskResult Update()
{
bool flag = condition[ConditionFlag.BoundByDuty];
bool flag2 = condition[ConditionFlag.InDutyQueue];
if (flag && !_wasInDuty)
{
logger.LogInformation("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.DisableLevelingMode();
return ETaskResult.TaskComplete;
}
if (autoDutyIpc.IsStopped() && !flag && !flag2 && _wasInDuty && DateTime.Now - _lastStatusMessage > TimeSpan.FromSeconds(30L))
{
int num = base.Task.RequiredLevel - b;
logger.LogInformation("Waiting for leveling mode to continue (current: {CurrentLevel}, need: {RequiredLevel}, {LevelsNeeded} more levels needed)", b, base.Task.RequiredLevel, num);
_lastStatusMessage = DateTime.Now;
}
return ETaskResult.StillRunning;
}
public void StopNow()
{
autoDutyIpc.Stop();
}
public override bool ShouldInterruptOnDamage()
{
return false;
}
}
}