Compare commits

..

19 Commits

Author SHA1 Message Date
59793d19dc
Update 'At the End of our Hope' 2025-02-23 01:06:55 +01:00
2ada2fa9dc
Update for post-ARR quest battles 2025-02-23 00:54:28 +01:00
fe1d86bf5b
Add previously missing change in config class 2025-02-22 23:11:37 +01:00
71b40496fb
Fix interrupts in interactions 2025-02-22 23:11:08 +01:00
224825b071
Check for unexpected party members when entering instanced duties 2025-02-22 23:08:58 +01:00
ed797143b3
Update quest battle metadata 2025-02-22 22:28:04 +01:00
22aa81cf75
Fix Crystal Tower quests ignoring 'NextQuestId' 2025-02-22 19:52:10 +01:00
dcdc288b08
Add special handling for Lahabrea fight 2025-02-22 01:06:41 +01:00
a70e195a93
Configure a few early MSQ battles 2025-02-22 00:03:06 +01:00
71e0b01dbc
Add quest battle difficulty selection; UI tweaks 2025-02-21 12:21:01 +01:00
31eb121cf0
Add quest battle notes 2025-02-21 03:22:47 +01:00
a75286e927
Add UI to enable/disable quest battles 2025-02-21 02:19:01 +01:00
3820647827
Restructure config window 2025-02-20 21:16:48 +01:00
097c67ed5d
Second draft for auto-completing quest battles 2025-02-20 20:45:38 +01:00
92873554cc
Draft for auto-completing quest battles (HW MSQ) 2025-02-20 01:34:59 +01:00
b35ee13704
[GatheringPathRenderer] Replace ECommons/Splatoon with Pictomancy 2025-02-18 18:46:16 +01:00
11cde2a2d6
Minor ARR updates 2025-02-18 17:32:02 +01:00
04ab38cc59
Add extra waypoint for 'Big Trouble in Little Ala Mhigo' to prevent running into a table 2025-02-16 15:53:27 +01:00
c89b81f478 Merge pull request 'Updated preset for vbm v0.0.0.290' () from xanunderscore/Questionable:master into master
Reviewed-on: 
2025-02-11 23:47:23 +00:00
123 changed files with 3650 additions and 746 deletions
.gitmodulesDirectory.Build.targets
GatheringPathRenderer
LLib
QuestPathGenerator
QuestPaths
2.x - A Realm Reborn
Class Quests
MSQ-1
MSQ-2
Unlocks/Misc
3.x - Heavensward
4.x - Stormblood/Class Quests
5.x - Shadowbringers
quest-v1.json
Questionable.Model
Questionable.sln
Questionable

6
.gitmodules vendored

@ -1,9 +1,9 @@
[submodule "LLib"] [submodule "LLib"]
path = LLib path = LLib
url = https://git.carvel.li/liza/LLib.git url = https://git.carvel.li/liza/LLib.git
[submodule "vendor/ECommons"]
path = vendor/ECommons
url = https://github.com/NightmareXIV/ECommons.git
[submodule "vendor/NotificationMasterAPI"] [submodule "vendor/NotificationMasterAPI"]
path = vendor/NotificationMasterAPI path = vendor/NotificationMasterAPI
url = https://github.com/NightmareXIV/NotificationMasterAPI.git url = https://github.com/NightmareXIV/NotificationMasterAPI.git
[submodule "vendor/pictomancy"]
path = vendor/pictomancy
url = https://github.com/sourpuh/ffxiv_pictomancy

@ -1,5 +1,5 @@
<Project> <Project>
<PropertyGroup Condition="$(MSBuildProjectName) != 'GatheringPathRenderer'"> <PropertyGroup Condition="$(MSBuildProjectName) != 'GatheringPathRenderer'">
<Version>4.19</Version> <Version>4.20</Version>
</PropertyGroup> </PropertyGroup>
</Project> </Project>

@ -9,7 +9,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\LLib\LLib.csproj" /> <ProjectReference Include="..\LLib\LLib.csproj" />
<ProjectReference Include="..\Questionable.Model\Questionable.Model.csproj" /> <ProjectReference Include="..\Questionable.Model\Questionable.Model.csproj" />
<ProjectReference Include="..\vendor\ECommons\ECommons\ECommons.csproj" /> <ProjectReference Include="..\vendor\pictomancy\Pictomancy\Pictomancy.csproj" />
</ItemGroup> </ItemGroup>
<Import Project="..\LLib\LLib.targets"/> <Import Project="..\LLib\LLib.targets"/>

@ -2,6 +2,6 @@
"Name": "GatheringPathRenderer", "Name": "GatheringPathRenderer",
"Author": "Liza Carvelli", "Author": "Liza Carvelli",
"Punchline": "[Questionable dev plugin]: Renders gathering location.", "Punchline": "[Questionable dev plugin]: Renders gathering location.",
"Description": "[Questionable dev plugin]: Renders gathering location using Splatoon.", "Description": "[Questionable dev plugin]: Renders gathering location using Pictomancy.",
"RepoUrl": "https://git.carvel.li/liza/Questionable/src/branch/master/GatheringPathRenderer" "RepoUrl": "https://git.carvel.li/liza/Questionable/src/branch/master/GatheringPathRenderer"
} }

@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Numerics;
using System.Text.Encodings.Web; using System.Text.Encodings.Web;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Nodes; using System.Text.Json.Nodes;
@ -13,11 +14,9 @@ using Dalamud.Game.ClientState.Objects;
using Dalamud.Interface.Windowing; using Dalamud.Interface.Windowing;
using Dalamud.Plugin; using Dalamud.Plugin;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using ECommons;
using ECommons.Schedulers;
using ECommons.SplatoonAPI;
using GatheringPathRenderer.Windows; using GatheringPathRenderer.Windows;
using LLib.GameData; using LLib.GameData;
using Pictomancy;
using Questionable.Model.Gathering; using Questionable.Model.Gathering;
namespace GatheringPathRenderer; namespace GatheringPathRenderer;
@ -25,10 +24,8 @@ namespace GatheringPathRenderer;
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")] [SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
public sealed class RendererPlugin : IDalamudPlugin public sealed class RendererPlugin : IDalamudPlugin
{ {
private const long OnTerritoryChange = -2;
private readonly WindowSystem _windowSystem = new(nameof(RendererPlugin)); private readonly WindowSystem _windowSystem = new(nameof(RendererPlugin));
private readonly List<uint> _colors = [0xFFFF2020, 0xFF20FF20, 0xFF2020FF, 0xFFFFFF20, 0xFFFF20FF, 0xFF20FFFF]; private readonly List<uint> _colors = [0x40FF2020, 0x4020FF20, 0x402020FF, 0x40FFFF20, 0x40FF20FF, 0x4020FFFF];
private readonly IDalamudPluginInterface _pluginInterface; private readonly IDalamudPluginInterface _pluginInterface;
private readonly IClientState _clientState; private readonly IClientState _clientState;
@ -58,7 +55,8 @@ public sealed class RendererPlugin : IDalamudPlugin
_editorCommands = new EditorCommands(this, dataManager, commandManager, targetManager, clientState, chatGui, _editorCommands = new EditorCommands(this, dataManager, commandManager, targetManager, clientState, chatGui,
configuration); configuration);
var configWindow = new ConfigWindow(pluginInterface, configuration); var configWindow = new ConfigWindow(pluginInterface, configuration);
_editorWindow = new EditorWindow(this, _editorCommands, dataManager, targetManager, clientState, objectTable, configWindow) _editorWindow = new EditorWindow(this, _editorCommands, dataManager, targetManager, clientState, objectTable,
configWindow)
{ IsOpen = true }; { IsOpen = true };
_windowSystem.AddWindow(configWindow); _windowSystem.AddWindow(configWindow);
_windowSystem.AddWindow(_editorWindow); _windowSystem.AddWindow(_editorWindow);
@ -67,14 +65,12 @@ public sealed class RendererPlugin : IDalamudPlugin
_pluginInterface.GetIpcSubscriber<object>("Questionable.ReloadData") _pluginInterface.GetIpcSubscriber<object>("Questionable.ReloadData")
.Subscribe(Reload); .Subscribe(Reload);
ECommonsMain.Init(pluginInterface, this, Module.SplatoonAPI); PictoService.Initialize(pluginInterface);
LoadGatheringLocationsFromDirectory(); LoadGatheringLocationsFromDirectory();
_pluginInterface.UiBuilder.Draw += _windowSystem.Draw; _pluginInterface.UiBuilder.Draw += _windowSystem.Draw;
_clientState.TerritoryChanged += TerritoryChanged; _pluginInterface.UiBuilder.Draw += Draw;
_clientState.ClassJobChanged += ClassJobChanged; _clientState.ClassJobChanged += ClassJobChanged;
if (_clientState.IsLoggedIn)
TerritoryChanged(_clientState.TerritoryType);
} }
internal DirectoryInfo PathsDirectory internal DirectoryInfo PathsDirectory
@ -93,7 +89,8 @@ public sealed class RendererPlugin : IDalamudPlugin
throw new Exception($"Unable to resolve project path ({_pluginInterface.AssemblyLocation.Directory})"); throw new Exception($"Unable to resolve project path ({_pluginInterface.AssemblyLocation.Directory})");
#else #else
var allPluginsDirectory = _pluginInterface.ConfigFile.Directory ?? throw new Exception("Unknown directory for plugin configs"); var allPluginsDirectory =
_pluginInterface.ConfigFile.Directory ?? throw new Exception("Unknown directory for plugin configs");
return allPluginsDirectory return allPluginsDirectory
.CreateSubdirectory("Questionable") .CreateSubdirectory("Questionable")
.CreateSubdirectory("GatheringPaths"); .CreateSubdirectory("GatheringPaths");
@ -104,7 +101,6 @@ public sealed class RendererPlugin : IDalamudPlugin
internal void Reload() internal void Reload()
{ {
LoadGatheringLocationsFromDirectory(); LoadGatheringLocationsFromDirectory();
Redraw();
} }
private void LoadGatheringLocationsFromDirectory() private void LoadGatheringLocationsFromDirectory()
@ -124,7 +120,6 @@ public sealed class RendererPlugin : IDalamudPlugin
_pluginLog.Information( _pluginLog.Information(
$"Loaded {_gatheringLocations.Count} gathering root locations from {PathsDirectory.FullName} directory"); $"Loaded {_gatheringLocations.Count} gathering root locations from {PathsDirectory.FullName} directory");
#endif #endif
} }
catch (Exception e) catch (Exception e)
{ {
@ -209,142 +204,114 @@ public sealed class RendererPlugin : IDalamudPlugin
} }
} }
private void TerritoryChanged(ushort territoryId) => Redraw();
private void ClassJobChanged(uint classJobId) private void ClassJobChanged(uint classJobId)
{ {
_currentClassJob = (EClassJob)classJobId; _currentClassJob = (EClassJob)classJobId;
Redraw(_currentClassJob);
} }
internal void Redraw() => Redraw(_currentClassJob); private void Draw()
private void Redraw(EClassJob classJob)
{ {
Splatoon.RemoveDynamicElements("GatheringPathRenderer"); if (!_currentClassJob.IsGatherer())
if (!classJob.IsGatherer())
return; return;
var elements = GetLocationsInTerritory(_clientState.TerritoryType) using var drawList = PictoService.Draw();
.SelectMany(location => if (drawList == null)
location.Root.Groups.SelectMany(group => return;
group.Nodes.SelectMany(node => node.Locations
.SelectMany(x =>
{
bool isUnsaved = false;
bool isCone = false;
int minimumAngle = 0;
int maximumAngle = 0;
if (_editorWindow.TryGetOverride(x.InternalId, out LocationOverride? locationOverride) &&
locationOverride != null)
{
isUnsaved = locationOverride.NeedsSave();
if (locationOverride.IsCone())
{
isCone = true;
minimumAngle = locationOverride.MinimumAngle.GetValueOrDefault();
maximumAngle = locationOverride.MaximumAngle.GetValueOrDefault();
}
}
if (!isCone && x.IsCone()) Vector3 position = _clientState.LocalPlayer?.Position ?? Vector3.Zero;
foreach (var location in GetLocationsInTerritory(_clientState.TerritoryType))
{
if (!location.Root.Groups.Any(gr =>
gr.Nodes.Any(
no => no.Locations.Any(
loc => Vector3.Distance(loc.Position, position) < 200f))))
continue;
foreach (var group in location.Root.Groups)
{
foreach (GatheringNode node in group.Nodes)
{
foreach (var x in node.Locations)
{
bool isUnsaved = false;
bool isCone = false;
float minimumAngle = 0;
float maximumAngle = 0;
if (_editorWindow.TryGetOverride(x.InternalId, out LocationOverride? locationOverride) &&
locationOverride != null)
{
isUnsaved = locationOverride.NeedsSave();
if (locationOverride.IsCone())
{ {
isCone = true; isCone = true;
minimumAngle = x.MinimumAngle.GetValueOrDefault(); minimumAngle = locationOverride.MinimumAngle.GetValueOrDefault();
maximumAngle = x.MaximumAngle.GetValueOrDefault(); maximumAngle = locationOverride.MaximumAngle.GetValueOrDefault();
} }
}
#if false if (!isCone && x.IsCone())
var a = GatheringMath.CalculateLandingLocation(x, 0, 0); {
var b = GatheringMath.CalculateLandingLocation(x, 1, 1); isCone = true;
#endif minimumAngle = x.MinimumAngle.GetValueOrDefault();
return new List<Element> maximumAngle = x.MaximumAngle.GetValueOrDefault();
{ }
new Element(isCone
? ElementType.ConeAtFixedCoordinates
: ElementType.CircleAtFixedCoordinates)
{
refX = x.Position.X,
refY = x.Position.Z,
refZ = x.Position.Y,
Filled = true,
radius = locationOverride?.MinimumDistance ?? x.CalculateMinimumDistance(),
Donut = (locationOverride?.MaximumDistance ?? x.CalculateMaximumDistance()) -
(locationOverride?.MinimumDistance ?? x.CalculateMinimumDistance()),
color = _colors[location.Root.Groups.IndexOf(group) % _colors.Count],
Enabled = true,
coneAngleMin = minimumAngle,
coneAngleMax = maximumAngle,
tether = false,
},
new Element(ElementType.CircleAtFixedCoordinates)
{
refX = x.Position.X,
refY = x.Position.Z,
refZ = x.Position.Y,
color = 0xFFFFFFFF,
radius = 0.1f,
Enabled = true,
overlayText =
$"{location.Root.Groups.IndexOf(group)} // {node.DataId} / {node.Locations.IndexOf(x)}",
overlayBGColor = isUnsaved ? 0xFF2020FF : 0xFF000000,
},
#if false
new Element(ElementType.CircleAtFixedCoordinates)
{
refX = a.X,
refY = a.Z,
refZ = a.Y,
color = _colors[0],
radius = 0.1f,
Enabled = true,
overlayText = "Min Angle"
},
new Element(ElementType.CircleAtFixedCoordinates)
{
refX = b.X,
refY = b.Z,
refZ = b.Y,
color = _colors[1],
radius = 0.1f,
Enabled = true,
overlayText = "Max Angle"
}
#endif
};
}))))
.ToList();
if (elements.Count == 0) minimumAngle *= (float)Math.PI / 180;
{ maximumAngle *= (float)Math.PI / 180;
_pluginLog.Information("No new elements to render."); if (!isCone || maximumAngle - minimumAngle >= 2 * Math.PI)
return; {
minimumAngle = 0;
maximumAngle = (float)Math.PI * 2;
}
uint color = _colors[location.Root.Groups.IndexOf(group) % _colors.Count];
drawList.AddFanFilled(x.Position,
locationOverride?.MinimumDistance ?? x.CalculateMinimumDistance(),
locationOverride?.MaximumDistance ?? x.CalculateMaximumDistance(),
minimumAngle, maximumAngle, color);
drawList.AddFan(x.Position,
locationOverride?.MinimumDistance ?? x.CalculateMinimumDistance(),
locationOverride?.MaximumDistance ?? x.CalculateMaximumDistance(),
minimumAngle, maximumAngle, color | 0xFF000000);
drawList.AddText(x.Position, isUnsaved ? 0xFFFF0000 : 0xFFFFFFFF, $"{location.Root.Groups.IndexOf(group)} // {node.DataId} / {node.Locations.IndexOf(x)} || {minimumAngle}, {maximumAngle}", 1f);
#if false
var a = GatheringMath.CalculateLandingLocation(x, 0, 0);
var b = GatheringMath.CalculateLandingLocation(x, 1, 1);
new Element(ElementType.CircleAtFixedCoordinates)
{
refX = a.X,
refY = a.Z,
refZ = a.Y,
color = _colors[0],
radius = 0.1f,
Enabled = true,
overlayText = "Min Angle"
},
new Element(ElementType.CircleAtFixedCoordinates)
{
refX = b.X,
refY = b.Z,
refZ = b.Y,
color = _colors[1],
radius = 0.1f,
Enabled = true,
overlayText = "Max Angle"
}
#endif
}
}
}
} }
_ = new TickScheduler(delegate
{
try
{
Splatoon.AddDynamicElements("GatheringPathRenderer",
elements.ToArray(),
new[] { OnTerritoryChange });
_pluginLog.Information($"Created {elements.Count} splatoon elements.");
}
catch (Exception e)
{
_pluginLog.Error(e, "Unable to create splatoon layer");
}
});
} }
public void Dispose() public void Dispose()
{ {
_clientState.ClassJobChanged -= ClassJobChanged; _clientState.ClassJobChanged -= ClassJobChanged;
_clientState.TerritoryChanged -= TerritoryChanged; _pluginInterface.UiBuilder.Draw -= Draw;
_pluginInterface.UiBuilder.Draw -= _windowSystem.Draw; _pluginInterface.UiBuilder.Draw -= _windowSystem.Draw;
Splatoon.RemoveDynamicElements("GatheringPathRenderer"); PictoService.Dispose();
ECommonsMain.Dispose();
_pluginInterface.GetIpcSubscriber<object>("Questionable.ReloadData") _pluginInterface.GetIpcSubscriber<object>("Questionable.ReloadData")
.Unsubscribe(Reload); .Unsubscribe(Reload);

@ -154,7 +154,6 @@ internal sealed class EditorWindow : Window
{ {
locationOverride.MinimumAngle = minAngle; locationOverride.MinimumAngle = minAngle;
locationOverride.MaximumAngle = maxAngle; locationOverride.MaximumAngle = maxAngle;
_plugin.Redraw();
} }
float minDistance = locationOverride.MinimumDistance ?? location.CalculateMinimumDistance(); float minDistance = locationOverride.MinimumDistance ?? location.CalculateMinimumDistance();
@ -163,7 +162,6 @@ internal sealed class EditorWindow : Window
{ {
locationOverride.MinimumDistance = minDistance; locationOverride.MinimumDistance = minDistance;
locationOverride.MaximumDistance = maxDistance; locationOverride.MaximumDistance = maxDistance;
_plugin.Redraw();
} }
bool unsaved = locationOverride.NeedsSave(); bool unsaved = locationOverride.NeedsSave();
@ -194,7 +192,6 @@ internal sealed class EditorWindow : Window
if (ImGui.Button("Reset")) if (ImGui.Button("Reset"))
{ {
_changes[location.InternalId] = new LocationOverride(); _changes[location.InternalId] = new LocationOverride();
_plugin.Redraw();
} }
ImGui.EndDisabled(); ImGui.EndDisabled();

@ -35,6 +35,16 @@
"resolved": "8.0.0", "resolved": "8.0.0",
"contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ==" "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ=="
}, },
"Microsoft.NETCore.Platforms": {
"type": "Transitive",
"resolved": "1.1.0",
"contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A=="
},
"Microsoft.NETCore.Targets": {
"type": "Transitive",
"resolved": "1.1.0",
"contentHash": "aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg=="
},
"Microsoft.SourceLink.AzureRepos.Git": { "Microsoft.SourceLink.AzureRepos.Git": {
"type": "Transitive", "type": "Transitive",
"resolved": "1.1.1", "resolved": "1.1.1",
@ -76,13 +86,944 @@
"Microsoft.SourceLink.Common": "1.1.1" "Microsoft.SourceLink.Common": "1.1.1"
} }
}, },
"Microsoft.Win32.Primitives": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "9ZQKCWxH7Ijp9BfahvL2Zyf1cJIk8XYLF6Yjzr2yi0b2cOut/HQ31qf1ThHAgCc3WiZMdnWcfJCgN82/0UunxA==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0"
}
},
"NETStandard.Library": {
"type": "Transitive",
"resolved": "1.6.1",
"contentHash": "WcSp3+vP+yHNgS8EV5J7pZ9IRpeDuARBPN28by8zqff1wJQXm26PVU8L3/fYLBJVU7BtDyqNVWq2KlCVvSSR4A==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.Win32.Primitives": "4.3.0",
"System.AppContext": "4.3.0",
"System.Collections": "4.3.0",
"System.Collections.Concurrent": "4.3.0",
"System.Console": "4.3.0",
"System.Diagnostics.Debug": "4.3.0",
"System.Diagnostics.Tools": "4.3.0",
"System.Diagnostics.Tracing": "4.3.0",
"System.Globalization": "4.3.0",
"System.Globalization.Calendars": "4.3.0",
"System.IO": "4.3.0",
"System.IO.Compression": "4.3.0",
"System.IO.Compression.ZipFile": "4.3.0",
"System.IO.FileSystem": "4.3.0",
"System.IO.FileSystem.Primitives": "4.3.0",
"System.Linq": "4.3.0",
"System.Linq.Expressions": "4.3.0",
"System.Net.Http": "4.3.0",
"System.Net.Primitives": "4.3.0",
"System.Net.Sockets": "4.3.0",
"System.ObjectModel": "4.3.0",
"System.Reflection": "4.3.0",
"System.Reflection.Extensions": "4.3.0",
"System.Reflection.Primitives": "4.3.0",
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0",
"System.Runtime.Extensions": "4.3.0",
"System.Runtime.Handles": "4.3.0",
"System.Runtime.InteropServices": "4.3.0",
"System.Runtime.InteropServices.RuntimeInformation": "4.3.0",
"System.Runtime.Numerics": "4.3.0",
"System.Security.Cryptography.Algorithms": "4.3.0",
"System.Security.Cryptography.Encoding": "4.3.0",
"System.Security.Cryptography.Primitives": "4.3.0",
"System.Security.Cryptography.X509Certificates": "4.3.0",
"System.Text.Encoding": "4.3.0",
"System.Text.Encoding.Extensions": "4.3.0",
"System.Text.RegularExpressions": "4.3.0",
"System.Threading": "4.3.0",
"System.Threading.Tasks": "4.3.0",
"System.Threading.Timer": "4.3.0",
"System.Xml.ReaderWriter": "4.3.0",
"System.Xml.XDocument": "4.3.0"
}
},
"runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "HdSSp5MnJSsg08KMfZThpuLPJpPwE5hBXvHwoKWosyHHfe8Mh5WKT0ylEOf6yNzX6Ngjxe4Whkafh5q7Ymac4Q=="
},
"runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "+yH1a49wJMy8Zt4yx5RhJrxO/DBDByAiCzNwiETI+1S4mPdCu0OY4djdciC7Vssk0l22wQaDLrXxXkp+3+7bVA=="
},
"runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "c3YNH1GQJbfIPJeCnr4avseugSqPrxwIqzthYyZDN6EuOyNOzq+y2KSUfRcXauya1sF4foESTgwM5e1A8arAKw=="
},
"runtime.native.System": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "c/qWt2LieNZIj1jGnVNsE2Kl23Ya2aSTBuXMD6V7k9KWr6l16Tqdwq+hJScEpWER9753NWC8h96PaVNY5Ld7Jw==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0"
}
},
"runtime.native.System.IO.Compression": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "INBPonS5QPEgn7naufQFXJEp3zX6L4bwHgJ/ZH78aBTpeNfQMtf7C6VrAFhlq2xxWBveIOWyFzQjJ8XzHMhdOQ==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0"
}
},
"runtime.native.System.Net.Http": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "ZVuZJqnnegJhd2k/PtAbbIcZ3aZeITq3sj06oKfMBSfphW3HDmk/t4ObvbOk/JA/swGR0LNqMksAh/f7gpTROg==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0"
}
},
"runtime.native.System.Security.Cryptography.Apple": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "DloMk88juo0OuOWr56QG7MNchmafTLYWvABy36izkrLI5VledI0rq28KGs1i9wbpeT9NPQrx/wTf8U2vazqQ3Q==",
"dependencies": {
"runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "4.3.0"
}
},
"runtime.native.System.Security.Cryptography.OpenSsl": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "NS1U+700m4KFRHR5o4vo9DSlTmlCKu/u7dtE5sUHVIPB+xpXxYQvgBgA6wEIeCz6Yfn0Z52/72WYsToCEPJnrw==",
"dependencies": {
"runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0",
"runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0",
"runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0",
"runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0",
"runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0",
"runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0",
"runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0",
"runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0",
"runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0",
"runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0"
}
},
"runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "b3pthNgxxFcD+Pc0WSEoC0+md3MyhRS6aCEeenvNE3Fdw1HyJ18ZhRFVJJzIeR/O/jpxPboB805Ho0T3Ul7w8A=="
},
"runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "KeLz4HClKf+nFS7p/6Fi/CqyLXh81FpiGzcmuS8DGi9lUqSnZ6Es23/gv2O+1XVGfrbNmviF7CckBpavkBoIFQ=="
},
"runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "kVXCuMTrTlxq4XOOMAysuNwsXWpYeboGddNGpIgNSZmv1b6r/s/DPk0fYMB7Q5Qo4bY68o48jt4T4y5BVecbCQ=="
},
"runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "X7IdhILzr4ROXd8mI1BUCQMSHSQwelUlBjF1JyTKCjXaOGn2fB4EKBxQbCK2VjO3WaWIdlXZL3W6TiIVnrhX4g=="
},
"runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "nyFNiCk/r+VOiIqreLix8yN+q3Wga9+SE8BCgkf+2BwEKiNx6DyvFjCgkfV743/grxv8jHJ8gUK4XEQw7yzRYg=="
},
"runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "ytoewC6wGorL7KoCAvRfsgoJPJbNq+64k2SqW6JcOAebWsFUvCCYgfzQMrnpvPiEl4OrblUlhF2ji+Q1+SVLrQ=="
},
"runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "I8bKw2I8k58Wx7fMKQJn2R8lamboCAiHfHeV/pS65ScKWMMI0+wJkLYlEKvgW1D/XvSl/221clBoR2q9QNNM7A=="
},
"runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "VB5cn/7OzUfzdnC8tqAIMQciVLiq2epm2NrAm1E9OjNRyG4lVhfR61SMcLizejzQP8R8Uf/0l5qOIbUEi+RdEg=="
},
"SharpDX": {
"type": "Transitive",
"resolved": "4.2.0",
"contentHash": "3pv0LFMvfK/dv1qISJnn8xBeeT6R/FRvr0EV4KI2DGsL84Qlv6P7isWqxGyU0LCwlSVCJN3jgHJ4Bl0KI2PJww==",
"dependencies": {
"NETStandard.Library": "1.6.1"
}
},
"SharpDX.D3DCompiler": {
"type": "Transitive",
"resolved": "4.2.0",
"contentHash": "Rnsd6Ilp127xbXqhTit8WKFQUrXwWxqVGpglyWDNkIBCk0tWXNQEjrJpsl0KAObzyZaa33+EXAikLVt5fnd3GA==",
"dependencies": {
"NETStandard.Library": "1.6.1",
"SharpDX": "4.2.0"
}
},
"SharpDX.Direct2D1": {
"type": "Transitive",
"resolved": "4.2.0",
"contentHash": "Qs8LzDMaQf1u3KB8ArHu9pDv6itZ++QXs99a/bVAG+nKr0Hx5NG4mcN5vsfE0mVR2TkeHfeUm4PksRah6VUPtA==",
"dependencies": {
"NETStandard.Library": "1.6.1",
"SharpDX": "4.2.0",
"SharpDX.DXGI": "4.2.0"
}
},
"SharpDX.Direct3D11": {
"type": "Transitive",
"resolved": "4.2.0",
"contentHash": "oTm/iT5X/IIuJ8kNYP+DTC/MhBhqtRF5dbgPPFgLBdQv0BKzNTzXQQXd7SveBFjQg6hXEAJ2jGCAzNYvGFc9LA==",
"dependencies": {
"NETStandard.Library": "1.6.1",
"SharpDX": "4.2.0",
"SharpDX.DXGI": "4.2.0"
}
},
"SharpDX.DXGI": {
"type": "Transitive",
"resolved": "4.2.0",
"contentHash": "UjKqkgWc8U+SP+j3LBzFP6OB6Ntapjih7Xo+g1rLcsGbIb5KwewBrBChaUu7sil8rWoeVU/k0EJd3SMN4VqNZw==",
"dependencies": {
"NETStandard.Library": "1.6.1",
"SharpDX": "4.2.0"
}
},
"System.AppContext": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "fKC+rmaLfeIzUhagxY17Q9siv/sPrjjKcfNg1Ic8IlQkZLipo8ljcaZQu4VtI4Jqbzjc2VTjzGLF6WmsRXAEgA==",
"dependencies": {
"System.Runtime": "4.3.0"
}
},
"System.Buffers": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "ratu44uTIHgeBeI0dE8DWvmXVBSo4u7ozRZZHOMmK/JPpYyo0dAfgSiHlpiObMQ5lEtEyIXA40sKRYg5J6A8uQ==",
"dependencies": {
"System.Diagnostics.Debug": "4.3.0",
"System.Diagnostics.Tracing": "4.3.0",
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0",
"System.Threading": "4.3.0"
}
},
"System.Collections": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "3Dcj85/TBdVpL5Zr+gEEBUuFe2icOnLalmEh9hfck1PTYbbyWuZgh4fmm2ysCLTrqLQw6t3TgTyJ+VLp+Qb+Lw==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0"
}
},
"System.Collections.Concurrent": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "ztl69Xp0Y/UXCL+3v3tEU+lIy+bvjKNUmopn1wep/a291pVPK7dxBd6T7WnlQqRog+d1a/hSsgRsmFnIBKTPLQ==",
"dependencies": {
"System.Collections": "4.3.0",
"System.Diagnostics.Debug": "4.3.0",
"System.Diagnostics.Tracing": "4.3.0",
"System.Globalization": "4.3.0",
"System.Reflection": "4.3.0",
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0",
"System.Runtime.Extensions": "4.3.0",
"System.Threading": "4.3.0",
"System.Threading.Tasks": "4.3.0"
}
},
"System.Console": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "DHDrIxiqk1h03m6khKWV2X8p/uvN79rgSqpilL6uzpmSfxfU5ng8VcPtW4qsDsQDHiTv6IPV9TmD5M/vElPNLg==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.IO": "4.3.0",
"System.Runtime": "4.3.0",
"System.Text.Encoding": "4.3.0"
}
},
"System.Diagnostics.Debug": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "ZUhUOdqmaG5Jk3Xdb8xi5kIyQYAA4PnTNlHx1mu9ZY3qv4ELIdKbnL/akbGaKi2RnNUWaZsAs31rvzFdewTj2g==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0"
}
},
"System.Diagnostics.DiagnosticSource": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "tD6kosZnTAGdrEa0tZSuFyunMbt/5KYDnHdndJYGqZoNy00XVXyACd5d6KnE1YgYv3ne2CjtAfNXo/fwEhnKUA==",
"dependencies": {
"System.Collections": "4.3.0",
"System.Diagnostics.Tracing": "4.3.0",
"System.Reflection": "4.3.0",
"System.Runtime": "4.3.0",
"System.Threading": "4.3.0"
}
},
"System.Diagnostics.Tools": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "UUvkJfSYJMM6x527dJg2VyWPSRqIVB0Z7dbjHst1zmwTXz5CcXSYJFWRpuigfbO1Lf7yfZiIaEUesfnl/g5EyA==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0"
}
},
"System.Diagnostics.Tracing": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "rswfv0f/Cqkh78rA5S8eN8Neocz234+emGCtTF3lxPY96F+mmmUen6tbn0glN6PMvlKQb9bPAY5e9u7fgPTkKw==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0"
}
},
"System.Globalization": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "kYdVd2f2PAdFGblzFswE4hkNANJBKRmsfa2X5LG2AcWE1c7/4t0pYae1L8vfZ5xvE2nK/R9JprtToA61OSHWIg==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0"
}
},
"System.Globalization.Calendars": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "GUlBtdOWT4LTV3I+9/PJW+56AnnChTaOqqTLFtdmype/L500M2LIyXgmtd9X2P2VOkmJd5c67H5SaC2QcL1bFA==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Globalization": "4.3.0",
"System.Runtime": "4.3.0"
}
},
"System.Globalization.Extensions": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "FhKmdR6MPG+pxow6wGtNAWdZh7noIOpdD5TwQ3CprzgIE1bBBoim0vbR1+AWsWjQmU7zXHgQo4TWSP6lCeiWcQ==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"System.Globalization": "4.3.0",
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0",
"System.Runtime.Extensions": "4.3.0",
"System.Runtime.InteropServices": "4.3.0"
}
},
"System.IO": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0",
"System.Text.Encoding": "4.3.0",
"System.Threading.Tasks": "4.3.0"
}
},
"System.IO.Compression": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "YHndyoiV90iu4iKG115ibkhrG+S3jBm8Ap9OwoUAzO5oPDAWcr0SFwQFm0HjM8WkEZWo0zvLTyLmbvTkW1bXgg==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"System.Buffers": "4.3.0",
"System.Collections": "4.3.0",
"System.Diagnostics.Debug": "4.3.0",
"System.IO": "4.3.0",
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0",
"System.Runtime.Extensions": "4.3.0",
"System.Runtime.Handles": "4.3.0",
"System.Runtime.InteropServices": "4.3.0",
"System.Text.Encoding": "4.3.0",
"System.Threading": "4.3.0",
"System.Threading.Tasks": "4.3.0",
"runtime.native.System": "4.3.0",
"runtime.native.System.IO.Compression": "4.3.0"
}
},
"System.IO.Compression.ZipFile": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "G4HwjEsgIwy3JFBduZ9quBkAu+eUwjIdJleuNSgmUojbH6O3mlvEIme+GHx/cLlTAPcrnnL7GqvB9pTlWRfhOg==",
"dependencies": {
"System.Buffers": "4.3.0",
"System.IO": "4.3.0",
"System.IO.Compression": "4.3.0",
"System.IO.FileSystem": "4.3.0",
"System.IO.FileSystem.Primitives": "4.3.0",
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0",
"System.Runtime.Extensions": "4.3.0",
"System.Text.Encoding": "4.3.0"
}
},
"System.IO.FileSystem": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "3wEMARTnuio+ulnvi+hkRNROYwa1kylvYahhcLk4HSoVdl+xxTFVeVlYOfLwrDPImGls0mDqbMhrza8qnWPTdA==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.IO": "4.3.0",
"System.IO.FileSystem.Primitives": "4.3.0",
"System.Runtime": "4.3.0",
"System.Runtime.Handles": "4.3.0",
"System.Text.Encoding": "4.3.0",
"System.Threading.Tasks": "4.3.0"
}
},
"System.IO.FileSystem.Primitives": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "6QOb2XFLch7bEc4lIcJH49nJN2HV+OC3fHDgsLVsBVBk3Y4hFAnOBGzJ2lUu7CyDDFo9IBWkSsnbkT6IBwwiMw==",
"dependencies": {
"System.Runtime": "4.3.0"
}
},
"System.Linq": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "5DbqIUpsDp0dFftytzuMmc0oeMdQwjcP/EWxsksIz/w1TcFRkZ3yKKz0PqiYFMmEwPSWw+qNVqD7PJ889JzHbw==",
"dependencies": {
"System.Collections": "4.3.0",
"System.Diagnostics.Debug": "4.3.0",
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0",
"System.Runtime.Extensions": "4.3.0"
}
},
"System.Linq.Expressions": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "PGKkrd2khG4CnlyJwxwwaWWiSiWFNBGlgXvJpeO0xCXrZ89ODrQ6tjEWS/kOqZ8GwEOUATtKtzp1eRgmYNfclg==",
"dependencies": {
"System.Collections": "4.3.0",
"System.Diagnostics.Debug": "4.3.0",
"System.Globalization": "4.3.0",
"System.IO": "4.3.0",
"System.Linq": "4.3.0",
"System.ObjectModel": "4.3.0",
"System.Reflection": "4.3.0",
"System.Reflection.Emit": "4.3.0",
"System.Reflection.Emit.ILGeneration": "4.3.0",
"System.Reflection.Emit.Lightweight": "4.3.0",
"System.Reflection.Extensions": "4.3.0",
"System.Reflection.Primitives": "4.3.0",
"System.Reflection.TypeExtensions": "4.3.0",
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0",
"System.Runtime.Extensions": "4.3.0",
"System.Threading": "4.3.0"
}
},
"System.Net.Http": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "sYg+FtILtRQuYWSIAuNOELwVuVsxVyJGWQyOnlAzhV4xvhyFnON1bAzYYC+jjRW8JREM45R0R5Dgi8MTC5sEwA==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"System.Collections": "4.3.0",
"System.Diagnostics.Debug": "4.3.0",
"System.Diagnostics.DiagnosticSource": "4.3.0",
"System.Diagnostics.Tracing": "4.3.0",
"System.Globalization": "4.3.0",
"System.Globalization.Extensions": "4.3.0",
"System.IO": "4.3.0",
"System.IO.FileSystem": "4.3.0",
"System.Net.Primitives": "4.3.0",
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0",
"System.Runtime.Extensions": "4.3.0",
"System.Runtime.Handles": "4.3.0",
"System.Runtime.InteropServices": "4.3.0",
"System.Security.Cryptography.Algorithms": "4.3.0",
"System.Security.Cryptography.Encoding": "4.3.0",
"System.Security.Cryptography.OpenSsl": "4.3.0",
"System.Security.Cryptography.Primitives": "4.3.0",
"System.Security.Cryptography.X509Certificates": "4.3.0",
"System.Text.Encoding": "4.3.0",
"System.Threading": "4.3.0",
"System.Threading.Tasks": "4.3.0",
"runtime.native.System": "4.3.0",
"runtime.native.System.Net.Http": "4.3.0",
"runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0"
}
},
"System.Net.Primitives": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "qOu+hDwFwoZPbzPvwut2qATe3ygjeQBDQj91xlsaqGFQUI5i4ZnZb8yyQuLGpDGivEPIt8EJkd1BVzVoP31FXA==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0",
"System.Runtime.Handles": "4.3.0"
}
},
"System.Net.Sockets": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "m6icV6TqQOAdgt5N/9I5KNpjom/5NFtkmGseEH+AK/hny8XrytLH3+b5M8zL/Ycg3fhIocFpUMyl/wpFnVRvdw==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.IO": "4.3.0",
"System.Net.Primitives": "4.3.0",
"System.Runtime": "4.3.0",
"System.Threading.Tasks": "4.3.0"
}
},
"System.ObjectModel": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "bdX+80eKv9bN6K4N+d77OankKHGn6CH711a6fcOpMQu2Fckp/Ft4L/kW9WznHpyR0NRAvJutzOMHNNlBGvxQzQ==",
"dependencies": {
"System.Collections": "4.3.0",
"System.Diagnostics.Debug": "4.3.0",
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0",
"System.Threading": "4.3.0"
}
},
"System.Reflection": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.IO": "4.3.0",
"System.Reflection.Primitives": "4.3.0",
"System.Runtime": "4.3.0"
}
},
"System.Reflection.Emit": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "228FG0jLcIwTVJyz8CLFKueVqQK36ANazUManGaJHkO0icjiIypKW7YLWLIWahyIkdh5M7mV2dJepllLyA1SKg==",
"dependencies": {
"System.IO": "4.3.0",
"System.Reflection": "4.3.0",
"System.Reflection.Emit.ILGeneration": "4.3.0",
"System.Reflection.Primitives": "4.3.0",
"System.Runtime": "4.3.0"
}
},
"System.Reflection.Emit.ILGeneration": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "59tBslAk9733NXLrUJrwNZEzbMAcu8k344OYo+wfSVygcgZ9lgBdGIzH/nrg3LYhXceynyvTc8t5/GD4Ri0/ng==",
"dependencies": {
"System.Reflection": "4.3.0",
"System.Reflection.Primitives": "4.3.0",
"System.Runtime": "4.3.0"
}
},
"System.Reflection.Emit.Lightweight": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "oadVHGSMsTmZsAF864QYN1t1QzZjIcuKU3l2S9cZOwDdDueNTrqq1yRj7koFfIGEnKpt6NjpL3rOzRhs4ryOgA==",
"dependencies": {
"System.Reflection": "4.3.0",
"System.Reflection.Emit.ILGeneration": "4.3.0",
"System.Reflection.Primitives": "4.3.0",
"System.Runtime": "4.3.0"
}
},
"System.Reflection.Extensions": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "rJkrJD3kBI5B712aRu4DpSIiHRtr6QlfZSQsb0hYHrDCZORXCFjQfoipo2LaMUHoT9i1B7j7MnfaEKWDFmFQNQ==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Reflection": "4.3.0",
"System.Runtime": "4.3.0"
}
},
"System.Reflection.Primitives": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0"
}
},
"System.Reflection.TypeExtensions": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "7u6ulLcZbyxB5Gq0nMkQttcdBTx57ibzw+4IOXEfR+sXYQoHvjW5LTLyNr8O22UIMrqYbchJQJnos4eooYzYJA==",
"dependencies": {
"System.Reflection": "4.3.0",
"System.Runtime": "4.3.0"
}
},
"System.Resources.ResourceManager": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "/zrcPkkWdZmI4F92gL/TPumP98AVDu/Wxr3CSJGQQ+XN6wbRZcyfSKVoPo17ilb3iOr0cCRqJInGwNMolqhS8A==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Globalization": "4.3.0",
"System.Reflection": "4.3.0",
"System.Runtime": "4.3.0"
}
},
"System.Runtime": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0"
}
},
"System.Runtime.Extensions": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "guW0uK0fn5fcJJ1tJVXYd7/1h5F+pea1r7FLSOz/f8vPEqbR2ZAknuRDvTQ8PzAilDveOxNjSfr0CHfIQfFk8g==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0"
}
},
"System.Runtime.Handles": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "OKiSUN7DmTWeYb3l51A7EYaeNMnvxwE249YtZz7yooT4gOZhmTjIn48KgSsw2k2lYdLgTKNJw/ZIfSElwDRVgg==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0"
}
},
"System.Runtime.InteropServices": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "uv1ynXqiMK8mp1GM3jDqPCFN66eJ5w5XNomaK2XD+TuCroNTLFGeZ+WCmBMcBDyTFKou3P6cR6J/QsaqDp7fGQ==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Reflection": "4.3.0",
"System.Reflection.Primitives": "4.3.0",
"System.Runtime": "4.3.0",
"System.Runtime.Handles": "4.3.0"
}
},
"System.Runtime.InteropServices.RuntimeInformation": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "cbz4YJMqRDR7oLeMRbdYv7mYzc++17lNhScCX0goO2XpGWdvAt60CGN+FHdePUEHCe/Jy9jUlvNAiNdM+7jsOw==",
"dependencies": {
"System.Reflection": "4.3.0",
"System.Reflection.Extensions": "4.3.0",
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0",
"System.Runtime.InteropServices": "4.3.0",
"System.Threading": "4.3.0",
"runtime.native.System": "4.3.0"
}
},
"System.Runtime.Numerics": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "yMH+MfdzHjy17l2KESnPiF2dwq7T+xLnSJar7slyimAkUh/gTrS9/UQOtv7xarskJ2/XDSNvfLGOBQPjL7PaHQ==",
"dependencies": {
"System.Globalization": "4.3.0",
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0",
"System.Runtime.Extensions": "4.3.0"
}
},
"System.Security.Cryptography.Algorithms": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "W1kd2Y8mYSCgc3ULTAZ0hOP2dSdG5YauTb1089T0/kRcN2MpSAW1izOFROrJgxSlMn3ArsgHXagigyi+ibhevg==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"System.Collections": "4.3.0",
"System.IO": "4.3.0",
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0",
"System.Runtime.Extensions": "4.3.0",
"System.Runtime.Handles": "4.3.0",
"System.Runtime.InteropServices": "4.3.0",
"System.Runtime.Numerics": "4.3.0",
"System.Security.Cryptography.Encoding": "4.3.0",
"System.Security.Cryptography.Primitives": "4.3.0",
"System.Text.Encoding": "4.3.0",
"runtime.native.System.Security.Cryptography.Apple": "4.3.0",
"runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0"
}
},
"System.Security.Cryptography.Cng": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "03idZOqFlsKRL4W+LuCpJ6dBYDUWReug6lZjBa3uJWnk5sPCUXckocevTaUA8iT/MFSrY/2HXkOt753xQ/cf8g==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"System.IO": "4.3.0",
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0",
"System.Runtime.Extensions": "4.3.0",
"System.Runtime.Handles": "4.3.0",
"System.Runtime.InteropServices": "4.3.0",
"System.Security.Cryptography.Algorithms": "4.3.0",
"System.Security.Cryptography.Encoding": "4.3.0",
"System.Security.Cryptography.Primitives": "4.3.0",
"System.Text.Encoding": "4.3.0"
}
},
"System.Security.Cryptography.Csp": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "X4s/FCkEUnRGnwR3aSfVIkldBmtURMhmexALNTwpjklzxWU7yjMk7GHLKOZTNkgnWnE0q7+BCf9N2LVRWxewaA==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"System.IO": "4.3.0",
"System.Reflection": "4.3.0",
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0",
"System.Runtime.Extensions": "4.3.0",
"System.Runtime.Handles": "4.3.0",
"System.Runtime.InteropServices": "4.3.0",
"System.Security.Cryptography.Algorithms": "4.3.0",
"System.Security.Cryptography.Encoding": "4.3.0",
"System.Security.Cryptography.Primitives": "4.3.0",
"System.Text.Encoding": "4.3.0",
"System.Threading": "4.3.0"
}
},
"System.Security.Cryptography.Encoding": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "1DEWjZZly9ae9C79vFwqaO5kaOlI5q+3/55ohmq/7dpDyDfc8lYe7YVxJUZ5MF/NtbkRjwFRo14yM4OEo9EmDw==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"System.Collections": "4.3.0",
"System.Collections.Concurrent": "4.3.0",
"System.Linq": "4.3.0",
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0",
"System.Runtime.Extensions": "4.3.0",
"System.Runtime.Handles": "4.3.0",
"System.Runtime.InteropServices": "4.3.0",
"System.Security.Cryptography.Primitives": "4.3.0",
"System.Text.Encoding": "4.3.0",
"runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0"
}
},
"System.Security.Cryptography.OpenSsl": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "h4CEgOgv5PKVF/HwaHzJRiVboL2THYCou97zpmhjghx5frc7fIvlkY1jL+lnIQyChrJDMNEXS6r7byGif8Cy4w==",
"dependencies": {
"System.Collections": "4.3.0",
"System.IO": "4.3.0",
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0",
"System.Runtime.Extensions": "4.3.0",
"System.Runtime.Handles": "4.3.0",
"System.Runtime.InteropServices": "4.3.0",
"System.Runtime.Numerics": "4.3.0",
"System.Security.Cryptography.Algorithms": "4.3.0",
"System.Security.Cryptography.Encoding": "4.3.0",
"System.Security.Cryptography.Primitives": "4.3.0",
"System.Text.Encoding": "4.3.0",
"runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0"
}
},
"System.Security.Cryptography.Primitives": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==",
"dependencies": {
"System.Diagnostics.Debug": "4.3.0",
"System.Globalization": "4.3.0",
"System.IO": "4.3.0",
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0",
"System.Threading": "4.3.0",
"System.Threading.Tasks": "4.3.0"
}
},
"System.Security.Cryptography.X509Certificates": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "t2Tmu6Y2NtJ2um0RtcuhP7ZdNNxXEgUm2JeoA/0NvlMjAhKCnM1NX07TDl3244mVp3QU6LPEhT3HTtH1uF7IYw==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"System.Collections": "4.3.0",
"System.Diagnostics.Debug": "4.3.0",
"System.Globalization": "4.3.0",
"System.Globalization.Calendars": "4.3.0",
"System.IO": "4.3.0",
"System.IO.FileSystem": "4.3.0",
"System.IO.FileSystem.Primitives": "4.3.0",
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0",
"System.Runtime.Extensions": "4.3.0",
"System.Runtime.Handles": "4.3.0",
"System.Runtime.InteropServices": "4.3.0",
"System.Runtime.Numerics": "4.3.0",
"System.Security.Cryptography.Algorithms": "4.3.0",
"System.Security.Cryptography.Cng": "4.3.0",
"System.Security.Cryptography.Csp": "4.3.0",
"System.Security.Cryptography.Encoding": "4.3.0",
"System.Security.Cryptography.OpenSsl": "4.3.0",
"System.Security.Cryptography.Primitives": "4.3.0",
"System.Text.Encoding": "4.3.0",
"System.Threading": "4.3.0",
"runtime.native.System": "4.3.0",
"runtime.native.System.Net.Http": "4.3.0",
"runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0"
}
},
"System.Text.Encoding": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0"
}
},
"System.Text.Encoding.Extensions": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "YVMK0Bt/A43RmwizJoZ22ei2nmrhobgeiYwFzC4YAN+nue8RF6djXDMog0UCn+brerQoYVyaS+ghy9P/MUVcmw==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0",
"System.Text.Encoding": "4.3.0"
}
},
"System.Text.Json": { "System.Text.Json": {
"type": "Transitive", "type": "Transitive",
"resolved": "8.0.5", "resolved": "8.0.5",
"contentHash": "0f1B50Ss7rqxXiaBJyzUu9bWFOO2/zSlifZ/UNMdiIpDYe4cY4LQQicP4nirK1OS31I43rn062UIJ1Q9bpmHpg==" "contentHash": "0f1B50Ss7rqxXiaBJyzUu9bWFOO2/zSlifZ/UNMdiIpDYe4cY4LQQicP4nirK1OS31I43rn062UIJ1Q9bpmHpg=="
}, },
"ecommons": { "System.Text.RegularExpressions": {
"type": "Project" "type": "Transitive",
"resolved": "4.3.0",
"contentHash": "RpT2DA+L660cBt1FssIE9CAGpLFdFPuheB7pLpKpn6ZXNby7jDERe8Ua/Ne2xGiwLVG2JOqziiaVCGDon5sKFA==",
"dependencies": {
"System.Runtime": "4.3.0"
}
},
"System.Threading": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "VkUS0kOBcUf3Wwm0TSbrevDDZ6BlM+b/HRiapRFWjM5O0NS0LviG0glKmFK+hhPDd1XFeSdU1GmlLhb2CoVpIw==",
"dependencies": {
"System.Runtime": "4.3.0",
"System.Threading.Tasks": "4.3.0"
}
},
"System.Threading.Tasks": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0"
}
},
"System.Threading.Tasks.Extensions": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "npvJkVKl5rKXrtl1Kkm6OhOUaYGEiF9wFbppFRWSMoApKzt2PiPHT2Bb8a5sAWxprvdOAtvaARS9QYMznEUtug==",
"dependencies": {
"System.Collections": "4.3.0",
"System.Runtime": "4.3.0",
"System.Threading.Tasks": "4.3.0"
}
},
"System.Threading.Timer": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "Z6YfyYTCg7lOZjJzBjONJTFKGN9/NIYKSxhU5GRd+DTwHSZyvWp1xuI5aR+dLg+ayyC5Xv57KiY4oJ0tMO89fQ==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0"
}
},
"System.Xml.ReaderWriter": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "GrprA+Z0RUXaR4N7/eW71j1rgMnEnEVlgii49GZyAjTH7uliMnrOU3HNFBr6fEDBCJCIdlVNq9hHbaDR621XBA==",
"dependencies": {
"System.Collections": "4.3.0",
"System.Diagnostics.Debug": "4.3.0",
"System.Globalization": "4.3.0",
"System.IO": "4.3.0",
"System.IO.FileSystem": "4.3.0",
"System.IO.FileSystem.Primitives": "4.3.0",
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0",
"System.Runtime.Extensions": "4.3.0",
"System.Runtime.InteropServices": "4.3.0",
"System.Text.Encoding": "4.3.0",
"System.Text.Encoding.Extensions": "4.3.0",
"System.Text.RegularExpressions": "4.3.0",
"System.Threading.Tasks": "4.3.0",
"System.Threading.Tasks.Extensions": "4.3.0"
}
},
"System.Xml.XDocument": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "5zJ0XDxAIg8iy+t4aMnQAu0MqVbqyvfoUVl1yDV61xdo3Vth45oA2FoY4pPkxYAH5f8ixpmTqXeEIya95x0aCQ==",
"dependencies": {
"System.Collections": "4.3.0",
"System.Diagnostics.Debug": "4.3.0",
"System.Diagnostics.Tools": "4.3.0",
"System.Globalization": "4.3.0",
"System.IO": "4.3.0",
"System.Reflection": "4.3.0",
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0",
"System.Runtime.Extensions": "4.3.0",
"System.Text.Encoding": "4.3.0",
"System.Threading": "4.3.0",
"System.Xml.ReaderWriter": "4.3.0"
}
}, },
"llib": { "llib": {
"type": "Project", "type": "Project",
@ -90,6 +1031,14 @@
"DalamudPackager": "[11.0.0, )" "DalamudPackager": "[11.0.0, )"
} }
}, },
"pictomancy": {
"type": "Project",
"dependencies": {
"SharpDX.D3DCompiler": "[4.2.0, )",
"SharpDX.Direct2D1": "[4.2.0, )",
"SharpDX.Direct3D11": "[4.2.0, )"
}
},
"questionable.model": { "questionable.model": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {

2
LLib

@ -1 +1 @@
Subproject commit 746d14681baa91132784ab17f8f49671e86ea211 Subproject commit edab3c7ecc6bd66ac07e3c3938eb9c8a835a1c42

@ -108,7 +108,7 @@ internal static class QuestStepExtensions
AssignmentList(nameof(QuestStep.ComplexCombatData), step.ComplexCombatData) AssignmentList(nameof(QuestStep.ComplexCombatData), step.ComplexCombatData)
.AsSyntaxNodeOrToken(), .AsSyntaxNodeOrToken(),
Assignment(nameof(QuestStep.CombatItemUse), step.CombatItemUse, Assignment(nameof(QuestStep.CombatItemUse), step.CombatItemUse,
emptyStep.CombatItemUse) emptyStep.CombatItemUse)
.AsSyntaxNodeOrToken(), .AsSyntaxNodeOrToken(),
Assignment(nameof(QuestStep.CombatDelaySecondsAtStart), Assignment(nameof(QuestStep.CombatDelaySecondsAtStart),
step.CombatDelaySecondsAtStart, step.CombatDelaySecondsAtStart,
@ -123,6 +123,9 @@ internal static class QuestStepExtensions
Assignment(nameof(QuestStep.AutoDutyEnabled), Assignment(nameof(QuestStep.AutoDutyEnabled),
step.AutoDutyEnabled, emptyStep.AutoDutyEnabled) step.AutoDutyEnabled, emptyStep.AutoDutyEnabled)
.AsSyntaxNodeOrToken(), .AsSyntaxNodeOrToken(),
Assignment(nameof(QuestStep.SinglePlayerDutyOptions), step.SinglePlayerDutyOptions,
emptyStep.SinglePlayerDutyOptions)
.AsSyntaxNodeOrToken(),
Assignment(nameof(QuestStep.SkipConditions), step.SkipConditions, Assignment(nameof(QuestStep.SkipConditions), step.SkipConditions,
emptyStep.SkipConditions) emptyStep.SkipConditions)
.AsSyntaxNodeOrToken(), .AsSyntaxNodeOrToken(),

@ -0,0 +1,30 @@
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Questionable.Model.Questing;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using static Questionable.QuestPathGenerator.RoslynShortcuts;
namespace Questionable.QuestPathGenerator.RoslynElements;
internal static class SinglePlayerDutyOptionsExtensions
{
public static ExpressionSyntax ToExpressionSyntax(this SinglePlayerDutyOptions dutyOptions)
{
var emptyOptions = new SinglePlayerDutyOptions();
return ObjectCreationExpression(
IdentifierName(nameof(SinglePlayerDutyOptions)))
.WithInitializer(
InitializerExpression(
SyntaxKind.ObjectInitializerExpression,
SeparatedList<ExpressionSyntax>(
SyntaxNodeList(
Assignment(nameof(SinglePlayerDutyOptions.Enabled),
dutyOptions.Enabled, emptyOptions.Enabled)
.AsSyntaxNodeOrToken(),
AssignmentList(nameof(SinglePlayerDutyOptions.Notes), dutyOptions.Notes)
.AsSyntaxNodeOrToken(),
Assignment(nameof(SinglePlayerDutyOptions.Index),
dutyOptions.Index, emptyOptions.Index)
.AsSyntaxNodeOrToken()))));
}
}

@ -62,6 +62,7 @@ public static class RoslynShortcuts
ComplexCombatData complexCombatData => complexCombatData.ToExpressionSyntax(), ComplexCombatData complexCombatData => complexCombatData.ToExpressionSyntax(),
QuestWorkValue questWorkValue => questWorkValue.ToExpressionSyntax(), QuestWorkValue questWorkValue => questWorkValue.ToExpressionSyntax(),
List<QuestWorkValue> list => list.ToExpressionSyntax(), // TODO fix in AssignmentList List<QuestWorkValue> list => list.ToExpressionSyntax(), // TODO fix in AssignmentList
SinglePlayerDutyOptions dutyOptions => dutyOptions.ToExpressionSyntax(),
SkipConditions skipConditions => skipConditions.ToExpressionSyntax(), SkipConditions skipConditions => skipConditions.ToExpressionSyntax(),
SkipStepConditions skipStepConditions => skipStepConditions.ToExpressionSyntax(), SkipStepConditions skipStepConditions => skipStepConditions.ToExpressionSyntax(),
SkipItemConditions skipItemCondition => skipItemCondition.ToExpressionSyntax(), SkipItemConditions skipItemCondition => skipItemCondition.ToExpressionSyntax(),

@ -57,6 +57,9 @@
}, },
"TerritoryId": 153, "TerritoryId": 153,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Index": 1
},
"Fly": true "Fly": true
} }
] ]

@ -62,6 +62,9 @@
}, },
"TerritoryId": 154, "TerritoryId": 154,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Index": 1
},
"AetheryteShortcut": "North Shroud - Fallgourd Float", "AetheryteShortcut": "North Shroud - Fallgourd Float",
"Fly": true "Fly": true
} }

@ -119,7 +119,10 @@
"Z": 29.06836 "Z": 29.06836
}, },
"TerritoryId": 152, "TerritoryId": 152,
"InteractionType": "SinglePlayerDuty" "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Index": 1
}
} }
] ]
}, },

@ -140,6 +140,10 @@
}, },
"TerritoryId": 141, "TerritoryId": 141,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": true,
"TestedBossModVersion": 292
},
"Fly": true "Fly": true
} }
] ]

@ -92,6 +92,9 @@
}, },
"TerritoryId": 130, "TerritoryId": 130,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Index": 1
},
"AetheryteShortcut": "Ul'dah" "AetheryteShortcut": "Ul'dah"
} }
] ]

@ -35,6 +35,10 @@
}, },
"TerritoryId": 137, "TerritoryId": 137,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": true,
"TestedBossModVersion": 292
},
"AetheryteShortcut": "Eastern La Noscea - Wineport", "AetheryteShortcut": "Eastern La Noscea - Wineport",
"Fly": true "Fly": true
} }

