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 Joeys Wayfinder v1.0.0
JoeyBadManners.Wayfinder.dll
Decompiled 3 hours 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.Reflection; using System.Runtime.CompilerServices; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using JoeyBadManners.Wayfinder.Core; using JoeyBadManners.Wayfinder.Discovery; using JoeyBadManners.Wayfinder.HUD; using JoeyBadManners.Wayfinder.Icons; using JoeyBadManners.Wayfinder.Management; using JoeyBadManners.Wayfinder.Map; using JoeyBadManners.Wayfinder.Patches; using JoeyBadManners.Wayfinder.Pins; using JoeyBadManners.Wayfinder.Tracking; using TMPro; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.UI; [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: CompilationRelaxations(8)] [assembly: AssemblyVersion("0.0.0.0")] namespace JoeyBadManners.Wayfinder { [BepInPlugin("com.joeybadmanners.wayfinder", "Joey's Wayfinder", "1.0.0")] public sealed class WayfinderPlugin : BaseUnityPlugin { public const string PluginGuid = "com.joeybadmanners.wayfinder"; public const string PluginName = "Joey's Wayfinder"; public const string PluginVersion = "1.0.0"; private const float DiscoverySliceSpacing = 0.005f; private const double SlowDiscoveryWarningMs = 4.0; private long _loadedWorldUid = long.MinValue; private float _nextBootstrapCheck; private float _nextDiscoverySliceTime; private int _discoverySlicePhase; internal static WayfinderPlugin Instance { get; private set; } internal static Harmony Harmony { get; private set; } internal static WayfinderConfig Settings { get; private set; } internal static RuntimeIconRegistry Icons { get; private set; } internal static WayfinderPinDatabase Pins { get; private set; } internal static WayfinderPinManagementService PinManager { get; private set; } internal static WayfinderPinManagerUI PinManagerUI { get; private set; } internal static WayfinderMapController Map { get; private set; } internal static ResourceDiscovery Discovery { get; private set; } internal static AreaScanner Scanner { get; private set; } internal static StructureDiscovery Structures { get; private set; } internal static TraderDiscovery Traders { get; private set; } internal static SpawnerDiscovery Spawners { get; private set; } internal static RunestoneDiscovery Runestones { get; private set; } internal static PortalDiscovery Portals { get; private set; } internal static VehicleDiscovery Vehicles { get; private set; } internal static EnemySightingDiscovery Sightings { get; private set; } internal static OreDiagnosticScanner OreDiagnostics { get; private set; } internal static DiscoveryArtConsistency ArtConsistency { get; private set; } internal static TrackedBeaconController TrackingBeacons { get; private set; } internal static WayfinderCompassHUD CompassHUD { get; private set; } internal static GravestoneIntegration Gravestones { get; private set; } private void Awake() { //IL_0221: Unknown result type (might be due to invalid IL or missing references) //IL_022b: Expected O, but got Unknown Instance = this; Settings = new WayfinderConfig(((BaseUnityPlugin)this).Config); Icons = new RuntimeIconRegistry(((BaseUnityPlugin)this).Logger, Settings); Pins = new WayfinderPinDatabase(((BaseUnityPlugin)this).Logger, Settings); PinManager = new WayfinderPinManagementService(Settings, Pins); Map = new WayfinderMapController(((BaseUnityPlugin)this).Logger, Settings, Icons, Pins, PinManager); PinManagerUI = new WayfinderPinManagerUI(((BaseUnityPlugin)this).Logger, Settings, PinManager, Icons); Discovery = new ResourceDiscovery(((BaseUnityPlugin)this).Logger, Settings, Icons, Pins); Scanner = new AreaScanner(((BaseUnityPlugin)this).Logger, Settings, Discovery); Structures = new StructureDiscovery(((BaseUnityPlugin)this).Logger, Settings, Pins, Icons); Traders = new TraderDiscovery(((BaseUnityPlugin)this).Logger, Settings, Pins); Spawners = new SpawnerDiscovery(((BaseUnityPlugin)this).Logger, Settings, Pins, Icons); Runestones = new RunestoneDiscovery(((BaseUnityPlugin)this).Logger, Settings, Pins); Portals = new PortalDiscovery(((BaseUnityPlugin)this).Logger, Settings, Pins); Vehicles = new VehicleDiscovery(((BaseUnityPlugin)this).Logger, Settings, Pins); Sightings = new EnemySightingDiscovery(((BaseUnityPlugin)this).Logger, Settings, Icons, Pins); OreDiagnostics = new OreDiagnosticScanner(((BaseUnityPlugin)this).Logger, Settings); ArtConsistency = new DiscoveryArtConsistency(((BaseUnityPlugin)this).Logger, Settings, Icons, Pins); TrackingBeacons = new TrackedBeaconController(((BaseUnityPlugin)this).Logger, Settings, PinManager); CompassHUD = new WayfinderCompassHUD(((BaseUnityPlugin)this).Logger, Settings, PinManager, Icons); Gravestones = new GravestoneIntegration(((BaseUnityPlugin)this).Logger, Settings, Icons); Harmony = new Harmony("com.joeybadmanners.wayfinder"); Harmony.PatchAll(typeof(WayfinderPlugin).Assembly); int num = WayfinderGameplayInputCaptureInstaller.Install(Harmony); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Joey's Wayfinder 1.0.0 loaded."); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Client-side Wayfinder systems initialized. No server installation is required. Runtime-safe input hooks installed: " + num + ".")); } private void Update() { if (PinManagerUI != null) { PinManagerUI.TickInput(); } if (TrackingBeacons != null) { TrackingBeacons.Tick(); } if (Vehicles != null) { Vehicles.Tick(); } if (Gravestones != null) { Gravestones.Tick(); } if (Map != null) { Map.RefreshDynamicVehiclePositions(); Map.Tick(); } TickDiscoveryWorkSlice(); if (Time.unscaledTime < _nextBootstrapCheck) { return; } _nextBootstrapCheck = Time.unscaledTime + 1f; try { BootstrapWorldState(); BootstrapIconRegistry(); Discovery.RepairKnownResourceIcons(); Discovery.ApplyLiveFilterCleanup(); if (ArtConsistency != null) { ArtConsistency.Tick(); } Portals.Tick(); OreDiagnostics.Tick(); Pins.Tick(); } catch (Exception ex) { if (Settings.DebugLogging.Value) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Bootstrap check failed: " + ex)); } } } private void TickDiscoveryWorkSlice() { if (Time.unscaledTime < _nextDiscoverySliceTime) { return; } _nextDiscoverySliceTime = Time.unscaledTime + 0.005f; double previousFrameMs = (double)Time.unscaledDeltaTime * 1000.0; double adaptiveDiscoveryBudgetMs = GetAdaptiveDiscoveryBudgetMs(previousFrameMs); if (adaptiveDiscoveryBudgetMs <= 0.0) { return; } string text = string.Empty; bool flag = false; long timestamp = Stopwatch.GetTimestamp(); try { switch (_discoverySlicePhase) { case 0: text = "AreaScanner"; if (Scanner != null) { flag = Scanner.TickBudgeted(adaptiveDiscoveryBudgetMs); } break; case 1: text = "EnemySightings"; if (Sightings != null) { flag = Sightings.TickBudgeted(adaptiveDiscoveryBudgetMs); } break; case 2: text = "Structures"; if (Structures != null) { flag = Structures.TickBudgeted(adaptiveDiscoveryBudgetMs); } break; case 3: text = "Spawners"; if (Spawners != null) { flag = Spawners.TickBudgeted(adaptiveDiscoveryBudgetMs); } break; case 4: text = "Runestones"; if (Runestones != null) { flag = Runestones.TickBudgeted(adaptiveDiscoveryBudgetMs); } break; default: text = "Traders"; if (Traders != null) { flag = Traders.TickBudgeted(adaptiveDiscoveryBudgetMs); } break; } } catch (Exception ex) { if (Settings != null && Settings.DebugLogging.Value) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Discovery slice " + text + " failed: " + ex)); } } finally { _discoverySlicePhase = (_discoverySlicePhase + 1) % 6; } if (Settings == null || !Settings.DebugLogging.Value) { return; } double num = (double)(Stopwatch.GetTimestamp() - timestamp) * 1000.0 / (double)Stopwatch.Frequency; if (num >= 4.0) { string text2 = string.Empty; if (text == "AreaScanner" && Scanner != null) { text2 = Scanner.GetPerfDetail(); } ((BaseUnityPlugin)this).Logger.LogWarning((object)("JW PERF: " + text + " slice took " + num.ToString("0.00") + " ms (budget=" + adaptiveDiscoveryBudgetMs.ToString("0.00") + "ms, pending=" + flag + ", prevFrame=" + previousFrameMs.ToString("0.00") + "ms)." + text2)); } } private static double GetAdaptiveDiscoveryBudgetMs(double previousFrameMs) { if (previousFrameMs >= 40.0) { return 0.0; } if (previousFrameMs >= 25.0) { return 0.75; } if (previousFrameMs >= 18.0) { return 1.0; } if (previousFrameMs >= 14.0) { return 1.4; } if (previousFrameMs >= 10.0) { return 2.0; } return 2.75; } private void BootstrapWorldState() { if ((Object)(object)ZNet.instance == (Object)null) { CloseWorldSessionIfNeeded(); return; } long num = 0L; try { num = ZNet.instance.GetWorldUID(); } catch { CloseWorldSessionIfNeeded(); return; } if (num == 0) { CloseWorldSessionIfNeeded(); } else if ((num != _loadedWorldUid || Pins.WorldUid != num) && Pins.LoadWorld(num, ZNet.instance.GetWorldName())) { _loadedWorldUid = num; LiveDiscoveryRegistry.ResetForWorld(num); PhysicalSpawnerIndex.Reset(); LiveDiscoveryRegistry.SyncFromLoadedLocations(); if (Vehicles != null) { Vehicles.ResetSession(); } if (Portals != null) { Portals.ResetSession(); } if (Traders != null) { Traders.ResetSession(); } if (ArtConsistency != null) { ArtConsistency.ResetSession(); } if (Gravestones != null) { Gravestones.ResetSession(); } Map.ForceRefresh(); } } private void CloseWorldSessionIfNeeded() { if ((_loadedWorldUid != long.MinValue || Pins.WorldUid != 0) && Pins.UnloadWorld()) { if (TrackingBeacons != null) { TrackingBeacons.Clear(); } if (Vehicles != null) { Vehicles.ClearSession(); } if (Portals != null) { Portals.ClearSession(); } if (Traders != null) { Traders.ResetSession(); } if (ArtConsistency != null) { ArtConsistency.ResetSession(); } if (Gravestones != null) { Gravestones.ClearSession(); } LiveDiscoveryRegistry.ResetForWorld(0L); PhysicalSpawnerIndex.Reset(); _loadedWorldUid = long.MinValue; Map.ForceRefresh(); } } private void BootstrapIconRegistry() { if (!((Object)(object)ObjectDB.instance == (Object)null) && ObjectDB.instance.m_items != null && ObjectDB.instance.m_items.Count != 0 && (!Icons.IsBuilt || Icons.IsDirty)) { Icons.Build(ObjectDB.instance); Discovery.RepairKnownResourceIcons(force: true); Map.ForceRefresh(); } } private void LateUpdate() { if (Map != null && (Object)(object)Minimap.instance != (Object)null) { Map.EnforceNativeShipMarkerVisibility(Minimap.instance); Map.EnforceVanillaPersistentPinUiVisibility(Minimap.instance); } } private void OnGUI() { if (Map != null) { Map.DrawLargeMapNameLabels(); } if (CompassHUD != null) { CompassHUD.Draw(); } if (PinManagerUI != null) { PinManagerUI.DrawMapButton(); PinManagerUI.Draw(); } } private void OnDestroy() { try { if (TrackingBeacons != null) { TrackingBeacons.Clear(); } if (Pins != null) { Pins.Save(); } if (Harmony != null) { Harmony.UnpatchSelf(); } } catch { } } } } namespace JoeyBadManners.Wayfinder.Core { internal sealed class WayfinderConfig { internal readonly ConfigEntry<bool> Enabled; internal readonly ConfigEntry<bool> DebugLogging; internal readonly ConfigEntry<bool> VerboseRuntimeLogging; internal readonly ConfigEntry<bool> SupersedeVanillaPinVisuals; internal readonly ConfigEntry<float> BossPinScale; internal readonly ConfigEntry<bool> RightClickRemovesWayfinderPins; internal readonly ConfigEntry<bool> SuppressRemovedAutoPins; internal readonly ConfigEntry<string> HiddenCategories; internal readonly ConfigEntry<KeyboardShortcut> PinManagerToggleKey; internal readonly ConfigEntry<bool> ShowPinManagerMapButton; internal readonly ConfigEntry<bool> ReplaceVanillaPersistentPinControls; internal readonly ConfigEntry<float> NearbyDiscoveryListRadius; internal readonly ConfigEntry<bool> OverrideVanillaBossNames; internal readonly ConfigEntry<string> BossNameOverrides; internal readonly ConfigEntry<bool> ResourceClustering; internal readonly ConfigEntry<float> ResourceClusterLinkDistance; internal readonly ConfigEntry<bool> ShowClusterCounts; internal readonly ConfigEntry<bool> InteractionDiscovery; internal readonly ConfigEntry<bool> AreaScanning; internal readonly ConfigEntry<float> AreaScanRadius; internal readonly ConfigEntry<bool> MatchAreaScanToMapRevealRadius; internal readonly ConfigEntry<bool> ScanUnexploredAreas; internal readonly ConfigEntry<bool> EnemySightings; internal readonly ConfigEntry<float> EnemySightingRadius; internal readonly ConfigEntry<float> EnemySightingClusterDistance; internal readonly ConfigEntry<bool> ScanDungeonContents; internal readonly ConfigEntry<bool> AutoPinStone; internal readonly ConfigEntry<bool> AutoPinBranches; internal readonly ConfigEntry<bool> AutoPinFlint; internal readonly ConfigEntry<bool> AutoPinDandelions; internal readonly ConfigEntry<bool> RememberGeneratedStructures; internal readonly ConfigEntry<float> StructureDiscoveryRadius; internal readonly ConfigEntry<bool> RememberPhysicalSpawners; internal readonly ConfigEntry<bool> AutoPinSurtlingSpawners; internal readonly ConfigEntry<float> SpawnerDiscoveryRadius; internal readonly ConfigEntry<bool> SuppressSightingsInsideSpawnerRadius; internal readonly ConfigEntry<float> SpawnerSightingSuppressionPadding; internal readonly ConfigEntry<float> SpawnerSightingExclusionRadius; internal readonly ConfigEntry<bool> RememberPortals; internal readonly ConfigEntry<float> PortalDiscoveryRadius; internal readonly ConfigEntry<float> PortalSyncInterval; internal readonly ConfigEntry<bool> PortalNameFromTag; internal readonly ConfigEntry<bool> ShowPortalNamesOnLargeMap; internal readonly ConfigEntry<bool> RemoveDestroyedPortals; internal readonly ConfigEntry<bool> CompassEnabled; internal readonly ConfigEntry<bool> CompassShowDistance; internal readonly ConfigEntry<bool> CompassShowNames; internal readonly ConfigEntry<bool> CompassShowCardinals; internal readonly ConfigEntry<bool> CompassClampOffscreenTracked; internal readonly ConfigEntry<float> CompassMaxDistance; internal readonly ConfigEntry<float> CompassWidth; internal readonly ConfigEntry<float> CompassTopOffset; internal readonly ConfigEntry<float> CompassArcDegrees; internal readonly ConfigEntry<float> CompassOpacity; internal readonly ConfigEntry<int> MaxTrackedPins; internal readonly ConfigEntry<bool> WaypointEnabled; internal readonly ConfigEntry<bool> WaypointShowVerticalDifference; internal readonly ConfigEntry<bool> BeaconEnabled; internal readonly ConfigEntry<float> BeaconOpacity; internal readonly ConfigEntry<float> BeaconHeight; internal readonly ConfigEntry<float> BeaconWidth; internal readonly ConfigEntry<bool> TrackBoats; internal readonly ConfigEntry<bool> TrackCarts; internal readonly ConfigEntry<bool> KeepLastKnownVehiclePosition; internal readonly ConfigEntry<bool> RemoveDestroyedVehicles; internal readonly ConfigEntry<float> VehicleDiscoveryRadius; internal readonly ConfigEntry<float> VehicleUpdateInterval; internal readonly ConfigEntry<float> LastKnownVehicleOpacity; internal readonly ConfigEntry<bool> ShowLastKnownVehicleBadge; internal readonly ConfigEntry<bool> ShowVehicleNamesOnLargeMap; internal readonly ConfigEntry<bool> ExpandedExploration; internal readonly ConfigEntry<float> WalkingExploreMultiplier; internal readonly ConfigEntry<float> SailingExploreMultiplier; internal readonly ConfigEntry<float> AreaScanInterval; internal readonly ConfigEntry<float> EnemySightingInterval; internal readonly ConfigEntry<float> StaticDiscoveryInterval; internal readonly ConfigEntry<float> SpawnerIndexCacheSeconds; internal readonly ConfigEntry<float> PersistenceFlushInterval; internal readonly ConfigEntry<float> MapVisualRefreshInterval; internal WayfinderConfig(ConfigFile config) { //IL_0105: Unknown result type (might be due to invalid IL or missing references) Enabled = config.Bind<bool>("00 - General", "Enabled", true, "Enable Joey's Wayfinder."); DebugLogging = config.Bind<bool>("00 - General", "DebugLogging", false, "Write additional diagnostic information to the BepInEx log."); VerboseRuntimeLogging = config.Bind<bool>("00 - General", "VerboseRuntimeLogging", false, "With DebugLogging enabled, also write high-frequency scan/sync/save/input diagnostics. Off by default to keep normal debug logs readable."); SupersedeVanillaPinVisuals = config.Bind<bool>("10 - Map", "SupersedeVanillaPinVisuals", true, "Keep vanilla pin data intact but let Wayfinder replace supported pin visuals."); BossPinScale = config.Bind<float>("10 - Map", "BossPinScale", 1.35f, "Visual scale used for Vegvisir-discovered boss pins when Wayfinder trophy styling is active."); RightClickRemovesWayfinderPins = config.Bind<bool>("10 - Map", "RightClickRemovesWayfinderPins", true, "Allow normal map right-click removal to delete Wayfinder pins too. Managed portal pins are protected from accidental map removal; delete them deliberately from the Pins panel instead."); SuppressRemovedAutoPins = config.Bind<bool>("10 - Map", "SuppressRemovedAutoPins", true, "When you manually remove an automatically discovered Wayfinder pin, remember that choice so the same scanned objects do not immediately reappear."); HiddenCategories = config.Bind<string>("11 - Pin Manager", "HiddenCategories", string.Empty, "Comma-separated Wayfinder categories hidden from map rendering. The in-map pin manager edits this automatically."); PinManagerToggleKey = config.Bind<KeyboardShortcut>("11 - Pin Manager", "ToggleKey", new KeyboardShortcut((KeyCode)288, (KeyCode[])(object)new KeyCode[0]), "Fallback shortcut for opening or closing the Wayfinder Pin Manager while the large map is open."); ShowPinManagerMapButton = config.Bind<bool>("11 - Pin Manager", "ShowMapButton", true, "Show Joey's Wayfinder compact controls on the large map."); ReplaceVanillaPersistentPinControls = config.Bind<bool>("11 - Pin Manager", "ReplaceVanillaPersistentPinControls", true, "Hide Valheim's old persistent pin-type strip and old Add/Cross-off/Remove pin hints while the large map is open. Wayfinder's compact controls replace that workflow. Vanilla middle-mouse ping and Visible to other players are intentionally preserved."); NearbyDiscoveryListRadius = config.Bind<float>("11 - Pin Manager", "NearbyDiscoveryListRadius", 100f, "In the compact Pins view, automatically discovered Resources/Sightings/Habitats farther than this are hidden from the LIST only when search is empty. Their map markers remain visible. Searching by name shows matching results at any remembered distance."); OverrideVanillaBossNames = config.Bind<bool>("11 - Pin Manager", "OverrideVanillaBossNames", true, "Let Wayfinder control the visible labels of native/Vegvisir boss pins without changing the underlying vanilla pin data."); BossNameOverrides = config.Bind<string>("11 - Pin Manager", "BossNameOverrides", "Eikthyr=Eikthyr;TheElder=The Elder;Bonemass=Bonemass;Moder=Moder;Yagluth=Yagluth;Queen=The Queen;Fader=Fader", "Wayfinder display names for native boss pins. The in-map manager edits this automatically."); ResourceClustering = config.Bind<bool>("20 - Discovery", "ResourceClustering", true, "Merge nearby observations of the SAME resource into connected clusters."); ResourceClusterLinkDistance = config.Bind<float>("20 - Discovery", "ResourceClusterLinkDistance", 6f, "A resource joins a cluster when it is within this many meters of ANY member of that same-resource cluster. Chained members stay one cluster."); ShowClusterCounts = config.Bind<bool>("20 - Discovery", "ShowClusterCounts", true, "Show counts such as Raspberries x5 or Copper x2."); InteractionDiscovery = config.Bind<bool>("20 - Discovery", "InteractionDiscovery", true, "Remember resources/locations after legitimate interaction or discovery."); AreaScanning = config.Bind<bool>("20 - Discovery", "AreaScanning", false, "Optionally scan the surrounding area for configured map targets. Off by default. Buried Silver is always interaction-only and is never revealed by area scanning."); AreaScanRadius = config.Bind<float>("20 - Discovery", "AreaScanRadius", 100f, "Manual area scan radius in meters. Used only when MatchAreaScanToMapRevealRadius is disabled."); MatchAreaScanToMapRevealRadius = config.Bind<bool>("20 - Discovery", "MatchAreaScanToMapRevealRadius", true, "Match Wayfinder's area scan radius to the player's current map reveal/exploration radius. On by default so Wayfinder only remembers visible things within the same area the map is revealing."); ScanUnexploredAreas = config.Bind<bool>("20 - Discovery", "ScanUnexploredAreas", false, "Allow automatic discovery to create pins in unexplored map fog. Off by default."); EnemySightings = config.Bind<bool>("20 - Discovery", "EnemySightings", false, "Track ordinary enemy sightings/hotspots. Off by default to avoid clutter."); EnemySightingRadius = config.Bind<float>("20 - Discovery", "EnemySightingRadius", 80f, "Maximum range in meters for optional enemy sighting discovery."); EnemySightingClusterDistance = config.Bind<float>("20 - Discovery", "EnemySightingClusterDistance", 20f, "Same-enemy sightings within this connected distance merge into one activity hotspot."); ScanDungeonContents = config.Bind<bool>("20 - Discovery", "ScanDungeonContents", false, "Allow resource discovery/pinning inside generated dungeon interiors. Off by default because the dungeon entrance pin is usually enough."); AutoPinStone = config.Bind<bool>("21 - Resource Filters", "Stone", false, "Automatically pin loose Stone sources. Off by default to avoid map clutter."); AutoPinBranches = config.Bind<bool>("21 - Resource Filters", "Branches", false, "Automatically pin branch/basic Wood pickups. Off by default to avoid map clutter."); AutoPinFlint = config.Bind<bool>("21 - Resource Filters", "Flint", false, "Automatically pin Flint pickups. Off by default to avoid map clutter."); AutoPinDandelions = config.Bind<bool>("21 - Resource Filters", "Dandelions", false, "Automatically pin Dandelions. Off by default to avoid map clutter."); RememberGeneratedStructures = config.Bind<bool>("22 - Structures", "RememberGeneratedStructures", true, "Remember generated overworld structures/locations as Wayfinder POIs after they are actually loaded/discovered."); StructureDiscoveryRadius = config.Bind<float>("22 - Structures", "DiscoveryRadius", 80f, "Maximum distance in meters for remembering loaded generated structures around the player."); RememberPhysicalSpawners = config.Bind<bool>("23 - Spawners", "RememberPhysicalSpawners", true, "Remember persistent physical world spawners after they are legitimately loaded/discovered, using the dedicated Wayfinder spawner icon."); AutoPinSurtlingSpawners = config.Bind<bool>("23 - Spawners", "SurtlingSpawners", true, "Automatically remember Surtling fire geyser/spawner locations using the dedicated Wayfinder spawner marker."); SpawnerDiscoveryRadius = config.Bind<float>("23 - Spawners", "DiscoveryRadius", 80f, "Maximum distance in meters for remembering loaded physical spawners around the player."); SuppressSightingsInsideSpawnerRadius = config.Bind<bool>("23 - Spawners", "SuppressSightingsInsideSpawnerRadius", true, "Suppress ordinary enemy-sighting/hotspot pins for creatures currently inside the radius of a physical spawner that can actually spawn that creature."); SpawnerSightingSuppressionPadding = config.Bind<float>("23 - Spawners", "SightingSuppressionPadding", 0f, "Optional extra meters added to the physical spawner radius when suppressing matching spawned-enemy sightings."); SpawnerSightingExclusionRadius = config.Bind<float>("23 - Spawners", "SightingExclusionRadius", 25f, "Blanket no-sighting bubble around every known physical spawner. Any enemy sighting inside this many meters is suppressed so the spawner marker remains readable."); RememberPortals = config.Bind<bool>("24 - Portals", "RememberPortals", true, "Remember loaded player/world portals as dedicated Wayfinder Portal pins."); PortalDiscoveryRadius = config.Bind<float>("24 - Portals", "DiscoveryRadius", 120f, "Maximum distance in meters for remembering and live-syncing loaded portals around the player."); PortalSyncInterval = config.Bind<float>("24 - Portals", "SyncInterval", 1f, "Seconds between portal tag/connection-state sync passes. Portal renames normally update within this interval."); PortalNameFromTag = config.Bind<bool>("24 - Portals", "NamePinsFromTag", true, "Automatically keep discovered portal pin names synchronized to the portal's actual in-game tag. Untagged portals are named Portal unless that individual portal has a manual Wayfinder name override."); ShowPortalNamesOnLargeMap = config.Bind<bool>("24 - Portals", "ShowNamesOnLargeMap", true, "Show remembered portal names/tags beside their Wayfinder icons on the large map. Small minimap labels stay hidden to avoid clutter."); RemoveDestroyedPortals = config.Bind<bool>("24 - Portals", "RemoveDestroyedPortals", true, "Remove the remembered Wayfinder portal record when the client actually observes that portal being destroyed."); CompassEnabled = config.Bind<bool>("30 - Compass", "Enabled", true, "Enable the Wayfinder horizontal compass HUD."); CompassShowDistance = config.Bind<bool>("30 - Compass", "ShowDistance", true, "Show live meter distance beside tracked compass markers."); CompassShowNames = config.Bind<bool>("30 - Compass", "ShowNames", false, "Show tracked pin names on the compass. Off by default to keep the HUD compact."); CompassShowCardinals = config.Bind<bool>("30 - Compass", "ShowCardinals", true, "Show N/E/S/W and bearing ticks on the compass band."); CompassClampOffscreenTracked = config.Bind<bool>("30 - Compass", "ClampOffscreenTracked", true, "Keep tracked targets outside the visible compass arc pinned to the left/right edge so you still know which way to turn."); CompassMaxDistance = config.Bind<float>("30 - Compass", "MaxDistance", 0f, "Legacy compatibility setting. Explicitly tracked Wayfinder targets are always shown at any practical world distance; this value no longer culls tracked compass markers."); CompassWidth = config.Bind<float>("30 - Compass", "Width", 720f, "Compass width in screen pixels."); CompassTopOffset = config.Bind<float>("30 - Compass", "TopOffset", 18f, "Distance in screen pixels from the top edge."); CompassArcDegrees = config.Bind<float>("30 - Compass", "VisibleArcDegrees", 180f, "Horizontal bearing arc represented across the compass width."); CompassOpacity = config.Bind<float>("30 - Compass", "Opacity", 0.72f, "Opacity of the compass background/band. Marker colors remain more vivid."); MaxTrackedPins = config.Bind<int>("30 - Compass", "MaxTrackedPins", 5, "Maximum number of Wayfinder pins that may be explicitly tracked at once. Tracking is separate from deletion and vanilla map pings."); WaypointEnabled = config.Bind<bool>("40 - Waypoint", "Enabled", true, "Enable Wayfinder tracked-target navigation features."); WaypointShowVerticalDifference = config.Bind<bool>("40 - Waypoint", "ShowVerticalDifference", true, "Show target elevation difference for tracked targets when supported by the HUD."); BeaconEnabled = config.Bind<bool>("40 - Waypoint", "BeaconEnabled", true, "Show subtle vertical beams for explicitly tracked Wayfinder pins."); BeaconOpacity = config.Bind<float>("40 - Waypoint", "BeaconOpacity", 1f, "Opacity of tracked-target beacon beams."); BeaconHeight = config.Bind<float>("40 - Waypoint", "BeaconHeight", 100f, "Height in meters of each tracked-target beacon beam."); BeaconWidth = config.Bind<float>("40 - Waypoint", "BeaconWidth", 1f, "Width in meters of each tracked-target beacon beam."); TrackBoats = config.Bind<bool>("50 - Vehicles", "TrackBoats", true, "Remember legitimately encountered loaded boats as dynamic Wayfinder vehicle markers. They can then be explicitly tracked as T1-T5 targets."); TrackCarts = config.Bind<bool>("50 - Vehicles", "TrackCarts", true, "Remember legitimately encountered loaded carts as dynamic Wayfinder vehicle markers. They can then be explicitly tracked as T1-T5 targets."); KeepLastKnownVehiclePosition = config.Bind<bool>("50 - Vehicles", "KeepLastKnownPosition", true, "Keep the most recently known map position when a remembered boat/cart leaves loaded range."); RemoveDestroyedVehicles = config.Bind<bool>("50 - Vehicles", "RemoveDestroyedVehicles", true, "Remove a remembered dynamic vehicle marker when Wayfinder observes that boat/cart being actually destroyed."); VehicleDiscoveryRadius = config.Bind<float>("50 - Vehicles", "DiscoveryRadius", 160f, "Maximum horizontal distance in meters for first remembering a loaded boat/cart. Once a specific vehicle is remembered, Wayfinder keeps updating it anywhere that same stable vehicle remains loaded."); VehicleUpdateInterval = config.Bind<float>("50 - Vehicles", "UpdateInterval", 0.5f, "Seconds between dynamic boat/cart position updates. Lower values are smoother but scan more often."); LastKnownVehicleOpacity = config.Bind<float>("50 - Vehicles", "LastKnownOpacity", 0.58f, "Opacity multiplier for remembered boat/cart markers that are no longer currently observed. Also dims their tracked compass icon/beam so last-known positions are not mistaken for live positions."); ShowLastKnownVehicleBadge = config.Bind<bool>("50 - Vehicles", "ShowLastKnownBadge", true, "Show a small amber clock-ring badge on map markers that represent a boat/cart's last known position rather than a currently observed live vehicle."); ShowVehicleNamesOnLargeMap = config.Bind<bool>("50 - Vehicles", "ShowNamesOnLargeMap", true, "Show remembered boat/cart names beside their Wayfinder icons on the large map. Small minimap labels stay hidden to avoid clutter."); ExpandedExploration = config.Bind<bool>("60 - Exploration", "Enabled", false, "Enable custom map exploration radii. Off by default."); WalkingExploreMultiplier = config.Bind<float>("60 - Exploration", "WalkingMultiplier", 1f, "Map exploration multiplier while travelling on land."); SailingExploreMultiplier = config.Bind<float>("60 - Exploration", "SailingMultiplier", 1f, "Map exploration multiplier while sailing."); AreaScanInterval = config.Bind<float>("90 - Performance", "AreaScanInterval", 4f, "Seconds between optional area scans. Higher values reduce scan spikes while preserving discovery behavior."); EnemySightingInterval = config.Bind<float>("90 - Performance", "EnemySightingInterval", 4f, "Seconds between optional enemy-sighting scans. Higher values reduce CPU cost."); StaticDiscoveryInterval = config.Bind<float>("90 - Performance", "StaticDiscoveryInterval", 5f, "Seconds between structure/spawner discovery passes."); SpawnerIndexCacheSeconds = config.Bind<float>("90 - Performance", "SpawnerIndexCacheSeconds", 10f, "How long physical-spawner footprint data is cached before being rebuilt."); PersistenceFlushInterval = config.Bind<float>("90 - Performance", "PersistenceFlushInterval", 4f, "Batch automatic Wayfinder disk writes for this many seconds. Logout/quit still forces an immediate save."); MapVisualRefreshInterval = config.Bind<float>("90 - Performance", "MapVisualRefreshInterval", 0.25f, "Minimum seconds between full Wayfinder map-visual refresh passes."); } } internal static class WayfinderDiscoveryRadius { internal static float GetEffectiveAreaScanRadius(WayfinderConfig config) { if (config == null) { return 100f; } if (!config.MatchAreaScanToMapRevealRadius.Value) { return Mathf.Max(1f, config.AreaScanRadius.Value); } Minimap instance = Minimap.instance; float num = (((Object)(object)instance != (Object)null) ? instance.m_exploreRadius : config.AreaScanRadius.Value); if (config.ExpandedExploration.Value && (Object)(object)Player.m_localPlayer != (Object)null) { float num2 = (((Character)Player.m_localPlayer).IsAttachedToShip() ? config.SailingExploreMultiplier.Value : config.WalkingExploreMultiplier.Value); num *= Mathf.Max(0f, num2); } return Mathf.Max(1f, num); } } } namespace JoeyBadManners.Wayfinder.Discovery { internal sealed class AreaScanner { private sealed class ResourceComponentCacheEntry { internal Collider Collider; internal Pickable Pickable; internal MineRock MineRock; internal MineRock5 MineRock5; internal DropOnDestroyed DropOnDestroyed; internal float ExpiresAt; } private sealed class VisualOreCacheEntry { internal Collider Collider; internal GameObject Source; internal string ResourcePrefab; internal float ExpiresAt; } private static readonly MethodInfo IsExploredMethod = AccessTools.Method(typeof(Minimap), "IsExplored", new Type[1] { typeof(Vector3) }, (Type[])null); private readonly ManualLogSource _log; private readonly WayfinderConfig _config; private readonly ResourceDiscovery _resources; private float _nextScanTime; private float _nextColliderCachePrune; private readonly Collider[] _colliderBuffer = (Collider[])(object)new Collider[4096]; private readonly HashSet<string> _visualOreObjects = new HashSet<string>(); private readonly HashSet<int> _scanPickables = new HashSet<int>(); private readonly HashSet<int> _scanMineRocks = new HashSet<int>(); private readonly HashSet<int> _scanMineRocks5 = new HashSet<int>(); private readonly HashSet<int> _scanDestroyedDrops = new HashSet<int>(); private readonly Dictionary<int, ResourceComponentCacheEntry> _resourceColliderCache = new Dictionary<int, ResourceComponentCacheEntry>(); private readonly Dictionary<int, VisualOreCacheEntry> _visualOreCache = new Dictionary<int, VisualOreCacheEntry>(); private bool _scanInProgress; private int _scanCursor; private int _nearbyColliderCount; private Vector3 _scanCenter; private float _scanRadius; private float _scanRadiusSq; private int _scanObserved; private int _scanPickableCount; private int _scanMineRockCount; private int _scanMineRock5Count; private int _scanDestroyedDropCount; private int _scanVisualOreCount; private int _lastProcessedThisSlice; private double _lastQueryMs; internal AreaScanner(ManualLogSource log, WayfinderConfig config, ResourceDiscovery resources) { _log = log; _config = config; _resources = resources; } internal void Tick() { TickBudgeted(1000.0); } internal bool TickBudgeted(double budgetMs) { if (!_config.Enabled.Value || !_config.AreaScanning.Value || (Object)(object)Player.m_localPlayer == (Object)null) { ResetActiveScan(); return false; } if (!_config.ScanDungeonContents.Value && ((Character)Player.m_localPlayer).InInterior()) { ResetActiveScan(); return false; } long startTimestamp = DiscoveryWorkBudget.Start(); if (!_scanInProgress) { if (Time.unscaledTime < _nextScanTime) { return false; } BeginScan(); if (DiscoveryWorkBudget.Expired(startTimestamp, budgetMs)) { return true; } } _lastProcessedThisSlice = 0; while (_scanCursor < _nearbyColliderCount) { Collider collider = _colliderBuffer[_scanCursor++]; _lastProcessedThisSlice++; ProcessCollider(collider); if (DiscoveryWorkBudget.Expired(startTimestamp, budgetMs)) { return true; } } if (DiscoveryWorkBudget.Expired(startTimestamp, budgetMs)) { return true; } FinishScan(); return false; } internal string GetPerfDetail() { if (_scanInProgress) { return " query=" + _lastQueryMs.ToString("0.00") + "ms processed=" + _scanCursor + "/" + _nearbyColliderCount + " yielded=true"; } return " query=" + _lastQueryMs.ToString("0.00") + "ms processed=" + _nearbyColliderCount + "/" + _nearbyColliderCount + " yielded=false"; } private void BeginScan() { //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_00c8: Unknown result type (might be due to invalid IL or missing references) _nextScanTime = Time.unscaledTime + Mathf.Max(1f, _config.AreaScanInterval.Value); _scanCenter = ((Component)Player.m_localPlayer).transform.position; _scanRadius = WayfinderDiscoveryRadius.GetEffectiveAreaScanRadius(_config); _scanRadiusSq = _scanRadius * _scanRadius; _scanObserved = 0; _scanPickableCount = 0; _scanMineRockCount = 0; _scanMineRock5Count = 0; _scanDestroyedDropCount = 0; _scanVisualOreCount = 0; _scanPickables.Clear(); _scanMineRocks.Clear(); _scanMineRocks5.Clear(); _scanDestroyedDrops.Clear(); _visualOreObjects.Clear(); long startTimestamp = DiscoveryWorkBudget.Start(); _nearbyColliderCount = Physics.OverlapSphereNonAlloc(_scanCenter, _scanRadius, _colliderBuffer, -1, (QueryTriggerInteraction)2); _lastQueryMs = DiscoveryWorkBudget.ElapsedMilliseconds(startTimestamp); _scanCursor = 0; _scanInProgress = true; } private void ProcessCollider(Collider collider) { //IL_0039: 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_00b9: 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) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_01bd: Unknown result type (might be due to invalid IL or missing references) //IL_01c2: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_0277: Unknown result type (might be due to invalid IL or missing references) //IL_027c: Unknown result type (might be due to invalid IL or missing references) //IL_027f: Unknown result type (might be due to invalid IL or missing references) //IL_0284: Unknown result type (might be due to invalid IL or missing references) //IL_01d4: Unknown result type (might be due to invalid IL or missing references) //IL_01d9: Unknown result type (might be due to invalid IL or missing references) //IL_0167: Unknown result type (might be due to invalid IL or missing references) //IL_0294: Unknown result type (might be due to invalid IL or missing references) //IL_01e9: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)collider == (Object)null) { return; } ResourceComponentCacheEntry cachedResourceComponents = GetCachedResourceComponents(collider); bool flag = false; if (cachedResourceComponents != null) { Pickable pickable = cachedResourceComponents.Pickable; if ((Object)(object)pickable != (Object)null) { flag = true; int instanceID = ((Object)pickable).GetInstanceID(); Vector3 position = ((Component)pickable).transform.position; if (_scanPickables.Add(instanceID) && WithinHorizontalRadius(_scanCenter, position, _scanRadiusSq) && MayReveal(position)) { _resources.ObservePickableFromScan(pickable); _scanObserved++; _scanPickableCount++; } } MineRock mineRock = cachedResourceComponents.MineRock; if ((Object)(object)mineRock != (Object)null) { flag = true; int instanceID2 = ((Object)mineRock).GetInstanceID(); Vector3 position2 = ((Component)mineRock).transform.position; if (_scanMineRocks.Add(instanceID2) && WithinHorizontalRadius(_scanCenter, position2, _scanRadiusSq) && MayReveal(position2)) { _resources.ObserveMineRockFromScan(mineRock); _scanObserved++; _scanMineRockCount++; } } MineRock5 mineRock2 = cachedResourceComponents.MineRock5; if ((Object)(object)mineRock2 != (Object)null) { flag = true; int instanceID3 = ((Object)mineRock2).GetInstanceID(); Vector3 position3 = ((Component)mineRock2).transform.position; if (_scanMineRocks5.Add(instanceID3) && WithinHorizontalRadius(_scanCenter, position3, _scanRadiusSq) && MayReveal(position3)) { _resources.ObserveMineRock5FromScan(mineRock2); _scanObserved++; _scanMineRock5Count++; } } DropOnDestroyed dropOnDestroyed = cachedResourceComponents.DropOnDestroyed; if ((Object)(object)dropOnDestroyed != (Object)null) { flag = true; int instanceID4 = ((Object)dropOnDestroyed).GetInstanceID(); Vector3 position4 = ((Component)dropOnDestroyed).transform.position; if (_scanDestroyedDrops.Add(instanceID4) && WithinHorizontalRadius(_scanCenter, position4, _scanRadiusSq) && MayReveal(position4) && _resources.ObserveDropOnDestroyedFromScan(dropOnDestroyed)) { _scanObserved++; _scanDestroyedDropCount++; } } } if (flag || !TryGetCachedVisibleMineable(collider, out var source, out var resourcePrefab) || (Object)(object)source == (Object)null || string.IsNullOrEmpty(resourcePrefab)) { return; } string item = ((Object)source).GetInstanceID() + ":" + resourcePrefab; if (_visualOreObjects.Add(item)) { Vector3 position5 = source.transform.position; if (WithinHorizontalRadius(_scanCenter, position5, _scanRadiusSq) && MayReveal(position5) && _resources.ObserveVisibleMineableFromScan(source, resourcePrefab)) { _scanObserved++; _scanVisualOreCount++; } } } private void FinishScan() { _scanInProgress = false; if (Time.unscaledTime >= _nextColliderCachePrune) { _nextColliderCachePrune = Time.unscaledTime + 60f; PruneColliderCaches(); } if (_config.DebugLogging.Value && _config.VerboseRuntimeLogging.Value) { _log.LogInfo((object)("Area scan checked " + _scanObserved + " nearby resource sources inside " + _scanRadius.ToString("0") + "m" + (_config.MatchAreaScanToMapRevealRadius.Value ? " [map-matched]" : " [manual]") + " (Pickable=" + _scanPickableCount + ", MineRock=" + _scanMineRockCount + ", MineRock5=" + _scanMineRock5Count + ", VisualOre=" + _scanVisualOreCount + ", DropOnDestroyed=" + _scanDestroyedDropCount + "; local collider scan).")); if (_nearbyColliderCount >= _colliderBuffer.Length) { _log.LogWarning((object)("Area scan collider buffer filled (" + _colliderBuffer.Length + "). Nearby discovery may be incomplete in this unusually dense area.")); } } } private void ResetActiveScan() { _scanInProgress = false; _scanCursor = 0; _nearbyColliderCount = 0; } private ResourceComponentCacheEntry GetCachedResourceComponents(Collider collider) { if ((Object)(object)collider == (Object)null) { return null; } int instanceID = ((Object)collider).GetInstanceID(); if (_resourceColliderCache.TryGetValue(instanceID, out var value) && value != null && object.ReferenceEquals(value.Collider, collider) && Time.unscaledTime < value.ExpiresAt) { return value; } value = new ResourceComponentCacheEntry(); value.Collider = collider; value.ExpiresAt = Time.unscaledTime + 45f; try { value.Pickable = ((Component)collider).GetComponentInParent<Pickable>(); if ((Object)(object)value.Pickable == (Object)null) { value.MineRock5 = ((Component)collider).GetComponentInParent<MineRock5>(); if ((Object)(object)value.MineRock5 == (Object)null) { value.MineRock = ((Component)collider).GetComponentInParent<MineRock>(); if ((Object)(object)value.MineRock == (Object)null) { value.DropOnDestroyed = ((Component)collider).GetComponentInParent<DropOnDestroyed>(); } } } } catch { } _resourceColliderCache[instanceID] = value; return value; } private void PruneColliderCaches() { List<int> list = new List<int>(); float unscaledTime = Time.unscaledTime; foreach (KeyValuePair<int, ResourceComponentCacheEntry> item in _resourceColliderCache) { ResourceComponentCacheEntry value = item.Value; if (value == null || (Object)(object)value.Collider == (Object)null || unscaledTime >= value.ExpiresAt) { list.Add(item.Key); } } for (int i = 0; i < list.Count; i++) { _resourceColliderCache.Remove(list[i]); } list.Clear(); foreach (KeyValuePair<int, VisualOreCacheEntry> item2 in _visualOreCache) { VisualOreCacheEntry value2 = item2.Value; if (value2 == null || (Object)(object)value2.Collider == (Object)null || unscaledTime >= value2.ExpiresAt) { list.Add(item2.Key); } } for (int j = 0; j < list.Count; j++) { _visualOreCache.Remove(list[j]); } } private bool TryGetCachedVisibleMineable(Collider collider, out GameObject source, out string resourcePrefab) { source = null; resourcePrefab = string.Empty; if ((Object)(object)collider == (Object)null) { return false; } int instanceID = ((Object)collider).GetInstanceID(); if (_visualOreCache.TryGetValue(instanceID, out var value) && value != null && object.ReferenceEquals(value.Collider, collider) && Time.unscaledTime < value.ExpiresAt) { source = value.Source; resourcePrefab = value.ResourcePrefab ?? string.Empty; if ((Object)(object)source != (Object)null) { return !string.IsNullOrEmpty(resourcePrefab); } return false; } GameObject source2; string resourcePrefab2; bool flag = TryFindVisibleMineable(collider, out source2, out resourcePrefab2); VisualOreCacheEntry visualOreCacheEntry = new VisualOreCacheEntry(); visualOreCacheEntry.Collider = collider; visualOreCacheEntry.Source = (flag ? source2 : null); visualOreCacheEntry.ResourcePrefab = (flag ? resourcePrefab2 : string.Empty); visualOreCacheEntry.ExpiresAt = Time.unscaledTime + (flag ? 60f : 30f); value = visualOreCacheEntry; _visualOreCache[instanceID] = value; source = value.Source; resourcePrefab = value.ResourcePrefab; if (flag && (Object)(object)source != (Object)null) { return !string.IsNullOrEmpty(resourcePrefab); } return false; } private static bool TryFindVisibleMineable(Collider collider, out GameObject source, out string resourcePrefab) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) source = null; resourcePrefab = string.Empty; if ((Object)(object)collider == (Object)null) { return false; } Transform val = ((Component)collider).transform; GameObject val2 = null; string text = string.Empty; int num = 0; while ((Object)(object)val != (Object)null && num < 7) { GameObject gameObject = ((Component)val).gameObject; if (!((Object)(object)gameObject == (Object)null)) { Scene scene = gameObject.scene; if (((Scene)(ref scene)).IsValid()) { if (IdentifyVisibleSurfaceMineable(gameObject, out var resourcePrefab2)) { if (IsHiddenWishboneResource(resourcePrefab2, ((Object)gameObject).name)) { return false; } source = gameObject; resourcePrefab = resourcePrefab2; return true; } if (TryIdentifyMineableFromMaterials(gameObject, out var resourcePrefab3)) { if (IsHiddenWishboneResource(resourcePrefab3, ((Object)gameObject).name)) { return false; } if ((Object)(object)val2 == (Object)null) { val2 = gameObject; text = resourcePrefab3; } } } } num++; val = val.parent; } if ((Object)(object)val2 != (Object)null && !string.IsNullOrEmpty(text)) { source = val2; resourcePrefab = text; return true; } return false; } private static bool IdentifyVisibleSurfaceMineable(GameObject go, out string resourcePrefab) { resourcePrefab = string.Empty; if ((Object)(object)go == (Object)null) { return false; } string text = NormalizeOreText(((Object)go).name ?? string.Empty); if (ContainsIronIdentity(text) || text.Contains("silver")) { return false; } MineRock5 component = go.GetComponent<MineRock5>(); if ((Object)(object)component != (Object)null) { text += NormalizeOreText(SafeMineRock5Name(component)); } MineRock component2 = go.GetComponent<MineRock>(); if ((Object)(object)component2 != (Object)null) { text += NormalizeOreText(SafeMineRockName(component2)); } DropOnDestroyed component3 = go.GetComponent<DropOnDestroyed>(); bool flag = (Object)(object)component != (Object)null || (Object)(object)component2 != (Object)null || (Object)(object)component3 != (Object)null; if (text.Contains("copper") && (flag || LooksLikeDepositName(text))) { resourcePrefab = "CopperOre"; return true; } if (text.Contains("tin") && (flag || LooksLikeDepositName(text))) { resourcePrefab = "TinOre"; return true; } if (text.Contains("obsidian") && (flag || LooksLikeDepositName(text))) { resourcePrefab = "Obsidian"; return true; } if (text.Contains("flametal") && (flag || LooksLikeDepositName(text) || text.Contains("meteor"))) { resourcePrefab = "FlametalOreNew"; return true; } if (text.Contains("blackmarble") && flag) { resourcePrefab = "BlackMarble"; return true; } if (text.Contains("softtissue") && flag) { resourcePrefab = "SoftTissue"; return true; } if (text.Contains("crystal") && flag) { resourcePrefab = "Crystal"; return true; } return false; } private static bool TryIdentifyMineableFromMaterials(GameObject go, out string resourcePrefab) { resourcePrefab = string.Empty; if ((Object)(object)go == (Object)null) { return false; } Renderer[] componentsInChildren; try { componentsInChildren = go.GetComponentsInChildren<Renderer>(true); } catch { return false; } foreach (Renderer val in componentsInChildren) { if ((Object)(object)val == (Object)null || val.sharedMaterials == null) { continue; } Material[] sharedMaterials = val.sharedMaterials; foreach (Material val2 in sharedMaterials) { if ((Object)(object)val2 == (Object)null) { continue; } string text = NormalizeOreText(((Object)val2).name ?? string.Empty); if (!text.Contains("silver") && !ContainsIronIdentity(text)) { bool flag = text.Contains("rock") || text.Contains("ore") || text.Contains("deposit") || text.Contains("vein") || text.Contains("meteor"); if (text.Contains("copper") && flag) { resourcePrefab = "CopperOre"; return true; } if (text.Contains("tin") && flag) { resourcePrefab = "TinOre"; return true; } if (text.Contains("obsidian") && flag) { resourcePrefab = "Obsidian"; return true; } if (text.Contains("flametal") && flag) { resourcePrefab = "FlametalOreNew"; return true; } if (text.Contains("blackmarble") && flag) { resourcePrefab = "BlackMarble"; return true; } if (text.Contains("softtissue") && (flag || text.Contains("tissue"))) { resourcePrefab = "SoftTissue"; return true; } if (text.Contains("crystal") && flag) { resourcePrefab = "Crystal"; return true; } } } } return false; } private static bool LooksLikeDepositName(string normalizedName) { if (!normalizedName.Contains("minerock") && !normalizedName.Contains("deposit") && !normalizedName.Contains("ore") && !normalizedName.Contains("vein") && !normalizedName.Contains("meteor")) { return normalizedName.Contains("rock"); } return true; } private static bool IsHiddenWishboneResource(string resourcePrefab, string objectName) { string text = NormalizeOreText((resourcePrefab ?? string.Empty) + " " + (objectName ?? string.Empty)); if (!text.Contains("silver")) { return ContainsIronIdentity(text); } return true; } private static bool ContainsIronIdentity(string normalized) { if (string.IsNullOrEmpty(normalized)) { return false; } if (!normalized.Contains("ironscrap") && !normalized.Contains("scrapiron") && !normalized.Contains("muddyscrap") && !normalized.Contains("muddypile") && !normalized.Contains("muddy")) { return normalized.Contains("buriediron"); } return true; } private static string SafeMineRock5Name(MineRock5 rock) { if ((Object)(object)rock == (Object)null) { return string.Empty; } try { return rock.m_name ?? string.Empty; } catch { return string.Empty; } } private static string SafeMineRockName(MineRock rock) { if ((Object)(object)rock == (Object)null) { return string.Empty; } try { return rock.m_name ?? string.Empty; } catch { return string.Empty; } } private static string NormalizeOreText(string value) { if (string.IsNullOrEmpty(value)) { return string.Empty; } char[] array = new char[value.Length]; int length = 0; for (int i = 0; i < value.Length; i++) { char c = char.ToLowerInvariant(value[i]); if (char.IsLetterOrDigit(c)) { array[length++] = c; } } return new string(array, 0, length); } private static bool IsLiveSceneObject(Component component) { //IL_001f: 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) if ((Object)(object)component == (Object)null || (Object)(object)component.gameObject == (Object)null) { return false; } try { Scene scene = component.gameObject.scene; return ((Scene)(ref scene)).IsValid(); } catch { return false; } } private bool MayReveal(Vector3 position) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) if (_config.ScanUnexploredAreas.Value) { return true; } Minimap instance = Minimap.instance; if ((Object)(object)instance == (Object)null || IsExploredMethod == null) { return false; } try { object obj = IsExploredMethod.Invoke(instance, new object[1] { position }); return obj is bool && (bool)obj; } catch { return false; } } private static bool WithinHorizontalRadius(Vector3 a, Vector3 b, float radiusSq) { float num = a.x - b.x; float num2 = a.z - b.z; return num * num + num2 * num2 <= radiusSq; } } internal sealed class DiscoveryArtConsistency { private readonly ManualLogSource _log; private readonly WayfinderConfig _config; private readonly RuntimeIconRegistry _icons; private readonly WayfinderPinDatabase _database; private long _processedWorldUid = long.MinValue; internal DiscoveryArtConsistency(ManualLogSource log, WayfinderConfig config, RuntimeIconRegistry icons, WayfinderPinDatabase database) { _log = log; _config = config; _icons = icons; _database = database; } internal void ResetSession() { _processedWorldUid = long.MinValue; } internal void Tick() { if (!_config.Enabled.Value || _database.WorldUid == 0 || _processedWorldUid == _database.WorldUid || _icons == null || !_icons.IsBuilt) { return; } _processedWorldUid = _database.WorldUid; IReadOnlyList<WayfinderPinRecord> records = _database.Records; if (records == null || records.Count == 0) { return; } List<WayfinderPinRecord> list = new List<WayfinderPinRecord>(); List<string> list2 = new List<string>(); int num = 0; int num2 = 0; int num3 = 0; for (int i = 0; i < records.Count; i++) { WayfinderPinRecord wayfinderPinRecord = records[i]; if (wayfinderPinRecord == null || wayfinderPinRecord.source == WayfinderPinSource.Manual || wayfinderPinRecord.source == WayfinderPinSource.Imported || wayfinderPinRecord.source == WayfinderPinSource.Vanilla) { continue; } string name = (wayfinderPinRecord.subtype ?? string.Empty) + " " + (wayfinderPinRecord.displayName ?? string.Empty); if (wayfinderPinRecord.category != WayfinderPinCategory.Boss && WayfinderIconPolicy.IsBossGuidanceRunestoneName(name)) { if (!string.IsNullOrEmpty(wayfinderPinRecord.id)) { list2.Add(wayfinderPinRecord.id); } continue; } bool flag = false; if (wayfinderPinRecord.category == WayfinderPinCategory.Sighting && (wayfinderPinRecord.iconOverride || string.IsNullOrEmpty(wayfinderPinRecord.iconKey) || string.Equals(wayfinderPinRecord.iconKey, "wayfinder:sighting", StringComparison.OrdinalIgnoreCase))) { WayfinderIconEntry wayfinderIconEntry = _icons.FindBestTrophy(wayfinderPinRecord.subtype); if (wayfinderIconEntry == null) { wayfinderIconEntry = _icons.FindBestTrophy(wayfinderPinRecord.displayName); } if (wayfinderIconEntry != null && !string.IsNullOrEmpty(wayfinderIconEntry.Key) && !string.Equals(wayfinderPinRecord.iconKey, wayfinderIconEntry.Key, StringComparison.OrdinalIgnoreCase)) { wayfinderPinRecord.iconKey = wayfinderIconEntry.Key; flag = true; num2++; } } if (WayfinderIconPolicy.IsRunestoneName(name) && !string.Equals(wayfinderPinRecord.iconKey, "wayfinder:runestone", StringComparison.OrdinalIgnoreCase)) { wayfinderPinRecord.iconKey = "wayfinder:runestone"; flag = true; num++; } string defaultIconKey = WayfinderIconPolicy.GetDefaultIconKey(wayfinderPinRecord); if (!string.IsNullOrEmpty(defaultIconKey) && !string.Equals(wayfinderPinRecord.iconKey, defaultIconKey, StringComparison.OrdinalIgnoreCase)) { wayfinderPinRecord.iconKey = defaultIconKey; flag = true; num3++; } if (flag) { list.Add(wayfinderPinRecord); } } for (int j = 0; j < list2.Count; j++) { _database.Remove(list2[j], suppressAutoRediscovery: false); } for (int k = 0; k < list.Count; k++) { _database.AddOrUpdate(list[k]); } if (_config.DebugLogging.Value) { _log.LogInfo((object)("Art/discovery consistency pass: removed " + list2.Count + " obsolete boss-guidance marker(s), normalized " + num + " runestone icon record(s), restored " + num2 + " enemy sighting trophy icon(s), repaired " + num3 + " other automatic semantic icon key(s). Manual/imported/vanilla records were untouched.")); } } } internal static class DiscoveryWorkBudget { internal static long Start() { return Stopwatch.GetTimestamp(); } internal static bool Expired(long startTimestamp, double budgetMs) { if (budgetMs <= 0.0) { return true; } return ElapsedMilliseconds(startTimestamp) >= budgetMs; } internal static double ElapsedMilliseconds(long startTimestamp) { return (double)(Stopwatch.GetTimestamp() - startTimestamp) * 1000.0 / (double)Stopwatch.Frequency; } } internal sealed class EnemySightingDiscovery { private const float DungeonInteriorY = 3000f; private static readonly MethodInfo IsPlayerMethod = AccessTools.Method(typeof(Character), "IsPlayer", Type.EmptyTypes, (Type[])null); private static readonly MethodInfo IsTamedMethod = AccessTools.Method(typeof(Character), "IsTamed", Type.EmptyTypes, (Type[])null); private static readonly MethodInfo IsBossMethod = AccessTools.Method(typeof(Character), "IsBoss", Type.EmptyTypes, (Type[])null); private static readonly MethodInfo GetHoverNameMethod = AccessTools.Method(typeof(Character), "GetHoverName", Type.EmptyTypes, (Type[])null); private static readonly MethodInfo IsEnemyMethod = FindIsEnemyMethod(); private static readonly MethodInfo IsExploredMethod = AccessTools.Method(typeof(Minimap), "IsExplored", new Type[1] { typeof(Vector3) }, (Type[])null); private readonly ManualLogSource _log; private readonly WayfinderConfig _config; private readonly RuntimeIconRegistry _icons; private readonly WayfinderPinDatabase _database; private float _nextScanTime; private long _seenWorldUid = long.MinValue; private readonly HashSet<int> _seenLiveInstances = new HashSet<int>(); private float _lastSpawnerCleanupTime; private bool _scanInProgress; private List<Character> _scanCharacters = new List<Character>(); private int _scanCursor; private Player _scanPlayer; private Vector3 _scanCenter; private float _scanRadiusSq; private List<PhysicalSpawnerFootprint> _scanSpawnerFootprints; private int _scanRemembered; private bool _cleanupAfterScan; internal EnemySightingDiscovery(ManualLogSource log, WayfinderConfig config, RuntimeIconRegistry icons, WayfinderPinDatabase database) { _log = log; _config = config; _icons = icons; _database = database; } internal void Tick() { TickBudgeted(1000.0); } internal bool TickBudgeted(double budgetMs) { if (!_config.Enabled.Value || !_config.EnemySightings.Value || _database.WorldUid == 0 || (Object)(object)Player.m_localPlayer == (Object)null) { ResetActiveScan(); return false; } if (_seenWorldUid != _database.WorldUid) { _seenWorldUid = _database.WorldUid; _seenLiveInstances.Clear(); _lastSpawnerCleanupTime = 0f; ResetActiveScan(); } long startTimestamp = DiscoveryWorkBudget.Start(); if (!_scanInProgress) { if (Time.unscaledTime < _nextScanTime) { return false; } BeginScan(); if (DiscoveryWorkBudget.Expired(startTimestamp, budgetMs)) { return true; } } int num = 0; while (_scanCursor < _scanCharacters.Count) { Character character = _scanCharacters[_scanCursor++]; num++; ProcessCharacter(character); if (DiscoveryWorkBudget.Expired(startTimestamp, budgetMs)) { return true; } } if (_cleanupAfterScan) { if (DiscoveryWorkBudget.Expired(startTimestamp, budgetMs)) { return true; } RunSpawnerCleanup(); _cleanupAfterScan = false; } if (_config.DebugLogging.Value && _config.VerboseRuntimeLogging.Value && _scanRemembered > 0) { _log.LogInfo((object)("Enemy sightings remembered/confirmed: " + _scanRemembered)); } _scanInProgress = false; _scanCharacters.Clear(); return false; } private void BeginScan() { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) _nextScanTime = Time.unscaledTime + Mathf.Max(1f, _config.EnemySightingInterval.Value); _scanPlayer = Player.m_localPlayer; _scanCenter = ((Component)_scanPlayer).transform.position; float num = Mathf.Max(5f, _config.EnemySightingRadius.Value); _scanRadiusSq = num * num; _scanRemembered = 0; _scanCursor = 0; _scanSpawnerFootprints = null; if (_config.SuppressSightingsInsideSpawnerRadius.Value) { _scanSpawnerFootprints = PhysicalSpawnerIndex.GetCached(_config.SpawnerSightingSuppressionPadding.Value, _config.SpawnerIndexCacheSeconds.Value); } _cleanupAfterScan = _config.SuppressSightingsInsideSpawnerRadius.Value && Time.unscaledTime - _lastSpawnerCleanupTime >= Mathf.Max(2f, _config.SpawnerIndexCacheSeconds.Value); List<Character> allCharacters = Character.GetAllCharacters(); _scanCharacters = ((allCharacters != null) ? new List<Character>(allCharacters) : new List<Character>()); _scanInProgress = true; } private void ProcessCharacter(Character character) { //IL_002c: 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_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: 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_010a: Unknown result type (might be due to invalid IL or missing references) //IL_017f: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)character == (Object)null || (Object)(object)((Component)character).gameObject == (Object)null || object.ReferenceEquals(character, _scanPlayer)) { return; } Vector3 position = ((Component)character).transform.position; if ((position.y > 3000f && !_config.ScanDungeonContents.Value) || !WithinHorizontalRadius(_scanCenter, position, _scanRadiusSq) || !MayReveal(position)) { return; } int instanceID = ((Object)character).GetInstanceID(); if (_seenLiveInstances.Contains(instanceID) || InvokeBool(character, IsPlayerMethod, fallback: false) || InvokeBool(character, IsTamedMethod, fallback: false) || InvokeBool(character, IsBossMethod, fallback: false) || !IsHostileOrMonster(character, _scanPlayer)) { return; } string prefabName = GetPrefabName(((Component)character).gameObject); if (string.IsNullOrEmpty(prefabName)) { return; } if (_config.SuppressSightingsInsideSpawnerRadius.Value) { float exclusionRadius = Mathf.Max(0f, _config.SpawnerSightingExclusionRadius.Value); if (PhysicalSpawnerIndex.WithinAny(_scanSpawnerFootprints, position, exclusionRadius) || PhysicalSpawnerIndex.Covers(_scanSpawnerFootprints, position, prefabName)) { return; } } string displayName = GetDisplayName(character, prefabName); string iconKey = string.Empty; if (_icons != null && _icons.IsBuilt) { WayfinderIconEntry wayfinderIconEntry = _icons.FindBestTrophy(prefabName); if (wayfinderIconEntry == null) { wayfinderIconEntry = _icons.FindBestTrophy(displayName); } if (wayfinderIconEntry != null) { iconKey = wayfinderIconEntry.Key; } } WayfinderPinRecord wayfinderPinRecord = _database.AddSightingObservation(prefabName, displayName, position, iconKey, "sighting:" + prefabName + ":" + instanceID, Mathf.Max(2f, _config.EnemySightingClusterDistance.Value)); if (wayfinderPinRecord != null) { _seenLiveInstances.Add(instanceID); _scanRemembered++; } } private void RunSpawnerCleanup() { _lastSpawnerCleanupTime = Time.unscaledTime; float exclusionRadius = Mathf.Max(0f, _config.SpawnerSightingExclusionRadius.Value); int num = _database.RemoveSightingMembers((string subtype, Vector3 position) => PhysicalSpawnerIndex.WithinAny(_scanSpawnerFootprints, position, exclusionRadius) || PhysicalSpawnerIndex.Covers(_scanSpawnerFootprints, position, subtype)); if (_config.DebugLogging.Value && num > 0) { _log.LogInfo((object)("Spawner-aware sightings: removed " + num + " redundant sighting member(s) inside matching physical spawner radii.")); } } private void ResetActiveScan() { _scanInProgress = false; _scanCharacters.Clear(); _scanCursor = 0; _scanPlayer = null; _scanSpawnerFootprints = null; _cleanupAfterScan = false; } private static MethodInfo FindIsEnemyMethod() { try { MethodInfo[] methods = typeof(BaseAI).GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (string.Equals(methodInfo.Name, "IsEnemy", StringComparison.Ordinal)) { ParameterInfo[] parameters = methodInfo.GetParameters(); if (methodInfo.IsStatic && parameters.Length == 2 && typeof(Character).IsAssignableFrom(parameters[0].ParameterType) && typeof(Character).IsAssignableFrom(parameters[1].ParameterType)) { return methodInfo; } } } } catch { } return null; } private static bool IsHostileOrMonster(Character character, Player player) { if ((Object)(object)character == (Object)null || (Object)(object)player == (Object)null || IsEnemyMethod == null) { return false; } try { object obj = IsEnemyMethod.Invoke(null, new object[2] { player, character }); return obj is bool && (bool)obj; } catch { return false; } } private static bool InvokeBool(object instance, MethodInfo method, bool fallback) { if (instance == null || method == null) { return fallback; } try { object obj = method.Invoke(instance, null); return (obj is bool) ? ((bool)obj) : fallback; } catch { return fallback; } } private static string GetDisplayName(Character character, string fallback) { string text = fallback; if ((Object)(object)character != (Object)null && GetHoverNameMethod != null) { try { object obj = GetHoverNameMethod.Invoke(character, null); string text2 = obj as string; if (!string.IsNullOrEmpty(text2)) { text = text2; } } catch { } } try { if (Localization.instance != null) { text = Localization.instance.Localize(text); } } catch { } if (!string.IsNullOrEmpty(text)) { return text; } return fallback; } private static string GetPrefabName(GameObject gameObject) { if ((Object)(object)gameObject == (Object)null) { return string.Empty; } try { string prefabName = Utils.GetPrefabName(gameObject); if (!string.IsNullOrEmpty(prefabName)) { return prefabName.Replace("(Clone)", string.Empty).Trim(); } } catch { } return (((Object)gameObject).name ?? string.Empty).Replace("(Clone)", string.Empty).Trim(); } private bool MayReveal(Vector3 position) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) if (_config.ScanUnexploredAreas.Value) { return true; } Minimap instance = Minimap.instance; if ((Object)(object)instance == (Object)null || IsExploredMethod == null) { return true; } try { object obj = IsExploredMethod.Invoke(instance, new object[1] { position }); return !(obj is bool) || (bool)obj; } catch { return true; } } private static bool WithinHorizontalRadius(Vector3 a, Vector3 b, float radiusSq) { float num = a.x - b.x; float num2 = a.z - b.z; return num * num + num2 * num2 <= radiusSq; } } internal sealed class GravestoneIntegration { private sealed class GraveState { internal string Key; internal Vector3 Position; internal PinData Pin; } private static readonly FieldInfo PinsField = AccessTools.Field(typeof(Minimap), "m_pins"); private static readonly Type ZNetViewType = AccessTools.TypeByName("ZNetView"); private static readonly MethodInfo GetZdoIdMethod = FindOptionalInstanceMethod(ZNetViewType, "GetZDOID"); private static readonly MethodInfo GetZdoMethod = FindOptionalInstanceMethod(ZNetViewType, "GetZDO"); private readonly ManualLogSource _log; private readonly WayfinderConfig _config; private readonly RuntimeIconRegistry _icons; private readonly Dictionary<string, GraveState> _graves = new Dictionary<string, GraveState>(StringComparer.Ordinal); private readonly Dictionary<PinData, string> _graveKeyByPin = new Dictionary<PinData, string>(); private float _nextBindTime; internal GravestoneIntegration(ManualLogSource log, WayfinderConfig config, RuntimeIconRegistry icons) { _log = log; _config = config; _icons = icons; } internal void ResetSession() { _graves.Clear(); _graveKeyByPin.Clear(); _nextBindTime = 0f; } internal void ClearSession() { ResetSession(); } internal void RegisterLoadedGrave(TombStone tombstone) { //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) if ((Object)(object)tombstone == (Object)null) { return; } Component val = null; try { if (ZNetViewType != null) { val = ((Component)tombstone).GetComponent(ZNetViewType); } } catch { } if ((Object)(object)val == (Object)null) { return; } string stableGraveKey = GetStableGraveKey(val); if (!string.IsNullOrEmpty(stableGraveKey)) { if (!_graves.TryGetValue(stableGraveKey, out var value) || value == null) { GraveState graveState = new GraveState(); graveState.Key = stableGraveKey; value = graveState; _graves[stableGraveKey] = value; } try { value.Position = ((Component)tombstone).transform.position; } catch { } TryBindStateToNativeDeathPin(value, Minimap.instance); } } internal void BeforePermanentNetworkDestroy(GameObject gameObject) { //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_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0064: 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_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)gameObject == (Object)null) { return; } TombStone val = null; try { val = gameObject.GetComponent<TombStone>(); } catch { } if ((Object)(object)val == (Object)null) { return; } Component val2 = null; try { if (ZNetViewType != null) { val2 = gameObject.GetComponent(ZNetViewType); } } catch { } if ((Object)(object)val2 == (Object)null) { return; } string stableGraveKey = GetStableGraveKey(val2); Vector3 position = Vector3.zero; try { position = ((Component)val).transform.position; } catch { } GraveState value = null; if (!string.IsNullOrEmpty(stableGraveKey)) { _graves.TryGetValue(stableGraveKey, out value); } if (value == null) { GraveState graveState = new GraveState(); graveState.Key = stableGraveKey ?? string.Empty; graveState.Position = position; value = graveState; } else { value.Position = position; } Minimap instance = Minimap.instance; if (value.Pin == null) { TryBindStateToNativeDeathPin(value, instance); } PinData pin = value.Pin; if (pin != null && (Object)(object)instance != (Object)null) { try { instance.RemovePin(pin); if (_config != null && _config.DebugLogging.Value && _config.VerboseRuntimeLogging.Value) { _log.LogInfo((object)("Gravestone permanently disappeared; removed its associated native death marker (" + (string.IsNullOrEmpty(stableGraveKey) ? "unkeyed" : stableGraveKey) + ").")); } } catch (Exception ex) { if (_config != null && _config.DebugLogging.Value) { _log.LogWarning((object)("Could not remove cleaned-up gravestone marker: " + ex.Message)); } } } if (pin != null) { _graveKeyByPin.Remove(pin); } if (!string.IsNullOrEmpty(stableGraveKey)) { _graves.Remove(stableGraveKey); } } internal void Tick() { if (_config == null || !_config.Enabled.Value || Time.unscaledTime < _nextBindTime) { return; } _nextBindTime = Time.unscaledTime + 0.25f; Minimap instance = Minimap.instance; if ((Object)(object)instance == (Object)null || _graves.Count == 0) { return; } List<PinData> pins = GetPins(instance); if (pins == null) { return; } List<string> list = null; foreach (KeyValuePair<string, GraveState> grafe in _graves) { GraveState value = grafe.Value; if (value != null && value.Pin != null && !pins.Contains(value.Pin)) { _graveKeyByPin.Remove(value.Pin); value.Pin = null; if (list == null) { list = new List<string>(); } list.Add(grafe.Key); } } foreach (KeyValuePair<string, GraveState> grafe2 in _graves) { GraveState value2 = grafe2.Value; if (value2 != null && value2.Pin == null) { TryBindStateToNativeDeathPin(value2, instance); } } } internal bool TryGetGravestoneSprite(out Sprite sprite) { sprite = null; if (_icons != null && _icons.TryGet("wayfinder:gravestone", out sprite)) { return (Object)(object)sprite != (Object)null; } return false; } internal static bool IsNativeDeathPin(PinData pin) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) if (pin == null) { return false; } string text = string.Empty; try { text = ((object)pin.m_type).ToString(); } catch { } if (!string.IsNullOrEmpty(text) && text.IndexOf("death", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } string a = pin.m_name ?? string.Empty; if (!string.Equals(a, "$msg_mapmarker_death", StringComparison.OrdinalIgnoreCase) && !string.Equals(a, "$map_death", StringComparison.OrdinalIgnoreCase)) { return string.Equals(a, "Death", StringComparison.OrdinalIgnoreCase); } return true; } private void TryBindStateToNativeDeathPin(GraveState state, Minimap map) { if (state == null || state.Pin != null || (Object)(object)map == (Object)null) { return; } List<PinData> pins = GetPins(map); if (pins == null) { return; } PinData val = null; float num = 144f; for (int i = 0; i < pins.Count; i++) { PinData val2 = pins[i]; if (IsNativeDeathPin(val2) && !_graveKeyByPin.ContainsKey(val2)) { float num2 = val2.m_pos.x - state.Position.x; float num3 = val2.m_pos.z - state.Position.z; float num4 = num2 * num2 + num3 * num3; if (num4 <= num) { num = num4; val = val2; } } } if (val != null) { state.Pin = val; _graveKeyByPin[val] = state.Key ?? string.Empty; } } private static List<PinData> GetPins(Minimap map) { if ((Object)(object)map == (Object)null || PinsField == null) { return null; } try { return PinsField.GetValue(map) as List<PinData>; } catch { return null; } } private static string GetStableGraveKey(Component view) { if ((Object)(object)view == (Object)null) { return string.Empty; } object obj = null; if (GetZdoIdMethod != null) { try { obj = GetZdoIdMethod.Invoke(view, null); } catch { obj = null; } } if (obj == null && GetZdoMethod != null) { try { object obj3 = GetZdoMethod.Invoke(view, null); if (obj3 != null) { FieldInfo field = obj3.GetType().GetField("m_uid", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { obj = field.GetValue(obj3); } } } catch { obj = null; } } string text = StableIdToString(obj); if (!string.IsNullOrEmpty(text)) { switch (text) { case "0": case "0:0": case "0_0": break; default: return "grave:" + text; } } return string.Empty; } private static MethodInfo FindOptionalInstanceMethod(Type type, string name) { if (type == null || string.IsNullOrEmpty(name)) { return null; } try { return type.GetMethod(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); } catch { return null; } } private static string StableIdToString(object id) { if (id == null) { return string.Empty; } try { Type type = id.GetType(); BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; object obj = ReadSilentMember(type, id, flags, "m_userID", "userID", "UserID"); object obj2 = ReadSilentMember(type, id, flags, "m_id", "id", "ID"); if (obj != null && obj2 != null) { string text = Convert.ToString(obj); string text2 = Convert.ToString(obj2); if (!string.IsNullOrEmpty(text) && !string.IsNullOrEmpty(text2)) { return text + ":" + text2; } } } catch { } try { return id.ToString(); } catch { return string.Empty; } } private static object ReadSilentMember(Type type, object instance, BindingFlags flags, params string[] names) { if (type == null || instance == null || names == null) { return null; } foreach (string text in names) { if (string.IsNullOrEmpty(text)) { continue; } try { FieldInfo field = type.GetField(text, flags); if (field != null) { return field.GetValue(instance); } } catch { } try { PropertyInfo property = type.GetProperty(text, flags); if (property != null && property.GetIndexParameters().Length == 0) { return property.GetValue(instance, null); } } catch { } } return null; } } internal static class LiveDiscoveryRegistry { private static readonly FieldInfo AllLocationsField = AccessTools.Field(typeof(Location), "s_allLocations"); private static readonly Dictionary<int, Location> Locations = new Dictionary<int, Location>(); private static readonly Dictionary<int, SpawnArea> SpawnAreas = new Dictionary<int, SpawnArea>(); private static readonly Dictionary<int, RuneStone> Runestones = new Dictionary<int, RuneStone>(); private static readonly Dictionary<int, Vegvisir> Vegvisirs = new Dictionary<int, Vegvisir>(); private static readonly Dictionary<int, Trader> Traders = new Dictionary<int, Trader>(); private static long _worldUid = long.MinValue; private static int _revision; internal static int Revision => _revision; internal static void ResetForWorld(long worldUid) { if (_worldUid != worldUid) { _worldUid = worldUid; Locations.Clear(); SpawnAreas.Clear(); Runestones.Clear(); Vegvisirs.Clear(); Traders.Clear(); _revision++; } } internal static void RegisterLocation(Location location) { if (!AddLive<Location>(Locations, location)) { return; } try { RegisterMany(((Component)location).GetComponentsInChildren<SpawnArea>(true), RegisterSpawnArea); RegisterMany(((Component)location).GetComponentsInChildren<RuneStone>(true), RegisterRunestone); RegisterMany(((Component)location).GetComponentsInChildren<Vegvisir>(true), RegisterVegvisir); RegisterMany(((Component)location).GetComponentsInChildren<Trader>(true), RegisterTrader); } catch { } } internal static void UnregisterLocation(Location location) { RemoveLive<Location>(Locations, location); } internal static void RegisterSpawnArea(SpawnArea area) { AddLive<SpawnArea>(SpawnAreas, area); } internal static void RegisterRunestone(RuneStone stone) { AddLive<RuneStone>(Runestones, stone); } internal static void RegisterVegvisir(Vegvisir vegvisir) { AddLive<Vegvisir>(Vegvisirs, vegvisir); } internal static void RegisterTrader(Trader trader) { AddLive<Trader>(Traders, trader); } internal static void SyncFromLoadedLocations() { if (AllLocationsField == null) { PruneDead(); return; } try { if (AllLocationsField.GetValue(null) is IEnumerable enumerable) { foreach (object item in enumerable) { Location val = (Location)((item is Location) ? item : null); if ((Object)(object)val != (Object)null) { RegisterLocation(val); } } } } catch { } PruneDead(); } internal static Location[] SnapshotLocations() { SyncFromLoadedLocations(); return Snapshot<Location>(Locations); } internal static SpawnArea[] SnapshotSpawnAreas() { SyncFromLoadedLocations(); return Snapshot<SpawnArea>(SpawnAreas); } internal static RuneStone[] SnapshotRunestones() { SyncFromLoadedLocations(); return Snapshot<RuneStone>(Runestones); } internal static Vegvisir[] SnapshotVegvisirs() { SyncFromLoadedLocations(); return Snapshot<Vegvisir>(Vegvisirs); } internal static Trader[] SnapshotTraders() { SyncFromLoadedLocations(); return Snapshot<Trader>(Traders); } private static bool AddLive<T>(Dictionary<int, T> registry, T value) where T : Object { if (registry == null || (Object)(object)value == (Object)null) { return false; } int instanceID; try { instanceID = ((Object)value/*cast due to .constrained prefix*/).GetInstanceID(); } catch { return false; } if (registry.TryGetValue(instanceID, out var value2) && (Object)(object)value2 != (Object)null) { return false; } registry[instanceID] = value; _revision++; return true; } private static void RemoveLive<T>(Dictionary<int, T> registry, T value) where T : Object { if (registry == null || object.ReferenceEquals(value, null)) { return; } try { if (registry.Remove(((Object)value/*cast due to .constrained prefix*/).GetInstanceID())) { _revision++; } } catch { } } private static void RegisterMany<T>(T[] values, Action<T> register) { if (values != null && register != null) { for (int i = 0; i < values.Length; i++) { register(values[i]); } } } private static T[] Snapshot<T>(Dictionary<int, T> registry) where T : Object { if (registry == null || registry.Count == 0) { return new T[0]; } List<T> list = new List<T>(registry.Count); List<int> list2 = null; foreach (KeyValuePair<int, T> item in registry) { T value = item.Value; if ((Object)(object)value == (Object)null) { if (list2 == null) { list2 = new List<int>(); } list2.Add(item.Key); } else { list.Add(value); } } if (list2 != null) { for (int i = 0; i < list2.Count; i++) { registry.Remove(list2[i]); } _revision++; } return list.ToArray(); } private static void PruneDead() { PruneDead<Location>(Locations); PruneDead<SpawnArea>(SpawnAreas); PruneDead<RuneStone>(Runestones); PruneDead<Vegvisir>(Vegvisirs); PruneDead<Trader>(Traders); } private static void PruneDead<T>(Dictionary<int, T> registry) where T : Object { if (registry == null || registry.Count == 0) { return; } List<int> list = null; foreach (KeyValuePair<int, T> item in registry) { if (!((Object)(object)item.Value != (Object)null)) { if (list == null) { list = new List<int>(); } list.Add(item.Key); } } if (list != null) { for (int i = 0; i < list.Count; i++) { registry.Remove(list[i]); } _revision++; } } } internal sealed class OreDiagnosticScanner { private readonly ManualLogSource _log; private readonly WayfinderConfig _config; private readonly HashSet<int> _loggedCandidates = new HashSet<int>(); private float _nextScanTime; internal OreDiagnosticScanner(ManualLogSource log, WayfinderConfig config) { _log = log; _config = config; } internal void Tick() { //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0060: 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_00b8: 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) if (!_config.DebugLogging.Value || !_config.VerboseRuntimeLogging.Value || (Object)(object)Player.m_localPlayer == (Object)null || Time.unscaledTime < _nextScanTime) { return; } _nextScanTime = Time.unscaledTime + 4f; Vector3 position = ((Component)Player.m_localPlayer).transform.position; Collider[] array = Physics.OverlapSphere(position, 22f, -1, (QueryTriggerInteraction)2); HashSet<int> hashSet = new HashSet<int>(); int num = 0; foreach (Collider val in array) { if ((Object)(object)val == (Object)null) { continue; } Transform val2 = ((Component)val).transform; int num2 = 0; while ((Object)(object)val2 != (Object)null && num2 < 5) { GameObject gameObject = ((Component)val2).gameObject; if (!((Object)(object)gameObject == (Object)null)) { Scene scene = gameObject.scene; if (((Scene)(ref scene)).IsValid()) { int instanceID = ((Object)gameObject).GetInstanceID(); if (hashSet.Add(instanceID) && !_loggedCandidates.Contains(instanceID) && LooksOreLike(gameObject, out var reason)) { _loggedCandidates.Add(instanceID); LogCandidate(gameObject, "nearby:" + reason); num++; if (num >= 12) { break; } } } } num2++; val2 = val2.parent; } if (num >= 12) { break; } } if (num > 0) { _log.LogInfo((object)("ORE-DIAG logged " + num + " new nearby ore/deposit candidate(s).")); } } internal void LogHit(GameObject gameObject, HitData hit, string hook) { //IL_0041: 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_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) if (!_config.DebugLogging.Value || !_config.VerboseRuntimeLogging.Value || (Object)(object)gameObject == (Object)null || (Object)(object)Player.m_localPlayer == (Object)null) { return; } try { Vector3 position = gameObject.transform.position; Vector3 position2 = ((Component)Player.m_localPlayer).transform.position; float num = position.x - position2.x; float num2 = position.z - position2.z; if (num * num + num2 * num2 > 1225f) { return; } } catch { } LogCandidate(gameObject, "HIT:" + (hook ?? "unknown")); } private void LogCandidate(GameObject go, string reason) { //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)go == (Object)null) { return; } try { StringBuilder stringBuilder = new StringBuilder(1024); StringBuilder stringBuilder2 = stringBuilder.Append("ORE-DIAG ").Append(reason).Append(" | prefab='") .Append(GetPrefabName(go)) .Append("'") .Append(" object='") .Append(((Object)go).name ?? string.Empty) .Append("'") .Append(" hierarchy='") .Append(GetHierarchy(go.transform)) .Append("'") .Append(" pos="); float x = go.transform.position.x; StringBuilder stringBuilder3 = stringBuilder2.Append(x.ToString("0.0")).Append(","); float y = go.transform.position.y; StringBuilder stringBuilder4 = stringBuilder3.Append(y.ToString("0.0")).Append(","); float z = go.transform.position.z; stringBuilder4.Append(z.ToString("0.0")).Append(" activeSelf=").Append(go.activeSelf) .Append(" activeHierarchy=") .Append(go.activeInHierarchy) .Append(" components=[") .Append(GetComponentNames(go)) .Append("]"); string value = TryGetHoverName(go); if (!string.IsNullOrEmpty(value)) { stringBuilder.Append(" hover='").Append(value).Append("'"); } string visualHints = GetVisualHints(go); if (!string.IsNullOrEmpty(visualHints)) { stringBuilder.Append(" visuals=[").Append(visualHints).Append("]"); } string interestingComponentFields = GetInterestingComponentFields(go); if (!string.IsNullOrEmpty(interestingComponentFields)) { stringBuilder.Append(" fields=[").Append(interestingComponentFields).Append("]"); } _log.LogInfo((object)stringBuilder.ToString()); } catch (Exception ex) { _log.LogWarning((object)("ORE-DIAG failed to describe candidate: " + ex.Message)); } } private static bool LooksOreLike(GameObject go, out string reason) { reason = string.Empty; if ((Object)(object)go == (Object)null) { return false; } string normalized = Normalize((((Object)go).name ?? string.Empty) + " " + GetHierarchy(go.transform)); if (ContainsOreToken(normalized)) { reason = "name"; return true; } Component[] components = go.GetComponents<Component>(); foreach (Component val in components) { if (!((Object)(object)val == (Object)null)) { string text = Normalize(((object)val).GetType().Name); if (text.Contains("minerock")) { reason = "component:" + ((object)val).GetType().Name; return true; } } } Renderer[] componentsInChildren = go.GetComponentsInChildren<Renderer>(true); foreach (Renderer val2 in componentsInChildren) { if ((Object)(object)val2 == (Object)null) { continue; } Material[] sharedMaterials = val2.sharedMaterials; if (sharedMaterials == null) { continue; } foreach (Material val3 in sharedMaterials) { if (!((Object)(object)val3 == (Object)null)) { string normalized2 = Normalize(((Object)val3).name ?? string.Empty); if (ContainsOreToken(normalized2)) { reason = "material:" + ((Object)val3).name; return true; } } } } MeshFilter[] componentsInChildren2 = go.GetComponentsInChildren<MeshFilter>(true); foreach (MeshFilter val4 in componentsInChildren2) { if (!((Object)(object)val4 == (Object)null) && !((Object)(object)val4.sharedMesh == (Object)null)) { string normalized3 = Normalize(((Object)val4.sharedMesh).name ?? string.Empty); if (ContainsOreToken(normalized3)) { reason = "mesh:" + ((Object)val4.sharedMesh).name; return true; } } } return false; } private static bool ContainsOreToken(string normalized) { if (string.IsNullOrEmpty(normalized)) { return false; } if (!normalized.Contains("copper") && !normalized.Contains("tin") && !normalized.Contains("silver") && !normalized.Contains("obsidian") && !normalized.Contains("flametal") && !normalized.Contains("deposit") && !normalized.Contains("ore")) { return normalized.Contains("minerock"); } return true; } private static string GetComponentNames(GameObject go) { Component[] components = go.GetComponents<Component>(); StringBuilder stringBuilder = new StringBuilder(); foreach (Component val in components) { if (!((Object)(object)val == (Object)null)) { if (stringBuilder.Length > 0) { stringBuilder.Append(","); } stringBuilder.Append(((object)val).GetType().Name); } } return stringBuilder.ToString(); } private static string GetInterestingComponentFields(GameObject go) { Component[] components = go.GetComponents<Component>(); StringBuilder stringBuilder = new StringBuilder(); BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; foreach (Component val in components) { if ((Object)(object)val == (Object)null) { continue; } string name = ((object)val).GetType().Name; string text = Normalize(name); if (!text.Contains("mine") && !text.Contains("drop") && !text.Contains("destruct") && !text.Contains("pickable") && !text.Contains("wear") && !text.Contains("health")) { continue; } FieldInfo[] fields; try { fields = ((object)val).GetType().GetFields(bindingAttr); } catch { continue; } int num = 0; for (int j = 0; j < fields.Length; j++) { if (num >= 8) { break; } FieldInfo fieldInfo = fields[j]; string text2 = Normalize(fieldInfo.Name); if (!text2.Contains("name") && !text2.Contains("drop") && !text2.Contains("item") && !text2.Contains("prefab") && !text2.Contains("health") && !text2.Contains("destroy")) { continue; } object value = null; try { value = fieldInfo.GetValue(val); } catch { } string value2 = RenderValue(value); if (!string.IsNullOrEmpty(value2)) { if (stringBuilder.Length > 0) { stringBuilder.Append("; "); } stringBuilder.Append(name).Append(".").Append(fieldInfo.Name) .Append("=") .Append(value2); num++; } } } return stringBuilder.ToString(); } private static string RenderValue(object value) { if (value == null) { return string.Empty; } if (value is string result) { return result; } GameObject val = (GameObject)((value is GameObject) ? value : null); if ((Object)(object)val != (Object)null) { return "GameObject(" + GetPrefabName(val) + ")"; } Object val2 = (Object)((value is Object) ? value : null); if (val2 != (Object)null) { return ((object)val2).GetType().Name + "(" + (val2.name ?? string.Empty) + ")"; } Type type = value.GetType(); string name = type.Name; if (name.IndexOf("DropTable", StringComparison.OrdinalIgnoreCase) >= 0) { string text = TryDescribeDropContainer(value); if (!string.IsNullOrEmpty(text)) { return name + "(" + text + ")"; } return name; } if (value is IEnumerable enumerable && !(value is string)) { string text2 = TryDescribeEnumerable(enumerable); if (!string.IsNullOrEmpty(text2)) { return name + "(" + text2 + ")"; } } if (type.IsPrimitive || value is decimal) { return Convert.ToString(value, CultureInfo.InvariantCulture); } return name; } private static string TryDescribeDropContainer(object container) { if (container == null) { return string.Empty; } BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; FieldInfo[] fields; try { fields = container.GetType().GetFields(bindingAttr); } catch { return string.Empty; } foreach (FieldInfo fieldInfo in fields) { string text = Normalize(fieldInfo.Name); if (!text.Contains("drop") && !text.Contains("item")) { continue; } object obj2 = null; try { obj2 = fieldInfo.GetValue(container); } catch { } if (obj2 is IEnumerable enumerable && !(obj2 is string)) { string text2 = TryDescribeEnumerable(enumerable); if (!string.IsNullOrEmpty(text2)) { return text2; } } } return string.Empty; } private static string TryDescribeEnumerable(IEnumerable enumerable) { if (enumerable == null) { return string.Empty; } StringBuilder stringBuilder = new StringBuilder(); int num = 0; foreach (object item in enumerable) { if (item == null || num >= 8) { continue; } GameObject val = (GameObject)((item is GameObject) ? item : null); if ((Object)(object)val != (Object)null) { if (stringBuilder.Length > 0) { stringBuilder.Append(","); } stringBuilder.Append(GetPrefabName(val)); num++; continue; } BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; FieldInfo[] fields; try { fields = item.GetType().GetFields(bindingAttr); } catch { continue; } for (int i = 0; i < fields.Length; i++) { if (!typeof(GameObject).IsAssignableFrom(fields[i].FieldType)) { continue; } string text = Normalize(fields[i].Name); if (!text.Contains("item") && !text.Contains("prefab") && !text.Contains("drop")) { continue; } GameObject val2 = null;