diff --git a/binaries/data/mods/public/gui/common/network.js b/binaries/data/mods/public/gui/common/network.js
index 0b235af146..1de4c0fa22 100644
--- a/binaries/data/mods/public/gui/common/network.js
+++ b/binaries/data/mods/public/gui/common/network.js
@@ -6,8 +6,9 @@ function getDisconnectReason(id)
case 0: return translate("Unknown reason");
case 1: return translate("Unexpected shutdown");
case 2: return translate("Incorrect network protocol version");
- case 3: return translate("Game has already started");
- default: return sprintf(translate("\\[Invalid value %(id)s]"), { id: id });
+ case 3: return translate("Game is loading, please try later");
+ case 4: return translate("Game has already started, no observers allowed");
+ default: return sprintf(translate("\\[Invalid value %(id)s]"), { "id": id });
}
}
diff --git a/binaries/data/mods/public/gui/gamesetup/gamesetup.js b/binaries/data/mods/public/gui/gamesetup/gamesetup.js
index 051e728617..d18cf4d0c5 100644
--- a/binaries/data/mods/public/gui/gamesetup/gamesetup.js
+++ b/binaries/data/mods/public/gui/gamesetup/gamesetup.js
@@ -154,6 +154,20 @@ function initMain()
mapFilters.list_data = getFilterIds();
g_GameAttributes.mapFilter = "default";
+ // For singleplayer reduce the size of more options dialog by three options (cheats, rated game, observer late join = 90px)
+ if (!g_IsNetworked)
+ {
+ Engine.GetGUIObjectByName("moreOptions").size = "50%-200 50%-195 50%+200 50%+160";
+ Engine.GetGUIObjectByName("hideMoreOptions").size = "50%-70 310 50%+70 336";
+ }
+ // For non-lobby multiplayergames reduce the size of the dialog by one option (rated game, 30px)
+ else if (g_IsNetworked && !Engine.HasXmppClient())
+ {
+ Engine.GetGUIObjectByName("moreOptions").size = "50%-200 50%-195 50%+200 50%+220";
+ Engine.GetGUIObjectByName("hideMoreOptions").size = "50%-70 370 50%+70 396";
+ Engine.GetGUIObjectByName("optionObserverLateJoin").size = "14 338 94% 366";
+ }
+
// Setup controls for host only
if (g_IsController)
{
@@ -255,6 +269,11 @@ function initMain()
updateGameAttributes();
};
+ Engine.GetGUIObjectByName("observerLateJoin").onPress = function() {
+ g_GameAttributes.settings.ObserverLateJoin = this.checked;
+ updateGameAttributes();
+ };
+
Engine.GetGUIObjectByName("disableTreasures").onPress = function() {
g_GameAttributes.settings.DisableTreasures = this.checked;
updateGameAttributes();
@@ -316,6 +335,7 @@ function initMain()
{
Engine.GetGUIObjectByName("optionCheats").hidden = false;
Engine.GetGUIObjectByName("enableCheats").checked = false;
+ Engine.GetGUIObjectByName("optionObserverLateJoin").hidden = false;
g_GameAttributes.settings.CheatsEnabled = false;
// Setup ranked option if we are connected to the lobby.
if (Engine.HasXmppClient())
@@ -331,6 +351,9 @@ function initMain()
{
Engine.GetGUIObjectByName("enableCheatsText").hidden = true;
Engine.GetGUIObjectByName("enableCheats").hidden = false;
+ Engine.GetGUIObjectByName("observerLateJoinText").hidden = true;
+ Engine.GetGUIObjectByName("observerLateJoin").hidden = false;
+
if (Engine.HasXmppClient())
{
Engine.GetGUIObjectByName("enableRatingText").hidden = true;
@@ -1188,10 +1211,12 @@ function onGameAttributesChange()
var populationCap = Engine.GetGUIObjectByName("populationCap");
var startingResources = Engine.GetGUIObjectByName("startingResources");
var ceasefire = Engine.GetGUIObjectByName("ceasefire");
+ var observerLateJoin = Engine.GetGUIObjectByName("observerLateJoin");
var numPlayersText= Engine.GetGUIObjectByName("numPlayersText");
var mapSizeDesc = Engine.GetGUIObjectByName("mapSizeDesc");
var mapSizeText = Engine.GetGUIObjectByName("mapSizeText");
+ var observerLateJoinText = Engine.GetGUIObjectByName("observerLateJoinText");
var revealMapText = Engine.GetGUIObjectByName("revealMapText");
var exploreMapText = Engine.GetGUIObjectByName("exploreMapText");
var disableTreasuresText = Engine.GetGUIObjectByName("disableTreasuresText");
@@ -1222,6 +1247,10 @@ function onGameAttributesChange()
}
else
enableRatingText.caption = "Unknown";
+
+ observerLateJoin.checked = g_GameAttributes.settings.ObserverLateJoin;
+ observerLateJoinText.caption = observerLateJoin.checked ? translate("Yes") : translate("No");
+
gameSpeedText.caption = g_GameSpeeds.names[speedIdx];
gameSpeedBox.selected = speedIdx;
populationCap.selected = (mapSettings.PopulationCap !== undefined && POPULATION_CAP_DATA.indexOf(mapSettings.PopulationCap) != -1 ? POPULATION_CAP_DATA.indexOf(mapSettings.PopulationCap) : POPULATION_CAP_DEFAULTIDX);
diff --git a/binaries/data/mods/public/gui/gamesetup/gamesetup.xml b/binaries/data/mods/public/gui/gamesetup/gamesetup.xml
index 4788977568..dc366903a5 100644
--- a/binaries/data/mods/public/gui/gamesetup/gamesetup.xml
+++ b/binaries/data/mods/public/gui/gamesetup/gamesetup.xml
@@ -270,7 +270,7 @@
-
+
+
+ Late Observer Joins:
+
+
+
+ Allow observers to join after the game started.
+
+
+
diff --git a/source/network/NetHost.h b/source/network/NetHost.h
index 402e5bb804..0552617708 100644
--- a/source/network/NetHost.h
+++ b/source/network/NetHost.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2011 Wildfire Games.
+/* Copyright (C) 2015 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -62,6 +62,7 @@ enum NetDisconnectReason
NDR_UNKNOWN = 0,
NDR_UNEXPECTED_SHUTDOWN,
NDR_INCORRECT_PROTOCOL_VERSION,
+ NDR_SERVER_LOADING,
NDR_SERVER_ALREADY_IN_GAME
};
diff --git a/source/network/NetMessages.h b/source/network/NetMessages.h
index 83ca5bb351..13a8aaaff0 100644
--- a/source/network/NetMessages.h
+++ b/source/network/NetMessages.h
@@ -28,7 +28,7 @@
#define PS_PROTOCOL_MAGIC 0x5073013f // 'P', 's', 0x01, '?'
#define PS_PROTOCOL_MAGIC_RESPONSE 0x50630121 // 'P', 'c', 0x01, '!'
-#define PS_PROTOCOL_VERSION 0x01010006 // Arbitrary protocol
+#define PS_PROTOCOL_VERSION 0x01010007 // Arbitrary protocol
#define PS_DEFAULT_PORT 0x5073 // 'P', 's'
// Defines the list of message types. The order of the list must not change.
diff --git a/source/network/NetServer.cpp b/source/network/NetServer.cpp
index d5f2204c70..6329c4304e 100644
--- a/source/network/NetServer.cpp
+++ b/source/network/NetServer.cpp
@@ -789,26 +789,40 @@ bool CNetServerWorker::OnAuthenticate(void* context, CFsmEvent* event)
CNetServerSession* session = (CNetServerSession*)context;
CNetServerWorker& server = session->GetServer();
- CAuthenticateMessage* message = (CAuthenticateMessage*)event->GetParamRef();
+ // Prohibit joins while the game is loading
+ if (server.m_State == SERVER_STATE_LOADING)
+ {
+ LOGMESSAGE("Refused connection while the game is loading");
+ session->Disconnect(NDR_SERVER_LOADING);
+ return true;
+ }
+ CAuthenticateMessage* message = (CAuthenticateMessage*)event->GetParamRef();
CStrW username = server.DeduplicatePlayerName(SanitisePlayerName(message->m_Name));
- bool isRejoining = false;
+ // Optionally allow observers to join after the game has started
+ bool observerLateJoin = false;
+ ScriptInterface& scriptInterface = server.GetScriptInterface();
+ JSContext* cx = scriptInterface.GetContext();
+ JSAutoRequest rq(cx);
+ JS::RootedValue settings(cx);
+ scriptInterface.GetProperty(server.m_GameAttributes.get(), "settings", &settings);
+ if (scriptInterface.HasProperty(settings, "ObserverLateJoin"))
+ scriptInterface.GetProperty(settings, "ObserverLateJoin", observerLateJoin);
+ // If the game has already started, only allow rejoins
+ bool isRejoining = false;
if (server.m_State != SERVER_STATE_PREGAME)
{
-// isRejoining = true; // uncomment this to test rejoining even if the player wasn't connected previously
-
// Search for an old disconnected player of the same name
// (TODO: if GUIDs were stable, we should use them instead)
- for (PlayerAssignmentMap::iterator it = server.m_PlayerAssignments.begin(); it != server.m_PlayerAssignments.end(); ++it)
- {
- if (!it->second.m_Enabled && it->second.m_Name == username)
- {
- isRejoining = true;
- break;
- }
- }
+ isRejoining =
+ observerLateJoin ||
+ std::find_if(
+ server.m_PlayerAssignments.begin(), server.m_PlayerAssignments.end(),
+ [&username] (const std::pair& pair)
+ { return pair.second.m_Enabled && pair.second.m_Name == username; })
+ != server.m_PlayerAssignments.end();
// Players who weren't already in the game are not allowed to join now that it's started
if (!isRejoining)