using System; using System.Collections.Generic; using System.Reflection; using Harmony; using HutongGames.PlayMaker; using UnityEngine; namespace MSCO { /// /// 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. /// public static class InputLock { static readonly string[] ControlScripts = { "CharacterMotor", "FPSInputController", "MouseLook", "SimpleSmoothMouseLook", "SmoothMouseLook", "MouseLook2", "MouseLook3" }; static readonly List disabled = new List(); 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()) { 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; } } /// /// 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. /// 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; } /// Globale Spielvariable "PlayerInMenu": true = Zeiger frei, Mausblick aus. static FsmBool PlayerInMenu() { try { return FsmVariables.GlobalVariables.FindFsmBool("PlayerInMenu"); } catch { return null; } } /// /// 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 ...). /// 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; } } }