forked from liza/Questionable
Start available priority quests if no msq quest is available
This commit is contained in:
parent
eb97c60065
commit
ada2cf2833
@ -8,7 +8,6 @@ using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
|||||||
using Dalamud.Game.ClientState.Objects;
|
using Dalamud.Game.ClientState.Objects;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Event;
|
using FFXIVClientStructs.FFXIV.Client.Game.Event;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||||
@ -31,8 +30,8 @@ internal sealed class GameUiController : IDisposable
|
|||||||
{
|
{
|
||||||
private readonly IAddonLifecycle _addonLifecycle;
|
private readonly IAddonLifecycle _addonLifecycle;
|
||||||
private readonly IDataManager _dataManager;
|
private readonly IDataManager _dataManager;
|
||||||
private readonly GameFunctions _gameFunctions;
|
|
||||||
private readonly QuestFunctions _questFunctions;
|
private readonly QuestFunctions _questFunctions;
|
||||||
|
private readonly AetheryteFunctions _aetheryteFunctions;
|
||||||
private readonly ExcelFunctions _excelFunctions;
|
private readonly ExcelFunctions _excelFunctions;
|
||||||
private readonly QuestController _questController;
|
private readonly QuestController _questController;
|
||||||
private readonly QuestRegistry _questRegistry;
|
private readonly QuestRegistry _questRegistry;
|
||||||
@ -46,8 +45,8 @@ internal sealed class GameUiController : IDisposable
|
|||||||
public GameUiController(
|
public GameUiController(
|
||||||
IAddonLifecycle addonLifecycle,
|
IAddonLifecycle addonLifecycle,
|
||||||
IDataManager dataManager,
|
IDataManager dataManager,
|
||||||
GameFunctions gameFunctions,
|
|
||||||
QuestFunctions questFunctions,
|
QuestFunctions questFunctions,
|
||||||
|
AetheryteFunctions aetheryteFunctions,
|
||||||
ExcelFunctions excelFunctions,
|
ExcelFunctions excelFunctions,
|
||||||
QuestController questController,
|
QuestController questController,
|
||||||
QuestRegistry questRegistry,
|
QuestRegistry questRegistry,
|
||||||
@ -60,8 +59,8 @@ internal sealed class GameUiController : IDisposable
|
|||||||
{
|
{
|
||||||
_addonLifecycle = addonLifecycle;
|
_addonLifecycle = addonLifecycle;
|
||||||
_dataManager = dataManager;
|
_dataManager = dataManager;
|
||||||
_gameFunctions = gameFunctions;
|
|
||||||
_questFunctions = questFunctions;
|
_questFunctions = questFunctions;
|
||||||
|
_aetheryteFunctions = aetheryteFunctions;
|
||||||
_excelFunctions = excelFunctions;
|
_excelFunctions = excelFunctions;
|
||||||
_questController = questController;
|
_questController = questController;
|
||||||
_questRegistry = questRegistry;
|
_questRegistry = questRegistry;
|
||||||
@ -570,7 +569,7 @@ internal sealed class GameUiController : IDisposable
|
|||||||
private unsafe bool HandleTravelYesNo(AddonSelectYesno* addonSelectYesno,
|
private unsafe bool HandleTravelYesNo(AddonSelectYesno* addonSelectYesno,
|
||||||
QuestController.QuestProgress currentQuest, string actualPrompt)
|
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...");
|
_logger.LogInformation("Automatically confirming return...");
|
||||||
addonSelectYesno->AtkUnitBase.FireCallbackInt(0);
|
addonSelectYesno->AtkUnitBase.FireCallbackInt(0);
|
||||||
|
@ -752,94 +752,23 @@ internal sealed class QuestController : MiniTaskController<QuestController>, IDi
|
|||||||
return currentStep?.AetheryteShortcut != null;
|
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()
|
public bool TryPickPriorityQuest()
|
||||||
{
|
{
|
||||||
if (!IsInterruptible() || _nextQuest != null || _gatheringQuest != null || _simulatedQuest != null)
|
if (!IsInterruptible() || _nextQuest != null || _gatheringQuest != null || _simulatedQuest != null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// don't start a second priority quest until the first one is resolved
|
ElementId? priorityQuestId = _questFunctions.GetNextPriorityQuestThatCanBeAccepted();
|
||||||
List<ElementId> priorityQuests = GetPriorityQuests();
|
if (priorityQuestId == null)
|
||||||
if (_startedQuest != null && priorityQuests.Contains(_startedQuest.Quest.Id))
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
foreach (ElementId questId in priorityQuests)
|
// don't start a second priority quest until the first one is resolved
|
||||||
|
if (_startedQuest != null && priorityQuestId == _startedQuest.Quest.Id)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (_questRegistry.TryGetQuest(priorityQuestId, out var quest))
|
||||||
{
|
{
|
||||||
if (!_questFunctions.IsReadyToAcceptQuest(questId) || !_questRegistry.TryGetQuest(questId, out var quest))
|
SetNextQuest(quest);
|
||||||
continue;
|
return true;
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
@ -25,7 +25,10 @@ internal static class AethernetShard
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 EAetheryteLocation AetheryteLocation { get; set; }
|
||||||
|
|
||||||
@ -37,7 +40,7 @@ internal static class AethernetShard
|
|||||||
|
|
||||||
public bool Start()
|
public bool Start()
|
||||||
{
|
{
|
||||||
if (!gameFunctions.IsAetheryteUnlocked(AetheryteLocation))
|
if (!aetheryteFunctions.IsAetheryteUnlocked(AetheryteLocation))
|
||||||
{
|
{
|
||||||
logger.LogInformation("Attuning to aethernet shard {AethernetShard}", AetheryteLocation);
|
logger.LogInformation("Attuning to aethernet shard {AethernetShard}", AetheryteLocation);
|
||||||
gameFunctions.InteractWith((uint)AetheryteLocation, ObjectKind.Aetheryte);
|
gameFunctions.InteractWith((uint)AetheryteLocation, ObjectKind.Aetheryte);
|
||||||
@ -49,7 +52,7 @@ internal static class AethernetShard
|
|||||||
}
|
}
|
||||||
|
|
||||||
public ETaskResult Update() =>
|
public ETaskResult Update() =>
|
||||||
gameFunctions.IsAetheryteUnlocked(AetheryteLocation)
|
aetheryteFunctions.IsAetheryteUnlocked(AetheryteLocation)
|
||||||
? ETaskResult.TaskComplete
|
? ETaskResult.TaskComplete
|
||||||
: ETaskResult.StillRunning;
|
: ETaskResult.StillRunning;
|
||||||
|
|
||||||
|
@ -24,7 +24,10 @@ internal static class Aetheryte
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 EAetheryteLocation AetheryteLocation { get; set; }
|
||||||
|
|
||||||
@ -36,7 +39,7 @@ internal static class Aetheryte
|
|||||||
|
|
||||||
public bool Start()
|
public bool Start()
|
||||||
{
|
{
|
||||||
if (!gameFunctions.IsAetheryteUnlocked(AetheryteLocation))
|
if (!aetheryteFunctions.IsAetheryteUnlocked(AetheryteLocation))
|
||||||
{
|
{
|
||||||
logger.LogInformation("Attuning to aetheryte {Aetheryte}", AetheryteLocation);
|
logger.LogInformation("Attuning to aetheryte {Aetheryte}", AetheryteLocation);
|
||||||
gameFunctions.InteractWith((uint)AetheryteLocation);
|
gameFunctions.InteractWith((uint)AetheryteLocation);
|
||||||
@ -48,7 +51,7 @@ internal static class Aetheryte
|
|||||||
}
|
}
|
||||||
|
|
||||||
public ETaskResult Update() =>
|
public ETaskResult Update() =>
|
||||||
gameFunctions.IsAetheryteUnlocked(AetheryteLocation)
|
aetheryteFunctions.IsAetheryteUnlocked(AetheryteLocation)
|
||||||
? ETaskResult.TaskComplete
|
? ETaskResult.TaskComplete
|
||||||
: ETaskResult.StillRunning;
|
: ETaskResult.StillRunning;
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ internal static class AethernetShortcut
|
|||||||
|
|
||||||
internal sealed class UseAethernetShortcut(
|
internal sealed class UseAethernetShortcut(
|
||||||
ILogger<UseAethernetShortcut> logger,
|
ILogger<UseAethernetShortcut> logger,
|
||||||
GameFunctions gameFunctions,
|
AetheryteFunctions aetheryteFunctions,
|
||||||
IClientState clientState,
|
IClientState clientState,
|
||||||
AetheryteData aetheryteData,
|
AetheryteData aetheryteData,
|
||||||
LifestreamIpc lifestreamIpc,
|
LifestreamIpc lifestreamIpc,
|
||||||
@ -72,22 +72,22 @@ internal static class AethernetShortcut
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (SkipConditions.AetheryteLocked != null &&
|
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");
|
logger.LogInformation("Skipping aethernet shortcut because the target aetheryte is locked");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SkipConditions.AetheryteUnlocked != null &&
|
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");
|
logger.LogInformation("Skipping aethernet shortcut because the target aetheryte is unlocked");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gameFunctions.IsAetheryteUnlocked(From) &&
|
if (aetheryteFunctions.IsAetheryteUnlocked(From) &&
|
||||||
gameFunctions.IsAetheryteUnlocked(To))
|
aetheryteFunctions.IsAetheryteUnlocked(To))
|
||||||
{
|
{
|
||||||
ushort territoryType = clientState.TerritoryType;
|
ushort territoryType = clientState.TerritoryType;
|
||||||
Vector3 playerPosition = clientState.LocalPlayer!.Position;
|
Vector3 playerPosition = clientState.LocalPlayer!.Position;
|
||||||
|
@ -17,7 +17,7 @@ internal static class AetheryteShortcut
|
|||||||
{
|
{
|
||||||
internal sealed class Factory(
|
internal sealed class Factory(
|
||||||
IServiceProvider serviceProvider,
|
IServiceProvider serviceProvider,
|
||||||
GameFunctions gameFunctions,
|
AetheryteFunctions aetheryteFunctions,
|
||||||
AetheryteData aetheryteData) : ITaskFactory
|
AetheryteData aetheryteData) : ITaskFactory
|
||||||
{
|
{
|
||||||
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
@ -29,7 +29,7 @@ internal static class AetheryteShortcut
|
|||||||
.With(step, step.AetheryteShortcut.Value, aetheryteData.TerritoryIds[step.AetheryteShortcut.Value]);
|
.With(step, step.AetheryteShortcut.Value, aetheryteData.TerritoryIds[step.AetheryteShortcut.Value]);
|
||||||
return
|
return
|
||||||
[
|
[
|
||||||
new WaitConditionTask(() => gameFunctions.CanTeleport(step.AetheryteShortcut.Value), "CanTeleport"),
|
new WaitConditionTask(() => aetheryteFunctions.CanTeleport(step.AetheryteShortcut.Value), "CanTeleport"),
|
||||||
task
|
task
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -40,7 +40,7 @@ internal static class AetheryteShortcut
|
|||||||
|
|
||||||
internal sealed class UseAetheryteShortcut(
|
internal sealed class UseAetheryteShortcut(
|
||||||
ILogger<UseAetheryteShortcut> logger,
|
ILogger<UseAetheryteShortcut> logger,
|
||||||
GameFunctions gameFunctions,
|
AetheryteFunctions aetheryteFunctions,
|
||||||
IClientState clientState,
|
IClientState clientState,
|
||||||
IChatGui chatGui,
|
IChatGui chatGui,
|
||||||
AetheryteData aetheryteData) : ISkippableTask
|
AetheryteData aetheryteData) : ISkippableTask
|
||||||
@ -80,14 +80,14 @@ internal static class AetheryteShortcut
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (skipConditions.AetheryteLocked != null &&
|
if (skipConditions.AetheryteLocked != null &&
|
||||||
!gameFunctions.IsAetheryteUnlocked(skipConditions.AetheryteLocked.Value))
|
!aetheryteFunctions.IsAetheryteUnlocked(skipConditions.AetheryteLocked.Value))
|
||||||
{
|
{
|
||||||
logger.LogInformation("Skipping aetheryte teleport due to SkipCondition (AetheryteLocked)");
|
logger.LogInformation("Skipping aetheryte teleport due to SkipCondition (AetheryteLocked)");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (skipConditions.AetheryteUnlocked != null &&
|
if (skipConditions.AetheryteUnlocked != null &&
|
||||||
gameFunctions.IsAetheryteUnlocked(skipConditions.AetheryteUnlocked.Value))
|
aetheryteFunctions.IsAetheryteUnlocked(skipConditions.AetheryteUnlocked.Value))
|
||||||
{
|
{
|
||||||
logger.LogInformation("Skipping aetheryte teleport due to SkipCondition (AetheryteUnlocked)");
|
logger.LogInformation("Skipping aetheryte teleport due to SkipCondition (AetheryteUnlocked)");
|
||||||
return false;
|
return false;
|
||||||
@ -124,12 +124,12 @@ internal static class AetheryteShortcut
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!gameFunctions.IsAetheryteUnlocked(TargetAetheryte))
|
if (!aetheryteFunctions.IsAetheryteUnlocked(TargetAetheryte))
|
||||||
{
|
{
|
||||||
chatGui.PrintError($"[Questionable] Aetheryte {TargetAetheryte} is not unlocked.");
|
chatGui.PrintError($"[Questionable] Aetheryte {TargetAetheryte} is not unlocked.");
|
||||||
throw new TaskException("Aetheryte 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...");
|
logger.LogInformation("Travelling via aetheryte...");
|
||||||
return true;
|
return true;
|
||||||
|
@ -41,6 +41,7 @@ internal static class SkipCondition
|
|||||||
|
|
||||||
internal sealed class CheckSkip(
|
internal sealed class CheckSkip(
|
||||||
ILogger<CheckSkip> logger,
|
ILogger<CheckSkip> logger,
|
||||||
|
AetheryteFunctions aetheryteFunctions,
|
||||||
GameFunctions gameFunctions,
|
GameFunctions gameFunctions,
|
||||||
QuestFunctions questFunctions,
|
QuestFunctions questFunctions,
|
||||||
IClientState clientState) : ITask
|
IClientState clientState) : ITask
|
||||||
@ -145,21 +146,21 @@ internal static class SkipCondition
|
|||||||
DataId: not null,
|
DataId: not null,
|
||||||
InteractionType: EInteractionType.AttuneAetheryte or EInteractionType.AttuneAethernetShard
|
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");
|
logger.LogInformation("Skipping step, as aetheryte/aethernet shard is unlocked");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SkipConditions.AetheryteLocked != null &&
|
if (SkipConditions.AetheryteLocked != null &&
|
||||||
!gameFunctions.IsAetheryteUnlocked(SkipConditions.AetheryteLocked.Value))
|
!aetheryteFunctions.IsAetheryteUnlocked(SkipConditions.AetheryteLocked.Value))
|
||||||
{
|
{
|
||||||
logger.LogInformation("Skipping step, as aetheryte is locked");
|
logger.LogInformation("Skipping step, as aetheryte is locked");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SkipConditions.AetheryteUnlocked != null &&
|
if (SkipConditions.AetheryteUnlocked != null &&
|
||||||
gameFunctions.IsAetheryteUnlocked(SkipConditions.AetheryteUnlocked.Value))
|
aetheryteFunctions.IsAetheryteUnlocked(SkipConditions.AetheryteUnlocked.Value))
|
||||||
{
|
{
|
||||||
logger.LogInformation("Skipping step, as aetheryte is unlocked");
|
logger.LogInformation("Skipping step, as aetheryte is unlocked");
|
||||||
return true;
|
return true;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using LLib.GameData;
|
using LLib.GameData;
|
||||||
@ -63,6 +64,11 @@ internal sealed class QuestData
|
|||||||
return _quests[elementId] ?? throw new ArgumentOutOfRangeException(nameof(elementId));
|
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)
|
public List<IQuestInfo> GetAllByIssuerDataId(uint targetId)
|
||||||
{
|
{
|
||||||
return _quests.Values
|
return _quests.Values
|
||||||
|
77
Questionable/Functions/AetheryteFunctions.cs
Normal file
77
Questionable/Functions/AetheryteFunctions.cs
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
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);
|
||||||
|
}
|
@ -74,62 +74,6 @@ internal sealed unsafe class GameFunctions
|
|||||||
.AsReadOnly();
|
.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)
|
public bool IsFlyingUnlocked(ushort territoryId)
|
||||||
{
|
{
|
||||||
if (_configuration.Advanced.NeverFly)
|
if (_configuration.Advanced.NeverFly)
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Dalamud.Memory;
|
using Dalamud.Memory;
|
||||||
@ -12,8 +13,10 @@ using LLib.GameData;
|
|||||||
using LLib.GameUI;
|
using LLib.GameUI;
|
||||||
using Lumina.Excel.GeneratedSheets;
|
using Lumina.Excel.GeneratedSheets;
|
||||||
using Questionable.Controller;
|
using Questionable.Controller;
|
||||||
|
using Questionable.Controller.Steps.Interactions;
|
||||||
using Questionable.Data;
|
using Questionable.Data;
|
||||||
using Questionable.Model;
|
using Questionable.Model;
|
||||||
|
using Questionable.Model.Common;
|
||||||
using Questionable.Model.Questing;
|
using Questionable.Model.Questing;
|
||||||
using GrandCompany = FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany;
|
using GrandCompany = FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany;
|
||||||
using Quest = Questionable.Model.Quest;
|
using Quest = Questionable.Model.Quest;
|
||||||
@ -24,16 +27,24 @@ internal sealed unsafe class QuestFunctions
|
|||||||
{
|
{
|
||||||
private readonly QuestRegistry _questRegistry;
|
private readonly QuestRegistry _questRegistry;
|
||||||
private readonly QuestData _questData;
|
private readonly QuestData _questData;
|
||||||
|
private readonly AetheryteFunctions _aetheryteFunctions;
|
||||||
private readonly Configuration _configuration;
|
private readonly Configuration _configuration;
|
||||||
private readonly IDataManager _dataManager;
|
private readonly IDataManager _dataManager;
|
||||||
private readonly IClientState _clientState;
|
private readonly IClientState _clientState;
|
||||||
private readonly IGameGui _gameGui;
|
private readonly IGameGui _gameGui;
|
||||||
|
|
||||||
public QuestFunctions(QuestRegistry questRegistry, QuestData questData, Configuration configuration,
|
public QuestFunctions(
|
||||||
IDataManager dataManager, IClientState clientState, IGameGui gameGui)
|
QuestRegistry questRegistry,
|
||||||
|
QuestData questData,
|
||||||
|
AetheryteFunctions aetheryteFunctions,
|
||||||
|
Configuration configuration,
|
||||||
|
IDataManager dataManager,
|
||||||
|
IClientState clientState,
|
||||||
|
IGameGui gameGui)
|
||||||
{
|
{
|
||||||
_questRegistry = questRegistry;
|
_questRegistry = questRegistry;
|
||||||
_questData = questData;
|
_questData = questData;
|
||||||
|
_aetheryteFunctions = aetheryteFunctions;
|
||||||
_configuration = configuration;
|
_configuration = configuration;
|
||||||
_dataManager = dataManager;
|
_dataManager = dataManager;
|
||||||
_clientState = clientState;
|
_clientState = clientState;
|
||||||
@ -99,8 +110,11 @@ internal sealed unsafe class QuestFunctions
|
|||||||
{
|
{
|
||||||
// always prioritize accepting MSQ quests, to make sure we don't turn in one MSQ quest and then go off to do
|
// 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.
|
// side quests until the end of time.
|
||||||
var msqQuest = GetMainScenarioQuest(questManager);
|
var msqQuest = GetMainScenarioQuest();
|
||||||
if (msqQuest.CurrentQuest is { Value: not 0 } && _questRegistry.IsKnownQuest(msqQuest.CurrentQuest))
|
if (msqQuest.CurrentQuest != null && !_questRegistry.IsKnownQuest(msqQuest.CurrentQuest))
|
||||||
|
msqQuest = default;
|
||||||
|
|
||||||
|
if (msqQuest.CurrentQuest != null && !IsQuestAccepted(msqQuest.CurrentQuest))
|
||||||
return msqQuest;
|
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,
|
// 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,
|
||||||
@ -133,14 +147,24 @@ internal sealed unsafe class QuestFunctions
|
|||||||
return (currentQuest, QuestManager.GetQuestSequence(currentQuest.Value));
|
return (currentQuest, QuestManager.GetQuestSequence(currentQuest.Value));
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we know no quest of those currently in the to-do list, just do MSQ
|
ElementId? priorityQuest = GetNextPriorityQuestThatCanBeAccepted();
|
||||||
return msqQuest;
|
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;
|
return default;
|
||||||
}
|
}
|
||||||
|
|
||||||
private (QuestId? CurrentQuest, byte Sequence) GetMainScenarioQuest(QuestManager* questManager)
|
private (QuestId? CurrentQuest, byte Sequence) GetMainScenarioQuest()
|
||||||
{
|
{
|
||||||
if (QuestManager.IsQuestComplete(3759)) // Memories Rekindled
|
if (QuestManager.IsQuestComplete(3759)) // Memories Rekindled
|
||||||
{
|
{
|
||||||
@ -177,6 +201,7 @@ internal sealed unsafe class QuestFunctions
|
|||||||
return default;
|
return default;
|
||||||
|
|
||||||
// if the MSQ is hidden, we generally ignore it
|
// if the MSQ is hidden, we generally ignore it
|
||||||
|
QuestManager* questManager = QuestManager.Instance();
|
||||||
if (IsQuestAccepted(currentQuest) && questManager->GetQuestById(currentQuest.Value)->IsHidden)
|
if (IsQuestAccepted(currentQuest) && questManager->GetQuestById(currentQuest.Value)->IsHidden)
|
||||||
return default;
|
return default;
|
||||||
|
|
||||||
@ -214,6 +239,72 @@ internal sealed unsafe class QuestFunctions
|
|||||||
return null;
|
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)
|
public bool IsReadyToAcceptQuest(ElementId questId)
|
||||||
{
|
{
|
||||||
_questRegistry.TryGetQuest(questId, out var quest);
|
_questRegistry.TryGetQuest(questId, out var quest);
|
||||||
|
@ -64,7 +64,7 @@ internal sealed class QuestInfo : IQuestInfo
|
|||||||
public ushort Level { get; }
|
public ushort Level { get; }
|
||||||
public uint IssuerDataId { get; }
|
public uint IssuerDataId { get; }
|
||||||
public bool IsRepeatable { get; }
|
public bool IsRepeatable { get; }
|
||||||
public ImmutableList<QuestId> PreviousQuests { get; }
|
public ImmutableList<QuestId> PreviousQuests { get; set; }
|
||||||
public QuestJoin PreviousQuestJoin { get; }
|
public QuestJoin PreviousQuestJoin { get; }
|
||||||
public ImmutableList<QuestId> QuestLocks { get; }
|
public ImmutableList<QuestId> QuestLocks { get; }
|
||||||
public QuestJoin QuestLockJoin { get; }
|
public QuestJoin QuestLockJoin { get; }
|
||||||
@ -88,4 +88,9 @@ internal sealed class QuestInfo : IQuestInfo
|
|||||||
All = 1,
|
All = 1,
|
||||||
AtLeastOne = 2,
|
AtLeastOne = 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void AddPreviousQuest(QuestId questId)
|
||||||
|
{
|
||||||
|
PreviousQuests = [..PreviousQuests, questId];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -98,6 +98,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
|
|||||||
|
|
||||||
private static void AddBasicFunctionsAndData(ServiceCollection serviceCollection)
|
private static void AddBasicFunctionsAndData(ServiceCollection serviceCollection)
|
||||||
{
|
{
|
||||||
|
serviceCollection.AddSingleton<AetheryteFunctions>();
|
||||||
serviceCollection.AddSingleton<ExcelFunctions>();
|
serviceCollection.AddSingleton<ExcelFunctions>();
|
||||||
serviceCollection.AddSingleton<GameFunctions>();
|
serviceCollection.AddSingleton<GameFunctions>();
|
||||||
serviceCollection.AddSingleton<ChatFunctions>();
|
serviceCollection.AddSingleton<ChatFunctions>();
|
||||||
|
@ -25,7 +25,6 @@ internal sealed class ActiveQuestComponent
|
|||||||
private readonly GatheringController _gatheringController;
|
private readonly GatheringController _gatheringController;
|
||||||
private readonly QuestFunctions _questFunctions;
|
private readonly QuestFunctions _questFunctions;
|
||||||
private readonly ICommandManager _commandManager;
|
private readonly ICommandManager _commandManager;
|
||||||
private readonly IDalamudPluginInterface _pluginInterface;
|
|
||||||
private readonly Configuration _configuration;
|
private readonly Configuration _configuration;
|
||||||
private readonly QuestRegistry _questRegistry;
|
private readonly QuestRegistry _questRegistry;
|
||||||
private readonly IChatGui _chatGui;
|
private readonly IChatGui _chatGui;
|
||||||
@ -37,7 +36,6 @@ internal sealed class ActiveQuestComponent
|
|||||||
GatheringController gatheringController,
|
GatheringController gatheringController,
|
||||||
QuestFunctions questFunctions,
|
QuestFunctions questFunctions,
|
||||||
ICommandManager commandManager,
|
ICommandManager commandManager,
|
||||||
IDalamudPluginInterface pluginInterface,
|
|
||||||
Configuration configuration,
|
Configuration configuration,
|
||||||
QuestRegistry questRegistry,
|
QuestRegistry questRegistry,
|
||||||
IChatGui chatGui)
|
IChatGui chatGui)
|
||||||
@ -48,7 +46,6 @@ internal sealed class ActiveQuestComponent
|
|||||||
_gatheringController = gatheringController;
|
_gatheringController = gatheringController;
|
||||||
_questFunctions = questFunctions;
|
_questFunctions = questFunctions;
|
||||||
_commandManager = commandManager;
|
_commandManager = commandManager;
|
||||||
_pluginInterface = pluginInterface;
|
|
||||||
_configuration = configuration;
|
_configuration = configuration;
|
||||||
_questRegistry = questRegistry;
|
_questRegistry = questRegistry;
|
||||||
_chatGui = chatGui;
|
_chatGui = chatGui;
|
||||||
|
@ -94,15 +94,22 @@ internal sealed class QuestTooltipComponent
|
|||||||
|
|
||||||
foreach (var q in quest.PreviousQuests)
|
foreach (var q in quest.PreviousQuests)
|
||||||
{
|
{
|
||||||
var qInfo = _questData.GetQuestInfo(q);
|
if (_questData.TryGetQuestInfo(q, out var qInfo))
|
||||||
var (iconColor, icon, _) = _uiUtils.GetQuestStyle(q);
|
{
|
||||||
if (!_questRegistry.IsKnownQuest(qInfo.QuestId))
|
var (iconColor, icon, _) = _uiUtils.GetQuestStyle(q);
|
||||||
iconColor = ImGuiColors.DalamudGrey;
|
if (!_questRegistry.IsKnownQuest(qInfo.QuestId))
|
||||||
|
iconColor = ImGuiColors.DalamudGrey;
|
||||||
|
|
||||||
_uiUtils.ChecklistItem(FormatQuestUnlockName(qInfo), iconColor, icon);
|
_uiUtils.ChecklistItem(FormatQuestUnlockName(qInfo), iconColor, icon);
|
||||||
|
|
||||||
if (qInfo is QuestInfo qstInfo && (counter <= 2 || icon != FontAwesomeIcon.Check))
|
if (qInfo is QuestInfo qstInfo && (counter <= 2 || icon != FontAwesomeIcon.Check))
|
||||||
DrawQuestUnlocks(qstInfo, counter + 1);
|
DrawQuestUnlocks(qstInfo, counter + 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
using var _ = ImRaii.Disabled();
|
||||||
|
_uiUtils.ChecklistItem($"Unknown Quest ({q})", ImGuiColors.DalamudGrey, FontAwesomeIcon.Question);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user