This commit is contained in:
pot0to 2025-02-23 22:49:51 -08:00
commit e74551693d
123 changed files with 3650 additions and 746 deletions

6
.gitmodules vendored
View File

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

View File

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

View File

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

View File

@ -2,6 +2,6 @@
"Name": "GatheringPathRenderer",
"Author": "Liza Carvelli",
"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"
}

View File

@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Numerics;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Nodes;
@ -13,11 +14,9 @@ using Dalamud.Game.ClientState.Objects;
using Dalamud.Interface.Windowing;
using Dalamud.Plugin;
using Dalamud.Plugin.Services;
using ECommons;
using ECommons.Schedulers;
using ECommons.SplatoonAPI;
using GatheringPathRenderer.Windows;
using LLib.GameData;
using Pictomancy;
using Questionable.Model.Gathering;
namespace GatheringPathRenderer;
@ -25,10 +24,8 @@ namespace GatheringPathRenderer;
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
public sealed class RendererPlugin : IDalamudPlugin
{
private const long OnTerritoryChange = -2;
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 IClientState _clientState;
@ -58,7 +55,8 @@ public sealed class RendererPlugin : IDalamudPlugin
_editorCommands = new EditorCommands(this, dataManager, commandManager, targetManager, clientState, chatGui,
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 };
_windowSystem.AddWindow(configWindow);
_windowSystem.AddWindow(_editorWindow);
@ -67,14 +65,12 @@ public sealed class RendererPlugin : IDalamudPlugin
_pluginInterface.GetIpcSubscriber<object>("Questionable.ReloadData")
.Subscribe(Reload);
ECommonsMain.Init(pluginInterface, this, Module.SplatoonAPI);
PictoService.Initialize(pluginInterface);
LoadGatheringLocationsFromDirectory();
_pluginInterface.UiBuilder.Draw += _windowSystem.Draw;
_clientState.TerritoryChanged += TerritoryChanged;
_pluginInterface.UiBuilder.Draw += Draw;
_clientState.ClassJobChanged += ClassJobChanged;
if (_clientState.IsLoggedIn)
TerritoryChanged(_clientState.TerritoryType);
}
internal DirectoryInfo PathsDirectory
@ -93,7 +89,8 @@ public sealed class RendererPlugin : IDalamudPlugin
throw new Exception($"Unable to resolve project path ({_pluginInterface.AssemblyLocation.Directory})");
#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
.CreateSubdirectory("Questionable")
.CreateSubdirectory("GatheringPaths");
@ -104,7 +101,6 @@ public sealed class RendererPlugin : IDalamudPlugin
internal void Reload()
{
LoadGatheringLocationsFromDirectory();
Redraw();
}
private void LoadGatheringLocationsFromDirectory()
@ -124,7 +120,6 @@ public sealed class RendererPlugin : IDalamudPlugin
_pluginLog.Information(
$"Loaded {_gatheringLocations.Count} gathering root locations from {PathsDirectory.FullName} directory");
#endif
}
catch (Exception e)
{
@ -209,142 +204,114 @@ public sealed class RendererPlugin : IDalamudPlugin
}
}
private void TerritoryChanged(ushort territoryId) => Redraw();
private void ClassJobChanged(uint classJobId)
{
_currentClassJob = (EClassJob)classJobId;
Redraw(_currentClassJob);
}
internal void Redraw() => Redraw(_currentClassJob);
private void Redraw(EClassJob classJob)
private void Draw()
{
Splatoon.RemoveDynamicElements("GatheringPathRenderer");
if (!classJob.IsGatherer())
if (!_currentClassJob.IsGatherer())
return;
var elements = GetLocationsInTerritory(_clientState.TerritoryType)
.SelectMany(location =>
location.Root.Groups.SelectMany(group =>
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();
}
}
using var drawList = PictoService.Draw();
if (drawList == null)
return;
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;
minimumAngle = x.MinimumAngle.GetValueOrDefault();
maximumAngle = x.MaximumAngle.GetValueOrDefault();
minimumAngle = locationOverride.MinimumAngle.GetValueOrDefault();
maximumAngle = locationOverride.MaximumAngle.GetValueOrDefault();
}
}
#if false
var a = GatheringMath.CalculateLandingLocation(x, 0, 0);
var b = GatheringMath.CalculateLandingLocation(x, 1, 1);
#endif
return new List<Element>
{
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 (!isCone && x.IsCone())
{
isCone = true;
minimumAngle = x.MinimumAngle.GetValueOrDefault();
maximumAngle = x.MaximumAngle.GetValueOrDefault();
}
if (elements.Count == 0)
{
_pluginLog.Information("No new elements to render.");
return;
minimumAngle *= (float)Math.PI / 180;
maximumAngle *= (float)Math.PI / 180;
if (!isCone || maximumAngle - minimumAngle >= 2 * Math.PI)
{
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()
{
_clientState.ClassJobChanged -= ClassJobChanged;
_clientState.TerritoryChanged -= TerritoryChanged;
_pluginInterface.UiBuilder.Draw -= Draw;
_pluginInterface.UiBuilder.Draw -= _windowSystem.Draw;
Splatoon.RemoveDynamicElements("GatheringPathRenderer");
ECommonsMain.Dispose();
PictoService.Dispose();
_pluginInterface.GetIpcSubscriber<object>("Questionable.ReloadData")
.Unsubscribe(Reload);

View File

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

View File

@ -35,6 +35,16 @@
"resolved": "8.0.0",
"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": {
"type": "Transitive",
"resolved": "1.1.1",
@ -76,13 +86,944 @@
"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": {
"type": "Transitive",
"resolved": "8.0.5",
"contentHash": "0f1B50Ss7rqxXiaBJyzUu9bWFOO2/zSlifZ/UNMdiIpDYe4cY4LQQicP4nirK1OS31I43rn062UIJ1Q9bpmHpg=="
},
"ecommons": {
"type": "Project"
"System.Text.RegularExpressions": {
"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": {
"type": "Project",
@ -90,6 +1031,14 @@
"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": {
"type": "Project",
"dependencies": {

2
LLib

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -33,6 +33,39 @@
{
"Sequence": 1,
"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": {
"X": -276.804,
@ -42,7 +75,12 @@
"TerritoryId": 152,
"InteractionType": "WalkTo",
"AetheryteShortcut": "East Shroud - Hawthorne Hut",
"Fly": true
"Fly": true,
"SkipConditions": {
"AetheryteShortcutIf": {
"AetheryteLocked": "East Shroud - Hawthorne Hut"
}
}
},
{
"DataId": 2000889,
@ -212,6 +250,10 @@
},
"TerritoryId": 152,
"InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": true,
"TestedBossModVersion": 292
},
"Fly": true
}
]

View File

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

View File

@ -111,7 +111,14 @@
"Z": 295.52136
},
"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"
]
}
}
]
},

View File

@ -28,7 +28,14 @@
"Z": -309.55975
},
"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)"
]
}
}
]
},

