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
|
"Z": -309.55975
|
||||||
},
|
},
|
||||||
"TerritoryId": 148,
|
"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,
|
"TerritoryId": 817,
|
||||||
"InteractionType": "SinglePlayerDuty",
|
"InteractionType": "SinglePlayerDuty",
|
||||||
|
"BossModEnabled": false,
|
||||||
|
"BossModNotes": [
|
||||||
|
"Doesn't walk to the teleporter to finish the duty"
|
||||||
|
],
|
||||||
"Fly": true,
|
"Fly": true,
|
||||||
"Comment": "A Sleep Disturbed (Opo-Opo, Wolf, Serpent)",
|
"Comment": "A Sleep Disturbed (Opo-Opo, Wolf, Serpent)",
|
||||||
"$": "The dialogue choices and data ids here are recycled",
|
"$": "The dialogue choices and data ids here are recycled",
|
||||||
|
@ -45,6 +45,7 @@ internal sealed class Configuration : IPluginConfiguration
|
|||||||
internal sealed class SinglePlayerDutyConfiguration
|
internal sealed class SinglePlayerDutyConfiguration
|
||||||
{
|
{
|
||||||
public bool RunSoloInstancesWithBossMod { get; set; }
|
public bool RunSoloInstancesWithBossMod { get; set; }
|
||||||
|
public byte RetryDifficulty { get; set; } = 2;
|
||||||
public HashSet<uint> WhitelistedSinglePlayerDutyCfcIds { get; set; } = [];
|
public HashSet<uint> WhitelistedSinglePlayerDutyCfcIds { get; set; } = [];
|
||||||
public HashSet<uint> BlacklistedSinglePlayerDutyCfcIds { get; set; } = [];
|
public HashSet<uint> BlacklistedSinglePlayerDutyCfcIds { get; set; } = [];
|
||||||
}
|
}
|
||||||
|
@ -47,6 +47,7 @@ internal sealed class InteractionUiController : IDisposable
|
|||||||
private readonly IClientState _clientState;
|
private readonly IClientState _clientState;
|
||||||
private readonly ShopController _shopController;
|
private readonly ShopController _shopController;
|
||||||
private readonly BossModIpc _bossModIpc;
|
private readonly BossModIpc _bossModIpc;
|
||||||
|
private readonly Configuration _configuration;
|
||||||
private readonly ILogger<InteractionUiController> _logger;
|
private readonly ILogger<InteractionUiController> _logger;
|
||||||
private readonly Regex _returnRegex;
|
private readonly Regex _returnRegex;
|
||||||
private readonly Regex _purchaseItemRegex;
|
private readonly Regex _purchaseItemRegex;
|
||||||
@ -71,6 +72,7 @@ internal sealed class InteractionUiController : IDisposable
|
|||||||
IClientState clientState,
|
IClientState clientState,
|
||||||
ShopController shopController,
|
ShopController shopController,
|
||||||
BossModIpc bossModIpc,
|
BossModIpc bossModIpc,
|
||||||
|
Configuration configuration,
|
||||||
ILogger<InteractionUiController> logger)
|
ILogger<InteractionUiController> logger)
|
||||||
{
|
{
|
||||||
_addonLifecycle = addonLifecycle;
|
_addonLifecycle = addonLifecycle;
|
||||||
@ -89,6 +91,7 @@ internal sealed class InteractionUiController : IDisposable
|
|||||||
_clientState = clientState;
|
_clientState = clientState;
|
||||||
_shopController = shopController;
|
_shopController = shopController;
|
||||||
_bossModIpc = bossModIpc;
|
_bossModIpc = bossModIpc;
|
||||||
|
_configuration = configuration;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
|
||||||
_returnRegex = _dataManager.GetExcelSheet<Addon>().GetRow(196).GetRegex(addon => addon.Text, pluginLog)!;
|
_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, "CutSceneSelectString", CutsceneSelectStringPostSetup);
|
||||||
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "SelectIconString", SelectIconStringPostSetup);
|
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "SelectIconString", SelectIconStringPostSetup);
|
||||||
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "SelectYesno", SelectYesnoPostSetup);
|
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "SelectYesno", SelectYesnoPostSetup);
|
||||||
|
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "DifficultySelectYesNo", DifficultySelectYesNoPostSetup);
|
||||||
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "PointMenu", PointMenuPostSetup);
|
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "PointMenu", PointMenuPostSetup);
|
||||||
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "HousingSelectBlock", HousingSelectBlockPostSetup);
|
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "HousingSelectBlock", HousingSelectBlockPostSetup);
|
||||||
|
|
||||||
@ -144,6 +148,12 @@ internal sealed class InteractionUiController : IDisposable
|
|||||||
SelectYesnoPostSetup(addonSelectYesno, true);
|
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))
|
if (_gameGui.TryGetAddonByName("PointMenu", out AtkUnitBase* addonPointMenu))
|
||||||
{
|
{
|
||||||
_logger.LogInformation("PointMenu is open");
|
_logger.LogInformation("PointMenu is open");
|
||||||
@ -669,8 +679,19 @@ internal sealed class InteractionUiController : IDisposable
|
|||||||
return true;
|
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 } &&
|
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, ...".
|
// 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.
|
// after you confirm 'Wait for Krile?'. However, if you fail that duty, you'll get a DifficultySelectYesNo.
|
||||||
|
|
||||||
// DifficultySelectYesNo → [0, 2] for very easy
|
// DifficultySelectYesNo → [0, 2] for very easy
|
||||||
_logger.LogInformation("DefaultYesNo: probably Single Player Duty");
|
_logger.LogInformation("SinglePlayerDutyYesNo: probably Single Player Duty");
|
||||||
addonSelectYesno->AtkUnitBase.FireCallbackInt(0);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -716,6 +736,44 @@ internal sealed class InteractionUiController : IDisposable
|
|||||||
return false;
|
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)
|
private ushort? FindTargetTerritoryFromQuestStep(QuestController.QuestProgress currentQuest)
|
||||||
{
|
{
|
||||||
// this can be triggered either manually (in which case we should increase the step counter), or automatically
|
// 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, "HousingSelectBlock", HousingSelectBlockPostSetup);
|
||||||
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "PointMenu", PointMenuPostSetup);
|
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "PointMenu", PointMenuPostSetup);
|
||||||
|
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "DifficultySelectYesNo", DifficultySelectYesNoPostSetup);
|
||||||
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "SelectYesno", SelectYesnoPostSetup);
|
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "SelectYesno", SelectYesnoPostSetup);
|
||||||
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "SelectIconString", SelectIconStringPostSetup);
|
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "SelectIconString", SelectIconStringPostSetup);
|
||||||
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "CutSceneSelectString", CutsceneSelectStringPostSetup);
|
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "CutSceneSelectString", CutsceneSelectStringPostSetup);
|
||||||
|
@ -25,12 +25,6 @@ namespace Questionable.Windows.ConfigComponents;
|
|||||||
|
|
||||||
internal sealed class SinglePlayerDutyConfigComponent : ConfigComponent
|
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 =
|
private static readonly List<(EClassJob ClassJob, string Name)> RoleQuestCategories =
|
||||||
[
|
[
|
||||||
(EClassJob.Paladin, "Tank Role Quests"),
|
(EClassJob.Paladin, "Tank Role Quests"),
|
||||||
@ -40,6 +34,15 @@ internal sealed class SinglePlayerDutyConfigComponent : ConfigComponent
|
|||||||
(EClassJob.BlackMage, "Magical Ranged 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 =
|
private ImmutableDictionary<EAetheryteLocation, List<SinglePlayerDutyInfo>> _startingCityBattles =
|
||||||
ImmutableDictionary<EAetheryteLocation, List<SinglePlayerDutyInfo>>.Empty;
|
ImmutableDictionary<EAetheryteLocation, List<SinglePlayerDutyInfo>>.Empty;
|
||||||
|
|
||||||
@ -72,6 +75,13 @@ internal sealed class SinglePlayerDutyConfigComponent : ConfigComponent
|
|||||||
_questData = questData;
|
_questData = questData;
|
||||||
_dataManager = dataManager;
|
_dataManager = dataManager;
|
||||||
_logger = logger;
|
_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()
|
public void Reload()
|
||||||
@ -256,8 +266,23 @@ internal sealed class SinglePlayerDutyConfigComponent : ConfigComponent
|
|||||||
Save();
|
Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.TextColored(ImGuiColors.DalamudRed,
|
using (ImRaii.PushIndent(ImGui.GetFrameHeight() + ImGui.GetStyle().ItemInnerSpacing.X))
|
||||||
"Work in Progress: For now, this will always use BossMod for combat.");
|
{
|
||||||
|
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();
|
ImGui.Separator();
|
||||||
|
|
||||||
@ -286,7 +311,7 @@ internal sealed class SinglePlayerDutyConfigComponent : ConfigComponent
|
|||||||
|
|
||||||
private void DrawMainScenarioConfigTable()
|
private void DrawMainScenarioConfigTable()
|
||||||
{
|
{
|
||||||
using var tab = ImRaii.TabItem("MSQ###MSQ");
|
using var tab = ImRaii.TabItem("Main Scenario Quests###MSQ");
|
||||||
if (!tab)
|
if (!tab)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -323,10 +348,19 @@ internal sealed class SinglePlayerDutyConfigComponent : ConfigComponent
|
|||||||
if (!child)
|
if (!child)
|
||||||
return;
|
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 (_jobQuestBattles.TryGetValue(classJob, out var dutyInfos))
|
||||||
{
|
{
|
||||||
|
if (priority != oldPriority)
|
||||||
|
{
|
||||||
|
oldPriority = priority;
|
||||||
|
ImGui.Spacing();
|
||||||
|
ImGui.Separator();
|
||||||
|
ImGui.Spacing();
|
||||||
|
}
|
||||||
|
|
||||||
string jobName = classJob.ToFriendlyString();
|
string jobName = classJob.ToFriendlyString();
|
||||||
if (classJob.IsClass())
|
if (classJob.IsClass())
|
||||||
jobName += $" / {classJob.AsJob().ToFriendlyString()}";
|
jobName += $" / {classJob.AsJob().ToFriendlyString()}";
|
||||||
@ -434,7 +468,8 @@ internal sealed class SinglePlayerDutyConfigComponent : ConfigComponent
|
|||||||
{
|
{
|
||||||
using var _ = ImRaii.Tooltip();
|
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)
|
foreach (string note in dutyInfo.Notes)
|
||||||
ImGui.BulletText(note);
|
ImGui.BulletText(note);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user