1158 lines
36 KiB
C#
1158 lines
36 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using System.Runtime.CompilerServices;
|
|
using Dalamud.Game.ClientState.Aetherytes;
|
|
using Dalamud.Game.Text;
|
|
using Dalamud.Memory;
|
|
using Dalamud.Plugin.Services;
|
|
using FFXIVClientStructs.FFXIV.Application.Network.WorkDefinitions;
|
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
|
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
|
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
|
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
|
using LLib.GameData;
|
|
using LLib.GameUI;
|
|
using Lumina.Excel.Sheets;
|
|
using Microsoft.Extensions.Logging;
|
|
using Questionable.Controller;
|
|
using Questionable.Data;
|
|
using Questionable.Model;
|
|
using Questionable.Model.Common;
|
|
using Questionable.Model.Questing;
|
|
using Questionable.Windows.QuestComponents;
|
|
|
|
namespace Questionable.Functions;
|
|
|
|
internal sealed class QuestFunctions
|
|
{
|
|
private readonly QuestRegistry _questRegistry;
|
|
|
|
private readonly QuestData _questData;
|
|
|
|
private readonly AetheryteFunctions _aetheryteFunctions;
|
|
|
|
private readonly AlliedSocietyQuestFunctions _alliedSocietyQuestFunctions;
|
|
|
|
private readonly AlliedSocietyData _alliedSocietyData;
|
|
|
|
private readonly AetheryteData _aetheryteData;
|
|
|
|
private readonly Configuration _configuration;
|
|
|
|
private readonly IDataManager _dataManager;
|
|
|
|
private readonly IClientState _clientState;
|
|
|
|
private readonly IGameGui _gameGui;
|
|
|
|
private readonly IAetheryteList _aetheryteList;
|
|
|
|
private readonly ILogger<QuestFunctions> _logger;
|
|
|
|
private readonly HashSet<ushort> _alreadyLoggedUnobtainableQuestsDetailed = new HashSet<ushort>();
|
|
|
|
private readonly HashSet<ushort> _alreadyLoggedLevelRequirements = new HashSet<ushort>();
|
|
|
|
public QuestFunctions(QuestRegistry questRegistry, QuestData questData, AetheryteFunctions aetheryteFunctions, AlliedSocietyQuestFunctions alliedSocietyQuestFunctions, AlliedSocietyData alliedSocietyData, AetheryteData aetheryteData, Configuration configuration, IDataManager dataManager, IClientState clientState, IGameGui gameGui, IAetheryteList aetheryteList, ILogger<QuestFunctions> logger)
|
|
{
|
|
_questRegistry = questRegistry;
|
|
_questData = questData;
|
|
_aetheryteFunctions = aetheryteFunctions;
|
|
_alliedSocietyQuestFunctions = alliedSocietyQuestFunctions;
|
|
_alliedSocietyData = alliedSocietyData;
|
|
_aetheryteData = aetheryteData;
|
|
_configuration = configuration;
|
|
_dataManager = dataManager;
|
|
_clientState = clientState;
|
|
_gameGui = gameGui;
|
|
_aetheryteList = aetheryteList;
|
|
_logger = logger;
|
|
}
|
|
|
|
public unsafe QuestReference GetCurrentQuest(bool allowNewMsq = true)
|
|
{
|
|
QuestReference currentQuestInternal = GetCurrentQuestInternal(allowNewMsq);
|
|
currentQuestInternal.Deconstruct(out ElementId CurrentQuest, out byte Sequence, out MainScenarioQuestState State);
|
|
ElementId elementId = CurrentQuest;
|
|
byte sequence = Sequence;
|
|
MainScenarioQuestState state = State;
|
|
PlayerState* ptr = PlayerState.Instance();
|
|
if (elementId == null || elementId.Value == 0)
|
|
{
|
|
if (_clientState.TerritoryType == 181)
|
|
{
|
|
return new QuestReference(new QuestId(107), 0, MainScenarioQuestState.Available);
|
|
}
|
|
if (_clientState.TerritoryType == 182)
|
|
{
|
|
return new QuestReference(new QuestId(594), 0, MainScenarioQuestState.Available);
|
|
}
|
|
if (_clientState.TerritoryType == 183)
|
|
{
|
|
return new QuestReference(new QuestId(39), 0, MainScenarioQuestState.Available);
|
|
}
|
|
return QuestReference.NoQuest(state);
|
|
}
|
|
if (elementId.Value == 681)
|
|
{
|
|
if (IsQuestAccepted(elementId) || IsQuestComplete(elementId))
|
|
{
|
|
return new QuestReference(elementId, sequence, state);
|
|
}
|
|
return _configuration.General.GrandCompany switch
|
|
{
|
|
FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany.TwinAdder => new QuestReference(new QuestId(680), 0, state),
|
|
FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany.Maelstrom => new QuestReference(new QuestId(681), 0, state),
|
|
FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany.ImmortalFlames => new QuestReference(new QuestId(682), 0, state),
|
|
_ => QuestReference.NoQuest(MainScenarioQuestState.Unavailable),
|
|
};
|
|
}
|
|
if (elementId.Value == 3856 && !ptr->IsMountUnlocked(1u))
|
|
{
|
|
ushort num = (FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany)ptr->GrandCompany switch
|
|
{
|
|
FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany.TwinAdder => 700,
|
|
FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany.Maelstrom => 701,
|
|
FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany.ImmortalFlames => 702,
|
|
_ => 0,
|
|
};
|
|
if (num != 0 && !QuestManager.IsQuestComplete(num))
|
|
{
|
|
return new QuestReference(new QuestId(num), QuestManager.GetQuestSequence(num), state);
|
|
}
|
|
}
|
|
else if (elementId.Value == 801)
|
|
{
|
|
QuestId questId = new QuestId(802);
|
|
if (IsQuestAccepted(questId))
|
|
{
|
|
return new QuestReference(questId, QuestManager.GetQuestSequence(questId.Value), state);
|
|
}
|
|
}
|
|
return currentQuestInternal;
|
|
}
|
|
|
|
public unsafe QuestReference GetCurrentQuestInternal(bool allowNewMsq)
|
|
{
|
|
QuestManager* ptr = QuestManager.Instance();
|
|
if (ptr == null)
|
|
{
|
|
return QuestReference.NoQuest(MainScenarioQuestState.Unavailable);
|
|
}
|
|
QuestReference questReference = GetMainScenarioQuest().Item1;
|
|
if (questReference.CurrentQuest != null && !_questRegistry.IsKnownQuest(questReference.CurrentQuest))
|
|
{
|
|
questReference = QuestReference.NoQuest(questReference.State);
|
|
}
|
|
if (questReference.CurrentQuest != null && !IsQuestAccepted(questReference.CurrentQuest))
|
|
{
|
|
if (allowNewMsq)
|
|
{
|
|
return questReference;
|
|
}
|
|
questReference = QuestReference.NoQuest(questReference.State);
|
|
}
|
|
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:
|
|
{
|
|
ElementId elementId = new QuestId(ptr->NormalQuests[trackingWork.Index].QuestId);
|
|
if (_questRegistry.IsKnownQuest(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 (list.Count > 0)
|
|
{
|
|
(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 (currentQuest, b) in list2)
|
|
{
|
|
if (b != byte.MaxValue)
|
|
{
|
|
return new QuestReference(currentQuest, b, questReference.State);
|
|
}
|
|
}
|
|
}
|
|
else if (!IsOnAlliedSocietyMount())
|
|
{
|
|
list2.Insert(0, (item, item2));
|
|
_alliedSocietyData.GetCommonAlliedSocietyNpcs(firstTrackedAlliedSociety, out uint[] normalNpcs, out uint[] _);
|
|
if (normalNpcs.Length != 0)
|
|
{
|
|
(ElementId, byte)? tuple3 = (from x in list2
|
|
where x.Sequence < byte.MaxValue
|
|
where IsInteractSequence(x.Quest, x.Sequence, normalNpcs)
|
|
select x).Cast<(ElementId, byte)?>().FirstOrDefault();
|
|
if (tuple3.HasValue)
|
|
{
|
|
return new QuestReference(tuple3.Value.Item1, tuple3.Value.Item2, questReference.State);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return new QuestReference(item, item2, questReference.State);
|
|
}
|
|
ElementId elementId2 = (from x in GetNextPriorityQuestsThatCanBeAccepted()
|
|
where x.IsAvailable
|
|
select x.QuestId).FirstOrDefault();
|
|
if (elementId2 != null)
|
|
{
|
|
return new QuestReference(elementId2, QuestManager.GetQuestSequence(elementId2.Value), questReference.State);
|
|
}
|
|
if (questReference.CurrentQuest != null)
|
|
{
|
|
return questReference;
|
|
}
|
|
return QuestReference.NoQuest(questReference.State);
|
|
}
|
|
|
|
public unsafe (QuestReference, string?) GetMainScenarioQuest()
|
|
{
|
|
if (QuestManager.IsQuestComplete(3759))
|
|
{
|
|
AgentInterface* agentByInternalId = AgentModule.Instance()->GetAgentByInternalId(AgentId.QuestRedoHud);
|
|
if (agentByInternalId != null && agentByInternalId->IsAgentActive())
|
|
{
|
|
AtkUnitBase* addonPtr;
|
|
bool flag = _gameGui.TryGetAddonByName<AtkUnitBase>("QuestRedoHud", out addonPtr) && addonPtr->AtkValuesCount == 4;
|
|
if (flag)
|
|
{
|
|
uint uInt = addonPtr->AtkValues->UInt;
|
|
bool flag2 = ((uInt == 0 || uInt - 2 <= 2) ? true : false);
|
|
flag = flag2;
|
|
}
|
|
if (flag)
|
|
{
|
|
ushort num = MemoryHelper.Read<ushort>((nint)((byte*)agentByInternalId + 46));
|
|
return (new QuestReference(new QuestId(num), QuestManager.GetQuestSequence(num), MainScenarioQuestState.Available), "NG+");
|
|
}
|
|
}
|
|
}
|
|
AgentScenarioTree* ptr = AgentScenarioTree.Instance();
|
|
if (ptr == null)
|
|
{
|
|
return (QuestReference.NoQuest(MainScenarioQuestState.Unavailable), "No Scenario Tree");
|
|
}
|
|
if (ptr->Data == null)
|
|
{
|
|
return (QuestReference.NoQuest(MainScenarioQuestState.LoadingScreen), "Scenario Tree Data is null");
|
|
}
|
|
QuestId questId = new QuestId(ptr->Data->CurrentScenarioQuest);
|
|
string item = $"sq: {questId}";
|
|
if (questId.Value == 0)
|
|
{
|
|
if (IsMainScenarioQuestComplete())
|
|
{
|
|
return (QuestReference.NoQuest(MainScenarioQuestState.Complete), "Main Scenario is complete");
|
|
}
|
|
PlayerState* playerState = PlayerState.Instance();
|
|
List<QuestInfo> list = (from q in _questData.MainScenarioQuests
|
|
where q.StartingCity == 0 || q.StartingCity == playerState->StartTown
|
|
where IsReadyToAcceptQuest(q.QuestId, ignoreLevel: true)
|
|
select q).ToList();
|
|
if (list.Count == 0)
|
|
{
|
|
return (QuestReference.NoQuest(MainScenarioQuestState.Unavailable), "No potential quests found");
|
|
}
|
|
if (list.Count > 1)
|
|
{
|
|
if (list.All(delegate(QuestInfo x)
|
|
{
|
|
ushort value = x.QuestId.Value;
|
|
return (uint)(value - 680) <= 2u;
|
|
}))
|
|
{
|
|
questId = new QuestId(681);
|
|
}
|
|
else if (list.Any((QuestInfo x) => x.QuestId.Value == 1583))
|
|
{
|
|
questId = new QuestId(1583);
|
|
}
|
|
else if (list.Any((QuestInfo x) => x.QuestId.Value == 2451))
|
|
{
|
|
questId = new QuestId(2451);
|
|
}
|
|
else if (list.Any((QuestInfo x) => x.QuestId.Value == 3282))
|
|
{
|
|
questId = new QuestId(3282);
|
|
}
|
|
else if (list.Any((QuestInfo x) => x.QuestId.Value == 4359))
|
|
{
|
|
questId = new QuestId(4359);
|
|
}
|
|
else if (list.Any((QuestInfo x) => x.QuestId.Value == 4865))
|
|
{
|
|
questId = new QuestId(4865);
|
|
}
|
|
if (list.Count != 1)
|
|
{
|
|
return (QuestReference.NoQuest(MainScenarioQuestState.Unavailable), "Multiple potential quests found: " + string.Join(", ", list.Select((QuestInfo x) => x.QuestId.Value)));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
questId = (QuestId)list.Single().QuestId;
|
|
}
|
|
}
|
|
QuestManager* ptr2 = QuestManager.Instance();
|
|
if (IsQuestAccepted(questId) && ptr2->GetQuestById(questId.Value)->IsHidden)
|
|
{
|
|
return (QuestReference.NoQuest(MainScenarioQuestState.Available), "Quest accepted but hidden");
|
|
}
|
|
if (IsQuestComplete(questId))
|
|
{
|
|
return (new QuestReference(questId, byte.MaxValue, MainScenarioQuestState.Available), $"Quest {questId.Value} complete");
|
|
}
|
|
if (!IsReadyToAcceptQuest(questId))
|
|
{
|
|
return (QuestReference.NoQuest(MainScenarioQuestState.Unavailable), $"Not readdy to accept quest {questId.Value}");
|
|
}
|
|
byte? b = _clientState.LocalPlayer?.Level;
|
|
if (!b.HasValue)
|
|
{
|
|
return (QuestReference.NoQuest(MainScenarioQuestState.LoadingScreen), "In loading screen");
|
|
}
|
|
if (_questRegistry.TryGetQuest(questId, out Questionable.Model.Quest quest) && quest.Info.Level > b)
|
|
{
|
|
return (QuestReference.NoQuest(MainScenarioQuestState.Unavailable), "Low level");
|
|
}
|
|
return (new QuestReference(questId, QuestManager.GetQuestSequence(questId.Value), MainScenarioQuestState.Available), item);
|
|
}
|
|
|
|
private unsafe bool IsOnAlliedSocietyMount()
|
|
{
|
|
BattleChara* ptr = (BattleChara*)(_clientState.LocalPlayer?.Address ?? 0);
|
|
if (ptr != null && ptr->Mount.MountId != 0)
|
|
{
|
|
return _alliedSocietyData.Mounts.ContainsKey(ptr->Mount.MountId);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private bool IsInteractSequence(ElementId questId, byte sequenceNo, uint[] dataIds)
|
|
{
|
|
if (_questRegistry.TryGetQuest(questId, out Questionable.Model.Quest quest))
|
|
{
|
|
return quest.FindSequence(sequenceNo)?.Steps.All(delegate(QuestStep x)
|
|
{
|
|
if (x == null || x.InteractionType != EInteractionType.WalkTo)
|
|
{
|
|
if (x != null && x.InteractionType == EInteractionType.Interact)
|
|
{
|
|
uint? dataId = x.DataId;
|
|
if (dataId.HasValue)
|
|
{
|
|
uint valueOrDefault = dataId.GetValueOrDefault();
|
|
return dataIds.Contains(valueOrDefault);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
return true;
|
|
}) ?? false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public unsafe QuestProgressInfo? GetQuestProgressInfo(ElementId elementId)
|
|
{
|
|
if (elementId is QuestId questId)
|
|
{
|
|
QuestWork* questById = QuestManager.Instance()->GetQuestById(questId.Value);
|
|
if (questById == null)
|
|
{
|
|
return null;
|
|
}
|
|
return new QuestProgressInfo(*questById);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public unsafe List<PriorityQuestInfo> GetNextPriorityQuestsThatCanBeAccepted()
|
|
{
|
|
InventoryManager* ptr = InventoryManager.Instance();
|
|
int gil = ptr->GetItemCountInContainer(1u, InventoryType.Currency, isHq: false, 0);
|
|
return (from x in GetPriorityQuests()
|
|
where IsReadyToAcceptQuest(x)
|
|
select x).Select(delegate(ElementId x)
|
|
{
|
|
if (!_questRegistry.TryGetQuest(x, out Questionable.Model.Quest quest))
|
|
{
|
|
return new PriorityQuestInfo(x, "Unknown quest");
|
|
}
|
|
QuestStep questStep = quest.FindSequence(0)?.FindStep(0);
|
|
if (questStep == null)
|
|
{
|
|
return new PriorityQuestInfo(x, "No sequence 0 with steps");
|
|
}
|
|
if (!_aetheryteFunctions.IsTeleportUnlocked())
|
|
{
|
|
return new PriorityQuestInfo(x, "Teleport not unlocked");
|
|
}
|
|
if (!questStep.IsTeleportableForPriorityQuests())
|
|
{
|
|
return new PriorityQuestInfo(x, "Can't teleport to start");
|
|
}
|
|
DefaultInterpolatedStringHandler handler;
|
|
if (gil < EstimateTeleportCosts(quest))
|
|
{
|
|
IFormatProvider invariantCulture = CultureInfo.InvariantCulture;
|
|
handler = new DefaultInterpolatedStringHandler(32, 2, invariantCulture);
|
|
handler.AppendLiteral("Not enough gil, estimated cost: ");
|
|
handler.AppendFormatted(EstimateTeleportCosts(quest), "N0");
|
|
handler.AppendFormatted(SeIconChar.Gil.ToIconString());
|
|
return new PriorityQuestInfo(x, string.Create(invariantCulture, ref handler));
|
|
}
|
|
EAetheryteLocation? value = quest.AllSteps().Select<(QuestSequence, int, QuestStep), EAetheryteLocation?>(delegate((QuestSequence Sequence, int StepId, QuestStep Step) y)
|
|
{
|
|
EAetheryteLocation? aetheryteShortcut = y.Step.AetheryteShortcut;
|
|
if (aetheryteShortcut.HasValue)
|
|
{
|
|
EAetheryteLocation valueOrDefault = aetheryteShortcut.GetValueOrDefault();
|
|
if (!_aetheryteFunctions.IsAetheryteUnlocked(valueOrDefault))
|
|
{
|
|
SkipConditions? skipConditions = y.Step.SkipConditions;
|
|
if (skipConditions == null || skipConditions.AetheryteShortcutIf?.AetheryteLocked != valueOrDefault)
|
|
{
|
|
return valueOrDefault;
|
|
}
|
|
}
|
|
}
|
|
AethernetShortcut aethernetShortcut = y.Step.AethernetShortcut;
|
|
if (aethernetShortcut != null)
|
|
{
|
|
if (!_aetheryteFunctions.IsAetheryteUnlocked(aethernetShortcut.From))
|
|
{
|
|
return aethernetShortcut.From;
|
|
}
|
|
if (!_aetheryteFunctions.IsAetheryteUnlocked(aethernetShortcut.To))
|
|
{
|
|
return aethernetShortcut.To;
|
|
}
|
|
}
|
|
return (EAetheryteLocation?)null;
|
|
}).FirstOrDefault((EAetheryteLocation? y) => y.HasValue);
|
|
if (value.HasValue)
|
|
{
|
|
handler = new DefaultInterpolatedStringHandler(18, 1);
|
|
handler.AppendLiteral("Aetheryte locked: ");
|
|
handler.AppendFormatted(value);
|
|
return new PriorityQuestInfo(x, handler.ToStringAndClear());
|
|
}
|
|
return new PriorityQuestInfo(x);
|
|
}).ToList();
|
|
}
|
|
|
|
private int EstimateTeleportCosts(Questionable.Model.Quest quest)
|
|
{
|
|
Dictionary<EAetheryteLocation, float> cheapTeleports = _aetheryteList.Where((IAetheryteEntry x) => x.IsFavourite || _aetheryteFunctions.IsFreeAetheryte((EAetheryteLocation)x.AetheryteId)).ToDictionary((IAetheryteEntry x) => (EAetheryteLocation)x.AetheryteId, (IAetheryteEntry x) => _aetheryteFunctions.IsFreeAetheryte((EAetheryteLocation)x.AetheryteId) ? 0f : 0.5f);
|
|
int baseCost = ((quest.Info.Expansion == EExpansionVersion.ARealmReborn) ? 300 : 1000);
|
|
return (from x in quest.AllSteps()
|
|
where x.Step.AetheryteShortcut.HasValue
|
|
select x).Sum<(QuestSequence, int, QuestStep)>(((QuestSequence Sequence, int StepId, QuestStep Step) x) => (int)((float)baseCost * cheapTeleports.GetValueOrDefault(x.Step.AetheryteShortcut.Value, 1f)));
|
|
}
|
|
|
|
public List<ElementId> GetPriorityQuests(bool onlyClassAndRoleQuests = false)
|
|
{
|
|
List<ElementId> list = new List<ElementId>();
|
|
if (!onlyClassAndRoleQuests)
|
|
{
|
|
if (!_configuration.Advanced.SkipARealmRebornHardModePrimals)
|
|
{
|
|
list.AddRange(QuestData.HardModePrimals.Skip(1));
|
|
}
|
|
if (!_configuration.Advanced.SkipCrystalTowerRaids)
|
|
{
|
|
list.AddRange(QuestData.CrystalTowerQuests);
|
|
}
|
|
}
|
|
if (!_configuration.Advanced.SkipClassJobQuests)
|
|
{
|
|
EClassJob valueOrDefault = ((EClassJob?)_clientState.LocalPlayer?.ClassJob.RowId).GetValueOrDefault();
|
|
uint[] shadowbringersRoleQuestChapters = QuestData.AllRoleQuestChapters.Select((IReadOnlyList<uint> x) => x[0]).ToArray();
|
|
if (valueOrDefault != EClassJob.Adventurer)
|
|
{
|
|
list.AddRange(from x in _questRegistry.GetKnownClassJobQuests(valueOrDefault).Where(delegate(QuestInfo x)
|
|
{
|
|
if (_questRegistry.TryGetQuest(x.QuestId, out Questionable.Model.Quest quest))
|
|
{
|
|
IQuestInfo info = quest.Info;
|
|
QuestInfo questInfo = info as QuestInfo;
|
|
if (questInfo != null)
|
|
{
|
|
if (shadowbringersRoleQuestChapters.Contains(questInfo.NewGamePlusChapter))
|
|
{
|
|
return !QuestData.FinalShadowbringersRoleQuests.Any(IsQuestComplete);
|
|
}
|
|
if (QuestData.AllRoleQuestChapters.Any((IReadOnlyList<uint> y) => y.Contains(questInfo.NewGamePlusChapter)))
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
})
|
|
select x.QuestId);
|
|
}
|
|
}
|
|
return list.Where(_questRegistry.IsKnownQuest).ToList();
|
|
}
|
|
|
|
public unsafe bool IsReadyToAcceptQuest(ElementId questId, bool ignoreLevel = false)
|
|
{
|
|
_questRegistry.TryGetQuest(questId, out Questionable.Model.Quest quest);
|
|
if (quest != null)
|
|
{
|
|
IQuestInfo info = quest.Info;
|
|
if (info != null && info.IsRepeatable)
|
|
{
|
|
if (IsQuestAccepted(questId))
|
|
{
|
|
return false;
|
|
}
|
|
if (questId is QuestId questId2 && IsDailyAlliedSocietyQuest(questId2))
|
|
{
|
|
if (QuestManager.Instance()->IsDailyQuestCompleted(questId.Value))
|
|
{
|
|
return false;
|
|
}
|
|
if (!IsDailyAlliedSocietyQuestAndAvailableToday(questId2))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else if (IsQuestComplete(questId))
|
|
{
|
|
return false;
|
|
}
|
|
goto IL_0077;
|
|
}
|
|
}
|
|
if (IsQuestAcceptedOrComplete(questId))
|
|
{
|
|
return false;
|
|
}
|
|
goto IL_0077;
|
|
IL_0077:
|
|
if (IsQuestLocked(questId))
|
|
{
|
|
return false;
|
|
}
|
|
if (!ignoreLevel)
|
|
{
|
|
byte b = _clientState.LocalPlayer?.Level ?? 0;
|
|
if (b == 0)
|
|
{
|
|
return false;
|
|
}
|
|
if (quest != null && quest.Info.Level > b)
|
|
{
|
|
if (_alreadyLoggedLevelRequirements.Add(questId.Value))
|
|
{
|
|
_logger.LogDebug("Quest {QuestId} level requirement not met: required {RequiredLevel}, current {CurrentLevel}", questId, quest.Info.Level, b);
|
|
}
|
|
return false;
|
|
}
|
|
if (quest == null && questId is QuestId questId3 && _questData.TryGetQuestInfo(questId3, out IQuestInfo questInfo) && questInfo is QuestInfo questInfo2 && questInfo2.Level > b)
|
|
{
|
|
if (_alreadyLoggedLevelRequirements.Add(questId3.Value))
|
|
{
|
|
_logger.LogDebug("Quest {QuestId} (from data) level requirement not met: required {RequiredLevel}, current {CurrentLevel}", questId3, questInfo2.Level, b);
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public bool IsQuestAcceptedOrComplete(ElementId elementId)
|
|
{
|
|
if (!IsQuestComplete(elementId))
|
|
{
|
|
return IsQuestAccepted(elementId);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public bool IsQuestAccepted(ElementId elementId)
|
|
{
|
|
if (elementId is QuestId questId)
|
|
{
|
|
return IsQuestAccepted(questId);
|
|
}
|
|
if (elementId is SatisfactionSupplyNpcId)
|
|
{
|
|
return false;
|
|
}
|
|
if (elementId is AlliedSocietyDailyId)
|
|
{
|
|
return false;
|
|
}
|
|
if (elementId is UnlockLinkId)
|
|
{
|
|
return false;
|
|
}
|
|
if (elementId is AethernetId)
|
|
{
|
|
return false;
|
|
}
|
|
if (elementId is AetherCurrentId)
|
|
{
|
|
return false;
|
|
}
|
|
throw new ArgumentOutOfRangeException("elementId");
|
|
}
|
|
|
|
public unsafe bool IsQuestAccepted(QuestId questId)
|
|
{
|
|
return QuestManager.Instance()->IsQuestAccepted(questId.Value);
|
|
}
|
|
|
|
public bool IsQuestComplete(ElementId elementId)
|
|
{
|
|
if (elementId is QuestId questId)
|
|
{
|
|
return IsQuestComplete(questId);
|
|
}
|
|
if (elementId is SatisfactionSupplyNpcId)
|
|
{
|
|
return false;
|
|
}
|
|
if (elementId is AlliedSocietyDailyId)
|
|
{
|
|
return false;
|
|
}
|
|
if (elementId is UnlockLinkId unlockLinkId)
|
|
{
|
|
return IsQuestComplete(unlockLinkId);
|
|
}
|
|
if (elementId is AethernetId aethernetId)
|
|
{
|
|
return IsQuestComplete(aethernetId);
|
|
}
|
|
if (elementId is AetherCurrentId aetherCurrentId)
|
|
{
|
|
return IsQuestComplete(aetherCurrentId);
|
|
}
|
|
throw new ArgumentOutOfRangeException("elementId");
|
|
}
|
|
|
|
public bool IsQuestComplete(QuestId questId)
|
|
{
|
|
return QuestManager.IsQuestComplete(questId.Value);
|
|
}
|
|
|
|
public unsafe bool IsQuestComplete(UnlockLinkId unlockLinkId)
|
|
{
|
|
return UIState.Instance()->IsUnlockLinkUnlocked(unlockLinkId.Value);
|
|
}
|
|
|
|
public bool IsQuestComplete(AethernetId aethernetId)
|
|
{
|
|
if (!_questRegistry.TryGetQuest(aethernetId, out Questionable.Model.Quest quest))
|
|
{
|
|
_logger.LogWarning("Aethernet quest {AethernetId} not found in registry", aethernetId);
|
|
return false;
|
|
}
|
|
List<(QuestSequence, int, QuestStep)> list = (from x in quest.AllSteps()
|
|
where x.Step.InteractionType == EInteractionType.AttuneAethernetShard && x.Step.AethernetShard.HasValue
|
|
select x).ToList();
|
|
if (list.Count == 0)
|
|
{
|
|
_logger.LogWarning("Aethernet quest {AethernetId} has no aethernet shard attunement steps", aethernetId);
|
|
return false;
|
|
}
|
|
return list.All<(QuestSequence, int, QuestStep)>(((QuestSequence Sequence, int StepId, QuestStep Step) step) => _aetheryteFunctions.IsAetheryteUnlocked(step.Step.AethernetShard.Value));
|
|
}
|
|
|
|
public bool IsQuestComplete(AetherCurrentId aetherCurrentId)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public bool IsQuestLocked(ElementId elementId, ElementId? extraCompletedQuest = null)
|
|
{
|
|
if (elementId is QuestId questId)
|
|
{
|
|
return IsQuestLocked(questId, extraCompletedQuest);
|
|
}
|
|
if (elementId is SatisfactionSupplyNpcId satisfactionSupplyNpcId)
|
|
{
|
|
return IsQuestLocked(satisfactionSupplyNpcId);
|
|
}
|
|
if (elementId is AlliedSocietyDailyId alliedSocietyDailyId)
|
|
{
|
|
return IsQuestLocked(alliedSocietyDailyId);
|
|
}
|
|
if (elementId is UnlockLinkId unlockLinkId)
|
|
{
|
|
return IsQuestLocked(unlockLinkId);
|
|
}
|
|
if (elementId is AethernetId aethernetId)
|
|
{
|
|
return IsQuestLocked(aethernetId);
|
|
}
|
|
if (elementId is AetherCurrentId aetherCurrentId)
|
|
{
|
|
return IsQuestLocked(aetherCurrentId);
|
|
}
|
|
throw new ArgumentOutOfRangeException("elementId");
|
|
}
|
|
|
|
private unsafe bool IsQuestLocked(QuestId questId, ElementId? extraCompletedQuest = null)
|
|
{
|
|
if (IsQuestUnobtainable(questId, extraCompletedQuest))
|
|
{
|
|
return true;
|
|
}
|
|
QuestInfo questInfo = (QuestInfo)_questData.GetQuestInfo(questId);
|
|
if (questInfo.GrandCompany != FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany.None && questInfo.GrandCompany != GetGrandCompany())
|
|
{
|
|
return true;
|
|
}
|
|
if (questInfo.AlliedSociety != EAlliedSociety.None && questInfo.IsRepeatable)
|
|
{
|
|
return !IsDailyAlliedSocietyQuestAndAvailableToday(questId);
|
|
}
|
|
if (questInfo.IsMoogleDeliveryQuest)
|
|
{
|
|
byte b = PlayerState.Instance()->DeliveryLevel;
|
|
if (extraCompletedQuest != null && _questData.TryGetQuestInfo(extraCompletedQuest, out IQuestInfo questInfo2) && questInfo2 is QuestInfo { IsMoogleDeliveryQuest: not false })
|
|
{
|
|
b++;
|
|
}
|
|
if (questInfo.MoogleDeliveryLevel > b)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
if (HasCompletedPreviousQuests(questInfo, extraCompletedQuest))
|
|
{
|
|
return !HasCompletedPreviousInstances(questInfo);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private bool IsQuestLocked(SatisfactionSupplyNpcId satisfactionSupplyNpcId)
|
|
{
|
|
SatisfactionSupplyInfo questInfo = (SatisfactionSupplyInfo)_questData.GetQuestInfo(satisfactionSupplyNpcId);
|
|
return !HasCompletedPreviousQuests(questInfo, null);
|
|
}
|
|
|
|
private unsafe bool IsQuestLocked(AlliedSocietyDailyId alliedSocietyDailyId)
|
|
{
|
|
byte beastTribeRank = PlayerState.Instance()->GetBeastTribeRank(alliedSocietyDailyId.AlliedSociety);
|
|
if (beastTribeRank != 0)
|
|
{
|
|
return beastTribeRank < alliedSocietyDailyId.Rank;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private static bool IsQuestLocked(UnlockLinkId unlockLinkId)
|
|
{
|
|
return IsQuestUnobtainable(unlockLinkId);
|
|
}
|
|
|
|
private bool IsQuestLocked(AethernetId aethernetId)
|
|
{
|
|
EAetheryteLocation aetheryteLocation = aethernetId.Value switch
|
|
{
|
|
1 => EAetheryteLocation.Limsa,
|
|
2 => EAetheryteLocation.Gridania,
|
|
3 => EAetheryteLocation.Uldah,
|
|
4 => EAetheryteLocation.GoldSaucer,
|
|
5 => EAetheryteLocation.Ishgard,
|
|
6 => EAetheryteLocation.Idyllshire,
|
|
7 => EAetheryteLocation.RhalgrsReach,
|
|
8 => EAetheryteLocation.Kugane,
|
|
9 => EAetheryteLocation.DomanEnclave,
|
|
10 => EAetheryteLocation.Crystarium,
|
|
11 => EAetheryteLocation.Eulmore,
|
|
12 => EAetheryteLocation.OldSharlayan,
|
|
13 => EAetheryteLocation.RadzAtHan,
|
|
14 => EAetheryteLocation.Tuliyollal,
|
|
15 => EAetheryteLocation.SolutionNine,
|
|
_ => throw new ArgumentOutOfRangeException("aethernetId", $"Unknown AethernetId: {aethernetId.Value}"),
|
|
};
|
|
if (!_aetheryteFunctions.IsAetheryteUnlocked(aetheryteLocation))
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private static bool IsQuestLocked(AetherCurrentId aetherCurrentId)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public bool IsDailyAlliedSocietyQuest(QuestId questId)
|
|
{
|
|
QuestInfo questInfo = (QuestInfo)_questData.GetQuestInfo(questId);
|
|
if (questInfo.AlliedSociety != EAlliedSociety.None)
|
|
{
|
|
return questInfo.IsRepeatable;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public bool IsDailyAlliedSocietyQuestAndAvailableToday(QuestId questId)
|
|
{
|
|
if (!IsDailyAlliedSocietyQuest(questId))
|
|
{
|
|
return false;
|
|
}
|
|
QuestInfo questInfo = (QuestInfo)_questData.GetQuestInfo(questId);
|
|
return _alliedSocietyQuestFunctions.GetAvailableAlliedSocietyQuests(questInfo.AlliedSociety).Contains(questId);
|
|
}
|
|
|
|
public bool IsQuestUnobtainable(ElementId elementId, ElementId? extraCompletedQuest = null)
|
|
{
|
|
if (elementId is QuestId questId)
|
|
{
|
|
return IsQuestUnobtainable(questId, extraCompletedQuest);
|
|
}
|
|
if (elementId is UnlockLinkId unlockLinkId)
|
|
{
|
|
return IsQuestUnobtainable(unlockLinkId);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public unsafe bool IsQuestUnobtainable(QuestId questId, ElementId? extraCompletedQuest = null)
|
|
{
|
|
IQuestInfo questInfo = _questData.GetQuestInfo(questId);
|
|
if (questInfo is UnlockLinkQuestInfo { QuestExpiry: { TimeOfDay: var timeOfDay } questExpiry })
|
|
{
|
|
TimeSpan timeSpan = new TimeSpan(23, 59, 59);
|
|
bool flag = false;
|
|
DateTime dateTime;
|
|
if (timeOfDay == TimeSpan.Zero || timeOfDay == timeSpan)
|
|
{
|
|
dateTime = EventInfoComponent.AtDailyReset(DateOnly.FromDateTime(questExpiry));
|
|
flag = true;
|
|
}
|
|
else
|
|
{
|
|
dateTime = ((questExpiry.Kind == DateTimeKind.Utc) ? questExpiry : questExpiry.ToUniversalTime());
|
|
}
|
|
if (_alreadyLoggedUnobtainableQuestsDetailed.Add(questId.Value))
|
|
{
|
|
_logger.LogDebug("UnlockLink quest {QuestId} expiry raw={ExpiryRaw} Kind={Kind} TimeOfDay={TimeOfDay}", questId, questExpiry.ToString("o"), questExpiry.Kind, questExpiry.TimeOfDay);
|
|
_logger.LogDebug("UnlockLink quest {QuestId} normalized expiryUtc={ExpiryUtc:o} treatedAsDailyReset={TreatedAsDailyReset}", questId, dateTime, flag);
|
|
}
|
|
if (DateTime.UtcNow > dateTime)
|
|
{
|
|
if (_alreadyLoggedUnobtainableQuestsDetailed.Add(questId.Value))
|
|
{
|
|
_logger.LogDebug("UnlockLink quest {QuestId} unobtainable: expiry {ExpiryUtc} (UTC) is before now {NowUtc}", questId, dateTime.ToString("o"), DateTime.UtcNow.ToString("o"));
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
QuestInfo questInfo2 = (QuestInfo)questInfo;
|
|
if ((int)questInfo2.Expansion > (int)PlayerState.Instance()->MaxExpansion)
|
|
{
|
|
return true;
|
|
}
|
|
if (questInfo2.JournalGenre >= 234 && questInfo2.JournalGenre <= 247)
|
|
{
|
|
if (_questRegistry.TryGetQuest(questId, out Questionable.Model.Quest quest))
|
|
{
|
|
List<QuestSequence> list = quest?.Root?.QuestSequence;
|
|
if (list != null && list.Count > 0)
|
|
{
|
|
goto IL_0288;
|
|
}
|
|
}
|
|
if (_alreadyLoggedUnobtainableQuestsDetailed.Add(questId.Value))
|
|
{
|
|
_questData.ApplySeasonalOverride(questId, isSeasonal: true, null);
|
|
_logger.LogDebug("Quest {QuestId} unobtainable: journal genre is 'event (seasonal)' and no quest path", questId);
|
|
}
|
|
return true;
|
|
}
|
|
goto IL_0288;
|
|
IL_0288:
|
|
if (questInfo2.QuestLocks.Count > 0)
|
|
{
|
|
int num = questInfo2.QuestLocks.Count((QuestId x) => IsQuestComplete(x) || x.Equals(extraCompletedQuest));
|
|
if (questInfo2.QuestLockJoin == EQuestJoin.All && questInfo2.QuestLocks.Count == num)
|
|
{
|
|
return true;
|
|
}
|
|
if (questInfo2.QuestLockJoin == EQuestJoin.AtLeastOne && num > 0)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
DateTime? seasonalQuestExpiry = questInfo2.SeasonalQuestExpiry;
|
|
if (seasonalQuestExpiry.HasValue)
|
|
{
|
|
DateTime valueOrDefault = seasonalQuestExpiry.GetValueOrDefault();
|
|
TimeSpan timeOfDay2 = valueOrDefault.TimeOfDay;
|
|
TimeSpan timeSpan2 = new TimeSpan(23, 59, 59);
|
|
bool flag2 = false;
|
|
DateTime dateTime2;
|
|
if (timeOfDay2 == TimeSpan.Zero || timeOfDay2 == timeSpan2)
|
|
{
|
|
dateTime2 = EventInfoComponent.AtDailyReset(DateOnly.FromDateTime(valueOrDefault));
|
|
flag2 = true;
|
|
}
|
|
else
|
|
{
|
|
dateTime2 = ((valueOrDefault.Kind == DateTimeKind.Utc) ? valueOrDefault : valueOrDefault.ToUniversalTime());
|
|
}
|
|
if (_alreadyLoggedUnobtainableQuestsDetailed.Add(questId.Value))
|
|
{
|
|
_logger.LogDebug("Quest {QuestId} seasonal expiry raw={ExpiryRaw} Kind={Kind} TimeOfDay={TimeOfDay}", questId, valueOrDefault.ToString("o"), valueOrDefault.Kind, valueOrDefault.TimeOfDay);
|
|
_logger.LogDebug("Quest {QuestId} normalized expiryUtc={ExpiryUtc:o} treatedAsDailyReset={TreatedAsDailyReset}", questId, dateTime2, flag2);
|
|
_logger.LogTrace("Quest {QuestId} expiry check: nowUtc={Now:o}, expiryUtc={Expiry:o}, expired={Expired}", questId, DateTime.UtcNow, dateTime2, DateTime.UtcNow > dateTime2);
|
|
}
|
|
if (DateTime.UtcNow > dateTime2)
|
|
{
|
|
if (_alreadyLoggedUnobtainableQuestsDetailed.Add(questId.Value))
|
|
{
|
|
_logger.LogDebug("Quest {QuestId} unobtainable: seasonal expiry {ExpiryUtc} (UTC) is before now {NowUtc}", questId, dateTime2.ToString("o"), DateTime.UtcNow.ToString("o"));
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
if ((questInfo2.IsSeasonalEvent || questInfo2.IsSeasonalQuest) && !(questInfo2.SeasonalQuestExpiry is DateTime))
|
|
{
|
|
if (_alreadyLoggedUnobtainableQuestsDetailed.Add(questId.Value))
|
|
{
|
|
_logger.LogDebug("Quest {QuestId} is seasonal/event with no expiry; ShowIncompleteSeasonalEvents={ShowIncomplete}", questId, _configuration.General.ShowIncompleteSeasonalEvents);
|
|
}
|
|
if (!_configuration.General.ShowIncompleteSeasonalEvents)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
if (_questData.GetLockedClassQuests().Contains(questId))
|
|
{
|
|
return true;
|
|
}
|
|
byte startTown = PlayerState.Instance()->StartTown;
|
|
if (questInfo2.StartingCity > 0 && questInfo2.StartingCity != startTown)
|
|
{
|
|
return true;
|
|
}
|
|
if (questId.Value == 674 && startTown == 3)
|
|
{
|
|
return true;
|
|
}
|
|
if (questId.Value == 673 && startTown != 3)
|
|
{
|
|
return true;
|
|
}
|
|
Dictionary<ushort, EClassJob> dictionary = new Dictionary<ushort, EClassJob>
|
|
{
|
|
{
|
|
108,
|
|
EClassJob.Marauder
|
|
},
|
|
{
|
|
109,
|
|
EClassJob.Arcanist
|
|
},
|
|
{
|
|
85,
|
|
EClassJob.Lancer
|
|
},
|
|
{
|
|
123,
|
|
EClassJob.Archer
|
|
},
|
|
{
|
|
124,
|
|
EClassJob.Conjurer
|
|
},
|
|
{
|
|
568,
|
|
EClassJob.Gladiator
|
|
},
|
|
{
|
|
569,
|
|
EClassJob.Pugilist
|
|
},
|
|
{
|
|
570,
|
|
EClassJob.Thaumaturge
|
|
}
|
|
};
|
|
if (dictionary.TryGetValue(questId.Value, out var value) && dictionary.Any((KeyValuePair<ushort, EClassJob> x) => IsQuestAcceptedOrComplete(new QuestId(x.Key))) && (EClassJob)PlayerState.Instance()->FirstClass != value)
|
|
{
|
|
return true;
|
|
}
|
|
if (IsQuestRemoved(questId))
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private static bool IsQuestUnobtainable(UnlockLinkId unlockLinkId)
|
|
{
|
|
if (unlockLinkId.Value == 506)
|
|
{
|
|
return !IsFestivalActive(160, (ushort)2);
|
|
}
|
|
if (unlockLinkId.Value == 568)
|
|
{
|
|
return !IsFestivalActive(160, (ushort)3);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private unsafe static bool IsFestivalActive(ushort id, ushort? phase = null)
|
|
{
|
|
for (int i = 0; i < GameMain.Instance()->ActiveFestivals.Length; i++)
|
|
{
|
|
GameMain.Festival festival = GameMain.Instance()->ActiveFestivals[i];
|
|
if (festival.Id == id)
|
|
{
|
|
if (phase.HasValue)
|
|
{
|
|
return festival.Phase == phase;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public bool IsQuestRemoved(ElementId elementId)
|
|
{
|
|
if (elementId is QuestId questId)
|
|
{
|
|
return IsQuestRemoved(questId);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private bool IsQuestRemoved(QuestId questId)
|
|
{
|
|
ushort value = questId.Value;
|
|
if (value == 487 || (uint)(value - 1428) <= 1u)
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private bool HasCompletedPreviousQuests(IQuestInfo questInfo, ElementId? extraCompletedQuest)
|
|
{
|
|
if (questInfo.PreviousQuests.Count == 0)
|
|
{
|
|
return true;
|
|
}
|
|
int num = questInfo.PreviousQuests.Count((PreviousQuestInfo x) => HasEnoughProgressOnPreviousQuest(x) || x.QuestId.Equals(extraCompletedQuest));
|
|
if (questInfo.PreviousQuestJoin == EQuestJoin.All && questInfo.PreviousQuests.Count == num)
|
|
{
|
|
return true;
|
|
}
|
|
if (questInfo.PreviousQuestJoin == EQuestJoin.AtLeastOne && num > 0)
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private bool HasEnoughProgressOnPreviousQuest(PreviousQuestInfo previousQuestInfo)
|
|
{
|
|
if (IsQuestComplete(previousQuestInfo.QuestId))
|
|
{
|
|
return true;
|
|
}
|
|
if (previousQuestInfo.Sequence != 0 && IsQuestAccepted(previousQuestInfo.QuestId))
|
|
{
|
|
QuestProgressInfo questProgressInfo = GetQuestProgressInfo(previousQuestInfo.QuestId);
|
|
if (questProgressInfo != null)
|
|
{
|
|
return questProgressInfo.Sequence >= previousQuestInfo.Sequence;
|
|
}
|
|
return false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private static bool HasCompletedPreviousInstances(QuestInfo questInfo)
|
|
{
|
|
if (questInfo.PreviousInstanceContent.Count == 0)
|
|
{
|
|
return true;
|
|
}
|
|
int num = questInfo.PreviousInstanceContent.Count((ushort x) => UIState.IsInstanceContentCompleted(x));
|
|
if (questInfo.PreviousInstanceContentJoin == EQuestJoin.All && questInfo.PreviousInstanceContent.Count == num)
|
|
{
|
|
return true;
|
|
}
|
|
if (questInfo.PreviousInstanceContentJoin == EQuestJoin.AtLeastOne && num > 0)
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public unsafe bool IsClassJobUnlocked(EClassJob classJob)
|
|
{
|
|
ClassJob row = _dataManager.GetExcelSheet<ClassJob>().GetRow((uint)classJob);
|
|
ushort num = (ushort)row.UnlockQuest.RowId;
|
|
if (num != 0)
|
|
{
|
|
return IsQuestComplete(new QuestId(num));
|
|
}
|
|
PlayerState* ptr = PlayerState.Instance();
|
|
if (ptr != null)
|
|
{
|
|
return ptr->ClassJobLevels[row.ExpArrayIndex] > 0;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public bool IsJobUnlocked(EClassJob classJob)
|
|
{
|
|
return IsClassJobUnlocked((EClassJob)_dataManager.GetExcelSheet<ClassJob>().GetRow((uint)classJob).ClassJobParent.RowId);
|
|
}
|
|
|
|
public unsafe FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany GetGrandCompany()
|
|
{
|
|
return (FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany)PlayerState.Instance()->GrandCompany;
|
|
}
|
|
|
|
public bool IsMainScenarioQuestComplete()
|
|
{
|
|
return IsQuestComplete(_questData.LastMainScenarioQuestId);
|
|
}
|
|
}
|