View File

@ -77,6 +77,13 @@
},
"TerritoryId": 148,
"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"
}
]

View File

@ -69,6 +69,13 @@
},
"TerritoryId": 135,
"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"
}
]

View File

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

View File

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

View File

@ -28,7 +28,14 @@
"Z": -141.7716
},
"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)"
]
}
}
]
},

View File

@ -58,6 +58,13 @@
},
"TerritoryId": 138,
"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"
}
]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -29,7 +29,7 @@
},
"TerritoryId": 141,
"InteractionType": "Combat",
"EnemySpawnType": "OverworldEnemies",
"EnemySpawnType": "FinishCombatIfAny",
"KillEnemyDataIds": [
352,
353
@ -53,6 +53,25 @@
{
"Sequence": 255,
"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,
"Position": {

View File

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

View File

@ -64,7 +64,14 @@
"Z": -131.48706
},
"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"
}
]

View File

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

View File

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

View File

@ -83,7 +83,14 @@
"Z": -12.985474
},
"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"
]
}
}
]
},

View File

@ -26,6 +26,28 @@
{
"Sequence": 1,
"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,
"Position": {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -100,6 +100,28 @@
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,
"Position": {
@ -109,7 +131,6 @@
},
"TerritoryId": 156,
"InteractionType": "Interact",
"Fly": true,
"$": "1 112 0 0 0 2 -> 2 96 0 0 0 34",
"CompletionQuestVariablesFlags": [
null,

View File

@ -71,6 +71,14 @@
},
"TerritoryId": 147,
"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,
"AetheryteShortcut": "Northern Thanalan - Ceruleum Processing Plant"
}

View File

@ -28,7 +28,16 @@
"Z": -328.66406
},
"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"
]
}
}
]
},

