muffin v7.4.7

This commit is contained in:
alydev 2025-12-28 12:35:39 +10:00
parent 1cc65e495d
commit 63f975ff4f
16 changed files with 1659 additions and 939 deletions

View file

@ -252,4 +252,140 @@ internal static class Duty
return false; 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 bool _started;
private DateTime _startTime;
private DateTime _lastRetryTime = DateTime.MinValue;
protected override bool Start()
{
logger.LogInformation("Starting AutoDuty Leveling mode to reach level {RequiredLevel} for quest '{QuestName}'", base.Task.RequiredLevel, base.Task.QuestName);
_started = autoDutyIpc.StartLevelingMode();
_startTime = DateTime.Now;
return _started;
}
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 - _lastRetryTime > TimeSpan.FromSeconds(5L))
{
logger.LogWarning("AutoDuty stopped before entering duty, retrying...");
_started = autoDutyIpc.StartLevelingMode();
_lastRetryTime = DateTime.Now;
}
if (DateTime.Now - _startTime > TimeSpan.FromSeconds(60L))
{
logger.LogError("AutoDuty failed to start after 60 seconds");
return ETaskResult.TaskComplete;
}
return ETaskResult.StillRunning;
}
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;
private DateTime _idleStartTime = DateTime.MinValue;
protected override bool Start()
{
_wasInDuty = false;
_idleStartTime = DateTime.MinValue;
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;
_idleStartTime = DateTime.MinValue;
}
if (flag || flag2)
{
_idleStartTime = DateTime.MinValue;
}
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);
return ETaskResult.TaskComplete;
}
if (autoDutyIpc.IsStopped() && !flag && !flag2)
{
if (_idleStartTime == DateTime.MinValue)
{
_idleStartTime = DateTime.Now;
}
if (_wasInDuty && DateTime.Now - _idleStartTime > TimeSpan.FromSeconds(3L))
{
_ = base.Task.RequiredLevel;
_lastStatusMessage = DateTime.Now;
logger.LogInformation("Starting another leveling run (current: {CurrentLevel}, need: {RequiredLevel})", b, base.Task.RequiredLevel);
autoDutyIpc.StartLevelingMode();
_wasInDuty = false;
_idleStartTime = DateTime.MinValue;
}
}
return ETaskResult.StillRunning;
}
public void StopNow()
{
autoDutyIpc.Stop();
}
public override bool ShouldInterruptOnDamage()
{
return false;
}
}
} }

View file

