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:
2026-10-03 00:34:03 +02:00
co-authored by Claude Opus 5.5
parent 166f873669
commit 027296d27c
17 changed files with 747 additions and 74 deletions
+166
View File
@@ -0,0 +1,166 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
namespace MSCO
{
/// <summary>
/// Kleiner eigener JSON-Leser (ohne Newtonsoft.Json - das braucht System.Runtime.Serialization, das ohne MSCLoader im
/// Spiel fehlt). Ergebnis: Dictionary&lt;string, object&gt; (Objekt, Schlüssel ohne Groß-/Kleinschreibung),
/// List&lt;object&gt;, string, double, bool oder null.
/// </summary>
public static class MiniJson
{
public static object Parse(string json)
{
if (json == null) throw new FormatException("No JSON");
int i = 0;
object value = ReadValue(json, ref i);
SkipWhitespace(json, ref i);
if (i < json.Length) throw new FormatException("Unexpected text at " + i);
return value;
}
// ---------------------------------------------------------------- Zugriff
public static string Str(Dictionary<string, object> o, string key, string fallback = null)
{
object v;
if (o == null || !o.TryGetValue(key, out v) || v == null) return fallback;
if (v is double) return ((double)v).ToString(CultureInfo.InvariantCulture);
return v.ToString();
}
public static int Int(Dictionary<string, object> o, string key, int fallback = 0)
{
object v;
if (o == null || !o.TryGetValue(key, out v)) return fallback;
if (v is double) return (int)(double)v;
int parsed;
return v is string && int.TryParse((string)v, NumberStyles.Integer, CultureInfo.InvariantCulture, out parsed) ? parsed : fallback;
}
public static bool Bool(Dictionary<string, object> o, string key, bool fallback = false)
{
object v;
if (o == null || !o.TryGetValue(key, out v)) return fallback;
return v is bool ? (bool)v : fallback;
}
public static List<string> Strings(Dictionary<string, object> o, string key)
{
var result = new List<string>();
object v;
if (o == null || !o.TryGetValue(key, out v)) return result;
var list = v as List<object>;
if (list != null) foreach (object item in list) if (item != null) result.Add(item.ToString());
return result;
}
// ---------------------------------------------------------------- Lesen
static object ReadValue(string s, ref int i)
{
SkipWhitespace(s, ref i);
if (i >= s.Length) throw new FormatException("Unexpected end of JSON");
char c = s[i];
if (c == '{') return ReadObject(s, ref i);
if (c == '[') return ReadArray(s, ref i);
if (c == '"') return ReadString(s, ref i);
if (c == '-' || (c >= '0' && c <= '9')) return ReadNumber(s, ref i);
if (Word(s, ref i, "true")) return true;
if (Word(s, ref i, "false")) return false;
if (Word(s, ref i, "null")) return null;
throw new FormatException("Unexpected '" + c + "' at " + i);
}
static Dictionary<string, object> ReadObject(string s, ref int i)
{
var o = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
i++; // {
SkipWhitespace(s, ref i);
if (i < s.Length && s[i] == '}') { i++; return o; }
while (true)
{
SkipWhitespace(s, ref i);
if (i >= s.Length || s[i] != '"') throw new FormatException("Expected a name at " + i);
string key = ReadString(s, ref i);
SkipWhitespace(s, ref i);
if (i >= s.Length || s[i] != ':') throw new FormatException("Expected ':' at " + i);
i++;
o[key] = ReadValue(s, ref i);
SkipWhitespace(s, ref i);
if (i >= s.Length) throw new FormatException("Unexpected end of JSON");
if (s[i] == ',') { i++; continue; }
if (s[i] == '}') { i++; return o; }
throw new FormatException("Expected ',' or '}' at " + i);
}
}
static List<object> ReadArray(string s, ref int i)
{
var list = new List<object>();
i++; // [
SkipWhitespace(s, ref i);
if (i < s.Length && s[i] == ']') { i++; return list; }
while (true)
{
list.Add(ReadValue(s, ref i));
SkipWhitespace(s, ref i);
if (i >= s.Length) throw new FormatException("Unexpected end of JSON");
if (s[i] == ',') { i++; continue; }
if (s[i] == ']') { i++; return list; }
throw new FormatException("Expected ',' or ']' at " + i);
}
}
static string ReadString(string s, ref int i)
{
var sb = new StringBuilder();
i++; // "
while (i < s.Length)
{
char c = s[i++];
if (c == '"') return sb.ToString();
if (c != '\\') { sb.Append(c); continue; }
if (i >= s.Length) break;
char e = s[i++];
switch (e)
{
case 'b': sb.Append('\b'); break;
case 'f': sb.Append('\f'); break;
case 'n': sb.Append('\n'); break;
case 'r': sb.Append('\r'); break;
case 't': sb.Append('\t'); break;
case 'u':
if (i + 4 > s.Length) throw new FormatException("Bad \\u escape");
sb.Append((char)int.Parse(s.Substring(i, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture));
i += 4;
break;
default: sb.Append(e); break; // \" \\ \/
}
}
throw new FormatException("Unterminated string");
}
static double ReadNumber(string s, ref int i)
{
int start = i;
while (i < s.Length && "+-0123456789.eE".IndexOf(s[i]) >= 0) i++;
return double.Parse(s.Substring(start, i - start), NumberStyles.Float, CultureInfo.InvariantCulture);
}
static bool Word(string s, ref int i, string word)
{
if (string.CompareOrdinal(s, i, word, 0, word.Length) != 0) return false;
i += word.Length;
return true;
}
static void SkipWhitespace(string s, ref int i)
{
while (i < s.Length && char.IsWhiteSpace(s[i])) i++;
}
}
}