View File

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

View File

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

View File

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

View File

@ -30,7 +30,11 @@
},
"TerritoryId": 397,
"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
}
}
]
},

View File

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

View File

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

View File

@ -28,7 +28,14 @@
"Z": 388.63196
},
"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"
]
}
}
]
},

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -61,7 +61,19 @@
"TerritoryId": 156,
"InteractionType": "Interact",
"AetheryteShortcut": "Mor Dhona",
"TargetTerritoryId": 351
"TargetTerritoryId": 351,
"SkipConditions": {
"AetheryteShortcutIf": {
"InTerritory": [
351
]
},
"StepIf": {
"InTerritory": [
351
]
}
}
},
{
"DataId": 1032081,
@ -73,13 +85,17 @@
"TerritoryId": 351,
"InteractionType": "SinglePlayerDuty",
"Comment": "Estinien vs. Arch Ultima",
"DialogueChoices": [
{
"Type": "YesNo",
"Prompt": "TEXT_LUCKMG110_03682_Q1_100_125",
"Yes": true
}
]
"SinglePlayerDutyOptions": {
"Enabled": false,
"TestedBossModVersion": 292,
"Notes": [
"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"
}
]
},

View File

@ -46,6 +46,13 @@
},
"TerritoryId": 817,
"InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": false,
"TestedBossModVersion": 292,
"Notes": [
"Doesn't walk to the teleporter to finish the duty"
]
},
"Fly": true,
"Comment": "A Sleep Disturbed (Opo-Opo, Wolf, Serpent)",
"$": "The dialogue choices and data ids here are recycled",

View File

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

View File

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

View File

