Add class quests as priority quests
This commit is contained in:
parent
348460a7c6
commit
7e9eb212e3
@ -4,11 +4,15 @@ using System.Diagnostics.CodeAnalysis;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Dalamud.Game.ClientState.Conditions;
|
using Dalamud.Game.ClientState.Conditions;
|
||||||
using Dalamud.Game.ClientState.Keys;
|
using Dalamud.Game.ClientState.Keys;
|
||||||
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
|
using LLib.GameData;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Questionable.Controller.Steps;
|
using Questionable.Controller.Steps;
|
||||||
|
using Questionable.Controller.Steps.Interactions;
|
||||||
using Questionable.Controller.Steps.Shared;
|
using Questionable.Controller.Steps.Shared;
|
||||||
|
using Questionable.Data;
|
||||||
using Questionable.External;
|
using Questionable.External;
|
||||||
using Questionable.Functions;
|
using Questionable.Functions;
|
||||||
using Questionable.Model;
|
using Questionable.Model;
|
||||||
@ -16,7 +20,7 @@ using Questionable.Model.Questing;
|
|||||||
|
|
||||||
namespace Questionable.Controller;
|
namespace Questionable.Controller;
|
||||||
|
|
||||||
internal sealed class QuestController : MiniTaskController<QuestController>
|
internal sealed class QuestController : MiniTaskController<QuestController>, IDisposable
|
||||||
{
|
{
|
||||||
private readonly IClientState _clientState;
|
private readonly IClientState _clientState;
|
||||||
private readonly GameFunctions _gameFunctions;
|
private readonly GameFunctions _gameFunctions;
|
||||||
@ -27,6 +31,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
|||||||
private readonly QuestRegistry _questRegistry;
|
private readonly QuestRegistry _questRegistry;
|
||||||
private readonly IKeyState _keyState;
|
private readonly IKeyState _keyState;
|
||||||
private readonly ICondition _condition;
|
private readonly ICondition _condition;
|
||||||
|
private readonly IToastGui _toastGui;
|
||||||
private readonly Configuration _configuration;
|
private readonly Configuration _configuration;
|
||||||
private readonly YesAlreadyIpc _yesAlreadyIpc;
|
private readonly YesAlreadyIpc _yesAlreadyIpc;
|
||||||
private readonly IReadOnlyList<ITaskFactory> _taskFactories;
|
private readonly IReadOnlyList<ITaskFactory> _taskFactories;
|
||||||
@ -64,6 +69,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
|||||||
IKeyState keyState,
|
IKeyState keyState,
|
||||||
IChatGui chatGui,
|
IChatGui chatGui,
|
||||||
ICondition condition,
|
ICondition condition,
|
||||||
|
IToastGui toastGui,
|
||||||
Configuration configuration,
|
Configuration configuration,
|
||||||
YesAlreadyIpc yesAlreadyIpc,
|
YesAlreadyIpc yesAlreadyIpc,
|
||||||
IEnumerable<ITaskFactory> taskFactories)
|
IEnumerable<ITaskFactory> taskFactories)
|
||||||
@ -78,11 +84,13 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
|||||||
_questRegistry = questRegistry;
|
_questRegistry = questRegistry;
|
||||||
_keyState = keyState;
|
_keyState = keyState;
|
||||||
_condition = condition;
|
_condition = condition;
|
||||||
|
_toastGui = toastGui;
|
||||||
_configuration = configuration;
|
_configuration = configuration;
|
||||||
_yesAlreadyIpc = yesAlreadyIpc;
|
_yesAlreadyIpc = yesAlreadyIpc;
|
||||||
_taskFactories = taskFactories.ToList().AsReadOnly();
|
_taskFactories = taskFactories.ToList().AsReadOnly();
|
||||||
|
|
||||||
_condition.ConditionChange += OnConditionChange;
|
_condition.ConditionChange += OnConditionChange;
|
||||||
|
_toastGui.ErrorToast += OnErrorToast;
|
||||||
}
|
}
|
||||||
|
|
||||||
public (QuestProgress Progress, ECurrentQuestType Type)? CurrentQuestDetails
|
public (QuestProgress Progress, ECurrentQuestType Type)? CurrentQuestDetails
|
||||||
@ -216,6 +224,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
|||||||
Stop("Pending quest accepted", continueIfAutomatic: true);
|
Stop("Pending quest accepted", continueIfAutomatic: true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_simulatedQuest == null && _nextQuest != null)
|
if (_simulatedQuest == null && _nextQuest != null)
|
||||||
{
|
{
|
||||||
// if the quest is accepted, we no longer track it
|
// if the quest is accepted, we no longer track it
|
||||||
@ -231,7 +240,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
|||||||
_nextQuest.Quest.Id);
|
_nextQuest.Quest.Id);
|
||||||
|
|
||||||
// if (_nextQuest.Quest.Id is LeveId)
|
// if (_nextQuest.Quest.Id is LeveId)
|
||||||
// _startedQuest = _nextQuest;
|
// _startedQuest = _nextQuest;
|
||||||
|
|
||||||
_nextQuest = null;
|
_nextQuest = null;
|
||||||
}
|
}
|
||||||
@ -693,34 +702,86 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
QuestSequence? currentSequence = currentQuest.Quest.FindSequence(currentQuest.Sequence);
|
QuestSequence? currentSequence = currentQuest.Quest.FindSequence(currentQuest.Sequence);
|
||||||
QuestStep? currentStep = currentSequence?.FindStep(currentQuest.Step);
|
if (currentQuest.Step > 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
// TODO Should this check that all previous steps have CompletionFlags so that we avoid running to places
|
QuestStep? currentStep = currentSequence?.FindStep(currentQuest.Step);
|
||||||
// no longer relevant for the non-priority quest (after we're done with the priority quest)?
|
|
||||||
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;
|
||||||
|
if (classJob != EClassJob.Adventurer)
|
||||||
|
{
|
||||||
|
priorityQuests.AddRange(_questRegistry.GetKnownClassJobQuests(classJob)
|
||||||
|
.Where(x => _questRegistry.TryGetQuest(x.QuestId, out Quest? quest) && quest.Info is QuestInfo
|
||||||
|
{
|
||||||
|
// ignore Endwalker/Dawntrail, as the class quests are optional
|
||||||
|
Expansion: EExpansionVersion.ARealmReborn or EExpansionVersion.Heavensward or EExpansionVersion.Stormblood or EExpansionVersion.Shadowbringers
|
||||||
|
})
|
||||||
|
.Select(x => x.QuestId));
|
||||||
|
}
|
||||||
|
|
||||||
|
return priorityQuests;
|
||||||
|
}
|
||||||
|
|
||||||
public bool TryPickPriorityQuest()
|
public bool TryPickPriorityQuest()
|
||||||
{
|
{
|
||||||
if (!IsInterruptible())
|
if (!IsInterruptible() || _nextQuest != null || _gatheringQuest != null || _simulatedQuest != null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
ushort[] priorityQuests =
|
// don't start a second priority quest until the first one is resolved
|
||||||
[
|
List<ElementId> priorityQuests = GetPriorityQuests();
|
||||||
1157, // Garuda (Hard)
|
if (_startedQuest != null && priorityQuests.Contains(_startedQuest.Quest.Id))
|
||||||
1158, // Titan (Hard)
|
return false;
|
||||||
];
|
|
||||||
|
|
||||||
foreach (var id in priorityQuests)
|
foreach (ElementId questId in priorityQuests)
|
||||||
{
|
{
|
||||||
var questId = new QuestId(id);
|
if (!_questFunctions.IsReadyToAcceptQuest(questId) || !_questRegistry.TryGetQuest(questId, 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);
|
SetNextQuest(quest);
|
||||||
|
|
||||||
_chatGui.Print(
|
_chatGui.Print(
|
||||||
$"[Questionable] Picking up quest '{quest.Info.Name}' as a priority over current main story/side quests.");
|
$"[Questionable] Picking up quest '{quest.Info.Name}' as a priority over current main story/side quests.");
|
||||||
return true;
|
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;
|
||||||
@ -738,6 +799,18 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
|||||||
conditionChangeAware.OnConditionChange(flag, value);
|
conditionChangeAware.OnConditionChange(flag, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnErrorToast(ref SeString message, ref bool ishandled)
|
||||||
|
{
|
||||||
|
if (_currentTask is IToastAware toastAware)
|
||||||
|
toastAware.OnErrorToast(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_toastGui.ErrorToast -= OnErrorToast;
|
||||||
|
_condition.ConditionChange -= OnConditionChange;
|
||||||
|
}
|
||||||
|
|
||||||
public sealed record StepProgress(
|
public sealed record StepProgress(
|
||||||
DateTime StartedAt,
|
DateTime StartedAt,
|
||||||
int PointMenuCounter = 0);
|
int PointMenuCounter = 0);
|
||||||
|
8
Questionable/Controller/Steps/IToastAware.cs
Normal file
8
Questionable/Controller/Steps/IToastAware.cs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
|
|
||||||
|
namespace Questionable.Controller.Steps;
|
||||||
|
|
||||||
|
public interface IToastAware
|
||||||
|
{
|
||||||
|
void OnErrorToast(SeString message);
|
||||||
|
}
|
@ -1,11 +1,14 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
|
using LLib;
|
||||||
using Lumina.Excel.GeneratedSheets;
|
using Lumina.Excel.GeneratedSheets;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Questionable.Functions;
|
||||||
using Questionable.Model.Questing;
|
using Questionable.Model.Questing;
|
||||||
using Quest = Questionable.Model.Quest;
|
using Quest = Questionable.Model.Quest;
|
||||||
|
|
||||||
@ -26,8 +29,9 @@ internal static class EquipItem
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class DoEquip(IDataManager dataManager, ILogger<DoEquip> logger) : ITask
|
internal sealed class DoEquip(IDataManager dataManager, ILogger<DoEquip> logger) : ITask, IToastAware
|
||||||
{
|
{
|
||||||
|
private const int MaxAttempts = 3;
|
||||||
private static readonly IReadOnlyList<InventoryType> SourceInventoryTypes =
|
private static readonly IReadOnlyList<InventoryType> SourceInventoryTypes =
|
||||||
[
|
[
|
||||||
InventoryType.ArmoryMainHand,
|
InventoryType.ArmoryMainHand,
|
||||||
@ -98,7 +102,7 @@ internal static class EquipItem
|
|||||||
private unsafe void Equip()
|
private unsafe void Equip()
|
||||||
{
|
{
|
||||||
++_attempts;
|
++_attempts;
|
||||||
if (_attempts > 3)
|
if (_attempts > MaxAttempts)
|
||||||
throw new TaskException("Unable to equip gear.");
|
throw new TaskException("Unable to equip gear.");
|
||||||
|
|
||||||
var inventoryManager = InventoryManager.Instance();
|
var inventoryManager = InventoryManager.Instance();
|
||||||
@ -169,5 +173,12 @@ internal static class EquipItem
|
|||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => $"Equip({_item.Name})";
|
public override string ToString() => $"Equip({_item.Name})";
|
||||||
|
|
||||||
|
public void OnErrorToast(SeString message)
|
||||||
|
{
|
||||||
|
string? insufficientArmoryChestSpace = dataManager.GetString<LogMessage>(709, x => x.Text);
|
||||||
|
if (GameFunctions.GameStringEquals(message.TextValue, insufficientArmoryChestSpace))
|
||||||
|
_attempts = MaxAttempts;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
|
using LLib.GameData;
|
||||||
using Lumina.Excel.GeneratedSheets;
|
using Lumina.Excel.GeneratedSheets;
|
||||||
using Questionable.Model;
|
using Questionable.Model;
|
||||||
using Questionable.Model.Questing;
|
using Questionable.Model.Questing;
|
||||||
@ -11,6 +12,8 @@ namespace Questionable.Data;
|
|||||||
|
|
||||||
internal sealed class QuestData
|
internal sealed class QuestData
|
||||||
{
|
{
|
||||||
|
public static readonly IReadOnlyList<QuestId> CrystalTowerQuests =
|
||||||
|
[new(1709), new(1200), new(1201), new(1202), new(1203), new(1474), new(494), new(495)];
|
||||||
private readonly Dictionary<ElementId, IQuestInfo> _quests;
|
private readonly Dictionary<ElementId, IQuestInfo> _quests;
|
||||||
|
|
||||||
public QuestData(IDataManager dataManager)
|
public QuestData(IDataManager dataManager)
|
||||||
|
@ -16,9 +16,6 @@ internal sealed class ARealmRebornComponent
|
|||||||
private static readonly QuestId GoodIntentions = new(363);
|
private static readonly QuestId GoodIntentions = new(363);
|
||||||
private static readonly ushort[] RequiredPrimalInstances = [20004, 20006, 20005];
|
private static readonly ushort[] RequiredPrimalInstances = [20004, 20006, 20005];
|
||||||
|
|
||||||
private static readonly QuestId[] RequiredAllianceRaidQuests =
|
|
||||||
[new(1709), new(1200), new(1201), new(1202), new(1203), new(1474), new(494), new(495)];
|
|
||||||
|
|
||||||
private readonly QuestFunctions _questFunctions;
|
private readonly QuestFunctions _questFunctions;
|
||||||
private readonly QuestData _questData;
|
private readonly QuestData _questData;
|
||||||
private readonly TerritoryData _territoryData;
|
private readonly TerritoryData _territoryData;
|
||||||
@ -64,7 +61,7 @@ internal sealed class ARealmRebornComponent
|
|||||||
|
|
||||||
private void DrawAllianceRaids()
|
private void DrawAllianceRaids()
|
||||||
{
|
{
|
||||||
bool complete = _questFunctions.IsQuestComplete(RequiredAllianceRaidQuests.Last());
|
bool complete = _questFunctions.IsQuestComplete(QuestData.CrystalTowerQuests[^1]);
|
||||||
bool hover = _uiUtils.ChecklistItem("Crystal Tower Raids", complete);
|
bool hover = _uiUtils.ChecklistItem("Crystal Tower Raids", complete);
|
||||||
if (complete || !hover)
|
if (complete || !hover)
|
||||||
return;
|
return;
|
||||||
@ -73,7 +70,7 @@ internal sealed class ARealmRebornComponent
|
|||||||
if (!tooltip)
|
if (!tooltip)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
foreach (var questId in RequiredAllianceRaidQuests)
|
foreach (var questId in QuestData.CrystalTowerQuests)
|
||||||
{
|
{
|
||||||
(Vector4 color, FontAwesomeIcon icon, _) = _uiUtils.GetQuestStyle(questId);
|
(Vector4 color, FontAwesomeIcon icon, _) = _uiUtils.GetQuestStyle(questId);
|
||||||
_uiUtils.ChecklistItem(_questData.GetQuestInfo(questId).Name, color, icon);
|
_uiUtils.ChecklistItem(_questData.GetQuestInfo(questId).Name, color, icon);
|
||||||
|
Loading…
Reference in New Issue
Block a user