@ -215,6 +215,10 @@ internal abstract class MiniTaskController<T> : IDisposable
public void OnErrorToast(ref SeString message, ref bool isHandled) public void OnErrorToast(ref SeString message, ref bool isHandled)
{ {
if (_taskQueue.AllTasksComplete)
{
return;
}
if (_taskQueue.CurrentTaskExecutor is IToastAware toastAware && toastAware.OnErrorToast(message)) if (_taskQueue.CurrentTaskExecutor is IToastAware toastAware && toastAware.OnErrorToast(message))
{ {
isHandled = true; isHandled = true;

View file

@ -16,6 +16,7 @@ using Questionable.Controller.Steps;
using Questionable.Controller.Steps.Interactions; using Questionable.Controller.Steps.Interactions;
using Questionable.Controller.Steps.Shared; using Questionable.Controller.Steps.Shared;
using Questionable.Data; using Questionable.Data;
using Questionable.External;
using Questionable.Functions; using Questionable.Functions;
using Questionable.Model; using Questionable.Model;
using Questionable.Model.Questing; using Questionable.Model.Questing;
@ -115,6 +116,8 @@ internal sealed class QuestController : MiniTaskController<QuestController>
private readonly SinglePlayerDutyConfigComponent _singlePlayerDutyConfigComponent; private readonly SinglePlayerDutyConfigComponent _singlePlayerDutyConfigComponent;
private readonly AutoDutyIpc _autoDutyIpc;
private readonly ILogger<QuestController> _logger; private readonly ILogger<QuestController> _logger;
private readonly object _progressLock = new object(); private readonly object _progressLock = new object();
@ -155,6 +158,8 @@ internal sealed class QuestController : MiniTaskController<QuestController>
private static readonly TimeSpan EscDoublePressWindow = TimeSpan.FromSeconds(1L); private static readonly TimeSpan EscDoublePressWindow = TimeSpan.FromSeconds(1L);
private HashSet<string> _stopConditionsMetAtStart = new HashSet<string>();
private const char ClipboardSeparator = ';'; private const char ClipboardSeparator = ';';
public EAutomationType AutomationType public EAutomationType AutomationType
@ -236,7 +241,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
public event AutomationTypeChangedEventHandler? AutomationTypeChanged; public event AutomationTypeChangedEventHandler? AutomationTypeChanged;
public QuestController(IClientState clientState, IObjectTable objectTable, GameFunctions gameFunctions, QuestFunctions questFunctions, MovementController movementController, CombatController combatController, GatheringController gatheringController, ILogger<QuestController> logger, QuestRegistry questRegistry, JournalData journalData, IKeyState keyState, IChatGui chatGui, ICondition condition, IToastGui toastGui, Configuration configuration, TaskCreator taskCreator, IServiceProvider serviceProvider, InterruptHandler interruptHandler, IDataManager dataManager, SinglePlayerDutyConfigComponent singlePlayerDutyConfigComponent) public QuestController(IClientState clientState, IObjectTable objectTable, GameFunctions gameFunctions, QuestFunctions questFunctions, MovementController movementController, CombatController combatController, GatheringController gatheringController, ILogger<QuestController> logger, QuestRegistry questRegistry, JournalData journalData, IKeyState keyState, IChatGui chatGui, ICondition condition, IToastGui toastGui, Configuration configuration, TaskCreator taskCreator, IServiceProvider serviceProvider, InterruptHandler interruptHandler, IDataManager dataManager, SinglePlayerDutyConfigComponent singlePlayerDutyConfigComponent, AutoDutyIpc autoDutyIpc)
: base(chatGui, condition, serviceProvider, interruptHandler, dataManager, logger) : base(chatGui, condition, serviceProvider, interruptHandler, dataManager, logger)
{ {
_clientState = clientState; _clientState = clientState;
@ -255,6 +260,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
_configuration = configuration; _configuration = configuration;
_taskCreator = taskCreator; _taskCreator = taskCreator;
_singlePlayerDutyConfigComponent = singlePlayerDutyConfigComponent; _singlePlayerDutyConfigComponent = singlePlayerDutyConfigComponent;
_autoDutyIpc = autoDutyIpc;
_logger = logger; _logger = logger;
_toastGui.ErrorToast += base.OnErrorToast; _toastGui.ErrorToast += base.OnErrorToast;
_toastGui.Toast += OnNormalToast; _toastGui.Toast += OnNormalToast;
@ -361,33 +367,46 @@ internal sealed class QuestController : MiniTaskController<QuestController>
if (_configuration.Stop.Enabled && _startedQuest != null) if (_configuration.Stop.Enabled && _startedQuest != null)
{ {
string text = _startedQuest.Quest.Id.ToString(); string text = _startedQuest.Quest.Id.ToString();
if (_configuration.Stop.LevelToStopAfter && IsRunning && _objectTable[0] is IPlayerCharacter playerCharacter && playerCharacter.Level >= _configuration.Stop.TargetLevel) if (_configuration.Stop.LevelStopMode != Configuration.EStopConditionMode.Off && IsRunning && _objectTable[0] is IPlayerCharacter playerCharacter && playerCharacter.Level >= _configuration.Stop.TargetLevel)
{ {
_logger.LogInformation("Reached level stop condition (current: {CurrentLevel}, target: {TargetLevel})", playerCharacter.Level, _configuration.Stop.TargetLevel); string item = $"level:{_configuration.Stop.TargetLevel}";
_chatGui.Print($"Character level {playerCharacter.Level} reached target level {_configuration.Stop.TargetLevel}.", "Questionable", 576); if (_configuration.Stop.LevelStopMode != Configuration.EStopConditionMode.Pause || !_stopConditionsMetAtStart.Contains(item))
Stop($"Level stop condition reached [{playerCharacter.Level}]"); {
return; _logger.LogInformation("Reached level stop condition (current: {CurrentLevel}, target: {TargetLevel}, mode: {Mode})", playerCharacter.Level, _configuration.Stop.TargetLevel, _configuration.Stop.LevelStopMode);
_chatGui.Print($"Character level {playerCharacter.Level} reached target level {_configuration.Stop.TargetLevel}.", "Questionable", 576);
Stop($"Level stop condition reached [{playerCharacter.Level}]");
return;
}
} }
if (_configuration.Stop.QuestSequences.TryGetValue(text, out var value) && value.HasValue) if (_configuration.Stop.QuestSequences.TryGetValue(text, out var value) && value.HasValue)
{ {
int sequence = _startedQuest.Sequence; int sequence = _startedQuest.Sequence;
if (sequence >= value.Value && IsRunning) if (sequence >= value.Value && IsRunning)
{ {
_logger.LogInformation("Reached quest-specific sequence stop condition (quest: {QuestId}, sequence: {CurrentSequence}, target: {TargetSequence})", _startedQuest.Quest.Id, sequence, value.Value); Configuration.EStopConditionMode valueOrDefault = _configuration.Stop.QuestStopModes.GetValueOrDefault(text, Configuration.EStopConditionMode.Pause);
_chatGui.Print($"Quest '{_startedQuest.Quest.Info.Name}' reached sequence {sequence}, configured stop sequence is {value.Value}.", "Questionable", 576); string item2 = $"questseq:{text}:{sequence}";
Stop($"Quest-specific sequence stop condition reached [{text}@{sequence}]"); if (valueOrDefault != Configuration.EStopConditionMode.Pause || !_stopConditionsMetAtStart.Contains(item2))
return; {
_logger.LogInformation("Reached quest-specific sequence stop condition (quest: {QuestId}, sequence: {CurrentSequence}, target: {TargetSequence})", _startedQuest.Quest.Id, sequence, value.Value);
_chatGui.Print($"Quest '{_startedQuest.Quest.Info.Name}' reached sequence {sequence}, configured stop sequence is {value.Value}.", "Questionable", 576);
Stop($"Quest-specific sequence stop condition reached [{text}@{sequence}]");
return;
}
} }
} }
else if (_configuration.Stop.SequenceToStopAfter && CurrentQuest != null) else if (_configuration.Stop.SequenceStopMode != Configuration.EStopConditionMode.Off && CurrentQuest != null)
{ {
int sequence2 = CurrentQuest.Sequence; int sequence2 = CurrentQuest.Sequence;
if (sequence2 >= _configuration.Stop.TargetSequence && IsRunning) if (sequence2 >= _configuration.Stop.TargetSequence && IsRunning)
{ {
_logger.LogInformation("Reached global quest sequence stop condition (sequence: {CurrentSequence}, target: {TargetSequence})", sequence2, _configuration.Stop.TargetSequence); string item3 = $"sequence:{text}:{sequence2}";
_chatGui.Print($"Quest sequence {sequence2} reached target sequence {_configuration.Stop.TargetSequence}.", "Questionable", 576); if (_configuration.Stop.SequenceStopMode != Configuration.EStopConditionMode.Pause || !_stopConditionsMetAtStart.Contains(item3))
Stop($"Sequence stop condition reached [{sequence2}]"); {
return; _logger.LogInformation("Reached global quest sequence stop condition (sequence: {CurrentSequence}, target: {TargetSequence}, mode: {Mode})", sequence2, _configuration.Stop.TargetSequence, _configuration.Stop.SequenceStopMode);
_chatGui.Print($"Quest sequence {sequence2} reached target sequence {_configuration.Stop.TargetSequence}.", "Questionable", 576);
Stop($"Sequence stop condition reached [{sequence2}]");
return;
}
} }
} }
} }
@ -402,17 +421,14 @@ internal sealed class QuestController : MiniTaskController<QuestController>
if (step == 0 || step == 255) if (step == 0 || step == 255)
{ {
flag2 = true; flag2 = true;
goto IL_0691; goto IL_07e7;
} }
} }
flag2 = false; flag2 = false;
goto IL_0691; goto IL_07e7;
} }
goto IL_0695; goto IL_07eb;
IL_0691: IL_07eb:
flag = flag2;
goto IL_0695;
IL_0695:
if (flag && DateTime.Now >= CurrentQuest.StepProgress.StartedAt.AddSeconds(15.0)) if (flag && DateTime.Now >= CurrentQuest.StepProgress.StartedAt.AddSeconds(15.0))
{ {
lock (_progressLock) lock (_progressLock)
@ -427,6 +443,10 @@ internal sealed class QuestController : MiniTaskController<QuestController>
CheckAutoRefreshCondition(); CheckAutoRefreshCondition();
UpdateCurrentTask(); UpdateCurrentTask();
} }
return;
IL_07e7:
flag = flag2;
goto IL_07eb;
} }
private void CheckAutoRefreshCondition() private void CheckAutoRefreshCondition()
@ -563,7 +583,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
int num = ManualPriorityQuests.RemoveAll((Quest q) => _questFunctions.IsQuestComplete(q.Id)); int num = ManualPriorityQuests.RemoveAll((Quest q) => _questFunctions.IsQuestComplete(q.Id));
if (num > 0) if (num > 0)
{ {
_logger.LogInformation("Removed {Count} completed priority quest(s)", num); _logger.LogInformation("Removed {Count} completed priority {QuestWord}", num, (num == 1) ? "quest" : "quests");
} }
} }
if (_pendingQuest != null) if (_pendingQuest != null)
@ -632,6 +652,19 @@ internal sealed class QuestController : MiniTaskController<QuestController>
} }
if (elementId == null || elementId.Value == 0) if (elementId == null || elementId.Value == 0)
{ {
(bool isLevelLocked, int levelsNeeded, int requiredLevel, string? questName) msqLevelLockInfo = _questFunctions.GetMsqLevelLockInfo();
bool item = msqLevelLockInfo.isLevelLocked;
int item2 = msqLevelLockInfo.levelsNeeded;
int item3 = msqLevelLockInfo.requiredLevel;
string item4 = msqLevelLockInfo.questName;
int currentPlayerLevel = (_objectTable[0] as IPlayerCharacter)?.Level ?? 0;
if (item && _autoDutyIpc.IsConfiguredToRunLevelingMode(currentPlayerLevel) && AutomationType == EAutomationType.Automatic && !_condition[ConditionFlag.BoundByDuty] && _taskQueue.AllTasksComplete)
{
_logger.LogInformation("MSQ '{QuestName}' requires level {RequiredLevel}, current level is {CurrentLevel} ({LevelsNeeded} levels needed). Starting AutoDuty Leveling mode.", item4, item3, item3 - item2, item2);
_taskQueue.Enqueue(new Duty.StartLevelingModeTask(item3, item4));
_taskQueue.Enqueue(new Duty.WaitLevelingModeTask(item3));
return;
}
if (_startedQuest != null) if (_startedQuest != null)
{ {
switch (mainScenarioQuestState) switch (mainScenarioQuestState)
@ -668,16 +701,28 @@ internal sealed class QuestController : MiniTaskController<QuestController>
_startedQuest = new QuestProgress(quest, b); _startedQuest = new QuestProgress(quest, b);
if (_objectTable[0] is IPlayerCharacter playerCharacter && playerCharacter.Level < quest.Info.Level) if (_objectTable[0] is IPlayerCharacter playerCharacter && playerCharacter.Level < quest.Info.Level)
{ {
_logger.LogInformation("Stopping automation, player level ({PlayerLevel}) < quest level ({QuestLevel}", playerCharacter.Level, quest.Info.Level); if (_autoDutyIpc.IsConfiguredToRunLevelingMode(playerCharacter.Level) && AutomationType == EAutomationType.Automatic && !_condition[ConditionFlag.BoundByDuty])
Stop("Quest level too high"); {
return; _logger.LogInformation("Player level ({PlayerLevel}) < quest level ({QuestLevel}), starting AutoDuty Leveling mode", playerCharacter.Level, quest.Info.Level);
ClearTasksInternal();
_taskQueue.Enqueue(new Duty.StartLevelingModeTask(quest.Info.Level, quest.Info.Name));
_taskQueue.Enqueue(new Duty.WaitLevelingModeTask(quest.Info.Level));
}
else
{
_logger.LogInformation("Stopping automation, player level ({PlayerLevel}) < quest level ({QuestLevel})", playerCharacter.Level, quest.Info.Level);
Stop("Quest level too high");
}
} }
if (AutomationType == EAutomationType.SingleQuestB) else
{ {
_logger.LogInformation("Single quest is finished"); if (AutomationType == EAutomationType.SingleQuestB)
AutomationType = EAutomationType.Manual; {
_logger.LogInformation("Single quest is finished");
AutomationType = EAutomationType.Manual;
}
CheckNextTasks("Different Quest");
} }
CheckNextTasks("Different Quest");
} }
else if (_startedQuest != null) else if (_startedQuest != null)
{ {
@ -853,6 +898,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
_nextQuest = null; _nextQuest = null;
_gatheringQuest = null; _gatheringQuest = null;
_lastTaskUpdate = DateTime.Now; _lastTaskUpdate = DateTime.Now;
_stopConditionsMetAtStart.Clear();
ResetAutoRefreshState(); ResetAutoRefreshState();
} }
} }
@ -967,6 +1013,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
{ {
if (!CheckAndBlockForStopConditions()) if (!CheckAndBlockForStopConditions())
{ {
RecordStopConditionsMetAtStart();
AutomationType = EAutomationType.Automatic; AutomationType = EAutomationType.Automatic;
ExecuteNextStep(); ExecuteNextStep();
} }
@ -979,6 +1026,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
{ {
if (!CheckAndBlockForStopConditions()) if (!CheckAndBlockForStopConditions())
{ {
RecordStopConditionsMetAtStart();
AutomationType = EAutomationType.GatheringOnly; AutomationType = EAutomationType.GatheringOnly;
ExecuteNextStep(); ExecuteNextStep();
} }
@ -991,6 +1039,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
{ {
if (!CheckAndBlockForStopConditions()) if (!CheckAndBlockForStopConditions())
{ {
RecordStopConditionsMetAtStart();
AutomationType = EAutomationType.SingleQuestA; AutomationType = EAutomationType.SingleQuestA;
ExecuteNextStep(); ExecuteNextStep();
} }
@ -1006,13 +1055,53 @@ internal sealed class QuestController : MiniTaskController<QuestController>
} }
} }
private void RecordStopConditionsMetAtStart()
{
_stopConditionsMetAtStart.Clear();
if (!_configuration.Stop.Enabled)
{
return;
}
if (_configuration.Stop.LevelStopMode == Configuration.EStopConditionMode.Pause && _objectTable[0] is IPlayerCharacter playerCharacter && playerCharacter.Level >= _configuration.Stop.TargetLevel)
{
_stopConditionsMetAtStart.Add($"level:{_configuration.Stop.TargetLevel}");
_logger.LogDebug("Recording level stop condition as already met at start: {Level}", _configuration.Stop.TargetLevel);
}
if (_configuration.Stop.SequenceStopMode == Configuration.EStopConditionMode.Pause && _startedQuest != null)
{
string text = _startedQuest.Quest.Id.ToString();
if (!_configuration.Stop.QuestSequences.ContainsKey(text))
{
int sequence = _startedQuest.Sequence;
if (sequence >= _configuration.Stop.TargetSequence)
{
_stopConditionsMetAtStart.Add($"sequence:{text}:{sequence}");
_logger.LogDebug("Recording global sequence stop condition as already met at start: quest {QuestId}, sequence {Sequence}", text, sequence);
}
}
}
foreach (ElementId item in _configuration.Stop.QuestsToStopAfter)
{
string text2 = item.ToString();
if (_configuration.Stop.QuestStopModes.GetValueOrDefault(text2, Configuration.EStopConditionMode.Pause) == Configuration.EStopConditionMode.Pause && _configuration.Stop.QuestSequences.TryGetValue(text2, out var value) && value.HasValue && _questFunctions.IsQuestAccepted(item))
{
QuestProgressInfo questProgressInfo = _questFunctions.GetQuestProgressInfo(item);
if (questProgressInfo != null && questProgressInfo.Sequence >= value.Value)
{
_stopConditionsMetAtStart.Add($"questseq:{text2}:{questProgressInfo.Sequence}");
_logger.LogDebug("Recording quest sequence stop condition as already met at start: quest {QuestId}, sequence {Sequence}", text2, questProgressInfo.Sequence);
}
}
}
}
private bool CheckAndBlockForStopConditions() private bool CheckAndBlockForStopConditions()
{ {
if (!_configuration.Stop.Enabled) if (!_configuration.Stop.Enabled)
{ {
return false; return false;
} }
if (_configuration.Stop.LevelToStopAfter && _objectTable[0] is IPlayerCharacter playerCharacter && playerCharacter.Level >= _configuration.Stop.TargetLevel) if (_configuration.Stop.LevelStopMode == Configuration.EStopConditionMode.Stop && _objectTable[0] is IPlayerCharacter playerCharacter && playerCharacter.Level >= _configuration.Stop.TargetLevel)
{ {
_logger.LogInformation("Blocking start: Level stop condition already met (current: {CurrentLevel}, target: {TargetLevel})", playerCharacter.Level, _configuration.Stop.TargetLevel); _logger.LogInformation("Blocking start: Level stop condition already met (current: {CurrentLevel}, target: {TargetLevel})", playerCharacter.Level, _configuration.Stop.TargetLevel);
_chatGui.Print($"Cannot start: Character level {playerCharacter.Level} has reached target level {_configuration.Stop.TargetLevel}.", "Questionable", 576); _chatGui.Print($"Cannot start: Character level {playerCharacter.Level} has reached target level {_configuration.Stop.TargetLevel}.", "Questionable", 576);
@ -1021,6 +1110,10 @@ internal sealed class QuestController : MiniTaskController<QuestController>
foreach (ElementId item in _configuration.Stop.QuestsToStopAfter) foreach (ElementId item in _configuration.Stop.QuestsToStopAfter)
{ {
string key = item.ToString(); string key = item.ToString();
if (_configuration.Stop.QuestStopModes.GetValueOrDefault(key, Configuration.EStopConditionMode.Pause) != Configuration.EStopConditionMode.Stop)
{
continue;
}
if (_configuration.Stop.QuestSequences.TryGetValue(key, out var value) && value.HasValue) if (_configuration.Stop.QuestSequences.TryGetValue(key, out var value) && value.HasValue)
{ {
if (!_questFunctions.IsQuestAccepted(item)) if (!_questFunctions.IsQuestAccepted(item))
@ -1048,7 +1141,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
return true; return true;
} }
} }
if (_configuration.Stop.SequenceToStopAfter && _startedQuest != null) if (_configuration.Stop.SequenceStopMode == Configuration.EStopConditionMode.Stop && _startedQuest != null)
{ {
string key2 = _startedQuest.Quest.Id.ToString(); string key2 = _startedQuest.Quest.Id.ToString();
if (!_configuration.Stop.QuestSequences.ContainsKey(key2)) if (!_configuration.Stop.QuestSequences.ContainsKey(key2))
@ -1075,6 +1168,23 @@ internal sealed class QuestController : MiniTaskController<QuestController>
var (questSequence, step, flag) = GetNextStep(); var (questSequence, step, flag) = GetNextStep();
if (CurrentQuest == null || questSequence == null) if (CurrentQuest == null || questSequence == null)
{ {
_logger.LogDebug("ExecuteNextStep: No current quest or sequence. Checking leveling mode conditions.");
if (AutomationType == EAutomationType.Automatic && !_condition[ConditionFlag.BoundByDuty])
{
(bool isLevelLocked, int levelsNeeded, int requiredLevel, string? questName) msqLevelLockInfo = _questFunctions.GetMsqLevelLockInfo();
bool item = msqLevelLockInfo.isLevelLocked;
int item2 = msqLevelLockInfo.levelsNeeded;
int item3 = msqLevelLockInfo.requiredLevel;
string item4 = msqLevelLockInfo.questName;
int currentPlayerLevel = (_objectTable[0] as IPlayerCharacter)?.Level ?? 0;
if (item && _autoDutyIpc.IsConfiguredToRunLevelingMode(currentPlayerLevel))
{
_logger.LogInformation("MSQ '{QuestName}' requires level {RequiredLevel}, current level is {CurrentLevel} ({LevelsNeeded} levels needed). Starting AutoDuty Leveling mode.", item4, item3, item3 - item2, item2);
_taskQueue.Enqueue(new Duty.StartLevelingModeTask(item3, item4));
_taskQueue.Enqueue(new Duty.WaitLevelingModeTask(item3));
return;
}
}
if (CurrentQuestDetails?.Progress.Quest.Id is SatisfactionSupplyNpcId && CurrentQuestDetails?.Progress.Sequence == 1) if (CurrentQuestDetails?.Progress.Quest.Id is SatisfactionSupplyNpcId && CurrentQuestDetails?.Progress.Sequence == 1)
{ {
(QuestProgress, ECurrentQuestType)? currentQuestDetails = CurrentQuestDetails; (QuestProgress, ECurrentQuestType)? currentQuestDetails = CurrentQuestDetails;
@ -1086,23 +1196,23 @@ internal sealed class QuestController : MiniTaskController<QuestController>
_logger.LogInformation("Completed delivery quest"); _logger.LogInformation("Completed delivery quest");
SetGatheringQuest(null); SetGatheringQuest(null);
Stop("Gathering quest complete"); Stop("Gathering quest complete");
goto IL_01dc; goto IL_02d0;
} }
} }
} }
_logger.LogWarning("Could not retrieve next quest step, not doing anything [{QuestId}, {Sequence}, {Step}]", CurrentQuest?.Quest.Id, CurrentQuest?.Sequence, CurrentQuest?.Step); _logger.LogWarning("Could not retrieve next quest step, not doing anything [{QuestId}, {Sequence}, {Step}]", CurrentQuest?.Quest.Id, CurrentQuest?.Sequence, CurrentQuest?.Step);
goto IL_01dc; goto IL_02d0;
} }
goto IL_01e8; goto IL_02dc;
IL_01e8: IL_02dc:
_movementController.Stop(); _movementController.Stop();
_combatController.Stop("Execute next step"); _combatController.Stop("Execute next step");
_gatheringController.Stop("Execute next step"); _gatheringController.Stop("Execute next step");
try try
{ {
foreach (ITask item in _taskCreator.CreateTasks(CurrentQuest.Quest, CurrentQuest.Sequence, questSequence, step)) foreach (ITask item5 in _taskCreator.CreateTasks(CurrentQuest.Quest, CurrentQuest.Sequence, questSequence, step))
{ {
_taskQueue.Enqueue(item); _taskQueue.Enqueue(item5);
} }
ResetAutoRefreshState(); ResetAutoRefreshState();
return; return;
@ -1114,12 +1224,12 @@ internal sealed class QuestController : MiniTaskController<QuestController>
Stop("Tasks failed to create"); Stop("Tasks failed to create");
return; return;
} }
IL_01dc: IL_02d0:
if (CurrentQuest == null || !flag) if (CurrentQuest == null || !flag)
{ {
return; return;
} }
goto IL_01e8; goto IL_02dc;
} }
public string ToStatString() public string ToStatString()

File diff suppressed because it is too large Load diff

View file

@ -35,6 +35,10 @@ internal sealed class AutoDutyIpc
private bool _loggedContentHasPathQueryWarning; private bool _loggedContentHasPathQueryWarning;
private bool _loggedLevelingModeWarning;
public const int MinimumLevelForLevelingMode = 15;
public AutoDutyIpc(IDalamudPluginInterface pluginInterface, Configuration configuration, TerritoryData territoryData, ILogger<AutoDutyIpc> logger) public AutoDutyIpc(IDalamudPluginInterface pluginInterface, Configuration configuration, TerritoryData territoryData, ILogger<AutoDutyIpc> logger)
{ {
_configuration = configuration; _configuration = configuration;
@ -46,6 +50,7 @@ internal sealed class AutoDutyIpc
_isStopped = pluginInterface.GetIpcSubscriber<bool>("AutoDuty.IsStopped"); _isStopped = pluginInterface.GetIpcSubscriber<bool>("AutoDuty.IsStopped");
_stop = pluginInterface.GetIpcSubscriber<object>("AutoDuty.Stop"); _stop = pluginInterface.GetIpcSubscriber<object>("AutoDuty.Stop");
_loggedContentHasPathQueryWarning = false; _loggedContentHasPathQueryWarning = false;
_loggedLevelingModeWarning = false;
} }
public bool IsConfiguredToRunContent(DutyOptions? dutyOptions) public bool IsConfiguredToRunContent(DutyOptions? dutyOptions)
@ -73,6 +78,24 @@ internal sealed class AutoDutyIpc
return false; return false;
} }
public bool IsConfiguredToRunLevelingMode()
{
return _configuration.Duties.RunLevelingModeWhenUnderleveled;
}
public bool IsConfiguredToRunLevelingMode(int currentPlayerLevel)
{
if (!_configuration.Duties.RunLevelingModeWhenUnderleveled)
{
return false;
}
if (currentPlayerLevel < 15)
{
return false;
}
return true;
}
public bool HasPath(uint cfcId) public bool HasPath(uint cfcId)
{ {
if (!_territoryData.TryGetContentFinderCondition(cfcId, out TerritoryData.ContentFinderConditionData contentFinderConditionData)) if (!_territoryData.TryGetContentFinderCondition(cfcId, out TerritoryData.ContentFinderConditionData contentFinderConditionData))
@ -118,6 +141,35 @@ internal sealed class AutoDutyIpc
} }
} }
public bool StartLevelingMode()
{
try
{
_logger.LogInformation("Starting AutoDuty Leveling mode (Support) - AutoDuty will select the best dungeon");
_setConfig.InvokeAction("leveling", "Support");
_run.InvokeAction(0u, 1, !_configuration.Advanced.DisableAutoDutyBareMode);
return true;
}
catch (IpcError ipcError)
{
if (!_loggedLevelingModeWarning)
{
_logger.LogWarning("Unable to start AutoDuty Leveling mode: {Message}", ipcError.Message);
_loggedLevelingModeWarning = true;
}
return false;
}
catch (Exception exception)
{
if (!_loggedLevelingModeWarning)
{
_logger.LogWarning(exception, "Unable to start AutoDuty Leveling mode");
_loggedLevelingModeWarning = true;
}
return false;
}
}
public bool IsStopped() public bool IsStopped()
{ {
try try

View file

@ -51,6 +51,17 @@ internal sealed class QuestionableIpc : IDisposable
public required int TargetValue { get; init; } public required int TargetValue { get; init; }
} }
public sealed class MsqLevelLockData
{
public required bool IsLevelLocked { get; init; }
public required int LevelsNeeded { get; init; }
public required int RequiredLevel { get; init; }
public required string? QuestName { get; init; }
}
private const string IpcIsRunning = "Questionable.IsRunning"; private const string IpcIsRunning = "Questionable.IsRunning";
private const string IpcGetCurrentQuestId = "Questionable.GetCurrentQuestId"; private const string IpcGetCurrentQuestId = "Questionable.GetCurrentQuestId";
@ -145,6 +156,18 @@ internal sealed class QuestionableIpc : IDisposable
private const string IpcGetAllQuestSequenceStopConditions = "Questionable.GetAllQuestSequenceStopConditions"; private const string IpcGetAllQuestSequenceStopConditions = "Questionable.GetAllQuestSequenceStopConditions";
private const string IpcGetLevelStopMode = "Questionable.GetLevelStopMode";
private const string IpcSetLevelStopMode = "Questionable.SetLevelStopMode";
private const string IpcGetSequenceStopMode = "Questionable.GetSequenceStopMode";
private const string IpcSetSequenceStopMode = "Questionable.SetSequenceStopMode";
private const string IpcGetQuestStopMode = "Questionable.GetQuestStopMode";
private const string IpcSetQuestStopMode = "Questionable.SetQuestStopMode";
private const string IpcGetAlliedSocietyRemainingAllowances = "Questionable.AlliedSociety.GetRemainingAllowances"; private const string IpcGetAlliedSocietyRemainingAllowances = "Questionable.AlliedSociety.GetRemainingAllowances";
private const string IpcGetAlliedSocietyTimeUntilReset = "Questionable.AlliedSociety.GetTimeUntilReset"; private const string IpcGetAlliedSocietyTimeUntilReset = "Questionable.AlliedSociety.GetTimeUntilReset";
@ -163,6 +186,16 @@ internal sealed class QuestionableIpc : IDisposable
private const string IpcGetAlliedSocietyOptimalQuests = "Questionable.AlliedSociety.GetOptimalQuests"; private const string IpcGetAlliedSocietyOptimalQuests = "Questionable.AlliedSociety.GetOptimalQuests";
private const string IpcIsLevelingModeEnabled = "Questionable.IsLevelingModeEnabled";
private const string IpcSetLevelingModeEnabled = "Questionable.SetLevelingModeEnabled";
private const string IpcGetMsqLevelLockInfo = "Questionable.GetMsqLevelLockInfo";
private const string IpcStartLevelingMode = "Questionable.StartLevelingMode";
private const string IpcStopLevelingMode = "Questionable.StopLevelingMode";
private readonly QuestController _questController; private readonly QuestController _questController;
private readonly QuestRegistry _questRegistry; private readonly QuestRegistry _questRegistry;
@ -181,6 +214,8 @@ internal sealed class QuestionableIpc : IDisposable
private readonly IServiceProvider _serviceProvider; private readonly IServiceProvider _serviceProvider;
private readonly AutoDutyIpc _autoDutyIpc;
private readonly ICallGateProvider<bool> _isRunning; private readonly ICallGateProvider<bool> _isRunning;
private readonly ICallGateProvider<string?> _getCurrentQuestId; private readonly ICallGateProvider<string?> _getCurrentQuestId;
@ -275,6 +310,18 @@ internal sealed class QuestionableIpc : IDisposable
private readonly ICallGateProvider<Dictionary<string, int>> _getAllQuestSequenceStopConditions; private readonly ICallGateProvider<Dictionary<string, int>> _getAllQuestSequenceStopConditions;
private readonly ICallGateProvider<int> _getLevelStopMode;
private readonly ICallGateProvider<int, bool> _setLevelStopMode;
private readonly ICallGateProvider<int> _getSequenceStopMode;
private readonly ICallGateProvider<int, bool> _setSequenceStopMode;
private readonly ICallGateProvider<string, int> _getQuestStopMode;
private readonly ICallGateProvider<string, int, bool> _setQuestStopMode;
private readonly ICallGateProvider<int> _getAlliedSocietyRemainingAllowances; private readonly ICallGateProvider<int> _getAlliedSocietyRemainingAllowances;
private readonly ICallGateProvider<long> _getAlliedSocietyTimeUntilReset; private readonly ICallGateProvider<long> _getAlliedSocietyTimeUntilReset;
@ -293,7 +340,17 @@ internal sealed class QuestionableIpc : IDisposable
private readonly ICallGateProvider<byte, List<string>> _getAlliedSocietyOptimalQuests; private readonly ICallGateProvider<byte, List<string>> _getAlliedSocietyOptimalQuests;
public QuestionableIpc(QuestController questController, EventInfoComponent eventInfoComponent, QuestRegistry questRegistry, QuestFunctions questFunctions, QuestData questData, ManualPriorityComponent manualPriorityComponent, PresetBuilderComponent presetBuilderComponent, Configuration configuration, IDalamudPluginInterface pluginInterface, IServiceProvider serviceProvider) private readonly ICallGateProvider<bool> _isLevelingModeEnabled;
private readonly ICallGateProvider<bool, bool> _setLevelingModeEnabled;
private readonly ICallGateProvider<MsqLevelLockData?> _getMsqLevelLockInfo;
private readonly ICallGateProvider<bool> _startLevelingMode;
private readonly ICallGateProvider<bool> _stopLevelingMode;
public QuestionableIpc(QuestController questController, EventInfoComponent eventInfoComponent, QuestRegistry questRegistry, QuestFunctions questFunctions, QuestData questData, ManualPriorityComponent manualPriorityComponent, PresetBuilderComponent presetBuilderComponent, Configuration configuration, IDalamudPluginInterface pluginInterface, IServiceProvider serviceProvider, AutoDutyIpc autoDutyIpc)
{ {
QuestionableIpc questionableIpc = this; QuestionableIpc questionableIpc = this;
_questController = questController; _questController = questController;
@ -305,6 +362,7 @@ internal sealed class QuestionableIpc : IDisposable
_configuration = configuration; _configuration = configuration;
_pluginInterface = pluginInterface; _pluginInterface = pluginInterface;
_serviceProvider = serviceProvider; _serviceProvider = serviceProvider;
_autoDutyIpc = autoDutyIpc;
_isRunning = pluginInterface.GetIpcProvider<bool>("Questionable.IsRunning"); _isRunning = pluginInterface.GetIpcProvider<bool>("Questionable.IsRunning");
_isRunning.RegisterFunc(() => questController.AutomationType != QuestController.EAutomationType.Manual || questController.IsRunning); _isRunning.RegisterFunc(() => questController.AutomationType != QuestController.EAutomationType.Manual || questController.IsRunning);
_getCurrentQuestId = pluginInterface.GetIpcProvider<string>("Questionable.GetCurrentQuestId"); _getCurrentQuestId = pluginInterface.GetIpcProvider<string>("Questionable.GetCurrentQuestId");
@ -400,6 +458,62 @@ internal sealed class QuestionableIpc : IDisposable
_removeQuestSequenceStopCondition.RegisterFunc(RemoveQuestSequenceStopCondition); _removeQuestSequenceStopCondition.RegisterFunc(RemoveQuestSequenceStopCondition);
_getAllQuestSequenceStopConditions = pluginInterface.GetIpcProvider<Dictionary<string, int>>("Questionable.GetAllQuestSequenceStopConditions"); _getAllQuestSequenceStopConditions = pluginInterface.GetIpcProvider<Dictionary<string, int>>("Questionable.GetAllQuestSequenceStopConditions");
_getAllQuestSequenceStopConditions.RegisterFunc(GetAllQuestSequenceStopConditions); _getAllQuestSequenceStopConditions.RegisterFunc(GetAllQuestSequenceStopConditions);
_getLevelStopMode = pluginInterface.GetIpcProvider<int>("Questionable.GetLevelStopMode");
_getLevelStopMode.RegisterFunc(() => (int)questionableIpc._configuration.Stop.LevelStopMode);
_setLevelStopMode = pluginInterface.GetIpcProvider<int, bool>("Questionable.SetLevelStopMode");
_setLevelStopMode.RegisterFunc(delegate(int mode)
{
if (!Enum.IsDefined(typeof(Configuration.EStopConditionMode), mode))
{
return false;
}
questionableIpc._configuration.Stop.LevelStopMode = (Configuration.EStopConditionMode)mode;
questionableIpc._pluginInterface.SavePluginConfig(questionableIpc._configuration);
return true;
});
_getSequenceStopMode = pluginInterface.GetIpcProvider<int>("Questionable.GetSequenceStopMode");
_getSequenceStopMode.RegisterFunc(() => (int)questionableIpc._configuration.Stop.SequenceStopMode);
_setSequenceStopMode = pluginInterface.GetIpcProvider<int, bool>("Questionable.SetSequenceStopMode");
_setSequenceStopMode.RegisterFunc(delegate(int mode)
{
if (!Enum.IsDefined(typeof(Configuration.EStopConditionMode), mode))
{
return false;
}
questionableIpc._configuration.Stop.SequenceStopMode = (Configuration.EStopConditionMode)mode;
questionableIpc._pluginInterface.SavePluginConfig(questionableIpc._configuration);
return true;
});
_getQuestStopMode = pluginInterface.GetIpcProvider<string, int>("Questionable.GetQuestStopMode");
_getQuestStopMode.RegisterFunc((string questId) => (int)(questionableIpc._configuration.Stop.QuestStopModes.TryGetValue(questId, out var value) ? value : ((Configuration.EStopConditionMode)(-1))));
_setQuestStopMode = pluginInterface.GetIpcProvider<string, int, bool>("Questionable.SetQuestStopMode");
_setQuestStopMode.RegisterFunc(delegate(string questId, int mode)
{
if (!Enum.IsDefined(typeof(Configuration.EStopConditionMode), mode))
{
return false;
}
if (ElementId.TryFromString(questId, out ElementId elementId) && elementId != null && questionableIpc._questRegistry.IsKnownQuest(elementId))
{
if (mode == 0)
{
questionableIpc._configuration.Stop.QuestsToStopAfter.Remove(elementId);
questionableIpc._configuration.Stop.QuestStopModes.Remove(questId);
questionableIpc._configuration.Stop.QuestSequences.Remove(questId);
}
else
{
if (!questionableIpc._configuration.Stop.QuestsToStopAfter.Contains(elementId))
{
questionableIpc._configuration.Stop.QuestsToStopAfter.Add(elementId);
}
questionableIpc._configuration.Stop.QuestStopModes[questId] = (Configuration.EStopConditionMode)mode;
}
questionableIpc._pluginInterface.SavePluginConfig(questionableIpc._configuration);
return true;
}
return false;
});
_getAlliedSocietyRemainingAllowances = pluginInterface.GetIpcProvider<int>("Questionable.AlliedSociety.GetRemainingAllowances"); _getAlliedSocietyRemainingAllowances = pluginInterface.GetIpcProvider<int>("Questionable.AlliedSociety.GetRemainingAllowances");
_getAlliedSocietyRemainingAllowances.RegisterFunc(GetAlliedSocietyRemainingAllowances); _getAlliedSocietyRemainingAllowances.RegisterFunc(GetAlliedSocietyRemainingAllowances);
_getAlliedSocietyTimeUntilReset = pluginInterface.GetIpcProvider<long>("Questionable.AlliedSociety.GetTimeUntilReset"); _getAlliedSocietyTimeUntilReset = pluginInterface.GetIpcProvider<long>("Questionable.AlliedSociety.GetTimeUntilReset");
@ -418,6 +532,16 @@ internal sealed class QuestionableIpc : IDisposable
_addAlliedSocietyOptimalQuests.RegisterFunc(AddAlliedSocietyOptimalQuests); _addAlliedSocietyOptimalQuests.RegisterFunc(AddAlliedSocietyOptimalQuests);
_getAlliedSocietyOptimalQuests = pluginInterface.GetIpcProvider<byte, List<string>>("Questionable.AlliedSociety.GetOptimalQuests"); _getAlliedSocietyOptimalQuests = pluginInterface.GetIpcProvider<byte, List<string>>("Questionable.AlliedSociety.GetOptimalQuests");
_getAlliedSocietyOptimalQuests.RegisterFunc(GetAlliedSocietyOptimalQuests); _getAlliedSocietyOptimalQuests.RegisterFunc(GetAlliedSocietyOptimalQuests);
_isLevelingModeEnabled = pluginInterface.GetIpcProvider<bool>("Questionable.IsLevelingModeEnabled");
_isLevelingModeEnabled.RegisterFunc(IsLevelingModeEnabled);
_setLevelingModeEnabled = pluginInterface.GetIpcProvider<bool, bool>("Questionable.SetLevelingModeEnabled");
_setLevelingModeEnabled.RegisterFunc(SetLevelingModeEnabled);
_getMsqLevelLockInfo = pluginInterface.GetIpcProvider<MsqLevelLockData>("Questionable.GetMsqLevelLockInfo");
_getMsqLevelLockInfo.RegisterFunc(GetMsqLevelLockInfo);
_startLevelingMode = pluginInterface.GetIpcProvider<bool>("Questionable.StartLevelingMode");
_startLevelingMode.RegisterFunc(StartLevelingMode);
_stopLevelingMode = pluginInterface.GetIpcProvider<bool>("Questionable.StopLevelingMode");
_stopLevelingMode.RegisterFunc(StopLevelingMode);
} }
private bool StartQuest(string questId, bool single) private bool StartQuest(string questId, bool single)
@ -969,7 +1093,7 @@ internal sealed class QuestionableIpc : IDisposable
{ {
return new StopConditionData return new StopConditionData
{ {
Enabled = _configuration.Stop.LevelToStopAfter, Enabled = (_configuration.Stop.LevelStopMode != Configuration.EStopConditionMode.Off),
TargetValue = _configuration.Stop.TargetLevel TargetValue = _configuration.Stop.TargetLevel
}; };
} }
@ -980,7 +1104,7 @@ internal sealed class QuestionableIpc : IDisposable
{ {
return false; return false;
} }
_configuration.Stop.LevelToStopAfter = enabled; _configuration.Stop.LevelStopMode = (enabled ? Configuration.EStopConditionMode.Pause : Configuration.EStopConditionMode.Off);
_configuration.Stop.TargetLevel = targetLevel; _configuration.Stop.TargetLevel = targetLevel;
_pluginInterface.SavePluginConfig(_configuration); _pluginInterface.SavePluginConfig(_configuration);
return true; return true;
@ -990,7 +1114,7 @@ internal sealed class QuestionableIpc : IDisposable
{ {
return new StopConditionData return new StopConditionData
{ {
Enabled = _configuration.Stop.SequenceToStopAfter, Enabled = (_configuration.Stop.SequenceStopMode != Configuration.EStopConditionMode.Off),
TargetValue = _configuration.Stop.TargetSequence TargetValue = _configuration.Stop.TargetSequence
}; };
} }
@ -1001,7 +1125,7 @@ internal sealed class QuestionableIpc : IDisposable
{ {
return false; return false;
} }
_configuration.Stop.SequenceToStopAfter = enabled; _configuration.Stop.SequenceStopMode = (enabled ? Configuration.EStopConditionMode.Pause : Configuration.EStopConditionMode.Off);
_configuration.Stop.TargetSequence = targetSequence; _configuration.Stop.TargetSequence = targetSequence;
_pluginInterface.SavePluginConfig(_configuration); _pluginInterface.SavePluginConfig(_configuration);
return true; return true;
@ -1213,6 +1337,56 @@ internal sealed class QuestionableIpc : IDisposable
select q.QuestId.ToString()).ToList(); select q.QuestId.ToString()).ToList();
} }
private bool IsLevelingModeEnabled()
{
return _configuration.Duties.RunLevelingModeWhenUnderleveled;
}
private bool SetLevelingModeEnabled(bool enabled)
{
_configuration.Duties.RunLevelingModeWhenUnderleveled = enabled;
_pluginInterface.SavePluginConfig(_configuration);
return true;
}
private MsqLevelLockData? GetMsqLevelLockInfo()
{
var (flag, levelsNeeded, requiredLevel, questName) = _questFunctions.GetMsqLevelLockInfo();
if (!flag)
{
return null;
}
return new MsqLevelLockData
{
IsLevelLocked = flag,
LevelsNeeded = levelsNeeded,
RequiredLevel = requiredLevel,
QuestName = questName
};
}
private bool StartLevelingMode()
{
if (!_autoDutyIpc.IsConfiguredToRunLevelingMode())
{
return false;
}
return _autoDutyIpc.StartLevelingMode();
}
private bool StopLevelingMode()
{
try
{
_autoDutyIpc.Stop();
return true;
}
catch
{
return false;
}
}
public void Dispose() public void Dispose()
{ {
_exportQuestPriority.UnregisterFunc(); _exportQuestPriority.UnregisterFunc();
@ -1262,6 +1436,12 @@ internal sealed class QuestionableIpc : IDisposable
_getStopQuestList.UnregisterFunc(); _getStopQuestList.UnregisterFunc();
_setStopConditionsEnabled.UnregisterFunc(); _setStopConditionsEnabled.UnregisterFunc();
_getStopConditionsEnabled.UnregisterFunc(); _getStopConditionsEnabled.UnregisterFunc();
_setQuestStopMode.UnregisterFunc();
_getQuestStopMode.UnregisterFunc();
_setSequenceStopMode.UnregisterFunc();
_getSequenceStopMode.UnregisterFunc();
_setLevelStopMode.UnregisterFunc();
_getLevelStopMode.UnregisterFunc();
_getAlliedSocietiesWithAvailableQuests.UnregisterFunc(); _getAlliedSocietiesWithAvailableQuests.UnregisterFunc();
_getAlliedSocietyCurrentRank.UnregisterFunc(); _getAlliedSocietyCurrentRank.UnregisterFunc();
_getAlliedSocietyIsMaxRank.UnregisterFunc(); _getAlliedSocietyIsMaxRank.UnregisterFunc();
@ -1271,5 +1451,10 @@ internal sealed class QuestionableIpc : IDisposable
_getAlliedSocietyRemainingAllowances.UnregisterFunc(); _getAlliedSocietyRemainingAllowances.UnregisterFunc();
_addAlliedSocietyOptimalQuests.UnregisterFunc(); _addAlliedSocietyOptimalQuests.UnregisterFunc();
_getAlliedSocietyOptimalQuests.UnregisterFunc(); _getAlliedSocietyOptimalQuests.UnregisterFunc();
_stopLevelingMode.UnregisterFunc();
_startLevelingMode.UnregisterFunc();
_getMsqLevelLockInfo.UnregisterFunc();
_setLevelingModeEnabled.UnregisterFunc();
_isLevelingModeEnabled.UnregisterFunc();
} }
} }