@ -91,6 +91,8 @@ public abstract class ElementId : IComparable<ElementId>, IEquatable<ElementId>
public sealed class QuestId(ushort value) : ElementId(value)
{
public static QuestId FromRowId(uint rowId) => new((ushort)(rowId & 0xFFFF));
public override string ToString()
{
return Value.ToString(CultureInfo.InvariantCulture);

View File

@ -75,6 +75,8 @@ public sealed class QuestStep
public JumpDestination? JumpDestination { get; set; }
public uint? ContentFinderConditionId { get; set; }
public bool AutoDutyEnabled { get; set; }
public SinglePlayerDutyOptions? SinglePlayerDutyOptions { get; set; }
public byte SinglePlayerDutyIndex => SinglePlayerDutyOptions?.Index ?? 0;
public SkipConditions? SkipConditions { get; set; }
public List<List<QuestWorkValue>?> RequiredQuestVariables { get; set; } = new();

View File

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

View File

@ -1,6 +1,6 @@
{
"$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",
"enum": [
"[Gridania] Aetheryte Plaza",

View File

@ -1,6 +1,6 @@
{
"$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",
"enum": [
"Gridania",

View File

@ -1,6 +1,6 @@
{
"$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",
"enum": [
"Gladiator",

View File

@ -1,6 +1,6 @@
{
"$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",
"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": {

View File

@ -1,6 +1,6 @@
{
"$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",
"description": "Position in the world",
"properties": {

View File

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

View File

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Dalamud.Configuration;
using Dalamud.Game.Text;
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
@ -14,6 +15,7 @@ internal sealed class Configuration : IPluginConfiguration
public int PluginSetupCompleteVersion { get; set; }
public GeneralConfiguration General { get; } = new();
public DutyConfiguration Duties { get; } = new();
public SinglePlayerDutyConfiguration SinglePlayerDuties { get; } = new();
public NotificationConfiguration Notifications { get; } = new();
public AdvancedConfiguration Advanced { get; } = new();
public WindowConfig DebugWindowConfig { get; } = new();
@ -41,6 +43,17 @@ internal sealed class Configuration : IPluginConfiguration
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
{
public bool Enabled { get; set; } = true;

View File

@ -9,33 +9,26 @@ using Questionable.Model;
using System;
using System.IO;
using System.Numerics;
using Questionable.External;
namespace Questionable.Controller.CombatModules;
internal sealed class BossModModule : ICombatModule, IDisposable
{
private const string Name = "BossMod";
private readonly ILogger<BossModModule> _logger;
private readonly BossModIpc _bossModIpc;
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")!;
public BossModModule(
ILogger<BossModModule> logger,
IDalamudPluginInterface pluginInterface,
BossModIpc bossModIpc,
Configuration configuration)
{
_logger = logger;
_bossModIpc = bossModIpc;
_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)
@ -43,26 +36,19 @@ internal sealed class BossModModule : ICombatModule, IDisposable
if (_configuration.General.CombatModule != Configuration.ECombatModule.BossMod)
return false;
try
{
return _getPreset.HasFunction;
}
catch (IpcError)
{
return false;
}
return _bossModIpc.IsSupported();
}
public bool Start(CombatController.CombatData combatData)
{
try
{
if (_getPreset.InvokeFunc("Questionable") == null)
if (_bossModIpc.GetPreset("Questionable") == null)
{
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;
}
catch (IpcError e)
@ -76,7 +62,7 @@ internal sealed class BossModModule : ICombatModule, IDisposable
{
try
{
_clearPreset.InvokeFunc();
_bossModIpc.ClearPreset();
return true;
}
catch (IpcError e)

View File

@ -19,6 +19,7 @@ using Lumina.Excel.Sheets;
using Microsoft.Extensions.Logging;
using Questionable.Controller.Steps.Interactions;
using Questionable.Data;
using Questionable.External;
using Questionable.Functions;
using Questionable.Model;
using Questionable.Model.Gathering;
@ -45,6 +46,8 @@ internal sealed class InteractionUiController : IDisposable
private readonly ITargetManager _targetManager;
private readonly IClientState _clientState;
private readonly ShopController _shopController;
private readonly BossModIpc _bossModIpc;
private readonly Configuration _configuration;
private readonly ILogger<InteractionUiController> _logger;
private readonly Regex _returnRegex;
private readonly Regex _purchaseItemRegex;
@ -68,6 +71,8 @@ internal sealed class InteractionUiController : IDisposable
IPluginLog pluginLog,
IClientState clientState,
ShopController shopController,
BossModIpc bossModIpc,
Configuration configuration,
ILogger<InteractionUiController> logger)
{
_addonLifecycle = addonLifecycle;
@ -85,6 +90,8 @@ internal sealed class InteractionUiController : IDisposable
_targetManager = targetManager;
_clientState = clientState;
_shopController = shopController;
_bossModIpc = bossModIpc;
_configuration = configuration;
_logger = logger;
_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, "SelectIconString", SelectIconStringPostSetup);
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "SelectYesno", SelectYesnoPostSetup);
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "DifficultySelectYesNo", DifficultySelectYesNoPostSetup);
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "PointMenu", PointMenuPostSetup);
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "HousingSelectBlock", HousingSelectBlockPostSetup);
@ -140,6 +148,12 @@ internal sealed class InteractionUiController : IDisposable
SelectYesnoPostSetup(addonSelectYesno, true);
}
if (_gameGui.TryGetAddonByName("DifficultySelectYesNo", out AtkUnitBase* addonDifficultySelectYesNo))
{
_logger.LogInformation("DifficultySelectYesNo window is open");
DifficultySelectYesNoPostSetup(addonDifficultySelectYesNo, true);
}
if (_gameGui.TryGetAddonByName("PointMenu", out AtkUnitBase* addonPointMenu))
{
_logger.LogInformation("PointMenu is open");
@ -176,7 +190,10 @@ internal sealed class InteractionUiController : IDisposable
int? answer = HandleListChoice(actualPrompt, answers, checkAllSteps) ?? HandleInstanceListChoice(actualPrompt);
if (answer != null)
{
_logger.LogInformation("Using choice {Choice} for list prompt '{Prompt}'", answer, actualPrompt);
addonSelectString->AtkUnitBase.FireCallbackInt(answer.Value);
}
}
private unsafe void CutsceneSelectStringPostSetup(AddonEvent type, AddonArgs args)
@ -224,6 +241,7 @@ internal sealed class InteractionUiController : IDisposable
int? answer = HandleListChoice(actualPrompt, answers, checkAllSteps);
if (answer != null)
{
_logger.LogInformation("Using choice {Choice} for list prompt '{Prompt}'", answer, actualPrompt);
addonSelectIconString->AtkUnitBase.FireCallbackInt(answer.Value);
return;
}
@ -266,6 +284,7 @@ internal sealed class InteractionUiController : IDisposable
int questSelection = answers.FindIndex(x => GameFunctions.GameStringEquals(questName, x));
if (questSelection >= 0)
{
_logger.LogInformation("Selecting quest {QuestName}", questName);
addonSelectIconString->AtkUnitBase.FireCallbackInt(questSelection);
return true;
}
@ -598,14 +617,14 @@ internal sealed class InteractionUiController : IDisposable
if (checkAllSteps)
{
var sequence = quest.FindSequence(currentQuest.Sequence);
if (sequence != null && HandleDefaultYesNo(addonSelectYesno, quest,
sequence.Steps.SelectMany(x => x.DialogueChoices).ToList(), actualPrompt))
if (sequence != null &&
sequence.Steps.Any(step => HandleDefaultYesNo(addonSelectYesno, quest, step, step.DialogueChoices, actualPrompt)))
return true;
}
else
{
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;
}
@ -619,7 +638,7 @@ internal sealed class InteractionUiController : IDisposable
Yes = true
};
if (HandleDefaultYesNo(addonSelectYesno, quest, [dialogueChoice], actualPrompt))
if (HandleDefaultYesNo(addonSelectYesno, quest, null, [dialogueChoice], actualPrompt))
return true;
}
@ -630,7 +649,7 @@ internal sealed class InteractionUiController : IDisposable
}
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);
foreach (var dialogueChoice in dialogueChoices)
@ -655,10 +674,35 @@ internal sealed class InteractionUiController : IDisposable
continue;
}
_logger.LogInformation("Returning {YesNo} for '{Prompt}'", dialogueChoice.Yes ? "Yes" : "No", actualPrompt);
addonSelectYesno->AtkUnitBase.FireCallbackInt(dialogueChoice.Yes ? 0 : 1);
return true;
}
if (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;
}
@ -692,6 +736,44 @@ internal sealed class InteractionUiController : IDisposable
return false;
}
private unsafe void DifficultySelectYesNoPostSetup(AddonEvent type, AddonArgs args)
{
AtkUnitBase* addonDifficultySelectYesNo = (AtkUnitBase*)args.Addon;
DifficultySelectYesNoPostSetup(addonDifficultySelectYesNo, false);
}
private unsafe void DifficultySelectYesNoPostSetup(AtkUnitBase* addonDifficultySelectYesNo, bool checkAllSteps)
{
var currentQuest = _questController.StartedQuest;
if (currentQuest == null)
return;
var quest = currentQuest.Quest;
bool autoConfirm;
if (checkAllSteps)
{
var sequence = quest.FindSequence(currentQuest.Sequence);
autoConfirm = sequence != null && sequence.Steps.Any(step => CheckSinglePlayerDutyYesNo(quest.Id, step));
}
else
{
var step = quest.FindSequence(currentQuest.Sequence)?.FindStep(currentQuest.Step);
autoConfirm = step != null && CheckSinglePlayerDutyYesNo(quest.Id, step);
}
if (autoConfirm)
{
_logger.LogInformation("Confirming difficulty ({Difficulty}) for quest battle", _configuration.SinglePlayerDuties.RetryDifficulty);
var selectChoice = stackalloc AtkValue[]
{
new() { Type = ValueType.Int, Int = 0 },
new() { Type = ValueType.Int, Int = _configuration.SinglePlayerDuties.RetryDifficulty }
};
addonDifficultySelectYesNo->FireCallback(2, selectChoice);
}
}
private ushort? FindTargetTerritoryFromQuestStep(QuestController.QuestProgress currentQuest)
{
// this can be triggered either manually (in which case we should increase the step counter), or automatically
@ -864,6 +946,7 @@ internal sealed class InteractionUiController : IDisposable
{
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "HousingSelectBlock", HousingSelectBlockPostSetup);
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "PointMenu", PointMenuPostSetup);
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "DifficultySelectYesNo", DifficultySelectYesNoPostSetup);
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "SelectYesno", SelectYesnoPostSetup);
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "SelectIconString", SelectIconStringPostSetup);
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "CutSceneSelectString", CutsceneSelectStringPostSetup);

