Ticket #3556: t3556_dedicated_server_WIP_v0.1.patch
File t3556_dedicated_server_WIP_v0.1.patch, 20.1 KB (added by , 8 years ago) |
---|
-
source/network/DedicatedServer.cpp
1 /* Copyright (C) 2015 Wildfire Games. 2 * This file is part of 0 A.D. 3 * 4 * 0 A.D. is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 2 of the License, or 7 * (at your option) any later version. 8 * 9 * 0 A.D. is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18 //#include "precompiled.h" ?? 19 20 #include "DedicatedServer.h" 21 #include "ps/CLogger.h" 22 23 u8 g_DedicatedHostPlayers = 0; 24 25 // TODO: make this a friend class of NetServer/NetServerWorker 26 // TODO: manage SERVER_STATE 27 // TODO: use ReplayLogger 28 // TODO: end the game when somebody won 29 // TODO: end the game when everybody left 30 // TODO: start a new game after one finished 31 32 void DedicatedServer::InitGameAttributes(ScriptInterface* scriptInterface, CNetServerWorker* serverWorker) 33 { 34 LOGERROR("[HOST] Initialize game-attributes"); 35 36 CStrW serverName(L"Autohost"); 37 //if (g_args.Has("dedicated-host-name")) 38 // serverName = wstring_from_utf8(g_args.Get("dedicated-host-name")); 39 40 JSContext* cx = scriptInterface->GetContext(); 41 JSAutoRequest rq(cx); 42 43 // Needs to be kept in sync with gamesetup.js 44 JS::RootedValue attribs(cx); 45 JS::RootedValue settings(cx); 46 JS::RootedValue playerAssignments(cx); 47 JS::RootedValue PlayerData(cx); 48 JS::RootedValue VictoryScripts(cx); 49 50 scriptInterface->Eval("({})", &attribs); 51 scriptInterface->Eval("({})", &settings); 52 scriptInterface->Eval("({})", &playerAssignments); 53 scriptInterface->Eval("([])", &PlayerData); 54 scriptInterface->Eval("([\"scripts/TriggerHelper.js\",\"scripts/ConquestCommon.js\",\"scripts/Conquest.js\"])", &VictoryScripts); 55 56 scriptInterface->SetProperty(settings, "AISeed", rand()); 57 scriptInterface->SetProperty(settings, "Seed", rand()); 58 scriptInterface->SetProperty(settings, "Ceasefire", 0); 59 scriptInterface->SetProperty(settings, "CheatsEnabled", false); 60 scriptInterface->SetProperty(settings, "GameType", std::wstring(L"conquest")); 61 scriptInterface->SetProperty(settings, "PlayerData", PlayerData); 62 scriptInterface->SetProperty(settings, "PopulationCap", 300); 63 scriptInterface->SetProperty(settings, "Size", 320); // map size 4 64 scriptInterface->SetProperty(settings, "StartingResources", 500); // medium res for nomad 65 scriptInterface->SetProperty(settings, "VictoryScripts", VictoryScripts); 66 // disable treasure, no revealed map, no explored map 67 // "timestamp": "1446043600", 68 // "engine_version": "0.0.19", 69 // "mods": ["mod","public"] 70 //"Preview": "acropolis_bay.png", 71 //CircularMap 72 //Description 73 scriptInterface->SetProperty(attribs, "gameSpeed", 1); 74 scriptInterface->SetProperty(attribs, "isNetworked", true); 75 scriptInterface->SetProperty(attribs, "map", std::wstring(L"random")); 76 scriptInterface->SetProperty(attribs, "mapFilter", std::wstring(L"default")); 77 scriptInterface->SetProperty(attribs, "mapPath", std::wstring(L"maps/random/")); 78 scriptInterface->SetProperty(attribs, "mapType", std::wstring(L"random")); 79 scriptInterface->SetProperty(attribs, "matchID", ps_generate_guid().FromUTF8()); 80 scriptInterface->SetProperty(attribs, "playerAssignments", playerAssignments); 81 scriptInterface->SetProperty(attribs, "serverName", serverName); 82 scriptInterface->SetProperty(attribs, "settings", settings); 83 84 serverWorker->UpdateGameAttributes(&attribs); 85 } 86 87 void DedicatedServer::UpdatePlayerAssignments(ScriptInterface& scriptInterface, CNetServerWorker& serverWorker) 88 { 89 JSContext* cx = scriptInterface.GetContext(); 90 JSAutoRequest rq(cx); 91 92 JS::RootedValue attribs(cx); 93 JS::RootedValue settings(cx); 94 JS::RootedValue PlayerData(cx); 95 96 attribs.set(serverWorker.m_GameAttributes.get()); 97 scriptInterface.GetProperty(attribs, "settings", &settings); 98 scriptInterface.Eval("([])", &PlayerData); 99 100 // Find all player IDs in active use 101 /* std::set<i32> usedIDs; 102 for (PlayerAssignmentMap::iterator it = worker..m_PlayerAssignments.begin(); it != m_PlayerAssignments.end(); ++it) 103 if (it->second.m_Enabled && it->second.m_PlayerID != -1) 104 usedIDs.insert(it->second.m_PlayerID); 105 */ 106 // Update player data 107 for (u8 i=0; i < serverWorker.m_PlayerAssignments.size(); i++) 108 { 109 JS::RootedValue player(cx); 110 scriptInterface.Eval("({})", &player); 111 //scriptInterface.SetProperty(player, "Name", "Some player"); 112 scriptInterface.SetProperty(player, "Team", -1); 113 scriptInterface.SetProperty(player, "Civ", std::wstring(L"random")); 114 scriptInterface.SetProperty(player, "AI", std::wstring(L"")); 115 scriptInterface.SetProperty(player, "AiDiff", 3); 116 scriptInterface.SetPropertyInt(PlayerData, i, player); 117 } 118 scriptInterface.SetProperty(settings, "PlayerData", PlayerData); 119 120 // Limit pop cap according to playercount 121 std::map<int,int> popCaps = { 122 {1, 300}, // 600 pop total, 700 with wonder 123 {2, 300}, // 600 pop total, 750 with wonder 124 {3, 200}, // 600 pop total, 750 with wonder 125 {4, 200}, // 800 pop total, 1000 with wonder 126 {5, 150}, // 750 pop total, 1000 with wonder 127 {6, 150}, // 900 pop total, 1200 with wonder 128 {7, 100}, // 700 pop total, 1050 with wonder 129 {8, 100} // 800 pop total, 1200 with wonder 130 }; 131 scriptInterface.SetProperty(settings, "PopulationCap", popCaps[serverWorker.m_PlayerAssignments.size()]); 132 133 scriptInterface.SetProperty(attribs, "settings", settings); 134 135 serverWorker.UpdateGameAttributes(&attribs); 136 137 // Also sends the player assignments 138 serverWorker.ClearAllPlayerReady(); 139 } 140 141 void DedicatedServer::OnChat(CNetServerSession* session, CChatMessage* message) 142 { 143 // CNetServerWorker& serverWorker = session->GetServer(); 144 LOGERROR("[HOST] %s: %s", utf8_from_wstring(session->GetUserName().c_str()), utf8_from_wstring(message->m_Message)); 145 } 146 147 void DedicatedServer::OnReady(CNetServerSession* session, CReadyMessage* message) 148 { 149 // CNetServerWorker& serverWorker = session->GetServer(); 150 LOGERROR("[HOST] %s is %s", utf8_from_wstring(session->GetUserName().c_str()), message->m_Status ? "ready " : "not ready"); 151 // TODO: start game if all are ready 152 // StartGame(); 153 } 154 155 void DedicatedServer::OnUserJoin(ScriptInterface* m_ScriptInterface, CNetServerSession* session) 156 { 157 CNetServerWorker& serverWorker = session->GetServer(); 158 ++g_DedicatedHostPlayers; 159 // TODO: if game has started, show "is starting to rejoin" 160 LOGERROR("[HOST] %s has joined (%s)", utf8_from_wstring(session->GetUserName()).c_str(), session->GetIPAddress().c_str()); 161 UpdatePlayerAssignments(*m_ScriptInterface, serverWorker); 162 } 163 164 void DedicatedServer::OnUserLeave(ScriptInterface* m_ScriptInterface, CNetServerSession* session) 165 { 166 CNetServerWorker& serverWorker = session->GetServer(); 167 --g_DedicatedHostPlayers; 168 LOGERROR("[HOST] %s has left", utf8_from_wstring(session->GetUserName())); 169 UpdatePlayerAssignments(*m_ScriptInterface, serverWorker); 170 } 171 172 void DedicatedServer::OnUserRejoined(CNetServerSession* session) 173 { 174 CNetServerWorker& serverWorker = session->GetServer(); 175 LOGERROR("[HOST] %s has finished rejoining.", utf8_from_wstring(serverWorker.m_PlayerAssignments[session->GetGUID()].m_Name.c_str())); 176 } 177 178 void DedicatedServer::OnStartGame() 179 { 180 LOGERROR("[HOST] The game has started."); 181 } -
source/network/DedicatedServer.h
1 /* Copyright (C) 2015 Wildfire Games. 2 * This file is part of 0 A.D. 3 * 4 * 0 A.D. is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 2 of the License, or 7 * (at your option) any later version. 8 * 9 * 0 A.D. is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18 // TODO: some ifndef define ? 19 20 #include "NetServer.h" 21 #include "NetSession.h" 22 #include "NetMessage.h" 23 24 #include "ps/GUID.h" 25 #include "scriptinterface/ScriptInterface.h" 26 27 /** 28 * Contains functions used by the dedicated server. 29 */ 30 namespace DedicatedServer 31 { 32 33 /* 34 * Contains the default gamesetup atributes for autohosted games. 35 * 36 * TODO: load from a JSON file 37 */ 38 void InitGameAttributes(ScriptInterface* scriptInterface, CNetServerWorker* serverWorker); 39 40 /** 41 * Sets the number of players to the number of connected clients, 42 * assigns unassigned players to unassigned slots, 43 * reduces the population count accordingly, 44 * resets ready states, 45 * sends the player assignments. 46 */ 47 void UpdatePlayerAssignments(ScriptInterface& scriptInterface, CNetServerWorker& serverWorker); 48 49 /** 50 * Lets the users chose their own civs and team numbers. 51 * 52 * TODO: we should let the clients chose their civs and team numbers (but nothing else) via the regular GUI elements 53 */ 54 void OnChat(CNetServerSession* session, CChatMessage* message); 55 56 /** 57 * Starts the game if all players are ready. 58 */ 59 void OnReady(CNetServerSession* session, CReadyMessage* message); 60 61 /** 62 * Updates the player assignments. 63 */ 64 void OnUserJoin(ScriptInterface* scriptInterface, CNetServerSession* session); 65 66 /** 67 * Updates the player assignments. 68 */ 69 void OnUserLeave(ScriptInterface* scriptInterface, CNetServerSession* session); 70 71 /** 72 * Used for logging only. 73 */ 74 void OnUserRejoined(CNetServerSession* session); 75 76 /** 77 * Used for logging only. 78 */ 79 void OnStartGame(); 80 81 } -
source/network/NetServer.cpp
17 17 18 18 #include "precompiled.h" 19 19 20 20 #include "NetServer.h" 21 21 22 #include "DedicatedServer.h" 22 23 #include "NetClient.h" 23 24 #include "NetMessage.h" 24 25 #include "NetSession.h" 25 26 #include "NetStats.h" 26 27 #include "NetTurnManager.h" … … private: 116 117 * XXX: We use some non-threadsafe functions from the worker thread. 117 118 * See http://trac.wildfiregames.com/ticket/654 118 119 */ 119 120 120 121 CNetServerWorker::CNetServerWorker(int autostartPlayers) : 122 m_IsDedicated(false), 121 123 m_AutostartPlayers(autostartPlayers), 122 124 m_Shutdown(false), 123 125 m_ScriptInterface(NULL), 124 126 m_NextHostID(1), m_Host(NULL), m_Stats(NULL) 125 127 { … … bool CNetServerWorker::Broadcast(const C 353 355 return ok; 354 356 } 355 357 356 358 void* CNetServerWorker::RunThread(void* data) 357 359 { 360 LOGERROR("CNetServerWorker::RunThread"); 358 361 debug_SetThreadName("NetServer"); 359 362 360 363 static_cast<CNetServerWorker*>(data)->Run(); 361 364 362 365 return NULL; 363 366 } 364 367 365 368 void CNetServerWorker::Run() 366 369 { 370 LOGMESSAGE("CNetServerWorker::Run"); 371 367 372 // The script runtime uses the profiler and therefore the thread must be registered before the runtime is created 368 373 g_Profiler2.RegisterCurrentThread("Net server"); 369 374 370 375 // To avoid the need for JS_SetContextThread, we create and use and destroy 371 376 // the script interface entirely within this network thread 372 377 m_ScriptInterface = new ScriptInterface("Engine", "Net server", ScriptInterface::CreateRuntime(g_ScriptRuntime)); 373 378 m_GameAttributes.set(m_ScriptInterface->GetJSRuntime(), JS::UndefinedValue()); 374 379 380 if (m_IsDedicated) 381 DedicatedServer::InitGameAttributes(m_ScriptInterface, this); 382 375 383 while (true) 376 384 { 377 385 if (!RunStep()) 378 386 break; 379 387 … … bool CNetServerWorker::HandleConnect(CNe 617 625 return session->SendMessage(&handshake); 618 626 } 619 627 620 628 void CNetServerWorker::OnUserJoin(CNetServerSession* session) 621 629 { 622 AddPlayer(session->GetGUID(), session->GetUserName()); 630 if (m_IsDedicated) 631 DedicatedServer::OnUserJoin(m_ScriptInterface, session); 632 else 633 AddPlayer(session->GetGUID(), session->GetUserName()); 623 634 624 635 CGameSetupMessage gameSetupMessage(GetScriptInterface()); 625 636 gameSetupMessage.m_Data = m_GameAttributes.get(); 626 637 session->SendMessage(&gameSetupMessage); 627 638 … … void CNetServerWorker::OnUserJoin(CNetSe 630 641 session->SendMessage(&assignMessage); 631 642 } 632 643 633 644 void CNetServerWorker::OnUserLeave(CNetServerSession* session) 634 645 { 635 RemovePlayer(session->GetGUID()); 646 if (m_IsDedicated) 647 DedicatedServer::OnUserLeave(m_ScriptInterface, session); 648 else 649 RemovePlayer(session->GetGUID()); 636 650 637 651 if (m_ServerTurnManager && session->GetCurrState() != NSS_JOIN_SYNCING) 638 652 m_ServerTurnManager->UninitialiseClient(session->GetHostID()); // TODO: only for non-observers 639 653 640 654 // TODO: ought to switch the player controlled by that client … … bool CNetServerWorker::OnChat(void* cont 918 932 919 933 CChatMessage* message = (CChatMessage*)event->GetParamRef(); 920 934 921 935 message->m_GUID = session->GetGUID(); 922 936 937 if (server.m_IsDedicated) 938 DedicatedServer::OnChat(session, message); 939 923 940 server.Broadcast(message); 924 941 925 942 return true; 926 943 } 927 944 … … bool CNetServerWorker::OnReady(void* con 936 953 937 954 message->m_GUID = session->GetGUID(); 938 955 939 956 server.Broadcast(message); 940 957 958 if (server.m_IsDedicated) 959 DedicatedServer::OnReady(session, message); 960 941 961 return true; 942 962 } 943 963 944 964 bool CNetServerWorker::OnLoadedGame(void* context, CFsmEvent* event) 945 965 { … … bool CNetServerWorker::OnRejoined(void* 1017 1037 1018 1038 CRejoinedMessage* message = (CRejoinedMessage*)event->GetParamRef(); 1019 1039 1020 1040 message->m_GUID = session->GetGUID(); 1021 1041 1042 if (server.m_IsDedicated) 1043 DedicatedServer::OnUserRejoined(session); 1044 1022 1045 server.Broadcast(message); 1023 1046 1024 1047 return true; 1025 1048 } 1026 1049 … … void CNetServerWorker::CheckGameLoadStat 1051 1074 m_State = SERVER_STATE_INGAME; 1052 1075 } 1053 1076 1054 1077 void CNetServerWorker::StartGame() 1055 1078 { 1079 if (m_IsDedicated) 1080 DedicatedServer::OnStartGame(); 1081 1056 1082 m_ServerTurnManager = new CNetServerTurnManager(*this); 1057 1083 1058 1084 for (size_t i = 0; i < m_Sessions.size(); ++i) 1059 1085 m_ServerTurnManager->InitialiseClient(m_Sessions[i]->GetHostID(), 0); // TODO: only for non-observers 1060 1086 … … void CNetServerWorker::UpdateGameAttribu 1081 1107 m_GameAttributes.set(m_ScriptInterface->GetJSRuntime(), attrs); 1082 1108 1083 1109 if (!m_Host) 1084 1110 return; 1085 1111 1112 if (m_IsDedicated) 1113 LOGMESSAGE("[HOST] The game settings have been updated."); 1114 1086 1115 CGameSetupMessage gameSetupMessage(GetScriptInterface()); 1087 1116 gameSetupMessage.m_Data.set(m_GameAttributes.get()); 1088 1117 Broadcast(&gameSetupMessage); 1089 1118 } 1090 1119 … … CNetServer::~CNetServer() 1151 1180 bool CNetServer::SetupConnection() 1152 1181 { 1153 1182 return m_Worker->SetupConnection(); 1154 1183 } 1155 1184 1185 void CNetServer::StartDedicatedHost() 1186 { 1187 m_Worker->m_IsDedicated = true; 1188 1189 LOGMESSAGE("[HOST] Starting dedicated host"); 1190 1191 if (!m_Worker->SetupConnection()) 1192 { 1193 LOGERROR("ERROR: Could not start the server!\n"); 1194 SAFE_DELETE(g_NetServer); 1195 return; 1196 } 1197 1198 // TODO: loop 'til doomsday? 1199 while (true); 1200 } 1201 1156 1202 void CNetServer::AssignPlayer(int playerID, const CStr& guid) 1157 1203 { 1158 1204 CScopeLock lock(m_Worker->m_WorkerMutex); 1159 1205 m_Worker->m_AssignPlayerQueue.emplace_back(playerID, guid); 1160 1206 } -
source/network/NetServer.h
public: 114 114 * @return true on success, false on error (e.g. port already in use) 115 115 */ 116 116 bool SetupConnection(); 117 117 118 118 /** 119 * Start the actual hosting. 120 */ 121 void StartDedicatedHost(); 122 123 /** 119 124 * Call from the GUI to update the player assignments. 120 125 * The given GUID will be (re)assigned to the given player ID. 121 126 * Any player currently using that ID will be unassigned. 122 127 * The changes will be asynchronously propagated to all clients. 123 128 */ … … public: 186 191 * Send a message to all clients who have completed the full connection process 187 192 * (i.e. are in the pre-game or in-game states). 188 193 */ 189 194 bool Broadcast(const CNetMessage* message); 190 195 191 p rivate:196 public: 192 197 friend class CNetServer; 193 198 friend class CNetFileReceiveTask_ServerRejoin; 194 199 195 200 CNetServerWorker(int autostartPlayers); 196 201 ~CNetServerWorker(); … … private: 294 299 295 300 CNetStatsTable* m_Stats; 296 301 297 302 NetServerState m_State; 298 303 304 bool m_IsDedicated; 305 299 306 CStrW m_ServerName; 300 307 CStrW m_WelcomeMessage; 301 308 302 309 u32 m_NextHostID; 303 310 -
source/network/NetSession.cpp
bool CNetClientSession::Connect(u16 port 76 76 g_ProfileViewer.AddRootTable(m_Stats); 77 77 78 78 return true; 79 79 } 80 80 81 CStr CNetServerSession::GetIPAddress() 82 { 83 char ipAddress[256] = "(error)"; 84 enet_address_get_host_ip(&(m_Peer->address), ipAddress, ARRAY_SIZE(ipAddress)); 85 return CStr(ipAddress); 86 } 87 81 88 void CNetClientSession::Disconnect(u32 reason) 82 89 { 83 90 ENSURE(m_Host && m_Server); 84 91 85 92 // TODO: ought to do reliable async disconnects, probably -
source/network/NetSession.h
public: 120 120 void SetUserName(const CStrW& name) { m_UserName = name; } 121 121 122 122 u32 GetHostID() const { return m_HostID; } 123 123 void SetHostID(u32 id) { m_HostID = id; } 124 124 125 /** 126 * Returns the IP address of the client. 127 */ 128 CStr GetIPAddress(); 129 125 130 /** 126 131 * Sends a disconnection notification to the client, 127 132 * and sends a NMT_CONNECTION_LOST message to the session FSM. 128 133 * The server will receive a disconnection notification after a while. 129 134 * The server will not receive any further messages sent via this session. -
source/ps/GameSetup/GameSetup.cpp
45 45 #include "gui/GUI.h" 46 46 #include "gui/GUIManager.h" 47 47 #include "gui/scripting/ScriptFunctions.h" 48 48 #include "i18n/L10n.h" 49 49 #include "maths/MathUtil.h" 50 #include "network/DedicatedServer.h" 50 51 #include "network/NetServer.h" 51 52 #include "network/NetClient.h" 52 53 53 54 #include "ps/CConsole.h" 54 55 #include "ps/CLogger.h" … … CStr8 LoadSettingsOfScenarioMap(const Vf 1190 1191 * -autostart="random/alpine_lakes" -autostart-seed=-1 -autostart-players=2 -autostart-civ=1:athen -autostart-civ=2:brit -autostart-ai=2:petra 1191 1192 */ 1192 1193 bool Autostart(const CmdLineArgs& args) 1193 1194 { 1194 1195 CStr autoStartName = args.Get("autostart"); 1195 1196 if (autoStartName.empty()) 1196 if (autoStartName.empty() && !args.Has("dedicated-host")) 1197 1197 return false; 1198 1198 1199 1199 g_Game = new CGame(); 1200 1200 1201 1201 ScriptInterface& scriptInterface = g_Game->GetSimulation2()->GetScriptInterface(); … … bool Autostart(const CmdLineArgs& args) 1298 1298 if (mapDirectory == L"scenarios") 1299 1299 mapType = "scenario"; 1300 1300 else 1301 1301 mapType = "skirmish"; 1302 1302 } 1303 else 1303 else if (!args.Has("dedicated-host")) 1304 1304 { 1305 1305 LOGERROR("Autostart: Unrecognized map type '%s'", utf8_from_wstring(mapDirectory)); 1306 1306 throw PSERROR_Game_World_MapLoadFailed("Unrecognized map type.\nConsult readme.txt for the currently supported types."); 1307 1307 } 1308 1308 1309 scriptInterface.SetProperty(attrs, "mapType", mapType); 1309 1310 scriptInterface.SetProperty(attrs, "map", std::string("maps/" + autoStartName)); 1310 1311 scriptInterface.SetProperty(settings, "mapType", mapType); 1311 1312 1312 1313 // Set seed for AIs … … bool Autostart(const CmdLineArgs& args) 1422 1423 if (args.Has("autostart-playername")) 1423 1424 { 1424 1425 userName = args.Get("autostart-playername").FromUTF8(); 1425 1426 } 1426 1427 1427 if (args.Has("autostart-host")) 1428 if (args.Has("dedicated-host")) 1429 { 1430 g_NetServer = new CNetServer(); 1431 g_NetServer->StartDedicatedHost(); 1432 CXeromyces::Terminate(); 1433 return true; 1434 } 1435 else if (args.Has("autostart-host")) 1428 1436 { 1429 1437 InitPs(true, L"page_loading.xml", &scriptInterface, mpInitData); 1430 1438 1431 1439 size_t maxPlayers = 2; 1432 1440 if (args.Has("autostart-host-players"))