qstbak/SmartNav/SmartNav.Data/TeleportTicketService.cs
2026-08-17 20:29:32 +10:00

132 lines
4.6 KiB
C#

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<ScannedTicket> ScannedTickets { get; }
public IReadOnlyList<TeleportTicket> Tickets { get; }
internal TeleportTicketService(List<TeleportTicket> tickets)
{
ScannedTickets = Array.Empty<ScannedTicket>();
Tickets = tickets;
}
public TeleportTicketService(IDataManager dataManager, SmartNavDataOptions dataOptions, ILogger<TeleportTicketService> logger)
{
ScannedTickets = ScanTicketItems(dataManager, logger);
Dictionary<uint, (ushort, Vector3)> dictionary = new Dictionary<uint, (ushort, Vector3)>();
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<ItemActionTelepo> excelSheet = dataManager.GetExcelSheet<ItemActionTelepo>();
List<TeleportTicket> list = new List<TeleportTicket>();
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<ScannedTicket> ScanTicketItems(IDataManager dataManager, ILogger logger)
{
ExcelSheet<Item> excelSheet = dataManager.GetExcelSheet<Item>();
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<ScannedTicket> list = new List<ScannedTicket>();
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<uint, (ushort TerritoryId, Vector3 Position)> destinations, TeleportTicketEntry entry)
{
if (entry.ItemId != 0 && entry.DestTerritoryId > 0)
{
destinations[entry.ItemId] = (entry.DestTerritoryId, entry.DestPosition);
}
}
private static void LoadDestinationsFrom(string path, Dictionary<uint, (ushort TerritoryId, Vector3 Position)> destinations, ILogger logger)
{
if (!File.Exists(path))
{
return;
}
TeleportTicketFile teleportTicketFile = JsonSerializer.Deserialize<TeleportTicketFile>(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);
}
}