forked from liza/Questionable
Add UI to enable/disable quest battles
This commit is contained in:
parent
3820647827
commit
a75286e927
2
LLib
2
LLib
@ -1 +1 @@
|
||||
Subproject commit 746d14681baa91132784ab17f8f49671e86ea211
|
||||
Subproject commit edab3c7ecc6bd66ac07e3c3938eb9c8a835a1c42
|
@ -34,7 +34,7 @@
|
||||
"Z": -509.51404
|
||||
},
|
||||
"TerritoryId": 622,
|
||||
"InteractionType": "Interact",
|
||||
"InteractionType": "SinglePlayerDuty",
|
||||
"Fly": true
|
||||
}
|
||||
]
|
||||
|
@ -35,7 +35,7 @@
|
||||
"Z": 686.427
|
||||
},
|
||||
"TerritoryId": 135,
|
||||
"InteractionType": "Interact",
|
||||
"InteractionType": "SinglePlayerDuty",
|
||||
"AetheryteShortcut": "Lower La Noscea - Moraby Drydocks"
|
||||
}
|
||||
]
|
||||
|
@ -14,7 +14,7 @@ internal sealed class Configuration : IPluginConfiguration
|
||||
public int PluginSetupCompleteVersion { get; set; }
|
||||
public GeneralConfiguration General { get; } = new();
|
||||
public DutyConfiguration Duties { get; } = new();
|
||||
public SoloDutyConfiguration SoloDuties { get; } = new();
|
||||
public SinglePlayerDutyConfiguration SinglePlayerDuties { get; } = new();
|
||||
public NotificationConfiguration Notifications { get; } = new();
|
||||
public AdvancedConfiguration Advanced { get; } = new();
|
||||
public WindowConfig DebugWindowConfig { get; } = new();
|
||||
@ -42,11 +42,11 @@ internal sealed class Configuration : IPluginConfiguration
|
||||
public HashSet<uint> BlacklistedDutyCfcIds { get; set; } = [];
|
||||
}
|
||||
|
||||
internal sealed class SoloDutyConfiguration
|
||||
internal sealed class SinglePlayerDutyConfiguration
|
||||
{
|
||||
public bool RunSoloInstancesWithBossMod { get; set; }
|
||||
public HashSet<uint> WhitelistedSoloDutyCfcIds { get; set; } = [];
|
||||
public HashSet<uint> BlacklistedSoloDutyCfcIds { get; set; } = [];
|
||||
public HashSet<uint> WhitelistedSinglePlayerDutyCfcIds { get; set; } = [];
|
||||
public HashSet<uint> BlacklistedSinglePlayerDutyCfcIds { get; set; } = [];
|
||||
}
|
||||
|
||||
internal sealed class NotificationConfiguration
|
||||
|
@ -15,6 +15,7 @@ using Questionable.External;
|
||||
using Questionable.Functions;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Questing;
|
||||
using Questionable.Windows.ConfigComponents;
|
||||
using Quest = Questionable.Model.Quest;
|
||||
|
||||
namespace Questionable.Controller;
|
||||
@ -35,6 +36,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||
private readonly Configuration _configuration;
|
||||
private readonly YesAlreadyIpc _yesAlreadyIpc;
|
||||
private readonly TaskCreator _taskCreator;
|
||||
private readonly SinglePlayerDutyConfigComponent _singlePlayerDutyConfigComponent;
|
||||
private readonly ILogger<QuestController> _logger;
|
||||
|
||||
private readonly object _progressLock = new();
|
||||
@ -76,7 +78,8 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||
TaskCreator taskCreator,
|
||||
IServiceProvider serviceProvider,
|
||||
InterruptHandler interruptHandler,
|
||||
IDataManager dataManager)
|
||||
IDataManager dataManager,
|
||||
SinglePlayerDutyConfigComponent singlePlayerDutyConfigComponent)
|
||||
: base(chatGui, condition, serviceProvider, interruptHandler, dataManager, logger)
|
||||
{
|
||||
_clientState = clientState;
|
||||
@ -93,6 +96,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||
_configuration = configuration;
|
||||
_yesAlreadyIpc = yesAlreadyIpc;
|
||||
_taskCreator = taskCreator;
|
||||
_singlePlayerDutyConfigComponent = singlePlayerDutyConfigComponent;
|
||||
_logger = logger;
|
||||
|
||||
_condition.ConditionChange += OnConditionChange;
|
||||
@ -169,6 +173,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
|
||||
DebugState = null;
|
||||
|
||||
_questRegistry.Reload();
|
||||
_singlePlayerDutyConfigComponent.Reload();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -240,11 +240,11 @@ internal sealed class QuestRegistry
|
||||
public bool TryGetQuest(ElementId questId, [NotNullWhen(true)] out Quest? quest)
|
||||
=> _quests.TryGetValue(questId, out quest);
|
||||
|
||||
public List<QuestInfo> GetKnownClassJobQuests(EClassJob classJob)
|
||||
public List<QuestInfo> GetKnownClassJobQuests(EClassJob classJob, bool includeRoleQuests = true)
|
||||
{
|
||||
List<QuestInfo> allQuests = [.._questData.GetClassJobQuests(classJob)];
|
||||
List<QuestInfo> allQuests = [.._questData.GetClassJobQuests(classJob, includeRoleQuests)];
|
||||
if (classJob.AsJob() != classJob)
|
||||
allQuests.AddRange(_questData.GetClassJobQuests(classJob.AsJob()));
|
||||
allQuests.AddRange(_questData.GetClassJobQuests(classJob.AsJob(), includeRoleQuests));
|
||||
|
||||
return allQuests
|
||||
.Where(x => IsKnownQuest(x.QuestId))
|
||||
|
@ -247,8 +247,8 @@ internal sealed class QuestData
|
||||
|
||||
private void AddPreviousQuest(QuestId questToUpdate, QuestId requiredQuestId)
|
||||
{
|
||||
QuestInfo quest = (QuestInfo)_quests[questToUpdate];
|
||||
quest.AddPreviousQuest(new PreviousQuestInfo(requiredQuestId));
|
||||
if (_quests.TryGetValue(questToUpdate, out IQuestInfo? quest) && quest is QuestInfo questInfo)
|
||||
questInfo.AddPreviousQuest(new PreviousQuestInfo(requiredQuestId));
|
||||
}
|
||||
|
||||
private void AddGcFollowUpQuests()
|
||||
@ -300,7 +300,7 @@ internal sealed class QuestData
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public List<QuestInfo> GetClassJobQuests(EClassJob classJob)
|
||||
public List<QuestInfo> GetClassJobQuests(EClassJob classJob, bool includeRoleQuests = false)
|
||||
{
|
||||
List<uint> chapterIds = classJob switch
|
||||
{
|
||||
@ -367,7 +367,20 @@ internal sealed class QuestData
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(classJob)),
|
||||
};
|
||||
|
||||
chapterIds.AddRange(classJob switch
|
||||
if (includeRoleQuests)
|
||||
{
|
||||
chapterIds.AddRange(GetRoleQuestIds(classJob));
|
||||
}
|
||||
|
||||
return GetQuestsInNewGamePlusChapters(chapterIds);
|
||||
}
|
||||
|
||||
public List<QuestInfo> GetRoleQuests(EClassJob referenceClassJob) =>
|
||||
GetQuestsInNewGamePlusChapters(GetRoleQuestIds(referenceClassJob).ToList());
|
||||
|
||||
private static IEnumerable<uint> GetRoleQuestIds(EClassJob classJob)
|
||||
{
|
||||
return classJob switch
|
||||
{
|
||||
_ when classJob.IsTank() => TankRoleQuests,
|
||||
_ when classJob.IsHealer() => HealerRoleQuests,
|
||||
@ -375,9 +388,7 @@ internal sealed class QuestData
|
||||
_ when classJob.IsPhysicalRanged() => PhysicalRangedRoleQuests,
|
||||
_ when classJob.IsCaster() && classJob != EClassJob.BlueMage => CasterRoleQuests,
|
||||
_ => []
|
||||
});
|
||||
|
||||
return GetQuestsInNewGamePlusChapters(chapterIds);
|
||||
};
|
||||
}
|
||||
|
||||
private List<QuestInfo> GetQuestsInNewGamePlusChapters(List<uint> chapterIds)
|
||||
|
@ -99,6 +99,11 @@ internal sealed class TerritoryData
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<(ElementId QuestId, byte Index, ContentFinderConditionData Data)> GetAllQuestsWithQuestBattles()
|
||||
{
|
||||
return _questsToCfc.Select(x => (x.Key.QuestId, x.Key.Index, _contentFinderConditions[x.Value]));
|
||||
}
|
||||
|
||||
private static string FixName(string name, ClientLanguage language)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name) || language != ClientLanguage.English)
|
||||
|
6
Questionable/External/BossModIpc.cs
vendored
6
Questionable/External/BossModIpc.cs
vendored
@ -84,16 +84,16 @@ internal sealed class BossModIpc
|
||||
|
||||
public bool IsConfiguredToRunSoloInstance(ElementId questId, byte dutyIndex, bool enabledByDefault)
|
||||
{
|
||||
if (!_configuration.SoloDuties.RunSoloInstancesWithBossMod)
|
||||
if (!_configuration.SinglePlayerDuties.RunSoloInstancesWithBossMod)
|
||||
return false;
|
||||
|
||||
if (!_territoryData.TryGetContentFinderConditionForSoloInstance(questId, dutyIndex, out var cfcData))
|
||||
return false;
|
||||
|
||||
if (_configuration.SoloDuties.BlacklistedSoloDutyCfcIds.Contains(cfcData.ContentFinderConditionId))
|
||||
if (_configuration.SinglePlayerDuties.BlacklistedSinglePlayerDutyCfcIds.Contains(cfcData.ContentFinderConditionId))
|
||||
return false;
|
||||
|
||||
if (_configuration.SoloDuties.WhitelistedSoloDutyCfcIds.Contains(cfcData.ContentFinderConditionId))
|
||||
if (_configuration.SinglePlayerDuties.WhitelistedSinglePlayerDutyCfcIds.Contains(cfcData.ContentFinderConditionId))
|
||||
return true;
|
||||
|
||||
return enabledByDefault;
|
||||
|
@ -302,6 +302,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
|
||||
|
||||
serviceCollection.AddSingleton<GeneralConfigComponent>();
|
||||
serviceCollection.AddSingleton<DutyConfigComponent>();
|
||||
serviceCollection.AddSingleton<SinglePlayerDutyConfigComponent>();
|
||||
serviceCollection.AddSingleton<NotificationConfigComponent>();
|
||||
serviceCollection.AddSingleton<DebugConfigComponent>();
|
||||
}
|
||||
@ -317,6 +318,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
|
||||
serviceCollection.AddSingleton<IQuestValidator, AethernetShortcutValidator>();
|
||||
serviceCollection.AddSingleton<IQuestValidator, DialogueChoiceValidator>();
|
||||
serviceCollection.AddSingleton<IQuestValidator, ClassQuestShouldHaveShortcutValidator>();
|
||||
serviceCollection.AddSingleton<IQuestValidator, SinglePlayerInstanceValidator>();
|
||||
serviceCollection.AddSingleton<IQuestValidator, UniqueSinglePlayerInstanceValidator>();
|
||||
serviceCollection.AddSingleton<JsonSchemaValidator>();
|
||||
serviceCollection.AddSingleton<IQuestValidator>(sp => sp.GetRequiredService<JsonSchemaValidator>());
|
||||
@ -326,6 +328,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
|
||||
{
|
||||
serviceProvider.GetRequiredService<QuestRegistry>().Reload();
|
||||
serviceProvider.GetRequiredService<GatheringPointRegistry>().Reload();
|
||||
serviceProvider.GetRequiredService<SinglePlayerDutyConfigComponent>().Reload();
|
||||
serviceProvider.GetRequiredService<CommandHandler>();
|
||||
serviceProvider.GetRequiredService<ContextMenuController>();
|
||||
serviceProvider.GetRequiredService<CraftworksSupplyController>();
|
||||
|
@ -19,4 +19,5 @@ public enum EIssueType
|
||||
InvalidExcelRef,
|
||||
ClassQuestWithoutAetheryteShortcut,
|
||||
DuplicateSinglePlayerInstance,
|
||||
UnusedSinglePlayerInstance,
|
||||
}
|
||||
|
@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Questionable.Data;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
namespace Questionable.Validation.Validators;
|
||||
|
||||
internal sealed class SinglePlayerInstanceValidator : IQuestValidator
|
||||
{
|
||||
private readonly Dictionary<ElementId, List<byte>> _questIdToDutyIndexes;
|
||||
|
||||
public SinglePlayerInstanceValidator(TerritoryData territoryData)
|
||||
{
|
||||
_questIdToDutyIndexes = territoryData.GetAllQuestsWithQuestBattles()
|
||||
.GroupBy(x => x.QuestId)
|
||||
.ToDictionary(x => x.Key, x => x.Select(y => y.Index).ToList());
|
||||
}
|
||||
|
||||
public IEnumerable<ValidationIssue> Validate(Quest quest)
|
||||
{
|
||||
if (_questIdToDutyIndexes.TryGetValue(quest.Id, out var indexes))
|
||||
{
|
||||
foreach (var index in indexes)
|
||||
{
|
||||
if (quest.AllSteps().Any(x =>
|
||||
x.Step.InteractionType == EInteractionType.SinglePlayerDuty &&
|
||||
x.Step.SinglePlayerDutyIndex == index))
|
||||
continue;
|
||||
|
||||
yield return new ValidationIssue
|
||||
{
|
||||
ElementId = quest.Id,
|
||||
Sequence = null,
|
||||
Step = null,
|
||||
Type = EIssueType.UnusedSinglePlayerInstance,
|
||||
Severity = EIssueSeverity.Error,
|
||||
Description = $"Single player instance {index} not used",
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -39,12 +39,12 @@ internal abstract class ConfigComponent
|
||||
|
||||
protected void Save() => _pluginInterface.SavePluginConfig(Configuration);
|
||||
|
||||
protected static string FormatLevel(int level)
|
||||
protected static string FormatLevel(int level, bool includePrefix = true)
|
||||
{
|
||||
if (level == 0)
|
||||
return string.Empty;
|
||||
|
||||
return $"{FormatLevel(level / 10)}{(SeIconChar.Number0 + level % 10).ToIconChar()}";
|
||||
return $"{(includePrefix ? SeIconChar.LevelEn.ToIconString() : string.Empty)}{FormatLevel(level / 10, false)}{(SeIconChar.Number0 + level % 10).ToIconChar()}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -14,7 +14,7 @@ internal sealed class DebugConfigComponent : ConfigComponent
|
||||
|
||||
public override void DrawTab()
|
||||
{
|
||||
using var tab = ImRaii.TabItem("Advanced");
|
||||
using var tab = ImRaii.TabItem("Advanced###Debug");
|
||||
if (!tab)
|
||||
return;
|
||||
|
||||
|
@ -60,14 +60,13 @@ internal sealed class DutyConfigComponent : ConfigComponent
|
||||
.GroupBy(x => x.Expansion)
|
||||
.ToDictionary(x => x.Key,
|
||||
x => x
|
||||
.Select(y => new DutyInfo(y.CfcId, y.TerritoryId,
|
||||
$"{SeIconChar.LevelEn.ToIconChar()}{FormatLevel(y.Level)} {y.Name}"))
|
||||
.Select(y => new DutyInfo(y.CfcId, y.TerritoryId, $"{FormatLevel(y.Level)} {y.Name}"))
|
||||
.ToList());
|
||||
}
|
||||
|
||||
public override void DrawTab()
|
||||
{
|
||||
using var tab = ImRaii.TabItem("Duties");
|
||||
using var tab = ImRaii.TabItem("Duties###Duties");
|
||||
if (!tab)
|
||||
return;
|
||||
|
||||
@ -96,37 +95,25 @@ internal sealed class DutyConfigComponent : ConfigComponent
|
||||
"https://docs.google.com/spreadsheets/d/151RlpqRcCpiD_VbQn6Duf-u-S71EP7d0mx3j1PDNoNA/edit?pli=1#gid=0");
|
||||
|
||||
ImGui.Separator();
|
||||
ImGui.Text("You can override the dungeon settings for each individual dungeon/trial:");
|
||||
ImGui.Text("You can override the settings for each individual dungeon/trial:");
|
||||
|
||||
DrawConfigTable(runInstancedContentWithAutoDuty);
|
||||
|
||||
DrawClipboardButtons();
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
using (var unused = ImRaii.Disabled(!ImGui.IsKeyDown(ImGuiKey.ModCtrl)))
|
||||
{
|
||||
if (ImGui.Button("Reset to default"))
|
||||
{
|
||||
Configuration.Duties.WhitelistedDutyCfcIds.Clear();
|
||||
Configuration.Duties.BlacklistedDutyCfcIds.Clear();
|
||||
Save();
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
|
||||
ImGui.SetTooltip("Hold CTRL to enable this button.");
|
||||
DrawResetButton();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawConfigTable(bool runInstancedContentWithAutoDuty)
|
||||
{
|
||||
using var child = ImRaii.Child("DutyConfiguration", new Vector2(-1, 400), true);
|
||||
using var child = ImRaii.Child("DutyConfiguration", new Vector2(650, 400), true);
|
||||
if (!child)
|
||||
return;
|
||||
|
||||
foreach (EExpansionVersion expansion in Enum.GetValues<EExpansionVersion>())
|
||||
{
|
||||
if (ImGui.CollapsingHeader(expansion.ToString()))
|
||||
if (ImGui.CollapsingHeader(expansion.ToFriendlyString()))
|
||||
{
|
||||
using var table = ImRaii.Table($"Duties{expansion}", 2, ImGuiTableFlags.SizingFixedFit);
|
||||
if (table)
|
||||
@ -245,5 +232,21 @@ internal sealed class DutyConfigComponent : ConfigComponent
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawResetButton()
|
||||
{
|
||||
using (ImRaii.Disabled(!ImGui.IsKeyDown(ImGuiKey.ModCtrl)))
|
||||
{
|
||||
if (ImGui.Button("Reset to default"))
|
||||
{
|
||||
Configuration.Duties.WhitelistedDutyCfcIds.Clear();
|
||||
Configuration.Duties.BlacklistedDutyCfcIds.Clear();
|
||||
Save();
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
|
||||
ImGui.SetTooltip("Hold CTRL to enable this button.");
|
||||
}
|
||||
|
||||
private sealed record DutyInfo(uint CfcId, uint TerritoryId, string Name);
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ internal sealed class GeneralConfigComponent : ConfigComponent
|
||||
|
||||
public override void DrawTab()
|
||||
{
|
||||
using var tab = ImRaii.TabItem("General");
|
||||
using var tab = ImRaii.TabItem("General###General");
|
||||
if (!tab)
|
||||
return;
|
||||
|
||||
|
@ -25,7 +25,7 @@ internal sealed class NotificationConfigComponent : ConfigComponent
|
||||
|
||||
public override void DrawTab()
|
||||
{
|
||||
using var tab = ImRaii.TabItem("Notifications");
|
||||
using var tab = ImRaii.TabItem("Notifications###Notifications");
|
||||
if (!tab)
|
||||
return;
|
||||
|
||||
|
@ -0,0 +1,464 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Dalamud.Game.Text;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Interface.Components;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Plugin.Services;
|
||||
using ImGuiNET;
|
||||
using LLib.GameData;
|
||||
using Lumina.Excel.Sheets;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Questionable.Controller;
|
||||
using Questionable.Controller.Steps.Interactions;
|
||||
using Questionable.Data;
|
||||
using Questionable.Model;
|
||||
using Questionable.Model.Common;
|
||||
using Questionable.Model.Questing;
|
||||
|
||||
namespace Questionable.Windows.ConfigComponents;
|
||||
|
||||
internal sealed class SinglePlayerDutyConfigComponent : ConfigComponent
|
||||
{
|
||||
private readonly TerritoryData _territoryData;
|
||||
private readonly QuestRegistry _questRegistry;
|
||||
private readonly QuestData _questData;
|
||||
private readonly IDataManager _dataManager;
|
||||
private readonly ILogger<SinglePlayerDutyConfigComponent> _logger;
|
||||
|
||||
private static readonly List<(EClassJob ClassJob, string Name)> RoleQuestCategories =
|
||||
[
|
||||
(EClassJob.Paladin, "Tank Role Quests"),
|
||||
(EClassJob.WhiteMage, "Healer Role Quests"),
|
||||
(EClassJob.Lancer, "Melee Role Quests"),
|
||||
(EClassJob.Bard, "Physical Ranged Role Quests"),
|
||||
(EClassJob.BlackMage, "Magical Ranged Role Quests"),
|
||||
];
|
||||
|
||||
private ImmutableDictionary<EAetheryteLocation, List<SinglePlayerDutyInfo>> _startingCityBattles = ImmutableDictionary<EAetheryteLocation, List<SinglePlayerDutyInfo>>.Empty;
|
||||
private ImmutableDictionary<EExpansionVersion, List<SinglePlayerDutyInfo>> _mainScenarioBattles = ImmutableDictionary<EExpansionVersion, List<SinglePlayerDutyInfo>>.Empty;
|
||||
private ImmutableDictionary<EClassJob, List<SinglePlayerDutyInfo>> _jobQuestBattles = ImmutableDictionary<EClassJob, List<SinglePlayerDutyInfo>>.Empty;
|
||||
private ImmutableDictionary<EClassJob, List<SinglePlayerDutyInfo>> _roleQuestBattles = ImmutableDictionary<EClassJob, List<SinglePlayerDutyInfo>>.Empty;
|
||||
private ImmutableList<SinglePlayerDutyInfo> _otherRoleQuestBattles = ImmutableList<SinglePlayerDutyInfo>.Empty;
|
||||
private ImmutableList<(string Label, List<SinglePlayerDutyInfo>)> _otherQuestBattles = ImmutableList<(string Label, List<SinglePlayerDutyInfo>)>.Empty;
|
||||
|
||||
public SinglePlayerDutyConfigComponent(
|
||||
IDalamudPluginInterface pluginInterface,
|
||||
Configuration configuration,
|
||||
TerritoryData territoryData,
|
||||
QuestRegistry questRegistry,
|
||||
QuestData questData,
|
||||
IDataManager dataManager,
|
||||
ILogger<SinglePlayerDutyConfigComponent> logger)
|
||||
: base(pluginInterface, configuration)
|
||||
{
|
||||
_territoryData = territoryData;
|
||||
_questRegistry = questRegistry;
|
||||
_questData = questData;
|
||||
_dataManager = dataManager;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Reload()
|
||||
{
|
||||
List<ElementId> questsWithMultipleBattles = _territoryData.GetAllQuestsWithQuestBattles()
|
||||
.GroupBy(x => x.QuestId)
|
||||
.Where(x => x.Count() > 1)
|
||||
.Select(x => x.Key)
|
||||
.ToList();
|
||||
|
||||
List<SinglePlayerDutyInfo> mainScenarioBattles = [];
|
||||
Dictionary<EAetheryteLocation, List<SinglePlayerDutyInfo>> startingCityBattles =
|
||||
new()
|
||||
{
|
||||
{ EAetheryteLocation.Limsa, [] },
|
||||
{ EAetheryteLocation.Gridania, [] },
|
||||
{ EAetheryteLocation.Uldah, [] },
|
||||
};
|
||||
|
||||
List<SinglePlayerDutyInfo> otherBattles = [];
|
||||
|
||||
Dictionary<ElementId, EClassJob> questIdsToJob = Enum.GetValues<EClassJob>()
|
||||
.Where(x => x != EClassJob.Adventurer && !x.IsCrafter() && !x.IsGatherer())
|
||||
.Where(x => x.IsClass() || !x.HasBaseClass())
|
||||
.SelectMany(x => _questRegistry.GetKnownClassJobQuests(x, false).Select(y => (y.QuestId, ClassJob: x)))
|
||||
.ToDictionary(x => x.QuestId, x => x.ClassJob);
|
||||
Dictionary<EClassJob, List<SinglePlayerDutyInfo>> jobQuestBattles = questIdsToJob.Values.Distinct()
|
||||
.ToDictionary(x => x, _ => new List<SinglePlayerDutyInfo>());
|
||||
|
||||
Dictionary<ElementId, List<EClassJob>> questIdToRole = RoleQuestCategories
|
||||
.SelectMany(x => _questData.GetRoleQuests(x.ClassJob).Select(y => (y.QuestId, x.ClassJob)))
|
||||
.GroupBy(x => x.QuestId)
|
||||
.ToDictionary(x => x.Key, x => x.Select(y => y.ClassJob).ToList());
|
||||
Dictionary<EClassJob, List<SinglePlayerDutyInfo>> roleQuestBattles = RoleQuestCategories
|
||||
.ToDictionary(x => x.ClassJob, _ => new List<SinglePlayerDutyInfo>());
|
||||
List<SinglePlayerDutyInfo> otherRoleQuestBattles = [];
|
||||
|
||||
foreach (var (questId, index, cfcData) in _territoryData.GetAllQuestsWithQuestBattles())
|
||||
{
|
||||
IQuestInfo questInfo = _questData.GetQuestInfo(questId);
|
||||
QuestStep questStep = new QuestStep
|
||||
{
|
||||
SinglePlayerDutyIndex = 0,
|
||||
BossModEnabled = false,
|
||||
};
|
||||
bool enabled;
|
||||
if (_questRegistry.TryGetQuest(questId, out var quest))
|
||||
{
|
||||
if (quest.Root.Disabled)
|
||||
{
|
||||
_logger.LogDebug("Disabling quest battle for quest {QuestId}, quest is disabled", questId);
|
||||
enabled = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
var foundStep = quest.AllSteps().FirstOrDefault(x =>
|
||||
x.Step.InteractionType == EInteractionType.SinglePlayerDuty &&
|
||||
x.Step.SinglePlayerDutyIndex == index);
|
||||
if (foundStep == default)
|
||||
{
|
||||
_logger.LogWarning("Disabling quest battle for quest {QuestId}, no battle with index {Index} found", questId, index);
|
||||
enabled = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
questStep = foundStep.Step;
|
||||
enabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogDebug("Disabling quest battle for quest {QuestId}, unknown quest", questId);
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
string name = $"{FormatLevel(questInfo.Level)} {questInfo.Name}";
|
||||
if (!string.IsNullOrEmpty(cfcData.Name) && !questInfo.Name.EndsWith(cfcData.Name, StringComparison.Ordinal))
|
||||
name += $" ({cfcData.Name})";
|
||||
|
||||
if (questsWithMultipleBattles.Contains(questId))
|
||||
name += $" (Part {questStep.SinglePlayerDutyIndex + 1})";
|
||||
else if (cfcData.ContentFinderConditionId is 674 or 691)
|
||||
name += " (Melee/Phys. Ranged)";
|
||||
|
||||
var dutyInfo = new SinglePlayerDutyInfo(
|
||||
cfcData.ContentFinderConditionId,
|
||||
cfcData.TerritoryId,
|
||||
name,
|
||||
questInfo.Expansion,
|
||||
questInfo.JournalGenre ?? uint.MaxValue,
|
||||
questInfo.SortKey,
|
||||
questStep.SinglePlayerDutyIndex,
|
||||
enabled,
|
||||
questStep.BossModEnabled);
|
||||
|
||||
if (cfcData.ContentFinderConditionId is 332 or 333 or 313 or 334)
|
||||
startingCityBattles[EAetheryteLocation.Limsa].Add(dutyInfo);
|
||||
else if (cfcData.ContentFinderConditionId is 296 or 297 or 299 or 298)
|
||||
startingCityBattles[EAetheryteLocation.Gridania].Add(dutyInfo);
|
||||
else if (cfcData.ContentFinderConditionId is 335 or 312 or 337 or 336)
|
||||
startingCityBattles[EAetheryteLocation.Uldah].Add(dutyInfo);
|
||||
else if (questInfo.IsMainScenarioQuest)
|
||||
mainScenarioBattles.Add(dutyInfo);
|
||||
else if (questIdsToJob.TryGetValue(questId, out EClassJob classJob))
|
||||
jobQuestBattles[classJob].Add(dutyInfo);
|
||||
else if (questIdToRole.TryGetValue(questId, out var classJobs))
|
||||
{
|
||||
foreach (var roleClassJob in classJobs)
|
||||
roleQuestBattles[roleClassJob].Add(dutyInfo);
|
||||
}
|
||||
else if (dutyInfo.CfcId is 845 or 1016)
|
||||
otherRoleQuestBattles.Add(dutyInfo);
|
||||
else
|
||||
otherBattles.Add(dutyInfo);
|
||||
}
|
||||
|
||||
_startingCityBattles = startingCityBattles
|
||||
.ToImmutableDictionary(x => x.Key,
|
||||
x => x.Value.OrderBy(y => y.SortKey)
|
||||
.ToList());
|
||||
_mainScenarioBattles = mainScenarioBattles
|
||||
.GroupBy(x => x.Expansion)
|
||||
.ToImmutableDictionary(x => x.Key,
|
||||
x =>
|
||||
x.OrderBy(y => y.JournalGenreId)
|
||||
.ThenBy(y => y.SortKey)
|
||||
.ThenBy(y => y.Index)
|
||||
.ToList());
|
||||
_jobQuestBattles = jobQuestBattles
|
||||
.Where(x => x.Value.Count > 0)
|
||||
.ToImmutableDictionary(x => x.Key,
|
||||
x =>
|
||||
x.Value
|
||||
// level 10 quests use the same quest battle for [you started as this class] and [you picked this class up later]
|
||||
.DistinctBy(y => y.CfcId)
|
||||
.OrderBy(y => y.JournalGenreId)
|
||||
.ThenBy(y => y.SortKey)
|
||||
.ThenBy(y => y.Index)
|
||||
.ToList());
|
||||
_roleQuestBattles = roleQuestBattles
|
||||
.ToImmutableDictionary(x => x.Key,
|
||||
x =>
|
||||
x.Value.OrderBy(y => y.JournalGenreId)
|
||||
.ThenBy(y => y.SortKey)
|
||||
.ThenBy(y => y.Index)
|
||||
.ToList());
|
||||
_otherRoleQuestBattles = otherRoleQuestBattles.ToImmutableList();
|
||||
_otherQuestBattles = otherBattles
|
||||
.OrderBy(x => x.JournalGenreId)
|
||||
.ThenBy(x => x.SortKey)
|
||||
.ThenBy(x => x.Index)
|
||||
.GroupBy(x => x.JournalGenreId)
|
||||
.Select(x => (BuildJournalGenreLabel(x.Key), x.ToList()))
|
||||
.ToImmutableList();
|
||||
}
|
||||
|
||||
private string BuildJournalGenreLabel(uint journalGenreId)
|
||||
{
|
||||
var journalGenre = _dataManager.GetExcelSheet<JournalGenre>().GetRow(journalGenreId);
|
||||
var journalCategory = journalGenre.JournalCategory.Value;
|
||||
|
||||
string genreName = journalGenre.Name.ExtractText();
|
||||
string categoryName = journalCategory.Name.ExtractText();
|
||||
|
||||
return $"{categoryName} {SeIconChar.ArrowRight.ToIconString()} {genreName}";
|
||||
}
|
||||
|
||||
public override void DrawTab()
|
||||
{
|
||||
using var tab = ImRaii.TabItem("Quest Battles###QuestBattles");
|
||||
if (!tab)
|
||||
return;
|
||||
|
||||
bool runSoloInstancesWithBossMod = Configuration.SinglePlayerDuties.RunSoloInstancesWithBossMod;
|
||||
if (ImGui.Checkbox("Run quest battles with BossMod", ref runSoloInstancesWithBossMod))
|
||||
{
|
||||
Configuration.SinglePlayerDuties.RunSoloInstancesWithBossMod = runSoloInstancesWithBossMod;
|
||||
Save();
|
||||
}
|
||||
|
||||
ImGui.TextColored(ImGuiColors.DalamudRed,
|
||||
"Work in Progress: For now, this will always use BossMod for combat.");
|
||||
|
||||
ImGui.Separator();
|
||||
|
||||
using (ImRaii.Disabled(!runSoloInstancesWithBossMod))
|
||||
{
|
||||
ImGui.Text(
|
||||
"Questionable includes a default list of quest battles that work if BossMod is installed.");
|
||||
ImGui.Text("The included list of quest battles can change with each update.");
|
||||
|
||||
ImGui.Separator();
|
||||
ImGui.Text("You can override the settings for each individual quest battle:");
|
||||
|
||||
|
||||
using var tabBar = ImRaii.TabBar("QuestionableConfigTabs");
|
||||
if (tabBar)
|
||||
{
|
||||
DrawMainScenarioConfigTable();
|
||||
DrawJobQuestConfigTable();
|
||||
DrawRoleQuestConfigTable();
|
||||
DrawOtherQuestConfigTable();
|
||||
}
|
||||
|
||||
DrawResetButton();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawMainScenarioConfigTable()
|
||||
{
|
||||
using var tab = ImRaii.TabItem("MSQ###MSQ");
|
||||
if (!tab)
|
||||
return;
|
||||
|
||||
using var child = BeginChildArea();
|
||||
if (!child)
|
||||
return;
|
||||
|
||||
if (ImGui.CollapsingHeader($"Limsa Lominsa ({FormatLevel(5)} - {FormatLevel(14)})"))
|
||||
DrawQuestTable("LimsaLominsa", _startingCityBattles[EAetheryteLocation.Limsa]);
|
||||
|
||||
if (ImGui.CollapsingHeader($"Gridania ({FormatLevel(5)} - {FormatLevel(14)})"))
|
||||
DrawQuestTable("Gridania", _startingCityBattles[EAetheryteLocation.Gridania]);
|
||||
|
||||
if (ImGui.CollapsingHeader($"Ul'dah ({FormatLevel(4)} - {FormatLevel(14)})"))
|
||||
DrawQuestTable("Uldah", _startingCityBattles[EAetheryteLocation.Uldah]);
|
||||
|
||||
foreach (EExpansionVersion expansion in Enum.GetValues<EExpansionVersion>())
|
||||
{
|
||||
if (_mainScenarioBattles.TryGetValue(expansion, out var dutyInfos))
|
||||
{
|
||||
if (ImGui.CollapsingHeader(expansion.ToFriendlyString()))
|
||||
DrawQuestTable($"Duties{expansion}", dutyInfos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawJobQuestConfigTable()
|
||||
{
|
||||
using var tab = ImRaii.TabItem("Class/Job Quests###JobQuests");
|
||||
if (!tab)
|
||||
return;
|
||||
|
||||
using var child = BeginChildArea();
|
||||
if (!child)
|
||||
return;
|
||||
|
||||
foreach (EClassJob classJob in Enum.GetValues<EClassJob>())
|
||||
{
|
||||
if (_jobQuestBattles.TryGetValue(classJob, out var dutyInfos))
|
||||
{
|
||||
string jobName = classJob.ToFriendlyString();
|
||||
if (classJob.IsClass())
|
||||
jobName += $" / {classJob.AsJob().ToFriendlyString()}";
|
||||
|
||||
if (ImGui.CollapsingHeader(jobName))
|
||||
DrawQuestTable($"JobQuests{classJob}", dutyInfos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawRoleQuestConfigTable()
|
||||
{
|
||||
using var tab = ImRaii.TabItem("Role Quests###RoleQuests");
|
||||
if (!tab)
|
||||
return;
|
||||
|
||||
using var child = BeginChildArea();
|
||||
if (!child)
|
||||
return;
|
||||
|
||||
foreach (var (classJob, label) in RoleQuestCategories)
|
||||
{
|
||||
if (_roleQuestBattles.TryGetValue(classJob, out var dutyInfos))
|
||||
{
|
||||
if (ImGui.CollapsingHeader(label))
|
||||
DrawQuestTable($"RoleQuests{classJob}", dutyInfos);
|
||||
}
|
||||
}
|
||||
|
||||
if(ImGui.CollapsingHeader("General Role Quests"))
|
||||
DrawQuestTable("RoleQuestsGeneral", _otherRoleQuestBattles);
|
||||
}
|
||||
|
||||
private void DrawOtherQuestConfigTable()
|
||||
{
|
||||
using var tab = ImRaii.TabItem("Other Quests###MiscQuests");
|
||||
if (!tab)
|
||||
return;
|
||||
|
||||
using var child = BeginChildArea();
|
||||
if (!child)
|
||||
return;
|
||||
|
||||
foreach (var (label, dutyInfos) in _otherQuestBattles)
|
||||
{
|
||||
if (ImGui.CollapsingHeader(label))
|
||||
DrawQuestTable($"Other{label}", dutyInfos);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawQuestTable(string label, IReadOnlyList<SinglePlayerDutyInfo> dutyInfos)
|
||||
{
|
||||
using var table = ImRaii.Table(label, 2, ImGuiTableFlags.SizingFixedFit);
|
||||
if (table)
|
||||
{
|
||||
ImGui.TableSetupColumn("Quest", ImGuiTableColumnFlags.WidthStretch);
|
||||
ImGui.TableSetupColumn("Options", ImGuiTableColumnFlags.WidthFixed, 200f);
|
||||
|
||||
foreach (var dutyInfo in dutyInfos)
|
||||
{
|
||||
ImGui.TableNextRow();
|
||||
|
||||
string[] labels = dutyInfo.BossModEnabledByDefault
|
||||
? SupportedCfcOptions
|
||||
: UnsupportedCfcOptions;
|
||||
int value = 0;
|
||||
if (Configuration.Duties.WhitelistedDutyCfcIds.Contains(dutyInfo.CfcId))
|
||||
value = 1;
|
||||
if (Configuration.Duties.BlacklistedDutyCfcIds.Contains(dutyInfo.CfcId))
|
||||
value = 2;
|
||||
|
||||
if (ImGui.TableNextColumn())
|
||||
{
|
||||
ImGui.AlignTextToFramePadding();
|
||||
ImGui.TextUnformatted(dutyInfo.Name);
|
||||
|
||||
if (ImGui.IsItemHovered() && Configuration.Advanced.AdditionalStatusInformation)
|
||||
{
|
||||
using var tooltip = ImRaii.Tooltip();
|
||||
if (tooltip)
|
||||
{
|
||||
ImGui.TextUnformatted(dutyInfo.Name);
|
||||
ImGui.Separator();
|
||||
ImGui.BulletText($"TerritoryId: {dutyInfo.TerritoryId}");
|
||||
ImGui.BulletText($"ContentFinderConditionId: {dutyInfo.CfcId}");
|
||||
}
|
||||
}
|
||||
|
||||
if (!dutyInfo.Enabled)
|
||||
{
|
||||
ImGuiComponents.HelpMarker("Questionable doesn't include support for this quest.",
|
||||
FontAwesomeIcon.Times, ImGuiColors.DalamudRed);
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui.TableNextColumn())
|
||||
{
|
||||
using var _ = ImRaii.PushId($"##Duty{dutyInfo.CfcId}");
|
||||
using (ImRaii.Disabled(!dutyInfo.Enabled))
|
||||
{
|
||||
ImGui.SetNextItemWidth(200);
|
||||
if (ImGui.Combo(string.Empty, ref value, labels, labels.Length))
|
||||
{
|
||||
Configuration.Duties.WhitelistedDutyCfcIds.Remove(dutyInfo.CfcId);
|
||||
Configuration.Duties.BlacklistedDutyCfcIds.Remove(dutyInfo.CfcId);
|
||||
|
||||
if (value == 1)
|
||||
Configuration.Duties.WhitelistedDutyCfcIds.Add(dutyInfo.CfcId);
|
||||
else if (value == 2)
|
||||
Configuration.Duties.BlacklistedDutyCfcIds.Add(dutyInfo.CfcId);
|
||||
|
||||
Save();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static ImRaii.IEndObject BeginChildArea() => ImRaii.Child("DutyConfiguration", new Vector2(650, 400), true);
|
||||
|
||||
private void DrawResetButton()
|
||||
{
|
||||
using (ImRaii.Disabled(!ImGui.IsKeyDown(ImGuiKey.ModCtrl)))
|
||||
{
|
||||
if (ImGui.Button("Reset to default"))
|
||||
{
|
||||
Configuration.SinglePlayerDuties.WhitelistedSinglePlayerDutyCfcIds.Clear();
|
||||
Configuration.SinglePlayerDuties.BlacklistedSinglePlayerDutyCfcIds.Clear();
|
||||
Save();
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
|
||||
ImGui.SetTooltip("Hold CTRL to enable this button.");
|
||||
}
|
||||
|
||||
private sealed record SinglePlayerDutyInfo(
|
||||
uint CfcId,
|
||||
uint TerritoryId,
|
||||
string Name,
|
||||
EExpansionVersion Expansion,
|
||||
uint JournalGenreId,
|
||||
ushort SortKey,
|
||||
byte Index,
|
||||
bool Enabled,
|
||||
bool BossModEnabledByDefault);
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
using Dalamud.Plugin;
|
||||
using ImGuiNET;
|
||||
using LLib.ImGui;
|
||||
using Questionable.Controller.Steps.Interactions;
|
||||
using Questionable.Windows.ConfigComponents;
|
||||
|
||||
namespace Questionable.Windows;
|
||||
@ -11,6 +12,7 @@ internal sealed class ConfigWindow : LWindow, IPersistableWindowConfig
|
||||
private readonly IDalamudPluginInterface _pluginInterface;
|
||||
private readonly GeneralConfigComponent _generalConfigComponent;
|
||||
private readonly DutyConfigComponent _dutyConfigComponent;
|
||||
private readonly SinglePlayerDutyConfigComponent _singlePlayerDutyConfigComponent;
|
||||
private readonly NotificationConfigComponent _notificationConfigComponent;
|
||||
private readonly DebugConfigComponent _debugConfigComponent;
|
||||
private readonly Configuration _configuration;
|
||||
@ -19,6 +21,7 @@ internal sealed class ConfigWindow : LWindow, IPersistableWindowConfig
|
||||
IDalamudPluginInterface pluginInterface,
|
||||
GeneralConfigComponent generalConfigComponent,
|
||||
DutyConfigComponent dutyConfigComponent,
|
||||
SinglePlayerDutyConfigComponent singlePlayerDutyConfigComponent,
|
||||
NotificationConfigComponent notificationConfigComponent,
|
||||
DebugConfigComponent debugConfigComponent,
|
||||
Configuration configuration)
|
||||
@ -27,6 +30,7 @@ internal sealed class ConfigWindow : LWindow, IPersistableWindowConfig
|
||||
_pluginInterface = pluginInterface;
|
||||
_generalConfigComponent = generalConfigComponent;
|
||||
_dutyConfigComponent = dutyConfigComponent;
|
||||
_singlePlayerDutyConfigComponent = singlePlayerDutyConfigComponent;
|
||||
_notificationConfigComponent = notificationConfigComponent;
|
||||
_debugConfigComponent = debugConfigComponent;
|
||||
_configuration = configuration;
|
||||
@ -42,6 +46,7 @@ internal sealed class ConfigWindow : LWindow, IPersistableWindowConfig
|
||||
|
||||
_generalConfigComponent.DrawTab();
|
||||
_dutyConfigComponent.DrawTab();
|
||||
_singlePlayerDutyConfigComponent.DrawTab();
|
||||
_notificationConfigComponent.DrawTab();
|
||||
_debugConfigComponent.DrawTab();
|
||||
}
|
||||
|
2
vendor/pictomancy
vendored
2
vendor/pictomancy
vendored
@ -1 +1 @@
|
||||
Subproject commit d147acc0ea5eed00e25b12508bf5d3fb8eefed53
|
||||
Subproject commit 70c0e31aabfbc7067c5b57fd02ee0c72ebc7a22e
|
Loading…
x
Reference in New Issue
Block a user