Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of REPOInstantSaveRolling v2.1.2
BepInEx/plugins/Helix65-REPO-Instant-Save/REPO-Instant-Save.dll
Decompiled 2 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using Photon.Pun; using REPO_Instant_Save.Config; using REPO_Instant_Save.Hotkeys; using REPO_Instant_Save.Save; using REPO_Instant_Save.UI; using REPO_Instant_Save.Util; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: IgnoresAccessChecksTo("Assembly-CSharp-firstpass")] [assembly: IgnoresAccessChecksTo("Assembly-CSharp")] [assembly: AssemblyCompany("REPO-Instant-Save")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("2.1.2.0")] [assembly: AssemblyInformationalVersion("2.1.2")] [assembly: AssemblyProduct("REPO-Instant-Save")] [assembly: AssemblyTitle("REPO-Instant-Save")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("2.1.2.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] 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; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace REPO_Instant_Save { internal static class ModInfo { public const string Guid = "Helix65.REPOInstantSave"; public const string Name = "REPO Instant Save"; public const string Version = "2.1.2"; } [BepInPlugin("Helix65.REPOInstantSave", "REPO Instant Save", "2.1.2")] public sealed class Plugin : BaseUnityPlugin { private readonly Harmony _harmony = new Harmony("Helix65.REPOInstantSave"); internal static Plugin? Instance { get; private set; } internal static ManualLogSource Log { get; private set; } internal PluginConfig ModConfig { get; private set; } private void Awake() { //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) Instance = this; Log = ((BaseUnityPlugin)this).Logger; Log.LogInfo((object)"REPO Instant Save v2.1.2 initialising…"); try { ModConfig = new PluginConfig(((BaseUnityPlugin)this).Config); _harmony.PatchAll(); ((Component)this).gameObject.AddComponent<HotkeyManager>(); ((Component)this).gameObject.AddComponent<SnapshotScheduler>(); ((Component)this).gameObject.AddComponent<SnapshotMenu>(); Log.LogInfo((object)(string.Format("{0} loaded. Save key: {1}, ", "REPO Instant Save", ModConfig.FullSaveKey.Value) + $"menu key: {ModConfig.MenuKey.Value}, auto-snapshot every " + $"{ModConfig.AutoSnapshotIntervalSeconds.Value}s " + $"(entry snapshot: {ModConfig.SnapshotOnLevelEntry.Value}, " + $"max {ModConfig.MaxSnapshots.Value}).")); } catch (Exception arg) { Log.LogError((object)string.Format("{0} failed to initialise: {1}", "REPO Instant Save", arg)); } } } } namespace REPO_Instant_Save.Util { internal static class GameState { public static bool IsHost => SemiFunc.IsMasterClientOrSingleplayer(); public static bool InRunLevel => SemiFunc.RunIsLevel(); public static bool StatsReady => (Object)(object)StatsManager.instance != (Object)null; public static bool LevelGenerated { get { LevelGenerator instance = LevelGenerator.Instance; if ((Object)(object)instance != (Object)null) { return instance.Generated; } return false; } } public static bool LocalPlayerAlive { get { GameDirector instance = GameDirector.instance; if ((Object)(object)instance == (Object)null || instance.PlayerList == null) { return false; } bool result = false; foreach (PlayerAvatar player in instance.PlayerList) { if (!((Object)(object)player == (Object)null) && player.isLocal) { result = true; if (player.deadSet) { return false; } if ((Object)(object)player.playerHealth != (Object)null && player.playerHealth.health <= 0) { return false; } } } return result; } } } internal static class HudNotify { private static readonly Color SuccessColor = new Color(0.45f, 1f, 0.45f); private static readonly Color FailureColor = new Color(1f, 0.45f, 0.45f); public static void Toast(string message, bool success) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) try { if (!((Object)(object)MissionUI.instance == (Object)null)) { SemiFunc.UIFocusText(message, success ? SuccessColor : FailureColor, Color.white, 3f); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("HUD toast failed: " + ex.Message)); } } } } namespace REPO_Instant_Save.UI { internal sealed class SnapshotMenu : MonoBehaviour { private const int WindowId = 23063; private const float WindowWidth = 620f; private const float WindowHeight = 600f; private const float CursorUnlockSeconds = 2f; private bool _open; private Rect _windowRect = new Rect(0f, 0f, 620f, 600f); private Vector2 _scroll; private List<SnapshotEntry> _entries = new List<SnapshotEntry>(); private string _saveName = ""; private float _nextRefresh; private string _status = ""; private bool _prevCursorVisible; private CursorLockMode _prevLockState; private bool _prevDisableInput; private float _prevInputDisableTimer; private bool _inputFrozen; private Texture2D? _cursorTexture; private Rect _cursorUv = new Rect(0f, 0f, 1f, 1f); private bool _cursorResolved; private bool _usingGameCursor; private Texture2D? _transparentCursor; private long _confirmDeleteTicks; private bool _confirmClearAll; public void Toggle() { SetOpen(!_open); } private void SetOpen(bool open) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) if (_open == open) { return; } _open = open; if (_open) { _prevCursorVisible = Cursor.visible; _prevLockState = Cursor.lockState; GameDirector instance = GameDirector.instance; _prevDisableInput = (Object)(object)instance != (Object)null && instance.DisableInput; _prevInputDisableTimer = (((Object)(object)PlayerController.instance != (Object)null) ? PlayerController.instance.InputDisableTimer : 0f); _usingGameCursor = ResolveGameCursor(); if (_usingGameCursor) { EnsureTransparentCursor(); } ApplyCursor(); _nextRefresh = 0f; _confirmDeleteTicks = 0L; _confirmClearAll = false; Refresh(); } else { RestoreCursor(); RestoreInput(); _confirmDeleteTicks = 0L; _confirmClearAll = false; } } private void OnDisable() { if (_open) { RestoreCursor(); RestoreInput(); _open = false; } } private void Update() { if (!_open) { return; } if (Input.GetKeyDown((KeyCode)27)) { SetOpen(open: false); return; } ApplyCursor(); FreezeInput(); if (Time.unscaledTime >= _nextRefresh) { Refresh(); _nextRefresh = Time.unscaledTime + 2f; } } private void EnsureTransparentCursor() { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown //IL_0042: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_transparentCursor != (Object)null)) { _transparentCursor = new Texture2D(1, 1, (TextureFormat)4, false) { hideFlags = (HideFlags)61 }; _transparentCursor.SetPixel(0, 0, new Color(0f, 0f, 0f, 0f)); _transparentCursor.Apply(); } } private void ApplyCursor() { //IL_007a: Unknown result type (might be due to invalid IL or missing references) if (_usingGameCursor) { if ((Object)(object)CursorManager.instance != (Object)null) { CursorManager.instance.Unlock(2f); } if ((Object)(object)MenuCursor.instance != (Object)null) { if ((Object)(object)_cursorTexture != (Object)null) { MenuCursor.instance.OverrideHide(0.2f); } else { MenuCursor.instance.Show(); } } Cursor.lockState = (CursorLockMode)0; Cursor.visible = false; if ((Object)(object)_transparentCursor != (Object)null) { Cursor.SetCursor(_transparentCursor, Vector2.zero, (CursorMode)0); } } else { if ((Object)(object)MenuCursor.instance != (Object)null) { MenuCursor.instance.OverrideHide(0.2f); } Cursor.lockState = (CursorLockMode)0; Cursor.visible = true; } } private void RestoreCursor() { //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Invalid comparison between Unknown and I4 if (_usingGameCursor) { Cursor.SetCursor((Texture2D)null, Vector2.zero, (CursorMode)0); if ((Object)(object)CursorManager.instance != (Object)null) { CursorManager.instance.Unlock(((int)_prevLockState == 1) ? 0.01f : 9999f); } } else { Cursor.visible = _prevCursorVisible; Cursor.lockState = _prevLockState; } } private void FreezeInput() { if (GameState.InRunLevel) { GameDirector instance = GameDirector.instance; if ((Object)(object)instance != (Object)null) { instance.SetDisableInput(0.25f); _inputFrozen = true; } if ((Object)(object)PlayerController.instance != (Object)null) { PlayerController.instance.InputDisableTimer = 0.25f; } } } private void RestoreInput() { if (_inputFrozen) { GameDirector instance = GameDirector.instance; if ((Object)(object)instance != (Object)null && !_prevDisableInput) { instance.DisableInput = false; } if ((Object)(object)PlayerController.instance != (Object)null) { PlayerController.instance.InputDisableTimer = _prevInputDisableTimer; } _inputFrozen = false; } } private bool ResolveGameCursor() { //IL_01b7: Unknown result type (might be due to invalid IL or missing references) //IL_01bc: Unknown result type (might be due to invalid IL or missing references) //IL_01bf: Unknown result type (might be due to invalid IL or missing references) //IL_01c4: Unknown result type (might be due to invalid IL or missing references) //IL_01c7: Unknown result type (might be due to invalid IL or missing references) //IL_01ce: Unknown result type (might be due to invalid IL or missing references) //IL_01d5: Unknown result type (might be due to invalid IL or missing references) //IL_01dc: Unknown result type (might be due to invalid IL or missing references) //IL_01e3: Unknown result type (might be due to invalid IL or missing references) //IL_01e8: Unknown result type (might be due to invalid IL or missing references) //IL_0250: Unknown result type (might be due to invalid IL or missing references) if (_cursorResolved) { return _usingGameCursor; } _cursorResolved = true; if (!(Plugin.Instance?.ModConfig?.UseGameCursor.Value ?? true)) { Plugin.Log.LogInfo((object)"Menu cursor: game cursor disabled in config; using OS cursor."); return false; } try { MenuCursor instance = MenuCursor.instance; if ((Object)(object)instance == (Object)null) { Plugin.Log.LogInfo((object)"Menu cursor: MenuCursor.instance not found; using OS cursor."); return false; } Renderer val = null; if (((Component)instance).transform.childCount > 0) { val = ((Component)((Component)instance).transform.GetChild(0)).GetComponent<Renderer>(); if ((Object)(object)val == (Object)null) { val = ((Component)((Component)instance).transform.GetChild(0)).GetComponentInChildren<Renderer>(true); } } if ((Object)(object)val == (Object)null) { val = ((Component)instance).GetComponentInChildren<Renderer>(true); } if ((Object)(object)val == (Object)null) { Plugin.Log.LogWarning((object)"Menu cursor: no Renderer found; using OS cursor."); return false; } Material val2 = (((Object)(object)val.sharedMaterial != (Object)null) ? val.sharedMaterial : val.material); if ((Object)(object)val2 == (Object)null) { Plugin.Log.LogWarning((object)"Menu cursor: no material found; using OS cursor."); return false; } Texture mainTexture = val2.mainTexture; Texture2D val3 = (Texture2D)(object)((mainTexture is Texture2D) ? mainTexture : null); if (val3 == null) { Plugin.Log.LogWarning((object)("Menu cursor: material '" + ((Object)val2).name + "' has no Texture2D mainTexture (" + (((object)val2.mainTexture)?.GetType().Name ?? "null") + "); using OS cursor.")); return false; } _cursorTexture = val3; Vector2 mainTextureOffset = val2.mainTextureOffset; Vector2 mainTextureScale = val2.mainTextureScale; _cursorUv = new Rect(mainTextureOffset.x, mainTextureOffset.y, mainTextureScale.x, mainTextureScale.y); Plugin.Log.LogInfo((object)("Menu cursor: using game cursor texture '" + ((Object)val3).name + "' " + $"({((Texture)val3).width}x{((Texture)val3).height}), renderer {((object)val).GetType().Name}, " + $"material '{((Object)val2).name}', uv {_cursorUv}.")); return true; } catch (Exception ex) { Plugin.Log.LogWarning((object)("Menu cursor: failed to resolve game cursor (" + ex.Message + "); using OS cursor.")); return false; } } private void DrawGameCursor() { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_004c: 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_0078: Unknown result type (might be due to invalid IL or missing references) if (_usingGameCursor && !((Object)(object)_cursorTexture == (Object)null)) { Vector2 mousePosition = Event.current.mousePosition; float num = Mathf.Clamp((float)Screen.height * 0.04f, 24f, 48f); Rect val = new Rect(mousePosition.x - num / 2f, mousePosition.y - num / 2f, num, num); int depth = GUI.depth; GUI.depth = -1000; GUI.DrawTextureWithTexCoords(val, (Texture)(object)_cursorTexture, _cursorUv); GUI.depth = depth; } } private void Refresh() { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) try { string text = SnapshotHistory.CurrentOrLatestSaveName(); if (text != _saveName) { _saveName = text; _scroll = Vector2.zero; } _entries = SnapshotHistory.List(_saveName); } catch (Exception ex) { _status = "List error: " + ex.Message; } } private void OnGUI() { //IL_0050: 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_006b: Expected O, but got Unknown //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) if (_open) { ApplyCursor(); ((Rect)(ref _windowRect)).x = ((float)Screen.width - 620f) / 2f; ((Rect)(ref _windowRect)).y = ((float)Screen.height - 600f) / 2f; _windowRect = GUI.Window(23063, _windowRect, new WindowFunction(DrawWindow), "R.E.P.O. Instant Save - backups"); DrawGameCursor(); } } private void DrawWindow(int id) { //IL_01b2: Unknown result type (might be due to invalid IL or missing references) //IL_01c6: Unknown result type (might be due to invalid IL or missing references) //IL_01cb: Unknown result type (might be due to invalid IL or missing references) //IL_052d: Unknown result type (might be due to invalid IL or missing references) bool valueOrDefault = Plugin.Instance?.ModConfig?.ShowSeconds.Value == true; int seconds = Plugin.Instance?.ModConfig?.AutoSnapshotIntervalSeconds.Value ?? 600; GUILayout.BeginVertical(Array.Empty<GUILayoutOption>()); GUILayout.Label("Сейв: " + (string.IsNullOrEmpty(_saveName) ? "(не найден)" : _saveName), Array.Empty<GUILayoutOption>()); GUILayout.Label("Автоснимок каждые " + FormatInterval(seconds) + ". Снимок делается, пока вы живы в уровне.", Array.Empty<GUILayoutOption>()); GUILayout.Space(4f); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); if (GUILayout.Button("Сохранить сейчас (F6)", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(28f) })) { bool flag = InstantSaveService.SaveNow(); _status = (flag ? "Снимок сохранён." : "Не удалось сохранить (смотрите лог)."); Refresh(); } if (GUILayout.Button("Обновить", (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(110f), GUILayout.Height(28f) })) { Refresh(); } GUILayout.EndHorizontal(); if (!string.IsNullOrEmpty(_status)) { GUILayout.Label(_status, Array.Empty<GUILayoutOption>()); } GUILayout.Space(6f); float num = Mathf.Max(150f, ((Rect)(ref _windowRect)).height - 290f); _scroll = GUILayout.BeginScrollView(_scroll, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(num) }); if (_entries.Count == 0) { GUILayout.Label("Пока нет снимков. Зайдите в уровень и подождите первый автоснимок.", Array.Empty<GUILayoutOption>()); } foreach (SnapshotEntry entry in _entries) { GUILayout.BeginHorizontal(GUIStyle.op_Implicit("box"), Array.Empty<GUILayoutOption>()); GUILayout.BeginVertical(Array.Empty<GUILayoutOption>()); GUILayout.Label(TimeText(entry, valueOrDefault) + " (" + AgeText(entry, valueOrDefault) + ")", Array.Empty<GUILayoutOption>()); GUILayout.Label($"{entry.levelName} сбор {entry.currentHaul}/{entry.haulGoal} " + "уровень " + (entry.runLevel?.ToString() ?? "?"), Array.Empty<GUILayoutOption>()); GUILayout.Label($"{entry.valuableCount} предметов, {entry.playerCount} игрок(ов)" + (entry.hasProgression ? "" : " [нет копии .es3]"), Array.Empty<GUILayoutOption>()); GUILayout.EndVertical(); GUI.enabled = entry.hasProgression; if (GUILayout.Button("Восстановить", (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(130f), GUILayout.Height(52f) })) { Restore(entry); } GUI.enabled = true; GUILayout.BeginVertical((GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(120f) }); if (_confirmDeleteTicks == entry.ticks) { if (GUILayout.Button("Удалить?", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(24f) })) { DeleteEntry(entry); } if (GUILayout.Button("Отмена", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(24f) })) { _confirmDeleteTicks = 0L; } } else if (GUILayout.Button("Удалить", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(52f) })) { _confirmDeleteTicks = entry.ticks; _confirmClearAll = false; } GUILayout.EndVertical(); GUILayout.EndHorizontal(); } GUILayout.EndScrollView(); GUILayout.Space(4f); if (_entries.Count > 0) { if (_confirmClearAll) { GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); if (GUILayout.Button("Точно удалить ВСЕ снимки?", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(28f) })) { ClearAll(); } if (GUILayout.Button("Отмена", (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(110f), GUILayout.Height(28f) })) { _confirmClearAll = false; } GUILayout.EndHorizontal(); } else if (GUILayout.Button("Очистить всю историю", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(28f) })) { _confirmClearAll = true; _confirmDeleteTicks = 0L; } GUILayout.Space(4f); } GUILayout.Label("Восстановление подставит .es3 снимка (деньги/квоты) и перезагрузит сохранённый уровень. Играйте дальше с этой точки.", Array.Empty<GUILayoutOption>()); if (GUILayout.Button("Закрыть (F8 / Esc)", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(28f) })) { SetOpen(open: false); } GUILayout.EndVertical(); GUI.DragWindow(new Rect(0f, 0f, 620f, 24f)); } private void Restore(SnapshotEntry entry) { if (string.IsNullOrEmpty(_saveName)) { _status = "Не найден сейв для восстановления."; return; } bool flag = RestoreService.RestoreEntry(_saveName, entry); _status = (flag ? "Восстановление запущено..." : "Не удалось восстановить (смотрите лог)."); if (flag) { SetOpen(open: false); } } private void DeleteEntry(SnapshotEntry entry) { _confirmDeleteTicks = 0L; if (string.IsNullOrEmpty(_saveName)) { _status = "Не найден сейв."; return; } bool flag = SnapshotHistory.Delete(_saveName, entry.ticks); _status = (flag ? "Снимок удалён." : "Не удалось удалить снимок (смотрите лог)."); Refresh(); } private void ClearAll() { _confirmClearAll = false; _confirmDeleteTicks = 0L; if (string.IsNullOrEmpty(_saveName)) { _status = "Не найден сейв."; return; } int num = SnapshotHistory.Clear(_saveName); _status = ((num > 0) ? $"Удалено снимков: {num}." : "История уже пуста."); Refresh(); } private static string FormatInterval(int seconds) { if (seconds < 60) { return $"{seconds} сек"; } int num = seconds / 60; int num2 = seconds % 60; if (num2 == 0) { if (num % 60 != 0 || num < 60) { return $"{num} мин"; } return $"{num / 60} ч"; } return $"{num} мин {num2} сек"; } private static string TimeText(SnapshotEntry entry, bool showSeconds) { DateTime dateTime = entry.TakenUtc.ToLocalTime(); string text = (showSeconds ? dateTime.ToString("HH:mm:ss") : dateTime.ToString("HH:mm")); if (!(dateTime.Date != DateTime.Now.Date)) { return text; } return dateTime.ToString("dd.MM ") + text; } private static string AgeText(SnapshotEntry entry, bool showSeconds) { TimeSpan timeSpan = DateTime.UtcNow - entry.TakenUtc; if (timeSpan.TotalSeconds < 0.0) { timeSpan = TimeSpan.Zero; } if (timeSpan.TotalMinutes < 1.0) { if (!showSeconds) { return "только что"; } return $"{(int)timeSpan.TotalSeconds} сек назад"; } if (timeSpan.TotalHours < 1.0) { if (!showSeconds) { return $"{(int)timeSpan.TotalMinutes} мин назад"; } return $"{(int)timeSpan.TotalMinutes} мин {timeSpan.Seconds} сек назад"; } if (!showSeconds) { return $"{(int)timeSpan.TotalHours} ч {timeSpan.Minutes} мин назад"; } return $"{(int)timeSpan.TotalHours} ч {timeSpan.Minutes} мин {timeSpan.Seconds} сек назад"; } } } namespace REPO_Instant_Save.Save { internal static class CrossSession { public static WorldSnapshot? Pending { get; private set; } public static bool RebuildActive { get; set; } public static Level? TargetLevel { get; private set; } public static long? PendingSelectionTicks { get; set; } public static void Arm(WorldSnapshot snapshot) { Pending = snapshot; RebuildActive = false; TargetLevel = FindLevel(snapshot.levelName); Plugin.Log.LogInfo((object)("Cross-session restore armed for '" + snapshot.levelName + "' " + $"(grid {snapshot.grid.tiles.Count} tiles, {snapshot.level.Count} level objects); " + "target level " + (((Object)(object)TargetLevel != (Object)null) ? "found" : "NOT FOUND") + ".")); } private static Level? FindLevel(string levelName) { RunManager instance = RunManager.instance; if ((Object)(object)instance == (Object)null || instance.levels == null) { return null; } foreach (Level level in instance.levels) { if ((Object)(object)level != (Object)null && ((Object)level).name == levelName) { return level; } } return null; } public static void Clear() { Pending = null; RebuildActive = false; TargetLevel = null; } public static bool ShouldRebuildCurrentLevel() { if (Pending == null || !Pending.grid.present) { return false; } Level val = (((Object)(object)RunManager.instance != (Object)null) ? RunManager.instance.levelCurrent : null); if ((Object)(object)val != (Object)null) { return ((Object)val).name == Pending.levelName; } return false; } } internal static class InstantSaveService { public static bool SaveNow(bool auto = false) { if (!GameState.IsHost) { Plugin.Log.LogWarning((object)"Instant Save ignored: only the host can save."); return false; } if (!GameState.InRunLevel) { Plugin.Log.LogWarning((object)"Instant Save ignored: you must be inside a level."); return false; } if (!GameState.StatsReady) { Plugin.Log.LogWarning((object)"Instant Save ignored: StatsManager not ready."); return false; } if (!GameState.LevelGenerated) { if (!auto) { Plugin.Log.LogWarning((object)"Instant Save ignored: the level is still generating."); } return false; } if (!GameState.LocalPlayerAlive) { if (!auto) { Plugin.Log.LogWarning((object)"Instant Save ignored: the local player is dead."); } return false; } try { string saveFileCurrent = StatsManager.instance.saveFileCurrent; if (string.IsNullOrEmpty(saveFileCurrent)) { Plugin.Log.LogWarning((object)"Instant Save ignored: no active save file yet."); return false; } WorldSnapshot worldSnapshot = WorldCapture.Capture(); StatsManager.instance.SaveGame(saveFileCurrent); StatsManager.instance.PlayersAddAll(); StatsManager.instance.saveFileReady = true; string es3SourcePath = Path.Combine(Application.persistentDataPath, "saves", saveFileCurrent, saveFileCurrent + ".es3"); int maxEntries = Plugin.Instance?.ModConfig?.MaxSnapshots.Value ?? 20; SnapshotHistory.Add(saveFileCurrent, worldSnapshot, es3SourcePath, maxEntries); Plugin.Log.LogInfo((object)("Instant Save captured for '" + saveFileCurrent + "' [" + worldSnapshot.levelName + "]: " + $"{worldSnapshot.level.Count} level objects, {worldSnapshot.valuables.Count} valuables, " + $"{worldSnapshot.extraction.Count} extraction points, {worldSnapshot.players.Count} players" + (auto ? " (auto)." : "."))); if (!auto) { Plugin? instance = Plugin.Instance; if (instance != null) { ((Component)instance).GetComponent<SnapshotScheduler>()?.OnManualSave(); } } return true; } catch (Exception arg) { Plugin.Log.LogError((object)$"Instant Save failed: {arg}"); return false; } } } internal static class InstantSaveStore { private const string FileName = "instantsave.json"; public static string FolderFor(string saveName) { return Path.Combine(Application.persistentDataPath, "saves", saveName); } public static string PathFor(string saveName) { return Path.Combine(FolderFor(saveName), "instantsave.json"); } public static void Write(string saveName, WorldSnapshot snapshot) { Directory.CreateDirectory(FolderFor(saveName)); string contents = JsonConvert.SerializeObject((object)snapshot, (Formatting)1); File.WriteAllText(PathFor(saveName), contents); } public static WorldSnapshot? Read(string saveName) { string path = PathFor(saveName); if (!File.Exists(path)) { return null; } return JsonConvert.DeserializeObject<WorldSnapshot>(File.ReadAllText(path)); } public static bool Exists(string saveName) { return File.Exists(PathFor(saveName)); } public static void Delete(string saveName) { string path = PathFor(saveName); if (File.Exists(path)) { File.Delete(path); } } } internal static class LevelRebuild { private static Dictionary<string, PrefabRef> _modulePrefabs = new Dictionary<string, PrefabRef>(); private static void BuildPrefabMap(LevelGenerator lg) { _modulePrefabs = new Dictionary<string, PrefabRef>(); Level level = lg.Level; if (!((Object)(object)level == (Object)null)) { AddAll(level.StartRooms); AddAll(level.ModulesNormal1); AddAll(level.ModulesNormal2); AddAll(level.ModulesNormal3); AddAll(level.ModulesPassage1); AddAll(level.ModulesPassage2); AddAll(level.ModulesPassage3); AddAll(level.ModulesDeadEnd1); AddAll(level.ModulesDeadEnd2); AddAll(level.ModulesDeadEnd3); AddAll(level.ModulesExtraction1); AddAll(level.ModulesExtraction2); AddAll(level.ModulesExtraction3); AddAll(level.ModulesMega1); AddAll(level.ModulesMega2); AddAll(level.ModulesMega3); AddAll(level.ModulesSpecial); } } private static void AddAll(List<PrefabRef> list) { if (list == null) { return; } foreach (PrefabRef item in list) { if (item == null) { continue; } string text = ((PrefabRef<GameObject>)(object)item).PrefabName; if (string.IsNullOrEmpty(text) && (Object)(object)((PrefabRef<GameObject>)(object)item).Prefab != (Object)null) { text = ((Object)((PrefabRef<GameObject>)(object)item).Prefab).name; } if (!string.IsNullOrEmpty(text)) { string key = Clean(text); if (!_modulePrefabs.ContainsKey(key)) { _modulePrefabs[key] = item; } } } } public static IEnumerator RebuildTiles(LevelGenerator lg, WorldSnapshot snap) { lg.waitingForSubCoroutine = true; BuildPrefabMap(lg); GridDto grid = snap.grid; lg.LevelWidth = Mathf.Max(1, grid.width); lg.LevelHeight = Mathf.Max(1, grid.height); lg.LevelGrid = new Tile[lg.LevelWidth, lg.LevelHeight]; for (int i = 0; i < lg.LevelWidth; i++) { for (int j = 0; j < lg.LevelHeight; j++) { lg.LevelGrid[i, j] = new Tile { x = i, y = j, active = false }; } } foreach (TileDto tile in grid.tiles) { if (tile.x >= 0 && tile.y >= 0 && tile.x < lg.LevelWidth && tile.y < lg.LevelHeight) { lg.LevelGrid[tile.x, tile.y] = new Tile { x = tile.x, y = tile.y, active = tile.active, first = tile.first, type = (Type)tile.type }; } } lg.ModuleAmount = grid.tiles.Count; RebuildMegaTiles(lg, snap); List<PrefabRef> refs = new List<PrefabRef>(); List<string> list = new List<string>(); foreach (SceneObjectDto item in snap.level) { if (!(item.kind != "module") && !IsStartRoom(item.name) && _modulePrefabs.TryGetValue(item.name, out PrefabRef value) && value != null) { string resourcePath = ((PrefabRef<GameObject>)(object)value).ResourcePath; if (!string.IsNullOrEmpty(resourcePath) && !list.Contains(resourcePath)) { refs.Add(value); list.Add(resourcePath); } } } if (refs.Count > 0) { yield return lg.PrefabsPrepare((IEnumerable<PrefabRef>)refs, (IEnumerable<string>)list, (IEnumerable<string>)null); } Plugin.Log.LogInfo((object)($"Rebuild: grid restored ({grid.tiles.Count} active tiles, {refs.Count} module prefabs, " + $"{lg.MegaTiles.Count} mega block(s)).")); yield return null; lg.waitingForSubCoroutine = false; } private static void RebuildMegaTiles(LevelGenerator lg, WorldSnapshot snap) { //IL_0058: 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_0069: Expected O, but got Unknown lg.MegaTiles.Clear(); foreach (SceneObjectDto item in snap.level) { if (!(item.kind != "module") && item.moduleType == 5) { Tile[] array = MegaTilesAt(lg, item.gridX, item.gridY); if (array != null) { lg.MegaTiles.Add(new MegaTile { tiles = array }); } } } } public static IEnumerator RebuildStartRoom(LevelGenerator lg, WorldSnapshot snap) { lg.waitingForSubCoroutine = true; SceneObjectDto sceneObjectDto = snap.level.FirstOrDefault((SceneObjectDto o) => o.kind == "module" && IsStartRoom(o.name)); if (sceneObjectDto != null && _modulePrefabs.TryGetValue(sceneObjectDto.name, out PrefabRef value) && value != null) { GameObject val = Spawn(value, sceneObjectDto.pos.ToVector3(), Quaternion.Euler(sceneObjectDto.euler.ToVector3())); if ((Object)(object)val != (Object)null) { val.transform.parent = lg.LevelParent.transform; } } else { Plugin.Log.LogWarning((object)("Rebuild: start room prefab '" + sceneObjectDto?.name + "' not found.")); } yield return null; lg.waitingForSubCoroutine = false; } public static IEnumerator RebuildModules(LevelGenerator lg, WorldSnapshot snap) { lg.waitingForSubCoroutine = true; lg.State = (LevelState)4; BuildPrefabMap(lg); int num = 0; int num2 = 0; foreach (SceneObjectDto item in snap.level) { if (!(item.kind != "module") && !IsStartRoom(item.name)) { if (SpawnSavedModule(lg, item)) { num++; continue; } num2++; Plugin.Log.LogWarning((object)$"Rebuild: could not spawn module '{item.name}' at ({item.gridX},{item.gridY})."); } } Plugin.Log.LogInfo((object)$"Rebuild: spawned {num} module(s), {num2} failed."); yield return null; lg.waitingForSubCoroutine = false; } public static bool SpawnSavedModule(LevelGenerator lg, SceneObjectDto dto) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) if (!_modulePrefabs.TryGetValue(dto.name, out PrefabRef value) || value == null) { return false; } GameObject val = Spawn(value, dto.pos.ToVector3(), Quaternion.Euler(dto.euler.ToVector3())); if ((Object)(object)val == (Object)null) { return false; } val.transform.parent = lg.LevelParent.transform; Module component = val.GetComponent<Module>(); if ((Object)(object)component == (Object)null) { return false; } int gridX = dto.gridX; int gridY = dto.gridY; component.GridX = gridX; component.GridY = gridY; if (dto.moduleType == 5) { Tile[] array = MegaTilesAt(lg, gridX, gridY); if (array != null) { Tile[] array2 = array; for (int i = 0; i < array2.Length; i++) { array2[i].module = component; } SetMegaConnections(lg, component, array); lg.ModulesSpawnedTarget++; return true; } } if (gridX >= 0 && gridY >= 0 && gridX < lg.LevelWidth && gridY < lg.LevelHeight) { Tile obj = lg.LevelGrid[gridX, gridY]; bool first = obj.first; component.ModuleConnectionSet(lg.GridCheckActive(gridX, gridY + 1), lg.GridCheckActive(gridX, gridY - 1) || first, lg.GridCheckActive(gridX + 1, gridY), lg.GridCheckActive(gridX - 1, gridY), first); obj.module = component; } else { component.ModuleConnectionSet(true, true, true, true, false); } lg.ModulesSpawnedTarget++; return true; } private static Tile[]? MegaTilesAt(LevelGenerator lg, int x, int y) { if (x < 0 || y <= 0 || x + 1 >= lg.LevelWidth || y >= lg.LevelHeight) { return null; } return (Tile[]?)(object)new Tile[4] { lg.LevelGrid[x, y], lg.LevelGrid[x, y - 1], lg.LevelGrid[x + 1, y], lg.LevelGrid[x + 1, y - 1] }; } private static void SetMegaConnections(LevelGenerator lg, Module module, Tile[] tiles) { bool flag = false; bool flag2 = false; bool flag3 = false; bool flag4 = false; bool flag5 = false; bool flag6 = false; bool flag7 = false; bool flag8 = false; bool flag9 = false; if (EdgeOpen(lg, tiles, tiles[0].x, tiles[0].y + 1)) { flag2 = true; } if (EdgeOpen(lg, tiles, tiles[2].x, tiles[2].y + 1)) { flag3 = true; } if (EdgeOpen(lg, tiles, tiles[2].x + 1, tiles[2].y)) { flag4 = true; } if (EdgeOpen(lg, tiles, tiles[3].x + 1, tiles[3].y)) { flag5 = true; } if (EdgeOpen(lg, tiles, tiles[3].x, tiles[3].y - 1)) { flag6 = true; } if (EdgeOpen(lg, tiles, tiles[1].x, tiles[1].y - 1)) { flag7 = true; } if (EdgeOpen(lg, tiles, tiles[1].x - 1, tiles[1].y)) { flag8 = true; } if (EdgeOpen(lg, tiles, tiles[0].x - 1, tiles[0].y)) { flag9 = true; } if (tiles[0].first || tiles[1].first || tiles[2].first || tiles[3].first) { flag = true; if (tiles[1].x == lg.LevelWidth / 2 && tiles[1].y == 0) { flag7 = true; } else if (tiles[3].x == lg.LevelWidth / 2 && tiles[3].y == 0) { flag6 = true; } } module.ModuleConnectionSetMega(flag3, flag2, flag4, flag5, flag6, flag7, flag9, flag8, flag); } private static bool EdgeOpen(LevelGenerator lg, Tile[] megaTiles, int x, int y) { if (!lg.GridCheckActive(x, y)) { return false; } Tile val = lg.GridGetTile(x, y); if (val != null) { return !megaTiles.Contains(val); } return true; } private static GameObject? Spawn(PrefabRef pr, Vector3 pos, Quaternion rot) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) if (pr == null) { return null; } if (GameManager.instance.gameMode != 0) { return PhotonNetwork.InstantiateRoomObject(((PrefabRef<GameObject>)(object)pr).ResourcePath, pos, rot, (byte)0, (object[])null); } GameObject prefab = ((PrefabRef<GameObject>)(object)pr).Prefab; if (!((Object)(object)prefab != (Object)null)) { return null; } return Object.Instantiate<GameObject>(prefab, pos, rot); } private static bool IsStartRoom(string name) { return name.StartsWith("Start Room", StringComparison.Ordinal); } private static string Clean(string name) { if (!name.EndsWith("(Clone)", StringComparison.Ordinal)) { return name; } return name.Substring(0, name.Length - "(Clone)".Length).TrimEnd(); } } internal static class ProgressionSaver { public static bool SaveNow() { if (!GameState.IsHost) { Plugin.Log.LogWarning((object)"Save ignored: only the host can save the run."); return false; } if (!GameState.StatsReady) { Plugin.Log.LogWarning((object)"Save ignored: StatsManager is not ready yet."); return false; } try { StatsManager.instance.SaveFileSave(); Plugin.Log.LogInfo((object)("Run saved to '" + StatsManager.instance.saveFileCurrent + "'.")); return true; } catch (Exception arg) { Plugin.Log.LogError((object)$"Failed to save run: {arg}"); return false; } } } internal static class RestoreService { private static bool _restoring; public static bool RestoreNow() { if (!GameState.IsHost) { Plugin.Log.LogWarning((object)"Restore ignored: only the host can load."); return false; } if (!GameState.StatsReady) { Plugin.Log.LogWarning((object)"Restore ignored: StatsManager not ready."); return false; } string saveFileCurrent = StatsManager.instance.saveFileCurrent; WorldSnapshot worldSnapshot = InstantSaveStore.Read(saveFileCurrent); if (worldSnapshot == null) { Plugin.Log.LogWarning((object)("No Instant Save snapshot found for '" + saveFileCurrent + "'.")); return false; } try { if ((Object)(object)Plugin.Instance == (Object)null) { Plugin.Log.LogWarning((object)"Restore ignored: plugin instance not ready."); return false; } ((MonoBehaviour)Plugin.Instance).StartCoroutine(WorldRestore.RestoreRoutine(worldSnapshot)); Plugin.Log.LogInfo((object)("Instant Save restore started from '" + saveFileCurrent + "' [" + worldSnapshot.levelName + "].")); return true; } catch (Exception arg) { Plugin.Log.LogError((object)$"Instant Save restore failed: {arg}"); return false; } } public static bool RestoreEntry(string saveName, SnapshotEntry entry) { if (_restoring) { Plugin.Log.LogWarning((object)"Restore ignored: another restore is already running."); return false; } if (!GameState.IsHost) { Plugin.Log.LogWarning((object)"Restore ignored: only the host can load."); return false; } if (!GameState.StatsReady) { Plugin.Log.LogWarning((object)"Restore ignored: StatsManager not ready."); return false; } if (string.IsNullOrEmpty(saveName) || entry == null) { Plugin.Log.LogWarning((object)"Restore ignored: no save/snapshot selected."); return false; } if (!SnapshotHistory.HasProgression(saveName, entry.ticks)) { Plugin.Log.LogWarning((object)$"Restore ignored: snapshot {entry.ticks} has no .es3 copy (quotas/money cannot roll back)."); return false; } if ((Object)(object)Plugin.Instance == (Object)null) { Plugin.Log.LogWarning((object)"Restore ignored: plugin instance not ready."); return false; } if (!SnapshotHistory.CopyProgression(saveName, entry.ticks)) { Plugin.Log.LogError((object)$"Restore failed: could not copy .es3 for snapshot {entry.ticks}."); return false; } SnapshotHistory.SetSelected(saveName, entry.ticks); _restoring = true; ((MonoBehaviour)Plugin.Instance).StartCoroutine(RestoreCoroutine(saveName, entry.ticks)); Plugin.Log.LogInfo((object)$"Restore: requested snapshot {entry.ticks} [{entry.levelName}] for save '{saveName}'."); return true; } private static IEnumerator RestoreCoroutine(string saveName, long ticks) { try { float deadline = Time.realtimeSinceStartup + 20f; bool ready = false; while (Time.realtimeSinceStartup < deadline) { RunManager instance = RunManager.instance; if ((Object)(object)instance != (Object)null && !instance.restarting && !instance.SceneHandoffActive) { ready = true; break; } yield return (object)new WaitForSecondsRealtime(0.25f); } if (!ready) { Plugin.Log.LogWarning((object)"Restore: game stayed busy; restore aborted (selection kept)."); SnapshotHistory.ClearSelected(saveName); yield break; } yield return null; if (!SnapshotHistory.CopyProgression(saveName, ticks)) { Plugin.Log.LogError((object)"Restore: .es3 copy disappeared before load; aborting."); SnapshotHistory.ClearSelected(saveName); yield break; } if (!SemiFunc.IsMultiplayer()) { SemiFunc.MenuActionSingleplayerGame(saveName, (List<string>)null); } else { RunManager.instance.ResetProgress(); GameManager.instance.SetConnectRandom(false); StatsManager.instance.LoadGame(saveName, (List<string>)null); RunManager.instance.ChangeLevel(true, false, (ChangeLevelType)1); } Plugin.Log.LogInfo((object)$"Restore: load triggered from snapshot {ticks}."); } finally { _restoring = false; } } } internal static class SnapshotHistory { private const string HistoryDirName = "instant_history"; private const string SnapshotPrefix = "snap_"; private const string ProgressionPrefix = "save_"; private const string MetaExtension = ".meta"; private const string JsonExtension = ".json"; private const string SelectedFileName = "selected.txt"; private const string PreRestoreFileName = "save_prerestore.es3"; public static string SaveFolder(string saveName) { return Path.Combine(Application.persistentDataPath, "saves", saveName); } public static string SaveFilePath(string saveName) { return Path.Combine(SaveFolder(saveName), saveName + ".es3"); } public static string HistoryFolder(string saveName) { return Path.Combine(SaveFolder(saveName), "instant_history"); } public static string ProgressionPath(string saveName, long ticks) { return Path.Combine(HistoryFolder(saveName), "save_" + ticks + ".es3"); } public static SnapshotEntry? Add(string saveName, WorldSnapshot snapshot, string es3SourcePath, int maxEntries) { if (string.IsNullOrEmpty(saveName) || snapshot == null) { return null; } string text = HistoryFolder(saveName); Directory.CreateDirectory(text); long num = (snapshot.takenUtcTicks = ((snapshot.takenUtcTicks != 0L) ? snapshot.takenUtcTicks : DateTime.UtcNow.Ticks)); string path = Path.Combine(text, "snap_" + num + ".json"); string path2 = Path.Combine(text, "snap_" + num + ".meta"); string destFileName = ProgressionPath(saveName, num); File.WriteAllText(path, JsonConvert.SerializeObject((object)snapshot, (Formatting)1)); if (!string.IsNullOrEmpty(es3SourcePath) && File.Exists(es3SourcePath)) { File.Copy(es3SourcePath, destFileName, overwrite: true); } else { Plugin.Log.LogWarning((object)$"Instant Save: no native .es3 at '{es3SourcePath}' - snapshot {num} has no progression copy."); } SnapshotEntry snapshotEntry = BuildEntry(saveName, num, snapshot); File.WriteAllText(path2, JsonConvert.SerializeObject((object)ToMeta(snapshotEntry), (Formatting)1)); try { File.WriteAllText(Path.Combine(SaveFolder(saveName), "instantsave.json"), JsonConvert.SerializeObject((object)snapshot, (Formatting)1)); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Instant Save: could not update instantsave.json: " + ex.Message)); } Prune(saveName, maxEntries); return snapshotEntry; } public static List<SnapshotEntry> List(string saveName) { List<SnapshotEntry> list = new List<SnapshotEntry>(); if (string.IsNullOrEmpty(saveName)) { return list; } string path = HistoryFolder(saveName); if (!Directory.Exists(path)) { return list; } string[] files = Directory.GetFiles(path, "snap_*.meta"); foreach (string text in files) { try { SnapshotMeta snapshotMeta = JsonConvert.DeserializeObject<SnapshotMeta>(File.ReadAllText(text)); if (snapshotMeta != null) { list.Add(new SnapshotEntry { ticks = snapshotMeta.ticks, levelName = (snapshotMeta.levelName ?? ""), runLevel = snapshotMeta.runLevel, currentHaul = snapshotMeta.currentHaul, haulGoal = snapshotMeta.haulGoal, valuableCount = snapshotMeta.valuableCount, playerCount = snapshotMeta.playerCount, hasProgression = HasProgression(saveName, snapshotMeta.ticks) }); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Instant Save: could not read '" + text + "': " + ex.Message)); } } return list.OrderByDescending((SnapshotEntry e) => e.ticks).ToList(); } public static WorldSnapshot? Read(string saveName, long ticks) { string path = Path.Combine(HistoryFolder(saveName), "snap_" + ticks + ".json"); if (!File.Exists(path)) { return null; } return JsonConvert.DeserializeObject<WorldSnapshot>(File.ReadAllText(path)); } public static bool HasProgression(string saveName, long ticks) { return File.Exists(ProgressionPath(saveName, ticks)); } public static bool CopyProgression(string saveName, long ticks) { string text = ProgressionPath(saveName, ticks); string text2 = SaveFilePath(saveName); if (!File.Exists(text)) { return false; } try { Directory.CreateDirectory(SaveFolder(saveName)); if (File.Exists(text2)) { File.Copy(text2, Path.Combine(HistoryFolder(saveName), "save_prerestore.es3"), overwrite: true); } File.Copy(text, text2, overwrite: true); return true; } catch (Exception arg) { Plugin.Log.LogError((object)$"Instant Save: could not restore .es3 {ticks}: {arg}"); return false; } } public static long? GetSelected(string saveName) { try { string path = Path.Combine(HistoryFolder(saveName), "selected.txt"); if (!File.Exists(path)) { return null; } long result; return long.TryParse(File.ReadAllText(path).Trim(), out result) ? new long?(result) : ((long?)null); } catch { return null; } } public static void SetSelected(string saveName, long ticks) { try { string text = HistoryFolder(saveName); Directory.CreateDirectory(text); File.WriteAllText(Path.Combine(text, "selected.txt"), ticks.ToString()); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Instant Save: could not persist selection: " + ex.Message)); } } public static void ClearSelected(string saveName) { try { string path = Path.Combine(HistoryFolder(saveName), "selected.txt"); if (File.Exists(path)) { File.Delete(path); } } catch { } } public static bool Delete(string saveName, long ticks, bool log = true) { if (string.IsNullOrEmpty(saveName) || ticks <= 0) { return false; } string path = HistoryFolder(saveName); string path2 = Path.Combine(path, "snap_" + ticks + ".json"); string path3 = Path.Combine(path, "snap_" + ticks + ".meta"); string path4 = ProgressionPath(saveName, ticks); TryDelete(path3); TryDelete(path2); TryDelete(path4); if (GetSelected(saveName) == ticks) { ClearSelected(saveName); } RefreshLegacyLatest(saveName); bool flag = !File.Exists(path3) && !File.Exists(path2) && !File.Exists(path4); if (log) { if (flag) { Plugin.Log.LogInfo((object)$"Instant Save: deleted snapshot {ticks} for '{saveName}'."); } else { Plugin.Log.LogWarning((object)$"Instant Save: could not fully delete snapshot {ticks} for '{saveName}'."); } } return flag; } public static int Clear(string saveName) { if (string.IsNullOrEmpty(saveName)) { return 0; } string path = HistoryFolder(saveName); if (!Directory.Exists(path)) { return 0; } int num = 0; try { string[] files = Directory.GetFiles(path, "snap_*.meta"); for (int i = 0; i < files.Length; i++) { long num2 = ParseTicks(Path.GetFileNameWithoutExtension(files[i])); if (num2 > 0 && Delete(saveName, num2, log: false)) { num++; } } files = Directory.GetFiles(path, "snap_*"); for (int i = 0; i < files.Length; i++) { TryDelete(files[i]); } files = Directory.GetFiles(path, "save_*.es3"); foreach (string path2 in files) { if (!string.Equals(Path.GetFileName(path2), "save_prerestore.es3", StringComparison.OrdinalIgnoreCase)) { TryDelete(path2); } } ClearSelected(saveName); TryDelete(Path.Combine(SaveFolder(saveName), "instantsave.json")); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Instant Save: clear history failed: " + ex.Message)); } Plugin.Log.LogInfo((object)$"Instant Save: cleared {num} snapshot(s) for '{saveName}'."); return num; } private static void RefreshLegacyLatest(string saveName) { try { string text = Path.Combine(SaveFolder(saveName), "instantsave.json"); SnapshotEntry snapshotEntry = List(saveName).FirstOrDefault(); if (snapshotEntry == null) { if (File.Exists(text)) { File.Delete(text); } return; } string text2 = Path.Combine(HistoryFolder(saveName), "snap_" + snapshotEntry.ticks + ".json"); if (File.Exists(text2)) { File.Copy(text2, text, overwrite: true); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Instant Save: could not refresh instantsave.json: " + ex.Message)); } } public static string CurrentOrLatestSaveName() { try { StatsManager instance = StatsManager.instance; string text = (((Object)(object)instance != (Object)null) ? instance.saveFileCurrent : null); if (!string.IsNullOrEmpty(text) && Directory.Exists(HistoryFolder(text))) { return text; } } catch { } string path = Path.Combine(Application.persistentDataPath, "saves"); if (!Directory.Exists(path)) { return ""; } string result = ""; DateTime dateTime = DateTime.MinValue; string[] directories = Directory.GetDirectories(path); foreach (string text2 in directories) { string path2 = Path.Combine(text2, "instant_history"); if (Directory.Exists(path2)) { DateTime lastWriteTimeUtc = Directory.GetLastWriteTimeUtc(path2); if (lastWriteTimeUtc > dateTime) { dateTime = lastWriteTimeUtc; result = Path.GetFileName(text2); } } } return result; } private static void Prune(string saveName, int maxEntries) { if (maxEntries <= 0) { return; } try { string text = HistoryFolder(saveName); var list = (from path in Directory.GetFiles(text, "snap_*.meta") select new { Path = path, Ticks = ParseTicks(Path.GetFileNameWithoutExtension(path)) } into x where x.Ticks > 0 orderby x.Ticks descending select x).ToList(); for (int num = maxEntries; num < list.Count; num++) { long ticks = list[num].Ticks; TryDelete(list[num].Path); TryDelete(Path.Combine(text, "snap_" + ticks + ".json")); TryDelete(ProgressionPath(saveName, ticks)); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Instant Save: history prune failed: " + ex.Message)); } } private static void TryDelete(string path) { try { if (File.Exists(path)) { File.Delete(path); } } catch { } } private static long ParseTicks(string fileNameWithoutExtension) { if (fileNameWithoutExtension.StartsWith("snap_", StringComparison.Ordinal)) { if (!long.TryParse(fileNameWithoutExtension.Substring("snap_".Length), out var result)) { return 0L; } return result; } return 0L; } private static SnapshotEntry BuildEntry(string saveName, long ticks, WorldSnapshot snapshot) { int currentHaul = 0; int haulGoal = 0; if (snapshot.round != null && snapshot.round.present) { currentHaul = snapshot.round.currentHaul; haulGoal = snapshot.round.haulGoal; } return new SnapshotEntry { ticks = ticks, levelName = (snapshot.levelName ?? ""), runLevel = snapshot.runLevel, currentHaul = currentHaul, haulGoal = haulGoal, valuableCount = ((snapshot.valuables != null) ? snapshot.valuables.Count : 0), playerCount = ((snapshot.players != null) ? snapshot.players.Count : 0), hasProgression = HasProgression(saveName, ticks) }; } private static SnapshotMeta ToMeta(SnapshotEntry entry) { return new SnapshotMeta { ticks = entry.ticks, levelName = entry.levelName, runLevel = entry.runLevel, currentHaul = entry.currentHaul, haulGoal = entry.haulGoal, valuableCount = entry.valuableCount, playerCount = entry.playerCount }; } } public sealed class SnapshotMeta { public long ticks { get; set; } public string levelName { get; set; } = ""; public int? runLevel { get; set; } public int currentHaul { get; set; } public int haulGoal { get; set; } public int valuableCount { get; set; } public int playerCount { get; set; } } public sealed class SnapshotEntry { public long ticks; public string levelName = ""; public int? runLevel; public int currentHaul; public int haulGoal; public int valuableCount; public int playerCount; public bool hasProgression; public DateTime TakenUtc => new DateTime(ticks, DateTimeKind.Utc); } public sealed class WorldSnapshot { public int version { get; set; } = 1; public string levelName { get; set; } = ""; public long takenUtcTicks { get; set; } public int? runLevel { get; set; } public List<SceneObjectDto> level { get; set; } = new List<SceneObjectDto>(); public List<ValuableDto> valuables { get; set; } = new List<ValuableDto>(); public List<HaulerDto> haulers { get; set; } = new List<HaulerDto>(); public List<ExtractionDto> extraction { get; set; } = new List<ExtractionDto>(); public List<PlayerDto> players { get; set; } = new List<PlayerDto>(); public RoundDto round { get; set; } = new RoundDto(); public List<HingeDto> hinges { get; set; } = new List<HingeDto>(); public GridDto grid { get; set; } = new GridDto(); public ChargingStationDto? chargingStation { get; set; } } public sealed class ChargingStationDto { public int charge { get; set; } public int chargeTotal { get; set; } public int crystals { get; set; } } public sealed class SceneObjectDto { public string name { get; set; } = ""; public string kind { get; set; } = "other"; public Vec3Dto pos { get; set; } = new Vec3Dto(); public Vec3Dto euler { get; set; } = new Vec3Dto(); public int gridX { get; set; } = -1; public int gridY { get; set; } = -1; public int moduleType { get; set; } = -1; } public sealed class GridDto { public bool present { get; set; } public int width { get; set; } public int height { get; set; } public List<TileDto> tiles { get; set; } = new List<TileDto>(); } public sealed class TileDto { public int x { get; set; } public int y { get; set; } public bool active { get; set; } public bool first { get; set; } public int connections { get; set; } public bool cTop { get; set; } public bool cRight { get; set; } public bool cBot { get; set; } public bool cLeft { get; set; } public int type { get; set; } } public sealed class ValuableDto { public string name { get; set; } = ""; public string kind { get; set; } = "valuable"; public Vec3Dto pos { get; set; } = new Vec3Dto(); public Vec3Dto euler { get; set; } = new Vec3Dto(); public float value { get; set; } public bool inHaul { get; set; } public string? itemAsset { get; set; } public string? instanceName { get; set; } public int battery { get; set; } = -1; public int batteryPct { get; set; } = -1; public string? inCart { get; set; } public Vec3Dto? inCartPos { get; set; } public Vec3Dto? inCartEuler { get; set; } } public sealed class HaulerDto { public string name { get; set; } = ""; public Vec3Dto pos { get; set; } = new Vec3Dto(); public float value { get; set; } public bool inHaul { get; set; } } public sealed class ExtractionDto { public string name { get; set; } = ""; public Vec3Dto pos { get; set; } = new Vec3Dto(); public int haulCurrent { get; set; } public int haulGoal { get; set; } public int state { get; set; } } public sealed class RoundDto { public bool present { get; set; } public int currentHaul { get; set; } public int totalHaul { get; set; } public int currentHaulMax { get; set; } public int haulGoal { get; set; } public int haulGoalMax { get; set; } public int extractionPoints { get; set; } public int extractionPointSurplus { get; set; } public int extractionPointsCompleted { get; set; } public int extractionHaulGoal { get; set; } public bool extractionPointActive { get; set; } public bool allExtractionPointsCompleted { get; set; } } public sealed class HingeDto { public string name { get; set; } = ""; public Vec3Dto pivot { get; set; } = new Vec3Dto(); public Vec3Dto pos { get; set; } = new Vec3Dto(); public Vec3Dto euler { get; set; } = new Vec3Dto(); public bool closed { get; set; } public bool broken { get; set; } public bool dead { get; set; } } public sealed class PlayerDto { public string steamId { get; set; } = ""; public string name { get; set; } = ""; public Vec3Dto pos { get; set; } = new Vec3Dto(); public Vec3Dto euler { get; set; } = new Vec3Dto(); public int health { get; set; } public List<InventoryItemDto> inventory { get; set; } = new List<InventoryItemDto>(); } public sealed class InventoryItemDto { public int spot { get; set; } public string instanceName { get; set; } = ""; public string itemAsset { get; set; } = ""; public int batteryPct { get; set; } = -1; } public sealed class Vec3Dto { public float x { get; set; } public float y { get; set; } public float z { get; set; } public Vec3Dto() { } public Vec3Dto(Vector3 v) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) x = v.x; y = v.y; z = v.z; } public Vector3 ToVector3() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) return new Vector3(x, y, z); } } internal sealed class SnapshotScheduler : MonoBehaviour { private const float EntrySnapshotDelay = 20f; private const float DeadRetryDelay = 30f; private float _nextCaptureAt; private string _levelKey = ""; private bool _entrySnapshotPending; private bool _loggedWaiting; private void Update() { PluginConfig pluginConfig = Plugin.Instance?.ModConfig; if (pluginConfig == null || !pluginConfig.AutoSnapshotEnabled.Value) { return; } float num = Mathf.Max(60f, (float)pluginConfig.AutoSnapshotIntervalSeconds.Value); string text = CurrentLevelKey(); if (text != _levelKey) { _levelKey = text; _entrySnapshotPending = pluginConfig.SnapshotOnLevelEntry.Value; _nextCaptureAt = Time.time + (_entrySnapshotPending ? 20f : num); _loggedWaiting = false; } else { if (!GameState.IsHost || !GameState.StatsReady || !GameState.InRunLevel || !GameState.LevelGenerated) { return; } if (!GameState.LocalPlayerAlive) { _nextCaptureAt = Mathf.Max(_nextCaptureAt, Time.time + 30f); } else if (_nextCaptureAt <= 0f) { _nextCaptureAt = Time.time + num; } else { if (Time.time < _nextCaptureAt) { return; } if (InstantSaveService.SaveNow(auto: true)) { _loggedWaiting = false; _entrySnapshotPending = false; _nextCaptureAt = Time.time + num; return; } _nextCaptureAt = Time.time + Mathf.Min(30f, num); if (!_loggedWaiting) { _loggedWaiting = true; Plugin.Log.LogWarning((object)"Auto-snapshot is waiting for a saveable moment (host in a level, save file ready)."); } } } } public void OnManualSave() { float num = Mathf.Max(60f, (float)((Plugin.Instance?.ModConfig)?.AutoSnapshotIntervalSeconds.Value ?? 600)); _entrySnapshotPending = false; _nextCaptureAt = Time.time + num; } private static string CurrentLevelKey() { RunManager instance = RunManager.instance; return (((Object)(object)instance != (Object)null && (Object)(object)instance.levelCurrent != (Object)null) ? ((Object)instance.levelCurrent).name : "?") + "#" + (((Object)(object)StatsManager.instance != (Object)null) ? StatsManager.instance.GetRunStatLevel() : (-1)); } } internal static class WorldCapture { public static WorldSnapshot Capture() { WorldSnapshot obj = new WorldSnapshot { levelName = (((Object)(object)RunManager.instance != (Object)null && (Object)(object)RunManager.instance.levelCurrent != (Object)null) ? ((Object)RunManager.instance.levelCurrent).name : "?"), takenUtcTicks = DateTime.UtcNow.Ticks, runLevel = (((Object)(object)StatsManager.instance != (Object)null) ? new int?(StatsManager.instance.GetRunStatLevel()) : ((int?)null)) }; CaptureLevelObjects(obj); CaptureGrid(obj); CaptureGrabbables(obj); CaptureHaulers(obj); CaptureExtraction(obj); CaptureRound(obj); CaptureHinges(obj); CapturePlayers(obj); CaptureChargingStation(obj); return obj; } private static int BatteryPercent(Component? component) { if ((Object)(object)component != (Object)null) { ItemBattery component2 = component.GetComponent<ItemBattery>(); if (component2 != null) { return Mathf.RoundToInt(Mathf.Clamp(component2.batteryLife, 0f, 100f)); } } return -1; } private static void CaptureChargingStation(WorldSnapshot snap) { ChargingStation instance = ChargingStation.instance; if (!((Object)(object)instance == (Object)null)) { snap.chargingStation = new ChargingStationDto { charge = instance.chargeInt, chargeTotal = instance.chargeTotal, crystals = ((instance.crystals != null) ? instance.crystals.Count : 0) }; Plugin.Log.LogInfo((object)($"Instant Save: captured charging station ({snap.chargingStation.charge} crystal(s), " + $"{snap.chargingStation.chargeTotal}% charge).")); } } private static void CaptureLevelObjects(WorldSnapshot snap) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Expected O, but got Unknown //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) LevelGenerator instance = LevelGenerator.Instance; if ((Object)(object)instance == (Object)null || (Object)(object)instance.LevelParent == (Object)null) { return; } foreach (Transform item in instance.LevelParent.transform) { Transform val = item; Module component = ((Component)val).GetComponent<Module>(); string kind = (((Object)(object)component != (Object)null) ? "module" : (((Object)(object)((Component)val).GetComponent<ModuleConnectObject>() != (Object)null) ? "connect" : "other")); snap.level.Add(new SceneObjectDto { name = Clean(((Object)val).name), kind = kind, pos = new Vec3Dto(val.position), euler = new Vec3Dto(val.eulerAngles), gridX = (((Object)(object)component != (Object)null) ? component.GridX : (-1)), gridY = (((Object)(object)component != (Object)null) ? component.GridY : (-1)), moduleType = ModuleTileType(instance, component) }); } } private static int ModuleTileType(LevelGenerator lg, Module? module) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Expected I4, but got Unknown if ((Object)(object)module == (Object)null || lg.LevelGrid == null) { return -1; } int gridX = module.GridX; int gridY = module.GridY; if (gridX < 0 || gridY < 0 || gridX >= lg.LevelWidth || gridY >= lg.LevelHeight) { return -1; } Tile val = lg.LevelGrid[gridX, gridY]; if (val == null) { return -1; } return (int)val.type; } private static void CaptureGrid(WorldSnapshot snap) { //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Expected I4, but got Unknown LevelGenerator instance = LevelGenerator.Instance; if ((Object)(object)instance == (Object)null || instance.LevelGrid == null) { return; } snap.grid.present = true; snap.grid.width = instance.LevelWidth; snap.grid.height = instance.LevelHeight; for (int i = 0; i < instance.LevelWidth; i++) { for (int j = 0; j < instance.LevelHeight; j++) { Tile val = instance.LevelGrid[i, j]; if (val != null && val.active) { snap.grid.tiles.Add(new TileDto { x = i, y = j, active = val.active, first = val.first, connections = val.connections, cTop = val.connectedTop, cRight = val.connectedRight, cBot = val.connectedBot, cLeft = val.connectedLeft, type = (int)val.type }); } } } } private static void CaptureGrabbables(WorldSnapshot snap) { //IL_00a1: 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_0207: Unknown result type (might be due to invalid IL or missing references) //IL_020c: Unknown result type (might be due to invalid IL or missing references) //IL_021f: Unknown result type (might be due to invalid IL or missing references) //IL_0224: Unknown result type (might be due to invalid IL or missing references) //IL_022f: Unknown result type (might be due to invalid IL or missing references) //IL_0234: Unknown result type (might be due to invalid IL or missing references) //IL_0239: Unknown result type (might be due to invalid IL or missing references) //IL_023d: Unknown result type (might be due to invalid IL or missing references) Dictionary<PhysGrabObject, PhysGrabObject> dictionary = BuildCartContainment(); PhysGrabObject[] array = Object.FindObjectsOfType<PhysGrabObject>(); foreach (PhysGrabObject val in array) { if ((Object)(object)val == (Object)null) { continue; } ValuableObject component = ((Component)val).GetComponent<ValuableObject>(); ItemAttributes component2 = ((Component)val).GetComponent<ItemAttributes>(); string text = (((Object)(object)((Component)val).GetComponent<PhysGrabCart>() != (Object)null) ? "cart" : (((Object)(object)component != (Object)null) ? "valuable" : (((Object)(object)component2 != (Object)null) ? "item" : null))); if (text != null) { ItemBattery component3 = ((Component)val).GetComponent<ItemBattery>(); ValuableDto valuableDto = new ValuableDto { name = Clean(((Object)val).name), kind = text, pos = new Vec3Dto(((Component)val).transform.position), euler = new Vec3Dto(((Component)val).transform.eulerAngles), value = (((Object)(object)component != (Object)null) ? component.dollarValueCurrent : 0f), inHaul = (text == "valuable" && (Object)(object)RoundDirector.instance != (Object)null && RoundDirector.instance.dollarHaulList != null && RoundDirector.instance.dollarHaulList.Contains(((Component)val).gameObject)), itemAsset = (((Object)(object)component2 != (Object)null && (Object)(object)component2.item != (Object)null) ? ((Object)component2.item).name : null), instanceName = (((Object)(object)component2 != (Object)null) ? component2.instanceName : null), battery = (((Object)(object)component3 != (Object)null) ? component3.batteryLifeInt : (-1)), batteryPct = (((Object)(object)component3 != (Object)null) ? Mathf.RoundToInt(Mathf.Clamp(component3.batteryLife, 0f, 100f)) : (-1)) }; if (text != "cart" && dictionary.TryGetValue(val, out var value) && (Object)(object)value != (Object)null) { Transform transform = ((Component)value).transform; valuableDto.inCart = Clean(((Object)value).name); valuableDto.inCartPos = new Vec3Dto(transform.InverseTransformPoint(((Component)val).transform.position)); Quaternion val2 = Quaternion.Inverse(transform.rotation) * ((Component)val).transform.rotation; valuableDto.inCartEuler = new Vec3Dto(((Quaternion)(ref val2)).eulerAngles); } snap.valuables.Add(valuableDto); } } } private static void CaptureHaulers(WorldSnapshot snap) { //IL_003d: Unknown result type (might be due to invalid IL or missing references) ItemValuableBox[] array = Object.FindObjectsOfType<ItemValuableBox>(); foreach (ItemValuableBox val in array) { if (!((Object)(object)val == (Object)null)) { snap.haulers.Add(new HaulerDto { name = Clean(((Object)val).name), pos = new Vec3Dto(((Component)val).transform.position), value = val.CurrentValue, inHaul = ((Object)(object)RoundDirector.instance != (Object)null && RoundDirector.instance.valuableBoxHaulList != null && RoundDirector.instance.valuableBoxHaulList.Contains(val)) }); } } Plugin.Log.LogInfo((object)$"Instant Save: captured {snap.haulers.Count} Hauler(s)."); } private static Dictionary<PhysGrabObject, PhysGrabObject> BuildCartContainment() { //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) Dictionary<PhysGrabObject, PhysGrabObject> dictionary = new Dictionary<PhysGrabObject, PhysGrabObject>(); PhysGrabCart[] array = Object.FindObjectsOfType<PhysGrabCart>(); foreach (PhysGrabCart val in array) { if ((Object)(object)val == (Object)null || (Object)(object)val.inCart == (Object)null) { continue; } PhysGrabObject component = ((Component)val).GetComponent<PhysGrabObject>(); if ((Object)(object)component == (Object)null) { continue; } Transform inCart = val.inCart; Collider[] array2 = Physics.OverlapBox(inCart.position, inCart.localScale / 2f, inCart.rotation); foreach (Collider val2 in array2) { if (!((Object)(object)val2 == (Object)null)) { PhysGrabObject componentInParent = ((Component)val2).GetComponentInParent<PhysGrabObject>(); if ((Object)(object)componentInParent != (Object)null && (Object)(object)componentInParent != (Object)(object)component && !dictionary.ContainsKey(componentInParent)) { dictionary[componentInParent] = component; } } } } return dictionary; } private static void CaptureExtraction(WorldSnapshot snap) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Expected I4, but got Unknown ExtractionPoint[] array = Object.FindObjectsOfType<ExtractionPoint>(); foreach (ExtractionPoint val in array) { if (!((Object)(object)val == (Object)null)) { snap.extraction.Add(new ExtractionDto { name = Clean(((Object)val).name), pos = new Vec3Dto(((Component)val).transform.position), haulCurrent = val.haulCurrent, haulGoal = val.haulGoal, state = (int)val.currentState }); } } } private static void CaptureRound(WorldSnapshot snap) { RoundDirector instance = RoundDirector.instance; if (!((Object)(object)instance == (Object)null)) { snap.round = new RoundDto { present = true, currentHaul = instance.currentHaul, totalHaul = instance.totalHaul, currentHaulMax = instance.currentHaulMax, haulGoal = instance.haulGoal, haulGoalMax = instance.haulGoalMax, extractionPoints = instance.extractionPoints, extractionPointSurplus = instance.extractionPointSurplus, extractionPointsCompleted = instance.extractionPointsCompleted, extractionHaulGoal = instance.extractionHaulGoal, extractionPointActive = instance.extractionPointActive, allExtractionPointsCompleted = instance.allExtractionPointsCompleted }; } } private static void CaptureHinges(WorldSnapshot snap) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) PhysGrabHinge[] array = Object.FindObjectsOfType<PhysGrabHinge>(); foreach (PhysGrabHinge val in array) { if (!((Object)(object)val == (Object)null)) { Vector3 v = (((Object)(object)val.hingePoint != (Object)null) ? val.hingePoint.position : ((Component)val).transform.position); snap.hinges.Add(new HingeDto { name = Clean(((Object)val).name), pivot = new Vec3Dto(v), pos = new Vec3Dto(((Component)val).transform.position), euler = new Vec3Dto(((Component)val).transform.eulerAngles), closed = val.closed, broken = val.broken, dead = val.dead }); } } } private static void CapturePlayers(WorldSnapshot snap) { //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)GameDirector.instance == (Object)null || GameDirector.instance.PlayerList == null) { return; } foreach (PlayerAvatar player in GameDirector.instance.PlayerList) { if (!((Object)(object)player == (Object)null)) { PlayerDto playerDto = new PlayerDto { steamId = (player.steamID ?? ""), name = (player.playerName ?? ""), pos = new Vec3Dto(((Component)player).transform.position), euler = new Vec3Dto(((Component)player).transform.eulerAngles), health = (((Object)(object)player.playerHealth != (Object)null) ? player.playerHealth.health : 0) }; CaptureInventory(player, playerDto); snap.players.Add(playerDto); } } } private static void CaptureInventory(PlayerAvatar player, PlayerDto dto) { Inventory instance = Inventory.instance; if (player.isLocal && (Object)(object)instance != (Object)null) { foreach (InventorySpot allSpot in instance.GetAllSpots()) { if (!((Object)(object)allSpot == (Object)null) && allSpot.IsOccupied()) { ItemEquippable currentItem = allSpot.CurrentItem; ItemAttributes val = (((Object)(object)currentItem != (Object)null) ? ((Component)currentItem).GetComponent<ItemAttributes>() : null); if (!((Object)(object)val == (Object)null)) { dto.inventory.Add(new InventoryItemDto { spot = allSpot.inventorySpotIndex, instanceName = (val.instanceName ?? ""), itemAsset = (((Object)(object)val.item != (Object)null) ? ((Object)val.item).name : ""), batteryPct = BatteryPercent((Component?)(object)currentItem) }); } } } if (dto.inventory.Count > 0) { return; } } StatsManager instance2 = StatsManager.instance; if ((Object)(object)instance2 == (Object)null || string.IsNullOrEmpty(player.steamID)) { return; } ItemEquippable[] array = Object.FindObjectsOfType<ItemEquippable>(); for (int i = 0; i < 3; i++) { if (!(i switch { 1 => instance2.playerInventorySpot2, 0 => instance2.playerInventorySpot1, _ => instance2.playerInventorySpot3, }).TryGetValue(player.steamID, out var value) || value == -1) { continue; } ItemEquippable[] array2 = array; foreach (ItemEquippable val2 in array2) { if ((Object)(object)val2 == (Object)null) { continue; } ItemAttributes component = ((Component)val2).GetComponent<ItemAttributes>(); if (!((Object)(object)component == (Object)null) && !string.IsNullOrEmpty(component.instanceName) && component.instanceName.GetHashCode() == value) { PlayerAvatar ownerPlayerAvatar = val2.GetOwnerPlayerAvatar(); if (!((Object)(object)ownerPlayerAvatar != (Object)null) || !((Object)(object)ownerPlayerAvatar != (Object)(object)player)) { dto.inventory.Add(new InventoryItemDto { spot = i, instanceName = component.instanceName, itemAsset = (((Object)(object)component.item != (Object)null) ? ((Object)component.item).name : ""), batteryPct = BatteryPercent((Component?)(object)val2) }); break; } } } } } private static string Clean(string name) { if (!name.EndsWith("(Clone)", StringComparison.Ordinal)) { return name; } return name.Substring(0, name.Length - "(Clone)".Length).TrimEnd(); } } internal static class WorldRestore { private readonly struct ValuableSource { public readonly GameObject Prefab; public readonly string ResourcePath; public ValuableSource(GameObject prefab, string resourcePath) { Prefab = prefab; ResourcePath = resourcePath; } } private const float ImpactGraceSeconds = 3f; private const int PlayerHoldFrames = 12; private static readonly List<(PhysGrabObject pgo, Vector3 pos, Quaternion rot)> HeldHinges = new List<(PhysGrabObject, Vector3, Quaternion)>(); private static readonly List<(PhysGrabObject inst, PhysGrabObject cart, Vector3 localPos, Quaternion localRot)> HeldCartItems = new List<(PhysGrabObject, PhysGrabObject, Vector3, Quaternion)>(); public static IEnumerator RestoreRoutine(WorldSnapshot snap) { RestoreGrabbables(snap); RestoreHaulers(snap); RefreshEnemies(); RestoreExtraction(snap); RestoreChargingStation(snap); RestoreHinges(snap); RestoreHealth(snap); RestorePlayers(snap); yield return (object)new WaitForSeconds(0.5f); RestoreInventories(snap); for (int i = 0; i < 12; i++) { HoldLocalPlayer(snap); HoldHinges(); HoldCartItems(); yield return (object)new WaitForFixedUpdate(); } ReapplyRoundAccounting(snap); for (int i = 0; i < 8; i++) { yield return (object)new WaitForSecondsRealtime(0.25f); ReapplyRoundAccounting(snap, log: false); } Plugin.Log.LogInfo((object)"Restore: complete."); } public static IEnumerator CrossSessionRestore(WorldSnapshot snap) { ClearSpawnedValuables(); yield return null; yield return RestoreRoutine(snap); } private static void ClearSpawnedValuables() { int num = 0; ValuableObject[] array = Object.FindObjectsOfType<ValuableObject>(); foreach (ValuableObject val in array) { if ((Object)(object)val == (Object)null) { continue; } if (SemiFunc.IsMultiplayer()) { if (PhotonNetwork.IsMasterClient) { PhotonNetwork.Destroy(((Component)val).gameObject); num++; } } else { Object.Destroy((Object)(object)((Component)val).gameObject); num++; } } Plugin.Log.LogInfo((object)$"Cross-session: cleared {num} generator-spawned valuable(s)."); } public static void RestorePlayers(WorldSnapshot snap) { //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)GameDirector.instance == (Object)null || GameDirector.instance.PlayerList == null) { return; } int num = 0; foreach (PlayerAvatar player in GameDirector.instance.PlayerList) { if (!((Object)(object)player == (Object)null)) { PlayerDto playerDto = snap.players.Find((PlayerDto x) => x.steamId == player.steamID); if (playerDto != null) { player.Spawn(playerDto.pos.ToVector3(), Quaternion.Euler(playerDto.euler.ToVector3()), false); ZeroVelocity(((Component)player).GetComponent<Rigidbody>()); num++; } } } Plugin.Log.LogInfo((object)$"Restore: teleported {num} player(s) to saved positions."); } private static void HoldLocalPlayer(WorldSnapshot snap) { //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)GameDirector.instance == (Object)null || GameDirector.instance.PlayerList == null) { return; } foreach (PlayerAvatar player in GameDirector.instance.PlayerList) { if ((Object)(object)player == (Object)null || !player.isLocal) { continue; } PlayerDto playerDto = snap.players.Find((PlayerDto x) => x.steamId == player.steamID); if (playerDto != null) { Vector3 position = playerDto.pos.ToVector3(); Quaternion rotation = Quaternion.Euler(playerDto.euler.ToVector3()); if ((Object)(object)PlayerController.instance != (Object)null) { ((Component)PlayerController.instance).transform.position = position; ((Component)PlayerController.instance).transform.rotation = rotation; } Rigidbody component = ((Component)player).GetComponent<Rigidbody>(); if ((Object)(object)component != (Object)null) { component.position = position; ZeroVelocity(component); } ((Component)player).transform.position = position; } } } public static void RestoreHealth(WorldSnapshot snap) { if ((Object)(object)GameDirector.instance == (Object)null || GameDirector.instance.PlayerList == null) { return; } int num = 0; foreach (PlayerAvatar player in GameDirector.instance.PlayerList) { if ((Object)(object)player == (Object)null || (Object)(object)player.playerHealth == (Object)null) { continue; } PlayerDto playerDto = snap.players.Find((PlayerDto x) => x.steamId == player.steamID); if (playerDto != null) { int health = player.playerHealth.health; int health2 = playerDto.health; if (health2 > health) { player.playerHealth.Heal(health2 - health, false); } else if (health2 < health && health2 > 0) { player.playerHealth.Hurt(health - health2, false, -1, true); } num++; } } Plugin.Log.LogInfo((object)$"Restore: set health on {num} player(s)."); } public static void RestoreInventories(WorldSnapshot snap) { if ((Object)(object)GameDirector.instance == (Object)null || GameDirector.instance.PlayerList == null) { return; } List<ItemEquippable> list = Object.FindObjectsOfType<ItemEquippable>().ToList(); HashSet<ItemEquippable> hashSet = new HashSet<ItemEquippable>(); int num = 0; foreach (PlayerAvatar player in GameDirector.instance.PlayerList) { if ((Object)(object)player == (Object)null) { continue; } PlayerDto playerDto = snap.players.Find((PlayerDto x) => x.steamId == player.steamID); if (playerDto?.inventory == null || playerDto.inventory.Count == 0) { continue; } foreach (InventoryItemDto item in playerDto.inventory) { if (item.spot < 0 || item.spot > 2) { continue; } ItemEquippable val = FindInventoryItem(list, hashSet, item, player); if ((Object)(object)val == (Object)null && !string.IsNullOrEmpty(item.itemAsset)) { val = RecreateInventoryItem(item, player); if ((Object)(object)val != (Object)null) { list.Add(val); } } if ((Object)(object)val == (Object)null) { Plugin.Log.LogWarning((object)("Restore: could not find/re-create item '" + item.instanceName + "' " + $"({item.itemAsset}) for slot {item.spot} of '{player.playerName}'.")); continue; } hashSet.Add(val); ItemAttributes component = ((Component)val).GetComponent<ItemAttributes>(); if ((Object)(object)component != (Object)null && !string.IsNullOrEmpty(item.instanceName)) { if (string.IsNullOrEmpty(component.instanceName)) { component.instanceName = item.instanceName; } else if (component.instanceName != item.instanceName) { Plugin.Log.LogWarning((object)($"Restore: slot {item.spot} matched '{component.instanceName}' " + "instead of saved '" + item.instanceName + "'.")); } } ApplyBattery(((Component)val).GetComponent<ItemBattery>(), item.batteryPct, -1, ((Object)(object)component != (Object)null) ? component.instanceName : item.instanceName); if (player.isLocal && (Object)(object)Inventory.instance != (Object)null) { InventorySpot spotByIndex = Inventory.instance.GetSpotByIndex(item.spot); if ((Object)(object)spotByIndex != (Object)null && spotByIndex.IsOccupied() && (Object)(object)spotByIndex.CurrentItem == (Object)(object)val) { continue; } } int num2 = -1; if (SemiFunc.IsMultiplayer() && (Object)(object)player.physGrabber != (Object)null && (Object)(object)player.physGrabber.photonView != (Object)null) { num2 = player.physGrabber.photonView.ViewID; } val.RequestEquip(item.spot, num2); if (!string.IsNullOrEmpty(item.instanceName)) { StatsManager.instance.PlayerInventoryUpdate(player.steamID, item.instanceName, item.spot, SemiFunc.IsMultiplayer()); } num++; } } Plugin.Log.LogInfo((object)$"Restore: re-equipped {num} inventory item(s)."); } private static ItemEquippable? FindInventoryItem(List<ItemEquippable> items, HashSet<ItemEquippable> used, InventoryItemDto dto, PlayerAvatar player) { ItemEquippable val = null; foreach (ItemEquippable item in items) { if ((Object)(object)item == (Object)null || used.Contains(item)) { continue; } ItemAttributes component = ((Component)item).GetComponent<ItemAttributes>(); if ((Object)(object)component == (Object)null) { continue; } PlayerAvatar ownerPlayerAvatar = item.GetOwnerPlayerAvatar(); if (!((Object)(object)ownerPlayerAvatar != (Object)null) || !((Object)(object)ownerPlayerAvatar != (Object)(object)player)) { if (!string.IsNullOrEmpty(dto.instanceName) && component.instanceName == dto.instanceName) { return item; } if ((Object)(object)val == (Object)null && !string.IsNullOrEmpty(dto.itemAsset) && (Object)(object)component.item != (Object)null && ((Object)component.item).name == dto.itemAsset) { val = item; } } } return val; } private static ItemEquippable? RecreateInventoryItem(InventoryItemDto dto, PlayerAvatar player) { //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_005e: 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_0097: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_0083: 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) if ((Object)(object)StatsManager.instance == (Object)null || !StatsManager.instance.itemDictionary.TryGetValue(dto.itemAsset, out var value) || (Object)(object)value == (Object)null || value.prefab == null) { return null; } Vector3 val = (((Object)(object)player != (Object)null) ? ((Component)player).transform.position : Vector3.zero); Quaternion val2 = (((Object)(object)player != (Object)null) ? ((Component)player).transform.rotation : Quaternion.identity); GameObject val3 = (SemiFunc.IsMultiplayer() ? PhotonNetwork.InstantiateRoomObject(((PrefabRef<GameObject>)(object)value.prefab).ResourcePath, val, val2, (byte)0, (object[])null) : Object.Instantiate<GameObject>(((PrefabRef<GameObject>)(object)value.prefab).Prefab, val, val2)); if ((Object)(object)val3 == (Object)null) { return null; } ItemAttributes component = val3.GetComponent<ItemAttributes>(); if ((Object)(object)component != (Object)null && !string.IsNullOrEmpty(dto.instanceName)) { component.instanceName = dto.instanceName; } ApplyBattery(val3.GetComponent<ItemBattery>(), dto.batteryPct, -1, dto.instanceName); return val3.GetComponent<ItemEquippable>(); } public static void RefreshEnemies() { EnemyDirector instance = EnemyDirector.instance; if ((Object)(object)instance == (Object)null || instance.enemiesSpawned == null) { return; } int num = 0; foreach (EnemyParent item in instance.enemiesSpawned.ToList()) { if (!((Object)(object)item == (Object)null)) { try { item.Despawn(); num++; } catch (Exception ex) { Plugin.Log.LogWarning((object)("Restore: enemy despawn failed: " + ex.Message)); } } } Plugin.Log.LogInfo((object)$"Restore: refreshed (despawned) {num} enemy group(s)."); } public static void RestoreExtraction(WorldSnapshot snap) { //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Unknown result type (might be due to invalid IL or missing references) RoundDirector instance = RoundDirector.instance; if ((Object)(object)instance != (Object)null && snap.round != null && snap.round.present) { instance.currentHaul = snap.round.currentHaul; instance.totalHaul = snap.round.totalHaul; instance.currentHaulMax = snap.round.currentHaulMax; instance.haulGoal = snap.round.haulGoal; instance.haulGoalMax = snap.round.haulGoalMax; instance.extractionPoints = snap.round.extractionPoints; instance.extractionPointSurplus = snap.round.extractionPointSurplus; instance.extractionPointsCompleted = snap.round.extractionPointsCompleted; instance.extractionHaulGoal = snap.round.extractionHaulGoal; instance.allExtractionPointsCompleted = snap.round.allExtractionPointsCompleted; } List<ExtractionPoint> list = Object.FindObjectsOfType<ExtractionPoint>().ToList(); HashSet<ExtractionPoint> hashSet = new HashSet<ExtractionPoint>(); int num = 0; foreach (ExtractionDto item in snap.extraction) { Vector3 val = item.pos.ToVector3(); ExtractionPoint val2 = null; float num2 = float.MaxValue; foreach (ExtractionPoint item2 in list) { if (!((Object)(object)item2 == (Object)null) && !hashSet.Contains(item2)) { Vector3 val3 = ((Component)item2).transform.position - val; float sqrMagnitude = ((Vector3)(ref val3)).sqrMagnitude; if (sqrMagnitude < num2) { num2 = sqrMagnitude; val2 = item2; } } } if ((Object)(object)val2 != (Object)null) { val2.UpdateLock(false); val2.haulGoal = item.haulGoal; if (item.state == 7) { MarkExtractionComplete(val2); } else { DriveExtractionState(val2, (State)1); } hashSet.Add(val2); num++; } } foreach (ExtractionPoint item3 in list) { if ((Object)(object)item3 != (Object)null) { item3.UpdateLock(false); } } if ((Object)(object)instance != (Object)null) { instance.extractionPointActive = false; instance.extractionPointCurrent = null; } Plugin.Log.LogInfo((object)$"Restore: reset {num} extraction point(s) + round accounting."); } private static void MarkExtractionComplete(ExtractionPoint ep) { ep.taxReturn = true; DriveExtractionState(ep, (State)7); if ((Object)(object)ep.platform != (Object)null) { ((Component)ep.platform).gameObject.SetActive(false); } } private static void DriveExtractionState(ExtractionPoint ep, State state) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003e: 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) try { PhotonView component = ((Component)ep).GetComponent<PhotonView>(); if ((Object)(object)component != (Object)null && SemiFunc.IsMultiplayer()) { component.RPC("StateSetRPC", (RpcTarget)0, new object[1] { state }); } else { ep.StateSetRPC(state, default(PhotonMessageInfo)); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Restore: extraction StateSet failed: " + ex.Message)); } } public static void ReapplyRoundAccounting(WorldSnapshot snap, bool log = true) { RoundDirector instance = RoundDirector.instance; if (!((Object)(object)instance == (Object)null) && snap.round != null && snap.round.present) { instance.haulGoal = snap.round.haulGoal; instance.haulGoalMax = snap.round.haulGoalMax; instance.extractionPoints = snap.round.extractionPoints; instance.extractionPointSurplus = snap.round.extractionPointSurplus; instance.extractionPointsCompleted = snap.round.extractionPointsCompleted; instance.extractionHaulGoal = snap.round.extractionHaulGoal; instance.allExtractionPointsCompleted = snap.round.allExtractionPointsCompleted; if (log) { Plugin.Log.LogInfo((object)$"Restore: re-applied round accounting (goal {instance.haulGoal}, completed {instance.extractionPointsCompleted})."); } } } public static void RestoreHinges(WorldSnapshot snap) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0043: 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_0124: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_0137: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Unknown result type (might be due to invalid IL or missing references) //IL_0159: 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_00a9: 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_00c3: Unknown result type (might be due to invalid IL or missing references) List<PhysGrabHinge> list = Object.FindObjectsOfType<PhysGrabHinge>().ToList(); HashSet<PhysGrabHinge> hashSet = new HashSet<PhysGrabHinge>(); int num = 0; HeldHinges.Clear(); foreach (Hin