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; using Questionable.Model.Questing; 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; private readonly Configuration _configuration; public EditorCommands(RendererPlugin plugin, IDataManager dataManager, ICommandManager commandManager, ITargetManager targetManager, IClientState clientState, IChatGui chatGui, Configuration configuration) { _plugin = plugin; _dataManager = dataManager; _commandManager = commandManager; _targetManager = targetManager; _clientState = clientState; _chatGui = chatGui; _configuration = configuration; _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[(EExpansionVersion)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 { Author = [_configuration.AuthorName], Steps = [ new QuestStep { TerritoryId = _clientState.TerritoryType, InteractionType = EInteractionType.None, } ], Groups = [ new GatheringNodeGroup { Nodes = [ new GatheringNode { DataId = target.DataId, Locations = [ new GatheringLocation { Position = target.Position } ] } ] } ] }; return (targetFile, root); } public void Dispose() { _commandManager.RemoveHandler("/qg"); } }