Files
MSCO/InputLock.cs
T
MrMahoandClaude Opus 5.5 027296d27c MSCO 2.9.1: fixes without MSCLoader, admin teleport, MSCO branding
- Own JSON reader (MiniJson) instead of Newtonsoft.Json - the server browser failed with
  "Could not load System.Runtime.Serialization" once MSCLoader was gone
- Mouse cursor visible in admin menu/chat/console (PlayerInMenu)
- Intro "Press any key" screen replaced by an MSCO loading screen; hook PlayMakerFSM.Awake too
- Join links while the game runs: picked up from MSCO\join.txt (written by the launcher)
- Admin menu: TELEPORT tab (home, shop, repair shop, inspection, villages, events, Satsuma)
- Window title/icon "MSCO - My Summer Car Online", MSCO logo in the main menu
- In-game UI restyled like the keymaster website (rounded cards, pills, glow, slim scrollbars)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 00:34:03 +02:00

167 lines
6.9 KiB
C#

using System;
using System.Collections.Generic;
using System.Reflection;
using Harmony;
using HutongGames.PlayMaker;
using UnityEngine;
namespace MSCO
{
/// <summary>
/// Solange ein Eingabefenster offen ist (F7-Konsole, Chat, Admin-Menü): Spieler bewegt sich nicht,
/// Kamera dreht nicht mit, Mauszeiger ist frei. Danach wird genau das wieder eingeschaltet,
/// was hier ausgeschaltet wurde.
/// </summary>
public static class InputLock
{
static readonly string[] ControlScripts =
{
"CharacterMotor", "FPSInputController", "MouseLook", "SimpleSmoothMouseLook", "SmoothMouseLook", "MouseLook2", "MouseLook3"
};
static readonly List<Behaviour> disabled = new List<Behaviour>();
static bool locked;
static bool previousVisible, previousInMenu;
static CursorLockMode previousLock;
static bool patched;
public static bool Locked { get { return locked; } }
public static void Set(bool on)
{
if (on) KeepCursorFree();
if (on == locked) return;
locked = on;
if (on)
{
PatchCursorActions();
previousVisible = Cursor.visible;
previousLock = Cursor.lockState;
FsmBool inMenu = PlayerInMenu();
previousInMenu = inMenu != null && inMenu.Value;
GameObject player = GameObject.Find("PLAYER");
if (player != null)
{
foreach (MonoBehaviour mb in player.GetComponentsInChildren<MonoBehaviour>())
{
if (mb == null || !mb.enabled || Array.IndexOf(ControlScripts, mb.GetType().Name) < 0) continue;
mb.enabled = false;
disabled.Add(mb);
}
}
KeepCursorFree();
}
else
{
foreach (Behaviour b in disabled) if (b != null) b.enabled = true;
disabled.Clear();
PlayMakerGUI.LockCursor = previousLock != CursorLockMode.None;
PlayMakerGUI.HideCursor = !previousVisible;
Cursor.visible = previousVisible;
Cursor.lockState = previousLock;
FsmBool inMenu = PlayerInMenu();
if (inMenu != null) inMenu.Value = previousInMenu;
}
}
/// <summary>
/// Das Spiel sperrt den Zeiger jeden Frame wieder (PlayMakerGUI.LockCursor + "SetMouseCursor"-Aktionen),
/// dadurch springt er in die Mitte. Solange offen: überall freigeben - auch aus OnGUI/LateUpdate aufrufen.
/// </summary>
public static void KeepCursorFree()
{
if (!locked) return;
PlayMakerGUI.LockCursor = false;
PlayMakerGUI.HideCursor = false;
Cursor.visible = true;
Cursor.lockState = CursorLockMode.None;
// So zeigt MSC selbst den Mauszeiger (Pausemenü, Kasse ...) - ohne MSCLoader brauchen wir das auch
FsmBool inMenu = PlayerInMenu();
if (inMenu != null) inMenu.Value = true;
}
/// <summary>Globale Spielvariable "PlayerInMenu": true = Zeiger frei, Mausblick aus.</summary>
static FsmBool PlayerInMenu()
{
try { return FsmVariables.GlobalVariables.FindFsmBool("PlayerInMenu"); }
catch { return null; }
}
/// <summary>
/// PlayMaker-Aktionen, die den Mauszeiger setzen, überspringen, solange ein Fenster offen ist. Außerdem die
/// Tastenabfragen des Spiels (cInput) sperren: sonst löst Tippen im Chat Spielaktionen aus (Benutzen, Hupe ...).
/// </summary>
static void PatchCursorActions()
{
if (patched) return;
patched = true;
PatchGameInput();
try
{
Type action = null;
foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
{
action = asm.GetType("HutongGames.PlayMaker.Actions.SetMouseCursor");
if (action != null) break;
}
if (action == null) return;
HarmonyInstance harmony = HarmonyInstance.Create("mbm.msco.inputlock");
var prefix = new HarmonyMethod(typeof(InputLock).GetMethod("SkipWhileLocked", BindingFlags.Static | BindingFlags.NonPublic));
foreach (string name in new[] { "OnEnter", "OnUpdate", "DoSetMouseCursor" })
{
MethodInfo m = action.GetMethod(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (m != null && m.DeclaringType == action) harmony.Patch(m, prefix, null);
}
}
catch (Exception e)
{
MscoLog.Warning("[MSCO] Could not patch cursor actions: " + e.Message);
}
}
static bool SkipWhileLocked() { return !locked; }
static void PatchGameInput()
{
try
{
Type input = typeof(cInput);
HarmonyInstance harmony = HarmonyInstance.Create("mbm.msco.inputlock.keys");
var boolPrefix = new HarmonyMethod(typeof(InputLock).GetMethod("NoKeyWhileLocked", BindingFlags.Static | BindingFlags.NonPublic));
var floatPrefix = new HarmonyMethod(typeof(InputLock).GetMethod("NoAxisWhileLocked", BindingFlags.Static | BindingFlags.NonPublic));
int count = 0;
foreach (MethodInfo m in input.GetMethods(BindingFlags.Static | BindingFlags.Public))
{
bool keyQuery = m.Name == "GetKey" || m.Name == "GetKeyDown" || m.Name == "GetKeyUp" ||
m.Name == "GetButton" || m.Name == "GetButtonDown" || m.Name == "GetButtonUp";
bool axisQuery = m.Name == "GetAxis" || m.Name == "GetAxisRaw";
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++; }
}
MscoLog.Info("[MSCO] Game input is paused while typing (" + count + " cInput queries)");
}
catch (Exception e)
{
MscoLog.Warning("[MSCO] Could not pause game input while typing: " + e.Message);
}
}
// Harmony-Prefixe: solange ein Eingabefenster offen ist, meldet cInput "nichts gedrückt"
static bool NoKeyWhileLocked(ref bool __result)
{
if (!locked) return true;
__result = false;
return false;
}
static bool NoAxisWhileLocked(ref float __result)
{
if (!locked) return true;
__result = 0f;
return false;
}
}
}