using System.Collections.Generic; using System.Linq; using System.Numerics; using Microsoft.Extensions.Logging; using SmartNav.Model.Navigation; namespace SmartNav.Data; internal static class ZoneBoundaryDerivation { public static List Derive(IReadOnlyList exitRanges, IReadOnlyDictionary<(ushort TerritoryId, uint InstanceId), PopRangeEntry> popRangeIndex, ILogger logger) { List list = new List(); HashSet<(ushort, uint)> hashSet = new HashSet<(ushort, uint)>(); List list2 = new List(); foreach (ExitRangeEntry exitRange in exitRanges) { if (hashSet.Contains((exitRange.SourceTerritoryId, exitRange.InstanceId)) || exitRange.DestTerritoryId == 0) { continue; } Vector3? vector = null; if (popRangeIndex.TryGetValue((exitRange.DestTerritoryId, exitRange.DestInstanceId), out PopRangeEntry value)) { vector = value.Position; } ExitRangeEntry exitRangeEntry = null; foreach (ExitRangeEntry exitRange2 in exitRanges) { if (exitRange2.SourceTerritoryId == exitRange.DestTerritoryId && exitRange2.DestTerritoryId == exitRange.SourceTerritoryId && !hashSet.Contains((exitRange2.SourceTerritoryId, exitRange2.InstanceId)) && ((exitRange2.DestInstanceId != 0 && exitRange2.DestInstanceId == exitRange.ReturnInstanceId) || (exitRange.DestInstanceId != 0 && exitRange.DestInstanceId == exitRange2.ReturnInstanceId) || (exitRange2.ReturnInstanceId != 0 && exitRange2.ReturnInstanceId == exitRange.InstanceId))) { exitRangeEntry = exitRange2; break; } } if (exitRangeEntry == null) { float num = float.MaxValue; ExitRangeEntry exitRangeEntry2 = null; foreach (ExitRangeEntry exitRange3 in exitRanges) { if (exitRange3.SourceTerritoryId != exitRange.DestTerritoryId || exitRange3.DestTerritoryId != exitRange.SourceTerritoryId || hashSet.Contains((exitRange3.SourceTerritoryId, exitRange3.InstanceId))) { continue; } if (popRangeIndex.TryGetValue((exitRange.SourceTerritoryId, exitRange3.DestInstanceId), out PopRangeEntry value2)) { float num2 = Vector3.Distance(exitRange.SourcePosition, value2.Position); if (num2 < num) { num = num2; exitRangeEntry = exitRange3; } } else if ((object)exitRangeEntry2 == null) { exitRangeEntry2 = exitRange3; } } if ((object)exitRangeEntry == null) { exitRangeEntry = exitRangeEntry2; } } Vector3? vector2 = (((object)exitRangeEntry != null) ? new Vector3?(exitRangeEntry.SourcePosition) : vector); if (!vector2.HasValue) { list2.Add(exitRange); continue; } list.Add(new ZoneBoundary { Id = $"lgb-{exitRange.SourceTerritoryId}-{exitRange.DestTerritoryId}-{exitRange.InstanceId}", Side1 = new BoundarySide { TerritoryId = exitRange.SourceTerritoryId, Position = exitRange.SourcePosition, Trigger = TriggerOf(exitRange) }, Side2 = new BoundarySide { TerritoryId = exitRange.DestTerritoryId, Position = vector2.Value, Trigger = ((exitRangeEntry == null) ? null : TriggerOf(exitRangeEntry)) }, OneWay = (exitRangeEntry == null) }); hashSet.Add((exitRange.SourceTerritoryId, exitRange.InstanceId)); if (exitRangeEntry != null) { hashSet.Add((exitRangeEntry.SourceTerritoryId, exitRangeEntry.InstanceId)); } } HashSet<(ushort, ushort)> hashSet2 = new HashSet<(ushort, ushort)>(); foreach (ZoneBoundary item in list) { hashSet2.Add((item.Side1.TerritoryId, item.Side2.TerritoryId)); hashSet2.Add((item.Side2.TerritoryId, item.Side1.TerritoryId)); } foreach (ExitRangeEntry item2 in list2) { if (hashSet2.Contains((item2.SourceTerritoryId, item2.DestTerritoryId))) { logger.LogDebug("Skipped redundant unresolvable ExitRange {InstanceId} in territory {SrcTerritory} -> {DestTerritory}", item2.InstanceId, item2.SourceTerritoryId, item2.DestTerritoryId); } else { logger.LogWarning("ExitRange {InstanceId} in territory {SrcTerritory} -> {DestTerritory}: could not resolve destination position (DestInstanceId={DestId})", item2.InstanceId, item2.SourceTerritoryId, item2.DestTerritoryId, item2.DestInstanceId); } } return list; } public static void ApplyOverrides(List boundaries, IReadOnlyCollection<(ushort TerritoryA, ushort TerritoryB)> flyingPairs, IReadOnlyList positionOverrides, ILogger logger) { HashSet<(ushort, ushort)> hashSet = new HashSet<(ushort, ushort)>(flyingPairs); foreach (ZoneBoundary boundary in boundaries) { if (hashSet.Contains(PairKey(boundary.Side1.TerritoryId, boundary.Side2.TerritoryId))) { boundary.RequiresFlying = true; } } Dictionary<(ushort, ushort), List> dictionary = (from b in boundaries group b by PairKey(b.Side1.TerritoryId, b.Side2.TerritoryId)).ToDictionary((IGrouping<(ushort, ushort), ZoneBoundary> g) => g.Key, (IGrouping<(ushort, ushort), ZoneBoundary> g) => g.ToList()); int num = 0; foreach (IGrouping<(ushort, ushort), ZoneBoundaryPositionOverride> item in from o in positionOverrides group o by (TerritoryA: o.TerritoryA, TerritoryB: o.TerritoryB)) { List list = item.ToList(); if (!dictionary.TryGetValue(item.Key, out var value)) { num += list.Count; logger.LogWarning("No derived boundary for override pair {TerritoryA}<->{TerritoryB} ({Count} overrides) - game data drift?", item.Key.Item1, item.Key.Item2, list.Count); continue; } List<(float, int, int)> list2 = new List<(float, int, int)>(); for (int num2 = 0; num2 < value.Count; num2++) { for (int num3 = 0; num3 < list.Count; num3++) { list2.Add((OverrideDistance(value[num2], list[num3]), num2, num3)); } } bool[] array = new bool[value.Count]; bool[] array2 = new bool[list.Count]; foreach (var item2 in list2.OrderBy<(float, int, int), float>(((float Distance, int GateIdx, int OverrideIdx) c) => c.Distance)) { if (!array[item2.Item2] && !array2[item2.Item3]) { array[item2.Item2] = true; array2[item2.Item3] = true; ZoneBoundary zoneBoundary = value[item2.Item2]; ZoneBoundaryPositionOverride zoneBoundaryPositionOverride = list[item2.Item3]; bool flag = zoneBoundary.Side1.TerritoryId == zoneBoundaryPositionOverride.TerritoryA; zoneBoundary.Side1.Position = (flag ? zoneBoundaryPositionOverride.PositionA : zoneBoundaryPositionOverride.PositionB); zoneBoundary.Side2.Position = (flag ? zoneBoundaryPositionOverride.PositionB : zoneBoundaryPositionOverride.PositionA); } } for (int num4 = 0; num4 < list.Count; num4++) { if (!array2[num4]) { num++; } } } if (num > 0) { logger.LogWarning("{Count} position overrides had no gate to attach to", num); } static (ushort, ushort) PairKey(ushort a, ushort b) { if (a > b) { return (b, a); } return (a, b); } } private static TriggerBox? TriggerOf(ExitRangeEntry exit) { if (!(exit.HalfExtents == Vector3.Zero)) { return new TriggerBox(exit.SourcePosition, exit.Rotation, exit.HalfExtents); } return null; } private static float OverrideDistance(ZoneBoundary gate, ZoneBoundaryPositionOverride ovr) { if (gate.Side1.TerritoryId != ovr.TerritoryA) { return Vector3.Distance(gate.Side1.Position, ovr.PositionB) + Vector3.Distance(gate.Side2.Position, ovr.PositionA); } return Vector3.Distance(gate.Side1.Position, ovr.PositionA) + Vector3.Distance(gate.Side2.Position, ovr.PositionB); } }