@ -116,6 +116,10 @@
}, },
"TerritoryId": 152, "TerritoryId": 152,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": true,
"TestedBossModVersion": 292
},
"Fly": true "Fly": true
} }
] ]

@ -65,7 +65,8 @@
"AetheryteShortcut": "East Shroud - Hawthorne Hut", "AetheryteShortcut": "East Shroud - Hawthorne Hut",
"SkipConditions": { "SkipConditions": {
"AetheryteShortcutIf": { "AetheryteShortcutIf": {
"InSameTerritory": true "InSameTerritory": true,
"AetheryteLocked": "East Shroud - Hawthorne Hut"
} }
} }
} }
@ -116,7 +117,11 @@
"Z": 35.568726 "Z": 35.568726
}, },
"TerritoryId": 152, "TerritoryId": 152,
"InteractionType": "SinglePlayerDuty" "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": true,
"TestedBossModVersion": 292
}
} }
] ]
}, },

@ -33,6 +33,39 @@
{ {
"Sequence": 1, "Sequence": 1,
"Steps": [ "Steps": [
{
"DataId": 1001263,
"Position": {
"X": 181.41443,
"Y": -2.3519497,
"Z": -240.40594
},
"TerritoryId": 133,
"InteractionType": "Interact",
"TargetTerritoryId": 152,
"AethernetShortcut": [
"[Gridania] Conjurers' Guild",
"[Gridania] Lancers' Guild"
],
"SkipConditions": {
"StepIf": {
"AetheryteUnlocked": "East Shroud - Hawthorne Hut",
"InTerritory": [
152
]
}
}
},
{
"TerritoryId": 152,
"InteractionType": "AttuneAetheryte",
"Aetheryte": "East Shroud - Hawthorne Hut",
"SkipConditions": {
"StepIf": {
"AetheryteUnlocked": "East Shroud - Hawthorne Hut"
}
}
},
{ {
"Position": { "Position": {
"X": -276.804, "X": -276.804,
@ -42,7 +75,12 @@
"TerritoryId": 152, "TerritoryId": 152,
"InteractionType": "WalkTo", "InteractionType": "WalkTo",
"AetheryteShortcut": "East Shroud - Hawthorne Hut", "AetheryteShortcut": "East Shroud - Hawthorne Hut",
"Fly": true "Fly": true,
"SkipConditions": {
"AetheryteShortcutIf": {
"AetheryteLocked": "East Shroud - Hawthorne Hut"
}
}
}, },
{ {
"DataId": 2000889, "DataId": 2000889,
@ -212,6 +250,10 @@
}, },
"TerritoryId": 152, "TerritoryId": 152,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": true,
"TestedBossModVersion": 292
},
"Fly": true "Fly": true
} }
] ]

@ -138,7 +138,11 @@
"Z": 192.2179 "Z": 192.2179
}, },
"TerritoryId": 148, "TerritoryId": 148,
"InteractionType": "SinglePlayerDuty" "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": true,
"TestedBossModVersion": 292
}
} }
] ]
}, },

@ -111,7 +111,14 @@
"Z": 295.52136 "Z": 295.52136
}, },
"TerritoryId": 148, "TerritoryId": 148,
"InteractionType": "SinglePlayerDuty" "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": true,
"TestedBossModVersion": 292,
"Notes": [
"Healer NPC is only killed after the boss dies; all NPCs need to be killed for the duty to complete"
]
}
} }
] ]
}, },

@ -28,7 +28,14 @@
"Z": -309.55975 "Z": -309.55975
}, },
"TerritoryId": 148, "TerritoryId": 148,
"InteractionType": "SinglePlayerDuty" "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": false,
"TestedBossModVersion": 292,
"Notes": [
"AI doesn't automatically target newly spawning adds until after the boss died, and dies (tested on CNJ)"
]
}
} }
] ]
}, },

@ -77,6 +77,13 @@
}, },
"TerritoryId": 148, "TerritoryId": 148,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": true,
"TestedBossModVersion": 292,
"Notes": [
"(Phase 1) Healer NPCs are only killed after the boss dies - allied NPCs will kill them eventually; all NPCs need to be killed for the duty to complete"
]
},
"AetheryteShortcut": "Central Shroud - Bentbranch Meadows" "AetheryteShortcut": "Central Shroud - Bentbranch Meadows"
} }
] ]

@ -69,6 +69,13 @@
}, },
"TerritoryId": 135, "TerritoryId": 135,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": false,
"TestedBossModVersion": 292,
"Notes": [
"(Phase 1, second enemy group) Stuck with enemy being out of sight -- but still able to attack you (tested on ACN)"
]
},
"AetheryteShortcut": "Lower La Noscea - Moraby Drydocks" "AetheryteShortcut": "Lower La Noscea - Moraby Drydocks"
} }
] ]

@ -45,8 +45,11 @@
"TerritoryId": 134, "TerritoryId": 134,
"InteractionType": "Combat", "InteractionType": "Combat",
"EnemySpawnType": "AutoOnEnterArea", "EnemySpawnType": "AutoOnEnterArea",
"KillEnemyDataIds": [ "ComplexCombatData": [
52 {
"DataId": 52,
"IgnoreQuestMarker": true
}
] ]
}, },
{ {

@ -73,7 +73,11 @@
"Z": -432.15082 "Z": -432.15082
}, },
"TerritoryId": 134, "TerritoryId": 134,
"InteractionType": "SinglePlayerDuty" "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": true,
"TestedBossModVersion": 292
}
} }
] ]
}, },

@ -28,7 +28,14 @@
"Z": -141.7716 "Z": -141.7716
}, },
"TerritoryId": 134, "TerritoryId": 134,
"InteractionType": "SinglePlayerDuty" "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": false,
"TestedBossModVersion": 292,
"Notes": [
"AI doesn't automatically target newly spawning adds until after the boss died (requires healing luck on ACN)"
]
}
} }
] ]
}, },

@ -58,6 +58,13 @@
}, },
"TerritoryId": 138, "TerritoryId": 138,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": true,
"TestedBossModVersion": 292,
"Notes": [
"(Phase 1) Kills PGL NPCs and then the boss - allied NPCs will kill most other NPCs eventually; all NPCs need to be killed for the duty to complete"
]
},
"AetheryteShortcut": "Western La Noscea - Swiftperch" "AetheryteShortcut": "Western La Noscea - Swiftperch"
} }
] ]

@ -44,7 +44,11 @@
"Z": -242.51166 "Z": -242.51166
}, },
"TerritoryId": 145, "TerritoryId": 145,
"InteractionType": "SinglePlayerDuty" "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": true,
"TestedBossModVersion": 292
}
} }
] ]
}, },

