Add quest battle notes

This commit is contained in:
Liza 2025-02-21 03:22:47 +01:00
parent a75286e927
commit 31eb121cf0
Signed by: liza
GPG Key ID: 2C41B84815CF6445
8 changed files with 110 additions and 30 deletions

View File

@ -126,6 +126,9 @@ internal static class QuestStepExtensions
Assignment(nameof(QuestStep.BossModEnabled),
step.BossModEnabled, emptyStep.BossModEnabled)
.AsSyntaxNodeOrToken(),
Assignment(nameof(QuestStep.BossModNotes),
step.BossModNotes, emptyStep.BossModNotes)
.AsSyntaxNodeOrToken(),
Assignment(nameof(QuestStep.SinglePlayerDutyIndex),
step.SinglePlayerDutyIndex, emptyStep.SinglePlayerDutyIndex)
.AsSyntaxNodeOrToken(),

View File

@ -103,7 +103,11 @@
"Z": 479.9724
},
"TerritoryId": 1053,
"InteractionType": "SinglePlayerDuty"
"InteractionType": "SinglePlayerDuty",
"BossModEnabled": false,
"BossModNotes": [
"Doesn't handle death properly"
]
}
]
},

View File

@ -61,7 +61,19 @@
"TerritoryId": 156,
"InteractionType": "Interact",
"AetheryteShortcut": "Mor Dhona",
"TargetTerritoryId": 351
"TargetTerritoryId": 351,
"SkipConditions": {
"AetheryteShortcutIf": {
"InTerritory": [
351
]
},
"StepIf": {
"InTerritory": [
351
]
}
}
},
{
"DataId": 1032081,
@ -73,13 +85,14 @@
"TerritoryId": 351,
"InteractionType": "SinglePlayerDuty",
"Comment": "Estinien vs. Arch Ultima",
"DialogueChoices": [
{
"Type": "YesNo",
"Prompt": "TEXT_LUCKMG110_03682_Q1_100_125",
"Yes": true
}
]
"BossModEnabled": false,
"BossModNotes": [
"AI doesn't move automatically for the first boss",
"AI doesn't move automatically for the dialogue with gaius on the bridge",
"After walking downstairs automatically, AI tries to run back towards the stairs (ignoring the arena boudnary)",
"After moving from the arena boundary, AI doesn't move into melee range and stops too far away when initially attacking"
],
"$.1": "This doesn't have a duty confirmation dialog, so we're treating TEXT_LUCKMG110_03682_Q1_100_125 as one"
}
]
},

View File

@ -1270,6 +1270,12 @@
"BossModEnabled": {
"type": "boolean"
},
"BossModNotes": {
"type": "array",
"items": {
"type": "string"
}
},
"SinglePlayerDutyIndex": {
"type": "integer",
"minimum": 0,

View File

@ -76,6 +76,7 @@ public sealed class QuestStep
public uint? ContentFinderConditionId { get; set; }
public bool AutoDutyEnabled { get; set; }
public bool BossModEnabled { get; set; }
public List<string> BossModNotes { get; set; } = [];
public byte SinglePlayerDutyIndex { get; set; }
public SkipConditions? SkipConditions { get; set; }

View File

@ -19,6 +19,7 @@ using Lumina.Excel.Sheets;
using Microsoft.Extensions.Logging;
using Questionable.Controller.Steps.Interactions;
using Questionable.Data;
using Questionable.External;
using Questionable.Functions;
using Questionable.Model;
using Questionable.Model.Gathering;
@ -45,6 +46,7 @@ internal sealed class InteractionUiController : IDisposable
private readonly ITargetManager _targetManager;
private readonly IClientState _clientState;
private readonly ShopController _shopController;
private readonly BossModIpc _bossModIpc;
private readonly ILogger<InteractionUiController> _logger;
private readonly Regex _returnRegex;
private readonly Regex _purchaseItemRegex;
@ -68,6 +70,7 @@ internal sealed class InteractionUiController : IDisposable
IPluginLog pluginLog,
IClientState clientState,
ShopController shopController,
BossModIpc bossModIpc,
ILogger<InteractionUiController> logger)
{
_addonLifecycle = addonLifecycle;
@ -85,6 +88,7 @@ internal sealed class InteractionUiController : IDisposable
_targetManager = targetManager;
_clientState = clientState;
_shopController = shopController;
_bossModIpc = bossModIpc;
_logger = logger;
_returnRegex = _dataManager.GetExcelSheet<Addon>().GetRow(196).GetRegex(addon => addon.Text, pluginLog)!;
@ -176,8 +180,11 @@ internal sealed class InteractionUiController : IDisposable
int? answer = HandleListChoice(actualPrompt, answers, checkAllSteps) ?? HandleInstanceListChoice(actualPrompt);
if (answer != null)
{
_logger.LogInformation("Using choice {Choice} for list prompt '{Prompt}'", answer, actualPrompt);
addonSelectString->AtkUnitBase.FireCallbackInt(answer.Value);
}
}
private unsafe void CutsceneSelectStringPostSetup(AddonEvent type, AddonArgs args)
{
@ -224,6 +231,7 @@ internal sealed class InteractionUiController : IDisposable
int? answer = HandleListChoice(actualPrompt, answers, checkAllSteps);
if (answer != null)
{
_logger.LogInformation("Using choice {Choice} for list prompt '{Prompt}'", answer, actualPrompt);
addonSelectIconString->AtkUnitBase.FireCallbackInt(answer.Value);
return;
}
@ -266,6 +274,7 @@ internal sealed class InteractionUiController : IDisposable
int questSelection = answers.FindIndex(x => GameFunctions.GameStringEquals(questName, x));
if (questSelection >= 0)
{
_logger.LogInformation("Selecting quest {QuestName}", questName);
addonSelectIconString->AtkUnitBase.FireCallbackInt(questSelection);
return true;
}
@ -655,13 +664,21 @@ internal sealed class InteractionUiController : IDisposable
continue;
}
_logger.LogInformation("Returning {YesNo} for '{Prompt}'", dialogueChoice.Yes ? "Yes" : "No", actualPrompt);
addonSelectYesno->AtkUnitBase.FireCallbackInt(dialogueChoice.Yes ? 0 : 1);
return true;
}
if (step is { InteractionType: EInteractionType.SinglePlayerDuty, BossModEnabled: true })
if (step is { InteractionType: EInteractionType.SinglePlayerDuty } &&
_bossModIpc.IsConfiguredToRunSoloInstance(quest.Id, step.SinglePlayerDutyIndex, step.BossModEnabled))
{
_logger.LogTrace("DefaultYesNo: probably Single Player Duty");
// Most of these are yes/no dialogs "Duty calls, ...".
//
// For 'Vows of Virtue, Deeds of Cruelty', there's no such dialog, and it just puts you into the instance
// 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);
return true;
}

