MiscTweaks: cancellable autoload of the most recent save
From the main menu, a short countdown loads the newest save. Any input cancels so a misclick does not dump you into a session.
This commit is contained in:
parent
9f2097e579
commit
16d118c4a9
6 changed files with 283 additions and 0 deletions
|
|
@ -21,6 +21,7 @@ I originally planned on releasing individual mods, but considering my workflow o
|
||||||
| Mesh LOD | Adds level-of-detail to rolling stock: distant cars progressively shed detail and finally collapse to a cheap proxy box, cutting triangle count on large saves. |
|
| Mesh LOD | Adds level-of-detail to rolling stock: distant cars progressively shed detail and finally collapse to a cheap proxy box, cutting triangle count on large saves. |
|
||||||
| Base Game Performance | Smooths Unity's incremental garbage collector and Nature Renderer grass streaming to reduce camera-motion hitches without lowering visual quality. |
|
| Base Game Performance | Smooths Unity's incremental garbage collector and Nature Renderer grass streaming to reduce camera-motion hitches without lowering visual quality. |
|
||||||
| Profiler | Unified in-game performance overlay with hitch attribution captures. Console: `/rpf overlay`, `/s3bench` |
|
| Profiler | Unified in-game performance overlay with hitch attribution captures. Console: `/rpf overlay`, `/s3bench` |
|
||||||
|
| Misc Tweaks | Small QoL: cancellable autoload of the most recent save from the main menu. |
|
||||||
|
|
||||||
All modules are disabled by default; enable them per-module from the S³ settings page. A game restart is required for enable/disable to take effect. More modules will follow; S³ is designed to grow.
|
All modules are disabled by default; enable them per-module from the S³ settings page. A game restart is required for enable/disable to take effect. More modules will follow; S³ is designed to grow.
|
||||||
|
|
||||||
|
|
@ -197,6 +198,13 @@ Toggle the overlay with `/rpf overlay`, or from the Profiler settings page. Run
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Misc Tweaks
|
||||||
|
|
||||||
|
After a short cancellable countdown on the main menu, load the most recent save.
|
||||||
|
Any key cancels. Enable it from the S³ settings page.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Migrating from the standalone mods
|
## Migrating from the standalone mods
|
||||||
|
|
||||||
S³ replaces the separate **Physics Optimizer** (`RailroaderPhysicsOverhaul`) and
|
S³ replaces the separate **Physics Optimizer** (`RailroaderPhysicsOverhaul`) and
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ public static class Main
|
||||||
_registry.Register(new Modules.MeshLod.MeshLodModule());
|
_registry.Register(new Modules.MeshLod.MeshLodModule());
|
||||||
_registry.Register(new Modules.BaseGamePerf.BaseGamePerfModule());
|
_registry.Register(new Modules.BaseGamePerf.BaseGamePerfModule());
|
||||||
_registry.Register(new Modules.Profiler.ProfilerModule());
|
_registry.Register(new Modules.Profiler.ProfilerModule());
|
||||||
|
_registry.Register(new Modules.MiscTweaks.MiscTweaksModule());
|
||||||
_registry.Register(new Modules.Popout.PopoutModule());
|
_registry.Register(new Modules.Popout.PopoutModule());
|
||||||
|
|
||||||
_registry.EnableConfigured();
|
_registry.EnableConfigured();
|
||||||
|
|
|
||||||
44
src/Modules/MiscTweaks/MiscTweaksModule.cs
Normal file
44
src/Modules/MiscTweaks/MiscTweaksModule.cs
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
using S3.Core;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace S3.Modules.MiscTweaks;
|
||||||
|
|
||||||
|
public sealed class MiscTweaksModule : IModule
|
||||||
|
{
|
||||||
|
const string SettingsFile = "S3.misctweaks.json";
|
||||||
|
|
||||||
|
public static MiscTweaksSettings Settings { get; private set; } = new();
|
||||||
|
|
||||||
|
static GameObject? _host;
|
||||||
|
|
||||||
|
public MiscTweaksModule() =>
|
||||||
|
Settings = SettingsStore.Load<MiscTweaksSettings>(SettingsFile);
|
||||||
|
|
||||||
|
public string Id => "misctweaks";
|
||||||
|
public string DisplayName => "Misc. Tweaks";
|
||||||
|
public string Description =>
|
||||||
|
"Small quality-of-life changes that do not belong to a larger module.";
|
||||||
|
|
||||||
|
public bool Enabled
|
||||||
|
{
|
||||||
|
get => Settings.enabled;
|
||||||
|
set => Settings.enabled = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnEnable()
|
||||||
|
{
|
||||||
|
_host = new GameObject("[S3] MiscTweaksHost");
|
||||||
|
Object.DontDestroyOnLoad(_host);
|
||||||
|
_host.AddComponent<RecentSaveAutoLoader>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnDisable()
|
||||||
|
{
|
||||||
|
if (_host != null) Object.Destroy(_host);
|
||||||
|
_host = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SaveSettings() => Persist();
|
||||||
|
internal static void Persist() => SettingsStore.Save(SettingsFile, Settings);
|
||||||
|
public void DrawSettings() => MiscTweaksSettingsUI.Draw();
|
||||||
|
}
|
||||||
11
src/Modules/MiscTweaks/MiscTweaksSettings.cs
Normal file
11
src/Modules/MiscTweaks/MiscTweaksSettings.cs
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace S3.Modules.MiscTweaks;
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public class MiscTweaksSettings
|
||||||
|
{
|
||||||
|
public bool enabled = false;
|
||||||
|
public bool autoLoadMostRecent = false;
|
||||||
|
public float autoLoadCountdownSeconds = 3f;
|
||||||
|
}
|
||||||
42
src/Modules/MiscTweaks/MiscTweaksSettingsUI.cs
Normal file
42
src/Modules/MiscTweaks/MiscTweaksSettingsUI.cs
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace S3.Modules.MiscTweaks;
|
||||||
|
|
||||||
|
static class MiscTweaksSettingsUI
|
||||||
|
{
|
||||||
|
public static void Draw()
|
||||||
|
{
|
||||||
|
MiscTweaksSettings settings = MiscTweaksModule.Settings;
|
||||||
|
bool changed = false;
|
||||||
|
|
||||||
|
GUILayout.Label("<b>Recent save autoload</b>");
|
||||||
|
GUILayout.Space(4f);
|
||||||
|
bool enabled = GUILayout.Toggle(
|
||||||
|
settings.autoLoadMostRecent,
|
||||||
|
" Automatically load the most recently modified save from the main menu");
|
||||||
|
if (enabled != settings.autoLoadMostRecent)
|
||||||
|
{
|
||||||
|
settings.autoLoadMostRecent = enabled;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
GUILayout.BeginHorizontal();
|
||||||
|
GUILayout.Label("Countdown (seconds)", GUILayout.Width(175f));
|
||||||
|
float countdown = GUILayout.HorizontalSlider(
|
||||||
|
settings.autoLoadCountdownSeconds, 1f, 15f, GUILayout.Width(180f));
|
||||||
|
GUILayout.Label(settings.autoLoadCountdownSeconds.ToString("0"), GUILayout.Width(48f));
|
||||||
|
GUILayout.EndHorizontal();
|
||||||
|
countdown = Mathf.Round(countdown);
|
||||||
|
if (Mathf.Abs(countdown - settings.autoLoadCountdownSeconds) >= 0.5f)
|
||||||
|
{
|
||||||
|
settings.autoLoadCountdownSeconds = countdown;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
GUILayout.Label(
|
||||||
|
" Press any keyboard, mouse, or controller button during the countdown to cancel.");
|
||||||
|
|
||||||
|
if (changed)
|
||||||
|
MiscTweaksModule.Persist();
|
||||||
|
}
|
||||||
|
}
|
||||||
177
src/Modules/MiscTweaks/RecentSaveAutoLoader.cs
Normal file
177
src/Modules/MiscTweaks/RecentSaveAutoLoader.cs
Normal file
|
|
@ -0,0 +1,177 @@
|
||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
using Game;
|
||||||
|
using Game.Persistence;
|
||||||
|
using Game.State;
|
||||||
|
using HarmonyLib;
|
||||||
|
using S3.Core;
|
||||||
|
using UI.Menu;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace S3.Modules.MiscTweaks;
|
||||||
|
|
||||||
|
sealed class RecentSaveAutoLoader : MonoBehaviour
|
||||||
|
{
|
||||||
|
static readonly MethodInfo? StartSingleplayer =
|
||||||
|
AccessTools.Method(
|
||||||
|
typeof(MenuManager),
|
||||||
|
"StartGameSinglePlayer",
|
||||||
|
new[] { typeof(GameSetup) });
|
||||||
|
|
||||||
|
bool _wasMainMenu;
|
||||||
|
bool _attempted;
|
||||||
|
bool _counting;
|
||||||
|
bool _cancelled;
|
||||||
|
float _loadAt;
|
||||||
|
float _acceptCancelAt;
|
||||||
|
string _saveName = "";
|
||||||
|
DateTime _saveDate;
|
||||||
|
string _status = "";
|
||||||
|
|
||||||
|
void Update()
|
||||||
|
{
|
||||||
|
bool mainMenu = false;
|
||||||
|
try { mainMenu = SceneDescriptor.MainMenu.IsLoaded; }
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
if (!mainMenu)
|
||||||
|
{
|
||||||
|
_wasMainMenu = false;
|
||||||
|
_attempted = false;
|
||||||
|
_counting = false;
|
||||||
|
_cancelled = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_wasMainMenu)
|
||||||
|
{
|
||||||
|
_wasMainMenu = true;
|
||||||
|
_attempted = false;
|
||||||
|
_counting = false;
|
||||||
|
_cancelled = false;
|
||||||
|
_status = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
MiscTweaksSettings settings = MiscTweaksModule.Settings;
|
||||||
|
if (!settings.autoLoadMostRecent || _attempted) return;
|
||||||
|
if (!_counting)
|
||||||
|
{
|
||||||
|
TryStartCountdown(settings);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Time.unscaledTime >= _acceptCancelAt && AnyButtonDown())
|
||||||
|
{
|
||||||
|
_counting = false;
|
||||||
|
_cancelled = true;
|
||||||
|
_attempted = true;
|
||||||
|
_status = "Recent-save autoload cancelled.";
|
||||||
|
Log.Info("[misc] " + _status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Time.unscaledTime < _loadAt) return;
|
||||||
|
_counting = false;
|
||||||
|
_attempted = true;
|
||||||
|
LoadRecentSave();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TryStartCountdown(MiscTweaksSettings settings)
|
||||||
|
{
|
||||||
|
if (StartSingleplayer == null)
|
||||||
|
{
|
||||||
|
_attempted = true;
|
||||||
|
_status = "Recent-save autoload unavailable: game launch method not found.";
|
||||||
|
Log.Warn("[misc] " + _status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (FindObjectOfType<MenuManager>() == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var saves = WorldStore.FindSaveInfos();
|
||||||
|
if (saves == null || saves.Count == 0)
|
||||||
|
{
|
||||||
|
_attempted = true;
|
||||||
|
_status = "No saves found.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
WorldStore.SaveInfo recent = saves[0];
|
||||||
|
_saveName = recent.Name;
|
||||||
|
_saveDate = recent.Date;
|
||||||
|
float seconds = Mathf.Clamp(settings.autoLoadCountdownSeconds, 1f, 15f);
|
||||||
|
_loadAt = Time.unscaledTime + seconds;
|
||||||
|
_acceptCancelAt = Time.unscaledTime + 0.15f;
|
||||||
|
_counting = true;
|
||||||
|
_status = $"Loading {_saveName} in {seconds:0} seconds...";
|
||||||
|
Log.Info($"[misc] {_status}");
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoadRecentSave()
|
||||||
|
{
|
||||||
|
MenuManager? manager = FindObjectOfType<MenuManager>();
|
||||||
|
if (manager == null || StartSingleplayer == null)
|
||||||
|
{
|
||||||
|
_status = "Recent-save autoload cancelled: main menu is no longer ready.";
|
||||||
|
Log.Warn("[misc] " + _status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_status = "Loading " + _saveName + "...";
|
||||||
|
Log.Info("[misc] " + _status);
|
||||||
|
StartSingleplayer.Invoke(manager, new object[] { new GameSetup(_saveName) });
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_status = "Recent-save autoload failed: " + (e.InnerException?.Message ?? e.Message);
|
||||||
|
Log.Error("[misc] " + _status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool AnyButtonDown()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (Input.anyKeyDown) return true;
|
||||||
|
for (int i = 0; i < 7; i++)
|
||||||
|
if (Input.GetMouseButtonDown(i)) return true;
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnGUI()
|
||||||
|
{
|
||||||
|
if (!_counting || _cancelled) return;
|
||||||
|
float remaining = Mathf.Max(0f, _loadAt - Time.unscaledTime);
|
||||||
|
const float width = 520f;
|
||||||
|
const float height = 104f;
|
||||||
|
Rect box = new Rect(
|
||||||
|
(Screen.width - width) * 0.5f,
|
||||||
|
Mathf.Max(24f, Screen.height * 0.13f),
|
||||||
|
width,
|
||||||
|
height);
|
||||||
|
GUI.Box(box, "");
|
||||||
|
var title = new GUIStyle(GUI.skin.label)
|
||||||
|
{
|
||||||
|
alignment = TextAnchor.MiddleCenter,
|
||||||
|
fontSize = 20,
|
||||||
|
fontStyle = FontStyle.Bold,
|
||||||
|
};
|
||||||
|
var detail = new GUIStyle(GUI.skin.label)
|
||||||
|
{
|
||||||
|
alignment = TextAnchor.MiddleCenter,
|
||||||
|
fontSize = 14,
|
||||||
|
};
|
||||||
|
GUI.Label(
|
||||||
|
new Rect(box.x + 12f, box.y + 10f, box.width - 24f, 32f),
|
||||||
|
$"Loading most recent save in {Mathf.CeilToInt(remaining)}...",
|
||||||
|
title);
|
||||||
|
GUI.Label(
|
||||||
|
new Rect(box.x + 12f, box.y + 43f, box.width - 24f, 50f),
|
||||||
|
$"{_saveName} ({_saveDate:g})\nPress any keyboard, mouse, or controller button to cancel",
|
||||||
|
detail);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue