MSCO 2.10.0: mod support, ESC no longer quits from the main menu
- MscoMod API (ModApi.cs) and ModLoader: own mods from MSCO\Mods, hooks for menu/game/update/GUI, session and player events, Send/OnMessage between players (MsgType.ModMessage) - Server mods: ModList before Welcome, consent prompt (INSTALL & JOIN), download with SHA-256 check into MSCO\Mods\Servers, active only on that server; game start waits for the download - Mods page in the MSCO menu, console command "mods" - Server browser: MODS pill, VANILLA/MODDED filters, mod list in the server profile - PlayMaker ApplicationQuit is ignored in the main menu (ESC used to close the game) - MODDING.md and examples/HelloMod Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+94
-2
@@ -10,7 +10,7 @@ namespace MSCO
|
||||
/// </summary>
|
||||
public class LobbyUI
|
||||
{
|
||||
enum Page { P2P, ServerBrowser, Favorites }
|
||||
enum Page { P2P, ServerBrowser, Favorites, Mods }
|
||||
|
||||
// Vollbild, automatisch skaliert: gebaut für RefHeight x RefWidth, vergrößert/verkleinert passend zur Auflösung
|
||||
const float RefHeight = 760f;
|
||||
@@ -54,7 +54,7 @@ namespace MSCO
|
||||
bool browserLoaded;
|
||||
string selectedServer; // "adresse:port" des geöffneten Server-Profils
|
||||
string searchInput = "";
|
||||
bool showSteam = true, showNonSteam = true, hideFull;
|
||||
bool showSteam = true, showNonSteam = true, hideFull, showVanilla = true, showModded = true;
|
||||
readonly Dictionary<string, string> nicknameEdits = new Dictionary<string, string>(); // Favoriten-Tab: Adresse -> bearbeiteter Kurzname
|
||||
string favoriteAddInput = "";
|
||||
Vector2 favoritesScroll;
|
||||
@@ -106,6 +106,7 @@ namespace MSCO
|
||||
if (Minimized && !mp.IsInSession && (mp.DisconnectNotice != null || mp.PendingJoinHost != null || mp.PendingVersionWarning != null))
|
||||
Minimized = false;
|
||||
if (Minimized && mp.IsReconnecting) Minimized = false;
|
||||
if (Minimized && mp.PendingMods != null) Minimized = false;
|
||||
if (Minimized) { DrawMinimized(mp); return; }
|
||||
ScreenRect = new Rect(0, 0, Screen.width, Screen.height);
|
||||
DrawScaled(mp);
|
||||
@@ -122,6 +123,7 @@ namespace MSCO
|
||||
// Getrennt / Neu-Verbinden: eigenes Fenster in der Bildmitte, alles andere gesperrt
|
||||
if (disconnected) { DrawPrompt(mp, false); return; }
|
||||
if (mp.IsReconnecting) { DrawPrompt(mp, true); return; }
|
||||
if (mp.PendingMods != null && mp.IsInSession) { DrawModsPrompt(mp); return; }
|
||||
var window = new Rect(0, 0, vw, vh);
|
||||
GUI.Box(window, GUIContent.none, windowBox);
|
||||
float glowWidth = Mathf.Min(vw * 0.9f, S(1300));
|
||||
@@ -143,6 +145,7 @@ namespace MSCO
|
||||
else if (mp.IsInSession) DrawSession(mp);
|
||||
else if (page == Page.P2P) DrawP2P(mp);
|
||||
else if (page == Page.Favorites) DrawFavorites(mp);
|
||||
else if (page == Page.Mods) DrawMods();
|
||||
else DrawServerBrowser(mp);
|
||||
GUILayout.EndArea();
|
||||
GUI.skin.verticalScrollbar = oldBar;
|
||||
@@ -200,6 +203,9 @@ namespace MSCO
|
||||
y += S(44);
|
||||
int favCount = Favorites.All.Count;
|
||||
if (NavButton(new Rect(r.x, y, r.width, S(42)), "Favorites" + (favCount > 0 ? " (" + favCount + ")" : ""), page == Page.Favorites)) page = Page.Favorites;
|
||||
y += S(44);
|
||||
int modCount = ModLoader.All.Count;
|
||||
if (NavButton(new Rect(r.x, y, r.width, S(42)), "Mods" + (modCount > 0 ? " (" + modCount + ")" : ""), page == Page.Mods)) page = Page.Mods;
|
||||
|
||||
// Eigenes Hauptmenü: Spiel beenden (die Knöpfe des Spiel-Menüs sind ausgeblendet)
|
||||
if (quitConfirm)
|
||||
@@ -287,6 +293,10 @@ namespace MSCO
|
||||
showNonSteam = Chip(showNonSteam, "NON-STEAM");
|
||||
GUILayout.Space(S(6));
|
||||
hideFull = Chip(hideFull, "HIDE FULL");
|
||||
GUILayout.Space(S(12));
|
||||
showVanilla = Chip(showVanilla, "VANILLA");
|
||||
GUILayout.Space(S(6));
|
||||
showModded = Chip(showModded, "MODDED");
|
||||
GUILayout.EndHorizontal();
|
||||
GUILayout.Space(S(6));
|
||||
|
||||
@@ -295,6 +305,7 @@ namespace MSCO
|
||||
shownServers = all.FindAll(s =>
|
||||
(s.RequireSteam ? showSteam : showNonSteam) &&
|
||||
(!hideFull || s.Players < s.MaxPlayers) &&
|
||||
(s.Modded ? showModded : showVanilla) &&
|
||||
(search.Length == 0 || (s.Name ?? "").ToLower().Contains(search) || (s.Owner ?? "").ToLower().Contains(search) ||
|
||||
(s.Tags != null && s.Tags.Exists(t => (t ?? "").Contains(search)))));
|
||||
List<ServerListEntry> servers = shownServers;
|
||||
@@ -334,6 +345,7 @@ namespace MSCO
|
||||
Favorites.Favorite fav = Favorites.FindByAddress(s.Address, s.Port);
|
||||
if (s.Featured) nameX += Pill(nameX, row, "FEATURED", pillFeaturedStyle);
|
||||
if (fav != null) nameX += Pill(nameX, row, fav.Nickname, pillMutedStyle);
|
||||
if (s.Modded) nameX += Pill(nameX, row, s.Mods.Count > 0 ? "MODS " + s.Mods.Count : "MODDED", pillStyle);
|
||||
if (s.IsLocal)
|
||||
{
|
||||
// Server auf diesem PC (localhost)
|
||||
@@ -649,6 +661,7 @@ namespace MSCO
|
||||
InfoLine("ONLINE FOR", s.UptimeSeconds >= 0 ? FormatUptime(s.UptimeSeconds) : "-", rowNameStyle);
|
||||
InfoLine("PING", s.Ping < 0 ? "-" : s.Ping + " ms", PingStyle(s.Ping));
|
||||
InfoLine("ADDRESS", s.Address + ":" + s.Port, rowNameStyle);
|
||||
if (s.Mods.Count > 0) InfoLine("MODS", string.Join(", ", s.Mods.ToArray()), textStyle);
|
||||
GUILayout.EndVertical();
|
||||
GUILayout.Space(S(16));
|
||||
|
||||
@@ -819,6 +832,85 @@ namespace MSCO
|
||||
if (GUI.Button(new Rect(r.xMax - S(32) - S(170), r.yMax - S(28) - S(42), S(170), S(42)), "OK", primaryStyle)) mp.ClearDisconnectNotice();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- Mods
|
||||
|
||||
Vector2 modsScroll;
|
||||
|
||||
/// <summary>Server verlangt Mods, die hier fehlen: zeigen, was geladen würde, und fragen.</summary>
|
||||
void DrawModsPrompt(MultiplayerManager mp)
|
||||
{
|
||||
if (texDim == null) texDim = Solid(new Color(0f, 0f, 0f, 0.6f));
|
||||
GUI.DrawTexture(new Rect(0, 0, vw, vh), texDim);
|
||||
List<MultiplayerManager.ServerModInfo> list = mp.PendingMods;
|
||||
float height = S(250) + S(46) * Mathf.Min(list.Count, 6);
|
||||
var r = new Rect(Mathf.Round((vw - S(620)) / 2f), Mathf.Round((vh - height) / 2f), S(620), height);
|
||||
UiTheme.DrawShadow(r);
|
||||
GUI.Box(r, GUIContent.none, windowBox);
|
||||
GUI.DrawTexture(new Rect(r.x, r.y, r.width, S(4)), texAccent);
|
||||
|
||||
float x = r.x + S(32), w = r.width - S(64), y = r.y + S(26);
|
||||
GUI.Label(new Rect(x, y, w, S(34)), "THIS SERVER USES MODS", titleStyle);
|
||||
y += S(44);
|
||||
GUI.Label(new Rect(x, y, w, S(44)), "To play here you need these mods. Mods are programs - they run on your PC. " +
|
||||
"Only install them from servers you trust.", textStyle);
|
||||
y += S(52);
|
||||
for (int i = 0; i < list.Count && i < 6; i++)
|
||||
{
|
||||
MultiplayerManager.ServerModInfo m = list[i];
|
||||
var row = new Rect(x, y, w, S(40));
|
||||
GUI.Box(row, GUIContent.none, rowBox);
|
||||
GUI.Label(new Rect(row.x + S(14), row.y, w * 0.55f, row.height), m.Name + (string.IsNullOrEmpty(m.Version) ? "" : " " + m.Version), rowNameStyle);
|
||||
GUI.Label(new Rect(row.x + w * 0.55f, row.y, w * 0.45f - S(14), row.height),
|
||||
(m.Size / 1024) + " KB · SHA-256 " + (m.Sha256.Length > 12 ? m.Sha256.Substring(0, 12) + "…" : m.Sha256), headerCellStyle);
|
||||
y += S(46);
|
||||
}
|
||||
if (list.Count > 6) GUI.Label(new Rect(x, y - S(6), w, S(20)), "+ " + (list.Count - 6) + " more", textStyle);
|
||||
|
||||
float by = r.yMax - S(28) - S(42);
|
||||
if (GUI.Button(new Rect(r.xMax - S(32) - S(220), by, S(220), S(42)), "INSTALL & JOIN", primaryStyle)) mp.AcceptServerMods();
|
||||
if (GUI.Button(new Rect(r.xMax - S(32) - S(220) - S(12) - S(140), by, S(140), S(42)), "CANCEL", secondaryStyle)) mp.DeclineServerMods();
|
||||
}
|
||||
|
||||
/// <summary>Seite "Mods": geladene MSCO-Mods, Ordner öffnen.</summary>
|
||||
void DrawMods()
|
||||
{
|
||||
GUILayout.BeginHorizontal();
|
||||
GUILayout.Label("Mods", titleStyle, GUILayout.ExpandWidth(true));
|
||||
if (GUILayout.Button("OPEN FOLDER", tabStyle, GUILayout.Height(S(28)), GUILayout.Width(S(130))))
|
||||
{
|
||||
try
|
||||
{
|
||||
System.IO.Directory.CreateDirectory(ModLoader.Folder);
|
||||
System.Diagnostics.Process.Start("explorer.exe", "\"" + ModLoader.Folder + "\"");
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
GUILayout.Label("Put MSCO mods (.dll) into MSCO\\Mods in the game folder and restart the game - or manage them in the MSCO Launcher. " +
|
||||
"Mods from a server are downloaded when you join it (you are asked first) and only run on that server.", textStyle);
|
||||
GUILayout.Space(S(12));
|
||||
|
||||
modsScroll = GUILayout.BeginScrollView(modsScroll, GUILayout.Height(ListHeight(S(150f), S(140f))));
|
||||
if (ModLoader.All.Count == 0) GUILayout.Label("No mods installed.", textStyle);
|
||||
foreach (ModLoader.LoadedMod m in ModLoader.All)
|
||||
{
|
||||
Rect row = GUILayoutUtility.GetRect(S(10), S(52), GUILayout.ExpandWidth(true));
|
||||
var box = new Rect(row.x, row.y + S(3), row.width, row.height - S(6));
|
||||
GUI.Box(box, GUIContent.none, rowBox);
|
||||
GUI.DrawTexture(new Rect(box.x + S(14), box.y + (box.height - S(8)) / 2f, S(8), S(8)),
|
||||
m.Error != null ? texPending : m.Active ? texOnline : texMuted);
|
||||
string name = "?", version = "", author = "";
|
||||
try { name = m.Mod.Name; version = m.Mod.Version; author = m.Mod.Author; } catch { }
|
||||
float nx = box.x + S(32);
|
||||
if (m.FromServer) nx += Pill(nx, new Rect(box.x, box.y, box.width, box.height), "SERVER", pillMutedStyle);
|
||||
GUI.Label(new Rect(nx, box.y, box.width * 0.5f, box.height), name + " " + version, rowNameStyle);
|
||||
string state = m.Error != null ? "turned off: " + m.Error : m.Active ? "active" : m.FromServer ? "only on its server" : "off";
|
||||
GUI.Label(new Rect(box.x + box.width * 0.55f, box.y, box.width * 0.45f - S(14), box.height),
|
||||
(string.IsNullOrEmpty(author) ? "" : "by " + author + " · ") + state, headerCellStyle);
|
||||
}
|
||||
GUILayout.EndScrollView();
|
||||
}
|
||||
|
||||
bool newGameConfirm;
|
||||
|
||||
/// <summary>Host: eigenen Spielstand weiterspielen oder ein neues Spiel anlegen (mit Sicherheitsabfrage).</summary>
|
||||
|
||||
Reference in New Issue
Block a user