View file

@ -72,6 +72,10 @@ internal sealed class QuestFunctions
private ElementId? _lastLoggedAcceptedHiddenMsq; private ElementId? _lastLoggedAcceptedHiddenMsq;
private bool _loggedNoClassQuestsAvailable;
private bool _loggedAdventurerClass;
public QuestFunctions(QuestRegistry questRegistry, QuestData questData, JournalData journalData, AetheryteFunctions aetheryteFunctions, AlliedSocietyQuestFunctions alliedSocietyQuestFunctions, AlliedSocietyData alliedSocietyData, AetheryteData aetheryteData, Configuration configuration, IDataManager dataManager, IObjectTable objectTable, IClientState clientState, IGameGui gameGui, IAetheryteList aetheryteList, ILogger<QuestFunctions> logger) public QuestFunctions(QuestRegistry questRegistry, QuestData questData, JournalData journalData, AetheryteFunctions aetheryteFunctions, AlliedSocietyQuestFunctions alliedSocietyQuestFunctions, AlliedSocietyData alliedSocietyData, AetheryteData aetheryteData, Configuration configuration, IDataManager dataManager, IObjectTable objectTable, IClientState clientState, IGameGui gameGui, IAetheryteList aetheryteList, ILogger<QuestFunctions> logger)
{ {
_questRegistry = questRegistry; _questRegistry = questRegistry;
@ -195,6 +199,8 @@ internal sealed class QuestFunctions
{ {
_logger.LogInformation("MSQ {MsqId} requires level {RequiredLevel}, current level {CurrentLevel}. Checking for early class quests to level up.", questReference.CurrentQuest, quest.Info.Level, currentLevel); _logger.LogInformation("MSQ {MsqId} requires level {RequiredLevel}, current level {CurrentLevel}. Checking for early class quests to level up.", questReference.CurrentQuest, quest.Info.Level, currentLevel);
_lastLoggedLevelLockedMsq = questReference.CurrentQuest; _lastLoggedLevelLockedMsq = questReference.CurrentQuest;
_loggedNoClassQuestsAvailable = false;
_loggedAdventurerClass = false;
} }
if (valueOrDefault != EClassJob.Adventurer) if (valueOrDefault != EClassJob.Adventurer)
{ {
@ -222,10 +228,10 @@ internal sealed class QuestFunctions
return new QuestReference(questId2, QuestManager.GetQuestSequence(questId2.Value), questReference.State); return new QuestReference(questId2, QuestManager.GetQuestSequence(questId2.Value), questReference.State);
} }
} }
else if (_lastLoggedLevelLockedMsq == questReference.CurrentQuest && _lastLoggedForcedClassQuest == null) else if (!_loggedNoClassQuestsAvailable)
{ {
_logger.LogWarning("No class quests passed the filter for {ClassJob} at level {CurrentLevel}", valueOrDefault, currentLevel); _logger.LogWarning("No class quests passed the filter for {ClassJob} at level {CurrentLevel}", valueOrDefault, currentLevel);
_lastLoggedLevelLockedMsq = null; _loggedNoClassQuestsAvailable = true;
} }
List<QuestInfo> list2 = (from x in _questRegistry.GetKnownClassJobQuests(valueOrDefault, includeRoleQuests: false) List<QuestInfo> list2 = (from x in _questRegistry.GetKnownClassJobQuests(valueOrDefault, includeRoleQuests: false)
where x.Level <= currentLevel && x.Level <= 5 && !IsQuestAcceptedOrComplete(x.QuestId) && IsReadyToAcceptQuest(x.QuestId) where x.Level <= currentLevel && x.Level <= 5 && !IsQuestAcceptedOrComplete(x.QuestId) && IsReadyToAcceptQuest(x.QuestId)
@ -237,11 +243,16 @@ internal sealed class QuestFunctions
_logger.LogInformation("MSQ level locked. Prioritizing class quest {ClassQuestId} for {ClassJob} (level {QuestLevel}) from registry", questId3, valueOrDefault, list2.First().Level); _logger.LogInformation("MSQ level locked. Prioritizing class quest {ClassQuestId} for {ClassJob} (level {QuestLevel}) from registry", questId3, valueOrDefault, list2.First().Level);
return new QuestReference(questId3, QuestManager.GetQuestSequence(questId3.Value), questReference.State); return new QuestReference(questId3, QuestManager.GetQuestSequence(questId3.Value), questReference.State);
} }
_logger.LogWarning("MSQ level locked but no available early class quests found for {ClassJob} at level {CurrentLevel}", valueOrDefault, currentLevel); if (!_loggedNoClassQuestsAvailable)
{
_logger.LogWarning("MSQ level locked but no available early class quests found for {ClassJob} at level {CurrentLevel}", valueOrDefault, currentLevel);
_loggedNoClassQuestsAvailable = true;
}
} }
else else if (!_loggedAdventurerClass)
{ {
_logger.LogWarning("Current class is Adventurer, cannot find class quests"); _logger.LogWarning("Current class is Adventurer, cannot find class quests");
_loggedAdventurerClass = true;
} }
Questionable.Model.Quest quest2; Questionable.Model.Quest quest2;
ElementId elementId = (from x in GetNextPriorityQuestsThatCanBeAccepted() ElementId elementId = (from x in GetNextPriorityQuestsThatCanBeAccepted()
@ -253,11 +264,17 @@ internal sealed class QuestFunctions
_logger.LogInformation("MSQ {MsqId} requires level {RequiredLevel}, current level {CurrentLevel}. Prioritizing early class quest {ClassQuestId} (from priority list)", questReference.CurrentQuest, quest.Info.Level, currentLevel, elementId); _logger.LogInformation("MSQ {MsqId} requires level {RequiredLevel}, current level {CurrentLevel}. Prioritizing early class quest {ClassQuestId} (from priority list)", questReference.CurrentQuest, quest.Info.Level, currentLevel, elementId);
return new QuestReference(elementId, QuestManager.GetQuestSequence(elementId.Value), questReference.State); return new QuestReference(elementId, QuestManager.GetQuestSequence(elementId.Value), questReference.State);
} }
_logger.LogWarning("MSQ {MsqId} is level locked (requires {RequiredLevel}, current {CurrentLevel}) and no early class quests available. Cannot proceed.", questReference.CurrentQuest, quest.Info.Level, currentLevel); if (!_loggedNoClassQuestsAvailable)
{
_logger.LogWarning("MSQ {MsqId} is level locked (requires {RequiredLevel}, current {CurrentLevel}) and no early class quests available. Cannot proceed.", questReference.CurrentQuest, quest.Info.Level, currentLevel);
_loggedNoClassQuestsAvailable = true;
}
return QuestReference.NoQuest(MainScenarioQuestState.Unavailable); return QuestReference.NoQuest(MainScenarioQuestState.Unavailable);
} }
_lastLoggedLevelLockedMsq = null; _lastLoggedLevelLockedMsq = null;
_lastLoggedForcedClassQuest = null; _lastLoggedForcedClassQuest = null;
_loggedNoClassQuestsAvailable = false;
_loggedAdventurerClass = false;
} }
if (questReference.CurrentQuest != null && !IsQuestAccepted(questReference.CurrentQuest)) if (questReference.CurrentQuest != null && !IsQuestAccepted(questReference.CurrentQuest))
{ {
@ -931,7 +948,7 @@ internal sealed class QuestFunctions
private static bool IsQuestLocked(UnlockLinkId unlockLinkId) private static bool IsQuestLocked(UnlockLinkId unlockLinkId)
{ {
return IsQuestUnobtainable(unlockLinkId); return IsQuestUnobtainableStatic(unlockLinkId);
} }
private bool IsQuestLocked(AethernetId aethernetId) private bool IsQuestLocked(AethernetId aethernetId)
@ -995,11 +1012,24 @@ internal sealed class QuestFunctions
} }
if (elementId is UnlockLinkId unlockLinkId) if (elementId is UnlockLinkId unlockLinkId)
{ {
return IsQuestUnobtainable(unlockLinkId); return IsQuestUnobtainableStatic(unlockLinkId);
} }
return false; return false;
} }
private static bool IsQuestUnobtainableStatic(UnlockLinkId unlockLinkId)
{
if (unlockLinkId.Value == 506)
{
return !IsFestivalActive(160, (ushort)2);
}
if (unlockLinkId.Value == 568)
{
return !IsFestivalActive(160, (ushort)3);
}
return true;
}
public unsafe bool IsQuestUnobtainable(QuestId questId, ElementId? extraCompletedQuest = null) public unsafe bool IsQuestUnobtainable(QuestId questId, ElementId? extraCompletedQuest = null)
{ {
IQuestInfo questInfo = _questData.GetQuestInfo(questId); IQuestInfo questInfo = _questData.GetQuestInfo(questId);
@ -1024,10 +1054,6 @@ internal sealed class QuestFunctions
} }
if (DateTime.UtcNow > dateTime) if (DateTime.UtcNow > dateTime)
{ {
if (_alreadyLoggedUnobtainableQuestsDetailed.Add(questId.Value))
{
_logger.LogDebug("UnlockLink quest {QuestId} unobtainable: expiry {ExpiryUtc} (UTC) is before now {NowUtc}", questId, dateTime.ToString("o"), DateTime.UtcNow.ToString("o"));
}
return true; return true;
} }
} }
@ -1043,7 +1069,7 @@ internal sealed class QuestFunctions
List<QuestSequence> list = quest?.Root?.QuestSequence; List<QuestSequence> list = quest?.Root?.QuestSequence;
if (list != null && list.Count > 0) if (list != null && list.Count > 0)
{ {
goto IL_027a; goto IL_0228;
} }
} }
if (_alreadyLoggedUnobtainableQuestsDetailed.Add(questId.Value)) if (_alreadyLoggedUnobtainableQuestsDetailed.Add(questId.Value))
@ -1053,8 +1079,8 @@ internal sealed class QuestFunctions
} }
return true; return true;
} }
goto IL_027a; goto IL_0228;
IL_027a: IL_0228:
if (questInfo2.QuestLocks.Count > 0) if (questInfo2.QuestLocks.Count > 0)
{ {
int num = questInfo2.QuestLocks.Count((QuestId x) => IsQuestComplete(x) || x.Equals(extraCompletedQuest)); int num = questInfo2.QuestLocks.Count((QuestId x) => IsQuestComplete(x) || x.Equals(extraCompletedQuest));
@ -1073,41 +1099,15 @@ internal sealed class QuestFunctions
DateTime valueOrDefault = seasonalQuestExpiry.GetValueOrDefault(); DateTime valueOrDefault = seasonalQuestExpiry.GetValueOrDefault();
TimeSpan timeOfDay2 = valueOrDefault.TimeOfDay; TimeSpan timeOfDay2 = valueOrDefault.TimeOfDay;
TimeSpan timeSpan2 = new TimeSpan(23, 59, 59); TimeSpan timeSpan2 = new TimeSpan(23, 59, 59);
bool flag2 = false; DateTime dateTime2 = ((!(timeOfDay2 == TimeSpan.Zero) && !(timeOfDay2 == timeSpan2)) ? ((valueOrDefault.Kind == DateTimeKind.Utc) ? valueOrDefault : valueOrDefault.ToUniversalTime()) : EventInfoComponent.AtDailyReset(DateOnly.FromDateTime(valueOrDefault)));
DateTime dateTime2;
if (timeOfDay2 == TimeSpan.Zero || timeOfDay2 == timeSpan2)
{
dateTime2 = EventInfoComponent.AtDailyReset(DateOnly.FromDateTime(valueOrDefault));
flag2 = true;
}
else
{
dateTime2 = ((valueOrDefault.Kind == DateTimeKind.Utc) ? valueOrDefault : valueOrDefault.ToUniversalTime());
}
if (_alreadyLoggedUnobtainableQuestsDetailed.Add(questId.Value))
{
_logger.LogDebug("Quest {QuestId} seasonal expiry raw={ExpiryRaw} Kind={Kind} TimeOfDay={TimeOfDay} treatedAsDailyReset={TreatedAsDailyReset}", questId, valueOrDefault.ToString("o"), valueOrDefault.Kind, valueOrDefault.TimeOfDay, flag2);
_logger.LogDebug("Quest {QuestId} expiry check: nowUtc={Now:o}, expiryUtc={Expiry:o}, expired={Expired}", questId, DateTime.UtcNow, dateTime2, DateTime.UtcNow > dateTime2);
}
if (DateTime.UtcNow > dateTime2) if (DateTime.UtcNow > dateTime2)
{ {
if (_alreadyLoggedUnobtainableQuestsDetailed.Add(questId.Value))
{
_logger.LogDebug("Quest {QuestId} unobtainable: seasonal expiry {ExpiryUtc} (UTC) is before now {NowUtc}", questId, dateTime2.ToString("o"), DateTime.UtcNow.ToString("o"));
}
return true; return true;
} }
} }
if ((questInfo2.IsSeasonalEvent || questInfo2.IsSeasonalQuest) && !(questInfo2.SeasonalQuestExpiry is DateTime)) if ((questInfo2.IsSeasonalEvent || questInfo2.IsSeasonalQuest) && !questInfo2.SeasonalQuestExpiry.HasValue && !_configuration.General.ShowIncompleteSeasonalEvents)
{ {
if (_alreadyLoggedUnobtainableQuestsDetailed.Add(questId.Value)) return true;
{
_logger.LogDebug("Quest {QuestId} is seasonal/event with no expiry; ShowIncompleteSeasonalEvents={ShowIncomplete}", questId, _configuration.General.ShowIncompleteSeasonalEvents);
}
if (!_configuration.General.ShowIncompleteSeasonalEvents)
{
return true;
}
} }
if (_questData.GetLockedClassQuests().Contains(questId)) if (_questData.GetLockedClassQuests().Contains(questId))
{ {
@ -1172,19 +1172,6 @@ internal sealed class QuestFunctions
return false; return false;
} }
private static bool IsQuestUnobtainable(UnlockLinkId unlockLinkId)
{
if (unlockLinkId.Value == 506)
{
return !IsFestivalActive(160, (ushort)2);
}
if (unlockLinkId.Value == 568)
{
return !IsFestivalActive(160, (ushort)3);
}
return true;
}
private unsafe static bool IsFestivalActive(ushort id, ushort? phase = null) private unsafe static bool IsFestivalActive(ushort id, ushort? phase = null)
{ {
for (int i = 0; i < GameMain.Instance()->ActiveFestivals.Length; i++) for (int i = 0; i < GameMain.Instance()->ActiveFestivals.Length; i++)
@ -1310,4 +1297,43 @@ internal sealed class QuestFunctions
{ {
return IsQuestComplete(_questData.LastMainScenarioQuestId); return IsQuestComplete(_questData.LastMainScenarioQuestId);
} }
public (bool isLevelLocked, int levelsNeeded, int requiredLevel, string? questName) GetMsqLevelLockInfo()
{
byte b = _objectTable.LocalPlayer?.Level ?? 0;
if (b == 0)
{
return (isLevelLocked: false, levelsNeeded: 0, requiredLevel: 0, questName: null);
}
QuestReference item = GetMainScenarioQuest().Item1;
if (item.CurrentQuest == null)
{
return (isLevelLocked: false, levelsNeeded: 0, requiredLevel: 0, questName: null);
}
if (IsQuestAccepted(item.CurrentQuest))
{
return (isLevelLocked: false, levelsNeeded: 0, requiredLevel: 0, questName: null);
}
if (_questRegistry.TryGetQuest(item.CurrentQuest, out Questionable.Model.Quest quest))
{
int level = quest.Info.Level;
if (level <= b)
{
return (isLevelLocked: false, levelsNeeded: 0, requiredLevel: level, questName: null);
}
int item2 = level - b;
return (isLevelLocked: true, levelsNeeded: item2, requiredLevel: level, questName: quest.Info.Name);
}
if (item.CurrentQuest is QuestId elementId && _questData.TryGetQuestInfo(elementId, out IQuestInfo questInfo) && questInfo is QuestInfo questInfo2)
{
int level2 = questInfo2.Level;
if (level2 <= b)
{
return (isLevelLocked: false, levelsNeeded: 0, requiredLevel: level2, questName: null);
}
int item3 = level2 - b;
return (isLevelLocked: true, levelsNeeded: item3, requiredLevel: level2, questName: questInfo2.Name);
}
return (isLevelLocked: false, levelsNeeded: 0, requiredLevel: 0, questName: null);
}
} }

