Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Dungeon Guide v0.1.2
BepInEx/plugins/DungeonGuide/DungeonGuide.dll
Decompiled 2 weeks ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using System.Threading; using System.Threading.Tasks; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using TMPro; using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("DungeonGuide")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.1.2.681")] [assembly: AssemblyInformationalVersion("0.1.2.681-dev.1+6c1a03dc8674c116f621c709a674400ec00a44c7")] [assembly: AssemblyProduct("DungeonGuide")] [assembly: AssemblyTitle("DungeonGuide")] [assembly: AssemblyVersion("0.1.2.0")] namespace DungeonGuide; internal sealed class ChestActions { private readonly GuideText _text; private readonly ChestSelector _selector; private readonly ChestSnapshotReader _reader; private readonly GraphLocator _locator; private readonly SessionLifecycle _lifecycle; private readonly UserNotice _notice; private readonly NoteDialog _nameDialog; public event Action<ChestRecord> DestinationRequested; public ChestActions(ChestSelector selector, ChestSnapshotReader reader, NoteDialog nameDialog, GraphLocator locator, SessionLifecycle lifecycle, UserNotice notice, GuideText text) { _text = text; _selector = selector; _reader = reader; _nameDialog = nameDialog; _locator = locator; _lifecycle = lifecycle; _notice = notice; } public void Observe(DungeonSession session) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) Container[] array = Object.FindObjectsByType<Container>((FindObjectsSortMode)0); foreach (Container val in array) { if (((Bounds)(ref session.Area.Bounds)).Contains(((Component)val).transform.position)) { ChestSnapshot val2 = _reader.Read(val, discovered: false); int num = _locator.Snap(session.Scan, UnityPoints.V(val2.Position), 4f); val2.Discovered = num >= 0 && session.Exploration.Has(session.Scan.Graph.Nodes[num]); session.Chests.Observe(val2); } } } private ChestRecord Actual(DungeonSession session) { Container val = _selector.Select(Player.m_localPlayer); if (!Object.op_Implicit((Object)(object)val)) { _notice.Show(_text.ChestTargetRequired); return null; } return session.Chests.Observe(_reader.Read(val, discovered: true)); } public void Mark(DungeonSession session) { ChestRecord val = Actual(session); if (val != null) { session.Chests.Mark(val); _lifecycle.Save(session); this.DestinationRequested?.Invoke(val); _notice.Show(_text.CollectChest + val.label); } } public ChestRecord Collect(DungeonSession session) { ChestRecord val = Actual(session); if (val == null) { return null; } session.Chests.Collect(val); _lifecycle.Save(session); _notice.Show(_text.ChestCollected + val.label); return val; } public void Name(DungeonSession session, Func<bool> stillActive) { ChestRecord chest = Actual(session); if (chest == null || !Object.op_Implicit((Object)(object)TextInput.instance)) { return; } _nameDialog.Request(chest.label, _text.ChestNotePrompt, delegate(string value) { if (stillActive() && !string.IsNullOrWhiteSpace(value)) { chest.label = value.Trim(); session.Chests.Mark(chest); _lifecycle.Save(session); _notice.Show(_text.ChestSaved + chest.label); } }); } } internal sealed class CommandDispatcher { private readonly GuideText _text; private readonly GuideRuntime _runtime; private readonly MapViewState _view; private readonly NavigationCoordinator _navigator; private readonly ChestActions _chests; private readonly WaypointActions _waypoints; private readonly GuideSettings _settings; private readonly UserNotice _notice; private readonly FloorNavigationPolicy _floorPolicy; public event Action ViewChanged; public event Action FitRequested; public CommandDispatcher(GuideRuntime runtime, MapViewState view, NavigationCoordinator navigator, ChestActions chests, WaypointActions waypoints, GuideSettings settings, UserNotice notice, FloorNavigationPolicy floorPolicy, GuideText text) { _text = text; _runtime = runtime; _view = view; _navigator = navigator; _chests = chests; _waypoints = waypoints; _settings = settings; _notice = notice; _floorPolicy = floorPolicy; } public void Execute(GuideCommand command) { //IL_005b: Unknown result type (might be due to invalid IL or missing references) if (command == GuideCommand.ToggleOverlay) { _view.Overlay = !_view.Overlay; return; } DungeonSession session = _runtime.Session; if (session != null) { ApplyCommand(command, session); if (session.Scan != null && Object.op_Implicit((Object)(object)Player.m_localPlayer)) { _navigator.Recalculate(session, ((Component)Player.m_localPlayer).transform.position); } this.ViewChanged?.Invoke(); } } private void NavigateFloor(DungeonSession session, int delta) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) int num = _floorPolicy.Next(session.Layout, ((Component)Player.m_localPlayer).transform.position.y, delta); if (num < 0 || num >= session.Floors.Count) { _notice.Show((session.HasPartialGeometry && session.Scanning) ? _text.PreparingRoute : _text.NoAdjacentFloor); return; } _view.Follow(); _navigator.Floor(num, session.Floors[num], delta); } private void ApplyCommand(GuideCommand command, DungeonSession session) { if (!_waypoints.TryExecute(command, session) && !ApplyViewCommand(command, session) && !ApplyNavigationCommand(command, session)) { ApplyChestCommand(command, session); } } private bool ApplyViewCommand(GuideCommand command, DungeonSession session) { switch (command) { case GuideCommand.ToggleWorld: _view.Dungeon = !_view.Dungeon; return true; case GuideCommand.ToggleOverview: _view.Overview = !_view.Overview; return true; case GuideCommand.ToggleReveal: _view.RevealAll = !_view.RevealAll; _settings.SaveView(_view); return true; case GuideCommand.FloorUp: _view.Overview = false; _view.ChangeFloor(1, session.Floors.Count); return true; case GuideCommand.FloorDown: _view.Overview = false; _view.ChangeFloor(-1, session.Floors.Count); return true; case GuideCommand.Follow: _view.Overview = false; _view.Follow(); this.FitRequested?.Invoke(); return true; default: return false; } } private bool ApplyNavigationCommand(GuideCommand command, DungeonSession session) { switch (command) { case GuideCommand.NavigateUp: NavigateFloor(session, 1); return true; case GuideCommand.NavigateDown: NavigateFloor(session, -1); return true; case GuideCommand.Explore: _navigator.Explore(); return true; case GuideCommand.Entrance: _navigator.Entrance(); return true; case GuideCommand.Stop: _navigator.Stop(); return true; default: return false; } } private void ApplyChestCommand(GuideCommand command, DungeonSession session) { switch (command) { case GuideCommand.MarkChest: _chests.Mark(session); break; case GuideCommand.NameChest: _chests.Name(session, () => _runtime.Session == session); break; case GuideCommand.CollectChest: CollectChest(session); break; case GuideCommand.NextChest: SelectNextChest(session); break; } } private void CollectChest(DungeonSession session) { ChestRecord val = _chests.Collect(session); if (val != null && _navigator.Chest == val) { _navigator.Stop(); } } private void SelectNextChest(DungeonSession session) { _chests.Observe(session); List<ChestRecord> list = session.Chests.Targets(); if (list.Count == 0) { _notice.Show(_text.NoKnownChest); return; } int num = list.FindIndex((ChestRecord c) => c == _navigator.Chest); _navigator.ToChest(list[(num + 1) % list.Count]); } } internal sealed class DungeonGeometrySessions : IDisposable { private readonly GuideSettings _settings; private readonly ManualLogSource _log; private readonly DungeonSessionCache _memory = new DungeonSessionCache(); private readonly GeometryCacheRepository _repository; private readonly GeometryCacheWorker _worker = new GeometryCacheWorker(); private readonly GeometryCacheRestore _restore; private readonly GeometryCacheCapture _capture; private readonly RestoredPassageValidation _validation; private readonly List<Task<bool>> _invalidations = new List<Task<bool>>(); private string _world; public event Action Restored; public event Action<DungeonSession> NeedsScan; public event Action PermissionsChanged; public event Action WorldChanged; public DungeonGeometrySessions(GuideSettings settings, GeometryCacheRepository repository, ManualLogSource log, RestoredPassageValidation validation) { _settings = settings; _repository = repository; _log = log; _validation = validation; _restore = new GeometryCacheRestore(repository, _worker); _capture = new GeometryCacheCapture(repository, _worker, log); } public DungeonSession Resume(DungeonArea area, string world) { SelectWorld(world); string key = GeometryCacheKey.Create(area, world, _settings); DungeonSession dungeonSession = _memory.Find(key); if (dungeonSession == null) { return null; } if (BindArea(dungeonSession, area) && dungeonSession.Scan != null) { _capture.Enqueue(key, CachedGeometryBinding.Capture(dungeonSession)); } _validation.Begin(dungeonSession); _log.LogInfo((object)("Dungeon geometry reused from memory: " + area.Id)); return dungeonSession; } private static bool BindArea(DungeonSession session, DungeonArea area) { //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) if (!area.ObservedEntrance && session.Area.ObservedEntrance) { area.Entrance = session.Area.Entrance; area.ObservedEntrance = true; } bool result = area.ObservedEntrance && (!session.Area.ObservedEntrance || Point.Distance(area.Entrance.Value, session.Area.Entrance.Value) > 0.01f); session.Area = area; return result; } public bool RefreshArea(DungeonSession session, DungeonArea area) { string text = GeometryCacheKey.Create(area, session.Data.world, _settings); bool num = text != session.GeometryCacheKey; BindArea(session, area); if (num) { _memory.Remove(session.GeometryCacheKey); _capture.Cancel(session.GeometryCacheKey); _validation.Cancel(); session.GeometryCacheKey = text; session.GeometryNeedsScan = true; session.HasPartialGeometry = true; session.NavigationReady = false; Action action = this.PermissionsChanged; if (action == null) { return num; } action(); } return num; } public void RefreshPermissions(DungeonSession session) { if (session?.Scan != null && !session.HasPartialGeometry && !session.GeometryNeedsScan && !session.Scanning) { _validation.Begin(session); this.PermissionsChanged?.Invoke(); } } public void Open(DungeonSession session) { session.GeometryCacheKey = GeometryCacheKey.Create(session.Area, session.Data.world, _settings); DungeonSession dungeonSession = _memory.Find(session.GeometryCacheKey); if (dungeonSession != null && dungeonSession != session && dungeonSession.Scan != null) { CachedGeometryBinding.Restore(session, CachedGeometryBinding.Capture(dungeonSession), dungeonSession.Scan); _memory.Remember(session.GeometryCacheKey, session); _validation.Begin(session); this.Restored?.Invoke(); if (session.GeometryNeedsScan) { this.NeedsScan?.Invoke(session); } } else { _restore.Begin(session); } } public void Published(DungeonSession session) { if (session?.Scan != null && session.Scan.Graph.Nodes.Count != 0 && session.Floors.Count != 0) { _memory.Remember(session.GeometryCacheKey, session); if (session.HasPartialGeometry) { _capture.Enqueue(session.GeometryCacheKey, CachedGeometryBinding.Capture(session)); } if (!session.HasPartialGeometry) { _validation.Begin(session); } } } public Task<bool> StorePrepared(DungeonSession session, byte[] payload, CancellationToken cancellation) { string key = session.GeometryCacheKey; _capture.Cancel(key); return _worker.Enqueue(() => _repository.Save(key, payload, cancellation)); } public void Leave(DungeonSession session) { _restore.Cancel(); _validation.Cancel(); if (session.Scan != null && session.Scan.Graph.Nodes.Count > 0 && session.Data.world == _world) { _memory.Remember(session.GeometryCacheKey, session); } } public void Invalidate(DungeonSession session, bool mutation = false) { if (mutation) { session.GeometryDirty = true; session.GeometryMutationVersion++; } session.GeometryNeedsScan = true; session.NavigationReady = false; _validation.Cancel(); if (session.Scan != null) { this.PermissionsChanged?.Invoke(); } string key = session.GeometryCacheKey; _capture.Cancel(key); _invalidations.Add(_worker.Enqueue(delegate { _repository.Invalidate(key); return true; })); } public void Tick(DungeonSession current, string world) { SelectWorld(world); ObserveInvalidations(); CompleteRestore(current); _validation.Tick(current); _capture.Tick(); } private void SelectWorld(string world) { if (_world != world) { _world = world; _restore.Cancel(); _validation.Cancel(); _memory.Clear(); this.WorldChanged?.Invoke(); } } private void CompleteRestore(DungeonSession session) { if (!_restore.TryTake(session, out var geometry, out var scan, out var error)) { return; } if (error != null) { _log.LogWarning((object)("Cannot restore dungeon geometry; scanning automatically: " + error)); } if (geometry == null || scan.Graph.Nodes.Count == 0) { this.NeedsScan?.Invoke(session); return; } if (geometry.Complete || session.Scan == null) { CachedGeometryBinding.Restore(session, geometry, scan); } _validation.Begin(session); _memory.Remember(session.GeometryCacheKey, session); _log.LogInfo((object)("Dungeon geometry restored from disk: " + session.Area.Id + ", nodes=" + scan.Graph.Nodes.Count)); this.Restored?.Invoke(); if (session.GeometryNeedsScan) { this.NeedsScan?.Invoke(session); } } private void ObserveInvalidations() { for (int num = _invalidations.Count - 1; num >= 0; num--) { Task<bool> task = _invalidations[num]; if (task.IsCompleted) { if (task.IsFaulted) { _log.LogWarning((object)("Cannot invalidate dungeon geometry cache: " + task.Exception.GetBaseException())); } _invalidations.RemoveAt(num); } } } public void Dispose() { _restore.Cancel(); _validation.Cancel(); _capture.Dispose(); _memory.Clear(); foreach (Task<bool> invalidation in _invalidations) { invalidation.ContinueWith(delegate(Task<bool> failed) { _ = failed.Exception; }, CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.Default); } _invalidations.Clear(); } } internal sealed class DungeonSession { public DungeonArea Area; public SavedRun Data; public ScanResult Scan; public readonly Exploration Exploration = new Exploration(); public readonly Exploration OverviewExploration = new Exploration(); public readonly Exploration VisitedRoomCoverage = new Exploration(); public ChestRegistry Chests; public List<float> Floors = new List<float>(); public FloorLayout Layout = new FloorLayout(); public bool Scanning; public bool HasPartialGeometry; public bool NavigationReady; public bool ReadOnly; public int GeometryRevision; public bool GeometryNeedsScan; public string GeometryCacheKey { get; set; } public bool GeometryDirty { get; set; } public int GeometryMutationVersion { get; set; } } internal sealed class DungeonSessionCache { private const int Capacity = 3; private readonly Dictionary<string, DungeonSession> _sessions = new Dictionary<string, DungeonSession>(); private readonly LinkedList<string> _recent = new LinkedList<string>(); public DungeonSession Find(string key) { if (!_sessions.TryGetValue(key, out var value)) { return null; } _recent.Remove(key); _recent.AddLast(key); return value; } public void Remember(string key, DungeonSession session) { _sessions[key] = session; _recent.Remove(key); _recent.AddLast(key); while (_sessions.Count > 3) { string value = _recent.First.Value; _recent.RemoveFirst(); _sessions.Remove(value); } } public void Remove(string key) { _sessions.Remove(key); _recent.Remove(key); } public void Clear() { _sessions.Clear(); _recent.Clear(); } } internal enum GuideCommand { ToggleWorld, ToggleReveal, ToggleOverlay, ToggleOverview, FloorUp, FloorDown, Follow, NavigateUp, NavigateDown, Explore, Entrance, Stop, MarkChest, NameChest, CollectChest, NextChest, MarkLocation, NextLocation, RenameLocation, RemoveLocation } internal sealed class GuideGameLoop : IDisposable { private readonly GuideRuntime _runtime; private readonly MapViewState _view; private readonly InputRouter _input; private readonly ChestActions _chests; private readonly NavigationCoordinator _navigator; private readonly MapTextureRenderer _texture; private readonly NativeMapPresenter _maps; private readonly OverlayPresenter _overlay; private readonly NavigationHud _hud; private float _nextObserve; private float _nextRoute; private bool _disposed; private readonly DynamicRouteValidator _validator; private string _blockedHint; private IReadOnlyList<float> _knownFloors = Array.Empty<float>(); public GuideGameLoop(GuideRuntime runtime, MapViewState view, InputRouter input, ChestActions chests, NavigationCoordinator navigator, MapTextureRenderer texture, NativeMapPresenter maps, OverlayPresenter overlay, NavigationHud hud, DynamicRouteValidator validator) { _runtime = runtime; _view = view; _input = input; _chests = chests; _navigator = navigator; _texture = texture; _maps = maps; _overlay = overlay; _hud = hud; _validator = validator; runtime.Entered += Enter; runtime.Left += navigator.Stop; runtime.ScanCompleted += Scanned; runtime.WorldChanged += texture.ClearWorld; } private void Enter() { _knownFloors = Array.Empty<float>(); _view.Enter(); _navigator.Stop(); _maps.Center(); _nextObserve = (_nextRoute = 0f); } private void Scanned() { List<float> floors = _runtime.Session.Floors; _view.RemapFloors(_knownFloors, (IReadOnlyList<float>)floors); _knownFloors = floors; _navigator.InvalidateRoute(); _nextObserve = (_nextRoute = 0f); } public void RefreshView() { _nextRoute = 0f; } public void Tick() { if (!_disposed) { _runtime.Tick(); _input.Tick(_view.Dungeon && (_maps.IsLargeOpen || _view.Overlay)); DungeonSession session = _runtime.Session; Player localPlayer = Player.m_localPlayer; TrackPhysicalFloor(session, localPlayer); _texture.Tick(session, _view); if (session != null && Object.op_Implicit((Object)(object)localPlayer) && session.Scan != null) { ObserveChests(session); UpdateRoute(session, localPlayer); _navigator.AdvanceFallback(session, localPlayer); RenderMap(session, localPlayer); } } } private void TrackPhysicalFloor(DungeonSession session, Player player) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) if (session?.Scan != null && Object.op_Implicit((Object)(object)player)) { _view.Track((IReadOnlyList<float>)session.Floors, ((Component)player).transform.position.y); } } public void LateTick() { if (!_disposed) { _maps.Tick(_runtime.Session); _overlay.Tick(_runtime.Session, _view); } } public void Draw() { if (!_disposed) { _hud.Draw(_runtime.Session, _view, _navigator, _blockedHint); } } public void Dispose() { if (!_disposed) { _disposed = true; _navigator.Stop(); _runtime.Dispose(); _runtime.Entered -= Enter; _runtime.Left -= _navigator.Stop; _runtime.ScanCompleted -= Scanned; _runtime.WorldChanged -= _texture.ClearWorld; _maps.Dispose(); _overlay.Dispose(); _texture.Dispose(); } } private void ObserveChests(DungeonSession session) { if (Time.unscaledTime >= _nextObserve) { _nextObserve = Time.unscaledTime + 3f; _chests.Observe(session); } } private void UpdateRoute(DungeonSession session, Player player) { //IL_004f: Unknown result type (might be due to invalid IL or missing references) if (Time.unscaledTime >= _nextRoute) { _nextRoute = Time.unscaledTime + 1f; _blockedHint = (session.NavigationReady ? _validator.Inspect(session, _navigator, player) : null); _navigator.Recalculate(session, ((Component)player).transform.position); } } private void RenderMap(DungeonSession session, Player player) { if (_view.Dungeon) { _texture.Render(session, _view, _navigator, player); } } } internal sealed class GuideRuntime : IDisposable { private readonly SessionScanController _scans; private readonly DungeonLocator _locator; private readonly DungeonActivationPolicy _activation; private readonly SessionLifecycle _lifecycle; private readonly ExplorationRecorder _exploration; private readonly TraversalRecorder _traversals; private readonly DungeonGeometrySessions _geometry; private readonly DoorGeometryMonitor _doorGeometry; private float _nextDetect; private bool _entrancePending; private readonly RoomVisitRecorder _rooms = new RoomVisitRecorder(); private readonly PassageEquipmentWatch _equipment = new PassageEquipmentWatch(); private float _nextEquipment; private float _nextSave; private float _geometryRefresh = float.PositiveInfinity; public DungeonSession Session { get; private set; } private string World => GameWorld.Id?.ToString(CultureInfo.InvariantCulture); public event Action Entered; public event Action Left; public event Action ScanCompleted; public event Action WorldChanged; public GuideRuntime(MonoBehaviour host, GuideSettings settings, DungeonLocator locator, DungeonActivationPolicy activation, GeometryScanner scanner, FloorDetector floors, SessionLifecycle lifecycle, ExplorationRecorder exploration, TraversalRecorder traversals, ManualLogSource log, UserNotice notice, DungeonGeometrySessions geometry) { _locator = locator; _activation = activation; _lifecycle = lifecycle; _exploration = exploration; _traversals = traversals; _geometry = geometry; _doorGeometry = new DoorGeometryMonitor(GeometryChanged); _geometry.Restored += delegate { this.ScanCompleted?.Invoke(); }; _geometry.PermissionsChanged += delegate { this.ScanCompleted?.Invoke(); }; _geometry.WorldChanged += delegate { this.WorldChanged?.Invoke(); }; _geometry.NeedsScan += ScanMissing; _scans = new SessionScanController(host, settings, scanner, floors, log, () => Session, _geometry.StorePrepared); _scans.ScanCompleted += GeometryPublished; _scans.FullScanStarted += BeginFullScan; } public void Tick() { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; string world = World; _geometry.Tick(Session, world); if (!Object.op_Implicit((Object)(object)localPlayer) || world == null) { Close(); } else if (RetainSession(((Component)localPlayer).transform.position)) { DetectIfDue(); if (Session != null) { _rooms.Track(Session, ((Component)localPlayer).transform.position); RefreshEquipmentIfDue(localPlayer); _doorGeometry.Tick(Time.unscaledTime); RefreshGeometryIfDue(); TrackMovement(localPlayer); SaveIfDue(); } } } private void Detect() { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if (!Object.op_Implicit((Object)(object)localPlayer)) { return; } DungeonArea dungeonArea = _locator.Locate(((Component)localPlayer).transform.position); if (dungeonArea == null) { if (Session != null && (!Session.Area.IsManual || !CanRetainSession(((Component)localPlayer).transform.position))) { Close(); } } else { ActivateArea(dungeonArea, localPlayer); } } private bool CanRetainSession(Vector3 position) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) return _activation.CanRetainSession(Session.Area.IsManual, ((Bounds)(ref Session.Area.Bounds)).Contains(position), Character.InInterior(position), false); } public void DoorChanged() { if (Session != null) { _geometry.RefreshPermissions(Session); } } public void DoorObserved(Door door) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) if (Session != null && Object.op_Implicit((Object)(object)door) && ((Bounds)(ref Session.Area.Bounds)).Contains(((Component)door).transform.position)) { DoorChanged(); _doorGeometry.Observe(door, Time.unscaledTime); } } public void GeometryChanged() { if (Session != null) { _scans.InvalidateCacheWrite(); _geometry.Invalidate(Session, mutation: true); _geometryRefresh = Time.unscaledTime + 1f; } } private void StartScan() { if (Session == null || Session.Scanning) { return; } if (_geometry.RefreshArea(Session, Session.Area)) { _geometry.Open(Session); return; } if (!Session.GeometryNeedsScan) { _geometry.Invalidate(Session); } _scans.Start(Session); } private void BeginFullScan() { _geometry.RefreshArea(Session, Session.Area); _geometryRefresh = float.PositiveInfinity; } private void ScanMissing(DungeonSession session) { if (Session == session) { StartScan(); } } private void GeometryPublished() { if (!Session.HasPartialGeometry && !float.IsPositiveInfinity(_geometryRefresh)) { Session.GeometryNeedsScan = true; } _geometry.Published(Session); this.ScanCompleted?.Invoke(); } private void Close() { if (Session != null) { _scans.CancelAnalysis(); _lifecycle.Save(Session); _scans.Stop(); Session.Scanning = false; _geometry.Leave(Session); Session = null; _traversals.Reset(); _rooms.Reset(); _equipment.Reset(); _exploration.Reset(); _doorGeometry.Reset(); _geometryRefresh = float.PositiveInfinity; this.Left?.Invoke(); } } public void Dispose() { Close(); _geometry.Dispose(); } private bool RetainSession(Vector3 position) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) bool num = Character.InInterior(position); if (Session != null && (Session.Data.world != World || !CanRetainSession(position))) { Close(); } if (!num && Session == null) { return false; } return true; } public void EntranceObserved() { _entrancePending = true; _nextDetect = 0f; } private void DetectIfDue() { if (Time.unscaledTime >= _nextDetect) { _nextDetect = Time.unscaledTime + (_entrancePending ? 0.25f : 3f); Detect(); if (Session != null) { _entrancePending = false; } } } private void RefreshGeometryIfDue() { if (Time.unscaledTime >= _geometryRefresh) { if (Session.Scanning) { _geometryRefresh = Time.unscaledTime + 0.5f; return; } _geometryRefresh = float.PositiveInfinity; StartScan(); } } private void TrackMovement(Player player) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) if (Session.Scan != null) { _traversals.Tick(Session, player); _exploration.Track(Session.Scan, Session.Exploration, ((Component)player).transform.position, ((Character)player).GetEyePoint(), (Session.Floors.Count == 0) ? (-1) : FloorDetector.Nearest((IReadOnlyList<float>)Session.Floors, ((Component)player).transform.position.y)); } } private void SaveIfDue() { if (Time.unscaledTime >= _nextSave) { _nextSave = Time.unscaledTime + 15f; _lifecycle.Save(Session); } } private void RefreshEquipmentIfDue(Player player) { if (Time.unscaledTime >= _nextEquipment) { _nextEquipment = Time.unscaledTime + 1f; if (_equipment.Changed(player)) { DoorChanged(); } } } private void ActivateArea(DungeonArea area, Player player) { //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) if (Session?.Area.Id == area.Id) { if (_geometry.RefreshArea(Session, area)) { _scans.CancelAnalysis(); _scans.Stop(); Session.Scanning = false; _geometry.Open(Session); } return; } Close(); Session = _geometry.Resume(area, World); if (Session != null) { this.Entered?.Invoke(); this.ScanCompleted?.Invoke(); if (Session.GeometryNeedsScan) { _geometry.Open(Session); } } else { Session = _lifecycle.Open(area, World, UnityPoints.P(((Component)player).transform.position)); this.Entered?.Invoke(); _geometry.Open(Session); } } } internal interface IRunRepository { SavedRun Load(string world, string dungeon, Point start); void Save(SavedRun data); } internal sealed class NavigationCoordinator { private readonly RoutePlanner _planner; private readonly GraphLocator _locator; private readonly FloorNavigationPolicy _floorPolicy; private readonly GrappleFallbackSearch _grapple; private float _destinationHeight; private int _floorDirection; public WaypointRecord Waypoint { get; private set; } public NavigationGoal Goal { get; private set; } public NavigationStatus Status { get; private set; } public ChestRecord Chest { get; private set; } public int DestinationFloor { get; private set; } public int FloorDirection => _floorDirection; public List<int> Route { get; private set; } = new List<int>(); public bool SearchingGrapple => _grapple.Pending; public bool RequiresGrapple { get { if (_grapple.Route.Count > 0) { return Route == _grapple.Route; } return false; } } public Vector3 GrappleAnchor => _grapple.Anchor; public NavigationCoordinator(RoutePlanner planner, GraphLocator locator, FloorNavigationPolicy floorPolicy, GrappleFallbackSearch grapple) { _planner = planner; _locator = locator; _floorPolicy = floorPolicy; _grapple = grapple; } public void Stop() { _grapple.Reset(); Goal = NavigationGoal.None; _floorDirection = 0; Chest = null; Waypoint = null; Route.Clear(); Status = NavigationStatus.None; } public void Explore() { InvalidateRoute(); Goal = NavigationGoal.Explore; Chest = null; Waypoint = null; } public void ToChest(ChestRecord chest) { InvalidateRoute(); Goal = NavigationGoal.Chest; Chest = chest; Waypoint = null; } public void ToWaypoint(WaypointRecord waypoint) { InvalidateRoute(); Goal = NavigationGoal.Waypoint; Chest = null; Waypoint = waypoint; } public void Entrance() { InvalidateRoute(); Goal = NavigationGoal.Entrance; Chest = null; Waypoint = null; } public void Floor(int floor, float height, int direction) { InvalidateRoute(); Goal = NavigationGoal.Floor; DestinationFloor = floor; _destinationHeight = height; _floorDirection = Math.Sign(direction); Chest = null; Waypoint = null; } public void InvalidateRoute() { _grapple.Reset(); Route.Clear(); Status = NavigationStatus.None; } public void Recalculate(DungeonSession session, Vector3 player) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) Route = new List<int>(); Status = NavigationStatus.None; if (Goal != NavigationGoal.None && session.Scan != null && session.NavigationReady) { int num = _locator.Snap(session.Scan, player); if (num < 0) { Status = NavigationStatus.OffGraph; } else if (PrepareFloorDestination(session, num, player)) { FindRoute(session, num); } } } private bool PrepareFloorDestination(DungeonSession session, int start, Vector3 player) { //IL_003d: Unknown result type (might be due to invalid IL or missing references) if (Goal != NavigationGoal.Floor) { return true; } DestinationFloor = _floorPolicy.Resolve(session.Layout, _destinationHeight); if (DestinationFloor < 0) { Status = NavigationStatus.NoRoute; return false; } if (ReachedFloor(session, start, player)) { return AdvanceFloorDestination(session); } return true; } private bool ReachedFloor(DungeonSession session, int start, Vector3 player) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) if (_floorPolicy.IsDestination(session.Scan.Graph, session.Layout, start, DestinationFloor) && Math.Abs(player.y - _destinationHeight) <= 0.75f) { return ((Character)Player.m_localPlayer).IsOnGround(); } return false; } private bool AdvanceFloorDestination(DungeonSession session) { _grapple.Reset(); DestinationFloor = _floorPolicy.Next(session.Layout, _destinationHeight, _floorDirection); if (DestinationFloor < 0) { Status = NavigationStatus.FloorLimit; return false; } _destinationHeight = session.Floors[DestinationFloor]; return true; } private int ResolvePointDestination(DungeonSession session) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) int result = -1; if (Goal == NavigationGoal.Chest) { result = _locator.Snap(session.Scan, UnityPoints.V(Chest.position), 4.5f); } if (Goal == NavigationGoal.Waypoint) { int floor = FloorDetector.Nearest((IReadOnlyList<float>)session.Floors, Waypoint.position.y); result = _locator.Snap(session.Scan, UnityPoints.V(Waypoint.position), 4.5f, floor); } if (Goal == NavigationGoal.Entrance) { result = _locator.Snap(session.Scan, UnityPoints.V((Point)(((??)session.Area.Entrance) ?? session.Data.start)), 4.5f); } return result; } private void FindRoute(DungeonSession session, int start) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Expected O, but got Unknown ScanResult scan = session.Scan; int destination = ResolvePointDestination(session); RoutePolicy val = new RoutePolicy(); Route = _planner.Find(scan.Graph, start, (Predicate<int>)((int n) => IsDestination(session, n, destination)), val); if (Route.Count > 0) { _grapple.Reset(); return; } _grapple.Request(scan, start, (int n) => IsDestination(session, n, destination), val, Player.m_localPlayer); Route = _grapple.Route; if (Route.Count == 0) { Status = ((Goal == NavigationGoal.Explore) ? NavigationStatus.ExplorationExhausted : NavigationStatus.NoRoute); } } public bool IsGrapplePassage(int from, int to) { if (RequiresGrapple) { return _grapple.IsPassage(from, to); } return false; } public void AdvanceFallback(DungeonSession session, Player player) { bool requiresGrapple = RequiresGrapple; _grapple.Advance(session, player); if (requiresGrapple && _grapple.Route.Count == 0) { Route = new List<int>(); Status = NavigationStatus.NoRoute; } if (_grapple.Route.Count > 0) { Route = _grapple.Route; Status = NavigationStatus.None; } } private bool IsDestination(DungeonSession session, int node, int pointDestination) { return Goal switch { NavigationGoal.Explore => !session.Exploration.Has(session.Scan.Graph.Nodes[node]), NavigationGoal.Floor => _floorPolicy.IsDestination(session.Scan.Graph, session.Layout, node, DestinationFloor), _ => node == pointDestination, }; } } internal enum NavigationGoal { None, Explore, Chest, Entrance, Floor, Waypoint } internal enum NavigationStatus { None, OffGraph, NoRoute, ExplorationExhausted, FloorLimit } internal sealed class SessionLifecycle { private readonly GuideText _text; private readonly IRunRepository _repository; private readonly ManualLogSource _log; private readonly UserNotice _notice; public SessionLifecycle(IRunRepository repository, ManualLogSource log, UserNotice notice, GuideText text) { _text = text; _repository = repository; _log = log; _notice = notice; } public DungeonSession Open(DungeonArea area, string world, Point start) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Expected O, but got Unknown //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Expected O, but got Unknown DungeonSession dungeonSession = new DungeonSession { Area = area }; try { dungeonSession.Data = _repository.Load(world, area.Id, start); } catch (Exception ex) { dungeonSession.ReadOnly = true; dungeonSession.Data = new SavedRun { world = world, dungeon = area.Id, start = start }; _log.LogError((object)("Cannot load run; persistence disabled for this run: " + ex)); _notice.Show(_text.SaveReadFailed); } dungeonSession.Exploration.Load((IEnumerable<string>)dungeonSession.Data.visited); dungeonSession.Chests = new ChestRegistry(dungeonSession.Data.chests, (Func<string>)(() => _text.SelectedLanguage)); return dungeonSession; } public bool Save(DungeonSession session) { if (session == null || session.ReadOnly) { return false; } session.Data.visited = session.Exploration.Export(); try { _repository.Save(session.Data); return true; } catch (Exception ex) { _log.LogError((object)("Cannot save guide run: " + ex)); _notice.Show(_text.SaveWriteFailed); return false; } } } internal sealed class SessionScanController { private readonly MonoBehaviour _host; private readonly GuideSettings _settings; private readonly GeometryScanner _scanner; private readonly FloorDetector _floors; private readonly ManualLogSource _log; private readonly Func<DungeonSession> _current; private readonly Func<DungeonSession, byte[], CancellationToken, Task<bool>> _persist; private CancellationTokenSource _cacheWrite; private int _sampleVersion; private Coroutine _scanJob; private FloorAnalysisJob _floorAnalysis; private float _scanStarted; private float _nextProgress; private bool _preparingPreview; private DungeonSession Session => _current(); public event Action ScanCompleted; public event Action FullScanStarted; public SessionScanController(MonoBehaviour host, GuideSettings settings, GeometryScanner scanner, FloorDetector floors, ManualLogSource log, Func<DungeonSession> current, Func<DungeonSession, byte[], CancellationToken, Task<bool>> persist) { _host = host; _settings = settings; _scanner = scanner; _floors = floors; _log = log; _current = current; _persist = persist; } public void Start(DungeonSession session) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) if (session != null && !session.Scanning) { Bounds[] rooms = session.Area.Rooms; for (int i = 0; i < rooms.Length; i++) { Bounds val = rooms[i]; _log.LogInfo((object)("Dungeon room bounds: size=" + ((object)((Bounds)(ref val)).size/*cast due to .constrained prefix*/).ToString() + ", center=" + ((object)((Bounds)(ref val)).center/*cast due to .constrained prefix*/).ToString())); } _cacheWrite?.Dispose(); _cacheWrite = new CancellationTokenSource(); session.Scanning = true; _scanStarted = Time.realtimeSinceStartup; _nextProgress = _scanStarted; DungeonArea preview = ScanPreviewArea.Create(session); _scanJob = _host.StartCoroutine(Guard(BuildScan(session, preview), session)); } } public void CancelAnalysis() { InvalidateCacheWrite(); _cacheWrite?.Dispose(); _cacheWrite = null; _scanner.CancelAnalysis(); FloorAnalysisJob floorAnalysis = _floorAnalysis; if (floorAnalysis != null) { floorAnalysis.Dispose(); } _floorAnalysis = null; } public void InvalidateCacheWrite() { _cacheWrite?.Cancel(); } public void Stop() { if (_scanJob != null) { _host.StopCoroutine(_scanJob); } _scanJob = null; } private IEnumerator BuildScan(DungeonSession session, DungeonArea preview) { if (preview != null) { foreach (object item in CoroutinePhase.Forward(BuildPass(session, preview, preview: true))) { yield return item; } } if (Session != session) { yield break; } foreach (object item2 in CoroutinePhase.Forward(BuildPass(session, session.Area, preview: false))) { yield return item2; } } private IEnumerator BuildPass(DungeonSession session, DungeonArea area, bool preview) { BeginPass(session, preview); ScanResult result = null; foreach (object item in CoroutinePhase.Forward(SampleGeometry(area, delegate(ScanResult completed) { result = completed; }, preview))) { yield return item; } if (Session != session || (preview && result.Graph.Nodes.Count == 0)) { yield break; } if (preview) { PublishPreview(session, result); yield break; } FloorLayout layout = null; foreach (object item2 in CoroutinePhase.Forward(AnalyzeFloors(session, result, delegate(FloorLayout completed) { layout = completed; }))) { yield return item2; } if (Session == session && layout != null) { PublishScan(session, result, layout, preview); } } private void BeginPass(DungeonSession session, bool preview) { if (!preview) { _cacheWrite?.Dispose(); _cacheWrite = new CancellationTokenSource(); _sampleVersion = session.GeometryMutationVersion; this.FullScanStarted?.Invoke(); } _preparingPreview = preview; } private void PublishPreview(DungeonSession session, ScanResult result) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; FloorLayout val = PreviewFloorClassifier.Classify(result.Graph, Object.op_Implicit((Object)(object)localPlayer) ? UnityPoints.P(((Component)localPlayer).transform.position) : session.Data.start); if (val.Levels.Count > 0) { PublishScan(session, result, val, preview: true); } } private void PublishScan(DungeonSession session, ScanResult result, FloorLayout layout, bool preview) { session.OverviewExploration.Load((IEnumerable<string>)Array.Empty<string>()); session.VisitedRoomCoverage.Load((IEnumerable<string>)Array.Empty<string>()); session.Scan = result; session.Layout = layout; session.Floors = layout.Levels; session.GeometryRevision++; session.HasPartialGeometry = preview; session.GeometryDirty = !preview && _sampleVersion != session.GeometryMutationVersion; session.GeometryNeedsScan = preview || session.GeometryDirty; session.NavigationReady = !preview; session.Scanning = preview; _log.LogInfo((object)((preview ? "Scan preview ready: " : "Scan complete: ") + session.Area.Id + ", nodes=" + result.Graph.Nodes.Count + ", floors=" + session.Floors.Count + ", incomplete=" + result.Truncated + ", elapsedSeconds=" + (Time.realtimeSinceStartup - _scanStarted).ToString("F1", CultureInfo.InvariantCulture))); _log.LogInfo((object)("Floor navigation landings=" + layout.LandingNodes.Count)); this.ScanCompleted?.Invoke(); } private IEnumerator Guard(IEnumerator task, DungeonSession session) { try { while (MoveNextSafely(task)) { ReportProgress(); yield return task.Current; } } finally { SessionScanController sessionScanController = this; (task as IDisposable)?.Dispose(); if (sessionScanController.Session == session) { session.Scanning = false; sessionScanController._scanJob = null; } } } private IEnumerator SampleGeometry(DungeonArea area, Action<ScanResult> completed, bool preview) { return _scanner.Scan(area, Mathf.Clamp(_settings.Spacing.Value, 0.75f, 3f), Mathf.Clamp(_settings.Columns.Value, 1000, 120000), Mathf.Clamp(_settings.Budget.Value, 1, 8192), Mathf.Clamp(_settings.MillisecondsPerFrame.Value, 0.25f, 4f), completed, preview); } private IEnumerator AnalyzeFloors(DungeonSession session, ScanResult result, Action<FloorLayout> completed) { FloorDetector detector = _floors; GeometryCachePreparation preparation = new GeometryCachePreparation(session.GeometryCacheKey, result.Spacing, result.Truncated, session.Area.ObservedEntrance ? session.Area.Entrance : ((Point?)null)); FloorAnalysisJob analysis = new FloorAnalysisJob((Func<CancellationToken, FloorLayout>)((CancellationToken token) => preparation.Analyze(detector, result.Graph, token))); try { _floorAnalysis = analysis; try { FloorLayout layout = null; foreach (object item in CoroutinePhase.Forward(AwaitAnalysis(analysis, session, delegate(FloorLayout value) { layout = value; }))) { yield return item; } if (Session != session || layout == null) { yield break; } foreach (object item2 in CoroutinePhase.Forward(PersistPrepared(session, preparation))) { yield return item2; } if (Session == session) { completed(layout); } } finally { SessionScanController sessionScanController = this; if (sessionScanController._floorAnalysis == analysis) { sessionScanController._floorAnalysis = null; } } } finally { ((IDisposable)analysis)?.Dispose(); } } private IEnumerator AwaitAnalysis(FloorAnalysisJob analysis, DungeonSession session, Action<FloorLayout> completed) { FloorLayout obj = default(FloorLayout); while (!analysis.TryComplete(ref obj)) { yield return null; if (Session != session) { yield break; } } completed(obj); } private IEnumerator PersistPrepared(DungeonSession session, GeometryCachePreparation preparation) { if (preparation.Error != null) { _log.LogWarning((object)("Cannot prepare persistent geometry; keeping the live map: " + preparation.Error)); yield break; } CancellationToken arg = _cacheWrite?.Token ?? CancellationToken.None; if (preparation.Payload == null || arg.IsCancellationRequested) { yield break; } Task<bool> task = _persist(session, preparation.Payload, arg); task.ContinueWith(delegate(Task<bool> failed) { _ = failed.Exception; }, CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.Default); while (!task.IsCompleted) { yield return null; if (Session != session) { yield break; } } if (task.IsFaulted && !(task.Exception.GetBaseException() is OperationCanceledException)) { _log.LogWarning((object)("Cannot persist completed geometry; keeping the live map: " + task.Exception.GetBaseException())); } else if (!task.IsFaulted && !task.IsCanceled && !task.Result) { _log.LogWarning((object)"Preserving a newer geometry cache; the live map remains available."); } } private bool MoveNextSafely(IEnumerator task) { try { _scanner.BeginSlice(); return task.MoveNext(); } catch (Exception ex) { _log.LogError((object)("Geometry scan failed: " + ex)); return false; } finally { _scanner.EndSlice(); } } private void ReportProgress() { float realtimeSinceStartup = Time.realtimeSinceStartup; if (!(realtimeSinceStartup < _nextProgress)) { _nextProgress = realtimeSinceStartup + 5f; string text = ((_floorAnalysis == null) ? _scanner.Phase.ToString() : "FloorAnalysisJob"); _log.LogInfo((object)("Scan progress: preview=" + _preparingPreview + ", phase=" + text + ", columns=" + _scanner.ProcessedColumns + ", nodes=" + _scanner.SampledNodes + ", steps=" + _scanner.WorkSteps + ", elapsedSeconds=" + (realtimeSinceStartup - _scanStarted).ToString("F1", CultureInfo.InvariantCulture))); } } } internal sealed class WaypointActions { private readonly GuideRuntime _runtime; private readonly SessionLifecycle _lifecycle; private readonly NavigationCoordinator _navigator; private readonly NoteDialog _dialog; private readonly UserNotice _notice; private readonly GuideText _text; public WaypointActions(GuideRuntime runtime, SessionLifecycle lifecycle, NavigationCoordinator navigator, NoteDialog dialog, UserNotice notice, GuideText text) { _runtime = runtime; _lifecycle = lifecycle; _navigator = navigator; _dialog = dialog; _notice = notice; _text = text; } public bool TryExecute(GuideCommand command, DungeonSession session) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) switch (command) { case GuideCommand.MarkLocation: if (Object.op_Implicit((Object)(object)Player.m_localPlayer)) { Create(session, UnityPoints.P(((Component)Player.m_localPlayer).transform.position)); } return true; case GuideCommand.NextLocation: Select(session, new WaypointRegistry(session.Data.waypoints).Next(_navigator.Waypoint)); return true; case GuideCommand.RenameLocation: Rename(session, _navigator.Waypoint); return true; case GuideCommand.RemoveLocation: Remove(session, _navigator.Waypoint); return true; default: return false; } } public void Create(DungeonSession session, Point position) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) if (Writable(session)) { WaypointRecord val = new WaypointRegistry(session.Data.waypoints).Add(position, _text.WaypointPrefix + (session.Data.waypoints.Count + 1)); _navigator.ToWaypoint(val); SaveNotice(session, _text.WaypointAdded + val.label); } } public void Select(DungeonSession session, WaypointRecord record) { if (record == null || !session.Data.waypoints.Contains(record)) { _notice.Show(_text.NoWaypoint); return; } _navigator.ToWaypoint(record); _notice.Show(_text.WaypointSelected + record.label); } public void Rename(DungeonSession session, WaypointRecord record) { if (Owned(session, record) && Writable(session)) { _dialog.Request(record.label, _text.WaypointNamePrompt, delegate(string value) { AcceptName(session, record, value); }); } } private void AcceptName(DungeonSession session, WaypointRecord record, string value) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) if (_runtime.Session == session && !session.ReadOnly && new WaypointRegistry(session.Data.waypoints).Rename(record, value)) { SaveNotice(session, _text.ChestSaved + record.label); } } public void Remove(DungeonSession session, WaypointRecord record) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) if (Owned(session, record) && Writable(session)) { new WaypointRegistry(session.Data.waypoints).Remove(record); if (_navigator.Waypoint == record) { _navigator.Stop(); } SaveNotice(session, _text.WaypointRemoved + record.label); } } private bool Owned(DungeonSession session, WaypointRecord record) { if (record != null && session.Data.waypoints.Contains(record)) { return true; } _notice.Show(_text.NoWaypoint); return false; } private bool Writable(DungeonSession session) { if (_runtime.Session == session && !session.ReadOnly) { return true; } _notice.Show(_text.SaveWriteFailed); return false; } private void SaveNotice(DungeonSession session, string message) { if (_lifecycle.Save(session)) { _notice.Show(message); } } } [BepInPlugin("zip.dungeonguide", "Valheim Dungeon Guide", "0.1.2.681")] public sealed class DungeonGuide : BaseUnityPlugin { private GuideGameLoop _loop; private MapHooks _hooks; private WorldChangeHooks _changes; private DungeonEntranceHooks _entranceHooks; private float _nextError; private void Awake() { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Expected O, but got Unknown //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Expected O, but got Unknown //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Expected O, but got Unknown //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Expected O, but got Unknown //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Expected O, but got Unknown //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Expected O, but got Unknown GameLanguage gameLanguage = new GameLanguage(); GuideText text = new GuideText((Func<string>)gameLanguage.Current); GuideSettings settings = new GuideSettings(((BaseUnityPlugin)this).Config); UserNotice notice = new UserNotice(); MapViewState val = CreateMapState(settings); GeometryPhysics physics = new GeometryPhysics(); GraphLocator locator = new GraphLocator(physics); SessionLifecycle lifecycle = CreateSessionLifecycle(notice, text); DungeonEntranceRecorder dungeonEntranceRecorder = new DungeonEntranceRecorder(); GuideRuntime guideRuntime = CreateRuntime(settings, physics, locator, lifecycle, notice, text, dungeonEntranceRecorder); dungeonEntranceRecorder.Observed += guideRuntime.EntranceObserved; FloorNavigationPolicy floorPolicy = new FloorNavigationPolicy(); FloorNumbering numbering = new FloorNumbering((Func<string>)gameLanguage.Current); NavigationCoordinator navigationCoordinator = new NavigationCoordinator(new RoutePlanner(), locator, floorPolicy, new GrappleFallbackSearch(physics)); RouteCursor cursor = new RouteCursor(); ChestActions chestActions = CreateChestActions(locator, lifecycle, notice, text); chestActions.DestinationRequested += navigationCoordinator.ToChest; WaypointActions waypoints = new WaypointActions(guideRuntime, lifecycle, navigationCoordinator, new NoteDialog(), notice, text); CommandDispatcher commandDispatcher = new CommandDispatcher(guideRuntime, val, navigationCoordinator, chestActions, waypoints, settings, notice, floorPolicy, text); InputRouter input = new InputRouter(settings, commandDispatcher.Execute); MapTextureRenderer mapTextureRenderer = new MapTextureRenderer(new MapExtentFitter()); NativeMapPresenter nativeMapPresenter = new NativeMapPresenter(val, mapTextureRenderer, new MapViewportController(), numbering, commandDispatcher.Execute, new MapTargetSelector(navigationCoordinator.ToChest, waypoints), text); _loop = new GuideGameLoop(guideRuntime, val, input, chestActions, navigationCoordinator, mapTextureRenderer, nativeMapPresenter, new OverlayPresenter(mapTextureRenderer, numbering, text), new NavigationHud(new NavigationInstructionFormatter(cursor, numbering, text)), new DynamicRouteValidator(physics, cursor, text)); commandDispatcher.ViewChanged += _loop.RefreshView; commandDispatcher.FitRequested += nativeMapPresenter.Center; InstallMapHooks(nativeMapPresenter, val, guideRuntime, settings); InstallWorldChangeHooks(guideRuntime); _entranceHooks = new DungeonEntranceHooks(dungeonEntranceRecorder.Observe, delegate(Exception e) { ((BaseUnityPlugin)this).Logger.LogError((object)e); }); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Valheim Dungeon Guide 0.1.2.681-dev.1+6c1a03dc8674c116f621c709a674400ec00a44c7 loaded. M: map; Alt+M: World/Dungeon; F6: optional overlay. Explored-only by default."); } private void Update() { Guard(delegate { _loop?.Tick(); }); } private void LateUpdate() { Guard(delegate { _loop?.LateTick(); }); } private void OnGUI() { Guard(delegate { _loop?.Draw(); }); } private void Guard(Action action) { try { action(); } catch (Exception ex) { if (Time.unscaledTime >= _nextError) { _nextError = Time.unscaledTime + 5f; ((BaseUnityPlugin)this).Logger.LogError((object)ex); } } } private void OnApplicationQuit() { _loop?.Dispose(); } private void OnDestroy() { _entranceHooks?.Dispose(); _entranceHooks = null; _changes?.Dispose(); _changes = null; _hooks?.Dispose(); _hooks = null; _loop?.Dispose(); _loop = null; } private static MapViewState CreateMapState(GuideSettings settings) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Expected O, but got Unknown return new MapViewState { RevealAll = settings.RevealAll.Value }; } private static GeometryScanner CreateScanner(GeometryPhysics physics) { return new GeometryScanner(physics, new PassageEvaluator(physics, new DoorPassageVerifier(), new BreakablePassageVerifier(new DestructibleDamageProbe())), new JumpEvaluator(physics)); } private SessionLifecycle CreateSessionLifecycle(UserNotice notice, GuideText text) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Expected O, but got Unknown return new SessionLifecycle(new JsonRunRepository(Path.Combine(Paths.ConfigPath, "DungeonGuide"), new SaveMigration()), ((BaseUnityPlugin)this).Logger, notice, text); } private GuideRuntime CreateRuntime(GuideSettings settings, GeometryPhysics physics, GraphLocator locator, SessionLifecycle lifecycle, UserNotice notice, GuideText text, DungeonEntranceRecorder entrances) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Expected O, but got Unknown //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Expected O, but got Unknown //IL_0080: Expected O, but got Unknown //IL_0080: Expected O, but got Unknown return new GuideRuntime((MonoBehaviour)(object)this, settings, new DungeonLocator(text, entrances), new DungeonActivationPolicy(), CreateScanner(physics), new FloorDetector(new FloorLandingDetector()), lifecycle, new ExplorationRecorder(physics, settings), new TraversalRecorder(new ObservedTraversalReplay(physics), new TraversalProofVerifier()), ((BaseUnityPlugin)this).Logger, notice, new DungeonGeometrySessions(settings, new GeometryCacheRepository(Path.Combine(Paths.ConfigPath, "DungeonGuide", "Geometry")), ((BaseUnityPlugin)this).Logger, new RestoredPassageValidation(physics, new DoorPassageVerifier(), new BreakablePassageVerifier(new DestructibleDamageProbe())))); } private static ChestActions CreateChestActions(GraphLocator locator, SessionLifecycle lifecycle, UserNotice notice, GuideText text) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Expected O, but got Unknown return new ChestActions(new ChestSelector(new ChestTargetPolicy()), new ChestSnapshotReader(), new NoteDialog(), locator, lifecycle, notice, text); } private void InstallMapHooks(NativeMapPresenter maps, MapViewState view, GuideRuntime runtime, GuideSettings settings) { _hooks = new MapHooks(() => maps.DungeonVisible, delegate { view.Dungeon = false; }, delegate { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) if (runtime.Session != null) { KeyboardShortcut value = settings.World.Value; return ((KeyboardShortcut)(ref value)).IsDown(); } return false; }); } private void InstallWorldChangeHooks(GuideRuntime runtime) { DestructionRecorder destructionRecorder = new DestructionRecorder(() => runtime.Session, runtime.GeometryChanged, runtime.DoorChanged); _changes = new WorldChangeHooks(runtime.DoorObserved, destructionRecorder.Observe, delegate(Exception e) { ((BaseUnityPlugin)this).Logger.LogError((object)e); }); } } internal sealed class BreakablePassageVerifier { private readonly DestructibleDamageProbe _damage; public BreakablePassageVerifier(DestructibleDamageProbe damage) { _damage = damage; } public bool Matches(IReadOnlyList<Collider> blockers, out string prefab, out bool verified) { //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) prefab = null; verified = false; if (blockers.Count == 0) { return false; } Destructible componentInParent = ((Component)blockers[0]).GetComponentInParent<Destructible>(); if (!Object.op_Implicit((Object)(object)componentInParent) || Object.op_Implicit((Object)(object)((Component)componentInParent).GetComponentInParent<Piece>())) { return false; } foreach (Collider blocker in blockers) { if (!((Object)(object)((Component)blocker).GetComponentInParent<Destructible>() != (Object)(object)componentInParent)) { Bounds bounds = blocker.bounds; float x = ((Bounds)(ref bounds)).size.x; bounds = blocker.bounds; if (Mathf.Min(x, ((Bounds)(ref bounds)).size.z) < 1.2f) { continue; } } return false; } prefab = ((Object)((Component)componentInParent).gameObject).name.Replace("(Clone)", "").Trim(); verified = _damage.CanBreak(componentInParent); return true; } } internal static class CachedGeometryBinding { public static CachedGeometry Capture(DungeonSession session) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Expected O, but got Unknown return new CachedGeometry(session.Scan.Graph, session.Layout) { Spacing = session.Scan.Spacing, Truncated = session.Scan.Truncated, Complete = (!session.HasPartialGeometry && !session.GeometryNeedsScan && !session.GeometryDirty), Entrance = (session.Area.ObservedEntrance ? session.Area.Entrance : ((Point?)null)) }; } public static ScanResult Index(CachedGeometry geometry, CancellationToken cancellation) { ScanResult scanResult = new ScanResult(geometry.Graph) { Spacing = geometry.Spacing, Truncated = geometry.Truncated }; for (int i = 0; i < scanResult.Graph.Nodes.Count; i++) { cancellation.ThrowIfCancellationRequested(); IndexColumn(scanResult, i); IndexTraversals(scanResult, i); } foreach (byte item in scanResult.SpecialPassages.Build(scanResult.Graph, cancellation)) { _ = item; } return scanResult; } private static void IndexColumn(ScanResult scan, int index) { NavNode val = scan.Graph.Nodes[index]; (int, int) key = ((int)Math.Round(val.Position.x / scan.Spacing), (int)Math.Round(val.Position.z / scan.Spacing)); if (!scan.Columns.TryGetValue(key, out var value)) { value = new List<int>(); scan.Columns.Add(key, value); } value.Add(index); } private static void IndexTraversals(ScanResult scan, int index) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Invalid comparison between Unknown and I4 //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Invalid comparison between Unknown and I4 NavNode val = scan.Graph.Nodes[index]; foreach (NavEdge edge in val.Edges) { if ((int)edge.Kind == 4 || (int)edge.Kind == 5) { scan.TraversalCandidates[(val.Key, scan.Graph.Nodes[edge.To].Key)] = edge; } } } public static void Restore(DungeonSession session, CachedGeometry geometry, ScanResult scan) { session.Scan = scan; session.Layout = geometry.Layout; session.Floors = geometry.Layout.Levels; session.NavigationReady = geometry.Complete; session.HasPartialGeometry = !geometry.Complete; session.GeometryNeedsScan = !geometry.Complete || session.GeometryDirty; session.GeometryRevision++; if (!session.Area.ObservedEntrance && geometry.Entrance.HasValue) { session.Area.Entrance = geometry.Entrance; session.Area.ObservedEntrance = true; } } } internal sealed class ChestSelector { private readonly FieldInfo _currentContainer = typeof(InventoryGui).GetField("m_currentContainer", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private readonly FieldInfo _containerPanel = typeof(InventoryGui).GetField("m_container", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private readonly ChestTargetPolicy _policy; public ChestSelector(ChestTargetPolicy policy) { _policy = policy; } public Container Select(Player player) { //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Invalid comparison between Unknown and I4 //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Invalid comparison between Unknown and I4 InventoryGui instance = InventoryGui.instance; Container val = (Container)(Object.op_Implicit((Object)(object)instance) ? /*isinst with value type is only supported in some contexts*/: null); Component val2 = (Component)(Object.op_Implicit((Object)(object)instance) ? /*isinst with value type is only supported in some contexts*/: null); GameObject hoverObject = ((Humanoid)player).GetHoverObject(); Container val3 = (Object.op_Implicit((Object)(object)hoverObject) ? hoverObject.GetComponentInParent<Container>() : null); bool flag = Object.op_Implicit((Object)(object)instance) && InventoryGui.IsVisible() && Object.op_Implicit((Object)(object)val2) && val2.gameObject.activeInHierarchy && Object.op_Implicit((Object)(object)val); ChestTargetKind val4 = _policy.Select(flag, Object.op_Implicit((Object)(object)val) && InRange(val, player), Object.op_Implicit((Object)(object)val3) && !string.IsNullOrEmpty(val3.GetHoverText()), Object.op_Implicit((Object)(object)val3) && InRange(val3, player)); if ((int)val4 != 1) { if ((int)val4 == 2) { return val3; } return null; } return val; } private bool InRange(Container chest, Player player) { //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) Collider[] array = (from c in ((Component)chest).GetComponentsInChildren<Collider>() where c.enabled && !c.isTrigger select c).ToArray(); float range = player.m_maxInteractDistance + 0.5f; if (array.Length == 0) { return Vector3.Distance(((Component)player).transform.position, ((Component)chest).transform.position) < range; } return array.Any((Collider c) => Vector3.Distance(c.ClosestPoint(((Component)player).transform.position + Vector3.up), ((Component)player).transform.position + Vector3.up) < range); } } internal sealed class ChestSnapshotReader { public ChestSnapshot Read(Container chest, bool discovered) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Expected O, but got Unknown Vector3 position = ((Component)chest).transform.position; Vector3 v = FloorPosition(chest, position); string text = UnityPoints.PositionId(position); string id = Identity(chest, text); string contents = Contents(chest); return new ChestSnapshot { Id = id, LegacyId = text, Position = UnityPoints.P(v), Contents = contents, Discovered = discovered }; } private static Vector3 FloorPosition(Container chest, Vector3 transformPosition) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) Vector3 result = transformPosition; Collider val = ((IEnumerable<Collider>)((Component)chest).GetComponentsInChildren<Collider>()).FirstOrDefault((Func<Collider, bool>)((Collider c) => c.enabled && !c.isTrigger)); if (Object.op_Implicit((Object)(object)val)) { Bounds bounds = val.bounds; float x = ((Bounds)(ref bounds)).center.x; bounds = val.bounds; float y = ((Bounds)(ref bounds)).min.y; bounds = val.bounds; ((Vector3)(ref result))..ctor(x, y, ((Bounds)(ref bounds)).center.z); } return result; } private unsafe static string Identity(Container chest, string legacy) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) ZNetView component = ((Component)chest).GetComponent<ZNetView>(); ZDO val = (Object.op_Implicit((Object)(object)component) ? component.GetZDO() : null); if (val == null || ((ZDOID)(ref val.m_uid)).Equals(ZDOID.None)) { return legacy; } ZDOID uid = val.m_uid; return "zdo_" + ((object)(*(ZDOID*)(&uid))/*cast due to .constrained prefix*/).ToString(); } private static string Contents(Container chest) { string result = null; Inventory inventory = chest.GetInventory(); if (inventory != null) { result = string.Join(", ", from i in inventory.GetAllItems().Take(5) select i.m_stack + "× " + ((Localization.instance != null) ? Localization.instance.Localize(i.m_shared.m_name) : i.m_shared.m_name)); } return result; } } internal sealed class DestructibleDamageProbe { private readonly Dictionary<Destructible, bool> _results = new Dictionary<Destructible, bool>(); private int _frame = -1; public bool CanBreak(Destructible target) { if (_frame != Time.frameCount) { _frame = Time.frameCount; _results.Clear(); } if (!_results.TryGetValue(target, out var value)) { value = HasHealth(target) && HasEffectiveEquipment(target, Player.m_localPlayer); _results[target] = value; } return value; } private static bool HasHealth(Destructible target) { ZNetView component = ((Component)target).GetComponent<ZNetView>(); if (Object.op_Implicit((Object)(object)component) && component.IsValid()) { return component.GetZDO().GetFloat(ZDOVars.s_health, target.m_health) > 0f; } return false; } private static bool HasEffectiveEquipment(Destructible target, Player player) { if (!Object.op_Implicit((Object)(object)player)) { return false; } Inventory inventory = ((Humanoid)player).GetInventory(); if (CanDamage(target, ((Humanoid)player).GetCurrentWeapon(), inventory)) { return true; } foreach (ItemData allItem in inventory.GetAllItems()) { if (CanDamage(target, allItem, inventory)) { return true; } } return false; } private static bool CanDamage(Destructible target, ItemData item, Inventory inventory) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Invalid comparison between Unknown and I4 if (item == null || item.m_stack <= 0 || (!item.IsWeapon() && (int)item.m_shared.m_itemType != 19) || (item.m_shared.m_useDurability && item.m_durability <= 0f)) { return false; } if (!EffectiveAttack(target, item, item.m_shared.m_attack, inventory)) { return EffectiveAttack(target, item, item.m_shared.m_secondaryAttack, inventory); } return true; } private static bool EffectiveAttack(Destructible target, ItemData item, Attack attack, Inventory inventory) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Invalid comparison between Unknown and I4 //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Invalid comparison between Unknown and I4 if (attack == null || string.IsNullOrEmpty(attack.m_attackAnimation) || attack.m_damageMultiplier <= 0f || attack.m_cantUseInDungeon || (int)attack.m_attackType == 3 || (int)attack.m_attackType == 5) { return false; } if (string.IsNullOrEmpty(item.m_shared.m_ammoType)) { return EffectiveDamage(target, item, attack, null); } foreach (ItemData allItem in inventory.GetAllItems()) { if (CompatibleAmmo(item, allItem) && EffectiveDamage(target, item, attack, allItem)) { return true; } } return false; } private static bool CompatibleAmmo(ItemData weapon, ItemData ammo) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Invalid comparison between Unknown and I4 //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Invalid comparison between Unknown and I4 //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Invalid comparison between Unknown and I4 ItemType itemType = ammo.m_shared.m_itemType; if (ammo.m_stack > 0 && ammo.m_shared.m_ammoType == weapon.m_shared.m_ammoType) { if ((int)itemType != 9 && (int)itemType != 23) { return (int)itemType == 2; } return true; } return false; } private static bool EffectiveDamage(Destructible target, ItemData item, Attack attack, ItemData ammo) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Expected O, but got Unknown //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) HitData val = new HitData { m_toolTier = (short)item.m_shared.m_toolTier, m_itemWorldLevel = (byte)item.m_worldLevel, m_damage = item.GetDamage() }; if (!val.CheckToolTier(target.m_minToolTier, false)) { return false; } if (ammo != null) { ((DamageTypes)(ref val.m_damage)).Add(ammo.GetDamage(), 1); } ((DamageTypes)(ref val.m_damage)).Modify(attack.m_damageMultiplier); DamageModifier val2 = default(DamageModifier); val.ApplyResistance(target.m_damages, ref val2); return val.GetTotalDamage() > 0f; } } internal sealed class DestructionRecorder { private readonly Func<DungeonSession> _session; private readonly Action _geometryChanged; private readonly Action _permissionsChanged; public DestructionRecorder(Func<DungeonSession> session, Action geometryChanged, Action permissionsChanged) { _session = session; _geometryChanged = geometryChanged; _permissionsChanged = permissionsChanged; } public void Observe(Destructible target) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) DungeonSession dungeonSession = _session(); if (dungeonSession != null && Object.op_Implicit((Object)(object)target) && ((Bounds)(ref dungeonSession.Area.Bounds)).Contains(((Component)target).transform.position) && !Object.op_Implicit((Object)(object)((Component)target).GetComponentInParent<Container>()) && !Object.op_Implicit((Object)(object)((Component)target).GetComponentInParent<Character>())) { if (IsPassageOnly(dungeonSession.Scan, target)) { _permissionsChanged(); } else { _geometryChanged(); } } } private static bool IsPassageOnly(ScanResult scan, Destructible target) { //IL_0080: Unknown result type (might be due to invalid IL or missing references) string item = ((Object)((Component)target).gameObject).name.Replace("(Clone)", "").Trim(); if (scan == null || !scan.SpecialPassages.BreakablePrefabs.Contains(item) || Object.op_Implicit((Object)(object)((Component)target).GetComponentInParent<Piece>())) { return false; } foreach (Collider item2 in from c in ((Component)target).GetComponentsInChildren<Collider>() where c.enabled && !c.isTrigger select c) { if (SupportsSamples(scan, item2.bounds)) { return false; } } return true; } private static bool SupportsSamples(ScanResult scan, Bounds bounds) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) ((Bounds)(ref bounds)).Expand(new Vector3(0.05f, 0.5f, 0.05f)); int num = Mathf.FloorToInt(((Bounds)(ref bounds)).min.x / scan.Spacing); int num2 = Mathf.CeilToInt(((Bounds)(ref bounds)).max.x / scan.Spacing); int num3 = Mathf.FloorToInt(((Bounds)(ref bounds)).min.z / scan.Spacing); int num4 = Mathf.CeilToInt(((Bounds)(ref bounds)).max.z / scan.Spacing); if ((long)(num2 - num + 1) * (long)(num4 - num3 + 1) > 1024) { return true; } for (int i = num; i <= num2; i++) { for (int j = num3; j <= num4; j++) { if (scan.Columns.TryGetValue((i, j), out var value) && value.Any((int index) => ((Bounds)(ref bounds)).Contains(UnityPoints.V(scan.Graph.Nodes[index].Position)))) { return true; } } } return false; } } internal sealed class DoorGeometryMonitor { private readonly List<DoorGeometryObservation> _pending = new List<DoorGeometryObservation>(); private readonly Action _geometryChanged; private float _nextCheck; public DoorGeometryMonitor(Action geometryChanged) { _geometryChanged = geometryChanged; } public void Observe(Door door, float now) { foreach (DoorGeometryObservation item in _pending) { if ((Object)(object)item.Door == (Object)(object)door) { item.Restart(now); return; } } _pending.Add(new DoorGeometryObservation(door, now)); } public void Tick(float now) { if (_pending.Count != 0 && !(now < _nextCheck)) { _nextCheck = now + 0.1f; if (CompleteSettled(now)) { _geometryChanged(); } } } private bool CompleteSettled(float now) { bool flag = false; for (int num = _pending.Count - 1; num >= 0; num--) { DoorGeometryObservation doorGeometryObservation = _pending[num]; if (!Object.op_Implicit((Object)(object)doorGeometryObservation.Door)) { _pending.RemoveAt(num); } else if (doorGeometryObservation.Settled(now)) { flag |= doorGeometryObservation.FloorChanged; _pending.RemoveAt(num); } } return flag; } public void Reset() { _pending.Clear(); _nextCheck = 0f; } } internal sealed class DoorGeometryObservation { private readonly DoorGeometrySnapshot _before; private DoorGeometrySnapshot _latest; private readonly Animator _animator; private float _stableSince; public Door Door { get; } public bool FloorChanged => _before.ChangesFloor(_latest); public DoorGeometryObservation(Door door, float now) { Door = door; _before = (_latest = new DoorGeometrySnapshot(door)); _animator = ((Component)door).GetComponentInChildren<Animator>(); _stableSince = now; } public void Restart(float now) { _stableSince = now; } public bool Settled(float now) { DoorGeometrySnapshot doorGeometrySnapshot = new DoorGeometrySnapshot(Door); if (!_latest.Matches(doorGeometrySnapshot)) { _latest = doorGeometrySnapshot; _stableSince = now; } if (now - _stableSince >= 0.5f) { return !Animating(); } return false; } private bool Animating() { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) if (!Object.op_Implicit((Object)(object)_animator) || !((Behaviour)_animator).isActiveAndEnabled || !Object.op_Implicit((Object)(object)_animator.runtimeAnimatorController)) { return false; } AnimatorStateInfo currentAnimatorStateInfo = _animator.GetCurrentAnimatorStateInfo(0); if (!_animator.IsInTransition(0)) { if (!((AnimatorStateInfo)(ref currentAnimatorStateInfo)).loop) { return ((AnimatorStateInfo)(ref currentAnimatorStateInfo)).normalizedTime < 1f; } return false; } return true; } } internal sealed class DoorGeometrySnapshot { private readonly Dictionary<int, Bounds> _solids = new Dictionary<int, Bounds>(); public DoorGeometrySnapshot(Door door) { //IL_003d: Unknown result type (might be due to invalid IL or missing references) Collider[] componentsInChildren = ((Component)door).GetComponentsInChildren<Collider>(); foreach (Collider val in componentsInChildren) { if (val.enabled && !val.isTrigger) { _solids.Add(((Object)val).GetInstanceID(), val.bounds); } } } public bool Matches(DoorGeometrySnapshot other) { Bounds value; if (_solids.Count == other._solids.Count) { return _solids.All((KeyValuePair<int, Bounds> solid) => other._solids.TryGetValue(solid.Key, out value) && SameBounds(solid.Value, value)); } return false; } public bool ChangesFloor(DoorGeometrySnapshot other) { if (!_solids.Any((KeyValuePair<int, Bounds> solid) => MovedFloor(solid, other))) { return other._solids.Any((KeyValuePair<int, Bounds> solid) => !_solids.ContainsKey(solid.Key) && FloorLike(solid.Value)); } return true; } private static bool MovedFloor(KeyValuePair<int, Bounds> solid, DoorGeometrySnapshot other) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) if (!other._solids.TryGetValue(solid.Key, out var value)) { return FloorLike(solid.Value); } if (!SameBounds(solid.Value, value)) { if (!FloorLike(solid.Value)) { return FloorLike(value); } return true; } return false; } private static bool SameBounds(Bounds a, Bounds b) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) Vector3 val = ((Bounds)(ref a)).center - ((Bounds)(ref b)).center; if (((Vector3)(ref val)).sqrMagnitude <= 0.0001f) { val = ((Bounds)(ref a)).size - ((Bounds)(ref b)).size; return ((Vector3)(ref val)).sqrMagnitude <= 0.0001f; } return false; } private static bool FloorLike(Bounds bounds) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) float num = Mathf.Min(((Bounds)(ref bounds)).size.x, ((Bounds)(ref bounds)).size.z); if (num >= 0.5f) { return ((Bounds)(ref bounds)).size.y <= num * 0.5f; } return false; } } internal sealed class DoorPassageVerifier { private readonly MethodInfo _haveKey = typeof(Door).GetMethod("HaveKey", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); public bool Matches(IReadOnlyList<Collider> blockers, out Door door) { door = ((Component)blockers[0]).GetComponentInParent<Door>(); if (!Object.op_Implicit((Object)(object)door)) { return false; } foreach (Collider blocker in blockers) { if ((Object)(object)((Component)blocker).GetComponentInParent<Door>() != (Object)(object)door) { return false; } } return true; } public bool CanOpen(Door door) { if (!Object.op_Implicit((Object)(object)Player.m_localPlayer)) { return false; } if ((Object)(object)door.m_keyItem == (Object)null) { return true; } ZNetView component = ((Component)door).GetComponent<ZNetView>(); if (Object.op_Implicit((Object)(object)component) && component.IsValid() && component.GetZDO().GetInt(ZDOVars.s_state, 0) != 0) { return true; } if (_haveKey != null) { return (bool)_haveKey.Invoke(door, new object[2] { Player.m_localPlayer, true }); } return false; } } internal sealed class DungeonArea { public string Id; public string Label; public bool IsManual; public Bounds Bounds; public Bounds[] Rooms; public DungeonRoom[] RoomVolumes = Array.Empty<DungeonRoom>(); public Point? Entrance; public bool ObservedEntrance; } internal sealed class DungeonAreaFactory { private readonly GuideText _text; private readonly DungeonEntranceRecorder _entrances; public DungeonAreaFactory(GuideText text, DungeonEntranceRecorder entrances) { _text = text; _entrances = entrances; } public DungeonArea Generated(DungeonGenerator generator, Vector3 position) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) Room[] componentsInChildren = ((Component)generator).GetComponentsInChildren<Room>(true); if (!componentsInChildren.Any((Room room) => DungeonRoomGeometry.Contains(room, position))) { return null; } Bounds[] rooms = componentsInChildren.Select(DungeonRoomGeometry.WorldBounds).ToArray(); Point? val = _entrances.Find(componentsInChildren); return new DungeonArea { Id = "dungeon_" + UnityPoints.PositionId(((Component)generator).transform.position) + "_" + ((object)Unsafe.As<Theme, Theme>(ref generator.m_themes)/*cast due to .constrained prefix*/).ToString(), Label = ((object)Unsafe.As<Theme, Theme>(ref generator.m_themes)/*cast due to .constrained prefix*/).ToString(), Bounds = DungeonRoomGeometry.Enclose(rooms), Rooms = rooms, RoomVolumes = componentsInChildren.Select((Room room) => new DungeonRoom(room)).ToArray(), Entrance = (val ?? FindEntrance(componentsInChildren)), ObservedEntrance = val.HasValue }; } public DungeonArea Manual(Vector3 position, float radius, float height) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) DungeonArea dungeonArea = new DungeonArea(); dungeonArea.Id = "fortress_" + Mathf.FloorToInt(position.x / 128f) + "_" + Mathf.FloorToInt(position.y / 1000f) + "_" + Mathf.FloorToInt(position.z / 128f); dungeonArea.Label = _text.ManualArea; dungeonArea.IsManual = true; dungeonArea.Bounds = new Bounds(position, new Vector3(radius * 2f, height * 2f, radius * 2f)); dungeonArea.Rooms = Array.Empty<Bounds>(); return dungeonArea; } private static Point? FindEntrance(Room[] rooms) { //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) Room val = ((IEnumerable<Room>)rooms).FirstOrDefault((Func<Room, bool>)((Room room) => room.m_entrance)); if (!Object.op_Implicit((Object)(object)val)) { return null; } RoomConnection val2 = val.GetConnections()?.FirstOrDefault((Func<RoomConnection, bool>)((RoomConnection candidate) => Object.op_Implicit((Object)(object)candidate) && candidate.m_entrance)); return UnityPoints.P(Object.op_Implicit((Object)(object)val2) ? ((Component)val2).transform.position : ((Component)val).transform.position); } } internal sealed class DungeonEntranceHooks : IDisposable { private readonly Harmony _harmony = new Harmony("zip.dungeonguide.entrance"); private static Action<Teleport, Humanoid> s_observe; private static Action<Exception> s_report; public DungeonEntranceHooks(Action<Teleport, Humanoid> observe, Action<Exception> report) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected O, but got Unknown //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Expected O, but got Unknown s_observe = observe; s_report = report; MethodInfo methodInfo = AccessTools.Method(typeof(Teleport), "Interact", new Type[3] { typeof(Humanoid), typeof(bool), typeof(bool) }, (Type[])null); if (methodInfo == null) { throw new MissingMethodException(typeof(Teleport).FullName, "Interact"); } _harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(DungeonEntranceHooks), "Observe", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } private static void Observe(Teleport __instance, Humanoid __0, bool __result) { if (!__result) { return; } try { s_observe?.Invoke(__instance, __0); } catch (Exception obj) { s_report?.Invoke(obj); } } public void Dispose() { _harmony.UnpatchSelf(); s_observe = null; s_report = null; } } internal sealed class DungeonEntranceRecorder { private Point _doorway; private Point _arrival; private bool _observed; private long _world; public event Action Observed; public void Observe(Teleport source, Humanoid character) { //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) Teleport val = (Object.op_Implicit((Object)(object)source) ? source.m_targetPoint : null); long? id = GameWorld.Id; if (Object.op_Implicit((Object)(object)val) && Object.op_Implicit((Object)(object)Player.m_localPlayer) && !((Object)(object)character != (Object)(object)Player.m_localPlayer) && id.HasValue && !Character.InInterior(((Component)source).transform.position) && Character.InInterior(((Component)val).transform.position)) { Transform transform = ((Component)val).transform; _doorway = UnityPoints.P(transform.position); _arrival = UnityPoints.P(transform.position + transform.forward - transform.up); _observed = true; _world = id.Value; this.Observed?.Invoke(); } } public Point? Find(Room[] rooms) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) if (!_observed || _world != GameWorld.Id || !rooms.Any((Room room) => DungeonRoomGeometry.Contains(room, UnityPoints.V(_doorway)) || DungeonRoomGeometry.Contains(room, UnityPoints.V(_arrival)))) { return null; } return _arrival; } } internal sealed class DungeonLocator { private readonly DungeonAreaFactory _areas; public DungeonLocator(GuideText text, DungeonEntranceRecorder entrances) { _areas = new DungeonAreaFactory(text, entrances); } public DungeonArea Locate(Vector3 position) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) if (!Character.InInterior(position)) { return null; } return FindClosestArea(position); } public DungeonArea Manual(Vector3 position, float radius, float height) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) return _areas.Manual(position, radius, height); } private DungeonArea FindClosestArea(Vector3 position) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) DungeonArea result = null; float num = float.MaxValue; DungeonGenerator[] array = Object.FindObjectsByType<DungeonGenerator>((FindObjectsSortMode)0); foreach (DungeonGenerator generator in array) { DungeonArea dungeonArea = _areas.Generated(generator, position); if (dungeonArea != null) { float num2 = Vector3.Distance(((Bounds)(ref dungeonArea.Bounds)).ClosestPoint(position), position); if (num2 < 4f && num2 < num) { result = dungeonArea; num = num2; } } } return result; } } internal sealed class DungeonRoom { private readonly RoomVolume _volume; private readonly Matrix4x4 _localToWorld; public string Id { get; } public DungeonRoom(Room room) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Expected O,
BepInEx/plugins/DungeonGuide/cs/DungeonGuide.Domain.resources.dll
Decompiled 2 weeks agousing System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyCompany("DungeonGuide.Domain")] [assembly: AssemblyCopyright(" ")] [assembly: AssemblyFileVersion("0.1.2.681")] [assembly: AssemblyInformationalVersion("0.1.2.681-dev.1+6c1a03dc8674c116f621c709a674400ec00a44c7")] [assembly: AssemblyProduct("DungeonGuide.Domain")] [assembly: AssemblyTitle("DungeonGuide.Domain")] [assembly: AssemblyVersion("0.1.2.0")]
BepInEx/plugins/DungeonGuide/DungeonGuide.Domain.dll
Decompiled 2 weeks ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: InternalsVisibleTo("DungeonGuide")] [assembly: InternalsVisibleTo("DungeonGuide.Tests")] [assembly: NeutralResourcesLanguage("en")] [assembly: TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName = ".NET Standard 2.0")] [assembly: AssemblyCompany("DungeonGuide.Domain")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.1.2.681")] [assembly: AssemblyInformationalVersion("0.1.2.681-dev.1+6c1a03dc8674c116f621c709a674400ec00a44c7")] [assembly: AssemblyProduct("DungeonGuide.Domain")] [assembly: AssemblyTitle("DungeonGuide.Domain")] [assembly: AssemblyVersion("0.1.2.0")] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] internal sealed class IsReadOnlyAttribute : Attribute { } } namespace DungeonGuide { internal sealed class CachedGeometry { public float Spacing; public bool Truncated; public bool Complete; public Point? Entrance; public NavGraph Graph { get; } public FloorLayout Layout { get; } public CachedGeometry(NavGraph graph = null, FloorLayout layout = null) { Graph = graph ?? new NavGraph(); Layout = layout ?? new FloorLayout(); } } [Serializable] public sealed class ChestRecord { public string id; public string label; public string contents; public Point position; public bool collect; public bool done; public bool discovered; } public sealed class ChestRegistry { private readonly List<ChestRecord> _records; private readonly GuideText _text; public ChestRegistry(List<ChestRecord> records, Func<string> selectedLanguage = null) { _records = records; _text = new GuideText(selectedLanguage); } public ChestRecord Observe(ChestSnapshot snapshot) { ChestRecord chestRecord = _records.FirstOrDefault((ChestRecord c) => c.id == snapshot.Id || c.id == snapshot.LegacyId); if (chestRecord == null) { chestRecord = new ChestRecord { label = _text.ChestPrefix + (_records.Count + 1) }; _records.Add(chestRecord); } chestRecord.id = snapshot.Id; chestRecord.position = snapshot.Position; if (snapshot.Contents != null) { chestRecord.contents = snapshot.Contents; } chestRecord.discovered |= snapshot.Discovered; return chestRecord; } public void Mark(ChestRecord record) { record.collect = true; record.done = false; record.discovered = true; } public void Collect(ChestRecord record) { record.collect = false; record.done = true; record.discovered = true; } public List<ChestRecord> Targets() { return (from c in _records where !c.done && (c.collect || (c.discovered && !string.IsNullOrEmpty(c.contents))) orderby c.collect descending, c.label select c).ToList(); } } public sealed class ChestSnapshot { public string Id; public string LegacyId; public string Contents; public Point Position; public bool Discovered; } public enum ChestTargetKind { None, Open, Aimed } public sealed class ChestTargetPolicy { public ChestTargetKind Select(bool openVisible, bool openInRange, bool aimedInteractive, bool aimedInRange) { if (openVisible && openInRange) { return ChestTargetKind.Open; } if (aimedInteractive && aimedInRange) { return ChestTargetKind.Aimed; } return ChestTargetKind.None; } } internal sealed class GuideText { private static readonly ResourceManager s_resources = new ResourceManager("DungeonGuide.Domain.Content.Strings", typeof(GuideText).Assembly); private static readonly CultureInfo s_czech = CultureInfo.GetCultureInfo("cs"); private readonly Func<string> _selectedLanguage; internal string SelectedLanguage => _selectedLanguage?.Invoke(); internal bool IsCzech => string.Equals(SelectedLanguage, "Czech", StringComparison.OrdinalIgnoreCase); internal string ChestTargetRequired => Get("ChestTargetRequired"); internal string ChestSaved => Get("ChestSaved"); internal string ChestCollected => Get("ChestCollected"); internal string NoKnownChest => Get("NoKnownChest"); internal string NoAdjacentFloor => Get("NoAdjacentFloor"); internal string SaveReadFailed => Get("SaveReadFailed"); internal string SaveWriteFailed => Get("SaveWriteFailed"); internal string ChestNotePrompt => Get("ChestNotePrompt"); internal string ItemCountSeparator => Get("ItemCountSeparator"); internal string ManualArea => Get("ManualArea"); internal string OpenDoor => Get("OpenDoor"); internal string BreakObstacle => Get("BreakObstacle"); internal string RouteBlocked => Get("RouteBlocked"); internal string WorldMap => Get("WorldMap"); internal string FloorDown => Get("FloorDown"); internal string FloorUp => Get("FloorUp"); internal string FollowFloor => Get("FollowFloor"); internal string Explored => Get("Explored"); internal string NavigateUp => Get("NavigateUp"); internal string NavigateDown => Get("NavigateDown"); internal string Explore => Get("Explore"); internal string Entrance => Get("Entrance"); internal string Stop => Get("Stop"); internal string Chests => Get("Chests"); internal string ChestControls => Get("ChestControls"); internal string DungeonMap => Get("DungeonMap"); internal string PreparingMap => Get("PreparingMap"); internal string FullDungeon => Get("FullDungeon"); internal string Yes => Get("Yes"); internal string No => Get("No"); internal string MapPanelsUnavailable => Get("MapPanelsUnavailable"); internal string EntranceRoom => Get("EntranceRoom"); internal string UnknownEntrance => Get("UnknownEntrance"); internal string ApproachPassage => Get("ApproachPassage"); internal string ExplorationExhausted => Get("ExplorationExhausted"); internal string NoVerifiedRoute => Get("NoVerifiedRoute"); internal string PreparingRoute => Get("PreparingRoute"); internal string NearDestination => Get("NearDestination"); internal string GrappleInstruction => Get("GrappleInstruction"); internal string GrappleRequired => Get("GrappleRequired"); internal string SearchingGrapple => Get("SearchingGrapple"); internal string LegendGrapple => Get("LegendGrapple"); internal string JumpInstruction => Get("JumpInstruction"); internal string DropInstruction => Get("DropInstruction"); internal string Straight => Get("Straight"); internal string TurnAround => Get("TurnAround"); internal string Right => Get("Right"); internal string Left => Get("Left"); internal string UpIndicator => Get("UpIndicator"); internal string DownIndicator => Get("DownIndicator"); internal string Separator => Get("Separator"); internal string OverlayControls => Get("OverlayControls"); internal string FloorUnknown => Get("FloorUnknown"); internal string EntranceFloor => Get("EntranceFloor"); internal string FloorNegative => Get("FloorNegative"); internal string FloorPositive => Get("FloorPositive"); internal string FloorPrefix => Get("FloorPrefix"); internal string BottomUp => Get("BottomUp"); internal string CollectChest => Get("CollectChest"); internal string ChestDestination => Get("ChestDestination"); internal string ChestPrefix => Get("ChestPrefix"); internal string More => Get("More"); internal string Less => Get("Less"); internal string Overview => Get("Overview"); internal string FloorDetail => Get("FloorDetail"); internal string NoExploredFloors => Get("NoExploredFloors"); internal string OverviewHint => Get("OverviewHint"); internal string PreviewSuffix => Get("PreviewSuffix"); internal string Legend => Get("Legend"); internal string LegendOverlayHint => Get("LegendOverlayHint"); internal string LegendClose => Get("LegendClose"); internal string LegendScrollHint => Get("LegendScrollHint"); internal string LegendExplored => Get("LegendExplored"); internal string LegendVisited => Get("LegendVisited"); internal string LegendFog => Get("LegendFog"); internal string LegendWalls => Get("LegendWalls"); internal string LegendPlayer => Get("LegendPlayer"); internal string LegendRoute => Get("LegendRoute"); internal string LegendStairs => Get("LegendStairs"); internal string LegendEntrance => Get("LegendEntrance"); internal string LegendChest => Get("LegendChest"); internal string LegendCollection => Get("LegendCollection"); internal string LegendUnverified => Get("LegendUnverified"); internal string WaypointPrefix => Get("WaypointPrefix"); internal string WaypointNamePrompt => Get("WaypointNamePrompt"); internal string WaypointAdded => Get("WaypointAdded"); internal string WaypointSelected => Get("WaypointSelected"); internal string WaypointRemoved => Get("WaypointRemoved"); internal string NoWaypoint => Get("NoWaypoint"); internal string WaypointControls => Get("WaypointControls"); internal string WaypointMapControls => Get("WaypointMapControls"); internal string LegendWaypoint => Get("LegendWaypoint"); internal GuideText(Func<string> selectedLanguage = null) { _selectedLanguage = selectedLanguage; } private string Get(string key) { return s_resources.GetString(key, IsCzech ? s_czech : CultureInfo.InvariantCulture) ?? throw new MissingManifestResourceException("Missing localized text: " + key); } } internal static class CoroutinePhase { public static IEnumerable<object> Forward(IEnumerator phase) { try { while (phase.MoveNext()) { yield return phase.Current; } } finally { (phase as IDisposable)?.Dispose(); } } } public sealed class DungeonActivationPolicy { public bool CanScanManually(bool inInterior, bool allowOutdoorMapping = false) { return inInterior || allowOutdoorMapping; } public bool CanRetainSession(bool isManualArea, bool insideBounds, bool inInterior, bool allowOutdoorMapping = false) { if (insideBounds) { if (!inInterior) { return isManualArea && allowOutdoorMapping; } return true; } return false; } } public sealed class Exploration { private readonly HashSet<string> _known = new HashSet<string>(); private readonly Dictionary<int, int> _floorRevisions = new Dictionary<int, int>(); internal List<(string Key, int Floor, long Timestamp)> Changes { get; } = new List<(string, int, long)>(); internal int Generation { get; private set; } public int Revision { get; private set; } internal int FloorRevision(int floor) { int value; int value2; return (_floorRevisions.TryGetValue(floor, out value) ? value : 0) + (_floorRevisions.TryGetValue(-1, out value2) ? value2 : 0); } private void Changed(string key, int floor) { Changes.Add((key, floor, Stopwatch.GetTimestamp())); _floorRevisions[floor] = (_floorRevisions.TryGetValue(floor, out var value) ? value : 0) + 1; Revision++; } public void Load(IEnumerable<string> keys) { _known.Clear(); Changes.Clear(); _floorRevisions.Clear(); Generation++; foreach (string key in keys) { _known.Add(key); } Revision++; } public bool Has(Point p) { return _known.Contains(Point.Key(p)); } public bool Has(NavNode node) { return _known.Contains(node.Key); } public bool Reveal(NavNode node) { if (!_known.Add(node.Key)) { return false; } Changed(node.Key, node.Floor); return true; } public bool Reveal(Point p) { if (!_known.Add(Point.Key(p))) { return false; } Changed(Point.Key(p), -1); return true; } public List<string> Export() { return new List<string>(_known); } } internal static class ExplorationRevealPolicy { public const float MaximumRadius = 10f; public static bool TryReveal(Exploration exploration, NavNode node, Point player, int physicalFloor, Func<Point, Point, bool> visible, float radius = 10f) { radius = Math.Min(radius, 10f); if (physicalFloor < 0 || node.Floor != physicalFloor || exploration.Has(node) || HorizontalSquared(player, node.Position) > radius * radius) { return false; } if (visible(player, node.Position)) { return exploration.Reveal(node); } return false; } private static float HorizontalSquared(Point a, Point b) { return (a.x - b.x) * (a.x - b.x) + (a.z - b.z) * (a.z - b.z); } } internal sealed class ExplorationSweep : IDisposable { private IEnumerator<NavNode> _nearby; private IEnumerator<NavNode> _distant; private bool _preferNearby = true; public bool HasDistant => _distant != null; public void RefreshNearby(IEnumerable<NavNode> candidates) { _nearby?.Dispose(); _nearby = candidates.GetEnumerator(); } public void RefreshDistant(IEnumerable<NavNode> candidates) { _distant?.Dispose(); _distant = candidates.GetEnumerator(); } public bool TryTake(out NavNode node) { bool preferNearby = _preferNearby; _preferNearby = !preferNearby; if (!preferNearby) { if (!Advance(ref _distant, out node)) { return Advance(ref _nearby, out node); } return true; } if (!Advance(ref _nearby, out node)) { return Advance(ref _distant, out node); } return true; } private static bool Advance(ref IEnumerator<NavNode> candidates, out NavNode node) { node = null; if (candidates == null) { return false; } if (candidates.MoveNext()) { node = candidates.Current; return true; } candidates.Dispose(); candidates = null; return false; } public void Reset() { _nearby?.Dispose(); _distant?.Dispose(); _nearby = (_distant = null); _preferNearby = true; } public void Dispose() { Reset(); } } internal sealed class FloorAnalysisJob : IDisposable { private readonly CancellationTokenSource _cancellation = new CancellationTokenSource(); private readonly Task<FloorLayout> _task; private bool _disposed; public FloorAnalysisJob(FloorDetector detector, NavGraph graph, float spacing) : this((CancellationToken token) => detector.Detect(graph, spacing, token)) { } internal FloorAnalysisJob(Func<CancellationToken, FloorLayout> analyze) { CancellationToken token = _cancellation.Token; _task = Task.Run(() => analyze(token), token); _task.ContinueWith(delegate(Task<FloorLayout> task) { _ = task.Exception; }, CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); } public bool TryComplete(out FloorLayout layout) { layout = null; if (_disposed || !_task.IsCompleted) { return false; } layout = _task.GetAwaiter().GetResult(); return true; } public void Dispose() { if (!_disposed) { _disposed = true; _cancellation.Cancel(); _cancellation.Dispose(); } } } public sealed class FloorDetector { private readonly FloorLandingDetector _landingDetector; public FloorDetector(FloorLandingDetector landingDetector) { _landingDetector = landingDetector; } public FloorLayout Detect(NavGraph graph, float spacing, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); int minimum = Math.Max(6, (int)Math.Ceiling(16f / (spacing * spacing))); List<(float, int, List<int>)> surfaces = FloorSurfaceCollector.Collect(graph, spacing, minimum, _landingDetector, cancellationToken); List<float> collection = FloorLevelSelector.Select(graph, surfaces, minimum, cancellationToken); FloorLayout floorLayout = new FloorLayout(); floorLayout.Levels.AddRange(collection); AssignNodeFloors(graph, floorLayout, cancellationToken); RegisterLandings(graph, floorLayout, surfaces, cancellationToken); return floorLayout; } private static void AssignNodeFloors(NavGraph graph, FloorLayout layout, CancellationToken cancellationToken) { foreach (NavNode node in graph.Nodes) { cancellationToken.ThrowIfCancellationRequested(); node.Floor = Nearest(layout.Levels, node.Position.y); } } private static void RegisterLandings(NavGraph graph, FloorLayout layout, List<(float height, int count, List<int> landings)> surfaces, CancellationToken cancellationToken) { foreach (var surface in surfaces) { cancellationToken.ThrowIfCancellationRequested(); foreach (int item in surface.landings) { cancellationToken.ThrowIfCancellationRequested(); NavNode navNode = graph.Nodes[item]; if (layout.Levels.Count > 0 && Math.Abs(navNode.Position.y - layout.Levels[navNode.Floor]) <= 0.4f) { layout.LandingNodes.Add(item); } } } } public static int Nearest(IReadOnlyList<float> levels, float y) { int result = 0; float num = float.MaxValue; for (int i = 0; i < levels.Count; i++) { float num2 = Math.Abs(levels[i] - y); if (num2 < num) { result = i; num = num2; } } return result; } } internal sealed class FloorFogCache : IDisposable { private FloorPlanRaster _raster; private int _revision = -1; private FloorFogMask _pending; private IEnumerator<byte> _build; public FloorFogMask Ready { get; private set; } public void Tick(FloorPlanRaster raster, Exploration exploration, FrameWorkBudget budget) { if (_raster != raster || (_build == null && _revision != exploration.Revision)) { _build?.Dispose(); _raster = raster; _revision = exploration.Revision; _pending = new FloorFogMask(raster); _build = _pending.Build(exploration).GetEnumerator(); } while (_build != null && budget.TryStartWork()) { if (!_build.MoveNext()) { _build.Dispose(); _build = null; Ready = _pending; _pending = null; } } } public void Dispose() { _build?.Dispose(); _build = null; _raster = null; FloorFogMask pending = (Ready = null); _pending = pending; _revision = -1; } } internal sealed class FloorFogMask { public FloorPlanRaster Raster { get; } public byte[] Opacity { get; } public FloorFogMask(FloorPlanRaster raster) { Raster = raster; Opacity = new byte[raster.Size * raster.Size]; } public IEnumerable<byte> Build(Exploration exploration) { int feather = Math.Max(1, Math.Min(8, (int)Math.Ceiling(0.4f / Raster.Span * (float)Raster.Size))); byte[] distance = new byte[Opacity.Length]; foreach (byte item in MeasureExploredInterior(exploration, distance, feather)) { yield return item; } foreach (byte item2 in ApplyOpacity(distance, feather)) { yield return item2; } } public bool Visible(Point point) { int num = (int)Math.Floor(Raster.X(point.x)); int num2 = (int)Math.Floor(Raster.Y(point.z)); if (num >= 0 && num2 >= 0 && num < Raster.Size && num2 < Raster.Size) { return Opacity[num2 * Raster.Size + num] < byte.MaxValue; } return false; } private IEnumerable<byte> MeasureExploredInterior(Exploration exploration, byte[] distance, int feather) { int size = Raster.Size; for (int y = 0; y < size; y++) { yield return 0; for (int i = 0; i < size; i++) { int num = y * size + i; int num2 = Raster.Owners[num] - 1; if (num2 >= 0 && exploration.Has(Raster.Graph.Nodes[num2])) { int val = ((i > 0) ? distance[num - 1] : 0); int val2 = ((y > 0) ? distance[num - size] : 0); distance[num] = (byte)Math.Min(feather, Math.Min(val, val2) + 1); } } } } private IEnumerable<byte> ApplyOpacity(byte[] distance, int feather) { int size = Raster.Size; for (int y = size - 1; y >= 0; y--) { yield return 0; for (int num = size - 1; num >= 0; num--) { int num2 = y * size + num; if (distance[num2] > 0) { int val = ((num + 1 < size) ? distance[num2 + 1] : 0); int val2 = ((y + 1 < size) ? distance[num2 + size] : 0); distance[num2] = (byte)Math.Min(distance[num2], Math.Min(val, val2) + 1); } Opacity[num2] = (byte)(255 - distance[num2] * 255 / feather); } } } } public sealed class FloorLandingDetector { public List<int> Find(NavGraph graph, IReadOnlyList<int> surface, float spacing, CancellationToken cancellationToken = default(CancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); HashSet<(int, int)> cells = IndexSurface(graph, surface, spacing, cancellationToken); return SelectLandings(graph, surface, cells, spacing, cancellationToken); } private static HashSet<(int x, int z)> IndexSurface(NavGraph graph, IReadOnlyList<int> surface, float spacing, CancellationToken cancellationToken) { HashSet<(int, int)> hashSet = new HashSet<(int, int)>(); foreach (int item in surface) { cancellationToken.ThrowIfCancellationRequested(); hashSet.Add(CellAt(graph.Nodes[item].Position, spacing)); } return hashSet; } private static List<int> SelectLandings(NavGraph graph, IReadOnlyList<int> surface, HashSet<(int x, int z)> cells, float spacing, CancellationToken cancellationToken) { List<int> list = new List<int>(); foreach (int item in surface) { cancellationToken.ThrowIfCancellationRequested(); (int, int) center = CellAt(graph.Nodes[item].Position, spacing); if (HasBroadSupport(cells, center, cancellationToken)) { list.Add(item); } } return list; } private static (int x, int z) CellAt(Point position, float spacing) { return (x: (int)Math.Round(position.x / spacing), z: (int)Math.Round(position.z / spacing)); } private static bool HasBroadSupport(HashSet<(int x, int z)> cells, (int x, int z) center, CancellationToken cancellationToken) { int num = 0; for (int i = -2; i <= 2; i++) { cancellationToken.ThrowIfCancellationRequested(); for (int j = -2; j <= 2; j++) { cancellationToken.ThrowIfCancellationRequested(); if (i * i + j * j <= 5 && cells.Contains((center.x + i, center.z + j))) { num++; if (num >= 16) { return true; } } } } return false; } } public sealed class FloorLayout { public List<float> Levels { get; } = new List<float>(); public HashSet<int> LandingNodes { get; } = new HashSet<int>(); } internal static class FloorLevelSelector { internal static List<float> Select(NavGraph graph, List<(float height, int count, List<int> landings)> surfaces, int minimum, CancellationToken cancellationToken) { List<(float height, int count)> list = (from s in surfaces group s by (int)Math.Round(s.height * 4f) into g select (height: (float)g.Key / 4f, count: g.Sum(((float height, int count, List<int> landings) s) => s.count)) into g orderby g.count descending select g).ToList(); List<float> list2 = new List<float>(); foreach (var bin in list) { cancellationToken.ThrowIfCancellationRequested(); if (bin.count >= minimum && list2.All((float h) => Math.Abs(h - bin.height) > 2f)) { list2.Add(bin.height); } } if (list2.Count == 0 && graph.Nodes.Count > 0) { list2.Add(graph.Nodes.OrderBy((NavNode n) => n.Position.y).ElementAt(graph.Nodes.Count / 2).Position.y); } list2.Sort(); return list2; } } public sealed class FloorNavigationPolicy { public int Next(FloorLayout layout, float sourceHeight, int direction) { if (direction > 0) { for (int i = 0; i < layout.Levels.Count; i++) { if (layout.Levels[i] > sourceHeight + 0.75f) { return i; } } } else { for (int num = layout.Levels.Count - 1; num >= 0; num--) { if (layout.Levels[num] < sourceHeight - 0.75f) { return num; } } } return -1; } public bool IsDestination(NavGraph graph, FloorLayout layout, int node, int floor) { if (node >= 0 && node < graph.Nodes.Count && floor >= 0 && floor < layout.Levels.Count && layout.LandingNodes.Contains(node) && graph.Nodes[node].Flat && graph.Nodes[node].Floor == floor) { return Math.Abs(graph.Nodes[node].Position.y - layout.Levels[floor]) <= 0.4f; } return false; } public int Resolve(FloorLayout layout, float targetHeight) { if (layout.Levels.Count == 0) { return -1; } int num = FloorDetector.Nearest(layout.Levels, targetHeight); if (!(Math.Abs(layout.Levels[num] - targetHeight) <= 0.75f)) { return -1; } return num; } } public sealed class FloorNumbering { private readonly GuideText _text; public FloorNumbering(Func<string> selectedLanguage = null) { _text = new GuideText(selectedLanguage); } public string Label(IReadOnlyList<float> levels, int floor, float? entranceHeight) { if (levels.Count == 0 || floor < 0 || floor >= levels.Count) { return _text.FloorUnknown; } if (!entranceHeight.HasValue) { return _text.FloorPrefix + floor + _text.BottomUp; } int num = floor - FloorDetector.Nearest(levels, entranceHeight.Value); if (num != 0) { if (num <= 0) { return _text.FloorNegative + -num; } return _text.FloorPositive + num; } return _text.EntranceFloor; } } internal sealed class FloorPlanBuilder { public IEnumerable<byte> Build(NavGraph graph, float spacing, FloorPlanRaster raster) { raster.Graph = graph; foreach (byte item in RasterizeCoverage(graph, spacing, raster)) { yield return item; } foreach (byte item2 in TraceBoundaries(raster)) { yield return item2; } foreach (byte item3 in new StairMarkerGrouping(graph, raster).Build()) { yield return item3; } } private static bool Wall(FloorPlanRaster raster, int owner, int x, int y) { if (x < 0 || y < 0 || x >= raster.Size || y >= raster.Size) { return true; } int num = raster.Owners[y * raster.Size + x]; if (num != 0) { if (num != owner) { return !raster.Connections.Contains((owner, num)); } return false; } return true; } private static IEnumerable<byte> Box(FloorPlanRaster raster, Point a, Point b, float half, int first, int second) { int x0 = Math.Max(0, (int)Math.Ceiling(raster.X(Math.Min(a.x, b.x) - half) - 0.5f)); int x1 = Math.Min(raster.Size - 1, (int)Math.Floor(raster.X(Math.Max(a.x, b.x) + half) - 0.5f)); int num = Math.Max(0, (int)Math.Ceiling(raster.Y(Math.Min(a.z, b.z) - half) - 0.5f)); int y1 = Math.Min(raster.Size - 1, (int)Math.Floor(raster.Y(Math.Max(a.z, b.z) + half) - 0.5f)); float ax = raster.X(a.x); float ay = raster.Y(a.z); float bx = raster.X(b.x); float by = raster.Y(b.z); for (int i = num; i <= y1; i++) { yield return 0; for (int j = x0; j <= x1; j++) { float num2 = ((float)j + 0.5f - ax) * ((float)j + 0.5f - ax) + ((float)i + 0.5f - ay) * ((float)i + 0.5f - ay); float num3 = ((float)j + 0.5f - bx) * ((float)j + 0.5f - bx) + ((float)i + 0.5f - by) * ((float)i + 0.5f - by); raster.Owners[i * raster.Size + j] = ((num2 <= num3) ? first : second) + 1; } } } private static IEnumerable<byte> RasterizeCoverage(NavGraph graph, float spacing, FloorPlanRaster raster) { float half = spacing * 0.44f; for (int id = 0; id < graph.Nodes.Count; id++) { yield return 0; NavNode navNode = graph.Nodes[id]; if (navNode.Floor != raster.Floor) { continue; } foreach (byte item in Box(raster, navNode.Position, navNode.Position, half, id, id)) { yield return item; } foreach (byte item2 in RasterizeConnections(graph, spacing, raster, id, half)) { yield return item2; } } } private static IEnumerable<byte> RasterizeConnections(NavGraph graph, float spacing, FloorPlanRaster raster, int id, float half) { NavNode node = graph.Nodes[id]; bool feature = false; foreach (NavEdge edge in node.Edges) { NavNode navNode = graph.Nodes[edge.To]; feature |= navNode.Floor != node.Floor || StairMarkerPolicy.IsTransition(node, navNode, edge) || edge.Kind == PassageKind.Breakable || edge.Kind == PassageKind.Jump || edge.Kind == PassageKind.Drop; if (!CanBridge(node, navNode, edge, spacing)) { continue; } raster.Connections.Add((id + 1, edge.To + 1)); raster.Connections.Add((edge.To + 1, id + 1)); if (id >= edge.To) { continue; } foreach (byte item in Box(raster, node.Position, navNode.Position, half, id, edge.To)) { yield return item; } } if (feature) { raster.Features.Add(id); } } private static IEnumerable<byte> TraceBoundaries(FloorPlanRaster raster) { for (int y = 0; y < raster.Size; y++) { yield return 0; for (int i = 0; i < raster.Size; i++) { int num = y * raster.Size + i; int num2 = raster.Owners[num]; raster.Boundary[num] = num2 != 0 && (Wall(raster, num2, i - 1, y) || Wall(raster, num2, i + 1, y) || Wall(raster, num2, i, y - 1) || Wall(raster, num2, i, y + 1)); } } } private static bool CanBridge(NavNode node, NavNode other, NavEdge edge, float spacing) { float num = Math.Abs(other.Position.x - node.Position.x); float num2 = Math.Abs(other.Position.z - node.Position.z); if (other.Floor == node.Floor && edge.Verified && edge.Kind == PassageKind.Walk && !(num + num2 > spacing * 1.1f) && !(num + num2 < spacing * 0.5f)) { return !(Math.Min(num, num2) > spacing * 0.1f); } return false; } } internal sealed class FloorPlanCache : IDisposable { private NavGraph _graph; private int _floor = -1; private int _size; private float _spacing; private float _span; private Point _center; private FloorPlanRaster _pending; private IEnumerator<byte> _build; public FloorPlanRaster Ready { get; private set; } public void Tick(NavGraph graph, int floor, float spacing, int size, Point center, float span, FrameWorkBudget budget) { if (_graph != graph || _floor != floor || _size != size || _spacing != spacing || _span != span || _center.x != center.x || _center.z != center.z) { _build?.Dispose(); _graph = graph; _floor = floor; _size = size; _spacing = spacing; _span = span; _center = center; _pending = new FloorPlanRaster(size, floor, center, span); _build = new FloorPlanBuilder().Build(graph, spacing, _pending).GetEnumerator(); } while (_build != null && budget.TryStartWork()) { if (!_build.MoveNext()) { _build.Dispose(); _build = null; Ready = _pending; _pending = null; } } } public void Dispose() { _build?.Dispose(); _build = null; _graph = null; FloorPlanRaster pending = (Ready = null); _pending = pending; _floor = -1; } } internal sealed class FloorPlanRaster { public NavGraph Graph { get; internal set; } public int Size { get; } public int Floor { get; } public Point Center { get; } public float Span { get; } public int[] Owners { get; } public bool[] Boundary { get; } public List<int> Features { get; } = new List<int>(); public List<StairMarkerGroup> StairGroups { get; } = new List<StairMarkerGroup>(); public HashSet<(int, int)> Connections { get; } = new HashSet<(int, int)>(); public FloorPlanRaster(int size, int floor, Point center, float span) { if (size < 2 || !(span > 0f)) { throw new ArgumentOutOfRangeException("size"); } Size = size; Floor = floor; Center = center; Span = span; Owners = new int[size * size]; Boundary = new bool[size * size]; } public float X(float x) { return ((x - Center.x) / Span + 0.5f) * (float)Size; } public float Y(float z) { return ((z - Center.z) / Span + 0.5f) * (float)Size; } public int NodeAt(Point point) { int num = (int)Math.Floor(X(point.x)); int num2 = (int)Math.Floor(Y(point.z)); if (num >= 0 && num2 >= 0 && num < Size && num2 < Size) { return Owners[num2 * Size + num] - 1; } return -1; } } internal sealed class FloorPrefetchScheduler { private int _priorityCursor; private int _refreshCursor; private bool _refreshNext; public bool TrySelect(IReadOnlyList<int> targets, int priorityCount, bool foregroundReady, Func<int, bool> isReady, FrameWorkBudget budget, out int floor) { floor = -1; if (!foregroundReady || targets.Count <= 1 || !budget.TryStartWork()) { return false; } int num = Math.Min(targets.Count, Math.Max(1, priorityCount)); if (TryPriority(targets, num, isReady, out floor)) { return true; } return TryDistantOrRefresh(targets, num, isReady, out floor); } private bool TryPriority(IReadOnlyList<int> targets, int end, Func<int, bool> isReady, out int floor) { int num = end - 1; for (int i = 0; i < num; i++) { int num2 = (_priorityCursor + i) % num; if (!isReady(targets[num2 + 1])) { _priorityCursor = (num2 + 1) % num; floor = targets[num2 + 1]; return true; } } floor = -1; return false; } private bool TryDistantOrRefresh(IReadOnlyList<int> targets, int start, Func<int, bool> isReady, out int floor) { if (_refreshNext && TryRefresh(targets, isReady, out floor)) { _refreshNext = false; return true; } if (TryDistant(targets, start, isReady, out floor)) { _refreshNext = true; return true; } return TryRefresh(targets, isReady, out floor); } private static bool TryDistant(IReadOnlyList<int> targets, int start, Func<int, bool> isReady, out int floor) { for (int i = start; i < targets.Count; i++) { if (!isReady(targets[i])) { floor = targets[i]; return true; } } floor = -1; return false; } private bool TryRefresh(IReadOnlyList<int> targets, Func<int, bool> isReady, out int floor) { int num = targets.Count - 1; for (int i = 0; i < num; i++) { int num2 = (_refreshCursor + i) % num; if (isReady(targets[num2 + 1])) { _refreshCursor = (num2 + 1) % num; floor = targets[num2 + 1]; return true; } } floor = -1; return false; } public void Reset() { _priorityCursor = (_refreshCursor = 0); _refreshNext = false; } } internal static class FloorPreloadPolicy { public static int Fill(List<int> target, int physicalFloor, int selectedFloor, int floorCount) { target.Clear(); AddValid(target, selectedFloor, floorCount); bool flag = physicalFloor >= 0 && physicalFloor < floorCount; if (flag) { AddNeighborhood(target, physicalFloor, floorCount); } int count = target.Count; if (count > 0) { AddRemaining(target, flag ? physicalFloor : selectedFloor, floorCount); } return count; } private static void AddNeighborhood(List<int> target, int floor, int count) { AddValid(target, floor, count); AddValid(target, floor + 1, count); AddValid(target, floor - 1, count); } private static void AddRemaining(List<int> target, int center, int count) { for (int i = 1; i < count; i++) { AddValid(target, center + i, count); AddValid(target, center - i, count); } } private static void AddValid(List<int> target, int floor, int count) { if (floor >= 0 && floor < count && !target.Contains(floor)) { target.Add(floor); } } } internal sealed class FloorResourceCache<T> : IDisposable where T : class, IDisposable { private readonly Dictionary<(NavGraph Graph, int Floor), T> _resources = new Dictionary<(NavGraph, int), T>(); private readonly List<(NavGraph Graph, int Floor)> _expired = new List<(NavGraph, int)>(); public int Count => _resources.Count; public IEnumerable<T> Resources => _resources.Values; public bool TryGet(NavGraph graph, int floor, out T resource) { return _resources.TryGetValue((graph, floor), out resource); } public void Add(NavGraph graph, int floor, T resource) { _resources.Add((graph, floor), resource); } public void Retain(NavGraph graph, IReadOnlyList<int> floors, T displayed) { CollectExpired(graph, floors, displayed); DisposeExpired(); } private void CollectExpired(NavGraph graph, IReadOnlyList<int> floors, T displayed) { _expired.Clear(); foreach (KeyValuePair<(NavGraph, int), T> resource in _resources) { if ((resource.Key.Item1 != graph || !Contains(floors, resource.Key.Item2)) && resource.Value != displayed) { _expired.Add(resource.Key); } } } private static bool Contains(IReadOnlyList<int> floors, int floor) { for (int i = 0; i < floors.Count; i++) { if (floors[i] == floor) { return true; } } return false; } private void DisposeExpired() { foreach (var item in _expired) { _resources[item].Dispose(); _resources.Remove(item); } _expired.Clear(); } public void Dispose() { foreach (T value in _resources.Values) { value.Dispose(); } _resources.Clear(); _expired.Clear(); } } internal static class FloorSurfaceCollector { internal static List<(float height, int count, List<int> landings)> Collect(NavGraph graph, float spacing, int minimum, FloorLandingDetector landingDetector, CancellationToken cancellationToken) { List<(float, int, List<int>)> list = new List<(float, int, List<int>)>(); HashSet<int> hashSet = new HashSet<int>(); for (int i = 0; i < graph.Nodes.Count; i++) { cancellationToken.ThrowIfCancellationRequested(); if (!graph.Nodes[i].Flat || !hashSet.Add(i)) { continue; } List<int> list2 = CollectSurface(graph, i, hashSet, cancellationToken); if (list2.Count >= minimum) { List<int> list3 = landingDetector.Find(graph, list2, spacing, cancellationToken); if (list3.Count >= 4) { list.Add((AverageHeight(graph, list2, cancellationToken), list2.Count, list3)); } } } return list; } private static List<int> CollectSurface(NavGraph graph, int start, HashSet<int> seen, CancellationToken cancellationToken) { float y = graph.Nodes[start].Position.y; List<int> list = new List<int>(); Queue<int> queue = new Queue<int>(); queue.Enqueue(start); while (queue.Count > 0) { cancellationToken.ThrowIfCancellationRequested(); int num = queue.Dequeue(); list.Add(num); foreach (NavEdge edge in graph.Nodes[num].Edges) { cancellationToken.ThrowIfCancellationRequested(); NavNode navNode = graph.Nodes[edge.To]; if (edge.Verified && (edge.Kind == PassageKind.Walk || edge.Kind == PassageKind.Stairs) && navNode.Flat && Math.Abs(navNode.Position.y - y) < 0.35f && seen.Add(edge.To)) { queue.Enqueue(edge.To); } } } return list; } private static float AverageHeight(NavGraph graph, List<int> members, CancellationToken cancellationToken) { float num = 0f; foreach (int member in members) { cancellationToken.ThrowIfCancellationRequested(); num += graph.Nodes[member].Position.y; } return num / (float)members.Count; } } internal readonly struct FogTileRegion { private readonly int _x; private readonly int _y; private readonly int _width; private readonly int _height; private readonly int _halo; public int X => _x; public int Y => _y; public int Width => _width; public int Height => _height; public int TotalTiles { get; } public int Stride => _width + _halo * 2; public int Rows => _height + _halo * 2; public FogTileRegion(int size, int tile, int tileSize, int halo) { int num = (size + tileSize - 1) / tileSize; _x = tile % num * tileSize; _y = tile / num * tileSize; _width = Math.Min(tileSize, size - _x); _height = Math.Min(tileSize, size - _y); _halo = halo; TotalTiles = num * num; } public int WorldX(int x) { return _x + x - _halo; } public int WorldY(int y) { return _y + y - _halo; } public bool ContainsInterior(int x, int y) { if (x >= _halo && x < _halo + _width && y >= _halo) { return y < _halo + _height; } return false; } } internal sealed class FrameWorkBudget { private readonly int _maximumSteps; private readonly double _maximumMilliseconds; private readonly Func<double> _clock; private readonly Func<int> _frame; private readonly FrameWorkBudget _parent; private int _lastFrame = -1; private double _started; private double _elapsed; private bool _running = true; private int _steps; public long TotalSteps { get; private set; } public FrameWorkBudget(int maximumSteps, double maximumMilliseconds, Func<double> clock = null, Func<int> frame = null) : this(maximumSteps, maximumMilliseconds, clock, frame, null) { } private FrameWorkBudget(int maximumSteps, double maximumMilliseconds, Func<double> clock, Func<int> frame, FrameWorkBudget parent) { if (maximumSteps < 1) { throw new ArgumentOutOfRangeException("maximumSteps"); } if (!(maximumMilliseconds > 0.0) || double.IsInfinity(maximumMilliseconds)) { throw new ArgumentOutOfRangeException("maximumMilliseconds"); } _frame = frame; _parent = parent; _maximumSteps = maximumSteps; _maximumMilliseconds = maximumMilliseconds; _clock = clock ?? ((Func<double>)(() => (double)Stopwatch.GetTimestamp() * (1000.0 / (double)Stopwatch.Frequency))); BeginFrame(); } public FrameWorkBudget CreateSlice(double fraction) { if (!(fraction > 0.0) || !(fraction <= 1.0)) { throw new ArgumentOutOfRangeException("fraction"); } return new FrameWorkBudget(Math.Max(1, (int)((double)_maximumSteps * fraction)), _maximumMilliseconds * fraction, _clock, null, this); } public void BeginFrame() { if (_frame != null) { int num = _frame(); if (_lastFrame == num) { return; } _lastFrame = num; } _started = _clock(); _elapsed = 0.0; _steps = 0; } public void Pause() { if (_running) { _elapsed += _clock() - _started; _running = false; } } public void Resume() { if (!_running) { _started = _clock(); _running = true; } } public bool TryStartWork() { if (_steps >= _maximumSteps || _elapsed + (_running ? (_clock() - _started) : 0.0) >= _maximumMilliseconds) { return false; } if (_parent != null && !_parent.TryStartWork()) { return false; } _steps++; TotalSteps++; return true; } } internal static class GeometryCacheFormat { internal const int Version = 1; internal const int MaxNodes = 250000; internal const int MaxEdges = 4000000; internal const int MaxFloors = 4096; internal const int MaxBytes = 67108864; internal static void WriteText(BinaryWriter writer, string value) { byte[] bytes = Encoding.UTF8.GetBytes(value ?? ""); Count(bytes.Length, 4096); writer.Write(bytes.Length); writer.Write(bytes); } internal static string ReadText(BinaryReader reader) { int num = Count(reader.ReadInt32(), 4096); byte[] array = reader.ReadBytes(num); if (array.Length != num) { throw new EndOfStreamException("Incomplete geometry cache text."); } return new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true).GetString(array); } internal static int Count(int count, int limit) { if (count < 0 || count > limit) { throw new InvalidDataException("Geometry cache count exceeds its supported limits."); } return count; } internal static void WritePoint(BinaryWriter writer, Point point) { writer.Write(point.x); writer.Write(point.y); writer.Write(point.z); } internal static Point ReadPoint(BinaryReader reader) { return new Point(Finite(reader.ReadSingle()), Finite(reader.ReadSingle()), Finite(reader.ReadSingle())); } internal static float Finite(float value) { if (float.IsNaN(value) || float.IsInfinity(value)) { throw new InvalidDataException("Geometry cache contains non-finite coordinates."); } return value; } internal static int Node(int node, int count) { if (node < 0 || node >= count) { throw new InvalidDataException("Geometry cache contains an invalid node reference."); } return node; } } internal static class GeometryCacheReader { public static CachedGeometry Read(Stream stream, string key, CancellationToken cancellation = default(CancellationToken)) { using BinaryReader binaryReader = new BinaryReader(stream); string text = GeometryCacheFormat.ReadText(binaryReader); int num = binaryReader.ReadInt32(); if (text != "ValheimDungeonGeometry" || num < 1) { throw new InvalidDataException("Unsupported dungeon geometry cache format."); } if (num > 1) { throw new NotSupportedException("The geometry cache requires a newer plugin; preserve the file."); } if (GeometryCacheFormat.ReadText(binaryReader) != key) { throw new InvalidDataException("Dungeon geometry cache identity does not match."); } CachedGeometry cachedGeometry = ReadHeader(binaryReader); int num2 = GeometryCacheFormat.Count(binaryReader.ReadInt32(), 250000); ReadLayout(binaryReader, cachedGeometry.Layout, num2); ReadGraph(binaryReader, cachedGeometry, num2, cancellation); if (stream.Position != stream.Length) { throw new InvalidDataException("Dungeon geometry cache has unexpected trailing data."); } return cachedGeometry; } private static CachedGeometry ReadHeader(BinaryReader reader) { float num = GeometryCacheFormat.Finite(reader.ReadSingle()); if (num < 0.75f || num > 3f) { throw new InvalidDataException("Unsupported cached scan spacing."); } CachedGeometry cachedGeometry = new CachedGeometry { Spacing = num, Complete = reader.ReadBoolean(), Truncated = reader.ReadBoolean() }; if (reader.ReadBoolean()) { cachedGeometry.Entrance = GeometryCacheFormat.ReadPoint(reader); } return cachedGeometry; } private static void ReadLayout(BinaryReader reader, FloorLayout layout, int nodes) { int num = GeometryCacheFormat.Count(reader.ReadInt32(), 4096); if (num == 0 && nodes > 0) { throw new InvalidDataException("Cached nodes have no floor layout."); } for (int i = 0; i < num; i++) { float num2 = GeometryCacheFormat.Finite(reader.ReadSingle()); if (i > 0 && num2 < layout.Levels[i - 1]) { throw new InvalidDataException("Cached floor levels are not ordered by elevation."); } layout.Levels.Add(num2); } ReadLandings(reader, layout, nodes); } private static void ReadLandings(BinaryReader reader, FloorLayout layout, int nodes) { int num = GeometryCacheFormat.Count(reader.ReadInt32(), nodes); for (int i = 0; i < num; i++) { if (!layout.LandingNodes.Add(GeometryCacheFormat.Node(reader.ReadInt32(), nodes))) { throw new InvalidDataException("Duplicate cached floor landing."); } } } private static void ReadGraph(BinaryReader reader, CachedGeometry geometry, int count, CancellationToken cancellation) { int num = 0; for (int i = 0; i < count; i++) { cancellation.ThrowIfCancellationRequested(); NavNode navNode = new NavNode(GeometryCacheFormat.ReadPoint(reader), reader.ReadBoolean()) { Floor = GeometryCacheFormat.Node(reader.ReadInt32(), geometry.Layout.Levels.Count) }; int num2 = GeometryCacheFormat.Count(reader.ReadInt32(), 4000000 - num); num += num2; ReadEdges(reader, navNode, i, num2, count, cancellation); geometry.Graph.Nodes.Add(navNode); } } private static void ReadEdges(BinaryReader reader, NavNode node, int source, int count, int nodes, CancellationToken cancellation) { HashSet<int> hashSet = new HashSet<int>(); for (int i = 0; i < count; i++) { cancellation.ThrowIfCancellationRequested(); int num = GeometryCacheFormat.Node(reader.ReadInt32(), nodes); if (num == source || !hashSet.Add(num)) { throw new InvalidDataException("Duplicate or self-referencing cached passage."); } PassageKind passageKind = (PassageKind)reader.ReadByte(); if (passageKind < PassageKind.Walk || passageKind > PassageKind.Drop) { throw new InvalidDataException("Unknown cached passage kind."); } bool verified = reader.ReadBoolean() && (passageKind == PassageKind.Walk || passageKind == PassageKind.Stairs); string text = GeometryCacheFormat.ReadText(reader); node.Edges.Add(new NavEdge(num, passageKind, verified, (text.Length == 0) ? null : text)); } } } internal sealed class GeometryCacheWriter : IDisposable { private readonly MemoryStream _stream = new MemoryStream(); private readonly BinaryWriter _writer; private int _edges; public byte[] Payload => _stream.ToArray(); public long Length => _stream.Length; public GeometryCacheWriter() { _writer = new BinaryWriter(_stream); } public IEnumerable<byte> Encode(string key, CachedGeometry geometry) { WriteHeader(key, geometry); foreach (byte item in WriteLevels(geometry)) { yield return item; } foreach (byte item2 in WriteLandings(geometry)) { yield return item2; } foreach (byte item3 in WriteGraph(geometry)) { yield return item3; } _writer.Flush(); } private IEnumerable<byte> WriteLevels(CachedGeometry geometry) { foreach (float level in geometry.Layout.Levels) { _writer.Write(level); yield return 0; } } private IEnumerable<byte> WriteLandings(CachedGeometry geometry) { _writer.Write(geometry.Layout.LandingNodes.Count); foreach (int landingNode in geometry.Layout.LandingNodes) { _writer.Write(landingNode); yield return 0; } } private IEnumerable<byte> WriteGraph(CachedGeometry geometry) { foreach (NavNode node in geometry.Graph.Nodes) { foreach (byte item in WriteNode(node)) { yield return item; } } } private void WriteHeader(string key, CachedGeometry geometry) { GeometryCacheFormat.WriteText(_writer, "ValheimDungeonGeometry"); _writer.Write(1); GeometryCacheFormat.WriteText(_writer, key); _writer.Write(geometry.Spacing); _writer.Write(geometry.Complete); _writer.Write(geometry.Truncated); _writer.Write(geometry.Entrance.HasValue); if (geometry.Entrance.HasValue) { GeometryCacheFormat.WritePoint(_writer, geometry.Entrance.Value); } _writer.Write(GeometryCacheFormat.Count(geometry.Graph.Nodes.Count, 250000)); _writer.Write(GeometryCacheFormat.Count(geometry.Layout.Levels.Count, 4096)); } private IEnumerable<byte> WriteNode(NavNode node) { GeometryCacheFormat.WritePoint(_writer, node.Position); _writer.Write(node.Flat); _writer.Write(node.Floor); NavEdge[] edges = node.Edges.Select((NavEdge edge) => new NavEdge(edge.To, edge.Kind, edge.Verified, edge.Obstacle)).ToArray(); _edges = GeometryCacheFormat.Count(_edges + edges.Length, 4000000); _writer.Write(edges.Length); yield return 0; NavEdge[] array = edges; foreach (NavEdge navEdge in array) { _writer.Write(navEdge.To); _writer.Write((byte)navEdge.Kind); _writer.Write(navEdge.Verified && (navEdge.Kind == PassageKind.Walk || navEdge.Kind == PassageKind.Stairs)); GeometryCacheFormat.WriteText(_writer, navEdge.Obstacle); yield return 0; } } public void Dispose() { _writer.Dispose(); } } internal sealed class GrappleCandidateIndex { private readonly SortedSet<(float Distance, int Node)> _sources = new SortedSet<(float, int)>(); private Point _landingMin; private Point _landingMax; private Point _goalMin; private Point _goalMax; public Dictionary<(int, int), List<int>> Columns { get; } = new Dictionary<(int, int), List<int>>(); public IEnumerable<int> Sources => _sources.Select(((float Distance, int Node) source) => source.Node); public IEnumerable<object> Build(NavGraph graph, GrappleRouteTopology topology, float spacing, float range, RoutePolicy policy, FrameWorkBudget work) { _landingMin = (_goalMin = new Point(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity)); _landingMax = (_goalMax = new Point(float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity)); foreach (object item in ReadGoalBounds(graph, topology, work)) { yield return item; } foreach (object item2 in IndexLandings(graph, topology, spacing, policy, work)) { yield return item2; } foreach (object item3 in RankLaunches(graph, topology, spacing, range, policy, work)) { yield return item3; } } private IEnumerable<object> ReadGoalBounds(NavGraph graph, GrappleRouteTopology topology, FrameWorkBudget work) { foreach (int node in topology.Goals) { while (!work.TryStartWork()) { yield return null; work.BeginFrame(); } Expand(graph.Nodes[node].Position, ref _goalMin, ref _goalMax); } } private IEnumerable<object> IndexLandings(NavGraph graph, GrappleRouteTopology topology, float spacing, RoutePolicy policy, FrameWorkBudget work) { Predicate<int> member = topology.IsLanding; foreach (int node in topology.Landings) { while (!work.TryStartWork()) { yield return null; work.BeginFrame(); } if (GrappleLaunchBoundary.Contains(graph, node, spacing, policy, member)) { IndexLanding(graph.Nodes[node].Position, node, spacing); } } } private IEnumerable<object> RankLaunches(NavGraph graph, GrappleRouteTopology topology, float spacing, float range, RoutePolicy policy, FrameWorkBudget work) { Predicate<int> member = topology.IsSource; foreach (int node in topology.Sources) { while (!work.TryStartWork()) { yield return null; work.BeginFrame(); } Point position = graph.Nodes[node].Position; if (InRange(position, range) && GrappleLaunchBoundary.Contains(graph, node, spacing, policy, member)) { _sources.Add((DistanceSquared(position, _goalMin, _goalMax), node)); } } } private bool InRange(Point source, float range) { return DistanceSquared(source, _landingMin, _landingMax) <= range * range; } private void IndexLanding(Point position, int node, float spacing) { (int, int) key = ((int)Math.Round(position.x / spacing), (int)Math.Round(position.z / spacing)); if (!Columns.TryGetValue(key, out var value)) { value = new List<int>(); Columns.Add(key, value); } value.Add(node); Expand(position, ref _landingMin, ref _landingMax); } private static void Expand(Point point, ref Point min, ref Point max) { min = new Point(Math.Min(min.x, point.x), Math.Min(min.y, point.y), Math.Min(min.z, point.z)); max = new Point(Math.Max(max.x, point.x), Math.Max(max.y, point.y), Math.Max(max.z, point.z)); } private static float DistanceSquared(Point point, Point min, Point max) { float num = Math.Max(0f, Math.Max(min.x - point.x, point.x - max.x)); float num2 = Math.Max(0f, Math.Max(min.y - point.y, point.y - max.y)); float num3 = Math.Max(0f, Math.Max(min.z - point.z, point.z - max.z)); return num * num + num2 * num2 + num3 * num3; } } internal static class GrappleLaunchBoundary { public static bool Contains(NavGraph graph, int source, float spacing, RoutePolicy policy, Predicate<int> member) { Point position = graph.Nodes[source].Position; int num = 0; foreach (NavEdge edge in graph.Nodes[source].Edges) { if (!member(edge.To) || !policy.Allows(edge) || edge.Kind == PassageKind.Jump || edge.Kind == PassageKind.Drop) { continue; } Point position2 = graph.Nodes[edge.To].Position; int num2 = (int)Math.Round((position2.x - position.x) / spacing); int num3 = (int)Math.Round((position2.z - position.z) / spacing); if (!(Math.Abs(position2.y - position.y) <= spacing) || Math.Abs(num2) + Math.Abs(num3) != 1) { continue; } int num4 = num; int num5; switch (num2) { case -1: if (num3 != 0) { goto default; } num5 = 1; break; case 1: if (num3 != 0) { goto default; } num5 = 2; break; case 0: if (num3 != -1) { if (num3 != 1) { goto default; } num5 = 8; break; } num5 = 4; break; default: num5 = 0; break; } num = num4 | num5; } return num != 15; } } internal sealed class GrappleRouteTopology { private readonly Dictionary<int, int> _fromStart = new Dictionary<int, int>(); private readonly Dictionary<int, int> _towardGoal = new Dictionary<int, int>(); public List<int> Goals { get; } = new List<int>(); public IEnumerable<int> Sources => _fromStart.Keys; public IEnumerable<int> Landings { get { foreach (int key in _towardGoal.Keys) { if (IsLanding(key)) { yield return key; } } } } public bool HasLandings { get; private set; } public bool IsSource(int node) { return _fromStart.ContainsKey(node); } public bool IsLanding(int node) { if (!_fromStart.ContainsKey(node)) { return _towardGoal.ContainsKey(node); } return false; } public IEnumerable<object> Build(NavGraph graph, int start, Predicate<int> target, RoutePolicy policy, FrameWorkBudget work) { Dictionary<int, List<int>> reverse = new Dictionary<int, List<int>>(); foreach (object item in IndexIncoming(graph, policy, reverse, work)) { yield return item; } foreach (object item2 in Traverse(new int[1] { start }, _fromStart, (int n) => Outgoing(graph, n, policy), work)) { yield return item2; } List<int> goals = Goals; foreach (object item3 in CollectGoals(graph.Nodes.Count, target, goals, work)) { yield return item3; } if (!HasLandings) { yield break; } List<int> value; foreach (object item4 in Traverse(goals, _towardGoal, (int n) => (!reverse.TryGetValue(n, out value)) ? ((IEnumerable<int>)Array.Empty<int>()) : ((IEnumerable<int>)value), work)) { yield return item4; } } public List<int> Join(int launch, int landing) { List<int> list = new List<int>(); if (!_fromStart.ContainsKey(launch) || !IsLanding(landing)) { return list; } AppendChain(list, _fromStart, launch); list.Reverse(); AppendChain(list, _towardGoal, landing); return list; } private IEnumerable<object> CollectGoals(int count, Predicate<int> target, List<int> goals, FrameWorkBudget work) { for (int i = 0; i < count; i++) { while (!work.TryStartWork()) { yield return null; work.BeginFrame(); } if (target(i)) { goals.Add(i); HasLandings |= !_fromStart.ContainsKey(i); } } } private static void AppendChain(List<int> route, Dictionary<int, int> parents, int node) { while (node >= 0) { route.Add(node); node = parents[node]; } } private static IEnumerable<int> Outgoing(NavGraph graph, int node, RoutePolicy policy) { foreach (NavEdge edge in graph.Nodes[node].Edges) { if (policy.Allows(edge)) { yield return edge.To; } } } private static IEnumerable<object> IndexIncoming(NavGraph graph, RoutePolicy policy, Dictionary<int, List<int>> reverse, FrameWorkBudget work) { for (int source = 0; source < graph.Nodes.Count; source++) { while (!work.TryStartWork()) { yield return null; work.BeginFrame(); } foreach (NavEdge edge in graph.Nodes[source].Edges) { while (!work.TryStartWork()) { yield return null; work.BeginFrame(); } if (policy.Allows(edge)) { if (!reverse.TryGetValue(edge.To, out var value)) { value = new List<int>(); reverse.Add(edge.To, value); } value.Add(source); } } } } private static IEnumerable<object> Traverse(IEnumerable<int> roots, Dictionary<int, int> parents, Func<int, IEnumerable<int>> adjacent, FrameWorkBudget work) { Queue<int> queue = new Queue<int>(); foreach (int root in roots) { while (!work.TryStartWork()) { yield return null; work.BeginFrame(); } parents[root] = -1; queue.Enqueue(root); } while (queue.Count > 0) { int root = queue.Dequeue(); foreach (int next in adjacent(root)) { while (!work.TryStartWork()) { yield return null; work.BeginFrame(); } if (!parents.ContainsKey(next)) { parents.Add(next, root); queue.Enqueue(next); } } } } } internal readonly struct GrappleShot { private readonly Point _velocity; private readonly float _gravity; private readonly float _duration; private GrappleShot(Point velocity, float gravity, float duration) { _velocity = velocity; _gravity = gravity; _duration = duration; } public Point At(float progress) { float num = _duration * progress; return new Point(_velocity.x * num, _velocity.y * num - 0.5f * _gravity * num * num, _velocity.z * num); } public static bool TryCreate(Point delta, float speed, float gravity, float lifetime, out GrappleShot shot) { shot = default(GrappleShot); if (!Finite(delta.x) || !Finite(delta.y) || !Finite(delta.z) || !Finite(speed) || speed <= 0f || !Finite(gravity) || gravity < 0f || !Finite(lifetime) || lifetime < 0f) { return false; } float num = Duration((double)delta.x * (double)delta.x + (double)delta.y * (double)delta.y + (double)delta.z * (double)delta.z, delta.y, speed, gravity); if (!(num > 0f) || !Finite(num) || (lifetime > 0f && num > lifetime)) { return false; } Point velocity = new Point(delta.x / num, delta.y / num + 0.5f * gravity * num, delta.z / num); shot = new GrappleShot(velocity, gravity, num); return true; } private static float Duration(double squaredDistance, float rise, float speed, float gravity) { double num = (double)speed * (double)speed - (double)(gravity * rise); double num2 = num * num - (double)gravity * (double)gravity * squaredDistance; if (num <= 0.0 || num2 < 0.0) { return 0f; } return (float)Math.Sqrt(2.0 * squaredDistance / (num + Math.Sqrt(num2))); } private static bool Finite(float value) { if (!float.IsNaN(value)) { return !float.IsInfinity(value); } return false; } } internal sealed class IncrementalFloorFog { internal const int TileSize = 32; private readonly FloorPlanRaster _raster; private readonly Dictionary<string, HashSet<int>> _tiles = new Dictionary<string, HashSet<int>>(); private readonly Queue<int> _dirty = new Queue<int>(); private readonly HashSet<int> _queued = new HashSet<int>(); private readonly int _columns; private readonly int _feather; private int _cursor; private int _generation = -1; public FloorFogMask Fog { get; } public IncrementalFloorFog(FloorPlanRaster raster) { _raster = raster; _columns = (raster.Size + 32 - 1) / 32; _feather = Math.Max(1, Math.Min(8, (int)Math.Ceiling(0.4f / raster.Span * (float)raster.Size))); Fog = new FloorFogMask(raster); for (int i = 0; i < Fog.Opacity.Length; i++) { Fog.Opacity[i] = byte.MaxValue; } } public IEnumerable<byte> Initialize() { for (int y = 0; y < _raster.Size; y++) { yield return 0; for (int i = 0; i < _raster.Size; i++) { int num = _raster.Owners[y * _raster.Size + i] - 1; if (num < 0) { continue; } string key = _raster.Graph.Nodes[num].Key; if (!_tiles.TryGetValue(key, out var value)) { value = new HashSet<int>(); _tiles.Add(key, value); } for (int j = Math.Max(0, y - _feather) / 32; j <= Math.Min(_raster.Size - 1, y + _feather) / 32; j++) { for (int k = Math.Max(0, i - _feather) / 32; k <= Math.Min(_raster.Size - 1, i + _feather) / 32; k++) { value.Add(j * _columns + k); } } } } } public void Observe(Exploration exploration) { if (_generation != exploration.Generation) { _generation = exploration.Generation; _cursor = 0; InvalidateAll(); } while (_cursor < exploration.Changes.Count) { (string, int, long) tuple = exploration.Changes[_cursor++]; if (!_tiles.TryGetValue(tuple.Item1, out var value)) { continue; } foreach (int item in value) { Queue(item); } } } public void InvalidateAll() { for (int i = 0; i < _columns * _columns; i++) { Queue(i); } } public bool TryTake(out int tile) { if (_dirty.Count == 0) { tile = -1; return false; } tile = _dirty.Dequeue(); _queued.Remove(tile); return true; } public IEnumerable<byte> Update(int tile, Exploration exploration) { FogTileRegion region = new FogTileRegion(_raster.Size, tile, 32, _feather); byte[] distance = new byte[region.Stride * region.Rows]; foreach (byte item in MeasureInterior(region, exploration, distance)) { yield return item; } foreach (byte item2 in ApplyOpacity(region, distance)) { yield return item2; } } private IEnumerable<byte> MeasureInterior(FogTileRegion region, Exploration exploration, byte[] distance) { for (int y = 0; y < region.Rows; y++) { yield return 0; for (int i = 0; i < region.Stride; i++) { MeasurePixel(region, exploration, distance, i, y); } } } private void MeasurePixel(FogTileRegion region, Exploration exploration, byte[] distance, int x, int y) { int num = region.WorldX(x); int num2 = region.WorldY(y); if (num >= 0 && num2 >= 0 && num < _raster.Size && num2 < _raster.Size) { int num3 = _raster.Owners[num2 * _raster.Size + num] - 1; if (num3 >= 0 && exploration.Has(_raster.Graph.Nodes[num3])) { int num4 = y * region.Stride + x; int val = ((x > 0) ? distance[num4 - 1] : 0); int val2 = ((y > 0) ? distance[num4 - region.Stride] : 0); distance[num4] = (byte)Math.Min(_feather, Math.Min(val, val2) + 1); } } } private IEnumerable<byte> ApplyOpacity(FogTileRegion region, byte[] distance) { for (int y = region.Rows - 1; y >= 0; y--) { yield return 0; for (int num = region.Stride - 1; num >= 0; num--) { RefineDistance(region, distance, num, y); PublishOpacity(region, distance[y * region.Stride + num], num, y); } } } private static void RefineDistance(FogTileRegion region, byte[] distance, int x, int y) { int num = y * region.Stride + x; if (distance[num] != 0) { int val = ((x + 1 < region.Stride) ? distance[num + 1] : 0); int val2 = ((y + 1 < region.Rows) ? distance[num + region.Stride] : 0); distance[num] = (byte)Math.Min(distance[num], Math.Min(val, val2) + 1); } } private void PublishOpacity(FogTileRegion region, byte distance, int x, int y) { if (region.ContainsInterior(x, y)) { Fog.Opacity[region.WorldY(y) * _raster.Size + region.WorldX(x)] = (byte)(255 - distance * 255 / _feather); } } public void Invalidate(string key) { if (!_tiles.TryGetValue(key, out var value)) { return; } foreach (int item in value) { Queue(item); } } private void Queue(int tile) { if (_queued.Add(tile)) { _dirty.Enqueue(tile); } } } internal static class JumpCandidateGeometry { public static bool WithinRange(float horizontalDistance, float rise) { if (horizontalDistance >= 1.5f && horizontalDistance <= 2.8f && rise <= 0.9f) { return rise >= -2f; } return false; } public static float ArcLift(float progress) { return 0.8f + 4f * progress * (1f - progress) * 0.9f; } } public sealed class MapExtentFitter { public MapViewport Fit(NavGraph graph, int floor, Point center, float span) { if (span <= 0f) { return new MapViewport(0f, 0f, 1f); } (float, float, float, float) bounds = FindBounds(graph, floor); if (bounds.Item1 == float.MaxValue) { return new MapViewport(0f, 0f, 1f); } return ProjectBounds(bounds, center, span); } private static (float minX, float maxX, float minZ, float maxZ) FindBounds(NavGraph graph, int floor) { float num = float.MaxValue; float num2 = float.MinValue; float num3 = float.MaxValue; float num4 = float.MinValue; foreach (NavNode node in graph.Nodes) { if (node.Floor == floor) { num = Math.Min(num, node.Position.x); num2 = Math.Max(num2, node.Position.x); num3 = Math.Min(num3, node.Position.z); num4 = Math.Max(num4, node.Position.z); } } return (minX: num, maxX: num2, minZ: num3, maxZ: num4); } private static MapViewport ProjectBounds((float minX, float maxX, float minZ, float maxZ) bounds, Point center, float span) { (float minX, float maxX, float minZ, float maxZ) tuple = bounds; float item = tuple.minX; float item2 = tuple.maxX; float item3 = tuple.minZ; float item4 = tuple.maxZ; float num = Math.Max(0.12f, Math.Min(1f, (Math.Max(item2 - item, item4 - item3) + 4f) / span)); float val = ((item + item2) * 0.5f - center.x) / span + 0.5f - num * 0.5f; float val2 = ((item3 + item4) * 0.5f - center.z) / span + 0.5f - num * 0.5f; return new MapViewport(Math.Max(0f, Math.Min(1f - num, val)), Math.Max(0f, Math.Min(1f - num, val2)), num); } } internal sealed class MapToolbarState { private bool _wasOpen; public bool Expanded { get; private set; } public void SetMapOpen(bool open) { if (!open || !_wasOpen) { Collapse(); } _wasOpen = open; } public void Toggle() { Expanded = !Expanded; } public void Collapse() { Expanded = false; } } public struct MapViewport { public float X; public float Y; public float Size; public MapViewport(float x, float y, float size) { X = x; Y = y; Size = size; } } public sealed class MapViewState { public bool Dungeon = true; public bool Overlay; public bool Overview; public bool RevealAll; private int? _manualFloor; public int CurrentFloor { get; private set; } public int SelectedFloor => _manualFloor ?? CurrentFloor; public void Enter() { Dungeon = true; Overview = false; _manualFloor = null; CurrentFloor = 0; } public void Track(IReadOnlyList<float> levels, float y) { if (levels.Count != 0) { CurrentFloor = Math.Min(CurrentFloor, levels.Count - 1); if (_manualFloor.HasValue) { _manualFloor = Math.Min(_manualFloor.Value, levels.Count - 1); } int num = FloorDetector.Nearest(levels, y); if (Math.Abs(y - levels[num]) + 0.65f < Math.Abs(y - levels[CurrentFloor])) { CurrentFloor = num; } } } public void RemapFloors(IReadOnlyList<float> previous, IReadOnlyList<float> replacement) { if (previous.Count != 0 && replacement.Count != 0) { float y = previous[Math.Min(CurrentFloor, previous.Count - 1)]; CurrentFloor = FloorDetector.Nearest(replacement, y); if (_manualFloor.HasValue) { float y2 = previous[Math.Min(_manualFloor.Value, previous.Count - 1)]; _manualFloor = FloorDetector.Nearest(replacement, y2); } } } public void ChangeFloor(int delta, int count) { if (count > 0) { _manualFloor = Math.Max(0, Math.Min(count - 1, SelectedFloor + delta)); } } public void OpenFloor(int floor, int count) { if (floor >= 0 && floor < count) { _manualFloor = floor; Overview = false; } } public void Follow() { _manualFloor = null; } } internal static class NativeMapInteractionPolicy { private static readonly HashSet<string> s_pointerMethods = new HashSet<string> { "OnMapLeftDown", "OnMapLeftUp", "OnMapDblClick", "OnMapLeftClick", "OnMapMiddleClick", "OnSmallMapClick", "RemovePinUnderPointer", "DebugTeleport", "OnPressedPingIcon", "ShowPinNameInput" }; public static bool RequiresPointerGuard(string method) { return s_pointerMethods.Contains(method); } } public sealed class NavEdge { public int To; public PassageKind Kind; public bool Verified; public string Obstacle; public NavEdge(int to, PassageKind kind, bool verified, string obstacle = null) { To = to; Kind = kind; Verified = verified; Obstacle = obstacle; } } public sealed class NavGraph { public readonly List<NavNode> Nodes = new List<NavNode>(); public void Connect(int a, int b, PassageKind kind = PassageKind.Walk, bool verified = true, bool bidirectional = true, string obstacle = null) { Add(a, b, kind, verified, obstacle); if (bidirectional) { Add(b, a, kind, verified, obstacle); } } private void Add(int a, int b, PassageKind kind, bool verified, string obstacle) { if (a != b && !Nodes[a].Edges.Exists((NavEdge e) => e.To == b)) { Nodes[a].Edges.Add(new NavEdge(b, kind, verified, obstacle)); } } public int Nearest(Point p, float limit) { int result = -1; for (int i = 0; i < Nodes.Count; i++) { float num = Point.Distance(p, Nodes[i].Position); if (num < limit) { result = i; limit = num; } } return result; } } public sealed class NavNode { public Point Position; public bool Flat; public int Floor; public readonly List<NavEdge> Edges = new List<NavEdge>(); public string Key { get; } public NavNode(Point p, bool flat = true) { Position = p; Key = Point.Key(p); Flat = flat; } } internal static class NeighborColumnPairs { public static IEnumerable<(int from, int to)> Enumerate(IReadOnlyDictionary<(int x, int z), List<int>> columns, (int x, int z) origin, IReadOnlyList<(int x, int z)> offsets) { List<int> source = columns[origin]; foreach (var offset in offsets) { if (!columns.TryGetValue((origin.x + offset.x, origin.z + offset.z), out var value)) { continue; } foreach (var item in CrossProduct(source, value)) { yield return item; } } } private static IEnumerable<(int from, int to)> CrossProduct(List<int> source, List<int> destination) { foreach (int from in source) { foreach (int item in destination) { yield return (from: from, to: item); } } } } internal static class ObservedTraversalBinding { public static bool Bind(NavGraph graph, IDictionary<(string, string), NavEdge> candidates, TraversalProof proof, int from, int to) { if (from < 0 || to < 0 || from >= graph.Nodes.Count || to >= graph.Nodes.Count || from == to) { return false; } NavNode navNode = graph.Nodes[from]; NavNode navNode2 = graph.Nodes[to]; proof.from = navNode.Position; proof.to = navNode2.Position; NavEdge navEdge = navNode.Edges.FirstOrDefault((NavEdge e) => e.To == to); if (navEdge == null) { PassageKind kind = ((navNode2.Position.y < navNode.Position.y - 0.6f) ? PassageKind.Drop : PassageKind.Jump); graph.Connect(from, to, kind, verified: false, bidirectional: false); navEdge = navNode.Edges.First((NavEdge e) => e.To == to); } if (navEdge.Kind != PassageKind.Jump && navEdge.Kind != PassageKind.Drop) { return false; } navEdge.Verified = false; candidates[(navNode.Key, navNode2.Key)] = navEdge; return true; } } internal sealed class OverviewCatalogPair { private readonly OverviewFloorCatalog _full = new OverviewFloorCatalog(); private readonly OverviewFloorCatalog _known = new OverviewFloorCatalog(); private NavGraph _graph; private int _generation; public bool Ready { get; private set; } public int Revision { get; private set; } public IReadOnlyList<OverviewFloor> AllFloors { get { if (!Ready) { return Array.Empty<OverviewFloor>(); } return _full.Floors; } } public IReadOnlyList<OverviewFloor> Visible(bool reveal) { if (!Ready) { return Array.Empty<OverviewFloor>(); } if (!reveal) { return _known.Floors; } return _full.Floors; } public IEnumerable<byte> Build(NavGraph graph, Exploration exploration) { Ready = false; Revision++; _graph = graph; _generation = exploration.Generation; foreach (byte item in _full.Build(graph, exploration, revealAll: true)) { yield return item; } foreach (byte item2 in _known.Build(graph, exploration, revealAll: false)) { yield return item2; } Publish(exploration, _generation); } public IEnumerable<byte> Update(Exploration exploration) { int generation = exploration.Generation; bool num = _generation != generation; if (num) { Ready = false; Revision++; } IEnumerable<byte> enumerable = (num ? _known.Build(_graph, exploration, revealAll: false) : _known.Update(exploration)); using (IEnumerator<byte> job = enumerable.GetEnumerator()) { while (generation == exploration.Generation && job.MoveNext()) { yield return job.Current; } } Publish(exploration, generation); } public OverviewFloor FullFloor(int floor) { return Find(AllFloors, floor); } public OverviewFloor KnownFloor(int floor) { return Find(Visible(reveal: false), floor); } private void Publish(Exploration exploration, int generation) { Ready = generation == exploration.Generation; _generation = generation; Revision++; } private static OverviewFloor Find(IReadOnlyList<OverviewFloor> floors, int id) { for (int i = 0; i < floors.Count; i++) { if (floors[i].Floor == id) { return floors[i]; } } return null; } } internal sealed class OverviewFloor { public int Floor { get; } public Point Minimum { get; private set; } public Point Maximum { get; private set; } public OverviewFloor(int floor, Point position) { Floor = floor; Minimum = (Maximum = position); } public void Include(Point position) { Minimum = new Point(Math.Min(Minimum.x, position.x), Minimum.y, Math.Min(Minimum.z, position.z)); Maximum = new Point(Math.Max(Maximum.x, position.x), Maximum.y, Math.Max(Maximum.z, position.z)); } } internal sealed class OverviewFloorCatalog { private readonly Dictionary<int, OverviewFloor> _entries = new Dictionary<int, OverviewFloor>(); private readonly Dictionary<string, NavNode> _nodes = new Dictionary<string, NavNode>(); private int _cursor; public IReadOnlyList<OverviewFloor> Floors { get; private set; } = new List<OverviewFloor>(); public IEnumerable<byte> Build(NavGraph graph, Exploration exploration, bool revealAll) { Dictionary<int, OverviewFloor> floors = _entries; _entries.Clear(); _nodes.Clear(); _cursor = 0; foreach (NavNode node in graph.Nodes) { yield return 0; _nodes[node.Key] = node; if (node.Floor >= 0 && (revealAll || exploration.Has(node))) { Include(floors, node); } } Floors = floors.Values.OrderByDescending((OverviewFloor floor) => floor.Floor).ToList(); } public IEnumerable<byte> Update(Exploration exploration) { int count = exploration.Changes.Count; while (_cursor < count) { yield return 0; string item = exploration.Changes[_cursor++].Key; if (_nodes.TryGetValue(item, out var value) && value.Floor >= 0 && exploration.Has(value)) { Include(_entries, value); } } Floors = _entries.Values.OrderByDescending((OverviewFloor floor) => floor.Floor).ToList(); } private static void Include(Dictionary<int, OverviewFloor> floors, NavNode node) { if (!floors.TryGetValue(node.Floor, out var value)) { floors.Add(node.Floor, new OverviewFloor(node.Floor, node.Position)); } else { value.Include(node.Position); } } } internal readonly struct OverviewFrame { public int Floor { get; } public Point Center { get; } public float Span { get; } public OverviewFrame(OverviewFloor floor, float spacing) { Floor = floor.Floor; Center = new Point((floor.Minimum.x + floor.Maximum.x) / 2f, 0f, (floor.Minimum.z + floor.Maximum.z) / 2f); Span = Math.Max(floor.Maximum.x - floor.Minimum.x, floor.Maximum.z - floor.Minimum.z) + spacing * 3f; } public bool Matches(FloorPlanRaster raster) { if (raster != null && raster.Floor == Floor && raster.Center.x == Center.x && raster.Center.z == Center.z) { return raster.Span == Span; } return false; } public (float X, float Y, float Size) Crop(FloorPlanRaster raster) { float num = Span / raster.Span; return (X: (Center.x - raster.Center.x) / raster.Span + 0.5f - num / 2f, Y: (Center.z - raster.Center.z) / raster.Span + 0.5f - num / 2f, Size: num); } } public enum PassageKind { Walk, Stairs, Door, Breakable, Jump, Drop } internal sealed class PassagePairCatalog { private static readonly (int, int)[] s_walk = new(int, int)[4] { (1, 0), (0, 1), (1, 1), (1, -1) }; private static readonly (int, int)[] s_gap = new(int, int)[4] { (2, 0), (0, 2), (-2, 0), (0, -2) }; public Dictionary<(int, int), List<(int from, int to)>> Walks { get; } = new Dictionary<(int, int), List<(int, int)>>(); public Dictionary<(int, int), List<(int from, int to)>> Gaps { get; } = new Dictionary<(int, int), List<(int, int)>>(); public static PassagePairCatalog Build(NavGraph graph, IReadOnlyDictionary<(int, int), List<int>> columns, CancellationToken cancellation = default(CancellationToken)) { PassagePairCatalog passagePairCatalog = new PassagePairCatalog(); foreach (var key in columns.Keys) { cancellation.ThrowIfCancellationRequested(); passagePairCatalog.Walks[key] = Select(graph, columns, key, s_walk, includeJumps: false, cancellation); passagePairCatalog.Gaps[key] = Select(graph, columns, key, s_gap, includeJumps: true, cancellation); } return passagePairCatalog; } private static List<(int from, int to)> Select(NavGraph graph, IReadOnlyDictionary<(int, int), List<int>> columns, (int, int) column, IReadOnlyList<(int, int)> offsets, bool includeJumps, CancellationToken cancellation) { List<(int, int)> list = new List<(int, int)>(); foreach (var item in NeighborColumnPairs.Enumerate(columns, column, offsets)) { cancellation.ThrowIfCancellationRequested(); Point position = graph.Nodes[item.from].Position; Point position2 = graph.Nodes[item.to].Position; float num = position2.x - position.x; float num2 = position2.z - position.z; float num3 = (float)Math.Sqrt(num * num + num2 * num2); float num4 = position2.y - position.y; if (Math.Abs(num4) <= num3 * 0.85f + 0.18f || (includeJumps && JumpCandidateGeometry.WithinRange(num3, num4))) { list.Add(item); } } return list; } } [Serializable] public struct Point { public float x; public float y; public float z; public Point(float x, float y, float z) { this.x = x; this.y = y; this.z = z; } public static float Distance(Point a, Point b) { float num = a.x - b.x; float num2 = a.y - b.y; float num3 = a.z - b.z; return (float)Math.Sqrt(num * num + num2 * num2 + num3 * num3); } public static string Key(Point p) { return (int)Math.Round(p.x * 20f) + "_" + (int)Math.Round(p.y * 2f) + "_" + (int)Math.Round(p.z * 20f); } } internal static class PreviewFloorClassifier { public static FloorLayout Classify(NavGraph graph, Point player) { int num = graph.Nearest(player, 5f); FloorLayout floorLayout = new FloorLayout(); if (num < 0) { return floorLayout; } float y = graph.Nodes[num].Position.y; floorLayout.Levels.Add(y); foreach (NavNode node in graph.Nodes) { node.Floor = ((!(Math.Abs(node.Position.y - y) <= 1.2f)) ? (-1) : 0); } return floorLayout; } } [Serializable] public sealed class RoomVisit { public string room; public Point local; } internal static class RoomVisitLayerPolicy { public static int Resolve(IReadOnlyList<float> levels, float height, bool partial) { if (levels.Count == 0) { return -1; } int num = FloorDetector.Nearest(levels, height); if (!partial || !(Math.Abs(levels[num] - height) > 1.2f)) { return num; } return -1; } } internal sealed class RoomVolume { private readonly Point _origin; private readonly Point _x; private readonly Point _y; private readonly Point _z; private readonly Point _half; public RoomVolume(Point origin, Point x, Point y, Point z, Point half) { _origin = origin; _x = x; _y = y; _z = z; _half = half; } public Point Local(Point world) { Point a = new Point(world.x - _origin.x, world.y - _origin.y, world.z - _origin.z); return new Point(Dot(a, _x), Dot(a, _y), Dot(a, _z)); } public bool Contains(Point world) { Point point = Local(world); if (Math.Abs(point.x) <= _half.x && Math.Abs(point.y) <= _half.y) { return Math.Abs(point.z) <= _half.z; } return false; } private static float Dot(Point a, Point b) { return a.x * b.x + a.y * b.y + a.z * b.z; } } public sealed class RouteCursor { public int Nearest(NavGraph graph, IReadOnlyList<int> route, Point player) { int result = 0; float num = float.MaxValue; for (int i = 0; i < route.Count; i++) { float num2 = Point.Distance(player, graph.Nodes[route[i]].Position); if (num2 < num) { num = num2; result = i; } } return result; } } internal static class RouteInstructionLookahead { public static (int End, float Distance) Measure(NavGraph graph, IReadOnlyList<int> route, int start, Point player, Func<int, int, bool> special = null) { if (route.Count == 0) { return (End: -1, Distance: 0f); } int i = Math.Min(start + 1, route.Count - 1); float num = Point.Distance(player, graph.Nodes[route[i]].Position); for (; i + 1 < route.Count && Continues(graph, route, start, i, special); i++) { num += Point.Distance(graph.Nodes[route[i]].Position, graph.Nodes[route[i + 1]].Position); } return (End: i, Distance: num); } private static bool Continues(NavGraph graph, IReadOnlyList<int> route, int start, int end, Func<int, int, bool> special) { NavNode navNode = graph.Nodes[route[start]]; NavNode navNode2 = graph.Nodes[route[end]]; NavEdge navEdge = navNode.Edges.FirstOrDefault((NavEdge edge) => edge.To == route[start + 1]); NavEdge navEdge2 = navNode2.Edges.FirstOrDefault((NavEdge edge) => edge.To == route[end + 1]); if (Ordinary(navEdge) && Ordinary(navEdge2) && navEdge.Kind == navEdge2.Kind && navNode2.Floor == navNode.Floor && (special == null || !special(route[start], route[start + 1])) && (special == null || !special(route[end], route[end + 1]))) { return Aligned(navNode.Position, graph.Nodes[route[start + 1]].Position, navNode2.Position, graph.Nodes[route[end + 1]].Position); } return false; } private static bool Ordinary(NavEdge edge) { if (edge != null && edge.Verified) { if (edge.Kind != PassageKind.Walk) { return edge.Kind == PassageKind.Stairs; } return true; } return false; } private static bool Aligned(Point a, Point b, Point c, Point d) { float num = b.x - a.x; float num2 = b.z - a.z; float num3 = d.x - c.x; float num4 = d.z - c.z; double num5 = Math.Sqrt((num * num + num2 * num2) * (num3 * num3 + num4 * num4)); if (num5 > 0.0001 && (double)(num * num3 + num2 * num4) / num5 >= Math.Cos(Math.PI * 5.0 / 36.0)) { return VerticalDirection(b.y - a.y) == VerticalDirection(d.y - c.y); } return false; } private static int VerticalDirection(float height) { if (!(Math.Abs(height) < 0.1f)) { return Math.Sign(height); } return 0; } } public sealed class RoutePlanner { public List<int> Find(NavGraph graph, int start, Predicate<int> target, RoutePolicy policy) { if (start < 0 || start >= graph.Nodes.Count) { return new List<int>(); } float[] array = new float[graph.Nodes.Count]; int[] array2 = new int[array.Length]; for (int i = 0; i < array.Length; i++) { array[i] = float.PositiveInfinity; array2[i] = -1; } SortedSet<(float, int)> sortedSet = new SortedSet<(float, int)>(); array[start] = 0f; sortedSet.Add((0f, start)); while (sortedSet.Count > 0) { (float, int) min = sortedSet.Min; sortedSet.Remove(min); if (target(min.Item2)) { return ReconstructRoute(array2, min.Item2); } RelaxEdges(graph, min, policy, array, array2, sortedSet); } return new List<int>(); } private static List<int> ReconstructRoute(int[] parent, int destination) { List<int> list = new List<int>(); for (int num = destination; num != -1; num = parent[num]) { list.Add(num); } list.Reverse(); return list; } private static void RelaxEdges(NavGraph graph, (float, int) c, RoutePolicy policy, float[] distance, int[] parent, SortedSet<(float, int)> queue) { foreach (NavEdge edge in graph.Nodes[c.Item2].Edges) { if (policy.Allows(edge)) { int to = edge.To; float num = c.Item1 + Point.Distance(graph.Nodes[c.Item2].Position, graph.Nodes[to].Position) + policy.Penalty(edge); if (!(num >= distance[to])) { queue.Remove((distance[to], to)); distance[to] = num; parent[to] = c.Item2; queue.Add((num, to)); } } } } } public sealed class RoutePolicy { public bool Allows(NavEdge e) { return e.Verified; } public float Penalty(NavEdge e) { return (e.Kind == PassageKind.Breakable) ? 25 : ((e.Kind == PassageKind.Jump || e.Kind == PassageKind.Drop) ? 15 : ((e.Kind == PassageKind.Door) ? 3 : 0)); } } [Serializable] public sealed class SavedRun { public int version = 4; public string world; public string dungeon; public List<string> visited = new List<string>(); public List<RoomVisit> visitedRooms = new List<RoomVisit>(); public List<ChestRecord> chests = new List<ChestRecord>(); public List<WaypointRecord> waypoints = new List<WaypointRecord>(); public List<TraversalProof> traversals = new List<TraversalProof>(); public Point start; } public sealed class SaveMigration { public SavedRun Upgrade(SavedRun data) { if (data.version > 4) { throw new InvalidOperationException("The saved data requires a newer plugin version."); } InitializeCollections(data); ValidateWaypoints(data.waypoints); MigrateLegacyChests(data); data.version = 4; return data; } private static void InitializeCollections(SavedRun data) { SavedRun savedRun = data; if (savedRun.chests == null) { savedRun.chests = new List<ChestRecord>(); } savedRun = data; if (savedRun.visited == null) { savedRun.visited = new List<string>(); } savedRun = data; if (savedRun.traversals == null) { savedRun.traversals = new List<TraversalProof>(); } savedRun = data; if (savedRun.visitedRooms == null) { savedRun.visitedRooms = new List<RoomVisit>(); } savedRun = data; if (savedRun.waypoints == null) { savedRun.waypoints = new List<WaypointRecord>(); } } private static void ValidateWaypoints(IEnumerable<WaypointRecord> records) { HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal); foreach (WaypointRecord record in records) { if (!WaypointRegistry.IsValid(record) || !hashSet.Add(record.id)) { throw new InvalidOperationException("The saved return locations contain invalid or duplicate records."); } } } private static void MigrateLegacyChests(SavedRun data) { if (data.version >= 2) { return; } foreach (ChestRecord chest in data.chests) { chest.discovered = chest.collect || chest.done; } } } public sealed class ScanPreviewRegion { public Point Minimum { get; } public Point Maximum { get; } private ScanPreviewRegion(Point minimum, Point maximum) { Minimum = minimum; Maximum = maximum; } public static ScanPreviewRegion Create(Point minimum, Point maximum, Point player) { if (!Contains(minimum, maximum, player)) { return null; } return new ScanPreviewRegion(new Point(Math.Max(minimum.x, player.x - 16f), Math.Max(minimum.y, player.y - 4f), Math.Max(minimum.z, player.z - 16f)), new Point(Math.Min(maximum.x, player.x + 16f), Math.Min(maximum.y, player.y + 4f), Math.Min(maximum.z, player.z + 16f))); } private static bool Contains(Point minimum, Point maximum, Point player) { if (player.x >= minimum.x && player.x <= maximum.x && player.y >= minimum.y && player.y <= maximum.y && player.z >= minimum.z) { return player.z <= maximum.z; } return false; } } internal sealed class SpecialPassageIndex { private NavGraph _graph; public List<(int Source, NavEdge Edge)> Passages { get; } = new List<(int, NavEdge)>(); public HashSet<string> BreakablePrefabs { get; } = new HashSet<string>(StringComparer.Ordinal); public IEnumerable<byte> Build(NavGraph graph, CancellationToken cancellation = default(CancellationToken)) { if (_graph == graph) { yield break; } Passages.Clear(); BreakablePrefabs.Clear(); for (int source = 0; source < graph.Nodes.Count; source++) { cancellation.ThrowIfCancellationRequested(); yield return 0; foreach (NavEdge edge in graph.Nodes[source].Edges) { if (edge.Kind == PassageKind.Door || edge.Kind == PassageKind.Breakable) { Passages.Add((source, edge)); if (edge.Kind == PassageKind.Breakable && edge.Obstacle != null) { BreakablePrefabs.Add(edge.Obstacle); } } yield return 0; } } _graph = graph; } } internal sealed class StairMarkerGroup { public Point Center; public List<int> Nodes { get; } = new List<int>(); } internal sealed class StairMarkerGrouping { private readonly NavGraph _graph; private readonly FloorPlanRaster _raster; private readonly Dictionary<int, List<int>> _neighbors = new Dictionary<int, List<int>>(); private readonly HashSet<int> _visited = new HashSet<int>(); public StairMarkerGrouping(NavGraph graph, FloorPlanRaster raster) { _graph = graph; _raster = raster; } public IEnumerable<byte> Build() { foreach (byte item in IndexCandidates()) { yield return item; } foreach (byte item2 in ConnectCandidates()) { yield return item2; } foreach (byte item3 in CollectGroups()) { yield return item3; } } private IEnumerable<byte> CollectGroups() { foreach (int id in _raster.Features) { yield return 0; if (!_neighbors.ContainsKey(id) || !_visited.Add(id)) { continue; } foreach (byte item in CollectGroup(id)) { yield return item; } } } private IEnumerable<byte> IndexCandidates() { foreach (int feature in _raster.Features) { (bool, bool) tuple = StairMarkerPolicy.Directions(_graph, feature); if (_graph.Nodes[feature].Floor == _raster.Floor && (tuple.Item1 || tuple.Item2)) { _neighbors.Add(feature, new List<int>()); } yield return 0; } } private IEnumerable<byte> ConnectCandidates() { foreach (int id in _neighbors.Keys) { NavEdge[] array = _graph.Nodes[id].Edges.ToArray(); foreach (NavEdge navEdge in array) { if (navEdge.Verified && (navEdge.Kind == PassageKind.Walk || navEdge.Kind == PassageKind.Stairs) && _neighbors.ContainsKey(navEdge.To) && !_neighbors[id].Contains(navEdge.To)) { _neighbors[id].Add(navEdge.To); _neighbors[navEdge.To].Add(id); } yield return 0; } } } private IEnumerable<byte> CollectGroup(int seed) { StairMarkerGroup group = new StairMarkerGroup(); Queue<int> pending = new Queue<int>(); pending.Enqueue(seed); double sumX = 0.0; double sumZ = 0.0; while (pending.Count > 0) { int num = pending.Dequeue(); group.Nodes.Add(num); sumX += (double)_graph.Nodes[num].Position.x; sumZ += (double)_graph.Nodes[num].Position.z; foreach (int item in _neighbors[num]) { if (_visited.Add(item)) { pending.Enqueue(item); } yield return 0; } } group.Center = new Point((float)(sumX / (double)group.Nodes.Count), 0f, (float)(sumZ / (double)group.Nodes.Count)); _raster.StairGroups.Add(group); } } internal static class StairMarkerPolicy { public static bool IsTransition(NavNode from, NavNode to, NavEdge edge) { if (edge.Verified && (edge.Kind == PassageKind.Stairs || (edge.Kind == PassageKind.Walk && from.Floor != to.Floor))) { return Math.Abs(to.Position.y - from.Position.y) > 0.1f; } return false; } public static (bool Up, bool Down) Directions(NavGraph graph, int id) { NavNode navNode = graph.Nodes[id]; bool flag = false; bool flag2 = false; foreach (NavEdge edge in navNode.Edges) { NavNode navNode2 = graph.Nodes[edge.To]; if (IsTransition(navNode, navNode2, edge)) { flag |= navNode2.Position.y > navNode.Position.y; flag2 |= navNode2.Position.y < navNode.Position.y; } } return (Up: flag, Down: flag2); } } internal static class StairMarkerSelection { private const float LandingHeightTolerance = 0.15f; public static (int Up, int Down) Select(NavGraph graph, StairMarkerGroup group, Exploration exploration, bool reveal) { int num = -1; int num2 = -1; foreach (int node in group.Nodes) { if (reveal || exploration.Has(graph.Nodes[node])) { (bool Up, bool Down) tuple = StairMarkerPolicy.Directions(graph, node); if (tuple.Up && PrefersLanding(graph, node, num, group.Center, lowest: true)) { num = node; } if (tuple.Down && PrefersLanding(graph, node, num2, group.Center, lowest: false)) { num2 = node; } } } return (Up: num, Down: num2); } private static bool PrefersLanding(NavGraph graph, int candidate, int current, Point center, bool lowest) { if (current < 0) { return true; } Point position = graph.Nodes[candidate].Position; Point position2 = graph.Nodes[current].Position; if (Math.Abs(position.y - position2.y) > 0.15f) { if (!lowest) { return position.y > position2.y; } return position.y < position2.y; } return DistanceSquared(position, center) < DistanceSquared(position2, center); } private static float DistanceSquared(Point point, Point center) { float num = point.x - center.x; float num2 = point.z - center.z; return num * num + num2 * num2; } } [Serializable] public sealed class TraversalProof { public Point from; public Point to; public float jumpForce; public float jumpSkill; } internal sealed class TraversalProofVerifier { private readonly List<NavEdge> _verified = new List<NavEdge>(); public void Reset() { foreach (NavEdge item in _verified) { item.Verified = false; } _verified.Clear(); } public void Apply(IReadOnlyDictionary<(string, string), NavEdge> candidates, IReadOnlyList<TraversalProof> proofs, float jumpForce, float jumpSkill, float stamina, float jumpStaminaUsage) { Reset(); foreach (TraversalProof proof in proofs) { if (candidates.TryGetValue((Point.Key(proof.from), Point.Key(proof.to)), out var value)) { value.Verified = proof.jumpForce <= jumpForce + 0.01f && proof.jumpSkill <= jumpSkill + 0.01f && (value.Kind == PassageKind.Drop || stamina >= jumpStaminaUsage); if (value.Verified) { _verified.Add(value); } } } } } internal static class WaypointMapPolicy { public static NavNode Resolve(FloorPlanRaster raster, float u, float v, Exploration exploration, bool reveal) { if (raster?.Graph == null || !(u >= 0f) || !(u < 1f) || !(v >= 0f) || !(v < 1f)) { return null; } int num = (int)(v * (float)raster.Size) * raster.Size + (int)(u * (float)raster.Size); int num2 = raster.Owners[num] - 1; if (raster.Boundary[num] || num2 < 0 || num2 >= raster.Graph.Nodes.Count) { return null; } NavNode navNode = raster.Graph.Nodes[num2]; if (navNode.Floor != raster.Floor || (!reveal && !exploration.Has(navNode))) { return null; } return navNode; } } [Serializable] public sealed class WaypointRecord { public string id; public string label; public Point position; } internal sealed class WaypointRegistry { private readonly List<WaypointRecord> _records; public WaypointRegistry(List<WaypointRecord> records) { _records = records; } public WaypointRecord Add(Point position, string label) { WaypointRecord waypointRecord = new WaypointRecord { id = Guid.NewGuid().ToString("N"), label = label?.Trim(), position = position }; if (!IsValid(waypointRecord)) { throw new ArgumentException("Waypoint requires a name of 1–80 characters and finite coordinates."); } _records.Add(waypointRecord); return waypointRecord; } public bool Rename(WaypointRecord record, string label) { if (!_records.Contains(record) || !ValidLabel(label?.Trim())) { return false; } record.label = label.Trim(); return true; } public bool Remove(WaypointRecord record) { return _records.Remove(record); } public WaypointRecord Next(WaypointRecord current) { if (_records.Count == 0) { return null; } return _records[(_records.IndexOf(current) + 1) % _records.Count]; } public static bool IsValid(WaypointRecord record) { if (record != null && !string.IsNullOrWh