View File

@ -10,11 +10,13 @@ using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game;
using Microsoft.Extensions.Logging;
using Questionable.Controller.Steps;
using Questionable.Controller.Steps.Interactions;
using Questionable.Controller.Steps.Shared;
using Questionable.External;
using Questionable.Functions;
using Questionable.Model;
using Questionable.Model.Questing;
using Questionable.Windows.ConfigComponents;
using Quest = Questionable.Model.Quest;
namespace Questionable.Controller;
@ -35,6 +37,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
private readonly Configuration _configuration;
private readonly YesAlreadyIpc _yesAlreadyIpc;
private readonly TaskCreator _taskCreator;
private readonly SinglePlayerDutyConfigComponent _singlePlayerDutyConfigComponent;
private readonly ILogger<QuestController> _logger;
private readonly object _progressLock = new();
@ -76,7 +79,8 @@ internal sealed class QuestController : MiniTaskController<QuestController>
TaskCreator taskCreator,
IServiceProvider serviceProvider,
InterruptHandler interruptHandler,
IDataManager dataManager)
IDataManager dataManager,
SinglePlayerDutyConfigComponent singlePlayerDutyConfigComponent)
: base(chatGui, condition, serviceProvider, interruptHandler, dataManager, logger)
{
_clientState = clientState;
@ -93,6 +97,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
_configuration = configuration;
_yesAlreadyIpc = yesAlreadyIpc;
_taskCreator = taskCreator;
_singlePlayerDutyConfigComponent = singlePlayerDutyConfigComponent;
_logger = logger;
_condition.ConditionChange += OnConditionChange;
@ -169,6 +174,7 @@ internal sealed class QuestController : MiniTaskController<QuestController>
DebugState = null;
_questRegistry.Reload();
_singlePlayerDutyConfigComponent.Reload();
}
}
@ -195,7 +201,13 @@ internal sealed class QuestController : MiniTaskController<QuestController>
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");
_movementController.Stop();
@ -757,6 +769,10 @@ internal sealed class QuestController : MiniTaskController<QuestController>
if (ManualPriorityQuests.Contains(currentQuest.Quest))
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)
return false;