View file

@ -107,7 +107,7 @@ internal sealed class QuestValidator
AlliedSociety = x.Key, AlliedSociety = x.Key,
Type = EIssueType.QuestDisabled, Type = EIssueType.QuestDisabled,
Severity = EIssueSeverity.None, Severity = EIssueSeverity.None,
Description = $"{x.Value.Count} disabled quest(s): {value}" Description = $"{x.Value.Count} disabled {((x.Value.Count == 1) ? "quest" : "quests")}: {value}"
}; };
}); });
} }

View file

@ -39,29 +39,27 @@ internal sealed class DutyConfigComponent : ConfigComponent
private readonly Dictionary<EExpansionVersion, List<DutyInfo>> _allTrialNames; private readonly Dictionary<EExpansionVersion, List<DutyInfo>> _allTrialNames;
private readonly Dictionary<EExpansionVersion, List<DutyInfo>> _allNormalRaidNames;
private readonly Dictionary<EExpansionVersion, List<DutyInfo>> _allAllianceRaidNames;
public DutyConfigComponent(IDalamudPluginInterface pluginInterface, Configuration configuration, IDataManager dataManager, QuestRegistry questRegistry, AutoDutyIpc autoDutyIpc, TerritoryData territoryData) public DutyConfigComponent(IDalamudPluginInterface pluginInterface, Configuration configuration, IDataManager dataManager, QuestRegistry questRegistry, AutoDutyIpc autoDutyIpc, TerritoryData territoryData)
: base(pluginInterface, configuration) : base(pluginInterface, configuration)
{ {
_questRegistry = questRegistry; _questRegistry = questRegistry;
_autoDutyIpc = autoDutyIpc; _autoDutyIpc = autoDutyIpc;
var source = (from x in dataManager.GetExcelSheet<DawnContent>() _contentFinderConditionNames = (from x in dataManager.GetExcelSheet<ContentFinderCondition>()
where x.RowId != 0 && !x.Unknown16 where x.RowId != 0 && x.Content.RowId != 0 && x.ContentType.RowId == 2
orderby x.Unknown15
select x.Content.ValueNullable into x
where x.HasValue
select x.Value into x
select new select new
{ {
Expansion = (EExpansionVersion)x.TerritoryType.Value.ExVersion.RowId, Expansion = (EExpansionVersion)x.TerritoryType.Value.ExVersion.RowId,
CfcId = x.RowId, CfcId = x.RowId,
Name = (territoryData.GetContentFinderCondition(x.RowId)?.Name ?? "?"), Name = (territoryData.GetContentFinderCondition(x.RowId)?.Name ?? x.Name.ToDalamudString().ToString()),
TerritoryId = x.TerritoryType.RowId, TerritoryId = x.TerritoryType.RowId,
ContentType = x.ContentType.RowId,
Level = x.ClassJobLevelRequired, Level = x.ClassJobLevelRequired,
SortKey = x.SortKey SortKey = x.SortKey
}).ToList(); } into x
_contentFinderConditionNames = (from x in source orderby x.SortKey
where x.ContentType == 2
group x by x.Expansion).ToDictionary(x => x.Key, x => x.Select(y => new DutyInfo(y.CfcId, y.TerritoryId, ConfigComponent.FormatLevel(y.Level) + " " + y.Name)).ToList()); group x by x.Expansion).ToDictionary(x => x.Key, x => x.Select(y => new DutyInfo(y.CfcId, y.TerritoryId, ConfigComponent.FormatLevel(y.Level) + " " + y.Name)).ToList());
_allTrialNames = (from x in dataManager.GetExcelSheet<ContentFinderCondition>() _allTrialNames = (from x in dataManager.GetExcelSheet<ContentFinderCondition>()
where x.RowId != 0 && x.Content.RowId != 0 && x.ContentType.RowId == 4 where x.RowId != 0 && x.Content.RowId != 0 && x.ContentType.RowId == 4
@ -76,6 +74,34 @@ internal sealed class DutyConfigComponent : ConfigComponent
} into x } into x
orderby x.SortKey orderby x.SortKey
group x by x.Expansion).ToDictionary(x => x.Key, x => x.Select(y => new DutyInfo(y.CfcId, y.TerritoryId, ConfigComponent.FormatLevel(y.Level) + " " + y.Name)).ToList()); group x by x.Expansion).ToDictionary(x => x.Key, x => x.Select(y => new DutyInfo(y.CfcId, y.TerritoryId, ConfigComponent.FormatLevel(y.Level) + " " + y.Name)).ToList());
_allNormalRaidNames = (from x in dataManager.GetExcelSheet<ContentFinderCondition>()
where x.RowId != 0 && x.Content.RowId != 0 && x.ContentType.RowId == 5
where x.ContentMemberType.RowId == 3
select new
{
Expansion = (EExpansionVersion)x.TerritoryType.Value.ExVersion.RowId,
CfcId = x.RowId,
Name = (territoryData.GetContentFinderCondition(x.RowId)?.Name ?? x.Name.ToDalamudString().ToString()),
TerritoryId = x.TerritoryType.RowId,
Level = x.ClassJobLevelRequired,
SortKey = x.SortKey
} into x
orderby x.SortKey
group x by x.Expansion).ToDictionary(x => x.Key, x => x.Select(y => new DutyInfo(y.CfcId, y.TerritoryId, ConfigComponent.FormatLevel(y.Level) + " " + y.Name)).ToList());
_allAllianceRaidNames = (from x in dataManager.GetExcelSheet<ContentFinderCondition>()
where x.RowId != 0 && x.Content.RowId != 0 && x.ContentType.RowId == 5
where x.ContentMemberType.RowId == 4
select new
{
Expansion = (EExpansionVersion)x.TerritoryType.Value.ExVersion.RowId,
CfcId = x.RowId,
Name = (territoryData.GetContentFinderCondition(x.RowId)?.Name ?? x.Name.ToDalamudString().ToString()),
TerritoryId = x.TerritoryType.RowId,
Level = x.ClassJobLevelRequired,
SortKey = x.SortKey
} into x
orderby x.SortKey
group x by x.Expansion).ToDictionary(x => x.Key, x => x.Select(y => new DutyInfo(y.CfcId, y.TerritoryId, ConfigComponent.FormatLevel(y.Level) + " " + y.Name)).ToList());
} }
public override void DrawTab() public override void DrawTab()
@ -106,6 +132,15 @@ internal sealed class DutyConfigComponent : ConfigComponent
} }
} }
ImGui.Spacing(); ImGui.Spacing();
bool v2 = base.Configuration.Duties.RunLevelingModeWhenUnderleveled;
if (ImGui.Checkbox("Run AutoDuty Leveling mode when underleveled for MSQ", ref v2))
{
base.Configuration.Duties.RunLevelingModeWhenUnderleveled = v2;
Save();
}
ImGui.SameLine();
ImGuiComponents.HelpMarker("When enabled, Questionable will automatically run AutoDuty's Leveling mode when your character is underleveled for the next Main Scenario Quest.\n\nLeveling mode runs the highest available dungeon for your level to gain XP.\n\nThis is useful for characters without the Road to 90 XP buff who may not have enough XP to continue the MSQ.");
ImGui.Spacing();
ImGui.Text("Default duty mode:"); ImGui.Text("Default duty mode:");
ImGui.SameLine(); ImGui.SameLine();
int currentItem = (int)base.Configuration.Duties.DefaultDutyMode; int currentItem = (int)base.Configuration.Duties.DefaultDutyMode;
@ -128,7 +163,7 @@ internal sealed class DutyConfigComponent : ConfigComponent
Util.OpenLink("https://docs.google.com/spreadsheets/d/151RlpqRcCpiD_VbQn6Duf-u-S71EP7d0mx3j1PDNoNA/edit?pli=1#gid=0"); Util.OpenLink("https://docs.google.com/spreadsheets/d/151RlpqRcCpiD_VbQn6Duf-u-S71EP7d0mx3j1PDNoNA/edit?pli=1#gid=0");
} }
ImGui.Separator(); ImGui.Separator();
ImGui.Text("You can override the settings for each individual dungeon/trial:"); ImGui.Text("You can override the settings for each individual duty:");
using ImRaii.IEndObject endObject2 = ImRaii.TabBar("DutyTypeTabs"); using ImRaii.IEndObject endObject2 = ImRaii.TabBar("DutyTypeTabs");
if (endObject2) if (endObject2)
{ {
@ -139,10 +174,24 @@ internal sealed class DutyConfigComponent : ConfigComponent
DrawConfigTable(v, _contentFinderConditionNames); DrawConfigTable(v, _contentFinderConditionNames);
} }
} }
using ImRaii.IEndObject endObject4 = ImRaii.TabItem("Trials"); using (ImRaii.IEndObject endObject4 = ImRaii.TabItem("Trials"))
if (endObject4)
{ {
DrawConfigTable(v, _allTrialNames); if (endObject4)
{
DrawConfigTable(v, _allTrialNames);
}
}
using (ImRaii.IEndObject endObject5 = ImRaii.TabItem("Normal Raids"))
{
if (endObject5)
{
DrawConfigTable(v, _allNormalRaidNames);
}
}
using ImRaii.IEndObject endObject6 = ImRaii.TabItem("Alliance Raids");
if (endObject6)
{
DrawConfigTable(v, _allAllianceRaidNames);
} }
} }
DrawEnableAllButton(); DrawEnableAllButton();
@ -375,7 +424,7 @@ internal sealed class DutyConfigComponent : ConfigComponent
{ {
base.Configuration.Duties.BlacklistedDutyCfcIds.Clear(); base.Configuration.Duties.BlacklistedDutyCfcIds.Clear();
base.Configuration.Duties.WhitelistedDutyCfcIds.Clear(); base.Configuration.Duties.WhitelistedDutyCfcIds.Clear();
foreach (List<DutyInfo> item2 in _contentFinderConditionNames.Values.Concat<List<DutyInfo>>(_allTrialNames.Values)) foreach (List<DutyInfo> item2 in _contentFinderConditionNames.Values.Concat<List<DutyInfo>>(_allTrialNames.Values).Concat<List<DutyInfo>>(_allNormalRaidNames.Values).Concat<List<DutyInfo>>(_allAllianceRaidNames.Values))
{ {
foreach (var (item, _, _) in item2) foreach (var (item, _, _) in item2)
{ {

View file

@ -19,6 +19,8 @@ namespace Questionable.Windows.ConfigComponents;
internal sealed class StopConditionComponent : ConfigComponent internal sealed class StopConditionComponent : ConfigComponent
{ {
private static readonly string[] StopModeNames = new string[3] { "Off", "Pause", "Stop" };
private readonly IDalamudPluginInterface _pluginInterface; private readonly IDalamudPluginInterface _pluginInterface;
private readonly QuestSelector _questSelector; private readonly QuestSelector _questSelector;
@ -55,6 +57,7 @@ internal sealed class StopConditionComponent : ConfigComponent
_questSelector.QuestSelected = delegate(Quest quest) _questSelector.QuestSelected = delegate(Quest quest)
{ {
configuration.Stop.QuestsToStopAfter.Add(quest.Id); configuration.Stop.QuestsToStopAfter.Add(quest.Id);
configuration.Stop.QuestStopModes[quest.Id.ToString()] = Questionable.Configuration.EStopConditionMode.Stop;
stopConditionComponent.Save(); stopConditionComponent.Save();
}; };
} }
@ -67,26 +70,30 @@ internal sealed class StopConditionComponent : ConfigComponent
return; return;
} }
bool v = base.Configuration.Stop.Enabled; bool v = base.Configuration.Stop.Enabled;
if (ImGui.Checkbox("Stop Questionable when any of the conditions below are met", ref v)) if (ImGui.Checkbox("Enable stop conditions", ref v))
{ {
base.Configuration.Stop.Enabled = v; base.Configuration.Stop.Enabled = v;
Save(); Save();
} }
ImGui.SameLine();
ImGuiComponents.HelpMarker("Pause: Stops automation when condition is met, but allows resuming past it.\nStop: True stop, blocks automation from starting/resuming if condition is already met.");
ImGui.Separator(); ImGui.Separator();
using (ImRaii.Disabled(!v)) using (ImRaii.Disabled(!v))
{ {
ImGui.Text("Stop when character level reaches:"); ImGui.Text("Stop when character level reaches:");
bool v2 = base.Configuration.Stop.LevelToStopAfter; int currentItem = (int)base.Configuration.Stop.LevelStopMode;
if (ImGui.Checkbox("Enable level stop condition", ref v2)) ImGui.SetNextItemWidth(100f);
if (ImGui.Combo((ImU8String)"##LevelMode", ref currentItem, (ReadOnlySpan<string>)StopModeNames, StopModeNames.Length))
{ {
base.Configuration.Stop.LevelToStopAfter = v2; base.Configuration.Stop.LevelStopMode = (Configuration.EStopConditionMode)currentItem;
Save(); Save();
} }
using (ImRaii.Disabled(!v2)) ImGui.SameLine();
using (ImRaii.Disabled(base.Configuration.Stop.LevelStopMode == Questionable.Configuration.EStopConditionMode.Off))
{ {
int data = base.Configuration.Stop.TargetLevel; int data = base.Configuration.Stop.TargetLevel;
ImGui.SetNextItemWidth(100f); ImGui.SetNextItemWidth(100f);
if (ImGui.InputInt("Stop at level", ref data, 1, 5)) if (ImGui.InputInt("Target level", ref data, 1, 5))
{ {
base.Configuration.Stop.TargetLevel = Math.Max(1, Math.Min(100, data)); base.Configuration.Stop.TargetLevel = Math.Max(1, Math.Min(100, data));
Save(); Save();
@ -103,18 +110,20 @@ internal sealed class StopConditionComponent : ConfigComponent
} }
} }
ImGui.Separator(); ImGui.Separator();
ImGui.Text("Stop on quest sequence:"); ImGui.Text("Stop on quest sequence (global):");
bool v3 = base.Configuration.Stop.SequenceToStopAfter; int currentItem2 = (int)base.Configuration.Stop.SequenceStopMode;
if (ImGui.Checkbox("Enable global quest sequence stop condition", ref v3)) ImGui.SetNextItemWidth(100f);
if (ImGui.Combo((ImU8String)"##SequenceMode", ref currentItem2, (ReadOnlySpan<string>)StopModeNames, StopModeNames.Length))
{ {
base.Configuration.Stop.SequenceToStopAfter = v3; base.Configuration.Stop.SequenceStopMode = (Configuration.EStopConditionMode)currentItem2;
Save(); Save();
} }
using (ImRaii.Disabled(!v3)) ImGui.SameLine();
using (ImRaii.Disabled(base.Configuration.Stop.SequenceStopMode == Questionable.Configuration.EStopConditionMode.Off))
{ {
int data2 = base.Configuration.Stop.TargetSequence; int data2 = base.Configuration.Stop.TargetSequence;
ImGui.SetNextItemWidth(100f); ImGui.SetNextItemWidth(100f);
if (ImGui.InputInt("Stop at sequence", ref data2, 1, 1)) if (ImGui.InputInt("Target sequence", ref data2, 1, 1))
{ {
base.Configuration.Stop.TargetSequence = Math.Max(0, Math.Min(255, data2)); base.Configuration.Stop.TargetSequence = Math.Max(0, Math.Min(255, data2));
Save(); Save();
@ -130,8 +139,8 @@ internal sealed class StopConditionComponent : ConfigComponent
text2.AppendLiteral(")"); text2.AppendLiteral(")");
ImGui.TextDisabled(text2); ImGui.TextDisabled(text2);
} }
ImGui.TextWrapped("Note: Individual quest sequences below override this global setting.");
} }
ImGui.TextWrapped("Note: Individual quest sequences below override this global setting.");
ImGui.Separator(); ImGui.Separator();
ImGui.Text("Stop when completing quests (or reaching specific sequences):"); ImGui.Text("Stop when completing quests (or reaching specific sequences):");
DrawCurrentlyAcceptedQuests(); DrawCurrentlyAcceptedQuests();
@ -143,11 +152,13 @@ internal sealed class StopConditionComponent : ConfigComponent
{ {
if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Trash, "Clear All")) if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Trash, "Clear All"))
{ {
base.Configuration.Stop.QuestsToStopAfter.Clear();
foreach (ElementId item in questsToStopAfter) foreach (ElementId item in questsToStopAfter)
{ {
base.Configuration.Stop.QuestSequences.Remove(item.ToString()); string key = item.ToString();
base.Configuration.Stop.QuestSequences.Remove(key);
base.Configuration.Stop.QuestStopModes.Remove(key);
} }
base.Configuration.Stop.QuestsToStopAfter.Clear();
Save(); Save();
} }
} }
@ -186,16 +197,34 @@ internal sealed class StopConditionComponent : ConfigComponent
{ {
_questTooltipComponent.Draw(quest2.Info); _questTooltipComponent.Draw(quest2.Info);
} }
ImGui.SameLine();
string text3 = elementId.ToString(); string text3 = elementId.ToString();
base.Configuration.Stop.QuestSequences.TryGetValue(text3, out var value); int currentItem3 = (int)base.Configuration.Stop.QuestStopModes.GetValueOrDefault(text3, Questionable.Configuration.EStopConditionMode.Stop);
bool v4 = value.HasValue; ImGui.SameLine();
ImU8String label = new ImU8String(8, 1); ImGui.SetNextItemWidth(70f);
label.AppendLiteral("##UseSeq"); ImU8String label = new ImU8String(6, 1);
label.AppendLiteral("##Mode");
label.AppendFormatted(text3); label.AppendFormatted(text3);
if (ImGui.Checkbox(label, ref v4)) if (ImGui.Combo(label, ref currentItem3, (ReadOnlySpan<string>)StopModeNames, StopModeNames.Length))
{ {
if (v4) if (currentItem3 == 0)
{
quest = quest2;
}
else
{
base.Configuration.Stop.QuestStopModes[text3] = (Configuration.EStopConditionMode)currentItem3;
Save();
}
}
ImGui.SameLine();
base.Configuration.Stop.QuestSequences.TryGetValue(text3, out var value);
bool v2 = value.HasValue;
ImU8String label2 = new ImU8String(8, 1);
label2.AppendLiteral("##UseSeq");
label2.AppendFormatted(text3);
if (ImGui.Checkbox(label2, ref v2))
{
if (v2)
{ {
base.Configuration.Stop.QuestSequences[text3] = 1; base.Configuration.Stop.QuestSequences[text3] = 1;
} }
@ -209,17 +238,17 @@ internal sealed class StopConditionComponent : ConfigComponent
{ {
ImGui.SetTooltip("Stop at specific sequence (unchecked = stop on quest completion)"); ImGui.SetTooltip("Stop at specific sequence (unchecked = stop on quest completion)");
} }
using (ImRaii.Disabled(!v4)) using (ImRaii.Disabled(!v2))
{ {
ImGui.SameLine(); ImGui.SameLine();
ImGui.Text("Seq:"); ImGui.Text("Seq:");
ImGui.SameLine(); ImGui.SameLine();
ImGui.SetNextItemWidth(85f); ImGui.SetNextItemWidth(85f);
int data3 = value ?? 1; int data3 = value ?? 1;
ImU8String label2 = new ImU8String(10, 1); ImU8String label3 = new ImU8String(10, 1);
label2.AppendLiteral("##SeqValue"); label3.AppendLiteral("##SeqValue");
label2.AppendFormatted(text3); label3.AppendFormatted(text3);
if (ImGui.InputInt(label2, ref data3, 1, 1) && v4) if (ImGui.InputInt(label3, ref data3, 1, 1) && v2)
{ {
base.Configuration.Stop.QuestSequences[text3] = Math.Max(0, Math.Min(255, data3)); base.Configuration.Stop.QuestSequences[text3] = Math.Max(0, Math.Min(255, data3));
Save(); Save();
@ -237,8 +266,10 @@ internal sealed class StopConditionComponent : ConfigComponent
} }
if (quest != null) if (quest != null)
{ {
string key2 = quest.Id.ToString();
base.Configuration.Stop.QuestsToStopAfter.Remove(quest.Id); base.Configuration.Stop.QuestsToStopAfter.Remove(quest.Id);
base.Configuration.Stop.QuestSequences.Remove(quest.Id.ToString()); base.Configuration.Stop.QuestSequences.Remove(key2);
base.Configuration.Stop.QuestStopModes.Remove(key2);
Save(); Save();
} }
} }
@ -286,6 +317,7 @@ internal sealed class StopConditionComponent : ConfigComponent
if (ImGuiComponents.IconButton($"##Add{item.Id}", FontAwesomeIcon.Plus)) if (ImGuiComponents.IconButton($"##Add{item.Id}", FontAwesomeIcon.Plus))
{ {
base.Configuration.Stop.QuestsToStopAfter.Add(item.Id); base.Configuration.Stop.QuestsToStopAfter.Add(item.Id);
base.Configuration.Stop.QuestStopModes[item.Id.ToString()] = Questionable.Configuration.EStopConditionMode.Stop;
Save(); Save();
} }
} }

View file

@ -103,7 +103,7 @@ internal sealed class QuestJournalUtils
} }
else else
{ {
ImGui.SetTooltip((incompletePrerequisiteQuests.Count == 1) ? "Add this quest to the priority list" : $"Add this quest and {incompletePrerequisiteQuests.Count - 1} required quest(s) to the priority list in completion order"); ImGui.SetTooltip((incompletePrerequisiteQuests.Count == 1) ? "Add this quest to the priority list" : $"Add this quest and {incompletePrerequisiteQuests.Count - 1} required {((incompletePrerequisiteQuests.Count - 1 == 1) ? "quest" : "quests")} to the priority list in completion order");
} }
} }
bool flag = _commandManager.Commands.ContainsKey("/questinfo"); bool flag = _commandManager.Commands.ContainsKey("/questinfo");

