From 0c15459302c5180dde6a3d47dcce361c8e1d4958 Mon Sep 17 00:00:00 2001 From: MrMaho Date: Sun, 27 Sep 2026 09:53:40 +0200 Subject: [PATCH] All mod messages go to the MSCO console (F7) instead of the MSCLoader console; still written to output_log.txt Co-Authored-By: Claude Opus 5.5 --- DiscordPresence.cs | 2 +- DoorSync.cs | 6 +++--- FsmSync.cs | 20 +++++++++---------- InputLock.cs | 6 +++--- JoinLinks.cs | 4 ++-- MSCO.cs | 2 +- MSCO.csproj | 1 + MenuHelper.cs | 4 ++-- MscoLog.cs | 46 +++++++++++++++++++++++++++++++++++++++++++ MultiplayerManager.cs | 30 ++++++++++++++-------------- ObjectSync.cs | 6 +++--- Overlays.cs | 1 + SafetyPatches.cs | 6 +++--- SaveSync.cs | 6 +++--- SteamLobby.cs | 2 +- VehicleStateSync.cs | 2 +- WorldSync.cs | 4 ++-- 17 files changed, 98 insertions(+), 50 deletions(-) create mode 100644 MscoLog.cs diff --git a/DiscordPresence.cs b/DiscordPresence.cs index 36b7909..220a10f 100644 --- a/DiscordPresence.cs +++ b/DiscordPresence.cs @@ -45,7 +45,7 @@ namespace MSCO if (running) return; if (!Configured) { - ModConsole.Print("[MSCO] Discord Rich Presence: no application id set (DiscordPresence.ApplicationId)"); + MscoLog.Info("[MSCO] Discord Rich Presence: no application id set (DiscordPresence.ApplicationId)"); return; } if (Application.platform != RuntimePlatform.WindowsPlayer && Application.platform != RuntimePlatform.WindowsEditor) return; diff --git a/DoorSync.cs b/DoorSync.cs index 00fdf1d..b331a18 100644 --- a/DoorSync.cs +++ b/DoorSync.cs @@ -62,7 +62,7 @@ namespace MSCO t.QuietUntil = now + Cooldown; string key = t.Key; send(MsgType.DoorState, NetSession.Build(w => { w.Write(key); w.Write(state); })); - if (FsmSync.Debug) ModConsole.Print("[MSCO] >> DOOR " + key + " -> " + state); + if (FsmSync.Debug) MscoLog.Info("[MSCO] >> DOOR " + key + " -> " + state); } } @@ -80,7 +80,7 @@ namespace MSCO Scan(); // Objekt vielleicht erst seit kurzem aktiv if (!byKey.TryGetValue(key, out t) || t.Fsm == null) { - if (FsmSync.Debug) ModConsole.Warning("[MSCO] << DOOR not found: " + key); + if (FsmSync.Debug) MscoLog.Warning("[MSCO] << DOOR not found: " + key); return; } } @@ -91,7 +91,7 @@ namespace MSCO t.QuietUntil = Time.realtimeSinceStartup + Cooldown; // Folge-Zustände nicht zurückschicken t.LastState = state; FsmSync.SwitchStateSilently(t.Fsm, target); - if (FsmSync.Debug) ModConsole.Print("[MSCO] << DOOR " + key + " -> " + state); + if (FsmSync.Debug) MscoLog.Info("[MSCO] << DOOR " + key + " -> " + state); } void Scan() diff --git a/FsmSync.cs b/FsmSync.cs index 352045a..0f40cc0 100644 --- a/FsmSync.cs +++ b/FsmSync.cs @@ -84,11 +84,11 @@ namespace MSCO // Klick-Interaktionen (Lichtschalter, Knöpfe, Radio ...) über Zustandswechsel erkennen MethodInfo switchPrefix = typeof(FsmSync).GetMethod("SwitchStatePrefix", BindingFlags.NonPublic | BindingFlags.Static); harmony.Patch(switchState, new HarmonyMethod(switchPrefix), null); - ModConsole.Print("[MSCO] FSM sync ready"); + MscoLog.Info("[MSCO] FSM sync ready"); } catch (Exception e) { - ModConsole.Error("[MSCO] FSM sync patch failed: " + e); + MscoLog.Error("[MSCO] FSM sync patch failed: " + e); } } @@ -163,7 +163,7 @@ namespace MSCO } catch (Exception e) { - ModConsole.Error("[MSCO] Click capture error: " + e.Message); + MscoLog.Error("[MSCO] Click capture error: " + e.Message); } } @@ -212,7 +212,7 @@ namespace MSCO Position = go.transform.position }; Outbox.Add(msg); - if (Debug) ModConsole.Print("[MSCO] >> CLICK " + msg.Path + " [" + msg.FsmName + " -> " + msg.State + "]"); + if (Debug) MscoLog.Info("[MSCO] >> CLICK " + msg.Path + " [" + msg.FsmName + " -> " + msg.State + "]"); } /// Prüft der Zustand nur Maus/Tasten/Raycast (z.B. "Maus zeigt auf den Griff")? @@ -262,11 +262,11 @@ namespace MSCO Position = go.transform.position }; Outbox.Add(msg); - if (Debug) ModConsole.Print("[MSCO] >> " + msg.EventName + " " + msg.Path + " [" + msg.FsmName + " -> " + msg.State + "]"); + if (Debug) MscoLog.Info("[MSCO] >> " + msg.EventName + " " + msg.Path + " [" + msg.FsmName + " -> " + msg.State + "]"); } catch (Exception e) { - ModConsole.Error("[MSCO] FSM capture error: " + e.Message); + MscoLog.Error("[MSCO] FSM capture error: " + e.Message); } } @@ -288,17 +288,17 @@ namespace MSCO PlayMakerFSM fsm = Resolve(msg); if (fsm == null) { - if (Debug) ModConsole.Warning("[MSCO] << not found: " + msg.Path + " [" + msg.FsmName + "]"); + if (Debug) MscoLog.Warning("[MSCO] << not found: " + msg.Path + " [" + msg.FsmName + "]"); return; } FsmState state = fsm.Fsm.GetState(msg.State); if (state == null || switchState == null) return; - if (Debug) ModConsole.Print("[MSCO] << " + msg.EventName + " " + msg.Path + " [" + msg.FsmName + " -> " + msg.State + "]"); + if (Debug) MscoLog.Info("[MSCO] << " + msg.EventName + " " + msg.Path + " [" + msg.FsmName + " -> " + msg.State + "]"); applying++; lastApplyTime = Time.realtimeSinceStartup; try { switchState.Invoke(fsm.Fsm, new object[] { state }); } - catch (Exception e) { ModConsole.Error("[MSCO] FSM apply error: " + e.Message); } + catch (Exception e) { MscoLog.Error("[MSCO] FSM apply error: " + e.Message); } finally { applying--; } } @@ -309,7 +309,7 @@ namespace MSCO applying++; lastApplyTime = Time.realtimeSinceStartup; try { switchState.Invoke(fsm.Fsm, new object[] { state }); } - catch (Exception e) { ModConsole.Error("[MSCO] FSM switch error: " + e.Message); } + catch (Exception e) { MscoLog.Error("[MSCO] FSM switch error: " + e.Message); } finally { applying--; } } diff --git a/InputLock.cs b/InputLock.cs index c0ecc43..d7f39d4 100644 --- a/InputLock.cs +++ b/InputLock.cs @@ -103,7 +103,7 @@ namespace MSCO } catch (Exception e) { - ModConsole.Warning("[MSCO] Could not patch cursor actions: " + e.Message); + MscoLog.Warning("[MSCO] Could not patch cursor actions: " + e.Message); } } @@ -126,11 +126,11 @@ namespace MSCO if (keyQuery && m.ReturnType == typeof(bool)) { harmony.Patch(m, boolPrefix, null); count++; } else if (axisQuery && m.ReturnType == typeof(float)) { harmony.Patch(m, floatPrefix, null); count++; } } - ModConsole.Print("[MSCO] Game input is paused while typing (" + count + " cInput queries)"); + MscoLog.Info("[MSCO] Game input is paused while typing (" + count + " cInput queries)"); } catch (Exception e) { - ModConsole.Warning("[MSCO] Could not pause game input while typing: " + e.Message); + MscoLog.Warning("[MSCO] Could not pause game input while typing: " + e.Message); } } diff --git a/JoinLinks.cs b/JoinLinks.cs index ee472f3..c8078b6 100644 --- a/JoinLinks.cs +++ b/JoinLinks.cs @@ -26,7 +26,7 @@ namespace MSCO string steamExe = SteamExePath(); if (steamExe == null) { - ModConsole.Warning("[MSCO] Steam not found - JOIN links from the website are not available."); + MscoLog.Warning("[MSCO] Steam not found - JOIN links from the website are not available."); return; } string command = "\"" + steamExe + "\" -applaunch " + MySummerCarAppId + " " + Argument + " \"%1\""; @@ -41,7 +41,7 @@ namespace MSCO } catch (Exception e) { - ModConsole.Warning("[MSCO] Could not register msco:// links: " + e.Message); + MscoLog.Warning("[MSCO] Could not register msco:// links: " + e.Message); } } diff --git a/MSCO.cs b/MSCO.cs index b4a3dc1..86f34c8 100644 --- a/MSCO.cs +++ b/MSCO.cs @@ -47,7 +47,7 @@ namespace MSCO private void Mod_OnMenuLoad() { - ModConsole.Print("Welcome to MSCO by mbm.wtf"); + MscoLog.Info("Welcome to MSCO by mbm.wtf"); // Netzwerk + Lobby leben in einem eigenen Objekt, das Szenenwechsel überlebt MultiplayerManager.Ensure(); if (!commandAdded) diff --git a/MSCO.csproj b/MSCO.csproj index 91eec66..3e4c1b7 100644 --- a/MSCO.csproj +++ b/MSCO.csproj @@ -75,6 +75,7 @@ + diff --git a/MenuHelper.cs b/MenuHelper.cs index 383169e..c57bf1e 100644 --- a/MenuHelper.cs +++ b/MenuHelper.cs @@ -57,12 +57,12 @@ namespace MSCO FsmState state; if (switchState != null && FindContinueLoadState(out fsm, out state)) { - ModConsole.Print("[MSCO] Loading via CONTINUE (" + fsm.name + " / " + state.Name + ")"); + MscoLog.Info("[MSCO] Loading via CONTINUE (" + fsm.name + " / " + state.Name + ")"); switchState.Invoke(fsm.Fsm, new object[] { state }); } else { - ModConsole.Warning("[MSCO] CONTINUE button not found, loading GAME directly"); + MscoLog.Warning("[MSCO] CONTINUE button not found, loading GAME directly"); Application.LoadLevel(GameScene); } } diff --git a/MscoLog.cs b/MscoLog.cs new file mode 100644 index 0000000..db8f330 --- /dev/null +++ b/MscoLog.cs @@ -0,0 +1,46 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace MSCO +{ + /// + /// Zentrales Log der Mod: alle Meldungen landen in der MSCO-Konsole (F7), nicht in der MSCLoader-Konsole. + /// Zusätzlich gehen sie per Debug.Log in die output_log.txt des Spiels (zur Fehlersuche), ohne dass sich die + /// MSCLoader-Konsole öffnet. Thread-sicher: Meldungen aus Hintergrund-Threads werden im Hauptthread übernommen. + /// + public static class MscoLog + { + const string Prefix = "[MSCO] "; + static readonly Queue pending = new Queue(); + static readonly object gate = new object(); + + public static void Info(string text) { Add(text, ""); } + public static void Warning(string text) { Add(text, "! "); } + public static void Error(string text) { Add(text, "ERROR "); } + + static void Add(string text, string marker) + { + string line = Strip(text ?? ""); + Debug.Log(Prefix + line); // output_log.txt + lock (gate) + { + pending.Enqueue(marker + line); + while (pending.Count > 500) pending.Dequeue(); // falls die Konsole (noch) nicht abgeholt hat + } + } + + /// Neue Meldungen abholen (Hauptthread, vom MultiplayerManager jedes Frame). + public static void Drain(System.Action sink) + { + lock (gate) + { + while (pending.Count > 0) sink(pending.Dequeue()); + } + } + + static string Strip(string text) + { + return text.StartsWith(Prefix) ? text.Substring(Prefix.Length) : text; + } + } +} diff --git a/MultiplayerManager.cs b/MultiplayerManager.cs index 8f5f490..0dbba41 100644 --- a/MultiplayerManager.cs +++ b/MultiplayerManager.cs @@ -498,7 +498,7 @@ namespace MSCO { // Meldungen des Discord-Threads (verbunden, Fehler ...) in die Konsole string discordLog; - while ((discordLog = discord.TakeLog()) != null) ModConsole.Print("[MSCO] " + discordLog); + while ((discordLog = discord.TakeLog()) != null) MscoLog.Info("[MSCO] " + discordLog); if (Time.realtimeSinceStartup < nextPresenceUpdate) return; nextPresenceUpdate = Time.realtimeSinceStartup + 2f; @@ -652,22 +652,22 @@ namespace MSCO { if (!MenuHelper.InGame) { - ModConsole.Print("[MSCO] You can only save while in the game."); + MscoLog.Info("[MSCO] You can only save while in the game."); return; } if (session != null && dedicated) { - ModConsole.Print("[MSCO] The server saves the world automatically."); + MscoLog.Info("[MSCO] The server saves the world automatically."); return; } if (session != null && !session.IsHost) { - ModConsole.Print("[MSCO] Only the host can save the game."); + MscoLog.Info("[MSCO] Only the host can save the game."); return; } savingMessageUntil = Time.realtimeSinceStartup + 2.5f; // "SAVING GAME..." in der Bildschirmmitte PlayMakerFSM.BroadcastEvent("SAVEGAME"); - ModConsole.Print("[MSCO] Game saved."); + MscoLog.Info("[MSCO] Game saved."); } // ---------------------------------------------------------------- Update @@ -679,6 +679,7 @@ namespace MSCO // erst nach MINIMIZE wieder. Über der eingeklappten Leiste ebenfalls nicht (sonst öffnet sich z.B. der Changelog). MenuInputBlocker.SetBlocked(MenuHelper.InMenu && (lobby.IsOpen || lobby.ContainsMouse() || overlays.AnyOpen)); UpdateOverlayKeys(); + MscoLog.Drain(overlays.Log); // alle Mod-Meldungen in die MSCO-Konsole (F7) UpdatePresence(); steam.Tick(); @@ -1156,7 +1157,7 @@ namespace MSCO foreach (byte[] msg in fileMessages) session.SendTo(p.Id, MsgType.SaveFile, msg); session.SendTo(p.Id, MsgType.StartGame, NetSession.Build(w => w.Write(lateJoin))); } - ModConsole.Print("[MSCO] Sent save (" + files.Count + " files) to " + targets.Count + " player(s)"); + MscoLog.Info("[MSCO] Sent save (" + files.Count + " files) to " + targets.Count + " player(s)"); } void OnLevelWasLoaded(int level) @@ -1205,7 +1206,7 @@ namespace MSCO void TryRestoreBackup() { try { SaveSync.RestoreBackup(); } - catch (System.Exception e) { ModConsole.Error("[MSCO] Could not restore own save: " + e.Message); } + catch (System.Exception e) { MscoLog.Error("[MSCO] Could not restore own save: " + e.Message); } } // ---------------------------------------------------------------- Im Spiel @@ -1221,7 +1222,7 @@ namespace MSCO localPlayer = player.transform; playerCamera = FindPlayerCamera(player); objects.Head = playerCamera != null ? playerCamera.transform : null; - ModConsole.Print("[MSCO] Player camera: " + (playerCamera != null ? playerCamera.name : "none")); + MscoLog.Info("[MSCO] Player camera: " + (playerCamera != null ? playerCamera.name : "none")); } if (teleportToHost) TryTeleportToHost(); @@ -1326,8 +1327,8 @@ namespace MSCO float now = Time.realtimeSinceStartup, last; if (lastErrorReport.TryGetValue(what, out last) && now - last < 30f) return; lastErrorReport[what] = now; - ModConsole.Error("[MSCO] " + what + " failed: " + e); - overlays.Log("Error in " + what + ": " + e.Message); + MscoLog.Error(what + " failed: " + e.Message); + Debug.Log("[MSCO] " + what + " failed: " + e); // voller Stacktrace nur in output_log.txt } void FixedUpdate() @@ -1382,7 +1383,7 @@ namespace MSCO RaycastHit hit; if (Physics.Raycast(spawn + Vector3.up * 2f, Vector3.down, out hit, 6f)) spawn.y = Mathf.Max(spawn.y, hit.point.y); localPlayer.position = spawn + Vector3.up * 0.2f; - ModConsole.Print("[MSCO] Back at your last position " + spawn.ToString("F0")); + MscoLog.Info("[MSCO] Back at your last position " + spawn.ToString("F0")); } } @@ -1426,7 +1427,7 @@ namespace MSCO // Fahrzeugzustand (Tank, Öl, Schrauben ...) if (r.BaseStream.Length - r.BaseStream.Position >= 4) vehicleState.ApplySnapshot(r); - ModConsole.Print("[MSCO] World synced: " + applied + "/" + objectCount + " objects, " + eventCount + " part/bolt actions"); + MscoLog.Info("[MSCO] World synced: " + applied + "/" + objectCount + " objects, " + eventCount + " part/bolt actions"); SetStatus("Connected"); } @@ -1500,7 +1501,7 @@ namespace MSCO { // Platzhalter-Figur: Kapsel mit Kopf und Hand p.Avatar = RemotePlayer.Create("MSCO_Player_" + from, PlayerColor(from), state, from); - ModConsole.Print("[MSCO] " + p.Name + " is in the game"); + MscoLog.Info("[MSCO] " + p.Name + " is in the game"); } p.Avatar.SetTarget(state); } @@ -1617,8 +1618,7 @@ namespace MSCO void SetStatus(string text) { status = text; - ModConsole.Print("[MSCO] " + text); - if (!string.IsNullOrEmpty(text)) overlays.Log(text); + if (!string.IsNullOrEmpty(text)) MscoLog.Info(text); } } } diff --git a/ObjectSync.cs b/ObjectSync.cs index 817bc29..523dc9b 100644 --- a/ObjectSync.cs +++ b/ObjectSync.cs @@ -563,7 +563,7 @@ namespace MSCO } b.NoMotionClaimUntil = Time.time + HandoverCooldown; if (b.RunawayCount <= 1 || stuck) - ModConsole.Warning("[MSCO] Stopped " + b.RootName + " - it was flung away by the physics" + (stuck ? " (stuck, lifted out)" : "")); + MscoLog.Warning("[MSCO] Stopped " + b.RootName + " - it was flung away by the physics" + (stuck ? " (stuck, lifted out)" : "")); } /// @@ -714,7 +714,7 @@ namespace MSCO if (best != null) { pickedObject = best.FsmVariables.FindFsmGameObject("PickedObject"); - ModConsole.Print("[MSCO] Found pickup FSM on " + best.gameObject.name + + MscoLog.Info("[MSCO] Found pickup FSM on " + best.gameObject.name + (best.gameObject.activeInHierarchy ? "" : " (inactive)")); } } @@ -826,7 +826,7 @@ namespace MSCO byRb[c.Key] = b; added++; } - if (added > 0) ModConsole.Print("[MSCO] Syncing " + byId.Count + " objects (+" + added + ")"); + if (added > 0) MscoLog.Info("[MSCO] Syncing " + byId.Count + " objects (+" + added + ")"); } static bool ShouldSync(Rigidbody rb, out bool isVehicle) diff --git a/Overlays.cs b/Overlays.cs index 10c9d9e..88951e3 100644 --- a/Overlays.cs +++ b/Overlays.cs @@ -359,6 +359,7 @@ namespace MSCO field = UiTheme.Field(14); logText = UiTheme.Label(12, UiTheme.Text); logText.wordWrap = true; + logText.richText = true; // Warnungen/Fehler farbig markiert (MscoLog) logText.margin = new RectOffset(0, 0, 0, 0); caption = UiTheme.Label(11, UiTheme.Muted, FontStyle.Bold); title = UiTheme.Label(22, UiTheme.Text, FontStyle.Bold); diff --git a/SafetyPatches.cs b/SafetyPatches.cs index 4b84b07..caeefa6 100644 --- a/SafetyPatches.cs +++ b/SafetyPatches.cs @@ -32,7 +32,7 @@ namespace MSCO if (action != null) break; } MethodInfo onEnter = action != null ? action.GetMethod("OnEnter", BindingFlags.Instance | BindingFlags.Public) : null; - if (onEnter == null) { ModConsole.Warning("[MSCO] SetParent action not found - hierarchy guard disabled"); return; } + if (onEnter == null) { MscoLog.Warning("[MSCO] SetParent action not found - hierarchy guard disabled"); return; } gameObjectField = action.GetField("gameObject"); parentField = action.GetField("parent"); @@ -41,7 +41,7 @@ namespace MSCO } catch (Exception e) { - ModConsole.Warning("[MSCO] Could not install hierarchy guard: " + e.Message); + MscoLog.Warning("[MSCO] Could not install hierarchy guard: " + e.Message); } } @@ -60,7 +60,7 @@ namespace MSCO blocked++; if (blocked <= 20) - ModConsole.Warning("[MSCO] Blocked SetParent that would crash the game: " + go.name + " -> " + parent.Value.name + + MscoLog.Warning("[MSCO] Blocked SetParent that would crash the game: " + go.name + " -> " + parent.Value.name + " (" + __instance.Fsm.Name + ")"); __instance.Finish(); return false; diff --git a/SaveSync.cs b/SaveSync.cs index 94eace2..135f644 100644 --- a/SaveSync.cs +++ b/SaveSync.cs @@ -48,7 +48,7 @@ namespace MSCO if (File.Exists(path)) File.Copy(path, Path.Combine(BackupDir, name), true); } File.WriteAllText(Marker, "MSCO backup of the client's own save. Restored automatically."); - ModConsole.Print("[MSCO] Own save backed up to " + BackupDir); + MscoLog.Info("[MSCO] Own save backed up to " + BackupDir); } DeleteSaveFiles(); } @@ -58,7 +58,7 @@ namespace MSCO // Nur bekannte Dateinamen erlauben, damit der Host nichts anderes überschreiben kann if (System.Array.IndexOf(Files, name) < 0) { - ModConsole.Warning("[MSCO] Ignored unexpected save file: " + name); + MscoLog.Warning("[MSCO] Ignored unexpected save file: " + name); return; } File.WriteAllBytes(Path.Combine(SaveDir, name), data); @@ -74,7 +74,7 @@ namespace MSCO if (File.Exists(backup)) File.Copy(backup, Path.Combine(SaveDir, name), true); } Directory.Delete(BackupDir, true); - ModConsole.Print("[MSCO] Own save restored."); + MscoLog.Info("[MSCO] Own save restored."); } static void DeleteSaveFiles() diff --git a/SteamLobby.cs b/SteamLobby.cs index 202ee01..7e1fe46 100644 --- a/SteamLobby.cs +++ b/SteamLobby.cs @@ -107,7 +107,7 @@ namespace MSCO } catch (Exception e) { - ModConsole.Error("[MSCO] Steam init failed: " + e.Message); + MscoLog.Error("[MSCO] Steam init failed: " + e.Message); } return initialized; } diff --git a/VehicleStateSync.cs b/VehicleStateSync.cs index ac81ab9..debde65 100644 --- a/VehicleStateSync.cs +++ b/VehicleStateSync.cs @@ -144,7 +144,7 @@ namespace MSCO foreach (FsmInt n in fv.IntVariables) Register(prefix, root, n, 2); foreach (FsmBool b in fv.BoolVariables) Register(prefix, root, b, 3); } - if (vars.Count != before) ModConsole.Print("[MSCO] Vehicle state: " + vars.Count + " values synced"); + if (vars.Count != before) MscoLog.Info("[MSCO] Vehicle state: " + vars.Count + " values synced"); } void Register(string prefix, string root, NamedVariable variable, byte type) diff --git a/WorldSync.cs b/WorldSync.cs index 37e8c43..44731f9 100644 --- a/WorldSync.cs +++ b/WorldSync.cs @@ -175,9 +175,9 @@ namespace MSCO } if (tracked.Count == 0) return; // Welt noch nicht fertig geladen - später nochmal discovered = true; - ModConsole.Print("[MSCO] Time/weather sync: " + tracked.Count + " FSMs"); + MscoLog.Info("[MSCO] Time/weather sync: " + tracked.Count + " FSMs"); if (FsmSync.Debug) - foreach (string key in tracked.Keys) ModConsole.Print("[MSCO] " + key); + foreach (string key in tracked.Keys) MscoLog.Info("[MSCO] " + key); } } }