View File

@ -27,6 +27,7 @@ internal sealed class QuestRegistry
private readonly JsonSchemaValidator _jsonSchemaValidator;
private readonly ILogger<QuestRegistry> _logger;
private readonly LeveData _leveData;
private readonly TerritoryData _territoryData;
private readonly ICallGateProvider<object> _reloadDataIpc;
private readonly Dictionary<ElementId, Quest> _quests = [];
@ -34,7 +35,7 @@ internal sealed class QuestRegistry
public QuestRegistry(IDalamudPluginInterface pluginInterface, QuestData questData,
QuestValidator questValidator, JsonSchemaValidator jsonSchemaValidator,
ILogger<QuestRegistry> logger, LeveData leveData)
ILogger<QuestRegistry> logger, LeveData leveData, TerritoryData territoryData)
{
_pluginInterface = pluginInterface;
_questData = questData;
@ -42,6 +43,7 @@ internal sealed class QuestRegistry
_jsonSchemaValidator = jsonSchemaValidator;
_logger = logger;
_leveData = leveData;
_territoryData = territoryData;
_reloadDataIpc = _pluginInterface.GetIpcProvider<object>("Questionable.ReloadData");
}
@ -150,9 +152,15 @@ internal sealed class QuestRegistry
foreach (var quest in _quests.Values)
{
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)
=> _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)
allQuests.AddRange(_questData.GetClassJobQuests(classJob.AsJob()));
allQuests.AddRange(_questData.GetClassJobQuests(classJob.AsJob(), includeRoleQuests));
return allQuests
.Where(x => IsKnownQuest(x.QuestId))

View File