View file

@ -15,6 +15,7 @@ using Dalamud.Plugin.Services;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Questionable.Controller; using Questionable.Controller;
using Questionable.Controller.Steps.Shared; using Questionable.Controller.Steps.Shared;
using Questionable.External;
using Questionable.Functions; using Questionable.Functions;
using Questionable.Model; using Questionable.Model;
using Questionable.Model.Questing; using Questionable.Model.Questing;
@ -49,6 +50,8 @@ internal sealed class ActiveQuestComponent
private readonly IChatGui _chatGui; private readonly IChatGui _chatGui;
private readonly AutoDutyIpc _autoDutyIpc;
private readonly ILogger<ActiveQuestComponent> _logger; private readonly ILogger<ActiveQuestComponent> _logger;
public event EventHandler? Reload; public event EventHandler? Reload;
@ -60,7 +63,7 @@ internal sealed class ActiveQuestComponent
return _003CRegexGenerator_g_003EFBB8301322196CF81C64F1652C2FA6E1D6BF3907141F781E9D97ABED51BF056C4__MultipleWhitespaceRegex_0.Instance; return _003CRegexGenerator_g_003EFBB8301322196CF81C64F1652C2FA6E1D6BF3907141F781E9D97ABED51BF056C4__MultipleWhitespaceRegex_0.Instance;
} }
public ActiveQuestComponent(QuestController questController, MovementController movementController, CombatController combatController, GatheringController gatheringController, QuestFunctions questFunctions, ICommandManager commandManager, Configuration configuration, QuestRegistry questRegistry, PriorityWindow priorityWindow, UiUtils uiUtils, IObjectTable objectTable, IClientState clientState, IChatGui chatGui, ILogger<ActiveQuestComponent> logger) public ActiveQuestComponent(QuestController questController, MovementController movementController, CombatController combatController, GatheringController gatheringController, QuestFunctions questFunctions, ICommandManager commandManager, Configuration configuration, QuestRegistry questRegistry, PriorityWindow priorityWindow, UiUtils uiUtils, IObjectTable objectTable, IClientState clientState, IChatGui chatGui, AutoDutyIpc autoDutyIpc, ILogger<ActiveQuestComponent> logger)
{ {
_questController = questController; _questController = questController;
_movementController = movementController; _movementController = movementController;
@ -75,6 +78,7 @@ internal sealed class ActiveQuestComponent
_objectTable = objectTable; _objectTable = objectTable;
_clientState = clientState; _clientState = clientState;
_chatGui = chatGui; _chatGui = chatGui;
_autoDutyIpc = autoDutyIpc;
_logger = logger; _logger = logger;
} }
@ -146,30 +150,97 @@ internal sealed class ActiveQuestComponent
_logger.LogError(ex, "Could not handle active quest buttons"); _logger.LogError(ex, "Could not handle active quest buttons");
} }
DrawSimulationControls(); DrawSimulationControls();
return;
}
(bool isLevelLocked, int levelsNeeded, int requiredLevel, string? questName) msqLevelLockInfo = _questFunctions.GetMsqLevelLockInfo();
bool item = msqLevelLockInfo.isLevelLocked;
int item2 = msqLevelLockInfo.levelsNeeded;
int item3 = msqLevelLockInfo.requiredLevel;
string item4 = msqLevelLockInfo.questName;
int currentPlayerLevel = _objectTable.LocalPlayer?.Level ?? 0;
if (item && _autoDutyIpc.IsConfiguredToRunLevelingMode(currentPlayerLevel) && item4 != null)
{
Vector4 col = ImGuiColors.DalamudYellow;
ImU8String text = new ImU8String(22, 2);
text.AppendLiteral("MSQ '");
text.AppendFormatted(item4);
text.AppendLiteral("' requires level ");
text.AppendFormatted(item3);
ImGui.TextColored(in col, text);
col = ImGuiColors.DalamudGrey;
ImU8String text2 = new ImU8String(61, 2);
text2.AppendLiteral("You need ");
text2.AppendFormatted(item2);
text2.AppendLiteral(" more level");
text2.AppendFormatted((item2 == 1) ? string.Empty : "s");
text2.AppendLiteral(" - Leveling mode will start automatically");
ImGui.TextColored(in col, text2);
using (ImRaii.Disabled(_questController.IsRunning || !_autoDutyIpc.IsStopped()))
{
if (ImGuiComponents.IconButton(FontAwesomeIcon.Play))
{
_logger.LogInformation("Start button clicked with MSQ level-locked. Starting AutoDuty leveling mode.");
if (_autoDutyIpc.StartLevelingMode())
{
_chatGui.Print($"Starting AutoDuty Leveling mode to reach level {item3} for MSQ '{item4}'.", "Questionable", 576);
}
else
{
_chatGui.PrintError("Failed to start AutoDuty Leveling mode. Please check that AutoDuty is installed and configured.", "Questionable", 576);
}
}
}
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
{
if (!_autoDutyIpc.IsStopped())
{
ImGui.SetTooltip("AutoDuty is currently running.");
}
else
{
ImU8String tooltip = new ImU8String(53, 1);
tooltip.AppendLiteral("Start AutoDuty Leveling mode to reach level ");
tooltip.AppendFormatted(item3);
tooltip.AppendLiteral(" for MSQ.");
ImGui.SetTooltip(tooltip);
}
}
} }
else else
{ {
ImGui.Text("No active quest"); ImGui.Text("No active quest");
if (!isMinimized) }
if (!isMinimized)
{
Vector4 col = ImGuiColors.DalamudGrey;
ImU8String text3 = new ImU8String(14, 1);
text3.AppendFormatted(_questRegistry.Count);
text3.AppendLiteral(" quests loaded");
ImGui.TextColored(in col, text3);
}
ImGui.SameLine();
if (ImGuiComponents.IconButton(FontAwesomeIcon.Stop))
{
_movementController.Stop();
_questController.Stop("Manual (no active quest)");
_gatheringController.Stop("Manual (no active quest)");
if (!_autoDutyIpc.IsStopped())
{ {
Vector4 col = ImGuiColors.DalamudGrey; try
ImU8String text = new ImU8String(14, 1); {
text.AppendFormatted(_questRegistry.Count); _autoDutyIpc.Stop();
text.AppendLiteral(" quests loaded"); }
ImGui.TextColored(in col, text); catch (Exception exception)
} {
if (ImGuiComponents.IconButton(FontAwesomeIcon.Stop)) _logger.LogWarning(exception, "Failed to stop AutoDuty");
{ }
_movementController.Stop();
_questController.Stop("Manual (no active quest)");
_gatheringController.Stop("Manual (no active quest)");
}
ImGui.SameLine();
if (ImGuiComponents.IconButton(FontAwesomeIcon.SortAmountDown))
{
_priorityWindow.ToggleOrUncollapse();
} }
} }
ImGui.SameLine();
if (ImGuiComponents.IconButton(FontAwesomeIcon.SortAmountDown))
{
_priorityWindow.ToggleOrUncollapse();
}
} }
private void DrawQuestNames(QuestController.QuestProgress currentQuest, QuestController.ECurrentQuestType? currentQuestType) private void DrawQuestNames(QuestController.QuestProgress currentQuest, QuestController.ECurrentQuestType? currentQuestType)
@ -237,9 +308,9 @@ internal sealed class ActiveQuestComponent
ImGui.SameLine(); ImGui.SameLine();
ImGui.TextColored(ImGuiColors.DalamudRed, "Disabled"); ImGui.TextColored(ImGuiColors.DalamudRed, "Disabled");
} }
bool flag = _configuration.Stop.Enabled && _configuration.Stop.LevelToStopAfter; bool flag = _configuration.Stop.Enabled && _configuration.Stop.LevelStopMode != Configuration.EStopConditionMode.Off;
bool flag2 = _configuration.Stop.Enabled && _configuration.Stop.QuestsToStopAfter.Any((ElementId x) => !_questFunctions.IsQuestComplete(x) && !_questFunctions.IsQuestUnobtainable(x)); bool flag2 = _configuration.Stop.Enabled && _configuration.Stop.QuestsToStopAfter.Any((ElementId x) => !_questFunctions.IsQuestComplete(x) && !_questFunctions.IsQuestUnobtainable(x));
bool flag3 = _configuration.Stop.Enabled && _configuration.Stop.SequenceToStopAfter; bool flag3 = _configuration.Stop.Enabled && _configuration.Stop.SequenceStopMode != Configuration.EStopConditionMode.Off;
if (flag || flag2 || flag3) if (flag || flag2 || flag3)
{ {
ImGui.SameLine(); ImGui.SameLine();

View file

@ -253,12 +253,9 @@ internal sealed class EventInfoComponent
} }
if (_questFunctions.IsQuestUnobtainable(questInfo.QuestId)) if (_questFunctions.IsQuestUnobtainable(questInfo.QuestId))
{ {
if (_alreadyLoggedActiveSeasonalSkip.Add(questInfo.QuestId.Value)) continue;
{
_logger.LogDebug("Skipping quest {QuestId} '{Name}': marked unobtainable", questInfo.QuestId, questInfo.Name);
}
} }
else if (questInfo is UnlockLinkQuestInfo { QuestExpiry: var questExpiry }) if (questInfo is UnlockLinkQuestInfo { QuestExpiry: var questExpiry })
{ {
if (questExpiry.HasValue) if (questExpiry.HasValue)
{ {
@ -287,15 +284,10 @@ internal sealed class EventInfoComponent
if (seasonalQuestExpiry.HasValue) if (seasonalQuestExpiry.HasValue)
{ {
DateTime valueOrDefault2 = seasonalQuestExpiry.GetValueOrDefault(); DateTime valueOrDefault2 = seasonalQuestExpiry.GetValueOrDefault();
DateTime dateTime2 = NormalizeExpiry(valueOrDefault2); if (NormalizeExpiry(valueOrDefault2) > DateTime.UtcNow)
if (dateTime2 > DateTime.UtcNow)
{ {
yield return questInfo; yield return questInfo;
} }
else if (_alreadyLoggedActiveSeasonalSkip.Add(questInfo.QuestId.Value))
{
_logger.LogDebug("Skipping quest {QuestId} '{Name}': seasonal expiry {Expiry:o} UTC is not in the future", questInfo.QuestId, questInfo.Name, dateTime2);
}
} }
else if (questInfo2.IsSeasonalQuest && !questInfo2.SeasonalQuestExpiry.HasValue) else if (questInfo2.IsSeasonalQuest && !questInfo2.SeasonalQuestExpiry.HasValue)
{ {

View file

@ -103,10 +103,12 @@ internal sealed class PresetBuilderComponent
ImGui.SetTooltip("No preset quests are currently in the priority list."); ImGui.SetTooltip("No preset quests are currently in the priority list.");
return; return;
} }
ImU8String tooltip = new ImU8String(59, 1); ImU8String tooltip = new ImU8String(51, 2);
tooltip.AppendLiteral("Remove all "); tooltip.AppendLiteral("Remove all ");
tooltip.AppendFormatted(list.Count); tooltip.AppendFormatted(list.Count);
tooltip.AppendLiteral(" preset-related quest(s) from the priority list."); tooltip.AppendLiteral(" preset-related ");
tooltip.AppendFormatted((list.Count == 1) ? "quest" : "quests");
tooltip.AppendLiteral(" from the priority list.");
ImGui.SetTooltip(tooltip); ImGui.SetTooltip(tooltip);
} }
} }