@ -79,6 +79,10 @@
}, },
"TerritoryId": 130, "TerritoryId": 130,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": true,
"TestedBossModVersion": 292
},
"AetheryteShortcut": "Ul'dah", "AetheryteShortcut": "Ul'dah",
"AethernetShortcut": [ "AethernetShortcut": [
"[Ul'dah] Aetheryte Plaza", "[Ul'dah] Aetheryte Plaza",
@ -87,6 +91,9 @@
} }
] ]
}, },
{
"Sequence": 5
},
{ {
"Sequence": 255, "Sequence": 255,
"Steps": [ "Steps": [

@ -63,12 +63,22 @@
"AethernetShortcut": [ "AethernetShortcut": [
"[Gridania] Aetheryte Plaza", "[Gridania] Aetheryte Plaza",
"[Gridania] Lancers' Guild" "[Gridania] Lancers' Guild"
] ],
"SkipConditions": {
"StepIf": {
"AetheryteUnlocked": "East Shroud - Hawthorne Hut"
}
}
}, },
{ {
"TerritoryId": 152, "TerritoryId": 152,
"InteractionType": "AttuneAetheryte", "InteractionType": "AttuneAetheryte",
"Aetheryte": "East Shroud - Hawthorne Hut" "Aetheryte": "East Shroud - Hawthorne Hut",
"SkipConditions": {
"StepIf": {
"AetheryteUnlocked": "East Shroud - Hawthorne Hut"
}
}
}, },
{ {
"DataId": 1004886, "DataId": 1004886,
@ -78,7 +88,17 @@
"Z": 475.30322 "Z": 475.30322
}, },
"TerritoryId": 152, "TerritoryId": 152,
"InteractionType": "SinglePlayerDuty" "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": true,
"TestedBossModVersion": 292
},
"AetheryteShortcut": "East Shroud - Hawthorne Hut",
"SkipConditions": {
"AetheryteShortcutIf": {
"InSameTerritory": true
}
}
} }
] ]
}, },

@ -64,6 +64,10 @@
}, },
"TerritoryId": 135, "TerritoryId": 135,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": true,
"TestedBossModVersion": 292
},
"AethernetShortcut": [ "AethernetShortcut": [
"[Limsa Lominsa] The Aftcastle", "[Limsa Lominsa] The Aftcastle",
"[Limsa Lominsa] Tempest Gate (Lower La Noscea)" "[Limsa Lominsa] Tempest Gate (Lower La Noscea)"

@ -59,6 +59,10 @@
}, },
"TerritoryId": 140, "TerritoryId": 140,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": true,
"TestedBossModVersion": 292
},
"AetheryteShortcut": "Western Thanalan - Horizon" "AetheryteShortcut": "Western Thanalan - Horizon"
} }
] ]

@ -46,7 +46,8 @@
}, },
"StopDistance": 7, "StopDistance": 7,
"TerritoryId": 141, "TerritoryId": 141,
"InteractionType": "Interact" "InteractionType": "Interact",
"DelaySecondsAtStart": 2
} }
] ]
}, },

@ -158,7 +158,11 @@
"Z": 117.29602 "Z": 117.29602
}, },
"TerritoryId": 141, "TerritoryId": 141,
"InteractionType": "SinglePlayerDuty" "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": true,
"TestedBossModVersion": 292
}
} }
] ]
}, },

@ -21,6 +21,15 @@
{ {
"Sequence": 255, "Sequence": 255,
"Steps": [ "Steps": [
{
"Position": {
"X": -174.73444,
"Y": 15.450659,
"Z": -266.76144
},
"TerritoryId": 140,
"InteractionType": "WalkTo"
},
{ {
"Position": { "Position": {
"X": -289.1099, "X": -289.1099,

@ -37,7 +37,11 @@
"Z": -293.1411 "Z": -293.1411
}, },
"TerritoryId": 140, "TerritoryId": 140,
"InteractionType": "SinglePlayerDuty" "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": true,
"TestedBossModVersion": 292
}
} }
] ]
}, },

@ -29,7 +29,7 @@
}, },
"TerritoryId": 141, "TerritoryId": 141,
"InteractionType": "Combat", "InteractionType": "Combat",
"EnemySpawnType": "OverworldEnemies", "EnemySpawnType": "FinishCombatIfAny",
"KillEnemyDataIds": [ "KillEnemyDataIds": [
352, 352,
353 353
@ -53,6 +53,25 @@
{ {
"Sequence": 255, "Sequence": 255,
"Steps": [ "Steps": [
{
"Position": {
"X": 131.78122,
"Y": 20.119337,
"Z": -115.27284
},
"TerritoryId": 141,
"InteractionType": "WalkTo"
},
{
"Position": {
"X": 127.7017,
"Y": -0.15994573,
"Z": -161.89238
},
"TerritoryId": 141,
"InteractionType": "WalkTo",
"DisableNavmesh": true
},
{ {
"DataId": 1001605, "DataId": 1001605,
"Position": { "Position": {

@ -28,7 +28,11 @@
"Z": 536.88855 "Z": 536.88855
}, },
"TerritoryId": 141, "TerritoryId": 141,
"InteractionType": "SinglePlayerDuty" "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": true,
"TestedBossModVersion": 292
}
} }
] ]
}, },

@ -64,7 +64,14 @@
"Z": -131.48706 "Z": -131.48706
}, },
"TerritoryId": 141, "TerritoryId": 141,
"InteractionType": "Interact", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": true,
"TestedBossModVersion": 292,
"Notes": [
"(Phase 1) Healer NPCs are only killed after the boss dies - allied NPCs will kill them eventually; all NPCs need to be killed for the duty to complete"
]
},
"AetheryteShortcut": "Central Thanalan - Black Brush Station" "AetheryteShortcut": "Central Thanalan - Black Brush Station"
} }
] ]

@ -73,13 +73,23 @@
}, },
"TerritoryId": 133, "TerritoryId": 133,
"InteractionType": "Interact", "InteractionType": "Interact",
"TargetTerritoryId": 152 "TargetTerritoryId": 152,
"SkipConditions": {
"StepIf": {
"AetheryteUnlocked": "East Shroud - Hawthorne Hut"
}
}
}, },
{ {
"TerritoryId": 152, "TerritoryId": 152,
"InteractionType": "AttuneAetheryte", "InteractionType": "AttuneAetheryte",
"Aetheryte": "East Shroud - Hawthorne Hut", "Aetheryte": "East Shroud - Hawthorne Hut",
"StopDistance": 5 "StopDistance": 5,
"SkipConditions": {
"StepIf": {
"AetheryteUnlocked": "East Shroud - Hawthorne Hut"
}
}
}, },
{ {
"DataId": 1006188, "DataId": 1006188,
@ -89,7 +99,13 @@
"Z": 283.4973 "Z": 283.4973
}, },
"TerritoryId": 152, "TerritoryId": 152,
"InteractionType": "CompleteQuest" "InteractionType": "CompleteQuest",
"AetheryteShortcut": "East Shroud - Hawthorne Hut",
"SkipConditions": {
"AetheryteShortcutIf": {
"InSameTerritory": true
}
}
} }
] ]
} }

@ -64,7 +64,11 @@
"Z": -39.383606 "Z": -39.383606
}, },
"TerritoryId": 152, "TerritoryId": 152,
"InteractionType": "SinglePlayerDuty" "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": true,
"TestedBossModVersion": 292
}
} }
] ]
}, },

@ -83,7 +83,14 @@
"Z": -12.985474 "Z": -12.985474
}, },
"TerritoryId": 153, "TerritoryId": 153,
"InteractionType": "SinglePlayerDuty" "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": true,
"TestedBossModVersion": 292,
"Notes": [
"AI will kill initial adds before the boss, but not switch target whenever new enemies spawn; all NPCs need to be killed for the duty to complete"
]
}
} }
] ]
}, },

@ -26,6 +26,28 @@
{ {
"Sequence": 1, "Sequence": 1,
"Steps": [ "Steps": [
{
"Position": {
"X": -225.94685,
"Y": 26.139933,
"Z": -340.8984
},
"TerritoryId": 146,
"InteractionType": "WalkTo",
"Mount": true,
"SkipConditions": {
"StepIf": {
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
16
]
}
}
},
{ {
"DataId": 2001980, "DataId": 2001980,
"Position": { "Position": {

@ -159,7 +159,11 @@
"Z": -805.478 "Z": -805.478
}, },
"TerritoryId": 140, "TerritoryId": 140,
"InteractionType": "SinglePlayerDuty" "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": true,
"TestedBossModVersion": 292
}
} }
] ]
}, },

@ -103,7 +103,11 @@
"Z": 479.9724 "Z": 479.9724
}, },
"TerritoryId": 1053, "TerritoryId": 1053,
"InteractionType": "SinglePlayerDuty" "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": true,
"TestedBossModVersion": 292
}
} }
] ]
}, },

@ -68,6 +68,15 @@
{ {
"Sequence": 3, "Sequence": 3,
"Steps": [ "Steps": [
{
"Position": {
"X": -561.9863,
"Y": 9.919454,
"Z": 66.29564
},
"TerritoryId": 152,
"InteractionType": "WalkTo"
},
{ {
"DataId": 1008276, "DataId": 1008276,
"Position": { "Position": {

@ -102,7 +102,7 @@
}, },
"TerritoryId": 137, "TerritoryId": 137,
"InteractionType": "WalkTo", "InteractionType": "WalkTo",
"Fly": true "Mount": true
}, },
{ {
"Position": { "Position": {

@ -78,6 +78,10 @@
"StopDistance": 1, "StopDistance": 1,
"TerritoryId": 156, "TerritoryId": 156,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": true,
"TestedBossModVersion": 292
},
"Fly": true "Fly": true
} }
] ]

@ -120,6 +120,15 @@
8 8
] ]
}, },
{
"Position": {
"X": -140.77458,
"Y": 39.99999,
"Z": 155.4174
},
"TerritoryId": 128,
"InteractionType": "WalkTo"
},
{ {
"DataId": 1009133, "DataId": 1009133,
"Position": { "Position": {

@ -100,6 +100,28 @@
2 2
] ]
}, },
{
"Position": {
"X": 86.662384,
"Y": 28.34813,
"Z": -627.5218
},
"TerritoryId": 156,
"InteractionType": "WalkTo",
"Fly": true,
"SkipConditions": {
"StepIf": {
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
32
]
}
}
},
{ {
"DataId": 1009143, "DataId": 1009143,
"Position": { "Position": {
@ -109,7 +131,6 @@
}, },
"TerritoryId": 156, "TerritoryId": 156,
"InteractionType": "Interact", "InteractionType": "Interact",
"Fly": true,
"$": "1 112 0 0 0 2 -> 2 96 0 0 0 34", "$": "1 112 0 0 0 2 -> 2 96 0 0 0 34",
"CompletionQuestVariablesFlags": [ "CompletionQuestVariablesFlags": [
null, null,

@ -71,6 +71,14 @@
}, },
"TerritoryId": 147, "TerritoryId": 147,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": true,
"TestedBossModVersion": 292,
"Notes": [
"Will target Eline first (other NPCs later), and move to some -other- group of NPCs; only re-targets once they're at 1 HP (for Eline) or die",
"If the target isn't in melee range but other NPCs are, whether any AOEs are used for nearby enemies seems random"
]
},
"Fly": true, "Fly": true,
"AetheryteShortcut": "Northern Thanalan - Ceruleum Processing Plant" "AetheryteShortcut": "Northern Thanalan - Ceruleum Processing Plant"
} }

@ -28,7 +28,16 @@
"Z": -328.66406 "Z": -328.66406
}, },
"TerritoryId": 155, "TerritoryId": 155,
"InteractionType": "SinglePlayerDuty" "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": false,
"TestedBossModVersion": 292,
"Notes": [
"WIP: Needs to be re-tested",
"AI doesn't move after starting the instance, so enemies won't be triggered",
"(First Barrier) If the player is too far south, after being stunned by Vishap's roar, AI doesn't move out of the AOE and dies to the Cauterize"
]
}
} }
] ]
}, },

@ -80,7 +80,8 @@
}, },
"TerritoryId": 148, "TerritoryId": 148,
"InteractionType": "UseItem", "InteractionType": "UseItem",
"ItemId": 4868 "ItemId": 4868,
"Fly": true
}, },
{ {
"Position": { "Position": {

@ -46,6 +46,10 @@
}, },
"TerritoryId": 155, "TerritoryId": 155,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": true,
"TestedBossModVersion": 292
},
"Fly": true "Fly": true
} }
] ]

@ -95,7 +95,11 @@
}, },
"TerritoryId": 138, "TerritoryId": 138,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"Fly": true "Fly": true,
"SinglePlayerDutyOptions": {
"Enabled": true,
"TestedBossModVersion": 292
}
} }
] ]
}, },

@ -30,7 +30,11 @@
}, },
"TerritoryId": 397, "TerritoryId": 397,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"Comment": "Walk straight to Gorgagne Mills basement, ignore footprints" "Comment": "Walk straight to Gorgagne Mills basement, ignore footprints",
"SinglePlayerDutyOptions": {
"Enabled": true,
"TestedBossModVersion": 292
}
} }
] ]
}, },

@ -58,7 +58,11 @@
"Z": 349.96558 "Z": 349.96558
}, },
"TerritoryId": 401, "TerritoryId": 401,
"InteractionType": "SinglePlayerDuty" "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": true,
"TestedBossModVersion": 292
}
} }
] ]
}, },

@ -78,7 +78,11 @@
"AethernetShortcut": [ "AethernetShortcut": [
"[Ishgard] The Forgotten Knight", "[Ishgard] The Forgotten Knight",
"[Ishgard] The Tribunal" "[Ishgard] The Tribunal"
] ],
"SinglePlayerDutyOptions": {
"Enabled": true,
"TestedBossModVersion": 292
}
} }
] ]
}, },

@ -28,7 +28,14 @@
"Z": 388.63196 "Z": 388.63196
}, },
"TerritoryId": 145, "TerritoryId": 145,
"InteractionType": "SinglePlayerDuty" "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": true,
"TestedBossModVersion": 292,
"Notes": [
"Will not move into melee range to kill the gate; Alphinaud will kill it after a while"
]
}
} }
] ]
}, },

@ -21,6 +21,16 @@
{ {
"Sequence": 1, "Sequence": 1,
"Steps": [ "Steps": [
{
"Position": {
"X": 474.62885,
"Y": 200.2377,
"Z": 657.9519
},
"TerritoryId": 397,
"InteractionType": "WalkTo",
"AetheryteShortcut": "Coerthas Western Highlands - Falcon's Nest"
},
{ {
"Position": { "Position": {
"X": 486.38373, "X": 486.38373,
@ -28,8 +38,7 @@
"Z": 239.54294 "Z": 239.54294
}, },
"TerritoryId": 397, "TerritoryId": 397,
"InteractionType": "WalkTo", "InteractionType": "WalkTo"
"AetheryteShortcut": "Coerthas Western Highlands - Falcon's Nest"
}, },
{ {
"Position": { "Position": {
@ -69,7 +78,11 @@
}, },
"TerritoryId": 397, "TerritoryId": 397,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"DisableNavmesh": true "DisableNavmesh": true,
"SinglePlayerDutyOptions": {
"Enabled": true,
"TestedBossModVersion": 292
}
} }
] ]
}, },

@ -59,7 +59,14 @@
"KillEnemyDataIds": [ "KillEnemyDataIds": [
4015 4015
], ],
"$": "0 0 0 0 0 0 -> " "CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
128
]
}, },
{ {
"Position": { "Position": {
@ -72,6 +79,14 @@
"EnemySpawnType": "AutoOnEnterArea", "EnemySpawnType": "AutoOnEnterArea",
"KillEnemyDataIds": [ "KillEnemyDataIds": [
4015 4015
],
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
64
] ]
} }
] ]

@ -89,6 +89,16 @@
"InteractionType": "WalkTo", "InteractionType": "WalkTo",
"Mount": true "Mount": true
}, },
{
"Position": {
"X": -335.0186,
"Y": 13.983504,
"Z": -100.87753
},
"TerritoryId": 140,
"InteractionType": "WalkTo",
"Fly": true
},
{ {
"DataId": 1004019, "DataId": 1004019,
"Position": { "Position": {
@ -98,7 +108,6 @@
}, },
"TerritoryId": 140, "TerritoryId": 140,
"InteractionType": "Interact", "InteractionType": "Interact",
"Fly": true,
"TargetTerritoryId": 140 "TargetTerritoryId": 140
}, },
{ {

@ -74,7 +74,11 @@
"Z": 37.247192 "Z": 37.247192
}, },
"TerritoryId": 418, "TerritoryId": 418,
"InteractionType": "SinglePlayerDuty" "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": true,
"TestedBossModVersion": 292
}
} }
] ]
}, },

