Favorites: heart toggle, Favorites tab with nicknames, /switch <nickname> quick server switch, fav command
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+183
-2
@@ -10,7 +10,7 @@ namespace MSCO
|
||||
/// </summary>
|
||||
public class LobbyUI
|
||||
{
|
||||
enum Page { P2P, ServerBrowser }
|
||||
enum Page { P2P, ServerBrowser, Favorites }
|
||||
|
||||
const float Width = 760f;
|
||||
const float Height = 460f;
|
||||
@@ -40,6 +40,10 @@ namespace MSCO
|
||||
string selectedServer; // "adresse:port" des geöffneten Server-Profils
|
||||
string searchInput = "";
|
||||
bool showSteam = true, showNonSteam = true, hideFull;
|
||||
readonly Dictionary<string, string> nicknameEdits = new Dictionary<string, string>(); // Favoriten-Tab: Adresse -> bearbeiteter Kurzname
|
||||
string favoriteAddInput = "";
|
||||
Vector2 favoritesScroll;
|
||||
Texture2D texHeartOn, texHeartOff;
|
||||
string nameInput;
|
||||
string directInput;
|
||||
const string PrefDirect = "MSCO_LastServer";
|
||||
@@ -92,6 +96,7 @@ namespace MSCO
|
||||
else if (pendingJoin) DrawJoinConfirm(mp);
|
||||
else if (mp.IsInSession) DrawSession(mp);
|
||||
else if (page == Page.P2P) DrawP2P(mp);
|
||||
else if (page == Page.Favorites) DrawFavorites(mp);
|
||||
else DrawServerBrowser(mp);
|
||||
GUILayout.EndArea();
|
||||
|
||||
@@ -118,6 +123,9 @@ namespace MSCO
|
||||
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;
|
||||
y += 44;
|
||||
int favCount = Favorites.All.Count;
|
||||
if (NavButton(new Rect(r.x, y, r.width, 42), "Favorites" + (favCount > 0 ? " (" + favCount + ")" : ""), page == Page.Favorites)) page = Page.Favorites;
|
||||
|
||||
// Eigenes Hauptmenü: Spiel beenden (die Knöpfe des Spiel-Menüs sind ausgeblendet)
|
||||
if (quitConfirm)
|
||||
@@ -232,12 +240,17 @@ namespace MSCO
|
||||
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);
|
||||
|
||||
// Herz: als Favorit merken / entfernen (vor dem Zeilen-Knopf, damit es den Klick bekommt)
|
||||
Heart(new Rect(row.xMax - 58, row.y + 7, 20, 20), s);
|
||||
|
||||
// Klick auf die Zeile öffnet das Server-Profil (dort wird verbunden)
|
||||
if (GUI.Button(new Rect(row.x, row.y, row.width, 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;
|
||||
Favorites.Favorite fav = Favorites.FindByAddress(s.Address, s.Port);
|
||||
if (fav != null) nameX += Pill(nameX, row, fav.Nickname, pillMutedStyle);
|
||||
if (s.IsLocal)
|
||||
{
|
||||
// Server auf diesem PC (localhost)
|
||||
@@ -302,6 +315,168 @@ namespace MSCO
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- Favoriten
|
||||
|
||||
/// <summary>Herz: gefüllt = Favorit. Klick merkt den Server (Kurzname aus dem Namen) bzw. entfernt ihn.</summary>
|
||||
void Heart(Rect r, ServerListEntry s)
|
||||
{
|
||||
EnsureHearts();
|
||||
Favorites.Favorite fav = Favorites.FindByAddress(s.Address, s.Port);
|
||||
bool hover = r.Contains(Event.current.mousePosition);
|
||||
GUI.DrawTexture(r, fav != null || hover ? texHeartOn : texHeartOff);
|
||||
if (GUI.Button(r, GUIContent.none, GUIStyle.none))
|
||||
{
|
||||
if (fav != null) Favorites.Remove(fav);
|
||||
else AddFavorite(s.Name, s.Address, s.Port);
|
||||
}
|
||||
}
|
||||
|
||||
static void AddFavorite(string serverName, string host, int port)
|
||||
{
|
||||
// Kurzname aus dem Servernamen; ist er schon vergeben, eine Zahl anhängen
|
||||
string baseNick = Favorites.SuggestNickname(serverName), nick = baseNick;
|
||||
for (int i = 2; Favorites.Find(nick) != null && i < 100; i++) nick = baseNick + i;
|
||||
string error = Favorites.Set(nick, host, port, serverName);
|
||||
if (error != null) MscoLog.Warning(error);
|
||||
}
|
||||
|
||||
/// <summary>Tab "Favorites": Kurznamen setzen, beitreten, entfernen, per Adresse hinzufügen.</summary>
|
||||
void DrawFavorites(MultiplayerManager mp)
|
||||
{
|
||||
EnsureHearts();
|
||||
GUILayout.Label("Favorites", titleStyle);
|
||||
GUILayout.Label("Give each server a nickname - switch in the game with /switch <nickname> in the chat or F7 console.", textStyle);
|
||||
GUILayout.Space(8);
|
||||
|
||||
List<Favorites.Favorite> favs = Favorites.All;
|
||||
favoritesScroll = GUILayout.BeginScrollView(favoritesScroll, GUILayout.Height(220));
|
||||
if (favs.Count == 0)
|
||||
GUILayout.Label("No favorites yet. Click the heart next to a server in the Server Browser, or add one by address below.", textStyle);
|
||||
Favorites.Favorite remove = null;
|
||||
foreach (Favorites.Favorite f in favs)
|
||||
{
|
||||
ServerListEntry live = mp.Browser.Servers.Find(x => x.Port == f.Port && string.Equals(x.Address, f.Host, System.StringComparison.OrdinalIgnoreCase));
|
||||
GUILayout.BeginHorizontal(rowBox, GUILayout.Height(44));
|
||||
GUILayout.Space(6);
|
||||
|
||||
// Herz (entfernen)
|
||||
Rect heart = GUILayoutUtility.GetRect(22, 22, GUILayout.Width(22), GUILayout.Height(40));
|
||||
Rect heartIcon = new Rect(heart.x, heart.y + 10, 20, 20);
|
||||
GUI.DrawTexture(heartIcon, texHeartOn);
|
||||
if (GUI.Button(heartIcon, GUIContent.none, GUIStyle.none)) remove = f;
|
||||
GUILayout.Space(8);
|
||||
|
||||
// Kurzname bearbeiten
|
||||
string edit;
|
||||
if (!nicknameEdits.TryGetValue(f.Address, out edit)) edit = f.Nickname;
|
||||
GUILayout.BeginVertical(GUILayout.Width(150));
|
||||
GUILayout.Space(7);
|
||||
edit = GUILayout.TextField(edit, 20, fieldStyle, GUILayout.Height(28), GUILayout.Width(150));
|
||||
GUILayout.EndVertical();
|
||||
nicknameEdits[f.Address] = edit;
|
||||
GUILayout.Space(6);
|
||||
bool changed = Favorites.CleanNickname(edit) != f.Nickname;
|
||||
GUILayout.BeginVertical(GUILayout.Width(64));
|
||||
GUILayout.Space(7);
|
||||
GUI.enabled = changed;
|
||||
if (GUILayout.Button("SAVE", tabStyle, GUILayout.Height(28), GUILayout.Width(64)))
|
||||
{
|
||||
string error = Favorites.Set(edit, f.Host, f.Port, f.ServerName);
|
||||
if (error != null) MscoLog.Warning(error);
|
||||
nicknameEdits.Remove(f.Address);
|
||||
}
|
||||
GUI.enabled = true;
|
||||
GUILayout.EndVertical();
|
||||
GUILayout.Space(10);
|
||||
|
||||
// Server + Status
|
||||
GUILayout.BeginVertical();
|
||||
GUILayout.Space(4);
|
||||
GUILayout.Label(string.IsNullOrEmpty(f.ServerName) ? f.Address : f.ServerName, rowNameStyle);
|
||||
GUILayout.Label(live == null ? f.Address + " · not listed / offline"
|
||||
: f.Address + " · " + live.Players + "/" + live.MaxPlayers + " · " + (live.Ping < 0 ? "..." : live.Ping + " ms"), textStyle);
|
||||
GUILayout.EndVertical();
|
||||
GUILayout.FlexibleSpace();
|
||||
|
||||
GUILayout.BeginVertical(GUILayout.Width(90));
|
||||
GUILayout.Space(7);
|
||||
if (GUILayout.Button("JOIN", primaryStyle, GUILayout.Height(30), GUILayout.Width(90))) mp.SwitchServer(f.Nickname);
|
||||
GUILayout.EndVertical();
|
||||
GUILayout.Space(6);
|
||||
GUILayout.EndHorizontal();
|
||||
GUILayout.Space(4);
|
||||
}
|
||||
if (remove != null) { Favorites.Remove(remove); nicknameEdits.Remove(remove.Address); }
|
||||
GUILayout.EndScrollView();
|
||||
|
||||
GUILayout.FlexibleSpace();
|
||||
GUILayout.Label("ADD BY ADDRESS", captionStyle);
|
||||
GUILayout.BeginHorizontal();
|
||||
favoriteAddInput = GUILayout.TextField(favoriteAddInput ?? "", 60, fieldStyle, GUILayout.Height(34));
|
||||
if (string.IsNullOrEmpty(favoriteAddInput))
|
||||
{
|
||||
Rect field = GUILayoutUtility.GetLastRect();
|
||||
GUI.Label(new Rect(field.x + 10, field.y, field.width, field.height), "host:port", placeholderStyle);
|
||||
}
|
||||
GUILayout.Space(10);
|
||||
if (GUILayout.Button("ADD", secondaryStyle, GUILayout.Height(34), GUILayout.Width(110)))
|
||||
{
|
||||
string host;
|
||||
int port;
|
||||
if (ServerBrowser.TryParseAddress(favoriteAddInput, out host, out port))
|
||||
{
|
||||
ServerListEntry live = mp.Browser.Servers.Find(x => x.Port == port && x.Address == host);
|
||||
AddFavorite(live != null ? live.Name : host, host, port);
|
||||
favoriteAddInput = "";
|
||||
}
|
||||
else MscoLog.Warning("Enter an address like 1.2.3.4:7777");
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
void EnsureHearts()
|
||||
{
|
||||
if (texHeartOn != null && texHeartOff != null) return;
|
||||
texHeartOn = HeartTexture(new Color(0.93f, 0.28f, 0.33f), true);
|
||||
texHeartOff = HeartTexture(new Color(1f, 1f, 1f, 0.35f), false);
|
||||
}
|
||||
|
||||
/// <summary>Herz als Textur (gefüllt oder nur Umriss), weich geglättet.</summary>
|
||||
static Texture2D HeartTexture(Color color, bool filled)
|
||||
{
|
||||
const int size = 48;
|
||||
var tex = new Texture2D(size, size, TextureFormat.ARGB32, false);
|
||||
for (int py = 0; py < size; py++)
|
||||
for (int px = 0; px < size; px++)
|
||||
{
|
||||
// Herz-Kurve (x² + y² - 1)³ - x² y³ <= 0, mehrfach abgetastet für glatte Kanten
|
||||
int inside = 0, samples = 0;
|
||||
for (int sy = 0; sy < 3; sy++)
|
||||
for (int sx = 0; sx < 3; sx++)
|
||||
{
|
||||
float x = ((px + (sx + 0.5f) / 3f) / size - 0.5f) * 2.6f;
|
||||
float y = ((py + (sy + 0.5f) / 3f) / size - 0.55f) * 2.6f;
|
||||
float a = x * x + y * y - 1f;
|
||||
bool inHeart = a * a * a - x * x * y * y * y <= 0f;
|
||||
if (!filled && inHeart)
|
||||
{
|
||||
// Umriss: innen ein kleineres Herz aussparen
|
||||
float x2 = x * 1.28f, y2 = (y + 0.08f) * 1.28f;
|
||||
float b = x2 * x2 + y2 * y2 - 1f;
|
||||
if (b * b * b - x2 * x2 * y2 * y2 * y2 <= 0f) inHeart = false;
|
||||
}
|
||||
if (inHeart) inside++;
|
||||
samples++;
|
||||
}
|
||||
tex.SetPixel(px, py, new Color(color.r, color.g, color.b, color.a * inside / samples));
|
||||
}
|
||||
tex.filterMode = FilterMode.Bilinear;
|
||||
tex.wrapMode = TextureWrapMode.Clamp;
|
||||
tex.Apply();
|
||||
tex.hideFlags = HideFlags.HideAndDontSave;
|
||||
return tex;
|
||||
}
|
||||
|
||||
/// <summary>Server-Profil: wem der Server gehört, Version, Spieler, Laufzeit ...</summary>
|
||||
void DrawServerProfile(MultiplayerManager mp, ServerListEntry s)
|
||||
{
|
||||
@@ -332,6 +507,10 @@ namespace MSCO
|
||||
GUILayout.EndHorizontal();
|
||||
GUILayout.EndVertical();
|
||||
GUILayout.Space(12);
|
||||
Rect heartSlot = GUILayoutUtility.GetRect(44, 44, GUILayout.Width(44), GUILayout.Height(44));
|
||||
GUI.Box(heartSlot, GUIContent.none, rowBox);
|
||||
Heart(new Rect(heartSlot.x + 11, heartSlot.y + 11, 22, 22), s);
|
||||
GUILayout.Space(8);
|
||||
GUI.enabled = CanJoin(mp, s);
|
||||
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);
|
||||
@@ -511,7 +690,9 @@ namespace MSCO
|
||||
var r = new Rect((Screen.width - PromptWidth) / 2f, (Screen.height - PromptHeight) / 2f, PromptWidth, PromptHeight);
|
||||
ScreenRect = new Rect(0, 0, Screen.width, Screen.height); // Spielmenü dahinter komplett sperren
|
||||
GUI.Box(r, GUIContent.none, windowBox);
|
||||
string title = reconnecting ? "RECONNECTING" : mp.DisconnectTitle;
|
||||
string title = reconnecting
|
||||
? ((mp.ReconnectMessage ?? "").StartsWith("Switching") ? "SWITCHING SERVER" : "RECONNECTING")
|
||||
: mp.DisconnectTitle;
|
||||
bool bad = !reconnecting && title != "DISCONNECTED";
|
||||
GUI.DrawTexture(new Rect(r.x, r.y, r.width, 4), bad ? texDanger : texAccent);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user