View file

@ -58,11 +58,13 @@ internal sealed class Configuration : IPluginConfiguration
public Dictionary<string, int?> QuestSequences { get; set; } = new Dictionary<string, int?>(); public Dictionary<string, int?> QuestSequences { get; set; } = new Dictionary<string, int?>();
public bool LevelToStopAfter { get; set; } public Dictionary<string, EStopConditionMode> QuestStopModes { get; set; } = new Dictionary<string, EStopConditionMode>();
public EStopConditionMode LevelStopMode { get; set; }
public int TargetLevel { get; set; } = 50; public int TargetLevel { get; set; } = 50;
public bool SequenceToStopAfter { get; set; } public EStopConditionMode SequenceStopMode { get; set; }
public int TargetSequence { get; set; } = 1; public int TargetSequence { get; set; } = 1;
} }
@ -80,6 +82,8 @@ internal sealed class Configuration : IPluginConfiguration
public Dictionary<uint, EDutyMode> DutyModeOverrides { get; set; } = new Dictionary<uint, EDutyMode>(); public Dictionary<uint, EDutyMode> DutyModeOverrides { get; set; } = new Dictionary<uint, EDutyMode>();
public Dictionary<string, bool> ExpansionHeaderStates { get; set; } = new Dictionary<string, bool>(); public Dictionary<string, bool> ExpansionHeaderStates { get; set; } = new Dictionary<string, bool>();
public bool RunLevelingModeWhenUnderleveled { get; set; }
} }
internal sealed class SinglePlayerDutyConfiguration internal sealed class SinglePlayerDutyConfiguration
@ -135,6 +139,13 @@ internal sealed class Configuration : IPluginConfiguration
RotationSolverReborn RotationSolverReborn
} }
internal enum EStopConditionMode
{
Off,
Pause,
Stop
}
public sealed class ElementIdNConverter : JsonConverter<ElementId> public sealed class ElementIdNConverter : JsonConverter<ElementId>
{ {
public override void WriteJson(JsonWriter writer, ElementId? value, JsonSerializer serializer) public override void WriteJson(JsonWriter writer, ElementId? value, JsonSerializer serializer)

View file

@ -157,6 +157,8 @@ public sealed class QuestionablePlugin : IDalamudPlugin, IDisposable
serviceCollection.AddTaskExecutor<Duty.WaitForPartyTask, Duty.WaitForPartyExecutor>(); serviceCollection.AddTaskExecutor<Duty.WaitForPartyTask, Duty.WaitForPartyExecutor>();
serviceCollection.AddTaskExecutor<Duty.StartAutoDutyTask, Duty.StartAutoDutyExecutor>(); serviceCollection.AddTaskExecutor<Duty.StartAutoDutyTask, Duty.StartAutoDutyExecutor>();
serviceCollection.AddTaskExecutor<Duty.WaitAutoDutyTask, Duty.WaitAutoDutyExecutor>(); serviceCollection.AddTaskExecutor<Duty.WaitAutoDutyTask, Duty.WaitAutoDutyExecutor>();
serviceCollection.AddTaskExecutor<Duty.StartLevelingModeTask, Duty.StartLevelingModeExecutor>();
serviceCollection.AddTaskExecutor<Duty.WaitLevelingModeTask, Duty.WaitLevelingModeExecutor>();
serviceCollection.AddTaskFactory<Emote.Factory>(); serviceCollection.AddTaskFactory<Emote.Factory>();
serviceCollection.AddTaskExecutor<Emote.UseOnObject, Emote.UseOnObjectExecutor>(); serviceCollection.AddTaskExecutor<Emote.UseOnObject, Emote.UseOnObjectExecutor>();
serviceCollection.AddTaskExecutor<Emote.UseOnSelf, Emote.UseOnSelfExecutor>(); serviceCollection.AddTaskExecutor<Emote.UseOnSelf, Emote.UseOnSelfExecutor>();