Index: binaries/system/readme.txt
===================================================================
--- binaries/system/readme.txt (revision 17146)
+++ binaries/system/readme.txt (working copy)
@@ -4,10 +4,15 @@ Basic gameplay:
-autostart=... load a map instead of showing main menu (see below)
-editor launch the Atlas scenario editor
-mod=NAME start the game using NAME mod
-quickstart load faster (disables audio and some system info logging)
+Dedicated Server:
+-dedicated-host starts a dedicated multiplayer server (without graphics)
+ please specify the mods, for example -mod="public"
+-dedicated-lobby advertize the game of the dedicated host in the lobby
+
Autostart:
-autostart="TYPEDIR/MAPNAME" enables autostart and sets MAPNAME; TYPEDIR is skirmishes, scenarios, or random
-autostart-ai=PLAYER:AI sets the AI for PLAYER (e.g. 2:petra)
-autostart-aidiff=PLAYER:DIFF sets the DIFFiculty of PLAYER's AI (0: sandbox, 5: very hard)
-autostart-civ=PLAYER:CIV sets PLAYER's civilisation to CIV (skirmish and random maps only)
Index: source/main.cpp
===================================================================
--- source/main.cpp (revision 17146)
+++ source/main.cpp (working copy)
@@ -527,11 +527,21 @@ static void RunGameOrAtlas(int argc, con
{
flags &= ~INIT_MODS;
Shutdown(SHUTDOWN_FROM_CONFIG);
continue;
}
+
+ if (args.Has("dedicated-host"))
+ {
+ (new CDedicatedServer())->StartHosting();
+ while (true)
+ SDL_Delay(100); // TODO: optimize this value
+ continue;
+ }
+
InitGraphics(args, 0);
+
MainControllerInit();
while (!quit)
Frame();
Shutdown(0);
MainControllerShutdown();
Index: source/network/DedicatedServer.cpp
===================================================================
--- source/network/DedicatedServer.cpp (revision 0)
+++ source/network/DedicatedServer.cpp (working copy)
@@ -0,0 +1,200 @@
+/* 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
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with 0 A.D. If not, see .
+ */
+
+// TODO: use lobby
+// TODO: manage SERVER_STATE
+// TODO: we should let the clients chose their civs and team numbers (but nothing else) via the regular GUI elements
+// TODO: use ReplayLogger
+// TODO: end the game when somebody won or everybody left
+// TODO: start a new game after one finished
+
+// TODO: #include "precompiled.h" ??
+//#include "DedicatedServer.h"
+#include "NetServer.h"
+
+#include "ps/CLogger.h"
+
+CDedicatedServer::CDedicatedServer()
+{
+}
+
+CDedicatedServer::~CDedicatedServer()
+{
+}
+
+void CDedicatedServer::StartHosting()
+{
+ LOGERROR("[HOST] Starting dedicated host");
+
+ g_NetServer = new CNetServer(true);
+
+ if (!g_NetServer->m_Worker->SetupConnection())
+ {
+ LOGERROR("ERROR: Could not start the server!");
+ delete g_NetServer;
+ }
+}
+
+void CDedicatedServer::Initialize()
+{
+ LOGERROR("[HOST] Initializing dedicated servers");
+
+ m_Worker = g_NetServer->m_Worker;
+ m_ScriptInterface = g_NetServer->m_Worker->m_ScriptInterface;
+
+ CStrW serverName(L"Autohost");
+ //if (g_args.Has("dedicated-host-name"))
+ // serverName = wstring_from_utf8(g_args.Get("dedicated-host-name"));
+ JSContext* cx = m_ScriptInterface->GetContext();
+ JSAutoRequest rq(cx);
+
+ // Needs to be kept in sync with gamesetup.js
+ // TODO: load from a JSON file
+ JS::RootedValue attribs(cx);
+ JS::RootedValue settings(cx);
+ JS::RootedValue playerAssignments(cx);
+ JS::RootedValue PlayerData(cx);
+ JS::RootedValue VictoryScripts(cx);
+
+ m_ScriptInterface->Eval("({})", &attribs);
+ m_ScriptInterface->Eval("({})", &settings);
+ m_ScriptInterface->Eval("({})", &playerAssignments);
+ m_ScriptInterface->Eval("([])", &PlayerData);
+ m_ScriptInterface->Eval("([\"scripts/TriggerHelper.js\",\"scripts/ConquestCommon.js\",\"scripts/Conquest.js\"])", &VictoryScripts);
+
+ m_ScriptInterface->SetProperty(settings, "AISeed", rand());
+ m_ScriptInterface->SetProperty(settings, "Seed", rand());
+ m_ScriptInterface->SetProperty(settings, "Ceasefire", 0);
+ m_ScriptInterface->SetProperty(settings, "CheatsEnabled", false);
+ m_ScriptInterface->SetProperty(settings, "GameType", std::wstring(L"conquest"));
+ m_ScriptInterface->SetProperty(settings, "PlayerData", PlayerData);
+ m_ScriptInterface->SetProperty(settings, "PopulationCap", 300);
+ m_ScriptInterface->SetProperty(settings, "Size", 320); // map size 4
+ m_ScriptInterface->SetProperty(settings, "StartingResources", 500); // medium res for nomad
+ m_ScriptInterface->SetProperty(settings, "VictoryScripts", VictoryScripts);
+// disable treasure, no revealed map, no explored map
+ // "timestamp": "1446043600",
+ // "engine_version": "0.0.19",
+ // "mods": ["mod","public"]
+ //"Preview": "acropolis_bay.png",
+ //CircularMap
+ //Description
+ m_ScriptInterface->SetProperty(attribs, "gameSpeed", 1);
+ m_ScriptInterface->SetProperty(attribs, "isNetworked", true);
+ m_ScriptInterface->SetProperty(attribs, "map", std::wstring(L"random"));
+ m_ScriptInterface->SetProperty(attribs, "mapFilter", std::wstring(L"default"));
+ m_ScriptInterface->SetProperty(attribs, "mapPath", std::wstring(L"maps/random/"));
+ m_ScriptInterface->SetProperty(attribs, "mapType", std::wstring(L"random"));
+ m_ScriptInterface->SetProperty(attribs, "matchID", ps_generate_guid().FromUTF8());
+ m_ScriptInterface->SetProperty(attribs, "playerAssignments", playerAssignments);
+ m_ScriptInterface->SetProperty(attribs, "serverName", serverName);
+ m_ScriptInterface->SetProperty(attribs, "settings", settings);
+
+ m_Worker->UpdateGameAttributes(&attribs);
+}
+
+void CDedicatedServer::UpdatePlayerAssignments()
+{
+ JSContext* cx = m_ScriptInterface->GetContext();
+ JSAutoRequest rq(cx);
+
+ JS::RootedValue attribs(cx);
+ JS::RootedValue settings(cx);
+ JS::RootedValue PlayerData(cx);
+
+ attribs.set(m_Worker->m_GameAttributes.get());
+ m_ScriptInterface->GetProperty(attribs, "settings", &settings);
+ m_ScriptInterface->Eval("([])", &PlayerData);
+
+ // Find all player IDs in active use
+/* std::set usedIDs;
+ for (PlayerAssignmentMap::iterator it = m_Worker->.m_PlayerAssignments.begin(); it != m_PlayerAssignments.end(); ++it)
+ if (it->second.m_Enabled && it->second.m_PlayerID != -1)
+ usedIDs.insert(it->second.m_PlayerID);
+*/
+ // Update player data
+ for (u8 i=0; im_PlayerAssignments.size(); i++)
+ {
+ JS::RootedValue player(cx);
+ m_ScriptInterface->Eval("({})", &player);
+ //m_ScriptInterface->SetProperty(player, "Name", "Some player");
+ m_ScriptInterface->SetProperty(player, "Team", -1);
+ m_ScriptInterface->SetProperty(player, "Civ", std::wstring(L"random"));
+ m_ScriptInterface->SetProperty(player, "AI", std::wstring(L""));
+ m_ScriptInterface->SetProperty(player, "AiDiff", 3);
+ m_ScriptInterface->SetPropertyInt(PlayerData, i, player);
+ }
+ m_ScriptInterface->SetProperty(settings, "PlayerData", PlayerData);
+
+ // Limit pop cap according to playercount
+ std::map popCaps = {
+ {1, 300}, // 600 pop total, 700 with wonder
+ {2, 300}, // 600 pop total, 750 with wonder
+ {3, 200}, // 600 pop total, 750 with wonder
+ {4, 200}, // 800 pop total, 1000 with wonder
+ {5, 150}, // 750 pop total, 1000 with wonder
+ {6, 150}, // 900 pop total, 1200 with wonder
+ {7, 100}, // 700 pop total, 1050 with wonder
+ {8, 100} // 800 pop total, 1200 with wonder
+ };
+ m_ScriptInterface->SetProperty(settings, "PopulationCap", popCaps[m_Worker->m_PlayerAssignments.size()]);
+
+ // TODO: send chat that pop cap has been adopted
+
+ m_ScriptInterface->SetProperty(attribs, "settings", settings);
+
+ m_Worker->UpdateGameAttributes(&attribs);
+
+ // Sends the player assignments
+ m_Worker->ClearAllPlayerReady();
+}
+
+void CDedicatedServer::OnChat(CNetServerSession* session, CChatMessage* message)
+{
+ LOGERROR("[HOST] %s: %s", utf8_from_wstring(session->GetUserName().c_str()), utf8_from_wstring(message->m_Message));
+}
+
+void CDedicatedServer::OnReady(CNetServerSession* session, CReadyMessage* message)
+{
+ LOGERROR("[HOST] %s is %s", utf8_from_wstring(session->GetUserName().c_str()), message->m_Status ? "ready " : "not ready");
+ // TODO if all ready: m_Worker->StartGame();
+}
+
+void CDedicatedServer::OnUserJoin(CNetServerSession* session)
+{
+ // TODO: if game has started, show "is starting to rejoin"
+ LOGERROR("[HOST] %s has joined (%s)", utf8_from_wstring(session->GetUserName()).c_str(), session->GetIPAddress().c_str());
+ ++m_PlayerCount;
+ UpdatePlayerAssignments();
+}
+
+void CDedicatedServer::OnUserLeave(CNetServerSession* session)
+{
+ LOGERROR("[HOST] %s has left", utf8_from_wstring(session->GetUserName()));
+ --m_PlayerCount;
+ UpdatePlayerAssignments();
+}
+
+void CDedicatedServer::OnUserRejoined(CNetServerSession* session)
+{
+ LOGERROR("[HOST] %s has finished rejoining.", utf8_from_wstring(m_Worker->m_PlayerAssignments[session->GetGUID()].m_Name.c_str()));
+}
+
+void CDedicatedServer::OnStartGame()
+{
+ LOGERROR("[HOST] The game has started.");
+}
Index: source/network/DedicatedServer.h
===================================================================
--- source/network/DedicatedServer.h (revision 0)
+++ source/network/DedicatedServer.h (working copy)
@@ -0,0 +1,108 @@
+/* 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
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with 0 A.D. If not, see .
+ */
+
+// TODO: some ifndef define NETSERVER_H ?
+
+#include "NetServer.h"
+#include "NetSession.h"
+#include "NetMessage.h"
+
+#include "ps/GUID.h"
+#include "scriptinterface/ScriptInterface.h"
+
+/**
+ * Contains functions used by the dedicated server.
+ */
+class CDedicatedServer
+{
+ NONCOPYABLE(CDedicatedServer);
+
+private:
+ friend class NetServer;
+ friend class NetServerWorker;
+
+ /**
+ * Used to control the server.
+ */
+ CNetServerWorker* m_Worker = nullptr;
+
+ /**
+ * Used to control the server.
+ */
+ ScriptInterface* m_ScriptInterface = nullptr;
+
+ bool m_IsHosting = false;
+
+ /**
+ * Used to setup the game.
+ */
+ u8 m_PlayerCount = 0;
+
+public:
+
+ CDedicatedServer();
+ ~CDedicatedServer();
+
+ /**
+ * Create the host. Starts listening on UDP port 20595 for player connects.
+ */
+ void StartHosting();
+
+ /*
+ * Contains the default gamesetup atributes. Called after CNetServerWorker::Run() initializes the ScriptInterface.
+ */
+ void Initialize();
+
+ /**
+ * Sets the number of players to the number of connected clients,
+ * assigns unassigned players to unassigned slots,
+ * reduces the population count accordingly,
+ * resets ready states,
+ * sends the player assignments.
+ */
+ void UpdatePlayerAssignments();
+
+ /**
+ * Updates the player assignments.
+ */
+ void OnUserJoin(CNetServerSession* session);
+
+ /**
+ * Updates the player assignments.
+ */
+ void OnUserLeave(CNetServerSession* session);
+
+ /**
+ * Lets the users chose their own civs and team numbers.
+ */
+ void OnChat(CNetServerSession* session, CChatMessage* message);
+
+ /**
+ * Starts the game if all players are ready.
+ */
+ void OnReady(CNetServerSession* session, CReadyMessage* message);
+
+ /**
+ * Used for logging only.
+ */
+ void OnStartGame();
+
+ /**
+ * Used for logging only.
+ */
+ void OnUserRejoined(CNetServerSession* session);
+};
Index: source/network/NetServer.cpp
===================================================================
--- source/network/NetServer.cpp (revision 17146)
+++ source/network/NetServer.cpp (working copy)
@@ -106,20 +106,22 @@ public:
CJoinSyncStartMessage message;
session->SendMessage(&message);
}
private:
+
CNetServerWorker& m_Server;
u32 m_RejoinerHostID;
};
/*
* XXX: We use some non-threadsafe functions from the worker thread.
* See http://trac.wildfiregames.com/ticket/654
*/
-CNetServerWorker::CNetServerWorker(int autostartPlayers) :
+CNetServerWorker::CNetServerWorker(bool isDedicated, int autostartPlayers) :
+ m_DedicatedServer(isDedicated ? new CDedicatedServer() : NULL),
m_AutostartPlayers(autostartPlayers),
m_Shutdown(false),
m_ScriptInterface(NULL),
m_NextHostID(1), m_Host(NULL), m_Stats(NULL)
{
@@ -159,10 +161,11 @@ CNetServerWorker::~CNetServerWorker()
{
enet_host_destroy(m_Host);
}
delete m_ServerTurnManager;
+ delete m_DedicatedServer;
}
bool CNetServerWorker::SetupConnection()
{
ENSURE(m_State == SERVER_STATE_UNCONNECTED);
@@ -364,16 +367,19 @@ void* CNetServerWorker::RunThread(void*
void CNetServerWorker::Run()
{
// The script runtime uses the profiler and therefore the thread must be registered before the runtime is created
g_Profiler2.RegisterCurrentThread("Net server");
-
+
// To avoid the need for JS_SetContextThread, we create and use and destroy
// the script interface entirely within this network thread
m_ScriptInterface = new ScriptInterface("Engine", "Net server", ScriptInterface::CreateRuntime(g_ScriptRuntime));
m_GameAttributes.set(m_ScriptInterface->GetJSRuntime(), JS::UndefinedValue());
+ if (m_DedicatedServer)
+ m_DedicatedServer->Initialize();
+
while (true)
{
if (!RunStep())
break;
@@ -617,11 +623,14 @@ bool CNetServerWorker::HandleConnect(CNe
return session->SendMessage(&handshake);
}
void CNetServerWorker::OnUserJoin(CNetServerSession* session)
{
- AddPlayer(session->GetGUID(), session->GetUserName());
+ if (m_DedicatedServer)
+ m_DedicatedServer->OnUserJoin(session);
+ else
+ AddPlayer(session->GetGUID(), session->GetUserName());
CGameSetupMessage gameSetupMessage(GetScriptInterface());
gameSetupMessage.m_Data = m_GameAttributes.get();
session->SendMessage(&gameSetupMessage);
@@ -630,11 +639,14 @@ void CNetServerWorker::OnUserJoin(CNetSe
session->SendMessage(&assignMessage);
}
void CNetServerWorker::OnUserLeave(CNetServerSession* session)
{
- RemovePlayer(session->GetGUID());
+ if (m_DedicatedServer)
+ m_DedicatedServer->OnUserLeave(session);
+ else
+ RemovePlayer(session->GetGUID());
if (m_ServerTurnManager && session->GetCurrState() != NSS_JOIN_SYNCING)
m_ServerTurnManager->UninitialiseClient(session->GetHostID()); // TODO: only for non-observers
// TODO: ought to switch the player controlled by that client
@@ -858,10 +870,12 @@ bool CNetServerWorker::OnAuthenticate(vo
// Request a copy of the current game state from an existing player,
// so we can send it on to the new player
// Assume session 0 is most likely the local player, so they're
// the most efficient client to request a copy from
+
+ // TODO: When using a dedicated server we should use the client that didn't reconnect for the longest time
CNetServerSession* sourceSession = server.m_Sessions.at(0);
sourceSession->GetFileTransferer().StartTask(
shared_ptr(new CNetFileReceiveTask_ServerRejoin(server, newHostID))
);
@@ -918,10 +932,13 @@ bool CNetServerWorker::OnChat(void* cont
CChatMessage* message = (CChatMessage*)event->GetParamRef();
message->m_GUID = session->GetGUID();
+ if (server.m_DedicatedServer)
+ server.m_DedicatedServer->OnChat(session, message);
+
server.Broadcast(message);
return true;
}
@@ -936,10 +953,13 @@ bool CNetServerWorker::OnReady(void* con
message->m_GUID = session->GetGUID();
server.Broadcast(message);
+ if (server.m_DedicatedServer)
+ server.m_DedicatedServer->OnReady(session, message);
+
return true;
}
bool CNetServerWorker::OnLoadedGame(void* context, CFsmEvent* event)
{
@@ -1017,10 +1037,13 @@ bool CNetServerWorker::OnRejoined(void*
CRejoinedMessage* message = (CRejoinedMessage*)event->GetParamRef();
message->m_GUID = session->GetGUID();
+ if (server.m_DedicatedServer)
+ server.m_DedicatedServer->OnUserRejoined(session);
+
server.Broadcast(message);
return true;
}
@@ -1051,10 +1074,13 @@ void CNetServerWorker::CheckGameLoadStat
m_State = SERVER_STATE_INGAME;
}
void CNetServerWorker::StartGame()
{
+ if (m_DedicatedServer)
+ m_DedicatedServer->OnStartGame();
+
m_ServerTurnManager = new CNetServerTurnManager(*this);
for (size_t i = 0; i < m_Sessions.size(); ++i)
m_ServerTurnManager->InitialiseClient(m_Sessions[i]->GetHostID(), 0); // TODO: only for non-observers
@@ -1081,10 +1107,13 @@ void CNetServerWorker::UpdateGameAttribu
m_GameAttributes.set(m_ScriptInterface->GetJSRuntime(), attrs);
if (!m_Host)
return;
+ if (m_DedicatedServer)
+ LOGMESSAGE("[HOST] The game settings have been updated.");
+
CGameSetupMessage gameSetupMessage(GetScriptInterface());
gameSetupMessage.m_Data.set(m_GameAttributes.get());
Broadcast(&gameSetupMessage);
}
@@ -1136,12 +1165,12 @@ CStrW CNetServerWorker::DeduplicatePlaye
}
-CNetServer::CNetServer(int autostartPlayers) :
- m_Worker(new CNetServerWorker(autostartPlayers))
+CNetServer::CNetServer(bool isDedicated, int autostartPlayers) :
+ m_Worker(new CNetServerWorker(isDedicated, autostartPlayers))
{
}
CNetServer::~CNetServer()
{
Index: source/network/NetServer.h
===================================================================
--- source/network/NetServer.h (revision 17146)
+++ source/network/NetServer.h (working copy)
@@ -16,10 +16,11 @@
*/
#ifndef NETSERVER_H
#define NETSERVER_H
+#include "DedicatedServer.h"
#include "NetFileTransfer.h"
#include "NetHost.h"
#include "lib/config2.h"
#include "ps/ThreadUtil.h"
@@ -89,24 +90,28 @@ enum NetServerSessionState
NSS_INGAME
};
/**
* Network server interface. Handles all the coordination between players.
- * One person runs this object, and every player (including the host) connects their CNetClient to it.
+ * The host runs this object and every player connects their CNetClient to it.
+ * If it is a dedicated server, the host will not be a player (nor observer).
*
- * The actual work is performed by CNetServerWorker in a separate thread.
+ * The actual work is performed by CNetServerWorker and CDedicatedServer in a separate thread.
*/
class CNetServer
{
NONCOPYABLE(CNetServer);
public:
/**
* Construct a new network server.
+ *
+ * @param dedicated Whether or not to use dedicated mode (i.e. no graphics and no local simulation).
+ *
* @param autostartPlayers if positive then StartGame will be called automatically
* once this many players are connected (intended for the command-line testing mode).
*/
- CNetServer(int autostartPlayers = -1);
+ CNetServer(bool dedicated = false, int autostartPlayers = -1);
~CNetServer();
/**
* Begin listening for network connections.
@@ -153,10 +158,13 @@ public:
* TODO: we should replace this with some adapative lag-dependent computation.
*/
void SetTurnLength(u32 msecs);
private:
+
+ friend class CDedicatedServer;
+
CNetServerWorker* m_Worker;
};
/**
* Network server worker thread.
@@ -187,14 +195,15 @@ public:
* (i.e. are in the pre-game or in-game states).
*/
bool Broadcast(const CNetMessage* message);
private:
+ friend class CDedicatedServer;
friend class CNetServer;
friend class CNetFileReceiveTask_ServerRejoin;
- CNetServerWorker(int autostartPlayers);
+ CNetServerWorker(bool dedicated, int autostartPlayers);
~CNetServerWorker();
/**
* Begin listening for network connections.
* @return true on success, false on error (e.g. port already in use)
@@ -299,10 +308,12 @@ private:
CStrW m_ServerName;
CStrW m_WelcomeMessage;
u32 m_NextHostID;
+ CDedicatedServer* m_DedicatedServer;
+
CNetServerTurnManager* m_ServerTurnManager;
/**
* A copy of all simulation commands received so far, indexed by
* turn number, to simplify support for rejoining etc.
Index: source/network/NetSession.cpp
===================================================================
--- source/network/NetSession.cpp (revision 17146)
+++ source/network/NetSession.cpp (working copy)
@@ -76,10 +76,17 @@ bool CNetClientSession::Connect(u16 port
g_ProfileViewer.AddRootTable(m_Stats);
return true;
}
+CStr CNetServerSession::GetIPAddress()
+{
+ char ipAddress[256] = "(error)";
+ enet_address_get_host_ip(&(m_Peer->address), ipAddress, ARRAY_SIZE(ipAddress));
+ return CStr(ipAddress);
+}
+
void CNetClientSession::Disconnect(u32 reason)
{
ENSURE(m_Host && m_Server);
// TODO: ought to do reliable async disconnects, probably
Index: source/network/NetSession.h
===================================================================
--- source/network/NetSession.h (revision 17146)
+++ source/network/NetSession.h (working copy)
@@ -120,10 +120,15 @@ public:
void SetUserName(const CStrW& name) { m_UserName = name; }
u32 GetHostID() const { return m_HostID; }
void SetHostID(u32 id) { m_HostID = id; }
+ /**
+ * Returns the IP address of the client.
+ */
+ CStr GetIPAddress();
+
/**
* Sends a disconnection notification to the client,
* and sends a NMT_CONNECTION_LOST message to the session FSM.
* The server will receive a disconnection notification after a while.
* The server will not receive any further messages sent via this session.
Index: source/network/NetTurnManager.cpp
===================================================================
--- source/network/NetTurnManager.cpp (revision 17146)
+++ source/network/NetTurnManager.cpp (working copy)
@@ -630,10 +630,11 @@ void CNetServerTurnManager::NotifyFinish
{
if (it->first > newest)
break;
// Assume the host is correct (maybe we should choose the most common instead to help debugging)
+ // TODO: for dedicated hosts, we have to chose the hash of a client that didn't rejoin for the longest period
std::string expected = it->second.begin()->second;
// Find all players that are OOS on that turn
std::vector OOSPlayerNames;
for (std::map::iterator cit = it->second.begin(); cit != it->second.end(); ++cit)
Index: source/ps/GameSetup/GameSetup.cpp
===================================================================
--- source/ps/GameSetup/GameSetup.cpp (revision 17146)
+++ source/ps/GameSetup/GameSetup.cpp (working copy)
@@ -933,11 +933,12 @@ bool Init(const CmdLineArgs& args, int f
}
CNetHost::Initialize();
#if CONFIG2_AUDIO
- ISoundManager::CreateSoundManager();
+ if (!args.Has("dedicated-host"))
+ ISoundManager::CreateSoundManager();
#endif
// Check if there are mods specified on the command line,
// or if we already set the mods (~INIT_MODS),
// else check if there are mods that should be loaded specified