611 lines
22 KiB
C#
611 lines
22 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Numerics;
|
|
using System.Text.Json;
|
|
using Dalamud.Plugin.Services;
|
|
using LLib.GameData;
|
|
using LLib.Shop;
|
|
using Lumina.Excel;
|
|
using Lumina.Excel.Sheets;
|
|
using Microsoft.Extensions.Logging;
|
|
using SmartNav.Model.Navigation;
|
|
|
|
namespace SmartNav.Data;
|
|
|
|
public sealed class WarpDataService
|
|
{
|
|
private sealed record Snapshot(IReadOnlyList<WarpInfo> Warps, IReadOnlyDictionary<uint, WarpInfo> WarpsByRowId, IReadOnlySet<uint> SkippedWarpIds, int TotalLuminaWarps, int NavigationRelevantWarps, int WarpsWithDestination, int WarpsWithUnknownSource, int WarpsWithDerivedSource, int WarpsWithDerivedArrival, int SkippedNoDest, int SkippedSourceBlocked, int SkippedDestBlocked, bool DerivedSourcesPending);
|
|
|
|
public sealed record WarpInfo(uint RowId, string? Name, string? Question, ushort SourceTerritoryId, Vector3 SourcePosition, uint? NpcDataId, string? NpcName, ushort DestTerritoryId, Vector3? DestPosition, int GilCost, uint[] RequiredQuestIds, uint[]? AvailableDuringMsqIds = null, ushort RequiredClassLevel = 0, bool DerivedSource = false, bool RequireAllQuests = true, bool DerivedArrival = false);
|
|
|
|
private const uint WarpHandlerType = 2u;
|
|
|
|
private const uint ArrayEventHandlerType = 13u;
|
|
|
|
private static readonly HashSet<ETerritoryIntendedUse> NonNavigableIntendedUses = new HashSet<ETerritoryIntendedUse>
|
|
{
|
|
ETerritoryIntendedUse.Inn,
|
|
ETerritoryIntendedUse.Dungeon,
|
|
ETerritoryIntendedUse.VariantDungeon,
|
|
ETerritoryIntendedUse.MordionGaol,
|
|
ETerritoryIntendedUse.BeforeTrialDung,
|
|
ETerritoryIntendedUse.AllianceRaid,
|
|
ETerritoryIntendedUse.PreEwOverworldQuestBattle,
|
|
ETerritoryIntendedUse.Trial,
|
|
ETerritoryIntendedUse.WaitingRoom,
|
|
ETerritoryIntendedUse.HousingIndoor,
|
|
ETerritoryIntendedUse.SoloOverworldInstances,
|
|
ETerritoryIntendedUse.Raid1,
|
|
ETerritoryIntendedUse.Raid2,
|
|
ETerritoryIntendedUse.Frontline,
|
|
ETerritoryIntendedUse.LordOfVerminion,
|
|
ETerritoryIntendedUse.ExploratoryMissions,
|
|
ETerritoryIntendedUse.HallOfTheNovice,
|
|
ETerritoryIntendedUse.CrystallineConflict,
|
|
ETerritoryIntendedUse.SoloDuty,
|
|
ETerritoryIntendedUse.GrandCompanyBarracks,
|
|
ETerritoryIntendedUse.DeepDungeon,
|
|
ETerritoryIntendedUse.TreasureMapInstance,
|
|
ETerritoryIntendedUse.SeasonalInstancedArea,
|
|
ETerritoryIntendedUse.TripleTriadBattlehall,
|
|
ETerritoryIntendedUse.CrystallineConflictCustomMatch,
|
|
ETerritoryIntendedUse.DiademHuntingGrounds,
|
|
ETerritoryIntendedUse.RivalWings,
|
|
ETerritoryIntendedUse.LeapOfFaith,
|
|
ETerritoryIntendedUse.MaskedCarnival,
|
|
ETerritoryIntendedUse.OceanFishing,
|
|
ETerritoryIntendedUse.Diadem,
|
|
ETerritoryIntendedUse.TripleTriadOpenTournament,
|
|
ETerritoryIntendedUse.TripleTriadInvitationalParlor,
|
|
ETerritoryIntendedUse.DelubrumReginae,
|
|
ETerritoryIntendedUse.DelubrumReginaeSavage,
|
|
ETerritoryIntendedUse.EndwalkerMsqSoloOverworld,
|
|
ETerritoryIntendedUse.Elysion,
|
|
ETerritoryIntendedUse.CriterionDungeon,
|
|
ETerritoryIntendedUse.CriterionDungeonSavage,
|
|
ETerritoryIntendedUse.Blunderville
|
|
};
|
|
|
|
private readonly IDataManager? _dataManager;
|
|
|
|
private readonly SmartNavDataOptions? _dataOptions;
|
|
|
|
private readonly NpcPositionCache? _npcPositionCache;
|
|
|
|
private readonly ILogger<WarpDataService>? _logger;
|
|
|
|
private readonly object _applyLock = new object();
|
|
|
|
private volatile Snapshot _current;
|
|
|
|
public IReadOnlyList<WarpInfo> Warps => _current.Warps;
|
|
|
|
public IReadOnlyDictionary<uint, WarpInfo> WarpsByRowId => _current.WarpsByRowId;
|
|
|
|
public IReadOnlySet<uint> SkippedWarpIds => _current.SkippedWarpIds;
|
|
|
|
public int TotalLuminaWarps => _current.TotalLuminaWarps;
|
|
|
|
public int NavigationRelevantWarps => _current.NavigationRelevantWarps;
|
|
|
|
public int WarpsWithDestination => _current.WarpsWithDestination;
|
|
|
|
public int WarpsWithUnknownSource => _current.WarpsWithUnknownSource;
|
|
|
|
public int WarpsWithDerivedSource => _current.WarpsWithDerivedSource;
|
|
|
|
public int WarpsWithDerivedArrival => _current.WarpsWithDerivedArrival;
|
|
|
|
public int SkippedNoDest => _current.SkippedNoDest;
|
|
|
|
public int SkippedSourceBlocked => _current.SkippedSourceBlocked;
|
|
|
|
public int SkippedDestBlocked => _current.SkippedDestBlocked;
|
|
|
|
public bool DerivedSourcesPending => _current.DerivedSourcesPending;
|
|
|
|
internal WarpDataService(List<WarpInfo> warps, HashSet<uint>? skippedWarpIds = null)
|
|
{
|
|
Dictionary<uint, WarpInfo> dictionary = new Dictionary<uint, WarpInfo>();
|
|
foreach (WarpInfo warp in warps)
|
|
{
|
|
dictionary.TryAdd(warp.RowId, warp);
|
|
}
|
|
_current = new Snapshot(warps, dictionary, skippedWarpIds ?? new HashSet<uint>(), 0, warps.Count, 0, 0, warps.Count((WarpInfo w) => w.DerivedSource), warps.Count((WarpInfo w) => w.DerivedArrival), 0, 0, 0, DerivedSourcesPending: false);
|
|
}
|
|
|
|
public WarpDataService(IDataManager dataManager, SmartNavDataOptions dataOptions, NpcPositionCache npcPositionCache, ILogger<WarpDataService> logger)
|
|
{
|
|
_dataManager = dataManager;
|
|
_dataOptions = dataOptions;
|
|
_npcPositionCache = npcPositionCache;
|
|
_logger = logger;
|
|
_current = BuildSnapshot();
|
|
}
|
|
|
|
public bool ApplyDerivedSources()
|
|
{
|
|
if (_npcPositionCache == null)
|
|
{
|
|
return false;
|
|
}
|
|
lock (_applyLock)
|
|
{
|
|
if (!_current.DerivedSourcesPending || !_npcPositionCache.IsBuilt)
|
|
{
|
|
return false;
|
|
}
|
|
_current = BuildSnapshot();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
private Snapshot BuildSnapshot()
|
|
{
|
|
IDataManager dataManager = _dataManager;
|
|
SmartNavDataOptions dataOptions = _dataOptions;
|
|
NpcPositionCache npcPositionCache = _npcPositionCache;
|
|
ILogger<WarpDataService> logger = _logger;
|
|
bool isBuilt = npcPositionCache.IsBuilt;
|
|
Func<uint, (ushort, Vector3, bool)?> getPlacement = (isBuilt ? new Func<uint, (ushort, Vector3, bool)?>(npcPositionCache.TryGetPlacement) : ((Func<uint, (ushort, Vector3, bool)?>)((uint _) => ((ushort TerritoryId, Vector3 Position, bool FestivalOnly)?)null)));
|
|
List<WarpDestinationEntry> list = new List<WarpDestinationEntry>();
|
|
HashSet<uint> hashSet = new HashSet<uint>();
|
|
var (readOnlyList, readOnlyDictionary) = AssemblyWarpDestinationLoader.GetOverrides(dataOptions.DevSourceDirectory?.FullName);
|
|
foreach (uint item4 in readOnlyList)
|
|
{
|
|
hashSet.Add(item4);
|
|
}
|
|
WarpDestinationEntry value;
|
|
foreach (KeyValuePair<string, WarpDestinationEntry> item5 in readOnlyDictionary)
|
|
{
|
|
item5.Deconstruct(out var _, out value);
|
|
WarpDestinationEntry item = value;
|
|
list.Add(item);
|
|
}
|
|
if (dataOptions.UserOverrideDirectory != null)
|
|
{
|
|
LoadWarpOverridesFrom(Path.Combine(dataOptions.UserOverrideDirectory.FullName, "warp-destinations.json"), list, hashSet);
|
|
}
|
|
Dictionary<(uint, uint), WarpDestinationEntry> dictionary = new Dictionary<(uint, uint), WarpDestinationEntry>();
|
|
Dictionary<uint, WarpDestinationEntry> dictionary2 = new Dictionary<uint, WarpDestinationEntry>();
|
|
foreach (WarpDestinationEntry item6 in list)
|
|
{
|
|
uint? npcDataId = item6.NpcDataId;
|
|
if (npcDataId.HasValue)
|
|
{
|
|
uint valueOrDefault = npcDataId.GetValueOrDefault();
|
|
dictionary[(item6.WarpRowId, valueOrDefault)] = item6;
|
|
}
|
|
else
|
|
{
|
|
dictionary2[item6.WarpRowId] = item6;
|
|
}
|
|
}
|
|
HashSet<ushort> hashSet2 = new HashSet<ushort>();
|
|
foreach (WarpDestinationEntry item7 in list)
|
|
{
|
|
if (item7.SourceTerritoryId > 0)
|
|
{
|
|
hashSet2.Add(item7.SourceTerritoryId);
|
|
}
|
|
if (item7.DestTerritoryId > 0)
|
|
{
|
|
hashSet2.Add(item7.DestTerritoryId);
|
|
}
|
|
}
|
|
Dictionary<ushort, ETerritoryIntendedUse> dictionary3 = (from x in dataManager.GetExcelSheet<TerritoryType>()
|
|
where x.RowId != 0
|
|
select x).ToDictionary((TerritoryType x) => (ushort)x.RowId, (TerritoryType x) => (ETerritoryIntendedUse)x.TerritoryIntendedUse.RowId);
|
|
Dictionary<uint, List<uint>> dictionary4 = new Dictionary<uint, List<uint>>();
|
|
int nestedArraysSkipped = 0;
|
|
ExcelSheet<ArrayEventHandler> arrayEventHandlers = dataManager.GetExcelSheet<ArrayEventHandler>();
|
|
Func<uint, IEnumerable<uint>> arrayMemberLookup = delegate(uint handlerId)
|
|
{
|
|
ArrayEventHandler? rowOrDefault = arrayEventHandlers.GetRowOrDefault(handlerId);
|
|
return (!rowOrDefault.HasValue) ? null : (from x in rowOrDefault.GetValueOrDefault().Data
|
|
where x.RowId != 0
|
|
select x.RowId).ToList();
|
|
};
|
|
foreach (ENpcBase item8 in dataManager.GetExcelSheet<ENpcBase>())
|
|
{
|
|
foreach (RowRef eNpcDatum in item8.ENpcData)
|
|
{
|
|
AddWarpFronter(dictionary4, eNpcDatum.RowId, item8.RowId, arrayMemberLookup, ref nestedArraysSkipped);
|
|
}
|
|
}
|
|
foreach (EObj item9 in dataManager.GetExcelSheet<EObj>())
|
|
{
|
|
AddWarpFronter(dictionary4, item9.Data.RowId, item9.RowId, arrayMemberLookup, ref nestedArraysSkipped);
|
|
}
|
|
if (nestedArraysSkipped > 0)
|
|
{
|
|
logger.LogWarning("Warp fronter index: {Count} nested ArrayEventHandler refs skipped (hop depth capped at 1)", nestedArraysSkipped);
|
|
}
|
|
Dictionary<uint, WarpInfo> dictionary5 = new Dictionary<uint, WarpInfo>();
|
|
Dictionary<uint, uint> dictionary6 = new Dictionary<uint, uint>();
|
|
Dictionary<uint, List<(uint, ushort, Vector3)>> dictionary7 = new Dictionary<uint, List<(uint, ushort, Vector3)>>();
|
|
int num = 0;
|
|
int num2 = 0;
|
|
int num3 = 0;
|
|
int num4 = 0;
|
|
(uint, uint) key2;
|
|
foreach (Warp item10 in from x in dataManager.GetExcelSheet<Warp>()
|
|
where x.RowId != 0
|
|
select x)
|
|
{
|
|
num++;
|
|
ushort num5 = (ushort)item10.TerritoryType.RowId;
|
|
if (num5 == 0)
|
|
{
|
|
num2++;
|
|
continue;
|
|
}
|
|
if (dictionary3.TryGetValue(num5, out var value2) && NonNavigableIntendedUses.Contains(value2) && !hashSet2.Contains(num5))
|
|
{
|
|
num4++;
|
|
continue;
|
|
}
|
|
dictionary6[item10.RowId] = item10.PopRange.RowId;
|
|
ushort num6 = 0;
|
|
Vector3 sourcePosition = Vector3.Zero;
|
|
uint? npcDataId2 = null;
|
|
string npcName = null;
|
|
if (dictionary2.TryGetValue(item10.RowId, out var value3))
|
|
{
|
|
num6 = value3.SourceTerritoryId;
|
|
sourcePosition = value3.SourcePosition;
|
|
npcDataId2 = value3.NpcDataId;
|
|
}
|
|
if (num6 == 0)
|
|
{
|
|
foreach (KeyValuePair<(uint, uint), WarpDestinationEntry> item11 in dictionary)
|
|
{
|
|
item11.Deconstruct(out key2, out value);
|
|
(uint, uint) tuple2 = key2;
|
|
WarpDestinationEntry warpDestinationEntry = value;
|
|
if (tuple2.Item1 == item10.RowId && warpDestinationEntry.SourceTerritoryId > 0)
|
|
{
|
|
num6 = warpDestinationEntry.SourceTerritoryId;
|
|
sourcePosition = warpDestinationEntry.SourcePosition;
|
|
npcDataId2 = warpDestinationEntry.NpcDataId;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
bool derivedSource = false;
|
|
if (num6 == 0)
|
|
{
|
|
List<(uint, ushort, Vector3)> list2 = ResolveDerivedSources(item10.RowId, dictionary4, getPlacement);
|
|
if (list2.Count > 0)
|
|
{
|
|
(uint, ushort, Vector3) tuple3 = list2[0];
|
|
num6 = tuple3.Item2;
|
|
sourcePosition = tuple3.Item3;
|
|
npcDataId2 = tuple3.Item1;
|
|
npcName = ResolveObjectName(tuple3.Item1);
|
|
derivedSource = true;
|
|
if (list2.Count > 1)
|
|
{
|
|
dictionary7[item10.RowId] = list2;
|
|
}
|
|
}
|
|
}
|
|
if (num6 > 0 && dictionary3.TryGetValue(num6, out var value4) && NonNavigableIntendedUses.Contains(value4) && !hashSet2.Contains(num6))
|
|
{
|
|
num3++;
|
|
continue;
|
|
}
|
|
int gilCost = 0;
|
|
ushort requiredClassLevel = 0;
|
|
List<uint> list3 = new List<uint>();
|
|
bool requireAllQuests = true;
|
|
WarpCondition? valueNullable = item10.WarpCondition.ValueNullable;
|
|
if (valueNullable.HasValue)
|
|
{
|
|
gilCost = valueNullable.Value.Gil;
|
|
requiredClassLevel = valueNullable.Value.ClassLevel;
|
|
if (valueNullable.Value.RequiredQuest1.RowId != 0)
|
|
{
|
|
list3.Add(valueNullable.Value.RequiredQuest1.RowId);
|
|
}
|
|
if (valueNullable.Value.RequiredQuest2.RowId != 0)
|
|
{
|
|
list3.Add(valueNullable.Value.RequiredQuest2.RowId);
|
|
}
|
|
if (valueNullable.Value.RequiredQuest3.RowId != 0)
|
|
{
|
|
list3.Add(valueNullable.Value.RequiredQuest3.RowId);
|
|
}
|
|
if (valueNullable.Value.RequiredQuest4.RowId != 0)
|
|
{
|
|
list3.Add(valueNullable.Value.RequiredQuest4.RowId);
|
|
}
|
|
requireAllQuests = valueNullable.Value.CompleteParam == 1;
|
|
}
|
|
string text = item10.Name.ExtractText();
|
|
if (string.IsNullOrEmpty(text))
|
|
{
|
|
text = null;
|
|
}
|
|
string text2 = item10.Question.ExtractText();
|
|
if (string.IsNullOrEmpty(text2))
|
|
{
|
|
text2 = null;
|
|
}
|
|
dictionary5[item10.RowId] = new WarpInfo(item10.RowId, text, text2, num6, sourcePosition, npcDataId2, npcName, num5, null, gilCost, list3.ToArray(), null, requiredClassLevel, derivedSource, requireAllQuests);
|
|
}
|
|
List<WarpInfo> list4 = new List<WarpInfo>();
|
|
Dictionary<uint, WarpInfo> dictionary8 = new Dictionary<uint, WarpInfo>();
|
|
HashSet<(uint, uint)> hashSet3 = new HashSet<(uint, uint)>();
|
|
ExcelSheet<Level> levelSheet = dataManager.GetExcelSheet<Level>();
|
|
Func<uint, (ushort, Vector3)?> levelLookup = delegate(uint popId)
|
|
{
|
|
Level? rowOrDefault = levelSheet.GetRowOrDefault(popId);
|
|
return (!rowOrDefault.HasValue) ? (((ushort, Vector3)?)null) : new(ushort, Vector3)?(((ushort)rowOrDefault.Value.Territory.RowId, new Vector3(rowOrDefault.Value.X, rowOrDefault.Value.Y, rowOrDefault.Value.Z)));
|
|
};
|
|
DerivedArrivalFile arrivals = AssemblyDerivedArrivalLoader.GetArrivals(dataOptions.DevSourceDirectory?.FullName);
|
|
if (!string.IsNullOrEmpty(arrivals.GameVersion) && !string.IsNullOrEmpty(dataOptions.GameVersion) && arrivals.GameVersion != dataOptions.GameVersion)
|
|
{
|
|
logger.LogWarning("Derived-arrival data was generated for game version {DataVersion} but the game is {LiveVersion} - rerun SmartNav.Data.ArrivalScanner", arrivals.GameVersion, dataOptions.GameVersion);
|
|
}
|
|
uint key3;
|
|
foreach (KeyValuePair<uint, WarpInfo> item12 in dictionary5)
|
|
{
|
|
item12.Deconstruct(out key3, out var value5);
|
|
uint num7 = key3;
|
|
WarpInfo warpInfo = value5;
|
|
Vector3? destPosition = null;
|
|
uint[] array = null;
|
|
foreach (KeyValuePair<(uint, uint), WarpDestinationEntry> item13 in dictionary)
|
|
{
|
|
item13.Deconstruct(out key2, out value);
|
|
(uint, uint) tuple4 = key2;
|
|
WarpDestinationEntry warpDestinationEntry2 = value;
|
|
if (tuple4.Item1 != num7)
|
|
{
|
|
continue;
|
|
}
|
|
if (!destPosition.HasValue && warpDestinationEntry2.DestPosition != Vector3.Zero)
|
|
{
|
|
destPosition = warpDestinationEntry2.DestPosition;
|
|
}
|
|
if (array == null)
|
|
{
|
|
List<uint> availableDuringMsqIds = warpDestinationEntry2.AvailableDuringMsqIds;
|
|
if (availableDuringMsqIds != null && availableDuringMsqIds.Count > 0)
|
|
{
|
|
array = warpDestinationEntry2.AvailableDuringMsqIds.ToArray();
|
|
}
|
|
}
|
|
}
|
|
if (!destPosition.HasValue && dictionary2.TryGetValue(num7, out var value6) && value6.DestPosition != Vector3.Zero)
|
|
{
|
|
destPosition = value6.DestPosition;
|
|
}
|
|
if (array == null && dictionary2.TryGetValue(num7, out var value7))
|
|
{
|
|
List<uint> availableDuringMsqIds = value7.AvailableDuringMsqIds;
|
|
if (availableDuringMsqIds != null && availableDuringMsqIds.Count > 0)
|
|
{
|
|
array = value7.AvailableDuringMsqIds.ToArray();
|
|
}
|
|
}
|
|
bool derivedArrival = false;
|
|
if (!destPosition.HasValue && dictionary6.TryGetValue(num7, out var value8) && value8 != 0)
|
|
{
|
|
Vector3? vector = ResolveDerivedArrival(value8, warpInfo.DestTerritoryId, arrivals.Arrivals.GetValueOrDefault(num7), levelLookup);
|
|
if (vector.HasValue)
|
|
{
|
|
destPosition = vector;
|
|
derivedArrival = true;
|
|
}
|
|
}
|
|
WarpInfo warpInfo2 = warpInfo with
|
|
{
|
|
DestPosition = destPosition,
|
|
AvailableDuringMsqIds = array,
|
|
DerivedArrival = derivedArrival
|
|
};
|
|
list4.Add(warpInfo2);
|
|
dictionary8[num7] = warpInfo2;
|
|
uint? npcDataId = warpInfo.NpcDataId;
|
|
if (npcDataId.HasValue)
|
|
{
|
|
uint valueOrDefault2 = npcDataId.GetValueOrDefault();
|
|
hashSet3.Add((num7, valueOrDefault2));
|
|
}
|
|
}
|
|
foreach (KeyValuePair<(uint, uint), WarpDestinationEntry> item14 in dictionary)
|
|
{
|
|
item14.Deconstruct(out key2, out value);
|
|
(uint, uint) item2 = key2;
|
|
WarpDestinationEntry warpDestinationEntry3 = value;
|
|
if (dictionary5.ContainsKey(item2.Item1) && !hashSet3.Contains(item2))
|
|
{
|
|
WarpInfo warpInfo3 = dictionary5[item2.Item1];
|
|
WarpInfo warpInfo4 = dictionary8[item2.Item1];
|
|
Vector3? destPosition2 = ((warpDestinationEntry3.DestPosition != Vector3.Zero) ? new Vector3?(warpDestinationEntry3.DestPosition) : warpInfo4.DestPosition);
|
|
uint item3 = item2.Item1;
|
|
string? name = warpInfo3.Name;
|
|
string? question = warpInfo3.Question;
|
|
ushort sourceTerritoryId = warpDestinationEntry3.SourceTerritoryId;
|
|
Vector3 sourcePosition2 = warpDestinationEntry3.SourcePosition;
|
|
uint? npcDataId3 = warpDestinationEntry3.NpcDataId;
|
|
ushort destTerritoryId = warpInfo3.DestTerritoryId;
|
|
int gilCost2 = warpInfo3.GilCost;
|
|
uint[] requiredQuestIds = warpInfo3.RequiredQuestIds;
|
|
List<uint> availableDuringMsqIds = warpDestinationEntry3.AvailableDuringMsqIds;
|
|
list4.Add(new WarpInfo(item3, name, question, sourceTerritoryId, sourcePosition2, npcDataId3, null, destTerritoryId, destPosition2, gilCost2, requiredQuestIds, (availableDuringMsqIds != null && availableDuringMsqIds.Count > 0) ? warpDestinationEntry3.AvailableDuringMsqIds.ToArray() : null, warpInfo3.RequiredClassLevel, DerivedSource: false, warpInfo3.RequireAllQuests, warpDestinationEntry3.DestPosition == Vector3.Zero && warpInfo4.DerivedArrival));
|
|
hashSet3.Add(item2);
|
|
}
|
|
}
|
|
foreach (KeyValuePair<uint, List<(uint, ushort, Vector3)>> item15 in dictionary7)
|
|
{
|
|
item15.Deconstruct(out key3, out var value9);
|
|
uint num8 = key3;
|
|
List<(uint, ushort, Vector3)> list5 = value9;
|
|
if (!dictionary8.TryGetValue(num8, out var value10))
|
|
{
|
|
continue;
|
|
}
|
|
foreach (var item16 in list5)
|
|
{
|
|
if (hashSet3.Add((num8, item16.Item1)) && (!dictionary3.TryGetValue(item16.Item2, out var value11) || !NonNavigableIntendedUses.Contains(value11) || hashSet2.Contains(item16.Item2)))
|
|
{
|
|
list4.Add(value10 with
|
|
{
|
|
SourceTerritoryId = item16.Item2,
|
|
SourcePosition = item16.Item3,
|
|
NpcDataId = item16.Item1,
|
|
NpcName = ResolveObjectName(item16.Item1),
|
|
DerivedSource = true
|
|
});
|
|
}
|
|
}
|
|
}
|
|
int num9 = list4.Count((WarpInfo w) => w.DestPosition.HasValue);
|
|
int num10 = list4.Count((WarpInfo w) => w.SourceTerritoryId == 0);
|
|
int num11 = list4.Count((WarpInfo w) => w.DerivedSource);
|
|
int num12 = list4.Count((WarpInfo w) => w.DerivedArrival);
|
|
logger.LogDebug("WarpDataService: {Total} Lumina warps, {Relevant} navigation-relevant ({UniqueIds} unique IDs), {WithDest} with destination, {DerivedSrc} derived source, {DerivedArr} derived arrival, {UnknownSrc} unknown source{Pending}", num, list4.Count, dictionary8.Count, num9, num11, num12, num10, isBuilt ? "" : " (derived sources pending NPC position cache)");
|
|
logger.LogDebug("WarpDataService filtered: {NoDest} no dest territory, {UserSkipped} user-skipped, {SourceBlocked} source territory blocked, {DestBlocked} dest territory blocked", num2, hashSet.Count, num3, num4);
|
|
return new Snapshot(list4, dictionary8, hashSet, num, list4.Count, num9, num10, num11, num12, num2, num3, num4, !isBuilt);
|
|
string? ResolveObjectName(uint objectId)
|
|
{
|
|
ENpcResident? rowOrDefault = dataManager.GetExcelSheet<ENpcResident>().GetRowOrDefault(objectId);
|
|
if (rowOrDefault.HasValue)
|
|
{
|
|
string text3 = rowOrDefault.Value.Singular.ExtractText();
|
|
if (!string.IsNullOrEmpty(text3))
|
|
{
|
|
return text3;
|
|
}
|
|
}
|
|
EObjName? rowOrDefault2 = dataManager.GetExcelSheet<EObjName>().GetRowOrDefault(objectId);
|
|
if (rowOrDefault2.HasValue)
|
|
{
|
|
string text4 = rowOrDefault2.Value.Singular.ExtractText();
|
|
if (!string.IsNullOrEmpty(text4))
|
|
{
|
|
return text4;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
internal static void AddWarpFronter(Dictionary<uint, List<uint>> index, uint handlerId, uint fronterObjectId, Func<uint, IEnumerable<uint>?> arrayMemberLookup, ref int nestedArraysSkipped)
|
|
{
|
|
if (handlerId == 0)
|
|
{
|
|
return;
|
|
}
|
|
switch (handlerId >> 16)
|
|
{
|
|
case 2u:
|
|
AddFronterEntry(index, handlerId, fronterObjectId);
|
|
break;
|
|
case 13u:
|
|
{
|
|
IEnumerable<uint> enumerable = arrayMemberLookup(handlerId);
|
|
if (enumerable == null)
|
|
{
|
|
break;
|
|
}
|
|
{
|
|
foreach (uint item in enumerable)
|
|
{
|
|
if (item >> 16 == 2)
|
|
{
|
|
AddFronterEntry(index, item, fronterObjectId);
|
|
}
|
|
else if (item >> 16 == 13)
|
|
{
|
|
nestedArraysSkipped++;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void AddFronterEntry(Dictionary<uint, List<uint>> index, uint warpRowId, uint fronterObjectId)
|
|
{
|
|
if (!index.TryGetValue(warpRowId, out List<uint> value))
|
|
{
|
|
value = (index[warpRowId] = new List<uint>());
|
|
}
|
|
if (!value.Contains(fronterObjectId))
|
|
{
|
|
value.Add(fronterObjectId);
|
|
}
|
|
}
|
|
|
|
internal static Vector3? ResolveDerivedArrival(uint popRangeId, ushort destTerritoryId, DerivedArrivalEntry? embedded, Func<uint, (ushort TerritoryId, Vector3 Position)?> levelLookup)
|
|
{
|
|
if (popRangeId == 0)
|
|
{
|
|
return null;
|
|
}
|
|
if (embedded != null && embedded.PopRangeId == popRangeId && embedded.TerritoryId == destTerritoryId)
|
|
{
|
|
return new Vector3(embedded.X, embedded.Y, embedded.Z);
|
|
}
|
|
(ushort, Vector3)? tuple = levelLookup(popRangeId);
|
|
if (tuple.HasValue)
|
|
{
|
|
(ushort, Vector3) valueOrDefault = tuple.GetValueOrDefault();
|
|
if (valueOrDefault.Item1 == destTerritoryId)
|
|
{
|
|
return valueOrDefault.Item2;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
internal static List<(uint NpcDataId, ushort TerritoryId, Vector3 Position)> ResolveDerivedSources(uint warpRowId, IReadOnlyDictionary<uint, List<uint>> fronterIndex, Func<uint, (ushort TerritoryId, Vector3 Position, bool FestivalOnly)?> getPlacement)
|
|
{
|
|
List<(uint, ushort, Vector3)> list = new List<(uint, ushort, Vector3)>();
|
|
if (!fronterIndex.TryGetValue(warpRowId, out List<uint> value))
|
|
{
|
|
return list;
|
|
}
|
|
foreach (uint item in value.Order())
|
|
{
|
|
(ushort, Vector3, bool)? tuple = getPlacement(item);
|
|
if (tuple.HasValue)
|
|
{
|
|
(ushort, Vector3, bool) valueOrDefault = tuple.GetValueOrDefault();
|
|
if (!valueOrDefault.Item3)
|
|
{
|
|
list.Add((item, valueOrDefault.Item1, valueOrDefault.Item2));
|
|
}
|
|
}
|
|
}
|
|
return list;
|
|
}
|
|
|
|
private static void LoadWarpOverridesFrom(string path, List<WarpDestinationEntry> target, HashSet<uint> skippedIds)
|
|
{
|
|
if (!File.Exists(path))
|
|
{
|
|
return;
|
|
}
|
|
WarpDestinationOverrides warpDestinationOverrides = JsonSerializer.Deserialize<WarpDestinationOverrides>(File.ReadAllText(path));
|
|
if (warpDestinationOverrides == null)
|
|
{
|
|
return;
|
|
}
|
|
foreach (uint skippedWarpId in warpDestinationOverrides.SkippedWarpIds)
|
|
{
|
|
skippedIds.Add(skippedWarpId);
|
|
}
|
|
foreach (var (_, item) in warpDestinationOverrides.Destinations)
|
|
{
|
|
target.Add(item);
|
|
}
|
|
}
|
|
}
|