forked from liza/Questionable
Add quest battle difficulty selection; UI tweaks
This commit is contained in:
parent
31eb121cf0
commit
71e0b01dbc
@ -28,7 +28,11 @@
|
||||
"Z": -309.55975
|
||||
},
|
||||
"TerritoryId": 148,
|
||||
"InteractionType": "SinglePlayerDuty"
|
||||
"InteractionType": "SinglePlayerDuty",
|
||||
"BossModEnabled": false,
|
||||
"BossModNotes": [
|
||||
"AI doesn't automatically target newly spawning adds and dies until after the boss died (tested on CNJ)"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -46,6 +46,10 @@
|
||||
},
|
||||
"TerritoryId": 817,
|
||||
"InteractionType": "SinglePlayerDuty",
|
||||
"BossModEnabled": false,
|
||||
"BossModNotes": [
|
||||
"Doesn't walk to the teleporter to finish the duty"
|
||||
],
|
||||
"Fly": true,
|
||||
"Comment": "A Sleep Disturbed (Opo-Opo, Wolf, Serpent)",
|
||||
"$": "The dialogue choices and data ids here are recycled",
|
||||
|
@ -45,6 +45,7 @@ internal sealed class Configuration : IPluginConfiguration
|
||||
internal sealed class SinglePlayerDutyConfiguration
|
||||
{
|
||||
public bool RunSoloInstancesWithBossMod { get; set; }
|
||||
public byte RetryDifficulty { get; set; } = 2;
|
||||
public HashSet<uint> WhitelistedSinglePlayerDutyCfcIds { get; set; } = [];
|
||||
public HashSet<uint> BlacklistedSinglePlayerDutyCfcIds { get; set; } = [];
|
||||
}
|
||||
|
@ -47,6 +47,7 @@ internal sealed class InteractionUiController : IDisposable
|
||||
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;
|
||||
@ -71,6 +72,7 @@ internal sealed class InteractionUiController : IDisposable
|
||||
IClientState clientState,
|
||||
ShopController shopController,
|
||||
BossModIpc bossModIpc,
|
||||
Configuration configuration,
|
||||
ILogger<InteractionUiController> logger)
|
||||
{
|
||||
_addonLifecycle = addonLifecycle;
|
||||
@ -89,6 +91,7 @@ internal sealed class InteractionUiController : IDisposable
|
||||
_clientState = clientState;
|
||||
_shopController = shopController;
|
||||
_bossModIpc = bossModIpc;
|
||||
_configuration = configuration;
|
||||
_logger = logger;
|
||||
|
||||
_returnRegex = _dataManager.GetExcelSheet<Addon>().GetRow(196).GetRegex(addon => addon.Text, pluginLog)!;
|
||||
@ -98,6 +101,7 @@ internal sealed class InteractionUiController : IDisposable
|
||||
_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);
|
||||
|
||||
@ -144,6 +148,12 @@ internal sealed class InteractionUiController : IDisposable
|
||||
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");
|
||||
@ -669,8 +679,19 @@ internal sealed class InteractionUiController : IDisposable
|
||||
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, ...".
|
||||
//
|
||||
@ -678,8 +699,7 @@ internal sealed class InteractionUiController : IDisposable
|
||||
// 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;
|
||||
}
|
||||
|
||||
@ -716,6 +736,44 @@ internal sealed class InteractionUiController : IDisposable
|
||||
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
|
||||
@ -888,6 +946,7 @@ internal sealed class InteractionUiController : IDisposable
|
||||
{
|
||||
_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);
|
||||
|
@ -25,12 +25,6 @@ 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"),
|
||||
@ -40,6 +34,15 @@ internal sealed class SinglePlayerDutyConfigComponent : ConfigComponent
|
||||
(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;
|
||||
|
||||
@ -72,6 +75,13 @@ internal sealed class SinglePlayerDutyConfigComponent : ConfigComponent
|
||||
_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()
|
||||
@ -256,9 +266,24 @@ internal sealed class SinglePlayerDutyConfigComponent : ConfigComponent
|
||||
Save();
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
using (ImRaii.Disabled(!runSoloInstancesWithBossMod))
|
||||
@ -286,7 +311,7 @@ internal sealed class SinglePlayerDutyConfigComponent : ConfigComponent
|
||||
|
||||
private void DrawMainScenarioConfigTable()
|
||||
{
|
||||
using var tab = ImRaii.TabItem("MSQ###MSQ");
|
||||
using var tab = ImRaii.TabItem("Main Scenario Quests###MSQ");
|
||||
if (!tab)
|
||||
return;
|
||||
|
||||
@ -323,10 +348,19 @@ internal sealed class SinglePlayerDutyConfigComponent : ConfigComponent
|
||||
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()}";
|
||||
@ -434,7 +468,8 @@ internal sealed class SinglePlayerDutyConfigComponent : ConfigComponent
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user