GE update

sb-p1
Liza 2024-08-03 03:21:11 +02:00
parent 9bfbc99144
commit ff7ee27fde
Signed by: liza
GPG Key ID: 7199F8D727D55F67
12 changed files with 883 additions and 81 deletions

View File

@ -0,0 +1,218 @@
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<string> 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<string> arguments)
{
var target = _targetManager.Target;
if (target == null || target.ObjectKind != ObjectKind.GatheringPoint)
throw new Exception("No valid target");
var gatheringPoint = _dataManager.GetExcelSheet<GatheringPoint>()!.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, string.Join(" ", arguments));
_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,
string fileName)
{
if (string.IsNullOrEmpty(fileName))
throw new ArgumentException(nameof(fileName));
// determine target folder
DirectoryInfo? targetFolder = _plugin.GetLocationsInTerritory(_clientState.TerritoryType).FirstOrDefault()
?.File.Directory;
if (targetFolder == null)
{
var territoryInfo = _dataManager.GetExcelSheet<TerritoryType>()!.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}_{fileName}.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");
}
}

View File

@ -4,11 +4,16 @@ using System.IO;
using System.Linq;
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 GatheringPathRenderer.Windows;
using Questionable.Model;
using Questionable.Model.Gathering;
namespace GatheringPathRenderer;
@ -16,73 +21,83 @@ namespace GatheringPathRenderer;
public sealed class RendererPlugin : IDalamudPlugin
{
private const long OnTerritoryChange = -2;
private readonly WindowSystem _windowSystem = new(nameof(RendererPlugin));
private readonly List<uint> _colors = [0xFFFF2020, 0xFF20FF20, 0xFF2020FF, 0xFFFFFF20, 0xFFFF20FF, 0xFF20FFFF];
private readonly IDalamudPluginInterface _pluginInterface;
private readonly IClientState _clientState;
private readonly IPluginLog _pluginLog;
private readonly List<(ushort Id, GatheringRoot Root)> _gatheringLocations = [];
public RendererPlugin(IDalamudPluginInterface pluginInterface, IClientState clientState, IPluginLog pluginLog)
private readonly EditorCommands _editorCommands;
private readonly EditorWindow _editorWindow;
private readonly List<GatheringLocationContext> _gatheringLocations = [];
public RendererPlugin(IDalamudPluginInterface pluginInterface, IClientState clientState,
ICommandManager commandManager, IDataManager dataManager, ITargetManager targetManager, IChatGui chatGui,
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) { IsOpen = true };
_windowSystem.AddWindow(_editorWindow);
_pluginInterface.GetIpcSubscriber<object>("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);
}
private void Reload()
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();
TerritoryChanged(_clientState.TerritoryType);
Redraw();
}
private void LoadGatheringLocationsFromDirectory()
{
_gatheringLocations.Clear();
DirectoryInfo? solutionDirectory = _pluginInterface.AssemblyLocation.Directory?.Parent?.Parent?.Parent;
if (solutionDirectory != null)
try
{
DirectoryInfo pathProjectDirectory =
new DirectoryInfo(Path.Combine(solutionDirectory.FullName, "GatheringPaths"));
if (pathProjectDirectory.Exists)
{
try
{
LoadFromDirectory(
new DirectoryInfo(Path.Combine(pathProjectDirectory.FullName, "2.x - A Realm Reborn")));
LoadFromDirectory(
new DirectoryInfo(Path.Combine(pathProjectDirectory.FullName, "3.x - Heavensward")));
LoadFromDirectory(
new DirectoryInfo(Path.Combine(pathProjectDirectory.FullName, "4.x - Stormblood")));
LoadFromDirectory(
new DirectoryInfo(Path.Combine(pathProjectDirectory.FullName, "5.x - Shadowbringers")));
LoadFromDirectory(
new DirectoryInfo(Path.Combine(pathProjectDirectory.FullName, "6.x - Endwalker")));
LoadFromDirectory(
new DirectoryInfo(Path.Combine(pathProjectDirectory.FullName, "7.x - Dawntrail")));
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 quests from project directory");
}
}
else
_pluginLog.Warning($"Project directory {pathProjectDirectory} does not exist");
_pluginLog.Information(
$"Loaded {_gatheringLocations.Count} gathering root locations from project directory");
}
catch (Exception e)
{
_pluginLog.Error(e, "Failed to load paths from project directory");
}
else
_pluginLog.Warning($"Solution directory {solutionDirectory} does not exist");
}
private void LoadFromDirectory(DirectoryInfo directory)
@ -96,7 +111,7 @@ public sealed class RendererPlugin : IDalamudPlugin
try
{
using FileStream stream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read);
LoadLocationFromStream(fileInfo.Name, stream);
LoadLocationFromStream(fileInfo, stream);
}
catch (Exception e)
{
@ -108,26 +123,78 @@ public sealed class RendererPlugin : IDalamudPlugin
LoadFromDirectory(childDirectory);
}
private void LoadLocationFromStream(string fileName, Stream stream)
private void LoadLocationFromStream(FileInfo fileInfo, Stream stream)
{
var locationNode = JsonNode.Parse(stream)!;
GatheringRoot root = locationNode.Deserialize<GatheringRoot>()!;
_gatheringLocations.Add((ushort.Parse(fileName.Split('_')[0]), root));
_gatheringLocations.Add(new GatheringLocationContext(fileInfo, ushort.Parse(fileInfo.Name.Split('_')[0]),
root));
}
private void TerritoryChanged(ushort territoryId)
internal IEnumerable<GatheringLocationContext> 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 = _gatheringLocations
.Where(x => x.Root.TerritoryId == territoryId)
.SelectMany(v =>
v.Root.Groups.SelectMany(group =>
var elements = GetLocationsInTerritory(_clientState.TerritoryType)
.SelectMany(location =>
location.Root.Groups.SelectMany(group =>
group.Nodes.SelectMany(node => node.Locations
.SelectMany(x =>
new List<Element>
{
bool isCone = false;
int minimumAngle = 0;
int maximumAngle = 0;
if (_editorWindow.TryGetOverride(x.InternalId, out LocationOverride? locationOverride) && locationOverride != null)
{
new Element(x.IsCone()
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();
}
return new List<Element>
{
new Element(isCone
? ElementType.ConeAtFixedCoordinates
: ElementType.CircleAtFixedCoordinates)
{
@ -135,12 +202,13 @@ public sealed class RendererPlugin : IDalamudPlugin
refY = x.Position.Z,
refZ = x.Position.Y,
Filled = true,
radius = x.MinimumDistance,
Donut = x.MaximumDistance - x.MinimumDistance,
color = 0x2020FF80,
radius = x.CalculateMinimumDistance(),
Donut = x.CalculateMaximumDistance() - x.CalculateMinimumDistance(),
color = _colors[location.Root.Groups.IndexOf(group) % _colors.Count],
Enabled = true,
coneAngleMin = x.IsCone() ? (int)x.MinimumAngle.GetValueOrDefault() : 0,
coneAngleMax = x.IsCone() ? (int)x.MaximumAngle.GetValueOrDefault() : 0
coneAngleMin = minimumAngle,
coneAngleMax = maximumAngle,
tether = false,
},
new Element(ElementType.CircleAtFixedCoordinates)
{
@ -149,9 +217,11 @@ public sealed class RendererPlugin : IDalamudPlugin
refZ = x.Position.Y,
color = 0x00000000,
Enabled = true,
overlayText = $"{v.Id} // {node.DataId} / {node.Locations.IndexOf(x)}"
overlayText =
$"{location.Root.Groups.IndexOf(group)} // {node.DataId} / {node.Locations.IndexOf(x)}",
}
}))))
};
}))))
.ToList();
if (elements.Count == 0)
@ -179,11 +249,16 @@ public sealed class RendererPlugin : IDalamudPlugin
public void Dispose()
{
_clientState.TerritoryChanged -= TerritoryChanged;
_pluginInterface.UiBuilder.Draw -= _windowSystem.Draw;
Splatoon.RemoveDynamicElements("GatheringPathRenderer");
ECommonsMain.Dispose();
_pluginInterface.GetIpcSubscriber<object>("Questionable.ReloadData")
.Unsubscribe(Reload);
_editorCommands.Dispose();
}
internal sealed record GatheringLocationContext(FileInfo File, ushort Id, GatheringRoot Root);
}