View File

@ -84,6 +84,9 @@ internal sealed class BossModIpc
public bool IsConfiguredToRunSoloInstance(ElementId questId, byte dutyIndex, bool enabledByDefault)
{
if (!IsSupported())
return false;
if (!_configuration.SinglePlayerDuties.RunSoloInstancesWithBossMod)
return false;

View File

@ -40,12 +40,22 @@ internal sealed class SinglePlayerDutyConfigComponent : ConfigComponent
(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 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;
private ImmutableList<(string Label, List<SinglePlayerDutyInfo>)> _otherQuestBattles =
ImmutableList<(string Label, List<SinglePlayerDutyInfo>)>.Empty;
public SinglePlayerDutyConfigComponent(
IDalamudPluginInterface pluginInterface,
@ -122,7 +132,9 @@ internal sealed class SinglePlayerDutyConfigComponent : ConfigComponent
x.Step.SinglePlayerDutyIndex == index);
if (foundStep == default)
{
_logger.LogWarning("Disabling quest battle for quest {QuestId}, no battle with index {Index} found", questId, index);
_logger.LogWarning(
"Disabling quest battle for quest {QuestId}, no battle with index {Index} found", questId,
index);
enabled = false;
}
else
@ -156,7 +168,8 @@ internal sealed class SinglePlayerDutyConfigComponent : ConfigComponent
questInfo.SortKey,
questStep.SinglePlayerDutyIndex,
enabled,
questStep.BossModEnabled);
questStep.BossModEnabled,
questStep.BossModNotes);
if (cfcData.ContentFinderConditionId is 332 or 333 or 313 or 334)
startingCityBattles[EAetheryteLocation.Limsa].Add(dutyInfo);
@ -380,9 +393,9 @@ internal sealed class SinglePlayerDutyConfigComponent : ConfigComponent
? SupportedCfcOptions
: UnsupportedCfcOptions;
int value = 0;
if (Configuration.Duties.WhitelistedDutyCfcIds.Contains(dutyInfo.CfcId))
if (Configuration.SinglePlayerDuties.WhitelistedSinglePlayerDutyCfcIds.Contains(dutyInfo.CfcId))
value = 1;
if (Configuration.Duties.BlacklistedDutyCfcIds.Contains(dutyInfo.CfcId))
if (Configuration.SinglePlayerDuties.BlacklistedSinglePlayerDutyCfcIds.Contains(dutyInfo.CfcId))
value = 2;
if (ImGui.TableNextColumn())
@ -407,6 +420,25 @@ internal sealed class SinglePlayerDutyConfigComponent : ConfigComponent
ImGuiComponents.HelpMarker("Questionable doesn't include support for this quest.",
FontAwesomeIcon.Times, ImGuiColors.DalamudRed);
}
else if (dutyInfo.Notes.Count > 0)
{
using var color = new ImRaii.Color();
color.Push(ImGuiCol.TextDisabled, ImGuiColors.DalamudYellow);
ImGui.SameLine();
using (ImRaii.PushFont(UiBuilder.IconFont))
{
ImGui.TextDisabled(FontAwesomeIcon.ExclamationTriangle.ToIconString());
}
if (ImGui.IsItemHovered())
{
using var _ = ImRaii.Tooltip();
ImGui.TextColored(ImGuiColors.DalamudYellow, "While testing, the following issues have been found:");
foreach (string note in dutyInfo.Notes)
ImGui.BulletText(note);
}
}
}
if (ImGui.TableNextColumn())
@ -417,13 +449,13 @@ internal sealed class SinglePlayerDutyConfigComponent : ConfigComponent
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);
Configuration.SinglePlayerDuties.WhitelistedSinglePlayerDutyCfcIds.Remove(dutyInfo.CfcId);
Configuration.SinglePlayerDuties.BlacklistedSinglePlayerDutyCfcIds.Remove(dutyInfo.CfcId);
if (value == 1)
Configuration.Duties.WhitelistedDutyCfcIds.Add(dutyInfo.CfcId);
Configuration.SinglePlayerDuties.WhitelistedSinglePlayerDutyCfcIds.Add(dutyInfo.CfcId);
else if (value == 2)
Configuration.Duties.BlacklistedDutyCfcIds.Add(dutyInfo.CfcId);
Configuration.SinglePlayerDuties.BlacklistedSinglePlayerDutyCfcIds.Add(dutyInfo.CfcId);
Save();
}
@ -460,5 +492,6 @@ internal sealed class SinglePlayerDutyConfigComponent : ConfigComponent
ushort SortKey,
byte Index,
bool Enabled,
bool BossModEnabledByDefault);
bool BossModEnabledByDefault,
List<string> Notes);
}