step.CompletionQuestVariablesFlags);
}
- return [unmount, task];
+ return [unmount, new WaitAtEnd.WaitDelay(TimeSpan.FromSeconds(0.5)), task];
}
else if (step.DataId != null)
{
.Where(x => x.RowId > 0 && x.Quest.Row > 0)
.ToDictionary(x => x.Quest.Row, x => x.Redo);
+ Dictionary<uint, byte> startingCities = new();
+ for (byte redoChapter = 1; redoChapter <= 3; ++redoChapter)
+ {
+ var questRedo = dataManager.GetExcelSheet<QuestRedo>()!.GetRow(redoChapter)!;
+ foreach (var quest in questRedo.Quest.Where(x => x.Row > 0))
+ startingCities[quest.Row] = redoChapter;
+ }
+
List<IQuestInfo> quests =
[
..dataManager.GetExcelSheet<Quest>()!
.Where(x => x.RowId > 0)
.Where(x => x.IssuerLocation.Row > 0)
- .Select(x => new QuestInfo(x, questChapters.GetValueOrDefault(x.RowId))),
+ .Select(x => new QuestInfo(x, questChapters.GetValueOrDefault(x.RowId),
+ startingCities.GetValueOrDefault(x.RowId))),
..dataManager.GetExcelSheet<SatisfactionNpc>()!
.Where(x => x.RowId > 0)
.Select(x => new SatisfactionSupplyInfo(x)),
AddPreviousQuest(new QuestId(3821), new QuestId(spearfishing));
AddPreviousQuest(new QuestId(3833), new QuestId(spearfishing));
*/
+
+ // initial city quests are side quests
+ ((QuestInfo)_quests[new QuestId(107)]).StartingCity = 1;
+ ((QuestInfo)_quests[new QuestId(39)]).StartingCity = 2;
+ ((QuestInfo)_quests[new QuestId(594)]).StartingCity = 3;
+
+ // follow-up quests to picking a GC
+ AddGcFollowUpQuests();
}
private void AddPreviousQuest(QuestId questToUpdate, QuestId requiredQuestId)
quest.AddPreviousQuest(new QuestInfo.PreviousQuestInfo(requiredQuestId));
}
+ private void AddGcFollowUpQuests()
+ {
+ QuestId[] questIds = [new(683), new(684), new(685)];
+ foreach (QuestId questId in questIds)
+ {
+ QuestInfo quest = (QuestInfo)_quests[questId];
+ quest.AddQuestLocks(QuestInfo.QuestJoin.AtLeastOne, questIds.Where(x => x != questId).ToArray());
+ }
+ }
+
public IQuestInfo GetQuestInfo(ElementId elementId)
{
return _quests[elementId] ?? throw new ArgumentOutOfRangeException(nameof(elementId));
return !HasCompletedPreviousQuests(questInfo, extraCompletedQuest) || !HasCompletedPreviousInstances(questInfo);
}
+ public bool IsQuestUnobtainable(ElementId elementId, ElementId? extraCompletedQuest = null)
+ {
+ if (elementId is QuestId questId)
+ return IsQuestUnobtainable(questId, extraCompletedQuest);
+ else
+ return false;
+ }
+
public bool IsQuestUnobtainable(QuestId questId, ElementId? extraCompletedQuest = null)
{
var questInfo = (QuestInfo)_questData.GetQuestInfo(questId);
if (_questData.GetLockedClassQuests().Contains(questId))
return true;
+ unsafe
+ {
+ var startingCity = PlayerState.Instance()->StartTown;
+ if (questInfo.StartingCity > 0 && questInfo.StartingCity != startingCity)
+ return true;
+
+ if (questId.Value == 674 && startingCity == 3)
+ return true;
+ if (questId.Value == 673 && startingCity != 3)
+ return true;
+
+ Dictionary<ushort, EClassJob> closeToHomeQuests = new()
+ {
+ { 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 (closeToHomeQuests.TryGetValue(questId.Value, out EClassJob neededStartingClass))
+ {
+ EClassJob actualStartingClass = (EClassJob)PlayerState.Instance()->FirstClass;
+ if (actualStartingClass != neededStartingClass)
+ return true;
+ }
+ }
+
return false;
}
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
internal sealed class QuestInfo : IQuestInfo
{
- public QuestInfo(ExcelQuest quest, ushort newGamePlusChapter)
+ public QuestInfo(ExcelQuest quest, ushort newGamePlusChapter, byte startingCity)
{
QuestId = new QuestId((ushort)(quest.RowId & 0xFFFF));
ClassJobs = QuestInfoUtils.AsList(quest.ClassJobCategory0.Value!);
IsSeasonalEvent = quest.Festival.Row != 0;
NewGamePlusChapter = newGamePlusChapter;
+ StartingCity = startingCity;
Expansion = (EExpansionVersion)quest.Expansion.Row;
}
public ushort Level { get; }
public uint IssuerDataId { get; }
public bool IsRepeatable { get; }
- public ImmutableList<PreviousQuestInfo> PreviousQuests { get; set; }
+ public ImmutableList<PreviousQuestInfo> PreviousQuests { get; private set; }
public QuestJoin PreviousQuestJoin { get; }
- public ImmutableList<QuestId> QuestLocks { get; }
- public QuestJoin QuestLockJoin { get; }
+ public ImmutableList<QuestId> QuestLocks { get; private set; }
+ public QuestJoin QuestLockJoin { get; private set; }
public List<ushort> PreviousInstanceContent { get; }
public QuestJoin PreviousInstanceContentJoin { get; }
public uint? JournalGenre { get; }
public IReadOnlyList<EClassJob> ClassJobs { get; }
public bool IsSeasonalEvent { get; }
public ushort NewGamePlusChapter { get; }
+ public byte StartingCity { get; set; }
public EExpansionVersion Expansion { get; }
[UsedImplicitly(ImplicitUseKindFlags.Assign, ImplicitUseTargetFlags.Members)]
PreviousQuests = [..PreviousQuests, questId];
}
+ public void AddQuestLocks(QuestJoin questJoin, params QuestId[] questId)
+ {
+ if (QuestLocks.Count > 0 && QuestLockJoin != questJoin)
+ throw new InvalidOperationException();
+
+ QuestLockJoin = questJoin;
+ QuestLocks = [..QuestLocks, ..questId];
+ }
+
public sealed record PreviousQuestInfo(QuestId QuestId, byte Sequence = 0);
}
internal sealed class QuestJournalComponent
{
- private readonly Dictionary<JournalData.Genre, (int Available, int Completed)> _genreCounts = new();
- private readonly Dictionary<JournalData.Category, (int Available, int Completed)> _categoryCounts = new();
- private readonly Dictionary<JournalData.Section, (int Available, int Completed)> _sectionCounts = new();
+ private readonly Dictionary<JournalData.Genre, (int Available, int Obtainable, int Completed)> _genreCounts = [];
+
+ private readonly Dictionary<JournalData.Category, (int Available, int Obtainable, int Completed)> _categoryCounts =
+ [];
+
+ private readonly Dictionary<JournalData.Section, (int Available, int Obtainable, int Completed)> _sectionCounts =
+ [];
private readonly JournalData _journalData;
private readonly QuestRegistry _questRegistry;
if (filter.Section.QuestCount == 0)
return;
- (int supported, int completed) = _sectionCounts.GetValueOrDefault(filter.Section);
+ (int available, int obtainable, int completed) = _sectionCounts.GetValueOrDefault(filter.Section);
ImGui.TableNextRow();
ImGui.TableNextColumn();
bool open = ImGui.TreeNodeEx(filter.Section.Name, ImGuiTreeNodeFlags.SpanFullWidth);
ImGui.TableNextColumn();
- DrawCount(supported, filter.Section.QuestCount);
+ DrawCount(available, filter.Section.QuestCount);
ImGui.TableNextColumn();
- DrawCount(completed, filter.Section.QuestCount);
+ DrawCount(completed, obtainable);
if (open)
{
if (filter.Category.QuestCount == 0)
return;
- (int supported, int completed) = _categoryCounts.GetValueOrDefault(filter.Category);
+ (int available, int obtainable, int completed) = _categoryCounts.GetValueOrDefault(filter.Category);
ImGui.TableNextRow();
ImGui.TableNextColumn();
bool open = ImGui.TreeNodeEx(filter.Category.Name, ImGuiTreeNodeFlags.SpanFullWidth);
ImGui.TableNextColumn();
- DrawCount(supported, filter.Category.QuestCount);
+ DrawCount(available, filter.Category.QuestCount);
ImGui.TableNextColumn();
- DrawCount(completed, filter.Category.QuestCount);
+ DrawCount(completed, obtainable);
if (open)
{
if (filter.Genre.QuestCount == 0)
return;
- (int supported, int completed) = _genreCounts.GetValueOrDefault(filter.Genre);
+ (int supported, int obtainable, int completed) = _genreCounts.GetValueOrDefault(filter.Genre);
ImGui.TableNextRow();
ImGui.TableNextColumn();
ImGui.TableNextColumn();
DrawCount(supported, filter.Genre.QuestCount);
ImGui.TableNextColumn();
- DrawCount(completed, filter.Genre.QuestCount);
+ DrawCount(completed, obtainable);
if (open)
{
bool openInQuestMap = _commandManager.Commands.TryGetValue("/questinfo", out var commandInfo);
if (ImGui.MenuItem("View in Quest Map", questInfo.QuestId is QuestId && openInQuestMap))
{
- _commandManager.DispatchCommand("/questinfo", questInfo.QuestId.ToString() ?? string.Empty, commandInfo!);
+ _commandManager.DispatchCommand("/questinfo", questInfo.QuestId.ToString() ?? string.Empty,
+ commandInfo!);
}
ImGui.EndPopup();
{
int available = genre.Quests.Count(x =>
_questRegistry.TryGetQuest(x.QuestId, out var quest) && !quest.Root.Disabled);
+ int obtainable = genre.Quests.Count(x => !_questFunctions.IsQuestUnobtainable(x.QuestId));
int completed = genre.Quests.Count(x => _questFunctions.IsQuestComplete(x.QuestId));
- _genreCounts[genre] = (available, completed);
+ _genreCounts[genre] = (available, obtainable, completed);
}
foreach (var category in _journalData.Categories)
.Select(x => x.Value)
.ToList();
int available = counts.Sum(x => x.Available);
+ int obtainable = counts.Sum(x => x.Obtainable);
int completed = counts.Sum(x => x.Completed);
- _categoryCounts[category] = (available, completed);
+ _categoryCounts[category] = (available, obtainable, completed);
}
foreach (var section in _journalData.Sections)
.Select(x => x.Value)
.ToList();
int available = counts.Sum(x => x.Available);
+ int obtainable = counts.Sum(x => x.Obtainable);
int completed = counts.Sum(x => x.Completed);
- _sectionCounts[section] = (available, completed);
+ _sectionCounts[section] = (available, obtainable, completed);
}
}
internal void ClearCounts()
{
foreach (var genreCount in _genreCounts.ToList())
- _genreCounts[genreCount.Key] = (genreCount.Value.Available, 0);
+ _genreCounts[genreCount.Key] = (genreCount.Value.Available, genreCount.Value.Available, 0);
foreach (var categoryCount in _categoryCounts.ToList())
- _categoryCounts[categoryCount.Key] = (categoryCount.Value.Available, 0);
+ _categoryCounts[categoryCount.Key] = (categoryCount.Value.Available, categoryCount.Value.Available, 0);
foreach (var sectionCount in _sectionCounts.ToList())
- _sectionCounts[sectionCount.Key] = (sectionCount.Value.Available, 0);
+ _sectionCounts[sectionCount.Key] = (sectionCount.Value.Available, sectionCount.Value.Available, 0);
}
private sealed record FilteredSection(JournalData.Section Section, List<FilteredCategory> Categories);
return (ImGuiColors.DalamudYellow, FontAwesomeIcon.PersonWalkingArrowRight, "Active");
else if (_questFunctions.IsQuestAcceptedOrComplete(elementId))
return (ImGuiColors.ParsedGreen, FontAwesomeIcon.Check, "Complete");
+ else if (_questFunctions.IsQuestUnobtainable(elementId))
+ return (ImGuiColors.DalamudGrey, FontAwesomeIcon.Minus, "Unobtainable");
else if (_questFunctions.IsQuestLocked(elementId))
return (ImGuiColors.DalamudRed, FontAwesomeIcon.Times, "Locked");
else