@ -56,7 +56,11 @@
"TerritoryId": 401, "TerritoryId": 401,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"Emote": "lookout", "Emote": "lookout",
"StopDistance": 0.25 "StopDistance": 0.25,
"SinglePlayerDutyOptions": {
"Enabled": true,
"TestedBossModVersion": 292
}
} }
] ]
}, },

@ -47,7 +47,11 @@
"AethernetShortcut": [ "AethernetShortcut": [
"[Idyllshire] Aetheryte Plaza", "[Idyllshire] Aetheryte Plaza",
"[Idyllshire] Epilogue Gate (Eastern Hinterlands)" "[Idyllshire] Epilogue Gate (Eastern Hinterlands)"
] ],
"SinglePlayerDutyOptions": {
"Enabled": true,
"TestedBossModVersion": 292
}
} }
] ]
}, },

@ -68,7 +68,11 @@
"Z": 553.97876 "Z": 553.97876
}, },
"TerritoryId": 402, "TerritoryId": 402,
"InteractionType": "SinglePlayerDuty" "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": true,
"TestedBossModVersion": 292
}
} }
] ]
}, },

@ -34,7 +34,7 @@
"Z": -509.51404 "Z": -509.51404
}, },
"TerritoryId": 622, "TerritoryId": 622,
"InteractionType": "Interact", "InteractionType": "SinglePlayerDuty",
"Fly": true "Fly": true
} }
] ]

@ -35,7 +35,7 @@
"Z": 686.427 "Z": 686.427
}, },
"TerritoryId": 135, "TerritoryId": 135,
"InteractionType": "Interact", "InteractionType": "SinglePlayerDuty",
"AetheryteShortcut": "Lower La Noscea - Moraby Drydocks" "AetheryteShortcut": "Lower La Noscea - Moraby Drydocks"
} }
] ]

@ -61,7 +61,19 @@
"TerritoryId": 156, "TerritoryId": 156,
"InteractionType": "Interact", "InteractionType": "Interact",
"AetheryteShortcut": "Mor Dhona", "AetheryteShortcut": "Mor Dhona",
"TargetTerritoryId": 351 "TargetTerritoryId": 351,
"SkipConditions": {
"AetheryteShortcutIf": {
"InTerritory": [
351
]
},
"StepIf": {
"InTerritory": [
351
]
}
}
}, },
{ {
"DataId": 1032081, "DataId": 1032081,
@ -73,13 +85,17 @@
"TerritoryId": 351, "TerritoryId": 351,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"Comment": "Estinien vs. Arch Ultima", "Comment": "Estinien vs. Arch Ultima",
"DialogueChoices": [ "SinglePlayerDutyOptions": {
{ "Enabled": false,
"Type": "YesNo", "TestedBossModVersion": 292,
"Prompt": "TEXT_LUCKMG110_03682_Q1_100_125", "Notes": [
"Yes": true "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"
]
},
"$": "This doesn't have a duty confirmation dialog, so we're treating TEXT_LUCKMG110_03682_Q1_100_125 as one"
} }
] ]
}, },

@ -46,6 +46,13 @@
}, },
"TerritoryId": 817, "TerritoryId": 817,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": false,
"TestedBossModVersion": 292,
"Notes": [
"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",

@ -104,6 +104,9 @@
"StopDistance": 5, "StopDistance": 5,
"TerritoryId": 829, "TerritoryId": 829,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Index": 1
},
"DialogueChoices": [ "DialogueChoices": [
{ {
"Type": "List", "Type": "List",

@ -1257,6 +1257,50 @@
] ]
} }
}, },
{
"if": {
"properties": {
"InteractionType": {
"const": "SinglePlayerDuty"
}
}
},
"then": {
"properties": {
"SinglePlayerDutyOptions": {
"type": "object",
"properties": {
"Enabled": {
"type": "boolean"
},
"Notes": {
"type": "array",
"items": {
"type": "string"
}
},
"Index": {
"type": "integer",
"minimum": 0,
"maximum": 1,
"description": "If a quest has multiple solo instances (which affects 5 quests total), indicates which one this is"
},
"TestedBossModVersion": {
"type": "number",
"minimum": 292
},
"$": {
"type": "string"
}
},
"TODO_required": [
"Enabled"
],
"additionalProperties": false
}
}
}
},
{ {
"if": { "if": {
"properties": { "properties": {

@ -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);

@ -75,6 +75,8 @@ public sealed class QuestStep
public JumpDestination? JumpDestination { get; set; } public JumpDestination? JumpDestination { get; set; }
public uint? ContentFinderConditionId { get; set; } public uint? ContentFinderConditionId { get; set; }
public bool AutoDutyEnabled { get; set; } public bool AutoDutyEnabled { get; set; }
public SinglePlayerDutyOptions? SinglePlayerDutyOptions { get; set; }
public byte SinglePlayerDutyIndex => SinglePlayerDutyOptions?.Index ?? 0;
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();

@ -0,0 +1,10 @@
using System.Collections.Generic;
namespace Questionable.Model.Questing;
public sealed class SinglePlayerDutyOptions
{
public bool Enabled { get; set; }
public List<string> Notes { get; set; } = [];
public byte Index { get; set; }
}

@ -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": {

@ -19,8 +19,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GatheringPaths", "Gathering
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GatheringPathRenderer", "GatheringPathRenderer\GatheringPathRenderer.csproj", "{F514DA95-9867-4F3F-8062-ACE0C62E8740}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GatheringPathRenderer", "GatheringPathRenderer\GatheringPathRenderer.csproj", "{F514DA95-9867-4F3F-8062-ACE0C62E8740}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ECommons", "vendor\ECommons\ECommons\ECommons.csproj", "{A12D7B4B-8E6E-4DCF-A41A-12F62E9FF94B}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{BBFFC6EA-15B1-48FC-B4D3-D9491278C27F}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{BBFFC6EA-15B1-48FC-B4D3-D9491278C27F}"
ProjectSection(SolutionItems) = preProject ProjectSection(SolutionItems) = preProject
Directory.Build.targets = Directory.Build.targets Directory.Build.targets = Directory.Build.targets
@ -30,6 +28,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "vendor", "vendor", "{8F5EC9
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NotificationMasterAPI", "vendor\NotificationMasterAPI\NotificationMasterAPI\NotificationMasterAPI.csproj", "{9BD494ED-22F2-487B-BCE1-435399A8720E}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NotificationMasterAPI", "vendor\NotificationMasterAPI\NotificationMasterAPI\NotificationMasterAPI.csproj", "{9BD494ED-22F2-487B-BCE1-435399A8720E}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pictomancy", "vendor\pictomancy\Pictomancy\Pictomancy.csproj", "{D1AE2F8C-BDE7-457F-A369-973101044A25}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64 Debug|x64 = Debug|x64
@ -68,20 +68,20 @@ Global
{F514DA95-9867-4F3F-8062-ACE0C62E8740}.Debug|x64.Build.0 = Debug|x64 {F514DA95-9867-4F3F-8062-ACE0C62E8740}.Debug|x64.Build.0 = Debug|x64
{F514DA95-9867-4F3F-8062-ACE0C62E8740}.Release|x64.ActiveCfg = Release|x64 {F514DA95-9867-4F3F-8062-ACE0C62E8740}.Release|x64.ActiveCfg = Release|x64
{F514DA95-9867-4F3F-8062-ACE0C62E8740}.Release|x64.Build.0 = Release|x64 {F514DA95-9867-4F3F-8062-ACE0C62E8740}.Release|x64.Build.0 = Release|x64
{A12D7B4B-8E6E-4DCF-A41A-12F62E9FF94B}.Debug|x64.ActiveCfg = Debug|x64
{A12D7B4B-8E6E-4DCF-A41A-12F62E9FF94B}.Debug|x64.Build.0 = Debug|x64
{A12D7B4B-8E6E-4DCF-A41A-12F62E9FF94B}.Release|x64.ActiveCfg = Release|x64
{A12D7B4B-8E6E-4DCF-A41A-12F62E9FF94B}.Release|x64.Build.0 = Release|x64
{9BD494ED-22F2-487B-BCE1-435399A8720E}.Debug|x64.ActiveCfg = Debug|x64 {9BD494ED-22F2-487B-BCE1-435399A8720E}.Debug|x64.ActiveCfg = Debug|x64
{9BD494ED-22F2-487B-BCE1-435399A8720E}.Debug|x64.Build.0 = Debug|x64 {9BD494ED-22F2-487B-BCE1-435399A8720E}.Debug|x64.Build.0 = Debug|x64
{9BD494ED-22F2-487B-BCE1-435399A8720E}.Release|x64.ActiveCfg = Release|x64 {9BD494ED-22F2-487B-BCE1-435399A8720E}.Release|x64.ActiveCfg = Release|x64
{9BD494ED-22F2-487B-BCE1-435399A8720E}.Release|x64.Build.0 = Release|x64 {9BD494ED-22F2-487B-BCE1-435399A8720E}.Release|x64.Build.0 = Release|x64
{D1AE2F8C-BDE7-457F-A369-973101044A25}.Debug|x64.ActiveCfg = Debug|x64
{D1AE2F8C-BDE7-457F-A369-973101044A25}.Debug|x64.Build.0 = Debug|x64
{D1AE2F8C-BDE7-457F-A369-973101044A25}.Release|x64.ActiveCfg = Release|x64
{D1AE2F8C-BDE7-457F-A369-973101044A25}.Release|x64.Build.0 = Release|x64
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
EndGlobalSection EndGlobalSection
GlobalSection(NestedProjects) = preSolution GlobalSection(NestedProjects) = preSolution
{A12D7B4B-8E6E-4DCF-A41A-12F62E9FF94B} = {8F5EC9D5-4CE7-433B-BB3A-782500E84DDB}
{9BD494ED-22F2-487B-BCE1-435399A8720E} = {8F5EC9D5-4CE7-433B-BB3A-782500E84DDB} {9BD494ED-22F2-487B-BCE1-435399A8720E} = {8F5EC9D5-4CE7-433B-BB3A-782500E84DDB}
{D1AE2F8C-BDE7-457F-A369-973101044A25} = {8F5EC9D5-4CE7-433B-BB3A-782500E84DDB}
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal

@ -1,4 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Dalamud.Configuration; using Dalamud.Configuration;
using Dalamud.Game.Text; using Dalamud.Game.Text;
using FFXIVClientStructs.FFXIV.Client.UI.Agent; using FFXIVClientStructs.FFXIV.Client.UI.Agent;
@ -14,6 +15,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 SinglePlayerDutyConfiguration SinglePlayerDuties { 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 +43,17 @@ internal sealed class Configuration : IPluginConfiguration
public HashSet<uint> BlacklistedDutyCfcIds { get; set; } = []; public HashSet<uint> BlacklistedDutyCfcIds { get; set; } = [];
} }
internal sealed class SinglePlayerDutyConfiguration
{
public bool RunSoloInstancesWithBossMod { get; set; }
[SuppressMessage("Performance", "CA1822", Justification = "Will be fixed when no longer WIP")]
public byte RetryDifficulty => 0;
public HashSet<uint> WhitelistedSinglePlayerDutyCfcIds { get; set; } = [];
public HashSet<uint> BlacklistedSinglePlayerDutyCfcIds { get; set; } = [];
}
internal sealed class NotificationConfiguration internal sealed class NotificationConfiguration
{ {
public bool Enabled { get; set; } = true; public bool Enabled { get; set; } = true;

@ -9,33 +9,26 @@ using Questionable.Model;
using System; using System;
using System.IO; using System.IO;
using System.Numerics; using System.Numerics;
using Questionable.External;
namespace Questionable.Controller.CombatModules; namespace Questionable.Controller.CombatModules;
internal sealed class BossModModule : ICombatModule, IDisposable internal sealed class BossModModule : ICombatModule, IDisposable
{ {
private const string Name = "BossMod";
private readonly ILogger<BossModModule> _logger; private readonly ILogger<BossModModule> _logger;
private readonly BossModIpc _bossModIpc;
private readonly Configuration _configuration; private readonly Configuration _configuration;
private readonly ICallGateSubscriber<string, string?> _getPreset;
private readonly ICallGateSubscriber<string, bool, bool> _createPreset;
private readonly ICallGateSubscriber<string, bool> _setPreset;
private readonly ICallGateSubscriber<bool> _clearPreset;
private static Stream Preset => typeof(BossModModule).Assembly.GetManifestResourceStream("Questionable.Controller.CombatModules.BossModPreset")!; private static Stream Preset => typeof(BossModModule).Assembly.GetManifestResourceStream("Questionable.Controller.CombatModules.BossModPreset")!;
public BossModModule( public BossModModule(
ILogger<BossModModule> logger, ILogger<BossModModule> logger,
IDalamudPluginInterface pluginInterface, BossModIpc bossModIpc,
Configuration configuration) Configuration configuration)
{ {
_logger = logger; _logger = logger;
_bossModIpc = bossModIpc;
_configuration = configuration; _configuration = configuration;
_getPreset = pluginInterface.GetIpcSubscriber<string, string?>($"{Name}.Presets.Get");
_createPreset = pluginInterface.GetIpcSubscriber<string, bool, bool>($"{Name}.Presets.Create");
_setPreset = pluginInterface.GetIpcSubscriber<string, bool>($"{Name}.Presets.SetActive");
_clearPreset = pluginInterface.GetIpcSubscriber<bool>($"{Name}.Presets.ClearActive");
} }
public bool CanHandleFight(CombatController.CombatData combatData) public bool CanHandleFight(CombatController.CombatData combatData)
@ -43,26 +36,19 @@ internal sealed class BossModModule : ICombatModule, IDisposable
if (_configuration.General.CombatModule != Configuration.ECombatModule.BossMod) if (_configuration.General.CombatModule != Configuration.ECombatModule.BossMod)
return false; return false;
try return _bossModIpc.IsSupported();
{
return _getPreset.HasFunction;
}
catch (IpcError)
{
return false;
}
} }
public bool Start(CombatController.CombatData combatData) public bool Start(CombatController.CombatData combatData)
{ {
try try
{ {
if (_getPreset.InvokeFunc("Questionable") == null) if (_bossModIpc.GetPreset("Questionable") == null)
{ {
using var reader = new StreamReader(Preset); using var reader = new StreamReader(Preset);
_logger.LogInformation("Loading Questionable BossMod Preset: {LoadedState}", _createPreset.InvokeFunc(reader.ReadToEnd(), true)); _logger.LogInformation("Loading Questionable BossMod Preset: {LoadedState}", _bossModIpc.CreatePreset(reader.ReadToEnd(), true));
} }
_setPreset.InvokeFunc("Questionable"); _bossModIpc.SetPreset("Questionable");
return true; return true;
} }
catch (IpcError e) catch (IpcError e)
@ -76,7 +62,7 @@ internal sealed class BossModModule : ICombatModule, IDisposable
{ {
try try
{ {
_clearPreset.InvokeFunc(); _bossModIpc.ClearPreset();
return true; return true;
} }
catch (IpcError e) catch (IpcError e)

@ -19,6 +19,7 @@ using Lumina.Excel.Sheets;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Questionable.Controller.Steps.Interactions; using Questionable.Controller.Steps.Interactions;
using Questionable.Data; using Questionable.Data;
using Questionable.External;
using Questionable.Functions; using Questionable.Functions;
using Questionable.Model; using Questionable.Model;
using Questionable.Model.Gathering; using Questionable.Model.Gathering;
@ -45,6 +46,8 @@ internal sealed class InteractionUiController : IDisposable
private readonly ITargetManager _targetManager; private readonly ITargetManager _targetManager;
private readonly IClientState _clientState; private readonly IClientState _clientState;
private readonly ShopController _shopController; private readonly ShopController _shopController;
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;
@ -68,6 +71,8 @@ internal sealed class InteractionUiController : IDisposable
IPluginLog pluginLog, IPluginLog pluginLog,
IClientState clientState, IClientState clientState,
ShopController shopController, ShopController shopController,
BossModIpc bossModIpc,
Configuration configuration,
ILogger<InteractionUiController> logger) ILogger<InteractionUiController> logger)
{ {
_addonLifecycle = addonLifecycle; _addonLifecycle = addonLifecycle;
@ -85,6 +90,8 @@ internal sealed class InteractionUiController : IDisposable
_targetManager = targetManager; _targetManager = targetManager;
_clientState = clientState; _clientState = clientState;
_shopController = shopController; _shopController = shopController;
_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)!;
@ -94,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);
@ -140,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");
@ -176,7 +190,10 @@ internal sealed class InteractionUiController : IDisposable
int? answer = HandleListChoice(actualPrompt, answers, checkAllSteps) ?? HandleInstanceListChoice(actualPrompt); int? answer = HandleListChoice(actualPrompt, answers, checkAllSteps) ?? HandleInstanceListChoice(actualPrompt);
if (answer != null) if (answer != null)
{
_logger.LogInformation("Using choice {Choice} for list prompt '{Prompt}'", answer, actualPrompt);
addonSelectString->AtkUnitBase.FireCallbackInt(answer.Value); addonSelectString->AtkUnitBase.FireCallbackInt(answer.Value);
}
} }
private unsafe void CutsceneSelectStringPostSetup(AddonEvent type, AddonArgs args) private unsafe void CutsceneSelectStringPostSetup(AddonEvent type, AddonArgs args)
@ -224,6 +241,7 @@ internal sealed class InteractionUiController : IDisposable
int? answer = HandleListChoice(actualPrompt, answers, checkAllSteps); int? answer = HandleListChoice(actualPrompt, answers, checkAllSteps);
if (answer != null) if (answer != null)
{ {
_logger.LogInformation("Using choice {Choice} for list prompt '{Prompt}'", answer, actualPrompt);
addonSelectIconString->AtkUnitBase.FireCallbackInt(answer.Value); addonSelectIconString->AtkUnitBase.FireCallbackInt(answer.Value);
return; return;
} }
@ -266,6 +284,7 @@ internal sealed class InteractionUiController : IDisposable
int questSelection = answers.FindIndex(x => GameFunctions.GameStringEquals(questName, x)); int questSelection = answers.FindIndex(x => GameFunctions.GameStringEquals(questName, x));
if (questSelection >= 0) if (questSelection >= 0)
{ {
_logger.LogInformation("Selecting quest {QuestName}", questName);
addonSelectIconString->AtkUnitBase.FireCallbackInt(questSelection); addonSelectIconString->AtkUnitBase.FireCallbackInt(questSelection);
return true; return true;
} }
@ -598,14 +617,14 @@ internal sealed class InteractionUiController : IDisposable
if (checkAllSteps) if (checkAllSteps)
{ {
var sequence = quest.FindSequence(currentQuest.Sequence); var sequence = quest.FindSequence(currentQuest.Sequence);
if (sequence != null && HandleDefaultYesNo(addonSelectYesno, quest, if (sequence != null &&
sequence.Steps.SelectMany(x => x.DialogueChoices).ToList(), actualPrompt)) sequence.Steps.Any(step => HandleDefaultYesNo(addonSelectYesno, quest, step, step.DialogueChoices, actualPrompt)))
return true; return true;
} }
else else
{ {
var step = quest.FindSequence(currentQuest.Sequence)?.FindStep(currentQuest.Step); var step = quest.FindSequence(currentQuest.Sequence)?.FindStep(currentQuest.Step);
if (step != null && HandleDefaultYesNo(addonSelectYesno, quest, step.DialogueChoices, actualPrompt)) if (step != null && HandleDefaultYesNo(addonSelectYesno, quest, step, step.DialogueChoices, actualPrompt))
return true; return true;
} }
@ -619,7 +638,7 @@ internal sealed class InteractionUiController : IDisposable
Yes = true Yes = true
}; };
if (HandleDefaultYesNo(addonSelectYesno, quest, [dialogueChoice], actualPrompt)) if (HandleDefaultYesNo(addonSelectYesno, quest, null, [dialogueChoice], actualPrompt))
return true; return true;
} }
@ -630,7 +649,7 @@ internal sealed class InteractionUiController : IDisposable
} }
private unsafe bool HandleDefaultYesNo(AddonSelectYesno* addonSelectYesno, Quest quest, private unsafe bool HandleDefaultYesNo(AddonSelectYesno* addonSelectYesno, Quest quest,
List<DialogueChoice> dialogueChoices, string actualPrompt) QuestStep? step, List<DialogueChoice> dialogueChoices, string actualPrompt)
{ {
_logger.LogTrace("DefaultYesNo: Choice count: {Count}", dialogueChoices.Count); _logger.LogTrace("DefaultYesNo: Choice count: {Count}", dialogueChoices.Count);
foreach (var dialogueChoice in dialogueChoices) foreach (var dialogueChoice in dialogueChoices)
@ -655,10 +674,35 @@ internal sealed class InteractionUiController : IDisposable
continue; continue;
} }
_logger.LogInformation("Returning {YesNo} for '{Prompt}'", dialogueChoice.Yes ? "Yes" : "No", actualPrompt);
addonSelectYesno->AtkUnitBase.FireCallbackInt(dialogueChoice.Yes ? 0 : 1); addonSelectYesno->AtkUnitBase.FireCallbackInt(dialogueChoice.Yes ? 0 : 1);
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 } &&
_bossModIpc.IsConfiguredToRunSoloInstance(questId, step.SinglePlayerDutyOptions))
{
// 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("SinglePlayerDutyYesNo: probably Single Player Duty");
return true;
}
return false; return false;
} }
@ -692,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
@ -864,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);

