611 lines
29 KiB
C#
611 lines
29 KiB
C#
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
|
|
namespace MSCO
|
|
{
|
|
/// <summary>
|
|
/// Multiplayer-Launcher im Hauptmenü (dunkles Design mit Seitenleiste, Statusleiste
|
|
/// und Minimieren). Seiten: P2P (Spieler hostet selbst) und Server Browser (Platzhalter,
|
|
/// dort kommt später auch Direct Connect zu dedizierten Servern hin).
|
|
/// </summary>
|
|
public class LobbyUI
|
|
{
|
|
enum Page { P2P, ServerBrowser }
|
|
|
|
const float Width = 760f;
|
|
const float Height = 460f;
|
|
const float SidebarWidth = 190f;
|
|
const float StatusHeight = 36f;
|
|
|
|
|
|
static readonly Color Background = Hex(0x17, 0x17, 0x1b);
|
|
static readonly Color Sidebar = Hex(0x0f, 0x0f, 0x12);
|
|
static readonly Color Field = Hex(0x24, 0x24, 0x2a);
|
|
static readonly Color FieldFocus = Hex(0x2e, 0x2e, 0x36);
|
|
static readonly Color Accent = Hex(0xf2, 0x6b, 0x1d);
|
|
static readonly Color AccentHover = Hex(0xff, 0x82, 0x3a);
|
|
static readonly Color Secondary = Hex(0x2c, 0x2c, 0x33);
|
|
static readonly Color SecondaryHover = Hex(0x3a, 0x3a, 0x43);
|
|
static readonly Color Muted = Hex(0x8e, 0x8e, 0x99);
|
|
static readonly Color Online = Hex(0x3d, 0xd6, 0x8c);
|
|
static readonly Color Pending = Hex(0xf2, 0xc2, 0x1d);
|
|
|
|
Page page = Page.P2P;
|
|
bool minimized;
|
|
Vector2 playerScroll;
|
|
Vector2 serverScroll;
|
|
bool browserLoaded;
|
|
string selectedServer; // "adresse:port" des geöffneten Server-Profils
|
|
string searchInput = "";
|
|
bool showSteam = true, showNonSteam = true, hideFull;
|
|
string nameInput;
|
|
string directInput;
|
|
const string PrefDirect = "MSCO_LastServer";
|
|
|
|
bool stylesReady;
|
|
Texture2D texBackground, texSidebar, texAccent, texOnline, texPending, texMuted, texRow;
|
|
GUIStyle windowBox, sidebarBox, rowBox, accentBox, pillStyle, pillMutedStyle, chipOnStyle, chipOffStyle;
|
|
GUIStyle logoStyle, taglineStyle, navStyle, navActiveStyle, titleStyle, textStyle, captionStyle,
|
|
fieldStyle, primaryStyle, secondaryStyle, statusStyle, tabStyle, headerCellStyle,
|
|
rowNameStyle, rowTagStyle, badgeStyle, toggleStyle, placeholderStyle;
|
|
|
|
/// <summary>Fläche, die das Fenster gerade belegt (GUI-Koordinaten, oben = 0).</summary>
|
|
public Rect ScreenRect { get; private set; }
|
|
|
|
/// <summary>Fenster offen (nicht minimiert) - dann ist das Spielmenü dahinter gesperrt.</summary>
|
|
public bool IsOpen { get { return !minimized; } }
|
|
|
|
/// <summary>Ist die Maus gerade über dem Fenster bzw. der eingeklappten Leiste?</summary>
|
|
public bool ContainsMouse()
|
|
{
|
|
Vector3 m = Input.mousePosition;
|
|
return ScreenRect.Contains(new Vector2(m.x, Screen.height - m.y));
|
|
}
|
|
|
|
public void Draw(MultiplayerManager mp)
|
|
{
|
|
EnsureStyles();
|
|
bool pendingJoin = mp.PendingJoinHost != null && !mp.IsInSession;
|
|
bool versionWarning = mp.PendingVersionWarning != null && !mp.IsInSession;
|
|
if (pendingJoin || versionWarning) minimized = false; // JOIN-Link / Warnung: Fenster zeigen
|
|
if (minimized) { DrawMinimized(mp); return; }
|
|
|
|
var window = new Rect((Screen.width - Width) / 2f, (Screen.height - Height) / 2f, Width, Height);
|
|
ScreenRect = window;
|
|
GUI.Box(window, GUIContent.none, windowBox);
|
|
|
|
DrawSidebar(new Rect(window.x, window.y, SidebarWidth, Height - StatusHeight));
|
|
|
|
var content = new Rect(window.x + SidebarWidth + 32, window.y + 28,
|
|
Width - SidebarWidth - 64, Height - StatusHeight - 52);
|
|
GUILayout.BeginArea(content);
|
|
if (versionWarning) DrawVersionWarning(mp);
|
|
else if (pendingJoin) DrawJoinConfirm(mp);
|
|
else if (mp.IsInSession) DrawSession(mp);
|
|
else if (page == Page.P2P) DrawP2P(mp);
|
|
else DrawServerBrowser(mp);
|
|
GUILayout.EndArea();
|
|
|
|
DrawStatusBar(mp, new Rect(window.x, window.yMax - StatusHeight, Width, StatusHeight));
|
|
}
|
|
|
|
// ---------------------------------------------------------------- Bereiche
|
|
|
|
void DrawSidebar(Rect r)
|
|
{
|
|
GUI.Box(r, GUIContent.none, sidebarBox);
|
|
// Logo + Schriftzug
|
|
Texture2D logo = Branding.LogoTexture;
|
|
float textX = r.x + 22;
|
|
if (logo != null)
|
|
{
|
|
GUI.DrawTexture(new Rect(r.x + 20, r.y + 20, 44, 44), logo);
|
|
textX = r.x + 72;
|
|
}
|
|
GUI.Label(new Rect(textX, r.y + 24, r.xMax - textX, 40), "MSCO", logoStyle);
|
|
GUI.Label(new Rect(r.x + 22, r.y + 70, r.width - 30, 20), "MY SUMMER CAR ONLINE", taglineStyle);
|
|
|
|
float y = r.y + 110;
|
|
if (NavButton(new Rect(r.x, y, r.width, 42), "P2P", page == Page.P2P)) page = Page.P2P;
|
|
y += 44;
|
|
if (NavButton(new Rect(r.x, y, r.width, 42), "Server Browser", page == Page.ServerBrowser)) page = Page.ServerBrowser;
|
|
|
|
GUI.Label(new Rect(r.x + 24, r.yMax - 30, r.width - 48, 20), "v" + MSCO.ModVersion, taglineStyle);
|
|
}
|
|
|
|
bool NavButton(Rect r, string text, bool active)
|
|
{
|
|
if (active) GUI.Box(new Rect(r.x + 4, r.y + 8, 4, r.height - 16), GUIContent.none, accentBox); // Akzentbalken links
|
|
return GUI.Button(r, text, active ? navActiveStyle : navStyle);
|
|
}
|
|
|
|
void DrawP2P(MultiplayerManager mp)
|
|
{
|
|
GUILayout.Label("P2P", titleStyle);
|
|
GUILayout.Label("Host a game from your own save and invite up to " + (MultiplayerManager.MaxPlayers - 1) +
|
|
" friends through Steam.", textStyle);
|
|
GUILayout.Space(18);
|
|
|
|
if (mp.SteamAvailable)
|
|
{
|
|
if (GUILayout.Button("HOST GAME", primaryStyle, GUILayout.Height(46), GUILayout.Width(250)))
|
|
mp.HostSteam();
|
|
GUILayout.Space(14);
|
|
GUILayout.Label("To join a friend: accept their invite in Steam, or right-click them in your " +
|
|
"Steam friends list and choose \"Join Game\".", textStyle);
|
|
}
|
|
else
|
|
{
|
|
GUILayout.Label("Steam is not running - start the game through Steam to host or join.", textStyle);
|
|
}
|
|
}
|
|
|
|
void DrawServerBrowser(MultiplayerManager mp)
|
|
{
|
|
ServerBrowser browser = mp.Browser;
|
|
// Beim ersten Öffnen laden, danach automatisch alle 15 s (Spielerzahl, Ping, neue Server)
|
|
if (!browserLoaded || (!browser.Loading && Time.realtimeSinceStartup - browser.LastRefresh > ServerBrowser.AutoRefreshSeconds))
|
|
{
|
|
browserLoaded = true;
|
|
browser.Refresh(mp);
|
|
}
|
|
|
|
// Server, die auf die direkte Abfrage nicht antworten (z.B. gerade geschlossen), gar nicht zeigen
|
|
List<ServerListEntry> all = browser.Servers.FindAll(s => !s.Offline);
|
|
|
|
// Server-Profil (Klick auf einen Server)
|
|
if (selectedServer != null)
|
|
{
|
|
ServerListEntry selected = all.Find(s => s.Address + ":" + s.Port == selectedServer);
|
|
DrawServerProfile(mp, selected);
|
|
return;
|
|
}
|
|
|
|
GUILayout.BeginHorizontal();
|
|
// Titel darf nicht vom REFRESH-Button zusammengedrückt werden (sonst Zeilenumbruch)
|
|
GUILayout.Label("Server Browser", titleStyle, GUILayout.ExpandWidth(true));
|
|
if (GUILayout.Button(browser.Loading ? "LOADING..." : "REFRESH", tabStyle, GUILayout.Height(28), GUILayout.Width(110)))
|
|
browser.Refresh(mp);
|
|
GUILayout.EndHorizontal();
|
|
|
|
// Suche + Filter
|
|
GUILayout.BeginHorizontal();
|
|
searchInput = GUILayout.TextField(searchInput, 40, fieldStyle, GUILayout.Height(26), GUILayout.Width(170));
|
|
if (string.IsNullOrEmpty(searchInput))
|
|
{
|
|
Rect field = GUILayoutUtility.GetLastRect();
|
|
GUI.Label(new Rect(field.x + 10, field.y, field.width, field.height), "Search...", placeholderStyle);
|
|
}
|
|
GUILayout.Space(12);
|
|
showSteam = Chip(showSteam, "STEAM");
|
|
GUILayout.Space(6);
|
|
showNonSteam = Chip(showNonSteam, "NON-STEAM");
|
|
GUILayout.Space(6);
|
|
hideFull = Chip(hideFull, "HIDE FULL");
|
|
GUILayout.EndHorizontal();
|
|
GUILayout.Space(6);
|
|
|
|
string search = (searchInput ?? "").Trim().ToLower();
|
|
List<ServerListEntry> servers = all.FindAll(s =>
|
|
(s.RequireSteam ? showSteam : showNonSteam) &&
|
|
(!hideFull || s.Players < s.MaxPlayers) &&
|
|
(search.Length == 0 || (s.Name ?? "").ToLower().Contains(search) || (s.Owner ?? "").ToLower().Contains(search)));
|
|
|
|
// Tabellenkopf
|
|
Rect header = GUILayoutUtility.GetRect(10, 26, GUILayout.ExpandWidth(true));
|
|
GUI.Box(header, GUIContent.none, rowBox);
|
|
GUI.Label(new Rect(header.x + 12, header.y, header.width * 0.48f, header.height), "SERVER NAME", headerCellStyle);
|
|
GUI.Label(new Rect(header.x + header.width * 0.5f, header.y, 70, header.height), "PLAYERS", headerCellStyle);
|
|
GUI.Label(new Rect(header.x + header.width * 0.62f, header.y, 70, header.height), "TYPE", headerCellStyle);
|
|
GUI.Label(new Rect(header.x + header.width * 0.76f, header.y, 50, header.height), "PING", headerCellStyle);
|
|
|
|
serverScroll = GUILayout.BeginScrollView(serverScroll, GUILayout.Height(140));
|
|
if (browser.Error != null) GUILayout.Label(browser.Error, textStyle);
|
|
if (servers.Count == 0)
|
|
GUILayout.Label(browser.Loading ? "Loading servers..." : all.Count > 0 ? "No servers match your filters." : "No servers online right now.", textStyle);
|
|
foreach (ServerListEntry s in servers)
|
|
{
|
|
Rect row = GUILayoutUtility.GetRect(10, 34, GUILayout.ExpandWidth(true));
|
|
GUI.Box(new Rect(row.x, row.y + 2, row.width, row.height - 4), GUIContent.none, rowBox);
|
|
|
|
// Klick auf die Zeile (außer JOIN) öffnet das Server-Profil
|
|
if (GUI.Button(new Rect(row.x, row.y, row.width - 82, row.height), GUIContent.none, GUIStyle.none))
|
|
selectedServer = s.Address + ":" + s.Port;
|
|
|
|
GUI.DrawTexture(new Rect(row.x + 10, row.y + 13, 8, 8), s.Ping < 0 ? texPending : texOnline);
|
|
float nameX = row.x + 26;
|
|
if (s.IsLocal)
|
|
{
|
|
// Server auf diesem PC (wie bei FiveM "localhost")
|
|
nameX += Pill(nameX, row, "LOCAL", pillStyle);
|
|
}
|
|
if (!string.IsNullOrEmpty(s.Version) && s.Version != MSCO.ModVersion)
|
|
{
|
|
nameX += Pill(nameX, row, "v" + s.Version, pillStyle); // abweichende Version
|
|
}
|
|
GUI.Label(new Rect(nameX, row.y, row.x + row.width * 0.49f - nameX, row.height), s.Name, rowNameStyle);
|
|
GUI.Label(new Rect(row.x + row.width * 0.5f, row.y, 70, row.height), s.Players + "/" + s.MaxPlayers, rowNameStyle);
|
|
Pill(row.x + row.width * 0.62f, row, s.RequireSteam ? "STEAM" : "NO STEAM", s.RequireSteam ? pillStyle : pillMutedStyle);
|
|
GUI.Label(new Rect(row.x + row.width * 0.76f, row.y, 50, row.height), s.Ping < 0 ? "..." : s.Ping + " ms", rowNameStyle);
|
|
|
|
// Andere Version: trotzdem joinbar, beim Verbinden kommt eine Warnung
|
|
GUI.enabled = CanJoin(mp, s);
|
|
if (GUI.Button(new Rect(row.xMax - 76, row.y + 4, 70, row.height - 8), s.Players >= s.MaxPlayers ? "FULL" : "JOIN", primaryStyle))
|
|
mp.JoinServer(s.Address, s.Port, s.Name, s.MaxPlayers);
|
|
GUI.enabled = true;
|
|
}
|
|
GUILayout.EndScrollView();
|
|
|
|
GUILayout.FlexibleSpace();
|
|
GUILayout.BeginHorizontal();
|
|
if (!mp.SteamAvailable)
|
|
{
|
|
// Ohne Steam: eigener Name (nur für Non-Steam-Server)
|
|
GUILayout.BeginVertical(GUILayout.Width(150));
|
|
GUILayout.Label("YOUR NAME", captionStyle);
|
|
DrawNameField(mp, 150);
|
|
GUILayout.EndVertical();
|
|
GUILayout.Space(10);
|
|
}
|
|
GUILayout.BeginVertical();
|
|
GUILayout.Label("DIRECT CONNECT", captionStyle);
|
|
GUILayout.BeginHorizontal();
|
|
directInput = GUILayout.TextField(directInput, fieldStyle, GUILayout.Height(34));
|
|
GUILayout.Space(10);
|
|
if (GUILayout.Button("CONNECT", primaryStyle, GUILayout.Height(34), GUILayout.Width(110)))
|
|
{
|
|
string host;
|
|
int port;
|
|
if (ServerBrowser.TryParseAddress(directInput, out host, out port))
|
|
{
|
|
PlayerPrefs.SetString(PrefDirect, directInput.Trim());
|
|
mp.JoinServer(host, port);
|
|
}
|
|
}
|
|
GUILayout.EndHorizontal();
|
|
GUILayout.EndVertical();
|
|
GUILayout.EndHorizontal();
|
|
}
|
|
|
|
static bool CanJoin(MultiplayerManager mp, ServerListEntry s)
|
|
{
|
|
return s.Players < s.MaxPlayers && (!s.RequireSteam || mp.SteamAvailable);
|
|
}
|
|
|
|
void DrawNameField(MultiplayerManager mp, float width)
|
|
{
|
|
if (nameInput == null) nameInput = mp.PlayerName;
|
|
string edited = GUILayout.TextField(nameInput, 24, fieldStyle, GUILayout.Height(34), GUILayout.Width(width));
|
|
if (edited != nameInput)
|
|
{
|
|
nameInput = edited;
|
|
if (edited.Trim().Length > 0) mp.PlayerName = edited.Trim();
|
|
}
|
|
}
|
|
|
|
/// <summary>Server-Profil wie bei FiveM: wem der Server gehört, Version, Spieler, Laufzeit ...</summary>
|
|
void DrawServerProfile(MultiplayerManager mp, ServerListEntry s)
|
|
{
|
|
if (GUILayout.Button("◂ BACK", tabStyle, GUILayout.Height(26), GUILayout.Width(90)))
|
|
{
|
|
selectedServer = null;
|
|
return;
|
|
}
|
|
GUILayout.Space(6);
|
|
|
|
if (s == null)
|
|
{
|
|
GUILayout.Label("Server offline", titleStyle);
|
|
GUILayout.Label("This server does not respond anymore.", textStyle);
|
|
return;
|
|
}
|
|
|
|
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();
|
|
GUILayout.EndHorizontal();
|
|
if (!string.IsNullOrEmpty(s.Description)) GUILayout.Label(s.Description, textStyle);
|
|
GUILayout.Space(10);
|
|
|
|
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);
|
|
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.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();
|
|
GUILayout.BeginHorizontal();
|
|
GUI.enabled = CanJoin(mp, s);
|
|
if (GUILayout.Button(s.Players >= s.MaxPlayers ? "SERVER FULL" : "JOIN SERVER", primaryStyle, GUILayout.Height(42), GUILayout.Width(190)))
|
|
mp.JoinServer(s.Address, s.Port, s.Name, s.MaxPlayers);
|
|
GUI.enabled = true;
|
|
GUILayout.Space(12);
|
|
if (s.RequireSteam && !mp.SteamAvailable)
|
|
GUILayout.Label("This server requires Steam.", textStyle);
|
|
else if (!mp.SteamAvailable)
|
|
{
|
|
GUILayout.Label("NAME", captionStyle, GUILayout.Width(42));
|
|
DrawNameField(mp, 150);
|
|
}
|
|
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)
|
|
{
|
|
Vector2 size = style.CalcSize(new GUIContent(text));
|
|
GUI.Label(new Rect(x, row.y + (row.height - 18) / 2f, size.x, 18), text, style);
|
|
return size.x + 8;
|
|
}
|
|
|
|
/// <summary>Filter-Chip (an = orange umrandet), ersetzt die Unity-Häkchen.</summary>
|
|
bool Chip(bool on, string text)
|
|
{
|
|
if (GUILayout.Button(text, on ? chipOnStyle : chipOffStyle, GUILayout.Height(26), GUILayout.ExpandWidth(false))) on = !on;
|
|
return on;
|
|
}
|
|
|
|
void InfoRow(string caption, string value, GUIStyle valueStyle)
|
|
{
|
|
GUILayout.Label(caption, captionStyle);
|
|
GUILayout.Label(value, valueStyle);
|
|
GUILayout.Space(2);
|
|
}
|
|
|
|
static string FormatUptime(int seconds)
|
|
{
|
|
int days = seconds / 86400, hours = seconds / 3600 % 24, minutes = seconds / 60 % 60;
|
|
if (days > 0) return days + "d " + hours + "h";
|
|
if (hours > 0) return hours + "h " + minutes + "m";
|
|
return Mathf.Max(1, minutes) + " min";
|
|
}
|
|
|
|
/// <summary>Server hat eine andere Version: warnen, Verbinden nur auf Wunsch.</summary>
|
|
void DrawVersionWarning(MultiplayerManager mp)
|
|
{
|
|
MultiplayerManager.VersionWarning w = mp.PendingVersionWarning;
|
|
GUILayout.Label("Version mismatch", titleStyle);
|
|
GUILayout.Label("This server runs a different MSCO version than you. You can still join, but things may not " +
|
|
"sync correctly (players, items, cars). For the best experience use the same version.", textStyle);
|
|
GUILayout.Space(16);
|
|
|
|
GUILayout.Label("SERVER", captionStyle);
|
|
GUILayout.Label(w.Name + " (" + w.Host + ":" + w.Port + ")", rowNameStyle);
|
|
GUILayout.Space(8);
|
|
GUILayout.Label("VERSIONS", captionStyle);
|
|
GUILayout.Label("Server v" + w.ServerVersion + " - You v" + MSCO.ModVersion, rowTagStyle);
|
|
GUILayout.Space(22);
|
|
|
|
GUILayout.BeginHorizontal();
|
|
if (GUILayout.Button("JOIN ANYWAY", primaryStyle, GUILayout.Height(42), GUILayout.Width(170)))
|
|
mp.ConfirmVersionWarning();
|
|
GUILayout.Space(10);
|
|
if (GUILayout.Button("CANCEL", secondaryStyle, GUILayout.Height(42), GUILayout.Width(170)))
|
|
mp.CancelVersionWarning();
|
|
GUILayout.EndHorizontal();
|
|
}
|
|
|
|
/// <summary>Nachfrage nach einem JOIN-Link der Website (keine Webseite soll einen ungefragt verbinden).</summary>
|
|
void DrawJoinConfirm(MultiplayerManager mp)
|
|
{
|
|
GUILayout.Label("Join server?", titleStyle);
|
|
GUILayout.Label("You opened a join link from the server list.", textStyle);
|
|
GUILayout.Space(18);
|
|
|
|
GUILayout.Label("SERVER ADDRESS", captionStyle);
|
|
GUILayout.Label(mp.PendingJoinHost + ":" + mp.PendingJoinPort, rowNameStyle);
|
|
GUILayout.Space(24);
|
|
|
|
if (!mp.SteamAvailable)
|
|
GUILayout.Label("Steam is required to join servers - start the game through Steam.", textStyle);
|
|
|
|
GUILayout.BeginHorizontal();
|
|
GUI.enabled = mp.SteamAvailable;
|
|
if (GUILayout.Button("JOIN", primaryStyle, GUILayout.Height(42), GUILayout.Width(170)))
|
|
mp.ConfirmPendingJoin();
|
|
GUI.enabled = true;
|
|
GUILayout.Space(10);
|
|
if (GUILayout.Button("CANCEL", secondaryStyle, GUILayout.Height(42), GUILayout.Width(170)))
|
|
mp.CancelPendingJoin();
|
|
GUILayout.EndHorizontal();
|
|
}
|
|
|
|
void DrawSession(MultiplayerManager mp)
|
|
{
|
|
GUILayout.Label(mp.IsHost ? "Hosting" : "Connected", titleStyle);
|
|
GUILayout.Label(mp.IsHost
|
|
? "Your game is open. Start when everyone has joined."
|
|
: "Waiting for the host to start the game.", textStyle);
|
|
GUILayout.Space(16);
|
|
|
|
GUILayout.Label("PLAYERS " + mp.Players.Count + "/" + MultiplayerManager.MaxPlayers, captionStyle);
|
|
playerScroll = GUILayout.BeginScrollView(playerScroll, GUILayout.Height(170));
|
|
foreach (PlayerInfo p in mp.Players)
|
|
{
|
|
Rect row = GUILayoutUtility.GetRect(10, 32, GUILayout.ExpandWidth(true));
|
|
GUI.Box(new Rect(row.x, row.y, row.width, row.height - 4), GUIContent.none, rowBox);
|
|
GUI.DrawTexture(new Rect(row.x + 12, row.y + 11, 8, 8), texOnline);
|
|
GUI.Label(new Rect(row.x + 30, row.y, row.width - 150, row.height - 2), p.Name, rowNameStyle);
|
|
string tag = p.Id == NetSession.HostId ? "HOST" : "";
|
|
if (p.IsLocal) tag = tag.Length > 0 ? tag + " · YOU" : "YOU";
|
|
GUI.Label(new Rect(row.xMax - 120, row.y, 110, row.height - 2), tag, rowTagStyle);
|
|
}
|
|
GUILayout.EndScrollView();
|
|
GUILayout.Space(14);
|
|
|
|
GUILayout.BeginHorizontal();
|
|
if (mp.IsHost)
|
|
{
|
|
if (GUILayout.Button("START GAME", primaryStyle, GUILayout.Height(42), GUILayout.Width(150)))
|
|
mp.StartHostGame();
|
|
GUILayout.Space(10);
|
|
if (mp.IsSteamSession && mp.Players.Count < MultiplayerManager.MaxPlayers &&
|
|
GUILayout.Button("INVITE FRIENDS", secondaryStyle, GUILayout.Height(42), GUILayout.Width(150)))
|
|
mp.InviteFriends();
|
|
GUILayout.Space(10);
|
|
}
|
|
if (GUILayout.Button("DISCONNECT", secondaryStyle, GUILayout.Height(42), GUILayout.Width(150)))
|
|
mp.Disconnect();
|
|
GUILayout.EndHorizontal();
|
|
}
|
|
|
|
void DrawStatusBar(MultiplayerManager mp, Rect r)
|
|
{
|
|
GUI.Box(r, GUIContent.none, sidebarBox);
|
|
Texture2D dot = mp.IsPeerReady ? texOnline : mp.IsInSession ? texPending : texMuted;
|
|
GUI.DrawTexture(new Rect(r.x + 18, r.y + (r.height - 8) / 2f, 8, 8), dot);
|
|
GUI.Label(new Rect(r.x + 36, r.y, r.width - 180, r.height), mp.Status, statusStyle);
|
|
if (GUI.Button(new Rect(r.xMax - 118, r.y + 6, 106, r.height - 12), "MINIMIZE", tabStyle))
|
|
minimized = true;
|
|
}
|
|
|
|
void DrawMinimized(MultiplayerManager mp)
|
|
{
|
|
var r = new Rect((Screen.width - 220) / 2f, 0, 220, 30);
|
|
ScreenRect = r;
|
|
GUI.Box(r, GUIContent.none, sidebarBox);
|
|
GUI.Box(new Rect(r.x + 10, r.yMax - 3, r.width - 20, 3), GUIContent.none, accentBox);
|
|
Texture2D dot = mp.IsPeerReady ? texOnline : mp.IsInSession ? texPending : texMuted;
|
|
Texture2D logo = Branding.LogoTexture;
|
|
if (logo != null) GUI.DrawTexture(new Rect(r.x + 8, r.y + 4, 22, 22), logo);
|
|
GUI.DrawTexture(new Rect(r.x + 36, r.y + 11, 8, 8), dot);
|
|
if (GUI.Button(new Rect(r.x + 48, r.y, r.width - 48, r.height), "MSCO MULTIPLAYER ▾", tabStyle))
|
|
minimized = false;
|
|
}
|
|
|
|
// ---------------------------------------------------------------- Hilfen
|
|
|
|
void EnsureStyles()
|
|
{
|
|
if (stylesReady && texBackground != null) return;
|
|
|
|
// PlayerPrefs erst hier lesen (nicht im Konstruktor erlaubt)
|
|
directInput = PlayerPrefs.GetString(PrefDirect, "");
|
|
|
|
texBackground = Solid(Background);
|
|
texSidebar = Solid(Sidebar);
|
|
texAccent = Solid(Accent);
|
|
texOnline = UiTheme.Rounded(Online, 4); // runde Status-Punkte
|
|
texPending = UiTheme.Rounded(Pending, 4);
|
|
texMuted = UiTheme.Rounded(Muted, 4);
|
|
texRow = Solid(Field);
|
|
windowBox = UiTheme.Panel9(Background, 10);
|
|
sidebarBox = UiTheme.Panel9(Sidebar, 10);
|
|
rowBox = UiTheme.Panel9(Field, UiTheme.Radius);
|
|
accentBox = UiTheme.Panel9(Accent, 1);
|
|
|
|
pillStyle = UiTheme.Panel9(new Color(Accent.r, Accent.g, Accent.b, 0.18f), 4);
|
|
pillStyle.normal.textColor = Accent;
|
|
pillStyle.fontSize = 10;
|
|
pillStyle.fontStyle = FontStyle.Bold;
|
|
pillStyle.alignment = TextAnchor.MiddleCenter;
|
|
pillStyle.padding = new RectOffset(7, 7, 0, 0);
|
|
pillStyle.wordWrap = false;
|
|
pillMutedStyle = new GUIStyle(pillStyle);
|
|
pillMutedStyle.normal.background = UiTheme.Rounded(new Color(1f, 1f, 1f, 0.08f), 4);
|
|
pillMutedStyle.normal.textColor = Muted;
|
|
|
|
chipOnStyle = Button(new Color(Accent.r, Accent.g, Accent.b, 0.22f), new Color(Accent.r, Accent.g, Accent.b, 0.32f), AccentHover, 11, FontStyle.Bold);
|
|
chipOffStyle = Button(Field, FieldFocus, Muted, 11, FontStyle.Bold);
|
|
chipOnStyle.padding = chipOffStyle.padding = new RectOffset(12, 12, 0, 0);
|
|
|
|
logoStyle = Label(28, Color.white, FontStyle.Bold);
|
|
taglineStyle = Label(10, Muted, FontStyle.Bold);
|
|
titleStyle = Label(24, Color.white, FontStyle.Bold);
|
|
textStyle = Label(13, Muted, FontStyle.Normal);
|
|
textStyle.wordWrap = true;
|
|
captionStyle = Label(11, Muted, FontStyle.Bold);
|
|
captionStyle.margin.bottom = 6;
|
|
statusStyle = Label(12, Color.white, FontStyle.Normal);
|
|
statusStyle.alignment = TextAnchor.MiddleLeft;
|
|
headerCellStyle = Label(11, Muted, FontStyle.Bold);
|
|
headerCellStyle.alignment = TextAnchor.MiddleLeft;
|
|
rowNameStyle = Label(14, Color.white, FontStyle.Normal);
|
|
rowNameStyle.alignment = TextAnchor.MiddleLeft;
|
|
rowTagStyle = Label(11, Accent, FontStyle.Bold);
|
|
rowTagStyle.alignment = TextAnchor.MiddleRight;
|
|
badgeStyle = new GUIStyle(rowTagStyle);
|
|
badgeStyle.alignment = TextAnchor.MiddleLeft;
|
|
badgeStyle.padding = new RectOffset(6, 6, 2, 2);
|
|
placeholderStyle = Label(13, Muted, FontStyle.Italic);
|
|
placeholderStyle.alignment = TextAnchor.MiddleLeft;
|
|
toggleStyle = new GUIStyle(GUI.skin.toggle);
|
|
toggleStyle.normal.textColor = toggleStyle.onNormal.textColor = toggleStyle.hover.textColor = toggleStyle.onHover.textColor = Color.white;
|
|
toggleStyle.fontSize = 11;
|
|
toggleStyle.fontStyle = FontStyle.Bold;
|
|
|
|
navStyle = Button(Sidebar, Secondary, Muted, 14, FontStyle.Normal);
|
|
navStyle.alignment = TextAnchor.MiddleLeft;
|
|
navStyle.padding = new RectOffset(26, 10, 0, 0);
|
|
navActiveStyle = Button(Secondary, Secondary, Color.white, 14, FontStyle.Bold);
|
|
navActiveStyle.alignment = TextAnchor.MiddleLeft;
|
|
navActiveStyle.padding = new RectOffset(26, 10, 0, 0);
|
|
|
|
primaryStyle = Button(Accent, AccentHover, Color.white, 14, FontStyle.Bold);
|
|
secondaryStyle = Button(Secondary, SecondaryHover, Color.white, 14, FontStyle.Bold);
|
|
tabStyle = Button(Sidebar, Secondary, Muted, 11, FontStyle.Bold);
|
|
|
|
fieldStyle = UiTheme.Field(14);
|
|
|
|
stylesReady = true;
|
|
}
|
|
|
|
static GUIStyle Label(int size, Color color, FontStyle font)
|
|
{
|
|
var s = new GUIStyle(GUI.skin.label);
|
|
s.fontSize = size;
|
|
s.fontStyle = font;
|
|
s.normal.textColor = color;
|
|
return s;
|
|
}
|
|
|
|
static GUIStyle Button(Color normal, Color hover, Color text, int size, FontStyle font)
|
|
{
|
|
var s = new GUIStyle(GUI.skin.button);
|
|
// abgerundete Ecken (9-Slice), siehe UiTheme
|
|
s.normal.background = UiTheme.Rounded(normal);
|
|
s.hover.background = UiTheme.Rounded(hover);
|
|
s.active.background = UiTheme.Rounded(hover);
|
|
s.focused.background = UiTheme.Rounded(normal);
|
|
s.normal.textColor = s.hover.textColor = s.active.textColor = s.focused.textColor = text;
|
|
s.border = new RectOffset(UiTheme.Radius + 2, UiTheme.Radius + 2, UiTheme.Radius + 2, UiTheme.Radius + 2);
|
|
s.fontSize = size;
|
|
s.fontStyle = font;
|
|
return s;
|
|
}
|
|
|
|
static Texture2D Solid(Color color)
|
|
{
|
|
var tex = new Texture2D(1, 1);
|
|
tex.SetPixel(0, 0, color);
|
|
tex.Apply();
|
|
tex.hideFlags = HideFlags.HideAndDontSave;
|
|
return tex;
|
|
}
|
|
|
|
static Color Hex(int r, int g, int b)
|
|
{
|
|
return new Color(r / 255f, g / 255f, b / 255f, 1f);
|
|
}
|
|
}
|
|
}
|