using Dalamud.Game.ClientState.Objects;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game.Event;
-using FFXIVClientStructs.FFXIV.Client.Game.Object;
using FFXIVClientStructs.FFXIV.Client.Game.UI;
using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
{
private readonly IAddonLifecycle _addonLifecycle;
private readonly IDataManager _dataManager;
- private readonly GameFunctions _gameFunctions;
private readonly QuestFunctions _questFunctions;
+ private readonly AetheryteFunctions _aetheryteFunctions;
private readonly ExcelFunctions _excelFunctions;
private readonly QuestController _questController;
private readonly QuestRegistry _questRegistry;
public GameUiController(
IAddonLifecycle addonLifecycle,
IDataManager dataManager,
- GameFunctions gameFunctions,
QuestFunctions questFunctions,
+ AetheryteFunctions aetheryteFunctions,
ExcelFunctions excelFunctions,
QuestController questController,
QuestRegistry questRegistry,
{
_addonLifecycle = addonLifecycle;
_dataManager = dataManager;
- _gameFunctions = gameFunctions;
_questFunctions = questFunctions;
+ _aetheryteFunctions = aetheryteFunctions;
_excelFunctions = excelFunctions;
_questController = questController;
_questRegistry = questRegistry;
private unsafe bool HandleTravelYesNo(AddonSelectYesno* addonSelectYesno,
QuestController.QuestProgress currentQuest, string actualPrompt)
{
- if (_gameFunctions.ReturnRequestedAt >= DateTime.Now.AddSeconds(-2) && _returnRegex.IsMatch(actualPrompt))
+ if (_aetheryteFunctions.ReturnRequestedAt >= DateTime.Now.AddSeconds(-2) && _returnRegex.IsMatch(actualPrompt))
{
_logger.LogInformation("Automatically confirming return...");
addonSelectYesno->AtkUnitBase.FireCallbackInt(0);
return currentStep?.AetheryteShortcut != null;
}
- private List<ElementId> GetPriorityQuests()
- {
- List<ElementId> priorityQuests =
- [
- new QuestId(1157), // Garuda (Hard)
- new QuestId(1158), // Titan (Hard)
- ..QuestData.CrystalTowerQuests
- ];
-
- EClassJob classJob = (EClassJob?)_clientState.LocalPlayer?.ClassJob.Id ?? EClassJob.Adventurer;
- ushort[] shadowbringersRoleQuestChapters = QuestData.AllRoleQuestChapters.Select(x => x[0]).ToArray();
- if (classJob != EClassJob.Adventurer)
- {
- priorityQuests.AddRange(_questRegistry.GetKnownClassJobQuests(classJob)
- .Where(x =>
- {
- if (!_questRegistry.TryGetQuest(x.QuestId, out Quest? quest) ||
- quest.Info is not QuestInfo questInfo)
- return false;
-
- // if no shadowbringers role quest is complete, (at least one) is required
- if (shadowbringersRoleQuestChapters.Contains(questInfo.NewGamePlusChapter))
- return !QuestData.FinalShadowbringersRoleQuests.Any(_questFunctions.IsQuestComplete);
-
- // ignore all other role quests
- if (QuestData.AllRoleQuestChapters.Any(y => y.Contains(questInfo.NewGamePlusChapter)))
- return false;
-
- // even job quests for the later expacs (after role quests were introduced) might have skills locked
- // behind them, e.g. reaper and sage
-
- return true;
- })
- .Select(x => x.QuestId));
- }
-
- return priorityQuests;
- }
-
public bool TryPickPriorityQuest()
{
if (!IsInterruptible() || _nextQuest != null || _gatheringQuest != null || _simulatedQuest != null)
return false;
+ ElementId? priorityQuestId = _questFunctions.GetNextPriorityQuestThatCanBeAccepted();
+ if (priorityQuestId == null)
+ return false;
+
// don't start a second priority quest until the first one is resolved
- List<ElementId> priorityQuests = GetPriorityQuests();
- if (_startedQuest != null && priorityQuests.Contains(_startedQuest.Quest.Id))
+ if (_startedQuest != null && priorityQuestId == _startedQuest.Quest.Id)
return false;
- foreach (ElementId questId in priorityQuests)
+ if (_questRegistry.TryGetQuest(priorityQuestId, out var quest))
{
- if (!_questFunctions.IsReadyToAcceptQuest(questId) || !_questRegistry.TryGetQuest(questId, out var quest))
- continue;
-
- var firstStep = quest.FindSequence(0)?.FindStep(0);
- if (firstStep == null)
- continue;
-
- if (firstStep.AetheryteShortcut is { } aetheryteShortcut)
- {
- if (_gameFunctions.IsAetheryteUnlocked(aetheryteShortcut))
- {
- _logger.LogInformation("Priority quest is accessible via aetheryte {Aetheryte}", aetheryteShortcut);
- SetNextQuest(quest);
-
- _chatGui.Print(
- $"[Questionable] Picking up quest '{quest.Info.Name}' as a priority over current main story/side quests.");
- return true;
- }
- else
- {
- _logger.LogWarning("Ignoring priority quest {QuestId} / {QuestName}, aetheryte locked", quest.Id,
- quest.Info.Name);
- }
- }
-
- if (firstStep is { InteractionType: EInteractionType.UseItem, ItemId: UseItem.VesperBayAetheryteTicket })
- {
- _logger.LogInformation("Priority quest is accessible via vesper bay");
- SetNextQuest(quest);
-
- _chatGui.Print(
- $"[Questionable] Picking up quest '{quest.Info.Name}' as a priority over current main story/side quests.");
- return true;
- }
- else
- _logger.LogTrace("Ignoring priority quest {QuestId} / {QuestName}, as we don't know how to get there",
- questId, quest.Info.Name);
+ SetNextQuest(quest);
+ return true;
}
return false;
}
}
- internal sealed class DoAttune(GameFunctions gameFunctions, ILogger<DoAttune> logger) : ITask
+ internal sealed class DoAttune(
+ AetheryteFunctions aetheryteFunctions,
+ GameFunctions gameFunctions,
+ ILogger<DoAttune> logger) : ITask
{
public EAetheryteLocation AetheryteLocation { get; set; }
public bool Start()
{
- if (!gameFunctions.IsAetheryteUnlocked(AetheryteLocation))
+ if (!aetheryteFunctions.IsAetheryteUnlocked(AetheryteLocation))
{
logger.LogInformation("Attuning to aethernet shard {AethernetShard}", AetheryteLocation);
gameFunctions.InteractWith((uint)AetheryteLocation, ObjectKind.Aetheryte);
}
public ETaskResult Update() =>
- gameFunctions.IsAetheryteUnlocked(AetheryteLocation)
+ aetheryteFunctions.IsAetheryteUnlocked(AetheryteLocation)
? ETaskResult.TaskComplete
: ETaskResult.StillRunning;
}
}
- internal sealed class DoAttune(GameFunctions gameFunctions, ILogger<DoAttune> logger) : ITask
+ internal sealed class DoAttune(
+ AetheryteFunctions aetheryteFunctions,
+ GameFunctions gameFunctions,
+ ILogger<DoAttune> logger) : ITask
{
public EAetheryteLocation AetheryteLocation { get; set; }
public bool Start()
{
- if (!gameFunctions.IsAetheryteUnlocked(AetheryteLocation))
+ if (!aetheryteFunctions.IsAetheryteUnlocked(AetheryteLocation))
{
logger.LogInformation("Attuning to aetheryte {Aetheryte}", AetheryteLocation);
gameFunctions.InteractWith((uint)AetheryteLocation);
}
public ETaskResult Update() =>
- gameFunctions.IsAetheryteUnlocked(AetheryteLocation)
+ aetheryteFunctions.IsAetheryteUnlocked(AetheryteLocation)
? ETaskResult.TaskComplete
: ETaskResult.StillRunning;
internal sealed class UseAethernetShortcut(
ILogger<UseAethernetShortcut> logger,
- GameFunctions gameFunctions,
+ AetheryteFunctions aetheryteFunctions,
IClientState clientState,
AetheryteData aetheryteData,
LifestreamIpc lifestreamIpc,
}
if (SkipConditions.AetheryteLocked != null &&
- !gameFunctions.IsAetheryteUnlocked(SkipConditions.AetheryteLocked.Value))
+ !aetheryteFunctions.IsAetheryteUnlocked(SkipConditions.AetheryteLocked.Value))
{
logger.LogInformation("Skipping aethernet shortcut because the target aetheryte is locked");
return false;
}
if (SkipConditions.AetheryteUnlocked != null &&
- gameFunctions.IsAetheryteUnlocked(SkipConditions.AetheryteUnlocked.Value))
+ aetheryteFunctions.IsAetheryteUnlocked(SkipConditions.AetheryteUnlocked.Value))
{
logger.LogInformation("Skipping aethernet shortcut because the target aetheryte is unlocked");
return false;
}
}
- if (gameFunctions.IsAetheryteUnlocked(From) &&
- gameFunctions.IsAetheryteUnlocked(To))
+ if (aetheryteFunctions.IsAetheryteUnlocked(From) &&
+ aetheryteFunctions.IsAetheryteUnlocked(To))
{
ushort territoryType = clientState.TerritoryType;
Vector3 playerPosition = clientState.LocalPlayer!.Position;
{
internal sealed class Factory(
IServiceProvider serviceProvider,
- GameFunctions gameFunctions,
+ AetheryteFunctions aetheryteFunctions,
AetheryteData aetheryteData) : ITaskFactory
{
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
.With(step, step.AetheryteShortcut.Value, aetheryteData.TerritoryIds[step.AetheryteShortcut.Value]);
return
[
- new WaitConditionTask(() => gameFunctions.CanTeleport(step.AetheryteShortcut.Value), "CanTeleport"),
+ new WaitConditionTask(() => aetheryteFunctions.CanTeleport(step.AetheryteShortcut.Value), "CanTeleport"),
task
];
}
internal sealed class UseAetheryteShortcut(
ILogger<UseAetheryteShortcut> logger,
- GameFunctions gameFunctions,
+ AetheryteFunctions aetheryteFunctions,
IClientState clientState,
IChatGui chatGui,
AetheryteData aetheryteData) : ISkippableTask
}
if (skipConditions.AetheryteLocked != null &&
- !gameFunctions.IsAetheryteUnlocked(skipConditions.AetheryteLocked.Value))
+ !aetheryteFunctions.IsAetheryteUnlocked(skipConditions.AetheryteLocked.Value))
{
logger.LogInformation("Skipping aetheryte teleport due to SkipCondition (AetheryteLocked)");
return false;
}
if (skipConditions.AetheryteUnlocked != null &&
- gameFunctions.IsAetheryteUnlocked(skipConditions.AetheryteUnlocked.Value))
+ aetheryteFunctions.IsAetheryteUnlocked(skipConditions.AetheryteUnlocked.Value))
{
logger.LogInformation("Skipping aetheryte teleport due to SkipCondition (AetheryteUnlocked)");
return false;
}
}
- if (!gameFunctions.IsAetheryteUnlocked(TargetAetheryte))
+ if (!aetheryteFunctions.IsAetheryteUnlocked(TargetAetheryte))
{
chatGui.PrintError($"[Questionable] Aetheryte {TargetAetheryte} is not unlocked.");
throw new TaskException("Aetheryte is not unlocked");
}
- else if (gameFunctions.TeleportAetheryte(TargetAetheryte))
+ else if (aetheryteFunctions.TeleportAetheryte(TargetAetheryte))
{
logger.LogInformation("Travelling via aetheryte...");
return true;
internal sealed class CheckSkip(
ILogger<CheckSkip> logger,
+ AetheryteFunctions aetheryteFunctions,
GameFunctions gameFunctions,
QuestFunctions questFunctions,
IClientState clientState) : ITask
DataId: not null,
InteractionType: EInteractionType.AttuneAetheryte or EInteractionType.AttuneAethernetShard
} &&
- gameFunctions.IsAetheryteUnlocked((EAetheryteLocation)Step.DataId.Value))
+ aetheryteFunctions.IsAetheryteUnlocked((EAetheryteLocation)Step.DataId.Value))
{
logger.LogInformation("Skipping step, as aetheryte/aethernet shard is unlocked");
return true;
}
if (SkipConditions.AetheryteLocked != null &&
- !gameFunctions.IsAetheryteUnlocked(SkipConditions.AetheryteLocked.Value))
+ !aetheryteFunctions.IsAetheryteUnlocked(SkipConditions.AetheryteLocked.Value))
{
logger.LogInformation("Skipping step, as aetheryte is locked");
return true;
}
if (SkipConditions.AetheryteUnlocked != null &&
- gameFunctions.IsAetheryteUnlocked(SkipConditions.AetheryteUnlocked.Value))
+ aetheryteFunctions.IsAetheryteUnlocked(SkipConditions.AetheryteUnlocked.Value))
{
logger.LogInformation("Skipping step, as aetheryte is unlocked");
return true;
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Dalamud.Plugin.Services;
using LLib.GameData;
return _quests[elementId] ?? throw new ArgumentOutOfRangeException(nameof(elementId));
}
+ public bool TryGetQuestInfo(ElementId elementId, [NotNullWhen(true)] out IQuestInfo? questInfo)
+ {
+ return _quests.TryGetValue(elementId, out questInfo);
+ }
+
public List<IQuestInfo> GetAllByIssuerDataId(uint targetId)
{
return _quests.Values
--- /dev/null
+using System;
+using FFXIVClientStructs.FFXIV.Client.Game;
+using FFXIVClientStructs.FFXIV.Client.Game.UI;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Questionable.Model.Common;
+using Questionable.Model.Questing;
+
+namespace Questionable.Functions;
+
+internal sealed unsafe class AetheryteFunctions
+{
+ private readonly IServiceProvider _serviceProvider;
+ private readonly ILogger<AetheryteFunctions> _logger;
+
+ public AetheryteFunctions(IServiceProvider serviceProvider, ILogger<AetheryteFunctions> logger)
+ {
+ _serviceProvider = serviceProvider;
+ _logger = logger;
+ }
+
+ public DateTime ReturnRequestedAt { get; set; } = DateTime.MinValue;
+
+ public bool IsAetheryteUnlocked(uint aetheryteId, out byte subIndex)
+ {
+ subIndex = 0;
+
+ var uiState = UIState.Instance();
+ return uiState != null && uiState->IsAetheryteUnlocked(aetheryteId);
+ }
+
+ public bool IsAetheryteUnlocked(EAetheryteLocation aetheryteLocation)
+ {
+ if (aetheryteLocation == EAetheryteLocation.IshgardFirmament)
+ return _serviceProvider.GetRequiredService<QuestFunctions>().IsQuestComplete(new QuestId(3672));
+ return IsAetheryteUnlocked((uint)aetheryteLocation, out _);
+ }
+
+ public bool CanTeleport(EAetheryteLocation aetheryteLocation)
+ {
+ if ((ushort)aetheryteLocation == PlayerState.Instance()->HomeAetheryteId &&
+ ActionManager.Instance()->GetActionStatus(ActionType.GeneralAction, 8) == 0)
+ return true;
+
+ return ActionManager.Instance()->GetActionStatus(ActionType.Action, 5) == 0;
+ }
+
+ public bool TeleportAetheryte(uint aetheryteId)
+ {
+ _logger.LogDebug("Attempting to teleport to aetheryte {AetheryteId}", aetheryteId);
+ if (IsAetheryteUnlocked(aetheryteId, out var subIndex))
+ {
+ if (aetheryteId == PlayerState.Instance()->HomeAetheryteId &&
+ ActionManager.Instance()->GetActionStatus(ActionType.GeneralAction, 8) == 0)
+ {
+ ReturnRequestedAt = DateTime.Now;
+ if (ActionManager.Instance()->UseAction(ActionType.GeneralAction, 8))
+ {
+ _logger.LogInformation("Using 'return' for home aetheryte");
+ return true;
+ }
+ }
+
+ if (ActionManager.Instance()->GetActionStatus(ActionType.Action, 5) == 0)
+ {
+ // fallback if return isn't available or (more likely) on a different aetheryte
+ _logger.LogInformation("Teleporting to aetheryte {AetheryteId}", aetheryteId);
+ return Telepo.Instance()->Teleport(aetheryteId, subIndex);
+ }
+ }
+
+ return false;
+ }
+
+ public bool TeleportAetheryte(EAetheryteLocation aetheryteLocation)
+ => TeleportAetheryte((uint)aetheryteLocation);
+}
.AsReadOnly();
}
- public DateTime ReturnRequestedAt { get; set; } = DateTime.MinValue;
-
- public bool IsAetheryteUnlocked(uint aetheryteId, out byte subIndex)
- {
- subIndex = 0;
-
- var uiState = UIState.Instance();
- return uiState != null && uiState->IsAetheryteUnlocked(aetheryteId);
- }
-
- public bool IsAetheryteUnlocked(EAetheryteLocation aetheryteLocation)
- {
- if (aetheryteLocation == EAetheryteLocation.IshgardFirmament)
- return _questFunctions.IsQuestComplete(new QuestId(3672));
- return IsAetheryteUnlocked((uint)aetheryteLocation, out _);
- }
-
- public bool CanTeleport(EAetheryteLocation aetheryteLocation)
- {
- if ((ushort)aetheryteLocation == PlayerState.Instance()->HomeAetheryteId &&
- ActionManager.Instance()->GetActionStatus(ActionType.GeneralAction, 8) == 0)
- return true;
-
- return ActionManager.Instance()->GetActionStatus(ActionType.Action, 5) == 0;
- }
-
- public bool TeleportAetheryte(uint aetheryteId)
- {
- _logger.LogDebug("Attempting to teleport to aetheryte {AetheryteId}", aetheryteId);
- if (IsAetheryteUnlocked(aetheryteId, out var subIndex))
- {
- if (aetheryteId == PlayerState.Instance()->HomeAetheryteId &&
- ActionManager.Instance()->GetActionStatus(ActionType.GeneralAction, 8) == 0)
- {
- ReturnRequestedAt = DateTime.Now;
- if (ActionManager.Instance()->UseAction(ActionType.GeneralAction, 8))
- {
- _logger.LogInformation("Using 'return' for home aetheryte");
- return true;
- }
- }
-
- if (ActionManager.Instance()->GetActionStatus(ActionType.Action, 5) == 0)
- {
- // fallback if return isn't available or (more likely) on a different aetheryte
- _logger.LogInformation("Teleporting to aetheryte {AetheryteId}", aetheryteId);
- return Telepo.Instance()->Teleport(aetheryteId, subIndex);
- }
- }
-
- return false;
- }
-
- public bool TeleportAetheryte(EAetheryteLocation aetheryteLocation)
- => TeleportAetheryte((uint)aetheryteLocation);
-
public bool IsFlyingUnlocked(ushort territoryId)
{
if (_configuration.Advanced.NeverFly)
using System;
+using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Dalamud.Memory;
using LLib.GameUI;
using Lumina.Excel.GeneratedSheets;
using Questionable.Controller;
+using Questionable.Controller.Steps.Interactions;
using Questionable.Data;
using Questionable.Model;
+using Questionable.Model.Common;
using Questionable.Model.Questing;
using GrandCompany = FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany;
using Quest = Questionable.Model.Quest;
{
private readonly QuestRegistry _questRegistry;
private readonly QuestData _questData;
+ private readonly AetheryteFunctions _aetheryteFunctions;
private readonly Configuration _configuration;
private readonly IDataManager _dataManager;
private readonly IClientState _clientState;
private readonly IGameGui _gameGui;
- public QuestFunctions(QuestRegistry questRegistry, QuestData questData, Configuration configuration,
- IDataManager dataManager, IClientState clientState, IGameGui gameGui)
+ public QuestFunctions(
+ QuestRegistry questRegistry,
+ QuestData questData,
+ AetheryteFunctions aetheryteFunctions,
+ Configuration configuration,
+ IDataManager dataManager,
+ IClientState clientState,
+ IGameGui gameGui)
{
_questRegistry = questRegistry;
_questData = questData;
+ _aetheryteFunctions = aetheryteFunctions;
_configuration = configuration;
_dataManager = dataManager;
_clientState = clientState;
{
// always prioritize accepting MSQ quests, to make sure we don't turn in one MSQ quest and then go off to do
// side quests until the end of time.
- var msqQuest = GetMainScenarioQuest(questManager);
- if (msqQuest.CurrentQuest is { Value: not 0 } && _questRegistry.IsKnownQuest(msqQuest.CurrentQuest))
+ var msqQuest = GetMainScenarioQuest();
+ if (msqQuest.CurrentQuest != null && !_questRegistry.IsKnownQuest(msqQuest.CurrentQuest))
+ msqQuest = default;
+
+ if (msqQuest.CurrentQuest != null && !IsQuestAccepted(msqQuest.CurrentQuest))
return msqQuest;
// Use the quests in the same order as they're shown in the to-do list, e.g. if the MSQ is the first item,
return (currentQuest, QuestManager.GetQuestSequence(currentQuest.Value));
}
- // if we know no quest of those currently in the to-do list, just do MSQ
- return msqQuest;
+ ElementId? priorityQuest = GetNextPriorityQuestThatCanBeAccepted();
+ if (priorityQuest != null)
+ {
+ // if we have an accepted msq quest, and know of no quest of those currently in the to-do list...
+ // (1) try and find a priority quest to do
+ return (priorityQuest, QuestManager.GetQuestSequence(priorityQuest.Value));
+ }
+ else if (msqQuest.CurrentQuest != null)
+ {
+ // (2) just do a normal msq quest
+ return msqQuest;
+ }
}
return default;
}
- private (QuestId? CurrentQuest, byte Sequence) GetMainScenarioQuest(QuestManager* questManager)
+ private (QuestId? CurrentQuest, byte Sequence) GetMainScenarioQuest()
{
if (QuestManager.IsQuestComplete(3759)) // Memories Rekindled
{
return default;
// if the MSQ is hidden, we generally ignore it
+ QuestManager* questManager = QuestManager.Instance();
if (IsQuestAccepted(currentQuest) && questManager->GetQuestById(currentQuest.Value)->IsHidden)
return default;
return null;
}
+ public ElementId? GetNextPriorityQuestThatCanBeAccepted()
+ {
+ return GetPriorityQuestsThatCanBeAccepted()
+ .FirstOrDefault(x =>
+ {
+ if (!_questRegistry.TryGetQuest(x, out Quest? quest))
+ return false;
+
+ var firstStep = quest.FindSequence(0)?.FindStep(0);
+ if (firstStep == null)
+ return false;
+
+ if (firstStep.AetheryteShortcut is { } aetheryteShortcut &&
+ _aetheryteFunctions.IsAetheryteUnlocked(aetheryteShortcut))
+ return true;
+
+ if (firstStep is
+ { InteractionType: EInteractionType.UseItem, ItemId: UseItem.VesperBayAetheryteTicket })
+ return true;
+
+ return false;
+ });
+ }
+
+ private List<ElementId> GetPriorityQuestsThatCanBeAccepted()
+ {
+ List<ElementId> priorityQuests =
+ [
+ new QuestId(1157), // Garuda (Hard)
+ new QuestId(1158), // Titan (Hard)
+ ..QuestData.CrystalTowerQuests
+ ];
+
+ EClassJob classJob = (EClassJob?)_clientState.LocalPlayer?.ClassJob.Id ?? EClassJob.Adventurer;
+ ushort[] shadowbringersRoleQuestChapters = QuestData.AllRoleQuestChapters.Select(x => x[0]).ToArray();
+ if (classJob != EClassJob.Adventurer)
+ {
+ priorityQuests.AddRange(_questRegistry.GetKnownClassJobQuests(classJob)
+ .Where(x =>
+ {
+ if (!_questRegistry.TryGetQuest(x.QuestId, out Quest? quest) ||
+ quest.Info is not QuestInfo questInfo)
+ return false;
+
+ // if no shadowbringers role quest is complete, (at least one) is required
+ if (shadowbringersRoleQuestChapters.Contains(questInfo.NewGamePlusChapter))
+ return !QuestData.FinalShadowbringersRoleQuests.Any(IsQuestComplete);
+
+ // ignore all other role quests
+ if (QuestData.AllRoleQuestChapters.Any(y => y.Contains(questInfo.NewGamePlusChapter)))
+ return false;
+
+ // even job quests for the later expacs (after role quests were introduced) might have skills locked
+ // behind them, e.g. reaper and sage
+
+ return true;
+ })
+ .Select(x => x.QuestId));
+ }
+
+ return priorityQuests
+ .Where(_questRegistry.IsKnownQuest)
+ .Where(IsReadyToAcceptQuest)
+ .ToList();
+ }
+
public bool IsReadyToAcceptQuest(ElementId questId)
{
_questRegistry.TryGetQuest(questId, out var quest);
public ushort Level { get; }
public uint IssuerDataId { get; }
public bool IsRepeatable { get; }
- public ImmutableList<QuestId> PreviousQuests { get; }
+ public ImmutableList<QuestId> PreviousQuests { get; set; }
public QuestJoin PreviousQuestJoin { get; }
public ImmutableList<QuestId> QuestLocks { get; }
public QuestJoin QuestLockJoin { get; }
All = 1,
AtLeastOne = 2,
}
+
+ public void AddPreviousQuest(QuestId questId)
+ {
+ PreviousQuests = [..PreviousQuests, questId];
+ }
}
private static void AddBasicFunctionsAndData(ServiceCollection serviceCollection)
{
+ serviceCollection.AddSingleton<AetheryteFunctions>();
serviceCollection.AddSingleton<ExcelFunctions>();
serviceCollection.AddSingleton<GameFunctions>();
serviceCollection.AddSingleton<ChatFunctions>();
private readonly GatheringController _gatheringController;
private readonly QuestFunctions _questFunctions;
private readonly ICommandManager _commandManager;
- private readonly IDalamudPluginInterface _pluginInterface;
private readonly Configuration _configuration;
private readonly QuestRegistry _questRegistry;
private readonly IChatGui _chatGui;
GatheringController gatheringController,
QuestFunctions questFunctions,
ICommandManager commandManager,
- IDalamudPluginInterface pluginInterface,
Configuration configuration,
QuestRegistry questRegistry,
IChatGui chatGui)
_gatheringController = gatheringController;
_questFunctions = questFunctions;
_commandManager = commandManager;
- _pluginInterface = pluginInterface;
_configuration = configuration;
_questRegistry = questRegistry;
_chatGui = chatGui;
foreach (var q in quest.PreviousQuests)
{
- var qInfo = _questData.GetQuestInfo(q);
- var (iconColor, icon, _) = _uiUtils.GetQuestStyle(q);
- if (!_questRegistry.IsKnownQuest(qInfo.QuestId))
- iconColor = ImGuiColors.DalamudGrey;
-
- _uiUtils.ChecklistItem(FormatQuestUnlockName(qInfo), iconColor, icon);
-
- if (qInfo is QuestInfo qstInfo && (counter <= 2 || icon != FontAwesomeIcon.Check))
- DrawQuestUnlocks(qstInfo, counter + 1);
+ if (_questData.TryGetQuestInfo(q, out var qInfo))
+ {
+ var (iconColor, icon, _) = _uiUtils.GetQuestStyle(q);
+ if (!_questRegistry.IsKnownQuest(qInfo.QuestId))
+ iconColor = ImGuiColors.DalamudGrey;
+
+ _uiUtils.ChecklistItem(FormatQuestUnlockName(qInfo), iconColor, icon);
+
+ if (qInfo is QuestInfo qstInfo && (counter <= 2 || icon != FontAwesomeIcon.Check))
+ DrawQuestUnlocks(qstInfo, counter + 1);
+ }
+ else
+ {
+ using var _ = ImRaii.Disabled();
+ _uiUtils.ChecklistItem($"Unknown Quest ({q})", ImGuiColors.DalamudGrey, FontAwesomeIcon.Question);
+ }
}
}