using System; using System.Collections.Generic; using System.IO; using System.Numerics; using System.Text.Json; using Dalamud.Plugin.Services; using Lumina.Excel; using Lumina.Excel.Sheets; using Microsoft.Extensions.Logging; using SmartNav.Model.Navigation; namespace SmartNav.Data; public sealed class TeleportTicketService { public sealed record ScannedTicket(uint ItemId, string Name, uint ItemActionRowId, ushort[] ItemActionData); public sealed record TeleportTicket(uint ItemId, string Name, ushort TerritoryId, Vector3 Position, byte RequiredGrandCompany = 0, uint RequiredQuestId = 0u); private static readonly uint[] KnownTicketItemIds = new uint[5] { 21069u, 21070u, 21071u, 28064u, 30362u }; public IReadOnlyList ScannedTickets { get; } public IReadOnlyList Tickets { get; } internal TeleportTicketService(List tickets) { ScannedTickets = Array.Empty(); Tickets = tickets; } public TeleportTicketService(IDataManager dataManager, SmartNavDataOptions dataOptions, ILogger logger) { ScannedTickets = ScanTicketItems(dataManager, logger); Dictionary dictionary = new Dictionary(); foreach (TeleportTicketEntry value2 in AssemblyTeleportTicketLoader.GetTickets(dataOptions.DevSourceDirectory?.FullName).Values) { ApplyEntry(dictionary, value2); } if (dataOptions.UserOverrideDirectory != null) { LoadDestinationsFrom(Path.Combine(dataOptions.UserOverrideDirectory.FullName, "teleport-tickets.json"), dictionary, logger); } ExcelSheet excelSheet = dataManager.GetExcelSheet(); List list = new List(); foreach (ScannedTicket scannedTicket in ScannedTickets) { if (!dictionary.TryGetValue(scannedTicket.ItemId, out var value)) { logger.LogDebug("Teleport ticket {ItemId} ({Name}) has no known destination - record it via the debug window to make it routable", scannedTicket.ItemId, scannedTicket.Name); } else { uint num = excelSheet.GetRowOrDefault(scannedTicket.ItemActionData[0])?.Requirement.RowId ?? 0; list.Add(new TeleportTicket(scannedTicket.ItemId, scannedTicket.Name, value.Item1, value.Item2, (byte)((num >= 1 && num <= 3) ? ((byte)num) : 0), (num >= 65536) ? num : 0u)); } } Tickets = list; logger.LogInformation("Teleport tickets: {Scanned} items found, {Resolved} with destinations", ScannedTickets.Count, list.Count); } private static List ScanTicketItems(IDataManager dataManager, ILogger logger) { ExcelSheet excelSheet = dataManager.GetExcelSheet(); uint? num = null; uint[] knownTicketItemIds = KnownTicketItemIds; foreach (uint rowId in knownTicketItemIds) { ItemAction? itemAction = excelSheet.GetRowOrDefault(rowId)?.ItemAction.ValueNullable; if (itemAction.HasValue) { num = itemAction.Value.Action.RowId; break; } } List list = new List(); if (!num.HasValue) { logger.LogWarning("Teleport tickets: could not discover the ItemAction type from known items"); return list; } foreach (Item item in excelSheet) { if (item.RowId == 0) { continue; } ItemAction? valueNullable = item.ItemAction.ValueNullable; if (valueNullable.HasValue && valueNullable.Value.Action.RowId == num) { string text = item.Name.ExtractText(); if (!string.IsNullOrEmpty(text)) { list.Add(new ScannedTicket(item.RowId, text, valueNullable.Value.RowId, new ushort[3] { valueNullable.Value.Data[0], valueNullable.Value.Data[1], valueNullable.Value.Data[2] })); } } } list.Sort((ScannedTicket a, ScannedTicket b) => a.ItemId.CompareTo(b.ItemId)); return list; } private static void ApplyEntry(Dictionary destinations, TeleportTicketEntry entry) { if (entry.ItemId != 0 && entry.DestTerritoryId > 0) { destinations[entry.ItemId] = (entry.DestTerritoryId, entry.DestPosition); } } private static void LoadDestinationsFrom(string path, Dictionary destinations, ILogger logger) { if (!File.Exists(path)) { return; } TeleportTicketFile teleportTicketFile = JsonSerializer.Deserialize(File.ReadAllText(path)); if (teleportTicketFile == null) { return; } foreach (TeleportTicketEntry value in teleportTicketFile.Tickets.Values) { ApplyEntry(destinations, value); } logger.LogDebug("Teleport tickets: loaded {Count} destinations from {Path}", teleportTicketFile.Tickets.Count, path); } }