diff --git a/.gitmodules b/.gitmodules index 4ac68e07..0bc08a36 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [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 diff --git a/GatheringPathRenderer/EditorCommands.cs b/GatheringPathRenderer/EditorCommands.cs new file mode 100644 index 00000000..d15e6207 --- /dev/null +++ b/GatheringPathRenderer/EditorCommands.cs @@ -0,0 +1,215 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Numerics; +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; +using Dalamud.Game.ClientState.Objects; +using Dalamud.Game.ClientState.Objects.Enums; +using Dalamud.Game.ClientState.Objects.Types; +using Dalamud.Game.Command; +using Dalamud.Plugin.Services; +using Lumina.Excel.GeneratedSheets; +using Questionable.Model; +using Questionable.Model.Gathering; + +namespace GatheringPathRenderer; + +internal sealed class EditorCommands : IDisposable +{ + private readonly RendererPlugin _plugin; + private readonly IDataManager _dataManager; + private readonly ICommandManager _commandManager; + private readonly ITargetManager _targetManager; + private readonly IClientState _clientState; + private readonly IChatGui _chatGui; + + public EditorCommands(RendererPlugin plugin, IDataManager dataManager, ICommandManager commandManager, + ITargetManager targetManager, IClientState clientState, IChatGui chatGui) + { + _plugin = plugin; + _dataManager = dataManager; + _commandManager = commandManager; + _targetManager = targetManager; + _clientState = clientState; + _chatGui = chatGui; + + _commandManager.AddHandler("/qg", new CommandInfo(ProcessCommand)); + } + + private void ProcessCommand(string command, string argument) + { + string[] parts = argument.Split(' '); + string subCommand = parts[0]; + List arguments = parts.Skip(1).ToList(); + + try + { + switch (subCommand) + { + case "add": + CreateOrAddLocationToGroup(arguments); + break; + } + } + catch (Exception e) + { + _chatGui.PrintError(e.ToString(), "qG"); + } + } + + private void CreateOrAddLocationToGroup(List arguments) + { + var target = _targetManager.Target; + if (target == null || target.ObjectKind != ObjectKind.GatheringPoint) + throw new Exception("No valid target"); + + var gatheringPoint = _dataManager.GetExcelSheet()!.GetRow(target.DataId); + if (gatheringPoint == null) + throw new Exception("Invalid gathering point"); + + FileInfo targetFile; + GatheringRoot root; + var locationsInTerritory = _plugin.GetLocationsInTerritory(_clientState.TerritoryType).ToList(); + var location = locationsInTerritory.SingleOrDefault(x => x.Id == gatheringPoint.GatheringPointBase.Row); + if (location != null) + { + targetFile = location.File; + root = location.Root; + + // if this is an existing node, ignore it + var existingNode = root.Groups.SelectMany(x => x.Nodes.Where(y => y.DataId == target.DataId)) + .Any(x => x.Locations.Any(y => Vector3.Distance(y.Position, target.Position) < 0.1f)); + if (existingNode) + throw new Exception("Node already exists"); + + if (arguments.Contains("group")) + AddToNewGroup(root, target); + else + AddToExistingGroup(root, target); + } + else + { + (targetFile, root) = CreateNewFile(gatheringPoint, target); + _chatGui.Print($"Creating new file under {targetFile.FullName}", "qG"); + } + + _plugin.Save(targetFile, root); + } + + public void AddToNewGroup(GatheringRoot root, IGameObject target) + { + root.Groups.Add(new GatheringNodeGroup + { + Nodes = + [ + new GatheringNode + { + DataId = target.DataId, + Locations = + [ + new GatheringLocation + { + Position = target.Position, + } + ] + } + ] + }); + _chatGui.Print("Added group.", "qG"); + } + + public void AddToExistingGroup(GatheringRoot root, IGameObject target) + { + // find the same data id + var node = root.Groups.SelectMany(x => x.Nodes) + .SingleOrDefault(x => x.DataId == target.DataId); + if (node != null) + { + node.Locations.Add(new GatheringLocation + { + Position = target.Position, + }); + _chatGui.Print($"Added location to existing node {target.DataId}.", "qG"); + } + else + { + // find the closest group + var closestGroup = root.Groups + .Select(group => new + { + Group = group, + Distance = group.Nodes.Min(x => + x.Locations.Min(y => + Vector3.Distance(_clientState.LocalPlayer!.Position, y.Position))) + }) + .OrderBy(x => x.Distance) + .First(); + + closestGroup.Group.Nodes.Add(new GatheringNode + { + DataId = target.DataId, + Locations = + [ + new GatheringLocation + { + Position = target.Position, + } + ] + }); + _chatGui.Print($"Added new node {target.DataId}.", "qG"); + } + } + + public (FileInfo targetFile, GatheringRoot root) CreateNewFile(GatheringPoint gatheringPoint, IGameObject target) + { + // determine target folder + DirectoryInfo? targetFolder = _plugin.GetLocationsInTerritory(_clientState.TerritoryType).FirstOrDefault() + ?.File.Directory; + if (targetFolder == null) + { + var territoryInfo = _dataManager.GetExcelSheet()!.GetRow(_clientState.TerritoryType)!; + targetFolder = _plugin.PathsDirectory + .CreateSubdirectory(ExpansionData.ExpansionFolders[(byte)territoryInfo.ExVersion.Row]) + .CreateSubdirectory(territoryInfo.PlaceName.Value!.Name.ToString()); + } + + FileInfo targetFile = + new FileInfo( + Path.Combine(targetFolder.FullName, + $"{gatheringPoint.GatheringPointBase.Row}_{gatheringPoint.PlaceName.Value!.Name}_{(_clientState.LocalPlayer!.ClassJob.Id == 16 ? "MIN" : "BTN")}.json")); + var root = new GatheringRoot + { + TerritoryId = _clientState.TerritoryType, + Groups = + [ + new GatheringNodeGroup + { + Nodes = + [ + new GatheringNode + { + DataId = target.DataId, + Locations = + [ + new GatheringLocation + { + Position = target.Position + } + ] + } + ] + } + ] + }; + return (targetFile, root); + } + + public void Dispose() + { + _commandManager.RemoveHandler("/qg"); + } +} diff --git a/GatheringPathRenderer/GatheringPathRenderer.csproj b/GatheringPathRenderer/GatheringPathRenderer.csproj new file mode 100644 index 00000000..1d3aeae5 --- /dev/null +++ b/GatheringPathRenderer/GatheringPathRenderer.csproj @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/GatheringPathRenderer/GatheringPathRenderer.json b/GatheringPathRenderer/GatheringPathRenderer.json new file mode 100644 index 00000000..8d68d1d8 --- /dev/null +++ b/GatheringPathRenderer/GatheringPathRenderer.json @@ -0,0 +1,6 @@ +{ + "Name": "GatheringPathRenderer", + "Author": "Liza Carvelli", + "Punchline": "dev only plugin: Renders gathering location.", + "Description": "dev only plugin: Renders gathering location (without ECommons polluting the entire normal project)." +} diff --git a/GatheringPathRenderer/RendererPlugin.cs b/GatheringPathRenderer/RendererPlugin.cs new file mode 100644 index 00000000..ef2b352c --- /dev/null +++ b/GatheringPathRenderer/RendererPlugin.cs @@ -0,0 +1,290 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; +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 FFXIVClientStructs.FFXIV.Common.Math; +using GatheringPathRenderer.Windows; +using Questionable.Model; +using Questionable.Model.Gathering; + +namespace GatheringPathRenderer; + +public sealed class RendererPlugin : IDalamudPlugin +{ + private const long OnTerritoryChange = -2; + + private readonly WindowSystem _windowSystem = new(nameof(RendererPlugin)); + private readonly List _colors = [0xFFFF2020, 0xFF20FF20, 0xFF2020FF, 0xFFFFFF20, 0xFFFF20FF, 0xFF20FFFF]; + + private readonly IDalamudPluginInterface _pluginInterface; + private readonly IClientState _clientState; + private readonly IPluginLog _pluginLog; + + private readonly EditorCommands _editorCommands; + private readonly EditorWindow _editorWindow; + + private readonly List _gatheringLocations = []; + + public RendererPlugin(IDalamudPluginInterface pluginInterface, IClientState clientState, + ICommandManager commandManager, IDataManager dataManager, ITargetManager targetManager, IChatGui chatGui, + IObjectTable objectTable, IPluginLog pluginLog) + { + _pluginInterface = pluginInterface; + _clientState = clientState; + _pluginLog = pluginLog; + + _editorCommands = new EditorCommands(this, dataManager, commandManager, targetManager, clientState, chatGui); + _editorWindow = new EditorWindow(this, _editorCommands, dataManager, targetManager, clientState, objectTable) + { IsOpen = true }; + _windowSystem.AddWindow(_editorWindow); + + _pluginInterface.GetIpcSubscriber("Questionable.ReloadData") + .Subscribe(Reload); + + ECommonsMain.Init(pluginInterface, this, Module.SplatoonAPI); + LoadGatheringLocationsFromDirectory(); + + _pluginInterface.UiBuilder.Draw += _windowSystem.Draw; + _clientState.TerritoryChanged += TerritoryChanged; + if (_clientState.IsLoggedIn) + TerritoryChanged(_clientState.TerritoryType); + } + + internal DirectoryInfo PathsDirectory + { + get + { + DirectoryInfo? solutionDirectory = _pluginInterface.AssemblyLocation.Directory?.Parent?.Parent?.Parent; + if (solutionDirectory != null) + { + DirectoryInfo pathProjectDirectory = + new DirectoryInfo(Path.Combine(solutionDirectory.FullName, "GatheringPaths")); + if (pathProjectDirectory.Exists) + return pathProjectDirectory; + } + + throw new Exception("Unable to resolve project path"); + } + } + + internal void Reload() + { + LoadGatheringLocationsFromDirectory(); + Redraw(); + } + + private void LoadGatheringLocationsFromDirectory() + { + _gatheringLocations.Clear(); + + try + { + foreach (var expansionFolder in ExpansionData.ExpansionFolders.Values) + LoadFromDirectory( + new DirectoryInfo(Path.Combine(PathsDirectory.FullName, expansionFolder))); + + _pluginLog.Information( + $"Loaded {_gatheringLocations.Count} gathering root locations from project directory"); + } + catch (Exception e) + { + _pluginLog.Error(e, "Failed to load paths from project directory"); + } + } + + private void LoadFromDirectory(DirectoryInfo directory) + { + if (!directory.Exists) + return; + + _pluginLog.Information($"Loading locations from {directory}"); + foreach (FileInfo fileInfo in directory.GetFiles("*.json")) + { + try + { + using FileStream stream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read); + LoadLocationFromStream(fileInfo, stream); + } + catch (Exception e) + { + throw new InvalidDataException($"Unable to load file {fileInfo.FullName}", e); + } + } + + foreach (DirectoryInfo childDirectory in directory.GetDirectories()) + LoadFromDirectory(childDirectory); + } + + private void LoadLocationFromStream(FileInfo fileInfo, Stream stream) + { + var locationNode = JsonNode.Parse(stream)!; + GatheringRoot root = locationNode.Deserialize()!; + _gatheringLocations.Add(new GatheringLocationContext(fileInfo, ushort.Parse(fileInfo.Name.Split('_')[0]), + root)); + } + + internal IEnumerable GetLocationsInTerritory(ushort territoryId) + => _gatheringLocations.Where(x => x.Root.TerritoryId == territoryId); + + internal void Save(FileInfo targetFile, GatheringRoot root) + { + JsonSerializerOptions options = new() + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault, + WriteIndented = true, + }; + using (var stream = File.Create(targetFile.FullName)) + { + var jsonNode = (JsonObject)JsonSerializer.SerializeToNode(root, options)!; + var newNode = new JsonObject(); + newNode.Add("$schema", + "https://git.carvel.li/liza/Questionable/raw/branch/master/GatheringPaths/gatheringlocation-v1.json"); + foreach (var (key, value) in jsonNode) + newNode.Add(key, value?.DeepClone()); + + using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions + { + Indented = true + }); + newNode.WriteTo(writer, options); + } + + Reload(); + } + + private void TerritoryChanged(ushort territoryId) => Redraw(); + + internal void Redraw() + { + Splatoon.RemoveDynamicElements("GatheringPathRenderer"); + + var elements = GetLocationsInTerritory(_clientState.TerritoryType) + .SelectMany(location => + location.Root.Groups.SelectMany(group => + group.Nodes.SelectMany(node => node.Locations + .SelectMany(x => + { + bool isCone = false; + int minimumAngle = 0; + int maximumAngle = 0; + if (_editorWindow.TryGetOverride(x.InternalId, out LocationOverride? locationOverride) && + locationOverride != null) + { + if (locationOverride.IsCone()) + { + isCone = true; + minimumAngle = locationOverride.MinimumAngle.GetValueOrDefault(); + maximumAngle = locationOverride.MaximumAngle.GetValueOrDefault(); + } + } + + if (!isCone && x.IsCone()) + { + isCone = true; + minimumAngle = x.MinimumAngle.GetValueOrDefault(); + maximumAngle = x.MaximumAngle.GetValueOrDefault(); + } + + var a = GatheringMath.CalculateLandingLocation(x, 0, 0); + var b = GatheringMath.CalculateLandingLocation(x, 1, 1); + return new List + { + new Element(isCone + ? ElementType.ConeAtFixedCoordinates + : ElementType.CircleAtFixedCoordinates) + { + refX = x.Position.X, + refY = x.Position.Z, + refZ = x.Position.Y, + Filled = true, + radius = x.CalculateMinimumDistance(), + Donut = x.CalculateMaximumDistance() - 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 = 0x00000000, + Enabled = true, + overlayText = + $"{location.Root.Groups.IndexOf(group)} // {node.DataId} / {node.Locations.IndexOf(x)}", + }, + 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" + } + }; + })))) + .ToList(); + + if (elements.Count == 0) + { + _pluginLog.Information("No new elements to render."); + return; + } + + _ = 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.TerritoryChanged -= TerritoryChanged; + _pluginInterface.UiBuilder.Draw -= _windowSystem.Draw; + + Splatoon.RemoveDynamicElements("GatheringPathRenderer"); + ECommonsMain.Dispose(); + + _pluginInterface.GetIpcSubscriber("Questionable.ReloadData") + .Unsubscribe(Reload); + + _editorCommands.Dispose(); + } + + internal sealed record GatheringLocationContext(FileInfo File, ushort Id, GatheringRoot Root); +} diff --git a/GatheringPathRenderer/Windows/EditorWindow.cs b/GatheringPathRenderer/Windows/EditorWindow.cs new file mode 100644 index 00000000..d2edf65e --- /dev/null +++ b/GatheringPathRenderer/Windows/EditorWindow.cs @@ -0,0 +1,226 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Numerics; +using Dalamud.Game.ClientState.Objects; +using Dalamud.Game.ClientState.Objects.Enums; +using Dalamud.Game.ClientState.Objects.Types; +using Dalamud.Interface.Colors; +using Dalamud.Interface.Windowing; +using Dalamud.Plugin.Services; +using ImGuiNET; +using Lumina.Excel.GeneratedSheets; +using Questionable.Model.Gathering; + +namespace GatheringPathRenderer.Windows; + +internal sealed class EditorWindow : Window +{ + private readonly RendererPlugin _plugin; + private readonly EditorCommands _editorCommands; + private readonly IDataManager _dataManager; + private readonly ITargetManager _targetManager; + private readonly IClientState _clientState; + private readonly IObjectTable _objectTable; + + private readonly Dictionary _changes = []; + + private IGameObject? _target; + + private (RendererPlugin.GatheringLocationContext Context, GatheringNode Node, GatheringLocation Location)? + _targetLocation; + + public EditorWindow(RendererPlugin plugin, EditorCommands editorCommands, IDataManager dataManager, + ITargetManager targetManager, IClientState clientState, IObjectTable objectTable) + : base("Gathering Path Editor###QuestionableGatheringPathEditor") + { + _plugin = plugin; + _editorCommands = editorCommands; + _dataManager = dataManager; + _targetManager = targetManager; + _clientState = clientState; + _objectTable = objectTable; + + SizeConstraints = new WindowSizeConstraints + { + MinimumSize = new Vector2(300, 300), + }; + ShowCloseButton = false; + } + + public override void Update() + { + _target = _targetManager.Target; + var gatheringLocations = _plugin.GetLocationsInTerritory(_clientState.TerritoryType); + var location = gatheringLocations.SelectMany(context => + context.Root.Groups.SelectMany(group => + group.Nodes + .SelectMany(node => node.Locations + .Where(location => + { + if (_target != null) + return Vector3.Distance(location.Position, _target.Position) < 0.1f; + else + return Vector3.Distance(location.Position, _clientState.LocalPlayer!.Position) < 3f; + }) + .Select(location => new { Context = context, Node = node, Location = location })))) + .FirstOrDefault(); + if (_target != null && _target.ObjectKind != ObjectKind.GatheringPoint) + { + _target = null; + _targetLocation = null; + return; + } + + if (location == null) + { + _targetLocation = null; + return; + } + + _target ??= _objectTable.FirstOrDefault( + x => x.ObjectKind == ObjectKind.GatheringPoint && + x.DataId == location.Node.DataId && + Vector3.Distance(location.Location.Position, _clientState.LocalPlayer!.Position) < 3f); + _targetLocation = (location.Context, location.Node, location.Location); + } + + public override bool DrawConditions() + { + return _target != null || _targetLocation != null; + } + + public override void Draw() + { + if (_target != null && _targetLocation != null) + { + var context = _targetLocation.Value.Context; + var node = _targetLocation.Value.Node; + var location = _targetLocation.Value.Location; + ImGui.Text(context.File.Directory?.Name ?? string.Empty); + ImGui.Indent(); + ImGui.Text(context.File.Name); + ImGui.Unindent(); + ImGui.Text($"{_target.DataId} // {location.InternalId}"); + ImGui.Text(string.Create(CultureInfo.InvariantCulture, $"{location.Position:G}")); + + if (!_changes.TryGetValue(location.InternalId, out LocationOverride? locationOverride)) + { + locationOverride = new LocationOverride(); + _changes[location.InternalId] = locationOverride; + } + + int minAngle = locationOverride.MinimumAngle ?? location.MinimumAngle.GetValueOrDefault(); + if (ImGui.DragInt("Min Angle", ref minAngle, 5, -360, 360)) + { + locationOverride.MinimumAngle = minAngle; + locationOverride.MaximumAngle ??= location.MaximumAngle.GetValueOrDefault(); + _plugin.Redraw(); + } + + int maxAngle = locationOverride.MaximumAngle ?? location.MaximumAngle.GetValueOrDefault(); + if (ImGui.DragInt("Max Angle", ref maxAngle, 5, -360, 360)) + { + locationOverride.MinimumAngle ??= location.MinimumAngle.GetValueOrDefault(); + locationOverride.MaximumAngle = maxAngle; + _plugin.Redraw(); + } + + bool unsaved = locationOverride is { MinimumAngle: not null, MaximumAngle: not null }; + ImGui.BeginDisabled(!unsaved); + if (unsaved) + ImGui.PushStyleColor(ImGuiCol.Button, ImGuiColors.DalamudRed); + if (ImGui.Button("Save")) + { + location.MinimumAngle = locationOverride.MinimumAngle; + location.MaximumAngle = locationOverride.MaximumAngle; + _plugin.Save(context.File, context.Root); + } + if (unsaved) + ImGui.PopStyleColor(); + + ImGui.SameLine(); + if (ImGui.Button("Reset")) + { + _changes[location.InternalId] = new LocationOverride(); + _plugin.Redraw(); + } + + ImGui.EndDisabled(); + + + List nodesInObjectTable = _objectTable + .Where(x => x.ObjectKind == ObjectKind.GatheringPoint && x.DataId == _target.DataId) + .ToList(); + List missingLocations = nodesInObjectTable + .Where(x => !node.Locations.Any(y => Vector3.Distance(x.Position, y.Position) < 0.1f)) + .ToList(); + if (missingLocations.Count > 0) + { + if (ImGui.Button("Add missing locations")) + { + foreach (var missing in missingLocations) + _editorCommands.AddToExistingGroup(context.Root, missing); + + _plugin.Save(context.File, context.Root); + } + } + } + else if (_target != null) + { + var gatheringPoint = _dataManager.GetExcelSheet()!.GetRow(_target.DataId); + if (gatheringPoint == null) + return; + + var locationsInTerritory = _plugin.GetLocationsInTerritory(_clientState.TerritoryType).ToList(); + var location = locationsInTerritory.SingleOrDefault(x => x.Id == gatheringPoint.GatheringPointBase.Row); + if (location != null) + { + var targetFile = location.File; + var root = location.Root; + + if (ImGui.Button("Add to closest group")) + { + _editorCommands.AddToExistingGroup(root, _target); + _plugin.Save(targetFile, root); + } + + ImGui.BeginDisabled(root.Groups.Any(group => group.Nodes.Any(node => node.DataId == _target.DataId))); + ImGui.SameLine(); + if (ImGui.Button("Add as new group")) + { + _editorCommands.AddToNewGroup(root, _target); + _plugin.Save(targetFile, root); + } + + ImGui.EndDisabled(); + } + else + { + if (ImGui.Button("Create location")) + { + var (targetFile, root) = _editorCommands.CreateNewFile(gatheringPoint, _target); + _plugin.Save(targetFile, root); + } + } + } + } + + public bool TryGetOverride(Guid internalId, out LocationOverride? locationOverride) + => _changes.TryGetValue(internalId, out locationOverride); +} + +internal sealed class LocationOverride +{ + public int? MinimumAngle { get; set; } + public int? MaximumAngle { get; set; } + public float? MinimumDistance { get; set; } + public float? MaximumDistance { get; set; } + + public bool IsCone() + { + return MinimumAngle != null && MaximumAngle != null && MinimumAngle != MaximumAngle; + } +} diff --git a/GatheringPathRenderer/packages.lock.json b/GatheringPathRenderer/packages.lock.json new file mode 100644 index 00000000..c7a267af --- /dev/null +++ b/GatheringPathRenderer/packages.lock.json @@ -0,0 +1,109 @@ +{ + "version": 1, + "dependencies": { + "net8.0-windows7.0": { + "DalamudPackager": { + "type": "Direct", + "requested": "[2.1.13, )", + "resolved": "2.1.13", + "contentHash": "rMN1omGe8536f4xLMvx9NwfvpAc9YFFfeXJ1t4P4PE6Gu8WCIoFliR1sh07hM+bfODmesk/dvMbji7vNI+B/pQ==" + }, + "DotNet.ReproducibleBuilds": { + "type": "Direct", + "requested": "[1.1.1, )", + "resolved": "1.1.1", + "contentHash": "+H2t/t34h6mhEoUvHi8yGXyuZ2GjSovcGYehJrS2MDm2XgmPfZL2Sdxg+uL2lKgZ4M6tTwKHIlxOob2bgh0NRQ==", + "dependencies": { + "Microsoft.SourceLink.AzureRepos.Git": "1.1.1", + "Microsoft.SourceLink.Bitbucket.Git": "1.1.1", + "Microsoft.SourceLink.GitHub": "1.1.1", + "Microsoft.SourceLink.GitLab": "1.1.1" + } + }, + "Microsoft.SourceLink.Gitea": { + "type": "Direct", + "requested": "[8.0.0, )", + "resolved": "8.0.0", + "contentHash": "KOBodmDnlWGIqZt2hT47Q69TIoGhIApDVLCyyj9TT5ct8ju16AbHYcB4XeknoHX562wO1pMS/1DfBIZK+V+sxg==", + "dependencies": { + "Microsoft.Build.Tasks.Git": "8.0.0", + "Microsoft.SourceLink.Common": "8.0.0" + } + }, + "Microsoft.Build.Tasks.Git": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ==" + }, + "Microsoft.SourceLink.AzureRepos.Git": { + "type": "Transitive", + "resolved": "1.1.1", + "contentHash": "qB5urvw9LO2bG3eVAkuL+2ughxz2rR7aYgm2iyrB8Rlk9cp2ndvGRCvehk3rNIhRuNtQaeKwctOl1KvWiklv5w==", + "dependencies": { + "Microsoft.Build.Tasks.Git": "1.1.1", + "Microsoft.SourceLink.Common": "1.1.1" + } + }, + "Microsoft.SourceLink.Bitbucket.Git": { + "type": "Transitive", + "resolved": "1.1.1", + "contentHash": "cDzxXwlyWpLWaH0em4Idj0H3AmVo3L/6xRXKssYemx+7W52iNskj/SQ4FOmfCb8YQt39otTDNMveCZzYtMoucQ==", + "dependencies": { + "Microsoft.Build.Tasks.Git": "1.1.1", + "Microsoft.SourceLink.Common": "1.1.1" + } + }, + "Microsoft.SourceLink.Common": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" + }, + "Microsoft.SourceLink.GitHub": { + "type": "Transitive", + "resolved": "1.1.1", + "contentHash": "IaJGnOv/M7UQjRJks7B6p7pbPnOwisYGOIzqCz5ilGFTApZ3ktOR+6zJ12ZRPInulBmdAf1SrGdDG2MU8g6XTw==", + "dependencies": { + "Microsoft.Build.Tasks.Git": "1.1.1", + "Microsoft.SourceLink.Common": "1.1.1" + } + }, + "Microsoft.SourceLink.GitLab": { + "type": "Transitive", + "resolved": "1.1.1", + "contentHash": "tvsg47DDLqqedlPeYVE2lmiTpND8F0hkrealQ5hYltSmvruy/Gr5nHAKSsjyw5L3NeM/HLMI5ORv7on/M4qyZw==", + "dependencies": { + "Microsoft.Build.Tasks.Git": "1.1.1", + "Microsoft.SourceLink.Common": "1.1.1" + } + }, + "System.Text.Encodings.Web": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "yev/k9GHAEGx2Rg3/tU6MQh4HGBXJs70y7j1LaM1i/ER9po+6nnQ6RRqTJn1E7Xu0fbIFK80Nh5EoODxrbxwBQ==" + }, + "System.Text.Json": { + "type": "Transitive", + "resolved": "8.0.4", + "contentHash": "bAkhgDJ88XTsqczoxEMliSrpijKZHhbJQldhAmObj/RbrN3sU5dcokuXmWJWsdQAhiMJ9bTayWsL1C9fbbCRhw==", + "dependencies": { + "System.Text.Encodings.Web": "8.0.0" + } + }, + "ecommons": { + "type": "Project" + }, + "gatheringpaths": { + "type": "Project", + "dependencies": { + "Questionable.Model": "[1.0.0, )" + } + }, + "questionable.model": { + "type": "Project", + "dependencies": { + "System.Text.Json": "[8.0.4, )" + } + } + } + } +} \ No newline at end of file diff --git a/GatheringPaths/2.x - A Realm Reborn/.gitkeep b/GatheringPaths/2.x - A Realm Reborn/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/GatheringPaths/3.x - Heavensward/.gitkeep b/GatheringPaths/3.x - Heavensward/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/GatheringPaths/4.x - Stormblood/.gitkeep b/GatheringPaths/4.x - Stormblood/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/GatheringPaths/5.x - Shadowbringers/.gitkeep b/GatheringPaths/5.x - Shadowbringers/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/GatheringPaths/6.x - Endwalker/Garlemald/822_Monitoring Station G_MIN.json b/GatheringPaths/6.x - Endwalker/Garlemald/822_Monitoring Station G_MIN.json new file mode 100644 index 00000000..2ba70d89 --- /dev/null +++ b/GatheringPaths/6.x - Endwalker/Garlemald/822_Monitoring Station G_MIN.json @@ -0,0 +1,158 @@ +{ + "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/GatheringPaths/gatheringlocation-v1.json", + "Author": [], + "TerritoryId": 958, + "AetheryteShortcut": "Garlemald - Camp Broken Glass", + "Groups": [ + { + "Nodes": [ + { + "DataId": 33932, + "Locations": [ + { + "Position": { + "X": -80.95969, + "Y": -9.810837, + "Z": 462.2579 + }, + "MinimumAngle": 130, + "MaximumAngle": 260 + } + ] + }, + { + "DataId": 33933, + "Locations": [ + { + "Position": { + "X": -72.11935, + "Y": -10.90324, + "Z": 471.2258 + }, + "MinimumAngle": 105, + "MaximumAngle": 250 + }, + { + "Position": { + "X": -98.97565, + "Y": -5.664787, + "Z": 463.9966 + }, + "MinimumAngle": 60, + "MaximumAngle": 230 + }, + { + "Position": { + "X": -63.49503, + "Y": -11.21235, + "Z": 469.3839 + }, + "MinimumAngle": 80, + "MaximumAngle": 255 + } + ] + } + ] + }, + { + "Nodes": [ + { + "DataId": 33931, + "Locations": [ + { + "Position": { + "X": -61.34306, + "Y": 6.11244, + "Z": 318.3409 + }, + "MinimumAngle": -120, + "MaximumAngle": 70 + }, + { + "Position": { + "X": -61.47854, + "Y": 6.076105, + "Z": 281.4938 + }, + "MinimumAngle": 65, + "MaximumAngle": 240 + }, + { + "Position": { + "X": -73.25829, + "Y": 6.108262, + "Z": 302.9926 + }, + "MinimumAngle": 50, + "MaximumAngle": 220 + } + ] + }, + { + "DataId": 33930, + "Locations": [ + { + "Position": { + "X": -51.28564, + "Y": 6.088318, + "Z": 318.0529 + }, + "MinimumAngle": -65, + "MaximumAngle": 110 + } + ] + } + ] + }, + { + "Nodes": [ + { + "DataId": 33935, + "Locations": [ + { + "Position": { + "X": 72.58704, + "Y": -11.59895, + "Z": 354.757 + }, + "MinimumAngle": 75, + "MaximumAngle": 235 + }, + { + "Position": { + "X": 65.33016, + "Y": -11.61111, + "Z": 358.7321 + }, + "MinimumAngle": 65, + "MaximumAngle": 235 + }, + { + "Position": { + "X": 68.21196, + "Y": -11.81954, + "Z": 366.5172 + }, + "MinimumAngle": 5, + "MaximumAngle": 85 + } + ] + }, + { + "DataId": 33934, + "Locations": [ + { + "Position": { + "X": 81.30492, + "Y": -11.53227, + "Z": 347.9922 + }, + "MinimumAngle": 50, + "MaximumAngle": 215 + } + ] + } + ] + } + ] +} diff --git a/GatheringPaths/6.x - Endwalker/Mare Lamentorum/821_The Crushing Brand_MIN.json b/GatheringPaths/6.x - Endwalker/Mare Lamentorum/821_The Crushing Brand_MIN.json new file mode 100644 index 00000000..0b667246 --- /dev/null +++ b/GatheringPaths/6.x - Endwalker/Mare Lamentorum/821_The Crushing Brand_MIN.json @@ -0,0 +1,157 @@ +{ + "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/GatheringPaths/gatheringlocation-v1.json", + "Author": [], + "TerritoryId": 959, + "Groups": [ + { + "Nodes": [ + { + "DataId": 33929, + "Locations": [ + { + "Position": { + "X": 304.4121, + "Y": 118.8077, + "Z": 673.4494 + }, + "MinimumAngle": 50, + "MaximumAngle": 230 + }, + { + "Position": { + "X": 297.7666, + "Y": 119.4976, + "Z": 679.5604 + }, + "MinimumAngle": 50, + "MaximumAngle": 220 + }, + { + "Position": { + "X": 322.163, + "Y": 119.0883, + "Z": 657.4384 + }, + "MinimumAngle": 55, + "MaximumAngle": 235 + } + ] + }, + { + "DataId": 33928, + "Locations": [ + { + "Position": { + "X": 313.72, + "Y": 118.3442, + "Z": 664.8668 + }, + "MinimumAngle": 60, + "MaximumAngle": 230 + } + ] + } + ] + }, + { + "Nodes": [ + { + "DataId": 33927, + "Locations": [ + { + "Position": { + "X": 394.3838, + "Y": 144.7951, + "Z": 820.7851 + }, + "MinimumAngle": 75, + "MaximumAngle": 250 + }, + { + "Position": { + "X": 421.0549, + "Y": 143.6111, + "Z": 805.9457 + }, + "MinimumAngle": 60, + "MaximumAngle": 225 + }, + { + "Position": { + "X": 414.2961, + "Y": 143.2405, + "Z": 811.3884 + }, + "MinimumAngle": 65, + "MaximumAngle": 230 + } + ] + }, + { + "DataId": 33926, + "Locations": [ + { + "Position": { + "X": 405.2481, + "Y": 143.6621, + "Z": 816.6496 + }, + "MinimumAngle": 75, + "MaximumAngle": 230 + } + ] + } + ] + }, + { + "Nodes": [ + { + "DataId": 33925, + "Locations": [ + { + "Position": { + "X": 474.679, + "Y": 143.4776, + "Z": 698.5961 + }, + "MinimumAngle": 20, + "MaximumAngle": 170 + }, + { + "Position": { + "X": 474.8585, + "Y": 144.2588, + "Z": 685.7468 + }, + "MinimumAngle": 0, + "MaximumAngle": 155 + }, + { + "Position": { + "X": 467.506, + "Y": 144.9235, + "Z": 654.2 + }, + "MinimumAngle": 0, + "MaximumAngle": 150 + } + ] + }, + { + "DataId": 33924, + "Locations": [ + { + "Position": { + "X": 470.7754, + "Y": 144.8793, + "Z": 672.114 + }, + "MinimumAngle": -5, + "MaximumAngle": 165 + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/GatheringPaths/6.x - Endwalker/Thavnair/820_The Hamsa Hatchery_MIN.json b/GatheringPaths/6.x - Endwalker/Thavnair/820_The Hamsa Hatchery_MIN.json new file mode 100644 index 00000000..d5a75e9c --- /dev/null +++ b/GatheringPaths/6.x - Endwalker/Thavnair/820_The Hamsa Hatchery_MIN.json @@ -0,0 +1,159 @@ +{ + "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/GatheringPaths/gatheringlocation-v1.json", + "Author": "liza", + "TerritoryId": 957, + "AetheryteShortcut": "Thavnair - Great Work", + "Groups": [ + { + "Nodes": [ + { + "DataId": 33918, + "Locations": [ + { + "Position": { + "X": -582.5132, + "Y": 40.54578, + "Z": -426.0171 + }, + "MinimumAngle": -50, + "MaximumAngle": 90 + } + ] + }, + { + "DataId": 33919, + "Locations": [ + { + "Position": { + "X": -578.2101, + "Y": 41.27147, + "Z": -447.6376 + }, + "MinimumAngle": 130, + "MaximumAngle": 220 + }, + { + "Position": { + "X": -546.2882, + "Y": 44.52267, + "Z": -435.8184 + }, + "MinimumAngle": 200, + "MaximumAngle": 360 + }, + { + "Position": { + "X": -606.7445, + "Y": 38.37634, + "Z": -425.5284 + }, + "MinimumAngle": -80, + "MaximumAngle": 70 + } + ] + } + ] + }, + { + "Nodes": [ + { + "DataId": 33920, + "Locations": [ + { + "Position": { + "X": -488.2276, + "Y": 34.71221, + "Z": -359.6945 + }, + "MinimumAngle": 20, + "MaximumAngle": 128, + "MinimumDistance": 1.3 + } + ] + }, + { + "DataId": 33921, + "Locations": [ + { + "Position": { + "X": -498.8687, + "Y": 31.08014, + "Z": -351.9397 + }, + "MinimumAngle": 40, + "MaximumAngle": 190 + }, + { + "Position": { + "X": -490.7759, + "Y": 28.70215, + "Z": -344.4114 + }, + "MinimumAngle": -110, + "MaximumAngle": 60 + }, + { + "Position": { + "X": -494.1286, + "Y": 32.89971, + "Z": -355.0208 + }, + "MinimumAngle": 80, + "MaximumAngle": 230 + } + ] + } + ] + }, + { + "Nodes": [ + { + "DataId": 33922, + "Locations": [ + { + "Position": { + "X": -304.0609, + "Y": 68.76999, + "Z": -479.1875 + }, + "MinimumAngle": -110, + "MaximumAngle": 70 + } + ] + }, + { + "DataId": 33923, + "Locations": [ + { + "Position": { + "X": -293.6989, + "Y": 68.77935, + "Z": -484.2256 + }, + "MinimumAngle": -30, + "MaximumAngle": 110 + }, + { + "Position": { + "X": -295.0806, + "Y": 69.12621, + "Z": -498.1898 + }, + "MinimumAngle": 10, + "MaximumAngle": 200 + }, + { + "Position": { + "X": -281.4858, + "Y": 67.64153, + "Z": -477.6673 + }, + "MinimumAngle": -105, + "MaximumAngle": 75 + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/GatheringPaths/7.x - Dawntrail/Urqopacha/974_Chabameki_MIN.json b/GatheringPaths/7.x - Dawntrail/Urqopacha/974_Chabameki_MIN.json new file mode 100644 index 00000000..8b3ade12 --- /dev/null +++ b/GatheringPaths/7.x - Dawntrail/Urqopacha/974_Chabameki_MIN.json @@ -0,0 +1,158 @@ +{ + "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/GatheringPaths/gatheringlocation-v1.json", + "Author": [], + "TerritoryId": 1187, + "AetheryteShortcut": "Urqopacha - Wachunpelo", + "Groups": [ + { + "Nodes": [ + { + "DataId": 34749, + "Locations": [ + { + "Position": { + "X": -392.813, + "Y": -47.04364, + "Z": -386.862 + }, + "MinimumAngle": -10, + "MaximumAngle": 240 + } + ] + }, + { + "DataId": 34750, + "Locations": [ + { + "Position": { + "X": -402.8987, + "Y": -45.59287, + "Z": -390.7613 + }, + "MinimumAngle": 220, + "MaximumAngle": 305 + }, + { + "Position": { + "X": -388.9036, + "Y": -46.86702, + "Z": -381.3985 + }, + "MinimumAngle": -50, + "MaximumAngle": 210 + }, + { + "Position": { + "X": -394.2657, + "Y": -47.86026, + "Z": -394.9654 + }, + "MinimumAngle": -120, + "MaximumAngle": 120 + } + ] + } + ] + }, + { + "Nodes": [ + { + "DataId": 34753, + "Locations": [ + { + "Position": { + "X": -541.7726, + "Y": -22.952, + "Z": -517.8604 + }, + "MinimumAngle": 215, + "MaximumAngle": 330 + } + ] + }, + { + "DataId": 34754, + "Locations": [ + { + "Position": { + "X": -522.9433, + "Y": -25.87319, + "Z": -537.3257 + }, + "MinimumAngle": 225, + "MaximumAngle": 360 + }, + { + "Position": { + "X": -532.3487, + "Y": -22.79275, + "Z": -510.8069 + }, + "MinimumAngle": 135, + "MaximumAngle": 270 + }, + { + "Position": { + "X": -536.2922, + "Y": -23.79476, + "Z": -526.0406 + }, + "MinimumAngle": -110, + "MaximumAngle": 35 + } + ] + } + ] + }, + { + "Nodes": [ + { + "DataId": 34751, + "Locations": [ + { + "Position": { + "X": -448.8079, + "Y": -14.9586, + "Z": -658.0133 + }, + "MinimumAngle": -45, + "MaximumAngle": 115 + } + ] + }, + { + "DataId": 34752, + "Locations": [ + { + "Position": { + "X": -452.2813, + "Y": -12.43015, + "Z": -665.0275 + }, + "MinimumAngle": 0, + "MaximumAngle": 150 + }, + { + "Position": { + "X": -431.5875, + "Y": -16.68724, + "Z": -656.528 + }, + "MinimumAngle": -35, + "MaximumAngle": 90 + }, + { + "Position": { + "X": -439.8079, + "Y": -16.67447, + "Z": -654.6749 + }, + "MinimumAngle": -45, + "MaximumAngle": 85 + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/GatheringPaths/7.x - Dawntrail/Urqopacha/992_Chabameki_BTN.json b/GatheringPaths/7.x - Dawntrail/Urqopacha/992_Chabameki_BTN.json new file mode 100644 index 00000000..494b74d6 --- /dev/null +++ b/GatheringPaths/7.x - Dawntrail/Urqopacha/992_Chabameki_BTN.json @@ -0,0 +1,150 @@ +{ + "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/GatheringPaths/gatheringlocation-v1.json", + "Author": [], + "TerritoryId": 1187, + "AetheryteShortcut": "Urqopacha - Wachunpelo", + "Groups": [ + { + "Nodes": [ + { + "DataId": 34857, + "Locations": [ + { + "Position": { + "X": -12.48859, + "Y": -133.2091, + "Z": -427.7497 + } + } + ] + }, + { + "DataId": 34858, + "Locations": [ + { + "Position": { + "X": -22.41956, + "Y": -129.3952, + "Z": -396.6573 + } + }, + { + "Position": { + "X": -16.08351, + "Y": -137.6674, + "Z": -464.35 + }, + "MinimumAngle": -65, + "MaximumAngle": 145 + }, + { + "Position": { + "X": -9.000858, + "Y": -134.9256, + "Z": -439.0332 + }, + "MinimumAngle": -125, + "MaximumAngle": 105 + } + ] + } + ] + }, + { + "Nodes": [ + { + "DataId": 34861, + "Locations": [ + { + "Position": { + "X": -234.8222, + "Y": -99.01237, + "Z": -376.7287 + }, + "MinimumAngle": -180, + "MaximumAngle": 40 + } + ] + }, + { + "DataId": 34862, + "Locations": [ + { + "Position": { + "X": -236.0182, + "Y": -97.50027, + "Z": -372.1523 + }, + "MinimumAngle": -180, + "MaximumAngle": 45 + }, + { + "Position": { + "X": -249.7221, + "Y": -96.55618, + "Z": -386.2397 + }, + "MinimumAngle": 35, + "MaximumAngle": 280 + }, + { + "Position": { + "X": -241.8424, + "Y": -99.37369, + "Z": -386.2889 + }, + "MinimumAngle": -300, + "MaximumAngle": -45 + } + ] + } + ] + }, + { + "Nodes": [ + { + "DataId": 34860, + "Locations": [ + { + "Position": { + "X": -169.8177, + "Y": -85.61841, + "Z": -240.1007 + } + }, + { + "Position": { + "X": -116.6446, + "Y": -93.99508, + "Z": -274.6102 + }, + "MinimumAngle": -140, + "MaximumAngle": 150 + }, + { + "Position": { + "X": -133.936, + "Y": -91.54122, + "Z": -273.3963 + }, + "MinimumAngle": -155, + "MaximumAngle": 85 + } + ] + }, + { + "DataId": 34859, + "Locations": [ + { + "Position": { + "X": -131.9198, + "Y": -89.88039, + "Z": -249.5422 + } + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/GatheringPaths/7.x - Dawntrail/Urqopacha/993_Chabayuqeq_MIN.json b/GatheringPaths/7.x - Dawntrail/Urqopacha/993_Chabayuqeq_MIN.json new file mode 100644 index 00000000..b0117970 --- /dev/null +++ b/GatheringPaths/7.x - Dawntrail/Urqopacha/993_Chabayuqeq_MIN.json @@ -0,0 +1,148 @@ +{ + "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/GatheringPaths/gatheringlocation-v1.json", + "Author": [], + "TerritoryId": 1187, + "AetheryteShortcut": "Urqopacha - Wachunpelo", + "Groups": [ + { + "Nodes": [ + { + "DataId": 34866, + "Locations": [ + { + "Position": { + "X": 242.7737, + "Y": -135.9734, + "Z": -431.2313 + }, + "MinimumAngle": -55, + "MaximumAngle": 100 + }, + { + "Position": { + "X": 302.1836, + "Y": -135.4149, + "Z": -359.7965 + }, + "MinimumAngle": 5, + "MaximumAngle": 155 + }, + { + "Position": { + "X": 256.1657, + "Y": -135.744, + "Z": -414.7577 + } + } + ] + }, + { + "DataId": 34865, + "Locations": [ + { + "Position": { + "X": 269.7338, + "Y": -134.0488, + "Z": -381.6242 + }, + "MinimumAngle": -85, + "MaximumAngle": 145 + } + ] + } + ] + }, + { + "Nodes": [ + { + "DataId": 34868, + "Locations": [ + { + "Position": { + "X": 389.1952, + "Y": -154.3099, + "Z": -368.3658 + }, + "MinimumAngle": 105, + "MaximumAngle": 345 + }, + { + "Position": { + "X": 401.9319, + "Y": -150.0004, + "Z": -408.114 + }, + "MinimumAngle": -70, + "MaximumAngle": 85 + }, + { + "Position": { + "X": 406.1098, + "Y": -152.2166, + "Z": -364.7227 + }, + "MinimumAngle": -210, + "MaximumAngle": 35 + } + ] + }, + { + "DataId": 34867, + "Locations": [ + { + "Position": { + "X": 399.1297, + "Y": -152.1141, + "Z": -394.71 + }, + "MinimumAngle": 120, + "MaximumAngle": 330 + } + ] + } + ] + }, + { + "Nodes": [ + { + "DataId": 34864, + "Locations": [ + { + "Position": { + "X": 359.517, + "Y": -161.1972, + "Z": -644.0471 + } + }, + { + "Position": { + "X": 307.4235, + "Y": -159.1669, + "Z": -622.6444 + } + }, + { + "Position": { + "X": 348.5925, + "Y": -165.3805, + "Z": -671.4193 + } + } + ] + }, + { + "DataId": 34863, + "Locations": [ + { + "Position": { + "X": 323.8758, + "Y": -162.9682, + "Z": -648.8156 + } + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/GatheringPaths/AssemblyGatheringLocationLoader.cs b/GatheringPaths/AssemblyGatheringLocationLoader.cs new file mode 100644 index 00000000..e74e59f8 --- /dev/null +++ b/GatheringPaths/AssemblyGatheringLocationLoader.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using Questionable.Model.Gathering; + +namespace Questionable.GatheringPaths; + +[SuppressMessage("ReSharper", "PartialTypeWithSinglePart", Justification = "Required for RELEASE")] +public static partial class AssemblyGatheringLocationLoader +{ + private static Dictionary? _locations; + + public static IReadOnlyDictionary GetLocations() + { + if (_locations == null) + { + _locations = []; + LoadLocations(); + } + + return _locations ?? throw new InvalidOperationException("location data is not initialized"); + } + + public static Stream QuestSchema => + typeof(AssemblyGatheringLocationLoader).Assembly.GetManifestResourceStream("Questionable.GatheringPaths.GatheringLocationSchema")!; + + [SuppressMessage("ReSharper", "UnusedMember.Local")] + private static void AddLocation(ushort questId, GatheringRoot root) => _locations![questId] = root; +} diff --git a/GatheringPaths/GatheringPaths.csproj b/GatheringPaths/GatheringPaths.csproj new file mode 100644 index 00000000..8a5f3291 --- /dev/null +++ b/GatheringPaths/GatheringPaths.csproj @@ -0,0 +1,43 @@ + + + net8.0-windows + 12 + enable + Questionable.GatheringPaths + true + true + none + $(SolutionDir)=X:\ + true + x64 + + + + + + + + + + + Questionable.GatheringPaths.GatheringLocationSchema + + + + + + + + + + + + + + + + + + + + diff --git a/GatheringPaths/gatheringlocation-v1.json b/GatheringPaths/gatheringlocation-v1.json new file mode 100644 index 00000000..5c71b876 --- /dev/null +++ b/GatheringPaths/gatheringlocation-v1.json @@ -0,0 +1,120 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://git.carvel.li/liza/Questionable/raw/branch/master/GatheringPaths/gatheringlocation-v1.json", + "title": "Gathering Location V1", + "description": "A series of gathering locationsk", + "type": "object", + "properties": { + "$schema": { + "type": "string", + "const": "https://git.carvel.li/liza/Questionable/raw/branch/master/GatheringPaths/gatheringlocation-v1.json" + }, + "Author": { + "description": "Author of the gathering location data", + "type": [ + "string", + "array" + ], + "items": { + "type": "string" + } + }, + "TerritoryId": { + "type": "number" + }, + "AetheryteShortcut": { + "$ref": "https://git.carvel.li/liza/Questionable/raw/branch/master/Questionable.Model/common-schema.json#/$defs/Aetheryte" + }, + "Groups": { + "type": "array", + "items": { + "type": "object", + "properties": { + "Nodes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "DataId": { + "type": "number", + "minimum": 30000, + "maximum": 50000 + }, + "Locations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "Position": { + "$ref": "#/$defs/Vector3" + }, + "MinimumAngle": { + "type": "number", + "minimum": -360, + "maximum": 360 + }, + "MaximumAngle": { + "type": "number", + "minimum": -360, + "maximum": 360 + }, + "MinimumDistance": { + "type": "number", + "minimum": 0 + }, + "MaximumDistance": { + "type": "number", + "exclusiveMinimum": 0 + } + }, + "required": [ + "Position" + ], + "additionalProperties": false + } + } + }, + "required": [ + "DataId" + ], + "additionalProperties": false + } + } + }, + "required": [ + "Nodes" + ], + "additionalProperties": false + } + } + }, + "required": [ + "$schema", + "Author", + "TerritoryId", + "Groups" + ], + "additionalProperties": false, + "$defs": { + "Vector3": { + "type": "object", + "description": "Position to (typically) walk to", + "properties": { + "X": { + "type": "number" + }, + "Y": { + "type": "number" + }, + "Z": { + "type": "number" + } + }, + "required": [ + "X", + "Y", + "Z" + ] + } + } +} diff --git a/GatheringPaths/packages.lock.json b/GatheringPaths/packages.lock.json new file mode 100644 index 00000000..408e267a --- /dev/null +++ b/GatheringPaths/packages.lock.json @@ -0,0 +1,26 @@ +{ + "version": 1, + "dependencies": { + "net8.0-windows7.0": { + "System.Text.Encodings.Web": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "yev/k9GHAEGx2Rg3/tU6MQh4HGBXJs70y7j1LaM1i/ER9po+6nnQ6RRqTJn1E7Xu0fbIFK80Nh5EoODxrbxwBQ==" + }, + "System.Text.Json": { + "type": "Transitive", + "resolved": "8.0.4", + "contentHash": "bAkhgDJ88XTsqczoxEMliSrpijKZHhbJQldhAmObj/RbrN3sU5dcokuXmWJWsdQAhiMJ9bTayWsL1C9fbbCRhw==", + "dependencies": { + "System.Text.Encodings.Web": "8.0.0" + } + }, + "questionable.model": { + "type": "Project", + "dependencies": { + "System.Text.Json": "[8.0.4, )" + } + } + } + } +} \ No newline at end of file diff --git a/QuestPathGenerator.Tests/QuestGeneratorTest.cs b/QuestPathGenerator.Tests/QuestGeneratorTest.cs index 55d4cd64..f08606c1 100644 --- a/QuestPathGenerator.Tests/QuestGeneratorTest.cs +++ b/QuestPathGenerator.Tests/QuestGeneratorTest.cs @@ -1,4 +1,4 @@ -using Questionable.Model.V1; +using Questionable.Model.Questing; using Questionable.QuestPathGenerator; using Xunit; diff --git a/QuestPathGenerator/GatheringSourceGenerator.cs b/QuestPathGenerator/GatheringSourceGenerator.cs new file mode 100644 index 00000000..4995c3e4 --- /dev/null +++ b/QuestPathGenerator/GatheringSourceGenerator.cs @@ -0,0 +1,163 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using System.Text.Json; +using Json.Schema; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Questionable.Model.Gathering; +using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; +using static Questionable.QuestPathGenerator.RoslynShortcuts; + +namespace Questionable.QuestPathGenerator; + +[Generator] +[SuppressMessage("MicrosoftCodeAnalysisReleaseTracking", "RS2008")] +public class GatheringSourceGenerator : ISourceGenerator +{ + private static readonly DiagnosticDescriptor InvalidJson = new("GPG0001", + "Invalid JSON", + "Invalid gathering file: {0}", + nameof(GatheringSourceGenerator), + DiagnosticSeverity.Error, + true); + + public void Initialize(GeneratorInitializationContext context) + { + // No initialization required for this generator. + } + + public void Execute(GeneratorExecutionContext context) + { + // Find schema definition + AdditionalText? gatheringSchema = + context.AdditionalFiles.SingleOrDefault(x => Path.GetFileName(x.Path) == "gatheringlocation-v1.json"); + if (gatheringSchema != null) + GenerateGatheringSource(context, gatheringSchema); + } + + private void GenerateGatheringSource(GeneratorExecutionContext context, AdditionalText jsonSchemaFile) + { + var gatheringSchema = JsonSchema.FromText(jsonSchemaFile.GetText()!.ToString()); + + List<(ushort, GatheringRoot)> gatheringLocations = []; + foreach (var (id, node) in Utils.GetAdditionalFiles(context, jsonSchemaFile, gatheringSchema, InvalidJson)) + { + var gatheringLocation = node.Deserialize()!; + gatheringLocations.Add((id, gatheringLocation)); + } + + if (gatheringLocations.Count == 0) + return; + + var partitionedLocations = gatheringLocations + .OrderBy(x => x.Item1) + .GroupBy(x => $"LoadLocation{x.Item1 / 100}") + .ToList(); + + var methods = Utils.CreateMethods("LoadLocations", partitionedLocations, CreateInitializer); + + var code = + CompilationUnit() + .WithUsings( + List( + new[] + { + UsingDirective( + IdentifierName("System")), + UsingDirective( + QualifiedName( + IdentifierName("System"), + IdentifierName("Numerics"))), + UsingDirective( + QualifiedName( + IdentifierName("System"), + IdentifierName("IO"))), + UsingDirective( + QualifiedName( + QualifiedName( + IdentifierName("System"), IdentifierName("Collections")), + IdentifierName("Generic"))), + UsingDirective( + QualifiedName( + QualifiedName( + IdentifierName("Questionable"), + IdentifierName("Model")), + IdentifierName("Gathering"))), + UsingDirective( + QualifiedName( + QualifiedName( + IdentifierName("Questionable"), + IdentifierName("Model")), + IdentifierName("Common"))) + })) + .WithMembers( + SingletonList( + FileScopedNamespaceDeclaration( + QualifiedName( + IdentifierName("Questionable"), + IdentifierName("GatheringPaths"))) + .WithMembers( + SingletonList( + ClassDeclaration("AssemblyGatheringLocationLoader") + .WithModifiers( + TokenList(Token(SyntaxKind.PartialKeyword))) + .WithMembers(List(methods)))))) + .NormalizeWhitespace(); + + // Add the source code to the compilation. + context.AddSource("AssemblyGatheringLocationLoader.g.cs", code.ToFullString()); + } + + private static StatementSyntax[] CreateInitializer(List<(ushort QuestId, GatheringRoot Root)> quests) + { + List statements = []; + + foreach (var quest in quests) + { + statements.Add( + ExpressionStatement( + InvocationExpression( + IdentifierName("AddLocation")) + .WithArgumentList( + ArgumentList( + SeparatedList( + new SyntaxNodeOrToken[] + { + Argument( + LiteralExpression(SyntaxKind.NumericLiteralExpression, + Literal(quest.QuestId))), + Token(SyntaxKind.CommaToken), + Argument(CreateGatheringRootExpression(quest.QuestId, quest.Root)) + }))))); + } + + return statements.ToArray(); + } + + private static ObjectCreationExpressionSyntax CreateGatheringRootExpression(ushort locationId, GatheringRoot root) + { + try + { + return ObjectCreationExpression( + IdentifierName(nameof(GatheringRoot))) + .WithInitializer( + InitializerExpression( + SyntaxKind.ObjectInitializerExpression, + SeparatedList( + SyntaxNodeList( + AssignmentList(nameof(GatheringRoot.Author), root.Author).AsSyntaxNodeOrToken(), + Assignment(nameof(GatheringRoot.TerritoryId), root.TerritoryId, default) + .AsSyntaxNodeOrToken(), + Assignment(nameof(GatheringRoot.AetheryteShortcut), root.AetheryteShortcut, null), + AssignmentList(nameof(GatheringRoot.Groups), root.Groups).AsSyntaxNodeOrToken())))); + } + catch (Exception e) + { + throw new Exception($"GatheringGen[{locationId}]: {e.Message}", e); + } + } +} diff --git a/QuestPathGenerator/QuestSourceGenerator.cs b/QuestPathGenerator/QuestSourceGenerator.cs index 47d311f2..b6cccbab 100644 --- a/QuestPathGenerator/QuestSourceGenerator.cs +++ b/QuestPathGenerator/QuestSourceGenerator.cs @@ -1,16 +1,14 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Globalization; using System.IO; using System.Linq; using System.Text.Json; -using System.Text.Json.Nodes; using Json.Schema; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Questionable.Model.V1; +using Questionable.Model.Questing; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; using static Questionable.QuestPathGenerator.RoslynShortcuts; @@ -38,47 +36,21 @@ public class QuestSourceGenerator : ISourceGenerator public void Execute(GeneratorExecutionContext context) { - List<(ushort, QuestRoot)> quests = []; - // Find schema definition - AdditionalText jsonSchemaFile = - context.AdditionalFiles.Single(x => Path.GetFileName(x.Path) == "quest-v1.json"); + AdditionalText? questSchema = + context.AdditionalFiles.SingleOrDefault(x => Path.GetFileName(x.Path) == "quest-v1.json"); + if (questSchema != null) + GenerateQuestSource(context, questSchema); + } + + private void GenerateQuestSource(GeneratorExecutionContext context, AdditionalText jsonSchemaFile) + { var questSchema = JsonSchema.FromText(jsonSchemaFile.GetText()!.ToString()); - // Go through all files marked as an Additional File in file properties. - foreach (var additionalFile in context.AdditionalFiles) + List<(ushort, QuestRoot)> quests = []; + foreach (var (id, node) in Utils.GetAdditionalFiles(context, jsonSchemaFile, questSchema, InvalidJson)) { - if (additionalFile == null || additionalFile == jsonSchemaFile) - continue; - - if (Path.GetExtension(additionalFile.Path) != ".json") - continue; - - string name = Path.GetFileName(additionalFile.Path); - if (!name.Contains('_')) - continue; - - ushort id = ushort.Parse(name.Substring(0, name.IndexOf('_'))); - - var text = additionalFile.GetText(); - if (text == null) - continue; - - var questNode = JsonNode.Parse(text.ToString()); - var evaluationResult = questSchema.Evaluate(questNode, new EvaluationOptions - { - Culture = CultureInfo.InvariantCulture, - OutputFormat = OutputFormat.List - }); - if (!evaluationResult.IsValid) - { - var error = Diagnostic.Create(InvalidJson, - null, - Path.GetFileName(additionalFile.Path)); - context.ReportDiagnostic(error); - } - - var quest = questNode.Deserialize()!; + var quest = node.Deserialize()!; if (quest.Disabled) { quest.Author = []; @@ -97,38 +69,7 @@ public class QuestSourceGenerator : ISourceGenerator .GroupBy(x => $"LoadQuests{x.Item1 / 50}") .ToList(); - List methods = - [ - MethodDeclaration( - PredefinedType( - Token(SyntaxKind.VoidKeyword)), - Identifier("LoadQuests")) - .WithModifiers( - TokenList( - Token(SyntaxKind.PrivateKeyword), - Token(SyntaxKind.StaticKeyword))) - .WithBody( - Block( - partitionedQuests - .Select(x => - ExpressionStatement( - InvocationExpression( - IdentifierName(x.Key)))))) - ]; - - foreach (var partition in partitionedQuests) - { - methods.Add(MethodDeclaration( - PredefinedType( - Token(SyntaxKind.VoidKeyword)), - Identifier(partition.Key)) - .WithModifiers( - TokenList( - Token(SyntaxKind.PrivateKeyword), - Token(SyntaxKind.StaticKeyword))) - .WithBody( - Block(CreateInitializer(partition.ToList())))); - } + var methods = Utils.CreateMethods("LoadQuests", partitionedQuests, CreateInitializer); var code = CompilationUnit() @@ -156,7 +97,13 @@ public class QuestSourceGenerator : ISourceGenerator QualifiedName( IdentifierName("Questionable"), IdentifierName("Model")), - IdentifierName("V1"))) + IdentifierName("Questing"))), + UsingDirective( + QualifiedName( + QualifiedName( + IdentifierName("Questionable"), + IdentifierName("Model")), + IdentifierName("Common"))) })) .WithMembers( SingletonList( diff --git a/QuestPathGenerator/RoslynShortcuts.cs b/QuestPathGenerator/RoslynShortcuts.cs index 63ccdcd6..07b82912 100644 --- a/QuestPathGenerator/RoslynShortcuts.cs +++ b/QuestPathGenerator/RoslynShortcuts.cs @@ -6,7 +6,9 @@ using System.Numerics; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Questionable.Model.V1; +using Questionable.Model.Common; +using Questionable.Model.Gathering; +using Questionable.Model.Questing; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; namespace Questionable.QuestPathGenerator; @@ -213,7 +215,9 @@ public static class RoslynShortcuts { Argument(LiteralValue(qwv.High)), Token(SyntaxKind.CommaToken), - Argument(LiteralValue(qwv.Low)) + Argument(LiteralValue(qwv.Low)), + Token(SyntaxKind.CommaToken), + Argument(LiteralValue(qwv.Mode)) }))); } else if (value is List list) @@ -255,6 +259,9 @@ public static class RoslynShortcuts Assignment(nameof(SkipStepConditions.Never), skipStepConditions.Never, emptyStep.Never) .AsSyntaxNodeOrToken(), + AssignmentList(nameof(SkipStepConditions.CompletionQuestVariablesFlags), + skipStepConditions.CompletionQuestVariablesFlags) + .AsSyntaxNodeOrToken(), Assignment(nameof(SkipStepConditions.Flying), skipStepConditions.Flying, emptyStep.Flying) .AsSyntaxNodeOrToken(), @@ -307,6 +314,57 @@ public static class RoslynShortcuts Assignment(nameof(SkipAetheryteCondition.InSameTerritory), skipAetheryteCondition.InSameTerritory, emptyAetheryte.InSameTerritory))))); } + else if (value is GatheringNodeGroup nodeGroup) + { + return ObjectCreationExpression( + IdentifierName(nameof(GatheringNodeGroup))) + .WithInitializer( + InitializerExpression( + SyntaxKind.ObjectInitializerExpression, + SeparatedList( + SyntaxNodeList( + AssignmentList(nameof(GatheringNodeGroup.Nodes), nodeGroup.Nodes) + .AsSyntaxNodeOrToken())))); + } + else if (value is GatheringNode nodeLocation) + { + var emptyLocation = new GatheringNode(); + return ObjectCreationExpression( + IdentifierName(nameof(GatheringNode))) + .WithInitializer( + InitializerExpression( + SyntaxKind.ObjectInitializerExpression, + SeparatedList( + SyntaxNodeList( + Assignment(nameof(GatheringNode.DataId), nodeLocation.DataId, + emptyLocation.DataId) + .AsSyntaxNodeOrToken(), + AssignmentList(nameof(GatheringNode.Locations), nodeLocation.Locations) + .AsSyntaxNodeOrToken())))); + } + else if (value is GatheringLocation location) + { + var emptyLocation = new GatheringLocation(); + return ObjectCreationExpression( + IdentifierName(nameof(GatheringLocation))) + .WithInitializer( + InitializerExpression( + SyntaxKind.ObjectInitializerExpression, + SeparatedList( + SyntaxNodeList( + Assignment(nameof(GatheringLocation.Position), location.Position, + emptyLocation.Position).AsSyntaxNodeOrToken(), + Assignment(nameof(GatheringLocation.MinimumAngle), location.MinimumAngle, + emptyLocation.MinimumAngle).AsSyntaxNodeOrToken(), + Assignment(nameof(GatheringLocation.MaximumAngle), location.MaximumAngle, + emptyLocation.MaximumAngle).AsSyntaxNodeOrToken(), + Assignment(nameof(GatheringLocation.MinimumDistance), + location.MinimumDistance, emptyLocation.MinimumDistance) + .AsSyntaxNodeOrToken(), + Assignment(nameof(GatheringLocation.MaximumDistance), + location.MaximumDistance, emptyLocation.MaximumDistance) + .AsSyntaxNodeOrToken())))); + } else if (value is null) return LiteralExpression(SyntaxKind.NullLiteralExpression); } diff --git a/QuestPathGenerator/Utils.cs b/QuestPathGenerator/Utils.cs new file mode 100644 index 00000000..5e1baac0 --- /dev/null +++ b/QuestPathGenerator/Utils.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text.Json.Nodes; +using Json.Schema; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; + +namespace Questionable.QuestPathGenerator; + +public static class Utils +{ + public static IEnumerable<(ushort, JsonNode)> GetAdditionalFiles(GeneratorExecutionContext context, + AdditionalText jsonSchemaFile, JsonSchema jsonSchema, DiagnosticDescriptor invalidJson) + { + var commonSchemaFile = context.AdditionalFiles.Single(x => Path.GetFileName(x.Path) == "common-schema.json"); + List jsonSchemaFiles = [jsonSchemaFile, commonSchemaFile]; + + SchemaRegistry.Global.Register( + new Uri("https://git.carvel.li/liza/Questionable/raw/branch/master/Questionable.Model/common-schema.json"), + JsonSchema.FromText(commonSchemaFile.GetText()!.ToString())); + + foreach (var additionalFile in context.AdditionalFiles) + { + if (additionalFile == null || jsonSchemaFiles.Contains(additionalFile)) + continue; + + if (Path.GetExtension(additionalFile.Path) != ".json") + continue; + + string name = Path.GetFileName(additionalFile.Path); + if (!name.Contains("_")) + continue; + + ushort id = ushort.Parse(name.Substring(0, name.IndexOf('_'))); + + var text = additionalFile.GetText(); + if (text == null) + continue; + + var node = JsonNode.Parse(text.ToString()); + if (node == null) + continue; + + string? schemaLocation = node["$schema"]?.GetValue(); + if (schemaLocation == null || new Uri(schemaLocation) != jsonSchema.GetId()) + continue; + + var evaluationResult = jsonSchema.Evaluate(node, new EvaluationOptions + { + Culture = CultureInfo.InvariantCulture, + OutputFormat = OutputFormat.List, + }); + if (evaluationResult.HasErrors) + { + var error = Diagnostic.Create(invalidJson, + null, + Path.GetFileName(additionalFile.Path)); + context.ReportDiagnostic(error); + continue; + } + + yield return (id, node); + } + } + + public static List CreateMethods(string prefix, + List> partitions, + Func, StatementSyntax[]> toInitializers) + { + List methods = + [ + MethodDeclaration( + PredefinedType( + Token(SyntaxKind.VoidKeyword)), + Identifier(prefix)) + .WithModifiers( + TokenList( + Token(SyntaxKind.PrivateKeyword), + Token(SyntaxKind.StaticKeyword))) + .WithBody( + Block( + partitions + .Select(x => + ExpressionStatement( + InvocationExpression( + IdentifierName(x.Key)))))) + ]; + + foreach (var partition in partitions) + { + methods.Add(MethodDeclaration( + PredefinedType( + Token(SyntaxKind.VoidKeyword)), + Identifier(partition.Key)) + .WithModifiers( + TokenList( + Token(SyntaxKind.PrivateKeyword), + Token(SyntaxKind.StaticKeyword))) + .WithBody( + Block(toInitializers(partition.ToList())))); + } + + return methods; + } +} diff --git a/QuestPaths/2.x - A Realm Reborn/Class Quests/GLA/253_Way of the Gladiator.json b/QuestPaths/2.x - A Realm Reborn/Class Quests/GLA/253_Way of the Gladiator.json index 790d5671..09113bd9 100644 --- a/QuestPaths/2.x - A Realm Reborn/Class Quests/GLA/253_Way of the Gladiator.json +++ b/QuestPaths/2.x - A Realm Reborn/Class Quests/GLA/253_Way of the Gladiator.json @@ -1,7 +1,6 @@ { "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json", "Author": "liza", - "Disabled": true, "QuestSequence": [ { "Sequence": 0, @@ -24,6 +23,183 @@ ] } ] + }, + { + "Sequence": 1, + "Steps": [ + { + "DataId": 1001739, + "Position": { + "X": -94.529724, + "Y": 6.4999976, + "Z": 39.81079 + }, + "TerritoryId": 131, + "InteractionType": "Interact", + "DialogueChoices": [ + { + "Type": "YesNo", + "Prompt": "TEXT_CLSGLA020_00253_Q2_000_1", + "Yes": true + } + ] + } + ] + }, + { + "Sequence": 2, + "Steps": [ + { + "Position": { + "X": 45.13088, + "Y": 3.889354, + "Z": -166.51999 + }, + "TerritoryId": 130, + "InteractionType": "WalkTo", + "AethernetShortcut": [ + "[Ul'dah] Gladiators' Guild", + "[Ul'dah] Adventurers' Guild" + ], + "SkipConditions": { + "StepIf": { + "InTerritory": [ + 141 + ] + } + } + }, + { + "Position": { + "X": -116.10664, + "Y": 10.801613, + "Z": 276.979 + }, + "TerritoryId": 141, + "InteractionType": "Combat", + "EnemySpawnType": "OverworldEnemies", + "ComplexCombatData": [ + { + "DataId": 351, + "MinimumKillCount": 3, + "CompletionQuestVariablesFlags": [ + { + "Low": 3, + "Mode": "Exact" + }, + null, + null, + null, + null, + null + ] + }, + { + "DataId": 141, + "MinimumKillCount": 3, + "CompletionQuestVariablesFlags": [ + null, + { + "High": 3, + "Mode": "Exact" + }, + null, + null, + null, + null + ] + } + ], + "CompletionQuestVariablesFlags": [ + { + "Low": 3, + "Mode": "Exact" + }, + { + "High": 3, + "Mode": "Exact" + }, + null, + null, + null, + null + ] + }, + { + "Position": { + "X": 39.635372, + "Y": 3.2401803, + "Z": 273.41232 + }, + "TerritoryId": 141, + "InteractionType": "Combat", + "EnemySpawnType": "OverworldEnemies", + "ComplexCombatData": [ + { + "DataId": 205, + "MinimumKillCount": 3, + "CompletionQuestVariablesFlags": [ + null, + { + "Low": 3, + "Mode": "Exact" + }, + null, + null, + null, + null + ] + } + ], + "CompletionQuestVariablesFlags": [ + null, + { + "Low": 3, + "Mode": "Exact" + }, + null, + null, + null, + null + ] + } + ] + }, + { + "Sequence": 255, + "Steps": [ + { + "Position": { + "X": -118.17538, + "Y": 18.35357, + "Z": 341.3039 + }, + "TerritoryId": 141, + "InteractionType": "WalkTo", + "SkipConditions": { + "StepIf": { + "NotInTerritory": [ + 141 + ] + } + } + }, + { + "DataId": 1001739, + "Position": { + "X": -94.529724, + "Y": 6.4999976, + "Z": 39.81079 + }, + "TerritoryId": 131, + "InteractionType": "CompleteQuest", + "AethernetShortcut": [ + "[Ul'dah] Adventurers' Guild", + "[Ul'dah] Gladiators' Guild" + ], + "NextQuestId": 256 + } + ] } ] } diff --git a/QuestPaths/2.x - A Realm Reborn/Class Quests/GLA/256_Kicking the Hornet's Nest.json b/QuestPaths/2.x - A Realm Reborn/Class Quests/GLA/256_Kicking the Hornet's Nest.json new file mode 100644 index 00000000..88320c09 --- /dev/null +++ b/QuestPaths/2.x - A Realm Reborn/Class Quests/GLA/256_Kicking the Hornet's Nest.json @@ -0,0 +1,286 @@ +{ + "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json", + "Author": "liza", + "QuestSequence": [ + { + "Sequence": 0, + "Steps": [ + { + "DataId": 1001739, + "Position": { + "X": -94.529724, + "Y": 6.4999976, + "Z": 39.81079 + }, + "TerritoryId": 131, + "InteractionType": "AcceptQuest" + } + ] + }, + { + "Sequence": 1, + "Steps": [ + { + "DataId": 1003990, + "Position": { + "X": 77.25635, + "Y": 4.0999947, + "Z": -138.62823 + }, + "TerritoryId": 130, + "InteractionType": "Emote", + "Emote": "me", + "AethernetShortcut": [ + "[Ul'dah] Gladiators' Guild", + "[Ul'dah] Adventurers' Guild" + ], + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 64 + ] + }, + { + "DataId": 1003984, + "Position": { + "X": 45.029297, + "Y": 3.9999998, + "Z": -128.16058 + }, + "TerritoryId": 130, + "InteractionType": "Emote", + "Emote": "me", + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + }, + { + "DataId": 1003992, + "Position": { + "X": 12.191956, + "Y": 4.0999947, + "Z": -155.53528 + }, + "TerritoryId": 130, + "InteractionType": "Emote", + "Emote": "me", + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 32 + ] + }, + { + "DataId": 1003994, + "Position": { + "X": 15.976135, + "Y": 7.9999995, + "Z": -124.071106 + }, + "TerritoryId": 130, + "InteractionType": "Emote", + "Emote": "me", + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 16 + ] + } + ] + }, + { + "Sequence": 2, + "Steps": [ + { + "DataId": 1004222, + "Position": { + "X": 21.927185, + "Y": 7.1999974, + "Z": -97.39838 + }, + "TerritoryId": 130, + "InteractionType": "Emote", + "Emote": "me" + } + ] + }, + { + "Sequence": 3, + "Steps": [ + { + "DataId": 1001353, + "Position": { + "X": 21.072632, + "Y": 7.45, + "Z": -78.78235 + }, + "TerritoryId": 130, + "InteractionType": "Interact" + } + ] + }, + { + "Sequence": 4, + "Steps": [ + { + "DataId": 1001739, + "Position": { + "X": -94.529724, + "Y": 6.4999976, + "Z": 39.81079 + }, + "TerritoryId": 131, + "InteractionType": "Interact", + "AethernetShortcut": [ + "[Ul'dah] Adventurers' Guild", + "[Ul'dah] Gladiators' Guild" + ] + } + ] + }, + { + "Sequence": 5, + "Steps": [ + { + "Position": { + "X": -112.70276, + "Y": 7.7544775, + "Z": 9.123527 + }, + "TerritoryId": 131, + "InteractionType": "WalkTo" + }, + { + "Position": { + "X": -183.00035, + "Y": 13.958975, + "Z": -13.998203 + }, + "TerritoryId": 130, + "InteractionType": "WalkTo" + }, + { + "DataId": 1003985, + "Position": { + "X": 201.52588, + "Y": 52.038116, + "Z": 149.40112 + }, + "TerritoryId": 140, + "InteractionType": "Interact" + } + ] + }, + { + "Sequence": 6, + "Steps": [ + { + "DataId": 2001408, + "Position": { + "X": 154.37549, + "Y": 52.536743, + "Z": 200.91553 + }, + "TerritoryId": 140, + "InteractionType": "Combat", + "EnemySpawnType": "AfterInteraction", + "KillEnemyDataIds": [ + 1246 + ], + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + }, + { + "DataId": 2001409, + "Position": { + "X": 141.83252, + "Y": 52.994507, + "Z": 221.54565 + }, + "TerritoryId": 140, + "InteractionType": "Combat", + "EnemySpawnType": "AfterInteraction", + "KillEnemyDataIds": [ + 1390 + ], + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 64 + ] + } + ] + }, + { + "Sequence": 7, + "Steps": [ + { + "DataId": 1003985, + "Position": { + "X": 201.52588, + "Y": 52.038116, + "Z": 149.40112 + }, + "TerritoryId": 140, + "InteractionType": "Interact" + } + ] + }, + { + "Sequence": 255, + "Steps": [ + { + "Position": { + "X": 473.74796, + "Y": 96.62057, + "Z": 159.993 + }, + "TerritoryId": 140, + "InteractionType": "WalkTo" + }, + { + "Position": { + "X": -117.06801, + "Y": 9.195247, + "Z": 9.181297 + }, + "TerritoryId": 130, + "InteractionType": "WalkTo" + }, + { + "DataId": 1001739, + "Position": { + "X": -94.529724, + "Y": 6.4999976, + "Z": 39.81079 + }, + "TerritoryId": 131, + "InteractionType": "CompleteQuest" + } + ] + } + ] +} diff --git a/QuestPaths/2.x - A Realm Reborn/Class Quests/SCH/1098_The Last Remnants.json b/QuestPaths/2.x - A Realm Reborn/Class Quests/SCH/1098_The Last Remnants.json index e2b2a77f..66e6af53 100644 --- a/QuestPaths/2.x - A Realm Reborn/Class Quests/SCH/1098_The Last Remnants.json +++ b/QuestPaths/2.x - A Realm Reborn/Class Quests/SCH/1098_The Last Remnants.json @@ -37,16 +37,18 @@ "SkipConditions": { "AetheryteShortcutIf": { "InSameTerritory": true + }, + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 64 + ] } - }, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -64 - ] + } }, { "DataId": 2002380, @@ -76,14 +78,18 @@ "TerritoryId": 180, "InteractionType": "WalkTo", "Fly": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -128 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + } + } }, { "DataId": 2002379, @@ -112,14 +118,18 @@ "TerritoryId": 180, "InteractionType": "WalkTo", "Fly": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -16 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 16 + ] + } + } }, { "DataId": 2002382, @@ -148,14 +158,18 @@ "TerritoryId": 180, "InteractionType": "WalkTo", "Fly": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -32 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 32 + ] + } + } }, { "DataId": 2002381, diff --git a/QuestPaths/2.x - A Realm Reborn/Class Quests/SCH/1101_For Your Fellow Man.json b/QuestPaths/2.x - A Realm Reborn/Class Quests/SCH/1101_For Your Fellow Man.json index b52d04ba..a2b64690 100644 --- a/QuestPaths/2.x - A Realm Reborn/Class Quests/SCH/1101_For Your Fellow Man.json +++ b/QuestPaths/2.x - A Realm Reborn/Class Quests/SCH/1101_For Your Fellow Man.json @@ -65,14 +65,18 @@ "TerritoryId": 139, "InteractionType": "WalkTo", "Mount": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -128 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + } + } }, { "DataId": 1007844, @@ -120,16 +124,16 @@ }, "TerritoryId": 139, "InteractionType": "WalkTo", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -32 - ], "SkipConditions": { "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 32 + ], "Flying": "Unlocked" } } diff --git a/QuestPaths/2.x - A Realm Reborn/Class Quests/WAR/1052_Looking the Part.json b/QuestPaths/2.x - A Realm Reborn/Class Quests/WAR/1052_Looking the Part.json index f75098fa..c87c93ab 100644 --- a/QuestPaths/2.x - A Realm Reborn/Class Quests/WAR/1052_Looking the Part.json +++ b/QuestPaths/2.x - A Realm Reborn/Class Quests/WAR/1052_Looking the Part.json @@ -28,18 +28,20 @@ }, "TerritoryId": 155, "InteractionType": "WalkTo", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -128 - ], "AetheryteShortcut": "Coerthas Central Highlands - Camp Dragonhead", "SkipConditions": { "AetheryteShortcutIf": { "InSameTerritory": true + }, + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] } }, "Fly": true @@ -56,14 +58,18 @@ "KillEnemyDataIds": [ 1918 ], - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -128 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + } + } }, { "DataId": 2002308, @@ -127,14 +133,18 @@ "InteractionType": "UseItem", "ItemId": 30362, "TargetTerritoryId": 140, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -32 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 32 + ] + } + } }, { "Position": { @@ -148,14 +158,18 @@ "KillEnemyDataIds": [ 1920 ], - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -32 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 32 + ] + } + } }, { "DataId": 2002310, diff --git a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Gridania/377_Don't Look Down.json b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Gridania/377_Don't Look Down.json index d4d4a3eb..e3a5ea4f 100644 --- a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Gridania/377_Don't Look Down.json +++ b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Gridania/377_Don't Look Down.json @@ -65,14 +65,18 @@ }, "TerritoryId": 148, "InteractionType": "WalkTo", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -128 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + } + } }, { "Position": { @@ -83,14 +87,18 @@ "TerritoryId": 148, "InteractionType": "WalkTo", "DisableNavmesh": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -128 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + } + } }, { "DataId": 2000748, @@ -120,14 +128,18 @@ "TerritoryId": 148, "InteractionType": "WalkTo", "DisableNavmesh": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -16 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 16 + ] + } + } }, { "Position": { @@ -137,14 +149,18 @@ }, "TerritoryId": 148, "InteractionType": "WalkTo", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -16 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 16 + ] + } + } }, { "DataId": 2000751, diff --git a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Limsa/465_Washed Up.json b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Limsa/465_Washed Up.json index 51da268c..8a9700f8 100644 --- a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Limsa/465_Washed Up.json +++ b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Limsa/465_Washed Up.json @@ -36,14 +36,18 @@ }, "StopDistance": 3 }, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -8 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 8 + ] + } + } }, { "DataId": 1002639, diff --git a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Shared/574_Dressed to Deceive.json b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Shared/574_Dressed to Deceive.json index 982e2df4..75423c9d 100644 --- a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Shared/574_Dressed to Deceive.json +++ b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Shared/574_Dressed to Deceive.json @@ -64,14 +64,18 @@ }, "StopDistance": 5 }, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -16 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 16 + ] + } + } }, { "DataId": 1004506, diff --git a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Ul'dah/671_Nothing to See Here.json b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Ul'dah/671_Nothing to See Here.json index 10b55d22..e9f7f5ad 100644 --- a/QuestPaths/2.x - A Realm Reborn/MSQ-1/Ul'dah/671_Nothing to See Here.json +++ b/QuestPaths/2.x - A Realm Reborn/MSQ-1/Ul'dah/671_Nothing to See Here.json @@ -65,14 +65,18 @@ "TerritoryId": 141, "InteractionType": "WalkTo", "DisableNavmesh": false, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -128 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + } + } }, { "DataId": 1004599, diff --git a/QuestPaths/2.x - A Realm Reborn/MSQ-2/A3-South Shroud, Buscarron’s Druthers/3861_Microbrewing.json b/QuestPaths/2.x - A Realm Reborn/MSQ-2/A3-South Shroud, Buscarron’s Druthers/3861_Microbrewing.json index c71fd8b1..1178aff5 100644 --- a/QuestPaths/2.x - A Realm Reborn/MSQ-2/A3-South Shroud, Buscarron’s Druthers/3861_Microbrewing.json +++ b/QuestPaths/2.x - A Realm Reborn/MSQ-2/A3-South Shroud, Buscarron’s Druthers/3861_Microbrewing.json @@ -29,6 +29,7 @@ "TerritoryId": 139, "InteractionType": "WalkTo", "DisableNavmesh": true, + "Mount": true, "Comment": "Avoids swimming" }, { diff --git a/QuestPaths/2.x - A Realm Reborn/MSQ-2/A3-South Shroud, Buscarron’s Druthers/738_Sylphish Concerns.json b/QuestPaths/2.x - A Realm Reborn/MSQ-2/A3-South Shroud, Buscarron’s Druthers/738_Sylphish Concerns.json index c35b2110..86d44f5d 100644 --- a/QuestPaths/2.x - A Realm Reborn/MSQ-2/A3-South Shroud, Buscarron’s Druthers/738_Sylphish Concerns.json +++ b/QuestPaths/2.x - A Realm Reborn/MSQ-2/A3-South Shroud, Buscarron’s Druthers/738_Sylphish Concerns.json @@ -63,14 +63,18 @@ }, "DelaySeconds": 0.25 }, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -8 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 8 + ] + } + } }, { "DataId": 2001953, @@ -92,6 +96,27 @@ 8 ] }, + { + "Position": { + "X": -103.87269, + "Y": 5.116502, + "Z": -73.9041 + }, + "TerritoryId": 153, + "InteractionType": "WalkTo", + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 32 + ] + } + } + }, { "Position": { "X": -90.467575, @@ -109,14 +134,18 @@ }, "DelaySeconds": 0.25 }, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -32 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 32 + ] + } + } }, { "DataId": 2001951, @@ -146,14 +175,18 @@ "TerritoryId": 153, "InteractionType": "WalkTo", "DisableNavmesh": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -16 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 16 + ] + } + } }, { "Position": { @@ -163,14 +196,18 @@ }, "TerritoryId": 153, "InteractionType": "WalkTo", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -16 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 16 + ] + } + } }, { "DataId": 2001952, diff --git a/QuestPaths/2.x - A Realm Reborn/MSQ-2/A4-Back from the Woods/747_Shadow of Darkness.json b/QuestPaths/2.x - A Realm Reborn/MSQ-2/A4-Back from the Woods/747_Shadow of Darkness.json index 6f3f239d..b5cbedd0 100644 --- a/QuestPaths/2.x - A Realm Reborn/MSQ-2/A4-Back from the Woods/747_Shadow of Darkness.json +++ b/QuestPaths/2.x - A Realm Reborn/MSQ-2/A4-Back from the Woods/747_Shadow of Darkness.json @@ -20,6 +20,17 @@ { "Sequence": 1, "Steps": [ + { + "Position": { + "X": -119.1183, + "Y": 3.7999938, + "Z": -104.33473 + }, + "TerritoryId": 130, + "InteractionType": "WalkTo", + "AetheryteShortcut": "Ul'dah", + "$": "Ul'dah Aetheryte to Immortal Flames" + }, { "DataId": 1004576, "Position": { @@ -28,8 +39,7 @@ "Z": -114.67157 }, "TerritoryId": 130, - "InteractionType": "Interact", - "AetheryteShortcut": "Ul'dah" + "InteractionType": "Interact" } ] }, diff --git a/QuestPaths/2.x - A Realm Reborn/MSQ-2/A5-Southern Thanalan, Little Ala Mhigo/761_Tea for Three.json b/QuestPaths/2.x - A Realm Reborn/MSQ-2/A5-Southern Thanalan, Little Ala Mhigo/761_Tea for Three.json index 215cbf4e..eba78647 100644 --- a/QuestPaths/2.x - A Realm Reborn/MSQ-2/A5-Southern Thanalan, Little Ala Mhigo/761_Tea for Three.json +++ b/QuestPaths/2.x - A Realm Reborn/MSQ-2/A5-Southern Thanalan, Little Ala Mhigo/761_Tea for Three.json @@ -26,6 +26,49 @@ { "Sequence": 1, "Steps": [ + { + "Position": { + "X": -179.69392, + "Y": 18.008331, + "Z": -279.60886 + }, + "TerritoryId": 146, + "InteractionType": "WalkTo", + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + } + } + }, + { + "Position": { + "X": -184.29613, + "Y": 3.5985415, + "Z": -246.7013 + }, + "TerritoryId": 146, + "InteractionType": "WalkTo", + "DisableNavmesh": true, + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + } + } + }, { "DataId": 1006702, "Position": { diff --git a/QuestPaths/2.x - A Realm Reborn/MSQ-2/A7-Southern Thanalan, Big Trouble in Little Ala Mhigo/778_Wilred Wants You.json b/QuestPaths/2.x - A Realm Reborn/MSQ-2/A7-Southern Thanalan, Big Trouble in Little Ala Mhigo/778_Wilred Wants You.json index 51abeee8..6d840e0f 100644 --- a/QuestPaths/2.x - A Realm Reborn/MSQ-2/A7-Southern Thanalan, Big Trouble in Little Ala Mhigo/778_Wilred Wants You.json +++ b/QuestPaths/2.x - A Realm Reborn/MSQ-2/A7-Southern Thanalan, Big Trouble in Little Ala Mhigo/778_Wilred Wants You.json @@ -12,6 +12,7 @@ "Y": 26.138475, "Z": -355.0622 }, + "StopDistance": 7, "TerritoryId": 146, "InteractionType": "AcceptQuest", "AetheryteShortcut": "Southern Thanalan - Little Ala Mhigo", @@ -53,6 +54,7 @@ "Y": 19.02249, "Z": -557.8546 }, + "StopDistance": 7, "TerritoryId": 146, "InteractionType": "Interact" } diff --git a/QuestPaths/2.x - A Realm Reborn/MSQ-2/B6-Coerthas Central Highlands, Camp Dragonhead/889_Three for Three.json b/QuestPaths/2.x - A Realm Reborn/MSQ-2/B6-Coerthas Central Highlands, Camp Dragonhead/889_Three for Three.json index eb2aae1f..3ff98b75 100644 --- a/QuestPaths/2.x - A Realm Reborn/MSQ-2/B6-Coerthas Central Highlands, Camp Dragonhead/889_Three for Three.json +++ b/QuestPaths/2.x - A Realm Reborn/MSQ-2/B6-Coerthas Central Highlands, Camp Dragonhead/889_Three for Three.json @@ -71,14 +71,18 @@ 725, 726 ], - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -128 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + } + } }, { "DataId": 2002261, diff --git a/QuestPaths/2.x - A Realm Reborn/MSQ-2/B7-Coerthas Central Highlands, Camp Dragonhead/897_The Talk of Coerthas.json b/QuestPaths/2.x - A Realm Reborn/MSQ-2/B7-Coerthas Central Highlands, Camp Dragonhead/897_The Talk of Coerthas.json index 24d368a5..fc0042f5 100644 --- a/QuestPaths/2.x - A Realm Reborn/MSQ-2/B7-Coerthas Central Highlands, Camp Dragonhead/897_The Talk of Coerthas.json +++ b/QuestPaths/2.x - A Realm Reborn/MSQ-2/B7-Coerthas Central Highlands, Camp Dragonhead/897_The Talk of Coerthas.json @@ -64,14 +64,18 @@ "InteractionType": "WalkTo", "$": "NW Skyfire Locks door (inside)", "DisableNavmesh": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -64 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 64 + ] + } + } }, { "DataId": 1006396, diff --git a/QuestPaths/2.x - A Realm Reborn/MSQ-2/C2-Western La Noscea, Isles of Umbra/960_It's Probably Not Pirates.json b/QuestPaths/2.x - A Realm Reborn/MSQ-2/C2-Western La Noscea, Isles of Umbra/960_It's Probably Not Pirates.json index c9e1dab9..0887b40b 100644 --- a/QuestPaths/2.x - A Realm Reborn/MSQ-2/C2-Western La Noscea, Isles of Umbra/960_It's Probably Not Pirates.json +++ b/QuestPaths/2.x - A Realm Reborn/MSQ-2/C2-Western La Noscea, Isles of Umbra/960_It's Probably Not Pirates.json @@ -34,14 +34,18 @@ }, "TerritoryId": 138, "InteractionType": "WalkTo", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -32 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 32 + ] + } + } }, { "Position": { @@ -52,14 +56,18 @@ "TerritoryId": 138, "InteractionType": "WalkTo", "DisableNavmesh": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -32 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 32 + ] + } + } }, { "DataId": 1006500, diff --git a/QuestPaths/2.x - A Realm Reborn/MSQ-2/C6-Mor Dhona/1001_Drowning Out the Voices.json b/QuestPaths/2.x - A Realm Reborn/MSQ-2/C6-Mor Dhona/1001_Drowning Out the Voices.json index 805400af..c9fc5c3a 100644 --- a/QuestPaths/2.x - A Realm Reborn/MSQ-2/C6-Mor Dhona/1001_Drowning Out the Voices.json +++ b/QuestPaths/2.x - A Realm Reborn/MSQ-2/C6-Mor Dhona/1001_Drowning Out the Voices.json @@ -94,14 +94,18 @@ "TerritoryId": 156, "InteractionType": "WalkTo", "Comment": "Avoids pit", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -32 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 32 + ] + } + } }, { "Position": { @@ -112,14 +116,18 @@ "TerritoryId": 156, "InteractionType": "WalkTo", "Comment": "Move into LOS", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -32 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 32 + ] + } + } }, { "DataId": 2002230, diff --git a/QuestPaths/2.x - A Realm Reborn/MSQ-2/E3-2.3/1460_Brave New Companions.json b/QuestPaths/2.x - A Realm Reborn/MSQ-2/E3-2.3/1460_Brave New Companions.json index 4ce90314..d2020977 100644 --- a/QuestPaths/2.x - A Realm Reborn/MSQ-2/E3-2.3/1460_Brave New Companions.json +++ b/QuestPaths/2.x - A Realm Reborn/MSQ-2/E3-2.3/1460_Brave New Companions.json @@ -68,14 +68,18 @@ "TerritoryId": 351, "InteractionType": "Interact", "TargetTerritoryId": 156, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -2 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 2 + ] + } + } }, { "DataId": 1009147, diff --git a/QuestPaths/2.x - A Realm Reborn/Raid Quests/1709_Legacy of Allag.json b/QuestPaths/2.x - A Realm Reborn/Raid Quests/1709_Legacy of Allag.json index e2ab15d0..687fc403 100644 --- a/QuestPaths/2.x - A Realm Reborn/Raid Quests/1709_Legacy of Allag.json +++ b/QuestPaths/2.x - A Realm Reborn/Raid Quests/1709_Legacy of Allag.json @@ -76,14 +76,18 @@ "InteractionType": "WalkTo", "Fly": true, "AetheryteShortcut": "Upper La Noscea - Camp Bronze Lake", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -128 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + } + } }, { "Position": { @@ -94,14 +98,18 @@ "TerritoryId": 139, "InteractionType": "WalkTo", "TargetTerritoryId": 180, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -128 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + } + } }, { "Position": { @@ -112,27 +120,35 @@ "TerritoryId": 180, "InteractionType": "WalkTo", "Fly": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -128 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + } + } }, { "TerritoryId": 180, "InteractionType": "AttuneAetheryte", "Aetheryte": "Outer La Noscea - Camp Overlook", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -128 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + } + } }, { "Position": { @@ -143,14 +159,18 @@ "TerritoryId": 180, "InteractionType": "WalkTo", "Fly": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -128 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + } + } }, { "Position": { @@ -161,14 +181,18 @@ "TerritoryId": 180, "InteractionType": "WalkTo", "Fly": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -128 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + } + } }, { "Position": { @@ -179,14 +203,18 @@ "TerritoryId": 180, "InteractionType": "WalkTo", "Fly": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -128 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + } + } }, { "DataId": 2000075, @@ -221,14 +249,18 @@ "InteractionType": "WalkTo", "Fly": true, "AetheryteShortcut": "Western La Noscea - Aleport", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -64 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 64 + ] + } + } }, { "DataId": 2000076, diff --git a/QuestPaths/2.x - A Realm Reborn/Tribal/Kobolds/Story/1321_How Low Can You Go.json b/QuestPaths/2.x - A Realm Reborn/Tribal/Kobolds/Story/1321_How Low Can You Go.json index 7f119f73..b53e459c 100644 --- a/QuestPaths/2.x - A Realm Reborn/Tribal/Kobolds/Story/1321_How Low Can You Go.json +++ b/QuestPaths/2.x - A Realm Reborn/Tribal/Kobolds/Story/1321_How Low Can You Go.json @@ -28,14 +28,18 @@ }, "TerritoryId": 180, "InteractionType": "WalkTo", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -32 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 32 + ] + } + } }, { "DataId": 2003715, diff --git a/QuestPaths/3.x - Heavensward/Aether Currents/The Churning Mists/1828_Hide Your Moogles.json b/QuestPaths/3.x - Heavensward/Aether Currents/The Churning Mists/1828_Hide Your Moogles.json index e152eea9..7d7043cf 100644 --- a/QuestPaths/3.x - Heavensward/Aether Currents/The Churning Mists/1828_Hide Your Moogles.json +++ b/QuestPaths/3.x - Heavensward/Aether Currents/The Churning Mists/1828_Hide Your Moogles.json @@ -28,14 +28,18 @@ }, "TerritoryId": 400, "InteractionType": "WalkTo", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -64 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 64 + ] + } + } }, { "Position": { @@ -46,14 +50,18 @@ "TerritoryId": 400, "InteractionType": "WalkTo", "DisableNavmesh": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -64 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 64 + ] + } + } }, { "Position": { @@ -63,14 +71,18 @@ }, "TerritoryId": 400, "InteractionType": "WalkTo", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -64 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 64 + ] + } + } }, { "DataId": 1013424, @@ -118,14 +130,18 @@ }, "TerritoryId": 400, "InteractionType": "WalkTo", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -128 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + } + } }, { "Position": { @@ -144,14 +160,18 @@ "Z": 380.9018 } }, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -128 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + } + } }, { "DataId": 1013421, diff --git a/QuestPaths/3.x - Heavensward/Aether Currents/The Dravanian Forelands/1771_Some Bad News.json b/QuestPaths/3.x - Heavensward/Aether Currents/The Dravanian Forelands/1771_Some Bad News.json index 4010255c..c584f07b 100644 --- a/QuestPaths/3.x - Heavensward/Aether Currents/The Dravanian Forelands/1771_Some Bad News.json +++ b/QuestPaths/3.x - Heavensward/Aether Currents/The Dravanian Forelands/1771_Some Bad News.json @@ -28,14 +28,18 @@ }, "TerritoryId": 398, "InteractionType": "WalkTo", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -32 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 32 + ] + } + } }, { "Position": { @@ -46,14 +50,18 @@ "TerritoryId": 398, "InteractionType": "WalkTo", "DisableNavmesh": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -32 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 32 + ] + } + } }, { "Position": { @@ -64,14 +72,18 @@ "TerritoryId": 398, "InteractionType": "WalkTo", "DisableNavmesh": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -32 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 32 + ] + } + } }, { "DataId": 1011932, @@ -99,14 +111,18 @@ }, "TerritoryId": 398, "InteractionType": "WalkTo", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -64 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 64 + ] + } + } }, { "Position": { @@ -117,14 +133,18 @@ "TerritoryId": 398, "InteractionType": "WalkTo", "DisableNavmesh": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -64 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 64 + ] + } + } }, { "DataId": 1011924, diff --git a/QuestPaths/3.x - Heavensward/Aether Currents/The Dravanian Forelands/1797_The Hunter Becomes the Kweh.json b/QuestPaths/3.x - Heavensward/Aether Currents/The Dravanian Forelands/1797_The Hunter Becomes the Kweh.json index 672fe5fc..2deff7b9 100644 --- a/QuestPaths/3.x - Heavensward/Aether Currents/The Dravanian Forelands/1797_The Hunter Becomes the Kweh.json +++ b/QuestPaths/3.x - Heavensward/Aether Currents/The Dravanian Forelands/1797_The Hunter Becomes the Kweh.json @@ -34,14 +34,18 @@ }, "TerritoryId": 398, "InteractionType": "WalkTo", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -32 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 32 + ] + } + } }, { "Position": { @@ -52,14 +56,18 @@ "TerritoryId": 398, "InteractionType": "WalkTo", "DisableNavmesh": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -32 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 32 + ] + } + } }, { "Position": { @@ -70,14 +78,18 @@ "TerritoryId": 398, "InteractionType": "WalkTo", "DisableNavmesh": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -32 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 32 + ] + } + } }, { "DataId": 1011932, @@ -106,14 +118,18 @@ }, "TerritoryId": 398, "InteractionType": "WalkTo", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -64 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 64 + ] + } + } }, { "Position": { @@ -124,14 +140,18 @@ "TerritoryId": 398, "InteractionType": "WalkTo", "DisableNavmesh": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -64 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 64 + ] + } + } }, { "DataId": 1011924, diff --git a/QuestPaths/3.x - Heavensward/MSQ/A1-Coerthas Western Highlands 1, Sea of Clouds 1/1593_Sense of Urgency.json b/QuestPaths/3.x - Heavensward/MSQ/A1-Coerthas Western Highlands 1, Sea of Clouds 1/1593_Sense of Urgency.json index 499045db..4a8d50ac 100644 --- a/QuestPaths/3.x - Heavensward/MSQ/A1-Coerthas Western Highlands 1, Sea of Clouds 1/1593_Sense of Urgency.json +++ b/QuestPaths/3.x - Heavensward/MSQ/A1-Coerthas Western Highlands 1, Sea of Clouds 1/1593_Sense of Urgency.json @@ -29,14 +29,18 @@ "TerritoryId": 401, "InteractionType": "WalkTo", "Mount": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -128 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + } + } }, { "Position": { @@ -47,14 +51,18 @@ "TerritoryId": 401, "InteractionType": "WalkTo", "DisableNavmesh": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -128 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + } + } }, { "DataId": 1013498, diff --git a/QuestPaths/3.x - Heavensward/MSQ/A6-The Dravanian Hinterlands/1662_Hour of Departure.json b/QuestPaths/3.x - Heavensward/MSQ/A6-The Dravanian Hinterlands/1662_Hour of Departure.json index b79ad407..ddf0e6ea 100644 --- a/QuestPaths/3.x - Heavensward/MSQ/A6-The Dravanian Hinterlands/1662_Hour of Departure.json +++ b/QuestPaths/3.x - Heavensward/MSQ/A6-The Dravanian Hinterlands/1662_Hour of Departure.json @@ -86,14 +86,18 @@ }, "TerritoryId": 418, "InteractionType": "WalkTo", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -32 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 32 + ] + } + } }, { "Position": { @@ -104,14 +108,18 @@ "TerritoryId": 418, "InteractionType": "WalkTo", "DisableNavmesh": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -32 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 32 + ] + } + } }, { "DataId": 1012251, diff --git a/QuestPaths/4.x - Stormblood/Tribal/Ananta/Dailies/3064_Thin-skinned.json b/QuestPaths/4.x - Stormblood/Tribal/Ananta/Dailies/3064_Thin-skinned.json index 59c56180..2e3c84b6 100644 --- a/QuestPaths/4.x - Stormblood/Tribal/Ananta/Dailies/3064_Thin-skinned.json +++ b/QuestPaths/4.x - Stormblood/Tribal/Ananta/Dailies/3064_Thin-skinned.json @@ -64,14 +64,18 @@ null, null ], - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -32 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 32 + ] + } + } }, { "DataId": 2009361, @@ -160,14 +164,18 @@ null, null ], - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -64 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 64 + ] + } + } }, { "DataId": 2009360, @@ -253,14 +261,18 @@ null, null ], - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -128 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + } + } }, { "DataId": 2009359, @@ -346,14 +358,18 @@ null, null ], - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -4 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 4 + ] + } + } }, { "DataId": 2009364, @@ -436,14 +452,18 @@ null, null ], - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -8 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 8 + ] + } + } }, { "DataId": 2009363, @@ -529,14 +549,18 @@ null, null ], - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -16 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 16 + ] + } + } }, { "DataId": 2009362, diff --git a/QuestPaths/5.x - Shadowbringers/Aether Currents/Lakeland/3380_A Jobb Well Done.json b/QuestPaths/5.x - Shadowbringers/Aether Currents/Lakeland/3380_A Jobb Well Done.json index 19c4d695..bb74f278 100644 --- a/QuestPaths/5.x - Shadowbringers/Aether Currents/Lakeland/3380_A Jobb Well Done.json +++ b/QuestPaths/5.x - Shadowbringers/Aether Currents/Lakeland/3380_A Jobb Well Done.json @@ -63,14 +63,18 @@ "TerritoryId": 813, "InteractionType": "WalkTo", "Mount": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -64 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 64 + ] + } + } }, { "Position": { @@ -81,14 +85,18 @@ "TerritoryId": 813, "InteractionType": "WalkTo", "DisableNavmesh": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -64 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 64 + ] + } + } }, { "DataId": 1027339, @@ -116,14 +124,18 @@ }, "TerritoryId": 813, "InteractionType": "WalkTo", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -32 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 32 + ] + } + } }, { "Position": { @@ -133,14 +145,18 @@ }, "TerritoryId": 813, "InteractionType": "WalkTo", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -32 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 32 + ] + } + } }, { "Position": { @@ -150,14 +166,18 @@ }, "TerritoryId": 813, "InteractionType": "WalkTo", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -32 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 32 + ] + } + } }, { "DataId": 1027420, diff --git a/QuestPaths/5.x - Shadowbringers/Aether Currents/Lakeland/3384_Imperative Repairs.json b/QuestPaths/5.x - Shadowbringers/Aether Currents/Lakeland/3384_Imperative Repairs.json index b54bc132..4f8715aa 100644 --- a/QuestPaths/5.x - Shadowbringers/Aether Currents/Lakeland/3384_Imperative Repairs.json +++ b/QuestPaths/5.x - Shadowbringers/Aether Currents/Lakeland/3384_Imperative Repairs.json @@ -29,14 +29,18 @@ "TerritoryId": 813, "InteractionType": "WalkTo", "Comment": "Tower Bottom", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -64 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 64 + ] + } + } }, { "Position": { @@ -48,14 +52,18 @@ "InteractionType": "WalkTo", "DisableNavmesh": true, "Comment": "Tower Bottom Platform 1", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -64 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 64 + ] + } + } }, { "DataId": 2010618, diff --git a/QuestPaths/5.x - Shadowbringers/MSQ/B-Il Mheg/3305_The Oracle of Light.json b/QuestPaths/5.x - Shadowbringers/MSQ/B-Il Mheg/3305_The Oracle of Light.json index 8c3baec4..5aeffbc0 100644 --- a/QuestPaths/5.x - Shadowbringers/MSQ/B-Il Mheg/3305_The Oracle of Light.json +++ b/QuestPaths/5.x - Shadowbringers/MSQ/B-Il Mheg/3305_The Oracle of Light.json @@ -74,14 +74,18 @@ }, "TerritoryId": 813, "InteractionType": "WalkTo", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -64 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 64 + ] + } + } }, { "DataId": 1028952, diff --git a/QuestPaths/5.x - Shadowbringers/MSQ/B-Il Mheg/3307_Sul Uin's Request.json b/QuestPaths/5.x - Shadowbringers/MSQ/B-Il Mheg/3307_Sul Uin's Request.json index b578fa65..b4801eed 100644 --- a/QuestPaths/5.x - Shadowbringers/MSQ/B-Il Mheg/3307_Sul Uin's Request.json +++ b/QuestPaths/5.x - Shadowbringers/MSQ/B-Il Mheg/3307_Sul Uin's Request.json @@ -28,14 +28,18 @@ }, "TerritoryId": 816, "InteractionType": "WalkTo", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -64 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 64 + ] + } + } }, { "DataId": 2009820, @@ -66,14 +70,18 @@ "TerritoryId": 816, "InteractionType": "WalkTo", "DisableNavmesh": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -128 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + } + } }, { "DataId": 2009819, diff --git a/QuestPaths/5.x - Shadowbringers/MSQ/B-Il Mheg/3315_Spore Sweeper.json b/QuestPaths/5.x - Shadowbringers/MSQ/B-Il Mheg/3315_Spore Sweeper.json index 93eeea77..99929255 100644 --- a/QuestPaths/5.x - Shadowbringers/MSQ/B-Il Mheg/3315_Spore Sweeper.json +++ b/QuestPaths/5.x - Shadowbringers/MSQ/B-Il Mheg/3315_Spore Sweeper.json @@ -46,14 +46,18 @@ }, "TerritoryId": 816, "InteractionType": "WalkTo", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -64 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 64 + ] + } + } }, { "Position": { @@ -64,14 +68,18 @@ "TerritoryId": 816, "InteractionType": "WalkTo", "DisableNavmesh": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -64 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 64 + ] + } + } }, { "DataId": 2009831, diff --git a/QuestPaths/5.x - Shadowbringers/MSQ/B-Il Mheg/3316_The Lawless Ones.json b/QuestPaths/5.x - Shadowbringers/MSQ/B-Il Mheg/3316_The Lawless Ones.json index 9acc6b95..07967866 100644 --- a/QuestPaths/5.x - Shadowbringers/MSQ/B-Il Mheg/3316_The Lawless Ones.json +++ b/QuestPaths/5.x - Shadowbringers/MSQ/B-Il Mheg/3316_The Lawless Ones.json @@ -150,14 +150,18 @@ }, "TerritoryId": 816, "InteractionType": "WalkTo", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -32 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 32 + ] + } + } }, { "Position": { @@ -168,14 +172,18 @@ "TerritoryId": 816, "InteractionType": "WalkTo", "DisableNavmesh": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -32 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 32 + ] + } + } }, { "DataId": 2009836, @@ -214,14 +222,18 @@ }, "DelaySeconds": 0.2 }, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -64 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 64 + ] + } + } }, { "Position": { @@ -241,14 +253,18 @@ }, "DelaySeconds": 0.1 }, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -64 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 64 + ] + } + } }, { "Position": { @@ -268,14 +284,18 @@ }, "DelaySeconds": 0.1 }, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -64 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 64 + ] + } + } }, { "Position": { @@ -295,14 +315,18 @@ }, "DelaySeconds": 0.2 }, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -64 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 64 + ] + } + } }, { "DataId": 2009835, diff --git a/QuestPaths/5.x - Shadowbringers/MSQ/C-Rak'tika/3335_Look to the Stars.json b/QuestPaths/5.x - Shadowbringers/MSQ/C-Rak'tika/3335_Look to the Stars.json index a005447b..668c8fee 100644 --- a/QuestPaths/5.x - Shadowbringers/MSQ/C-Rak'tika/3335_Look to the Stars.json +++ b/QuestPaths/5.x - Shadowbringers/MSQ/C-Rak'tika/3335_Look to the Stars.json @@ -79,14 +79,18 @@ "StopDistance": 2, "Type": "RepeatedJumps" }, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -16 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 16 + ] + } + } }, { "DataId": 2009890, @@ -128,14 +132,18 @@ "StopDistance": 2, "Type": "RepeatedJumps" }, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -128 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + } + } }, { "DataId": 2009887, @@ -166,14 +174,18 @@ "InteractionType": "WalkTo", "DisableNavmesh": true, "Mount": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -64 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 64 + ] + } + } }, { "Position": { @@ -196,14 +208,18 @@ "StopDistance": 2, "Type": "RepeatedJumps" }, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -64 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 64 + ] + } + } }, { "DataId": 2009888, @@ -245,14 +261,18 @@ "StopDistance": 2, "Type": "RepeatedJumps" }, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -32 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 32 + ] + } + } }, { "DataId": 2009889, diff --git a/QuestPaths/5.x - Shadowbringers/MSQ/E-Kholusia 2/3633_The Ladder.json b/QuestPaths/5.x - Shadowbringers/MSQ/E-Kholusia 2/3633_The Ladder.json index a7c9fd25..0c46f86d 100644 --- a/QuestPaths/5.x - Shadowbringers/MSQ/E-Kholusia 2/3633_The Ladder.json +++ b/QuestPaths/5.x - Shadowbringers/MSQ/E-Kholusia 2/3633_The Ladder.json @@ -60,14 +60,18 @@ }, "TerritoryId": 814, "InteractionType": "WalkTo", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -64 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 64 + ] + } + } }, { "DataId": 2010088, @@ -114,14 +118,18 @@ }, "TerritoryId": 814, "InteractionType": "WalkTo", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -32 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 32 + ] + } + } }, { "DataId": 2010089, diff --git a/QuestPaths/5.x - Shadowbringers/MSQ/F-Tempest/3645_In His Garden.json b/QuestPaths/5.x - Shadowbringers/MSQ/F-Tempest/3645_In His Garden.json index 8972210d..a2485ea1 100644 --- a/QuestPaths/5.x - Shadowbringers/MSQ/F-Tempest/3645_In His Garden.json +++ b/QuestPaths/5.x - Shadowbringers/MSQ/F-Tempest/3645_In His Garden.json @@ -48,14 +48,18 @@ "TerritoryId": 819, "InteractionType": "WalkTo", "DisableNavmesh": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -128 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + } + } }, { "DataId": 1027246, diff --git a/QuestPaths/5.x - Shadowbringers/Tribal/Pixies/Dailies/3689_Where There's Wool, There's a Way.json b/QuestPaths/5.x - Shadowbringers/Tribal/Pixies/Dailies/3689_Where There's Wool, There's a Way.json index 51110d72..6a4cbf23 100644 --- a/QuestPaths/5.x - Shadowbringers/Tribal/Pixies/Dailies/3689_Where There's Wool, There's a Way.json +++ b/QuestPaths/5.x - Shadowbringers/Tribal/Pixies/Dailies/3689_Where There's Wool, There's a Way.json @@ -29,14 +29,18 @@ "TerritoryId": 816, "InteractionType": "WalkTo", "Fly": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -128 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + } + } }, { "DataId": 2010856, @@ -83,14 +87,18 @@ }, "TerritoryId": 816, "InteractionType": "WalkTo", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -64 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 64 + ] + } + } }, { "DataId": 2010857, diff --git a/QuestPaths/5.x - Shadowbringers/Tribal/Pixies/Dailies/3716_The Chaser.json b/QuestPaths/5.x - Shadowbringers/Tribal/Pixies/Dailies/3716_The Chaser.json index 06f47e6e..750ac0b7 100644 --- a/QuestPaths/5.x - Shadowbringers/Tribal/Pixies/Dailies/3716_The Chaser.json +++ b/QuestPaths/5.x - Shadowbringers/Tribal/Pixies/Dailies/3716_The Chaser.json @@ -31,7 +31,35 @@ "InteractionType": "UseItem", "ItemId": 2002943, "AetheryteShortcut": "Il Mheg - Pia Enni", - "Fly": true + "Fly": true, + "RequiredQuestVariables": [ + null, + null, + [1], + null, + null, + null + ] + }, + { + "DataId": 1032202, + "Position": { + "X": 10.849121, + "Y": 100.161, + "Z": -886.22876 + }, + "TerritoryId": 816, + "InteractionType": "UseItem", + "ItemId": 2002943, + "Fly": true, + "RequiredQuestVariables": [ + null, + null, + [2], + null, + null, + null + ] } ] }, diff --git a/QuestPaths/5.x - Shadowbringers/Tribal/Pixies/Dailies/3716_The Chaser.md b/QuestPaths/5.x - Shadowbringers/Tribal/Pixies/Dailies/3716_The Chaser.md new file mode 100644 index 00000000..352f3b22 --- /dev/null +++ b/QuestPaths/5.x - Shadowbringers/Tribal/Pixies/Dailies/3716_The Chaser.md @@ -0,0 +1,3 @@ +0 0 ? 0 0 0 + 1 → 1032203 + 2 → 1032202 diff --git a/QuestPaths/6.x - Endwalker/4807_DebugGathering.json b/QuestPaths/6.x - Endwalker/4807_DebugGathering.json new file mode 100644 index 00000000..3d411f43 --- /dev/null +++ b/QuestPaths/6.x - Endwalker/4807_DebugGathering.json @@ -0,0 +1,27 @@ +{ + "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json", + "Author": "liza", + "QuestSequence": [ + { + "Sequence": 255, + "Steps": [ + { + "Position": { + "X": -435.39066, + "Y": -9.809827, + "Z": -594.5472 + }, + "TerritoryId": 1187, + "InteractionType": "WalkTo", + "Fly": true, + "RequiredGatheredItems": [ + { + "ItemId": 43992, + "ItemCount": 1234 + } + ] + } + ] + } + ] +} diff --git a/QuestPaths/6.x - Endwalker/Aether Currents/Mare Lamentorum/4241_Carrots Its Whats for Dinner.json b/QuestPaths/6.x - Endwalker/Aether Currents/Mare Lamentorum/4241_Carrots Its Whats for Dinner.json index 720ba8e8..0d7637ca 100644 --- a/QuestPaths/6.x - Endwalker/Aether Currents/Mare Lamentorum/4241_Carrots Its Whats for Dinner.json +++ b/QuestPaths/6.x - Endwalker/Aether Currents/Mare Lamentorum/4241_Carrots Its Whats for Dinner.json @@ -29,14 +29,18 @@ "StopDistance": 1, "TerritoryId": 959, "InteractionType": "WalkTo", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -128 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + } + } }, { "Position": { @@ -56,14 +60,18 @@ }, "Mount": false, "Comment": "Platform 1", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -128 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + } + } }, { "Position": { @@ -82,14 +90,18 @@ "DelaySeconds": 0.25 }, "Comment": "Platform 2", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -128 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + } + } }, { "Position": { @@ -108,14 +120,18 @@ "DelaySeconds": 0.25 }, "Comment": "Platform 3", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -128 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + } + } }, { "Position": { @@ -134,14 +150,18 @@ "DelaySeconds": 0.25 }, "Comment": "Platform 4", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -128 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + } + } }, { "Position": { @@ -160,14 +180,18 @@ "DelaySeconds": 0.25 }, "Comment": "Platform 5", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -128 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + } + } }, { "Position": { @@ -186,14 +210,18 @@ "DelaySeconds": 0.25 }, "Comment": "Platform 6", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -128 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + } + } }, { "Position": { @@ -204,14 +232,18 @@ "TerritoryId": 959, "InteractionType": "WalkTo", "DisableNavmesh": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -128 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + } + } }, { "DataId": 1041789, @@ -287,14 +319,18 @@ }, "Mount": false, "Comment": "Platform 1", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -32 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 32 + ] + } + } }, { "Position": { @@ -314,14 +350,18 @@ "DelaySeconds": 0.25 }, "Comment": "Platform 2", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -32 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 32 + ] + } + } }, { "Position": { @@ -341,14 +381,18 @@ "DelaySeconds": 0.25 }, "Comment": "Platform 3", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -32 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 32 + ] + } + } }, { "Position": { @@ -368,14 +412,18 @@ "DelaySeconds": 0.25 }, "Comment": "Platform 4", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -32 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 32 + ] + } + } }, { "Position": { @@ -395,14 +443,18 @@ "DelaySeconds": 0.25 }, "Comment": "Platform 5", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -32 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 32 + ] + } + } }, { "Position": { @@ -422,14 +474,18 @@ "DelaySeconds": 0.25 }, "Comment": "Platform 6", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -32 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 32 + ] + } + } }, { "DataId": 1041791, diff --git a/QuestPaths/6.x - Endwalker/Aether Currents/Thavnair/4203_Alchemist or Dancer.json b/QuestPaths/6.x - Endwalker/Aether Currents/Thavnair/4203_Alchemist or Dancer.json index 6e01fb6f..3b15ae0b 100644 --- a/QuestPaths/6.x - Endwalker/Aether Currents/Thavnair/4203_Alchemist or Dancer.json +++ b/QuestPaths/6.x - Endwalker/Aether Currents/Thavnair/4203_Alchemist or Dancer.json @@ -61,14 +61,18 @@ }, "TerritoryId": 957, "InteractionType": "WalkTo", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -32 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 32 + ] + } + } }, { "DataId": 2011914, diff --git a/QuestPaths/6.x - Endwalker/Aether Currents/Thavnair/4259_Radiant Patrol.json b/QuestPaths/6.x - Endwalker/Aether Currents/Thavnair/4259_Radiant Patrol.json index ca5a8652..43425dbe 100644 --- a/QuestPaths/6.x - Endwalker/Aether Currents/Thavnair/4259_Radiant Patrol.json +++ b/QuestPaths/6.x - Endwalker/Aether Currents/Thavnair/4259_Radiant Patrol.json @@ -74,7 +74,7 @@ null, null, null, - -64 + 64 ], "$": "QuestVariables after killing enemy: 17 1 0 0 0 64" }, diff --git a/QuestPaths/6.x - Endwalker/MSQ/B-Garlemald/4389_Personae non Gratae.json b/QuestPaths/6.x - Endwalker/MSQ/B-Garlemald/4389_Personae non Gratae.json index d76baf9d..9bee400f 100644 --- a/QuestPaths/6.x - Endwalker/MSQ/B-Garlemald/4389_Personae non Gratae.json +++ b/QuestPaths/6.x - Endwalker/MSQ/B-Garlemald/4389_Personae non Gratae.json @@ -66,14 +66,18 @@ }, "TerritoryId": 958, "InteractionType": "WalkTo", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -128 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + } + } }, { "DataId": 1037715, diff --git a/QuestPaths/6.x - Endwalker/MSQ/B-Garlemald/4393_Strange Bedfellows.json b/QuestPaths/6.x - Endwalker/MSQ/B-Garlemald/4393_Strange Bedfellows.json index 8dfb9b50..04342d49 100644 --- a/QuestPaths/6.x - Endwalker/MSQ/B-Garlemald/4393_Strange Bedfellows.json +++ b/QuestPaths/6.x - Endwalker/MSQ/B-Garlemald/4393_Strange Bedfellows.json @@ -112,14 +112,18 @@ "InteractionType": "WalkTo", "Mount": true, "DisableNavmesh": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -64 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 64 + ] + } + } }, { "DataId": 2012111, @@ -180,14 +184,18 @@ }, "TerritoryId": 958, "InteractionType": "WalkTo", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -128 - ], + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + } + }, "Comment": "Avoids combat" }, { @@ -219,14 +227,18 @@ "TerritoryId": 958, "InteractionType": "WalkTo", "Comment": "Avoids combat", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -16 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 16 + ] + } + } }, { "DataId": 2012110, diff --git a/QuestPaths/6.x - Endwalker/MSQ/D-Thavnair2/4410_The Blasphemy Unmasked.json b/QuestPaths/6.x - Endwalker/MSQ/D-Thavnair2/4410_The Blasphemy Unmasked.json index 436856e5..92f13d83 100644 --- a/QuestPaths/6.x - Endwalker/MSQ/D-Thavnair2/4410_The Blasphemy Unmasked.json +++ b/QuestPaths/6.x - Endwalker/MSQ/D-Thavnair2/4410_The Blasphemy Unmasked.json @@ -60,14 +60,18 @@ "TerritoryId": 963, "InteractionType": "WalkTo", "DisableNavmesh": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -128 - ], + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + } + }, "Sprint": true }, { diff --git a/QuestPaths/6.x - Endwalker/MSQ/E-Elpis/4420_Hope Upon a Flower.json b/QuestPaths/6.x - Endwalker/MSQ/E-Elpis/4420_Hope Upon a Flower.json index 90507512..82b13f6e 100644 --- a/QuestPaths/6.x - Endwalker/MSQ/E-Elpis/4420_Hope Upon a Flower.json +++ b/QuestPaths/6.x - Endwalker/MSQ/E-Elpis/4420_Hope Upon a Flower.json @@ -71,14 +71,18 @@ "Z": -1.1141448 } }, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -16 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 16 + ] + } + } }, { "DataId": 2012128, diff --git a/QuestPaths/6.x - Endwalker/MSQ/G-UltimaThule/4456_Roads Paved of Sacrifice.json b/QuestPaths/6.x - Endwalker/MSQ/G-UltimaThule/4456_Roads Paved of Sacrifice.json index 4289281a..deaef791 100644 --- a/QuestPaths/6.x - Endwalker/MSQ/G-UltimaThule/4456_Roads Paved of Sacrifice.json +++ b/QuestPaths/6.x - Endwalker/MSQ/G-UltimaThule/4456_Roads Paved of Sacrifice.json @@ -109,14 +109,18 @@ }, "TerritoryId": 960, "InteractionType": "WalkTo", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -128 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + } + } }, { "DataId": 2012354, diff --git a/QuestPaths/6.x - Endwalker/MSQ/H-6.1/4531_Sharing the Wealth.json b/QuestPaths/6.x - Endwalker/MSQ/H-6.1/4531_Sharing the Wealth.json index 476a8659..59686b6f 100644 --- a/QuestPaths/6.x - Endwalker/MSQ/H-6.1/4531_Sharing the Wealth.json +++ b/QuestPaths/6.x - Endwalker/MSQ/H-6.1/4531_Sharing the Wealth.json @@ -51,14 +51,18 @@ "TerritoryId": 957, "InteractionType": "WalkTo", "AetheryteShortcut": "Thavnair - Yedlihmad", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -128 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + } + } }, { "DataId": 1037631, diff --git a/QuestPaths/6.x - Endwalker/Side Quests/Thavnair/4491_Ogul Repays Her Favors.json b/QuestPaths/6.x - Endwalker/Side Quests/Thavnair/4491_Ogul Repays Her Favors.json index 4d77d3b3..f5f66dc4 100644 --- a/QuestPaths/6.x - Endwalker/Side Quests/Thavnair/4491_Ogul Repays Her Favors.json +++ b/QuestPaths/6.x - Endwalker/Side Quests/Thavnair/4491_Ogul Repays Her Favors.json @@ -97,14 +97,18 @@ "TerritoryId": 957, "InteractionType": "WalkTo", "Fly": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -128 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + } + } }, { "DataId": 1037655, @@ -151,14 +155,18 @@ }, "TerritoryId": 957, "InteractionType": "WalkTo", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -32 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 32 + ] + } + } }, { "DataId": 1037708, @@ -169,14 +177,18 @@ }, "TerritoryId": 957, "InteractionType": "Interact", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -32 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 32 + ] + } + } } ] }, diff --git a/QuestPaths/6.x - Endwalker/Side Quests/Thavnair/4495_The Sins We Bear.json b/QuestPaths/6.x - Endwalker/Side Quests/Thavnair/4495_The Sins We Bear.json index 952be85e..60491984 100644 --- a/QuestPaths/6.x - Endwalker/Side Quests/Thavnair/4495_The Sins We Bear.json +++ b/QuestPaths/6.x - Endwalker/Side Quests/Thavnair/4495_The Sins We Bear.json @@ -68,14 +68,18 @@ "TerritoryId": 957, "InteractionType": "WalkTo", "Fly": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -128 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + } + } }, { "DataId": 2012455, @@ -105,14 +109,18 @@ "TerritoryId": 957, "InteractionType": "WalkTo", "Fly": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -32 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 32 + ] + } + } }, { "DataId": 2012457, @@ -141,14 +149,18 @@ "TerritoryId": 957, "InteractionType": "WalkTo", "Fly": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -64 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 64 + ] + } + } }, { "DataId": 2012456, diff --git a/QuestPaths/6.x - Endwalker/Studium Deliveries/4473_The Faculty.json b/QuestPaths/6.x - Endwalker/Studium Deliveries/4473_The Faculty.json new file mode 100644 index 00000000..e960c9f3 --- /dev/null +++ b/QuestPaths/6.x - Endwalker/Studium Deliveries/4473_The Faculty.json @@ -0,0 +1,36 @@ +{ + "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json", + "Author": "liza", + "QuestSequence": [ + { + "Sequence": 0, + "Steps": [ + { + "DataId": 1038500, + "Position": { + "X": -357.83936, + "Y": 21.84602, + "Z": -91.32526 + }, + "TerritoryId": 962, + "InteractionType": "AcceptQuest" + } + ] + }, + { + "Sequence": 255, + "Steps": [ + { + "DataId": 1038500, + "Position": { + "X": -357.83936, + "Y": 21.84602, + "Z": -91.32526 + }, + "TerritoryId": 962, + "InteractionType": "CompleteQuest" + } + ] + } + ] +} diff --git a/QuestPaths/6.x - Endwalker/Studium Deliveries/MIN, BTN/4153_Cultured Pursuits.json b/QuestPaths/6.x - Endwalker/Studium Deliveries/MIN, BTN/4153_Cultured Pursuits.json new file mode 100644 index 00000000..d9f54e22 --- /dev/null +++ b/QuestPaths/6.x - Endwalker/Studium Deliveries/MIN, BTN/4153_Cultured Pursuits.json @@ -0,0 +1,68 @@ +{ + "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json", + "Author": "liza", + "QuestSequence": [ + { + "Sequence": 0, + "Steps": [ + { + "DataId": 1038504, + "Position": { + "X": -357.62573, + "Y": 21.64856, + "Z": -95.99457 + }, + "TerritoryId": 962, + "InteractionType": "AcceptQuest" + } + ] + }, + { + "Sequence": 1, + "Steps": [ + { + "DataId": 1038505, + "Position": { + "X": -376.45538, + "Y": 18.999998, + "Z": 37.00305 + }, + "TerritoryId": 962, + "InteractionType": "Interact" + } + ] + }, + { + "Sequence": 2, + "Steps": [ + { + "DataId": 1038503, + "Position": { + "X": -367.0863, + "Y": 21.84602, + "Z": -101.701416 + }, + "TerritoryId": 962, + "InteractionType": "Interact" + } + ] + }, + { + "Sequence": 255, + "Steps": [ + { + "DataId": 1038501, + "Position": { + "X": -367.3305, + "Y": 21.846018, + "Z": -102.983154 + }, + "StopDistance": 7, + "TerritoryId": 962, + "InteractionType": "CompleteQuest", + "NextQuestId": 4154 + } + ] + } + ] +} diff --git a/QuestPaths/6.x - Endwalker/Studium Deliveries/MIN, BTN/4154_Cooking Up a Culture.json b/QuestPaths/6.x - Endwalker/Studium Deliveries/MIN, BTN/4154_Cooking Up a Culture.json new file mode 100644 index 00000000..30720303 --- /dev/null +++ b/QuestPaths/6.x - Endwalker/Studium Deliveries/MIN, BTN/4154_Cooking Up a Culture.json @@ -0,0 +1,49 @@ +{ + "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json", + "Author": "liza", + "QuestSequence": [ + { + "Sequence": 0, + "Steps": [ + { + "DataId": 1038501, + "Position": { + "X": -367.3305, + "Y": 21.846018, + "Z": -102.983154 + }, + "TerritoryId": 962, + "InteractionType": "AcceptQuest" + } + ] + }, + { + "Sequence": 255, + "Steps": [ + { + "DataId": 1038501, + "Position": { + "X": -367.3305, + "Y": 21.846018, + "Z": -102.983154 + }, + "TerritoryId": 962, + "InteractionType": "CompleteQuest", + "AetheryteShortcut": "Old Sharlayan", + "AethernetShortcut": [ + "[Old Sharlayan] Aetheryte Plaza", + "[Old Sharlayan] The Studium" + ], + "RequiredGatheredItems": [ + { + "ItemId": 35600, + "ItemCount": 6, + "Collectability": 600 + } + ], + "NextQuestId": 4155 + } + ] + } + ] +} diff --git a/QuestPaths/6.x - Endwalker/Studium Deliveries/MIN, BTN/4155_The Culture of Ceruleum.json b/QuestPaths/6.x - Endwalker/Studium Deliveries/MIN, BTN/4155_The Culture of Ceruleum.json new file mode 100644 index 00000000..ca598552 --- /dev/null +++ b/QuestPaths/6.x - Endwalker/Studium Deliveries/MIN, BTN/4155_The Culture of Ceruleum.json @@ -0,0 +1,49 @@ +{ + "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json", + "Author": "liza", + "QuestSequence": [ + { + "Sequence": 0, + "Steps": [ + { + "DataId": 1038501, + "Position": { + "X": -367.3305, + "Y": 21.846018, + "Z": -102.983154 + }, + "TerritoryId": 962, + "InteractionType": "AcceptQuest" + } + ] + }, + { + "Sequence": 255, + "Steps": [ + { + "DataId": 1038501, + "Position": { + "X": -367.3305, + "Y": 21.846018, + "Z": -102.983154 + }, + "TerritoryId": 962, + "InteractionType": "CompleteQuest", + "AetheryteShortcut": "Old Sharlayan", + "AethernetShortcut": [ + "[Old Sharlayan] Aetheryte Plaza", + "[Old Sharlayan] The Studium" + ], + "RequiredGatheredItems": [ + { + "ItemId": 35601, + "ItemCount": 6, + "Collectability": 600 + } + ], + "NextQuestId": 4156 + } + ] + } + ] +} diff --git a/QuestPaths/6.x - Endwalker/Studium Deliveries/MIN, BTN/4156_The Culture of Carrots.json b/QuestPaths/6.x - Endwalker/Studium Deliveries/MIN, BTN/4156_The Culture of Carrots.json new file mode 100644 index 00000000..d25034d3 --- /dev/null +++ b/QuestPaths/6.x - Endwalker/Studium Deliveries/MIN, BTN/4156_The Culture of Carrots.json @@ -0,0 +1,49 @@ +{ + "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json", + "Author": "liza", + "QuestSequence": [ + { + "Sequence": 0, + "Steps": [ + { + "DataId": 1038501, + "Position": { + "X": -367.3305, + "Y": 21.846018, + "Z": -102.983154 + }, + "TerritoryId": 962, + "InteractionType": "AcceptQuest" + } + ] + }, + { + "Sequence": 255, + "Steps": [ + { + "DataId": 1038501, + "Position": { + "X": -367.3305, + "Y": 21.846018, + "Z": -102.983154 + }, + "TerritoryId": 962, + "InteractionType": "CompleteQuest", + "AetheryteShortcut": "Old Sharlayan", + "AethernetShortcut": [ + "[Old Sharlayan] Aetheryte Plaza", + "[Old Sharlayan] The Studium" + ], + "RequiredGatheredItems": [ + { + "ItemId": 35602, + "ItemCount": 6, + "Collectability": 600 + } + ], + "NextQuestId": 4157 + } + ] + } + ] +} diff --git a/QuestPaths/6.x - Endwalker/Studium Deliveries/MIN, BTN/4157_Hinageshi in Hingashi.json b/QuestPaths/6.x - Endwalker/Studium Deliveries/MIN, BTN/4157_Hinageshi in Hingashi.json new file mode 100644 index 00000000..a59c4db7 --- /dev/null +++ b/QuestPaths/6.x - Endwalker/Studium Deliveries/MIN, BTN/4157_Hinageshi in Hingashi.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json", + "Author": "liza", + "QuestSequence": [ + { + "Sequence": 0, + "Steps": [ + { + "DataId": 1038501, + "Position": { + "X": -367.3305, + "Y": 21.846018, + "Z": -102.983154 + }, + "TerritoryId": 962, + "InteractionType": "AcceptQuest" + } + ] + } + ] +} diff --git a/QuestPaths/6.x - Endwalker/Tribal/Arkasodara/Dailies/4557_Gulal Generosity.json b/QuestPaths/6.x - Endwalker/Tribal/Arkasodara/Dailies/4557_Gulal Generosity.json index 2250fc99..2f59883e 100644 --- a/QuestPaths/6.x - Endwalker/Tribal/Arkasodara/Dailies/4557_Gulal Generosity.json +++ b/QuestPaths/6.x - Endwalker/Tribal/Arkasodara/Dailies/4557_Gulal Generosity.json @@ -45,14 +45,18 @@ "InteractionType": "WalkTo", "Fly": true, "Land": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -32 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 32 + ] + } + } }, { "DataId": 1042372, @@ -84,14 +88,18 @@ "InteractionType": "WalkTo", "Fly": true, "Land": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -64 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 64 + ] + } + } }, { "DataId": 1042371, @@ -123,14 +131,18 @@ "InteractionType": "WalkTo", "Fly": true, "Land": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -128 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + } + } }, { "DataId": 1042373, diff --git a/QuestPaths/6.x - Endwalker/Tribal/Arkasodara/Dailies/4564_Olfactory Warfare.json b/QuestPaths/6.x - Endwalker/Tribal/Arkasodara/Dailies/4564_Olfactory Warfare.json index b9fda16a..b7219f5d 100644 --- a/QuestPaths/6.x - Endwalker/Tribal/Arkasodara/Dailies/4564_Olfactory Warfare.json +++ b/QuestPaths/6.x - Endwalker/Tribal/Arkasodara/Dailies/4564_Olfactory Warfare.json @@ -45,14 +45,18 @@ "InteractionType": "WalkTo", "Fly": true, "Land": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -128 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + } + } }, { "DataId": 2012887, @@ -84,14 +88,18 @@ "InteractionType": "WalkTo", "Fly": true, "Land": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -64 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 64 + ] + } + } }, { "DataId": 2012888, @@ -123,14 +131,18 @@ "InteractionType": "WalkTo", "Fly": true, "Land": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -32 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 32 + ] + } + } }, { "DataId": 2012889, @@ -149,7 +161,7 @@ null, null, null, - -32 + 32 ] } ] diff --git a/QuestPaths/6.x - Endwalker/Tribal/Arkasodara/Dailies/4570_Patching Up.json b/QuestPaths/6.x - Endwalker/Tribal/Arkasodara/Dailies/4570_Patching Up.json index 34493e0e..90e6b009 100644 --- a/QuestPaths/6.x - Endwalker/Tribal/Arkasodara/Dailies/4570_Patching Up.json +++ b/QuestPaths/6.x - Endwalker/Tribal/Arkasodara/Dailies/4570_Patching Up.json @@ -29,14 +29,18 @@ "TerritoryId": 957, "InteractionType": "WalkTo", "Fly": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -32 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 32 + ] + } + } }, { "DataId": 2012902, @@ -66,14 +70,18 @@ "TerritoryId": 957, "InteractionType": "WalkTo", "Fly": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -64 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 64 + ] + } + } }, { "DataId": 2012901, @@ -103,14 +111,18 @@ "TerritoryId": 957, "InteractionType": "WalkTo", "Fly": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -128 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + } + } }, { "DataId": 2012900, diff --git a/QuestPaths/6.x - Endwalker/Tribal/Arkasodara/Dailies/4573_Gathering Moss.json b/QuestPaths/6.x - Endwalker/Tribal/Arkasodara/Dailies/4573_Gathering Moss.json index 57905110..9685dbf8 100644 --- a/QuestPaths/6.x - Endwalker/Tribal/Arkasodara/Dailies/4573_Gathering Moss.json +++ b/QuestPaths/6.x - Endwalker/Tribal/Arkasodara/Dailies/4573_Gathering Moss.json @@ -30,14 +30,18 @@ "TerritoryId": 957, "InteractionType": "WalkTo", "Fly": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -32 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 32 + ] + } + } }, { "DataId": 2012907, diff --git a/QuestPaths/6.x - Endwalker/Tribal/Arkasodara/Dailies/4574_Blood from a Stonemason.json b/QuestPaths/6.x - Endwalker/Tribal/Arkasodara/Dailies/4574_Blood from a Stonemason.json index f79b047c..c84aa4ac 100644 --- a/QuestPaths/6.x - Endwalker/Tribal/Arkasodara/Dailies/4574_Blood from a Stonemason.json +++ b/QuestPaths/6.x - Endwalker/Tribal/Arkasodara/Dailies/4574_Blood from a Stonemason.json @@ -34,14 +34,18 @@ "[Radz-at-Han] The Gate of First Sight (Thavnair)" ], "Fly": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -32 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 32 + ] + } + } }, { "DataId": 1042462, diff --git a/QuestPaths/6.x - Endwalker/Tribal/Arkasodara/Dailies/4578_Steep the Legs.json b/QuestPaths/6.x - Endwalker/Tribal/Arkasodara/Dailies/4578_Steep the Legs.json index 42a2b0f7..c10304a9 100644 --- a/QuestPaths/6.x - Endwalker/Tribal/Arkasodara/Dailies/4578_Steep the Legs.json +++ b/QuestPaths/6.x - Endwalker/Tribal/Arkasodara/Dailies/4578_Steep the Legs.json @@ -1,7 +1,6 @@ { "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json", "Author": "liza", - "Disabled": true, "QuestSequence": [ { "Sequence": 0, @@ -21,7 +20,71 @@ { "Sequence": 1, "Steps": [ - + { + "DataId": 2012915, + "Position": { + "X": -383.505, + "Y": 1.0527954, + "Z": 362.99683 + }, + "TerritoryId": 957, + "InteractionType": "Combat", + "EnemySpawnType": "AfterInteraction", + "KillEnemyDataIds": [ + 14680 + ], + "Fly": true, + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 32 + ] + }, + { + "DataId": 2012914, + "Position": { + "X": -399.83215, + "Y": 1.6326294, + "Z": 291.73718 + }, + "TerritoryId": 957, + "InteractionType": "Interact", + "Fly": true, + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 64 + ] + }, + { + "DataId": 2012913, + "Position": { + "X": -444.0528, + "Y": 0.38146973, + "Z": 249.53076 + }, + "TerritoryId": 957, + "InteractionType": "Combat", + "EnemySpawnType": "AfterInteraction", + "KillEnemyDataIds": [ + 14680 + ], + "Fly": true, + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + } ] }, { diff --git a/QuestPaths/6.x - Endwalker/Tribal/Arkasodara/Story/4548_Defiant Ogul, Deified.json b/QuestPaths/6.x - Endwalker/Tribal/Arkasodara/Story/4548_Defiant Ogul, Deified.json new file mode 100644 index 00000000..69172cff --- /dev/null +++ b/QuestPaths/6.x - Endwalker/Tribal/Arkasodara/Story/4548_Defiant Ogul, Deified.json @@ -0,0 +1,134 @@ +{ + "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json", + "Author": "liza", + "QuestSequence": [ + { + "Sequence": 0, + "Steps": [ + { + "DataId": 1042300, + "Position": { + "X": -76.82922, + "Y": 39.977543, + "Z": 309.98706 + }, + "TerritoryId": 957, + "InteractionType": "AcceptQuest" + } + ] + }, + { + "Sequence": 1, + "Steps": [ + { + "DataId": 1042336, + "Position": { + "X": 409.964, + "Y": 18.253498, + "Z": -461.53967 + }, + "TerritoryId": 957, + "InteractionType": "Interact", + "AetheryteShortcut": "Thavnair - Palaka's Stand", + "Fly": true + } + ] + }, + { + "Sequence": 2, + "Steps": [ + { + "DataId": 2012970, + "Position": { + "X": 197.4364, + "Y": 9.323181, + "Z": -438.71216 + }, + "TerritoryId": 957, + "InteractionType": "Interact", + "Fly": true, + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 64 + ] + }, + { + "DataId": 2012924, + "Position": { + "X": 179.33923, + "Y": 7.309021, + "Z": -392.44684 + }, + "TerritoryId": 957, + "InteractionType": "Combat", + "EnemySpawnType": "AfterInteraction", + "KillEnemyDataIds": [ + 14683 + ], + "Fly": true, + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + } + ] + }, + { + "Sequence": 3, + "Steps": [ + { + "DataId": 1042339, + "Position": { + "X": 145.95251, + "Y": 9.608223, + "Z": -234.24127 + }, + "TerritoryId": 957, + "InteractionType": "Interact", + "Fly": true + } + ] + }, + { + "Sequence": 4, + "Steps": [ + { + "DataId": 1042343, + "Position": { + "X": 142.53455, + "Y": 10.132848, + "Z": -232.89844 + }, + "StopDistance": 7, + "TerritoryId": 957, + "InteractionType": "Interact" + } + ] + }, + { + "Sequence": 255, + "Steps": [ + { + "DataId": 1042300, + "Position": { + "X": -76.82922, + "Y": 39.977543, + "Z": 309.98706 + }, + "TerritoryId": 957, + "InteractionType": "CompleteQuest", + "AetheryteShortcut": "Thavnair - Yedlihmad", + "Fly": true + } + ] + } + ] +} diff --git a/QuestPaths/7.x - Dawntrail/Aether Currents/Urqopacha/5047_An Illuminating Ritual.json b/QuestPaths/7.x - Dawntrail/Aether Currents/Urqopacha/5047_An Illuminating Ritual.json index 119513fb..f3436491 100644 --- a/QuestPaths/7.x - Dawntrail/Aether Currents/Urqopacha/5047_An Illuminating Ritual.json +++ b/QuestPaths/7.x - Dawntrail/Aether Currents/Urqopacha/5047_An Illuminating Ritual.json @@ -111,14 +111,18 @@ }, "DelaySeconds": 0.25 }, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -64 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 64 + ] + } + } }, { "DataId": 2014134, diff --git a/QuestPaths/7.x - Dawntrail/Custom Deliveries/Wachumeqimeqi/4966_Wrought in Wachumeqimeqi.json b/QuestPaths/7.x - Dawntrail/Custom Deliveries/Wachumeqimeqi/4966_Wrought in Wachumeqimeqi.json new file mode 100644 index 00000000..0120cf2a --- /dev/null +++ b/QuestPaths/7.x - Dawntrail/Custom Deliveries/Wachumeqimeqi/4966_Wrought in Wachumeqimeqi.json @@ -0,0 +1,36 @@ +{ + "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json", + "Author": "liza", + "QuestSequence": [ + { + "Sequence": 0, + "Steps": [ + { + "DataId": 1047095, + "Position": { + "X": 139.5437, + "Y": -13.99, + "Z": 10.60498 + }, + "TerritoryId": 1185, + "InteractionType": "AcceptQuest" + } + ] + }, + { + "Sequence": 255, + "Steps": [ + { + "DataId": 1047095, + "Position": { + "X": 139.5437, + "Y": -13.99, + "Z": 10.60498 + }, + "TerritoryId": 1185, + "InteractionType": "CompleteQuest" + } + ] + } + ] +} diff --git a/QuestPaths/7.x - Dawntrail/Custom Deliveries/Wachumeqimeqi/MIN, BTN/4989_Hands for Hire.json b/QuestPaths/7.x - Dawntrail/Custom Deliveries/Wachumeqimeqi/MIN, BTN/4989_Hands for Hire.json new file mode 100644 index 00000000..36eb6d7b --- /dev/null +++ b/QuestPaths/7.x - Dawntrail/Custom Deliveries/Wachumeqimeqi/MIN, BTN/4989_Hands for Hire.json @@ -0,0 +1,36 @@ +{ + "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json", + "Author": "liza", + "QuestSequence": [ + { + "Sequence": 0, + "Steps": [ + { + "DataId": 1047151, + "Position": { + "X": 130.2052, + "Y": -14, + "Z": 16.952698 + }, + "TerritoryId": 1185, + "InteractionType": "AcceptQuest" + } + ] + }, + { + "Sequence": 255, + "Steps": [ + { + "DataId": 1047164, + "Position": { + "X": 217.21204, + "Y": -14, + "Z": -8.316223 + }, + "TerritoryId": 1185, + "InteractionType": "CompleteQuest" + } + ] + } + ] +} diff --git a/QuestPaths/7.x - Dawntrail/Custom Deliveries/Wachumeqimeqi/MIN, BTN/4990_Test of Talents.json b/QuestPaths/7.x - Dawntrail/Custom Deliveries/Wachumeqimeqi/MIN, BTN/4990_Test of Talents.json new file mode 100644 index 00000000..5693f6bf --- /dev/null +++ b/QuestPaths/7.x - Dawntrail/Custom Deliveries/Wachumeqimeqi/MIN, BTN/4990_Test of Talents.json @@ -0,0 +1,36 @@ +{ + "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json", + "Author": "liza", + "QuestSequence": [ + { + "Sequence": 0, + "Steps": [ + { + "DataId": 1047132, + "Position": { + "X": 217.36475, + "Y": -14.000001, + "Z": -5.6916504 + }, + "TerritoryId": 1185, + "InteractionType": "AcceptQuest" + } + ] + }, + { + "Sequence": 255, + "Steps": [ + { + "DataId": 1047132, + "Position": { + "X": 217.36475, + "Y": -14.000001, + "Z": -5.6916504 + }, + "TerritoryId": 1185, + "InteractionType": "CompleteQuest" + } + ] + } + ] +} diff --git a/QuestPaths/7.x - Dawntrail/MSQ/B-Kozama'uka2-Urqopacha2/4892_An Echo of Madness.json b/QuestPaths/7.x - Dawntrail/MSQ/B-Kozama'uka2-Urqopacha2/4892_An Echo of Madness.json index 355a6b30..f35bf22e 100644 --- a/QuestPaths/7.x - Dawntrail/MSQ/B-Kozama'uka2-Urqopacha2/4892_An Echo of Madness.json +++ b/QuestPaths/7.x - Dawntrail/MSQ/B-Kozama'uka2-Urqopacha2/4892_An Echo of Madness.json @@ -47,14 +47,18 @@ }, "TerritoryId": 1187, "InteractionType": "WalkTo", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -32 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 32 + ] + } + } }, { "DataId": 1046874, diff --git a/QuestPaths/7.x - Dawntrail/MSQ/C-Yak T'el/4907_Mamook Speaks.json b/QuestPaths/7.x - Dawntrail/MSQ/C-Yak T'el/4907_Mamook Speaks.json index bdfb619a..95739833 100644 --- a/QuestPaths/7.x - Dawntrail/MSQ/C-Yak T'el/4907_Mamook Speaks.json +++ b/QuestPaths/7.x - Dawntrail/MSQ/C-Yak T'el/4907_Mamook Speaks.json @@ -142,14 +142,18 @@ "InteractionType": "WalkTo", "DisableNavmesh": true, "Mount": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -128 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + } + } }, { "DataId": 2013653, @@ -177,14 +181,18 @@ }, "TerritoryId": 1189, "InteractionType": "WalkTo", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -32 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 32 + ] + } + } }, { "DataId": 2013655, diff --git a/QuestPaths/7.x - Dawntrail/MSQ/D-Shaaloani-HeritageFound1/4927_The Land of Levin.json b/QuestPaths/7.x - Dawntrail/MSQ/D-Shaaloani-HeritageFound1/4927_The Land of Levin.json index 092ada30..c846a700 100644 --- a/QuestPaths/7.x - Dawntrail/MSQ/D-Shaaloani-HeritageFound1/4927_The Land of Levin.json +++ b/QuestPaths/7.x - Dawntrail/MSQ/D-Shaaloani-HeritageFound1/4927_The Land of Levin.json @@ -113,14 +113,18 @@ }, "TerritoryId": 1191, "InteractionType": "WalkTo", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -32 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 32 + ] + } + } }, { "DataId": 2013819, diff --git a/QuestPaths/7.x - Dawntrail/MSQ/F-Living Memory/4952_A Knight of Alexandria.json b/QuestPaths/7.x - Dawntrail/MSQ/F-Living Memory/4952_A Knight of Alexandria.json index 2791cb5a..9fce9170 100644 --- a/QuestPaths/7.x - Dawntrail/MSQ/F-Living Memory/4952_A Knight of Alexandria.json +++ b/QuestPaths/7.x - Dawntrail/MSQ/F-Living Memory/4952_A Knight of Alexandria.json @@ -131,14 +131,18 @@ }, "TerritoryId": 1192, "InteractionType": "WalkTo", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -16 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 16 + ] + } + } }, { "DataId": 1047904, diff --git a/QuestPaths/7.x - Dawntrail/Side Quests/Heritage Found/5155_History Reforged.json b/QuestPaths/7.x - Dawntrail/Side Quests/Heritage Found/5155_History Reforged.json index 7218094c..6d3f5179 100644 --- a/QuestPaths/7.x - Dawntrail/Side Quests/Heritage Found/5155_History Reforged.json +++ b/QuestPaths/7.x - Dawntrail/Side Quests/Heritage Found/5155_History Reforged.json @@ -29,14 +29,18 @@ "TerritoryId": 1191, "InteractionType": "WalkTo", "Fly": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -128 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + } + } }, { "DataId": 2014091, @@ -85,14 +89,18 @@ "TerritoryId": 1191, "InteractionType": "WalkTo", "Fly": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -32 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 32 + ] + } + } }, { "DataId": 2014093, diff --git a/QuestPaths/7.x - Dawntrail/Side Quests/Kozama'uka/5075_Water Colors.json b/QuestPaths/7.x - Dawntrail/Side Quests/Kozama'uka/5075_Water Colors.json index 4d81d887..002e4fad 100644 --- a/QuestPaths/7.x - Dawntrail/Side Quests/Kozama'uka/5075_Water Colors.json +++ b/QuestPaths/7.x - Dawntrail/Side Quests/Kozama'uka/5075_Water Colors.json @@ -31,17 +31,17 @@ "Fly": true, "SkipConditions": { "StepIf": { - "Flying": "Locked" + "Flying": "Locked", + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 32 + ] } - }, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -32 - ] + } }, { "DataId": 2014184, diff --git a/QuestPaths/7.x - Dawntrail/Side Quests/Living Memory/5175_The Canals of History.json b/QuestPaths/7.x - Dawntrail/Side Quests/Living Memory/5175_The Canals of History.json index 92ac2270..2a09e97d 100644 --- a/QuestPaths/7.x - Dawntrail/Side Quests/Living Memory/5175_The Canals of History.json +++ b/QuestPaths/7.x - Dawntrail/Side Quests/Living Memory/5175_The Canals of History.json @@ -31,17 +31,17 @@ "Fly": true, "SkipConditions": { "StepIf": { - "Flying": "Locked" + "Flying": "Locked", + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] } - }, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -128 - ] + } }, { "DataId": 2013901, diff --git a/QuestPaths/7.x - Dawntrail/Side Quests/Living Memory/5181_Leaving This Place for Good.json b/QuestPaths/7.x - Dawntrail/Side Quests/Living Memory/5181_Leaving This Place for Good.json index 8062590b..a77798f7 100644 --- a/QuestPaths/7.x - Dawntrail/Side Quests/Living Memory/5181_Leaving This Place for Good.json +++ b/QuestPaths/7.x - Dawntrail/Side Quests/Living Memory/5181_Leaving This Place for Good.json @@ -49,14 +49,18 @@ "TerritoryId": 1192, "InteractionType": "WalkTo", "Fly": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -32 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 32 + ] + } + } }, { "DataId": 2013916, @@ -86,14 +90,18 @@ "TerritoryId": 1192, "InteractionType": "WalkTo", "Fly": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -128 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + } + } }, { "DataId": 2013917, diff --git a/QuestPaths/7.x - Dawntrail/Side Quests/Shaaloani/5121_Taste of the Wilds.json b/QuestPaths/7.x - Dawntrail/Side Quests/Shaaloani/5121_Taste of the Wilds.json index 77ee6e04..f1414639 100644 --- a/QuestPaths/7.x - Dawntrail/Side Quests/Shaaloani/5121_Taste of the Wilds.json +++ b/QuestPaths/7.x - Dawntrail/Side Quests/Shaaloani/5121_Taste of the Wilds.json @@ -29,14 +29,18 @@ "TerritoryId": 1190, "InteractionType": "WalkTo", "Fly": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -128 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + } + } }, { "DataId": 1049284, @@ -64,14 +68,18 @@ }, "TerritoryId": 1190, "InteractionType": "WalkTo", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -64 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 64 + ] + } + } }, { "DataId": 1049275, diff --git a/QuestPaths/7.x - Dawntrail/Side Quests/Shaaloani/5132_Death by Popoto.json b/QuestPaths/7.x - Dawntrail/Side Quests/Shaaloani/5132_Death by Popoto.json index c16f3f0d..0c7e9530 100644 --- a/QuestPaths/7.x - Dawntrail/Side Quests/Shaaloani/5132_Death by Popoto.json +++ b/QuestPaths/7.x - Dawntrail/Side Quests/Shaaloani/5132_Death by Popoto.json @@ -31,17 +31,17 @@ "Fly": true, "SkipConditions": { "StepIf": { - "Flying": "Locked" + "Flying": "Locked", + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] } - }, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -128 - ] + } }, { "DataId": 2014051, diff --git a/QuestPaths/7.x - Dawntrail/Side Quests/Shaaloani/5142_Back on Stage.json b/QuestPaths/7.x - Dawntrail/Side Quests/Shaaloani/5142_Back on Stage.json index 92cf0fac..8a873f7a 100644 --- a/QuestPaths/7.x - Dawntrail/Side Quests/Shaaloani/5142_Back on Stage.json +++ b/QuestPaths/7.x - Dawntrail/Side Quests/Shaaloani/5142_Back on Stage.json @@ -46,16 +46,16 @@ "TerritoryId": 1190, "InteractionType": "WalkTo", "Fly": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -64 - ], "SkipConditions": { "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 64 + ], "Flying": "Locked" } } @@ -89,16 +89,16 @@ "TerritoryId": 1190, "InteractionType": "WalkTo", "Fly": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -128 - ], "SkipConditions": { "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ], "Flying": "Locked" } }, @@ -131,16 +131,16 @@ }, "TerritoryId": 1190, "InteractionType": "WalkTo", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -32 - ], "SkipConditions": { "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 32 + ], "Flying": "Locked" } }, diff --git a/QuestPaths/7.x - Dawntrail/Side Quests/Shaaloani/5148_Dig for Victory.json b/QuestPaths/7.x - Dawntrail/Side Quests/Shaaloani/5148_Dig for Victory.json index d764508d..836fb20f 100644 --- a/QuestPaths/7.x - Dawntrail/Side Quests/Shaaloani/5148_Dig for Victory.json +++ b/QuestPaths/7.x - Dawntrail/Side Quests/Shaaloani/5148_Dig for Victory.json @@ -126,16 +126,16 @@ "InteractionType": "WalkTo", "AetheryteShortcut": "Shaaloani - Hhusatahwi", "Fly": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -128 - ], "SkipConditions": { "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ], "Flying": "Locked" } }, diff --git a/QuestPaths/7.x - Dawntrail/Side Quests/Yak T'el/5097_Sick Day.json b/QuestPaths/7.x - Dawntrail/Side Quests/Yak T'el/5097_Sick Day.json index f7afd7b5..5ec0cff1 100644 --- a/QuestPaths/7.x - Dawntrail/Side Quests/Yak T'el/5097_Sick Day.json +++ b/QuestPaths/7.x - Dawntrail/Side Quests/Yak T'el/5097_Sick Day.json @@ -49,14 +49,18 @@ "TerritoryId": 1189, "InteractionType": "WalkTo", "Fly": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -128 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + } + } }, { "DataId": 1048995, diff --git a/QuestPaths/7.x - Dawntrail/Side Quests/Yak T'el/5098_To Forge in the Forest.json b/QuestPaths/7.x - Dawntrail/Side Quests/Yak T'el/5098_To Forge in the Forest.json index 1995c178..007b8687 100644 --- a/QuestPaths/7.x - Dawntrail/Side Quests/Yak T'el/5098_To Forge in the Forest.json +++ b/QuestPaths/7.x - Dawntrail/Side Quests/Yak T'el/5098_To Forge in the Forest.json @@ -45,14 +45,18 @@ "TerritoryId": 1189, "InteractionType": "WalkTo", "Fly": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -128 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + } + } }, { "DataId": 2014313, @@ -82,14 +86,18 @@ "TerritoryId": 1189, "InteractionType": "WalkTo", "Fly": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -64 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 64 + ] + } + } }, { "DataId": 2014314, @@ -119,14 +127,18 @@ "TerritoryId": 1189, "InteractionType": "WalkTo", "Fly": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -32 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 32 + ] + } + } }, { "DataId": 2014315, diff --git a/QuestPaths/7.x - Dawntrail/Side Quests/Yak T'el/5101_Two Hearts Aflutter.json b/QuestPaths/7.x - Dawntrail/Side Quests/Yak T'el/5101_Two Hearts Aflutter.json index ee0174ba..bfd9d8fb 100644 --- a/QuestPaths/7.x - Dawntrail/Side Quests/Yak T'el/5101_Two Hearts Aflutter.json +++ b/QuestPaths/7.x - Dawntrail/Side Quests/Yak T'el/5101_Two Hearts Aflutter.json @@ -29,14 +29,18 @@ "TerritoryId": 1189, "InteractionType": "WalkTo", "Fly": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -128 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + } + } }, { "DataId": 1051049, @@ -48,14 +52,18 @@ "StopDistance": 4, "TerritoryId": 1189, "InteractionType": "Interact", - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -128 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 128 + ] + } + } }, { "Position": { @@ -66,14 +74,18 @@ "TerritoryId": 1189, "InteractionType": "WalkTo", "Fly": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -32 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 32 + ] + } + } }, { "DataId": 1051051, @@ -102,14 +114,18 @@ "TerritoryId": 1189, "InteractionType": "WalkTo", "Fly": true, - "CompletionQuestVariablesFlags": [ - null, - null, - null, - null, - null, - -64 - ] + "SkipConditions": { + "StepIf": { + "CompletionQuestVariablesFlags": [ + null, + null, + null, + null, + null, + 64 + ] + } + } }, { "DataId": 1051050, diff --git a/QuestPaths/AssemblyQuestLoader.cs b/QuestPaths/AssemblyQuestLoader.cs index afc7ea83..3ef7df15 100644 --- a/QuestPaths/AssemblyQuestLoader.cs +++ b/QuestPaths/AssemblyQuestLoader.cs @@ -2,8 +2,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; -using System.Reflection; -using Questionable.Model.V1; +using Questionable.Model.Questing; namespace Questionable.QuestPaths; @@ -28,5 +27,6 @@ public static partial class AssemblyQuestLoader public static Stream QuestSchema => typeof(AssemblyQuestLoader).Assembly.GetManifestResourceStream("Questionable.QuestPaths.QuestSchema")!; + [SuppressMessage("ReSharper", "UnusedMember.Local")] private static void AddQuest(ushort questId, QuestRoot root) => _quests![questId] = root; } diff --git a/QuestPaths/QuestPaths.csproj b/QuestPaths/QuestPaths.csproj index bb2b3663..5d91a016 100644 --- a/QuestPaths/QuestPaths.csproj +++ b/QuestPaths/QuestPaths.csproj @@ -23,6 +23,7 @@ Questionable.QuestPaths.QuestSchema + @@ -39,4 +40,8 @@ + + + + diff --git a/QuestPaths/Reusable Paths.md b/QuestPaths/Reusable Paths.md index 719a95f5..7bbfa2f5 100644 --- a/QuestPaths/Reusable Paths.md +++ b/QuestPaths/Reusable Paths.md @@ -5,6 +5,21 @@ outside). This vastly improves the pathfinding performance, and avoids attempting to fly e.g. under the map or into the building that can sometimes be found as valid paths. +## Ul'dah + +```json + { + "Position": { + "X": -119.1183, + "Y": 3.7999938, + "Z": -104.33473 + }, + "TerritoryId": 130, + "InteractionType": "WalkTo", + "AetheryteShortcut": "Ul'dah", + "$": "Ul'dah Aetheryte to Immortal Flames" + } +``` ## Mor Dhona ```json diff --git a/QuestPaths/quest-v1.json b/QuestPaths/quest-v1.json index 60629b40..f18d33a7 100644 --- a/QuestPaths/quest-v1.json +++ b/QuestPaths/quest-v1.json @@ -154,7 +154,7 @@ }, "AetheryteShortcut": { "description": "The Aetheryte to teleport to (before moving)", - "$ref": "#/$defs/Aetheryte" + "$ref": "https://git.carvel.li/liza/Questionable/raw/branch/master/Questionable.Model/common-schema.json#/$defs/Aetheryte" }, "AethernetShortcut": { "type": "array", @@ -162,7 +162,7 @@ "minItems": 2, "maxItems": 2, "items": { - "$ref": "#/$defs/AethernetShard" + "$ref": "https://git.carvel.li/liza/Questionable/raw/branch/master/Questionable.Model/common-schema.json#/$defs/AethernetShard" } }, "ItemId": { @@ -182,6 +182,9 @@ "Never": { "type": "boolean" }, + "CompletionQuestVariablesFlags": { + "$ref": "https://git.carvel.li/liza/Questionable/raw/branch/master/Questionable.Model/common-schema.json#/$defs/CompletionFlags" + }, "Flying": { "type": "string", "enum": [ @@ -237,7 +240,8 @@ "WakingSandsMainArea" ] } - } + }, + "additionalProperties": false }, "AetheryteShortcutIf": { "type": "object", @@ -248,7 +252,8 @@ "InSameTerritory": { "type": "boolean" } - } + }, + "additionalProperties": false }, "AethernetShortcutIf": { "type": "object", @@ -259,13 +264,14 @@ "InSameTerritory": { "type": "boolean" } - } + }, + "additionalProperties": false } }, "additionalProperties": false }, "CompletionQuestVariablesFlags": { - "$ref": "#/$defs/CompletionFlags" + "$ref": "https://git.carvel.li/liza/Questionable/raw/branch/master/Questionable.Model/common-schema.json#/$defs/CompletionFlags" }, "RequiredQuestVariables": { "type": "array", @@ -305,6 +311,30 @@ } } }, + "RequiredGatheredItems": { + "type": "array", + "items": { + "type": "object", + "properties": { + "ItemId": { + "type": "number" + }, + "ItemCount": { + "type": "number", + "exclusiveMinimum": 0 + }, + "Collectability": { + "type": "number", + "minimum": 0, + "maximum": 1000 + } + }, + "required": [ + "ItemId", + "ItemCount" + ] + } + }, "DelaySecondsAtStart": { "description": "Time to wait before starting", "type": [ @@ -377,7 +407,7 @@ "then": { "properties": { "Aetheryte": { - "$ref": "#/$defs/Aetheryte" + "$ref": "https://git.carvel.li/liza/Questionable/raw/branch/master/Questionable.Model/common-schema.json#/$defs/Aetheryte" }, "DataId": { "type": "null" @@ -409,7 +439,7 @@ "then": { "properties": { "AethernetShard": { - "$ref": "#/$defs/AethernetShard" + "$ref": "https://git.carvel.li/liza/Questionable/raw/branch/master/Questionable.Model/common-schema.json#/$defs/AethernetShard" }, "DataId": { "type": "null" @@ -501,7 +531,7 @@ "type": "integer" }, "CompletionQuestVariablesFlags": { - "$ref": "#/$defs/CompletionFlags" + "$ref": "https://git.carvel.li/liza/Questionable/raw/branch/master/Questionable.Model/common-schema.json#/$defs/CompletionFlags" }, "IgnoreQuestMarker": { "type": "boolean" @@ -736,6 +766,7 @@ "type": "string", "description": "The action to use", "enum": [ + "Cure", "Esuna", "Physick", "Buffet", @@ -1066,274 +1097,5 @@ "QuestSequence", "Author" ], - "additionalProperties": false, - "$defs": { - "Aetheryte": { - "type": "string", - "enum": [ - "Gridania", - "Central Shroud - Bentbranch Meadows", - "East Shroud - Hawthorne Hut", - "South Shroud - Quarrymill", - "South Shroud - Camp Tranquil", - "North Shroud - Fallgourd Float", - "Ul'dah", - "Western Thanalan - Horizon", - "Central Thanalan - Black Brush Station", - "Eastern Thanalan - Camp Drybone", - "Southern Thanalan - Little Ala Mhigo", - "Southern Thanalan - Forgotten Springs", - "Northern Thanalan - Camp Bluefog", - "Northern Thanalan - Ceruleum Processing Plant", - "Limsa Lominsa", - "Middle La Noscea - Summerford Farms", - "Lower La Noscea - Moraby Drydocks", - "Eastern La Noscea - Costa Del Sol", - "Eastern La Noscea - Wineport", - "Western La Noscea - Swiftperch", - "Western La Noscea - Aleport", - "Upper La Noscea - Camp Bronze Lake", - "Outer La Noscea - Camp Overlook", - "Coerthas Central Highlands - Camp Dragonhead", - "Mor Dhona", - "Gold Saucer", - "Wolves' Den Pier", - "Ishgard", - "Idyllshire", - "Coerthas Western Highlands - Falcon's Nest", - "The Sea of Clouds - Camp Cloudtop", - "The Sea of Clouds - Ok' Zundu", - "Azys Lla - Helix", - "The Dravanian Forelands - Tailfeather", - "The Dravanian Forelands - Anyx Trine", - "The Churning Mists - Moghome", - "The Churning Mists - Zenith", - "Rhalgr's Reach", - "Fringes - Castrum Oriens", - "Fringes - Peering Stones", - "Peaks - Ala Gannha", - "Peaks - Ala Ghiri", - "Lochs - Porta Praetoria", - "Lochs - Ala Mhigan Quarter", - "Kugane", - "Ruby Sea - Tamamizu", - "Ruby Sea - Onokoro", - "Yanxia - Namai", - "Yanxia - House of the Fierce", - "Azim Steppe - Reunion", - "Azim Steppe - Dawn Throne", - "Azim Steppe - Dhoro Iloh", - "Doman Enclave", - "Crystarium", - "Eulmore", - "Lakeland - Fort Jobb", - "Lakeland - Ostall Imperative", - "Kholusia - Stilltide", - "Kholusia - Wright", - "Kholusia - Tomra", - "Amh Araeng - Mord Souq", - "Amh Araeng - Inn at Journey's Head", - "Amh Araeng - Twine", - "Rak'tika - Slitherbough", - "Rak'tika - Fanow", - "Il Mheg - Lydha Lran", - "Il Mheg - Pia Enni", - "Il Mheg - Wolekdorf", - "Tempest - Ondo Cups", - "Tempest - Macarenses Angle", - "Old Sharlayan", - "Radz-at-Han", - "Labyrinthos - Archeion", - "Labyrinthos - Sharlayan Hamlet", - "Labyrinthos - Aporia", - "Thavnair - Yedlihmad", - "Thavnair - Great Work", - "Thavnair - Palaka's Stand", - "Garlemald - Camp Broken Glass", - "Garlemald - Tertium", - "Mare Lamentorum - Sinus Lacrimarum", - "Mare Lamentorum - Bestways Burrow", - "Elpis - Anagnorisis", - "Elpis - Twelve Wonders", - "Elpis - Poieten Oikos", - "Ultima Thule - Reah Tahra", - "Ultima Thule - Abode of the Ea", - "Ultima Thule - Base Omicron", - "Tuliyollal", - "Solution Nine", - "Urqopacha - Wachunpelo", - "Urqopacha - Worlar's Echo", - "Kozama'uka - Ok'hanu", - "Kozama'uka - Many Fires", - "Kozama'uka - Earthenshire", - "Yak T'el - Iq Br'aax", - "Yak T'el - Mamook", - "Shaaloani - Hhusatahwi", - "Shaaloani - Sheshenewezi Springs", - "Shaaloani - Mehwahhetsoan", - "Heritage Found - Yyasulani Station", - "Heritage Found - The Outskirts", - "Heritage Found - Electrope Strike", - "Living Memory - Leynode Mnemo", - "Living Memory - Leynode Pyro", - "Living Memory - Leynode Aero" - ] - }, - "AethernetShard": { - "type": "string", - "enum": [ - "[Gridania] Aetheryte Plaza", - "[Gridania] Archers' Guild", - "[Gridania] Leatherworkers' Guild & Shaded Bower", - "[Gridania] Lancers' Guild", - "[Gridania] Conjurers' Guild", - "[Gridania] Botanists' Guild", - "[Gridania] Mih Khetto's Amphitheatre", - "[Gridania] Blue Badger Gate (Central Shroud)", - "[Gridania] Yellow Serpent Gate (North Shroud)", - "[Gridania] White Wolf Gate (Central Shroud)", - "[Gridania] Airship Landing", - "[Ul'dah] Aetheryte Plaza", - "[Ul'dah] Adventurers' Guild", - "[Ul'dah] Thaumaturges' Guild", - "[Ul'dah] Gladiators' Guild", - "[Ul'dah] Miners' Guild", - "[Ul'dah] Weavers' Guild", - "[Ul'dah] Goldsmiths' Guild", - "[Ul'dah] Sapphire Avenue Exchange", - "[Ul'dah] Alchemists' Guild", - "[Ul'dah] Gate of the Sultana (Western Thanalan)", - "[Ul'dah] Gate of Nald (Central Thanalan)", - "[Ul'dah] Gate of Thal (Central Thanalan)", - "[Ul'dah] The Chamber of Rule", - "[Ul'dah] Airship Landing", - "[Limsa Lominsa] Aetheryte Plaza", - "[Limsa Lominsa] Arcanists' Guild", - "[Limsa Lominsa] Fishermens' Guild", - "[Limsa Lominsa] Hawkers' Alley", - "[Limsa Lominsa] The Aftcastle", - "[Limsa Lominsa] Culinarians' Guild", - "[Limsa Lominsa] Marauders' Guild", - "[Limsa Lominsa] Zephyr Gate (Middle La Noscea)", - "[Limsa Lominsa] Tempest Gate (Lower La Noscea)", - "[Limsa Lominsa] Airship Landing", - "[Ishgard] Aetheryte Plaza", - "[Ishgard] The Forgotten Knight", - "[Ishgard] Skysteel Manufactory", - "[Ishgard] The Brume", - "[Ishgard] Athenaeum Astrologicum", - "[Ishgard] The Jeweled Crozier", - "[Ishgard] Saint Reymanaud's Cathedral", - "[Ishgard] The Tribunal", - "[Ishgard] The Last Vigil", - "[Ishgard] The Gates of Judgement (Coerthas Central Highlands)", - "[Idyllshire] Aetheryte Plaza", - "[Idyllshire] West Idyllshire", - "[Idyllshire] Prologue Gate (Western Hinterlands)", - "[Idyllshire] Epilogue Gate (Eastern Hinterlands)", - "[Rhalgr's Reach] Aetheryte Plaza", - "[Rhalgr's Reach] Western Rhalgr's Reach", - "[Rhalgr's Reach] Northeastern Rhalgr's Reach", - "[Rhalgr's Reach] Fringes Gate", - "[Rhalgr's Reach] Peaks Gate", - "[Kugane] Aetheryte Plaza", - "[Kugane] Shiokaze Hostelry", - "[Kugane] Pier #1", - "[Kugane] Thavnairian Consulate", - "[Kugane] Kogane Dori Markets", - "[Kugane] Bokairo Inn", - "[Kugane] The Ruby Bazaar", - "[Kugane] Sekiseigumi Barracks", - "[Kugane] Rakuza District", - "[Kugane] The Ruby Price", - "[Kugane] Airship Landing", - "[Crystarium] Aetheryte Plaza", - "[Crystarium] Musica Universalis Markets", - "[Crystarium] Temenos Rookery", - "[Crystarium] The Dossal Gate", - "[Crystarium] The Pendants", - "[Crystarium] The Amaro Launch", - "[Crystarium] The Crystalline Mean", - "[Crystarium] The Cabinet of Curiosity", - "[Crystarium] Tessellation (Lakeland)", - "[Eulmore] Aetheryte Plaza", - "[Eulmore] Southeast Derelicts", - "[Eulmore] Nightsoil Pots", - "[Eulmore] The Glory Gate", - "[Eulmore] The Mainstay", - "[Eulmore] The Path to Glory (Kholusia)", - "[Old Sharlayan] Aetheryte Plaza", - "[Old Sharlayan] The Studium", - "[Old Sharlayan] The Baldesion Annex", - "[Old Sharlayan] The Rostra", - "[Old Sharlayan] The Leveilleur Estate", - "[Old Sharlayan] Journey's End", - "[Old Sharlayan] Scholar's Harbor", - "[Old Sharlayan] The Hall of Artifice (Labyrinthos)", - "[Radz-at-Han] Aetheryte Plaza", - "[Radz-at-Han] Meghaduta", - "[Radz-at-Han] Ruveydah Fibers", - "[Radz-at-Han] Airship Landing", - "[Radz-at-Han] Alzadaal's Peace", - "[Radz-at-Han] Hall of the Radiant Host", - "[Radz-at-Han] Mehryde's Meyhane", - "[Radz-at-Han] Kama", - "[Radz-at-Han] The High Crucible of Al-Kimiya", - "[Radz-at-Han] The Gate of First Sight (Thavnair)", - "[Tuliyollal] Aetheryte Plaza", - "[Tuliyollal] Dirigible Landing", - "[Tuliyollal] The Resplendent Quarter", - "[Tuliyollal] The For'ard Cabins", - "[Tuliyollal] Bayside Bevy Marketplace", - "[Tuliyollal] Vollok Shoonsa", - "[Tuliyollal] Wachumeqimeqi", - "[Tuliyollal] Brightploom Post", - "[Tuliyollal] Arch of the Dawn (Urqopacha)", - "[Tuliyollal] Arch of the Dawn (Kozama'uka)", - "[Tuliyollal] Ihuykatumu (Kozama'uka)", - "[Tuliyollal] Dirigible Landing (Yak T'el)", - "[Tuliyollal] Xak Tural Skygate (Shaaloani)", - "[Solution Nine] Aetheryte Plaza", - "[Solution Nine] Information Center", - "[Solution Nine] True Vue", - "[Solution Nine] Neon Stein", - "[Solution Nine] The Arcadion", - "[Solution Nine] Resolution", - "[Solution Nine] Nexus Arcade", - "[Solution Nine] Residential Sector", - "[Solution Nine] Scanning Port Nine (Heritage Found)" - ] - }, - "CompletionFlags": { - "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": { - "type": [ - "integer", - "null" - ], - "enum": [ - null, - 1, - 2, - 4, - 8, - 16, - 32, - 64, - 128, - -1, - -2, - -4, - -8, - -16, - -32, - -64, - -128 - ] - }, - "minItems": 6, - "maxItems": 6 - } - } + "additionalProperties": false } diff --git a/Questionable.Model/AssemblyModelLoader.cs b/Questionable.Model/AssemblyModelLoader.cs new file mode 100644 index 00000000..d167cb25 --- /dev/null +++ b/Questionable.Model/AssemblyModelLoader.cs @@ -0,0 +1,9 @@ +using System.IO; + +namespace Questionable.Model; + +public static class AssemblyModelLoader +{ + public static Stream CommonSchema => + typeof(AssemblyModelLoader).Assembly.GetManifestResourceStream("Questionable.Model.CommonSchema")!; +} diff --git a/Questionable.Model/V1/Converter/AetheryteConverter.cs b/Questionable.Model/Common/Converter/AetheryteConverter.cs similarity index 98% rename from Questionable.Model/V1/Converter/AetheryteConverter.cs rename to Questionable.Model/Common/Converter/AetheryteConverter.cs index ab0dd549..3887305e 100644 --- a/Questionable.Model/V1/Converter/AetheryteConverter.cs +++ b/Questionable.Model/Common/Converter/AetheryteConverter.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; +using Questionable.Model.Common.Converter; -namespace Questionable.Model.V1.Converter; +namespace Questionable.Model.Common.Converter; public sealed class AetheryteConverter() : EnumConverter(Values) { diff --git a/Questionable.Model/V1/Converter/EnumConverter.cs b/Questionable.Model/Common/Converter/EnumConverter.cs similarity index 96% rename from Questionable.Model/V1/Converter/EnumConverter.cs rename to Questionable.Model/Common/Converter/EnumConverter.cs index 3ed1267b..3708e28c 100644 --- a/Questionable.Model/V1/Converter/EnumConverter.cs +++ b/Questionable.Model/Common/Converter/EnumConverter.cs @@ -5,7 +5,7 @@ using System.Linq; using System.Text.Json; using System.Text.Json.Serialization; -namespace Questionable.Model.V1.Converter; +namespace Questionable.Model.Common.Converter; public abstract class EnumConverter : JsonConverter where T : Enum diff --git a/Questionable.Model/V1/Converter/StringListOrValueConverter.cs b/Questionable.Model/Common/Converter/StringListOrValueConverter.cs similarity index 86% rename from Questionable.Model/V1/Converter/StringListOrValueConverter.cs rename to Questionable.Model/Common/Converter/StringListOrValueConverter.cs index d3e6ea6f..ebdec1cf 100644 --- a/Questionable.Model/V1/Converter/StringListOrValueConverter.cs +++ b/Questionable.Model/Common/Converter/StringListOrValueConverter.cs @@ -3,11 +3,11 @@ using System.Collections.Generic; using System.Text.Json; using System.Text.Json.Serialization; -namespace Questionable.Model.V1.Converter; +namespace Questionable.Model.Common.Converter; public sealed class StringListOrValueConverter : JsonConverter> { - public override List? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override List Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (reader.TokenType == JsonTokenType.String) return [reader.GetString()!]; diff --git a/Questionable.Model/V1/Converter/VectorConverter.cs b/Questionable.Model/Common/Converter/VectorConverter.cs similarity index 91% rename from Questionable.Model/V1/Converter/VectorConverter.cs rename to Questionable.Model/Common/Converter/VectorConverter.cs index f75ffe6a..868e0216 100644 --- a/Questionable.Model/V1/Converter/VectorConverter.cs +++ b/Questionable.Model/Common/Converter/VectorConverter.cs @@ -3,7 +3,7 @@ using System.Numerics; using System.Text.Json; using System.Text.Json.Serialization; -namespace Questionable.Model.V1.Converter; +namespace Questionable.Model.Common.Converter; public sealed class VectorConverter : JsonConverter { @@ -57,8 +57,8 @@ public sealed class VectorConverter : JsonConverter { writer.WriteStartObject(); writer.WriteNumber(nameof(Vector3.X), value.X); - writer.WriteNumber(nameof(Vector3.Y), value.X); - writer.WriteNumber(nameof(Vector3.Z), value.X); + writer.WriteNumber(nameof(Vector3.Y), value.Y); + writer.WriteNumber(nameof(Vector3.Z), value.Z); writer.WriteEndObject(); } } diff --git a/Questionable.Model/V1/EAetheryteLocation.cs b/Questionable.Model/Common/EAetheryteLocation.cs similarity index 98% rename from Questionable.Model/V1/EAetheryteLocation.cs rename to Questionable.Model/Common/EAetheryteLocation.cs index 1ddde305..a00f7b51 100644 --- a/Questionable.Model/V1/EAetheryteLocation.cs +++ b/Questionable.Model/Common/EAetheryteLocation.cs @@ -1,7 +1,8 @@ using System.Text.Json.Serialization; -using Questionable.Model.V1.Converter; +using Questionable.Model.Common.Converter; +using Questionable.Model.Questing.Converter; -namespace Questionable.Model.V1; +namespace Questionable.Model.Common; [JsonConverter(typeof(AetheryteConverter))] public enum EAetheryteLocation diff --git a/Questionable.Model/ExpansionVersion.cs b/Questionable.Model/ExpansionVersion.cs new file mode 100644 index 00000000..fe92f160 --- /dev/null +++ b/Questionable.Model/ExpansionVersion.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; + +namespace Questionable.Model; + +public static class ExpansionData +{ + public static IReadOnlyDictionary ExpansionFolders = new Dictionary() + { + { 0, "2.x - A Realm Reborn" }, + { 1, "3.x - Heavensward" }, + { 2, "4.x - Stormblood" }, + { 3, "5.x - Shadowbringers" }, + { 4, "6.x - Endwalker" }, + { 5, "7.x - Dawntrail" } + }; +} diff --git a/Questionable.Model/Gathering/GatheringLocation.cs b/Questionable.Model/Gathering/GatheringLocation.cs new file mode 100644 index 00000000..badf7c2d --- /dev/null +++ b/Questionable.Model/Gathering/GatheringLocation.cs @@ -0,0 +1,28 @@ +using System; +using System.Numerics; +using System.Text.Json.Serialization; +using Questionable.Model.Common.Converter; + +namespace Questionable.Model.Gathering; + +public sealed class GatheringLocation +{ + [JsonIgnore] + public Guid InternalId { get; } = Guid.NewGuid(); + + [JsonConverter(typeof(VectorConverter))] + public Vector3 Position { get; set; } + + public int? MinimumAngle { get; set; } + public int? MaximumAngle { get; set; } + public float? MinimumDistance { get; set; } + public float? MaximumDistance { get; set; } + + public bool IsCone() + { + return MinimumAngle != null && MaximumAngle != null; + } + + public float CalculateMinimumDistance() => MinimumDistance ?? 1f; + public float CalculateMaximumDistance() => MaximumDistance ?? 3f; +} diff --git a/Questionable.Model/Gathering/GatheringNode.cs b/Questionable.Model/Gathering/GatheringNode.cs new file mode 100644 index 00000000..a88f53eb --- /dev/null +++ b/Questionable.Model/Gathering/GatheringNode.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace Questionable.Model.Gathering; + +public sealed class GatheringNode +{ + public uint DataId { get; set; } + + public List Locations { get; set; } = []; +} diff --git a/Questionable.Model/Gathering/GatheringNodeGroup.cs b/Questionable.Model/Gathering/GatheringNodeGroup.cs new file mode 100644 index 00000000..7922d625 --- /dev/null +++ b/Questionable.Model/Gathering/GatheringNodeGroup.cs @@ -0,0 +1,8 @@ +using System.Collections.Generic; + +namespace Questionable.Model.Gathering; + +public sealed class GatheringNodeGroup +{ + public List Nodes { get; set; } = []; +} diff --git a/Questionable.Model/Gathering/GatheringRoot.cs b/Questionable.Model/Gathering/GatheringRoot.cs new file mode 100644 index 00000000..5670482e --- /dev/null +++ b/Questionable.Model/Gathering/GatheringRoot.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Questionable.Model.Common; +using Questionable.Model.Common.Converter; + +namespace Questionable.Model.Gathering; + +public sealed class GatheringRoot +{ + [JsonConverter(typeof(StringListOrValueConverter))] + public List Author { get; set; } = []; + public ushort TerritoryId { get; set; } + + [JsonConverter(typeof(AetheryteConverter))] + public EAetheryteLocation? AetheryteShortcut { get; set; } + + public List Groups { get; set; } = []; +} diff --git a/Questionable.Model/GatheringMath.cs b/Questionable.Model/GatheringMath.cs new file mode 100644 index 00000000..46105e1a --- /dev/null +++ b/Questionable.Model/GatheringMath.cs @@ -0,0 +1,53 @@ +using System; +using System.Numerics; +using Questionable.Model.Gathering; + +namespace GatheringPathRenderer; + +public static class GatheringMath +{ + private static readonly Random RNG = new Random(); + + public static (Vector3, int, float) CalculateLandingLocation(GatheringLocation location) + { + int degrees; + if (location.IsCone()) + degrees = RNG.Next( + location.MinimumAngle.GetValueOrDefault(), + location.MaximumAngle.GetValueOrDefault()); + else + degrees = RNG.Next(0, 360); + + float range = RNG.Next( + (int)(location.CalculateMinimumDistance() * 100), + (int)((location.CalculateMaximumDistance() - location.CalculateMinimumDistance()) * 100)) / 100f; + return (CalculateLandingLocation(location.Position, degrees, range), degrees, range); + } + + public static Vector3 CalculateLandingLocation(GatheringLocation location, float angleScale, float rangeScale) + { + int degrees; + if (location.IsCone()) + degrees = location.MinimumAngle.GetValueOrDefault() + + (int)(angleScale * (location.MaximumAngle.GetValueOrDefault() - + location.MinimumAngle.GetValueOrDefault())); + else + degrees = (int)(rangeScale * 360); + + float range = + location.CalculateMinimumDistance() + + rangeScale * (location.CalculateMaximumDistance() - location.CalculateMinimumDistance()); + return CalculateLandingLocation(location.Position, degrees, range); + } + + private static Vector3 CalculateLandingLocation(Vector3 position, int degrees, float range) + { + float rad = -(float)(degrees * Math.PI / 180); + return new Vector3 + { + X = position.X + range * (float)Math.Sin(rad), + Y = position.Y, + Z = position.Z + range * (float)Math.Cos(rad) + }; + } +} diff --git a/Questionable.Model/V1/AethernetShortcut.cs b/Questionable.Model/Questing/AethernetShortcut.cs similarity index 66% rename from Questionable.Model/V1/AethernetShortcut.cs rename to Questionable.Model/Questing/AethernetShortcut.cs index 40514b58..3d046c44 100644 --- a/Questionable.Model/V1/AethernetShortcut.cs +++ b/Questionable.Model/Questing/AethernetShortcut.cs @@ -1,7 +1,8 @@ using System.Text.Json.Serialization; -using Questionable.Model.V1.Converter; +using Questionable.Model.Common; +using Questionable.Model.Questing.Converter; -namespace Questionable.Model.V1; +namespace Questionable.Model.Questing; [JsonConverter(typeof(AethernetShortcutConverter))] public sealed class AethernetShortcut diff --git a/Questionable.Model/V1/ChatMessage.cs b/Questionable.Model/Questing/ChatMessage.cs similarity index 75% rename from Questionable.Model/V1/ChatMessage.cs rename to Questionable.Model/Questing/ChatMessage.cs index 71721380..b0272135 100644 --- a/Questionable.Model/V1/ChatMessage.cs +++ b/Questionable.Model/Questing/ChatMessage.cs @@ -1,4 +1,4 @@ -namespace Questionable.Model.V1; +namespace Questionable.Model.Questing; public sealed class ChatMessage { diff --git a/Questionable.Model/V1/ComplexCombatData.cs b/Questionable.Model/Questing/ComplexCombatData.cs similarity index 76% rename from Questionable.Model/V1/ComplexCombatData.cs rename to Questionable.Model/Questing/ComplexCombatData.cs index a408a29f..5f85c243 100644 --- a/Questionable.Model/V1/ComplexCombatData.cs +++ b/Questionable.Model/Questing/ComplexCombatData.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace Questionable.Model.V1; +namespace Questionable.Model.Questing; public sealed class ComplexCombatData { @@ -14,6 +14,6 @@ public sealed class ComplexCombatData /// public uint? RewardItemId { get; set; } public int? RewardItemCount { get; set; } - public IList CompletionQuestVariablesFlags { get; set; } = new List(); + public IList CompletionQuestVariablesFlags { get; set; } = new List(); public bool IgnoreQuestMarker { get; set; } } diff --git a/Questionable.Model/V1/Converter/ActionConverter.cs b/Questionable.Model/Questing/Converter/ActionConverter.cs similarity index 81% rename from Questionable.Model/V1/Converter/ActionConverter.cs rename to Questionable.Model/Questing/Converter/ActionConverter.cs index 67a32a8d..7e62c011 100644 --- a/Questionable.Model/V1/Converter/ActionConverter.cs +++ b/Questionable.Model/Questing/Converter/ActionConverter.cs @@ -1,11 +1,13 @@ using System.Collections.Generic; +using Questionable.Model.Common.Converter; -namespace Questionable.Model.V1.Converter; +namespace Questionable.Model.Questing.Converter; public sealed class ActionConverter() : EnumConverter(Values) { private static readonly Dictionary Values = new() { + { EAction.Cure, "Cure" }, { EAction.Esuna, "Esuna" }, { EAction.Physick, "Physick" }, { EAction.Buffet, "Buffet" }, diff --git a/Questionable.Model/V1/Converter/AethernetShardConverter.cs b/Questionable.Model/Questing/Converter/AethernetShardConverter.cs similarity index 98% rename from Questionable.Model/V1/Converter/AethernetShardConverter.cs rename to Questionable.Model/Questing/Converter/AethernetShardConverter.cs index bfb259c2..bd07bc39 100644 --- a/Questionable.Model/V1/Converter/AethernetShardConverter.cs +++ b/Questionable.Model/Questing/Converter/AethernetShardConverter.cs @@ -1,6 +1,8 @@ using System.Collections.Generic; +using Questionable.Model.Common; +using Questionable.Model.Common.Converter; -namespace Questionable.Model.V1.Converter; +namespace Questionable.Model.Questing.Converter; public sealed class AethernetShardConverter() : EnumConverter(Values) { diff --git a/Questionable.Model/V1/Converter/AethernetShortcutConverter.cs b/Questionable.Model/Questing/Converter/AethernetShortcutConverter.cs similarity index 95% rename from Questionable.Model/V1/Converter/AethernetShortcutConverter.cs rename to Questionable.Model/Questing/Converter/AethernetShortcutConverter.cs index 8b90769f..d920d882 100644 --- a/Questionable.Model/V1/Converter/AethernetShortcutConverter.cs +++ b/Questionable.Model/Questing/Converter/AethernetShortcutConverter.cs @@ -3,8 +3,9 @@ using System.Collections.Generic; using System.Linq; using System.Text.Json; using System.Text.Json.Serialization; +using Questionable.Model.Common; -namespace Questionable.Model.V1.Converter; +namespace Questionable.Model.Questing.Converter; public sealed class AethernetShortcutConverter : JsonConverter { diff --git a/Questionable.Model/V1/Converter/DialogueChoiceTypeConverter.cs b/Questionable.Model/Questing/Converter/DialogueChoiceTypeConverter.cs similarity index 77% rename from Questionable.Model/V1/Converter/DialogueChoiceTypeConverter.cs rename to Questionable.Model/Questing/Converter/DialogueChoiceTypeConverter.cs index ea832cac..cd66ca9b 100644 --- a/Questionable.Model/V1/Converter/DialogueChoiceTypeConverter.cs +++ b/Questionable.Model/Questing/Converter/DialogueChoiceTypeConverter.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; +using Questionable.Model.Common.Converter; -namespace Questionable.Model.V1.Converter; +namespace Questionable.Model.Questing.Converter; public sealed class DialogueChoiceTypeConverter() : EnumConverter(Values) { diff --git a/Questionable.Model/V1/Converter/EmoteConverter.cs b/Questionable.Model/Questing/Converter/EmoteConverter.cs similarity index 91% rename from Questionable.Model/V1/Converter/EmoteConverter.cs rename to Questionable.Model/Questing/Converter/EmoteConverter.cs index 8a327517..20c97694 100644 --- a/Questionable.Model/V1/Converter/EmoteConverter.cs +++ b/Questionable.Model/Questing/Converter/EmoteConverter.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; +using Questionable.Model.Common.Converter; -namespace Questionable.Model.V1.Converter; +namespace Questionable.Model.Questing.Converter; public sealed class EmoteConverter() : EnumConverter(Values) { diff --git a/Questionable.Model/V1/Converter/EnemySpawnTypeConverter.cs b/Questionable.Model/Questing/Converter/EnemySpawnTypeConverter.cs similarity index 83% rename from Questionable.Model/V1/Converter/EnemySpawnTypeConverter.cs rename to Questionable.Model/Questing/Converter/EnemySpawnTypeConverter.cs index 5c5de532..e97faa4f 100644 --- a/Questionable.Model/V1/Converter/EnemySpawnTypeConverter.cs +++ b/Questionable.Model/Questing/Converter/EnemySpawnTypeConverter.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; +using Questionable.Model.Common.Converter; -namespace Questionable.Model.V1.Converter; +namespace Questionable.Model.Questing.Converter; public sealed class EnemySpawnTypeConverter() : EnumConverter(Values) { diff --git a/Questionable.Model/V1/Converter/ExcelRefConverter.cs b/Questionable.Model/Questing/Converter/ExcelRefConverter.cs similarity index 95% rename from Questionable.Model/V1/Converter/ExcelRefConverter.cs rename to Questionable.Model/Questing/Converter/ExcelRefConverter.cs index 06ba3ff1..03a8168e 100644 --- a/Questionable.Model/V1/Converter/ExcelRefConverter.cs +++ b/Questionable.Model/Questing/Converter/ExcelRefConverter.cs @@ -2,7 +2,7 @@ using System.Text.Json; using System.Text.Json.Serialization; -namespace Questionable.Model.V1.Converter; +namespace Questionable.Model.Questing.Converter; public sealed class ExcelRefConverter : JsonConverter { diff --git a/Questionable.Model/V1/Converter/InteractionTypeConverter.cs b/Questionable.Model/Questing/Converter/InteractionTypeConverter.cs similarity index 93% rename from Questionable.Model/V1/Converter/InteractionTypeConverter.cs rename to Questionable.Model/Questing/Converter/InteractionTypeConverter.cs index 08be6ff6..23f0366e 100644 --- a/Questionable.Model/V1/Converter/InteractionTypeConverter.cs +++ b/Questionable.Model/Questing/Converter/InteractionTypeConverter.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; +using Questionable.Model.Common.Converter; -namespace Questionable.Model.V1.Converter; +namespace Questionable.Model.Questing.Converter; public sealed class InteractionTypeConverter() : EnumConverter(Values) { diff --git a/Questionable.Model/V1/Converter/JumpTypeConverter.cs b/Questionable.Model/Questing/Converter/JumpTypeConverter.cs similarity index 76% rename from Questionable.Model/V1/Converter/JumpTypeConverter.cs rename to Questionable.Model/Questing/Converter/JumpTypeConverter.cs index 94c8ea2b..95bb0846 100644 --- a/Questionable.Model/V1/Converter/JumpTypeConverter.cs +++ b/Questionable.Model/Questing/Converter/JumpTypeConverter.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; +using Questionable.Model.Common.Converter; -namespace Questionable.Model.V1.Converter; +namespace Questionable.Model.Questing.Converter; public sealed class JumpTypeConverter() : EnumConverter(Values) { diff --git a/Questionable.Model/V1/Converter/LockedSkipConditionConverter.cs b/Questionable.Model/Questing/Converter/LockedSkipConditionConverter.cs similarity index 78% rename from Questionable.Model/V1/Converter/LockedSkipConditionConverter.cs rename to Questionable.Model/Questing/Converter/LockedSkipConditionConverter.cs index 5d53058e..72eb9dec 100644 --- a/Questionable.Model/V1/Converter/LockedSkipConditionConverter.cs +++ b/Questionable.Model/Questing/Converter/LockedSkipConditionConverter.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; +using Questionable.Model.Common.Converter; -namespace Questionable.Model.V1.Converter; +namespace Questionable.Model.Questing.Converter; public sealed class LockedSkipConditionConverter() : EnumConverter(Values) { diff --git a/Questionable.Model/V1/Converter/QuestWorkConfigConverter.cs b/Questionable.Model/Questing/Converter/QuestWorkConfigConverter.cs similarity index 81% rename from Questionable.Model/V1/Converter/QuestWorkConfigConverter.cs rename to Questionable.Model/Questing/Converter/QuestWorkConfigConverter.cs index 9f6b0e7e..b6cccc46 100644 --- a/Questionable.Model/V1/Converter/QuestWorkConfigConverter.cs +++ b/Questionable.Model/Questing/Converter/QuestWorkConfigConverter.cs @@ -2,7 +2,7 @@ using System.Text.Json; using System.Text.Json.Serialization; -namespace Questionable.Model.V1.Converter; +namespace Questionable.Model.Questing.Converter; public sealed class QuestWorkConfigConverter : JsonConverter { @@ -15,6 +15,7 @@ public sealed class QuestWorkConfigConverter : JsonConverter throw new JsonException(); byte? high = null, low = null; + EQuestWorkMode mode = EQuestWorkMode.Bitwise; while (reader.Read()) { switch (reader.TokenType) @@ -34,6 +35,10 @@ public sealed class QuestWorkConfigConverter : JsonConverter low = reader.GetByte(); break; + case nameof(QuestWorkValue.Mode): + mode = new QuestWorkModeConverter().Read(ref reader, typeof(EQuestWorkMode), options); + break; + default: throw new JsonException(); } @@ -41,7 +46,7 @@ public sealed class QuestWorkConfigConverter : JsonConverter break; case JsonTokenType.EndObject: - return new QuestWorkValue(high, low); + return new QuestWorkValue(high, low, mode); default: throw new JsonException(); diff --git a/Questionable.Model/Questing/Converter/QuestWorkModeConverter.cs b/Questionable.Model/Questing/Converter/QuestWorkModeConverter.cs new file mode 100644 index 00000000..322d6a19 --- /dev/null +++ b/Questionable.Model/Questing/Converter/QuestWorkModeConverter.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using Questionable.Model.Common.Converter; + +namespace Questionable.Model.Questing.Converter; + +public sealed class QuestWorkModeConverter() : EnumConverter(Values) +{ + private static readonly Dictionary Values = new() + { + { EQuestWorkMode.Bitwise, "Bitwise" }, + { EQuestWorkMode.Exact, "Exact" }, + }; +} diff --git a/Questionable.Model/V1/Converter/SkipConditionConverter.cs b/Questionable.Model/Questing/Converter/SkipConditionConverter.cs similarity index 76% rename from Questionable.Model/V1/Converter/SkipConditionConverter.cs rename to Questionable.Model/Questing/Converter/SkipConditionConverter.cs index f4b4e6fc..eadd84b1 100644 --- a/Questionable.Model/V1/Converter/SkipConditionConverter.cs +++ b/Questionable.Model/Questing/Converter/SkipConditionConverter.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; +using Questionable.Model.Common.Converter; -namespace Questionable.Model.V1.Converter; +namespace Questionable.Model.Questing.Converter; public sealed class SkipConditionConverter() : EnumConverter(Values) { diff --git a/Questionable.Model/V1/DialogueChoice.cs b/Questionable.Model/Questing/DialogueChoice.cs similarity index 87% rename from Questionable.Model/V1/DialogueChoice.cs rename to Questionable.Model/Questing/DialogueChoice.cs index 80b49d05..91370f5d 100644 --- a/Questionable.Model/V1/DialogueChoice.cs +++ b/Questionable.Model/Questing/DialogueChoice.cs @@ -1,7 +1,7 @@ using System.Text.Json.Serialization; -using Questionable.Model.V1.Converter; +using Questionable.Model.Questing.Converter; -namespace Questionable.Model.V1; +namespace Questionable.Model.Questing; public sealed class DialogueChoice { diff --git a/Questionable.Model/V1/EAction.cs b/Questionable.Model/Questing/EAction.cs similarity index 65% rename from Questionable.Model/V1/EAction.cs rename to Questionable.Model/Questing/EAction.cs index b83e6c86..c078e4a3 100644 --- a/Questionable.Model/V1/EAction.cs +++ b/Questionable.Model/Questing/EAction.cs @@ -1,11 +1,12 @@ using System.Text.Json.Serialization; -using Questionable.Model.V1.Converter; +using Questionable.Model.Questing.Converter; -namespace Questionable.Model.V1; +namespace Questionable.Model.Questing; [JsonConverter(typeof(ActionConverter))] public enum EAction { + Cure = 120, Esuna = 7568, Physick = 190, Buffet = 4931, @@ -14,6 +15,18 @@ public enum EAction RedGulal = 29382, YellowGulal = 29383, BlueGulal = 29384, + + CollectMiner = 240, + ScourMiner = 22182, + MeticulousMiner = 22184, + ScrutinyMiner = 22185, + + CollectBotanist = 815, + ScourBotanist = 22186, + MeticulousBotanist = 22188, + ScrutinyBotanist = 22189, + + } public static class EActionExtensions diff --git a/Questionable.Model/V1/EDialogChoiceType.cs b/Questionable.Model/Questing/EDialogChoiceType.cs similarity index 61% rename from Questionable.Model/V1/EDialogChoiceType.cs rename to Questionable.Model/Questing/EDialogChoiceType.cs index 066639a3..9b537471 100644 --- a/Questionable.Model/V1/EDialogChoiceType.cs +++ b/Questionable.Model/Questing/EDialogChoiceType.cs @@ -1,4 +1,4 @@ -namespace Questionable.Model.V1; +namespace Questionable.Model.Questing; public enum EDialogChoiceType { diff --git a/Questionable.Model/V1/EEmote.cs b/Questionable.Model/Questing/EEmote.cs similarity index 85% rename from Questionable.Model/V1/EEmote.cs rename to Questionable.Model/Questing/EEmote.cs index 6657a1dd..022930e9 100644 --- a/Questionable.Model/V1/EEmote.cs +++ b/Questionable.Model/Questing/EEmote.cs @@ -1,7 +1,7 @@ using System.Text.Json.Serialization; -using Questionable.Model.V1.Converter; +using Questionable.Model.Questing.Converter; -namespace Questionable.Model.V1; +namespace Questionable.Model.Questing; [JsonConverter(typeof(EmoteConverter))] public enum EEmote diff --git a/Questionable.Model/V1/EEnemySpawnType.cs b/Questionable.Model/Questing/EEnemySpawnType.cs similarity index 72% rename from Questionable.Model/V1/EEnemySpawnType.cs rename to Questionable.Model/Questing/EEnemySpawnType.cs index 8465f011..3c42b9f4 100644 --- a/Questionable.Model/V1/EEnemySpawnType.cs +++ b/Questionable.Model/Questing/EEnemySpawnType.cs @@ -1,7 +1,7 @@ using System.Text.Json.Serialization; -using Questionable.Model.V1.Converter; +using Questionable.Model.Questing.Converter; -namespace Questionable.Model.V1; +namespace Questionable.Model.Questing; [JsonConverter(typeof(EnemySpawnTypeConverter))] public enum EEnemySpawnType diff --git a/Questionable.Model/V1/EExtraSkipCondition.cs b/Questionable.Model/Questing/EExtraSkipCondition.cs similarity index 65% rename from Questionable.Model/V1/EExtraSkipCondition.cs rename to Questionable.Model/Questing/EExtraSkipCondition.cs index 227bf0f2..3f2836a9 100644 --- a/Questionable.Model/V1/EExtraSkipCondition.cs +++ b/Questionable.Model/Questing/EExtraSkipCondition.cs @@ -1,7 +1,7 @@ using System.Text.Json.Serialization; -using Questionable.Model.V1.Converter; +using Questionable.Model.Questing.Converter; -namespace Questionable.Model.V1; +namespace Questionable.Model.Questing; [JsonConverter(typeof(SkipConditionConverter))] public enum EExtraSkipCondition diff --git a/Questionable.Model/V1/EInteractionType.cs b/Questionable.Model/Questing/EInteractionType.cs similarity index 86% rename from Questionable.Model/V1/EInteractionType.cs rename to Questionable.Model/Questing/EInteractionType.cs index 843f600d..5080714a 100644 --- a/Questionable.Model/V1/EInteractionType.cs +++ b/Questionable.Model/Questing/EInteractionType.cs @@ -1,7 +1,7 @@ using System.Text.Json.Serialization; -using Questionable.Model.V1.Converter; +using Questionable.Model.Questing.Converter; -namespace Questionable.Model.V1; +namespace Questionable.Model.Questing; [JsonConverter(typeof(InteractionTypeConverter))] public enum EInteractionType diff --git a/Questionable.Model/V1/EJumpType.cs b/Questionable.Model/Questing/EJumpType.cs similarity index 63% rename from Questionable.Model/V1/EJumpType.cs rename to Questionable.Model/Questing/EJumpType.cs index b06365eb..e89931ac 100644 --- a/Questionable.Model/V1/EJumpType.cs +++ b/Questionable.Model/Questing/EJumpType.cs @@ -1,7 +1,7 @@ using System.Text.Json.Serialization; -using Questionable.Model.V1.Converter; +using Questionable.Model.Questing.Converter; -namespace Questionable.Model.V1; +namespace Questionable.Model.Questing; [JsonConverter(typeof(JumpTypeConverter))] public enum EJumpType diff --git a/Questionable.Model/V1/ELockedSkipCondition.cs b/Questionable.Model/Questing/ELockedSkipCondition.cs similarity index 65% rename from Questionable.Model/V1/ELockedSkipCondition.cs rename to Questionable.Model/Questing/ELockedSkipCondition.cs index 2863c262..7755b129 100644 --- a/Questionable.Model/V1/ELockedSkipCondition.cs +++ b/Questionable.Model/Questing/ELockedSkipCondition.cs @@ -1,7 +1,7 @@ using System.Text.Json.Serialization; -using Questionable.Model.V1.Converter; +using Questionable.Model.Questing.Converter; -namespace Questionable.Model.V1; +namespace Questionable.Model.Questing; [JsonConverter(typeof(LockedSkipConditionConverter))] public enum ELockedSkipCondition diff --git a/Questionable.Model/Questing/EQuestWorkMode.cs b/Questionable.Model/Questing/EQuestWorkMode.cs new file mode 100644 index 00000000..3d835225 --- /dev/null +++ b/Questionable.Model/Questing/EQuestWorkMode.cs @@ -0,0 +1,11 @@ +using System.Text.Json.Serialization; +using Questionable.Model.Questing.Converter; + +namespace Questionable.Model.Questing; + +[JsonConverter(typeof(QuestWorkModeConverter))] +public enum EQuestWorkMode +{ + Bitwise, + Exact, +} diff --git a/Questionable.Model/V1/ExcelRef.cs b/Questionable.Model/Questing/ExcelRef.cs similarity index 97% rename from Questionable.Model/V1/ExcelRef.cs rename to Questionable.Model/Questing/ExcelRef.cs index 295dd5ba..cdb55014 100644 --- a/Questionable.Model/V1/ExcelRef.cs +++ b/Questionable.Model/Questing/ExcelRef.cs @@ -1,6 +1,6 @@ using System; -namespace Questionable.Model.V1; +namespace Questionable.Model.Questing; public class ExcelRef { diff --git a/Questionable.Model/Questing/GatheredItem.cs b/Questionable.Model/Questing/GatheredItem.cs new file mode 100644 index 00000000..bfc6fd1a --- /dev/null +++ b/Questionable.Model/Questing/GatheredItem.cs @@ -0,0 +1,8 @@ +namespace Questionable.Model.Questing; + +public sealed class GatheredItem +{ + public uint ItemId { get; set; } + public int ItemCount { get; set; } + public ushort Collectability { get; set; } +} diff --git a/Questionable.Model/V1/JumpDestination.cs b/Questionable.Model/Questing/JumpDestination.cs similarity index 76% rename from Questionable.Model/V1/JumpDestination.cs rename to Questionable.Model/Questing/JumpDestination.cs index 2497560a..9682e21e 100644 --- a/Questionable.Model/V1/JumpDestination.cs +++ b/Questionable.Model/Questing/JumpDestination.cs @@ -1,8 +1,9 @@ using System.Numerics; using System.Text.Json.Serialization; -using Questionable.Model.V1.Converter; +using Questionable.Model.Common.Converter; +using Questionable.Model.Questing.Converter; -namespace Questionable.Model.V1; +namespace Questionable.Model.Questing; public sealed class JumpDestination { diff --git a/Questionable.Model/V1/QuestRoot.cs b/Questionable.Model/Questing/QuestRoot.cs similarity index 80% rename from Questionable.Model/V1/QuestRoot.cs rename to Questionable.Model/Questing/QuestRoot.cs index ada2e9fd..e1158fc8 100644 --- a/Questionable.Model/V1/QuestRoot.cs +++ b/Questionable.Model/Questing/QuestRoot.cs @@ -1,8 +1,9 @@ using System.Collections.Generic; using System.Text.Json.Serialization; -using Questionable.Model.V1.Converter; +using Questionable.Model.Common.Converter; +using Questionable.Model.Questing.Converter; -namespace Questionable.Model.V1; +namespace Questionable.Model.Questing; public sealed class QuestRoot { diff --git a/Questionable.Model/V1/QuestSequence.cs b/Questionable.Model/Questing/QuestSequence.cs similarity index 91% rename from Questionable.Model/V1/QuestSequence.cs rename to Questionable.Model/Questing/QuestSequence.cs index 73807e0b..3e42e098 100644 --- a/Questionable.Model/V1/QuestSequence.cs +++ b/Questionable.Model/Questing/QuestSequence.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Linq; -namespace Questionable.Model.V1; +namespace Questionable.Model.Questing; public sealed class QuestSequence { diff --git a/Questionable.Model/V1/QuestStep.cs b/Questionable.Model/Questing/QuestStep.cs similarity index 90% rename from Questionable.Model/V1/QuestStep.cs rename to Questionable.Model/Questing/QuestStep.cs index 37d4a0d8..4ec5f81d 100644 --- a/Questionable.Model/V1/QuestStep.cs +++ b/Questionable.Model/Questing/QuestStep.cs @@ -1,9 +1,11 @@ using System.Collections.Generic; using System.Numerics; using System.Text.Json.Serialization; -using Questionable.Model.V1.Converter; +using Questionable.Model.Common; +using Questionable.Model.Common.Converter; +using Questionable.Model.Questing.Converter; -namespace Questionable.Model.V1; +namespace Questionable.Model.Questing; public sealed class QuestStep { @@ -64,7 +66,8 @@ public sealed class QuestStep public SkipConditions? SkipConditions { get; set; } public List?> RequiredQuestVariables { get; set; } = new(); - public IList CompletionQuestVariablesFlags { get; set; } = new List(); + public List RequiredGatheredItems { get; set; } = []; + public IList CompletionQuestVariablesFlags { get; set; } = new List(); public IList DialogueChoices { get; set; } = new List(); public IList PointMenuChoices { get; set; } = new List(); diff --git a/Questionable.Model/Questing/QuestWorkValue.cs b/Questionable.Model/Questing/QuestWorkValue.cs new file mode 100644 index 00000000..d4db77ef --- /dev/null +++ b/Questionable.Model/Questing/QuestWorkValue.cs @@ -0,0 +1,30 @@ +using System; +using System.Text.Json.Serialization; +using Questionable.Model.Questing.Converter; + +namespace Questionable.Model.Questing; + +[JsonConverter(typeof(QuestWorkConfigConverter))] +public sealed class QuestWorkValue(byte? high, byte? low, EQuestWorkMode mode) +{ + public QuestWorkValue(byte value) + : this((byte)(value >> 4), (byte)(value & 0xF), EQuestWorkMode.Bitwise) + { + } + + public byte? High { get; set; } = high; + public byte? Low { get; set; } = low; + public EQuestWorkMode Mode { get; set; } = mode; + + public override string ToString() + { + if (High != null && Low != null) + return ((byte)(High << 4) + Low).ToString(); + else if (High != null) + return High + "H"; + else if (Low != null) + return Low + "L"; + else + return "-"; + } +} diff --git a/Questionable.Model/V1/SkipAetheryteCondition.cs b/Questionable.Model/Questing/SkipAetheryteCondition.cs similarity index 75% rename from Questionable.Model/V1/SkipAetheryteCondition.cs rename to Questionable.Model/Questing/SkipAetheryteCondition.cs index 86d0a1c6..0109e4de 100644 --- a/Questionable.Model/V1/SkipAetheryteCondition.cs +++ b/Questionable.Model/Questing/SkipAetheryteCondition.cs @@ -1,4 +1,4 @@ -namespace Questionable.Model.V1; +namespace Questionable.Model.Questing; public sealed class SkipAetheryteCondition { diff --git a/Questionable.Model/V1/SkipConditions.cs b/Questionable.Model/Questing/SkipConditions.cs similarity index 84% rename from Questionable.Model/V1/SkipConditions.cs rename to Questionable.Model/Questing/SkipConditions.cs index 5f866865..6525ec0f 100644 --- a/Questionable.Model/V1/SkipConditions.cs +++ b/Questionable.Model/Questing/SkipConditions.cs @@ -1,4 +1,4 @@ -namespace Questionable.Model.V1; +namespace Questionable.Model.Questing; public sealed class SkipConditions { diff --git a/Questionable.Model/V1/SkipItemConditions.cs b/Questionable.Model/Questing/SkipItemConditions.cs similarity index 67% rename from Questionable.Model/V1/SkipItemConditions.cs rename to Questionable.Model/Questing/SkipItemConditions.cs index 9154dcd4..04c38f6e 100644 --- a/Questionable.Model/V1/SkipItemConditions.cs +++ b/Questionable.Model/Questing/SkipItemConditions.cs @@ -1,4 +1,4 @@ -namespace Questionable.Model.V1; +namespace Questionable.Model.Questing; public sealed class SkipItemConditions { diff --git a/Questionable.Model/Questing/SkipStepConditions.cs b/Questionable.Model/Questing/SkipStepConditions.cs new file mode 100644 index 00000000..ee7e7dcf --- /dev/null +++ b/Questionable.Model/Questing/SkipStepConditions.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Questionable.Model.Questing; + +public sealed class SkipStepConditions +{ + public bool Never { get; set; } + public IList CompletionQuestVariablesFlags { get; set; } = new List(); + public ELockedSkipCondition? Flying { get; set; } + public ELockedSkipCondition? Chocobo { get; set; } + public bool NotTargetable { get; set; } + public List InTerritory { get; set; } = new(); + public List NotInTerritory { get; set; } = new(); + public SkipItemConditions? Item { get; set; } + public List QuestsAccepted { get; set; } = new(); + public List QuestsCompleted { get; set; } = new(); + public EExtraSkipCondition? ExtraCondition { get; set; } + + public bool HasSkipConditions() + { + if (Never) + return false; + return (CompletionQuestVariablesFlags.Count > 0 && CompletionQuestVariablesFlags.Any(x => x != null)) || + Flying != null || + Chocobo != null || + NotTargetable || + InTerritory.Count > 0 || + NotInTerritory.Count > 0 || + Item != null || + QuestsAccepted.Count > 0 || + QuestsCompleted.Count > 0 || + ExtraCondition != null; + } + + public override string ToString() + { + return + $"{nameof(Never)}: {Never}, {nameof(CompletionQuestVariablesFlags)}: {CompletionQuestVariablesFlags}, {nameof(Flying)}: {Flying}, {nameof(Chocobo)}: {Chocobo}, {nameof(NotTargetable)}: {NotTargetable}, {nameof(InTerritory)}: {string.Join(" ", InTerritory)}, {nameof(NotInTerritory)}: {string.Join(" ", NotInTerritory)}, {nameof(Item)}: {Item}, {nameof(QuestsAccepted)}: {string.Join(" ", QuestsAccepted)}, {nameof(QuestsCompleted)}: {string.Join(" ", QuestsCompleted)}, {nameof(ExtraCondition)}: {ExtraCondition}"; + } +} diff --git a/Questionable.Model/Questionable.Model.csproj b/Questionable.Model/Questionable.Model.csproj index cb320ec5..54301430 100644 --- a/Questionable.Model/Questionable.Model.csproj +++ b/Questionable.Model/Questionable.Model.csproj @@ -13,4 +13,12 @@ + + + + + + Questionable.Model.CommonSchema + + diff --git a/Questionable.Model/V1/QuestWorkValue.cs b/Questionable.Model/V1/QuestWorkValue.cs deleted file mode 100644 index 7321b2be..00000000 --- a/Questionable.Model/V1/QuestWorkValue.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Text.Json.Serialization; -using Questionable.Model.V1.Converter; - -namespace Questionable.Model.V1; - -[JsonConverter(typeof(QuestWorkConfigConverter))] -public sealed class QuestWorkValue(byte? high, byte? low) -{ - public QuestWorkValue(byte value) - : this((byte)(value >> 4), (byte)(value & 0xF)) - { - } - - public byte? High { get; set; } = high; - public byte? Low { get; set; } = low; -} diff --git a/Questionable.Model/V1/SkipStepConditions.cs b/Questionable.Model/V1/SkipStepConditions.cs deleted file mode 100644 index 316b1cc1..00000000 --- a/Questionable.Model/V1/SkipStepConditions.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Collections.Generic; - -namespace Questionable.Model.V1; - -public sealed class SkipStepConditions -{ - public bool Never { get; set; } - public ELockedSkipCondition? Flying { get; set; } - public ELockedSkipCondition? Chocobo { get; set; } - public bool NotTargetable { get; set; } - public List InTerritory { get; set; } = new(); - public List NotInTerritory { get; set; } = new(); - public SkipItemConditions? Item { get; set; } - public List QuestsAccepted { get; set; } = new(); - public List QuestsCompleted { get; set; } = new(); - public EExtraSkipCondition? ExtraCondition { get; set; } - - public bool HasSkipConditions() - { - if (Never) - return false; - return Flying != null || - Chocobo != null || - InTerritory.Count > 0 || - NotInTerritory.Count > 0 || - Item != null || - QuestsAccepted.Count > 0 || - QuestsCompleted.Count > 0; - } -} diff --git a/Questionable.Model/common-schema.json b/Questionable.Model/common-schema.json new file mode 100644 index 00000000..6017ecb7 --- /dev/null +++ b/Questionable.Model/common-schema.json @@ -0,0 +1,299 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://git.carvel.li/liza/Questionable/raw/branch/master/Questionable.Model/common-schema.json", + "$defs": { + "Aetheryte": { + "type": "string", + "enum": [ + "Gridania", + "Central Shroud - Bentbranch Meadows", + "East Shroud - Hawthorne Hut", + "South Shroud - Quarrymill", + "South Shroud - Camp Tranquil", + "North Shroud - Fallgourd Float", + "Ul'dah", + "Western Thanalan - Horizon", + "Central Thanalan - Black Brush Station", + "Eastern Thanalan - Camp Drybone", + "Southern Thanalan - Little Ala Mhigo", + "Southern Thanalan - Forgotten Springs", + "Northern Thanalan - Camp Bluefog", + "Northern Thanalan - Ceruleum Processing Plant", + "Limsa Lominsa", + "Middle La Noscea - Summerford Farms", + "Lower La Noscea - Moraby Drydocks", + "Eastern La Noscea - Costa Del Sol", + "Eastern La Noscea - Wineport", + "Western La Noscea - Swiftperch", + "Western La Noscea - Aleport", + "Upper La Noscea - Camp Bronze Lake", + "Outer La Noscea - Camp Overlook", + "Coerthas Central Highlands - Camp Dragonhead", + "Mor Dhona", + "Gold Saucer", + "Wolves' Den Pier", + "Ishgard", + "Idyllshire", + "Coerthas Western Highlands - Falcon's Nest", + "The Sea of Clouds - Camp Cloudtop", + "The Sea of Clouds - Ok' Zundu", + "Azys Lla - Helix", + "The Dravanian Forelands - Tailfeather", + "The Dravanian Forelands - Anyx Trine", + "The Churning Mists - Moghome", + "The Churning Mists - Zenith", + "Rhalgr's Reach", + "Fringes - Castrum Oriens", + "Fringes - Peering Stones", + "Peaks - Ala Gannha", + "Peaks - Ala Ghiri", + "Lochs - Porta Praetoria", + "Lochs - Ala Mhigan Quarter", + "Kugane", + "Ruby Sea - Tamamizu", + "Ruby Sea - Onokoro", + "Yanxia - Namai", + "Yanxia - House of the Fierce", + "Azim Steppe - Reunion", + "Azim Steppe - Dawn Throne", + "Azim Steppe - Dhoro Iloh", + "Doman Enclave", + "Crystarium", + "Eulmore", + "Lakeland - Fort Jobb", + "Lakeland - Ostall Imperative", + "Kholusia - Stilltide", + "Kholusia - Wright", + "Kholusia - Tomra", + "Amh Araeng - Mord Souq", + "Amh Araeng - Inn at Journey's Head", + "Amh Araeng - Twine", + "Rak'tika - Slitherbough", + "Rak'tika - Fanow", + "Il Mheg - Lydha Lran", + "Il Mheg - Pia Enni", + "Il Mheg - Wolekdorf", + "Tempest - Ondo Cups", + "Tempest - Macarenses Angle", + "Old Sharlayan", + "Radz-at-Han", + "Labyrinthos - Archeion", + "Labyrinthos - Sharlayan Hamlet", + "Labyrinthos - Aporia", + "Thavnair - Yedlihmad", + "Thavnair - Great Work", + "Thavnair - Palaka's Stand", + "Garlemald - Camp Broken Glass", + "Garlemald - Tertium", + "Mare Lamentorum - Sinus Lacrimarum", + "Mare Lamentorum - Bestways Burrow", + "Elpis - Anagnorisis", + "Elpis - Twelve Wonders", + "Elpis - Poieten Oikos", + "Ultima Thule - Reah Tahra", + "Ultima Thule - Abode of the Ea", + "Ultima Thule - Base Omicron", + "Tuliyollal", + "Solution Nine", + "Urqopacha - Wachunpelo", + "Urqopacha - Worlar's Echo", + "Kozama'uka - Ok'hanu", + "Kozama'uka - Many Fires", + "Kozama'uka - Earthenshire", + "Yak T'el - Iq Br'aax", + "Yak T'el - Mamook", + "Shaaloani - Hhusatahwi", + "Shaaloani - Sheshenewezi Springs", + "Shaaloani - Mehwahhetsoan", + "Heritage Found - Yyasulani Station", + "Heritage Found - The Outskirts", + "Heritage Found - Electrope Strike", + "Living Memory - Leynode Mnemo", + "Living Memory - Leynode Pyro", + "Living Memory - Leynode Aero" + ] + }, + "AethernetShard": { + "type": "string", + "enum": [ + "[Gridania] Aetheryte Plaza", + "[Gridania] Archers' Guild", + "[Gridania] Leatherworkers' Guild & Shaded Bower", + "[Gridania] Lancers' Guild", + "[Gridania] Conjurers' Guild", + "[Gridania] Botanists' Guild", + "[Gridania] Mih Khetto's Amphitheatre", + "[Gridania] Blue Badger Gate (Central Shroud)", + "[Gridania] Yellow Serpent Gate (North Shroud)", + "[Gridania] White Wolf Gate (Central Shroud)", + "[Gridania] Airship Landing", + "[Ul'dah] Aetheryte Plaza", + "[Ul'dah] Adventurers' Guild", + "[Ul'dah] Thaumaturges' Guild", + "[Ul'dah] Gladiators' Guild", + "[Ul'dah] Miners' Guild", + "[Ul'dah] Weavers' Guild", + "[Ul'dah] Goldsmiths' Guild", + "[Ul'dah] Sapphire Avenue Exchange", + "[Ul'dah] Alchemists' Guild", + "[Ul'dah] Gate of the Sultana (Western Thanalan)", + "[Ul'dah] Gate of Nald (Central Thanalan)", + "[Ul'dah] Gate of Thal (Central Thanalan)", + "[Ul'dah] The Chamber of Rule", + "[Ul'dah] Airship Landing", + "[Limsa Lominsa] Aetheryte Plaza", + "[Limsa Lominsa] Arcanists' Guild", + "[Limsa Lominsa] Fishermens' Guild", + "[Limsa Lominsa] Hawkers' Alley", + "[Limsa Lominsa] The Aftcastle", + "[Limsa Lominsa] Culinarians' Guild", + "[Limsa Lominsa] Marauders' Guild", + "[Limsa Lominsa] Zephyr Gate (Middle La Noscea)", + "[Limsa Lominsa] Tempest Gate (Lower La Noscea)", + "[Limsa Lominsa] Airship Landing", + "[Ishgard] Aetheryte Plaza", + "[Ishgard] The Forgotten Knight", + "[Ishgard] Skysteel Manufactory", + "[Ishgard] The Brume", + "[Ishgard] Athenaeum Astrologicum", + "[Ishgard] The Jeweled Crozier", + "[Ishgard] Saint Reymanaud's Cathedral", + "[Ishgard] The Tribunal", + "[Ishgard] The Last Vigil", + "[Ishgard] The Gates of Judgement (Coerthas Central Highlands)", + "[Idyllshire] Aetheryte Plaza", + "[Idyllshire] West Idyllshire", + "[Idyllshire] Prologue Gate (Western Hinterlands)", + "[Idyllshire] Epilogue Gate (Eastern Hinterlands)", + "[Rhalgr's Reach] Aetheryte Plaza", + "[Rhalgr's Reach] Western Rhalgr's Reach", + "[Rhalgr's Reach] Northeastern Rhalgr's Reach", + "[Rhalgr's Reach] Fringes Gate", + "[Rhalgr's Reach] Peaks Gate", + "[Kugane] Aetheryte Plaza", + "[Kugane] Shiokaze Hostelry", + "[Kugane] Pier #1", + "[Kugane] Thavnairian Consulate", + "[Kugane] Kogane Dori Markets", + "[Kugane] Bokairo Inn", + "[Kugane] The Ruby Bazaar", + "[Kugane] Sekiseigumi Barracks", + "[Kugane] Rakuza District", + "[Kugane] The Ruby Price", + "[Kugane] Airship Landing", + "[Crystarium] Aetheryte Plaza", + "[Crystarium] Musica Universalis Markets", + "[Crystarium] Temenos Rookery", + "[Crystarium] The Dossal Gate", + "[Crystarium] The Pendants", + "[Crystarium] The Amaro Launch", + "[Crystarium] The Crystalline Mean", + "[Crystarium] The Cabinet of Curiosity", + "[Crystarium] Tessellation (Lakeland)", + "[Eulmore] Aetheryte Plaza", + "[Eulmore] Southeast Derelicts", + "[Eulmore] Nightsoil Pots", + "[Eulmore] The Glory Gate", + "[Eulmore] The Mainstay", + "[Eulmore] The Path to Glory (Kholusia)", + "[Old Sharlayan] Aetheryte Plaza", + "[Old Sharlayan] The Studium", + "[Old Sharlayan] The Baldesion Annex", + "[Old Sharlayan] The Rostra", + "[Old Sharlayan] The Leveilleur Estate", + "[Old Sharlayan] Journey's End", + "[Old Sharlayan] Scholar's Harbor", + "[Old Sharlayan] The Hall of Artifice (Labyrinthos)", + "[Radz-at-Han] Aetheryte Plaza", + "[Radz-at-Han] Meghaduta", + "[Radz-at-Han] Ruveydah Fibers", + "[Radz-at-Han] Airship Landing", + "[Radz-at-Han] Alzadaal's Peace", + "[Radz-at-Han] Hall of the Radiant Host", + "[Radz-at-Han] Mehryde's Meyhane", + "[Radz-at-Han] Kama", + "[Radz-at-Han] The High Crucible of Al-Kimiya", + "[Radz-at-Han] The Gate of First Sight (Thavnair)", + "[Tuliyollal] Aetheryte Plaza", + "[Tuliyollal] Dirigible Landing", + "[Tuliyollal] The Resplendent Quarter", + "[Tuliyollal] The For'ard Cabins", + "[Tuliyollal] Bayside Bevy Marketplace", + "[Tuliyollal] Vollok Shoonsa", + "[Tuliyollal] Wachumeqimeqi", + "[Tuliyollal] Brightploom Post", + "[Tuliyollal] Arch of the Dawn (Urqopacha)", + "[Tuliyollal] Arch of the Dawn (Kozama'uka)", + "[Tuliyollal] Ihuykatumu (Kozama'uka)", + "[Tuliyollal] Dirigible Landing (Yak T'el)", + "[Tuliyollal] Xak Tural Skygate (Shaaloani)", + "[Solution Nine] Aetheryte Plaza", + "[Solution Nine] Information Center", + "[Solution Nine] True Vue", + "[Solution Nine] Neon Stein", + "[Solution Nine] The Arcadion", + "[Solution Nine] Resolution", + "[Solution Nine] Nexus Arcade", + "[Solution Nine] Residential Sector", + "[Solution Nine] Scanning Port Nine (Heritage Found)" + ] + }, + "CompletionFlags": { + "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": { + "oneOf": [ + { + "type": "object", + "properties": { + "High": { + "type": [ + "number", + "null" + ], + "minimum": 0, + "maximum": 15 + }, + "Low": { + "type": [ + "number", + "null" + ], + "minimum": 0, + "maximum": 15 + }, + "Negative": { + "type": "boolean" + }, + "Mode": { + "type": "string", + "enum": [ + "Bitwise", + "Exact" + ] + } + } + }, + { + "type": "number", + "enum": [ + 1, + 2, + 4, + 8, + 16, + 32, + 64, + 128 + ] + }, + { + "type": "null" + } + ] + }, + "minItems": 6, + "maxItems": 6 + } + } +} diff --git a/Questionable.sln b/Questionable.sln index a083acfb..7ca82fc8 100644 --- a/Questionable.sln +++ b/Questionable.sln @@ -15,6 +15,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Questionable.Model", "Quest EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QuestPathGenerator.Tests", "QuestPathGenerator.Tests\QuestPathGenerator.Tests.csproj", "{4FD6F346-8961-4BD5-BDA2-E5F426DE4FC7}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GatheringPaths", "GatheringPaths\GatheringPaths.csproj", "{8BF98BEF-6F00-4197-91ED-75F8F1C35FFB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GatheringPathRenderer", "GatheringPathRenderer\GatheringPathRenderer.csproj", "{F514DA95-9867-4F3F-8062-ACE0C62E8740}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ECommons", "vendor\ECommons\ECommons\ECommons.csproj", "{A12D7B4B-8E6E-4DCF-A41A-12F62E9FF94B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -45,6 +51,18 @@ Global {C91EEF13-A1AC-4A40-B695-DD4E378E5989}.Debug|x64.Build.0 = Debug|x64 {C91EEF13-A1AC-4A40-B695-DD4E378E5989}.Release|x64.ActiveCfg = Release|x64 {C91EEF13-A1AC-4A40-B695-DD4E378E5989}.Release|x64.Build.0 = Release|x64 + {8BF98BEF-6F00-4197-91ED-75F8F1C35FFB}.Debug|x64.ActiveCfg = Debug|Any CPU + {8BF98BEF-6F00-4197-91ED-75F8F1C35FFB}.Debug|x64.Build.0 = Debug|Any CPU + {8BF98BEF-6F00-4197-91ED-75F8F1C35FFB}.Release|x64.ActiveCfg = Release|Any CPU + {8BF98BEF-6F00-4197-91ED-75F8F1C35FFB}.Release|x64.Build.0 = Release|Any CPU + {F514DA95-9867-4F3F-8062-ACE0C62E8740}.Debug|x64.ActiveCfg = Debug|Any CPU + {F514DA95-9867-4F3F-8062-ACE0C62E8740}.Debug|x64.Build.0 = Debug|Any CPU + {F514DA95-9867-4F3F-8062-ACE0C62E8740}.Release|x64.ActiveCfg = Release|Any CPU + {F514DA95-9867-4F3F-8062-ACE0C62E8740}.Release|x64.Build.0 = Release|Any CPU + {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 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Questionable.sln.DotSettings b/Questionable.sln.DotSettings index 3588f8f8..a8a650e8 100644 --- a/Questionable.sln.DotSettings +++ b/Questionable.sln.DotSettings @@ -7,6 +7,7 @@ True True True + True True True True diff --git a/Questionable/ChatFunctions.cs b/Questionable/ChatFunctions.cs index 26ca9a22..d28946a8 100644 --- a/Questionable/ChatFunctions.cs +++ b/Questionable/ChatFunctions.cs @@ -14,7 +14,7 @@ using FFXIVClientStructs.FFXIV.Client.System.Memory; using FFXIVClientStructs.FFXIV.Client.System.String; using Lumina.Excel.GeneratedSheets; using Microsoft.Extensions.Logging; -using Questionable.Model.V1; +using Questionable.Model.Questing; namespace Questionable; diff --git a/Questionable/Configuration.cs b/Questionable/Configuration.cs index 4b276961..4424db23 100644 --- a/Questionable/Configuration.cs +++ b/Questionable/Configuration.cs @@ -18,6 +18,7 @@ internal sealed class Configuration : IPluginConfiguration public uint MountId { get; set; } = 71; public GrandCompany GrandCompany { get; set; } = GrandCompany.None; public bool HideInAllInstances { get; set; } = true; + public bool UseEscToCancelQuesting { get; set; } = true; } internal sealed class AdvancedConfiguration diff --git a/Questionable/Controller/CombatController.cs b/Questionable/Controller/CombatController.cs index d02def76..1f95e9da 100644 --- a/Questionable/Controller/CombatController.cs +++ b/Questionable/Controller/CombatController.cs @@ -14,7 +14,7 @@ using FFXIVClientStructs.FFXIV.Common.Math; using Microsoft.Extensions.Logging; using Questionable.Controller.CombatModules; using Questionable.Controller.Utils; -using Questionable.Model.V1; +using Questionable.Model.Questing; namespace Questionable.Controller; @@ -172,7 +172,7 @@ internal sealed class CombatController : IDisposable { var questWork = _gameFunctions.GetQuestEx(_currentFight.Data.QuestId); if (questWork != null && QuestWorkUtils.MatchesQuestWork(condition.CompletionQuestVariablesFlags, - questWork.Value, false)) + questWork.Value)) { _logger.LogInformation("Complex combat condition fulfilled: QuestWork matches"); _currentFight.Data.CompletedComplexDatas.Add(i); diff --git a/Questionable/Controller/GameUiController.cs b/Questionable/Controller/GameUiController.cs index 0bb4e12b..61d31fbd 100644 --- a/Questionable/Controller/GameUiController.cs +++ b/Questionable/Controller/GameUiController.cs @@ -14,7 +14,7 @@ using LLib.GameUI; using Lumina.Excel.GeneratedSheets; using Microsoft.Extensions.Logging; using Questionable.Data; -using Questionable.Model.V1; +using Questionable.Model.Questing; using Quest = Questionable.Model.Quest; using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType; diff --git a/Questionable/Controller/GatheringController.cs b/Questionable/Controller/GatheringController.cs new file mode 100644 index 00000000..ff19d3f4 --- /dev/null +++ b/Questionable/Controller/GatheringController.cs @@ -0,0 +1,203 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using Dalamud.Game.ClientState.Conditions; +using Dalamud.Game.ClientState.Objects.Enums; +using Dalamud.Plugin.Services; +using FFXIVClientStructs.FFXIV.Client.Game; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Questionable.Controller.Steps; +using Questionable.Controller.Steps.Common; +using Questionable.Controller.Steps.Gathering; +using Questionable.Controller.Steps.Interactions; +using Questionable.Controller.Steps.Shared; +using Questionable.External; +using Questionable.GatheringPaths; +using Questionable.Model.Gathering; + +namespace Questionable.Controller; + +internal sealed unsafe class GatheringController : MiniTaskController +{ + private readonly MovementController _movementController; + private readonly GameFunctions _gameFunctions; + private readonly NavmeshIpc _navmeshIpc; + private readonly IObjectTable _objectTable; + private readonly IServiceProvider _serviceProvider; + private readonly ICondition _condition; + + private CurrentRequest? _currentRequest; + + public GatheringController( + MovementController movementController, + GameFunctions gameFunctions, + NavmeshIpc navmeshIpc, + IObjectTable objectTable, + IChatGui chatGui, + ILogger logger, + IServiceProvider serviceProvider, + ICondition condition) + : base(chatGui, logger) + { + _movementController = movementController; + _gameFunctions = gameFunctions; + _navmeshIpc = navmeshIpc; + _objectTable = objectTable; + _serviceProvider = serviceProvider; + _condition = condition; + } + + public bool Start(GatheringRequest gatheringRequest) + { + if (!AssemblyGatheringLocationLoader.GetLocations() + .TryGetValue(gatheringRequest.GatheringPointId, out GatheringRoot? gatheringRoot)) + { + _logger.LogError("Unable to resolve gathering point, no path found for {ItemId} / point {PointId}", + gatheringRequest.ItemId, gatheringRequest.GatheringPointId); + return false; + } + + _currentRequest = new CurrentRequest + { + Data = gatheringRequest, + Root = gatheringRoot, + Nodes = gatheringRoot.Groups + // at least in EW-ish, there's one node with 1 fixed location and one node with 3 random locations + .SelectMany(x => x.Nodes.OrderBy(y => y.Locations.Count)) + .ToList(), + }; + + if (HasRequestedItems()) + { + _currentRequest = null; + return false; + } + + return true; + } + + public EStatus Update() + { + if (_currentRequest == null) + return EStatus.Complete; + + if (_movementController.IsPathfinding || _movementController.IsPathfinding) + return EStatus.Moving; + + if (HasRequestedItems() && !_condition[ConditionFlag.Gathering]) + return EStatus.Complete; + + if (_currentTask == null && _taskQueue.Count == 0) + GoToNextNode(); + + UpdateCurrentTask(); + return EStatus.Gathering; + } + + protected override void OnTaskComplete(ITask task) => GoToNextNode(); + + public override void Stop(string label) + { + _currentRequest = null; + _currentTask = null; + _taskQueue.Clear(); + } + + private void GoToNextNode() + { + if (_currentRequest == null) + return; + + if (_taskQueue.Count > 0) + return; + + var currentNode = _currentRequest.Nodes[_currentRequest.CurrentIndex++ % _currentRequest.Nodes.Count]; + + _taskQueue.Enqueue(_serviceProvider.GetRequiredService() + .With(_currentRequest.Root.TerritoryId, MountTask.EMountIf.Always)); + if (currentNode.Locations.Count > 1) + { + Vector3 averagePosition = new Vector3 + { + X = currentNode.Locations.Sum(x => x.Position.X) / currentNode.Locations.Count, + Y = currentNode.Locations.Select(x => x.Position.Y).Max() + 5f, + Z = currentNode.Locations.Sum(x => x.Position.Z) / currentNode.Locations.Count, + }; + bool fly = _gameFunctions.IsFlyingUnlocked(_currentRequest.Root.TerritoryId); + Vector3? pointOnFloor = _navmeshIpc.GetPointOnFloor(averagePosition, true); + if (pointOnFloor != null) + pointOnFloor = pointOnFloor.Value with { Y = pointOnFloor.Value.Y + (fly ? 3f : 0f) }; + + _taskQueue.Enqueue(_serviceProvider.GetRequiredService() + .With(_currentRequest.Root.TerritoryId, pointOnFloor ?? averagePosition, 50f, fly: fly, + ignoreDistanceToObject: true)); + } + + _taskQueue.Enqueue(_serviceProvider.GetRequiredService() + .With(_currentRequest.Root.TerritoryId, currentNode)); + _taskQueue.Enqueue(_serviceProvider.GetRequiredService() + .With(currentNode.DataId, true)); + _taskQueue.Enqueue(_serviceProvider.GetRequiredService() + .With(_currentRequest.Data, currentNode)); + if (_currentRequest.Data.Collectability > 0) + { + _taskQueue.Enqueue(_serviceProvider.GetRequiredService() + .With(_currentRequest.Data, currentNode)); + } + } + + private bool HasRequestedItems() + { + if (_currentRequest == null) + return true; + + InventoryManager* inventoryManager = InventoryManager.Instance(); + if (inventoryManager == null) + return false; + + return inventoryManager->GetInventoryItemCount(_currentRequest.Data.ItemId, + minCollectability: (short)_currentRequest.Data.Collectability) >= _currentRequest.Data.Quantity; + } + + public bool HasNodeDisappeared(GatheringNode node) + { + return !_objectTable.Any(x => + x.ObjectKind == ObjectKind.GatheringPoint && x.IsTargetable && x.DataId == node.DataId); + } + + public override IList GetRemainingTaskNames() + { + if (_currentTask != null) + return [_currentTask.ToString() ?? "?", .. base.GetRemainingTaskNames()]; + else + return base.GetRemainingTaskNames(); + } + + private sealed class CurrentRequest + { + public required GatheringRequest Data { get; init; } + public required GatheringRoot Root { get; init; } + + /// + /// To make indexing easy with , we flatten the list of gathering locations. + /// + public required List Nodes { get; init; } + + public int CurrentIndex { get; set; } + } + + public sealed record GatheringRequest( + ushort GatheringPointId, + uint ItemId, + int Quantity, + ushort Collectability = 0); + + public enum EStatus + { + Gathering, + Moving, + Complete, + } +} diff --git a/Questionable/Controller/MiniTaskController.cs b/Questionable/Controller/MiniTaskController.cs new file mode 100644 index 00000000..4d19d73b --- /dev/null +++ b/Questionable/Controller/MiniTaskController.cs @@ -0,0 +1,134 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Dalamud.Plugin.Services; +using Microsoft.Extensions.Logging; +using Questionable.Controller.Steps; +using Questionable.Controller.Steps.Shared; + +namespace Questionable.Controller; + +internal abstract class MiniTaskController +{ + protected readonly IChatGui _chatGui; + protected readonly ILogger _logger; + + protected readonly Queue _taskQueue = new(); + protected ITask? _currentTask; + + public MiniTaskController(IChatGui chatGui, ILogger logger) + { + _chatGui = chatGui; + _logger = logger; + } + + protected virtual void UpdateCurrentTask() + { + if (_currentTask == null) + { + if (_taskQueue.TryDequeue(out ITask? upcomingTask)) + { + try + { + _logger.LogInformation("Starting task {TaskName}", upcomingTask.ToString()); + if (upcomingTask.Start()) + { + _currentTask = upcomingTask; + return; + } + else + { + _logger.LogTrace("Task {TaskName} was skipped", upcomingTask.ToString()); + return; + } + } + catch (Exception e) + { + _logger.LogError(e, "Failed to start task {TaskName}", upcomingTask.ToString()); + _chatGui.PrintError( + $"[Questionable] Failed to start task '{upcomingTask}', please check /xllog for details."); + Stop("Task failed to start"); + return; + } + } + else + return; + } + + ETaskResult result; + try + { + result = _currentTask.Update(); + } + catch (Exception e) + { + _logger.LogError(e, "Failed to update task {TaskName}", _currentTask.ToString()); + _chatGui.PrintError( + $"[Questionable] Failed to update task '{_currentTask}', please check /xllog for details."); + Stop("Task failed to update"); + return; + } + + switch (result) + { + case ETaskResult.StillRunning: + return; + + case ETaskResult.SkipRemainingTasksForStep: + _logger.LogInformation("{Task} → {Result}, skipping remaining tasks for step", + _currentTask, result); + _currentTask = null; + + while (_taskQueue.TryDequeue(out ITask? nextTask)) + { + if (nextTask is ILastTask) + { + _currentTask = nextTask; + return; + } + } + + return; + + case ETaskResult.TaskComplete: + _logger.LogInformation("{Task} → {Result}, remaining tasks: {RemainingTaskCount}", + _currentTask, result, _taskQueue.Count); + + OnTaskComplete(_currentTask); + + _currentTask = null; + + // handled in next update + return; + + case ETaskResult.NextStep: + _logger.LogInformation("{Task} → {Result}", _currentTask, result); + + var lastTask = (ILastTask)_currentTask; + _currentTask = null; + + OnNextStep(lastTask); + return; + + case ETaskResult.End: + _logger.LogInformation("{Task} → {Result}", _currentTask, result); + _currentTask = null; + Stop("Task end"); + return; + } + } + + protected virtual void OnTaskComplete(ITask task) + { + } + + protected virtual void OnNextStep(ILastTask task) + { + + } + + public abstract void Stop(string label); + + public virtual IList GetRemainingTaskNames() => + _taskQueue.Select(x => x.ToString() ?? "?").ToList(); +} diff --git a/Questionable/Controller/MovementController.cs b/Questionable/Controller/MovementController.cs index 1db1d5cf..c4f1b05b 100644 --- a/Questionable/Controller/MovementController.cs +++ b/Questionable/Controller/MovementController.cs @@ -18,8 +18,10 @@ using Microsoft.Extensions.Logging; using Questionable.Controller.NavigationOverrides; using Questionable.External; using Questionable.Model; -using Questionable.Model.V1; -using Questionable.Model.V1.Converter; +using Questionable.Model.Common; +using Questionable.Model.Common.Converter; +using Questionable.Model.Questing; +using Questionable.Model.Questing.Converter; namespace Questionable.Controller; @@ -258,7 +260,7 @@ internal sealed class MovementController : IDisposable private bool IsOnFlightPath(Vector3 p) { - Vector3? pointOnFloor = _navmeshIpc.GetPointOnFloor(p); + Vector3? pointOnFloor = _navmeshIpc.GetPointOnFloor(p, true); return pointOnFloor != null && Math.Abs(pointOnFloor.Value.Y - p.Y) > 0.5f; } diff --git a/Questionable/Controller/QuestController.cs b/Questionable/Controller/QuestController.cs index 4838353a..9b7fe9a9 100644 --- a/Questionable/Controller/QuestController.cs +++ b/Questionable/Controller/QuestController.cs @@ -10,20 +10,19 @@ using Questionable.Controller.Steps; using Questionable.Controller.Steps.Shared; using Questionable.External; using Questionable.Model; -using Questionable.Model.V1; +using Questionable.Model.Questing; namespace Questionable.Controller; -internal sealed class QuestController +internal sealed class QuestController : MiniTaskController { private readonly IClientState _clientState; private readonly GameFunctions _gameFunctions; private readonly MovementController _movementController; private readonly CombatController _combatController; - private readonly ILogger _logger; + private readonly GatheringController _gatheringController; private readonly QuestRegistry _questRegistry; private readonly IKeyState _keyState; - private readonly IChatGui _chatGui; private readonly ICondition _condition; private readonly Configuration _configuration; private readonly YesAlreadyIpc _yesAlreadyIpc; @@ -34,8 +33,6 @@ internal sealed class QuestController private QuestProgress? _startedQuest; private QuestProgress? _nextQuest; private QuestProgress? _simulatedQuest; - private readonly Queue _taskQueue = new(); - private ITask? _currentTask; private bool _automatic; /// @@ -50,6 +47,7 @@ internal sealed class QuestController GameFunctions gameFunctions, MovementController movementController, CombatController combatController, + GatheringController gatheringController, ILogger logger, QuestRegistry questRegistry, IKeyState keyState, @@ -58,15 +56,15 @@ internal sealed class QuestController Configuration configuration, YesAlreadyIpc yesAlreadyIpc, IEnumerable taskFactories) + : base(chatGui, logger) { _clientState = clientState; _gameFunctions = gameFunctions; _movementController = movementController; _combatController = combatController; - _logger = logger; + _gatheringController = gatheringController; _questRegistry = questRegistry; _keyState = keyState; - _chatGui = chatGui; _condition = condition; _configuration = configuration; _yesAlreadyIpc = yesAlreadyIpc; @@ -138,15 +136,17 @@ internal sealed class QuestController Stop("HP = 0"); _movementController.Stop(); _combatController.Stop("HP = 0"); + _gatheringController.Stop("HP = 0"); } } - else if (_keyState[VirtualKey.ESCAPE]) + else if (_configuration.General.UseEscToCancelQuesting && _keyState[VirtualKey.ESCAPE]) { if (_currentTask != null || _taskQueue.Count > 0) { Stop("ESC pressed"); _movementController.Stop(); _combatController.Stop("ESC pressed"); + _gatheringController.Stop("ESC pressed"); } } @@ -377,9 +377,10 @@ internal sealed class QuestController _yesAlreadyIpc.RestoreYesAlready(); _combatController.Stop("ClearTasksInternal"); + _gatheringController.Stop("ClearTasksInternal"); } - public void Stop(string label, bool continueIfAutomatic = false) + public void Stop(string label, bool continueIfAutomatic) { using var scope = _logger.BeginScope(label); @@ -401,6 +402,8 @@ internal sealed class QuestController } } + public override void Stop(string label) => Stop(label, false); + public void SimulateQuest(Quest? quest) { _logger.LogInformation("SimulateQuest: {QuestId}", quest?.QuestId); @@ -419,103 +422,23 @@ internal sealed class QuestController _nextQuest = null; } - private void UpdateCurrentTask() + protected override void UpdateCurrentTask() { if (_gameFunctions.IsOccupied()) return; - if (_currentTask == null) - { - if (_taskQueue.TryDequeue(out ITask? upcomingTask)) - { - try - { - _logger.LogInformation("Starting task {TaskName}", upcomingTask.ToString()); - if (upcomingTask.Start()) - { - _currentTask = upcomingTask; - return; - } - else - { - _logger.LogTrace("Task {TaskName} was skipped", upcomingTask.ToString()); - return; - } - } - catch (Exception e) - { - _logger.LogError(e, "Failed to start task {TaskName}", upcomingTask.ToString()); - _chatGui.PrintError( - $"[Questionable] Failed to start task '{upcomingTask}', please check /xllog for details."); - Stop("Task failed to start"); - return; - } - } - else - return; - } + base.UpdateCurrentTask(); + } - ETaskResult result; - try - { - result = _currentTask.Update(); - } - catch (Exception e) - { - _logger.LogError(e, "Failed to update task {TaskName}", _currentTask.ToString()); - _chatGui.PrintError( - $"[Questionable] Failed to update task '{_currentTask}', please check /xllog for details."); - Stop("Task failed to update"); - return; - } + protected override void OnTaskComplete(ITask task) + { + if (task is WaitAtEnd.WaitQuestCompleted) + _simulatedQuest = null; + } - switch (result) - { - case ETaskResult.StillRunning: - return; - - case ETaskResult.SkipRemainingTasksForStep: - _logger.LogInformation("{Task} → {Result}, skipping remaining tasks for step", - _currentTask, result); - _currentTask = null; - - while (_taskQueue.TryDequeue(out ITask? nextTask)) - { - if (nextTask is ILastTask) - { - _currentTask = nextTask; - return; - } - } - - return; - - case ETaskResult.TaskComplete: - _logger.LogInformation("{Task} → {Result}, remaining tasks: {RemainingTaskCount}", - _currentTask, result, _taskQueue.Count); - - if (_currentTask is WaitAtEnd.WaitQuestCompleted) - _simulatedQuest = null; - - _currentTask = null; - - // handled in next update - return; - - case ETaskResult.NextStep: - _logger.LogInformation("{Task} → {Result}", _currentTask, result); - - var lastTask = (ILastTask)_currentTask; - _currentTask = null; - IncreaseStepCount(lastTask.QuestId, lastTask.Sequence, true); - return; - - case ETaskResult.End: - _logger.LogInformation("{Task} → {Result}", _currentTask, result); - _currentTask = null; - Stop("Task end"); - return; - } + protected override void OnNextStep(ILastTask task) + { + IncreaseStepCount(task.QuestId, task.Sequence, true); } public void ExecuteNextStep(bool automatic) @@ -536,41 +459,48 @@ internal sealed class QuestController _movementController.Stop(); _combatController.Stop("Execute next step"); + _gatheringController.Stop("Execute next step"); - var newTasks = _taskFactories - .SelectMany(x => - { - IList tasks = x.CreateAllTasks(CurrentQuest.Quest, seq, step).ToList(); - - if (tasks.Count > 0 && _logger.IsEnabled(LogLevel.Trace)) - { - string factoryName = x.GetType().FullName ?? x.GetType().Name; - if (factoryName.Contains('.', StringComparison.Ordinal)) - factoryName = factoryName[(factoryName.LastIndexOf('.') + 1)..]; - - _logger.LogTrace("Factory {FactoryName} created Task {TaskNames}", - factoryName, string.Join(", ", tasks.Select(y => y.ToString()))); - } - - return tasks; - }) - .ToList(); - if (newTasks.Count == 0) + try { - _logger.LogInformation("Nothing to execute for step?"); - return; + var newTasks = _taskFactories + .SelectMany(x => + { + IList tasks = x.CreateAllTasks(CurrentQuest.Quest, seq, step).ToList(); + + if (tasks.Count > 0 && _logger.IsEnabled(LogLevel.Trace)) + { + string factoryName = x.GetType().FullName ?? x.GetType().Name; + if (factoryName.Contains('.', StringComparison.Ordinal)) + factoryName = factoryName[(factoryName.LastIndexOf('.') + 1)..]; + + _logger.LogTrace("Factory {FactoryName} created Task {TaskNames}", + factoryName, string.Join(", ", tasks.Select(y => y.ToString()))); + } + + return tasks; + }) + .ToList(); + if (newTasks.Count == 0) + { + _logger.LogInformation("Nothing to execute for step?"); + return; + } + + _logger.LogInformation("Tasks for {QuestId}, {Sequence}, {Step}: {Tasks}", + CurrentQuest.Quest.QuestId, seq.Sequence, seq.Steps.IndexOf(step), + string.Join(", ", newTasks.Select(x => x.ToString()))); + foreach (var task in newTasks) + _taskQueue.Enqueue(task); + } + catch (Exception e) + { + _logger.LogError(e, "Failed to create tasks"); + _chatGui.PrintError("[Questionable] Failed to start next task sequence, please check /xllog for details."); + Stop("Tasks failed to create"); } - - _logger.LogInformation("Tasks for {QuestId}, {Sequence}, {Step}: {Tasks}", - CurrentQuest.Quest.QuestId, seq.Sequence, seq.Steps.IndexOf(step), - string.Join(", ", newTasks.Select(x => x.ToString()))); - foreach (var task in newTasks) - _taskQueue.Enqueue(task); } - public IList GetRemainingTaskNames() => - _taskQueue.Select(x => x.ToString() ?? "?").ToList(); - public string ToStatString() { return _currentTask == null ? $"- (+{_taskQueue.Count})" : $"{_currentTask} (+{_taskQueue.Count})"; diff --git a/Questionable/Controller/QuestRegistry.cs b/Questionable/Controller/QuestRegistry.cs index 71750a1c..da557891 100644 --- a/Questionable/Controller/QuestRegistry.cs +++ b/Questionable/Controller/QuestRegistry.cs @@ -8,10 +8,11 @@ using System.Linq; using System.Text.Json; using System.Text.Json.Nodes; using Dalamud.Plugin; +using Dalamud.Plugin.Ipc; using Microsoft.Extensions.Logging; using Questionable.Data; using Questionable.Model; -using Questionable.Model.V1; +using Questionable.Model.Questing; using Questionable.QuestPaths; using Questionable.Validation; using Questionable.Validation.Validators; @@ -23,8 +24,9 @@ internal sealed class QuestRegistry private readonly IDalamudPluginInterface _pluginInterface; private readonly QuestData _questData; private readonly QuestValidator _questValidator; - private readonly ILogger _logger; private readonly JsonSchemaValidator _jsonSchemaValidator; + private readonly ILogger _logger; + private readonly ICallGateProvider _reloadDataIpc; private readonly Dictionary _quests = new(); @@ -37,6 +39,7 @@ internal sealed class QuestRegistry _questValidator = questValidator; _jsonSchemaValidator = jsonSchemaValidator; _logger = logger; + _reloadDataIpc = _pluginInterface.GetIpcProvider("Questionable.ReloadData"); } public IEnumerable AllQuests => _quests.Values; @@ -66,6 +69,16 @@ internal sealed class QuestRegistry ValidateQuests(); Reloaded?.Invoke(this, EventArgs.Empty); + try + { + _reloadDataIpc.SendMessage(); + } + catch (Exception e) + { + // why does this even throw + _logger.LogWarning(e, "Error during Reload.SendMessage IPC"); + } + _logger.LogInformation("Loaded {Count} quests in total", _quests.Count); } @@ -101,24 +114,10 @@ internal sealed class QuestRegistry { try { - LoadFromDirectory( - new DirectoryInfo(Path.Combine(pathProjectDirectory.FullName, "2.x - A Realm Reborn")), - LogLevel.Trace); - LoadFromDirectory( - new DirectoryInfo(Path.Combine(pathProjectDirectory.FullName, "3.x - Heavensward")), - LogLevel.Trace); - LoadFromDirectory( - new DirectoryInfo(Path.Combine(pathProjectDirectory.FullName, "4.x - Stormblood")), - LogLevel.Trace); - LoadFromDirectory( - new DirectoryInfo(Path.Combine(pathProjectDirectory.FullName, "5.x - Shadowbringers")), - LogLevel.Trace); - LoadFromDirectory( - new DirectoryInfo(Path.Combine(pathProjectDirectory.FullName, "6.x - Endwalker")), - LogLevel.Trace); - LoadFromDirectory( - new DirectoryInfo(Path.Combine(pathProjectDirectory.FullName, "7.x - Dawntrail")), - LogLevel.Trace); + foreach (var expansionFolder in ExpansionData.ExpansionFolders.Values) + LoadFromDirectory( + new DirectoryInfo(Path.Combine(pathProjectDirectory.FullName, expansionFolder)), + LogLevel.Trace); } catch (Exception e) { diff --git a/Questionable/Controller/Steps/Common/NextQuest.cs b/Questionable/Controller/Steps/Common/NextQuest.cs index 30a705e6..dddc8f9d 100644 --- a/Questionable/Controller/Steps/Common/NextQuest.cs +++ b/Questionable/Controller/Steps/Common/NextQuest.cs @@ -2,7 +2,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Questionable.Model; -using Questionable.Model.V1; +using Questionable.Model.Questing; namespace Questionable.Controller.Steps.Common; diff --git a/Questionable/Controller/Steps/Gathering/DoGather.cs b/Questionable/Controller/Steps/Gathering/DoGather.cs new file mode 100644 index 00000000..914d91b1 --- /dev/null +++ b/Questionable/Controller/Steps/Gathering/DoGather.cs @@ -0,0 +1,77 @@ +using System.Collections.Generic; +using System.Linq; +using Dalamud.Game.ClientState.Conditions; +using Dalamud.Plugin.Services; +using FFXIVClientStructs.FFXIV.Component.GUI; +using LLib.GameUI; +using Questionable.Model.Gathering; + +namespace Questionable.Controller.Steps.Gathering; + +internal sealed class DoGather( + GatheringController gatheringController, + IGameGui gameGui, + ICondition condition) : ITask +{ + private GatheringController.GatheringRequest _currentRequest = null!; + private GatheringNode _currentNode = null!; + private bool _wasGathering; + private List? _slots; + + + public ITask With(GatheringController.GatheringRequest currentRequest, GatheringNode currentNode) + { + _currentRequest = currentRequest; + _currentNode = currentNode; + return this; + } + + public bool Start() => true; + + public unsafe ETaskResult Update() + { + if (gatheringController.HasNodeDisappeared(_currentNode)) + return ETaskResult.TaskComplete; + + if (condition[ConditionFlag.Gathering]) + { + if (gameGui.TryGetAddonByName("GatheringMasterpiece", out AtkUnitBase* _)) + return ETaskResult.TaskComplete; + + _wasGathering = true; + + if (gameGui.TryGetAddonByName("Gathering", out AtkUnitBase* atkUnitBase)) + { + _slots ??= ReadSlots(atkUnitBase); + var slot = _slots.Single(x => x.ItemId == _currentRequest.ItemId); + atkUnitBase->FireCallbackInt(slot.Index); + } + } + + return _wasGathering && !condition[ConditionFlag.Gathering] + ? ETaskResult.TaskComplete + : ETaskResult.StillRunning; + } + + private unsafe List ReadSlots(AtkUnitBase* atkUnitBase) + { + var atkValues = atkUnitBase->AtkValues; + List slots = new List(); + for (int i = 0; i < 8; ++i) + { + // +8 = new item? + uint itemId = atkValues[i * 11 + 7].UInt; + if (itemId == 0) + continue; + + var slot = new SlotInfo(i, itemId); + slots.Add(slot); + } + + return slots; + } + + public override string ToString() => "DoGather"; + + private sealed record SlotInfo(int Index, uint ItemId); +} diff --git a/Questionable/Controller/Steps/Gathering/DoGatherCollectable.cs b/Questionable/Controller/Steps/Gathering/DoGatherCollectable.cs new file mode 100644 index 00000000..6a331ac0 --- /dev/null +++ b/Questionable/Controller/Steps/Gathering/DoGatherCollectable.cs @@ -0,0 +1,151 @@ +using System.Collections.Generic; +using Dalamud.Game.Text; +using Dalamud.Plugin.Services; +using FFXIVClientStructs.FFXIV.Component.GUI; +using LLib.GameUI; +using Microsoft.Extensions.Logging; +using Questionable.Model.Gathering; +using Questionable.Model.Questing; + +namespace Questionable.Controller.Steps.Gathering; + +internal sealed class DoGatherCollectable( + GatheringController gatheringController, + GameFunctions gameFunctions, + IClientState clientState, + IGameGui gameGui, + ILogger logger) : ITask +{ + private GatheringController.GatheringRequest _currentRequest = null!; + private GatheringNode _currentNode = null!; + private Queue? _actionQueue; + + public ITask With(GatheringController.GatheringRequest currentRequest, GatheringNode currentNode) + { + _currentRequest = currentRequest; + _currentNode = currentNode; + return this; + } + + public bool Start() => true; + + public ETaskResult Update() + { + if (gatheringController.HasNodeDisappeared(_currentNode)) + return ETaskResult.TaskComplete; + + NodeCondition? nodeCondition = GetNodeCondition(); + if (nodeCondition == null) + return ETaskResult.TaskComplete; + + if (_actionQueue != null && _actionQueue.TryPeek(out EAction nextAction)) + { + if (gameFunctions.UseAction(nextAction)) + { + logger.LogInformation("Used action {Action} on node", nextAction); + _actionQueue.Dequeue(); + } + + return ETaskResult.StillRunning; + } + + if (nodeCondition.CollectabilityToGoal(_currentRequest.Collectability) > 0) + { + _actionQueue = GetNextActions(nodeCondition); + if (_actionQueue != null) + { + foreach (var action in _actionQueue) + logger.LogInformation("Next Actions {Action}", action); + return ETaskResult.StillRunning; + } + } + + _actionQueue = new Queue(); + _actionQueue.Enqueue(PickAction(EAction.CollectMiner, EAction.CollectBotanist)); + return ETaskResult.StillRunning; + } + + private unsafe NodeCondition? GetNodeCondition() + { + if (gameGui.TryGetAddonByName("GatheringMasterpiece", out AtkUnitBase* atkUnitBase)) + { + var atkValues = atkUnitBase->AtkValues; + return new NodeCondition( + CurrentCollectability: atkValues[13].UInt, + MaxCollectability: atkValues[14].UInt, + CurrentIntegrity: atkValues[62].UInt, + MaxIntegrity: atkValues[63].UInt, + ScrutinyActive: atkValues[80].Bool, + CollectabilityFromScour: atkValues[48].UInt, + CollectabilityFromMeticulous: atkValues[51].UInt + ); + } + + return null; + } + + private Queue? GetNextActions(NodeCondition nodeCondition) + { + uint gp = clientState.LocalPlayer!.CurrentGp; + Queue actions = new(); + + uint neededCollectability = nodeCondition.CollectabilityToGoal(_currentRequest.Collectability); + if (neededCollectability <= nodeCondition.CollectabilityFromMeticulous) + { + actions.Enqueue(PickAction(EAction.MeticulousMiner, EAction.MeticulousBotanist)); + return actions; + } + + if (neededCollectability <= nodeCondition.CollectabilityFromScour) + { + actions.Enqueue(PickAction(EAction.ScourMiner, EAction.ScourBotanist)); + return actions; + } + + // neither action directly solves our problem + if (!nodeCondition.ScrutinyActive && gp >= 200) + { + actions.Enqueue(PickAction(EAction.ScrutinyMiner, EAction.ScrutinyBotanist)); + return actions; + } + + if (nodeCondition.ScrutinyActive) + { + actions.Enqueue(PickAction(EAction.MeticulousMiner, EAction.MeticulousBotanist)); + return actions; + } + else + { + actions.Enqueue(PickAction(EAction.ScourMiner, EAction.ScourBotanist)); + return actions; + } + } + + private EAction PickAction(EAction minerAction, EAction botanistAction) + { + if (clientState.LocalPlayer?.ClassJob.Id == 16) + return minerAction; + else + return botanistAction; + } + + public override string ToString() => + $"DoGatherCollectable({SeIconChar.Collectible.ToIconString()} {_currentRequest.Collectability})"; + + private sealed record NodeCondition( + uint CurrentCollectability, + uint MaxCollectability, + uint CurrentIntegrity, + uint MaxIntegrity, + bool ScrutinyActive, + uint CollectabilityFromScour, + uint CollectabilityFromMeticulous) + { + public uint CollectabilityToGoal(uint goal) + { + if (goal >= CurrentCollectability) + return goal - CurrentCollectability; + return CurrentCollectability == 0 ? 1u : 0u; + } + } +} diff --git a/Questionable/Controller/Steps/Gathering/MoveToLandingLocation.cs b/Questionable/Controller/Steps/Gathering/MoveToLandingLocation.cs new file mode 100644 index 00000000..5f6a2844 --- /dev/null +++ b/Questionable/Controller/Steps/Gathering/MoveToLandingLocation.cs @@ -0,0 +1,79 @@ +using System; +using System.Globalization; +using System.Linq; +using System.Numerics; +using System.Security.Cryptography; +using Dalamud.Game.ClientState.Objects.Enums; +using Dalamud.Plugin.Services; +using GatheringPathRenderer; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Questionable.Controller.Steps.Shared; +using Questionable.External; +using Questionable.Model.Gathering; + +namespace Questionable.Controller.Steps.Gathering; + +internal sealed class MoveToLandingLocation( + IServiceProvider serviceProvider, + GameFunctions gameFunctions, + IObjectTable objectTable, + NavmeshIpc navmeshIpc, + ILogger logger) : ITask +{ + private ushort _territoryId; + private GatheringNode _gatheringNode = null!; + private ITask _moveTask = null!; + + public ITask With(ushort territoryId, GatheringNode gatheringNode) + { + _territoryId = territoryId; + _gatheringNode = gatheringNode; + return this; + } + + public bool Start() + { + var location = _gatheringNode.Locations.First(); + if (_gatheringNode.Locations.Count > 1) + { + var gameObject = objectTable.SingleOrDefault(x => + x.ObjectKind == ObjectKind.GatheringPoint && x.DataId == _gatheringNode.DataId && x.IsTargetable); + if (gameObject == null) + return false; + + location = _gatheringNode.Locations.Single(x => Vector3.Distance(x.Position, gameObject.Position) < 0.1f); + } + + var (target, degrees, range) = GatheringMath.CalculateLandingLocation(location); + logger.LogInformation("Preliminary landing location: {Location}, with degrees = {Degrees}, range = {Range}", + target.ToString("G", CultureInfo.InvariantCulture), degrees, range); + + bool fly = gameFunctions.IsFlyingUnlocked(_territoryId); + Vector3? pointOnFloor = navmeshIpc.GetPointOnFloor(target with { Y = target.Y + 5f }, false); + if (pointOnFloor != null) + pointOnFloor = pointOnFloor.Value with { Y = pointOnFloor.Value.Y + (fly ? 0.5f : 0f) }; + + // since we only allow points that can be landed on, the distance is important but the angle shouldn't matter + if (pointOnFloor != null && Vector3.Distance(pointOnFloor.Value, location.Position) > + location.CalculateMaximumDistance()) + { + pointOnFloor = location.Position + Vector3.Normalize(pointOnFloor.Value - location.Position) * location.CalculateMaximumDistance(); + logger.LogInformation("Adjusted landing location: {Location}", pointOnFloor.Value.ToString("G", CultureInfo.InvariantCulture)); } + else + { + logger.LogInformation("Final landing location: {Location}", + (pointOnFloor ?? target).ToString("G", CultureInfo.InvariantCulture)); + } + + + _moveTask = serviceProvider.GetRequiredService() + .With(_territoryId, pointOnFloor ?? target, 0.25f, dataId: _gatheringNode.DataId, fly: fly, + ignoreDistanceToObject: true); + return _moveTask.Start(); + } + + public ETaskResult Update() => _moveTask.Update(); + + public override string ToString() => $"Land/{_moveTask}"; +} diff --git a/Questionable/Controller/Steps/ITaskFactory.cs b/Questionable/Controller/Steps/ITaskFactory.cs index 159d5c12..287f029f 100644 --- a/Questionable/Controller/Steps/ITaskFactory.cs +++ b/Questionable/Controller/Steps/ITaskFactory.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; using Questionable.Model; -using Questionable.Model.V1; +using Questionable.Model.Questing; namespace Questionable.Controller.Steps; diff --git a/Questionable/Controller/Steps/Interactions/Action.cs b/Questionable/Controller/Steps/Interactions/Action.cs index 94125e43..000811ca 100644 --- a/Questionable/Controller/Steps/Interactions/Action.cs +++ b/Questionable/Controller/Steps/Interactions/Action.cs @@ -7,7 +7,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Questionable.Controller.Steps.Common; using Questionable.Model; -using Questionable.Model.V1; +using Questionable.Model.Questing; namespace Questionable.Controller.Steps.Interactions; diff --git a/Questionable/Controller/Steps/Interactions/AetherCurrent.cs b/Questionable/Controller/Steps/Interactions/AetherCurrent.cs index 82b13d27..3ae2c4b3 100644 --- a/Questionable/Controller/Steps/Interactions/AetherCurrent.cs +++ b/Questionable/Controller/Steps/Interactions/AetherCurrent.cs @@ -4,7 +4,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Questionable.Data; using Questionable.Model; -using Questionable.Model.V1; +using Questionable.Model.Questing; namespace Questionable.Controller.Steps.Interactions; diff --git a/Questionable/Controller/Steps/Interactions/AethernetShard.cs b/Questionable/Controller/Steps/Interactions/AethernetShard.cs index 87a4f348..b3219a6e 100644 --- a/Questionable/Controller/Steps/Interactions/AethernetShard.cs +++ b/Questionable/Controller/Steps/Interactions/AethernetShard.cs @@ -1,9 +1,9 @@ using System; -using FFXIVClientStructs.FFXIV.Client.Game.Object; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Questionable.Model; -using Questionable.Model.V1; +using Questionable.Model.Common; +using Questionable.Model.Questing; using ObjectKind = Dalamud.Game.ClientState.Objects.Enums.ObjectKind; namespace Questionable.Controller.Steps.Interactions; diff --git a/Questionable/Controller/Steps/Interactions/Aetheryte.cs b/Questionable/Controller/Steps/Interactions/Aetheryte.cs index a6bccc4c..c2cab7df 100644 --- a/Questionable/Controller/Steps/Interactions/Aetheryte.cs +++ b/Questionable/Controller/Steps/Interactions/Aetheryte.cs @@ -2,7 +2,8 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Questionable.Model; -using Questionable.Model.V1; +using Questionable.Model.Common; +using Questionable.Model.Questing; namespace Questionable.Controller.Steps.Interactions; diff --git a/Questionable/Controller/Steps/Interactions/Combat.cs b/Questionable/Controller/Steps/Interactions/Combat.cs index 11f5777d..2d7de402 100644 --- a/Questionable/Controller/Steps/Interactions/Combat.cs +++ b/Questionable/Controller/Steps/Interactions/Combat.cs @@ -6,7 +6,7 @@ using Questionable.Controller.Steps.Common; using Questionable.Controller.Steps.Shared; using Questionable.Controller.Utils; using Questionable.Model; -using Questionable.Model.V1; +using Questionable.Model.Questing; namespace Questionable.Controller.Steps.Interactions; @@ -82,10 +82,10 @@ internal static class Combat { private bool _isLastStep; private CombatController.CombatData _combatData = null!; - private IList _completionQuestVariableFlags = null!; + private IList _completionQuestVariableFlags = null!; public ITask With(ushort questId, bool isLastStep, EEnemySpawnType enemySpawnType, IList killEnemyDataIds, - IList completionQuestVariablesFlags, IList complexCombatData) + IList completionQuestVariablesFlags, IList complexCombatData) { _isLastStep = isLastStep; _combatData = new CombatController.CombatData @@ -113,7 +113,7 @@ internal static class Combat if (questWork == null) return ETaskResult.StillRunning; - if (QuestWorkUtils.MatchesQuestWork(_completionQuestVariableFlags, questWork.Value, false)) + if (QuestWorkUtils.MatchesQuestWork(_completionQuestVariableFlags, questWork.Value)) return ETaskResult.TaskComplete; else return ETaskResult.StillRunning; diff --git a/Questionable/Controller/Steps/Interactions/Dive.cs b/Questionable/Controller/Steps/Interactions/Dive.cs index 05e9cf44..3976eb73 100644 --- a/Questionable/Controller/Steps/Interactions/Dive.cs +++ b/Questionable/Controller/Steps/Interactions/Dive.cs @@ -12,7 +12,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Questionable.Controller.Steps.Common; using Questionable.Model; -using Questionable.Model.V1; +using Questionable.Model.Questing; namespace Questionable.Controller.Steps.Interactions; diff --git a/Questionable/Controller/Steps/Interactions/Duty.cs b/Questionable/Controller/Steps/Interactions/Duty.cs index a7de058c..ab2afdcd 100644 --- a/Questionable/Controller/Steps/Interactions/Duty.cs +++ b/Questionable/Controller/Steps/Interactions/Duty.cs @@ -3,7 +3,7 @@ using Dalamud.Game.ClientState.Conditions; using Dalamud.Plugin.Services; using Microsoft.Extensions.DependencyInjection; using Questionable.Model; -using Questionable.Model.V1; +using Questionable.Model.Questing; namespace Questionable.Controller.Steps.Interactions; diff --git a/Questionable/Controller/Steps/Interactions/Emote.cs b/Questionable/Controller/Steps/Interactions/Emote.cs index 44478475..0a5e9064 100644 --- a/Questionable/Controller/Steps/Interactions/Emote.cs +++ b/Questionable/Controller/Steps/Interactions/Emote.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using Microsoft.Extensions.DependencyInjection; using Questionable.Controller.Steps.Common; using Questionable.Model; -using Questionable.Model.V1; +using Questionable.Model.Questing; namespace Questionable.Controller.Steps.Interactions; diff --git a/Questionable/Controller/Steps/Interactions/EquipItem.cs b/Questionable/Controller/Steps/Interactions/EquipItem.cs index 9322563d..95aad266 100644 --- a/Questionable/Controller/Steps/Interactions/EquipItem.cs +++ b/Questionable/Controller/Steps/Interactions/EquipItem.cs @@ -6,7 +6,7 @@ using FFXIVClientStructs.FFXIV.Client.Game; using Lumina.Excel.GeneratedSheets; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using Questionable.Model.V1; +using Questionable.Model.Questing; using Quest = Questionable.Model.Quest; namespace Questionable.Controller.Steps.Interactions; diff --git a/Questionable/Controller/Steps/Interactions/Interact.cs b/Questionable/Controller/Steps/Interactions/Interact.cs index f49394de..df27d3c2 100644 --- a/Questionable/Controller/Steps/Interactions/Interact.cs +++ b/Questionable/Controller/Steps/Interactions/Interact.cs @@ -8,7 +8,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Questionable.Controller.Steps.Shared; using Questionable.Model; -using Questionable.Model.V1; +using Questionable.Model.Questing; namespace Questionable.Controller.Steps.Interactions; @@ -59,7 +59,7 @@ internal static class Interact public bool Start() { - IGameObject? gameObject = gameFunctions.FindObjectByDataId(DataId); + IGameObject? gameObject = gameFunctions.FindObjectByDataId(DataId, targetable: true); if (gameObject == null) { logger.LogWarning("No game object with dataId {DataId}", DataId); @@ -67,17 +67,19 @@ internal static class Interact } // this is only relevant for followers on quests - if (!gameObject.IsTargetable && condition[ConditionFlag.Mounted]) + if (!gameObject.IsTargetable && condition[ConditionFlag.Mounted] && + gameObject.ObjectKind != ObjectKind.GatheringPoint) { + logger.LogInformation("Preparing interaction for {DataId} by unmounting", DataId); _needsUnmount = true; gameFunctions.Unmount(); _continueAt = DateTime.Now.AddSeconds(1); return true; } - if (gameObject.IsTargetable && HasAnyMarker(gameObject)) + if (IsTargetable(gameObject) && HasAnyMarker(gameObject)) { - _interacted = gameFunctions.InteractWith(DataId); + _interacted = gameFunctions.InteractWith(gameObject); _continueAt = DateTime.Now.AddSeconds(0.5); return true; } @@ -104,11 +106,11 @@ internal static class Interact if (!_interacted) { - IGameObject? gameObject = gameFunctions.FindObjectByDataId(DataId); - if (gameObject == null || !gameObject.IsTargetable || !HasAnyMarker(gameObject)) + IGameObject? gameObject = gameFunctions.FindObjectByDataId(DataId, targetable: true); + if (gameObject == null || !IsTargetable(gameObject) || !HasAnyMarker(gameObject)) return ETaskResult.StillRunning; - _interacted = gameFunctions.InteractWith(DataId); + _interacted = gameFunctions.InteractWith(gameObject); _continueAt = DateTime.Now.AddSeconds(0.5); return ETaskResult.StillRunning; } @@ -125,6 +127,11 @@ internal static class Interact return gameObjectStruct->NamePlateIconId != 0; } + private static bool IsTargetable(IGameObject gameObject) + { + return gameObject.IsTargetable; + } + public override string ToString() => $"Interact({DataId})"; } } diff --git a/Questionable/Controller/Steps/Interactions/Jump.cs b/Questionable/Controller/Steps/Interactions/Jump.cs index 5c3d9add..0b0b099d 100644 --- a/Questionable/Controller/Steps/Interactions/Jump.cs +++ b/Questionable/Controller/Steps/Interactions/Jump.cs @@ -5,7 +5,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Questionable.Controller.Steps.Common; using Questionable.Model; -using Questionable.Model.V1; +using Questionable.Model.Questing; namespace Questionable.Controller.Steps.Interactions; diff --git a/Questionable/Controller/Steps/Interactions/Say.cs b/Questionable/Controller/Steps/Interactions/Say.cs index c833badc..20fc7f67 100644 --- a/Questionable/Controller/Steps/Interactions/Say.cs +++ b/Questionable/Controller/Steps/Interactions/Say.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using Microsoft.Extensions.DependencyInjection; using Questionable.Controller.Steps.Common; using Questionable.Model; -using Questionable.Model.V1; +using Questionable.Model.Questing; namespace Questionable.Controller.Steps.Interactions; diff --git a/Questionable/Controller/Steps/Interactions/SinglePlayerDuty.cs b/Questionable/Controller/Steps/Interactions/SinglePlayerDuty.cs index f7d5172a..648627c1 100644 --- a/Questionable/Controller/Steps/Interactions/SinglePlayerDuty.cs +++ b/Questionable/Controller/Steps/Interactions/SinglePlayerDuty.cs @@ -4,7 +4,7 @@ using Dalamud.Plugin.Services; using Microsoft.Extensions.DependencyInjection; using Questionable.External; using Questionable.Model; -using Questionable.Model.V1; +using Questionable.Model.Questing; namespace Questionable.Controller.Steps.Interactions; diff --git a/Questionable/Controller/Steps/Interactions/UseItem.cs b/Questionable/Controller/Steps/Interactions/UseItem.cs index bef6fade..e17f005e 100644 --- a/Questionable/Controller/Steps/Interactions/UseItem.cs +++ b/Questionable/Controller/Steps/Interactions/UseItem.cs @@ -13,7 +13,8 @@ using Questionable.Controller.Steps.Shared; using Questionable.Controller.Utils; using Questionable.Data; using Questionable.Model; -using Questionable.Model.V1; +using Questionable.Model.Common; +using Questionable.Model.Questing; using AethernetShortcut = Questionable.Controller.Steps.Shared.AethernetShortcut; namespace Questionable.Controller.Steps.Interactions; @@ -119,7 +120,7 @@ internal static class UseItem public ushort? QuestId { get; set; } public uint ItemId { get; set; } - public IList CompletionQuestVariablesFlags { get; set; } = new List(); + public IList CompletionQuestVariablesFlags { get; set; } = new List(); public bool StartingCombat { get; set; } protected abstract bool UseItem(); @@ -145,7 +146,7 @@ internal static class UseItem { QuestWork? questWork = gameFunctions.GetQuestEx(QuestId.Value); if (questWork != null && - QuestWorkUtils.MatchesQuestWork(CompletionQuestVariablesFlags, questWork.Value, false)) + QuestWorkUtils.MatchesQuestWork(CompletionQuestVariablesFlags, questWork.Value)) return ETaskResult.TaskComplete; } @@ -202,7 +203,7 @@ internal static class UseItem public uint DataId { get; set; } - public ITask With(ushort? questId, uint dataId, uint itemId, IList completionQuestVariablesFlags) + public ITask With(ushort? questId, uint dataId, uint itemId, IList completionQuestVariablesFlags) { QuestId = questId; DataId = dataId; @@ -226,7 +227,7 @@ internal static class UseItem public Vector3 Position { get; set; } - public ITask With(ushort? questId, Vector3 position, uint itemId, IList completionQuestVariablesFlags) + public ITask With(ushort? questId, Vector3 position, uint itemId, IList completionQuestVariablesFlags) { QuestId = questId; Position = position; @@ -248,7 +249,7 @@ internal static class UseItem public uint DataId { get; set; } - public ITask With(ushort? questId, uint dataId, uint itemId, IList completionQuestVariablesFlags, + public ITask With(ushort? questId, uint dataId, uint itemId, IList completionQuestVariablesFlags, bool startingCombat = false) { QuestId = questId; @@ -269,7 +270,7 @@ internal static class UseItem { private readonly GameFunctions _gameFunctions = gameFunctions; - public ITask With(ushort? questId, uint itemId, IList completionQuestVariablesFlags) + public ITask With(ushort? questId, uint itemId, IList completionQuestVariablesFlags) { QuestId = questId; ItemId = itemId; diff --git a/Questionable/Controller/Steps/Shared/AethernetShortcut.cs b/Questionable/Controller/Steps/Shared/AethernetShortcut.cs index df7f5788..a66e7c0f 100644 --- a/Questionable/Controller/Steps/Shared/AethernetShortcut.cs +++ b/Questionable/Controller/Steps/Shared/AethernetShortcut.cs @@ -8,8 +8,10 @@ using Microsoft.Extensions.Logging; using Questionable.Data; using Questionable.External; using Questionable.Model; -using Questionable.Model.V1; -using Questionable.Model.V1.Converter; +using Questionable.Model.Common; +using Questionable.Model.Common.Converter; +using Questionable.Model.Questing; +using Questionable.Model.Questing.Converter; namespace Questionable.Controller.Steps.Shared; diff --git a/Questionable/Controller/Steps/Shared/AetheryteShortcut.cs b/Questionable/Controller/Steps/Shared/AetheryteShortcut.cs index 413dd37a..ed8d1c07 100644 --- a/Questionable/Controller/Steps/Shared/AetheryteShortcut.cs +++ b/Questionable/Controller/Steps/Shared/AetheryteShortcut.cs @@ -7,7 +7,8 @@ using Microsoft.Extensions.Logging; using Questionable.Controller.Steps.Common; using Questionable.Data; using Questionable.Model; -using Questionable.Model.V1; +using Questionable.Model.Common; +using Questionable.Model.Questing; namespace Questionable.Controller.Steps.Shared; diff --git a/Questionable/Controller/Steps/Shared/GatheringRequiredItems.cs b/Questionable/Controller/Steps/Shared/GatheringRequiredItems.cs new file mode 100644 index 00000000..5d8022e6 --- /dev/null +++ b/Questionable/Controller/Steps/Shared/GatheringRequiredItems.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using Dalamud.Game.Text; +using Dalamud.Plugin.Services; +using Microsoft.Extensions.DependencyInjection; +using Questionable.Data; +using Questionable.GatheringPaths; +using Questionable.Model; +using Questionable.Model.Gathering; +using Questionable.Model.Questing; + +namespace Questionable.Controller.Steps.Shared; + +internal static class GatheringRequiredItems +{ + internal sealed class Factory( + IServiceProvider serviceProvider, + IClientState clientState, + GatheringData gatheringData) : ITaskFactory + { + public IEnumerable CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step) + { + foreach (var requiredGatheredItems in step.RequiredGatheredItems) + { + if (!gatheringData.TryGetGatheringPointId(requiredGatheredItems.ItemId, + clientState.LocalPlayer!.ClassJob.Id, out var gatheringPointId)) + throw new TaskException($"No gathering point found for item {requiredGatheredItems.ItemId}"); + + if (!AssemblyGatheringLocationLoader.GetLocations() + .TryGetValue(gatheringPointId, out GatheringRoot? gatheringRoot)) + throw new TaskException("No path found for gathering point"); + + if (gatheringRoot.AetheryteShortcut != null && clientState.TerritoryType != gatheringRoot.TerritoryId) + { + yield return serviceProvider.GetRequiredService() + .With(null, gatheringRoot.AetheryteShortcut.Value, gatheringRoot.TerritoryId); + } + + yield return serviceProvider.GetRequiredService() + .With(gatheringPointId, requiredGatheredItems); + } + } + + public ITask CreateTask(Quest quest, QuestSequence sequence, QuestStep step) + => throw new NotImplementedException(); + } + + internal sealed class StartGathering(GatheringController gatheringController) : ITask + { + private ushort _gatheringPointId; + private GatheredItem _gatheredItem = null!; + + public ITask With(ushort gatheringPointId, GatheredItem gatheredItem) + { + _gatheringPointId = gatheringPointId; + _gatheredItem = gatheredItem; + return this; + } + + public bool Start() + { + return gatheringController.Start(new GatheringController.GatheringRequest(_gatheringPointId, + _gatheredItem.ItemId, _gatheredItem.ItemCount, _gatheredItem.Collectability)); + } + + public ETaskResult Update() + { + if (gatheringController.Update() == GatheringController.EStatus.Complete) + return ETaskResult.TaskComplete; + + return ETaskResult.StillRunning; + } + + public override string ToString() + { + if (_gatheredItem.Collectability == 0) + return $"Gather({_gatheredItem.ItemCount}x {_gatheredItem.ItemId})"; + else + return $"Gather({_gatheredItem.ItemCount}x {_gatheredItem.ItemId} {SeIconChar.Collectible.ToIconString()} {_gatheredItem.Collectability})"; + } + } +} diff --git a/Questionable/Controller/Steps/Shared/Move.cs b/Questionable/Controller/Steps/Shared/Move.cs index e7474688..24d3e7f8 100644 --- a/Questionable/Controller/Steps/Shared/Move.cs +++ b/Questionable/Controller/Steps/Shared/Move.cs @@ -13,7 +13,7 @@ using Questionable.Controller.NavigationOverrides; using Questionable.Controller.Steps.Common; using Questionable.Data; using Questionable.Model; -using Questionable.Model.V1; +using Questionable.Model.Questing; namespace Questionable.Controller.Steps.Shared; diff --git a/Questionable/Controller/Steps/Shared/SkipCondition.cs b/Questionable/Controller/Steps/Shared/SkipCondition.cs index 6710071c..5d50ba18 100644 --- a/Questionable/Controller/Steps/Shared/SkipCondition.cs +++ b/Questionable/Controller/Steps/Shared/SkipCondition.cs @@ -11,7 +11,8 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Questionable.Controller.Utils; using Questionable.Model; -using Questionable.Model.V1; +using Questionable.Model.Common; +using Questionable.Model.Questing; namespace Questionable.Controller.Steps.Shared; @@ -156,15 +157,27 @@ internal static class SkipCondition } QuestWork? questWork = gameFunctions.GetQuestEx(QuestId); - if (questWork != null) + if (QuestWorkUtils.HasCompletionFlags(Step.CompletionQuestVariablesFlags) && questWork != null) { - if (QuestWorkUtils.MatchesQuestWork(Step.CompletionQuestVariablesFlags, questWork.Value, true)) + if (QuestWorkUtils.MatchesQuestWork(Step.CompletionQuestVariablesFlags, questWork.Value)) { - logger.LogInformation("Skipping step, as quest variables match"); + logger.LogInformation("Skipping step, as quest variables match (step is complete)"); return true; } + } - if (!QuestWorkUtils.MatchesRequiredQuestWorkConfig(Step.RequiredQuestVariables, questWork.Value, + if (Step is { SkipConditions.StepIf: { } conditions } && questWork != null) + { + if (QuestWorkUtils.MatchesQuestWork(conditions.CompletionQuestVariablesFlags, questWork.Value)) + { + logger.LogInformation("Skipping step, as quest variables match (step can be skipped)"); + return true; + } + } + + if (Step is { RequiredQuestVariables: { } requiredQuestVariables } && questWork != null) + { + if (!QuestWorkUtils.MatchesRequiredQuestWorkConfig(requiredQuestVariables, questWork.Value, logger)) { logger.LogInformation("Skipping step, as required variables do not match"); diff --git a/Questionable/Controller/Steps/Shared/StepDisabled.cs b/Questionable/Controller/Steps/Shared/StepDisabled.cs index 16bb8365..609f3143 100644 --- a/Questionable/Controller/Steps/Shared/StepDisabled.cs +++ b/Questionable/Controller/Steps/Shared/StepDisabled.cs @@ -2,7 +2,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Questionable.Model; -using Questionable.Model.V1; +using Questionable.Model.Questing; namespace Questionable.Controller.Steps.Shared; diff --git a/Questionable/Controller/Steps/Shared/WaitAtEnd.cs b/Questionable/Controller/Steps/Shared/WaitAtEnd.cs index 16a5589b..6d54623e 100644 --- a/Questionable/Controller/Steps/Shared/WaitAtEnd.cs +++ b/Questionable/Controller/Steps/Shared/WaitAtEnd.cs @@ -12,7 +12,7 @@ using Questionable.Controller.Steps.Common; using Questionable.Controller.Utils; using Questionable.Data; using Questionable.Model; -using Questionable.Model.V1; +using Questionable.Model.Questing; namespace Questionable.Controller.Steps.Shared; @@ -27,7 +27,7 @@ internal static class WaitAtEnd { public IEnumerable CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step) { - if (step.CompletionQuestVariablesFlags.Count == 6 && step.CompletionQuestVariablesFlags.Any(x => x is > 0)) + if (step.CompletionQuestVariablesFlags.Count == 6 && QuestWorkUtils.HasCompletionFlags(step.CompletionQuestVariablesFlags)) { var task = serviceProvider.GetRequiredService() .With(quest, step); @@ -164,7 +164,7 @@ internal static class WaitAtEnd { public Quest Quest { get; set; } = null!; public QuestStep Step { get; set; } = null!; - public IList Flags { get; set; } = null!; + public IList Flags { get; set; } = null!; public ITask With(Quest quest, QuestStep step) { @@ -180,13 +180,13 @@ internal static class WaitAtEnd { QuestWork? questWork = gameFunctions.GetQuestEx(Quest.QuestId); return questWork != null && - QuestWorkUtils.MatchesQuestWork(Step.CompletionQuestVariablesFlags, questWork.Value, false) + QuestWorkUtils.MatchesQuestWork(Step.CompletionQuestVariablesFlags, questWork.Value) ? ETaskResult.TaskComplete : ETaskResult.StillRunning; } public override string ToString() => - $"Wait(QW: {string.Join(", ", Flags.Select(x => x?.ToString(CultureInfo.InvariantCulture) ?? "-"))})"; + $"Wait(QW: {string.Join(", ", Flags.Select(x => x?.ToString() ?? "-"))})"; } internal sealed class WaitObjectAtPosition(GameFunctions gameFunctions) : ITask diff --git a/Questionable/Controller/Steps/Shared/WaitAtStart.cs b/Questionable/Controller/Steps/Shared/WaitAtStart.cs index 4a1f411e..b39e301d 100644 --- a/Questionable/Controller/Steps/Shared/WaitAtStart.cs +++ b/Questionable/Controller/Steps/Shared/WaitAtStart.cs @@ -2,7 +2,7 @@ using Microsoft.Extensions.DependencyInjection; using Questionable.Controller.Steps.Common; using Questionable.Model; -using Questionable.Model.V1; +using Questionable.Model.Questing; namespace Questionable.Controller.Steps.Shared; diff --git a/Questionable/Controller/Utils/QuestWorkUtils.cs b/Questionable/Controller/Utils/QuestWorkUtils.cs index 33edd498..59235918 100644 --- a/Questionable/Controller/Utils/QuestWorkUtils.cs +++ b/Questionable/Controller/Utils/QuestWorkUtils.cs @@ -4,46 +4,56 @@ using System.Linq; using FFXIVClientStructs.FFXIV.Application.Network.WorkDefinitions; using Microsoft.Extensions.Logging; using Questionable.Controller.Steps.Shared; -using Questionable.Model.V1; +using Questionable.Model.Questing; namespace Questionable.Controller.Utils; internal static class QuestWorkUtils { - public static bool HasCompletionFlags(IList completionQuestVariablesFlags) + public static bool HasCompletionFlags(IList completionQuestVariablesFlags) { - return completionQuestVariablesFlags.Count == 6 && completionQuestVariablesFlags.Any(x => x != null); + return completionQuestVariablesFlags.Count == 6 && completionQuestVariablesFlags.Any(x => x != null && (x.High != 0 || x.Low != 0)); } - /// - /// Positive values: Must be set to this value; will wait for the step to have these set. - /// Negative values: Will skip if set to this value, won't wait for this to be set. - /// - public static bool MatchesQuestWork(IList completionQuestVariablesFlags, QuestWork questWork, bool forSkip) + public static bool MatchesQuestWork(IList completionQuestVariablesFlags, QuestWork questWork) { if (!HasCompletionFlags(completionQuestVariablesFlags)) return false; for (int i = 0; i < 6; ++i) { - short? check = completionQuestVariablesFlags[i]; + QuestWorkValue? check = completionQuestVariablesFlags[i]; if (check == null) continue; - byte actualValue = questWork.Variables[i]; - byte checkByte = check > 0 ? (byte)check : (byte)-check; - if (forSkip) + EQuestWorkMode mode = check.Mode; + + byte actualHigh = (byte)(questWork.Variables[i] >> 4); + byte actualLow = (byte)(questWork.Variables[i] & 0xF); + + byte? checkHigh = check.High; + byte? checkLow = check.Low; + + byte expectedHigh = checkHigh.GetValueOrDefault(); + byte expectedLow = checkLow.GetValueOrDefault(); + if (mode == EQuestWorkMode.Exact) { - byte expectedValue = (byte)Math.Abs(check.Value); - if ((actualValue & checkByte) != expectedValue) + if (checkHigh != null && actualHigh != expectedHigh) + return false; + + if (checkLow != null && actualLow != expectedLow) return false; } - else if (!forSkip && check > 0) + else if (mode == EQuestWorkMode.Bitwise) { - byte expectedValue = check > 0 ? (byte)check : (byte)0; - if ((actualValue & checkByte) != expectedValue) + if (checkHigh != null && (actualHigh & checkHigh) != expectedHigh) + return false; + + if (checkLow != null && (actualLow & checkLow) != expectedLow) return false; } + else + throw new InvalidOperationException($"Unknown qw mode {mode}"); } return true; @@ -54,7 +64,7 @@ internal static class QuestWorkUtils { if (requiredQuestVariables.Count != 6 || requiredQuestVariables.All(x => x == null || x.Count == 0)) { - logger.LogInformation("No RQW defined"); + logger.LogDebug("No RQW defined"); return true; } @@ -71,7 +81,7 @@ internal static class QuestWorkUtils foreach (QuestWorkValue expectedValue in requiredQuestVariables[i]!) { - logger.LogInformation("H: {ExpectedHigh} - {ActualHigh}, L: {ExpectedLow} - {ActualLow}", + logger.LogDebug("H: {ExpectedHigh} - {ActualHigh}, L: {ExpectedLow} - {ActualLow}", expectedValue.High, high, expectedValue.Low, low); if (expectedValue.High != null && expectedValue.High != high) continue; diff --git a/Questionable/Data/AetheryteData.cs b/Questionable/Data/AetheryteData.cs index 9e216cdc..28d8e459 100644 --- a/Questionable/Data/AetheryteData.cs +++ b/Questionable/Data/AetheryteData.cs @@ -4,8 +4,7 @@ using System.Linq; using System.Numerics; using Dalamud.Plugin.Services; using Lumina.Excel.GeneratedSheets; -using Microsoft.Extensions.Logging; -using Questionable.Model.V1; +using Questionable.Model.Common; namespace Questionable.Data; diff --git a/Questionable/Data/GatheringData.cs b/Questionable/Data/GatheringData.cs new file mode 100644 index 00000000..ed44fdc2 --- /dev/null +++ b/Questionable/Data/GatheringData.cs @@ -0,0 +1,49 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Dalamud.Plugin.Services; +using Lumina.Excel.GeneratedSheets; + +namespace Questionable.Data; + +internal sealed class GatheringData +{ + private readonly Dictionary _gatheringItemToItem; + private readonly Dictionary _minerGatheringPoints = []; + private readonly Dictionary _botanistGatheringPoints = []; + + public GatheringData(IDataManager dataManager) + { + _gatheringItemToItem = dataManager.GetExcelSheet()! + .Where(x => x.RowId != 0 && x.Item != 0) + .ToDictionary(x => x.RowId, x => (uint)x.Item); + + foreach (var gatheringPointBase in dataManager.GetExcelSheet()!) + { + foreach (var gatheringItemId in gatheringPointBase.Item.Where(x => x != 0)) + { + if (_gatheringItemToItem.TryGetValue((uint)gatheringItemId, out uint itemId)) + { + if (gatheringPointBase.GatheringType.Row is 0 or 1) + _minerGatheringPoints[itemId] = (ushort)gatheringPointBase.RowId; + else if (gatheringPointBase.GatheringType.Row is 2 or 3) + _botanistGatheringPoints[itemId] = (ushort)gatheringPointBase.RowId; + } + } + } + } + + + public bool TryGetGatheringPointId(uint itemId, uint classJobId, out ushort gatheringPointId) + { + if (classJobId == 16) + return _minerGatheringPoints.TryGetValue(itemId, out gatheringPointId); + else if (classJobId == 17) + return _botanistGatheringPoints.TryGetValue(itemId, out gatheringPointId); + else + { + gatheringPointId = 0; + return false; + } + } +} diff --git a/Questionable/External/LifestreamIpc.cs b/Questionable/External/LifestreamIpc.cs index deab3a24..f3267709 100644 --- a/Questionable/External/LifestreamIpc.cs +++ b/Questionable/External/LifestreamIpc.cs @@ -1,7 +1,7 @@ using Dalamud.Plugin; using Dalamud.Plugin.Ipc; using Questionable.Data; -using Questionable.Model.V1; +using Questionable.Model.Common; namespace Questionable.External; diff --git a/Questionable/External/NavmeshIpc.cs b/Questionable/External/NavmeshIpc.cs index 2f6c5e36..0c8ce0a7 100644 --- a/Questionable/External/NavmeshIpc.cs +++ b/Questionable/External/NavmeshIpc.cs @@ -108,11 +108,11 @@ internal sealed class NavmeshIpc } } - public Vector3? GetPointOnFloor(Vector3 position) + public Vector3? GetPointOnFloor(Vector3 position, bool unlandable) { try { - return _queryPointOnFloor.InvokeFunc(position, true, 1); + return _queryPointOnFloor.InvokeFunc(position, unlandable, 0.2f); } catch (IpcError) { diff --git a/Questionable/GameFunctions.cs b/Questionable/GameFunctions.cs index ee975acc..cf824af6 100644 --- a/Questionable/GameFunctions.cs +++ b/Questionable/GameFunctions.cs @@ -24,7 +24,8 @@ using Microsoft.Extensions.Logging; using Questionable.Controller; using Questionable.Data; using Questionable.Model; -using Questionable.Model.V1; +using Questionable.Model.Common; +using Questionable.Model.Questing; using Action = Lumina.Excel.GeneratedSheets2.Action; using BattleChara = FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara; using ContentFinderCondition = Lumina.Excel.GeneratedSheets.ContentFinderCondition; @@ -409,10 +410,13 @@ internal sealed unsafe class GameFunctions playerState->IsAetherCurrentUnlocked(aetherCurrentId); } - public IGameObject? FindObjectByDataId(uint dataId, ObjectKind? kind = null) + public IGameObject? FindObjectByDataId(uint dataId, ObjectKind? kind = null, bool targetable = false) { foreach (var gameObject in _objectTable) { + if (targetable && !gameObject.IsTargetable) + continue; + if (gameObject.ObjectKind is ObjectKind.Player or ObjectKind.Companion or ObjectKind.MountType or ObjectKind.Retainer or ObjectKind.Housing) continue; @@ -431,19 +435,31 @@ internal sealed unsafe class GameFunctions { IGameObject? gameObject = FindObjectByDataId(dataId, kind); if (gameObject != null) - { - _logger.LogInformation("Setting target with {DataId} to {ObjectId}", dataId, gameObject.EntityId); - _targetManager.Target = null; - _targetManager.Target = gameObject; + return InteractWith(gameObject); + _logger.LogDebug("Game object is null"); + return false; + } + + public bool InteractWith(IGameObject gameObject) + { + _logger.LogInformation("Setting target with {DataId} to {ObjectId}", gameObject.DataId, gameObject.EntityId); + _targetManager.Target = null; + _targetManager.Target = gameObject; + + if (gameObject.ObjectKind == ObjectKind.GatheringPoint) + { + TargetSystem.Instance()->OpenObjectInteraction((GameObject*)gameObject.Address); + _logger.LogInformation("Interact result: (none) for GatheringPoint"); + return true; + } + else + { long result = (long)TargetSystem.Instance()->InteractWithObject((GameObject*)gameObject.Address, false); _logger.LogInformation("Interact result: {Result}", result); return result != 7 && result > 0; } - - _logger.LogDebug("Game object is null"); - return false; } public bool UseItem(uint itemId) @@ -730,7 +746,7 @@ internal sealed unsafe class GameFunctions _condition[ConditionFlag.OccupiedInQuestEvent] || _condition[ConditionFlag.OccupiedInCutSceneEvent] || _condition[ConditionFlag.Casting] || _condition[ConditionFlag.Unknown57] || _condition[ConditionFlag.BetweenAreas] || _condition[ConditionFlag.BetweenAreas51] || - _condition[ConditionFlag.Jumping61]; + _condition[ConditionFlag.Jumping61] || _condition[ConditionFlag.Gathering42]; } public bool IsLoadingScreenVisible() diff --git a/Questionable/Model/Quest.cs b/Questionable/Model/Quest.cs index a33bcd18..5124de9d 100644 --- a/Questionable/Model/Quest.cs +++ b/Questionable/Model/Quest.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; using System.Linq; -using Questionable.Model.V1; +using Questionable.Model.Questing; namespace Questionable.Model; diff --git a/Questionable/Questionable.csproj b/Questionable/Questionable.csproj index ae232ac1..ceb37e8c 100644 --- a/Questionable/Questionable.csproj +++ b/Questionable/Questionable.csproj @@ -18,6 +18,7 @@ + diff --git a/Questionable/QuestionablePlugin.cs b/Questionable/QuestionablePlugin.cs index df100963..944e6beb 100644 --- a/Questionable/QuestionablePlugin.cs +++ b/Questionable/QuestionablePlugin.cs @@ -13,6 +13,7 @@ using Questionable.Controller.CombatModules; using Questionable.Controller.NavigationOverrides; using Questionable.Controller.Steps.Shared; using Questionable.Controller.Steps.Common; +using Questionable.Controller.Steps.Gathering; using Questionable.Controller.Steps.Interactions; using Questionable.Data; using Questionable.External; @@ -89,6 +90,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); @@ -102,9 +104,13 @@ public sealed class QuestionablePlugin : IDalamudPlugin // individual tasks serviceCollection.AddTransient(); serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); // task factories serviceCollection.AddTaskWithFactory(); + serviceCollection.AddTaskWithFactory(); serviceCollection.AddTaskWithFactory(); serviceCollection.AddTaskWithFactory(); serviceCollection.AddTaskWithFactory(); @@ -149,6 +155,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); } diff --git a/Questionable/Validation/Validators/AethernetShortcutValidator.cs b/Questionable/Validation/Validators/AethernetShortcutValidator.cs index 36643c3e..e620d8a5 100644 --- a/Questionable/Validation/Validators/AethernetShortcutValidator.cs +++ b/Questionable/Validation/Validators/AethernetShortcutValidator.cs @@ -2,7 +2,7 @@ using System.Linq; using Questionable.Data; using Questionable.Model; -using Questionable.Model.V1; +using Questionable.Model.Questing; namespace Questionable.Validation.Validators; diff --git a/Questionable/Validation/Validators/BasicSequenceValidator.cs b/Questionable/Validation/Validators/BasicSequenceValidator.cs index 0c10e032..0127bc19 100644 --- a/Questionable/Validation/Validators/BasicSequenceValidator.cs +++ b/Questionable/Validation/Validators/BasicSequenceValidator.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Linq; using Questionable.Model; -using Questionable.Model.V1; +using Questionable.Model.Questing; namespace Questionable.Validation.Validators; diff --git a/Questionable/Validation/Validators/CompletionFlagsValidator.cs b/Questionable/Validation/Validators/CompletionFlagsValidator.cs index 462e9289..2cfc58d7 100644 --- a/Questionable/Validation/Validators/CompletionFlagsValidator.cs +++ b/Questionable/Validation/Validators/CompletionFlagsValidator.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Numerics; using Questionable.Controller.Utils; using Questionable.Model; +using Questionable.Model.Questing; namespace Questionable.Validation.Validators; @@ -19,10 +20,13 @@ internal sealed class CompletionFlagsValidator : IQuestValidator { return Enumerable.Range(0, 6).Select(y => { - short? value = x.CompletionQuestVariablesFlags[y]; - if (value == null || value.Value < 0) + QuestWorkValue? value = x.CompletionQuestVariablesFlags[y]; + if (value == null) return 0; - return (long)BitOperations.RotateLeft((ulong)value.Value, 8 * y); + + // this isn't perfect, as it assumes {High: 1, Low: null} == {High: 1, Low: 0} + return (long)BitOperations.RotateLeft( + (ulong)(value.High.GetValueOrDefault() * 16 + value.Low.GetValueOrDefault()), 8 * y); }) .Sum(); } @@ -46,7 +50,8 @@ internal sealed class CompletionFlagsValidator : IQuestValidator Step = i, Type = EIssueType.DuplicateCompletionFlags, Severity = EIssueSeverity.Error, - Description = $"Duplicate completion flags: {string.Join(", ", sequence.Steps[i].CompletionQuestVariablesFlags)}", + Description = + $"Duplicate completion flags: {string.Join(", ", sequence.Steps[i].CompletionQuestVariablesFlags)}", }; } } diff --git a/Questionable/Validation/Validators/JsonSchemaValidator.cs b/Questionable/Validation/Validators/JsonSchemaValidator.cs index b3ed6f4e..7cacc520 100644 --- a/Questionable/Validation/Validators/JsonSchemaValidator.cs +++ b/Questionable/Validation/Validators/JsonSchemaValidator.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Globalization; using System.Text.Json.Nodes; using Json.Schema; @@ -12,6 +13,13 @@ internal sealed class JsonSchemaValidator : IQuestValidator private readonly Dictionary _questNodes = new(); private JsonSchema? _questSchema; + public JsonSchemaValidator() + { + SchemaRegistry.Global.Register( + new Uri("https://git.carvel.li/liza/Questionable/raw/branch/master/Questionable.Model/common-schema.json"), + JsonSchema.FromStream(AssemblyModelLoader.CommonSchema).AsTask().Result); + } + public IEnumerable Validate(Quest quest) { _questSchema ??= JsonSchema.FromStream(AssemblyQuestLoader.QuestSchema).AsTask().Result; @@ -36,7 +44,6 @@ internal sealed class JsonSchemaValidator : IQuestValidator }; } } - } public void Enqueue(ushort questId, JsonNode questNode) => _questNodes[questId] = questNode; diff --git a/Questionable/Validation/Validators/UniqueStartStopValidator.cs b/Questionable/Validation/Validators/UniqueStartStopValidator.cs index b95522f1..b385306d 100644 --- a/Questionable/Validation/Validators/UniqueStartStopValidator.cs +++ b/Questionable/Validation/Validators/UniqueStartStopValidator.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Linq; using Questionable.Model; -using Questionable.Model.V1; +using Questionable.Model.Questing; namespace Questionable.Validation.Validators; diff --git a/Questionable/Windows/ConfigWindow.cs b/Questionable/Windows/ConfigWindow.cs index 11693300..0fa60d69 100644 --- a/Questionable/Windows/ConfigWindow.cs +++ b/Questionable/Windows/ConfigWindow.cs @@ -76,6 +76,13 @@ internal sealed class ConfigWindow : LWindow, IPersistableWindowConfig Save(); } + bool useEscToCancelQuesting = _configuration.General.UseEscToCancelQuesting; + if (ImGui.Checkbox("Use ESC to cancel questing/movement", ref useEscToCancelQuesting)) + { + _configuration.General.UseEscToCancelQuesting = useEscToCancelQuesting; + Save(); + } + ImGui.EndTabItem(); } diff --git a/Questionable/Windows/DebugOverlay.cs b/Questionable/Windows/DebugOverlay.cs index 20b572fe..e7cc34e7 100644 --- a/Questionable/Windows/DebugOverlay.cs +++ b/Questionable/Windows/DebugOverlay.cs @@ -11,7 +11,7 @@ using ImGuiNET; using Questionable.Controller; using Questionable.Data; using Questionable.Model; -using Questionable.Model.V1; +using Questionable.Model.Questing; namespace Questionable.Windows; diff --git a/Questionable/Windows/QuestComponents/ActiveQuestComponent.cs b/Questionable/Windows/QuestComponents/ActiveQuestComponent.cs index f819526a..17ab37f4 100644 --- a/Questionable/Windows/QuestComponents/ActiveQuestComponent.cs +++ b/Questionable/Windows/QuestComponents/ActiveQuestComponent.cs @@ -13,7 +13,7 @@ using FFXIVClientStructs.FFXIV.Application.Network.WorkDefinitions; using ImGuiNET; using Questionable.Controller; using Questionable.Controller.Steps.Shared; -using Questionable.Model.V1; +using Questionable.Model.Questing; namespace Questionable.Windows.QuestComponents; @@ -22,6 +22,7 @@ internal sealed class ActiveQuestComponent private readonly QuestController _questController; private readonly MovementController _movementController; private readonly CombatController _combatController; + private readonly GatheringController _gatheringController; private readonly GameFunctions _gameFunctions; private readonly ICommandManager _commandManager; private readonly IDalamudPluginInterface _pluginInterface; @@ -29,14 +30,22 @@ internal sealed class ActiveQuestComponent private readonly QuestRegistry _questRegistry; private readonly IChatGui _chatGui; - public ActiveQuestComponent(QuestController questController, MovementController movementController, - CombatController combatController, GameFunctions gameFunctions, ICommandManager commandManager, - IDalamudPluginInterface pluginInterface, Configuration configuration, QuestRegistry questRegistry, + public ActiveQuestComponent( + QuestController questController, + MovementController movementController, + CombatController combatController, + GatheringController gatheringController, + GameFunctions gameFunctions, + ICommandManager commandManager, + IDalamudPluginInterface pluginInterface, + Configuration configuration, + QuestRegistry questRegistry, IChatGui chatGui) { _questController = questController; _movementController = movementController; _combatController = combatController; + _gatheringController = gatheringController; _gameFunctions = gameFunctions; _commandManager = commandManager; _pluginInterface = pluginInterface; @@ -93,6 +102,7 @@ internal sealed class ActiveQuestComponent { _movementController.Stop(); _questController.Stop("Manual (no active quest)"); + _gatheringController.Stop("Manual (no active quest)"); } } } @@ -233,6 +243,7 @@ internal sealed class ActiveQuestComponent { _movementController.Stop(); _questController.Stop("Manual"); + _gatheringController.Stop("Manual"); } bool lastStep = currentStep == diff --git a/Questionable/Windows/QuestComponents/CreationUtilsComponent.cs b/Questionable/Windows/QuestComponents/CreationUtilsComponent.cs index a1fac0a4..b824f1af 100644 --- a/Questionable/Windows/QuestComponents/CreationUtilsComponent.cs +++ b/Questionable/Windows/QuestComponents/CreationUtilsComponent.cs @@ -16,7 +16,8 @@ using Microsoft.Extensions.Logging; using Questionable.Controller; using Questionable.Data; using Questionable.Model; -using Questionable.Model.V1; +using Questionable.Model.Common; +using Questionable.Model.Questing; using ObjectKind = Dalamud.Game.ClientState.Objects.Enums.ObjectKind; namespace Questionable.Windows.QuestComponents; @@ -160,23 +161,38 @@ internal sealed class CreationUtilsComponent "Left click: Copy target position as JSON.\nRight click: Copy target position as C# code."); if (copy) { - string interactionType = gameObject->NamePlateIconId switch + var target = _targetManager.Target; + if (target.ObjectKind == ObjectKind.GatheringPoint) { - 71201 or 71211 or 71221 or 71231 or 71341 or 71351 => "AcceptQuest", - 71202 or 71212 or 71222 or 71232 or 71342 or 71352 => "AcceptQuest", // repeatable - 71205 or 71215 or 71225 or 71235 or 71345 or 71355 => "CompleteQuest", - _ => "Interact", - }; - ImGui.SetClipboardText($$""" - "DataId": {{_targetManager.Target.DataId}}, - "Position": { - "X": {{_targetManager.Target.Position.X.ToString(CultureInfo.InvariantCulture)}}, - "Y": {{_targetManager.Target.Position.Y.ToString(CultureInfo.InvariantCulture)}}, - "Z": {{_targetManager.Target.Position.Z.ToString(CultureInfo.InvariantCulture)}} - }, - "TerritoryId": {{_clientState.TerritoryType}}, - "InteractionType": "{{interactionType}}" - """); + ImGui.SetClipboardText($$""" + "DataId": {{target.DataId}}, + "Position": { + "X": {{target.Position.X.ToString(CultureInfo.InvariantCulture)}}, + "Y": {{target.Position.Y.ToString(CultureInfo.InvariantCulture)}}, + "Z": {{target.Position.Z.ToString(CultureInfo.InvariantCulture)}} + } + """); + } + else + { + string interactionType = gameObject->NamePlateIconId switch + { + 71201 or 71211 or 71221 or 71231 or 71341 or 71351 => "AcceptQuest", + 71202 or 71212 or 71222 or 71232 or 71342 or 71352 => "AcceptQuest", // repeatable + 71205 or 71215 or 71225 or 71235 or 71345 or 71355 => "CompleteQuest", + _ => "Interact", + }; + ImGui.SetClipboardText($$""" + "DataId": {{target.DataId}}, + "Position": { + "X": {{target.Position.X.ToString(CultureInfo.InvariantCulture)}}, + "Y": {{target.Position.Y.ToString(CultureInfo.InvariantCulture)}}, + "Z": {{target.Position.Z.ToString(CultureInfo.InvariantCulture)}} + }, + "TerritoryId": {{_clientState.TerritoryType}}, + "InteractionType": "{{interactionType}}" + """); + } } else if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) { diff --git a/Questionable/Windows/QuestComponents/RemainingTasksComponent.cs b/Questionable/Windows/QuestComponents/RemainingTasksComponent.cs index bd35f699..84d44c90 100644 --- a/Questionable/Windows/QuestComponents/RemainingTasksComponent.cs +++ b/Questionable/Windows/QuestComponents/RemainingTasksComponent.cs @@ -1,4 +1,5 @@ -using ImGuiNET; +using System.Collections.Generic; +using ImGuiNET; using Questionable.Controller; namespace Questionable.Windows.QuestComponents; @@ -6,22 +7,36 @@ namespace Questionable.Windows.QuestComponents; internal sealed class RemainingTasksComponent { private readonly QuestController _questController; + private readonly GatheringController _gatheringController; - public RemainingTasksComponent(QuestController questController) + public RemainingTasksComponent(QuestController questController, GatheringController gatheringController) { _questController = questController; + _gatheringController = gatheringController; } public void Draw() { - var remainingTasks = _questController.GetRemainingTaskNames(); - if (remainingTasks.Count > 0) + IList gatheringTasks = _gatheringController.GetRemainingTaskNames(); + if (gatheringTasks.Count > 0) { ImGui.Separator(); ImGui.BeginDisabled(); - foreach (var task in remainingTasks) - ImGui.TextUnformatted(task); + foreach (var task in gatheringTasks) + ImGui.TextUnformatted($"G: {task}"); ImGui.EndDisabled(); } + else + { + var remainingTasks = _questController.GetRemainingTaskNames(); + if (remainingTasks.Count > 0) + { + ImGui.Separator(); + ImGui.BeginDisabled(); + foreach (var task in remainingTasks) + ImGui.TextUnformatted(task); + ImGui.EndDisabled(); + } + } } } diff --git a/Questionable/Windows/QuestSelectionWindow.cs b/Questionable/Windows/QuestSelectionWindow.cs index 6f420786..8da2c2d0 100644 --- a/Questionable/Windows/QuestSelectionWindow.cs +++ b/Questionable/Windows/QuestSelectionWindow.cs @@ -19,7 +19,7 @@ using LLib.ImGui; using Questionable.Controller; using Questionable.Data; using Questionable.Model; -using Questionable.Model.V1; +using Questionable.Model.Questing; using Questionable.Windows.QuestComponents; namespace Questionable.Windows; diff --git a/Questionable/packages.lock.json b/Questionable/packages.lock.json index 53ca99c0..1fd12865 100644 --- a/Questionable/packages.lock.json +++ b/Questionable/packages.lock.json @@ -179,6 +179,12 @@ "resolved": "8.0.0", "contentHash": "yev/k9GHAEGx2Rg3/tU6MQh4HGBXJs70y7j1LaM1i/ER9po+6nnQ6RRqTJn1E7Xu0fbIFK80Nh5EoODxrbxwBQ==" }, + "gatheringpaths": { + "type": "Project", + "dependencies": { + "Questionable.Model": "[1.0.0, )" + } + }, "llib": { "type": "Project", "dependencies": { diff --git a/vendor/ECommons b/vendor/ECommons new file mode 160000 index 00000000..9e90d003 --- /dev/null +++ b/vendor/ECommons @@ -0,0 +1 @@ +Subproject commit 9e90d0032f0efd4c9e65d9c5a8e8bd0e99557d68