@ -37,7 +37,7 @@ internal static class Mount
private bool _mountTriggered;
private DateTime _retryAt = DateTime.MinValue;
public MountResult EvaluateMountState()
public unsafe MountResult EvaluateMountState()
{
if (condition[ConditionFlag.Mounted])
return MountResult.DontMount;
@ -58,7 +58,7 @@ internal static class Mount
{
Vector3 playerPosition = clientState.LocalPlayer?.Position ?? Vector3.Zero;
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");
return MountResult.DontMount;

View File

@ -1,4 +1,6 @@
using Microsoft.Extensions.Logging;
using System.Linq;
using Microsoft.Extensions.Logging;
using Questionable.Data;
using Questionable.Functions;
using Questionable.Model;
using Questionable.Model.Questing;
@ -21,7 +23,7 @@ internal static class NextQuest
return null;
// 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 new SetQuestTask(step.NextQuestId, quest.Id);

View File

@ -14,6 +14,7 @@ internal static class SendNotification
internal sealed class Factory(
AutomatonIpc automatonIpc,
AutoDutyIpc autoDutyIpc,
BossModIpc bossModIpc,
TerritoryData territoryData) : SimpleTaskFactory
{
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
? territoryData.GetContentFinderCondition(step.ContentFinderConditionId.Value)?.Name
: 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,
};
}

View File

@ -96,6 +96,12 @@ internal static class Interact
private EInteractionState _interactionState = EInteractionState.None;
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 EInteractionType InteractionType { get; set; }
@ -179,7 +185,14 @@ internal static class Interact
return ETaskResult.StillRunning;
else if (ProgressContext.WasSuccessful() ||
_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);

View File

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

View File

@ -21,7 +21,8 @@ internal static class WaitAtEnd
IClientState clientState,
ICondition condition,
TerritoryData territoryData,
AutoDutyIpc autoDutyIpc)
AutoDutyIpc autoDutyIpc,
BossModIpc bossModIpc)
: ITaskFactory
{
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
@ -53,7 +54,7 @@ internal static class WaitAtEnd
return [new WaitNextStepOrSequence()];
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()];
case EInteractionType.WalkTo:

View File

@ -1,8 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Dalamud.Plugin.Services;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Questionable.Controller.Steps.Interactions;
using Questionable.Data;
using Questionable.Model;
using Questionable.Model.Questing;
@ -11,11 +14,19 @@ namespace Questionable.Controller.Steps;
internal sealed class TaskCreator
{
private readonly IServiceProvider _serviceProvider;
private readonly TerritoryData _territoryData;
private readonly IClientState _clientState;
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;
_territoryData = territoryData;
_clientState = clientState;
_logger = logger;
}
@ -40,6 +51,31 @@ internal sealed class TaskCreator
return tasks;
})
.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)
_logger.LogInformation("Nothing to execute for step?");
else

View File

@ -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,
}
}

View File

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

View File

@ -23,17 +23,17 @@ internal sealed class JournalData
var genreLimsa = new Genre(uint.MaxValue - 3, "Starting in Limsa Lominsa", 1,
new uint[] { 108, 109 }.Concat(limsaStart.QuestRedoParam.Select(x => x.Quest.RowId))
.Where(x => x != 0)
.Select(x => questData.GetQuestInfo(new QuestId((ushort)(x & 0xFFFF))))
.Select(x => questData.GetQuestInfo(QuestId.FromRowId(x)))
.ToList());
var genreGridania = new Genre(uint.MaxValue - 2, "Starting in Gridania", 1,
new uint[] { 85, 123, 124 }.Concat(gridaniaStart.QuestRedoParam.Select(x => x.Quest.RowId))
.Where(x => x != 0)
.Select(x => questData.GetQuestInfo(new QuestId((ushort)(x & 0xFFFF))))
.Select(x => questData.GetQuestInfo(QuestId.FromRowId(x)))
.ToList());
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))
.Where(x => x != 0)
.Select(x => questData.GetQuestInfo(new QuestId((ushort)(x & 0xFFFF))))
.Select(x => questData.GetQuestInfo(QuestId.FromRowId(x)))
.ToList());
genres.InsertRange(0, [genreLimsa, genreGridania, genreUldah]);
genres.Single(x => x.Id == 1)

View File

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

View File

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
@ -7,6 +8,7 @@ using Dalamud.Game;
using Dalamud.Plugin.Services;
using Dalamud.Utility;
using Lumina.Excel.Sheets;
using Questionable.Model.Questing;
namespace Questionable.Data;
@ -17,6 +19,7 @@ internal sealed class TerritoryData
private readonly ImmutableDictionary<ushort, uint> _dutyTerritories;
private readonly ImmutableDictionary<uint, string> _instanceNames;
private readonly ImmutableDictionary<uint, ContentFinderConditionData> _contentFinderConditions;
private readonly ImmutableDictionary<(ElementId QuestId, byte Index), uint> _questsToCfc;
public TerritoryData(IDataManager dataManager)
{
@ -45,9 +48,16 @@ internal sealed class TerritoryData
.ToImmutableDictionary(x => x.Content.RowId, x => x.Name.ToDalamudString().ToString());
_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))
.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);
@ -77,6 +87,23 @@ internal sealed class TerritoryData
[NotNullWhen(true)] out ContentFinderConditionData? 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)
{
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));
}
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(
uint ContentFinderConditionId,
string Name,

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