private readonly IClientState _clientState;
private readonly ShopController _shopController;
private readonly BossModIpc _bossModIpc;
+ private readonly Configuration _configuration;
private readonly ILogger<InteractionUiController> _logger;
private readonly Regex _returnRegex;
private readonly Regex _purchaseItemRegex;
IClientState clientState,
ShopController shopController,
BossModIpc bossModIpc,
+ Configuration configuration,
ILogger<InteractionUiController> logger)
{
_addonLifecycle = addonLifecycle;
_clientState = clientState;
_shopController = shopController;
_bossModIpc = bossModIpc;
+ _configuration = configuration;
_logger = logger;
_returnRegex = _dataManager.GetExcelSheet<Addon>().GetRow(196).GetRegex(addon => addon.Text, pluginLog)!;
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "CutSceneSelectString", CutsceneSelectStringPostSetup);
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "SelectIconString", SelectIconStringPostSetup);
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "SelectYesno", SelectYesnoPostSetup);
+ _addonLifecycle.RegisterListener(AddonEvent.PostSetup, "DifficultySelectYesNo", DifficultySelectYesNoPostSetup);
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "PointMenu", PointMenuPostSetup);
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "HousingSelectBlock", HousingSelectBlockPostSetup);
SelectYesnoPostSetup(addonSelectYesno, true);
}
+ if (_gameGui.TryGetAddonByName("DifficultySelectYesNo", out AtkUnitBase* addonDifficultySelectYesNo))
+ {
+ _logger.LogInformation("DifficultySelectYesNo window is open");
+ DifficultySelectYesNoPostSetup(addonDifficultySelectYesNo, true);
+ }
+
if (_gameGui.TryGetAddonByName("PointMenu", out AtkUnitBase* addonPointMenu))
{
_logger.LogInformation("PointMenu is open");
return true;
}
+ if (CheckSinglePlayerDutyYesNo(quest.Id, step))
+ {
+ addonSelectYesno->AtkUnitBase.FireCallbackInt(0);
+ return true;
+ }
+
+ return false;
+ }
+
+ private bool CheckSinglePlayerDutyYesNo(ElementId questId, QuestStep? step)
+ {
if (step is { InteractionType: EInteractionType.SinglePlayerDuty } &&
- _bossModIpc.IsConfiguredToRunSoloInstance(quest.Id, step.SinglePlayerDutyIndex, step.BossModEnabled))
+ _bossModIpc.IsConfiguredToRunSoloInstance(questId, step.SinglePlayerDutyIndex, step.BossModEnabled))
{
// Most of these are yes/no dialogs "Duty calls, ...".
//
// after you confirm 'Wait for Krile?'. However, if you fail that duty, you'll get a DifficultySelectYesNo.
// DifficultySelectYesNo → [0, 2] for very easy
- _logger.LogInformation("DefaultYesNo: probably Single Player Duty");
- addonSelectYesno->AtkUnitBase.FireCallbackInt(0);
+ _logger.LogInformation("SinglePlayerDutyYesNo: probably Single Player Duty");
return true;
}
return false;
}
+
+ private unsafe void DifficultySelectYesNoPostSetup(AddonEvent type, AddonArgs args)
+ {
+ AtkUnitBase* addonDifficultySelectYesNo = (AtkUnitBase*)args.Addon;
+ DifficultySelectYesNoPostSetup(addonDifficultySelectYesNo, false);
+ }
+
+ private unsafe void DifficultySelectYesNoPostSetup(AtkUnitBase* addonDifficultySelectYesNo, bool checkAllSteps)
+ {
+ var currentQuest = _questController.StartedQuest;
+ if (currentQuest == null)
+ return;
+
+ var quest = currentQuest.Quest;
+ bool autoConfirm;
+ if (checkAllSteps)
+ {
+ var sequence = quest.FindSequence(currentQuest.Sequence);
+ autoConfirm = sequence != null && sequence.Steps.Any(step => CheckSinglePlayerDutyYesNo(quest.Id, step));
+ }
+ else
+ {
+ var step = quest.FindSequence(currentQuest.Sequence)?.FindStep(currentQuest.Step);
+ autoConfirm = step != null && CheckSinglePlayerDutyYesNo(quest.Id, step);
+ }
+
+ if (autoConfirm)
+ {
+ _logger.LogInformation("Confirming difficulty ({Difficulty}) for quest battle", _configuration.SinglePlayerDuties.RetryDifficulty);
+ var selectChoice = stackalloc AtkValue[]
+ {
+ new() { Type = ValueType.Int, Int = 0 },
+ new() { Type = ValueType.Int, Int = _configuration.SinglePlayerDuties.RetryDifficulty }
+ };
+ addonDifficultySelectYesNo->FireCallback(2, selectChoice);
+ }
+ }
+
private ushort? FindTargetTerritoryFromQuestStep(QuestController.QuestProgress currentQuest)
{
// this can be triggered either manually (in which case we should increase the step counter), or automatically
{
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "HousingSelectBlock", HousingSelectBlockPostSetup);
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "PointMenu", PointMenuPostSetup);
+ _addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "DifficultySelectYesNo", DifficultySelectYesNoPostSetup);
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "SelectYesno", SelectYesnoPostSetup);
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "SelectIconString", SelectIconStringPostSetup);
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "CutSceneSelectString", CutsceneSelectStringPostSetup);
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.BlackMage, "Magical Ranged Role Quests"),
];
+ private readonly string[] _retryDifficulties = ["Normal", "Easy", "Very Easy"];
+
+ private readonly TerritoryData _territoryData;
+ private readonly QuestRegistry _questRegistry;
+ private readonly QuestData _questData;
+ private readonly IDataManager _dataManager;
+ private readonly ILogger<SinglePlayerDutyConfigComponent> _logger;
+ private readonly List<(EClassJob ClassJob, int Category)> _sortedClassJobs;
+
private ImmutableDictionary<EAetheryteLocation, List<SinglePlayerDutyInfo>> _startingCityBattles =
ImmutableDictionary<EAetheryteLocation, List<SinglePlayerDutyInfo>>.Empty;
_questData = questData;
_dataManager = dataManager;
_logger = logger;
+
+ _sortedClassJobs = dataManager.GetExcelSheet<ClassJob>()
+ .Where(x => x is { RowId: > 0, UIPriority: < 100 })
+ .Select(x => (ClassJob: (EClassJob)x.RowId, Priority: x.UIPriority))
+ .OrderBy(x => x.Priority)
+ .Select(x => (x.ClassJob, x.Priority / 10))
+ .ToList();
}
public void Reload()
Save();
}
- ImGui.TextColored(ImGuiColors.DalamudRed,
- "Work in Progress: For now, this will always use BossMod for combat.");
+ using (ImRaii.PushIndent(ImGui.GetFrameHeight() + ImGui.GetStyle().ItemInnerSpacing.X))
+ {
+ ImGui.AlignTextToFramePadding();
+ ImGui.TextColored(ImGuiColors.DalamudRed,
+ "Work in Progress: For now, this will always use BossMod for combat.");
+
+ using (ImRaii.Disabled(!runSoloInstancesWithBossMod))
+ {
+ int retryDifficulty = Configuration.SinglePlayerDuties.RetryDifficulty;
+ if (ImGui.Combo("Difficulty when retrying a quest battle", ref retryDifficulty, _retryDifficulties,
+ _retryDifficulties.Length))
+ {
+ Configuration.SinglePlayerDuties.RetryDifficulty = (byte)retryDifficulty;
+ Save();
+ }
+ }
+ }
ImGui.Separator();
private void DrawMainScenarioConfigTable()
{
- using var tab = ImRaii.TabItem("MSQ###MSQ");
+ using var tab = ImRaii.TabItem("Main Scenario Quests###MSQ");
if (!tab)
return;
if (!child)
return;
- foreach (EClassJob classJob in Enum.GetValues<EClassJob>())
+ int oldPriority = 0;
+ foreach (var (classJob, priority) in _sortedClassJobs)
{
if (_jobQuestBattles.TryGetValue(classJob, out var dutyInfos))
{
+ if (priority != oldPriority)
+ {
+ oldPriority = priority;
+ ImGui.Spacing();
+ ImGui.Separator();
+ ImGui.Spacing();
+ }
+
string jobName = classJob.ToFriendlyString();
if (classJob.IsClass())
jobName += $" / {classJob.AsJob().ToFriendlyString()}";
{
using var _ = ImRaii.Tooltip();
- ImGui.TextColored(ImGuiColors.DalamudYellow, "While testing, the following issues have been found:");
+ ImGui.TextColored(ImGuiColors.DalamudYellow,
+ "While testing, the following issues have been found:");
foreach (string note in dutyInfo.Notes)
ImGui.BulletText(note);
}