- Bootstrap: Doorstop entry point, loads Harmony/Newtonsoft from the MSCO folder, hooks PlayMakerArrayListProxy.Awake and starts MSCO in the main menu; skips the news screen - No MSCLoader reference anymore; key bindings in PlayerPrefs, bind console command - Harmony 1.2 and Newtonsoft.Json in lib/ (MIT notices); build copies to <game>\MSCO - README/CHANGELOG updated Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
152 lines
6.2 KiB
C#
152 lines
6.2 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Reflection;
|
|
using Harmony;
|
|
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;
|
|
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;
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <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;
|
|
}
|
|
|
|
/// <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;
|
|
}
|
|
}
|
|
}
|