Disconnect/kick prompt with reason, reconnect after world reset with retries; stop mass 'consumed' deletes on scene reload
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,16 @@ Server und Mod müssen beide 2.8 sein (Server: `git pull && docker compose up -d
|
||||
- **Verkehr**: NPC-Autos fahren bei allen an derselben Stelle (der erste Spieler simuliert, die anderen folgen)
|
||||
- `vehicles` in der F7-Konsole zeigt, wie viele Karosserie-Meshes, Job-Werte und Verkehrsautos synchronisiert werden
|
||||
|
||||
**Trennen & Neu-Verbinden**
|
||||
- Gekickt, gebannt, Server beendet, Verbindung verloren: eigenes Fenster in der Bildmitte mit **Grund** und OK
|
||||
(im Spiel wird der Grund vorher 2,5 s groß angezeigt)
|
||||
- `reset-world` / `reload-save`: Fenster „Reconnecting“ – verbindet automatisch neu, bis zu 5 Versuche (falls der
|
||||
Server gerade neu startet), mit CANCEL-Knopf
|
||||
|
||||
**Behoben**
|
||||
- Beim Neuladen der Welt wurden herumliegende Objekte als „verbraucht“ gemeldet und bei allen gelöscht. Jetzt werden
|
||||
nur einzelne echte Items gemeldet, Fahrzeuge/Teile nie gelöscht, und der Server nimmt höchstens 20 pro Minute an
|
||||
|
||||
**Leistung**
|
||||
- Mod: herumliegende Objekte werden nur noch jedes 4. Frame geprüft (eigene/gehaltene/gefahrene weiterhin jedes Frame),
|
||||
weniger Speicher-Allokationen pro Frame, Gelenke fremd gefahrener Autos seltener geprüft
|
||||
|
||||
+50
-24
@@ -73,7 +73,10 @@ namespace MSCO
|
||||
bool pendingJoin = mp.PendingJoinHost != null && !mp.IsInSession;
|
||||
bool versionWarning = mp.PendingVersionWarning != null && !mp.IsInSession;
|
||||
bool disconnected = mp.DisconnectNotice != null && !mp.IsInSession;
|
||||
if (pendingJoin || versionWarning || disconnected) minimized = false; // JOIN-Link / Warnung / Trennung: Fenster zeigen
|
||||
// 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 (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);
|
||||
@@ -85,10 +88,8 @@ namespace MSCO
|
||||
var content = new Rect(window.x + SidebarWidth + 32, window.y + 28,
|
||||
Width - SidebarWidth - 64, Height - StatusHeight - 52);
|
||||
GUILayout.BeginArea(content);
|
||||
if (disconnected) DrawDisconnectNotice(mp);
|
||||
else if (versionWarning) DrawVersionWarning(mp);
|
||||
if (versionWarning) DrawVersionWarning(mp);
|
||||
else if (pendingJoin) DrawJoinConfirm(mp);
|
||||
else if (mp.IsReconnecting) DrawReconnecting(mp);
|
||||
else if (mp.IsInSession) DrawSession(mp);
|
||||
else if (page == Page.P2P) DrawP2P(mp);
|
||||
else DrawServerBrowser(mp);
|
||||
@@ -486,29 +487,54 @@ namespace MSCO
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
/// <summary>Nach dem Trennen (Kick, Ban, Shutdown, Timeout ...): Grund anzeigen.</summary>
|
||||
void DrawDisconnectNotice(MultiplayerManager mp)
|
||||
const float PromptWidth = 540f;
|
||||
const float PromptHeight = 270f;
|
||||
Texture2D texDim, texDanger;
|
||||
GUIStyle promptTextStyle;
|
||||
|
||||
/// <summary>
|
||||
/// Fenster in der Bildmitte über abgedunkeltem Hintergrund: nach dem Trennen (Kick, Ban, Shutdown, Timeout ...)
|
||||
/// mit Grund und OK, bzw. beim automatischen Neu-Verbinden nach reset-world mit Versuch-Anzeige und CANCEL.
|
||||
/// </summary>
|
||||
void DrawPrompt(MultiplayerManager mp, bool reconnecting)
|
||||
{
|
||||
GUILayout.Label("Disconnected", titleStyle);
|
||||
GUILayout.Space(8);
|
||||
GUILayout.BeginVertical(rowBox);
|
||||
GUILayout.Space(6);
|
||||
GUILayout.Label("REASON", captionStyle);
|
||||
GUILayout.Label(mp.DisconnectNotice, rowNameStyle);
|
||||
GUILayout.Space(6);
|
||||
GUILayout.EndVertical();
|
||||
GUILayout.Space(8);
|
||||
GUILayout.Label("Your own save game has been restored.", textStyle);
|
||||
GUILayout.FlexibleSpace();
|
||||
if (GUILayout.Button("OK", primaryStyle, GUILayout.Height(42), GUILayout.Width(170))) mp.ClearDisconnectNotice();
|
||||
if (texDim == null) texDim = Solid(new Color(0f, 0f, 0f, 0.6f));
|
||||
if (texDanger == null) texDanger = Solid(Hex(0xe5, 0x48, 0x4d));
|
||||
if (promptTextStyle == null)
|
||||
{
|
||||
promptTextStyle = new GUIStyle(rowNameStyle);
|
||||
promptTextStyle.wordWrap = true;
|
||||
promptTextStyle.fontSize = 15;
|
||||
}
|
||||
GUI.DrawTexture(new Rect(0, 0, Screen.width, Screen.height), texDim);
|
||||
|
||||
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;
|
||||
bool bad = !reconnecting && title != "DISCONNECTED";
|
||||
GUI.DrawTexture(new Rect(r.x, r.y, r.width, 4), bad ? texDanger : texAccent);
|
||||
|
||||
float x = r.x + 32, w = r.width - 64, y = r.y + 28;
|
||||
GUI.Label(new Rect(x, y, w, 34), title, titleStyle);
|
||||
y += 50;
|
||||
GUI.Label(new Rect(x, y, w, 20), "REASON", captionStyle);
|
||||
|
||||
if (reconnecting)
|
||||
{
|
||||
GUI.Label(new Rect(x, y + 22, w, 44), mp.ReconnectMessage ?? "", promptTextStyle);
|
||||
string attempt = mp.ReconnectAttempt == 0
|
||||
? "Connecting again in a moment..."
|
||||
: "Attempt " + mp.ReconnectAttempt + " of " + mp.ReconnectMaxAttempts +
|
||||
(string.IsNullOrEmpty(mp.Status) ? "" : " - " + mp.Status);
|
||||
GUI.Label(new Rect(x, y + 74, w, 40), attempt, textStyle);
|
||||
if (GUI.Button(new Rect(r.xMax - 32 - 170, r.yMax - 28 - 42, 170, 42), "CANCEL", secondaryStyle)) mp.CancelReconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
void DrawReconnecting(MultiplayerManager mp)
|
||||
{
|
||||
GUILayout.Label("Reconnecting", titleStyle);
|
||||
GUILayout.Space(8);
|
||||
GUILayout.Label(mp.ReconnectMessage, rowNameStyle);
|
||||
GUILayout.Label("You will be connected to the server again in a moment.", textStyle);
|
||||
GUI.Label(new Rect(x, y + 22, w, 60), mp.DisconnectNotice ?? "", promptTextStyle);
|
||||
GUI.Label(new Rect(x, y + 88, w, 20), "Your own save game has been restored.", textStyle);
|
||||
if (GUI.Button(new Rect(r.xMax - 32 - 170, r.yMax - 28 - 42, 170, 42), "OK", primaryStyle)) mp.ClearDisconnectNotice();
|
||||
}
|
||||
|
||||
void DrawSession(MultiplayerManager mp)
|
||||
|
||||
+52
-6
@@ -594,6 +594,9 @@ namespace MSCO
|
||||
string centerMessage; // große Meldung in der Bildmitte
|
||||
float centerMessageUntil;
|
||||
float reconnectAt;
|
||||
int reconnectAttempts;
|
||||
const int MaxReconnectAttempts = 5;
|
||||
const float ReconnectRetrySeconds = 6f;
|
||||
|
||||
void ConnectToServer(string host, int port, string displayName, int maxPlayers)
|
||||
{
|
||||
@@ -817,14 +820,22 @@ namespace MSCO
|
||||
|
||||
steam.Tick();
|
||||
|
||||
// Nach reset-world o.ä.: im Hauptmenü automatisch wieder mit demselben Server verbinden
|
||||
if (reconnectMessage != null && session == null && MenuHelper.InMenu && Time.realtimeSinceStartup >= reconnectAt)
|
||||
// Nach reset-world o.ä.: im Hauptmenü automatisch wieder mit demselben Server verbinden. Mehrere Versuche -
|
||||
// der Server kann nach einem Neustart noch ein paar Sekunden brauchen.
|
||||
if (reconnectMessage != null && session == null && MenuHelper.InMenu && !checkingServer
|
||||
&& Time.realtimeSinceStartup >= reconnectAt)
|
||||
{
|
||||
string message = reconnectMessage;
|
||||
if (lastServerHost == null || reconnectAttempts >= MaxReconnectAttempts)
|
||||
{
|
||||
DisconnectNotice = "Could not reconnect to the server after " + reconnectAttempts + " attempts. " +
|
||||
"Try again from the server browser.";
|
||||
reconnectMessage = null;
|
||||
if (lastServerHost != null)
|
||||
reconnectAttempts = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
SetStatus(message);
|
||||
reconnectAttempts++;
|
||||
reconnectAt = Time.realtimeSinceStartup + ReconnectRetrySeconds;
|
||||
JoinServer(lastServerHost, lastServerPort, lastServerName, lastServerMax);
|
||||
}
|
||||
}
|
||||
@@ -909,6 +920,30 @@ namespace MSCO
|
||||
public string DisconnectNotice { get; private set; }
|
||||
public void ClearDisconnectNotice() { DisconnectNotice = null; }
|
||||
public bool IsReconnecting { get { return reconnectMessage != null && session == null; } }
|
||||
public int ReconnectAttempt { get { return reconnectAttempts; } }
|
||||
public int ReconnectMaxAttempts { get { return MaxReconnectAttempts; } }
|
||||
|
||||
/// <summary>Automatisches Neu-Verbinden abbrechen (Knopf im Reconnect-Fenster).</summary>
|
||||
public void CancelReconnect()
|
||||
{
|
||||
reconnectMessage = null;
|
||||
reconnectAttempts = 0;
|
||||
SetStatus("Reconnect cancelled");
|
||||
}
|
||||
|
||||
/// <summary>Überschrift für das Trennungs-Fenster.</summary>
|
||||
public string DisconnectTitle
|
||||
{
|
||||
get
|
||||
{
|
||||
string r = (DisconnectNotice ?? "").ToLower();
|
||||
if (r.Contains("banned")) return "BANNED";
|
||||
if (r.Contains("kick")) return "KICKED";
|
||||
if (r.Contains("shutting down") || r.Contains("server stopped") || r.Contains("shut down")) return "SERVER CLOSED";
|
||||
if (r.Contains("could not reconnect")) return "RECONNECT FAILED";
|
||||
return "DISCONNECTED";
|
||||
}
|
||||
}
|
||||
|
||||
bool worldSynced; // Server: Weltzustand angewendet
|
||||
|
||||
@@ -1120,6 +1155,8 @@ namespace MSCO
|
||||
AddPlayer(id, r.ReadString(), id == localId);
|
||||
}
|
||||
handshakeDone = true;
|
||||
reconnectMessage = null; // (wieder) verbunden
|
||||
reconnectAttempts = 0;
|
||||
SetStatus(dedicated ? "Connected to server - loading..." : "Connected - waiting for host to start...");
|
||||
break;
|
||||
}
|
||||
@@ -1127,8 +1164,17 @@ namespace MSCO
|
||||
case MsgType.Reject:
|
||||
{
|
||||
string why = r.ReadString();
|
||||
serverReason = why; // für den Hinweis im Hauptmenü (Kick, Ban, Server voll, Shutdown ...)
|
||||
serverReason = why; // für das Fenster im Hauptmenü (Kick, Ban, Server voll, Shutdown ...)
|
||||
if (why.ToLower().Contains("banned")) reconnectMessage = null; // gebannt: nicht weiter versuchen
|
||||
session.Close((dedicated || connectingToServer ? "Rejected by server: " : "Rejected by host: ") + why);
|
||||
if (MenuHelper.InGame)
|
||||
{
|
||||
// Im Spiel: Grund erst groß anzeigen, dann ins Hauptmenü (dort kommt das Fenster mit OK)
|
||||
centerMessage = why;
|
||||
centerMessageUntil = Time.realtimeSinceStartup + 3f;
|
||||
reconnectHoldUntil = Time.realtimeSinceStartup + 2.5f;
|
||||
return;
|
||||
}
|
||||
OnSessionEnded(session.CloseReason);
|
||||
break;
|
||||
}
|
||||
|
||||
+23
-6
@@ -16,6 +16,7 @@ namespace MSCO
|
||||
public int Id;
|
||||
public string Path;
|
||||
public Rigidbody Rb;
|
||||
public string Name;
|
||||
public GameObject Go; // zum Unterscheiden: nur Rigidbody weg (angebaut) oder ganzes Objekt weg (verbraucht)
|
||||
public Vector3 LastPos;
|
||||
public bool IsVehicle;
|
||||
@@ -101,6 +102,23 @@ namespace MSCO
|
||||
|
||||
public void MarkNetworkCreated(GameObject go) { if (go != null) networkCreated.Add(go); }
|
||||
|
||||
/// <summary>
|
||||
/// Verschwundene Items melden (gegessen, getrunken, verbraucht) - nur wenn das ganze Objekt weg ist (nicht nur der
|
||||
/// Rigidbody wie beim Anbauen), es ein Item ist und es bei uns passiert ist. Verschwinden viele auf einmal, wird
|
||||
/// gerade die Szene entladen (Neu laden, Hauptmenü) - dann NICHTS melden, sonst löscht man bei allen die Welt.
|
||||
/// </summary>
|
||||
void ReportConsumed(Transform player)
|
||||
{
|
||||
if (ItemDestroyed == null || removeList.Count == 0) return;
|
||||
if (player == null || removeList.Count > 2 || Time.time - loadTime < 30f) return;
|
||||
foreach (SyncedBody b in removeList)
|
||||
{
|
||||
if (b.Go != null || b.IsVehicle || b.Name == null || !b.Name.EndsWith("(itemx)")) continue;
|
||||
if (IsLocal(b) || (b.LastPos - lastPlayerPos).sqrMagnitude < LocalEventRange * LocalEventRange)
|
||||
ItemDestroyed(b);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Gibt es dieses Objekt bei uns schon an dieser Stelle? (dann nicht nochmal erzeugen)</summary>
|
||||
public bool HasNear(int id, Vector3 pos, float range)
|
||||
{
|
||||
@@ -113,6 +131,8 @@ namespace MSCO
|
||||
{
|
||||
SyncedBody b;
|
||||
if (!byId.TryGetValue(id, out b)) return false;
|
||||
// Nur echte Items (Essen, Getränke ...) - nie Fahrzeuge oder Teile, egal was ein anderer meldet
|
||||
if (b.IsVehicle || b.Name == null || !b.Name.EndsWith("(itemx)")) return false;
|
||||
if (b.Go != null) UnityEngine.Object.Destroy(b.Go);
|
||||
Unregister(b);
|
||||
return true;
|
||||
@@ -307,11 +327,6 @@ namespace MSCO
|
||||
if (b.OwnerId == SyncedBody.NoOwner && ((b.Id ^ frame) & 3) != 0 && b != held && b != driven) continue;
|
||||
if (b.Rb == null)
|
||||
{
|
||||
// Ganzes Objekt weg (gegessen, getrunken, verbraucht) - nicht nur der Rigidbody (Teil angebaut).
|
||||
// Nur melden, wenn es bei uns passiert ist: wir besaßen es oder standen daneben.
|
||||
if (b.Go == null && !b.IsVehicle && ItemDestroyed != null
|
||||
&& (IsLocal(b) || (b.LastPos - lastPlayerPos).sqrMagnitude < LocalEventRange * LocalEventRange))
|
||||
ItemDestroyed(b);
|
||||
removeList.Add(b);
|
||||
continue;
|
||||
}
|
||||
@@ -350,6 +365,7 @@ namespace MSCO
|
||||
else if (Time.time - b.LastRecvTime > RemoteTimeout) SetFree(b);
|
||||
}
|
||||
}
|
||||
ReportConsumed(player);
|
||||
foreach (SyncedBody b in removeList) Unregister(b);
|
||||
Held = held != null && held.Rb != null && IsLocal(held) ? held : null;
|
||||
|
||||
@@ -895,6 +911,7 @@ namespace MSCO
|
||||
Path = path,
|
||||
Rb = c.Key,
|
||||
Go = c.Key.gameObject,
|
||||
Name = c.Key.gameObject.name,
|
||||
LastPos = c.Key.position,
|
||||
IsVehicle = c.Value,
|
||||
RootName = c.Key.transform.root.name
|
||||
@@ -906,7 +923,7 @@ namespace MSCO
|
||||
// Neues Item nach dem ersten Suchlauf, bei uns in der Nähe und nicht übers Netz entstanden:
|
||||
// wahrscheinlich gekauft / ausgepackt / geliefert - den anderen melden
|
||||
bool isItem = !b.IsVehicle && b.Go.name.EndsWith("(itemx)");
|
||||
if (ScanComplete && isItem && ItemAppeared != null && !networkCreated.Contains(b.Go)
|
||||
if (ScanComplete && isItem && ItemAppeared != null && !networkCreated.Contains(b.Go) && Time.time - loadTime > 30f
|
||||
&& (b.LastPos - lastPlayerPos).sqrMagnitude < LocalEventRange * LocalEventRange)
|
||||
ItemAppeared(b);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user