862 lines
33 KiB
C#
862 lines
33 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Numerics;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
using Dalamud.Bindings.ImGui;
|
|
using Dalamud.Interface.Utility.Raii;
|
|
using Dalamud.Plugin.Services;
|
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
|
using Lumina.Data.Files;
|
|
using Lumina.Data.Parsing.Layer;
|
|
using Lumina.Excel;
|
|
using Lumina.Excel.Sheets;
|
|
using Microsoft.Extensions.Logging;
|
|
using Questionable.Data;
|
|
using SmartNav;
|
|
using SmartNav.Data;
|
|
using SmartNav.Graph;
|
|
using SmartNav.Model.Navigation;
|
|
|
|
namespace Questionable.Windows.SmartNavComponents;
|
|
|
|
internal sealed class DataProbesPanel
|
|
{
|
|
private static readonly string[] PlanFiles = new string[4] { "planmap.lgb", "planevent.lgb", "planner.lgb", "planlive.lgb" };
|
|
|
|
private static readonly JsonSerializerOptions IndentedJson = new JsonSerializerOptions
|
|
{
|
|
WriteIndented = true
|
|
};
|
|
|
|
private readonly NavRouter _navRouter;
|
|
|
|
private readonly WarpDataService _warpDataService;
|
|
|
|
private readonly TerritoryData _territoryData;
|
|
|
|
private readonly IDataManager _dataManager;
|
|
|
|
private readonly IClientState _clientState;
|
|
|
|
private readonly SmartNavDataOptions _dataOptions;
|
|
|
|
private readonly ILogger<DataProbesPanel> _logger;
|
|
|
|
private string? _multiQuestReport;
|
|
|
|
private bool _popRangeOnlyDestinationless = true;
|
|
|
|
private string? _popRangeReport;
|
|
|
|
private int _lvbTerritoryId;
|
|
|
|
private string? _lvbReport;
|
|
|
|
private string? _arrivalExportReport;
|
|
|
|
public DataProbesPanel(NavRouter navRouter, WarpDataService warpDataService, TerritoryData territoryData, IDataManager dataManager, IClientState clientState, SmartNavDataOptions dataOptions, ILogger<DataProbesPanel> logger)
|
|
{
|
|
_navRouter = navRouter;
|
|
_warpDataService = warpDataService;
|
|
_territoryData = territoryData;
|
|
_dataManager = dataManager;
|
|
_clientState = clientState;
|
|
_dataOptions = dataOptions;
|
|
_logger = logger;
|
|
}
|
|
|
|
public void Draw()
|
|
{
|
|
DrawMultiQuestWarpAudit();
|
|
ImGui.Separator();
|
|
DrawPopRangeProbe();
|
|
ImGui.Separator();
|
|
DrawDerivedArrivalExport();
|
|
ImGui.Separator();
|
|
DrawLayerSetProbe();
|
|
}
|
|
|
|
private static void DrawReport(string id, ref string? report)
|
|
{
|
|
if (report == null)
|
|
{
|
|
return;
|
|
}
|
|
ImGui.SameLine();
|
|
ImU8String label = new ImU8String(6, 1);
|
|
label.AppendLiteral("Copy##");
|
|
label.AppendFormatted(id);
|
|
if (ImGui.Button(label))
|
|
{
|
|
ImGui.SetClipboardText(report);
|
|
}
|
|
ImGui.SameLine();
|
|
ImU8String label2 = new ImU8String(7, 1);
|
|
label2.AppendLiteral("Clear##");
|
|
label2.AppendFormatted(id);
|
|
if (ImGui.Button(label2))
|
|
{
|
|
report = null;
|
|
return;
|
|
}
|
|
ImU8String strId = new ImU8String(7, 1);
|
|
strId.AppendLiteral("##");
|
|
strId.AppendFormatted(id);
|
|
strId.AppendLiteral("child");
|
|
using ImRaii.ChildDisposable childDisposable = ImRaii.Child(strId, new Vector2(-1f, 300f), border: true);
|
|
if ((bool)childDisposable)
|
|
{
|
|
ImGui.TextUnformatted(report);
|
|
}
|
|
}
|
|
|
|
private void DrawMultiQuestWarpAudit()
|
|
{
|
|
if (ImGui.CollapsingHeader("Multi-Quest Warp Audit (CompleteParam)###multiquestaudit", ImGuiTreeNodeFlags.DefaultOpen))
|
|
{
|
|
ImGui.TextWrapped("Warps whose WarpCondition names 2+ quests. Verifies the CompleteParam any-of fix: conds 3/9/10 (airships, ferry) should read any-of, cond 34 all-of. 'FIX CHANGES THIS' marks warps the pre-fix all-of admission got wrong for this character.");
|
|
if (ImGui.Button("Run Audit##multiquest"))
|
|
{
|
|
_multiQuestReport = RunGuarded(BuildMultiQuestReport);
|
|
}
|
|
DrawReport("multiquest", ref _multiQuestReport);
|
|
}
|
|
}
|
|
|
|
private string BuildMultiQuestReport()
|
|
{
|
|
StringBuilder stringBuilder = new StringBuilder();
|
|
ExcelSheet<Warp> excelSheet = _dataManager.GetExcelSheet<Warp>();
|
|
ExcelSheet<Quest> excelSheet2 = _dataManager.GetExcelSheet<Quest>();
|
|
Dictionary<uint, int> dictionary = (from n in _navRouter.StaticGraph.Nodes.Values.OfType<NavNode.WarpNode>()
|
|
group n by n.WarpRowId).ToDictionary((IGrouping<uint, NavNode.WarpNode> g) => g.Key, (IGrouping<uint, NavNode.WarpNode> g) => g.Count());
|
|
int num = 0;
|
|
StringBuilder stringBuilder2;
|
|
IFormatProvider invariantCulture;
|
|
StringBuilder.AppendInterpolatedStringHandler handler;
|
|
foreach (WarpDataService.WarpInfo item in from w in _warpDataService.Warps
|
|
group w by w.RowId into g
|
|
select g.First())
|
|
{
|
|
if (item.RequiredQuestIds.Length < 2)
|
|
{
|
|
continue;
|
|
}
|
|
num++;
|
|
Warp? rowOrDefault = excelSheet.GetRowOrDefault(item.RowId);
|
|
uint value = rowOrDefault?.WarpCondition.RowId ?? 0;
|
|
byte? b = rowOrDefault?.WarpCondition.ValueNullable?.CompleteParam;
|
|
stringBuilder2 = stringBuilder;
|
|
StringBuilder stringBuilder3 = stringBuilder2;
|
|
invariantCulture = CultureInfo.InvariantCulture;
|
|
IFormatProvider provider = invariantCulture;
|
|
handler = new StringBuilder.AppendInterpolatedStringHandler(12, 3, stringBuilder2, invariantCulture);
|
|
handler.AppendLiteral("warp ");
|
|
handler.AppendFormatted(item.RowId);
|
|
handler.AppendLiteral(" '");
|
|
handler.AppendFormatted(item.Name ?? item.Question ?? "?");
|
|
handler.AppendLiteral("' ");
|
|
handler.AppendLiteral("-> ");
|
|
handler.AppendFormatted(_territoryData.GetNameAndId(item.DestTerritoryId));
|
|
stringBuilder3.AppendLine(provider, ref handler);
|
|
stringBuilder2 = stringBuilder;
|
|
StringBuilder stringBuilder4 = stringBuilder2;
|
|
invariantCulture = CultureInfo.InvariantCulture;
|
|
IFormatProvider provider2 = invariantCulture;
|
|
handler = new StringBuilder.AppendInterpolatedStringHandler(47, 3, stringBuilder2, invariantCulture);
|
|
handler.AppendLiteral(" condition ");
|
|
handler.AppendFormatted(value);
|
|
handler.AppendLiteral(", CompleteParam=");
|
|
handler.AppendFormatted(b?.ToString(CultureInfo.InvariantCulture) ?? "?");
|
|
handler.AppendLiteral(", ");
|
|
handler.AppendLiteral("RequireAllQuests=");
|
|
handler.AppendFormatted(item.RequireAllQuests);
|
|
stringBuilder4.AppendLine(provider2, ref handler);
|
|
stringBuilder2 = stringBuilder;
|
|
StringBuilder stringBuilder5 = stringBuilder2;
|
|
invariantCulture = CultureInfo.InvariantCulture;
|
|
IFormatProvider provider3 = invariantCulture;
|
|
handler = new StringBuilder.AppendInterpolatedStringHandler(37, 4, stringBuilder2, invariantCulture);
|
|
handler.AppendLiteral(" source: territory ");
|
|
handler.AppendFormatted(item.SourceTerritoryId);
|
|
handler.AppendLiteral(" ");
|
|
handler.AppendFormatted(item.SourcePosition);
|
|
handler.AppendLiteral(", ");
|
|
handler.AppendLiteral("npc ");
|
|
handler.AppendFormatted(item.NpcDataId?.ToString(CultureInfo.InvariantCulture) ?? "-");
|
|
handler.AppendLiteral(", derived=");
|
|
handler.AppendFormatted(item.DerivedSource);
|
|
stringBuilder5.AppendLine(provider3, ref handler);
|
|
stringBuilder2 = stringBuilder;
|
|
StringBuilder stringBuilder6 = stringBuilder2;
|
|
invariantCulture = CultureInfo.InvariantCulture;
|
|
IFormatProvider provider4 = invariantCulture;
|
|
handler = new StringBuilder.AppendInterpolatedStringHandler(35, 1, stringBuilder2, invariantCulture);
|
|
handler.AppendLiteral(" static graph nodes for this row: ");
|
|
handler.AppendFormatted(dictionary.GetValueOrDefault(item.RowId));
|
|
stringBuilder6.AppendLine(provider4, ref handler);
|
|
int num2 = 0;
|
|
uint[] requiredQuestIds = item.RequiredQuestIds;
|
|
foreach (uint num4 in requiredQuestIds)
|
|
{
|
|
bool flag = QuestManager.IsQuestComplete((ushort)(num4 & 0xFFFF));
|
|
if (flag)
|
|
{
|
|
num2++;
|
|
}
|
|
string value2 = excelSheet2.GetRowOrDefault(num4)?.Name.ExtractText() ?? "?";
|
|
stringBuilder2 = stringBuilder;
|
|
StringBuilder stringBuilder7 = stringBuilder2;
|
|
invariantCulture = CultureInfo.InvariantCulture;
|
|
IFormatProvider provider5 = invariantCulture;
|
|
handler = new StringBuilder.AppendInterpolatedStringHandler(13, 3, stringBuilder2, invariantCulture);
|
|
handler.AppendLiteral(" quest ");
|
|
handler.AppendFormatted(num4);
|
|
handler.AppendLiteral(" '");
|
|
handler.AppendFormatted(value2);
|
|
handler.AppendLiteral("': ");
|
|
handler.AppendFormatted(flag ? "COMPLETE" : "not complete");
|
|
stringBuilder7.AppendLine(provider5, ref handler);
|
|
}
|
|
bool flag2 = (item.RequireAllQuests ? (num2 == item.RequiredQuestIds.Length) : (num2 > 0));
|
|
bool flag3 = num2 == item.RequiredQuestIds.Length;
|
|
stringBuilder2 = stringBuilder;
|
|
StringBuilder stringBuilder8 = stringBuilder2;
|
|
invariantCulture = CultureInfo.InvariantCulture;
|
|
IFormatProvider provider6 = invariantCulture;
|
|
handler = new StringBuilder.AppendInterpolatedStringHandler(34, 3, stringBuilder2, invariantCulture);
|
|
handler.AppendLiteral(" admission: now=");
|
|
handler.AppendFormatted(flag2);
|
|
handler.AppendLiteral(", pre-fix all-of=");
|
|
handler.AppendFormatted(flag3);
|
|
handler.AppendFormatted((flag2 != flag3) ? " <-- FIX CHANGES THIS" : "");
|
|
stringBuilder8.AppendLine(provider6, ref handler);
|
|
}
|
|
stringBuilder2 = stringBuilder;
|
|
StringBuilder stringBuilder9 = stringBuilder2;
|
|
invariantCulture = CultureInfo.InvariantCulture;
|
|
IFormatProvider provider7 = invariantCulture;
|
|
handler = new StringBuilder.AppendInterpolatedStringHandler(26, 1, stringBuilder2, invariantCulture);
|
|
handler.AppendFormatted(num);
|
|
handler.AppendLiteral(" multi-quest warps audited");
|
|
stringBuilder9.AppendLine(provider7, ref handler);
|
|
return stringBuilder.ToString();
|
|
}
|
|
|
|
private void DrawPopRangeProbe()
|
|
{
|
|
if (ImGui.CollapsingHeader("Arrival PopRange Plan-File Probe###poprangeprobe", ImGuiTreeNodeFlags.DefaultOpen))
|
|
{
|
|
ImGui.TextWrapped("For each warp, reads Warp.PopRange both ways: as a Level sheet row (SmartNav's source resolution) and as an LGB PopRange instance id in the destination territory's plan files (NoireLib's arrival claim: planevent holds most arrivals, planlive holds switched-content arrivals found nowhere else). Decides whether warp-destination seeding shrinks to 'extend the scan'. Unchecked, warps with a recorded arrival get a position delta (LGB PopRange translation vs recorded DestPosition) - the control for using LGB positions as arrivals.");
|
|
ImGui.Checkbox("Only destination-less warps (no recorded arrival)", ref _popRangeOnlyDestinationless);
|
|
if (ImGui.Button("Run Probe##poprange"))
|
|
{
|
|
_popRangeReport = RunGuarded(BuildPopRangeReport);
|
|
}
|
|
DrawReport("poprange", ref _popRangeReport);
|
|
}
|
|
}
|
|
|
|
private string BuildPopRangeReport()
|
|
{
|
|
StringBuilder stringBuilder = new StringBuilder();
|
|
ExcelSheet<Warp> excelSheet = _dataManager.GetExcelSheet<Warp>();
|
|
ExcelSheet<Level> excelSheet2 = _dataManager.GetExcelSheet<Level>();
|
|
Dictionary<uint, Dictionary<string, Dictionary<uint, Vector3>>> cache = new Dictionary<uint, Dictionary<string, Dictionary<uint, Vector3>>>();
|
|
Dictionary<string, int> dictionary = PlanFiles.ToDictionary((string f) => f, (string _) => 0);
|
|
List<(uint, float)> list = new List<(uint, float)>();
|
|
int num = 0;
|
|
int num2 = 0;
|
|
int num3 = 0;
|
|
StringBuilder stringBuilder2;
|
|
IFormatProvider invariantCulture;
|
|
StringBuilder.AppendInterpolatedStringHandler handler;
|
|
foreach (WarpDataService.WarpInfo item4 in from w in _warpDataService.Warps
|
|
group w by w.RowId into g
|
|
select g.First())
|
|
{
|
|
if (_popRangeOnlyDestinationless && item4.DestPosition.HasValue)
|
|
{
|
|
continue;
|
|
}
|
|
Warp? rowOrDefault = excelSheet.GetRowOrDefault(item4.RowId);
|
|
if (!rowOrDefault.HasValue)
|
|
{
|
|
continue;
|
|
}
|
|
num++;
|
|
uint rowId = rowOrDefault.Value.PopRange.RowId;
|
|
uint rowId2 = rowOrDefault.Value.TerritoryType.RowId;
|
|
bool flag = rowId != 0 && excelSheet2.HasRow(rowId);
|
|
if (flag)
|
|
{
|
|
num3++;
|
|
}
|
|
List<string> list2 = new List<string>();
|
|
Vector3? vector = null;
|
|
foreach (KeyValuePair<string, Dictionary<uint, Vector3>> item5 in ScanPopRanges(cache, rowId2))
|
|
{
|
|
item5.Deconstruct(out var key, out var value);
|
|
string text = key;
|
|
if (value.TryGetValue(rowId, out var value2))
|
|
{
|
|
list2.Add(text);
|
|
key = text;
|
|
dictionary[key]++;
|
|
Vector3 valueOrDefault = vector.GetValueOrDefault();
|
|
if (!vector.HasValue)
|
|
{
|
|
valueOrDefault = value2;
|
|
vector = valueOrDefault;
|
|
}
|
|
}
|
|
}
|
|
if (list2.Count == 0)
|
|
{
|
|
num2++;
|
|
}
|
|
string value3 = "";
|
|
if (vector.HasValue && !item4.DerivedArrival)
|
|
{
|
|
Vector3? destPosition = item4.DestPosition;
|
|
if (destPosition.HasValue)
|
|
{
|
|
Vector3 valueOrDefault2 = destPosition.GetValueOrDefault();
|
|
float item = Vector3.Distance(vector.Value, valueOrDefault2);
|
|
list.Add((item4.RowId, item));
|
|
value3 = ", delta=" + item.ToString("F1", CultureInfo.InvariantCulture) + "y";
|
|
goto IL_02c9;
|
|
}
|
|
}
|
|
if (item4.DerivedArrival)
|
|
{
|
|
value3 = ", derived-arrival";
|
|
}
|
|
goto IL_02c9;
|
|
IL_02c9:
|
|
stringBuilder2 = stringBuilder;
|
|
StringBuilder stringBuilder3 = stringBuilder2;
|
|
invariantCulture = CultureInfo.InvariantCulture;
|
|
IFormatProvider provider = invariantCulture;
|
|
handler = new StringBuilder.AppendInterpolatedStringHandler(39, 8, stringBuilder2, invariantCulture);
|
|
handler.AppendLiteral("warp ");
|
|
handler.AppendFormatted(item4.RowId);
|
|
handler.AppendLiteral(" '");
|
|
handler.AppendFormatted(item4.Name ?? item4.Question ?? "?");
|
|
handler.AppendLiteral("' popId=");
|
|
handler.AppendFormatted(rowId);
|
|
handler.AppendLiteral(" ");
|
|
handler.AppendLiteral("dest=");
|
|
handler.AppendFormatted(_territoryData.GetNameAndId(rowId2));
|
|
handler.AppendLiteral(": ");
|
|
handler.AppendLiteral("level-row=");
|
|
handler.AppendFormatted(flag);
|
|
handler.AppendLiteral(", lgb=");
|
|
handler.AppendFormatted((list2.Count > 0) ? string.Join("+", list2) : "NOT FOUND");
|
|
handler.AppendFormatted(vector.HasValue ? $" pos={vector.Value}" : "");
|
|
handler.AppendFormatted(value3);
|
|
stringBuilder3.AppendLine(provider, ref handler);
|
|
}
|
|
stringBuilder.AppendLine();
|
|
stringBuilder2 = stringBuilder;
|
|
StringBuilder stringBuilder4 = stringBuilder2;
|
|
invariantCulture = CultureInfo.InvariantCulture;
|
|
IFormatProvider provider2 = invariantCulture;
|
|
handler = new StringBuilder.AppendInterpolatedStringHandler(65, 3, stringBuilder2, invariantCulture);
|
|
handler.AppendFormatted(num);
|
|
handler.AppendLiteral(" warps probed, ");
|
|
handler.AppendFormatted(num3);
|
|
handler.AppendLiteral(" with a Level sheet row, ");
|
|
handler.AppendFormatted(num2);
|
|
handler.AppendLiteral(" with no LGB PopRange hit");
|
|
stringBuilder4.AppendLine(provider2, ref handler);
|
|
string[] planFiles = PlanFiles;
|
|
foreach (string text2 in planFiles)
|
|
{
|
|
stringBuilder2 = stringBuilder;
|
|
StringBuilder stringBuilder5 = stringBuilder2;
|
|
invariantCulture = CultureInfo.InvariantCulture;
|
|
IFormatProvider provider3 = invariantCulture;
|
|
handler = new StringBuilder.AppendInterpolatedStringHandler(17, 2, stringBuilder2, invariantCulture);
|
|
handler.AppendLiteral(" ");
|
|
handler.AppendFormatted(text2);
|
|
handler.AppendLiteral(": ");
|
|
handler.AppendFormatted(dictionary[text2]);
|
|
handler.AppendLiteral(" arrival hits");
|
|
stringBuilder5.AppendLine(provider3, ref handler);
|
|
}
|
|
if (list.Count > 0)
|
|
{
|
|
List<(uint, float)> list3 = list.OrderBy<(uint, float), float>(((uint RowId, float Delta) d) => d.Delta).ToList();
|
|
float item2 = list3[list3.Count / 2].Item2;
|
|
float item3 = list3[Math.Min(list3.Count - 1, (int)((double)list3.Count * 0.9))].Item2;
|
|
List<(uint, float)> list4 = list3.Where<(uint, float)>(((uint RowId, float Delta) d) => d.Delta > 20f).ToList();
|
|
stringBuilder2 = stringBuilder;
|
|
StringBuilder stringBuilder6 = stringBuilder2;
|
|
invariantCulture = CultureInfo.InvariantCulture;
|
|
IFormatProvider provider4 = invariantCulture;
|
|
handler = new StringBuilder.AppendInterpolatedStringHandler(112, 5, stringBuilder2, invariantCulture);
|
|
handler.AppendLiteral("recorded-arrival delta (LGB PopRange pos vs recorded DestPosition): ");
|
|
handler.AppendFormatted(list3.Count);
|
|
handler.AppendLiteral(" compared, ");
|
|
handler.AppendLiteral("median ");
|
|
handler.AppendFormatted(item2, "F1");
|
|
handler.AppendLiteral("y, p90 ");
|
|
handler.AppendFormatted(item3, "F1");
|
|
handler.AppendLiteral("y, max ");
|
|
handler.AppendFormatted(list3[list3.Count - 1].Item2, "F1");
|
|
handler.AppendLiteral("y, ");
|
|
handler.AppendFormatted(list4.Count);
|
|
handler.AppendLiteral(" over 20y");
|
|
stringBuilder6.AppendLine(provider4, ref handler);
|
|
if (list4.Count > 0)
|
|
{
|
|
stringBuilder2 = stringBuilder;
|
|
StringBuilder stringBuilder7 = stringBuilder2;
|
|
invariantCulture = CultureInfo.InvariantCulture;
|
|
IFormatProvider provider5 = invariantCulture;
|
|
handler = new StringBuilder.AppendInterpolatedStringHandler(17, 1, stringBuilder2, invariantCulture);
|
|
handler.AppendLiteral(" >20y outliers: ");
|
|
handler.AppendFormatted(string.Join(", ", list4.Select<(uint, float), string>(((uint RowId, float Delta) d) => $"{d.RowId} ({d.Delta.ToString("F1", CultureInfo.InvariantCulture)}y)")));
|
|
stringBuilder7.AppendLine(provider5, ref handler);
|
|
}
|
|
}
|
|
return stringBuilder.ToString();
|
|
}
|
|
|
|
private Dictionary<string, Dictionary<uint, Vector3>> ScanPopRanges(Dictionary<uint, Dictionary<string, Dictionary<uint, Vector3>>> cache, uint territoryId)
|
|
{
|
|
if (cache.TryGetValue(territoryId, out Dictionary<string, Dictionary<uint, Vector3>> value))
|
|
{
|
|
return value;
|
|
}
|
|
Dictionary<string, Dictionary<uint, Vector3>> dictionary = (cache[territoryId] = new Dictionary<string, Dictionary<uint, Vector3>>());
|
|
string text = _dataManager.GetExcelSheet<TerritoryType>().GetRowOrDefault(territoryId)?.Bg.ExtractText();
|
|
if (string.IsNullOrEmpty(text))
|
|
{
|
|
return dictionary;
|
|
}
|
|
int num = text.IndexOf("/level/", StringComparison.Ordinal);
|
|
if (num < 0)
|
|
{
|
|
return dictionary;
|
|
}
|
|
string[] planFiles = PlanFiles;
|
|
foreach (string text2 in planFiles)
|
|
{
|
|
Dictionary<uint, Vector3> dictionary3 = (dictionary[text2] = new Dictionary<uint, Vector3>());
|
|
string text3 = "bg/" + text.Substring(0, num + 1) + "level/" + text2;
|
|
try
|
|
{
|
|
LgbFile file = _dataManager.GetFile<LgbFile>(text3);
|
|
if (file == null)
|
|
{
|
|
continue;
|
|
}
|
|
LayerCommon.Layer[] layers = file.Layers;
|
|
for (int j = 0; j < layers.Length; j++)
|
|
{
|
|
LayerCommon.InstanceObject[] instanceObjects = layers[j].InstanceObjects;
|
|
for (int k = 0; k < instanceObjects.Length; k++)
|
|
{
|
|
LayerCommon.InstanceObject instanceObject = instanceObjects[k];
|
|
if (instanceObject.AssetType == LayerEntryType.PopRange)
|
|
{
|
|
dictionary3[instanceObject.InstanceId] = new Vector3(instanceObject.Transform.Translation.X, instanceObject.Transform.Translation.Y, instanceObject.Transform.Translation.Z);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
_logger.LogTrace(exception, "Failed to scan {Path}", text3);
|
|
}
|
|
}
|
|
return dictionary;
|
|
}
|
|
|
|
private void DrawDerivedArrivalExport()
|
|
{
|
|
if (!ImGui.CollapsingHeader("Derived Arrival Export (authoring)###arrivalexport"))
|
|
{
|
|
return;
|
|
}
|
|
ImGui.TextWrapped("Scans every Warp row's destination territory for its arrival PopRange and writes derived-arrivals.json into the SmartNav.Data checkout (DevSourceDirectory) for bundling - runtime never scans LGB itself. Rerun after game patches. The game FREEZES for the scan (~1-2 min on a Debug build) - expected, wait it out.");
|
|
if (_dataOptions.DevSourceDirectory == null)
|
|
{
|
|
ImGui.TextDisabled("DevSourceDirectory not set - available in DEBUG builds only.");
|
|
return;
|
|
}
|
|
if (ImGui.Button("Scan + Export##arrivalexportrun"))
|
|
{
|
|
_arrivalExportReport = RunGuarded(BuildDerivedArrivalExport);
|
|
}
|
|
DrawReport("arrivalexport", ref _arrivalExportReport);
|
|
}
|
|
|
|
private string BuildDerivedArrivalExport()
|
|
{
|
|
StringBuilder stringBuilder = new StringBuilder();
|
|
ExcelSheet<Warp> excelSheet = _dataManager.GetExcelSheet<Warp>();
|
|
Dictionary<uint, Dictionary<string, Dictionary<uint, Vector3>>> cache = new Dictionary<uint, Dictionary<string, Dictionary<uint, Vector3>>>();
|
|
Dictionary<string, int> dictionary = PlanFiles.ToDictionary((string f) => f, (string _) => 0);
|
|
Dictionary<uint, DerivedArrivalEntry> dictionary2 = new Dictionary<uint, DerivedArrivalEntry>();
|
|
List<string> list = new List<string>();
|
|
int num = 0;
|
|
foreach (Warp item in excelSheet.Where((Warp x) => x.RowId != 0))
|
|
{
|
|
uint rowId = item.PopRange.RowId;
|
|
ushort num2 = (ushort)item.TerritoryType.RowId;
|
|
if (rowId == 0 || num2 == 0)
|
|
{
|
|
continue;
|
|
}
|
|
num++;
|
|
Vector3? vector = null;
|
|
foreach (KeyValuePair<string, Dictionary<uint, Vector3>> item2 in ScanPopRanges(cache, num2))
|
|
{
|
|
item2.Deconstruct(out var key, out var value);
|
|
string text = key;
|
|
if (value.TryGetValue(rowId, out var value2))
|
|
{
|
|
key = text;
|
|
dictionary[key]++;
|
|
Vector3 valueOrDefault = vector.GetValueOrDefault();
|
|
if (!vector.HasValue)
|
|
{
|
|
valueOrDefault = value2;
|
|
vector = valueOrDefault;
|
|
}
|
|
}
|
|
}
|
|
if (vector.HasValue)
|
|
{
|
|
dictionary2[item.RowId] = new DerivedArrivalEntry
|
|
{
|
|
PopRangeId = rowId,
|
|
TerritoryId = num2,
|
|
X = vector.Value.X,
|
|
Y = vector.Value.Y,
|
|
Z = vector.Value.Z
|
|
};
|
|
continue;
|
|
}
|
|
string value3 = item.Name.ExtractText();
|
|
if (string.IsNullOrEmpty(value3))
|
|
{
|
|
value3 = item.Question.ExtractText();
|
|
}
|
|
list.Add($" {item.RowId} '{value3}' popId={rowId} dest={_territoryData.GetNameAndId(num2)}");
|
|
}
|
|
DerivedArrivalFile derivedArrivalFile = new DerivedArrivalFile
|
|
{
|
|
GameVersion = _dataOptions.GameVersion,
|
|
Arrivals = dictionary2
|
|
};
|
|
string text2 = Path.Combine(_dataOptions.DevSourceDirectory.FullName, "derived-arrivals.json");
|
|
File.WriteAllText(text2, JsonSerializer.Serialize(derivedArrivalFile, IndentedJson));
|
|
StringBuilder stringBuilder2 = stringBuilder;
|
|
StringBuilder stringBuilder3 = stringBuilder2;
|
|
IFormatProvider invariantCulture = CultureInfo.InvariantCulture;
|
|
IFormatProvider provider = invariantCulture;
|
|
StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(57, 2, stringBuilder2, invariantCulture);
|
|
handler.AppendFormatted(num);
|
|
handler.AppendLiteral(" warp rows with PopRange + destination, ");
|
|
handler.AppendFormatted(dictionary2.Count);
|
|
handler.AppendLiteral(" arrivals derived");
|
|
stringBuilder3.AppendLine(provider, ref handler);
|
|
string[] planFiles = PlanFiles;
|
|
foreach (string text3 in planFiles)
|
|
{
|
|
stringBuilder2 = stringBuilder;
|
|
StringBuilder stringBuilder4 = stringBuilder2;
|
|
invariantCulture = CultureInfo.InvariantCulture;
|
|
IFormatProvider provider2 = invariantCulture;
|
|
handler = new StringBuilder.AppendInterpolatedStringHandler(9, 2, stringBuilder2, invariantCulture);
|
|
handler.AppendLiteral(" ");
|
|
handler.AppendFormatted(text3);
|
|
handler.AppendLiteral(": ");
|
|
handler.AppendFormatted(dictionary[text3]);
|
|
handler.AppendLiteral(" hits");
|
|
stringBuilder4.AppendLine(provider2, ref handler);
|
|
}
|
|
stringBuilder2 = stringBuilder;
|
|
StringBuilder stringBuilder5 = stringBuilder2;
|
|
invariantCulture = CultureInfo.InvariantCulture;
|
|
IFormatProvider provider3 = invariantCulture;
|
|
handler = new StringBuilder.AppendInterpolatedStringHandler(98, 1, stringBuilder2, invariantCulture);
|
|
handler.AppendFormatted(list.Count);
|
|
handler.AppendLiteral(" unresolved (no plan file holds the pop id; Level-sheet fallback may still cover some at runtime):");
|
|
stringBuilder5.AppendLine(provider3, ref handler);
|
|
foreach (string item3 in list)
|
|
{
|
|
stringBuilder.AppendLine(item3);
|
|
}
|
|
stringBuilder.AppendLine();
|
|
stringBuilder2 = stringBuilder;
|
|
StringBuilder stringBuilder6 = stringBuilder2;
|
|
invariantCulture = CultureInfo.InvariantCulture;
|
|
IFormatProvider provider4 = invariantCulture;
|
|
handler = new StringBuilder.AppendInterpolatedStringHandler(25, 2, stringBuilder2, invariantCulture);
|
|
handler.AppendLiteral("written: ");
|
|
handler.AppendFormatted(text2);
|
|
handler.AppendLiteral(" (game version ");
|
|
handler.AppendFormatted(derivedArrivalFile.GameVersion ?? "?");
|
|
handler.AppendLiteral(")");
|
|
stringBuilder6.AppendLine(provider4, ref handler);
|
|
stringBuilder.AppendLine("DEBUG builds read this file directly via DevSourceDirectory - toggle the plugin to see the new arrivals; commit it in SmartNav to ship.");
|
|
return stringBuilder.ToString();
|
|
}
|
|
|
|
private void DrawLayerSetProbe()
|
|
{
|
|
if (!ImGui.CollapsingHeader("Layer-Set Attribution Probe (.lvb)###lvbprobe", ImGuiTreeNodeFlags.DefaultOpen))
|
|
{
|
|
return;
|
|
}
|
|
ImGui.TextWrapped("Parses the territory's .lvb layer-set table per the offsets claimed in NoireLib's GameData docs (0x1C-byte records, TerritoryType row at +0x0C) and each plan file's per-layer layer-set references. Verify against a known shared-level pair, e.g. Mist 339 vs s1h1_test 136: attributed territory ids must all appear in the 'territories sharing this Bg' line.");
|
|
ImGui.SetNextItemWidth(120f);
|
|
ImGui.InputInt("Territory id (0 = current)##lvbterritory", ref _lvbTerritoryId);
|
|
if (ImGui.Button("Parse##lvb"))
|
|
{
|
|
uint territoryId = ((_lvbTerritoryId > 0) ? ((uint)_lvbTerritoryId) : _clientState.TerritoryType);
|
|
_lvbReport = RunGuarded(() => BuildLayerSetReport(territoryId));
|
|
}
|
|
DrawReport("lvb", ref _lvbReport);
|
|
}
|
|
|
|
private string BuildLayerSetReport(uint territoryId)
|
|
{
|
|
StringBuilder stringBuilder = new StringBuilder();
|
|
ExcelSheet<TerritoryType> excelSheet = _dataManager.GetExcelSheet<TerritoryType>();
|
|
string bg = excelSheet.GetRowOrDefault(territoryId)?.Bg.ExtractText();
|
|
if (string.IsNullOrEmpty(bg))
|
|
{
|
|
return $"territory {territoryId} has no Bg path";
|
|
}
|
|
int num = bg.IndexOf("/level/", StringComparison.Ordinal);
|
|
if (num < 0)
|
|
{
|
|
return $"territory {territoryId} Bg '{bg}' has no /level/ segment";
|
|
}
|
|
string text = "bg/" + bg.Substring(0, num + 1) + "level/";
|
|
string text2 = bg.Substring(bg.LastIndexOf('/') + 1);
|
|
StringBuilder stringBuilder2 = stringBuilder;
|
|
StringBuilder stringBuilder3 = stringBuilder2;
|
|
IFormatProvider invariantCulture = CultureInfo.InvariantCulture;
|
|
IFormatProvider provider = invariantCulture;
|
|
StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(17, 2, stringBuilder2, invariantCulture);
|
|
handler.AppendLiteral("territory ");
|
|
handler.AppendFormatted(_territoryData.GetNameAndId(territoryId));
|
|
handler.AppendLiteral(", Bg '");
|
|
handler.AppendFormatted(bg);
|
|
handler.AppendLiteral("'");
|
|
stringBuilder3.AppendLine(provider, ref handler);
|
|
List<uint> list = (from t in excelSheet
|
|
where t.RowId != 0 && t.Bg.ExtractText() == bg
|
|
select t.RowId).ToList();
|
|
stringBuilder2 = stringBuilder;
|
|
StringBuilder stringBuilder4 = stringBuilder2;
|
|
invariantCulture = CultureInfo.InvariantCulture;
|
|
IFormatProvider provider2 = invariantCulture;
|
|
handler = new StringBuilder.AppendInterpolatedStringHandler(29, 1, stringBuilder2, invariantCulture);
|
|
handler.AppendLiteral("territories sharing this Bg: ");
|
|
handler.AppendFormatted(string.Join(", ", list));
|
|
stringBuilder4.AppendLine(provider2, ref handler);
|
|
List<(uint SetId, uint TerritoryId)> list2 = ParseLvbLayerSets(ReadFileBytes(text + text2 + ".lvb"), stringBuilder);
|
|
Dictionary<uint, List<uint>> owners = new Dictionary<uint, List<uint>>();
|
|
foreach (var item3 in list2)
|
|
{
|
|
uint item = item3.SetId;
|
|
uint item2 = item3.TerritoryId;
|
|
stringBuilder2 = stringBuilder;
|
|
StringBuilder stringBuilder5 = stringBuilder2;
|
|
invariantCulture = CultureInfo.InvariantCulture;
|
|
IFormatProvider provider3 = invariantCulture;
|
|
handler = new StringBuilder.AppendInterpolatedStringHandler(26, 3, stringBuilder2, invariantCulture);
|
|
handler.AppendLiteral(" layer set ");
|
|
handler.AppendFormatted(item);
|
|
handler.AppendLiteral(" -> territory ");
|
|
handler.AppendFormatted(item2);
|
|
handler.AppendFormatted(list.Contains(item2) ? "" : " <-- NOT IN SHARING SET (offsets suspect)");
|
|
stringBuilder5.AppendLine(provider3, ref handler);
|
|
if (item2 != 0)
|
|
{
|
|
if (!owners.TryGetValue(item, out List<uint> value))
|
|
{
|
|
value = (owners[item] = new List<uint>());
|
|
}
|
|
value.Add(item2);
|
|
}
|
|
}
|
|
string[] planFiles = PlanFiles;
|
|
foreach (string text3 in planFiles)
|
|
{
|
|
List<List<uint>> list4 = ParseLgbLayerSetReferences(ReadFileBytes(text + text3));
|
|
if (list4.Count == 0)
|
|
{
|
|
stringBuilder2 = stringBuilder;
|
|
StringBuilder stringBuilder6 = stringBuilder2;
|
|
invariantCulture = CultureInfo.InvariantCulture;
|
|
IFormatProvider provider4 = invariantCulture;
|
|
handler = new StringBuilder.AppendInterpolatedStringHandler(18, 1, stringBuilder2, invariantCulture);
|
|
handler.AppendFormatted(text3);
|
|
handler.AppendLiteral(": no layers parsed");
|
|
stringBuilder6.AppendLine(provider4, ref handler);
|
|
continue;
|
|
}
|
|
int value2 = list4.Count((List<uint> refs) => refs.Count > 0);
|
|
List<uint> value3;
|
|
List<string> list5 = (from setId in list4.SelectMany((List<uint> refs) => refs).Distinct()
|
|
select (!owners.TryGetValue(setId, out value3)) ? $"set {setId} unknown" : string.Join("/", value3)).Distinct().ToList();
|
|
stringBuilder2 = stringBuilder;
|
|
StringBuilder stringBuilder7 = stringBuilder2;
|
|
invariantCulture = CultureInfo.InvariantCulture;
|
|
IFormatProvider provider5 = invariantCulture;
|
|
handler = new StringBuilder.AppendInterpolatedStringHandler(48, 4, stringBuilder2, invariantCulture);
|
|
handler.AppendFormatted(text3);
|
|
handler.AppendLiteral(": ");
|
|
handler.AppendFormatted(list4.Count);
|
|
handler.AppendLiteral(" layers, ");
|
|
handler.AppendFormatted(value2);
|
|
handler.AppendLiteral(" with layer-set refs; ");
|
|
handler.AppendLiteral("attributed to: ");
|
|
handler.AppendFormatted((list5.Count > 0) ? string.Join(", ", list5) : "-");
|
|
stringBuilder7.AppendLine(provider5, ref handler);
|
|
}
|
|
return stringBuilder.ToString();
|
|
}
|
|
|
|
private byte[]? ReadFileBytes(string path)
|
|
{
|
|
try
|
|
{
|
|
return _dataManager.GetFile(path)?.Data;
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
_logger.LogTrace(exception, "Failed to read {Path}", path);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private static List<(uint SetId, uint TerritoryId)> ParseLvbLayerSets(byte[]? data, StringBuilder sb)
|
|
{
|
|
List<(uint, uint)> list = new List<(uint, uint)>();
|
|
if (data == null || data.Length < 36 || !HasMagic(data, 0, "LVB1") || !HasMagic(data, 12, "SCN1"))
|
|
{
|
|
sb.AppendLine(".lvb: missing or not LVB1/SCN1");
|
|
return list;
|
|
}
|
|
int num = 20 + BitConverter.ToInt32(data, 32);
|
|
if (num < 0 || num + 12 > data.Length)
|
|
{
|
|
sb.AppendLine(".lvb: layer-set folder offset out of range");
|
|
return list;
|
|
}
|
|
int num2 = BitConverter.ToInt32(data, num + 4);
|
|
int num3 = num + 12;
|
|
bool flag = ((num2 < 0 || num2 > 4096) ? true : false);
|
|
StringBuilder stringBuilder;
|
|
IFormatProvider invariantCulture;
|
|
StringBuilder.AppendInterpolatedStringHandler handler;
|
|
if (flag || num3 + num2 * 28 > data.Length)
|
|
{
|
|
stringBuilder = sb;
|
|
StringBuilder stringBuilder2 = stringBuilder;
|
|
invariantCulture = CultureInfo.InvariantCulture;
|
|
IFormatProvider provider = invariantCulture;
|
|
handler = new StringBuilder.AppendInterpolatedStringHandler(34, 1, stringBuilder, invariantCulture);
|
|
handler.AppendLiteral(".lvb: implausible layer-set count ");
|
|
handler.AppendFormatted(num2);
|
|
stringBuilder2.AppendLine(provider, ref handler);
|
|
return list;
|
|
}
|
|
for (int i = 0; i < num2; i++)
|
|
{
|
|
int num4 = num3 + i * 28;
|
|
list.Add((BitConverter.ToUInt32(data, num4), BitConverter.ToUInt32(data, num4 + 12)));
|
|
}
|
|
stringBuilder = sb;
|
|
StringBuilder stringBuilder3 = stringBuilder;
|
|
invariantCulture = CultureInfo.InvariantCulture;
|
|
IFormatProvider provider2 = invariantCulture;
|
|
handler = new StringBuilder.AppendInterpolatedStringHandler(17, 1, stringBuilder, invariantCulture);
|
|
handler.AppendLiteral(".lvb: ");
|
|
handler.AppendFormatted(num2);
|
|
handler.AppendLiteral(" layer sets");
|
|
stringBuilder3.AppendLine(provider2, ref handler);
|
|
return list;
|
|
}
|
|
|
|
private static List<List<uint>> ParseLgbLayerSetReferences(byte[]? data)
|
|
{
|
|
List<List<uint>> list = new List<List<uint>>();
|
|
if (data == null || data.Length < 36 || !HasMagic(data, 0, "LGB1") || !HasMagic(data, 12, "LGP1"))
|
|
{
|
|
return list;
|
|
}
|
|
int num = 20 + BitConverter.ToInt32(data, 28);
|
|
int num2 = BitConverter.ToInt32(data, 32);
|
|
bool flag = ((num2 < 0 || num2 > 100000) ? true : false);
|
|
if (flag || num < 0 || num + num2 * 4 > data.Length)
|
|
{
|
|
return list;
|
|
}
|
|
for (int i = 0; i < num2; i++)
|
|
{
|
|
List<uint> list2 = new List<uint>();
|
|
list.Add(list2);
|
|
int num3 = num + BitConverter.ToInt32(data, num + i * 4);
|
|
if (num3 < 0 || num3 + 36 > data.Length)
|
|
{
|
|
continue;
|
|
}
|
|
int num4 = num3 + BitConverter.ToInt32(data, num3 + 20);
|
|
if (num4 <= num3 || num4 + 12 > data.Length)
|
|
{
|
|
continue;
|
|
}
|
|
int num5 = num4 + BitConverter.ToInt32(data, num4 + 4);
|
|
int num6 = BitConverter.ToInt32(data, num4 + 8);
|
|
flag = ((num6 < 1 || num6 > 4096) ? true : false);
|
|
if (!flag && num5 >= 0 && num5 + num6 * 4 <= data.Length)
|
|
{
|
|
for (int j = 0; j < num6; j++)
|
|
{
|
|
list2.Add(BitConverter.ToUInt32(data, num5 + j * 4));
|
|
}
|
|
}
|
|
}
|
|
return list;
|
|
}
|
|
|
|
private static bool HasMagic(byte[] data, int at, string magic)
|
|
{
|
|
for (int i = 0; i < magic.Length; i++)
|
|
{
|
|
if (data[at + i] != (byte)magic[i])
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private string RunGuarded(Func<string> probe)
|
|
{
|
|
try
|
|
{
|
|
return probe();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Data probe failed");
|
|
return $"probe failed: {ex}";
|
|
}
|
|
}
|
|
}
|