View File

@ -0,0 +1,190 @@
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.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 Dictionary<Guid, LocationOverride> _changes = [];
private IGameObject? _target;
private (RendererPlugin.GatheringLocationContext, GatheringLocation)? _targetLocation;
private string _newFileName = string.Empty;
public EditorWindow(RendererPlugin plugin, EditorCommands editorCommands, IDataManager dataManager,
ITargetManager targetManager, IClientState clientState)
: base("Gathering Path Editor###QuestionableGatheringPathEditor")
{
_plugin = plugin;
_editorCommands = editorCommands;
_dataManager = dataManager;
_targetManager = targetManager;
_clientState = clientState;
SizeConstraints = new WindowSizeConstraints
{
MinimumSize = new Vector2(300, 300),
};
}
public override void Update()
{
_target = _targetManager.Target;
if (_target == null || _target.ObjectKind != ObjectKind.GatheringPoint)
{
_targetLocation = null;
return;
}
var gatheringLocations = _plugin.GetLocationsInTerritory(_clientState.TerritoryType);
var location = gatheringLocations.SelectMany(context =>
context.Root.Groups.SelectMany(group =>
group.Nodes
.Where(node => node.DataId == _target.DataId)
.SelectMany(node => node.Locations)
.Where(location => Vector3.Distance(location.Position, _target.Position) < 0.1f)
.Select(location => new { Context = context, Location = location })))
.FirstOrDefault();
if (location == null)
{
_targetLocation = null;
return;
}
_targetLocation = (location.Context, location.Location);
}
public override bool DrawConditions()
{
return _target != null || _targetLocation != null;
}
public override void Draw()
{
if (_target != null && _targetLocation != null)
{
var context = _targetLocation.Value.Item1;
var location = _targetLocation.Value.Item2;
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, -180, 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, -180, 360))
{
locationOverride.MinimumAngle ??= location.MinimumAngle.GetValueOrDefault();
locationOverride.MaximumAngle = maxAngle;
_plugin.Redraw();
}
ImGui.BeginDisabled(locationOverride.MinimumAngle == null && locationOverride.MaximumAngle == null);
if (ImGui.Button("Save"))
{
location.MinimumAngle = locationOverride.MinimumAngle;
location.MaximumAngle = locationOverride.MaximumAngle;
_plugin.Save(context.File, context.Root);
}
ImGui.SameLine();
if (ImGui.Button("Reset"))
{
_changes[location.InternalId] = new LocationOverride();
_plugin.Redraw();
}
ImGui.EndDisabled();
}
else if (_target != null)
{
var gatheringPoint = _dataManager.GetExcelSheet<GatheringPoint>()!.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
{
ImGui.InputText("File Name", ref _newFileName, 128);
ImGui.BeginDisabled(string.IsNullOrEmpty(_newFileName));
if (ImGui.Button("Create location"))
{
var (targetFile, root) = _editorCommands.CreateNewFile(gatheringPoint, _target, _newFileName);
_plugin.Save(targetFile, root);
_newFileName = string.Empty;
}
ImGui.EndDisabled();
}
}
}
public bool TryGetOverride(Guid internalId, out LocationOverride? locationOverride)
=> _changes.TryGetValue(internalId, out locationOverride);
}
internal 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;
}
}

