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>
This commit is contained in:
+78
-16
@@ -23,14 +23,16 @@ namespace Doorstop
|
||||
namespace MSCO
|
||||
{
|
||||
/// <summary>
|
||||
/// Startet MSCO eigenständig: Abhängigkeiten (Harmony, Newtonsoft.Json) aus dem MSCO-Ordner laden, warten bis das
|
||||
/// Startet MSCO eigenständig: Abhängigkeiten (Harmony) aus dem MSCO-Ordner laden, warten bis das
|
||||
/// Spiel seinen Code geladen hat, dann beim ersten PlayMakerArrayListProxy.Awake (Hauptmenü, Hauptthread) den
|
||||
/// MultiplayerManager anlegen. Protokoll: <Spiel>\MSCO\MSCO_Loader.txt.
|
||||
/// </summary>
|
||||
public static class Bootstrap
|
||||
{
|
||||
static string folder;
|
||||
static bool patched, started;
|
||||
/// <summary>Ordner von MSCO.dll (<Spiel>\MSCO).</summary>
|
||||
public static string Folder { get { return folder; } }
|
||||
static bool patchedGame, patchedPlayMaker, started;
|
||||
static StreamWriter log;
|
||||
|
||||
public static void Start()
|
||||
@@ -71,28 +73,34 @@ namespace MSCO
|
||||
|
||||
static void OnAssemblyLoaded(Assembly assembly)
|
||||
{
|
||||
if (patched) return;
|
||||
if (patchedGame && patchedPlayMaker) return;
|
||||
string name;
|
||||
try { name = assembly.GetName().Name; } catch { return; }
|
||||
if (name != "Assembly-CSharp") return;
|
||||
patched = true;
|
||||
try { Patch(assembly); }
|
||||
catch (Exception e) { Log("Could not hook into the game: " + e); }
|
||||
try
|
||||
{
|
||||
if (name == "Assembly-CSharp" && !patchedGame) { patchedGame = true; Hook(assembly, "PlayMakerArrayListProxy", "Awake"); }
|
||||
// PlayMaker läuft schon im Startbildschirm (Nachrichten, "Press any key") - so startet MSCO sofort
|
||||
if (name == "PlayMaker" && !patchedPlayMaker) { patchedPlayMaker = true; Hook(assembly, "PlayMakerFSM", "Awake"); }
|
||||
}
|
||||
catch (Exception e) { Log("Could not hook into " + name + ": " + e); }
|
||||
}
|
||||
|
||||
/// <summary>Haken in PlayMakerArrayListProxy.Awake (läuft im Hauptmenü früh auf dem Hauptthread).</summary>
|
||||
static void Patch(Assembly game)
|
||||
/// <summary>
|
||||
/// Haken in eine Awake-Methode, die früh auf dem Hauptthread läuft (PlayMakerFSM schon im Startbildschirm,
|
||||
/// PlayMakerArrayListProxy im Hauptmenü als Rückfallebene). Der erste, der läuft, startet MSCO.
|
||||
/// </summary>
|
||||
static void Hook(Assembly assembly, string typeName, string methodName)
|
||||
{
|
||||
Type proxy = game.GetType("PlayMakerArrayListProxy");
|
||||
MethodInfo awake = proxy != null ? proxy.GetMethod("Awake", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) : null;
|
||||
if (awake == null)
|
||||
Type type = assembly.GetType(typeName);
|
||||
MethodInfo method = type != null ? type.GetMethod(methodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) : null;
|
||||
if (method == null)
|
||||
{
|
||||
Log("PlayMakerArrayListProxy.Awake not found - MSCO cannot start");
|
||||
Log(typeName + "." + methodName + " not found");
|
||||
return;
|
||||
}
|
||||
HarmonyInstance.Create("mbm.msco.bootstrap").Patch(awake,
|
||||
HarmonyInstance.Create("mbm.msco.bootstrap." + typeName).Patch(method,
|
||||
new HarmonyMethod(typeof(Bootstrap).GetMethod("BeforeAwake", BindingFlags.Static | BindingFlags.NonPublic)), null);
|
||||
Log("Hooked into the game, waiting for the main menu");
|
||||
Log("Hooked into " + typeName + "." + methodName);
|
||||
}
|
||||
|
||||
static void BeforeAwake()
|
||||
@@ -116,24 +124,78 @@ namespace MSCO
|
||||
void Start()
|
||||
{
|
||||
MscoLog.Info("Welcome to MSCO by mbm.wtf");
|
||||
MenuBranding.ApplyWindow();
|
||||
// Den Nachrichten-Bildschirm des Spiels ("Latest news ... press any key") überspringen - direkt ins Hauptmenü
|
||||
string scene = Application.loadedLevelName;
|
||||
Bootstrap.Log("First scene: " + scene);
|
||||
if (scene != "MainMenu" && scene != "GAME")
|
||||
{
|
||||
// Erst einen Frame lang den MSCO-Ladebildschirm zeigen (Kameras und Ton des Startbildschirms aus), dann
|
||||
// laden - sonst steht während des Ladens (ein paar Sekunden) "Press any key" auf dem Bildschirm
|
||||
Bootstrap.Log("Skipping the intro/news screen");
|
||||
Application.LoadLevel("MainMenu");
|
||||
skipping = true;
|
||||
skipFrame = Time.frameCount;
|
||||
foreach (Camera c in Camera.allCameras) c.enabled = false;
|
||||
previousVolume = AudioListener.volume;
|
||||
AudioListener.volume = 0f;
|
||||
return;
|
||||
}
|
||||
MultiplayerManager.Ensure();
|
||||
}
|
||||
|
||||
bool skipping, loadingScreen;
|
||||
float previousVolume = -1f;
|
||||
int skipFrame;
|
||||
Texture2D black;
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (!skipping || Time.frameCount < skipFrame + 2) return;
|
||||
skipping = false;
|
||||
loadingScreen = true;
|
||||
Application.LoadLevel("MainMenu");
|
||||
}
|
||||
|
||||
void OnGUI()
|
||||
{
|
||||
if (!skipping && !loadingScreen) return;
|
||||
GUI.depth = -10000;
|
||||
if (black == null)
|
||||
{
|
||||
black = new Texture2D(1, 1);
|
||||
black.SetPixel(0, 0, new Color(0.05f, 0.05f, 0.063f));
|
||||
black.Apply();
|
||||
}
|
||||
GUI.DrawTexture(new Rect(0, 0, Screen.width, Screen.height), black);
|
||||
Texture2D logo = Branding.LogoTexture;
|
||||
float size = Mathf.Round(Screen.height * 0.14f);
|
||||
if (logo != null) GUI.DrawTexture(new Rect((Screen.width - size) / 2f, Screen.height * 0.40f - size / 2f, size, size), logo);
|
||||
var style = new GUIStyle(GUI.skin.label) { alignment = TextAnchor.MiddleCenter, fontSize = Mathf.RoundToInt(Screen.height * 0.022f) };
|
||||
style.normal.textColor = new Color(0.56f, 0.56f, 0.6f);
|
||||
GUI.Label(new Rect(0, Screen.height * 0.40f + size / 2f + 16f, Screen.width, 40f), "Loading MSCO …", style);
|
||||
}
|
||||
|
||||
void OnLevelWasLoaded(int level)
|
||||
{
|
||||
loadingScreen = false;
|
||||
if (previousVolume >= 0f) { AudioListener.volume = previousVolume; previousVolume = -1f; }
|
||||
Bootstrap.Log("Level loaded: " + Application.loadedLevelName);
|
||||
MenuBranding.ApplyWindow();
|
||||
if (Application.loadedLevelName == "MainMenu") StartCoroutine(BrandMenu());
|
||||
MultiplayerManager.Ensure();
|
||||
}
|
||||
|
||||
/// <summary>Logo im Hauptmenü ersetzen - ein paar Mal, falls das Menü es erst nach dem Laden zeigt.</summary>
|
||||
System.Collections.IEnumerator BrandMenu()
|
||||
{
|
||||
for (int i = 0; i < 4 && Application.loadedLevelName == "MainMenu"; i++)
|
||||
{
|
||||
yield return new WaitForSeconds(i == 0 ? 0.1f : 1f);
|
||||
try { MenuBranding.ReplaceMenuLogo(); }
|
||||
catch (System.Exception e) { Bootstrap.Log("Menu logo: " + e.Message); yield break; }
|
||||
}
|
||||
}
|
||||
|
||||
void OnApplicationQuit()
|
||||
{
|
||||
// Wer beendet das Spiel? (Diagnose)
|
||||
|
||||
Reference in New Issue
Block a user