Index: ps/trunk/binaries/data/config/default.cfg
===================================================================
--- ps/trunk/binaries/data/config/default.cfg
+++ ps/trunk/binaries/data/config/default.cfg
@@ -486,6 +486,8 @@
duplicateplayernames = false ; Rename joining player to "User (2)" if "User" is already connected, otherwise prohibit join.
lateobservers = everyone ; Allow observers to join the game after it started. Possible values: everyone, buddies, disabled.
observerlimit = 8 ; Prevent further observer joins in running games if this limit is reached
+observermaxlag = 10 ; Make clients wait for observers if they lag more than X turns behind. -1 means "never wait for observers".
+autocatchup = true ; Auto-accelerate the sim rate if lagging behind (as an observer).
[overlay]
fps = "false" ; Show frames per second in top right corner
Index: ps/trunk/binaries/data/mods/public/gui/options/options.json
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/options/options.json
+++ ps/trunk/binaries/data/mods/public/gui/options/options.json
@@ -29,12 +29,6 @@
},
{
"type": "boolean",
- "label": "Network warnings",
- "tooltip": "Show which player has a bad connection in multiplayer games.",
- "config": "overlay.netwarnings"
- },
- {
- "type": "boolean",
"label": "FPS overlay",
"tooltip": "Show frames per second in top right corner.",
"config": "overlay.fps"
@@ -58,25 +52,6 @@
"config": "gui.session.ceasefirecounter"
},
{
- "type": "dropdown",
- "label": "Late observer joins",
- "tooltip": "Allow everybody or buddies only to join the game as observer after it started.",
- "config": "network.lateobservers",
- "list": [
- { "value": "everyone", "label": "Everyone" },
- { "value": "buddies", "label": "Buddies" },
- { "value": "disabled", "label": "Disabled" }
- ]
- },
- {
- "type": "number",
- "label": "Observer limit",
- "tooltip": "Prevent further observers from joining if the limit is reached.",
- "config": "network.observerlimit",
- "min": 0,
- "max": 32
- },
- {
"type": "boolean",
"label": "Chat timestamp",
"tooltip": "Display the time at which a chat message was posted.",
@@ -425,7 +400,7 @@
]
},
{
- "label": "Lobby",
+ "label": "Networking / Lobby",
"tooltip": "These settings only affect the multiplayer.",
"options":
[
@@ -447,6 +422,45 @@
"label": "Game rating column",
"tooltip": "Show the average rating of the participating players in a column of the gamelist.",
"config": "lobby.columns.gamerating"
+ },
+ {
+ "type": "boolean",
+ "label": "Network warnings",
+ "tooltip": "Show which player has a bad connection in multiplayer games.",
+ "config": "overlay.netwarnings"
+ },
+ {
+ "type": "dropdown",
+ "label": "Late observer joins",
+ "tooltip": "Allow everybody or buddies only to join the game as observer after it started.",
+ "config": "network.lateobservers",
+ "list": [
+ { "value": "everyone", "label": "Everyone" },
+ { "value": "buddies", "label": "Buddies" },
+ { "value": "disabled", "label": "Disabled" }
+ ]
+ },
+ {
+ "type": "number",
+ "label": "Observer limit",
+ "tooltip": "Prevent further observers from joining if the limit is reached.",
+ "config": "network.observerlimit",
+ "min": 0,
+ "max": 32
+ },
+ {
+ "type": "number",
+ "label": "Max lag for observers",
+ "tooltip": "When hosting, pause the game if observers are lagging more than this many turns. If set to -1, observers are ignored.",
+ "config": "network.observermaxlag",
+ "min": -1,
+ "max": 10000
+ },
+ {
+ "type": "boolean",
+ "label": "(Observer) Speed up when lagging.",
+ "tooltip": "When observing a game, automatically speed up if you start lagging, to catch up with the live match.",
+ "config": "network.autocatchup"
}
]
},
Index: ps/trunk/binaries/data/mods/public/gui/session/NetworkDelayOverlay.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/NetworkDelayOverlay.js
+++ ps/trunk/binaries/data/mods/public/gui/session/NetworkDelayOverlay.js
@@ -0,0 +1,63 @@
+/**
+ * Shows an overlay if the game is lagging behind the net server.
+ */
+class NetworkDelayOverlay
+{
+ constructor()
+ {
+ this.netDelayOverlay = Engine.GetGUIObjectByName("netDelayOverlay");
+
+ this.netDelayOverlay.caption="toto";
+ this.caption = translate(this.Caption);
+ this.sprintfData = {};
+
+ this.initialSimRate = Engine.GetSimRate();
+ this.currentSimRate = this.initialSimRate;
+
+ setTimeout(() => this.CheckDelay(), 1000);
+ }
+
+ CheckDelay()
+ {
+ setTimeout(() => this.CheckDelay(), 1000);
+ let delay = +(Engine.HasNetClient() && Engine.GetPendingTurns());
+
+ if (g_IsObserver && Engine.ConfigDB_GetValue("user", "network.autocatchup"))
+ {
+ if (delay > this.MAX_LIVE_DELAY && this.currentSimRate <= this.initialSimRate)
+ {
+ this.currentSimRate = this.initialSimRate * 1.1;
+ Engine.SetSimRate(this.currentSimRate);
+ }
+ else if (delay <= this.NORMAL_DELAY && this.currentSimRate > this.initialSimRate)
+ {
+ this.currentSimRate = this.initialSimRate;
+ Engine.SetSimRate(this.currentSimRate);
+ }
+ }
+
+ if (delay < this.MAX_LIVE_DELAY)
+ {
+ this.netDelayOverlay.hidden = true;
+ return;
+ }
+ this.netDelayOverlay.hidden = false;
+ this.sprintfData.delay = (delay / this.TURNS_PER_SECOND);
+ this.sprintfData.delay = this.sprintfData.delay.toFixed(this.sprintfData.delay < 5 ? 1 : 0);
+ this.netDelayOverlay.caption = sprintf(this.caption, this.sprintfData);
+ }
+}
+
+/**
+ * Because of command delay, we can still be several turns behind the 'ready' turn and not
+ * particularly late. This should be kept in sync with the command delay.
+ */
+NetworkDelayOverlay.prototype.NORMAL_DELAY = 3;
+NetworkDelayOverlay.prototype.MAX_LIVE_DELAY = 6;
+
+/**
+ * This needs to be kept in sync with the turn length.
+ */
+NetworkDelayOverlay.prototype.TURNS_PER_SECOND = 5;
+
+NetworkDelayOverlay.prototype.Caption = translate("Delay to live stream: %(delay)ss");
Index: ps/trunk/binaries/data/mods/public/gui/session/NetworkDelayOverlay.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/NetworkDelayOverlay.xml
+++ ps/trunk/binaries/data/mods/public/gui/session/NetworkDelayOverlay.xml
@@ -0,0 +1,14 @@
+
+
+
Index: ps/trunk/binaries/data/mods/public/gui/session/session.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/session.js
+++ ps/trunk/binaries/data/mods/public/gui/session/session.js
@@ -20,6 +20,7 @@
var g_Menu;
var g_MiniMapPanel;
var g_NetworkStatusOverlay;
+var g_NetworkDelayOverlay;
var g_ObjectivesDialog;
var g_OutOfSyncNetwork;
var g_OutOfSyncReplay;
@@ -289,6 +290,7 @@
g_Menu = new Menu(g_PauseControl, g_PlayerViewControl, g_Chat);
g_MiniMapPanel = new MiniMapPanel(g_PlayerViewControl, g_DiplomacyColors, g_WorkerTypes);
g_NetworkStatusOverlay = new NetworkStatusOverlay();
+ g_NetworkDelayOverlay = new NetworkDelayOverlay();
g_ObjectivesDialog = new ObjectivesDialog(g_PlayerViewControl, mapCache);
g_OutOfSyncNetwork = new OutOfSyncNetwork();
g_OutOfSyncReplay = new OutOfSyncReplay();
Index: ps/trunk/binaries/data/mods/public/gui/session/session.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/session.xml
+++ ps/trunk/binaries/data/mods/public/gui/session/session.xml
@@ -38,6 +38,7 @@
+
Index: ps/trunk/source/network/NetServer.cpp
===================================================================
--- ps/trunk/source/network/NetServer.cpp
+++ ps/trunk/source/network/NetServer.cpp
@@ -747,7 +747,7 @@
RemovePlayer(session->GetGUID());
if (m_ServerTurnManager && session->GetCurrState() != NSS_JOIN_SYNCING)
- m_ServerTurnManager->UninitialiseClient(session->GetHostID()); // TODO: only for non-observers
+ m_ServerTurnManager->UninitialiseClient(session->GetHostID());
// TODO: ought to switch the player controlled by that client
// back to AI control, or something?
@@ -1402,7 +1402,9 @@
}
// Tell the turn manager to expect commands from this new client
- server.m_ServerTurnManager->InitialiseClient(session->GetHostID(), readyTurn);
+ // Special case: the controller shouldn't be treated as an observer in any case.
+ bool isObserver = server.m_PlayerAssignments[session->GetGUID()].m_PlayerID == -1 && server.m_ControllerGUID != session->GetGUID();
+ server.m_ServerTurnManager->InitialiseClient(session->GetHostID(), readyTurn, isObserver);
// Tell the client that everything has finished loading and it should start now
CLoadedGameMessage loaded;
@@ -1530,7 +1532,11 @@
m_ServerTurnManager = new CNetServerTurnManager(*this);
for (CNetServerSession* session : m_Sessions)
- m_ServerTurnManager->InitialiseClient(session->GetHostID(), 0); // TODO: only for non-observers
+ {
+ // Special case: the controller shouldn't be treated as an observer in any case.
+ bool isObserver = m_PlayerAssignments[session->GetGUID()].m_PlayerID == -1 && m_ControllerGUID != session->GetGUID();
+ m_ServerTurnManager->InitialiseClient(session->GetHostID(), 0, isObserver);
+ }
m_State = SERVER_STATE_LOADING;
Index: ps/trunk/source/network/NetServerTurnManager.h
===================================================================
--- ps/trunk/source/network/NetServerTurnManager.h
+++ ps/trunk/source/network/NetServerTurnManager.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2018 Wildfire Games.
+/* Copyright (C) 2021 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -18,16 +18,18 @@
#ifndef INCLUDED_NETSERVERTURNMANAGER
#define INCLUDED_NETSERVERTURNMANAGER
-#include