forked from liza/Questionable
267 lines
10 KiB
C#
267 lines
10 KiB
C#
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<Guid, LocationOverride> _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",
|
|
ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoNavFocus)
|
|
{
|
|
_plugin = plugin;
|
|
_editorCommands = editorCommands;
|
|
_dataManager = dataManager;
|
|
_targetManager = targetManager;
|
|
_clientState = clientState;
|
|
_objectTable = objectTable;
|
|
|
|
SizeConstraints = new WindowSizeConstraints
|
|
{
|
|
MinimumSize = new Vector2(300, 300),
|
|
};
|
|
|
|
RespectCloseHotkey = false;
|
|
ShowCloseButton = false;
|
|
AllowPinning = false;
|
|
AllowClickthrough = false;
|
|
}
|
|
|
|
public override void Update()
|
|
{
|
|
if (!_clientState.IsLoggedIn || _clientState.LocalPlayer == null)
|
|
{
|
|
_target = null;
|
|
_targetLocation = null;
|
|
return;
|
|
}
|
|
|
|
_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
|
|
.Select(location =>
|
|
{
|
|
float distance;
|
|
if (_target != null)
|
|
distance = Vector3.Distance(location.Position, _target.Position);
|
|
else
|
|
distance = Vector3.Distance(location.Position, _clientState.LocalPlayer.Position);
|
|
|
|
return new { Context = context, Node = node, Location = location, Distance = distance };
|
|
})
|
|
.Where(location => location.Distance < (_target == null ? 3f : 0.1f)))))
|
|
.MinBy(x => x.Distance);
|
|
if (_target != null && _target.ObjectKind != ObjectKind.GatheringPoint)
|
|
{
|
|
_target = null;
|
|
_targetLocation = null;
|
|
return;
|
|
}
|
|
|
|
if (location == null)
|
|
{
|
|
_targetLocation = null;
|
|
return;
|
|
}
|
|
|
|
_target ??= _objectTable
|
|
.Where(x => x.ObjectKind == ObjectKind.GatheringPoint && x.DataId == location.Node.DataId)
|
|
.Select(x => new
|
|
{
|
|
Object = x,
|
|
Distance = Vector3.Distance(location.Location.Position, _clientState.LocalPlayer.Position)
|
|
})
|
|
.Where(x => x.Distance < 3f)
|
|
.OrderBy(x => x.Distance)
|
|
.Select(x => x.Object)
|
|
.FirstOrDefault();
|
|
_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} +{node.Locations.Count - 1} / {location.InternalId.ToString().Substring(0, 4)}");
|
|
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();
|
|
int maxAngle = locationOverride.MaximumAngle ?? location.MaximumAngle.GetValueOrDefault();
|
|
if (ImGui.DragIntRange2("Angle", ref minAngle, ref maxAngle, 5, -360, 360))
|
|
{
|
|
locationOverride.MinimumAngle = minAngle;
|
|
locationOverride.MaximumAngle = maxAngle;
|
|
_plugin.Redraw();
|
|
}
|
|
|
|
float minDistance = locationOverride.MinimumDistance ?? location.CalculateMinimumDistance();
|
|
float maxDistance = locationOverride.MaximumDistance ?? location.CalculateMaximumDistance();
|
|
if (ImGui.DragFloatRange2("Distance", ref minDistance, ref maxDistance, 0.1f, 1f, 3f))
|
|
{
|
|
locationOverride.MinimumDistance = minDistance;
|
|
locationOverride.MaximumDistance = maxDistance;
|
|
_plugin.Redraw();
|
|
}
|
|
|
|
bool unsaved = locationOverride.NeedsSave();
|
|
ImGui.BeginDisabled(!unsaved);
|
|
if (unsaved)
|
|
ImGui.PushStyleColor(ImGuiCol.Button, ImGuiColors.DalamudRed);
|
|
if (ImGui.Button("Save"))
|
|
{
|
|
if (locationOverride is { MinimumAngle: not null, MaximumAngle: not null })
|
|
{
|
|
location.MinimumAngle = locationOverride.MinimumAngle ?? location.MinimumAngle;
|
|
location.MaximumAngle = locationOverride.MaximumAngle ?? location.MaximumAngle;
|
|
}
|
|
|
|
if (locationOverride is { MinimumDistance: not null, MaximumDistance: not null })
|
|
{
|
|
location.MinimumDistance = locationOverride.MinimumDistance;
|
|
location.MaximumDistance = locationOverride.MaximumDistance;
|
|
}
|
|
|
|
_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<IGameObject> nodesInObjectTable = _objectTable
|
|
.Where(x => x.ObjectKind == ObjectKind.GatheringPoint && x.DataId == _target.DataId)
|
|
.ToList();
|
|
List<IGameObject> 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<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
|
|
{
|
|
if (ImGui.Button($"Create location ({gatheringPoint.GatheringPointBase.Row})"))
|
|
{
|
|
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;
|
|
}
|
|
|
|
public bool NeedsSave()
|
|
{
|
|
return (MinimumAngle != null && MaximumAngle != null) || (MinimumDistance != null && MaximumDistance != null);
|
|
}
|
|
}
|