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 Dalamud.Game.ClientState.Conditions;
|
||||
using Dalamud.Game.ClientState.Keys;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using LLib.GameData;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Controller.Steps;
|
||||
using Questionable.Controller.Steps.Interactions;
|
||||
using Questionable.Controller.Steps.Shared;
|
||||
using Questionable.Data;
|
||||
using Questionable.External;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model;
|
||||
@ -16,7 +20,7 @@ using Questionable.Model.Questing;
|
||||
|
||||
namespace Questionable.Controller;
|
||||
|
||||
internal sealed class QuestController : MiniTaskController<QuestController>
|
||||
internal sealed class QuestController : MiniTaskController<QuestController>, IDisposable
|
||||
{
|
||||
private readonly IClientState _clientState;
|
||||
private readonly GameFunctions _gameFunctions;
|
||||
@ -27,6 +31,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||
private readonly QuestRegistry _questRegistry;
|
||||
private readonly IKeyState _keyState;
|
||||
private readonly ICondition _condition;
|
||||
private readonly IToastGui _toastGui;
|
||||
private readonly Configuration _configuration;
|
||||
private readonly YesAlreadyIpc _yesAlreadyIpc;
|
||||
private readonly IReadOnlyList<ITaskFactory> _taskFactories;
|
||||
@ -64,6 +69,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||
IKeyState keyState,
|
||||
IChatGui chatGui,
|
||||
ICondition condition,
|
||||
IToastGui toastGui,
|
||||
Configuration configuration,
|
||||
YesAlreadyIpc yesAlreadyIpc,
|
||||
IEnumerable<ITaskFactory> taskFactories)
|
||||
@ -78,11 +84,13 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||
_questRegistry = questRegistry;
|
||||
_keyState = keyState;
|
||||
_condition = condition;
|
||||
_toastGui = toastGui;
|
||||
_configuration = configuration;
|
||||
_yesAlreadyIpc = yesAlreadyIpc;
|
||||
_taskFactories = taskFactories.ToList().AsReadOnly();
|
||||
|
||||
_condition.ConditionChange += OnConditionChange;
|
||||
_toastGui.ErrorToast += OnErrorToast;
|
||||
}
|
||||
|
||||
public (QuestProgress Progress, ECurrentQuestType Type)? CurrentQuestDetails
|
||||
@ -216,6 +224,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||
Stop("Pending quest accepted", continueIfAutomatic: true);
|
||||
}
|
||||
}
|
||||
|
||||
if (_simulatedQuest == null && _nextQuest != null)
|
||||
{
|
||||
// if the quest is accepted, we no longer track it
|
||||
@ -693,34 +702,86 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||
return false;
|
||||
|
||||
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
|
||||
// no longer relevant for the non-priority quest (after we're done with the priority quest)?
|
||||
QuestStep? currentStep = currentSequence?.FindStep(currentQuest.Step);
|
||||
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()
|
||||
{
|
||||
if (!IsInterruptible())
|
||||
if (!IsInterruptible() || _nextQuest != null || _gatheringQuest != null || _simulatedQuest != null)
|
||||
return false;
|
||||
|
||||
ushort[] priorityQuests =
|
||||
[
|
||||
1157, // Garuda (Hard)
|
||||
1158, // Titan (Hard)
|
||||
];
|
||||
// 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))
|
||||
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);
|
||||
|
||||
_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;
|
||||
@ -738,6 +799,18 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||
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(
|
||||
DateTime StartedAt,
|
||||
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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using LLib;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model.Questing;
|
||||
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 =
|
||||
[
|
||||
InventoryType.ArmoryMainHand,
|
||||
@ -98,7 +102,7 @@ internal static class EquipItem
|
||||
private unsafe void Equip()
|
||||
{
|
||||
++_attempts;
|
||||
if (_attempts > 3)
|
||||
if (_attempts > MaxAttempts)
|
||||
throw new TaskException("Unable to equip gear.");
|
||||
|
||||
var inventoryManager = InventoryManager.Instance();
|
||||
@ -169,5 +173,12 @@ internal static class EquipItem
|
||||
}
|
||||
|
||||
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.Linq;
|
||||
using Dalamud.Plugin.Services;
|
||||
using LLib.GameData;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Questing;
|
||||
@ -11,6 +12,8 @@ namespace Questionable.Data;
|
||||
|
||||
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;
|
||||
|
||||
public QuestData(IDataManager dataManager)
|
||||
|
@ -16,9 +16,6 @@ internal sealed class ARealmRebornComponent
|
||||
private static readonly QuestId GoodIntentions = new(363);
|
||||
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 QuestData _questData;
|
||||
private readonly TerritoryData _territoryData;
|
||||
@ -64,7 +61,7 @@ internal sealed class ARealmRebornComponent
|
||||
|
||||
private void DrawAllianceRaids()
|
||||
{
|
||||
bool complete = _questFunctions.IsQuestComplete(RequiredAllianceRaidQuests.Last());
|
||||
bool complete = _questFunctions.IsQuestComplete(QuestData.CrystalTowerQuests[^1]);
|
||||
bool hover = _uiUtils.ChecklistItem("Crystal Tower Raids", complete);
|
||||
if (complete || !hover)
|
||||
return;
|
||||
@ -73,7 +70,7 @@ internal sealed class ARealmRebornComponent
|
||||
if (!tooltip)
|
||||
return;
|
||||
|
||||
foreach (var questId in RequiredAllianceRaidQuests)
|
||||
foreach (var questId in QuestData.CrystalTowerQuests)
|
||||
{
|
||||
(Vector4 color, FontAwesomeIcon icon, _) = _uiUtils.GetQuestStyle(questId);
|
||||
_uiUtils.ChecklistItem(_questData.GetQuestInfo(questId).Name, color, icon);
|
||||
|
Loading…
Reference in New Issue
Block a user