View File

@ -0,0 +1,112 @@
{
"$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/GatheringPaths/gatheringlocation-v1.json",
"Author": [],
"TerritoryId": 1187,
"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
}
]
}
]
},
{
"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
}
]
}
]
},
{
"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
}
]
}
]
}
]
}

View File

@ -0,0 +1,95 @@
{
"$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/GatheringPaths/gatheringlocation-v1.json",
"Author": [],
"TerritoryId": 1187,
"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
}
}
]
}
]
},
{
"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
}
]
}
]
},
{
"Nodes": [
{
"DataId": 34860,
"Locations": [
{
"Position": {
"X": -169.8177,
"Y": -85.61841,
"Z": -240.1007
}
}
]
},
{
"DataId": 34859,
"Locations": [
{
"Position": {
"X": -131.9198,
"Y": -89.88039,
"Z": -249.5422
}
}
]
}
]
}
]
}

View File

@ -0,0 +1,95 @@
{
"$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/GatheringPaths/gatheringlocation-v1.json",
"Author": [],
"TerritoryId": 1187,
"Groups": [
{
"Nodes": [
{
"DataId": 34866,
"Locations": [
{
"Position": {
"X": 242.7737,
"Y": -135.9734,
"Z": -431.2313
}
}
]
},
{
"DataId": 34865,
"Locations": [
{
"Position": {
"X": 269.7338,
"Y": -134.0488,
"Z": -381.6242
}
}
]
}
]
},
{
"Nodes": [
{
"DataId": 34868,
"Locations": [
{
"Position": {
"X": 389.1952,
"Y": -154.3099,
"Z": -368.3658
},
"MinimumAngle": 105,
"MaximumAngle": 345
}
]
},
{
"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
}
}
]
},
{
"DataId": 34863,
"Locations": [
{
"Position": {
"X": 323.8758,
"Y": -162.9682,
"Z": -648.8156
}
}
]
}
]
}
]
}

View File

@ -92,8 +92,7 @@
"$schema",
"Author",
"TerritoryId",
"AetheryteShortcut",
"Nodes"
"Groups"
],
"additionalProperties": false,
"$defs": {

View File

@ -57,8 +57,8 @@ public sealed class VectorConverter : JsonConverter<Vector3>
{
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();
}
}

View File

@ -0,0 +1,16 @@
using System.Collections.Generic;
namespace Questionable.Model;
public static class ExpansionData
{
public static IReadOnlyDictionary<byte, string> ExpansionFolders = new Dictionary<byte, string>()
{
{ 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" }
};
}

View File

@ -1,4 +1,5 @@
using System.Numerics;
using System;
using System.Numerics;
using System.Text.Json.Serialization;
using Questionable.Model.Common.Converter;
@ -6,16 +7,22 @@ namespace Questionable.Model.Gathering;
public sealed class GatheringLocation
{
[JsonIgnore]
public Guid InternalId { get; } = Guid.NewGuid();
[JsonConverter(typeof(VectorConverter))]
public Vector3 Position { get; set; }
public float? MinimumAngle { get; set; }
public float? MaximumAngle { get; set; }
public float MinimumDistance { get; set; } = 1f;
public float MaximumDistance { get; set; } = 3f;
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;
}

View File

@ -69,7 +69,16 @@ internal sealed class QuestRegistry
ValidateQuests();
Reloaded?.Invoke(this, EventArgs.Empty);
_reloadDataIpc.SendMessage();
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);
}
@ -105,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)
{