using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Compression; using System.Net; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Text; using System.Text.RegularExpressions; using System.Threading; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Jotunn; using Jotunn.Entities; using Jotunn.Managers; using Microsoft.CodeAnalysis; using Splatform; using TheConcernedCat.ConcernedCartographer.Atlas; using TheConcernedCat.ConcernedCartographer.Map; using TheConcernedCat.ConcernedCartographer.Persistence; using TheConcernedCat.ConcernedCartographer.Reporting; using TheConcernedCat.ConcernedCartographer.Roads; using TheConcernedCat.ConcernedCartographer.Runtime; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("The Concerned Cat")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCopyright("Copyright © 2026 Eren Cansunar")] [assembly: AssemblyFileVersion("0.10.0.0")] [assembly: AssemblyInformationalVersion("0.10.0+a23bef007a75b84282c3aa0e0043b9be468f3301")] [assembly: AssemblyProduct("Concerned Cartographer")] [assembly: AssemblyTitle("TheConcernedCat.ConcernedCartographer")] [assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/Weakened/ConcernedCatMods")] [assembly: AssemblyVersion("0.10.0.0")] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } } namespace TheConcernedCat.ConcernedCartographer { [BepInPlugin("com.theconcernedcat.valheim.concernedcartographer", "Concerned Cartographer", "0.10.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] public sealed class Plugin : BaseUnityPlugin { public const string PluginGuid = "com.theconcernedcat.valheim.concernedcartographer"; public const string PluginName = "Concerned Cartographer"; public const string PluginVersion = "0.10.0"; private CartographerRuntime? _runtime; private CrashReportingHub? _crashHub; private void Awake() { CartographerSettings settings = CartographerSettings.Bind(((BaseUnityPlugin)this).Config); LocalizationPersistence.Initialize(((BaseUnityPlugin)this).Logger); try { _crashHub = new CrashReportingHub(settings, BuildCrashContext()); _crashHub.Attach(((BaseUnityPlugin)this).Logger); } catch (Exception exception) { _crashHub = null; ((BaseUnityPlugin)this).Logger.LogWarning((object)("Crash reporting unavailable this session: " + SafeLogText.Brief(exception))); } _runtime = new CartographerRuntime(settings, ((BaseUnityPlugin)this).Logger); MinimapManager.OnVanillaMapAvailable += HandleMapAvailable; MinimapManager.OnVanillaMapDataLoaded += HandleMapDataLoaded; CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new RoadToolsCommand(_runtime)); CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new PinToolsCommand(_runtime)); CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new AtlasToolsCommand(_runtime)); CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new SurveyToolsCommand(_runtime)); CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new RouteToolsCommand(_runtime)); CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new SyncToolsCommand(_runtime)); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Concerned Cartographer 0.10.0 loaded"); LogEnvironment(settings); } private static string ResolveInformationalVersion() { try { return typeof(Plugin).Assembly.GetCustomAttribute()?.InformationalVersion ?? "0.10.0"; } catch { return "0.10.0"; } } private CrashReportContext BuildCrashContext() { string text = ResolveInformationalVersion(); string bepInExVersion = "unknown"; string jotunnVersion = "unknown"; try { bepInExVersion = typeof(BaseUnityPlugin).Assembly.GetName().Version?.ToString() ?? "unknown"; jotunnVersion = typeof(Main).Assembly.GetName().Version?.ToString() ?? "unknown"; } catch { } return new CrashReportContext { Release = "ConcernedCartographer@" + text, ModVersion = "0.10.0", ValheimVersion = ResolveGameVersion(), UnityVersion = Application.unityVersion, BepInExVersion = bepInExVersion, JotunnVersion = jotunnVersion, RuntimeState = SampleRuntimeState }; } private static CrashReportRuntimeState SampleRuntimeState() { bool multiplayer = false; bool noMap = false; bool mapOpen = false; try { multiplayer = (Object)(object)ZNet.instance != (Object)null && ZNet.instance.GetPeers().Count > 0; } catch { } try { noMap = (Object)(object)ZoneSystem.instance != (Object)null && ZoneSystem.instance.GetGlobalKey("nomap"); } catch { } try { mapOpen = Minimap.IsOpen(); } catch { } return new CrashReportRuntimeState(multiplayer, noMap, mapOpen); } private void LogEnvironment(CartographerSettings settings) { try { string text = typeof(BaseUnityPlugin).Assembly.GetName().Version?.ToString() ?? "unknown"; string text2 = typeof(Main).Assembly.GetName().Version?.ToString() ?? "unknown"; string text3 = ResolveGameVersion(); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Environment: Valheim " + text3 + ", Unity " + Application.unityVersion + ", BepInEx " + text + ", Jotunn " + text2 + ".")); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Release: ConcernedCartographer@" + ResolveInformationalVersion() + ".")); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Effective config (out-of-range values are clamped to documented ranges): " + $"Enabled={settings.Enabled.Value}, " + $"CaptureConstructionActions={settings.CaptureConstructionActions.Value}, " + $"ReconcileTerrainChanges={settings.ReconcileTerrainChanges.Value}, " + $"SampleIntervalSeconds={settings.SampleIntervalSeconds.Value}, " + $"MinimumPointSpacingMeters={settings.MinimumPointSpacingMeters.Value}, " + $"MaximumStrokeGapMeters={settings.MaximumStrokeGapMeters.Value}, " + $"DuplicateSuppressionMeters={settings.DuplicateSuppressionMeters.Value}, " + $"AutosaveIntervalSeconds={settings.AutosaveIntervalSeconds.Value}, " + $"PaintThreshold={settings.PaintThreshold.Value}, " + $"PaintSampleRadius={settings.PaintSampleRadius.Value}, " + $"LineWidthPixels={settings.LineWidthPixels.Value}, " + $"DebugLogging={settings.DebugLogging.Value}, " + $"DrawCalibrationMarkers={settings.DrawCalibrationMarkers.Value}.")); } catch (Exception exception) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not record environment versions: " + SafeLogText.Brief(exception))); } } private static string ResolveGameVersion() { try { Type typeFromHandle = typeof(Version); object obj = typeFromHandle.GetField("CurrentVersion", BindingFlags.Static | BindingFlags.Public)?.GetValue(null) ?? typeFromHandle.GetProperty("CurrentVersion", BindingFlags.Static | BindingFlags.Public)?.GetValue(null); if (obj != null) { return obj.ToString(); } MethodInfo[] methods = typeFromHandle.GetMethods(BindingFlags.Static | BindingFlags.Public); foreach (MethodInfo methodInfo in methods) { if (!(methodInfo.Name != "GetVersionString") && !(methodInfo.ReturnType != typeof(string))) { ParameterInfo[] parameters = methodInfo.GetParameters(); object[] array = new object[parameters.Length]; for (int j = 0; j < parameters.Length; j++) { array[j] = (parameters[j].HasDefaultValue ? parameters[j].DefaultValue : (parameters[j].ParameterType.IsValueType ? Activator.CreateInstance(parameters[j].ParameterType) : null)); } return (methodInfo.Invoke(null, array) as string) ?? "unknown"; } } } catch { } return "unknown"; } private void HandleMapAvailable() { _runtime?.OnMapAvailable(); } private void HandleMapDataLoaded() { _runtime?.OnMapDataReconstructed(); } private void Update() { _runtime?.Tick(Time.unscaledDeltaTime); } private void OnApplicationQuit() { _runtime?.SaveAll(); } private void OnDestroy() { MinimapManager.OnVanillaMapAvailable -= HandleMapAvailable; MinimapManager.OnVanillaMapDataLoaded -= HandleMapDataLoaded; _runtime?.Dispose(); _runtime = null; _crashHub?.Dispose(); _crashHub = null; } } } namespace TheConcernedCat.ConcernedCartographer.Runtime { internal sealed class AtlasToolsCommand : ConsoleCommand { private readonly CartographerRuntime _runtime; public override string Name => "cc_atlas"; public override string Help => "Concerned Cartographer atlas drawer and maintenance. Subcommands: status, query , clear, pins on|off, cluster on|off, dirt on|off, paved on|off, view save|apply|del , views, compat, backup, backups, restore , support. Drawer panel: DrawerHotkey (default L) on the large map."; public AtlasToolsCommand(CartographerRuntime runtime) { _runtime = runtime; } public override void Run(string[] args, Terminal context) { string text; try { text = _runtime.ExecuteAtlasCommand(args); } catch (Exception ex) { text = "Atlas tool failed: " + ex.Message; } if (context != null) { context.AddString(text); } } public override List CommandOptionList() { return new List { "status", "query", "clear", "pins", "cluster", "dirt", "paved", "view", "views", "compat", "backup", "backups", "restore", "support" }; } } internal sealed class CartographerRuntime : IDisposable { private readonly CartographerSettings _settings; private readonly ManualLogSource _log; private readonly RoadPersistence _persistence; private readonly PinPersistence _pinPersistence; private readonly GroundPaintProbe _probe; private readonly RoadOverlayRenderer _renderer; private readonly ConstructionCapture _constructionCapture; private readonly PinAdapter _pinAdapter; private readonly RateLimitedLog _rateLimited; private readonly ModalInputBlock _textFocusBlock = new ModalInputBlock((Action)GUIManager.BlockInput); private const float RedrawDebounceSeconds = 0.5f; private bool _redrawPending; private float _redrawElapsed; private RoadAtlas _atlas = new RoadAtlas(); private TerrainIntentMask _terrainIntent = new TerrainIntentMask(); private readonly TerrainIntentPersistence _terrainIntentPersistence; private RoadObservationPipeline? _pipeline; private RoadAtlasEditor? _editor; private RoadSurveyor? _surveyor; private PinStore _pinStore = new PinStore(); private PinCommandHandler? _pinCommands; private readonly PinWorkbenchPanel _workbenchPanel; private readonly PinDisplayController _displayController; private readonly AtlasDrawerPanel _drawerPanel; private readonly SavedViewPersistence _savedViewPersistence; private SavedViewStore _savedViews = new SavedViewStore(); private readonly MapUiCoordinator _mapUi; private readonly CrashConsentPanel _consentPanel; private bool _consentPromptChecked; private readonly PinPalettePanel _palettePanel; private readonly RoutesPanel _routesPanel; private readonly SurveyPanel _surveyPanel; private readonly SharePanel _sharePanel; private readonly SettingsPanel _settingsPanel; private readonly SystemMarkersPanel _systemMarkersPanel; private int _drawerToken; private int _paletteToken; private int _routesToken; private int _surveyToken; private int _shareToken; private int _settingsToken; private int _systemMarkersToken; private int _workbenchToken; private readonly QuickPinInputGate _quickPinGate = new QuickPinInputGate(); private readonly PaletteBirthTracker _birthTracker = new PaletteBirthTracker(); private float _hintElapsed; private readonly OrphanChromeSweep _chromeSweep = new OrphanChromeSweep(); private string _lastChromeSweepDiagnostics = ""; private readonly DefaultPanelRule _defaultPanel = new DefaultPanelRule(); private const float ContextGraceSeconds = 1.5f; private float _contextGrace; private readonly QuickPinCapture _quickPinCapture; private readonly SurveyEngine _surveyEngine = new SurveyEngine(); private readonly SurveyScanner _surveyScanner; private readonly SurveyRulePersistence _surveyRulePersistence; private readonly SurveyRejectedPersistence _surveyRejectedPersistence; private readonly RoutePersistence _routePersistence; private readonly RouteOverlayRenderer _routeRenderer; private readonly OverlayPanelRelabel _overlayRelabel; private float _relabelElapsed; private RouteStore _routeStore = new RouteStore(); private RouteCommandHandler? _routeCommands; private bool _routeRedrawPending; private float _routeRedrawElapsed; private readonly SyncInbox _syncInbox = new SyncInbox(); private readonly SyncTransport _syncTransport; private string _authorId = ""; private readonly CompatibilityRegistry _compatibility = new CompatibilityRegistry(); private readonly AtlasBackupTools _backupTools; private long? _worldUid; private bool _mapReady; private float _autosaveElapsed; private bool _disposed; private readonly MapSessionTracker _mapSession = new MapSessionTracker(); private string _lastRailDiagnostics = ""; private bool _onboardingChecked; private string _lastToolSummary = ""; internal PinStore Pins => _pinStore; internal PinAdapter PinAdapter => _pinAdapter; internal PinCommandHandler? PinCommands => _pinCommands; public CartographerRuntime(CartographerSettings settings, ManualLogSource log) { _settings = settings; _log = log; _persistence = new RoadPersistence(log); _terrainIntentPersistence = new TerrainIntentPersistence(log); _pinPersistence = new PinPersistence(log); _probe = new GroundPaintProbe(settings, log); _renderer = new RoadOverlayRenderer(settings, log); _rateLimited = new RateLimitedLog(log, 5f); _pinAdapter = new PinAdapter(log); _workbenchPanel = new PinWorkbenchPanel(log); _displayController = new PinDisplayController(log); _savedViewPersistence = new SavedViewPersistence(log); _savedViews = _savedViewPersistence.Load(); _drawerPanel = new AtlasDrawerPanel(log); WireDrawer(); _mapUi = new MapUiCoordinator(log); _consentPanel = new CrashConsentPanel(log, settings); _palettePanel = new PinPalettePanel(log); _routesPanel = new RoutesPanel(log, () => _routeCommands); _surveyPanel = new SurveyPanel(log, settings, ExecuteSurveyCommand, () => _surveyEngine.Observations, () => _surveyEngine, () => _surveyScanner); _sharePanel = new SharePanel(log, ExecuteSyncCommand, delegate { List list = new List(); foreach (SyncInbox.Envelope envelope in _syncInbox.Envelopes) { list.Add(envelope.AuthorName); } return list; }); _settingsPanel = new SettingsPanel(log, ExecuteAtlasCommand, ExecuteRoadCommand, delegate { _consentPanel.ShowSettings(); }); _systemMarkersPanel = new SystemMarkersPanel(log); _drawerToken = _mapUi.RegisterSurface(() => _drawerPanel.IsVisible, _drawerPanel.Hide); _paletteToken = _mapUi.RegisterSurface(() => _palettePanel.IsVisible, _palettePanel.Hide); _routesToken = _mapUi.RegisterSurface(() => _routesPanel.IsVisible, _routesPanel.Hide); _surveyToken = _mapUi.RegisterSurface(() => _surveyPanel.IsVisible, _surveyPanel.Hide); _shareToken = _mapUi.RegisterSurface(() => _sharePanel.IsVisible, _sharePanel.Hide); _settingsToken = _mapUi.RegisterSurface(() => _settingsPanel.IsVisible, _settingsPanel.Hide); _systemMarkersToken = _mapUi.RegisterSurface(() => _systemMarkersPanel.IsVisible, _systemMarkersPanel.Hide); _workbenchToken = _mapUi.RegisterSurface(() => _workbenchPanel.IsVisible, _workbenchPanel.Close); _mapUi.AtlasClicked = delegate { _mapUi.OpenExclusive(_drawerToken, ToggleDrawer); }; _mapUi.MarkersClicked = delegate { if (PaletteActive()) { _palettePanel.UiScale = _settings.UiScale.Value; _palettePanel.EnsureBuilt(); _mapUi.OpenExclusive(_paletteToken, _palettePanel.Toggle); } else { Player localPlayer = Player.m_localPlayer; if (localPlayer != null) { ((Character)localPlayer).Message((MessageType)1, "The enhanced marker palette is disabled (setting or a conflicting pin manager); the vanilla selector is shown instead.", 0, (Sprite)null); } } }; _mapUi.RoutesClicked = delegate { OpenSidePanel(_routesToken, _routesPanel); }; _mapUi.SurveyClicked = delegate { OpenSidePanel(_surveyToken, _surveyPanel); }; _mapUi.ShareClicked = delegate { OpenSidePanel(_shareToken, _sharePanel); }; _mapUi.SettingsClicked = delegate { OpenSidePanel(_settingsToken, _settingsPanel); }; _mapUi.QuickPinClicked = ArmQuickPin; _drawerPanel.PrivacyClicked = delegate { _consentPanel.ShowSettings(); }; _drawerPanel.SystemMarkersClicked = delegate { OpenSidePanel(_systemMarkersToken, _systemMarkersPanel); }; MapInputGate.Install(log); PlayerInputGate.Install(log); PlayerInputGate.SuppressAttack = () => _quickPinGate.SuppressAttack(Time.frameCount); PlayerInputGate.SuppressMenu = () => _quickPinGate.SuppressMenu(Time.frameCount); PinDeletionWatch.Install(log); PinDeletionWatch.ExplicitDelete = HandleExplicitVanillaDelete; MapInputGate.WheelGuard = () => Minimap.IsOpen() && (MapPointerGuard.IsPointerOverCcUi(Vector2.op_Implicit(Input.mousePosition)) || CcTextFocus.AnyFieldFocused()); MapPointerGuard.Clear(); MapPointerGuard.RegisterWidget(() => _mapUi.ToolbarObject); MapPointerGuard.RegisterWidget(() => _mapUi.ContextButtonObject); MapPointerGuard.RegisterWidget(() => _palettePanel.PanelObject); _palettePanel.IconChosen = delegate(IconRegistry.IconDefinition definition) { MinimapReflection.TrySelectIcon(definition.VanillaType); _birthTracker.Arm(definition.Id, definition.DefaultCategory); }; _palettePanel.SelectionCleared = delegate { _birthTracker.Disarm(); }; _quickPinCapture = new QuickPinCapture(settings, log); _surveyRulePersistence = new SurveyRulePersistence(log); _surveyRejectedPersistence = new SurveyRejectedPersistence(log); _surveyEngine.Rules = _surveyRulePersistence.LoadOrCreate(); _surveyScanner = new SurveyScanner(settings, log); _routePersistence = new RoutePersistence(log); _routeRenderer = new RouteOverlayRenderer(settings, log); _overlayRelabel = new OverlayPanelRelabel(log); _renderer.UserToggledOverlay += delegate(RoadKind kind, bool enabled) { if (kind == RoadKind.Dirt) { _settings.DrawerShowDirt.Value = enabled; } else { _settings.DrawerShowPaved.Value = enabled; } }; _routeRenderer.UserToggled += delegate(bool enabled) { _renderer.SetRouteVectorVisible(enabled); }; _authorId = AuthorIdentity.Get(log); _syncTransport = new SyncTransport(log, _syncInbox) { LocalAuthorId = _authorId }; _backupTools = new AtlasBackupTools(log); _constructionCapture = new ConstructionCapture(log); _constructionCapture.OperationCaptured += HandleTerrainOperation; } public void OnMapAvailable() { if (_disposed) { return; } if (!WorldContext.TryGetWorldUid(out var uid)) { _log.LogWarning((object)"Map became available before a world UID could be resolved; waiting for the next map event."); _mapReady = false; return; } SwitchWorld(uid); _mapReady = true; _log.LogInfo((object)string.Format("Map session lifecycle: generation {0} (map-available).", _mapSession.NoteTransition("map-available"))); _renderer.ResetMapSession(); _routeRenderer.ResetMapSession(); CcIconSprites.ResetSession(); _renderer.RedrawAll(_atlas); _pinAdapter.ReconcileOnMapReady(_pinStore); _mapSession.NoteBound(); _renderer.SetOverlayEnabled(RoadKind.Dirt, _settings.DrawerShowDirt.Value); _renderer.SetOverlayEnabled(RoadKind.Paved, _settings.DrawerShowPaved.Value); _displayController.ShowPins = _settings.DrawerShowPins.Value; _displayController.ClusterEnabled = _settings.DrawerCluster.Value; _displayController.Apply(_pinStore, _pinAdapter); _routeRenderer.RedrawAll(_routeStore); _renderer.MarkVectorDataDirty(); _syncTransport.EnsureRegistered(); _compatibility.Evaluate(_log); ShowOnboardingOnce(); if (_settings.DrawCalibrationMarkers.Value) { _renderer.DrawCalibrationMarkers(); } _log.LogInfo((object)$"Road atlas ready: {_atlas.Strokes.Count} stroke(s), {_atlas.PointCount} point(s)."); } public void OnMapDataReconstructed() { if (!_disposed) { if (!_mapReady) { OnMapAvailable(); return; } _log.LogInfo((object)string.Format("Map session lifecycle: generation {0} (map-data-loaded).", _mapSession.NoteTransition("map-data-loaded"))); _pinAdapter.ReconcileOnMapReady(_pinStore, "map-data-loaded"); _mapSession.NoteBound(); ReapplyDisplay(); } } private void HandleExplicitVanillaDelete(PinData pin) { if (!_disposed && _mapReady) { _pinAdapter.HandleExplicitVanillaDelete(_pinStore, pin); } } public void Tick(float unscaledDeltaTime) { //IL_0209: Unknown result type (might be due to invalid IL or missing references) //IL_026a: Unknown result type (might be due to invalid IL or missing references) //IL_026f: Unknown result type (might be due to invalid IL or missing references) //IL_021b: Unknown result type (might be due to invalid IL or missing references) //IL_0193: Unknown result type (might be due to invalid IL or missing references) //IL_01a5: Unknown result type (might be due to invalid IL or missing references) //IL_03c8: Unknown result type (might be due to invalid IL or missing references) //IL_0394: Unknown result type (might be due to invalid IL or missing references) //IL_0497: Unknown result type (might be due to invalid IL or missing references) //IL_048b: Unknown result type (might be due to invalid IL or missing references) //IL_04a5: Unknown result type (might be due to invalid IL or missing references) //IL_04aa: Unknown result type (might be due to invalid IL or missing references) //IL_04bc: Unknown result type (might be due to invalid IL or missing references) //IL_04c3: Unknown result type (might be due to invalid IL or missing references) //IL_04ca: Unknown result type (might be due to invalid IL or missing references) if (_disposed) { return; } _workbenchPanel.HandleFrame(); _consentPanel.HandleFrame(); _routesPanel.HandleFrame(); _surveyPanel.HandleFrame(); _sharePanel.HandleFrame(); _settingsPanel.HandleFrame(); _systemMarkersPanel.HandleFrame(); _palettePanel.HandleFrame(); if (!Minimap.IsOpen()) { MapInputGate.ConsumeClicks = false; } if (!_settings.Enabled.Value || !_mapReady || _surveyor == null) { MapInputGate.ConsumeClicks = false; _textFocusBlock.Release(); _quickPinGate.Disarm(); if (!_settings.Enabled.Value) { _renderer.EnsureTextureFallback(); _routeRenderer.TickVisibility(vectorRoutesActive: false); _overlayRelabel.Restore(); } if (Minimap.IsOpen()) { EnforceVanillaPaletteVisibility(); _palettePanel.SetUnavailable(); } if (_mapUi.AnySurfaceVisible) { _mapUi.CloseAllSurfaces(); } return; } _drawerPanel.HandleFrame(); bool flag = CcTextFocus.AnyFieldFocused(); if (flag) { _textFocusBlock.Acquire(); } else { _textFocusBlock.Release(); } if (!Minimap.IsOpen() && !Minimap.InTextInput() && !flag) { if (_quickPinGate.Armed) { switch (_quickPinGate.HandleFrame(Time.frameCount, Input.GetKeyDown((KeyCode)27), Input.GetMouseButtonDown(0) || ((int)_settings.QuickPinHotkey.Value != 0 && Input.GetKeyDown(_settings.QuickPinHotkey.Value)) || GamepadDown(_settings.WorkbenchGamepadButton.Value))) { case QuickPinInputGate.FrameAction.Cancel: { Player localPlayer = Player.m_localPlayer; if (localPlayer != null) { ((Character)localPlayer).Message((MessageType)1, AtlasStrings.Get("quickpin.cancelled"), 0, (Sprite)null); } break; } case QuickPinInputGate.FrameAction.Capture: CaptureQuickPin(); break; } } else if ((int)_settings.QuickPinHotkey.Value != 0 && Input.GetKeyDown(_settings.QuickPinHotkey.Value)) { CaptureQuickPin(); } } if (!Minimap.IsOpen()) { _defaultPanel.NoteMapClosed(); } if (Minimap.IsOpen() && !Minimap.InTextInput() && !flag) { _mapUi.EnsureBuilt(((object)_settings.DrawerHotkey.Value/*cast due to .constrained prefix*/).ToString()); if (!_consentPromptChecked) { _consentPromptChecked = true; if (_consentPanel.NeedsFirstRunPrompt) { _consentPanel.ShowFirstRun(); } } if (PaletteActive()) { _palettePanel.UiScale = _settings.UiScale.Value; _palettePanel.EnsureBuilt(); } else { _palettePanel.SetUnavailable(); } if (_defaultPanel.IsArmed && _defaultPanel.ShouldAutoOpen(PaletteActive() && !_mapUi.HasFailed && AtlasAccessAllowed(out string _), _mapUi.AnySurfaceVisible)) { _palettePanel.UiScale = _settings.UiScale.Value; _palettePanel.EnsureBuilt(); _mapUi.OpenExclusive(_paletteToken, delegate { if (!_palettePanel.IsVisible) { _palettePanel.Toggle(); } }); } UpdateEditHint(unscaledDeltaTime); if (_pinCommands != null && !_workbenchPanel.IsVisible && (Input.GetKeyDown(_settings.WorkbenchHotkey.Value) || GamepadDown(_settings.WorkbenchGamepadButton.Value))) { OpenWorkbenchAtCursor(); } if (Input.GetKeyDown(_settings.DrawerHotkey.Value) || GamepadDown(_settings.DrawerGamepadButton.Value)) { _mapUi.OpenExclusive(_drawerToken, ToggleDrawer); } if (_displayController.ZoomTierChanged()) { _displayController.Apply(_pinStore, _pinAdapter); } bool flag2 = (MapInputGate.ConsumeClicks = _routeCommands != null && _routeCommands.Mode != RouteCommandHandler.MapMode.None && _routeCommands.UiModeOwned); if (flag2) { MinimapReflection.TrySuppressMapDragThisFrame(); } if (_routeCommands != null && _routeCommands.Mode != RouteCommandHandler.MapMode.None && (flag2 || Input.GetKey(_settings.RouteDrawModifier.Value)) && MinimapReflection.TryScreenToWorldPoint(Input.mousePosition, out var worldPosition)) { bool flag3 = MapPointerGuard.IsPointerOverCcUi(Vector2.op_Implicit(Input.mousePosition)); _routeCommands.HandleMapFrame(new RoadPoint(worldPosition.x, worldPosition.y, worldPosition.z), Input.GetMouseButton(0) && !flag3, Input.GetMouseButtonDown(0) && !flag3); } } _routeRedrawElapsed += unscaledDeltaTime; if (_routeRedrawPending && _routeRedrawElapsed >= 0.5f) { _routeRedrawElapsed = 0f; _routeRedrawPending = false; _routeRenderer.RedrawAll(_routeStore); _renderer.MarkVectorDataDirty(); } if (!WorldContext.TryGetWorldUid(out var uid) || _worldUid != uid) { _mapReady = false; _log.LogInfo((object)string.Format("Map session lifecycle: generation {0} (world-unloaded).", _mapSession.NoteTransition("world-unloaded"))); _workbenchPanel.Close(); _mapUi.CloseAllSurfaces(); _drawerPanel.FlushPosition(); MapInputGate.ConsumeClicks = false; _textFocusBlock.Release(); _quickPinGate.Disarm(); _pipeline?.EndAllStrokes(); _displayController.Reset(); _mapUi.Reset(); _palettePanel.Reset(); _chromeSweep.RestoreAll(); _birthTracker.Reset(); _pinAdapter.Reset(); SaveIfDirty(); SavePinsSnapshot(); return; } _surveyor.Tick(unscaledDeltaTime); _renderer.TickVectorLayer(unscaledDeltaTime, _atlas, _routeStore); _routeRenderer.TickVisibility(_renderer.VectorLayerActive); _relabelElapsed += unscaledDeltaTime; if (_relabelElapsed >= 1f) { _relabelElapsed = 0f; if (Minimap.IsOpen()) { _overlayRelabel.EnsureApplied(); } } _surveyScanner.Tick(unscaledDeltaTime, _surveyEngine, _pinStore); if (_settings.EnhancedPinPalette.Value && MinimapReflection.TryGetNamePin(out PinData namePin)) { PinData val = _birthTracker.Observe(namePin); if (val != null) { HandlePaletteBirth(val); } if (namePin != null && _birthTracker.IsArmed) { _pinAdapter.ApplyImmediateSprite(namePin, _birthTracker.IconId); } } _redrawElapsed += unscaledDeltaTime; if (_redrawPending && _redrawElapsed >= 0.5f) { _redrawPending = false; _redrawElapsed = 0f; _renderer.RedrawAll(_atlas); } _autosaveElapsed += unscaledDeltaTime; if (_autosaveElapsed >= _settings.AutosaveIntervalSeconds.Value) { _autosaveElapsed = 0f; SaveIfDirty(); _pinAdapter.AbsorbVanillaChanges(_pinStore); if (_pinAdapter.NeedsRebind && _pinAdapter.IsOperational) { _pinAdapter.ReconcileOnMapReady(_pinStore, "rendering-loss-repair"); _mapSession.NoteBound(); ReapplyDisplay(); } _pinPersistence.FlushJournal(); _routePersistence.FlushJournal(); SaveRejectedIfDirty(); } } internal string ExecutePinCommand(string[] args) { //IL_0071: 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) if (!AtlasAccessAllowed(out string denial)) { return denial; } if (_disposed || !_mapReady || _pinCommands == null) { return "Concerned Cartographer: no world is loaded yet."; } Player localPlayer = Player.m_localPlayer; if (localPlayer == null) { return "Concerned Cartographer: no local player."; } if (args.Length != 0 && string.Equals(args[0], "edit", StringComparison.OrdinalIgnoreCase)) { OpenWorkbenchNear(((Component)localPlayer).transform.position); return "Opening the Pin Workbench for the nearest pin."; } return _pinCommands.Execute(args, ((Component)localPlayer).transform.position); } private void WireDrawer() { _drawerPanel.LoadPosition = () => _settings.DrawerPanelPosition.Value; _drawerPanel.PositionCaptured = delegate(string stored) { _settings.DrawerPanelPosition.Value = stored; }; _drawerPanel.DirtToggled = delegate(bool value) { _settings.DrawerShowDirt.Value = value; _renderer.SetOverlayEnabled(RoadKind.Dirt, value); }; _drawerPanel.PavedToggled = delegate(bool value) { _settings.DrawerShowPaved.Value = value; _renderer.SetOverlayEnabled(RoadKind.Paved, value); }; _drawerPanel.PinsToggled = delegate(bool value) { _settings.DrawerShowPins.Value = value; _displayController.ShowPins = value; ReapplyDisplay(); }; _drawerPanel.ClusterToggled = delegate(bool value) { _settings.DrawerCluster.Value = value; _displayController.ClusterEnabled = value; ReapplyDisplay(); }; _drawerPanel.QueryApplied = delegate(string query) { _displayController.SetQuery(query); ReapplyDisplay(); }; _drawerPanel.ViewSaved = delegate(string name) { _savedViews.Save(new SavedView(name, _displayController.QueryText, _settings.DrawerShowDirt.Value, _settings.DrawerShowPaved.Value, _displayController.ShowPins, _displayController.ClusterEnabled)); _savedViewPersistence.Save(_savedViews); }; _drawerPanel.ViewApplied = delegate(string name) { ApplySavedView(name); }; _drawerPanel.ResultClicked = OpenWorkbenchForId; _drawerPanel.StatusLine = () => $"{_displayController.VisibleCount} shown · {_displayController.HiddenByFilter} hidden · {_displayController.ClusterCount} grouped"; _drawerPanel.TopResults = delegate { List<(string, AtlasId)> list = new List<(string, AtlasId)>(); PinQuery pinQuery = PinQuery.Parse(_displayController.QueryText); foreach (AtlasPin item in _pinStore.Living) { if (!item.Archived && pinQuery.Matches(item)) { list.Add(((item.Name.Length == 0) ? "(unnamed)" : item.Name, item.Id)); if (list.Count >= 6) { break; } } } return list; }; _drawerPanel.ViewNames = delegate { List list = new List(); foreach (SavedView view in _savedViews.Views) { list.Add(view.Name); if (list.Count >= 5) { break; } } return list; }; } private bool ApplySavedView(string name) { if (!_savedViews.TryGet(name, out SavedView view)) { return false; } _settings.DrawerShowDirt.Value = view.ShowDirt; _settings.DrawerShowPaved.Value = view.ShowPaved; _settings.DrawerShowPins.Value = view.ShowPins; _settings.DrawerCluster.Value = view.ClusterEnabled; _renderer.SetOverlayEnabled(RoadKind.Dirt, view.ShowDirt); _renderer.SetOverlayEnabled(RoadKind.Paved, view.ShowPaved); _displayController.ShowPins = view.ShowPins; _displayController.ClusterEnabled = view.ClusterEnabled; _displayController.SetQuery(view.Query); ReapplyDisplay(); return true; } private void ReapplyDisplay() { if (_mapReady) { _displayController.Apply(_pinStore, _pinAdapter); } } private void ResyncPins() { _pinAdapter.SyncAllPins(_pinStore, _displayController.IsDisplayHidden); ReapplyDisplay(); } private void CaptureQuickPin() { if (_quickPinCapture.TryCapture(_pinStore, out string message, out AtlasPin created)) { if (created != null) { _displayController.MarkStickyVisible(created.Id); } ResyncPins(); } if (message.Length > 0) { Player localPlayer = Player.m_localPlayer; if (localPlayer != null) { ((Character)localPlayer).Message((MessageType)1, message, 0, (Sprite)null); } } } private bool PaletteActive() { if (_settings.EnhancedPinPalette.Value && !_compatibility.PinManagerPresent) { return !_palettePanel.HasFailed; } return false; } private void OpenSidePanel(int token, CcSidePanel panel) { if (!AtlasAccessAllowed(out string denial)) { Player localPlayer = Player.m_localPlayer; if (localPlayer != null) { ((Character)localPlayer).Message((MessageType)1, denial, 0, (Sprite)null); } } else { panel.UiScale = _settings.UiScale.Value; _mapUi.OpenExclusive(token, panel.Toggle); } } private void ArmQuickPin() { if (!AtlasAccessAllowed(out string denial)) { Player localPlayer = Player.m_localPlayer; if (localPlayer != null) { ((Character)localPlayer).Message((MessageType)1, denial, 0, (Sprite)null); } return; } try { _mapUi.CloseAllSurfaces(); Minimap instance = Minimap.instance; if (instance != null) { instance.SetMapMode((MapMode)1); } } catch { } _quickPinGate.Arm(Time.frameCount); Player localPlayer2 = Player.m_localPlayer; if (localPlayer2 != null) { ((Character)localPlayer2).Message((MessageType)2, AtlasStrings.Get("quickpin.armed"), 0, (Sprite)null); } } private void ToggleDrawer() { if (AtlasAccessAllowed(out string denial)) { _drawerPanel.UiScale = _settings.UiScale.Value; _drawerPanel.Toggle(_settings.DrawerShowDirt.Value, _settings.DrawerShowPaved.Value, _settings.DrawerShowPins.Value, _settings.DrawerCluster.Value); return; } Player localPlayer = Player.m_localPlayer; if (localPlayer != null) { ((Character)localPlayer).Message((MessageType)1, denial, 0, (Sprite)null); } } private void UpdateEditHint(float unscaledDeltaTime) { //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_0172: Unknown result type (might be due to invalid IL or missing references) _hintElapsed += unscaledDeltaTime; if (_hintElapsed < 0.2f) { return; } _hintElapsed = 0f; EnforceVanillaPaletteVisibility(); _mapUi.UpdateLayout(); if (_workbenchPanel.IsVisible) { _mapUi.SetHint(null); _mapUi.SetContext(null, null); return; } PinData pin = null; Vector3 worldPosition; float distance; bool flag = MinimapReflection.TryScreenToWorldPoint(Input.mousePosition, out worldPosition) && _pinAdapter.TryFindNearest(worldPosition, 30f, out pin, out distance); if (flag && _pinAdapter.TryGetManagedId(pin, out var id)) { _contextGrace = 1.5f; _mapUi.SetHint(AtlasStrings.Format("hud.editHint", _settings.WorkbenchHotkey.Value)); AtlasId captured = id; _mapUi.SetContext(AtlasStrings.Get("hud.editPin"), delegate { OpenWorkbenchForId(captured); }); } else if (flag && _pinAdapter.IsAdoptableVanilla(pin) && !_compatibility.PinManagerPresent) { _contextGrace = 1.5f; _mapUi.SetHint(AtlasStrings.Format("hud.editHint", _settings.WorkbenchHotkey.Value)); PinData capturedPin = pin; _mapUi.SetContext(AtlasStrings.Get("hud.upgradeEdit"), delegate { UpgradeAndEdit(capturedPin); }); } else if (!_mapUi.PointerOverContext) { _contextGrace -= 0.2f; if (!(_contextGrace > 0f)) { _mapUi.SetHint(null); _mapUi.SetContext(null, null); } } } private void OpenWorkbenchForId(AtlasId id) { if (_pinCommands == null || !_pinStore.TryGet(id, out AtlasPin pin) || pin.Deleted) { return; } if (!AtlasAccessAllowed(out string denial)) { Player localPlayer = Player.m_localPlayer; if (localPlayer != null) { ((Character)localPlayer).Message((MessageType)1, denial, 0, (Sprite)null); } } else { _workbenchPanel.UiScale = _settings.UiScale.Value; _mapUi.OpenExclusive(_workbenchToken, delegate { _workbenchPanel.OpenForManaged(pin, _pinCommands.Operations, ResyncPins); }); } } private void UpgradeAndEdit(PinData pin) { if (_pinCommands == null) { return; } if (!AtlasAccessAllowed(out string denial)) { Player localPlayer = Player.m_localPlayer; if (localPlayer != null) { ((Character)localPlayer).Message((MessageType)1, denial, 0, (Sprite)null); } } else { if (!_pinAdapter.ContainsPin(pin) || !_pinAdapter.IsAdoptableVanilla(pin)) { return; } AtlasPin managed = _pinAdapter.Adopt(_pinStore, pin); if (managed != null) { _workbenchPanel.UiScale = _settings.UiScale.Value; _mapUi.OpenExclusive(_workbenchToken, delegate { _workbenchPanel.OpenForManaged(managed, _pinCommands.Operations, ResyncPins); }); } } } private void HandlePaletteBirth(PinData born) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Expected I4, but got Unknown string committedName = born.m_name ?? ""; bool num = _pinAdapter.ContainsPin(born); PinData val = (num ? null : _pinAdapter.TryFindAdoptableAt(born.m_pos, committedName)); PaletteBirthResolution.Action action = PaletteBirthResolution.Decide(num, num && _pinAdapter.IsAdoptableVanilla(born), val != null, committedName); AtlasPin atlasPin; switch (action) { case PaletteBirthResolution.Action.AdoptBorn: atlasPin = _pinAdapter.Adopt(_pinStore, born); break; case PaletteBirthResolution.Action.AdoptReplacement: atlasPin = _pinAdapter.Adopt(_pinStore, val); _log.LogInfo((object)"Palette birth: the naming close replaced the pin object; adopted the replacement at the same spot (RC12 blocker 5)."); break; case PaletteBirthResolution.Action.RecreateManaged: { RoadPoint position = new RoadPoint(born.m_pos.x, born.m_pos.y, born.m_pos.z); int bornType = (int)born.m_type; atlasPin = _pinStore.Create(delegate(AtlasPin pin) { pin.Name = committedName; pin.IconId = IconRegistry.FromVanillaType(bornType); pin.Source = AtlasPinSource.Managed; pin.Position = position; }); _log.LogInfo((object)"Palette birth: the named pin's rendering vanished at naming close; recreated it as a managed marker (RC12 blocker 5)."); break; } case PaletteBirthResolution.Action.DropForeign: _log.LogInfo((object)"Palette birth: the named pin is not adoptable (foreign or already tracked); left untouched."); return; default: if (_settings.DebugLogging.Value) { _log.LogInfo((object)"Palette birth: naming was cancelled; nothing created."); } return; } if (atlasPin == null) { return; } string iconId = _birthTracker.IconId; string category = _birthTracker.Category; _pinStore.Mutate(atlasPin.Id, delegate(AtlasPin pin) { if (iconId.Length > 0) { pin.IconId = iconId; } pin.Category = category; pin.Source = AtlasPinSource.Managed; }); _palettePanel.NoteUsed(iconId); _displayController.MarkStickyVisible(atlasPin.Id); ResyncPins(); if (_settings.DebugLogging.Value) { _log.LogInfo((object)$"Palette marker born managed: {atlasPin.Id} icon {iconId} category \"{category}\"."); } } private void EnforceVanillaPaletteVisibility() { bool flag = !_settings.Enabled.Value || !_settings.EnhancedPinPalette.Value || _settings.ShowVanillaPinPalette.Value || _settings.ShowVanillaMapControls.Value || _compatibility.PinManagerPresent || _palettePanel.HasFailed || _mapUi.HasFailed; foreach (GameObject placeableIconButton in MinimapReflection.GetPlaceableIconButtons()) { if ((Object)(object)placeableIconButton != (Object)null && placeableIconButton.activeSelf != flag) { placeableIconButton.SetActive(flag); } } bool flag2 = !_settings.Enabled.Value || _settings.ShowVanillaMapControls.Value || _compatibility.PinManagerPresent || _systemMarkersPanel.HasFailed || _drawerPanel.HasFailed || _mapUi.HasFailed; foreach (GameObject systemFilterButton in MinimapReflection.GetSystemFilterButtons()) { if ((Object)(object)systemFilterButton != (Object)null && systemFilterButton.activeSelf != flag2) { systemFilterButton.SetActive(flag2); } } GameObject val = null; try { val = (((Object)(object)Minimap.instance != (Object)null && (Object)(object)Minimap.instance.m_publicPosition != (Object)null) ? ((Component)Minimap.instance.m_publicPosition).gameObject : null); if ((Object)(object)val != (Object)null && val.activeSelf != flag2) { val.SetActive(flag2); } } catch { } if (MinimapReflection.TryGetVanillaRailContainers(out GameObject placeablesContainer, out GameObject filtersContainer, out bool sharedContainer, out string diagnostics)) { if (sharedContainer) { SetRailContainerActive(placeablesContainer, flag || flag2); } else { if ((Object)(object)placeablesContainer != (Object)null) { SetRailContainerActive(placeablesContainer, flag); } if ((Object)(object)filtersContainer != (Object)null) { SetRailContainerActive(filtersContainer, flag2); } } } if (!string.Equals(diagnostics, _lastRailDiagnostics, StringComparison.Ordinal)) { _lastRailDiagnostics = diagnostics; _log.LogInfo((object)("Vanilla rail chrome: " + diagnostics + ".")); } if (OrphanChromeRule.MustRestore(flag || flag2)) { _chromeSweep.RestoreAll(); } else { List list = new List { placeablesContainer, filtersContainer, val }; foreach (GameObject placeableIconButton2 in MinimapReflection.GetPlaceableIconButtons()) { list.Add(placeableIconButton2); } foreach (GameObject systemFilterButton2 in MinimapReflection.GetSystemFilterButtons()) { list.Add(systemFilterButton2); } _chromeSweep.Sweep(list); } if (!string.Equals(_chromeSweep.LastDiagnostics, _lastChromeSweepDiagnostics, StringComparison.Ordinal)) { _lastChromeSweepDiagnostics = _chromeSweep.LastDiagnostics; if (_lastChromeSweepDiagnostics.Length > 0) { _log.LogInfo((object)("Vanilla chrome sweep: " + _lastChromeSweepDiagnostics + ".")); } } } private static void SetRailContainerActive(GameObject container, bool visible) { if (container.activeSelf != visible) { container.SetActive(visible); } } private void RestoreVanillaPalette() { try { _chromeSweep.RestoreAll(); if (MinimapReflection.TryGetVanillaRailContainers(out GameObject placeablesContainer, out GameObject filtersContainer, out bool _, out string _)) { if ((Object)(object)placeablesContainer != (Object)null && !placeablesContainer.activeSelf) { placeablesContainer.SetActive(true); } if ((Object)(object)filtersContainer != (Object)null && !filtersContainer.activeSelf) { filtersContainer.SetActive(true); } } foreach (GameObject placeableIconButton in MinimapReflection.GetPlaceableIconButtons()) { if ((Object)(object)placeableIconButton != (Object)null && !placeableIconButton.activeSelf) { placeableIconButton.SetActive(true); } } foreach (GameObject systemFilterButton in MinimapReflection.GetSystemFilterButtons()) { if ((Object)(object)systemFilterButton != (Object)null && !systemFilterButton.activeSelf) { systemFilterButton.SetActive(true); } } if ((Object)(object)Minimap.instance != (Object)null && (Object)(object)Minimap.instance.m_publicPosition != (Object)null && !((Component)Minimap.instance.m_publicPosition).gameObject.activeSelf) { ((Component)Minimap.instance.m_publicPosition).gameObject.SetActive(true); } } catch { } } private void OpenWorkbenchAtCursor() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) if (!MinimapReflection.TryScreenToWorldPoint(Input.mousePosition, out var worldPosition)) { Player localPlayer = Player.m_localPlayer; if (localPlayer == null) { return; } worldPosition = ((Component)localPlayer).transform.position; } OpenWorkbenchNear(worldPosition); } private bool AtlasAccessAllowed(out string denial) { //IL_003d: Unknown result type (might be due to invalid IL or missing references) denial = ""; try { if ((Object)(object)ZoneSystem.instance == (Object)null || !ZoneSystem.instance.GetGlobalKey("nomap")) { return true; } Player localPlayer = Player.m_localPlayer; if (localPlayer != null && SurveyScanner.AnyInstanceNear("piece_maptable", ((Component)localPlayer).transform.position, 10f)) { return true; } denial = AtlasStrings.Get("hud.noMapNeedTable"); return false; } catch { return true; } } private void ShowOnboardingOnce() { if (_onboardingChecked) { return; } _onboardingChecked = true; try { string path = Path.Combine(Paths.ConfigPath, "ConcernedCatMods", "ConcernedCartographer", "onboarding-shown.txt"); if (!File.Exists(path)) { Directory.CreateDirectory(Path.GetDirectoryName(path)); File.WriteAllText(path, DateTime.UtcNow.ToString("o")); Player localPlayer = Player.m_localPlayer; if (localPlayer != null) { ((Character)localPlayer).Message((MessageType)2, AtlasStrings.Get("hud.onboarding"), 0, (Sprite)null); } } } catch { } } private static bool GamepadDown(string buttonName) { if (string.IsNullOrEmpty(buttonName)) { return false; } try { return ZInput.GetButtonDown(buttonName); } catch { return false; } } private void OpenWorkbenchNear(Vector3 world) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_01fb: Unknown result type (might be due to invalid IL or missing references) if (_pinCommands == null) { return; } if (!AtlasAccessAllowed(out string denial)) { Player localPlayer = Player.m_localPlayer; if (localPlayer != null) { ((Character)localPlayer).Message((MessageType)1, denial, 0, (Sprite)null); } return; } _workbenchPanel.UiScale = _settings.UiScale.Value; RoadPoint other = new RoadPoint(world.x, world.y, world.z); PinOperations operations = _pinCommands.Operations; Action resync = ResyncPins; AtlasPin atlasPin = null; float num = 30f; foreach (AtlasPin item in _pinStore.Living) { float num2 = item.Position.HorizontalDistanceTo(in other); if (num2 < num) { num = num2; atlasPin = item; } } if (atlasPin != null) { AtlasPin captured = atlasPin; _mapUi.OpenExclusive(_workbenchToken, delegate { _workbenchPanel.OpenForManaged(captured, operations, resync); }); } else { if (!_pinAdapter.TryFindNearest(world, 30f, out PinData pin, out float _)) { return; } if (_pinAdapter.IsAdoptableVanilla(pin) && _compatibility.PinManagerPresent) { _workbenchPanel.OpenReadOnly("\"" + pin.m_name + "\" — another pin manager is installed; use 'cc_pins adopt' to manage this pin here"); } else if (_pinAdapter.IsAdoptableVanilla(pin)) { PinData captured2 = pin; _workbenchPanel.OpenAdoptPrompt(captured2.m_name ?? "", () => _pinAdapter.Adopt(_pinStore, captured2), operations, resync); } else { _workbenchPanel.OpenReadOnly($"\"{pin.m_name}\" ({pin.m_type})"); } } } public void SaveAll() { _drawerPanel.FlushPosition(); SaveIfDirty(); SavePinsSnapshot(); _savedViewPersistence.Save(_savedViews); } internal string ExecuteAtlasCommand(string[] args) { if (!AtlasAccessAllowed(out string denial)) { return denial; } if (_disposed || !_mapReady) { return "Concerned Cartographer: no world is loaded yet."; } string text = ((args.Length == 0) ? "status" : args[0].ToLowerInvariant()); string text2 = ((args.Length > 1) ? string.Join(" ", args, 1, args.Length - 1) : ""); switch (text) { case "status": return "Atlas view: query \"" + _displayController.QueryText + "\", pins " + (_displayController.ShowPins ? "on" : "off") + ", cluster " + (_displayController.ClusterEnabled ? "on" : "off") + ", dirt " + (_settings.DrawerShowDirt.Value ? "on" : "off") + ", paved " + (_settings.DrawerShowPaved.Value ? "on" : "off") + ". " + $"{_displayController.VisibleCount} shown, {_displayController.HiddenByFilter} filtered, {_displayController.ClusterCount} clusters."; case "query": _displayController.SetQuery(text2); ReapplyDisplay(); return $"Filter applied: \"{text2}\" — {_displayController.VisibleCount} shown, {_displayController.HiddenByFilter} filtered. Filters are display-only; 'cc_atlas clear' restores everything."; case "clear": _displayController.SetQuery(""); ReapplyDisplay(); return "Filter cleared; all pins shown."; case "paved": case "dirt": case "pins": case "cluster": { if (!TryParseOnOff(text2, out var enabled)) { return "Usage: cc_atlas " + text + " on|off"; } ApplyToggle(text, enabled); return text + " " + (enabled ? "on" : "off") + "."; } case "view": return HandleViewCommand(text2); case "compat": return _compatibility.Report(); case "backup": { long? worldUid = _worldUid; if (worldUid.HasValue) { long valueOrDefault = worldUid.GetValueOrDefault(); return "Backed up to " + _backupTools.Backup(valueOrDefault); } return "No world loaded."; } case "backups": { long? worldUid = _worldUid; if (worldUid.HasValue) { long valueOrDefault3 = worldUid.GetValueOrDefault(); List list = _backupTools.ListBackups(valueOrDefault3); if (list.Count == 0) { return "No backups yet. 'cc_atlas backup' creates one; exports/imports use the same folders."; } StringBuilder stringBuilder2 = new StringBuilder($"{list.Count} backup(s), newest first:"); for (int i = 0; i < list.Count && i < 10; i++) { stringBuilder2.Append($"\n {i + 1}. {Path.GetFileName(list[i])}"); } stringBuilder2.Append("\n'cc_atlas restore ' restores one (takes a safety backup first)."); return stringBuilder2.ToString(); } return "No world loaded."; } case "restore": { long? worldUid = _worldUid; if (worldUid.HasValue) { long valueOrDefault4 = worldUid.GetValueOrDefault(); List list2 = _backupTools.ListBackups(valueOrDefault4); if (!int.TryParse(text2.Trim(), out var result) || result < 1 || result > list2.Count) { return "Usage: cc_atlas restore (see 'cc_atlas backups')"; } return _backupTools.Restore(valueOrDefault4, list2[result - 1]); } return "No world loaded."; } case "support": { long? worldUid = _worldUid; if (worldUid.HasValue) { long valueOrDefault2 = worldUid.GetValueOrDefault(); string text3 = _backupTools.WriteSupportReport(valueOrDefault2, "0.10.0", $"enabled={_settings.Enabled.Value}, capture={_settings.CaptureConstructionActions.Value}, " + $"reconcile={_settings.ReconcileTerrainChanges.Value}, " + $"survey={_settings.SurveyRulesEnabled.Value}, cluster={_settings.DrawerCluster.Value}, " + $"contrast={_settings.HighContrast.Value}, uiScale={_settings.UiScale.Value}"); return "Sanitized support report (no positions/names/notes/world ids/paths) written to " + text3; } return "No world loaded."; } case "views": { StringBuilder stringBuilder = new StringBuilder("Saved views:"); if (_savedViews.Views.Count == 0) { return "Saved views: none. 'cc_atlas view save ' captures the current filter/layer state."; } foreach (SavedView view in _savedViews.Views) { stringBuilder.Append("\n \"" + view.Name + "\" — query \"" + view.Query + "\""); } return stringBuilder.ToString(); } default: return "Usage: cc_atlas [status|query |clear|pins on/off|cluster on/off|dirt on/off|paved on/off|view save/apply/del |views]"; } } internal string ExecuteSurveyCommand(string[] args) { if (!AtlasAccessAllowed(out string denial)) { return denial; } if (_disposed || !_mapReady) { return "Concerned Cartographer: no world is loaded yet."; } string text = ((args.Length == 0) ? "status" : args[0].ToLowerInvariant()); string text2 = ((args.Length > 1) ? args[1].ToLowerInvariant() : ""); string text3 = ((args.Length > 1) ? args[1] : ""); IReadOnlyList observations = _surveyEngine.Observations; switch (text) { case "status": return "Survey: " + (_settings.SurveyRulesEnabled.Value ? "ENABLED" : "disabled (Survey/SurveyRulesEnabled)") + ", " + $"{_surveyEngine.Rules.Rules.Count} rule(s), {_surveyEngine.Rules.Blacklist.Count} blacklist pattern(s), " + $"{observations.Count} pending observation(s). Rules file: {SurveyRulePersistence.RulePath}"; case "list": { if (observations.Count == 0) { return "No pending observations."; } StringBuilder stringBuilder = new StringBuilder($"{observations.Count} observation(s):"); for (int i = 0; i < observations.Count && i < 15; i++) { SurveyEngine.Observation observation = observations[i]; stringBuilder.Append($"\n {i + 1}. {observation.SuggestedName} [{observation.Category}] at ({observation.Position.X:0}, {observation.Position.Z:0})"); } if (observations.Count > 15) { stringBuilder.Append($"\n ... and {observations.Count - 15} more."); } stringBuilder.Append("\ncc_survey accept / reject "); return stringBuilder.ToString(); } case "accept": { if (text2 == "all") { int num3 = _surveyEngine.AcceptAll(_pinStore, delegate(AtlasPin atlasPin) { _displayController.MarkStickyVisible(atlasPin.Id); }); ResyncPins(); return $"Accepted {num3} observation(s) as markers."; } Guid? guid = null; int result3; if (text3.StartsWith("id:", StringComparison.OrdinalIgnoreCase) && Guid.TryParse(text3.Substring(3), out var result2)) { guid = result2; } else if (int.TryParse(text2, out result3) && result3 >= 1 && result3 <= observations.Count) { guid = observations[result3 - 1].Id; } if (guid.HasValue) { Guid valueOrDefault = guid.GetValueOrDefault(); if (!_surveyEngine.Accept(valueOrDefault, _pinStore, out AtlasPin created)) { return "That observation is no longer pending — the list just updated."; } if (created != null) { _displayController.MarkStickyVisible(created.Id); } ResyncPins(); return "Accepted \"" + created?.Name + "\" as a marker."; } return "Usage: cc_survey accept "; } case "reject": { if (text2 == "all") { int num6 = _surveyEngine.RejectAll(DateTime.UtcNow); SaveRejectedIfDirty(); return $"Rejected {num6} observation(s); they moved to the Rejected list."; } Guid? guid2 = null; int result7; if (text3.StartsWith("id:", StringComparison.OrdinalIgnoreCase) && Guid.TryParse(text3.Substring(3), out var result6)) { guid2 = result6; } else if (int.TryParse(text2, out result7) && result7 >= 1 && result7 <= observations.Count) { guid2 = observations[result7 - 1].Id; } if (guid2.HasValue) { Guid valueOrDefault2 = guid2.GetValueOrDefault(); if (!_surveyEngine.Reject(valueOrDefault2, DateTime.UtcNow)) { return "That observation is no longer pending — the list just updated."; } SaveRejectedIfDirty(); return "Rejected; it moved to the Rejected list."; } return "Usage: cc_survey reject "; } case "rejected": { if (_surveyEngine.Rejected.Count == 0) { return "The Rejected list is empty."; } StringBuilder stringBuilder2 = new StringBuilder($"{_surveyEngine.Rejected.Count} rejected:"); for (int num4 = 0; num4 < _surveyEngine.Rejected.Count && num4 < 15; num4++) { SurveyEngine.RejectedObservation rejectedObservation = _surveyEngine.Rejected[num4]; stringBuilder2.Append($"\n {num4 + 1}. {rejectedObservation.SuggestedName} [{rejectedObservation.Category}] at ({rejectedObservation.Position.X:0}, {rejectedObservation.Position.Z:0})"); } if (_surveyEngine.Rejected.Count > 15) { stringBuilder2.Append($"\n ... and {_surveyEngine.Rejected.Count - 15} more."); } return stringBuilder2.ToString(); } case "restore": { if (text2 == "all") { int num = _surveyEngine.RestoreAllRejected(DateTime.UtcNow); SaveRejectedIfDirty(); return $"Restored {num} rejected observation(s) to pending review."; } int num2 = -1; int result; if (text3.StartsWith("key:", StringComparison.OrdinalIgnoreCase)) { num2 = _surveyEngine.FindRejectedIndex(text3.Substring(4)); if (num2 < 0) { return "That rejected entry is no longer listed — the list just updated."; } } else if (int.TryParse(text2, out result)) { num2 = result - 1; } if (num2 >= 0 && _surveyEngine.RestoreRejected(num2, DateTime.UtcNow)) { SaveRejectedIfDirty(); return "Restored the rejected entry to pending review."; } return "Usage: cc_survey restore "; } case "acceptrejected": { int num5 = -1; int result5; if (text3.StartsWith("key:", StringComparison.OrdinalIgnoreCase)) { num5 = _surveyEngine.FindRejectedIndex(text3.Substring(4)); if (num5 < 0) { return "That rejected entry is no longer listed — the list just updated."; } } else if (int.TryParse(text2, out result5)) { num5 = result5 - 1; } if (num5 >= 0 && _surveyEngine.AcceptRejected(num5, _pinStore, out AtlasPin created2)) { if (created2 != null) { _displayController.MarkStickyVisible(created2.Id); } ResyncPins(); SaveRejectedIfDirty(); return "Accepted \"" + created2?.Name + "\" as a marker."; } return "Usage: cc_survey acceptrejected "; } case "rules": { if (_surveyEngine.Rules.Rules.Count == 0) { return "No survey rules. Add one from the Survey panel's Rules view."; } StringBuilder stringBuilder3 = new StringBuilder($"{_surveyEngine.Rules.Rules.Count} rule(s):"); for (int num7 = 0; num7 < _surveyEngine.Rules.Rules.Count; num7++) { SurveyRule surveyRule2 = _surveyEngine.Rules.Rules[num7]; stringBuilder3.Append(string.Format("\n {0}. [{1}] {2} → {3} ({4})", num7 + 1, surveyRule2.Enabled ? "on " : "OFF", surveyRule2.Pattern, surveyRule2.Category, surveyRule2.IconId)); } return stringBuilder3.ToString(); } case "ruleon": case "ruleoff": { if (int.TryParse(text2, out var result4)) { SurveyRule surveyRule = _surveyEngine.Rules.SetRuleEnabled(result4 - 1, text == "ruleon"); if (surveyRule != null) { _surveyRulePersistence.Save(_surveyEngine.Rules); return "Rule \"" + surveyRule.Pattern + "\" is now " + (surveyRule.Enabled ? "enabled" : "disabled") + "."; } } return "Usage: cc_survey ruleon / ruleoff "; } case "ruledel": { if (int.TryParse(text2, out var result8)) { SurveyRule surveyRule3 = _surveyEngine.Rules.RemoveRuleAt(result8 - 1); if (surveyRule3 != null) { _surveyRulePersistence.Save(_surveyEngine.Rules); return "Removed rule \"" + surveyRule3.Pattern + "\"."; } } return "Usage: cc_survey ruledel "; } case "ruleadd": { string text4 = SurveyRuleSet.Clean(text2); if (text4.Length == 0 || text4 == "*") { return "Usage: cc_survey ruleadd ('bush*' matches prefixes)"; } string text5 = ((args.Length > 2) ? args[2] : "cc:resource"); string text6 = ((args.Length > 3) ? string.Join(" ", args, 3, args.Length - 3) : "Resources"); _surveyEngine.Rules.AddRule(new SurveyRule(text4, text5, text6, 30f, 120f)); _surveyRulePersistence.Save(_surveyEngine.Rules); return "Added rule \"" + text4 + "\" → " + text6 + " (" + text5 + ")."; } case "reload": _surveyEngine.Rules = _surveyRulePersistence.LoadOrCreate(); return $"Reloaded {_surveyEngine.Rules.Rules.Count} rule(s) and {_surveyEngine.Rules.Blacklist.Count} blacklist pattern(s)."; case "path": return SurveyRulePersistence.RulePath + " (the file is the shareable import/export format)"; default: return "Usage: cc_survey [status|list|accept |reject |rejected|restore |acceptrejected |rules|ruleon/ruleoff/ruledel |ruleadd [icon] [category]|reload|path]"; } } private void SaveRejectedIfDirty() { if (!_surveyEngine.RejectedDirty) { return; } long? worldUid = _worldUid; if (worldUid.HasValue) { long valueOrDefault = worldUid.GetValueOrDefault(); if (_surveyRejectedPersistence.Save(valueOrDefault, _surveyEngine.Rejected)) { _surveyEngine.MarkRejectedClean(); } } } private string HandleViewCommand(string remainder) { int num = remainder.IndexOf(' '); string text = ((num < 0) ? remainder : remainder.Substring(0, num)).ToLowerInvariant(); string text2 = ((num < 0) ? "" : remainder.Substring(num + 1).Trim()); if (text2.Length == 0) { return "Usage: cc_atlas view save|apply|del "; } switch (text) { case "save": _drawerPanel.ViewSaved?.Invoke(text2); return "View \"" + text2 + "\" saved with the current filter and layer state."; case "apply": if (!ApplySavedView(text2)) { return "No view named \"" + text2 + "\"."; } return "View \"" + text2 + "\" applied."; case "del": { bool num2 = _savedViews.Remove(text2); _savedViewPersistence.Save(_savedViews); if (!num2) { return "No view named \"" + text2 + "\"."; } return "View \"" + text2 + "\" deleted."; } default: return "Usage: cc_atlas view save|apply|del "; } } private void ApplyToggle(string which, bool enabled) { switch (which) { case "pins": _settings.DrawerShowPins.Value = enabled; _displayController.ShowPins = enabled; ReapplyDisplay(); break; case "cluster": _settings.DrawerCluster.Value = enabled; _displayController.ClusterEnabled = enabled; ReapplyDisplay(); break; case "dirt": _settings.DrawerShowDirt.Value = enabled; _renderer.SetOverlayEnabled(RoadKind.Dirt, enabled); break; case "paved": _settings.DrawerShowPaved.Value = enabled; _renderer.SetOverlayEnabled(RoadKind.Paved, enabled); break; } } private static bool TryParseOnOff(string text, out bool enabled) { switch (text.Trim().ToLowerInvariant()) { case "on": case "true": case "1": enabled = true; return true; case "off": case "false": case "0": enabled = false; return true; default: enabled = false; return false; } } private void SavePinsSnapshot() { long? worldUid = _worldUid; if (worldUid.HasValue) { long valueOrDefault = worldUid.GetValueOrDefault(); _pinPersistence.Save(valueOrDefault, _pinStore); _routePersistence.Save(valueOrDefault, _routeStore); } } internal string ExecuteSyncCommand(string[] args) { if (!AtlasAccessAllowed(out string denial)) { return denial; } if (_disposed || !_mapReady) { return "Concerned Cartographer: no world is loaded yet."; } string text = ((args.Length == 0) ? "status" : args[0].ToLowerInvariant()); string text2 = ((args.Length > 1) ? string.Join(" ", args, 1, args.Length - 1).Trim() : ""); switch (text) { case "status": var (list3, list4) = SyncPlanner.CollectShared(_pinStore, _routeStore); return $"Sync: sharing {list3.Count} pin(s) and {list4.Count} route(s) " + $"(scope table/server, tombstones included). Inbox: {_syncInbox.Envelopes.Count} pending share(s). " + "Set a pin/route scope with 'cc_pins scope table' or 'cc_routes ...' to share it; 'cc_sync share' broadcasts."; case "share": { var (list, list2) = SyncPlanner.CollectShared(_pinStore, _routeStore); if (list.Count == 0 && list2.Count == 0) { return "Nothing is scoped for sharing yet. 'cc_pins scope table' near a pin shares it."; } Player localPlayer = Player.m_localPlayer; string authorName = ((localPlayer != null) ? localPlayer.GetPlayerName() : null) ?? ""; _syncTransport.Share(_authorId, authorName, list, list2, out string message); _log.LogInfo((object)("Sync share: " + message)); return message; } case "inbox": { if (_syncInbox.Envelopes.Count == 0) { return "Sync inbox: empty."; } StringBuilder stringBuilder = new StringBuilder("Sync inbox:"); foreach (SyncInbox.Envelope envelope3 in _syncInbox.Envelopes) { stringBuilder.Append($"\n {envelope3.AuthorName}: {envelope3.Pins.Count} pin(s), {envelope3.Routes.Count} route(s) " + $"at {envelope3.ReceivedUtc:HH:mm} UTC — 'cc_sync preview {envelope3.AuthorName}'"); } return stringBuilder.ToString(); } case "preview": { if (!_syncInbox.TryPeek(text2, out SyncInbox.Envelope envelope2)) { return "No pending share from \"" + text2 + "\". 'cc_sync inbox' lists them."; } SyncPlan syncPlan2 = SyncPlanner.Plan(_pinStore, _routeStore, envelope2.Pins, envelope2.Routes); List list5 = syncPlan2.DeletionNames(10); string text4 = ((list5.Count == 0) ? "" : ("\n Would DELETE: " + string.Join(", ", list5) + ((syncPlan2.TombstonePins.Count + syncPlan2.TombstoneRoutes.Count > list5.Count) ? $" (+{syncPlan2.TombstonePins.Count + syncPlan2.TombstoneRoutes.Count - list5.Count} more)" : ""))); return "Share from " + envelope2.AuthorName + ": " + syncPlan2.Summary() + "." + text4 + ((syncPlan2.PinConflicts.Count + syncPlan2.RouteConflicts.Count > 0) ? (" Apply with 'cc_sync apply " + envelope2.AuthorName + " mine' (keep local on conflicts) or '... theirs'.") : (" Apply with 'cc_sync apply " + envelope2.AuthorName + "'.")); } case "apply": { string[] array = text2.Split(new char[1] { ' ' }); bool flag = array.Length > 1 && string.Equals(array[^1], "theirs", StringComparison.OrdinalIgnoreCase); string text3 = ((flag || (array.Length > 1 && string.Equals(array[^1], "mine", StringComparison.OrdinalIgnoreCase))) ? string.Join(" ", array, 0, array.Length - 1) : text2); if (!_syncInbox.TryTake(text3, out SyncInbox.Envelope envelope)) { return "No pending share from \"" + text3 + "\"."; } SyncPlan syncPlan = SyncPlanner.Plan(_pinStore, _routeStore, envelope.Pins, envelope.Routes); int num = SyncPlanner.Apply(syncPlan, _pinStore, _routeStore, flag); ResyncPins(); _routeRedrawPending = true; SavePinsSnapshot(); _log.LogInfo((object)$"Sync apply: {num} change(s); {syncPlan.Summary()}"); return $"Applied {num} change(s) from {envelope.AuthorName} " + "(" + (flag ? "conflicts took their side" : "conflicts kept your side") + "). " + syncPlan.Summary(); } case "clear": _syncInbox.Clear(); return "Sync inbox cleared."; default: return "Usage: cc_sync [status|share|inbox|preview |apply [mine|theirs]|clear]"; } } internal string ExecuteRouteCommand(string[] args) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) if (!AtlasAccessAllowed(out string denial)) { return denial; } if (_disposed || !_mapReady || _routeCommands == null) { return "Concerned Cartographer: no world is loaded yet."; } Player localPlayer = Player.m_localPlayer; if (localPlayer == null) { return "Concerned Cartographer: no local player."; } return _routeCommands.Execute(args, ((Component)localPlayer).transform.position); } private void HandleTerrainOperation(CapturedTerrainOperation operation) { //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_0292: Unknown result type (might be due to invalid IL or missing references) if (_disposed || !_settings.Enabled.Value || !_mapReady || _pipeline == null) { return; } long? worldUid = _worldUid; if (!worldUid.HasValue) { return; } _rateLimited.Info("terrain-action-" + operation.Category, $"Terrain action classified: {operation.ActionDescription} r={operation.RadiusMeters:0.#}m."); RoadPoint center = new RoadPoint(operation.Position.x, operation.Position.y, operation.Position.z); float radiusMeters = operation.RadiusMeters + 1f; if (!operation.RoadKind.HasValue) { int num = _terrainIntent.AddExclusion(center.X, center.Z, radiusMeters); if (num > 0 && _settings.DebugLogging.Value) { _rateLimited.Info("terrain-intent-add", $"Terraforming r={operation.RadiusMeters:0.#}m: " + $"{num} cell(s) marked not-road ({_terrainIntent.Count} total)."); } } else { _terrainIntent.ClearExclusion(center.X, center.Z, radiusMeters); } RoadKind? roadKind; if (_settings.ReconcileTerrainChanges.Value) { int num2 = 0; roadKind = operation.RoadKind; if (roadKind.HasValue) { RoadKind valueOrDefault = roadKind.GetValueOrDefault(); RoadKind kind = ((valueOrDefault != RoadKind.Dirt) ? RoadKind.Dirt : RoadKind.Paved); num2 = RemoveCoverageWithBackup(kind, center, operation.RadiusMeters); } else { num2 = RemoveCoverageWithBackup(RoadKind.Dirt, center, operation.RadiusMeters) + RemoveCoverageWithBackup(RoadKind.Paved, center, operation.RadiusMeters); } if (num2 > 0) { _redrawPending = true; _log.LogInfo((object)("Reconciled a terrain change (" + operation.ActionDescription + ") " + $"r={operation.RadiusMeters:0.#}m: removed {num2} road point(s).")); } } roadKind = operation.RoadKind; if (roadKind.HasValue) { RoadKind valueOrDefault2 = roadKind.GetValueOrDefault(); if (_settings.CaptureConstructionActions.Value) { ObserveAndDraw(rules: new RoadSamplingRules(_settings.MinimumPointSpacingMeters.Value, _settings.MaximumStrokeGapMeters.Value, _settings.DuplicateSuppressionMeters.Value), source: RoadObservationSource.Construction, kind: valueOrDefault2, position: operation.Position, debugKey: "construction-observed"); } } } private int RemoveCoverageWithBackup(RoadKind kind, RoadPoint center, float radiusMeters) { _persistence.BackupBeforeReconciliation(_worldUid.Value); return _atlas.RemoveCoverage(kind, center, radiusMeters); } private void ObserveAndDraw(RoadObservationSource source, RoadKind kind, Vector3 position, RoadSamplingRules rules, string debugKey) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) if (!_disposed && _settings.Enabled.Value && _mapReady && _pipeline != null) { RoadObservation observation = new RoadObservation(source, kind, new RoadPoint(position.x, position.y, position.z)); int pointCount = _atlas.PointCount; if (_pipeline.Observe(in observation, rules, out var segment)) { _renderer.DrawSegment(segment); } else if (_atlas.PointCount > pointCount) { _renderer.DrawPoint(kind, observation.Position); } if (_settings.DebugLogging.Value) { _rateLimited.Info(debugKey, $"Observed {observation.Source}/{observation.Kind}."); } } } internal string ExecuteRoadCommand(string[] args) { //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_054f: Unknown result type (might be due to invalid IL or missing references) //IL_04c6: Unknown result type (might be due to invalid IL or missing references) //IL_04ec: Unknown result type (might be due to invalid IL or missing references) if (!AtlasAccessAllowed(out string denial)) { return denial; } if (!_disposed && _mapReady && _editor != null) { long? worldUid = _worldUid; if (worldUid.HasValue) { Player localPlayer = Player.m_localPlayer; if (localPlayer == null) { return "Concerned Cartographer: no local player."; } Vector3 position = ((Component)localPlayer).transform.position; RoadPoint position2 = new RoadPoint(position.x, position.y, position.z); string text = ((args.Length == 0) ? "status" : args[0].ToLowerInvariant()); float radius = 10f; if (args.Length > 1 && float.TryParse(args[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { radius = Mathf.Clamp(result, 1f, 100f); } bool flag; string summary; switch (text) { case "status": { int num3 = 0; foreach (RoadStroke stroke in _atlas.Strokes) { if (stroke.Hidden) { num3++; } } return $"Atlas: {_atlas.Strokes.Count} road(s), {_atlas.PointCount} point(s), " + $"{num3} hidden, undo depth {_editor.UndoCount}. {_editor.DescribeNearest(position2, radius)}"; } case "delete": flag = MutateWithBackup(() => _editor.DeleteNearest(position2, radius, out _lastToolSummary)); summary = _lastToolSummary; break; case "kind": flag = MutateWithBackup(() => _editor.ReclassifyNearest(position2, radius, out _lastToolSummary)); summary = _lastToolSummary; break; case "hide": flag = MutateWithBackup(() => _editor.SetHiddenNearest(position2, radius, hidden: true, out _lastToolSummary)); summary = _lastToolSummary; break; case "unhide": flag = MutateWithBackup(() => _editor.SetHiddenNearest(position2, radius, hidden: false, out _lastToolSummary)); summary = _lastToolSummary; break; case "split": flag = MutateWithBackup(() => _editor.SplitNearest(position2, radius, out _lastToolSummary)); summary = _lastToolSummary; break; case "join": flag = MutateWithBackup(() => _editor.JoinNearest(position2, radius, out _lastToolSummary)); summary = _lastToolSummary; break; case "rebuild": { float num = ((args.Length > 1) ? radius : 32f); _persistence.BackupBeforeReconciliation(_worldUid.Value); int num2 = _atlas.RemoveCoverage(RoadKind.Dirt, position2, num) + _atlas.RemoveCoverage(RoadKind.Paved, position2, num); flag = num2 > 0; summary = $"Cleared {num2} road point(s) within {num:0.#} m. " + "Roads return only when you Pathen/Pave the ground again ('cc_roads undo' reverts)."; break; } case "undo": flag = _editor.Undo(out summary); break; case "align": if (args.Length > 1 && string.Equals(args[1], "clear", StringComparison.OrdinalIgnoreCase)) { _renderer.ClearAlignmentProbe(); _renderer.RedrawAll(_atlas); _redrawPending = false; return "Alignment markers removed."; } if (args.Length > 1 && string.Equals(args[1], "live", StringComparison.OrdinalIgnoreCase)) { RoadKind kind; bool standingOnRoad = _probe.TryClassify(position, out kind); RoadPoint nearest; float distanceMeters; bool hasNearest = _atlas.TryGetNearestPointOnRoads(position2, 50f, out nearest, out distanceMeters); string result2 = LiveAlignmentProbe.BuildReport(position, standingOnRoad, kind, _surveyor?.LatestSample, _pipeline?.LastAccepted, hasNearest, nearest, distanceMeters, _renderer); _log.LogInfo((object)"cc_roads align live: diagnostic report written to the console (positions are never logged)."); return result2; } return _renderer.RunAlignmentProbe(position, _atlas); default: return "Usage: cc_roads [status|delete|kind|hide|unhide|split|join|rebuild|undo] [radius]."; } if (flag) { _redrawPending = true; SaveIfDirty(); _log.LogInfo((object)("Road tool '" + text + "': " + summary)); } return summary; } } return "Concerned Cartographer: no world is loaded yet."; } private bool MutateWithBackup(Func operation) { _persistence.BackupBeforeReconciliation(_worldUid.Value); return operation(); } public void SaveIfDirty() { long? worldUid = _worldUid; if (worldUid.HasValue) { if (_atlas.IsDirty && _persistence.Save(_worldUid.Value, _atlas)) { _atlas.MarkClean(); } if (_terrainIntent.IsDirty && _terrainIntentPersistence.Save(_worldUid.Value, _terrainIntent)) { _terrainIntent.MarkClean(); } } } public void Dispose() { if (!_disposed) { _workbenchPanel.Close(); RestoreVanillaPalette(); _overlayRelabel.Restore(); _textFocusBlock.Release(); _quickPinGate.Disarm(); MapInputGate.Uninstall(); PlayerInputGate.Uninstall(); PinDeletionWatch.Uninstall(); MapPointerGuard.Clear(); SaveIfDirty(); SavePinsSnapshot(); SaveRejectedIfDirty(); _pipeline?.EndAllStrokes(); _constructionCapture.OperationCaptured -= HandleTerrainOperation; _constructionCapture.Dispose(); _disposed = true; } } private void SwitchWorld(long uid) { if (_worldUid != uid || _surveyor == null) { SaveRejectedIfDirty(); SaveIfDirty(); SavePinsSnapshot(); _worldUid = uid; _atlas = _persistence.Load(uid); _terrainIntent = _terrainIntentPersistence.Load(uid); _pinStore = _pinPersistence.Load(uid); _surveyEngine.ResetSession(); _surveyEngine.LoadRejected(_surveyRejectedPersistence.Load(uid)); _pinStore.LocalAuthor = _authorId; _pinStore.Changed += _pinPersistence.QueueJournal; _pinAdapter.Reset(); _pinCommands = new PinCommandHandler(_pinStore, new PinOperations(_pinStore), _pinAdapter, _log, ResyncPins); _displayController.Reset(); _routeStore = _routePersistence.Load(uid); _routeStore.LocalAuthor = _authorId; _routeStore.Changed += _routePersistence.QueueJournal; _syncInbox.Clear(); _routeCommands = new RouteCommandHandler(_routeStore, new RouteOperations(_routeStore), _atlas, _settings, _log, delegate { _routeRedrawPending = true; }); _pipeline = new RoadObservationPipeline(_atlas, _terrainIntent); _editor = new RoadAtlasEditor(_atlas); _surveyor = new RoadSurveyor(_settings, _probe, _atlas, _log); _redrawPending = false; _autosaveElapsed = 0f; } } } internal sealed class CartographerSettings { public ConfigEntry Enabled { get; } public ConfigEntry CaptureConstructionActions { get; } public ConfigEntry ReconcileTerrainChanges { get; } public ConfigEntry SampleIntervalSeconds { get; } public ConfigEntry MinimumPointSpacingMeters { get; } public ConfigEntry MaximumStrokeGapMeters { get; } public ConfigEntry DuplicateSuppressionMeters { get; } public ConfigEntry AutosaveIntervalSeconds { get; } public ConfigEntry PaintThreshold { get; } public ConfigEntry PaintSampleRadius { get; } public ConfigEntry LineWidthPixels { get; } public ConfigEntry DebugLogging { get; } public ConfigEntry DrawCalibrationMarkers { get; } public ConfigEntry WorkbenchHotkey { get; } public ConfigEntry DrawerHotkey { get; } public ConfigEntry DrawerShowDirt { get; } public ConfigEntry DrawerShowPaved { get; } public ConfigEntry DrawerShowPins { get; } public ConfigEntry DrawerCluster { get; } public ConfigEntry DrawerPanelPosition { get; } public ConfigEntry QuickPinHotkey { get; } public ConfigEntry QuickPinDuplicateRadius { get; } public ConfigEntry SurveyRulesEnabled { get; } public ConfigEntry SurveyScanIntervalSeconds { get; } public ConfigEntry SurveyScanRadius { get; } public ConfigEntry SurveyBaseExclusionRadius { get; } public ConfigEntry SurveyMaxObservations { get; } public ConfigEntry RouteDrawModifier { get; } public ConfigEntry RouteEraseRadius { get; } public ConfigEntry RouteSnapRadius { get; } public ConfigEntry RouteOnRoadTolerance { get; } public ConfigEntry RouteOffRoadSpeed { get; } public ConfigEntry RouteOnRoadSpeed { get; } public ConfigEntry UiScale { get; } public ConfigEntry HighContrast { get; } public ConfigEntry WorkbenchGamepadButton { get; } public ConfigEntry DrawerGamepadButton { get; } public ConfigEntry EnhancedPinPalette { get; } public ConfigEntry ShowVanillaPinPalette { get; } public ConfigEntry CrashReportingConsent { get; } public ConfigEntry AcceptedPrivacyPolicyVersion { get; } public ConfigEntry SentryDsn { get; } public ConfigEntry ShowVanillaMapControls { get; } public ConfigEntry HighPrecisionLargeMapRoads { get; } private CartographerSettings(ConfigEntry enabled, ConfigEntry captureConstructionActions, ConfigEntry reconcileTerrainChanges, ConfigEntry sampleIntervalSeconds, ConfigEntry minimumPointSpacingMeters, ConfigEntry maximumStrokeGapMeters, ConfigEntry duplicateSuppressionMeters, ConfigEntry autosaveIntervalSeconds, ConfigEntry paintThreshold, ConfigEntry paintSampleRadius, ConfigEntry lineWidthPixels, ConfigEntry debugLogging, ConfigEntry drawCalibrationMarkers, ConfigEntry workbenchHotkey, ConfigEntry drawerHotkey, ConfigEntry drawerShowDirt, ConfigEntry drawerShowPaved, ConfigEntry drawerShowPins, ConfigEntry drawerCluster, ConfigEntry drawerPanelPosition, ConfigEntry quickPinHotkey, ConfigEntry quickPinDuplicateRadius, ConfigEntry surveyRulesEnabled, ConfigEntry surveyScanIntervalSeconds, ConfigEntry surveyScanRadius, ConfigEntry surveyBaseExclusionRadius, ConfigEntry surveyMaxObservations, ConfigEntry routeDrawModifier, ConfigEntry routeEraseRadius, ConfigEntry routeSnapRadius, ConfigEntry routeOnRoadTolerance, ConfigEntry routeOffRoadSpeed, ConfigEntry routeOnRoadSpeed, ConfigEntry uiScale, ConfigEntry highContrast, ConfigEntry workbenchGamepadButton, ConfigEntry drawerGamepadButton, ConfigEntry enhancedPinPalette, ConfigEntry showVanillaPinPalette, ConfigEntry crashReportingConsent, ConfigEntry acceptedPrivacyPolicyVersion, ConfigEntry sentryDsn, ConfigEntry showVanillaMapControls, ConfigEntry highPrecisionLargeMapRoads) { Enabled = enabled; CaptureConstructionActions = captureConstructionActions; ReconcileTerrainChanges = reconcileTerrainChanges; SampleIntervalSeconds = sampleIntervalSeconds; MinimumPointSpacingMeters = minimumPointSpacingMeters; MaximumStrokeGapMeters = maximumStrokeGapMeters; DuplicateSuppressionMeters = duplicateSuppressionMeters; AutosaveIntervalSeconds = autosaveIntervalSeconds; PaintThreshold = paintThreshold; PaintSampleRadius = paintSampleRadius; LineWidthPixels = lineWidthPixels; DebugLogging = debugLogging; DrawCalibrationMarkers = drawCalibrationMarkers; WorkbenchHotkey = workbenchHotkey; DrawerHotkey = drawerHotkey; DrawerShowDirt = drawerShowDirt; DrawerShowPaved = drawerShowPaved; DrawerShowPins = drawerShowPins; DrawerCluster = drawerCluster; DrawerPanelPosition = drawerPanelPosition; QuickPinHotkey = quickPinHotkey; QuickPinDuplicateRadius = quickPinDuplicateRadius; SurveyRulesEnabled = surveyRulesEnabled; SurveyScanIntervalSeconds = surveyScanIntervalSeconds; SurveyScanRadius = surveyScanRadius; SurveyBaseExclusionRadius = surveyBaseExclusionRadius; SurveyMaxObservations = surveyMaxObservations; RouteDrawModifier = routeDrawModifier; RouteEraseRadius = routeEraseRadius; RouteSnapRadius = routeSnapRadius; RouteOnRoadTolerance = routeOnRoadTolerance; RouteOffRoadSpeed = routeOffRoadSpeed; RouteOnRoadSpeed = routeOnRoadSpeed; UiScale = uiScale; HighContrast = highContrast; WorkbenchGamepadButton = workbenchGamepadButton; DrawerGamepadButton = drawerGamepadButton; EnhancedPinPalette = enhancedPinPalette; ShowVanillaPinPalette = showVanillaPinPalette; CrashReportingConsent = crashReportingConsent; AcceptedPrivacyPolicyVersion = acceptedPrivacyPolicyVersion; SentryDsn = sentryDsn; ShowVanillaMapControls = showVanillaMapControls; HighPrecisionLargeMapRoads = highPrecisionLargeMapRoads; } public static CartographerSettings Bind(ConfigFile config) { //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Expected O, but got Unknown //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Expected O, but got Unknown //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Expected O, but got Unknown //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Expected O, but got Unknown //IL_0137: Unknown result type (might be due to invalid IL or missing references) //IL_0141: Expected O, but got Unknown //IL_016a: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Expected O, but got Unknown //IL_0191: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Expected O, but got Unknown //IL_01b8: Unknown result type (might be due to invalid IL or missing references) //IL_01c2: Expected O, but got Unknown //IL_02d1: Unknown result type (might be due to invalid IL or missing references) //IL_02db: Expected O, but got Unknown //IL_031a: Unknown result type (might be due to invalid IL or missing references) //IL_0324: Expected O, but got Unknown //IL_034d: Unknown result type (might be due to invalid IL or missing references) //IL_0357: Expected O, but got Unknown //IL_0380: Unknown result type (might be due to invalid IL or missing references) //IL_038a: Expected O, but got Unknown //IL_03b0: Unknown result type (might be due to invalid IL or missing references) //IL_03ba: Expected O, but got Unknown //IL_03fd: Unknown result type (might be due to invalid IL or missing references) //IL_0407: Expected O, but got Unknown //IL_0430: Unknown result type (might be due to invalid IL or missing references) //IL_043a: Expected O, but got Unknown //IL_0463: Unknown result type (might be due to invalid IL or missing references) //IL_046d: Expected O, but got Unknown //IL_0496: Unknown result type (might be due to invalid IL or missing references) //IL_04a0: Expected O, but got Unknown //IL_04c9: Unknown result type (might be due to invalid IL or missing references) //IL_04d3: Expected O, but got Unknown //IL_04fc: Unknown result type (might be due to invalid IL or missing references) //IL_0506: Expected O, but got Unknown return new CartographerSettings(config.Bind("General", "Enabled", true, "Enable road surveying and map overlays."), config.Bind("Sources", "CaptureConstructionActions", true, "Record roads from your own successful Pathen (hoe) and Paved (cultivator/stonecutter) actions. This is the ONLY road source: walking existing paint never creates roads."), config.Bind("Sources", "ReconcileTerrainChanges", true, "When you level, raise, cultivate, reset, or repaint terrain, remove the covered road ink from the atlas so no ghost roads remain."), config.Bind("Survey", "SampleIntervalSeconds", 0.35f, new ConfigDescription("Seconds between diagnostic terrain samples under the player (feeds 'cc_roads align live'; never creates roads).", (AcceptableValueBase)(object)new AcceptableValueRange(0.1f, 5f), Array.Empty())), config.Bind("Survey", "MinimumPointSpacingMeters", 1.5f, new ConfigDescription("Minimum horizontal distance before a new road point is stored.", (AcceptableValueBase)(object)new AcceptableValueRange(0.5f, 20f), Array.Empty())), config.Bind("Survey", "MaximumStrokeGapMeters", 8f, new ConfigDescription("A larger gap starts a new stroke instead of drawing a long connector.", (AcceptableValueBase)(object)new AcceptableValueRange(2f, 100f), Array.Empty())), config.Bind("Survey", "DuplicateSuppressionMeters", 2f, new ConfigDescription("Skip samples within this distance of already-recorded road ink of the same kind, so re-walking a road never grows the atlas. 0 disables suppression; values above ~3 may also suppress tight hairpin switchbacks.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 10f), Array.Empty())), config.Bind("Persistence", "AutosaveIntervalSeconds", 15f, new ConfigDescription("Seconds between dirty-atlas autosaves.", (AcceptableValueBase)(object)new AcceptableValueRange(5f, 300f), Array.Empty())), config.Bind("Detection", "PaintThreshold", 0.4f, new ConfigDescription("Minimum averaged red/blue paint value used to identify roads.", (AcceptableValueBase)(object)new AcceptableValueRange(0.1f, 0.95f), Array.Empty())), config.Bind("Detection", "PaintSampleRadius", 1, new ConfigDescription("Terrain paint pixels sampled around the player (0 is a single pixel).", (AcceptableValueBase)(object)new AcceptableValueRange(0, 3), Array.Empty())), config.Bind("Map", "LineWidthPixels", 1, new ConfigDescription("Road line width on the map overlay, in map texels. One texel covers ~11.6 m of world, so widths above 1 make nearby roads merge into blobs.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 6), Array.Empty())), config.Bind("Diagnostics", "DebugLogging", false, "Write diagnostic road classification messages."), config.Bind("Diagnostics", "DrawCalibrationMarkers", false, "Draw fixed calibration crosses into the dirt overlay at world origin (magenta), +128m east (yellow), and +128m north (cyan) to verify overlay/map alignment."), config.Bind("Workbench", "WorkbenchHotkey", (KeyCode)112, "Key that opens the Pin Workbench for the pin under the cursor while the large map is open."), config.Bind("Drawer", "DrawerHotkey", (KeyCode)108, "Key that toggles the Atlas Drawer (layers, search, saved views) while the large map is open."), config.Bind("Drawer", "ShowDirtRoads", true, "Show the dirt-road layer."), config.Bind("Drawer", "ShowPavedRoads", true, "Show the paved-road layer."), config.Bind("Drawer", "ShowPins", true, "Show managed pins on the map."), config.Bind("Drawer", "Clustering", true, "Fold crowded pins into cluster markers when zoomed out (display only; never changes stored pins)."), config.Bind("Drawer", "PanelPosition", "", "Internal: the Atlas drawer's last dragged position as \"x,y\" canvas offsets from its right-center anchor. Written when the drawer closes; empty uses the default dock. Restored positions are clamped on-screen, so editing this can never strand the panel."), config.Bind("Workbench", "QuickPinHotkey", (KeyCode)288, "Key that pins the object you are looking at (set to None to disable). Never pins creatures."), config.Bind("Workbench", "QuickPinDuplicateRadius", 25f, new ConfigDescription("Skip a quick pin when a same-named pin already exists within this many meters (0 disables the check).", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 200f), Array.Empty())), config.Bind("Survey", "SurveyRulesEnabled", false, "Opt-in survey rules: nearby loaded objects matching survey-rules.tsv become reviewable observations (never pins directly)."), config.Bind("Survey", "SurveyScanIntervalSeconds", 10f, new ConfigDescription("Legacy, no effect since v1.0 RC10: the survey scans continuously on a bounded per-frame budget. Kept so existing config files load cleanly.", (AcceptableValueBase)(object)new AcceptableValueRange(2f, 60f), Array.Empty())), config.Bind("Survey", "SurveyScanRadius", 40f, new ConfigDescription("Survey scan radius around the player in meters.", (AcceptableValueBase)(object)new AcceptableValueRange(10f, 100f), Array.Empty())), config.Bind("Survey", "SurveyBaseExclusionRadius", 30f, new ConfigDescription("No observations within this distance of a pin categorized/tagged Base (0 disables).", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 100f), Array.Empty())), config.Bind("Survey", "SurveyMaxObservations", 200, new ConfigDescription("Hard cap on pending survey observations.", (AcceptableValueBase)(object)new AcceptableValueRange(10, 1000), Array.Empty())), config.Bind("Routes", "RouteDrawModifier", (KeyCode)304, "Modifier held with LeftClick on the large map for route draw/erase/waypoint modes (avoids vanilla map-drag conflicts)."), config.Bind("Routes", "RouteEraseRadius", 8f, new ConfigDescription("Route erase brush radius in meters.", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 30f), Array.Empty())), config.Bind("Routes", "RouteSnapRadius", 15f, new ConfigDescription("Waypoints snap to roads within this many meters.", (AcceptableValueBase)(object)new AcceptableValueRange(2f, 50f), Array.Empty())), config.Bind("Routes", "RouteOnRoadTolerance", 6f, new ConfigDescription("A route counts as on-road when within this distance of recorded road ink.", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 15f), Array.Empty())), config.Bind("Routes", "RouteOffRoadSpeed", 2.5f, new ConfigDescription("Off-road travel speed (m/s) for time estimates.", (AcceptableValueBase)(object)new AcceptableValueRange(0.5f, 15f), Array.Empty())), config.Bind("Routes", "RouteOnRoadSpeed", 5f, new ConfigDescription("On-road travel speed (m/s) for time estimates.", (AcceptableValueBase)(object)new AcceptableValueRange(0.5f, 15f), Array.Empty())), config.Bind("Accessibility", "UiScale", 1f, new ConfigDescription("Scale multiplier for Concerned Cartographer panels.", (AcceptableValueBase)(object)new AcceptableValueRange(0.8f, 1.6f), Array.Empty())), config.Bind("Accessibility", "HighContrast", false, "High-contrast map ink: near-black dirt, near-white paved, brighter route colors. Kinds stay distinguishable without color (dashed/dotted styles, icons, labels)."), config.Bind("Accessibility", "WorkbenchGamepadButton", "", "ZInput button name that opens the Pin Workbench (e.g. JoyLStick). Empty disables; conflicts are avoided by explicit opt-in."), config.Bind("Accessibility", "DrawerGamepadButton", "", "ZInput button name that toggles the Atlas Drawer. Empty disables."), config.Bind("Pins", "EnhancedPinPalette", true, "Show the Concerned Cartographer marker palette on the large map. Markers created through it are managed from birth (no upgrade step)."), config.Bind("Pins", "ShowVanillaPinPalette", false, "Keep Valheim's own five pin-icon buttons visible alongside (or instead of) the enhanced palette. Automatically treated as true when a known conflicting pin manager is installed."), config.Bind("Privacy", "SendCrashReports", CrashConsentState.Unknown, "Send anonymous crash reports when Concerned Cartographer hits an internal error. Unknown = not asked yet (a one-time dialog appears on the first large-map open); nothing is ever sent while Unknown or Disabled. What is and is not collected: PRIVACY.md. No gameplay analytics, ever."), config.Bind("Privacy", "AcceptedPrivacyPolicyVersion", 0, "Internal: the crash-reporting policy version the player answered. Re-prompts only if the collected data categories materially change in a future release."), config.Bind("Privacy", "SentryDsn", "", "Advanced: override the embedded crash-report ingestion DSN (a public event-submission key). Empty uses the built-in value; if both are empty, crash reporting is fully inert. NEVER put a Sentry auth token here."), config.Bind("Map", "ShowVanillaMapControls", false, "Show Valheim's own right-side map control rail (pin icon selectors, death/boss filter buttons, visible-to-others toggle) alongside the Concerned Cartographer toolbar. Default: the CC toolbar and Atlas System Markers replace it. Automatically treated as true when a known conflicting pin manager is installed."), config.Bind("Map", "HighPrecisionLargeMapRoads", true, "Render roads on the LARGE map as sub-texel vector geometry that pans/zooms with the map (DEF-v1.0-006), instead of only the 2048-texel texture overlay (which stays for the minimap and as fallback).")); } } internal sealed class CompatibilityRegistry { public sealed class KnownMod { public string GuidFragment { get; } public string DisplayName { get; } public string Behavior { get; } public bool Detected { get; set; } public string DetectedGuid { get; set; } = ""; public KnownMod(string guidFragment, string displayName, string behavior) { GuidFragment = guidFragment; DisplayName = displayName; Behavior = behavior; } } private readonly List _knownMods = new List { new KnownMod("pinnacle", "Pinnacle", "Pin manager detected: adoption stays fully manual (no adopt prompts from the hotkey on unadopted vanilla pins) so both managers never fight over a pin."), new KnownMod("pinassistant", "PinAssistant", "Pin manager detected: adoption stays fully manual; its auto-pins look like vanilla pins and are only touched if you explicitly adopt them."), new KnownMod("automappins", "AutoMapPins", "Auto-pinner detected: its pins are unsaved/foreign and are never adopted or edited."), new KnownMod("maproutes", "MapRoutes", "Route drawer detected: both route layers coexist (separate overlays); imports are not performed automatically."), new KnownMod("bettercartographytable", "Better Cartography Table", "Table mod detected: Concerned Cartographer sharing stays on its own cc_sync channel and never touches table data."), new KnownMod("onemap", "OneMap", "Shared-map mod detected: vanilla shared pins carry owner IDs and remain foreign/untouchable to Concerned Cartographer.") }; private bool _evaluated; public IReadOnlyList KnownMods => _knownMods; public bool PinManagerPresent { get; private set; } public void Evaluate(ManualLogSource log) { if (_evaluated) { return; } _evaluated = true; try { foreach (KeyValuePair pluginInfo in Chainloader.PluginInfos) { string text = pluginInfo.Key.ToLowerInvariant(); foreach (KnownMod knownMod in _knownMods) { if (!knownMod.Detected && text.Contains(knownMod.GuidFragment)) { knownMod.Detected = true; knownMod.DetectedGuid = pluginInfo.Key; log.LogInfo((object)("Compatibility: " + knownMod.DisplayName + " detected (" + pluginInfo.Key + "). " + knownMod.Behavior)); } } } foreach (KnownMod knownMod2 in _knownMods) { if (knownMod2.Detected && (knownMod2.GuidFragment == "pinnacle" || knownMod2.GuidFragment == "pinassistant")) { PinManagerPresent = true; } } } catch (Exception exception) { log.LogWarning((object)("Compatibility detection failed harmlessly: " + SafeLogText.Brief(exception))); } } public string Report() { StringBuilder stringBuilder = new StringBuilder("Compatibility:"); bool flag = false; foreach (KnownMod knownMod in _knownMods) { if (knownMod.Detected) { flag = true; stringBuilder.Append("\n " + knownMod.DisplayName + " (" + knownMod.DetectedGuid + ") — " + knownMod.Behavior); } } if (!flag) { stringBuilder.Append(" no known neighboring mods detected. Baseline interop safety applies regardless."); } return stringBuilder.ToString(); } } internal enum CrashConsentState { Unknown, Enabled, Disabled } internal static class CrashReportingConfig { public const string EmbeddedSentryDsn = "https://eec0ed91ddb82ee984103b4180573feb@o4511990602989568.ingest.us.sentry.io/4511990681436160"; public const int ConsentPolicyVersion = 1; public const string PrivacyPolicyUrl = "https://github.com/Weakened/ConcernedCatMods/blob/main/PRIVACY.md"; } internal sealed class CrashReportingHub : IDisposable { private readonly CartographerSettings _settings; private readonly ICrashReporter _reporter; private readonly CrashReportThrottle _noticeThrottle = new CrashReportThrottle(); private ManualLogSource? _attachedSource; private bool _inHandler; private bool _disposed; public bool ReportingActive { get { if (_settings.CrashReportingConsent.Value == CrashConsentState.Enabled) { if (_reporter is SentryCrashReporter sentryCrashReporter) { return sentryCrashReporter.IsOperational; } return false; } return false; } } public CrashReportingHub(CartographerSettings settings, CrashReportContext context) { _settings = settings; string text = settings.SentryDsn.Value.Trim(); if (text.Length == 0) { text = "https://eec0ed91ddb82ee984103b4180573feb@o4511990602989568.ingest.us.sentry.io/4511990681436160"; } ICrashReporter reporter = new NullCrashReporter(); if (text.Length > 0) { SentryCrashReporter sentryCrashReporter = new SentryCrashReporter(text); if (sentryCrashReporter.IsOperational) { reporter = sentryCrashReporter; } else { sentryCrashReporter.Dispose(); } } _reporter = reporter; _reporter.Initialize(context); SyncConsent(); _settings.CrashReportingConsent.SettingChanged += HandleConsentChanged; } public void Attach(ManualLogSource ownSource) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Expected O, but got Unknown _attachedSource = ownSource; ownSource.LogEvent += HandleOwnLogEvent; Application.logMessageReceived += new LogCallback(HandleUnityLog); } public void Dispose() { //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Expected O, but got Unknown if (_disposed) { return; } _disposed = true; try { _settings.CrashReportingConsent.SettingChanged -= HandleConsentChanged; if (_attachedSource != null) { _attachedSource.LogEvent -= HandleOwnLogEvent; } Application.logMessageReceived -= new LogCallback(HandleUnityLog); } catch { } _reporter.Dispose(); } private void HandleConsentChanged(object sender, EventArgs e) { SyncConsent(); } private void SyncConsent() { try { _reporter.ConsentGranted = _settings.CrashReportingConsent.Value == CrashConsentState.Enabled; } catch { } } private void HandleOwnLogEvent(object sender, LogEventArgs args) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Invalid comparison between Unknown and I4 //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Invalid comparison between Unknown and I4 if (_disposed || _inHandler || ((int)args.Level != 2 && (int)args.Level != 1)) { return; } _inHandler = true; try { string text = args.Data?.ToString() ?? ""; string subsystem = CrashSubsystems.Infer(text); _reporter.CaptureFatalSubsystemFailure(subsystem, text); NotifyPlayer(subsystem); } catch { } finally { _inHandler = false; } } private void HandleUnityLog(string condition, string stackTrace, LogType type) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Invalid comparison between Unknown and I4 if (_disposed || (int)type != 4) { return; } try { if ((stackTrace != null && stackTrace.Contains("TheConcernedCat.ConcernedCartographer")) || (condition != null && condition.Contains("TheConcernedCat.ConcernedCartographer"))) { _reporter.CaptureException("unhandled", condition + "\n" + stackTrace); } } catch { } } private void NotifyPlayer(string subsystem) { try { if (_noticeThrottle.ShouldNotify(subsystem)) { string key = (ReportingActive ? "privacy.noticeSent" : "privacy.noticeOff"); Player localPlayer = Player.m_localPlayer; if (localPlayer != null) { ((Character)localPlayer).Message((MessageType)1, AtlasStrings.Format(key, subsystem), 0, (Sprite)null); } } } catch { } } } internal sealed class PinCommandHandler { private const float DefaultSelectRadiusMeters = 15f; private const float DefaultDuplicateRadiusMeters = 25f; private readonly PinStore _store; private readonly PinOperations _operations; private readonly PinAdapter _adapter; private readonly ManualLogSource _log; private readonly Action _resyncMap; public PinOperations Operations => _operations; public PinCommandHandler(PinStore store, PinOperations operations, PinAdapter adapter, ManualLogSource log, Action resyncMap) { _store = store; _operations = operations; _adapter = adapter; _log = log; _resyncMap = resyncMap; } public string Execute(string[] args, Vector3 playerPosition) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0460: Unknown result type (might be due to invalid IL or missing references) RoadPoint position = new RoadPoint(playerPosition.x, playerPosition.y, playerPosition.z); string text = ((args.Length == 0) ? "status" : args[0].ToLowerInvariant()); string remainder = ((args.Length > 1) ? string.Join(" ", args, 1, args.Length - 1) : ""); return text switch { "status" => Status(position), "list" => ListPins(position, remainder), "adopt" => Adopt(playerPosition, remainder), "adoptall" => AdoptAll(remainder), "create" => Create(position, remainder), "name" => EditNearest(position, "rename to \"" + remainder + "\"", delegate(AtlasPin pin) { pin.Name = remainder; }), "icon" => SetIcon(position, remainder), "icons" => ListIcons(remainder), "category" => EditNearest(position, "category \"" + remainder + "\"", delegate(AtlasPin pin) { pin.Category = remainder; }), "color" => SetColor(position, remainder), "size" => SetSize(position, remainder), "note" => EditNearest(position, "note", delegate(AtlasPin pin) { pin.Notes = remainder; }), "tag+" => EditNearest(position, "tag +" + remainder, delegate(AtlasPin pin) { string text2 = remainder.Trim(); if (text2.Length > 0 && !pin.Tags.Contains(text2)) { pin.Tags.Add(text2); } }), "tag-" => EditNearest(position, "tag -" + remainder, delegate(AtlasPin pin) { pin.Tags.Remove(remainder.Trim()); }), "setstatus" => SetStatus(position, remainder), "check" => EditNearest(position, "check", delegate(AtlasPin pin) { pin.Checked = true; }), "uncheck" => EditNearest(position, "uncheck", delegate(AtlasPin pin) { pin.Checked = false; }), "scope" => SetScope(position, remainder), "move" => Move(position), "dup" => Duplicate(position), "archive" => Archive(position, archived: true), "unarchive" => Archive(position, archived: false), "delete" => Delete(position), "restore" => Restore(), "deleted" => ListDeleted(), "dups" => ListDuplicates(remainder), "merge" => Merge(position, remainder), "undo" => UndoRedo(undo: true), "redo" => UndoRedo(undo: false), "coords" => Coordinates(position), _ => "Usage: cc_pins [" + string.Join("|", "status", "list", "adopt", "adoptall", "create", "name", "icon", "icons", "category", "color", "size", "note", "tag+", "tag-", "setstatus", "check", "uncheck", "scope", "move", "dup", "archive", "unarchive", "delete", "restore", "deleted", "dups", "merge", "undo", "redo", "coords") + "] ...", }; } private string Status(RoadPoint position) { int num = 0; int num2 = 0; int num3 = 0; foreach (AtlasPin item in _store.All) { if (item.Deleted) { num3++; } else if (item.Archived) { num2++; } else { num++; } } int count = _adapter.ListAdoptable().Count; AtlasPin pin; float distance; string arg = (TryFindNearest(position, 15f, includeArchived: true, out pin, out distance) ? $"Nearest: {Describe(pin)}, {distance:0.#} m away." : "No managed pin nearby."); return $"Pins: {num} active, {num2} archived, {num3} deleted, {count} adoptable vanilla. " + $"Undo {_operations.UndoCount}/redo {_operations.RedoCount}. {arg}"; } private string ListPins(RoadPoint position, string filter) { List<(float, AtlasPin)> list = new List<(float, AtlasPin)>(); string needle = filter.Trim().ToLowerInvariant(); foreach (AtlasPin item in _store.Living) { if (!item.Archived && MatchesFilter(item, needle)) { list.Add((item.Position.HorizontalDistanceTo(in position), item)); } } list.Sort(((float Distance, AtlasPin Pin) a, (float Distance, AtlasPin Pin) b) => a.Distance.CompareTo(b.Distance)); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append($"{list.Count} pin(s)"); int num = Math.Min(list.Count, 15); for (int num2 = 0; num2 < num; num2++) { stringBuilder.Append($"\n {list[num2].Item1,7:0.#} m {Describe(list[num2].Item2)}"); } if (list.Count > num) { stringBuilder.Append($"\n ... and {list.Count - num} more (refine the filter)."); } return stringBuilder.ToString(); } private string Adopt(Vector3 playerPosition, string radiusText) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0050: 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) float num = ParseRadius(radiusText, 15f); List list = _adapter.ListAdoptable(); PinData val = null; float num2 = num; foreach (PinData item in list) { float num3 = Vector2.Distance(new Vector2(item.m_pos.x, item.m_pos.z), new Vector2(playerPosition.x, playerPosition.z)); if (num3 <= num2) { num2 = num3; val = item; } } if (val == null) { return $"No adoptable vanilla pin within {num:0.#} m (foreign and system pins are never adopted)."; } AtlasPin atlasPin = _adapter.Adopt(_store, val); if (atlasPin == null) { return "Adoption failed; see the log."; } _log.LogInfo((object)$"Adopted vanilla pin as {atlasPin.Id}."); return "Adopted \"" + atlasPin.Name + "\" (" + atlasPin.IconId + ") as a managed pin. Its position, icon, and checked state are unchanged."; } private string AdoptAll(string confirm) { List list = _adapter.ListAdoptable(); if (list.Count == 0) { return "No adoptable vanilla pins."; } if (!string.Equals(confirm.Trim(), "confirm", StringComparison.OrdinalIgnoreCase)) { return $"Would adopt {list.Count} vanilla pin(s), preserving position/icon/name/checked state. " + "Run 'cc_pins adoptall confirm' to proceed."; } int num = 0; foreach (PinData item in list) { if (_adapter.Adopt(_store, item) != null) { num++; } } _log.LogInfo((object)$"Batch-adopted {num} vanilla pin(s)."); return $"Adopted {num} vanilla pin(s)."; } private string Create(RoadPoint position, string name) { AtlasPin atlasPin = _store.Create(delegate(AtlasPin created) { created.Name = name.Trim(); created.Position = position; }); _adapter.SyncPin(_store, atlasPin.Id); return "Created " + Describe(atlasPin) + " at your position."; } private string EditNearest(RoadPoint position, string description, Action edit) { if (!TryFindNearest(position, 15f, includeArchived: true, out AtlasPin pin, out float _)) { return NoNearbyPin(); } _operations.BatchEdit(new AtlasId[1] { pin.Id }, edit, description); _adapter.SyncPin(_store, pin.Id); return "Updated " + Describe(pin) + " (" + description + ")."; } private string SetIcon(RoadPoint position, string iconId) { string text = iconId.Trim(); if (!IconRegistry.TryResolve(text, out IconRegistry.IconDefinition definition)) { return "Unknown icon '" + text + "'. Try 'cc_pins icons " + text + "' to search the registry."; } return EditNearest(position, "icon " + definition.Id, delegate(AtlasPin pin) { pin.IconId = definition.Id; }); } private string ListIcons(string query) { StringBuilder stringBuilder = new StringBuilder("Icons:"); foreach (IconRegistry.IconDefinition item in IconRegistry.Search(query)) { stringBuilder.Append("\n " + item.Id + " (" + item.DisplayName + ", " + item.DefaultCategory + ")"); } return stringBuilder.ToString(); } private string SetColor(RoadPoint position, string colorText) { string text = colorText.Trim().TrimStart(new char[1] { '#' }); if (string.Equals(text, "clear", StringComparison.OrdinalIgnoreCase)) { return EditNearest(position, "color cleared", delegate(AtlasPin pin) { pin.ColorArgb = null; }); } if (!uint.TryParse(text, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var result) || (text.Length != 6 && text.Length != 8)) { return "Usage: cc_pins color RRGGBB | AARRGGBB | clear"; } if (text.Length == 6) { result |= 0xFF000000u; } int argb = (int)result; return EditNearest(position, "color #" + text, delegate(AtlasPin pin) { pin.ColorArgb = argb; }); } private string SetSize(RoadPoint position, string sizeText) { if (!float.TryParse(sizeText.Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { return "Usage: cc_pins size <0.5..2.0>"; } float clamped = Mathf.Clamp(result, 0.5f, 2f); return EditNearest(position, $"size {clamped:0.##}", delegate(AtlasPin pin) { pin.SizeScale = clamped; }); } private string SetStatus(RoadPoint position, string statusText) { if (!Enum.TryParse(statusText.Trim(), ignoreCase: true, out var status) || !Enum.IsDefined(typeof(AtlasPinStatus), status)) { return "Usage: cc_pins setstatus none|todo|inprogress|done|warning"; } return EditNearest(position, $"status {status}", delegate(AtlasPin pin) { pin.Status = status; }); } private string SetScope(RoadPoint position, string scopeText) { if (!Enum.TryParse(scopeText.Trim(), ignoreCase: true, out var scope) || !Enum.IsDefined(typeof(AtlasScope), scope)) { return "Usage: cc_pins scope private|table|server (sharing intent; sync arrives in v0.6)"; } return EditNearest(position, $"scope {scope}", delegate(AtlasPin pin) { pin.Scope = scope; }); } private string Move(RoadPoint position) { if (!TryFindNearest(position, 100f, includeArchived: true, out AtlasPin pin, out float _)) { return "No managed pin within 100 m."; } _operations.Move(pin.Id, position); _adapter.SyncPin(_store, pin.Id); return "Moved " + Describe(pin) + " to your position. 'cc_pins undo' reverts."; } private string Duplicate(RoadPoint position) { if (!TryFindNearest(position, 15f, includeArchived: false, out AtlasPin pin, out float _)) { return NoNearbyPin(); } AtlasPin atlasPin = _operations.Duplicate(pin.Id); if (atlasPin == null) { return "Duplicate failed."; } _adapter.SyncPin(_store, atlasPin.Id); return "Duplicated as " + Describe(atlasPin) + " (offset 4 m east)."; } private string Archive(RoadPoint position, bool archived) { AtlasPin atlasPin = null; float num = 15f; foreach (AtlasPin item in _store.Living) { if (item.Archived != archived) { float num2 = item.Position.HorizontalDistanceTo(in position); if (num2 <= num) { num = num2; atlasPin = item; } } } if (atlasPin == null) { if (!archived) { return $"No archived pin within {15f:0.#} m."; } return NoNearbyPin(); } _operations.SetArchived(atlasPin.Id, archived); _adapter.SyncPin(_store, atlasPin.Id); return (archived ? "Archived" : "Unarchived") + " " + Describe(atlasPin) + "."; } private string Delete(RoadPoint position) { if (!TryFindNearest(position, 15f, includeArchived: true, out AtlasPin pin, out float _)) { return NoNearbyPin(); } _operations.Delete(pin.Id); _adapter.SyncPin(_store, pin.Id); return "Deleted " + Describe(pin) + ". 'cc_pins restore' or 'cc_pins undo' brings it back."; } private string Restore() { List list = _operations.RecentlyDeleted(1); if (list.Count == 0) { return "Nothing in the recently-deleted list."; } _operations.RestoreDeleted(list[0].Id); _adapter.SyncPin(_store, list[0].Id); return "Restored " + Describe(list[0]) + "."; } private string ListDeleted() { StringBuilder stringBuilder = new StringBuilder("Recently deleted:"); List list = _operations.RecentlyDeleted(); if (list.Count == 0) { return "Recently deleted: none."; } foreach (AtlasPin item in list) { stringBuilder.Append($"\n {Describe(item)} (deleted {item.DeletedUtc:HH:mm} UTC)"); } return stringBuilder.ToString(); } private string ListDuplicates(string radiusText) { float num = ParseRadius(radiusText, 25f); List> list = _operations.FindDuplicateGroups(num); if (list.Count == 0) { return $"No likely duplicates within {num:0.#} m of each other."; } StringBuilder stringBuilder = new StringBuilder($"{list.Count} duplicate group(s):"); foreach (List item in list) { stringBuilder.Append($"\n keep {Describe(item[0])} <- merge {item.Count - 1} other(s)"); } stringBuilder.Append("\nStand near a group and run 'cc_pins merge confirm'."); return stringBuilder.ToString(); } private string Merge(RoadPoint position, string confirm) { List> list = _operations.FindDuplicateGroups(25f); List list2 = null; float num = float.MaxValue; foreach (List item in list) { float num2 = item[0].Position.HorizontalDistanceTo(in position); if (num2 < num) { num = num2; list2 = item; } } if (list2 == null) { return "No duplicate group found."; } if (!string.Equals(confirm.Trim(), "confirm", StringComparison.OrdinalIgnoreCase)) { return $"Would merge {list2.Count - 1} pin(s) into {Describe(list2[0])}, preserving notes and provenance. " + "Run 'cc_pins merge confirm' to proceed."; } List list3 = new List(); for (int i = 1; i < list2.Count; i++) { list3.Add(list2[i].Id); } _operations.Merge(list2[0].Id, list3); _resyncMap(); return $"Merged {list3.Count} duplicate(s) into {Describe(list2[0])}. 'cc_pins undo' reverts."; } private string UndoRedo(bool undo) { if (undo ? _operations.Undo(out string summary) : _operations.Redo(out summary)) { _resyncMap(); } return summary; } private string Coordinates(RoadPoint position) { if (!TryFindNearest(position, 15f, includeArchived: true, out AtlasPin pin, out float _)) { return NoNearbyPin(); } string text = string.Format(CultureInfo.InvariantCulture, "{0:0.#}, {1:0.#}, {2:0.#}", pin.Position.X, pin.Position.Y, pin.Position.Z); try { GUIUtility.systemCopyBuffer = text; } catch { } return Describe(pin) + " at (" + text + ") — copied to clipboard."; } private bool TryFindNearest(RoadPoint position, float maxRadius, bool includeArchived, out AtlasPin? pin, out float distance) { pin = null; distance = float.MaxValue; foreach (AtlasPin item in _store.Living) { if (!item.Archived || includeArchived) { float num = item.Position.HorizontalDistanceTo(in position); if (num < distance) { distance = num; pin = item; } } } if (pin != null) { return distance <= maxRadius; } return false; } private static bool MatchesFilter(AtlasPin pin, string needle) { if (needle.Length == 0) { return true; } if (pin.Name.ToLowerInvariant().Contains(needle) || pin.Category.ToLowerInvariant().Contains(needle) || pin.IconId.ToLowerInvariant().Contains(needle)) { return true; } foreach (string tag in pin.Tags) { if (tag.ToLowerInvariant().Contains(needle)) { return true; } } return false; } private static float ParseRadius(string text, float fallback) { if (!float.TryParse(text.Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { return fallback; } return Mathf.Clamp(result, 1f, 200f); } private static string NoNearbyPin() { return $"No managed pin within {15f:0.#} m. 'cc_pins list' shows what exists; 'cc_pins adopt' adopts a vanilla pin."; } private static string Describe(AtlasPin pin) { string text = ((pin.Name.Length == 0) ? "(unnamed)" : ("\"" + pin.Name + "\"")); string text2 = ""; if (pin.Archived) { text2 += ", archived"; } if (pin.Deleted) { text2 += ", deleted"; } return $"{text} [{pin.IconId}, {pin.Source}{text2}]"; } } internal sealed class PinToolsCommand : ConsoleCommand { private readonly CartographerRuntime _runtime; public override string Name => "cc_pins"; public override string Help => "Concerned Cartographer pin workbench. Subcommands: edit (opens the panel), status, list, adopt, adoptall, create, name, icon, icons, category, color, size, note, tag+, tag-, setstatus, check, uncheck, scope, move, dup, archive, unarchive, delete, restore, deleted, dups, merge, undo, redo, coords. Most target the managed pin nearest you."; public PinToolsCommand(CartographerRuntime runtime) { _runtime = runtime; } public override void Run(string[] args, Terminal context) { string text; try { text = _runtime.ExecutePinCommand(args); } catch (Exception ex) { text = "Pin tool failed: " + ex.Message; } if (context != null) { context.AddString(text); } } public override List CommandOptionList() { return new List { "edit", "status", "list", "adopt", "adoptall", "create", "name", "icon", "icons", "category", "color", "size", "note", "tag+", "tag-", "setstatus", "check", "uncheck", "scope", "move", "dup", "archive", "unarchive", "delete", "restore", "deleted", "dups", "merge", "undo", "redo", "coords" }; } } internal sealed class QuickPinCapture { private readonly CartographerSettings _settings; private readonly ManualLogSource _log; public QuickPinCapture(CartographerSettings settings, ManualLogSource log) { _settings = settings; _log = log; } public bool TryCapture(PinStore store, out string message, out AtlasPin? created) { //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) message = ""; created = null; try { Player localPlayer = Player.m_localPlayer; if (localPlayer == null) { return false; } GameObject hoverObject = ((Humanoid)localPlayer).GetHoverObject(); if ((Object)(object)hoverObject == (Object)null) { message = AtlasStrings.Get("hud.quickPinNothing"); return false; } if ((Object)(object)hoverObject.GetComponentInParent() != (Object)null) { message = AtlasStrings.Get("hud.quickPinCreature"); return false; } string hoverName = ""; Hoverable componentInParent = hoverObject.GetComponentInParent(); if (componentInParent != null) { try { hoverName = componentInParent.GetHoverName() ?? ""; } catch { hoverName = ""; } } List list = new List { ((Object)hoverObject).name }; ZNetView componentInParent2 = hoverObject.GetComponentInParent(); if ((Object)(object)componentInParent2 != (Object)null && (Object)(object)((Component)componentInParent2).gameObject != (Object)null) { list.Add(((Object)((Component)componentInParent2).gameObject).name); } if ((Object)(object)hoverObject.transform.root != (Object)null) { list.Add(((Object)hoverObject.transform.root).name); } QuickPinSuggester.Suggestion suggestion = QuickPinSuggester.Suggest(hoverName, list); Vector3 position = hoverObject.transform.position; RoadPoint point = new RoadPoint(position.x, position.y, position.z); float value = _settings.QuickPinDuplicateRadius.Value; if (value > 0f) { foreach (AtlasPin item in store.Living) { if (!item.Archived && string.Equals(item.Name, suggestion.Name, StringComparison.OrdinalIgnoreCase) && item.Position.HorizontalDistanceTo(in point) <= value) { message = AtlasStrings.Format("hud.quickPinDuplicate", suggestion.Name, item.Position.HorizontalDistanceTo(in point).ToString("0.#")); return false; } } } AtlasPin atlasPin = (created = store.Create(delegate(AtlasPin newPin) { newPin.Name = suggestion.Name; newPin.IconId = suggestion.IconId; newPin.Category = suggestion.Category; newPin.Source = AtlasPinSource.Generated; newPin.Position = point; })); _log.LogInfo((object)$"Quick pin {atlasPin.Id}: {suggestion.IconId}."); message = AtlasStrings.Format("hud.quickPinned", suggestion.Name); return true; } catch (Exception exception) { _log.LogError((object)("Quick pin failed: " + SafeLogText.Describe(exception))); message = "Quick pin failed; see the log."; return false; } } } internal sealed class RateLimitedLog { private readonly ManualLogSource _log; private readonly float _minimumIntervalSeconds; private readonly Dictionary _lastLoggedAt = new Dictionary(); public RateLimitedLog(ManualLogSource log, float minimumIntervalSeconds) { _log = log; _minimumIntervalSeconds = minimumIntervalSeconds; } public void Info(string key, string message) { if (ShouldLog(key)) { _log.LogInfo((object)message); } } public void Warning(string key, string message) { if (ShouldLog(key)) { _log.LogWarning((object)message); } } public void Error(string key, string message) { if (ShouldLog(key)) { _log.LogError((object)message); } } private bool ShouldLog(string key) { float unscaledTime = Time.unscaledTime; if (_lastLoggedAt.TryGetValue(key, out var value) && unscaledTime - value < _minimumIntervalSeconds) { return false; } _lastLoggedAt[key] = unscaledTime; return true; } } internal sealed class RoadToolsCommand : ConsoleCommand { private readonly CartographerRuntime _runtime; public override string Name => "cc_roads"; public override string Help => "Concerned Cartographer road tools. Subcommands: status, delete, kind, hide, unhide, split, join, rebuild, undo. Each targets the recorded road nearest you; an optional number sets the search radius in meters (e.g. 'cc_roads delete 20'). 'align' runs the map-alignment diagnostic (native pin vs overlay cross); 'align live' runs the end-to-end player-vs-road-ink diagnosis (A/B/C/D verdicts); 'align clear' removes the pins."; public RoadToolsCommand(CartographerRuntime runtime) { _runtime = runtime; } public override void Run(string[] args, Terminal context) { string text; try { text = _runtime.ExecuteRoadCommand(args); } catch (Exception ex) { text = "Road tool failed: " + ex.Message; } if (context != null) { context.AddString(text); } } public override List CommandOptionList() { return new List { "status", "delete", "kind", "hide", "unhide", "split", "join", "rebuild", "undo", "align" }; } } internal sealed class RouteCommandHandler { public enum MapMode { None, Draw, Erase, Waypoint } private readonly RouteStore _store; private readonly RouteOperations _operations; private readonly RoadAtlas _roads; private readonly CartographerSettings _settings; private readonly ManualLogSource _log; private readonly Action _redraw; private RouteKind _uiPendingKind = RouteKind.Freehand; private string _uiBaseName = ""; private int _uiStrokeCount; private readonly FreeDrawStrokeGate _strokeGate = new FreeDrawStrokeGate(); private bool _uiRouteStarted; public MapMode Mode { get; private set; } public AtlasId ActiveRouteId { get; private set; } public bool SnapEnabled { get; private set; } = true; public RouteOperations Operations => _operations; public long ChangeStamp => _store.ChangeStamp; public bool UiModeOwned { get; private set; } public string ActiveRouteDisplayName { get { if (_uiRouteStarted && _store.TryGet(ActiveRouteId, out AtlasRoute route) && !route.Deleted) { return route.Name; } if (_uiBaseName.Length <= 0) { return "New route"; } return _uiBaseName; } } public RouteCommandHandler(RouteStore store, RouteOperations operations, RoadAtlas roads, CartographerSettings settings, ManualLogSource log, Action redraw) { _store = store; _operations = operations; _roads = roads; _settings = settings; _log = log; _redraw = redraw; } public string UiStart(RouteKind kind, string name) { Mode = ((kind == RouteKind.Freehand) ? MapMode.Draw : MapMode.Waypoint); UiModeOwned = true; _uiPendingKind = kind; _uiBaseName = ((name.Trim().Length == 0) ? "New route" : name.Trim()); _uiStrokeCount = 0; _uiRouteStarted = false; _strokeGate.Reset(); return _uiBaseName; } public void UiStartErase() { Mode = MapMode.Erase; UiModeOwned = true; _strokeGate.Reset(); } public void UiStop() { Mode = MapMode.None; UiModeOwned = false; _strokeGate.Reset(); _redraw(); } public void UiSetSnap(bool enabled) { SnapEnabled = enabled; } public List<(AtlasId Id, string Label)> UiListRoutes(int max) { List list = new List(_store.Living); list.Sort(delegate(AtlasRoute left, AtlasRoute right) { int num = left.Archived.CompareTo(right.Archived); if (num != 0) { return num; } int num2 = string.Compare(left.Name, right.Name, StringComparison.OrdinalIgnoreCase); return (num2 == 0) ? left.Id.Value.CompareTo(right.Id.Value) : num2; }); List<(AtlasId, string)> list2 = new List<(AtlasId, string)>(); foreach (AtlasRoute item in list) { if (list2.Count >= max) { break; } RouteEstimator.Estimate estimate = RouteEstimator.Compute(item.Points, _roads, _settings.RouteOnRoadTolerance.Value, _settings.RouteOffRoadSpeed.Value, _settings.RouteOnRoadSpeed.Value); string text = (item.Locked ? " L" : "") + (item.Archived ? " A" : ""); list2.Add((item.Id, $"{item.Name} [{item.Kind} · {item.Style} · {item.Status}]{text} {estimate.DistanceMeters:0} m")); } return list2; } public int LivingRouteCount() { int num = 0; foreach (AtlasRoute item in _store.Living) { _ = item; num++; } return num; } public string UiClearAll() { if (Mode != MapMode.None) { UiStop(); } List list = new List(); foreach (AtlasRoute item in _store.Living) { list.Add(item.Id); } foreach (AtlasId item2 in list) { _operations.Delete(item2); } _redraw(); if (list.Count != 0) { return $"Deleted {list.Count} route(s). Restore or Undo brings them back one at a time."; } return "No routes to clear."; } public string UiRename(AtlasId id, string name) { return UiEdit(id, delegate(AtlasRoute route) { route.Name = name.Trim(); }, "renamed to \"" + name.Trim() + "\""); } public string UiCycleStyle(AtlasId id) { if (!_store.TryGet(id, out AtlasRoute route) || route.Deleted) { return "Route no longer exists."; } RouteStyle next = ((route.Style == RouteStyle.Dotted) ? RouteStyle.Solid : (route.Style + 1)); return UiEdit(id, delegate(AtlasRoute r) { r.Style = next; }, $"style {next}"); } public string UiCycleStatus(AtlasId id) { if (!_store.TryGet(id, out AtlasRoute route) || route.Deleted) { return "Route no longer exists."; } RouteStatus next = ((route.Status == RouteStatus.Done) ? RouteStatus.Planned : (route.Status + 1)); return UiEdit(id, delegate(AtlasRoute r) { r.Status = next; }, $"status {next}"); } public string UiSetColor(AtlasId id, int? argb) { return UiEdit(id, delegate(AtlasRoute route) { route.ColorArgb = argb; }, (!argb.HasValue) ? "color cleared" : "color set"); } public string UiToggleLock(AtlasId id) { if (!_store.TryGet(id, out AtlasRoute route) || route.Deleted) { return "Route no longer exists."; } bool flag = !route.Locked; _operations.SetLocked(id, flag); return "\"" + route.Name + "\" " + (flag ? "locked (geometry edits rejected)" : "unlocked") + "."; } public string UiToggleArchive(AtlasId id) { if (!_store.TryGet(id, out AtlasRoute route) || route.Deleted) { return "Route no longer exists."; } bool flag = !route.Archived; _operations.SetArchived(id, flag); _redraw(); return "\"" + route.Name + "\" " + (flag ? "archived (hidden from the map)" : "unarchived") + "."; } public string UiDelete(AtlasId id) { if (!_store.TryGet(id, out AtlasRoute route) || route.Deleted) { return "Route no longer exists."; } if (Mode != MapMode.None && ActiveRouteId.Equals(id)) { UiStop(); } _operations.Delete(id); _redraw(); return "Deleted \"" + route.Name + "\". Restore or Undo reverts."; } public string UiRestoreLatest() { AtlasRoute atlasRoute = null; foreach (AtlasRoute item in _store.All) { if (item.Deleted && (atlasRoute == null || (item.DeletedUtc ?? DateTime.MinValue) > (atlasRoute.DeletedUtc ?? DateTime.MinValue))) { atlasRoute = item; } } if (atlasRoute != null) { _operations.RestoreDeleted(atlasRoute.Id); _redraw(); return "Restored \"" + atlasRoute.Name + "\"."; } return "No deleted route to restore."; } public string UiSplit(AtlasId id) { if (!_store.TryGet(id, out AtlasRoute route) || route.Deleted) { return "Route no longer exists."; } if (route.Points.Count < 3) { return "That route is too short to split."; } if (_operations.Split(id, route.Points.Count / 2) == null) { if (!route.Locked) { return "Split failed."; } return "The route is locked."; } _redraw(); return "Split \"" + route.Name + "\" at its midpoint."; } public string UiMerge(AtlasId keep, AtlasId absorbed) { if (keep.Equals(absorbed)) { return "Pick two different routes to merge."; } if (!_store.TryGet(keep, out AtlasRoute route) || !_store.TryGet(absorbed, out AtlasRoute route2)) { return "Route no longer exists."; } if (!_operations.Merge(keep, absorbed)) { return "Merge failed (locked or empty route)."; } _redraw(); return "Merged \"" + route2.Name + "\" into \"" + route.Name + "\". Undo reverts."; } public string UiMeasure(AtlasId id) { if (!_store.TryGet(id, out AtlasRoute route) || route.Deleted) { return "Route no longer exists."; } RouteEstimator.Estimate estimate = RouteEstimator.Compute(route.Points, _roads, _settings.RouteOnRoadTolerance.Value, _settings.RouteOffRoadSpeed.Value, _settings.RouteOnRoadSpeed.Value); return $"\"{route.Name}\": {estimate.DistanceMeters:0} m, {estimate.OnRoadFraction:P0} on roads, " + $"≈{estimate.EstimatedMinutes:0.#} min."; } public bool UiUndo(out string summary) { bool num = _operations.Undo(out summary); if (num) { _redraw(); } return num; } public bool UiRedo(out string summary) { bool num = _operations.Redo(out summary); if (num) { _redraw(); } return num; } private string UiEdit(AtlasId id, Action edit, string description) { if (!_store.TryGet(id, out AtlasRoute route) || route.Deleted) { return "Route no longer exists."; } _operations.EditMetadata(id, edit, description); _redraw(); return "\"" + route.Name + "\": " + description + "."; } public void HandleMapFrame(RoadPoint cursorWorld, bool actionHeld, bool actionClicked) { switch (Mode) { case MapMode.Draw: { if (!UiModeOwned) { if (actionHeld && _operations.AppendPoint(ActiveRouteId, cursorWorld)) { _redraw(); } break; } FreeDrawStrokeGate.Decision decision = _strokeGate.Observe(actionHeld, cursorWorld); if (decision.Kind == FreeDrawStrokeGate.DecisionKind.StartStroke) { StartUiStroke(); _operations.AppendPoint(ActiveRouteId, decision.StrokeStart); _operations.AppendPoint(ActiveRouteId, cursorWorld); _redraw(); } else if (decision.Kind == FreeDrawStrokeGate.DecisionKind.Append && _operations.AppendPoint(ActiveRouteId, cursorWorld)) { _redraw(); } break; } case MapMode.Erase: { if (!actionHeld) { break; } int num = 0; foreach (AtlasRoute item in new List(_store.Living)) { num += _operations.EraseNear(item.Id, cursorWorld, _settings.RouteEraseRadius.Value, out List _); } if (num > 0) { _redraw(); } break; } case MapMode.Waypoint: if (actionClicked) { if (UiModeOwned && !_uiRouteStarted) { AtlasRoute atlasRoute = _operations.StartRoute(RouteKind.Waypoint, _uiBaseName); ActiveRouteId = atlasRoute.Id; _uiRouteStarted = true; } AddWaypoint(cursorWorld); } break; } } private void StartUiStroke() { _uiStrokeCount++; string name = ((_uiStrokeCount == 1) ? _uiBaseName : $"{_uiBaseName} {_uiStrokeCount}"); AtlasRoute atlasRoute = _operations.StartRoute(RouteKind.Freehand, name); ActiveRouteId = atlasRoute.Id; _uiRouteStarted = true; } private void AddWaypoint(RoadPoint cursorWorld) { RoadPoint roadPoint = cursorWorld; if (SnapEnabled && _roads.TryGetNearestPointOnRoads(cursorWorld, _settings.RouteSnapRadius.Value, out var nearest, out var _)) { roadPoint = nearest; } if (!_store.TryGet(ActiveRouteId, out AtlasRoute route)) { return; } if (SnapEnabled && route.Points.Count > 0) { RoadPoint start = route.Points[route.Points.Count - 1]; List list = RoadGraphRouter.FindPath(_roads, start, roadPoint, _settings.RouteSnapRadius.Value); if (list != null && list.Count > 2) { for (int i = 1; i < list.Count; i++) { _operations.AppendPoint(ActiveRouteId, list[i]); } _redraw(); return; } } _operations.AppendPoint(ActiveRouteId, roadPoint); _redraw(); } public string Execute(string[] args, Vector3 playerPosition) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_044b: Unknown result type (might be due to invalid IL or missing references) //IL_041d: Unknown result type (might be due to invalid IL or missing references) //IL_03e2: Unknown result type (might be due to invalid IL or missing references) RoadPoint player = new RoadPoint(playerPosition.x, playerPosition.y, playerPosition.z); string text = ((args.Length == 0) ? "list" : args[0].ToLowerInvariant()); string remainder = ((args.Length > 1) ? string.Join(" ", args, 1, args.Length - 1) : ""); switch (text) { case "list": return ListRoutes(player); case "draw": case "waypoint": { AtlasRoute atlasRoute = _operations.StartRoute((text == "draw") ? RouteKind.Freehand : RouteKind.Waypoint, (remainder.Trim().Length == 0) ? "New route" : remainder.Trim()); ActiveRouteId = atlasRoute.Id; Mode = ((text == "draw") ? MapMode.Draw : MapMode.Waypoint); UiModeOwned = false; _uiBaseName = atlasRoute.Name; _uiRouteStarted = true; _strokeGate.Reset(); if (!(text == "draw")) { return string.Format("Waypoint route \"{0}\": {1}+LeftClick places waypoints (snap {2}). 'cc_routes stop' finishes.", atlasRoute.Name, _settings.RouteDrawModifier.Value, SnapEnabled ? "on" : "off"); } return $"Drawing \"{atlasRoute.Name}\": open the large map and hold {_settings.RouteDrawModifier.Value}+LeftClick to draw. 'cc_routes stop' finishes."; } case "erase": Mode = MapMode.Erase; UiModeOwned = false; return $"Erase mode: hold {_settings.RouteDrawModifier.Value}+LeftClick on the map to erase route ink " + $"({_settings.RouteEraseRadius.Value:0.#} m radius). 'cc_routes stop' finishes."; case "stop": Mode = MapMode.None; UiModeOwned = false; _redraw(); return "Route mode off."; case "snap": if (remainder.Trim().ToLowerInvariant() == "on") { SnapEnabled = true; } else { if (!(remainder.Trim().ToLowerInvariant() == "off")) { return "Usage: cc_routes snap on|off"; } SnapEnabled = false; } return "Road-aware snapping " + (SnapEnabled ? "on" : "off") + "."; case "measure": return Measure(player); case "name": return EditNearest(player, delegate(AtlasRoute atlasRoute2) { atlasRoute2.Name = remainder.Trim(); }, "renamed to \"" + remainder.Trim() + "\""); case "style": { if (!Enum.TryParse(remainder.Trim(), ignoreCase: true, out var style) || !Enum.IsDefined(typeof(RouteStyle), style)) { return "Usage: cc_routes style solid|dashed|dotted"; } return EditNearest(player, delegate(AtlasRoute atlasRoute2) { atlasRoute2.Style = style; }, $"style {style}"); } case "status": { if (!Enum.TryParse(remainder.Trim(), ignoreCase: true, out var status) || !Enum.IsDefined(typeof(RouteStatus), status)) { return "Usage: cc_routes status planned|active|done"; } return EditNearest(player, delegate(AtlasRoute atlasRoute2) { atlasRoute2.Status = status; }, $"status {status}"); } case "color": { string text2 = remainder.Trim().TrimStart(new char[1] { '#' }); if (string.Equals(text2, "clear", StringComparison.OrdinalIgnoreCase)) { return EditNearest(player, delegate(AtlasRoute atlasRoute2) { atlasRoute2.ColorArgb = null; }, "color cleared"); } if ((text2.Length == 6 || text2.Length == 8) && uint.TryParse(text2, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var result)) { if (text2.Length == 6) { result |= 0xFF000000u; } int argb = (int)result; return EditNearest(player, delegate(AtlasRoute atlasRoute2) { atlasRoute2.ColorArgb = argb; }, "color #" + text2); } return "Usage: cc_routes color RRGGBB|AARRGGBB|clear"; } case "lock": return LockNearest(player, locked: true); case "unlock": return LockNearest(player, locked: false); case "archive": return ArchiveNearest(player, archived: true); case "unarchive": return ArchiveNearest(player, archived: false); case "delete": { if (!TryFindNearest(player, out AtlasRoute route, out float _)) { return NoNearbyRoute(); } Mode = ((Mode == MapMode.None) ? Mode : MapMode.None); _operations.Delete(route.Id); _redraw(); return "Deleted route \"" + route.Name + "\". 'cc_routes undo' or 'cc_routes restore' reverts."; } case "restore": foreach (AtlasRoute item in _store.All) { if (item.Deleted) { _operations.RestoreDeleted(item.Id); _redraw(); return "Restored route \"" + item.Name + "\"."; } } return "No deleted route to restore."; case "split": return SplitNearest(player); case "merge": return MergeNearest(player); case "undo": { if (_operations.Undo(out string summary2)) { _redraw(); } return summary2; } case "redo": { if (_operations.Redo(out string summary)) { _redraw(); } return summary; } default: return "Usage: cc_routes [list|draw |waypoint |erase|stop|snap on/off|measure|name|style|status|color|lock|unlock|archive|unarchive|delete|restore|split|merge|undo|redo]"; } } private string ListRoutes(RoadPoint player) { StringBuilder stringBuilder = new StringBuilder(); int num = 0; foreach (AtlasRoute item in _store.Living) { num++; if (num <= 12) { RouteEstimator.Estimate estimate = RouteEstimator.Compute(item.Points, _roads, _settings.RouteOnRoadTolerance.Value, _settings.RouteOffRoadSpeed.Value, _settings.RouteOnRoadSpeed.Value); stringBuilder.Append($"\n \"{item.Name}\" [{item.Kind}, {item.Style}, {item.Status}" + (item.Locked ? ", locked" : "") + (item.Archived ? ", archived" : "") + "] " + $"{item.Points.Count} pts, {estimate.DistanceMeters:0} m"); } } string text = ((Mode == MapMode.None) ? "" : $" Mode: {Mode} (cc_routes stop ends it)."); if (num != 0) { return $"{num} route(s):{stringBuilder}{text}"; } return "No routes yet. 'cc_routes draw ' or 'cc_routes waypoint ' starts one." + text; } private string Measure(RoadPoint player) { if (!TryFindNearest(player, out AtlasRoute route, out float _)) { return NoNearbyRoute(); } RouteEstimator.Estimate estimate = RouteEstimator.Compute(route.Points, _roads, _settings.RouteOnRoadTolerance.Value, _settings.RouteOffRoadSpeed.Value, _settings.RouteOnRoadSpeed.Value); return $"\"{route.Name}\": {estimate.DistanceMeters:0} m, {estimate.OnRoadFraction:P0} on roads, " + $"≈{estimate.EstimatedMinutes:0.#} min at {_settings.RouteOffRoadSpeed.Value:0.#}/{_settings.RouteOnRoadSpeed.Value:0.#} m/s."; } private string EditNearest(RoadPoint player, Action edit, string description) { if (!TryFindNearest(player, out AtlasRoute route, out float _)) { return NoNearbyRoute(); } _operations.EditMetadata(route.Id, edit, description); _redraw(); return "Route \"" + route.Name + "\": " + description + "."; } private string LockNearest(RoadPoint player, bool locked) { if (!TryFindNearest(player, out AtlasRoute route, out float _)) { return NoNearbyRoute(); } _operations.SetLocked(route.Id, locked); return "Route \"" + route.Name + "\" " + (locked ? "locked (geometry edits rejected)" : "unlocked") + "."; } private string ArchiveNearest(RoadPoint player, bool archived) { AtlasRoute atlasRoute = null; float num = 100f; foreach (AtlasRoute item in _store.Living) { if (item.Archived != archived) { float num2 = DistanceToRoute(item, player); if (num2 <= num) { num = num2; atlasRoute = item; } } } if (atlasRoute == null) { if (!archived) { return "No archived route within 100 m."; } return NoNearbyRoute(); } _operations.SetArchived(atlasRoute.Id, archived); _redraw(); return "Route \"" + atlasRoute.Name + "\" " + (archived ? "archived (hidden from the map)" : "unarchived") + "."; } private string SplitNearest(RoadPoint player) { if (!TryFindNearest(player, out AtlasRoute route, out float _)) { return NoNearbyRoute(); } int num = -1; float num2 = float.MaxValue; for (int i = 1; i < route.Points.Count - 1; i++) { float num3 = route.Points[i].HorizontalDistanceTo(in player); if (num3 < num2) { num2 = num3; num = i; } } if (num < 0) { return "That route is too short to split."; } if (_operations.Split(route.Id, num) == null) { if (!route.Locked) { return "Split failed."; } return "The route is locked."; } _redraw(); return "Split \"" + route.Name + "\" at the point nearest you."; } private string MergeNearest(RoadPoint player) { AtlasRoute atlasRoute = null; AtlasRoute atlasRoute2 = null; float num = float.MaxValue; float num2 = float.MaxValue; foreach (AtlasRoute item in _store.Living) { float num3 = DistanceToRoute(item, player); if (num3 < num) { atlasRoute2 = atlasRoute; num2 = num; atlasRoute = item; num = num3; } else if (num3 < num2) { atlasRoute2 = item; num2 = num3; } } if (atlasRoute == null || atlasRoute2 == null || num2 > 100f) { return "Stand near the two routes to merge (both within 100 m)."; } if (!_operations.Merge(atlasRoute.Id, atlasRoute2.Id)) { return "Merge failed (locked or empty route)."; } _redraw(); return "Merged \"" + atlasRoute2.Name + "\" into \"" + atlasRoute.Name + "\". 'cc_routes undo' reverts."; } private bool TryFindNearest(RoadPoint player, out AtlasRoute? route, out float distance) { route = null; distance = float.MaxValue; if (Mode != MapMode.None && _store.TryGet(ActiveRouteId, out AtlasRoute route2) && !route2.Deleted) { route = route2; distance = 0f; return true; } foreach (AtlasRoute item in _store.Living) { float num = DistanceToRoute(item, player); if (num < distance) { distance = num; route = item; } } if (route != null) { return distance <= 100f; } return false; } private static float DistanceToRoute(AtlasRoute route, RoadPoint position) { float num = float.MaxValue; for (int i = 0; i < Math.Max(1, route.Points.Count - 1); i++) { if (route.Points.Count == 0) { break; } RoadPoint roadPoint = route.Points[i]; RoadPoint end = ((i + 1 < route.Points.Count) ? route.Points[i + 1] : roadPoint); num = Math.Min(num, RoadGeometry.HorizontalDistanceToSegment(position, roadPoint, end)); } return num; } private static string NoNearbyRoute() { return "No route within 100 m. 'cc_routes list' shows what exists."; } } internal sealed class RouteToolsCommand : ConsoleCommand { private readonly CartographerRuntime _runtime; public override string Name => "cc_routes"; public override string Help => "Concerned Cartographer routes. Subcommands: list, draw , waypoint , erase, stop, snap on|off, measure, name, style, status, color, lock, unlock, archive, unarchive, delete, restore, split, merge, undo, redo. Map modes use Modifier+LeftClick (default LeftShift)."; public RouteToolsCommand(CartographerRuntime runtime) { _runtime = runtime; } public override void Run(string[] args, Terminal context) { string text; try { text = _runtime.ExecuteRouteCommand(args); } catch (Exception ex) { text = "Route tool failed: " + ex.Message; } if (context != null) { context.AddString(text); } } public override List CommandOptionList() { return new List { "list", "draw", "waypoint", "erase", "stop", "snap", "measure", "name", "style", "status", "color", "lock", "unlock", "archive", "unarchive", "delete", "restore", "split", "merge", "undo", "redo" }; } } internal sealed class SurveyScanner { private const int PerTickExamineBudget = 48; private const float NotifyCoalesceSeconds = 10f; private static readonly FieldRef>? InstancesField = BuildInstancesRef(); private readonly CartographerSettings _settings; private readonly ManualLogSource _log; private readonly List _buffer = new List(); private int _cursor; private int _sweepExamined; private int _sweepAdded; private float _notifyElapsed = 10f; private int _unnotifiedAdded; private bool _disabledForSession; public DateTime? LastScanUtc { get; private set; } public int LastScanExamined { get; private set; } public int LastScanAdded { get; private set; } public bool DisabledForSession => _disabledForSession; public SurveyScanner(CartographerSettings settings, ManualLogSource log) { _settings = settings; _log = log; } public void RequestImmediateScan() { _buffer.Clear(); } public void Tick(float deltaTime, SurveyEngine engine, PinStore pins) { //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_01dd: Unknown result type (might be due to invalid IL or missing references) //IL_01e2: Unknown result type (might be due to invalid IL or missing references) //IL_01e4: Unknown result type (might be due to invalid IL or missing references) //IL_01e6: Unknown result type (might be due to invalid IL or missing references) //IL_020b: Unknown result type (might be due to invalid IL or missing references) //IL_0212: Unknown result type (might be due to invalid IL or missing references) //IL_0219: Unknown result type (might be due to invalid IL or missing references) if (_disabledForSession || !_settings.SurveyRulesEnabled.Value) { return; } _notifyElapsed += deltaTime; try { Player localPlayer = Player.m_localPlayer; if (localPlayer == null || InstancesField == null || (Object)(object)ZNetScene.instance == (Object)null) { return; } DateTime utcNow = DateTime.UtcNow; if (_buffer.Count == 0 || _cursor >= _buffer.Count) { if (_buffer.Count > 0) { LastScanUtc = utcNow; LastScanExamined = _sweepExamined; LastScanAdded = _sweepAdded; } _sweepExamined = 0; _sweepAdded = 0; _cursor = 0; engine.MaxObservations = _settings.SurveyMaxObservations.Value; engine.BaseExclusionRadiusMeters = _settings.SurveyBaseExclusionRadius.Value; engine.Prune(utcNow); _buffer.Clear(); foreach (KeyValuePair item in InstancesField.Invoke(ZNetScene.instance)) { _buffer.Add(item.Value); } if (_buffer.Count == 0) { return; } } Vector3 position = ((Component)localPlayer).transform.position; float value = _settings.SurveyScanRadius.Value; for (int num = Math.Min(_cursor + 48, _buffer.Count); _cursor < num; _cursor++) { _sweepExamined++; ZNetView val = _buffer[_cursor]; if ((Object)(object)val == (Object)null || (Object)(object)((Component)val).gameObject == (Object)null) { continue; } Vector3 position2 = ((Component)val).transform.position; if (!(Vector3.Distance(position2, position) > value) && !((Object)(object)((Component)val).GetComponent() != (Object)null)) { switch (engine.Offer(((Object)((Component)val).gameObject).name, new RoadPoint(position2.x, position2.y, position2.z), pins, utcNow)) { case SurveyEngine.OfferResult.Added: _sweepAdded++; _unnotifiedAdded++; continue; case SurveyEngine.OfferResult.CapReached: break; default: continue; } _cursor = _buffer.Count; break; } } if (_unnotifiedAdded > 0 && _notifyElapsed >= 10f) { ((Character)localPlayer).Message((MessageType)1, AtlasStrings.Format("hud.surveyObservations", _unnotifiedAdded), 0, (Sprite)null); _unnotifiedAdded = 0; _notifyElapsed = 0f; } } catch (Exception exception) { _disabledForSession = true; _log.LogError((object)("Survey scanner failed and was disabled for this session: " + SafeLogText.Describe(exception))); } } public static bool AnyInstanceNear(string prefabNameFragment, Vector3 position, float radius) { //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) if (InstancesField == null || (Object)(object)ZNetScene.instance == (Object)null) { return false; } try { foreach (KeyValuePair item in InstancesField.Invoke(ZNetScene.instance)) { ZNetView value = item.Value; if ((Object)(object)value != (Object)null && (Object)(object)((Component)value).gameObject != (Object)null && ((Object)((Component)value).gameObject).name.IndexOf(prefabNameFragment, StringComparison.OrdinalIgnoreCase) >= 0 && Vector3.Distance(((Component)value).transform.position, position) <= radius) { return true; } } } catch { } return false; } private static FieldRef>? BuildInstancesRef() { try { return AccessTools.FieldRefAccess>("m_instances"); } catch { return null; } } } internal sealed class SurveyToolsCommand : ConsoleCommand { private readonly CartographerRuntime _runtime; public override string Name => "cc_survey"; public override string Help => "Concerned Cartographer survey review. Subcommands: status, list, accept , reject , reload, path. Enable via Survey/SurveyRulesEnabled; rules live in survey-rules.tsv."; public SurveyToolsCommand(CartographerRuntime runtime) { _runtime = runtime; } public override void Run(string[] args, Terminal context) { string text; try { text = _runtime.ExecuteSurveyCommand(args); } catch (Exception ex) { text = "Survey tool failed: " + ex.Message; } if (context != null) { context.AddString(text); } } public override List CommandOptionList() { return new List { "status", "list", "accept", "reject", "reload", "path" }; } } internal sealed class SyncToolsCommand : ConsoleCommand { private readonly CartographerRuntime _runtime; public override string Name => "cc_sync"; public override string Help => "Concerned Cartographer atlas sharing. Subcommands: status, share, inbox, preview , apply [mine|theirs], clear. Only pins/routes scoped table/server travel; deletions propagate as tombstones and can never resurrect."; public SyncToolsCommand(CartographerRuntime runtime) { _runtime = runtime; } public override void Run(string[] args, Terminal context) { string text; try { text = _runtime.ExecuteSyncCommand(args); } catch (Exception ex) { text = "Sync tool failed: " + ex.Message; } if (context != null) { context.AddString(text); } } public override List CommandOptionList() { return new List { "status", "share", "inbox", "preview", "apply", "clear" }; } } internal sealed class SyncTransport { private const string RpcName = "CC_AtlasShare"; private const int ProtocolVersion = 1; private const int MaxCompressedBytes = 320000; private const int MaxDecompressedBytes = 4000000; private const int MaxRows = 20000; private readonly ManualLogSource _log; private readonly SyncInbox _inbox; private bool _registered; private bool _disabledForSession; public string LocalAuthorId { get; set; } = ""; public SyncTransport(ManualLogSource log, SyncInbox inbox) { _log = log; _inbox = inbox; } public void EnsureRegistered() { if (_registered || _disabledForSession || ZRoutedRpc.instance == null) { return; } try { ZRoutedRpc.instance.Register("CC_AtlasShare", (Action)OnShareReceived); _registered = true; } catch (Exception exception) { Disable(exception); } } public bool Share(string authorId, string authorName, IReadOnlyList pins, IReadOnlyList routes, out string message) { //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Expected O, but got Unknown message = ""; if (_disabledForSession || !_registered || ZRoutedRpc.instance == null) { message = "Sync transport is unavailable this session."; return false; } try { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("PINS"); foreach (AtlasPin pin in pins) { stringBuilder.AppendLine(PinCodec.SerializeRow(pin)); } stringBuilder.AppendLine("ROUTES"); foreach (AtlasRoute route in routes) { foreach (string item in RouteCodec.SerializeRoute(route)) { stringBuilder.AppendLine(item); } } byte[] array = AtlasCompression.Compress(Encoding.UTF8.GetBytes(stringBuilder.ToString())); if (array.Length > 320000) { message = $"The shared atlas is too large to broadcast ({array.Length / 1024} KB compressed). " + "Reduce the shared scope or archive old shared entities."; return false; } ZPackage val = new ZPackage(); val.Write(1); val.Write(authorId); val.Write(authorName); val.Write(array.Length); val.Write(array); ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.Everybody, "CC_AtlasShare", new object[1] { val }); message = $"Shared {pins.Count} pin(s) and {routes.Count} route(s) to all connected players."; return true; } catch (Exception exception) { Disable(exception); message = "Sharing failed; see the log."; return false; } } private void OnShareReceived(long sender, ZPackage package) { if (_disabledForSession) { return; } try { int num = package.ReadInt(); if (num != 1) { _log.LogWarning((object)$"Ignored an atlas share with protocol version {num} (mine is {1})."); return; } string text = AtlasText.SanitizeDisplay(package.ReadString(), 64); string text2 = AtlasText.SanitizeDisplay(package.ReadString(), 24); if (LocalAuthorId.Length > 0 && string.Equals(text, LocalAuthorId, StringComparison.Ordinal)) { return; } int num2 = package.ReadInt(); if (num2 <= 0 || num2 > 320000) { _log.LogWarning((object)"Ignored an oversized or empty atlas share."); return; } byte[] array = package.ReadByteArray(); if (array.Length != num2 || array.Length > 320000) { _log.LogWarning((object)"Ignored an atlas share whose payload did not match its declared size."); return; } if (!AtlasCompression.TryDecompress(array, 4000000, out byte[] output)) { _log.LogWarning((object)"Ignored an atlas share that was corrupt or decompressed beyond the safety cap."); return; } string[] array2 = Encoding.UTF8.GetString(output).Split(new char[1] { '\n' }); if (array2.Length > 20000) { _log.LogWarning((object)"Ignored an atlas share with too many rows."); return; } List list = new List(); List list2 = new List(); List list3 = null; string[] array3 = array2; for (int i = 0; i < array3.Length; i++) { string text3 = array3[i].TrimEnd(new char[1] { '\r' }); if (text3 == "PINS") { list3 = list; } else if (text3 == "ROUTES") { list3 = list2; } else if (list3 != null && text3.Length > 0) { list3.Add(text3); } } PinCodec.ParseResult parseResult = PinCodec.Parse(list); RouteCodec.ParseResult parseResult2 = RouteCodec.Parse(list2); if (_inbox != null && (parseResult.Pins.Count != 0 || parseResult2.Routes.Count != 0)) { _inbox.Add(new SyncInbox.Envelope(text, string.IsNullOrEmpty(text2) ? "Unknown Viking" : text2, parseResult.Pins, parseResult2.Routes, DateTime.UtcNow)); Player localPlayer = Player.m_localPlayer; if (localPlayer != null) { ((Character)localPlayer).Message((MessageType)1, AtlasStrings.Format("hud.syncReceived", text2), 0, (Sprite)null); } _log.LogInfo((object)($"Atlas share received: {parseResult.Pins.Count} pin(s), {parseResult2.Routes.Count} route(s), " + $"{parseResult.MalformedRows + parseResult2.MalformedRows} malformed row(s) skipped.")); } } catch (Exception exception) { _log.LogWarning((object)("Failed to read an atlas share: " + SafeLogText.Brief(exception))); } } private void Disable(Exception exception) { _disabledForSession = true; _log.LogError((object)("Sync transport failed and was disabled for this session: " + SafeLogText.Describe(exception))); } } internal static class WorldContext { public static bool TryGetWorldUid(out long uid) { uid = 0L; if (ZNet.instance == null) { return false; } uid = ZNet.instance.GetWorldUID(); return uid != 0; } } } namespace TheConcernedCat.ConcernedCartographer.Persistence { internal sealed class AtlasBackupTools { private readonly ManualLogSource _log; private static readonly string[] SidecarSuffixes = new string[3] { ".roads.tsv", ".pins.tsv", ".routes-atlas.tsv" }; private static string DataDirectory => Path.Combine(Paths.ConfigPath, "ConcernedCatMods", "ConcernedCartographer"); private static string BackupRoot => Path.Combine(DataDirectory, "backups"); public AtlasBackupTools(ManualLogSource log) { _log = log; } public string Backup(long worldUid, string label = "backup") { string arg = DateTime.UtcNow.ToString("yyyyMMdd-HHmmss", CultureInfo.InvariantCulture); string text = Path.Combine(BackupRoot, $"{worldUid}-{arg}-{label}"); Directory.CreateDirectory(text); int num = 0; string[] sidecarSuffixes = SidecarSuffixes; foreach (string text2 in sidecarSuffixes) { string text3 = Path.Combine(DataDirectory, worldUid.ToString(CultureInfo.InvariantCulture) + text2); if (File.Exists(text3)) { File.Copy(text3, Path.Combine(text, Path.GetFileName(text3)), overwrite: true); num++; } } _log.LogInfo((object)$"Atlas backup: {num} file(s) copied into a new backup folder."); return text; } public List ListBackups(long worldUid) { List list = new List(); if (!Directory.Exists(BackupRoot)) { return list; } string[] directories = Directory.GetDirectories(BackupRoot, worldUid.ToString(CultureInfo.InvariantCulture) + "-*"); foreach (string item in directories) { list.Add(item); } list.Sort(StringComparer.OrdinalIgnoreCase); list.Reverse(); return list; } public string Restore(long worldUid, string backupPath) { if (!Directory.Exists(backupPath)) { return "That backup no longer exists."; } Backup(worldUid, "pre-restore"); int num = 0; string[] files = Directory.GetFiles(backupPath); foreach (string text in files) { string destFileName = Path.Combine(DataDirectory, Path.GetFileName(text)); File.Copy(text, destFileName, overwrite: true); num++; } files = SidecarSuffixes; foreach (string text2 in files) { string path = Path.Combine(DataDirectory, worldUid.ToString(CultureInfo.InvariantCulture) + text2 + ".journal"); if (File.Exists(path)) { File.Delete(path); } } _log.LogInfo((object)$"Atlas restore: {num} file(s) restored from the chosen backup."); return $"Restored {num} file(s) from {Path.GetFileName(backupPath)} " + "(a pre-restore safety backup was taken). Log out and back in to load the restored atlas."; } public string WriteSupportReport(long worldUid, string pluginVersion, string effectiveConfig) { string text = Path.Combine(DataDirectory, "support-report.txt"); List<(string, string)> list = new List<(string, string)>(); string[] sidecarSuffixes = SidecarSuffixes; foreach (string text2 in sidecarSuffixes) { string text3 = Path.Combine(DataDirectory, worldUid.ToString(CultureInfo.InvariantCulture) + text2); if (!File.Exists(text3)) { list.Add((text2, "absent")); continue; } try { list.Add((text2, SupportReportComposer.DescribeSidecar(text2, File.ReadAllLines(text3), new FileInfo(text3).Length))); } catch (Exception exception) { list.Add((text2, SupportReportComposer.UnreadableStatus(exception))); } } File.WriteAllLines(text, SupportReportComposer.Compose(DateTime.UtcNow, pluginVersion, effectiveConfig, list, ListBackups(worldUid).Count)); return text; } } internal static class AuthorIdentity { private static string? _cached; public static string Get(ManualLogSource log) { if (_cached != null) { return _cached; } string path = Path.Combine(Paths.ConfigPath, "ConcernedCatMods", "ConcernedCartographer", "author-id.txt"); try { if (File.Exists(path)) { string text = File.ReadAllText(path).Trim(); if (Guid.TryParseExact(text, "N", out var _)) { _cached = text; return text; } } string text2 = Guid.NewGuid().ToString("N"); Directory.CreateDirectory(Path.GetDirectoryName(path)); File.WriteAllText(path, text2); _cached = text2; return text2; } catch (Exception exception) { log.LogWarning((object)("Could not persist an author identity; audit labels stay empty this session: " + SafeLogText.Brief(exception))); _cached = ""; return ""; } } } internal static class LocalizationPersistence { public static string OverridePath => Path.Combine(Paths.ConfigPath, "ConcernedCatMods", "ConcernedCartographer", "cartographer-strings.tsv"); public static string TemplatePath => Path.Combine(Paths.ConfigPath, "ConcernedCatMods", "ConcernedCartographer", "cartographer-strings-template.tsv"); public static void Initialize(ManualLogSource log) { try { Directory.CreateDirectory(Path.GetDirectoryName(TemplatePath)); File.WriteAllLines(TemplatePath, AtlasStrings.TranslatorTemplate()); if (File.Exists(OverridePath)) { int skippedRows; Dictionary dictionary = AtlasStrings.ParseOverrides(File.ReadAllLines(OverridePath), out skippedRows); AtlasStrings.LoadOverrides(dictionary); log.LogInfo((object)($"Loaded {dictionary.Count} translated string(s) from cartographer-strings.tsv" + ((skippedRows > 0) ? $" ({skippedRows} row(s) skipped)." : "."))); } } catch (Exception exception) { log.LogWarning((object)("Localization stayed at English defaults: " + SafeLogText.Brief(exception))); } } } internal sealed class PinPersistence { private readonly ManualLogSource _log; private readonly RateLimitedLog _rateLimited; private readonly List _pendingJournalRows = new List(); private long _journalWorldUid; public PinPersistence(ManualLogSource log) { _log = log; _rateLimited = new RateLimitedLog(log, 60f); } public PinStore Load(long worldUid) { _pendingJournalRows.Clear(); _journalWorldUid = worldUid; string snapshotPath = GetSnapshotPath(worldUid); string journalPath = GetJournalPath(worldUid); try { List list = new List(); if (File.Exists(snapshotPath)) { list.AddRange(File.ReadAllLines(snapshotPath)); } bool flag = false; if (File.Exists(journalPath)) { list.AddRange(File.ReadAllLines(journalPath)); flag = true; } PinCodec.ParseResult parseResult = PinCodec.Parse(list); if (parseResult.MalformedRows > 0) { _log.LogWarning((object)$"Skipped {parseResult.MalformedRows} malformed pin row(s) for this world."); } PinStore pinStore = new PinStore(parseResult.Pins); if (flag) { _log.LogInfo((object)($"Recovered pin journal for this world: {parseResult.Pins.Count} pin(s) after replay " + $"({parseResult.SupersededRows} superseded row(s)); compacting into a fresh snapshot.")); Save(worldUid, pinStore, force: true); } return pinStore; } catch (Exception exception) { _log.LogError((object)("Could not load pins for this world: " + SafeLogText.Describe(exception))); return new PinStore(); } } public void QueueJournal(AtlasPin pin) { _pendingJournalRows.Add(PinCodec.SerializeRow(pin)); } public void FlushJournal() { if (_pendingJournalRows.Count == 0) { return; } string journalPath = GetJournalPath(_journalWorldUid); try { Directory.CreateDirectory(Path.GetDirectoryName(journalPath)); File.AppendAllLines(journalPath, _pendingJournalRows); _pendingJournalRows.Clear(); } catch (Exception exception) { _rateLimited.Error("pin-journal", "Could not append the pin journal for this world: " + SafeLogText.Describe(exception)); } } public bool Save(long worldUid, PinStore store, bool force = false) { if (!force && !store.IsDirty) { return false; } string snapshotPath = GetSnapshotPath(worldUid); string text = snapshotPath + ".tmp"; try { Directory.CreateDirectory(Path.GetDirectoryName(snapshotPath)); using (StreamWriter streamWriter = new StreamWriter(text, append: false)) { foreach (string item in PinCodec.Serialize(store.All)) { streamWriter.WriteLine(item); } } File.Copy(text, snapshotPath, overwrite: true); File.Delete(text); _pendingJournalRows.Clear(); TryDelete(GetJournalPath(worldUid)); store.MarkClean(); return true; } catch (Exception exception) { _rateLimited.Error("pin-save", "Could not save pins for this world: " + SafeLogText.Describe(exception)); TryDelete(text); return false; } } private static string GetSnapshotPath(long worldUid) { return Path.Combine(Paths.ConfigPath, "ConcernedCatMods", "ConcernedCartographer", worldUid.ToString(CultureInfo.InvariantCulture) + ".pins.tsv"); } private static string GetJournalPath(long worldUid) { return GetSnapshotPath(worldUid) + ".journal"; } private static void TryDelete(string path) { try { if (File.Exists(path)) { File.Delete(path); } } catch { } } } internal sealed class RoadPersistence { private readonly ManualLogSource _log; private readonly RateLimitedLog _rateLimited; private readonly HashSet _legacyPathsAwaitingBackup = new HashSet(); private readonly HashSet _reconcileBackupsTaken = new HashSet(); public RoadPersistence(ManualLogSource log) { _log = log; _rateLimited = new RateLimitedLog(log, 60f); } public RoadAtlas Load(long worldUid) { string path = GetPath(worldUid); if (!File.Exists(path)) { return new RoadAtlas(); } try { RoadAtlasCodec.ParseResult parseResult = RoadAtlasCodec.Parse(File.ReadLines(path)); if (parseResult.MalformedRows > 0) { _log.LogWarning((object)$"Skipped {parseResult.MalformedRows} malformed road-atlas row(s) in this world's sidecar."); } if (parseResult.LegacyRows > 0) { _legacyPathsAwaitingBackup.Add(path); _log.LogInfo((object)($"This world's road atlas uses the v1 format ({parseResult.LegacyRows} row(s)); " + "the original will be kept as .v1.bak when it is first rewritten in v2.")); } RoadAtlas roadAtlas = new RoadAtlas(parseResult.Strokes); RoadAtlas.MigrationResult migrationResult = roadAtlas.RemoveNonConstructionStrokes(); if (migrationResult.RemovedStrokes > 0) { TakeAuthorityMigrationBackup(path); _log.LogInfo((object)($"Road source authority (v1): removed {migrationResult.RemovedStrokes} passive stroke(s) " + $"({migrationResult.RemovedPoints} point(s)) recorded by traversal/chunk recovery; " + $"{roadAtlas.Strokes.Count} explicit construction stroke(s) remain. " + "The pre-migration file was kept as .pre-authority.bak.")); } RoadAtlas.MaintenanceResult maintenanceResult = roadAtlas.PerformMaintenance(); if (maintenanceResult.MergedStrokes > 0 || maintenanceResult.RemovedPoints > 0) { _log.LogInfo((object)($"Road atlas maintenance: merged {maintenanceResult.MergedStrokes} stroke fragment(s), " + $"simplified away {maintenanceResult.RemovedPoints} point(s); {roadAtlas.Strokes.Count} stroke(s), " + $"{roadAtlas.PointCount} point(s) remain.")); } return roadAtlas; } catch (Exception exception) { _log.LogError((object)("Could not load road atlas from disk: " + SafeLogText.Describe(exception))); return new RoadAtlas(); } } public bool Save(long worldUid, RoadAtlas atlas) { string path = GetPath(worldUid); string text = path + ".tmp"; try { Directory.CreateDirectory(Path.GetDirectoryName(path)); if (_legacyPathsAwaitingBackup.Contains(path) && File.Exists(path)) { string text2 = path + ".v1.bak"; if (!File.Exists(text2)) { File.Copy(path, text2); _log.LogInfo((object)"Backed up the v1 road atlas beside its sidecar (.v1.bak) before the first v2 save."); } _legacyPathsAwaitingBackup.Remove(path); } using (StreamWriter streamWriter = new StreamWriter(text, append: false)) { foreach (string item in RoadAtlasCodec.Serialize(atlas.Strokes)) { streamWriter.WriteLine(item); } } File.Copy(text, path, overwrite: true); File.Delete(text); return true; } catch (Exception exception) { _rateLimited.Error("atlas-save", "Could not save road atlas to disk: " + SafeLogText.Describe(exception)); TryDelete(text); return false; } } private void TakeAuthorityMigrationBackup(string path) { try { string text = path + ".pre-authority.bak"; if (File.Exists(path) && !File.Exists(text)) { File.Copy(path, text); _log.LogInfo((object)"Backed up the pre-migration road atlas beside its sidecar (.pre-authority.bak)."); } } catch (Exception exception) { _rateLimited.Error("authority-backup", "Could not back up the road atlas before the authority migration: " + SafeLogText.Describe(exception)); } } public void BackupBeforeReconciliation(long worldUid) { if (!_reconcileBackupsTaken.Add(worldUid)) { return; } try { string path = GetPath(worldUid); if (File.Exists(path)) { File.Copy(path, path + ".pre-reconcile.bak", overwrite: true); _log.LogInfo((object)"Backed up the road atlas beside its sidecar (.pre-reconcile.bak) before this session's first reconciliation."); } } catch (Exception exception) { _rateLimited.Error("reconcile-backup", "Could not back up the road atlas before reconciliation: " + SafeLogText.Describe(exception)); } } private static string GetPath(long worldUid) { return Path.Combine(Paths.ConfigPath, "ConcernedCatMods", "ConcernedCartographer", worldUid.ToString(CultureInfo.InvariantCulture) + ".roads.tsv"); } private static void TryDelete(string path) { try { if (File.Exists(path)) { File.Delete(path); } } catch { } } } internal sealed class RoutePersistence { private readonly ManualLogSource _log; private readonly RateLimitedLog _rateLimited; private readonly Dictionary _pendingJournal = new Dictionary(); private long _journalWorldUid; public RoutePersistence(ManualLogSource log) { _log = log; _rateLimited = new RateLimitedLog(log, 60f); } public RouteStore Load(long worldUid) { _pendingJournal.Clear(); _journalWorldUid = worldUid; string snapshotPath = GetSnapshotPath(worldUid); string journalPath = GetJournalPath(worldUid); try { List list = new List(); if (File.Exists(snapshotPath)) { list.AddRange(File.ReadAllLines(snapshotPath)); } bool flag = false; if (File.Exists(journalPath)) { list.AddRange(File.ReadAllLines(journalPath)); flag = true; } RouteCodec.ParseResult parseResult = RouteCodec.Parse(list); if (parseResult.MalformedRows > 0) { _log.LogWarning((object)$"Skipped {parseResult.MalformedRows} malformed route row(s) for this world."); } RouteStore routeStore = new RouteStore(parseResult.Routes); if (flag) { _log.LogInfo((object)$"Recovered route journal for this world: {parseResult.Routes.Count} route(s) after replay."); Save(worldUid, routeStore, force: true); } return routeStore; } catch (Exception exception) { _log.LogError((object)("Could not load routes for this world: " + SafeLogText.Describe(exception))); return new RouteStore(); } } public void QueueJournal(AtlasRoute route) { _pendingJournal[route.Id.Value] = route; } public void FlushJournal() { if (_pendingJournal.Count == 0) { return; } string journalPath = GetJournalPath(_journalWorldUid); try { Directory.CreateDirectory(Path.GetDirectoryName(journalPath)); List list = new List(); foreach (AtlasRoute value in _pendingJournal.Values) { list.AddRange(RouteCodec.SerializeRoute(value)); } File.AppendAllLines(journalPath, list); _pendingJournal.Clear(); } catch (Exception exception) { _rateLimited.Error("route-journal", "Could not append the route journal: " + SafeLogText.Describe(exception)); } } public bool Save(long worldUid, RouteStore store, bool force = false) { if (!force && !store.IsDirty) { return false; } string snapshotPath = GetSnapshotPath(worldUid); string text = snapshotPath + ".tmp"; try { Directory.CreateDirectory(Path.GetDirectoryName(snapshotPath)); using (StreamWriter streamWriter = new StreamWriter(text, append: false)) { foreach (string item in RouteCodec.Serialize(store.All)) { streamWriter.WriteLine(item); } } File.Copy(text, snapshotPath, overwrite: true); File.Delete(text); _pendingJournal.Clear(); TryDelete(GetJournalPath(worldUid)); store.MarkClean(); return true; } catch (Exception exception) { _rateLimited.Error("route-save", "Could not save routes for this world: " + SafeLogText.Describe(exception)); TryDelete(text); return false; } } private static string GetSnapshotPath(long worldUid) { return Path.Combine(Paths.ConfigPath, "ConcernedCatMods", "ConcernedCartographer", worldUid.ToString(CultureInfo.InvariantCulture) + ".routes-atlas.tsv"); } private static string GetJournalPath(long worldUid) { return GetSnapshotPath(worldUid) + ".journal"; } private static void TryDelete(string path) { try { if (File.Exists(path)) { File.Delete(path); } } catch { } } } internal sealed class SavedViewPersistence { private readonly ManualLogSource _log; public SavedViewPersistence(ManualLogSource log) { _log = log; } public SavedViewStore Load() { string path = GetPath(); try { if (!File.Exists(path)) { return new SavedViewStore(); } int malformedRows; SavedViewStore result = SavedViewStore.Parse(File.ReadAllLines(path), out malformedRows); if (malformedRows > 0) { _log.LogWarning((object)$"Skipped {malformedRows} malformed saved-view row(s) in the saved-views file."); } return result; } catch (Exception exception) { _log.LogError((object)("Could not load saved views: " + SafeLogText.Describe(exception))); return new SavedViewStore(); } } public void Save(SavedViewStore store) { if (!store.IsDirty) { return; } string path = GetPath(); string text = path + ".tmp"; try { Directory.CreateDirectory(Path.GetDirectoryName(path)); File.WriteAllLines(text, store.Serialize()); File.Copy(text, path, overwrite: true); File.Delete(text); store.MarkClean(); } catch (Exception exception) { _log.LogError((object)("Could not save views: " + SafeLogText.Describe(exception))); } } private static string GetPath() { return Path.Combine(Paths.ConfigPath, "ConcernedCatMods", "ConcernedCartographer", "views.tsv"); } } internal sealed class SurveyRejectedPersistence { private readonly ManualLogSource _log; private readonly RateLimitedLog _rateLimited; public SurveyRejectedPersistence(ManualLogSource log) { _log = log; _rateLimited = new RateLimitedLog(log, 60f); } public List Load(long worldUid) { string path = GetPath(worldUid); if (!File.Exists(path)) { return new List(); } try { int malformedRows; List result = SurveyRejectedCodec.Parse(File.ReadLines(path), out malformedRows); if (malformedRows > 0) { _log.LogWarning((object)$"Skipped {malformedRows} malformed rejected-survey row(s) in this world's sidecar."); } return result; } catch (Exception exception) { _log.LogError((object)("Could not load the rejected-survey list from disk: " + SafeLogText.Describe(exception))); return new List(); } } public bool Save(long worldUid, IEnumerable entries) { string path = GetPath(worldUid); string text = path + ".tmp"; try { Directory.CreateDirectory(Path.GetDirectoryName(path)); File.WriteAllLines(text, SurveyRejectedCodec.Serialize(entries)); File.Copy(text, path, overwrite: true); File.Delete(text); return true; } catch (Exception exception) { _rateLimited.Error("survey-rejected-save", "Could not save the rejected-survey list to disk: " + SafeLogText.Describe(exception)); return false; } } private static string GetPath(long worldUid) { return Path.Combine(Paths.ConfigPath, "ConcernedCatMods", "ConcernedCartographer", worldUid.ToString(CultureInfo.InvariantCulture) + ".survey-rejected.tsv"); } } internal sealed class SurveyRulePersistence { private readonly ManualLogSource _log; public static string RulePath => Path.Combine(Paths.ConfigPath, "ConcernedCatMods", "ConcernedCartographer", "survey-rules.tsv"); public SurveyRulePersistence(ManualLogSource log) { _log = log; } public SurveyRuleSet LoadOrCreate() { try { if (!File.Exists(RulePath)) { Directory.CreateDirectory(Path.GetDirectoryName(RulePath)); File.WriteAllLines(RulePath, SurveyRuleSet.Default().Serialize()); _log.LogInfo((object)"Wrote the starter survey rules to survey-rules.tsv."); } else { string text = Normalize(File.ReadAllLines(RulePath)); if (text == Normalize(SurveyRuleSet.LegacyStarterSet().Serialize()) || text == Normalize(SurveyRuleSet.Rc8StarterSet().Serialize())) { File.WriteAllLines(RulePath, SurveyRuleSet.Default().Serialize()); _log.LogInfo((object)"Upgraded the untouched starter survey rules (survey-rules.tsv) to the v1 starter set (edited files are never touched)."); } } int malformedRows; SurveyRuleSet result = SurveyRuleSet.Parse(File.ReadAllLines(RulePath), out malformedRows); if (malformedRows > 0) { _log.LogWarning((object)$"Skipped {malformedRows} malformed survey rule(s) in survey-rules.tsv."); } return result; } catch (Exception exception) { _log.LogError((object)("Could not load survey rules; the survey stays inactive: " + SafeLogText.Describe(exception))); return new SurveyRuleSet(); } } public bool Save(SurveyRuleSet rules) { try { Directory.CreateDirectory(Path.GetDirectoryName(RulePath)); File.WriteAllLines(RulePath, rules.Serialize()); return true; } catch (Exception exception) { _log.LogError((object)("Could not save the survey rules to disk: " + SafeLogText.Describe(exception))); return false; } } private static string Normalize(IEnumerable lines) { StringBuilder stringBuilder = new StringBuilder(); foreach (string line in lines) { stringBuilder.Append(line.TrimEnd(Array.Empty())).Append('\n'); } return stringBuilder.ToString(); } } internal sealed class TerrainIntentPersistence { private readonly ManualLogSource _log; private readonly RateLimitedLog _rateLimited; public TerrainIntentPersistence(ManualLogSource log) { _log = log; _rateLimited = new RateLimitedLog(log, 60f); } public TerrainIntentMask Load(long worldUid) { string path = GetPath(worldUid); if (!File.Exists(path)) { return new TerrainIntentMask(); } try { TerrainIntentCodec.ParseResult parseResult = TerrainIntentCodec.Parse(File.ReadLines(path)); if (parseResult.UnsupportedVersion) { _log.LogWarning((object)"This world's terrain-intent sidecar has an unsupported header (written by a newer version?); starting with no exclusions for this session. The file is rewritten in v1 on the next save."); } else if (parseResult.MalformedRows > 0) { _log.LogWarning((object)$"Skipped {parseResult.MalformedRows} malformed terrain-intent row(s) in this world's sidecar."); } return parseResult.Mask; } catch (Exception exception) { _log.LogError((object)("Could not load terrain intent from disk: " + SafeLogText.Describe(exception))); return new TerrainIntentMask(); } } public bool Save(long worldUid, TerrainIntentMask mask) { string path = GetPath(worldUid); string text = path + ".tmp"; try { Directory.CreateDirectory(Path.GetDirectoryName(path)); using (StreamWriter streamWriter = new StreamWriter(text, append: false)) { foreach (string item in TerrainIntentCodec.Serialize(mask)) { streamWriter.WriteLine(item); } } File.Copy(text, path, overwrite: true); File.Delete(text); return true; } catch (Exception exception) { _rateLimited.Error("terrain-intent-save", "Could not save terrain intent to disk: " + SafeLogText.Describe(exception)); TryDelete(text); return false; } } private static string GetPath(long worldUid) { return Path.Combine(Paths.ConfigPath, "ConcernedCatMods", "ConcernedCartographer", worldUid.ToString(CultureInfo.InvariantCulture) + ".terrain-intent.tsv"); } private static void TryDelete(string path) { try { if (File.Exists(path)) { File.Delete(path); } } catch { } } } } namespace TheConcernedCat.ConcernedCartographer.Map { internal sealed class AtlasDrawerPanel { private const int ResultSlots = 5; private const int ViewSlots = 4; private const float PanelWidth = 380f; private const float PanelHeight = 700f; private const float EdgePadding = 22f; private const float ContentWidth = 336f; private const float LeftEdge = -168f; private const float ToggleLabelWidth = 210f; private const float ColumnGap = 8f; private const float ActionButtonWidth = 56f; private const float RowHeight = 28f; private const float ClearButtonWidth = 120f; private readonly ManualLogSource _log; public Func? LoadPosition; public Action? PositionCaptured; public Action? DirtToggled; public Action? PavedToggled; public Action? PinsToggled; public Action? ClusterToggled; public Action? QueryApplied; public Action? ViewSaved; public Action? ViewApplied; public Action? ResultClicked; public Action? PrivacyClicked; public Action? SystemMarkersClicked; public Func? StatusLine; public Func>? TopResults; public Func>? ViewNames; private GameObject? _panel; private Toggle? _dirt; private Toggle? _paved; private Toggle? _pins; private Toggle? _cluster; private InputField? _query; private InputField? _viewName; private Text? _status; private readonly Button[] _resultButtons = (Button[])(object)new Button[5]; private readonly Text[] _resultLabels = (Text[])(object)new Text[5]; private readonly AtlasId[] _resultIds = new AtlasId[5]; private readonly Button[] _viewButtons = (Button[])(object)new Button[4]; private readonly Text[] _viewLabels = (Text[])(object)new Text[4]; private bool _failed; private bool _suppressToggleEvents; private bool _hasLivePosition; private float _liveX; private float _liveY; private float _openedAtX; private float _openedAtY; public float UiScale = 1f; public bool IsVisible { get { if ((Object)(object)_panel != (Object)null) { return _panel.activeSelf; } return false; } } public bool HasFailed => _failed; public AtlasDrawerPanel(ManualLogSource log) { _log = log; } public void Toggle(bool showDirt, bool showPaved, bool showPins, bool cluster) { //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) if (IsVisible) { Hide(); } else { if (!EnsureBuilt()) { return; } try { _suppressToggleEvents = true; _dirt.isOn = showDirt; _paved.isOn = showPaved; _pins.isOn = showPins; _cluster.isOn = cluster; _suppressToggleEvents = false; RefreshLists(); _panel.transform.localScale = Vector3.one * UiScale; Vector2 val = ResolveOpenPosition(); ((RectTransform)_panel.transform).anchoredPosition = val; _openedAtX = val.x; _openedAtY = val.y; _panel.SetActive(true); EventSystem current = EventSystem.current; if (current != null) { current.SetSelectedGameObject(((Object)(object)_dirt != (Object)null) ? ((Component)_dirt).gameObject : null); } } catch (Exception exception) { Fail(exception); } } } public void Hide() { if ((Object)(object)_panel != (Object)null) { NotePosition(); _panel.SetActive(false); } FlushPosition(); } public void FlushPosition() { if (_hasLivePosition) { _hasLivePosition = false; PositionCaptured?.Invoke(PanelPositionRule.Serialize(_liveX, _liveY)); } } private Vector2 ResolveOpenPosition() { //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) Vector2 result = default(Vector2); ((Vector2)(ref result))..ctor(0f - 380f * UiScale / 2f - 30f, 0f); if (!PanelPositionRule.TryParse(LoadPosition?.Invoke(), out var x, out var y)) { return result; } RectTransform val = (RectTransform)(((Object)(object)GUIManager.CustomGUIFront != (Object)null) ? /*isinst with value type is only supported in some contexts*/: null); if ((Object)(object)val == (Object)null) { return result; } float x2 = x; float y2 = y; float uiScale = UiScale; Rect rect = val.rect; float width = ((Rect)(ref rect)).width; rect = val.rect; var (num, num2) = PanelPositionRule.Clamp(x2, y2, 380f, 700f, uiScale, width, ((Rect)(ref rect)).height); return new Vector2(num, num2); } private void NotePosition() { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //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) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_003d: 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 (!((Object)(object)_panel == (Object)null)) { Vector2 anchoredPosition = ((RectTransform)_panel.transform).anchoredPosition; _liveX = anchoredPosition.x; _liveY = anchoredPosition.y; if (Mathf.Abs(anchoredPosition.x - _openedAtX) > 0.5f || Mathf.Abs(anchoredPosition.y - _openedAtY) > 0.5f) { _hasLivePosition = true; } } } public void RefreshLists() { if (!IsVisible && (Object)(object)_panel == (Object)null) { return; } try { _status.text = StatusLine?.Invoke() ?? ""; List<(string, AtlasId)> list = TopResults?.Invoke() ?? new List<(string, AtlasId)>(); for (int i = 0; i < 5; i++) { bool flag = i < list.Count; ((Component)_resultButtons[i]).gameObject.SetActive(flag); if (flag) { _resultLabels[i].text = Truncate(list[i].Item1, 30); _resultIds[i] = list[i].Item2; } } List list2 = ViewNames?.Invoke() ?? new List(); for (int j = 0; j < 4; j++) { bool flag2 = j < list2.Count; ((Component)_viewButtons[j]).gameObject.SetActive(flag2); if (flag2) { _viewLabels[j].text = Truncate(list2[j], 26); } } } catch (Exception exception) { Fail(exception); } } public void HandleFrame() { if (IsVisible) { NotePosition(); if (Input.GetKeyDown((KeyCode)27) && !CcTextFocus.EscapeShouldOnlyBlur()) { Hide(); } } } private bool EnsureBuilt() { if (_failed) { return false; } if ((Object)(object)_panel != (Object)null) { return true; } if (GUIManager.Instance == null || (Object)(object)GUIManager.CustomGUIFront == (Object)null) { _log.LogWarning((object)"Atlas drawer UI is unavailable (no GUI root yet); use the cc_atlas console instead."); return false; } try { Build(); return (Object)(object)_panel != (Object)null; } catch (Exception exception) { Fail(exception); return false; } } private void Build() { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_0192: 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_01d1: Unknown result type (might be due to invalid IL or missing references) //IL_01e5: Unknown result type (might be due to invalid IL or missing references) //IL_0228: Unknown result type (might be due to invalid IL or missing references) //IL_0237: Unknown result type (might be due to invalid IL or missing references) //IL_0251: Unknown result type (might be due to invalid IL or missing references) //IL_0276: Unknown result type (might be due to invalid IL or missing references) //IL_0280: Expected O, but got Unknown //IL_02a8: Unknown result type (might be due to invalid IL or missing references) //IL_02b7: Unknown result type (might be due to invalid IL or missing references) //IL_02c2: Unknown result type (might be due to invalid IL or missing references) //IL_02e7: Unknown result type (might be due to invalid IL or missing references) //IL_02f1: Expected O, but got Unknown //IL_0314: Unknown result type (might be due to invalid IL or missing references) //IL_0323: Unknown result type (might be due to invalid IL or missing references) //IL_0337: Unknown result type (might be due to invalid IL or missing references) //IL_033f: Unknown result type (might be due to invalid IL or missing references) //IL_0345: Unknown result type (might be due to invalid IL or missing references) //IL_03c8: Unknown result type (might be due to invalid IL or missing references) //IL_03d7: Unknown result type (might be due to invalid IL or missing references) //IL_03e2: Unknown result type (might be due to invalid IL or missing references) //IL_042e: Unknown result type (might be due to invalid IL or missing references) //IL_0438: Expected O, but got Unknown //IL_0461: Unknown result type (might be due to invalid IL or missing references) //IL_048a: Unknown result type (might be due to invalid IL or missing references) //IL_0499: Unknown result type (might be due to invalid IL or missing references) //IL_04ad: Unknown result type (might be due to invalid IL or missing references) //IL_04f0: Unknown result type (might be due to invalid IL or missing references) //IL_04ff: Unknown result type (might be due to invalid IL or missing references) //IL_0519: Unknown result type (might be due to invalid IL or missing references) //IL_053e: Unknown result type (might be due to invalid IL or missing references) //IL_0548: Expected O, but got Unknown //IL_058b: Unknown result type (might be due to invalid IL or missing references) //IL_059a: Unknown result type (might be due to invalid IL or missing references) //IL_05a5: Unknown result type (might be due to invalid IL or missing references) //IL_05f1: Unknown result type (might be due to invalid IL or missing references) //IL_05fb: Expected O, but got Unknown //IL_0639: Unknown result type (might be due to invalid IL or missing references) //IL_0648: Unknown result type (might be due to invalid IL or missing references) //IL_0657: Unknown result type (might be due to invalid IL or missing references) //IL_0673: Unknown result type (might be due to invalid IL or missing references) //IL_0679: Unknown result type (might be due to invalid IL or missing references) //IL_06b6: Unknown result type (might be due to invalid IL or missing references) //IL_06c5: Unknown result type (might be due to invalid IL or missing references) //IL_06dd: Unknown result type (might be due to invalid IL or missing references) //IL_06ff: Unknown result type (might be due to invalid IL or missing references) //IL_0709: Expected O, but got Unknown //IL_0729: Unknown result type (might be due to invalid IL or missing references) //IL_0738: Unknown result type (might be due to invalid IL or missing references) //IL_0759: Unknown result type (might be due to invalid IL or missing references) //IL_077b: Unknown result type (might be due to invalid IL or missing references) //IL_0785: Expected O, but got Unknown GUIManager instance = GUIManager.Instance; Font averiaSerifBold = instance.AveriaSerifBold; Color val = default(Color); ((Color)(ref val))..ctor(0.9f, 0.8f, 0.6f, 1f); _panel = instance.CreateWoodpanel(GUIManager.CustomGUIFront.transform, new Vector2(1f, 0.5f), new Vector2(1f, 0.5f), new Vector2(-220f, 0f), 380f, 700f, true); instance.CreateText(AtlasStrings.Get("drawer.title"), _panel.transform, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -28f), averiaSerifBold, 20, val, true, Color.black, 336f, 30f, false); float y = -62f; AddSectionHeader(instance, averiaSerifBold, val, AtlasStrings.Get("drawer.layers"), ref y); _dirt = CreateToggleRow(instance, averiaSerifBold, AtlasStrings.Get("drawer.dirtRoads"), ref y, delegate(bool value) { if (!_suppressToggleEvents) { DirtToggled?.Invoke(value); } }); _paved = CreateToggleRow(instance, averiaSerifBold, AtlasStrings.Get("drawer.pavedRoads"), ref y, delegate(bool value) { if (!_suppressToggleEvents) { PavedToggled?.Invoke(value); } }); _pins = CreateToggleRow(instance, averiaSerifBold, AtlasStrings.Get("drawer.pins"), ref y, delegate(bool value) { if (!_suppressToggleEvents) { PinsToggled?.Invoke(value); RefreshLists(); } }); _cluster = CreateToggleRow(instance, averiaSerifBold, AtlasStrings.Get("drawer.clustering"), ref y, delegate(bool value) { if (!_suppressToggleEvents) { ClusterToggled?.Invoke(value); RefreshLists(); } }); y -= 6f; AddSectionHeader(instance, averiaSerifBold, val, AtlasStrings.Get("drawer.search"), ref y); float num = 272f; _query = instance.CreateInputField(_panel.transform, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(-168f + num / 2f, y), (ContentType)0, "name, tag:iron, near:…", 13, num, 28f).GetComponent(); ((UnityEvent)instance.CreateButton(AtlasStrings.Get("drawer.go"), _panel.transform, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(-168f + num + 8f + 28f, y), 56f, 28f).GetComponent