forked from liza/Questionable
GE update
This commit is contained in:
parent
9bfbc99144
commit
ff7ee27fde
218
GatheringPathRenderer/EditorCommands.cs
Normal file
218
GatheringPathRenderer/EditorCommands.cs
Normal 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");
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
190
GatheringPathRenderer/Windows/EditorWindow.cs
Normal file
190
GatheringPathRenderer/Windows/EditorWindow.cs
Normal 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;
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -92,8 +92,7 @@
|
||||
"$schema",
|
||||
"Author",
|
||||
"TerritoryId",
|
||||
"AetheryteShortcut",
|
||||
"Nodes"
|
||||
"Groups"
|
||||
],
|
||||
"additionalProperties": false,
|
||||
"$defs": {
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
16
Questionable.Model/ExpansionVersion.cs
Normal file
16
Questionable.Model/ExpansionVersion.cs
Normal 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" }
|
||||
};
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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)
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user