"InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": false,
- "TestedBossModVersion": "0.0.0.292",
+ "TestedBossModVersion": "0.1.2.4",
"Notes": [
- "WIP: Needs to be re-tested",
"AI doesn't move after starting the instance, so enemies won't be triggered",
"(First Barrier) If the player is too far south, after being stunned by Vishap's roar, AI doesn't move out of the AOE and dies to the Cauterize"
]
<s:Boolean x:Key="/Default/UserDictionary/Words/=orchestrion/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=ostall/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=palaka_0027s/@EntryIndexedValue">True</s:Boolean>
+ <s:Boolean x:Key="/Default/UserDictionary/Words/=primals/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=rostra/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=shaaloani/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=sheshenewezi/@EntryIndexedValue">True</s:Boolean>
public bool NeverFly { get; set; }
public bool AdditionalStatusInformation { get; set; }
public bool DisableAutoDutyBareMode { get; set; }
+ public bool SkipAetherCurrents { get; set; }
+ public bool SkipClassJobQuests { get; set; }
+ public bool SkipARealmRebornHardModePrimals { get; set; }
+ public bool SkipCrystalTowerRaids { get; set; }
}
internal enum ECombatModule
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;
return false;
// "ifrit bleeds, we can kill it" isn't listed as priority quest, as we accept it during the MSQ 'Moving On'
- if (currentQuest.Quest.Id is QuestId { Value: 1048 })
+ // the rest are priority quests, but that's fine here
+ if (QuestData.HardModePrimals.Contains(currentQuest.Quest.Id))
return false;
if (currentQuest.Quest.Info.AlliedSociety != EAlliedSociety.None)
internal sealed class CheckSkip(
ILogger<CheckSkip> logger,
+ Configuration configuration,
AetheryteFunctions aetheryteFunctions,
GameFunctions gameFunctions,
QuestFunctions questFunctions,
if (CheckAetheryteCondition(step, skipConditions))
return true;
- if (CheckAethernetCondition(step))
+ if (CheckAetherCurrentCondition(step))
return true;
if (CheckQuestWorkConditions(elementId, step))
return false;
}
- private bool CheckAethernetCondition(QuestStep step)
+ private bool CheckAetherCurrentCondition(QuestStep step)
{
if (step is { DataId: not null, InteractionType: EInteractionType.AttuneAetherCurrent } &&
gameFunctions.IsAetherCurrentUnlocked(step.DataId.Value))
return true;
}
+ if (step is { InteractionType: EInteractionType.AttuneAetherCurrent } &&
+ configuration.Advanced.SkipAetherCurrents)
+ {
+ logger.LogInformation("Skipping step, as aether currents should be skipped");
+ return true;
+ }
+
return false;
}
return true;
}
+ if (step.PickUpQuestId != null &&
+ configuration.Advanced.SkipAetherCurrents &&
+ QuestData.AetherCurrentQuests.Contains(step.PickUpQuestId))
+ {
+ logger.LogInformation("Skipping step, as aether current quests should be skipped");
+ return true;
+ }
+
+ if (step.PickUpQuestId != null &&
+ configuration.Advanced.SkipARealmRebornHardModePrimals &&
+ QuestData.HardModePrimals.Contains(step.PickUpQuestId))
+ {
+ logger.LogInformation("Skipping step, as hard mode primal quests should be skipped");
+ return true;
+ }
+
return false;
}
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
+using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Dalamud.Plugin.Services;
internal sealed class QuestData
{
+ public static readonly IReadOnlyList<QuestId> HardModePrimals = [new(1048), new(1157), new(1158)];
+
public static readonly IReadOnlyList<QuestId> CrystalTowerQuests =
[new(1709), new(1200), new(1201), new(1202), new(1203), new(1474), new(494), new(495)];
- public static readonly IReadOnlyList<uint> TankRoleQuests = [136, 154, 178];
- public static readonly IReadOnlyList<uint> HealerRoleQuests = [137, 155, 179];
- public static readonly IReadOnlyList<uint> MeleeRoleQuests = [138, 156, 180];
- public static readonly IReadOnlyList<uint> PhysicalRangedRoleQuests = [138, 157, 181];
- public static readonly IReadOnlyList<uint> CasterRoleQuests = [139, 158, 182];
+ public static readonly ImmutableDictionary<uint, ImmutableList<QuestId>> AetherCurrentQuestsByTerritory =
+ new Dictionary<uint, List<ushort>>
+ {
+ // Heavensward
+ { 397, [1744, 1759, 1760, 2111] },
+ { 398, [1771, 1790, 1797, 1802] },
+ { 399, [1936, 1945, 1963, 1966] },
+ { 400, [1819, 1823, 1828, 1835] },
+ { 401, [1748, 1874, 1909, 1910] },
+
+ // Stormblood
+ { 612, [2639, 2661, 2816, 2821] },
+ { 613, [2632, 2673, 2687, 2693] },
+ { 614, [2724, 2728, 2730, 2733] },
+ { 620, [2655, 2842, 2851, 2860] },
+ { 621, [2877, 2880, 2881, 2883] },
+ { 622, [2760, 2771, 2782, 2791] },
+
+ // Shadowbringers
+ { 813, [3380, 3384, 3385, 3386] },
+ { 814, [3360, 3371, 3537, 3556] },
+ { 815, [3375, 3503, 3511, 3525] },
+ { 816, [3395, 3398, 3404, 3427] },
+ { 817, [3444, 3467, 3478, 3656] },
+ { 818, [3588, 3592, 3593, 3594] },
+
+ // Endwalker
+ { 956, [4320, 4329, 4480, 4484] },
+ { 957, [4203, 4257, 4259, 4489] },
+ { 958, [4216, 4232, 4498, 4502] },
+ { 959, [4240, 4241, 4253, 4516] },
+ { 960, [4342, 4346, 4354, 4355] },
+ { 961, [4288, 4313, 4507, 4511] },
+
+ // Dawntrail
+ {1187, [5039, 5047, 5051, 5055]},
+ {1188, [5064, 5074, 5081, 5085]},
+ {1189, [5094, 5103, 5110, 5114]},
+ {1190, [5130, 5138, 5140, 5144]},
+ {1191, [5153, 5156, 5159, 5160]},
+ {1192, [5174, 5176, 5178, 5179]},
+ }
+ .ToImmutableDictionary(x => x.Key, x => x.Value.Select(y => new QuestId(y)).ToImmutableList());
+
+ public static ImmutableHashSet<QuestId> AetherCurrentQuests { get; } =
+ AetherCurrentQuestsByTerritory.Values.SelectMany(x => x).ToImmutableHashSet();
+
+ private static readonly IReadOnlyList<uint> TankRoleQuestChapters = [136, 154, 178];
+ private static readonly IReadOnlyList<uint> HealerRoleQuestChapters = [137, 155, 179];
+ private static readonly IReadOnlyList<uint> MeleeRoleQuestChapters = [138, 156, 180];
+ private static readonly IReadOnlyList<uint> PhysicalRangedRoleQuestChapters = [138, 157, 181];
+ private static readonly IReadOnlyList<uint> CasterRoleQuestChapters = [139, 158, 182];
public static readonly IReadOnlyList<IReadOnlyList<uint>> AllRoleQuestChapters =
[
- TankRoleQuests,
- HealerRoleQuests,
- MeleeRoleQuests,
- PhysicalRangedRoleQuests,
- CasterRoleQuests
+ TankRoleQuestChapters,
+ HealerRoleQuestChapters,
+ MeleeRoleQuestChapters,
+ PhysicalRangedRoleQuestChapters,
+ CasterRoleQuestChapters
];
public static readonly IReadOnlyList<QuestId> FinalShadowbringersRoleQuests =
{
return classJob switch
{
- _ when classJob.IsTank() => TankRoleQuests,
- _ when classJob.IsHealer() => HealerRoleQuests,
- _ when classJob.IsMelee() => MeleeRoleQuests,
- _ when classJob.IsPhysicalRanged() => PhysicalRangedRoleQuests,
- _ when classJob.IsCaster() && classJob != EClassJob.BlueMage => CasterRoleQuests,
+ _ when classJob.IsTank() => TankRoleQuestChapters,
+ _ when classJob.IsHealer() => HealerRoleQuestChapters,
+ _ when classJob.IsMelee() => MeleeRoleQuestChapters,
+ _ when classJob.IsPhysicalRanged() => PhysicalRangedRoleQuestChapters,
+ _ when classJob.IsCaster() && classJob != EClassJob.BlueMage => CasterRoleQuestChapters,
_ => []
};
}
List<ElementId> priorityQuests = [];
if (!onlyClassAndRoleQuests)
{
- priorityQuests.Add(new QuestId(1157)); // Garuda (Hard)
- priorityQuests.Add(new QuestId(1158)); // Titan (Hard)
- priorityQuests.AddRange(QuestData.CrystalTowerQuests);
+ if (!_configuration.Advanced.SkipARealmRebornHardModePrimals)
+ {
+ // Ifrit quest is handled as a pickup during MSQ
+ priorityQuests.AddRange(QuestData.HardModePrimals.Skip(1));
+ }
+
+ if (!_configuration.Advanced.SkipCrystalTowerRaids)
+ {
+ priorityQuests.AddRange(QuestData.CrystalTowerQuests);
+ }
}
- EClassJob classJob = (EClassJob?)_clientState.LocalPlayer?.ClassJob.RowId ?? EClassJob.Adventurer;
- uint[] shadowbringersRoleQuestChapters = QuestData.AllRoleQuestChapters.Select(x => x[0]).ToArray();
- if (classJob != EClassJob.Adventurer)
+ if (!_configuration.Advanced.SkipClassJobQuests)
{
- priorityQuests.AddRange(_questRegistry.GetKnownClassJobQuests(classJob)
- .Where(x =>
- {
- if (!_questRegistry.TryGetQuest(x.QuestId, out Quest? quest) ||
- quest.Info is not QuestInfo questInfo)
- return false;
+ EClassJob classJob = (EClassJob?)_clientState.LocalPlayer?.ClassJob.RowId ?? EClassJob.Adventurer;
+ uint[] shadowbringersRoleQuestChapters = QuestData.AllRoleQuestChapters.Select(x => x[0]).ToArray();
+ if (classJob != EClassJob.Adventurer)
+ {
+ priorityQuests.AddRange(_questRegistry.GetKnownClassJobQuests(classJob)
+ .Where(x =>
+ {
+ if (!_questRegistry.TryGetQuest(x.QuestId, out Quest? quest) ||
+ quest.Info is not QuestInfo questInfo)
+ return false;
- // if no shadowbringers role quest is complete, (at least one) is required
- if (shadowbringersRoleQuestChapters.Contains(questInfo.NewGamePlusChapter))
- return !QuestData.FinalShadowbringersRoleQuests.Any(IsQuestComplete);
+ // if no shadowbringers role quest is complete, (at least one) is required
+ if (shadowbringersRoleQuestChapters.Contains(questInfo.NewGamePlusChapter))
+ return !QuestData.FinalShadowbringersRoleQuests.Any(IsQuestComplete);
- // ignore all other role quests
- if (QuestData.AllRoleQuestChapters.Any(y => y.Contains(questInfo.NewGamePlusChapter)))
- return false;
+ // ignore all other role quests
+ if (QuestData.AllRoleQuestChapters.Any(y => y.Contains(questInfo.NewGamePlusChapter)))
+ return false;
- // even job quests for the later expacs (after role quests were introduced) might have skills locked
- // behind them, e.g. reaper and sage
+ // even job quests for the later expacs (after role quests were introduced) might have skills locked
+ // behind them, e.g. reaper and sage
- return true;
- })
- .Select(x => x.QuestId));
+ return true;
+ })
+ .Select(x => x.QuestId));
+ }
}
return priorityQuests
"Typically, the loop settings for AutoDuty are disabled when running dungeons with Questionable, since they can cause issues (or even shut down your PC).");
}
- ImGui.EndTabItem();
+ ImGui.Separator();
+ ImGui.Text("Quest/Interaction Skips");
+ using (_ = ImRaii.PushIndent())
+ {
+ bool skipAetherCurrents = Configuration.Advanced.SkipAetherCurrents;
+ if (ImGui.Checkbox("Don't pick up aether currents/aether current quests", ref skipAetherCurrents))
+ {
+ Configuration.Advanced.SkipAetherCurrents = skipAetherCurrents;
+ Save();
+ }
+
+ ImGui.SameLine();
+ ImGuiComponents.HelpMarker("If not done during the MSQ by Questionable, you have to manually pick up any missed aether currents/quests. There is no way to automatically pick up all missing aether currents.");
+
+ bool skipClassJobQuests = Configuration.Advanced.SkipClassJobQuests;
+ if (ImGui.Checkbox("Don't pick up class/job/role quests", ref skipClassJobQuests))
+ {
+ Configuration.Advanced.SkipClassJobQuests = skipClassJobQuests;
+ Save();
+ }
+
+ ImGui.SameLine();
+ ImGuiComponents.HelpMarker("Class and job skills for A Realm Reborn, Heavensward and (for the Lv70 skills) Stormblood are locked behind quests. Not recommended if you plan on queueing for instances with duty finder/party finder.");
+
+ bool skipARealmRebornHardModePrimals = Configuration.Advanced.SkipARealmRebornHardModePrimals;
+ if (ImGui.Checkbox("Don't pick up ARR hard mode primal quests", ref skipARealmRebornHardModePrimals))
+ {
+ Configuration.Advanced.SkipARealmRebornHardModePrimals = skipARealmRebornHardModePrimals;
+ Save();
+ }
+
+ ImGui.SameLine();
+ ImGuiComponents.HelpMarker("Hard mode Ifrit/Garuda/Titan are required for the Patch 2.5 quest 'Good Intentions' and to start Heavensward.");
+
+ bool skipCrystalTowerRaids = Configuration.Advanced.SkipCrystalTowerRaids;
+ if (ImGui.Checkbox("Don't pick up Crystal Tower quests", ref skipCrystalTowerRaids))
+ {
+ Configuration.Advanced.SkipCrystalTowerRaids = skipCrystalTowerRaids;
+ Save();
+ }
+
+ ImGui.SameLine();
+ ImGuiComponents.HelpMarker("Crystal Tower raids are required for the Patch 2.55 quest 'A Time to Every Purpose' and to start Heavensward.");
+ }
}
}
using System.Linq;
using Dalamud.Interface;
+using Dalamud.Interface.Colors;
+using Dalamud.Interface.Style;
using Dalamud.Interface.Utility.Raii;
using FFXIVClientStructs.FFXIV.Client.Game.UI;
using FFXIVClientStructs.FFXIV.Common.Math;
private readonly QuestData _questData;
private readonly TerritoryData _territoryData;
private readonly UiUtils _uiUtils;
+ private readonly Configuration _configuration;
public ARealmRebornComponent(QuestFunctions questFunctions, QuestData questData, TerritoryData territoryData,
- UiUtils uiUtils)
+ UiUtils uiUtils, Configuration configuration)
{
_questFunctions = questFunctions;
_questData = questData;
_territoryData = territoryData;
_uiUtils = uiUtils;
+ _configuration = configuration;
}
public bool ShouldDraw => !_questFunctions.IsQuestAcceptedOrComplete(ATimeForEveryPurpose) &&
private void DrawPrimals()
{
bool complete = UIState.IsInstanceContentCompleted(RequiredPrimalInstances.Last());
- bool hover = _uiUtils.ChecklistItem("Hard Mode Primals", complete);
+ bool hover = _uiUtils.ChecklistItem("Hard Mode Primals", complete,
+ _configuration.Advanced.SkipARealmRebornHardModePrimals ? ImGuiColors.DalamudGrey : null);
if (complete || !hover)
return;
private void DrawAllianceRaids()
{
bool complete = _questFunctions.IsQuestComplete(QuestData.CrystalTowerQuests[^1]);
- bool hover = _uiUtils.ChecklistItem("Crystal Tower Raids", complete);
+ bool hover = _uiUtils.ChecklistItem("Crystal Tower Raids", complete,
+ _configuration.Advanced.SkipCrystalTowerRaids ? ImGuiColors.DalamudGrey : null);
if (complete || !hover)
return;
return hover;
}
- public bool ChecklistItem(string text, bool complete)
+ public bool ChecklistItem(string text, bool complete, Vector4? colorOverride = null)
{
return ChecklistItem(text,
- complete ? ImGuiColors.ParsedGreen : ImGuiColors.DalamudRed,
+ colorOverride ?? (complete ? ImGuiColors.ParsedGreen : ImGuiColors.DalamudRed),
complete ? FontAwesomeIcon.Check : FontAwesomeIcon.Times);
}
}