diff --git a/Cheats.cs b/Cheats.cs
new file mode 100644
index 0000000..c9633bf
--- /dev/null
+++ b/Cheats.cs
@@ -0,0 +1,55 @@
+using System.Collections.Generic;
+using HutongGames.PlayMaker;
+using UnityEngine;
+
+namespace MSCO
+{
+ ///
+ /// Admin-Cheats für den eigenen Spieler (Geld, Bedürfnisse). Nur aus dem Admin-Menü erreichbar, also nur für
+ /// Server-Admins bzw. den P2P-Host. Geld wird über MoneySync automatisch an alle weitergegeben.
+ ///
+ public static class Cheats
+ {
+ // Globale PlayMaker-Variablen des Spielers: Wert, auf den "auffüllen" sie setzt
+ static readonly KeyValuePair[] Needs =
+ {
+ new KeyValuePair("PlayerHunger", 0f),
+ new KeyValuePair("PlayerThirst", 0f),
+ new KeyValuePair("PlayerStress", 0f),
+ new KeyValuePair("PlayerUrine", 0f),
+ new KeyValuePair("PlayerFatigue", 0f),
+ new KeyValuePair("PlayerDirtiness", 0f),
+ };
+
+ public static string AddMoney(float amount)
+ {
+ FsmFloat money = FsmVariables.GlobalVariables.FindFsmFloat("PlayerMoney");
+ if (money == null) return "Money not found (only works in the game)";
+ money.Value += amount;
+ return "+" + amount.ToString("0") + " MK (now " + money.Value.ToString("0") + " MK)";
+ }
+
+ public static string FillNeeds()
+ {
+ int done = 0;
+ foreach (KeyValuePair need in Needs)
+ {
+ FsmFloat v = FsmVariables.GlobalVariables.FindFsmFloat(need.Key);
+ if (v == null) continue;
+ v.Value = need.Value;
+ done++;
+ }
+ return done > 0 ? "Hunger, thirst, stress, fatigue, bladder and dirt reset" : "Player needs not found (only works in the game)";
+ }
+
+ /// Eigenen Spieler an eine Position setzen (etwas darüber, damit er nicht im Boden steckt).
+ public static bool Teleport(Transform player, Vector3 position)
+ {
+ if (player == null) return false;
+ player.position = position + Vector3.up * 0.3f;
+ Rigidbody rb = player.GetComponent();
+ if (rb != null && !rb.isKinematic) rb.velocity = Vector3.zero;
+ return true;
+ }
+ }
+}
diff --git a/Commands.cs b/Commands.cs
index 6309839..32a05ed 100644
--- a/Commands.cs
+++ b/Commands.cs
@@ -13,10 +13,9 @@ namespace MSCO
"disconnect - leave the server / game\n" +
"host [port] / join [port] - direct LAN game (no Steam)\n" +
"save - save the game (host / singleplayer)\n" +
- "/admin - admin menu (kick / ban)\n" +
+ "/admin - admin menu (kick / ban / teleport / cheats)\n" +
"vehicles | rig - debug info about vehicles / player models\n" +
"debug - toggle sync debug output\n" +
- "master [url] - show / change the server list address\n" +
"clear - clear this console";
public static void Execute(string[] args, Action print)
@@ -59,10 +58,6 @@ namespace MSCO
mp.JoinServer(host, serverPort);
break;
}
- case "master":
- if (args.Length > 1) ServerBrowser.MasterUrl = args[1];
- print("Master server: " + ServerBrowser.MasterUrl);
- break;
case "save":
mp.SaveGame();
break;
diff --git a/MSCO.csproj b/MSCO.csproj
index 4740fc0..6b90015 100644
--- a/MSCO.csproj
+++ b/MSCO.csproj
@@ -75,6 +75,7 @@
+
diff --git a/MultiplayerManager.cs b/MultiplayerManager.cs
index a8108ed..62a58bd 100644
--- a/MultiplayerManager.cs
+++ b/MultiplayerManager.cs
@@ -221,6 +221,66 @@ namespace MSCO
return list;
}
+ // ---------------------------------------------------------------- Cheats (Admin-Menü)
+
+ /// Cheats nur im Spiel und nur für Server-Admins bzw. den P2P-Host.
+ public bool CanCheat { get { return session != null && MenuHelper.InGame && (dedicated ? isAdmin : session.IsHost); } }
+
+ public void CheatMoney(float amount)
+ {
+ if (!CanCheat) return;
+ overlays.Log(Cheats.AddMoney(amount));
+ AnnounceCheat("gave themselves " + amount.ToString("0") + " MK");
+ }
+
+ public void CheatNeeds()
+ {
+ if (!CanCheat) return;
+ overlays.Log(Cheats.FillNeeds());
+ AnnounceCheat("refilled their needs");
+ }
+
+ /// Zu einem Mitspieler teleportieren.
+ public void GotoPlayer(int playerId)
+ {
+ PlayerInfo p;
+ if (!CanCheat || !players.TryGetValue(playerId, out p) || p.Avatar == null) { overlays.Log("Player position unknown"); return; }
+ passenger.Exit(localPlayer, playerCamera);
+ PlayerState s = p.Avatar.LastState;
+ Vector3 side = Quaternion.Euler(0f, s.RotationY, 0f) * Vector3.right;
+ if (Cheats.Teleport(localPlayer, s.Position + side * TeleportSideOffset)) overlays.Log("Teleported to " + p.Name);
+ }
+
+ /// Mitspieler zu sich holen (Server prüft, ob man Admin ist).
+ public void BringPlayer(int playerId)
+ {
+ if (!CanCheat || localPlayer == null || playerId == localId) return;
+ Vector3 pos = localPlayer.position + localPlayer.forward * TeleportSideOffset;
+ if (dedicated)
+ {
+ session.SendTo(NetSession.HostId, MsgType.AdminAction, NetSession.Build(w =>
+ {
+ w.Write((byte)3);
+ w.Write(playerId);
+ w.Write("");
+ w.Write(pos.x); w.Write(pos.y); w.Write(pos.z);
+ }));
+ }
+ else if (players.ContainsKey(playerId))
+ {
+ session.SendTo(playerId, MsgType.Teleport, NetSession.Build(w => { w.Write(pos.x); w.Write(pos.y); w.Write(pos.z); }));
+ overlays.Log("Brought " + players[playerId].Name + " to you");
+ }
+ }
+
+ /// Cheats für alle sichtbar im Chat melden.
+ void AnnounceCheat(string what)
+ {
+ string text = OwnName() + " " + what + " (admin)";
+ overlays.AddChat("", text, true);
+ SendGame(MsgType.Chat, NetSession.Build(w => w.Write("* " + text)));
+ }
+
public void AdminAction(bool ban, int playerId, string reason)
{
if (session == null) return;
@@ -835,6 +895,17 @@ namespace MSCO
SaveSync.WriteFile(file, data);
break;
+ case MsgType.Teleport:
+ {
+ // Ein Admin holt uns zu sich
+ var pos = new Vector3(r.ReadSingle(), r.ReadSingle(), r.ReadSingle());
+ if (!MenuHelper.InGame || localPlayer == null) break;
+ passenger.Exit(localPlayer, playerCamera);
+ Cheats.Teleport(localPlayer, pos);
+ overlays.AddChat("", "An admin teleported you", true);
+ break;
+ }
+
case MsgType.AdminStatus:
isAdmin = r.ReadBoolean();
if (isAdmin) overlays.Log("You are an admin on this server - type /admin in chat or 'admin' here.");
diff --git a/NetSession.cs b/NetSession.cs
index 1988ba1..f6893e4 100644
--- a/NetSession.cs
+++ b/NetSession.cs
@@ -37,7 +37,8 @@ namespace MSCO
AdminAction = 26, // Client -> Server: byte (1 = kick, 2 = ban), int Spieler-ID, string Grund
AdminResult = 27, // Server -> Client: string Meldung
Chat = 28, // Chatnachricht (string)
- VarSync = 29 // Fahrzeugzustand: int n, je [int key, byte typ, wert]
+ VarSync = 29, // Fahrzeugzustand: int n, je [int key, byte typ, wert]
+ Teleport = 30 // Admin holt einen Spieler zu sich: 3 float Position
}
public struct Message
diff --git a/Overlays.cs b/Overlays.cs
index f27364c..8071e10 100644
--- a/Overlays.cs
+++ b/Overlays.cs
@@ -263,7 +263,7 @@ namespace MSCO
void DrawAdmin(MultiplayerManager mp)
{
- float width = Mathf.Min(600, Screen.width - 40), height = Mathf.Min(460, Screen.height - 40);
+ float width = Mathf.Min(680, Screen.width - 40), height = Mathf.Min(560, Screen.height - 40);
var rect = new Rect((Screen.width - width) / 2f, (Screen.height - height) / 2f, width, height);
GUI.Box(rect, GUIContent.none, panel);
@@ -284,6 +284,21 @@ namespace MSCO
GUILayout.Label(mp.IsDedicated ? "Players on this server. Banned players cannot join again." : "Players in your game.", small);
GUILayout.Space(10);
+ // Cheats für den eigenen Spieler (nur im Spiel)
+ if (mp.CanCheat)
+ {
+ GUILayout.Label("CHEATS", caption);
+ GUILayout.BeginHorizontal();
+ if (GUILayout.Button("+1.000 MK", primary, GUILayout.Height(30))) mp.CheatMoney(1000f);
+ GUILayout.Space(6);
+ if (GUILayout.Button("+10.000 MK", primary, GUILayout.Height(30))) mp.CheatMoney(10000f);
+ GUILayout.Space(6);
+ if (GUILayout.Button("REFILL NEEDS", primary, GUILayout.Height(30))) mp.CheatNeeds();
+ GUILayout.EndHorizontal();
+ GUILayout.Label("Cheats are announced in the chat. GOTO / BRING teleport you to a player or a player to you.", small);
+ GUILayout.Space(10);
+ }
+
GUILayout.Label("REASON (optional)", caption);
adminReason = GUILayout.TextField(adminReason, 100, field, GUILayout.Height(32));
GUILayout.Space(12);
@@ -304,6 +319,14 @@ namespace MSCO
GUILayout.FlexibleSpace();
bool self = p.Id == mp.LocalId;
+ if (mp.CanCheat)
+ {
+ GUI.enabled = !self;
+ if (GUILayout.Button("GOTO", secondary, GUILayout.Height(30), GUILayout.Width(70))) mp.GotoPlayer(p.Id);
+ GUILayout.Space(6);
+ if (GUILayout.Button("BRING", secondary, GUILayout.Height(30), GUILayout.Width(70))) mp.BringPlayer(p.Id);
+ GUILayout.Space(12);
+ }
GUI.enabled = !self && !p.IsAdmin;
if (GUILayout.Button("KICK", secondary, GUILayout.Height(30), GUILayout.Width(80)))
mp.AdminAction(false, p.Id, adminReason);
diff --git a/README.md b/README.md
index e427976..1d0f799 100644
--- a/README.md
+++ b/README.md
@@ -31,7 +31,8 @@ Multiplayer-Mod für **My Summer Car** (MSCLoader). Spiel mit bis zu 3 Spielern
**Im Spiel**
- **T** – Chat
- **F8** – MSCO-Konsole mit allen Befehlen
-- **/admin** – Admin-Menü (Spieler kicken/bannen) für Server-Admins bzw. den P2P-Host
+- **/admin** – Admin-Menü für Server-Admins bzw. den P2P-Host: Spieler kicken/bannen, GOTO/BRING (teleportieren),
+ Cheats (+1.000 / +10.000 MK, Bedürfnisse auffüllen) – Cheats werden im Chat angezeigt
- **P** – als Beifahrer ein-/aussteigen
- Alle Tasten sind in den MSCLoader-Einstellungen der Mod änderbar
- Discord Rich Presence („In the Menus“, Server, Spielerzahl)
@@ -71,7 +72,6 @@ In der F8-Konsole direkt, in der MSCLoader-Konsole mit `mp` davor (z.B. `mp conn
| `admin` bzw. `/admin` im Chat | Admin-Menü |
| `vehicles`, `rig` | Diagnose: Fahrzeuge / Spielermodelle |
| `debug` | Sync-Debugausgaben an/aus |
-| `master [url]` | Adresse der Serverliste anzeigen/ändern |
| `clear` | Konsole leeren |
## Selbst bauen
diff --git a/ServerBrowser.cs b/ServerBrowser.cs
index 76bf099..e979f60 100644
--- a/ServerBrowser.cs
+++ b/ServerBrowser.cs
@@ -37,8 +37,8 @@ namespace MSCO
///
public class ServerBrowser
{
- public const string DefaultMasterUrl = "https://msco.mbm.wtf";
- const string PrefMasterUrl = "MSCO_MasterUrl";
+ /// Offizieller Master (Serverliste) - fest eingebaut.
+ public const string MasterUrl = "https://msco.mbm.wtf";
const int QueryTimeoutMs = 3000;
// Ein Server auf diesem PC erscheint automatisch oben in der Liste
@@ -54,13 +54,6 @@ namespace MSCO
/// Zeitpunkt der letzten Aktualisierung (Time.realtimeSinceStartup), für das Auto-Refresh.
public float LastRefresh { get; private set; }
- /// Adresse des Master-Servers (änderbar mit "mp master <url>", z.B. zum lokalen Testen).
- public static string MasterUrl
- {
- get { return PlayerPrefs.GetString(PrefMasterUrl, DefaultMasterUrl).TrimEnd('/'); }
- set { PlayerPrefs.SetString(PrefMasterUrl, string.IsNullOrEmpty(value) ? DefaultMasterUrl : value.TrimEnd('/')); }
- }
-
/// Lokale Server zuerst, dann die öffentliche Liste.
public List Servers
{