forked from liza/Questionable
Second draft for auto-completing quest battles
This commit is contained in:
parent
92873554cc
commit
097c67ed5d
@ -274,7 +274,7 @@ public sealed class RendererPlugin : IDalamudPlugin
|
|||||||
locationOverride?.MaximumDistance ?? x.CalculateMaximumDistance(),
|
locationOverride?.MaximumDistance ?? x.CalculateMaximumDistance(),
|
||||||
minimumAngle, maximumAngle, color | 0xFF000000);
|
minimumAngle, maximumAngle, color | 0xFF000000);
|
||||||
|
|
||||||
drawList.AddText(x.Position, 0xFFFFFFFF, $"{location.Root.Groups.IndexOf(group)} // {node.DataId} / {node.Locations.IndexOf(x)} || {minimumAngle}, {maximumAngle}", 1f);
|
drawList.AddText(x.Position, isUnsaved ? 0xFFFF0000 : 0xFFFFFFFF, $"{location.Root.Groups.IndexOf(group)} // {node.DataId} / {node.Locations.IndexOf(x)} || {minimumAngle}, {maximumAngle}", 1f);
|
||||||
#if false
|
#if false
|
||||||
var a = GatheringMath.CalculateLandingLocation(x, 0, 0);
|
var a = GatheringMath.CalculateLandingLocation(x, 0, 0);
|
||||||
var b = GatheringMath.CalculateLandingLocation(x, 1, 1);
|
var b = GatheringMath.CalculateLandingLocation(x, 1, 1);
|
||||||
|
@ -126,6 +126,9 @@ internal static class QuestStepExtensions
|
|||||||
Assignment(nameof(QuestStep.BossModEnabled),
|
Assignment(nameof(QuestStep.BossModEnabled),
|
||||||
step.BossModEnabled, emptyStep.BossModEnabled)
|
step.BossModEnabled, emptyStep.BossModEnabled)
|
||||||
.AsSyntaxNodeOrToken(),
|
.AsSyntaxNodeOrToken(),
|
||||||
|
Assignment(nameof(QuestStep.SinglePlayerDutyIndex),
|
||||||
|
step.SinglePlayerDutyIndex, emptyStep.SinglePlayerDutyIndex)
|
||||||
|
.AsSyntaxNodeOrToken(),
|
||||||
Assignment(nameof(QuestStep.SkipConditions), step.SkipConditions,
|
Assignment(nameof(QuestStep.SkipConditions), step.SkipConditions,
|
||||||
emptyStep.SkipConditions)
|
emptyStep.SkipConditions)
|
||||||
.AsSyntaxNodeOrToken(),
|
.AsSyntaxNodeOrToken(),
|
||||||
|
@ -57,6 +57,7 @@
|
|||||||
},
|
},
|
||||||
"TerritoryId": 153,
|
"TerritoryId": 153,
|
||||||
"InteractionType": "SinglePlayerDuty",
|
"InteractionType": "SinglePlayerDuty",
|
||||||
|
"SinglePlayerDutyIndex": 1,
|
||||||
"Fly": true
|
"Fly": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -62,6 +62,7 @@
|
|||||||
},
|
},
|
||||||
"TerritoryId": 154,
|
"TerritoryId": 154,
|
||||||
"InteractionType": "SinglePlayerDuty",
|
"InteractionType": "SinglePlayerDuty",
|
||||||
|
"SinglePlayerDutyIndex": 1,
|
||||||
"AetheryteShortcut": "North Shroud - Fallgourd Float",
|
"AetheryteShortcut": "North Shroud - Fallgourd Float",
|
||||||
"Fly": true
|
"Fly": true
|
||||||
}
|
}
|
||||||
|
@ -119,7 +119,8 @@
|
|||||||
"Z": 29.06836
|
"Z": 29.06836
|
||||||
},
|
},
|
||||||
"TerritoryId": 152,
|
"TerritoryId": 152,
|
||||||
"InteractionType": "SinglePlayerDuty"
|
"InteractionType": "SinglePlayerDuty",
|
||||||
|
"SinglePlayerDutyIndex": 1
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -92,6 +92,7 @@
|
|||||||
},
|
},
|
||||||
"TerritoryId": 130,
|
"TerritoryId": 130,
|
||||||
"InteractionType": "SinglePlayerDuty",
|
"InteractionType": "SinglePlayerDuty",
|
||||||
|
"SinglePlayerDutyIndex": 1,
|
||||||
"AetheryteShortcut": "Ul'dah"
|
"AetheryteShortcut": "Ul'dah"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -96,7 +96,6 @@
|
|||||||
"TerritoryId": 138,
|
"TerritoryId": 138,
|
||||||
"InteractionType": "SinglePlayerDuty",
|
"InteractionType": "SinglePlayerDuty",
|
||||||
"Fly": true,
|
"Fly": true,
|
||||||
"ContentFinderConditionId": 393,
|
|
||||||
"BossModEnabled": true
|
"BossModEnabled": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -59,7 +59,6 @@
|
|||||||
},
|
},
|
||||||
"TerritoryId": 401,
|
"TerritoryId": 401,
|
||||||
"InteractionType": "SinglePlayerDuty",
|
"InteractionType": "SinglePlayerDuty",
|
||||||
"ContentFinderConditionId": 395,
|
|
||||||
"BossModEnabled": true
|
"BossModEnabled": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -79,7 +79,6 @@
|
|||||||
"[Ishgard] The Forgotten Knight",
|
"[Ishgard] The Forgotten Knight",
|
||||||
"[Ishgard] The Tribunal"
|
"[Ishgard] The Tribunal"
|
||||||
],
|
],
|
||||||
"ContentFinderConditionId": 396,
|
|
||||||
"BossModEnabled": true
|
"BossModEnabled": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -29,7 +29,6 @@
|
|||||||
},
|
},
|
||||||
"TerritoryId": 145,
|
"TerritoryId": 145,
|
||||||
"InteractionType": "SinglePlayerDuty",
|
"InteractionType": "SinglePlayerDuty",
|
||||||
"ContentFinderConditionId": 400,
|
|
||||||
"BossModEnabled": true
|
"BossModEnabled": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -79,7 +79,6 @@
|
|||||||
"TerritoryId": 397,
|
"TerritoryId": 397,
|
||||||
"InteractionType": "SinglePlayerDuty",
|
"InteractionType": "SinglePlayerDuty",
|
||||||
"DisableNavmesh": true,
|
"DisableNavmesh": true,
|
||||||
"ContentFinderConditionId": 397,
|
|
||||||
"BossModEnabled": true
|
"BossModEnabled": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -75,7 +75,6 @@
|
|||||||
},
|
},
|
||||||
"TerritoryId": 418,
|
"TerritoryId": 418,
|
||||||
"InteractionType": "SinglePlayerDuty",
|
"InteractionType": "SinglePlayerDuty",
|
||||||
"ContentFinderConditionId": 398,
|
|
||||||
"BossModEnabled": true
|
"BossModEnabled": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -57,7 +57,6 @@
|
|||||||
"InteractionType": "SinglePlayerDuty",
|
"InteractionType": "SinglePlayerDuty",
|
||||||
"Emote": "lookout",
|
"Emote": "lookout",
|
||||||
"StopDistance": 0.25,
|
"StopDistance": 0.25,
|
||||||
"ContentFinderConditionId": 401,
|
|
||||||
"BossModEnabled": true
|
"BossModEnabled": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -48,7 +48,6 @@
|
|||||||
"[Idyllshire] Aetheryte Plaza",
|
"[Idyllshire] Aetheryte Plaza",
|
||||||
"[Idyllshire] Epilogue Gate (Eastern Hinterlands)"
|
"[Idyllshire] Epilogue Gate (Eastern Hinterlands)"
|
||||||
],
|
],
|
||||||
"ContentFinderConditionId": 422,
|
|
||||||
"BossModEnabled": true
|
"BossModEnabled": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -69,7 +69,6 @@
|
|||||||
},
|
},
|
||||||
"TerritoryId": 402,
|
"TerritoryId": 402,
|
||||||
"InteractionType": "SinglePlayerDuty",
|
"InteractionType": "SinglePlayerDuty",
|
||||||
"ContentFinderConditionId": 399,
|
|
||||||
"BossModEnabled": true
|
"BossModEnabled": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -104,6 +104,7 @@
|
|||||||
"StopDistance": 5,
|
"StopDistance": 5,
|
||||||
"TerritoryId": 829,
|
"TerritoryId": 829,
|
||||||
"InteractionType": "SinglePlayerDuty",
|
"InteractionType": "SinglePlayerDuty",
|
||||||
|
"SinglePlayerDutyIndex": 1,
|
||||||
"DialogueChoices": [
|
"DialogueChoices": [
|
||||||
{
|
{
|
||||||
"Type": "List",
|
"Type": "List",
|
||||||
|
@ -1267,13 +1267,14 @@
|
|||||||
},
|
},
|
||||||
"then": {
|
"then": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"ContentFinderConditionId": {
|
|
||||||
"type": "integer",
|
|
||||||
"exclusiveMinimum": 0,
|
|
||||||
"exclusiveMaximum": 3000
|
|
||||||
},
|
|
||||||
"BossModEnabled": {
|
"BossModEnabled": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"SinglePlayerDutyIndex": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 1,
|
||||||
|
"description": "If a quest has multiple solo instances (which affects 5 quests total), indicates which one this is"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -91,6 +91,8 @@ public abstract class ElementId : IComparable<ElementId>, IEquatable<ElementId>
|
|||||||
|
|
||||||
public sealed class QuestId(ushort value) : ElementId(value)
|
public sealed class QuestId(ushort value) : ElementId(value)
|
||||||
{
|
{
|
||||||
|
public static QuestId FromRowId(uint rowId) => new((ushort)(rowId & 0xFFFF));
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return Value.ToString(CultureInfo.InvariantCulture);
|
return Value.ToString(CultureInfo.InvariantCulture);
|
||||||
|
@ -76,6 +76,7 @@ public sealed class QuestStep
|
|||||||
public uint? ContentFinderConditionId { get; set; }
|
public uint? ContentFinderConditionId { get; set; }
|
||||||
public bool AutoDutyEnabled { get; set; }
|
public bool AutoDutyEnabled { get; set; }
|
||||||
public bool BossModEnabled { get; set; }
|
public bool BossModEnabled { get; set; }
|
||||||
|
public byte SinglePlayerDutyIndex { get; set; }
|
||||||
public SkipConditions? SkipConditions { get; set; }
|
public SkipConditions? SkipConditions { get; set; }
|
||||||
|
|
||||||
public List<List<QuestWorkValue>?> RequiredQuestVariables { get; set; } = new();
|
public List<List<QuestWorkValue>?> RequiredQuestVariables { get; set; } = new();
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
"$id": "/liza/Questionable/raw/branch/master/Questionable.Model/common-aethernetshard.json",
|
"$id": "https://git.carvel.li/liza/Questionable/raw/branch/master/Questionable.Model/common-aethernetshard.json",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"[Gridania] Aetheryte Plaza",
|
"[Gridania] Aetheryte Plaza",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
"$id": "/liza/Questionable/raw/branch/master/Questionable.Model/common-aetheryte.json",
|
"$id": "https://git.carvel.li/liza/Questionable/raw/branch/master/Questionable.Model/common-aetheryte.json",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"Gridania",
|
"Gridania",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
"$id": "/liza/Questionable/raw/branch/master/Questionable.Model/common-classjob.json",
|
"$id": "https://git.carvel.li//liza/Questionable/raw/branch/master/Questionable.Model/common-classjob.json",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"Gladiator",
|
"Gladiator",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
"$id": "/liza/Questionable/raw/branch/master/Questionable.Model/common-completionflags.json",
|
"$id": "https://git.carvel.li//liza/Questionable/raw/branch/master/Questionable.Model/common-completionflags.json",
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"description": "Quest Variables that dictate whether or not this step is skipped: null is don't check, positive values need to be set, negative values need to be unset",
|
"description": "Quest Variables that dictate whether or not this step is skipped: null is don't check, positive values need to be set, negative values need to be unset",
|
||||||
"items": {
|
"items": {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
"$id": "/liza/Questionable/raw/branch/master/Questionable.Model/common-vector3.json",
|
"$id": "https://git.carvel.li//liza/Questionable/raw/branch/master/Questionable.Model/common-vector3.json",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"description": "Position in the world",
|
"description": "Position in the world",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
@ -14,6 +14,7 @@ internal sealed class Configuration : IPluginConfiguration
|
|||||||
public int PluginSetupCompleteVersion { get; set; }
|
public int PluginSetupCompleteVersion { get; set; }
|
||||||
public GeneralConfiguration General { get; } = new();
|
public GeneralConfiguration General { get; } = new();
|
||||||
public DutyConfiguration Duties { get; } = new();
|
public DutyConfiguration Duties { get; } = new();
|
||||||
|
public SoloDutyConfiguration SoloDuties { get; } = new();
|
||||||
public NotificationConfiguration Notifications { get; } = new();
|
public NotificationConfiguration Notifications { get; } = new();
|
||||||
public AdvancedConfiguration Advanced { get; } = new();
|
public AdvancedConfiguration Advanced { get; } = new();
|
||||||
public WindowConfig DebugWindowConfig { get; } = new();
|
public WindowConfig DebugWindowConfig { get; } = new();
|
||||||
@ -41,6 +42,13 @@ internal sealed class Configuration : IPluginConfiguration
|
|||||||
public HashSet<uint> BlacklistedDutyCfcIds { get; set; } = [];
|
public HashSet<uint> BlacklistedDutyCfcIds { get; set; } = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal sealed class SoloDutyConfiguration
|
||||||
|
{
|
||||||
|
public bool RunSoloInstancesWithBossMod { get; set; }
|
||||||
|
public HashSet<uint> WhitelistedSoloDutyCfcIds { get; set; } = [];
|
||||||
|
public HashSet<uint> BlacklistedSoloDutyCfcIds { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
internal sealed class NotificationConfiguration
|
internal sealed class NotificationConfiguration
|
||||||
{
|
{
|
||||||
public bool Enabled { get; set; } = true;
|
public bool Enabled { get; set; } = true;
|
||||||
|
@ -27,6 +27,7 @@ internal sealed class QuestRegistry
|
|||||||
private readonly JsonSchemaValidator _jsonSchemaValidator;
|
private readonly JsonSchemaValidator _jsonSchemaValidator;
|
||||||
private readonly ILogger<QuestRegistry> _logger;
|
private readonly ILogger<QuestRegistry> _logger;
|
||||||
private readonly LeveData _leveData;
|
private readonly LeveData _leveData;
|
||||||
|
private readonly TerritoryData _territoryData;
|
||||||
|
|
||||||
private readonly ICallGateProvider<object> _reloadDataIpc;
|
private readonly ICallGateProvider<object> _reloadDataIpc;
|
||||||
private readonly Dictionary<ElementId, Quest> _quests = [];
|
private readonly Dictionary<ElementId, Quest> _quests = [];
|
||||||
@ -34,7 +35,7 @@ internal sealed class QuestRegistry
|
|||||||
|
|
||||||
public QuestRegistry(IDalamudPluginInterface pluginInterface, QuestData questData,
|
public QuestRegistry(IDalamudPluginInterface pluginInterface, QuestData questData,
|
||||||
QuestValidator questValidator, JsonSchemaValidator jsonSchemaValidator,
|
QuestValidator questValidator, JsonSchemaValidator jsonSchemaValidator,
|
||||||
ILogger<QuestRegistry> logger, LeveData leveData)
|
ILogger<QuestRegistry> logger, LeveData leveData, TerritoryData territoryData)
|
||||||
{
|
{
|
||||||
_pluginInterface = pluginInterface;
|
_pluginInterface = pluginInterface;
|
||||||
_questData = questData;
|
_questData = questData;
|
||||||
@ -42,6 +43,7 @@ internal sealed class QuestRegistry
|
|||||||
_jsonSchemaValidator = jsonSchemaValidator;
|
_jsonSchemaValidator = jsonSchemaValidator;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_leveData = leveData;
|
_leveData = leveData;
|
||||||
|
_territoryData = territoryData;
|
||||||
_reloadDataIpc = _pluginInterface.GetIpcProvider<object>("Questionable.ReloadData");
|
_reloadDataIpc = _pluginInterface.GetIpcProvider<object>("Questionable.ReloadData");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,10 +152,15 @@ internal sealed class QuestRegistry
|
|||||||
foreach (var quest in _quests.Values)
|
foreach (var quest in _quests.Values)
|
||||||
{
|
{
|
||||||
foreach (var dutyStep in quest.AllSteps().Where(x =>
|
foreach (var dutyStep in quest.AllSteps().Where(x =>
|
||||||
x.Step.InteractionType is EInteractionType.Duty or EInteractionType.SinglePlayerDuty
|
x.Step.InteractionType is EInteractionType.Duty or EInteractionType.SinglePlayerDuty))
|
||||||
&& x.Step.ContentFinderConditionId != null))
|
|
||||||
{
|
{
|
||||||
_contentFinderConditionIds[dutyStep.Step.ContentFinderConditionId!.Value] = (quest.Id, dutyStep.Step);
|
if (dutyStep.Step is { InteractionType: EInteractionType.Duty, ContentFinderConditionId: not null })
|
||||||
|
_contentFinderConditionIds[dutyStep.Step.ContentFinderConditionId!.Value] =
|
||||||
|
(quest.Id, dutyStep.Step);
|
||||||
|
else if (dutyStep.Step.InteractionType == EInteractionType.SinglePlayerDuty &&
|
||||||
|
_territoryData.TryGetContentFinderConditionForSoloInstance(quest.Id,
|
||||||
|
dutyStep.Step.SinglePlayerDutyIndex, out var cfcData))
|
||||||
|
_contentFinderConditionIds[cfcData.ContentFinderConditionId] = (quest.Id, dutyStep.Step);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ internal static class SendNotification
|
|||||||
internal sealed class Factory(
|
internal sealed class Factory(
|
||||||
AutomatonIpc automatonIpc,
|
AutomatonIpc automatonIpc,
|
||||||
AutoDutyIpc autoDutyIpc,
|
AutoDutyIpc autoDutyIpc,
|
||||||
|
BossModIpc bossModIpc,
|
||||||
TerritoryData territoryData) : SimpleTaskFactory
|
TerritoryData territoryData) : SimpleTaskFactory
|
||||||
{
|
{
|
||||||
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
@ -26,7 +27,7 @@ internal static class SendNotification
|
|||||||
new Task(step.InteractionType, step.ContentFinderConditionId.HasValue
|
new Task(step.InteractionType, step.ContentFinderConditionId.HasValue
|
||||||
? territoryData.GetContentFinderCondition(step.ContentFinderConditionId.Value)?.Name
|
? territoryData.GetContentFinderCondition(step.ContentFinderConditionId.Value)?.Name
|
||||||
: step.Comment),
|
: step.Comment),
|
||||||
EInteractionType.SinglePlayerDuty when !step.BossModEnabled =>
|
EInteractionType.SinglePlayerDuty when !bossModIpc.IsConfiguredToRunSoloInstance(quest.Id, step.SinglePlayerDutyIndex, step.BossModEnabled) =>
|
||||||
new Task(step.InteractionType, quest.Info.Name),
|
new Task(step.InteractionType, quest.Info.Name),
|
||||||
_ => null,
|
_ => null,
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
using Questionable.Controller.Steps.Shared;
|
using Questionable.Controller.Steps.Shared;
|
||||||
using Questionable.Data;
|
using Questionable.Data;
|
||||||
using Questionable.External;
|
using Questionable.External;
|
||||||
@ -11,20 +12,23 @@ namespace Questionable.Controller.Steps.Interactions;
|
|||||||
|
|
||||||
internal static class SinglePlayerDuty
|
internal static class SinglePlayerDuty
|
||||||
{
|
{
|
||||||
internal sealed class Factory : ITaskFactory
|
internal sealed class Factory(
|
||||||
|
BossModIpc bossModIpc,
|
||||||
|
TerritoryData territoryData) : ITaskFactory
|
||||||
{
|
{
|
||||||
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
{
|
{
|
||||||
if (step.InteractionType != EInteractionType.SinglePlayerDuty)
|
if (step.InteractionType != EInteractionType.SinglePlayerDuty)
|
||||||
yield break;
|
yield break;
|
||||||
|
|
||||||
if (step.BossModEnabled)
|
if (bossModIpc.IsConfiguredToRunSoloInstance(quest.Id, step.SinglePlayerDutyIndex, step.BossModEnabled))
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(step.ContentFinderConditionId);
|
if (!territoryData.TryGetContentFinderConditionForSoloInstance(quest.Id, step.SinglePlayerDutyIndex, out var cfcData))
|
||||||
|
throw new TaskException("Failed to get content finder condition for solo instance");
|
||||||
|
|
||||||
yield return new StartSinglePlayerDuty(step.ContentFinderConditionId.Value);
|
yield return new StartSinglePlayerDuty(cfcData.ContentFinderConditionId);
|
||||||
yield return new EnableAi();
|
yield return new EnableAi();
|
||||||
yield return new WaitSinglePlayerDuty(step.ContentFinderConditionId.Value);
|
yield return new WaitSinglePlayerDuty(cfcData.ContentFinderConditionId);
|
||||||
yield return new DisableAi();
|
yield return new DisableAi();
|
||||||
yield return new WaitAtEnd.WaitNextStepOrSequence();
|
yield return new WaitAtEnd.WaitNextStepOrSequence();
|
||||||
}
|
}
|
||||||
@ -36,19 +40,13 @@ internal static class SinglePlayerDuty
|
|||||||
public override string ToString() => $"Wait(BossMod, entered instance {ContentFinderConditionId})";
|
public override string ToString() => $"Wait(BossMod, entered instance {ContentFinderConditionId})";
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class StartSinglePlayerDutyExecutor(
|
internal sealed class StartSinglePlayerDutyExecutor : TaskExecutor<StartSinglePlayerDuty>
|
||||||
TerritoryData territoryData,
|
|
||||||
IClientState clientState) : TaskExecutor<StartSinglePlayerDuty>
|
|
||||||
{
|
{
|
||||||
protected override bool Start() => true;
|
protected override bool Start() => true;
|
||||||
|
|
||||||
public override ETaskResult Update()
|
public override unsafe ETaskResult Update()
|
||||||
{
|
{
|
||||||
if (!territoryData.TryGetContentFinderCondition(Task.ContentFinderConditionId,
|
return GameMain.Instance()->CurrentContentFinderConditionId == Task.ContentFinderConditionId
|
||||||
out var cfcData))
|
|
||||||
throw new TaskException("Failed to get territory ID for content finder condition");
|
|
||||||
|
|
||||||
return clientState.TerritoryType == cfcData.TerritoryId
|
|
||||||
? ETaskResult.TaskComplete
|
? ETaskResult.TaskComplete
|
||||||
: ETaskResult.StillRunning;
|
: ETaskResult.StillRunning;
|
||||||
}
|
}
|
||||||
@ -81,19 +79,13 @@ internal static class SinglePlayerDuty
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class WaitSinglePlayerDutyExecutor(
|
internal sealed class WaitSinglePlayerDutyExecutor(
|
||||||
TerritoryData territoryData,
|
|
||||||
IClientState clientState,
|
|
||||||
BossModIpc bossModIpc) : TaskExecutor<WaitSinglePlayerDuty>, IStoppableTaskExecutor
|
BossModIpc bossModIpc) : TaskExecutor<WaitSinglePlayerDuty>, IStoppableTaskExecutor
|
||||||
{
|
{
|
||||||
protected override bool Start() => true;
|
protected override bool Start() => true;
|
||||||
|
|
||||||
public override ETaskResult Update()
|
public override unsafe ETaskResult Update()
|
||||||
{
|
{
|
||||||
if (!territoryData.TryGetContentFinderCondition(Task.ContentFinderConditionId,
|
return GameMain.Instance()->CurrentContentFinderConditionId != Task.ContentFinderConditionId
|
||||||
out var cfcData))
|
|
||||||
throw new TaskException("Failed to get territory ID for content finder condition");
|
|
||||||
|
|
||||||
return clientState.TerritoryType != cfcData.TerritoryId
|
|
||||||
? ETaskResult.TaskComplete
|
? ETaskResult.TaskComplete
|
||||||
: ETaskResult.StillRunning;
|
: ETaskResult.StillRunning;
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,8 @@ internal static class WaitAtEnd
|
|||||||
IClientState clientState,
|
IClientState clientState,
|
||||||
ICondition condition,
|
ICondition condition,
|
||||||
TerritoryData territoryData,
|
TerritoryData territoryData,
|
||||||
AutoDutyIpc autoDutyIpc)
|
AutoDutyIpc autoDutyIpc,
|
||||||
|
BossModIpc bossModIpc)
|
||||||
: ITaskFactory
|
: ITaskFactory
|
||||||
{
|
{
|
||||||
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
|
||||||
@ -53,7 +54,7 @@ internal static class WaitAtEnd
|
|||||||
return [new WaitNextStepOrSequence()];
|
return [new WaitNextStepOrSequence()];
|
||||||
|
|
||||||
case EInteractionType.Duty when !autoDutyIpc.IsConfiguredToRunContent(step.ContentFinderConditionId, step.AutoDutyEnabled):
|
case EInteractionType.Duty when !autoDutyIpc.IsConfiguredToRunContent(step.ContentFinderConditionId, step.AutoDutyEnabled):
|
||||||
case EInteractionType.SinglePlayerDuty when !step.BossModEnabled:
|
case EInteractionType.SinglePlayerDuty when !bossModIpc.IsConfiguredToRunSoloInstance(quest.Id, step.SinglePlayerDutyIndex, step.BossModEnabled):
|
||||||
return [new EndAutomation()];
|
return [new EndAutomation()];
|
||||||
|
|
||||||
case EInteractionType.WalkTo:
|
case EInteractionType.WalkTo:
|
||||||
|
@ -23,17 +23,17 @@ internal sealed class JournalData
|
|||||||
var genreLimsa = new Genre(uint.MaxValue - 3, "Starting in Limsa Lominsa", 1,
|
var genreLimsa = new Genre(uint.MaxValue - 3, "Starting in Limsa Lominsa", 1,
|
||||||
new uint[] { 108, 109 }.Concat(limsaStart.QuestRedoParam.Select(x => x.Quest.RowId))
|
new uint[] { 108, 109 }.Concat(limsaStart.QuestRedoParam.Select(x => x.Quest.RowId))
|
||||||
.Where(x => x != 0)
|
.Where(x => x != 0)
|
||||||
.Select(x => questData.GetQuestInfo(new QuestId((ushort)(x & 0xFFFF))))
|
.Select(x => questData.GetQuestInfo(QuestId.FromRowId(x)))
|
||||||
.ToList());
|
.ToList());
|
||||||
var genreGridania = new Genre(uint.MaxValue - 2, "Starting in Gridania", 1,
|
var genreGridania = new Genre(uint.MaxValue - 2, "Starting in Gridania", 1,
|
||||||
new uint[] { 85, 123, 124 }.Concat(gridaniaStart.QuestRedoParam.Select(x => x.Quest.RowId))
|
new uint[] { 85, 123, 124 }.Concat(gridaniaStart.QuestRedoParam.Select(x => x.Quest.RowId))
|
||||||
.Where(x => x != 0)
|
.Where(x => x != 0)
|
||||||
.Select(x => questData.GetQuestInfo(new QuestId((ushort)(x & 0xFFFF))))
|
.Select(x => questData.GetQuestInfo(QuestId.FromRowId(x)))
|
||||||
.ToList());
|
.ToList());
|
||||||
var genreUldah = new Genre(uint.MaxValue - 1, "Starting in Ul'dah", 1,
|
var genreUldah = new Genre(uint.MaxValue - 1, "Starting in Ul'dah", 1,
|
||||||
new uint[] { 568, 569, 570 }.Concat(uldahStart.QuestRedoParam.Select(x => x.Quest.RowId))
|
new uint[] { 568, 569, 570 }.Concat(uldahStart.QuestRedoParam.Select(x => x.Quest.RowId))
|
||||||
.Where(x => x != 0)
|
.Where(x => x != 0)
|
||||||
.Select(x => questData.GetQuestInfo(new QuestId((ushort)(x & 0xFFFF))))
|
.Select(x => questData.GetQuestInfo(QuestId.FromRowId(x)))
|
||||||
.ToList());
|
.ToList());
|
||||||
genres.InsertRange(0, [genreLimsa, genreGridania, genreUldah]);
|
genres.InsertRange(0, [genreLimsa, genreGridania, genreUldah]);
|
||||||
genres.Single(x => x.Id == 1)
|
genres.Single(x => x.Id == 1)
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
@ -7,6 +8,7 @@ using Dalamud.Game;
|
|||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
using Lumina.Excel.Sheets;
|
using Lumina.Excel.Sheets;
|
||||||
|
using Questionable.Model.Questing;
|
||||||
|
|
||||||
namespace Questionable.Data;
|
namespace Questionable.Data;
|
||||||
|
|
||||||
@ -17,6 +19,7 @@ internal sealed class TerritoryData
|
|||||||
private readonly ImmutableDictionary<ushort, uint> _dutyTerritories;
|
private readonly ImmutableDictionary<ushort, uint> _dutyTerritories;
|
||||||
private readonly ImmutableDictionary<uint, string> _instanceNames;
|
private readonly ImmutableDictionary<uint, string> _instanceNames;
|
||||||
private readonly ImmutableDictionary<uint, ContentFinderConditionData> _contentFinderConditions;
|
private readonly ImmutableDictionary<uint, ContentFinderConditionData> _contentFinderConditions;
|
||||||
|
private readonly ImmutableDictionary<(ElementId QuestId, byte Index), uint> _questsToCfc;
|
||||||
|
|
||||||
public TerritoryData(IDataManager dataManager)
|
public TerritoryData(IDataManager dataManager)
|
||||||
{
|
{
|
||||||
@ -48,6 +51,13 @@ internal sealed class TerritoryData
|
|||||||
.Where(x => x.RowId > 0 && x.Content.RowId != 0 && x.ContentLinkType is 1 or 5 && x.ContentType.RowId != 6)
|
.Where(x => x.RowId > 0 && x.Content.RowId != 0 && x.ContentLinkType is 1 or 5 && x.ContentType.RowId != 6)
|
||||||
.Select(x => new ContentFinderConditionData(x, dataManager.Language))
|
.Select(x => new ContentFinderConditionData(x, dataManager.Language))
|
||||||
.ToImmutableDictionary(x => x.ContentFinderConditionId, x => x);
|
.ToImmutableDictionary(x => x.ContentFinderConditionId, x => x);
|
||||||
|
|
||||||
|
_questsToCfc = dataManager.GetExcelSheet<Quest>()
|
||||||
|
.Where(x => x is { RowId: > 0, IssuerLocation.RowId: > 0 })
|
||||||
|
.SelectMany(GetQuestBattles)
|
||||||
|
.Select(x => (x.QuestId, x.Index,
|
||||||
|
CfcId: LookupContentFinderConditionForQuestBattle(dataManager, x.QuestBattleId)))
|
||||||
|
.ToImmutableDictionary(x => (x.QuestId, x.Index), x => x.CfcId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string? GetName(ushort territoryId) => _territoryNames.GetValueOrDefault(territoryId);
|
public string? GetName(ushort territoryId) => _territoryNames.GetValueOrDefault(territoryId);
|
||||||
@ -77,6 +87,18 @@ internal sealed class TerritoryData
|
|||||||
[NotNullWhen(true)] out ContentFinderConditionData? contentFinderConditionData) =>
|
[NotNullWhen(true)] out ContentFinderConditionData? contentFinderConditionData) =>
|
||||||
_contentFinderConditions.TryGetValue(cfcId, out contentFinderConditionData);
|
_contentFinderConditions.TryGetValue(cfcId, out contentFinderConditionData);
|
||||||
|
|
||||||
|
public bool TryGetContentFinderConditionForSoloInstance(ElementId questId, byte index,
|
||||||
|
[NotNullWhen(true)] out ContentFinderConditionData? contentFinderConditionData)
|
||||||
|
{
|
||||||
|
if (_questsToCfc.TryGetValue((questId, index), out uint cfcId))
|
||||||
|
return _contentFinderConditions.TryGetValue(cfcId, out contentFinderConditionData);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
contentFinderConditionData = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static string FixName(string name, ClientLanguage language)
|
private static string FixName(string name, ClientLanguage language)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(name) || language != ClientLanguage.English)
|
if (string.IsNullOrEmpty(name) || language != ClientLanguage.English)
|
||||||
@ -85,6 +107,27 @@ internal sealed class TerritoryData
|
|||||||
return string.Concat(name[0].ToString().ToUpper(CultureInfo.InvariantCulture), name.AsSpan(1));
|
return string.Concat(name[0].ToString().ToUpper(CultureInfo.InvariantCulture), name.AsSpan(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<(ElementId QuestId, byte Index, uint QuestBattleId)> GetQuestBattles(Quest quest)
|
||||||
|
{
|
||||||
|
foreach (Quest.QuestParamsStruct t in quest.QuestParams)
|
||||||
|
{
|
||||||
|
if (t.ScriptInstruction == "QUESTBATTLE0")
|
||||||
|
yield return (QuestId.FromRowId(quest.RowId), 0, t.ScriptArg);
|
||||||
|
else if (t.ScriptInstruction == "QUESTBATTLE1")
|
||||||
|
yield return (QuestId.FromRowId(quest.RowId), 1, t.ScriptArg);
|
||||||
|
else if (t.ScriptInstruction.IsEmpty)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static uint LookupContentFinderConditionForQuestBattle(IDataManager dataManager, uint questBattleId)
|
||||||
|
{
|
||||||
|
if (questBattleId >= 5000)
|
||||||
|
return dataManager.GetExcelSheet<InstanceContent>().GetRow(questBattleId).Order;
|
||||||
|
else
|
||||||
|
return dataManager.GetExcelSheet<QuestBattleResident>().GetRow(questBattleId).Unknown0;
|
||||||
|
}
|
||||||
|
|
||||||
public sealed record ContentFinderConditionData(
|
public sealed record ContentFinderConditionData(
|
||||||
uint ContentFinderConditionId,
|
uint ContentFinderConditionId,
|
||||||
string Name,
|
string Name,
|
||||||
|
4
Questionable/External/AutoDutyIpc.cs
vendored
4
Questionable/External/AutoDutyIpc.cs
vendored
@ -31,7 +31,7 @@ internal sealed class AutoDutyIpc
|
|||||||
_stop = pluginInterface.GetIpcSubscriber<object>("AutoDuty.Stop");
|
_stop = pluginInterface.GetIpcSubscriber<object>("AutoDuty.Stop");
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsConfiguredToRunContent(uint? cfcId, bool autoDutyEnabled)
|
public bool IsConfiguredToRunContent(uint? cfcId, bool enabledByDefault)
|
||||||
{
|
{
|
||||||
if (cfcId == null)
|
if (cfcId == null)
|
||||||
return false;
|
return false;
|
||||||
@ -46,7 +46,7 @@ internal sealed class AutoDutyIpc
|
|||||||
_territoryData.TryGetContentFinderCondition(cfcId.Value, out _))
|
_territoryData.TryGetContentFinderCondition(cfcId.Value, out _))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
return autoDutyEnabled && HasPath(cfcId.Value);
|
return enabledByDefault && HasPath(cfcId.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool HasPath(uint cfcId)
|
public bool HasPath(uint cfcId)
|
||||||
|
32
Questionable/External/BossModIpc.cs
vendored
32
Questionable/External/BossModIpc.cs
vendored
@ -2,22 +2,33 @@ using Dalamud.Plugin;
|
|||||||
using Dalamud.Plugin.Ipc;
|
using Dalamud.Plugin.Ipc;
|
||||||
using Dalamud.Plugin.Ipc.Exceptions;
|
using Dalamud.Plugin.Ipc.Exceptions;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
|
using Questionable.Data;
|
||||||
|
using Questionable.Model.Questing;
|
||||||
|
|
||||||
namespace Questionable.External;
|
namespace Questionable.External;
|
||||||
|
|
||||||
internal sealed class BossModIpc
|
internal sealed class BossModIpc
|
||||||
{
|
{
|
||||||
private readonly ICommandManager _commandManager;
|
|
||||||
private const string Name = "BossMod";
|
private const string Name = "BossMod";
|
||||||
|
|
||||||
|
private readonly Configuration _configuration;
|
||||||
|
private readonly ICommandManager _commandManager;
|
||||||
|
private readonly TerritoryData _territoryData;
|
||||||
private readonly ICallGateSubscriber<string, string?> _getPreset;
|
private readonly ICallGateSubscriber<string, string?> _getPreset;
|
||||||
private readonly ICallGateSubscriber<string, bool, bool> _createPreset;
|
private readonly ICallGateSubscriber<string, bool, bool> _createPreset;
|
||||||
private readonly ICallGateSubscriber<string, bool> _setPreset;
|
private readonly ICallGateSubscriber<string, bool> _setPreset;
|
||||||
private readonly ICallGateSubscriber<bool> _clearPreset;
|
private readonly ICallGateSubscriber<bool> _clearPreset;
|
||||||
|
|
||||||
public BossModIpc(IDalamudPluginInterface pluginInterface, ICommandManager commandManager)
|
public BossModIpc(
|
||||||
|
IDalamudPluginInterface pluginInterface,
|
||||||
|
Configuration configuration,
|
||||||
|
ICommandManager commandManager,
|
||||||
|
TerritoryData territoryData)
|
||||||
{
|
{
|
||||||
|
_configuration = configuration;
|
||||||
_commandManager = commandManager;
|
_commandManager = commandManager;
|
||||||
|
_territoryData = territoryData;
|
||||||
|
|
||||||
_getPreset = pluginInterface.GetIpcSubscriber<string, string?>($"{Name}.Presets.Get");
|
_getPreset = pluginInterface.GetIpcSubscriber<string, string?>($"{Name}.Presets.Get");
|
||||||
_createPreset = pluginInterface.GetIpcSubscriber<string, bool, bool>($"{Name}.Presets.Create");
|
_createPreset = pluginInterface.GetIpcSubscriber<string, bool, bool>($"{Name}.Presets.Create");
|
||||||
_setPreset = pluginInterface.GetIpcSubscriber<string, bool>($"{Name}.Presets.SetActive");
|
_setPreset = pluginInterface.GetIpcSubscriber<string, bool>($"{Name}.Presets.SetActive");
|
||||||
@ -70,4 +81,21 @@ internal sealed class BossModIpc
|
|||||||
_commandManager.ProcessCommand("/vbm cfg ZoneModuleConfig EnableQuestBattles false");
|
_commandManager.ProcessCommand("/vbm cfg ZoneModuleConfig EnableQuestBattles false");
|
||||||
ClearPreset();
|
ClearPreset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsConfiguredToRunSoloInstance(ElementId questId, byte dutyIndex, bool enabledByDefault)
|
||||||
|
{
|
||||||
|
if (!_configuration.SoloDuties.RunSoloInstancesWithBossMod)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!_territoryData.TryGetContentFinderConditionForSoloInstance(questId, dutyIndex, out var cfcData))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (_configuration.SoloDuties.BlacklistedSoloDutyCfcIds.Contains(cfcData.ContentFinderConditionId))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (_configuration.SoloDuties.WhitelistedSoloDutyCfcIds.Contains(cfcData.ContentFinderConditionId))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return enabledByDefault;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
5
Questionable/External/QuestionableIpc.cs
vendored
5
Questionable/External/QuestionableIpc.cs
vendored
@ -41,10 +41,10 @@ internal sealed class QuestionableIpc : IDisposable
|
|||||||
eventInfoComponent.GetCurrentlyActiveEventQuests().Select(q => q.ToString()).ToList());
|
eventInfoComponent.GetCurrentlyActiveEventQuests().Select(q => q.ToString()).ToList());
|
||||||
|
|
||||||
_startQuest = pluginInterface.GetIpcProvider<string, bool>(IpcStartQuest);
|
_startQuest = pluginInterface.GetIpcProvider<string, bool>(IpcStartQuest);
|
||||||
_startQuest.RegisterFunc((string questId) => StartQuest(questController, questRegistry, questId, false));
|
_startQuest.RegisterFunc((questId) => StartQuest(questController, questRegistry, questId, false));
|
||||||
|
|
||||||
_startSingleQuest = pluginInterface.GetIpcProvider<string, bool>(IpcStartSingleQuest);
|
_startSingleQuest = pluginInterface.GetIpcProvider<string, bool>(IpcStartSingleQuest);
|
||||||
_startSingleQuest.RegisterFunc((string questId) => StartQuest(questController, questRegistry, questId, true));
|
_startSingleQuest.RegisterFunc((questId) => StartQuest(questController, questRegistry, questId, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool StartQuest(QuestController qc, QuestRegistry qr, string questId, bool single)
|
private static bool StartQuest(QuestController qc, QuestRegistry qr, string questId, bool single)
|
||||||
@ -63,6 +63,7 @@ internal sealed class QuestionableIpc : IDisposable
|
|||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
_startSingleQuest.UnregisterFunc();
|
||||||
_startQuest.UnregisterFunc();
|
_startQuest.UnregisterFunc();
|
||||||
_getCurrentlyActiveEventQuests.UnregisterFunc();
|
_getCurrentlyActiveEventQuests.UnregisterFunc();
|
||||||
_getCurrentQuestId.UnregisterFunc();
|
_getCurrentQuestId.UnregisterFunc();
|
||||||
|
@ -7,6 +7,7 @@ using Lumina.Excel.Sheets;
|
|||||||
using Questionable.Model.Questing;
|
using Questionable.Model.Questing;
|
||||||
using ExcelQuest = Lumina.Excel.Sheets.Quest;
|
using ExcelQuest = Lumina.Excel.Sheets.Quest;
|
||||||
using GrandCompany = FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany;
|
using GrandCompany = FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany;
|
||||||
|
using QQuestId = Questionable.Model.Questing.QuestId;
|
||||||
|
|
||||||
namespace Questionable.Model;
|
namespace Questionable.Model;
|
||||||
|
|
||||||
@ -14,7 +15,7 @@ internal sealed class QuestInfo : IQuestInfo
|
|||||||
{
|
{
|
||||||
public QuestInfo(ExcelQuest quest, uint newGamePlusChapter, byte startingCity, JournalGenreOverrides journalGenreOverrides)
|
public QuestInfo(ExcelQuest quest, uint newGamePlusChapter, byte startingCity, JournalGenreOverrides journalGenreOverrides)
|
||||||
{
|
{
|
||||||
QuestId = new QuestId((ushort)(quest.RowId & 0xFFFF));
|
QuestId = QQuestId.FromRowId(quest.RowId);
|
||||||
|
|
||||||
string suffix = QuestId.Value switch
|
string suffix = QuestId.Value switch
|
||||||
{
|
{
|
||||||
@ -41,15 +42,15 @@ internal sealed class QuestInfo : IQuestInfo
|
|||||||
PreviousQuests =
|
PreviousQuests =
|
||||||
new List<PreviousQuestInfo>
|
new List<PreviousQuestInfo>
|
||||||
{
|
{
|
||||||
new(ReplaceOldQuestIds((ushort)(quest.PreviousQuest[0].RowId & 0xFFFF)), quest.Unknown7),
|
new(ReplaceOldQuestIds(QQuestId.FromRowId(quest.PreviousQuest[0].RowId)), quest.Unknown7),
|
||||||
new(ReplaceOldQuestIds((ushort)(quest.PreviousQuest[1].RowId & 0xFFFF))),
|
new(ReplaceOldQuestIds(QQuestId.FromRowId(quest.PreviousQuest[1].RowId))),
|
||||||
new(ReplaceOldQuestIds((ushort)(quest.PreviousQuest[2].RowId & 0xFFFF)))
|
new(ReplaceOldQuestIds(QQuestId.FromRowId(quest.PreviousQuest[2].RowId)))
|
||||||
}
|
}
|
||||||
.Where(x => x.QuestId.Value != 0)
|
.Where(x => x.QuestId.Value != 0)
|
||||||
.ToImmutableList();
|
.ToImmutableList();
|
||||||
PreviousQuestJoin = (EQuestJoin)quest.PreviousQuestJoin;
|
PreviousQuestJoin = (EQuestJoin)quest.PreviousQuestJoin;
|
||||||
QuestLocks = quest.QuestLock
|
QuestLocks = quest.QuestLock
|
||||||
.Select(x => new QuestId((ushort)(x.RowId & 0xFFFFF)))
|
.Select(x => QQuestId.FromRowId(x.RowId))
|
||||||
.Where(x => x.Value != 0)
|
.Where(x => x.Value != 0)
|
||||||
.ToImmutableList();
|
.ToImmutableList();
|
||||||
QuestLockJoin = (EQuestJoin)quest.QuestLockJoin;
|
QuestLockJoin = (EQuestJoin)quest.QuestLockJoin;
|
||||||
@ -85,13 +86,13 @@ internal sealed class QuestInfo : IQuestInfo
|
|||||||
Expansion = (EExpansionVersion)quest.Expansion.RowId;
|
Expansion = (EExpansionVersion)quest.Expansion.RowId;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static QuestId ReplaceOldQuestIds(ushort questId)
|
private static QuestId ReplaceOldQuestIds(QuestId questId)
|
||||||
{
|
{
|
||||||
return new QuestId(questId switch
|
return questId.Value switch
|
||||||
{
|
{
|
||||||
524 => 4522,
|
524 => new QuestId(4522),
|
||||||
_ => questId,
|
_ => questId,
|
||||||
});
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public ElementId QuestId { get; }
|
public ElementId QuestId { get; }
|
||||||
|
@ -3,6 +3,7 @@ using System.Collections.Immutable;
|
|||||||
using LLib.GameData;
|
using LLib.GameData;
|
||||||
using Lumina.Excel.Sheets;
|
using Lumina.Excel.Sheets;
|
||||||
using Questionable.Model.Questing;
|
using Questionable.Model.Questing;
|
||||||
|
using QQuestId = Questionable.Model.Questing.QuestId;
|
||||||
|
|
||||||
namespace Questionable.Model;
|
namespace Questionable.Model;
|
||||||
|
|
||||||
@ -16,7 +17,7 @@ internal sealed class SatisfactionSupplyInfo : IQuestInfo
|
|||||||
Level = npc.LevelUnlock;
|
Level = npc.LevelUnlock;
|
||||||
SortKey = QuestId.Value;
|
SortKey = QuestId.Value;
|
||||||
Expansion = (EExpansionVersion)npc.QuestRequired.Value.Expansion.RowId;
|
Expansion = (EExpansionVersion)npc.QuestRequired.Value.Expansion.RowId;
|
||||||
PreviousQuests = [new PreviousQuestInfo(new QuestId((ushort)(npc.QuestRequired.RowId & 0xFFFF)))];
|
PreviousQuests = [new PreviousQuestInfo(QQuestId.FromRowId(npc.QuestRequired.RowId))];
|
||||||
}
|
}
|
||||||
|
|
||||||
public ElementId QuestId { get; }
|
public ElementId QuestId { get; }
|
||||||
|
@ -311,6 +311,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
|
|||||||
serviceCollection.AddSingleton<IQuestValidator, AethernetShortcutValidator>();
|
serviceCollection.AddSingleton<IQuestValidator, AethernetShortcutValidator>();
|
||||||
serviceCollection.AddSingleton<IQuestValidator, DialogueChoiceValidator>();
|
serviceCollection.AddSingleton<IQuestValidator, DialogueChoiceValidator>();
|
||||||
serviceCollection.AddSingleton<IQuestValidator, ClassQuestShouldHaveShortcutValidator>();
|
serviceCollection.AddSingleton<IQuestValidator, ClassQuestShouldHaveShortcutValidator>();
|
||||||
|
serviceCollection.AddSingleton<IQuestValidator, UniqueSinglePlayerInstanceValidator>();
|
||||||
serviceCollection.AddSingleton<JsonSchemaValidator>();
|
serviceCollection.AddSingleton<JsonSchemaValidator>();
|
||||||
serviceCollection.AddSingleton<IQuestValidator>(sp => sp.GetRequiredService<JsonSchemaValidator>());
|
serviceCollection.AddSingleton<IQuestValidator>(sp => sp.GetRequiredService<JsonSchemaValidator>());
|
||||||
}
|
}
|
||||||
|
@ -18,4 +18,5 @@ public enum EIssueType
|
|||||||
InvalidAethernetShortcut,
|
InvalidAethernetShortcut,
|
||||||
InvalidExcelRef,
|
InvalidExcelRef,
|
||||||
ClassQuestWithoutAetheryteShortcut,
|
ClassQuestWithoutAetheryteShortcut,
|
||||||
|
DuplicateSinglePlayerInstance,
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Questionable.Model;
|
||||||
|
using Questionable.Model.Questing;
|
||||||
|
|
||||||
|
namespace Questionable.Validation.Validators;
|
||||||
|
|
||||||
|
internal sealed class UniqueSinglePlayerInstanceValidator : IQuestValidator
|
||||||
|
{
|
||||||
|
public IEnumerable<ValidationIssue> Validate(Quest quest)
|
||||||
|
{
|
||||||
|
var singlePlayerInstances = quest.AllSteps()
|
||||||
|
.Where(x => x.Step.InteractionType == EInteractionType.SinglePlayerDuty)
|
||||||
|
.Select(x => (x.Sequence, x.StepId, x.Step.SinglePlayerDutyIndex))
|
||||||
|
.ToList();
|
||||||
|
if (singlePlayerInstances.DistinctBy(x => x.SinglePlayerDutyIndex).Count() < singlePlayerInstances.Count)
|
||||||
|
{
|
||||||
|
foreach (var singlePlayerInstance in singlePlayerInstances)
|
||||||
|
{
|
||||||
|
yield return new ValidationIssue
|
||||||
|
{
|
||||||
|
ElementId = quest.Id,
|
||||||
|
Sequence = (byte)singlePlayerInstance.Sequence.Sequence,
|
||||||
|
Step = singlePlayerInstance.StepId,
|
||||||
|
Type = EIssueType.DuplicateSinglePlayerInstance,
|
||||||
|
Severity = EIssueSeverity.Error,
|
||||||
|
Description = $"Duplicate singleplayer duty index: {singlePlayerInstance.SinglePlayerDutyIndex}",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -119,7 +119,7 @@ internal sealed class QuestSelectionWindow : LWindow
|
|||||||
|
|
||||||
foreach (var unacceptedQuest in Map.Instance()->UnacceptedQuestMarkers)
|
foreach (var unacceptedQuest in Map.Instance()->UnacceptedQuestMarkers)
|
||||||
{
|
{
|
||||||
QuestId questId = new QuestId((ushort)(unacceptedQuest.ObjectiveId & 0xFFFF));
|
QuestId questId = QuestId.FromRowId(unacceptedQuest.ObjectiveId);
|
||||||
if (_quests.All(q => q.QuestId != questId))
|
if (_quests.All(q => q.QuestId != questId))
|
||||||
_quests.Add(_questData.GetQuestInfo(questId));
|
_quests.Add(_questData.GetQuestInfo(questId));
|
||||||
}
|
}
|
||||||
|
7
global.json
Normal file
7
global.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"sdk": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"rollForward": "latestMinor",
|
||||||
|
"allowPrerelease": false
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user