@ -10,11 +10,13 @@ using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Questionable.Controller.Steps; using Questionable.Controller.Steps;
using Questionable.Controller.Steps.Interactions;
using Questionable.Controller.Steps.Shared; using Questionable.Controller.Steps.Shared;
using Questionable.External; using Questionable.External;
using Questionable.Functions; using Questionable.Functions;
using Questionable.Model; using Questionable.Model;
using Questionable.Model.Questing; using Questionable.Model.Questing;
using Questionable.Windows.ConfigComponents;
using Quest = Questionable.Model.Quest; using Quest = Questionable.Model.Quest;
namespace Questionable.Controller; namespace Questionable.Controller;
@ -35,6 +37,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
private readonly Configuration _configuration; private readonly Configuration _configuration;
private readonly YesAlreadyIpc _yesAlreadyIpc; private readonly YesAlreadyIpc _yesAlreadyIpc;
private readonly TaskCreator _taskCreator; private readonly TaskCreator _taskCreator;
private readonly SinglePlayerDutyConfigComponent _singlePlayerDutyConfigComponent;
private readonly ILogger<QuestController> _logger; private readonly ILogger<QuestController> _logger;
private readonly object _progressLock = new(); private readonly object _progressLock = new();
@ -76,7 +79,8 @@ internal sealed class QuestController : MiniTaskController<QuestController>
TaskCreator taskCreator, TaskCreator taskCreator,
IServiceProvider serviceProvider, IServiceProvider serviceProvider,
InterruptHandler interruptHandler, InterruptHandler interruptHandler,
IDataManager dataManager) IDataManager dataManager,
SinglePlayerDutyConfigComponent singlePlayerDutyConfigComponent)
: base(chatGui, condition, serviceProvider, interruptHandler, dataManager, logger) : base(chatGui, condition, serviceProvider, interruptHandler, dataManager, logger)
{ {
_clientState = clientState; _clientState = clientState;
@ -93,6 +97,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
_configuration = configuration; _configuration = configuration;
_yesAlreadyIpc = yesAlreadyIpc; _yesAlreadyIpc = yesAlreadyIpc;
_taskCreator = taskCreator; _taskCreator = taskCreator;
_singlePlayerDutyConfigComponent = singlePlayerDutyConfigComponent;
_logger = logger; _logger = logger;
_condition.ConditionChange += OnConditionChange; _condition.ConditionChange += OnConditionChange;
@ -169,6 +174,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
DebugState = null; DebugState = null;
_questRegistry.Reload(); _questRegistry.Reload();
_singlePlayerDutyConfigComponent.Reload();
} }
} }
@ -195,7 +201,13 @@ internal sealed class QuestController : MiniTaskController<QuestController>
if (!_clientState.IsLoggedIn || _condition[ConditionFlag.Unconscious]) if (!_clientState.IsLoggedIn || _condition[ConditionFlag.Unconscious])
{ {
if (!_taskQueue.AllTasksComplete) if (_condition[ConditionFlag.Unconscious] &&
_condition[ConditionFlag.SufferingStatusAffliction63] &&
_clientState.TerritoryType == SinglePlayerDuty.LahabreaTerritoryId)
{
// ignore, we're in the lahabrea fight
}
else if (!_taskQueue.AllTasksComplete)
{ {
Stop("HP = 0"); Stop("HP = 0");
_movementController.Stop(); _movementController.Stop();
@ -757,6 +769,10 @@ internal sealed class QuestController : MiniTaskController<QuestController>
if (ManualPriorityQuests.Contains(currentQuest.Quest)) if (ManualPriorityQuests.Contains(currentQuest.Quest))
return false; return false;
// "ifrit bleeds, we can kill it" isn't listed as priority quest, as we accept it during the MSQ 'Moving On'
if (currentQuest.Quest.Id is QuestId { Value: 1048 })
return false;
if (currentQuest.Quest.Info.AlliedSociety != EAlliedSociety.None) if (currentQuest.Quest.Info.AlliedSociety != EAlliedSociety.None)
return false; return false;

@ -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,9 +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 == EInteractionType.Duty && x.Step.ContentFinderConditionId != null)) x.Step.InteractionType is EInteractionType.Duty or EInteractionType.SinglePlayerDuty))
{ {
_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);
} }
} }
} }
@ -232,11 +240,11 @@ internal sealed class QuestRegistry
public bool TryGetQuest(ElementId questId, [NotNullWhen(true)] out Quest? quest) public bool TryGetQuest(ElementId questId, [NotNullWhen(true)] out Quest? quest)
=> _quests.TryGetValue(questId, out quest); => _quests.TryGetValue(questId, out quest);
public List<QuestInfo> GetKnownClassJobQuests(EClassJob classJob) public List<QuestInfo> GetKnownClassJobQuests(EClassJob classJob, bool includeRoleQuests = true)
{ {
List<QuestInfo> allQuests = [.._questData.GetClassJobQuests(classJob)]; List<QuestInfo> allQuests = [.._questData.GetClassJobQuests(classJob, includeRoleQuests)];
if (classJob.AsJob() != classJob) if (classJob.AsJob() != classJob)
allQuests.AddRange(_questData.GetClassJobQuests(classJob.AsJob())); allQuests.AddRange(_questData.GetClassJobQuests(classJob.AsJob(), includeRoleQuests));
return allQuests return allQuests
.Where(x => IsKnownQuest(x.QuestId)) .Where(x => IsKnownQuest(x.QuestId))

@ -37,7 +37,7 @@ internal static class Mount
private bool _mountTriggered; private bool _mountTriggered;
private DateTime _retryAt = DateTime.MinValue; private DateTime _retryAt = DateTime.MinValue;
public MountResult EvaluateMountState() public unsafe MountResult EvaluateMountState()
{ {
if (condition[ConditionFlag.Mounted]) if (condition[ConditionFlag.Mounted])
return MountResult.DontMount; return MountResult.DontMount;
@ -58,7 +58,7 @@ internal static class Mount
{ {
Vector3 playerPosition = clientState.LocalPlayer?.Position ?? Vector3.Zero; Vector3 playerPosition = clientState.LocalPlayer?.Position ?? Vector3.Zero;
float distance = System.Numerics.Vector3.Distance(playerPosition, Task.Position.GetValueOrDefault()); float distance = System.Numerics.Vector3.Distance(playerPosition, Task.Position.GetValueOrDefault());
if (Task.TerritoryId == clientState.TerritoryType && distance < 30f && !Conditions.IsDiving) if (Task.TerritoryId == clientState.TerritoryType && distance < 30f && !Conditions.Instance()->Diving)
{ {
logger.LogInformation("Not using mount, as we're close to the target"); logger.LogInformation("Not using mount, as we're close to the target");
return MountResult.DontMount; return MountResult.DontMount;

@ -1,4 +1,6 @@
using Microsoft.Extensions.Logging; using System.Linq;
using Microsoft.Extensions.Logging;
using Questionable.Data;
using Questionable.Functions; using Questionable.Functions;
using Questionable.Model; using Questionable.Model;
using Questionable.Model.Questing; using Questionable.Model.Questing;
@ -21,7 +23,7 @@ internal static class NextQuest
return null; return null;
// probably irrelevant, since pick up is handled elsewhere (and, in particular, checks for aetherytes and stuff) // probably irrelevant, since pick up is handled elsewhere (and, in particular, checks for aetherytes and stuff)
if (questFunctions.GetPriorityQuests().Contains(step.NextQuestId)) if (questFunctions.GetPriorityQuests(onlyClassAndRoleQuests: true).Contains(step.NextQuestId))
return null; return null;
return new SetQuestTask(step.NextQuestId, quest.Id); return new SetQuestTask(step.NextQuestId, quest.Id);

@ -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,8 @@ 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 => new Task(step.InteractionType, quest.Info.Name), EInteractionType.SinglePlayerDuty when !bossModIpc.IsConfiguredToRunSoloInstance(quest.Id, step.SinglePlayerDutyOptions) =>
new Task(step.InteractionType, quest.Info.Name),
_ => null, _ => null,
}; };
} }

@ -96,6 +96,12 @@ internal static class Interact
private EInteractionState _interactionState = EInteractionState.None; private EInteractionState _interactionState = EInteractionState.None;
private DateTime _continueAt = DateTime.MinValue; private DateTime _continueAt = DateTime.MinValue;
/// <summary>
/// A slight delay when we think an interaction has ended, to make sure that we're processing "Action cancelled"
/// prior to the next step (in case we're attacked).
/// </summary>
private bool delayedFinalCheck;
public Quest? Quest => Task.Quest; public Quest? Quest => Task.Quest;
public EInteractionType InteractionType { get; set; } public EInteractionType InteractionType { get; set; }
@ -179,7 +185,14 @@ internal static class Interact
return ETaskResult.StillRunning; return ETaskResult.StillRunning;
else if (ProgressContext.WasSuccessful() || else if (ProgressContext.WasSuccessful() ||
_interactionState == EInteractionState.InteractionConfirmed) _interactionState == EInteractionState.InteractionConfirmed)
return ETaskResult.TaskComplete; {
if (delayedFinalCheck)
return ETaskResult.TaskComplete;
_continueAt = DateTime.Now.AddSeconds(0.2);
delayedFinalCheck = true;
return ETaskResult.StillRunning;
}
} }
IGameObject? gameObject = gameFunctions.FindObjectByDataId(Task.DataId); IGameObject? gameObject = gameFunctions.FindObjectByDataId(Task.DataId);

@ -0,0 +1,161 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game;
using Questionable.Controller.Steps.Common;
using Questionable.Controller.Steps.Shared;
using Questionable.Data;
using Questionable.External;
using Questionable.Model;
using Questionable.Model.Questing;
namespace Questionable.Controller.Steps.Interactions;
internal static class SinglePlayerDuty
{
public const int LahabreaTerritoryId = 1052;
internal sealed class Factory(
BossModIpc bossModIpc,
TerritoryData territoryData,
ICondition condition,
IClientState clientState) : ITaskFactory
{
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
{
if (step.InteractionType != EInteractionType.SinglePlayerDuty)
yield break;
if (bossModIpc.IsConfiguredToRunSoloInstance(quest.Id, step.SinglePlayerDutyOptions))
{
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(cfcData.ContentFinderConditionId);
yield return new EnableAi();
if (cfcData.TerritoryId == LahabreaTerritoryId)
{
yield return new SetTarget(14643);
yield return new WaitCondition.Task(() => condition[ConditionFlag.Unconscious] || clientState.TerritoryType != LahabreaTerritoryId, "Wait(death)");
yield return new DisableAi();
yield return new WaitCondition.Task(() => !condition[ConditionFlag.Unconscious] || clientState.TerritoryType != LahabreaTerritoryId, "Wait(resurrection)");
yield return new EnableAi();
}
yield return new WaitSinglePlayerDuty(cfcData.ContentFinderConditionId);
yield return new DisableAi();
yield return new WaitAtEnd.WaitNextStepOrSequence();
}
}
}
internal sealed record StartSinglePlayerDuty(uint ContentFinderConditionId) : ITask
{
public override string ToString() => $"Wait(BossMod, entered instance {ContentFinderConditionId})";
}
internal sealed class StartSinglePlayerDutyExecutor : TaskExecutor<StartSinglePlayerDuty>
{
protected override bool Start() => true;
public override unsafe ETaskResult Update()
{
return GameMain.Instance()->CurrentContentFinderConditionId == Task.ContentFinderConditionId
? ETaskResult.TaskComplete
: ETaskResult.StillRunning;
}
public override bool ShouldInterruptOnDamage() => false;
}
internal sealed record EnableAi : ITask
{
public override string ToString() => "BossMod.EnableAi";
}
internal sealed class EnableAiExecutor(
BossModIpc bossModIpc) : TaskExecutor<EnableAi>
{
protected override bool Start()
{
bossModIpc.EnableAi();
return true;
}
public override ETaskResult Update() => ETaskResult.TaskComplete;
public override bool ShouldInterruptOnDamage() => false;
}
internal sealed record WaitSinglePlayerDuty(uint ContentFinderConditionId) : ITask
{
public override string ToString() => $"Wait(BossMod, left instance {ContentFinderConditionId})";
}
internal sealed class WaitSinglePlayerDutyExecutor(
BossModIpc bossModIpc) : TaskExecutor<WaitSinglePlayerDuty>, IStoppableTaskExecutor
{
protected override bool Start() => true;
public override unsafe ETaskResult Update()
{
return GameMain.Instance()->CurrentContentFinderConditionId != Task.ContentFinderConditionId
? ETaskResult.TaskComplete
: ETaskResult.StillRunning;
}
public void StopNow() => bossModIpc.DisableAi();
public override bool ShouldInterruptOnDamage() => false;
}
internal sealed record DisableAi : ITask
{
public override string ToString() => "BossMod.DisableAi";
}
internal sealed class DisableAiExecutor(
BossModIpc bossModIpc) : TaskExecutor<DisableAi>
{
protected override bool Start()
{
bossModIpc.DisableAi();
return true;
}
public override ETaskResult Update() => ETaskResult.TaskComplete;
public override bool ShouldInterruptOnDamage() => false;
}
// TODO this should be handled in VBM
internal sealed record SetTarget(uint DataId) : ITask
{
public override string ToString() => $"SetTarget({DataId})";
}
internal sealed class SetTargetExecutor(
ITargetManager targetManager,
IObjectTable objectTable) : TaskExecutor<SetTarget>
{
protected override bool Start() => true;
public override ETaskResult Update()
{
if (targetManager.Target?.DataId == Task.DataId)
return ETaskResult.TaskComplete;
IGameObject? gameObject = objectTable.FirstOrDefault(x => x.DataId == Task.DataId);
if (gameObject == null)
return ETaskResult.StillRunning;
targetManager.Target = gameObject;
return ETaskResult.StillRunning;
}
public override bool ShouldInterruptOnDamage() => false;
}
}

@ -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: case EInteractionType.SinglePlayerDuty when !bossModIpc.IsConfiguredToRunSoloInstance(quest.Id, step.SinglePlayerDutyOptions):
return [new EndAutomation()]; return [new EndAutomation()];
case EInteractionType.WalkTo: case EInteractionType.WalkTo:

@ -1,8 +1,11 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Dalamud.Plugin.Services;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Questionable.Controller.Steps.Interactions;
using Questionable.Data;
using Questionable.Model; using Questionable.Model;
using Questionable.Model.Questing; using Questionable.Model.Questing;
@ -11,11 +14,19 @@ namespace Questionable.Controller.Steps;
internal sealed class TaskCreator internal sealed class TaskCreator
{ {
private readonly IServiceProvider _serviceProvider; private readonly IServiceProvider _serviceProvider;
private readonly TerritoryData _territoryData;
private readonly IClientState _clientState;
private readonly ILogger<TaskCreator> _logger; private readonly ILogger<TaskCreator> _logger;
public TaskCreator(IServiceProvider serviceProvider, ILogger<TaskCreator> logger) public TaskCreator(
IServiceProvider serviceProvider,
TerritoryData territoryData,
IClientState clientState,
ILogger<TaskCreator> logger)
{ {
_serviceProvider = serviceProvider; _serviceProvider = serviceProvider;
_territoryData = territoryData;
_clientState = clientState;
_logger = logger; _logger = logger;
} }
@ -40,6 +51,31 @@ internal sealed class TaskCreator
return tasks; return tasks;
}) })
.ToList(); .ToList();
var singlePlayerDutyTask = newTasks
.Where(y => y is SinglePlayerDuty.StartSinglePlayerDuty)
.Cast<SinglePlayerDuty.StartSinglePlayerDuty>()
.FirstOrDefault();
if (singlePlayerDutyTask != null &&
_territoryData.TryGetContentFinderCondition(singlePlayerDutyTask.ContentFinderConditionId,
out var cfcData))
{
// if we have a single player duty in queue, we check if we're in the matching territory
// if yes, skip all steps before (e.g. teleporting, waiting for navmesh, moving, interacting)
if (_clientState.TerritoryType == cfcData.TerritoryId)
{
int index = newTasks.IndexOf(singlePlayerDutyTask);
_logger.LogWarning(
"Skipping {SkippedTaskCount} out of {TotalCount} tasks, questionable was started while in single player duty",
index + 1, newTasks.Count);
newTasks.RemoveRange(0, index + 1);
_logger.LogInformation("Next actual task: {NextTask}, total tasks left: {RemainingTaskCount}",
newTasks.FirstOrDefault(),
newTasks.Count);
}
}
if (newTasks.Count == 0) if (newTasks.Count == 0)
_logger.LogInformation("Nothing to execute for step?"); _logger.LogInformation("Nothing to execute for step?");
else else

