Add class quests as priority quests

This commit is contained in:
Liza 2024-08-10 19:50:15 +02:00
parent 348460a7c6
commit 7e9eb212e3
Signed by: liza
GPG Key ID: 7199F8D727D55F67
5 changed files with 113 additions and 21 deletions

View File

@ -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
@ -231,7 +240,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
_nextQuest.Quest.Id);
// if (_nextQuest.Quest.Id is LeveId)
// _startedQuest = _nextQuest;
// _startedQuest = _nextQuest;
_nextQuest = null;
}
@ -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);

View File

@ -0,0 +1,8 @@
using Dalamud.Game.Text.SeStringHandling;
namespace Questionable.Controller.Steps;
public interface IToastAware
{
void OnErrorToast(SeString message);
}

View File

@ -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;
}
}
}

View File

@ -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)

View File

@ -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);