muffin v7.5.12
This commit is contained in:
parent
3102923ce2
commit
911ed68baa
65 changed files with 2356 additions and 1539 deletions
|
|
@ -2,13 +2,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
using System.Threading;
|
||||
using Dalamud.Plugin.Services;
|
||||
using LLib.GameData;
|
||||
using Lumina.Data.Files;
|
||||
using Lumina.Data.Parsing.Layer;
|
||||
using Lumina.Excel;
|
||||
using Lumina.Excel.Sheets;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SmartNav.Model.Navigation;
|
||||
|
||||
|
|
@ -16,51 +10,61 @@ namespace SmartNav.Data;
|
|||
|
||||
public sealed class LgbZoneBoundarySource : IZoneBoundarySource
|
||||
{
|
||||
private sealed record LgbData(List<ExitRangeEntry> ExitRanges, Dictionary<(ushort TerritoryId, uint InstanceId), PopRangeEntry> PopRangeIndex);
|
||||
|
||||
private const uint BoundaryFileMagic = 1112425562u;
|
||||
|
||||
private const int BoundaryFileFormatVersion = 1;
|
||||
|
||||
private const string CacheFileName = "zone-boundary-cache.bin";
|
||||
|
||||
private readonly IDataManager _dataManager;
|
||||
private const int MaxPreallocatedEntries = 131072;
|
||||
|
||||
private readonly ILogger<LgbZoneBoundarySource> _logger;
|
||||
|
||||
private readonly Lazy<IReadOnlyList<ZoneBoundary>> _boundaries;
|
||||
|
||||
private readonly string? _cacheDirectory;
|
||||
private readonly string? _overrideDirectory;
|
||||
|
||||
private readonly string? _gameVersion;
|
||||
|
||||
public LgbZoneBoundarySource(IDataManager dataManager, ILogger<LgbZoneBoundarySource> logger, SmartNavDataOptions? dataOptions = null)
|
||||
private readonly object _buildLock = new object();
|
||||
|
||||
private IReadOnlyList<ZoneBoundary>? _boundaries;
|
||||
|
||||
public LgbZoneBoundarySource(ILogger<LgbZoneBoundarySource> logger, SmartNavDataOptions? dataOptions = null)
|
||||
{
|
||||
_dataManager = dataManager;
|
||||
_logger = logger;
|
||||
_cacheDirectory = dataOptions?.UserOverrideDirectory?.FullName;
|
||||
_overrideDirectory = dataOptions?.UserOverrideDirectory?.FullName;
|
||||
_gameVersion = dataOptions?.GameVersion;
|
||||
_boundaries = new Lazy<IReadOnlyList<ZoneBoundary>>(Build, LazyThreadSafetyMode.ExecutionAndPublication);
|
||||
}
|
||||
|
||||
public IReadOnlyList<ZoneBoundary> GetBoundaries()
|
||||
{
|
||||
return _boundaries.Value;
|
||||
lock (_buildLock)
|
||||
{
|
||||
return _boundaries ?? (_boundaries = Build());
|
||||
}
|
||||
}
|
||||
|
||||
public void Invalidate()
|
||||
{
|
||||
lock (_buildLock)
|
||||
{
|
||||
_boundaries = null;
|
||||
}
|
||||
}
|
||||
|
||||
private IReadOnlyList<ZoneBoundary> Build()
|
||||
{
|
||||
try
|
||||
{
|
||||
(List<ExitRangeEntry>, Dictionary<(ushort, uint), PopRangeEntry>)? tuple = TryLoadFromDisk();
|
||||
if (!tuple.HasValue)
|
||||
var (lgbData, text) = LoadRawData();
|
||||
if (lgbData == null)
|
||||
{
|
||||
_logger.LogInformation("No boundary cache found, falling back to in-process LGB scan");
|
||||
return Array.Empty<ZoneBoundary>();
|
||||
}
|
||||
(List<ExitRangeEntry>, Dictionary<(ushort, uint), PopRangeEntry>) obj = tuple ?? CollectLgbData();
|
||||
List<ExitRangeEntry> item = obj.Item1;
|
||||
Dictionary<(ushort, uint), PopRangeEntry> item2 = obj.Item2;
|
||||
List<ZoneBoundary> list = ZoneBoundaryDerivation.Derive(item, item2, _logger);
|
||||
List<ZoneBoundary> list = ZoneBoundaryDerivation.Derive(lgbData.ExitRanges, lgbData.PopRangeIndex, _logger);
|
||||
ZoneBoundaryDerivation.ApplyOverrides(list, ZoneBoundaryOverrides.FlyingPairs, ZoneBoundaryOverrides.PositionOverrides, _logger);
|
||||
_logger.LogDebug("Derived {Count} zone boundaries from {ExitCount} ExitRanges{Source}", list.Count, item.Count, tuple.HasValue ? " (from cache)" : "");
|
||||
_logger.LogDebug("Derived {Count} zone boundaries from {ExitCount} ExitRanges ({Source})", list.Count, lgbData.ExitRanges.Count, text);
|
||||
return list;
|
||||
}
|
||||
catch (Exception exception)
|
||||
|
|
@ -70,171 +74,95 @@ public sealed class LgbZoneBoundarySource : IZoneBoundarySource
|
|||
}
|
||||
}
|
||||
|
||||
private (List<ExitRangeEntry> ExitRanges, Dictionary<(ushort TerritoryId, uint InstanceId), PopRangeEntry> PopRangeIndex)? TryLoadFromDisk()
|
||||
private (LgbData? Data, string Source) LoadRawData()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_cacheDirectory) || string.IsNullOrEmpty(_gameVersion))
|
||||
LgbData lgbData = TryLoadOverrideFile("override file");
|
||||
if (lgbData != null)
|
||||
{
|
||||
return (Data: lgbData, Source: "override file");
|
||||
}
|
||||
return (Data: TryLoadEmbedded("embedded resource"), Source: "embedded resource");
|
||||
}
|
||||
|
||||
private LgbData? TryLoadOverrideFile(string source)
|
||||
{
|
||||
if (string.IsNullOrEmpty(_overrideDirectory))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
string path = Path.Combine(_cacheDirectory, "zone-boundary-cache.bin");
|
||||
if (!File.Exists(path))
|
||||
string text = Path.Combine(_overrideDirectory, "zone-boundary-cache.bin");
|
||||
if (!File.Exists(text))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
try
|
||||
{
|
||||
using BinaryReader binaryReader = new BinaryReader(File.OpenRead(path));
|
||||
if (binaryReader.ReadUInt32() != 1112425562 || binaryReader.ReadInt32() != 1)
|
||||
using Stream stream = File.OpenRead(text);
|
||||
LgbData lgbData = ReadCache(stream, source);
|
||||
if (lgbData.ExitRanges.Count == 0 && lgbData.PopRangeIndex.Count == 0)
|
||||
{
|
||||
_logger.LogDebug("Zone boundary cache has unknown header, discarding");
|
||||
_logger.LogWarning("Zone boundary {Source} {Path} holds no exits and no pops, ignoring it and using the embedded data", source, text);
|
||||
return null;
|
||||
}
|
||||
if (binaryReader.ReadString() != _gameVersion)
|
||||
{
|
||||
_logger.LogDebug("Zone boundary cache is for a different game version, discarding");
|
||||
return null;
|
||||
}
|
||||
int num = binaryReader.ReadInt32();
|
||||
List<ExitRangeEntry> list = new List<ExitRangeEntry>(num);
|
||||
for (int i = 0; i < num; i++)
|
||||
{
|
||||
list.Add(new ExitRangeEntry(binaryReader.ReadUInt16(), binaryReader.ReadUInt32(), new Vector3(binaryReader.ReadSingle(), binaryReader.ReadSingle(), binaryReader.ReadSingle()), binaryReader.ReadUInt16(), binaryReader.ReadUInt32(), binaryReader.ReadUInt32(), new Vector3(binaryReader.ReadSingle(), binaryReader.ReadSingle(), binaryReader.ReadSingle()), new Vector3(binaryReader.ReadSingle(), binaryReader.ReadSingle(), binaryReader.ReadSingle())));
|
||||
}
|
||||
int num2 = binaryReader.ReadInt32();
|
||||
Dictionary<(ushort, uint), PopRangeEntry> dictionary = new Dictionary<(ushort, uint), PopRangeEntry>(num2);
|
||||
for (int j = 0; j < num2; j++)
|
||||
{
|
||||
ushort num3 = binaryReader.ReadUInt16();
|
||||
uint num4 = binaryReader.ReadUInt32();
|
||||
Vector3 position = new Vector3(binaryReader.ReadSingle(), binaryReader.ReadSingle(), binaryReader.ReadSingle());
|
||||
dictionary[(num3, num4)] = new PopRangeEntry(num3, num4, position);
|
||||
}
|
||||
_logger.LogDebug("Zone boundary cache loaded from disk ({ExitCount} exits, {PopCount} pops)", list.Count, dictionary.Count);
|
||||
return (list, dictionary);
|
||||
return lgbData;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.LogWarning(exception, "Failed to load zone boundary cache, falling back to LGB scan");
|
||||
_logger.LogWarning(exception, "Failed to read zone boundary {Source}, falling back to the embedded data", source);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private LgbData? TryLoadEmbedded(string source)
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(path);
|
||||
using Stream stream = AssemblyLgbCacheLoader.OpenZoneBoundaryCache();
|
||||
return ReadCache(stream, source);
|
||||
}
|
||||
catch
|
||||
catch (Exception exception)
|
||||
{
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private (List<ExitRangeEntry> ExitRanges, Dictionary<(ushort TerritoryId, uint InstanceId), PopRangeEntry> PopRangeIndex) CollectLgbData()
|
||||
{
|
||||
List<ExitRangeEntry> list = new List<ExitRangeEntry>();
|
||||
Dictionary<(ushort, uint), PopRangeEntry> dictionary = new Dictionary<(ushort, uint), PopRangeEntry>();
|
||||
HashSet<uint> hashSet = new HashSet<uint>();
|
||||
foreach (Aetheryte item in _dataManager.GetExcelSheet<Aetheryte>())
|
||||
{
|
||||
hashSet.Add(item.Territory.RowId);
|
||||
}
|
||||
ExcelSheet<TerritoryType> excelSheet = _dataManager.GetExcelSheet<TerritoryType>();
|
||||
HashSet<string> visitedLgbPaths = new HashSet<string>();
|
||||
bool cacheFileResources = _dataManager.GameData.Options.CacheFileResources;
|
||||
_dataManager.GameData.Options.CacheFileResources = false;
|
||||
try
|
||||
{
|
||||
foreach (TerritoryType item2 in excelSheet)
|
||||
{
|
||||
if (hashSet.Contains(item2.RowId))
|
||||
{
|
||||
ScanTerritory(list, dictionary, visitedLgbPaths, item2);
|
||||
}
|
||||
}
|
||||
foreach (TerritoryType item3 in excelSheet)
|
||||
{
|
||||
ScanTerritory(list, dictionary, visitedLgbPaths, item3);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_dataManager.GameData.Options.CacheFileResources = cacheFileResources;
|
||||
}
|
||||
return (ExitRanges: list, PopRangeIndex: dictionary);
|
||||
}
|
||||
|
||||
private static bool IsNavigableOverworld(ETerritoryIntendedUse use)
|
||||
{
|
||||
switch (use)
|
||||
{
|
||||
case ETerritoryIntendedUse.Town:
|
||||
case ETerritoryIntendedUse.Overworld:
|
||||
case ETerritoryIntendedUse.OpeningArea:
|
||||
case ETerritoryIntendedUse.HousingOutdoor:
|
||||
case ETerritoryIntendedUse.Firmament:
|
||||
case ETerritoryIntendedUse.SanctumOfTheTwelve:
|
||||
case ETerritoryIntendedUse.GoldSaucer:
|
||||
case ETerritoryIntendedUse.Eureka:
|
||||
case ETerritoryIntendedUse.Bozja:
|
||||
case ETerritoryIntendedUse.IslandSanctuary:
|
||||
case ETerritoryIntendedUse.CosmicExploration:
|
||||
case ETerritoryIntendedUse.OccultCrescent:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
_logger.LogError(exception, "Failed to read zone boundary {Source} - building graph without boundary edges", source);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void ScanTerritory(List<ExitRangeEntry> exitRanges, Dictionary<(ushort TerritoryId, uint InstanceId), PopRangeEntry> popRangeIndex, HashSet<string> visitedLgbPaths, TerritoryType territory)
|
||||
private LgbData ReadCache(Stream stream, string source)
|
||||
{
|
||||
if (territory.RowId == 0 || !IsNavigableOverworld((ETerritoryIntendedUse)territory.TerritoryIntendedUse.RowId))
|
||||
using BinaryReader binaryReader = new BinaryReader(stream, Encoding.UTF8, leaveOpen: true);
|
||||
if (binaryReader.ReadUInt32() != 1112425562 || binaryReader.ReadInt32() != 1)
|
||||
{
|
||||
return;
|
||||
throw new InvalidDataException("zone boundary " + source + " has an unknown header");
|
||||
}
|
||||
string text = territory.Bg.ExtractText();
|
||||
if (string.IsNullOrEmpty(text))
|
||||
string text = binaryReader.ReadString();
|
||||
if (!string.IsNullOrEmpty(_gameVersion) && text != _gameVersion)
|
||||
{
|
||||
return;
|
||||
_logger.LogInformation("Zone boundary {Source} was built for game version {FileVersion}, running {GameVersion} - using it anyway", source, text, _gameVersion);
|
||||
}
|
||||
int num = text.IndexOf("/level/", StringComparison.Ordinal);
|
||||
int num = binaryReader.ReadInt32();
|
||||
if (num < 0)
|
||||
{
|
||||
return;
|
||||
throw new InvalidDataException($"zone boundary {source} has a negative exit count ({num})");
|
||||
}
|
||||
string text2 = "bg/" + text.Substring(0, num + 1) + "level/planmap.lgb";
|
||||
if (!visitedLgbPaths.Add(text2))
|
||||
List<ExitRangeEntry> list = new List<ExitRangeEntry>(Math.Min(num, 131072));
|
||||
for (int i = 0; i < num; i++)
|
||||
{
|
||||
return;
|
||||
list.Add(new ExitRangeEntry(binaryReader.ReadUInt16(), binaryReader.ReadUInt32(), new Vector3(binaryReader.ReadSingle(), binaryReader.ReadSingle(), binaryReader.ReadSingle()), binaryReader.ReadUInt16(), binaryReader.ReadUInt32(), binaryReader.ReadUInt32(), new Vector3(binaryReader.ReadSingle(), binaryReader.ReadSingle(), binaryReader.ReadSingle()), new Vector3(binaryReader.ReadSingle(), binaryReader.ReadSingle(), binaryReader.ReadSingle())));
|
||||
}
|
||||
ushort num2 = (ushort)territory.RowId;
|
||||
LgbFile file;
|
||||
try
|
||||
int num2 = binaryReader.ReadInt32();
|
||||
if (num2 < 0)
|
||||
{
|
||||
file = _dataManager.GetFile<LgbFile>(text2);
|
||||
throw new InvalidDataException($"zone boundary {source} has a negative pop count ({num2})");
|
||||
}
|
||||
catch (Exception exception)
|
||||
Dictionary<(ushort, uint), PopRangeEntry> dictionary = new Dictionary<(ushort, uint), PopRangeEntry>(Math.Min(num2, 131072));
|
||||
for (int j = 0; j < num2; j++)
|
||||
{
|
||||
_logger.LogTrace(exception, "Failed to load {Path}", text2);
|
||||
return;
|
||||
}
|
||||
if (file == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
LayerCommon.Layer[] layers = file.Layers;
|
||||
for (int i = 0; i < layers.Length; i++)
|
||||
{
|
||||
LayerCommon.InstanceObject[] instanceObjects = layers[i].InstanceObjects;
|
||||
for (int j = 0; j < instanceObjects.Length; j++)
|
||||
{
|
||||
LayerCommon.InstanceObject instanceObject = instanceObjects[j];
|
||||
if (instanceObject.AssetType == LayerEntryType.ExitRange)
|
||||
{
|
||||
LayerCommon.ExitRangeInstanceObject exitRangeInstanceObject = (LayerCommon.ExitRangeInstanceObject)(object)instanceObject.Object;
|
||||
exitRanges.Add(new ExitRangeEntry(num2, instanceObject.InstanceId, new Vector3(instanceObject.Transform.Translation.X, instanceObject.Transform.Translation.Y, instanceObject.Transform.Translation.Z), exitRangeInstanceObject.TerritoryType, exitRangeInstanceObject.DestInstanceId, exitRangeInstanceObject.ReturnInstanceId, new Vector3(instanceObject.Transform.Rotation.X, instanceObject.Transform.Rotation.Y, instanceObject.Transform.Rotation.Z), new Vector3(instanceObject.Transform.Scale.X, instanceObject.Transform.Scale.Y, instanceObject.Transform.Scale.Z)));
|
||||
}
|
||||
else if (instanceObject.AssetType == LayerEntryType.PopRange)
|
||||
{
|
||||
popRangeIndex[(num2, instanceObject.InstanceId)] = new PopRangeEntry(num2, instanceObject.InstanceId, new Vector3(instanceObject.Transform.Translation.X, instanceObject.Transform.Translation.Y, instanceObject.Transform.Translation.Z));
|
||||
}
|
||||
}
|
||||
ushort num3 = binaryReader.ReadUInt16();
|
||||
uint num4 = binaryReader.ReadUInt32();
|
||||
Vector3 position = new Vector3(binaryReader.ReadSingle(), binaryReader.ReadSingle(), binaryReader.ReadSingle());
|
||||
dictionary[(num3, num4)] = new PopRangeEntry(num3, num4, position);
|
||||
}
|
||||
_logger.LogDebug("Zone boundary data loaded from {Source} ({ExitCount} exits, {PopCount} pops)", source, list.Count, dictionary.Count);
|
||||
return new LgbData(list, dictionary);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue