Own main menu (game's Continue/New Game/Credits/Changelog/Quit hidden, QUIT in the launcher); new server profile layout; admin items: ratchet set, battery, shop items; session shows server player limit
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -19,6 +19,10 @@ Server und Mod müssen beide 2.7 sein (Server: `git pull && docker compose up -d
|
||||
- Letzte Position wird zuverlässig gespeichert (nicht mehr immer an der Toilette spawnen)
|
||||
- Server-Browser: Klick auf den Server öffnet das Profil (JOIN-Knopf in der Liste entfernt)
|
||||
- Alle Mod-Meldungen nur noch in der MSCO-Konsole (F7)
|
||||
- Eigenes Hauptmenü: Continue, New Game, Credits, Changelog und Quit des Spiels sind ausgeblendet, der MSCO-Launcher
|
||||
ist das Hauptmenü (mit eigenem QUIT)
|
||||
- Server-Profil neu: JOIN oben, Spielerliste rechts, Namensfeld ohne Steam in eigenem Kasten
|
||||
- Admin-ITEMS: Werkzeuge und Laden-Artikel (Ratschenkasten, Batterie, Motoröl, Kühlmittel, Bremsflüssigkeit ...)
|
||||
|
||||
**Behoben**
|
||||
- Auto fiel beim Fahren eines anderen Spielers auseinander (angebaute Teile rissen an ihren Gelenken ab)
|
||||
|
||||
+44
-6
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using HutongGames.PlayMaker;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MSCO
|
||||
@@ -7,31 +8,59 @@ namespace MSCO
|
||||
/// Admin: Items spawnen. Kopiert ein vorhandenes Item des Spiels (z.B. "beer case(itemx)" aus dem Laden) und
|
||||
/// setzt die Kopie an die gewünschte Stelle. Die Kopie behält den Namen des Originals, damit sich die Spiel-Logik
|
||||
/// (Trinken, Essen, Tanken ...) wie beim echten Item verhält und der Objekt-Sync sie bei allen gleich benennt.
|
||||
/// Werkzeuge wie der Ratschenkasten sind im Spiel keine Gegenstände, sondern ein Schalter ("hat der Spieler") -
|
||||
/// die werden dem Admin direkt gegeben.
|
||||
/// </summary>
|
||||
public static class ItemSpawner
|
||||
{
|
||||
/// <summary>Auswahl im Admin-Menü (Name ohne "(itemx)").</summary>
|
||||
/// <summary>Werkzeug statt Gegenstand: globale Spielvariable, die "gekauft" bedeutet.</summary>
|
||||
public const string RatchetSet = "ratchet set";
|
||||
|
||||
/// <summary>Auswahl im Admin-Menü (Anzeigename).</summary>
|
||||
public static readonly string[] Items =
|
||||
{
|
||||
"beer case", "sausages", "pizza", "macaron box", "milk", "juice", "ground coffee", "sugar", "yeast", "booze",
|
||||
"gasoline", "diesel", "spray can", "fire extinguisher", "flashlight", "lantern", "car jack", "floor jack",
|
||||
"spanner set", "sledgehammer", "radio", "camera", "grill charcoal", "fireworks bag", "water bucket"
|
||||
// Werkzeug / Auto
|
||||
RatchetSet, "spanner set", "battery", "car jack", "floor jack", "motor hoist", "flashlight", "lantern",
|
||||
"fire extinguisher", "spray can", "sledgehammer", "ax", "digging bar",
|
||||
// Flüssigkeiten / Kraftstoff
|
||||
"motor oil", "coolant", "brake fluid", "two stroke fuel", "gasoline", "diesel", "n2o bottle",
|
||||
// Essen / Trinken / Laden
|
||||
"beer case", "beer", "booze", "vodka shot", "milk", "juice", "coffee", "ground coffee", "sugar", "yeast",
|
||||
"sausages", "pizza", "macaron box", "potato chips", "cigarettes", "mosquito spray", "grill charcoal",
|
||||
// Sonstiges
|
||||
"grill", "radio", "camera", "helmet", "bucket", "water bucket", "fireworks bag", "parts magazine"
|
||||
};
|
||||
|
||||
// Items, die im Spiel nicht "(itemx)" heißen
|
||||
static readonly Dictionary<string, string> objectNames = new Dictionary<string, string>
|
||||
{
|
||||
{ "battery", "battery(Clone)" }
|
||||
};
|
||||
|
||||
static readonly Dictionary<string, GameObject> templates = new Dictionary<string, GameObject>();
|
||||
|
||||
public static bool IsKnown(string item) { return System.Array.IndexOf(Items, item) >= 0; }
|
||||
|
||||
static string ObjectName(string item)
|
||||
{
|
||||
string name;
|
||||
return objectNames.TryGetValue(item, out name) ? name : item + "(itemx)";
|
||||
}
|
||||
|
||||
static GameObject FindTemplate(string item)
|
||||
{
|
||||
GameObject cached;
|
||||
if (templates.TryGetValue(item, out cached) && cached != null) return cached;
|
||||
|
||||
string name = item + "(itemx)";
|
||||
string name = ObjectName(item);
|
||||
GameObject best = null;
|
||||
foreach (Object o in Resources.FindObjectsOfTypeAll(typeof(GameObject)))
|
||||
{
|
||||
var go = (GameObject)o;
|
||||
if (go.name != name || go.GetComponent<Rigidbody>() == null) continue;
|
||||
if (best == null || (go.activeInHierarchy && !best.activeInHierarchy)) best = go; // am liebsten ein echtes in der Welt
|
||||
// Keine eingebauten Teile kopieren (z.B. die Batterie im Auto): nicht unter einem anderen Rigidbody
|
||||
if (go.transform.parent != null && go.transform.parent.GetComponentInParent<Rigidbody>() != null) continue;
|
||||
if (best == null || (go.activeInHierarchy && !best.activeInHierarchy)) best = go; // am liebsten eins aus der Welt
|
||||
}
|
||||
templates[item] = best;
|
||||
return best;
|
||||
@@ -60,6 +89,15 @@ namespace MSCO
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Ratschenkasten "kaufen": der Spieler hat ihn dann wie nach dem Kauf im Laden.</summary>
|
||||
public static bool GiveRatchetSet()
|
||||
{
|
||||
FsmBool has = FsmVariables.GlobalVariables.FindFsmBool("PlayerHasRatchet");
|
||||
if (has == null) return false;
|
||||
has.Value = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void ClearCache() { templates.Clear(); }
|
||||
}
|
||||
}
|
||||
|
||||
+79
-37
@@ -31,6 +31,8 @@ namespace MSCO
|
||||
static readonly Color Pending = Hex(0xf2, 0xc2, 0x1d);
|
||||
|
||||
Page page = Page.P2P;
|
||||
Vector2 profileScroll;
|
||||
bool quitConfirm;
|
||||
bool minimized;
|
||||
Vector2 playerScroll;
|
||||
Vector2 serverScroll;
|
||||
@@ -46,6 +48,7 @@ namespace MSCO
|
||||
Texture2D texBackground, texSidebar, texAccent, texOnline, texPending, texMuted, texRow;
|
||||
List<ServerListEntry> shownAll, shownServers; // Stand des letzten Layout-Durchgangs
|
||||
string layoutError;
|
||||
GUIStyle dangerStyle;
|
||||
GUIStyle windowBox, sidebarBox, rowBox, accentBox, pillStyle, pillMutedStyle, chipOnStyle, chipOffStyle;
|
||||
GUIStyle logoStyle, taglineStyle, navStyle, navActiveStyle, titleStyle, textStyle, captionStyle,
|
||||
fieldStyle, primaryStyle, secondaryStyle, statusStyle, tabStyle, headerCellStyle,
|
||||
@@ -112,6 +115,14 @@ namespace MSCO
|
||||
y += 44;
|
||||
if (NavButton(new Rect(r.x, y, r.width, 42), "Server Browser", page == Page.ServerBrowser)) page = Page.ServerBrowser;
|
||||
|
||||
// Eigenes Hauptmenü: Spiel beenden (die Knöpfe des Spiel-Menüs sind ausgeblendet)
|
||||
if (quitConfirm)
|
||||
{
|
||||
if (GUI.Button(new Rect(r.x + 16, r.yMax - 118, r.width - 32, 36), "QUIT GAME?", dangerStyle)) Application.Quit();
|
||||
if (GUI.Button(new Rect(r.x + 16, r.yMax - 76, r.width - 32, 30), "CANCEL", tabStyle)) quitConfirm = false;
|
||||
}
|
||||
else if (GUI.Button(new Rect(r.x + 16, r.yMax - 76, r.width - 32, 36), "QUIT", tabStyle)) quitConfirm = true;
|
||||
|
||||
GUI.Label(new Rect(r.x + 24, r.yMax - 30, r.width - 48, 20), "v" + MSCO.ModVersion, taglineStyle);
|
||||
}
|
||||
|
||||
@@ -295,7 +306,7 @@ namespace MSCO
|
||||
selectedServer = null;
|
||||
return;
|
||||
}
|
||||
GUILayout.Space(6);
|
||||
GUILayout.Space(8);
|
||||
|
||||
if (s == null)
|
||||
{
|
||||
@@ -304,64 +315,94 @@ namespace MSCO
|
||||
return;
|
||||
}
|
||||
|
||||
// Kopf: Name + Abzeichen links, JOIN (und ohne Steam das Namensfeld) rechts - immer sichtbar,
|
||||
// egal wie lang Beschreibung und Spielerliste darunter sind
|
||||
// Kopf: Name + Abzeichen links, JOIN rechts - immer sichtbar
|
||||
GUILayout.BeginHorizontal();
|
||||
GUILayout.BeginVertical();
|
||||
GUILayout.Label(s.Name, titleStyle);
|
||||
GUILayout.BeginHorizontal();
|
||||
GUILayout.Label(s.RequireSteam ? "STEAM" : "NON-STEAM", badgeStyle, GUILayout.ExpandWidth(false));
|
||||
if (s.IsLocal) { GUILayout.Space(10); GUILayout.Label("LOCAL", badgeStyle, GUILayout.ExpandWidth(false)); }
|
||||
GUILayout.FlexibleSpace();
|
||||
Rect badgeRow = GUILayoutUtility.GetRect(10, 20, GUILayout.ExpandWidth(true));
|
||||
float bx = badgeRow.x;
|
||||
bx += Pill(bx, badgeRow, s.RequireSteam ? "STEAM" : "NON-STEAM", s.RequireSteam ? pillStyle : pillMutedStyle);
|
||||
if (s.IsLocal) bx += Pill(bx, badgeRow, "LOCAL", pillStyle);
|
||||
if (s.Version != MSCO.ModVersion) Pill(bx, badgeRow, "v" + s.Version, pillStyle);
|
||||
GUILayout.EndHorizontal();
|
||||
GUILayout.EndVertical();
|
||||
GUILayout.FlexibleSpace();
|
||||
GUILayout.BeginVertical(GUILayout.Width(190));
|
||||
GUILayout.Space(12);
|
||||
GUI.enabled = CanJoin(mp, s);
|
||||
if (GUILayout.Button(s.Players >= s.MaxPlayers ? "SERVER FULL" : "JOIN SERVER", primaryStyle, GUILayout.Height(42), GUILayout.Width(190)))
|
||||
if (GUILayout.Button(s.Players >= s.MaxPlayers ? "SERVER FULL" : "JOIN SERVER", primaryStyle, GUILayout.Height(44), GUILayout.Width(180)))
|
||||
mp.JoinServer(s.Address, s.Port, s.Name, s.MaxPlayers);
|
||||
GUI.enabled = true;
|
||||
if (s.RequireSteam && !mp.SteamAvailable)
|
||||
GUILayout.Label("This server requires Steam.", textStyle);
|
||||
else if (!mp.SteamAvailable)
|
||||
{
|
||||
GUILayout.Space(4);
|
||||
GUILayout.BeginHorizontal();
|
||||
GUILayout.Label("NAME", captionStyle, GUILayout.Width(42));
|
||||
DrawNameField(mp, 140);
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
GUILayout.EndVertical();
|
||||
GUILayout.EndHorizontal();
|
||||
if (!string.IsNullOrEmpty(s.Description)) GUILayout.Label(s.Description, textStyle);
|
||||
GUILayout.Space(10);
|
||||
|
||||
// Ohne Steam: Name wählen (sauber in einem eigenen Kasten)
|
||||
if (s.RequireSteam && !mp.SteamAvailable)
|
||||
{
|
||||
GUILayout.BeginHorizontal(rowBox, GUILayout.Height(40));
|
||||
GUILayout.Label("This server requires Steam - start the game through Steam to join.", textStyle);
|
||||
GUILayout.EndHorizontal();
|
||||
GUILayout.Space(10);
|
||||
}
|
||||
else if (!mp.SteamAvailable)
|
||||
{
|
||||
GUILayout.BeginHorizontal(rowBox, GUILayout.Height(44));
|
||||
GUILayout.Space(6);
|
||||
GUILayout.BeginVertical(GUILayout.Width(110));
|
||||
GUILayout.Space(4);
|
||||
GUILayout.Label("PLAYING AS", captionStyle);
|
||||
GUILayout.Label("no Steam", textStyle);
|
||||
GUILayout.EndVertical();
|
||||
GUILayout.BeginVertical();
|
||||
GUILayout.Space(5);
|
||||
DrawNameField(mp, 220);
|
||||
GUILayout.EndVertical();
|
||||
GUILayout.FlexibleSpace();
|
||||
GUILayout.EndHorizontal();
|
||||
GUILayout.Space(10);
|
||||
}
|
||||
|
||||
// Infos links, Spielerliste rechts
|
||||
bool versionOk = s.Version == MSCO.ModVersion;
|
||||
GUILayout.BeginHorizontal();
|
||||
GUILayout.BeginVertical(GUILayout.Width(250));
|
||||
InfoRow("HOSTED BY", string.IsNullOrEmpty(s.Owner) ? "unknown" : s.Owner, rowNameStyle);
|
||||
InfoRow("VERSION", "v" + s.Version + (versionOk ? "" : " (you: v" + MSCO.ModVersion + ")"), versionOk ? rowNameStyle : badgeStyle);
|
||||
InfoRow("STEAM", s.RequireSteam ? "Required" : "Not required", rowNameStyle);
|
||||
InfoRow("ONLINE FOR", s.UptimeSeconds >= 0 ? FormatUptime(s.UptimeSeconds) : "-", rowNameStyle);
|
||||
InfoLine("HOSTED BY", string.IsNullOrEmpty(s.Owner) ? "unknown" : s.Owner, rowNameStyle);
|
||||
InfoLine("VERSION", "v" + s.Version + (versionOk ? "" : " (you: v" + MSCO.ModVersion + ")"), versionOk ? rowNameStyle : badgeStyle);
|
||||
InfoLine("STEAM", s.RequireSteam ? "Required" : "Not required", rowNameStyle);
|
||||
InfoLine("ONLINE FOR", s.UptimeSeconds >= 0 ? FormatUptime(s.UptimeSeconds) : "-", rowNameStyle);
|
||||
InfoLine("PING", s.Ping < 0 ? "-" : s.Ping + " ms", rowNameStyle);
|
||||
InfoLine("ADDRESS", s.Address + ":" + s.Port, rowNameStyle);
|
||||
GUILayout.EndVertical();
|
||||
GUILayout.BeginVertical();
|
||||
InfoRow("PLAYERS", s.Players + " / " + s.MaxPlayers, rowNameStyle);
|
||||
InfoRow("PING", s.Ping < 0 ? "-" : s.Ping + " ms", rowNameStyle);
|
||||
InfoRow("ADDRESS", s.Address + ":" + s.Port, rowNameStyle);
|
||||
GUILayout.Space(16);
|
||||
|
||||
GUILayout.BeginVertical(rowBox, GUILayout.ExpandWidth(true), GUILayout.MinHeight(150));
|
||||
GUILayout.Space(6);
|
||||
GUILayout.Label("PLAYERS " + s.Players + " / " + s.MaxPlayers, captionStyle);
|
||||
profileScroll = GUILayout.BeginScrollView(profileScroll, GUILayout.Height(130));
|
||||
if (s.PlayerNames == null || s.PlayerNames.Count == 0)
|
||||
GUILayout.Label("Nobody online right now.", textStyle);
|
||||
else
|
||||
foreach (string name in s.PlayerNames)
|
||||
{
|
||||
Rect line = GUILayoutUtility.GetRect(10, 24, GUILayout.ExpandWidth(true));
|
||||
GUI.DrawTexture(new Rect(line.x + 2, line.y + 8, 8, 8), texOnline);
|
||||
GUI.Label(new Rect(line.x + 18, line.y, line.width - 18, line.height), name, rowNameStyle);
|
||||
}
|
||||
GUILayout.EndScrollView();
|
||||
GUILayout.EndVertical();
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
// Spielerliste
|
||||
if (s.PlayerNames != null && s.PlayerNames.Count > 0)
|
||||
{
|
||||
GUILayout.Space(4);
|
||||
GUILayout.Label("ONLINE", captionStyle);
|
||||
GUILayout.Label(string.Join(", ", s.PlayerNames.ToArray()), textStyle);
|
||||
}
|
||||
|
||||
GUILayout.FlexibleSpace();
|
||||
}
|
||||
|
||||
/// <summary>Eine Info-Zeile: Bezeichnung links (grau), Wert rechts.</summary>
|
||||
void InfoLine(string caption, string value, GUIStyle valueStyle)
|
||||
{
|
||||
GUILayout.BeginHorizontal(GUILayout.Height(26));
|
||||
GUILayout.Label(caption, captionStyle, GUILayout.Width(100));
|
||||
GUILayout.Label(value, valueStyle);
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
/// <summary>Kleines abgerundetes Abzeichen, so breit wie sein Text. Gibt die belegte Breite zurück.</summary>
|
||||
static float Pill(float x, Rect row, string text, GUIStyle style)
|
||||
{
|
||||
@@ -576,6 +617,7 @@ namespace MSCO
|
||||
navActiveStyle.padding = new RectOffset(26, 10, 0, 0);
|
||||
|
||||
primaryStyle = Button(Accent, AccentHover, Color.white, 14, FontStyle.Bold);
|
||||
dangerStyle = Button(UiTheme.Danger, UiTheme.DangerHover, Color.white, 13, FontStyle.Bold);
|
||||
secondaryStyle = Button(Secondary, SecondaryHover, Color.white, 14, FontStyle.Bold);
|
||||
tabStyle = Button(Sidebar, Secondary, Muted, 11, FontStyle.Bold);
|
||||
|
||||
|
||||
@@ -51,6 +51,27 @@ namespace MSCO
|
||||
}
|
||||
|
||||
/// <summary>Startet das Spiel wie ein Klick auf CONTINUE.</summary>
|
||||
// Knöpfe des Spiel-Hauptmenüs, die MSCO ersetzt (der MSCO-Launcher ist das Hauptmenü)
|
||||
static readonly string[] HiddenMenuButtons = { "ButtonContinue", "ButtonNewgame", "ButtonCredits", "ButtonQuit", "ButtonChangelog" };
|
||||
|
||||
/// <summary>
|
||||
/// Continue, New Game, Credits, Changelog und Quit des Spiels unsichtbar und unklickbar machen. Nicht abschalten:
|
||||
/// die Logik hinter CONTINUE lädt beim Beitreten den übertragenen Spielstand (StartGame). Gibt die Anzahl zurück.
|
||||
/// </summary>
|
||||
public static int HideGameMenuButtons()
|
||||
{
|
||||
int hidden = 0;
|
||||
foreach (string name in HiddenMenuButtons)
|
||||
{
|
||||
GameObject button = GameObject.Find(name);
|
||||
if (button == null) continue;
|
||||
foreach (Renderer r in button.GetComponentsInChildren<Renderer>(true)) r.enabled = false;
|
||||
foreach (Collider c in button.GetComponentsInChildren<Collider>(true)) c.enabled = false;
|
||||
hidden++;
|
||||
}
|
||||
return hidden;
|
||||
}
|
||||
|
||||
public static void StartGame()
|
||||
{
|
||||
PlayMakerFSM fsm;
|
||||
|
||||
+18
-1
@@ -344,6 +344,13 @@ namespace MSCO
|
||||
public void SpawnItem(string item)
|
||||
{
|
||||
if (!CanCheat || localPlayer == null) return;
|
||||
if (item == ItemSpawner.RatchetSet)
|
||||
{
|
||||
// Werkzeug, kein Gegenstand: nur für einen selbst freischalten
|
||||
overlays.Log(ItemSpawner.GiveRatchetSet() ? "You now have the ratchet set" : "Ratchet set not found (only works in the game)");
|
||||
AnnounceCheat("gave themselves the ratchet set");
|
||||
return;
|
||||
}
|
||||
Transform head = playerCamera != null ? playerCamera.transform : localPlayer;
|
||||
Vector3 forward = new Vector3(head.forward.x, 0f, head.forward.z).normalized;
|
||||
if (forward.sqrMagnitude < 0.01f) forward = localPlayer.forward;
|
||||
@@ -366,7 +373,7 @@ namespace MSCO
|
||||
string item = r.ReadString();
|
||||
var pos = new Vector3(r.ReadSingle(), r.ReadSingle(), r.ReadSingle());
|
||||
var rot = new Quaternion(r.ReadSingle(), r.ReadSingle(), r.ReadSingle(), r.ReadSingle());
|
||||
if (System.Array.IndexOf(ItemSpawner.Items, item) < 0) return false; // nur bekannte Items
|
||||
if (!ItemSpawner.IsKnown(item) || item == ItemSpawner.RatchetSet) return false; // nur bekannte Gegenstände
|
||||
return ItemSpawner.Spawn(item, pos, rot);
|
||||
}
|
||||
|
||||
@@ -740,6 +747,12 @@ namespace MSCO
|
||||
// Solange das Multiplayer-Fenster offen ist, reagiert das Spielmenü (CONTINUE, QUIT ...) gar nicht;
|
||||
// erst nach MINIMIZE wieder. Über der eingeklappten Leiste ebenfalls nicht (sonst öffnet sich z.B. der Changelog).
|
||||
MenuInputBlocker.SetBlocked(MenuHelper.InMenu && (lobby.IsOpen || lobby.ContainsMouse() || overlays.AnyOpen));
|
||||
// Eigenes Hauptmenü: Singleplayer-Knöpfe des Spiels ausblenden (einmal pro Menü-Besuch, sobald sie da sind)
|
||||
if (MenuHelper.InMenu && !menuButtonsHidden && Time.realtimeSinceStartup >= nextMenuButtonCheck)
|
||||
{
|
||||
nextMenuButtonCheck = Time.realtimeSinceStartup + 0.5f;
|
||||
menuButtonsHidden = MenuHelper.HideGameMenuButtons() > 0;
|
||||
}
|
||||
UpdateOverlayKeys();
|
||||
MscoLog.Drain(overlays.Log); // alle Mod-Meldungen in die MSCO-Konsole (F7)
|
||||
UpdatePresence();
|
||||
@@ -1232,8 +1245,12 @@ namespace MSCO
|
||||
MscoLog.Info("[MSCO] Sent save (" + files.Count + " files) to " + targets.Count + " player(s)");
|
||||
}
|
||||
|
||||
bool menuButtonsHidden;
|
||||
float nextMenuButtonCheck;
|
||||
|
||||
void OnLevelWasLoaded(int level)
|
||||
{
|
||||
menuButtonsHidden = false; // neues Hauptmenü: Knöpfe wieder ausblenden
|
||||
nextPresenceUpdate = 0f; // Discord gleich nach dem Szenenwechsel aktualisieren
|
||||
if (MenuHelper.InGame)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user