613 lines
24 KiB
C#
613 lines
24 KiB
C#
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Dalamud.Game.ClientState.Conditions;
|
|
using Dalamud.Plugin.Services;
|
|
using FFXIVClientStructs.FFXIV.Application.Network.WorkDefinitions;
|
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
|
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
|
using LLib.GameData;
|
|
using Microsoft.Extensions.Logging;
|
|
using Questionable.Data;
|
|
using Questionable.External;
|
|
using Questionable.Functions;
|
|
using Questionable.Model;
|
|
using Questionable.Model.Questing;
|
|
|
|
namespace Questionable.Controller;
|
|
|
|
internal sealed class QuestPriorityResolver
|
|
{
|
|
private readonly QuestFunctions _questFunctions;
|
|
|
|
private readonly QuestRegistry _questRegistry;
|
|
|
|
private readonly QuestData _questData;
|
|
|
|
private readonly AlliedSocietyData _alliedSocietyData;
|
|
|
|
private readonly AlliedSocietyQuestFunctions _alliedSocietyQuestFunctions;
|
|
|
|
private readonly Configuration _configuration;
|
|
|
|
private readonly IObjectTable _objectTable;
|
|
|
|
private readonly ICondition _condition;
|
|
|
|
private readonly IClientState _clientState;
|
|
|
|
private readonly AutoDutyIpc _autoDutyIpc;
|
|
|
|
private readonly ILogger<QuestPriorityResolver> _logger;
|
|
|
|
private readonly HashSet<ushort> _alreadyLoggedUnobtainableQuests = new HashSet<ushort>();
|
|
|
|
private ElementId? _lastLoggedLevelLockedMsq;
|
|
|
|
private ElementId? _lastLoggedForcedClassQuest;
|
|
|
|
private ElementId? _lastLoggedRegistryClassQuest;
|
|
|
|
private ElementId? _lastLoggedPriorityClassQuest;
|
|
|
|
private bool _loggedNoClassQuestsAvailable;
|
|
|
|
private bool _loggedAdventurerClass;
|
|
|
|
public QuestPriorityResolver(QuestFunctions questFunctions, QuestRegistry questRegistry, QuestData questData, AlliedSocietyData alliedSocietyData, AlliedSocietyQuestFunctions alliedSocietyQuestFunctions, Configuration configuration, IObjectTable objectTable, ICondition condition, IClientState clientState, AutoDutyIpc autoDutyIpc, ILogger<QuestPriorityResolver> logger)
|
|
{
|
|
_questFunctions = questFunctions;
|
|
_questRegistry = questRegistry;
|
|
_questData = questData;
|
|
_alliedSocietyData = alliedSocietyData;
|
|
_alliedSocietyQuestFunctions = alliedSocietyQuestFunctions;
|
|
_configuration = configuration;
|
|
_objectTable = objectTable;
|
|
_condition = condition;
|
|
_clientState = clientState;
|
|
_autoDutyIpc = autoDutyIpc;
|
|
_logger = logger;
|
|
}
|
|
|
|
public QuestReference GetCurrentQuest()
|
|
{
|
|
QuestResolution questResolution = ResolveFromGameState(new List<Quest>(), QuestController.EAutomationType.Automatic);
|
|
if (questResolution.HasQuest)
|
|
{
|
|
return new QuestReference(questResolution.QuestId, questResolution.Sequence, questResolution.MsqState);
|
|
}
|
|
return QuestReference.NoQuest(questResolution.MsqState);
|
|
}
|
|
|
|
public QuestResolution Resolve(QuestSlots slots)
|
|
{
|
|
if (slots.Simulated != null)
|
|
{
|
|
return QuestResolution.Slot(EQuestResolutionType.Simulated, slots.Simulated, "Simulated quest");
|
|
}
|
|
if (slots.Next != null && !_questFunctions.IsQuestBlacklisted(slots.Next.Quest.Id))
|
|
{
|
|
return QuestResolution.Slot(EQuestResolutionType.NextQuest, slots.Next, $"Next quest {slots.Next.Quest.Id}");
|
|
}
|
|
return ResolveFromGameState(slots.ManualPriorityQuests, slots.AutomationType);
|
|
}
|
|
|
|
private QuestResolution ResolveFromGameState(List<Quest> manualPriorityQuests, QuestController.EAutomationType automationType)
|
|
{
|
|
bool flag = automationType != QuestController.EAutomationType.SingleQuestB;
|
|
QuestReference resolvedMsqQuest = GetResolvedMsqQuest();
|
|
int num = _objectTable.LocalPlayer?.Level ?? 0;
|
|
EClassJob valueOrDefault = ((EClassJob?)_objectTable.LocalPlayer?.ClassJob.RowId).GetValueOrDefault();
|
|
QuestResolution? questResolution = TryResolveManualPriority(manualPriorityQuests, resolvedMsqQuest.State);
|
|
if (questResolution.HasValue)
|
|
{
|
|
QuestResolution valueOrDefault2 = questResolution.GetValueOrDefault();
|
|
return ApplyQuestRedirects(valueOrDefault2);
|
|
}
|
|
uint territoryType = _clientState.TerritoryType;
|
|
bool flag2 = territoryType - 181 <= 2;
|
|
if (flag2 && flag && _configuration.General.MsqPriority != Configuration.EMsqPriorityMode.Manual)
|
|
{
|
|
ElementId currentQuest = resolvedMsqQuest.CurrentQuest;
|
|
if ((object)currentQuest != null && currentQuest.Value > 0 && !_questFunctions.IsQuestAccepted(currentQuest))
|
|
{
|
|
return QuestResolution.ForQuest(EQuestResolutionType.MsqImmediate, currentQuest, resolvedMsqQuest.Sequence, resolvedMsqQuest.State, $"Starting city opening MSQ {currentQuest}");
|
|
}
|
|
}
|
|
questResolution = TryResolveInProgressClassQuest(valueOrDefault, resolvedMsqQuest.State);
|
|
if (questResolution.HasValue)
|
|
{
|
|
return questResolution.GetValueOrDefault();
|
|
}
|
|
questResolution = TryResolveClassJobPriority(valueOrDefault, resolvedMsqQuest.State);
|
|
if (questResolution.HasValue)
|
|
{
|
|
return questResolution.GetValueOrDefault();
|
|
}
|
|
if (resolvedMsqQuest.CurrentQuest != null && IsMsqLevelLocked(resolvedMsqQuest, num, automationType))
|
|
{
|
|
if (_lastLoggedLevelLockedMsq != resolvedMsqQuest.CurrentQuest)
|
|
{
|
|
_logger.LogInformation("MSQ {MsqId} requires higher level, current level {CurrentLevel}. Checking for early class quests.", resolvedMsqQuest.CurrentQuest, num);
|
|
_lastLoggedLevelLockedMsq = resolvedMsqQuest.CurrentQuest;
|
|
_loggedNoClassQuestsAvailable = false;
|
|
_loggedAdventurerClass = false;
|
|
}
|
|
return TryResolveNewClassQuest(valueOrDefault, num, resolvedMsqQuest.State) ?? TryResolveLevelingMode(num, automationType, resolvedMsqQuest.State) ?? QuestResolution.None(MainScenarioQuestState.Unavailable, "MSQ level-locked, no class quests or leveling available");
|
|
}
|
|
_lastLoggedLevelLockedMsq = null;
|
|
_lastLoggedForcedClassQuest = null;
|
|
_lastLoggedRegistryClassQuest = null;
|
|
_lastLoggedPriorityClassQuest = null;
|
|
_loggedNoClassQuestsAvailable = false;
|
|
_loggedAdventurerClass = false;
|
|
questResolution = TryResolveMsqImmediate(resolvedMsqQuest, flag);
|
|
if (questResolution.HasValue)
|
|
{
|
|
return questResolution.GetValueOrDefault();
|
|
}
|
|
questResolution = TryResolveTrackedQuest(resolvedMsqQuest.State);
|
|
if (questResolution.HasValue)
|
|
{
|
|
QuestResolution valueOrDefault3 = questResolution.GetValueOrDefault();
|
|
return ApplyQuestRedirects(valueOrDefault3);
|
|
}
|
|
questResolution = TryResolveAutoPriority(resolvedMsqQuest.State);
|
|
if (questResolution.HasValue)
|
|
{
|
|
QuestResolution valueOrDefault4 = questResolution.GetValueOrDefault();
|
|
return ApplyQuestRedirects(valueOrDefault4);
|
|
}
|
|
questResolution = TryResolveMsqFallback(resolvedMsqQuest, flag);
|
|
if (questResolution.HasValue)
|
|
{
|
|
return questResolution.GetValueOrDefault();
|
|
}
|
|
if (resolvedMsqQuest.CurrentQuest == null || resolvedMsqQuest.CurrentQuest.Value == 0)
|
|
{
|
|
questResolution = TryResolveStartingCity(resolvedMsqQuest.State);
|
|
if (questResolution.HasValue)
|
|
{
|
|
return questResolution.GetValueOrDefault();
|
|
}
|
|
}
|
|
return QuestResolution.None(resolvedMsqQuest.State, "No quest available");
|
|
}
|
|
|
|
private QuestReference GetResolvedMsqQuest()
|
|
{
|
|
QuestReference questReference = _questFunctions.GetMainScenarioQuest().Item1;
|
|
if (questReference.CurrentQuest != null && !_questRegistry.IsKnownQuest(questReference.CurrentQuest))
|
|
{
|
|
if (questReference.CurrentQuest is QuestId questId && _alreadyLoggedUnobtainableQuests.Add(questId.Value))
|
|
{
|
|
_logger.LogWarning("MSQ {MsqQuestId} is not in the quest registry", questReference.CurrentQuest);
|
|
}
|
|
questReference = QuestReference.NoQuest(questReference.State);
|
|
}
|
|
return ApplyMsqSpecialCases(questReference);
|
|
}
|
|
|
|
private QuestReference ApplyMsqSpecialCases(QuestReference msqQuest)
|
|
{
|
|
var (elementId2, sequence, state) = msqQuest;
|
|
if (elementId2 == null || elementId2.Value == 0)
|
|
{
|
|
return msqQuest;
|
|
}
|
|
if (elementId2.Value == 681)
|
|
{
|
|
if (_questFunctions.IsQuestAccepted(elementId2) || _questFunctions.IsQuestComplete(elementId2))
|
|
{
|
|
return new QuestReference(elementId2, sequence, state);
|
|
}
|
|
GrandCompany grandCompany = _configuration.General.GrandCompany;
|
|
if (grandCompany == GrandCompany.None)
|
|
{
|
|
grandCompany = PlayerStateHelper.GetStartTown() switch
|
|
{
|
|
1 => GrandCompany.Maelstrom,
|
|
2 => GrandCompany.TwinAdder,
|
|
3 => GrandCompany.ImmortalFlames,
|
|
_ => GrandCompany.None,
|
|
};
|
|
}
|
|
return grandCompany switch
|
|
{
|
|
GrandCompany.TwinAdder => new QuestReference(new QuestId(680), 0, state),
|
|
GrandCompany.Maelstrom => new QuestReference(new QuestId(681), 0, state),
|
|
GrandCompany.ImmortalFlames => new QuestReference(new QuestId(682), 0, state),
|
|
_ => QuestReference.NoQuest(MainScenarioQuestState.Unavailable),
|
|
};
|
|
}
|
|
if (elementId2.Value == 3856 && !PlayerStateHelper.IsMountUnlocked(1u))
|
|
{
|
|
ushort num = (GrandCompany)GrandCompanyHelper.GetGrandCompanyId() switch
|
|
{
|
|
GrandCompany.TwinAdder => 700,
|
|
GrandCompany.Maelstrom => 701,
|
|
GrandCompany.ImmortalFlames => 702,
|
|
_ => 0,
|
|
};
|
|
if (num != 0 && !QuestManager.IsQuestComplete(num))
|
|
{
|
|
return new QuestReference(new QuestId(num), QuestManager.GetQuestSequence(num), state);
|
|
}
|
|
}
|
|
if (elementId2.Value == 801)
|
|
{
|
|
QuestId questId = new QuestId(802);
|
|
if (_questFunctions.IsQuestAccepted(questId))
|
|
{
|
|
return new QuestReference(questId, QuestManager.GetQuestSequence(questId.Value), state);
|
|
}
|
|
}
|
|
return msqQuest;
|
|
}
|
|
|
|
private QuestResolution ApplyQuestRedirects(QuestResolution resolution)
|
|
{
|
|
ElementId questId = resolution.QuestId;
|
|
if ((object)questId != null && questId.Value == 801)
|
|
{
|
|
QuestId questId2 = new QuestId(802);
|
|
if (_questFunctions.IsQuestAccepted(questId2))
|
|
{
|
|
return resolution with
|
|
{
|
|
QuestId = questId2,
|
|
Sequence = QuestManager.GetQuestSequence(questId2.Value),
|
|
DebugReason = "Redirected 801 -> 802 (Broadening Horizons prerequisite)"
|
|
};
|
|
}
|
|
}
|
|
return resolution;
|
|
}
|
|
|
|
private QuestResolution? TryResolveManualPriority(List<Quest> manualPriorityQuests, MainScenarioQuestState msqState)
|
|
{
|
|
if (manualPriorityQuests.Count == 0)
|
|
{
|
|
return null;
|
|
}
|
|
(ElementId, byte) tuple = (from x in manualPriorityQuests.Where(delegate(Quest x)
|
|
{
|
|
if (_questFunctions.IsQuestBlacklisted(x.Id))
|
|
{
|
|
return false;
|
|
}
|
|
return _questFunctions.IsQuestAccepted(x.Id) ? ((!_configuration.Advanced.AutoPrioritizeAlliedSocietyRankUp || !(x.Id is QuestId questId) || !_questFunctions.WouldOvercapAlliedSocietyReputation(questId)) ? true : false) : _questFunctions.IsReadyToAcceptQuest(x.Id);
|
|
})
|
|
select (QuestId: x.Id, Sequence: _questFunctions.GetQuestProgressInfo(x.Id)?.Sequence ?? 0)).FirstOrDefault();
|
|
if ((object)tuple.Item1 != null)
|
|
{
|
|
return QuestResolution.ForQuest(EQuestResolutionType.ManualPriority, tuple.Item1, tuple.Item2, msqState, $"Manual priority quest {tuple.Item1}");
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private QuestResolution? TryResolveInProgressClassQuest(EClassJob currentClassJob, MainScenarioQuestState msqState)
|
|
{
|
|
if (_configuration.Advanced.SkipClassJobQuests || currentClassJob == EClassJob.Adventurer)
|
|
{
|
|
return null;
|
|
}
|
|
QuestInfo questInfo = (from x in _questData.GetClassJobQuests(currentClassJob)
|
|
where !_questFunctions.IsQuestBlacklisted(x.QuestId) && _questFunctions.IsQuestAccepted(x.QuestId) && !_questFunctions.IsQuestComplete(x.QuestId)
|
|
orderby x.Level
|
|
select x).FirstOrDefault();
|
|
if (questInfo == null)
|
|
{
|
|
return null;
|
|
}
|
|
_logger.LogTrace("Found in-progress class quest {QuestId}, continuing it", questInfo.QuestId);
|
|
return QuestResolution.ForQuest(EQuestResolutionType.InProgressClassQuest, questInfo.QuestId, QuestManager.GetQuestSequence(questInfo.QuestId.Value), msqState, $"In-progress class quest {questInfo.QuestId}");
|
|
}
|
|
|
|
private QuestResolution? TryResolveClassJobPriority(EClassJob currentClassJob, MainScenarioQuestState msqState)
|
|
{
|
|
if (_configuration.Advanced.SkipClassJobQuests || currentClassJob == EClassJob.Adventurer)
|
|
{
|
|
return null;
|
|
}
|
|
HashSet<ElementId> classJobQuestIds = (from x in _questRegistry.GetKnownClassJobQuests(currentClassJob, includeRoleQuests: false)
|
|
select x.QuestId).ToHashSet();
|
|
ElementId elementId = (from x in _questFunctions.GetNextPriorityQuestsThatCanBeAccepted()
|
|
where x.IsAvailable && classJobQuestIds.Contains(x.QuestId)
|
|
select x.QuestId).FirstOrDefault();
|
|
if (elementId == null)
|
|
{
|
|
return null;
|
|
}
|
|
return QuestResolution.ForQuest(EQuestResolutionType.NewClassQuest, elementId, QuestManager.GetQuestSequence(elementId.Value), msqState, $"Class/job priority quest {elementId}");
|
|
}
|
|
|
|
private bool IsMsqLevelLocked(QuestReference msqQuest, int currentLevel, QuestController.EAutomationType automationType)
|
|
{
|
|
if (msqQuest.CurrentQuest == null)
|
|
{
|
|
return false;
|
|
}
|
|
if (!_questRegistry.TryGetQuest(msqQuest.CurrentQuest, out Quest quest))
|
|
{
|
|
return false;
|
|
}
|
|
if (_questFunctions.IsQuestAccepted(msqQuest.CurrentQuest))
|
|
{
|
|
return false;
|
|
}
|
|
if (_configuration.General.MsqPriority == Configuration.EMsqPriorityMode.Manual)
|
|
{
|
|
return false;
|
|
}
|
|
return quest.Info.Level > currentLevel;
|
|
}
|
|
|
|
private QuestResolution? TryResolveNewClassQuest(EClassJob currentClassJob, int currentLevel, MainScenarioQuestState msqState)
|
|
{
|
|
if (currentClassJob == EClassJob.Adventurer)
|
|
{
|
|
if (!_loggedAdventurerClass)
|
|
{
|
|
_logger.LogWarning("Current class is Adventurer, cannot find class quests");
|
|
_loggedAdventurerClass = true;
|
|
}
|
|
return null;
|
|
}
|
|
List<QuestId> lockedQuests = QuestData.GetLockedClassQuests();
|
|
QuestInfo questInfo = (from x in _questData.GetClassJobQuests(currentClassJob)
|
|
where x.Level <= currentLevel && x.Level <= 5 && !_questFunctions.IsQuestComplete(x.QuestId) && !lockedQuests.Contains(x.QuestId)
|
|
orderby x.Level
|
|
select x).FirstOrDefault();
|
|
if (questInfo != null && !_questFunctions.IsQuestComplete(questInfo.QuestId))
|
|
{
|
|
if (_lastLoggedForcedClassQuest != questInfo.QuestId)
|
|
{
|
|
_logger.LogInformation("MSQ level locked. Forcing class quest {ClassQuestId} for {ClassJob} (level {QuestLevel})", questInfo.QuestId, currentClassJob, questInfo.Level);
|
|
_lastLoggedForcedClassQuest = questInfo.QuestId;
|
|
}
|
|
return QuestResolution.ForQuest(EQuestResolutionType.NewClassQuest, questInfo.QuestId, QuestManager.GetQuestSequence(questInfo.QuestId.Value), msqState, $"New class quest (data) {questInfo.QuestId}");
|
|
}
|
|
QuestInfo questInfo2 = (from x in _questRegistry.GetKnownClassJobQuests(currentClassJob, includeRoleQuests: false)
|
|
where x.Level <= currentLevel && x.Level <= 5 && !_questFunctions.IsQuestAcceptedOrComplete(x.QuestId) && _questFunctions.IsReadyToAcceptQuest(x.QuestId)
|
|
orderby x.Level
|
|
select x).FirstOrDefault();
|
|
if (questInfo2 != null)
|
|
{
|
|
if (_lastLoggedRegistryClassQuest != questInfo2.QuestId)
|
|
{
|
|
_logger.LogInformation("MSQ level locked. Prioritizing class quest {ClassQuestId} for {ClassJob} (level {QuestLevel}) from registry", questInfo2.QuestId, currentClassJob, questInfo2.Level);
|
|
_lastLoggedRegistryClassQuest = questInfo2.QuestId;
|
|
}
|
|
return QuestResolution.ForQuest(EQuestResolutionType.NewClassQuest, questInfo2.QuestId, QuestManager.GetQuestSequence(questInfo2.QuestId.Value), msqState, $"New class quest (registry) {questInfo2.QuestId}");
|
|
}
|
|
Quest quest;
|
|
ElementId elementId = (from x in _questFunctions.GetNextPriorityQuestsThatCanBeAccepted()
|
|
where x.IsAvailable
|
|
where _questRegistry.TryGetQuest(x.QuestId, out quest) && quest.Info.Level <= currentLevel && quest.Info is QuestInfo questInfo3 && questInfo3.ClassJobs.Count > 0 && questInfo3.ClassJobs.Any((EClassJob job) => !job.IsCrafter() && !job.IsGatherer())
|
|
select x.QuestId).FirstOrDefault();
|
|
if (elementId != null)
|
|
{
|
|
if (_lastLoggedPriorityClassQuest != elementId)
|
|
{
|
|
_logger.LogInformation("MSQ level locked. Prioritizing class quest {ClassQuestId} from priority list", elementId);
|
|
_lastLoggedPriorityClassQuest = elementId;
|
|
}
|
|
return QuestResolution.ForQuest(EQuestResolutionType.NewClassQuest, elementId, QuestManager.GetQuestSequence(elementId.Value), msqState, $"New class quest (priority) {elementId}");
|
|
}
|
|
if (!_loggedNoClassQuestsAvailable)
|
|
{
|
|
_logger.LogWarning("MSQ level locked but no available early class quests found for {ClassJob} at level {CurrentLevel}", currentClassJob, currentLevel);
|
|
_loggedNoClassQuestsAvailable = true;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private QuestResolution? TryResolveMsqImmediate(QuestReference msqQuest, bool allowNewMsq)
|
|
{
|
|
if (msqQuest.CurrentQuest == null || msqQuest.CurrentQuest.Value == 0)
|
|
{
|
|
return null;
|
|
}
|
|
if (_questFunctions.IsQuestAccepted(msqQuest.CurrentQuest))
|
|
{
|
|
return null;
|
|
}
|
|
if (_configuration.General.MsqPriority != Configuration.EMsqPriorityMode.Always)
|
|
{
|
|
return null;
|
|
}
|
|
if (!allowNewMsq)
|
|
{
|
|
return null;
|
|
}
|
|
return QuestResolution.ForQuest(EQuestResolutionType.MsqImmediate, msqQuest.CurrentQuest, msqQuest.Sequence, msqQuest.State, $"MSQ immediate (Always mode) {msqQuest.CurrentQuest}");
|
|
}
|
|
|
|
private unsafe QuestResolution? TryResolveTrackedQuest(MainScenarioQuestState msqState)
|
|
{
|
|
QuestManager* ptr = QuestManager.Instance();
|
|
if (ptr == null)
|
|
{
|
|
return null;
|
|
}
|
|
List<(ElementId, byte)> list = new List<(ElementId, byte)>();
|
|
for (int num = ptr->TrackedQuests.Length - 1; num >= 0; num--)
|
|
{
|
|
TrackingWork trackingWork = ptr->TrackedQuests[num];
|
|
switch (trackingWork.QuestType)
|
|
{
|
|
case 1:
|
|
{
|
|
QuestWork questWork = ptr->NormalQuests[trackingWork.Index];
|
|
if (!questWork.IsHidden)
|
|
{
|
|
ElementId elementId = new QuestId(questWork.QuestId);
|
|
if (_questRegistry.IsKnownQuest(elementId) && !_questFunctions.IsQuestBlacklisted(elementId))
|
|
{
|
|
list.Add((elementId, QuestManager.GetQuestSequence(elementId.Value)));
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (_configuration.General.SkipLowPriorityDuties && list.Count > 0)
|
|
{
|
|
IReadOnlyList<(uint ContentFinderConditionId, ElementId QuestId, int Sequence)> lowPriorityQuests = _questRegistry.LowPriorityContentFinderConditionQuests;
|
|
list.RemoveAll(((ElementId Quest, byte Sequence) x) => lowPriorityQuests.Any<(uint, ElementId, int)>(((uint ContentFinderConditionId, ElementId QuestId, int Sequence) y) => x.Quest == y.QuestId && x.Sequence == y.Sequence));
|
|
}
|
|
if (_configuration.Advanced.AutoPrioritizeAlliedSocietyRankUp && list.Count > 0)
|
|
{
|
|
list.RemoveAll(((ElementId Quest, byte Sequence) x) => x.Quest is QuestId questId2 && _questFunctions.WouldOvercapAlliedSocietyReputation(questId2));
|
|
}
|
|
if (list.Count == 0)
|
|
{
|
|
return null;
|
|
}
|
|
(ElementId, byte) tuple = list.First();
|
|
ElementId item = tuple.Item1;
|
|
byte item2 = tuple.Item2;
|
|
EAlliedSociety firstTrackedAlliedSociety = _alliedSocietyData.GetCommonAlliedSocietyTurnIn(item);
|
|
if (firstTrackedAlliedSociety != EAlliedSociety.None)
|
|
{
|
|
List<(ElementId, byte)> list2 = (from quest in list.Skip(1)
|
|
where _alliedSocietyData.GetCommonAlliedSocietyTurnIn(quest.Quest) == firstTrackedAlliedSociety
|
|
select quest).ToList();
|
|
if (list2.Count > 0)
|
|
{
|
|
if (item2 == byte.MaxValue)
|
|
{
|
|
foreach (var (elementId2, b) in list2)
|
|
{
|
|
if (b != byte.MaxValue)
|
|
{
|
|
return QuestResolution.ForQuest(EQuestResolutionType.TrackedQuest, elementId2, b, msqState, $"Allied society quest needs completing before turn-in {elementId2}");
|
|
}
|
|
}
|
|
if (_configuration.Advanced.AutoPrioritizeAlliedSocietyRankUp)
|
|
{
|
|
List<(ElementId, byte)> list3 = new List<(ElementId, byte)> { (item, item2) };
|
|
list3.AddRange(list2);
|
|
list3.Sort(delegate((ElementId Quest, byte Sequence) a, (ElementId Quest, byte Sequence) tuple5)
|
|
{
|
|
ushort num2 = (ushort)((a.Quest is QuestId questId2) ? _alliedSocietyQuestFunctions.GetQuestReputationReward(questId2) : 0);
|
|
ushort value = (ushort)((tuple5.Quest is QuestId questId3) ? _alliedSocietyQuestFunctions.GetQuestReputationReward(questId3) : 0);
|
|
return num2.CompareTo(value);
|
|
});
|
|
foreach (var (elementId3, sequence) in list3)
|
|
{
|
|
if (elementId3 is QuestId questId && !_alliedSocietyQuestFunctions.WouldOvercapReputation(questId))
|
|
{
|
|
return QuestResolution.ForQuest(EQuestResolutionType.TrackedQuest, elementId3, sequence, msqState, $"Allied society turn-in (cheapest first) {elementId3}");
|
|
}
|
|
}
|
|
QuestId rankUpQuestIfAtCapacity = _questFunctions.GetRankUpQuestIfAtCapacity(firstTrackedAlliedSociety);
|
|
if (rankUpQuestIfAtCapacity != null && !_questFunctions.IsQuestComplete(rankUpQuestIfAtCapacity) && (_questFunctions.IsQuestAccepted(rankUpQuestIfAtCapacity) || _questFunctions.IsReadyToAcceptQuest(rankUpQuestIfAtCapacity)))
|
|
{
|
|
return QuestResolution.ForQuest(EQuestResolutionType.TrackedQuest, rankUpQuestIfAtCapacity, (byte)(_questFunctions.IsQuestAccepted(rankUpQuestIfAtCapacity) ? QuestManager.GetQuestSequence(rankUpQuestIfAtCapacity.Value) : 0), msqState, $"Allied society rank-up quest {rankUpQuestIfAtCapacity}");
|
|
}
|
|
return QuestResolution.ForQuest(EQuestResolutionType.TrackedQuest, list3[0].Item1, list3[0].Item2, msqState, $"Allied society turn-in (all would overcap) {list3[0].Item1}");
|
|
}
|
|
}
|
|
else if (!_questFunctions.IsOnAlliedSocietyMount())
|
|
{
|
|
list2.Insert(0, (item, item2));
|
|
_alliedSocietyData.GetCommonAlliedSocietyNpcs(firstTrackedAlliedSociety, out uint[] normalNpcs, out uint[] _);
|
|
if (normalNpcs.Length != 0)
|
|
{
|
|
(ElementId, byte)? tuple4 = (from x in list2
|
|
where x.Sequence < byte.MaxValue
|
|
where _questFunctions.IsInteractSequence(x.Quest, x.Sequence, normalNpcs)
|
|
select x).Cast<(ElementId, byte)?>().FirstOrDefault();
|
|
if (tuple4.HasValue)
|
|
{
|
|
return QuestResolution.ForQuest(EQuestResolutionType.TrackedQuest, tuple4.Value.Item1, tuple4.Value.Item2, msqState, $"Allied society NPC interaction {tuple4.Value.Item1}");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return QuestResolution.ForQuest(EQuestResolutionType.TrackedQuest, item, item2, msqState, $"Tracked quest {item}");
|
|
}
|
|
|
|
private QuestResolution? TryResolveAutoPriority(MainScenarioQuestState msqState)
|
|
{
|
|
ElementId elementId = (from x in _questFunctions.GetNextPriorityQuestsThatCanBeAccepted()
|
|
where x.IsAvailable
|
|
select x.QuestId).FirstOrDefault();
|
|
if (elementId == null)
|
|
{
|
|
return null;
|
|
}
|
|
return QuestResolution.ForQuest(EQuestResolutionType.AutoPriority, elementId, QuestManager.GetQuestSequence(elementId.Value), msqState, $"Auto priority quest {elementId}");
|
|
}
|
|
|
|
private QuestResolution? TryResolveMsqFallback(QuestReference msqQuest, bool allowNewMsq)
|
|
{
|
|
if (msqQuest.CurrentQuest == null || msqQuest.CurrentQuest.Value == 0)
|
|
{
|
|
return null;
|
|
}
|
|
if (_questFunctions.IsQuestAccepted(msqQuest.CurrentQuest))
|
|
{
|
|
return null;
|
|
}
|
|
if (_configuration.General.MsqPriority == Configuration.EMsqPriorityMode.Manual)
|
|
{
|
|
return null;
|
|
}
|
|
if (!allowNewMsq)
|
|
{
|
|
return null;
|
|
}
|
|
return QuestResolution.ForQuest(EQuestResolutionType.MsqFallback, msqQuest.CurrentQuest, msqQuest.Sequence, msqQuest.State, $"MSQ fallback {msqQuest.CurrentQuest}");
|
|
}
|
|
|
|
private QuestResolution? TryResolveStartingCity(MainScenarioQuestState msqState)
|
|
{
|
|
if (_clientState.TerritoryType == 181)
|
|
{
|
|
return QuestResolution.ForQuest(EQuestResolutionType.MsqImmediate, new QuestId(107), 0, MainScenarioQuestState.Available, "Starting city: Limsa Lominsa");
|
|
}
|
|
if (_clientState.TerritoryType == 182)
|
|
{
|
|
return QuestResolution.ForQuest(EQuestResolutionType.MsqImmediate, new QuestId(594), 0, MainScenarioQuestState.Available, "Starting city: Ul'dah");
|
|
}
|
|
if (_clientState.TerritoryType == 183)
|
|
{
|
|
return QuestResolution.ForQuest(EQuestResolutionType.MsqImmediate, new QuestId(39), 0, MainScenarioQuestState.Available, "Starting city: Gridania");
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private QuestResolution? TryResolveLevelingMode(int currentPlayerLevel, QuestController.EAutomationType automationType, MainScenarioQuestState msqState)
|
|
{
|
|
if (!ShouldStartLevelingMode(currentPlayerLevel, automationType))
|
|
{
|
|
return null;
|
|
}
|
|
var (flag, _, targetLevel, questName) = _questFunctions.GetMsqLevelLockInfo();
|
|
if (!flag)
|
|
{
|
|
return null;
|
|
}
|
|
return QuestResolution.Leveling(targetLevel, questName, msqState);
|
|
}
|
|
|
|
public bool ShouldStartLevelingMode(int currentPlayerLevel, QuestController.EAutomationType automationType)
|
|
{
|
|
if (automationType != QuestController.EAutomationType.Automatic)
|
|
{
|
|
return false;
|
|
}
|
|
if (_condition[ConditionFlag.BoundByDuty] || _condition[ConditionFlag.InDutyQueue])
|
|
{
|
|
return false;
|
|
}
|
|
if (!_autoDutyIpc.IsConfiguredToRunLevelingMode(currentPlayerLevel))
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
}
|