@ -0,0 +1,169 @@
using System;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.Game.Group;
using JetBrains.Annotations;
using Microsoft.Extensions.Logging;
namespace Questionable.Controller.Utils;
internal sealed class PartyWatchDog : IDisposable
{
private readonly QuestController _questController;
private readonly IClientState _clientState;
private readonly IChatGui _chatGui;
private readonly ILogger<PartyWatchDog> _logger;
private ushort? _uncheckedTeritoryId;
public PartyWatchDog(QuestController questController, IClientState clientState, IChatGui chatGui,
ILogger<PartyWatchDog> logger)
{
_questController = questController;
_clientState = clientState;
_chatGui = chatGui;
_logger = logger;
_clientState.TerritoryChanged += TerritoryChanged;
}
private unsafe void TerritoryChanged(ushort newTerritoryId)
{
var intendedUse = (ETerritoryIntendedUseEnum)GameMain.Instance()->CurrentTerritoryIntendedUseId;
switch (intendedUse)
{
case ETerritoryIntendedUseEnum.Gaol:
case ETerritoryIntendedUseEnum.Frontline:
case ETerritoryIntendedUseEnum.LordOfVerminion:
case ETerritoryIntendedUseEnum.Diadem:
case ETerritoryIntendedUseEnum.CrystallineConflict:
case ETerritoryIntendedUseEnum.Battlehall:
case ETerritoryIntendedUseEnum.CrystallineConflict2:
case ETerritoryIntendedUseEnum.DeepDungeon:
case ETerritoryIntendedUseEnum.TreasureMapDuty:
case ETerritoryIntendedUseEnum.Diadem2:
case ETerritoryIntendedUseEnum.RivalWings:
case ETerritoryIntendedUseEnum.Eureka:
case ETerritoryIntendedUseEnum.LeapOfFaith:
case ETerritoryIntendedUseEnum.OceanFishing:
case ETerritoryIntendedUseEnum.Diadem3:
case ETerritoryIntendedUseEnum.Bozja:
case ETerritoryIntendedUseEnum.Battlehall2:
case ETerritoryIntendedUseEnum.Battlehall3:
case ETerritoryIntendedUseEnum.LargeScaleRaid:
case ETerritoryIntendedUseEnum.LargeScaleSavageRaid:
case ETerritoryIntendedUseEnum.Blunderville:
StopIfRunning($"Unsupported Area entered ({newTerritoryId})");
break;
case ETerritoryIntendedUseEnum.Dungeon:
case ETerritoryIntendedUseEnum.VariantDungeon:
case ETerritoryIntendedUseEnum.AllianceRaid:
case ETerritoryIntendedUseEnum.Trial:
case ETerritoryIntendedUseEnum.Raid:
case ETerritoryIntendedUseEnum.Raid2:
case ETerritoryIntendedUseEnum.SeasonalEvent:
case ETerritoryIntendedUseEnum.SeasonalEvent2:
case ETerritoryIntendedUseEnum.CriterionDuty:
case ETerritoryIntendedUseEnum.CriterionSavageDuty:
_uncheckedTeritoryId = newTerritoryId;
_logger.LogInformation("Will check territory {TerritoryId} after loading", newTerritoryId);
break;
}
}
public unsafe void Update()
{
if (_uncheckedTeritoryId == _clientState.TerritoryType && GameMain.Instance()->TerritoryLoadState == 2)
{
var groupManager = GroupManager.Instance();
if (groupManager == null)
return;
byte memberCount = groupManager->MainGroup.MemberCount;
bool isInAlliance = groupManager->MainGroup.IsAlliance;
_logger.LogDebug("Territory {TerritoryId} with {MemberCount} members, alliance: {IsInAlliance}",
_uncheckedTeritoryId, memberCount, isInAlliance);
if (memberCount > 1 || isInAlliance)
StopIfRunning("Other party members present");
_uncheckedTeritoryId = null;
}
}
private void StopIfRunning(string reason)
{
if (_questController.IsRunning || _questController.AutomationType != QuestController.EAutomationType.Manual)
{
_chatGui.PrintError(
$"Stopping Questionable: {reason}. If you believe this to be correct, please restart Questionable manually.",
CommandHandler.MessageTag, CommandHandler.TagColor);
_questController.Stop(reason);
}
}
public void Dispose()
{
_clientState.TerritoryChanged -= TerritoryChanged;
}
// from https://github.com/NightmareXIV/ECommons/blob/f69e460e95134c72592654059843b138b4c01a9e/ECommons/ExcelServices/TerritoryIntendedUseEnum.cs#L5
[UsedImplicitly(ImplicitUseTargetFlags.Members, Reason = "game data")]
private enum ETerritoryIntendedUseEnum : byte
{
CityArea = 0,
OpenWorld = 1,
Inn = 2,
Dungeon = 3,
VariantDungeon = 4,
Gaol = 5,
StartingArea = 6,
QuestArea = 7,
AllianceRaid = 8,
QuestBattle = 9,
Trial = 10,
QuestArea2 = 12,
ResidentialArea = 13,
HousingInstances = 14,
QuestArea3 = 15,
Raid = 16,
Raid2 = 17,
Frontline = 18,
ChocoboSquare = 20,
RestorationEvent = 21,
Sanctum = 22,
GoldSaucer = 23,
LordOfVerminion = 25,
Diadem = 26,
HallOfTheNovice = 27,
CrystallineConflict = 28,
QuestBattle2 = 29,
Barracks = 30,
DeepDungeon = 31,
SeasonalEvent = 32,
TreasureMapDuty = 33,
SeasonalEventDuty = 34,
Battlehall = 35,
CrystallineConflict2 = 37,
Diadem2 = 38,
RivalWings = 39,
Unknown1 = 40,
Eureka = 41,
SeasonalEvent2 = 43,
LeapOfFaith = 44,
MaskedCarnivale = 45,
OceanFishing = 46,
Diadem3 = 47,
Bozja = 48,
IslandSanctuary = 49,
Battlehall2 = 50,
Battlehall3 = 51,
LargeScaleRaid = 52,
LargeScaleSavageRaid = 53,
QuestArea4 = 54,
TribalInstance = 56,
CriterionDuty = 57,
CriterionSavageDuty = 58,
Blunderville = 59,
}
}

@ -7,6 +7,7 @@ using Dalamud.Plugin.Services;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Questionable.Controller; using Questionable.Controller;
using Questionable.Controller.GameUi; using Questionable.Controller.GameUi;
using Questionable.Controller.Utils;
using Questionable.Windows; using Questionable.Windows;
namespace Questionable; namespace Questionable;
@ -23,6 +24,7 @@ internal sealed class DalamudInitializer : IDisposable
private readonly ConfigWindow _configWindow; private readonly ConfigWindow _configWindow;
private readonly IToastGui _toastGui; private readonly IToastGui _toastGui;
private readonly Configuration _configuration; private readonly Configuration _configuration;
private readonly PartyWatchDog _partyWatchDog;
private readonly ILogger<DalamudInitializer> _logger; private readonly ILogger<DalamudInitializer> _logger;
public DalamudInitializer( public DalamudInitializer(
@ -42,6 +44,7 @@ internal sealed class DalamudInitializer : IDisposable
PriorityWindow priorityWindow, PriorityWindow priorityWindow,
IToastGui toastGui, IToastGui toastGui,
Configuration configuration, Configuration configuration,
PartyWatchDog partyWatchDog,
ILogger<DalamudInitializer> logger) ILogger<DalamudInitializer> logger)
{ {
_pluginInterface = pluginInterface; _pluginInterface = pluginInterface;
@ -54,6 +57,7 @@ internal sealed class DalamudInitializer : IDisposable
_configWindow = configWindow; _configWindow = configWindow;
_toastGui = toastGui; _toastGui = toastGui;
_configuration = configuration; _configuration = configuration;
_partyWatchDog = partyWatchDog;
_logger = logger; _logger = logger;
_windowSystem.AddWindow(oneTimeSetupWindow); _windowSystem.AddWindow(oneTimeSetupWindow);
@ -77,6 +81,7 @@ internal sealed class DalamudInitializer : IDisposable
private void FrameworkUpdate(IFramework framework) private void FrameworkUpdate(IFramework framework)
{ {
_partyWatchDog.Update();
_questController.Update(); _questController.Update();
try try

@ -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)

@ -247,8 +247,8 @@ internal sealed class QuestData
private void AddPreviousQuest(QuestId questToUpdate, QuestId requiredQuestId) private void AddPreviousQuest(QuestId questToUpdate, QuestId requiredQuestId)
{ {
QuestInfo quest = (QuestInfo)_quests[questToUpdate]; if (_quests.TryGetValue(questToUpdate, out IQuestInfo? quest) && quest is QuestInfo questInfo)
quest.AddPreviousQuest(new PreviousQuestInfo(requiredQuestId)); questInfo.AddPreviousQuest(new PreviousQuestInfo(requiredQuestId));
} }
private void AddGcFollowUpQuests() private void AddGcFollowUpQuests()
@ -300,7 +300,7 @@ internal sealed class QuestData
.ToList(); .ToList();
} }
public List<QuestInfo> GetClassJobQuests(EClassJob classJob) public List<QuestInfo> GetClassJobQuests(EClassJob classJob, bool includeRoleQuests = false)
{ {
List<uint> chapterIds = classJob switch List<uint> chapterIds = classJob switch
{ {
@ -367,7 +367,20 @@ internal sealed class QuestData
_ => throw new ArgumentOutOfRangeException(nameof(classJob)), _ => throw new ArgumentOutOfRangeException(nameof(classJob)),
}; };
chapterIds.AddRange(classJob switch if (includeRoleQuests)
{
chapterIds.AddRange(GetRoleQuestIds(classJob));
}
return GetQuestsInNewGamePlusChapters(chapterIds);
}
public List<QuestInfo> GetRoleQuests(EClassJob referenceClassJob) =>
GetQuestsInNewGamePlusChapters(GetRoleQuestIds(referenceClassJob).ToList());
private static IEnumerable<uint> GetRoleQuestIds(EClassJob classJob)
{
return classJob switch
{ {
_ when classJob.IsTank() => TankRoleQuests, _ when classJob.IsTank() => TankRoleQuests,
_ when classJob.IsHealer() => HealerRoleQuests, _ when classJob.IsHealer() => HealerRoleQuests,
@ -375,9 +388,7 @@ internal sealed class QuestData
_ when classJob.IsPhysicalRanged() => PhysicalRangedRoleQuests, _ when classJob.IsPhysicalRanged() => PhysicalRangedRoleQuests,
_ when classJob.IsCaster() && classJob != EClassJob.BlueMage => CasterRoleQuests, _ when classJob.IsCaster() && classJob != EClassJob.BlueMage => CasterRoleQuests,
_ => [] _ => []
}); };
return GetQuestsInNewGamePlusChapters(chapterIds);
} }
private List<QuestInfo> GetQuestsInNewGamePlusChapters(List<uint> chapterIds) private List<QuestInfo> GetQuestsInNewGamePlusChapters(List<uint> chapterIds)

@ -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)
{ {
@ -45,9 +48,16 @@ internal sealed class TerritoryData
.ToImmutableDictionary(x => x.Content.RowId, x => x.Name.ToDalamudString().ToString()); .ToImmutableDictionary(x => x.Content.RowId, x => x.Name.ToDalamudString().ToString());
_contentFinderConditions = dataManager.GetExcelSheet<ContentFinderCondition>() _contentFinderConditions = dataManager.GetExcelSheet<ContentFinderCondition>()
.Where(x => x.RowId > 0 && x.Content.RowId != 0 && x.ContentLinkType == 1 && 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,23 @@ 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;
}
}
public IEnumerable<(ElementId QuestId, byte Index, ContentFinderConditionData Data)> GetAllQuestsWithQuestBattles()
{
return _questsToCfc.Select(x => (x.Key.QuestId, x.Key.Index, _contentFinderConditions[x.Value]));
}
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 +112,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,

Some files were not shown because too many files have changed in this diff Show More