Ticket #3556: t3556_dedicated_server_WIP_v0.1.patch

File t3556_dedicated_server_WIP_v0.1.patch, 20.1 KB (added by elexis, 8 years ago)

Proof of concept. Server running and chat working. Still opens an unused window and has no features besides chat yet.

  • 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
     23u8 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
     32void 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
     87void 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
     141void 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
     147void 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
     155void 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
     164void 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
     172void 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
     178void 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 */
     30namespace DedicatedServer
     31{
     32
     33/*
     34 * Contains the default gamesetup atributes for autohosted games.
     35 *
     36 * TODO: load from a JSON file
     37 */
     38void 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 */
     47void 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 */
     54void OnChat(CNetServerSession* session, CChatMessage* message);
     55
     56/**
     57 * Starts the game if all players are ready.
     58 */
     59void OnReady(CNetServerSession* session, CReadyMessage* message);
     60
     61/**
     62 * Updates the player assignments.
     63 */
     64void OnUserJoin(ScriptInterface* scriptInterface, CNetServerSession* session);
     65
     66/**
     67 * Updates the player assignments.
     68 */
     69void OnUserLeave(ScriptInterface* scriptInterface, CNetServerSession* session);
     70
     71/**
     72 * Used for logging only.
     73 */
     74void OnUserRejoined(CNetServerSession* session);
     75
     76/**
     77 * Used for logging only.
     78 */
     79void OnStartGame();
     80
     81}
  • source/network/NetServer.cpp

     
    1717
    1818#include "precompiled.h"
    1919
    2020#include "NetServer.h"
    2121
     22#include "DedicatedServer.h"
    2223#include "NetClient.h"
    2324#include "NetMessage.h"
    2425#include "NetSession.h"
    2526#include "NetStats.h"
    2627#include "NetTurnManager.h"
    private:  
    116117 * XXX: We use some non-threadsafe functions from the worker thread.
    117118 * See http://trac.wildfiregames.com/ticket/654
    118119 */
    119120
    120121CNetServerWorker::CNetServerWorker(int autostartPlayers) :
     122    m_IsDedicated(false),
    121123    m_AutostartPlayers(autostartPlayers),
    122124    m_Shutdown(false),
    123125    m_ScriptInterface(NULL),
    124126    m_NextHostID(1), m_Host(NULL), m_Stats(NULL)
    125127{
    bool CNetServerWorker::Broadcast(const C  
    353355    return ok;
    354356}
    355357
    356358void* CNetServerWorker::RunThread(void* data)
    357359{
     360    LOGERROR("CNetServerWorker::RunThread");
    358361    debug_SetThreadName("NetServer");
    359362
    360363    static_cast<CNetServerWorker*>(data)->Run();
    361364
    362365    return NULL;
    363366}
    364367
    365368void CNetServerWorker::Run()
    366369{
     370    LOGMESSAGE("CNetServerWorker::Run");
     371
    367372    // The script runtime uses the profiler and therefore the thread must be registered before the runtime is created
    368373    g_Profiler2.RegisterCurrentThread("Net server");
    369    
     374
    370375    // To avoid the need for JS_SetContextThread, we create and use and destroy
    371376    // the script interface entirely within this network thread
    372377    m_ScriptInterface = new ScriptInterface("Engine", "Net server", ScriptInterface::CreateRuntime(g_ScriptRuntime));
    373378    m_GameAttributes.set(m_ScriptInterface->GetJSRuntime(), JS::UndefinedValue());
    374379
     380    if (m_IsDedicated)
     381        DedicatedServer::InitGameAttributes(m_ScriptInterface, this);
     382
    375383    while (true)
    376384    {
    377385        if (!RunStep())
    378386            break;
    379387
    bool CNetServerWorker::HandleConnect(CNe  
    617625    return session->SendMessage(&handshake);
    618626}
    619627
    620628void CNetServerWorker::OnUserJoin(CNetServerSession* session)
    621629{
    622     AddPlayer(session->GetGUID(), session->GetUserName());
     630    if (m_IsDedicated)
     631        DedicatedServer::OnUserJoin(m_ScriptInterface, session);
     632    else
     633        AddPlayer(session->GetGUID(), session->GetUserName());
    623634
    624635    CGameSetupMessage gameSetupMessage(GetScriptInterface());
    625636    gameSetupMessage.m_Data = m_GameAttributes.get();
    626637    session->SendMessage(&gameSetupMessage);
    627638
    void CNetServerWorker::OnUserJoin(CNetSe  
    630641    session->SendMessage(&assignMessage);
    631642}
    632643
    633644void CNetServerWorker::OnUserLeave(CNetServerSession* session)
    634645{
    635     RemovePlayer(session->GetGUID());
     646    if (m_IsDedicated)
     647        DedicatedServer::OnUserLeave(m_ScriptInterface, session);
     648    else
     649        RemovePlayer(session->GetGUID());
    636650
    637651    if (m_ServerTurnManager && session->GetCurrState() != NSS_JOIN_SYNCING)
    638652        m_ServerTurnManager->UninitialiseClient(session->GetHostID()); // TODO: only for non-observers
    639653
    640654    // TODO: ought to switch the player controlled by that client
    bool CNetServerWorker::OnChat(void* cont  
    918932
    919933    CChatMessage* message = (CChatMessage*)event->GetParamRef();
    920934
    921935    message->m_GUID = session->GetGUID();
    922936
     937    if (server.m_IsDedicated)
     938        DedicatedServer::OnChat(session, message);
     939
    923940    server.Broadcast(message);
    924941
    925942    return true;
    926943}
    927944
    bool CNetServerWorker::OnReady(void* con  
    936953
    937954    message->m_GUID = session->GetGUID();
    938955
    939956    server.Broadcast(message);
    940957
     958    if (server.m_IsDedicated)
     959        DedicatedServer::OnReady(session, message);
     960
    941961    return true;
    942962}
    943963
    944964bool CNetServerWorker::OnLoadedGame(void* context, CFsmEvent* event)
    945965{
    bool CNetServerWorker::OnRejoined(void*  
    10171037
    10181038    CRejoinedMessage* message = (CRejoinedMessage*)event->GetParamRef();
    10191039
    10201040    message->m_GUID = session->GetGUID();
    10211041
     1042    if (server.m_IsDedicated)
     1043        DedicatedServer::OnUserRejoined(session);
     1044
    10221045    server.Broadcast(message);
    10231046
    10241047    return true;
    10251048}
    10261049
    void CNetServerWorker::CheckGameLoadStat  
    10511074    m_State = SERVER_STATE_INGAME;
    10521075}
    10531076
    10541077void CNetServerWorker::StartGame()
    10551078{
     1079    if (m_IsDedicated)
     1080        DedicatedServer::OnStartGame();
     1081
    10561082    m_ServerTurnManager = new CNetServerTurnManager(*this);
    10571083
    10581084    for (size_t i = 0; i < m_Sessions.size(); ++i)
    10591085        m_ServerTurnManager->InitialiseClient(m_Sessions[i]->GetHostID(), 0); // TODO: only for non-observers
    10601086
    void CNetServerWorker::UpdateGameAttribu  
    10811107    m_GameAttributes.set(m_ScriptInterface->GetJSRuntime(), attrs);
    10821108
    10831109    if (!m_Host)
    10841110        return;
    10851111
     1112    if (m_IsDedicated)
     1113        LOGMESSAGE("[HOST] The game settings have been updated.");
     1114
    10861115    CGameSetupMessage gameSetupMessage(GetScriptInterface());
    10871116    gameSetupMessage.m_Data.set(m_GameAttributes.get());
    10881117    Broadcast(&gameSetupMessage);
    10891118}
    10901119
    CNetServer::~CNetServer()  
    11511180bool CNetServer::SetupConnection()
    11521181{
    11531182    return m_Worker->SetupConnection();
    11541183}
    11551184
     1185void 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
    11561202void CNetServer::AssignPlayer(int playerID, const CStr& guid)
    11571203{
    11581204    CScopeLock lock(m_Worker->m_WorkerMutex);
    11591205    m_Worker->m_AssignPlayerQueue.emplace_back(playerID, guid);
    11601206}
  • source/network/NetServer.h

    public:  
    114114     * @return true on success, false on error (e.g. port already in use)
    115115     */
    116116    bool SetupConnection();
    117117
    118118    /**
     119     * Start the actual hosting.
     120     */
     121    void StartDedicatedHost();
     122
     123    /**
    119124     * Call from the GUI to update the player assignments.
    120125     * The given GUID will be (re)assigned to the given player ID.
    121126     * Any player currently using that ID will be unassigned.
    122127     * The changes will be asynchronously propagated to all clients.
    123128     */
    public:  
    186191     * Send a message to all clients who have completed the full connection process
    187192     * (i.e. are in the pre-game or in-game states).
    188193     */
    189194    bool Broadcast(const CNetMessage* message);
    190195
    191 private:
     196public:
    192197    friend class CNetServer;
    193198    friend class CNetFileReceiveTask_ServerRejoin;
    194199
    195200    CNetServerWorker(int autostartPlayers);
    196201    ~CNetServerWorker();
    private:  
    294299
    295300    CNetStatsTable* m_Stats;
    296301
    297302    NetServerState m_State;
    298303
     304    bool m_IsDedicated;
     305
    299306    CStrW m_ServerName;
    300307    CStrW m_WelcomeMessage;
    301308
    302309    u32 m_NextHostID;
    303310
  • source/network/NetSession.cpp

    bool CNetClientSession::Connect(u16 port  
    7676        g_ProfileViewer.AddRootTable(m_Stats);
    7777
    7878    return true;
    7979}
    8080
     81CStr 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
    8188void CNetClientSession::Disconnect(u32 reason)
    8289{
    8390    ENSURE(m_Host && m_Server);
    8491
    8592    // TODO: ought to do reliable async disconnects, probably
  • source/network/NetSession.h

    public:  
    120120    void SetUserName(const CStrW& name) { m_UserName = name; }
    121121
    122122    u32 GetHostID() const { return m_HostID; }
    123123    void SetHostID(u32 id) { m_HostID = id; }
    124124
     125    /**
     126     * Returns the IP address of the client.
     127     */
     128    CStr GetIPAddress();
     129
    125130    /**
    126131     * Sends a disconnection notification to the client,
    127132     * and sends a NMT_CONNECTION_LOST message to the session FSM.
    128133     * The server will receive a disconnection notification after a while.
    129134     * The server will not receive any further messages sent via this session.
  • source/ps/GameSetup/GameSetup.cpp

     
    4545#include "gui/GUI.h"
    4646#include "gui/GUIManager.h"
    4747#include "gui/scripting/ScriptFunctions.h"
    4848#include "i18n/L10n.h"
    4949#include "maths/MathUtil.h"
     50#include "network/DedicatedServer.h"
    5051#include "network/NetServer.h"
    5152#include "network/NetClient.h"
    5253
    5354#include "ps/CConsole.h"
    5455#include "ps/CLogger.h"
    CStr8 LoadSettingsOfScenarioMap(const Vf  
    11901191 * -autostart="random/alpine_lakes" -autostart-seed=-1 -autostart-players=2 -autostart-civ=1:athen -autostart-civ=2:brit -autostart-ai=2:petra
    11911192*/
    11921193bool Autostart(const CmdLineArgs& args)
    11931194{
    11941195    CStr autoStartName = args.Get("autostart");
    1195 
    1196     if (autoStartName.empty())
     1196    if (autoStartName.empty() && !args.Has("dedicated-host"))
    11971197        return false;
    11981198
    11991199    g_Game = new CGame();
    12001200
    12011201    ScriptInterface& scriptInterface = g_Game->GetSimulation2()->GetScriptInterface();
    bool Autostart(const CmdLineArgs& args)  
    12981298        if (mapDirectory == L"scenarios")
    12991299            mapType = "scenario";
    13001300        else
    13011301            mapType = "skirmish";
    13021302    }
    1303     else
     1303    else if (!args.Has("dedicated-host"))
    13041304    {
    13051305        LOGERROR("Autostart: Unrecognized map type '%s'", utf8_from_wstring(mapDirectory));
    13061306        throw PSERROR_Game_World_MapLoadFailed("Unrecognized map type.\nConsult readme.txt for the currently supported types.");
    13071307    }
     1308
    13081309    scriptInterface.SetProperty(attrs, "mapType", mapType);
    13091310    scriptInterface.SetProperty(attrs, "map", std::string("maps/" + autoStartName));
    13101311    scriptInterface.SetProperty(settings, "mapType", mapType);
    13111312
    13121313    // Set seed for AIs
    bool Autostart(const CmdLineArgs& args)  
    14221423    if (args.Has("autostart-playername"))
    14231424    {
    14241425        userName = args.Get("autostart-playername").FromUTF8();
    14251426    }
    14261427
    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"))
    14281436    {
    14291437        InitPs(true, L"page_loading.xml", &scriptInterface, mpInitData);
    14301438
    14311439        size_t maxPlayers = 2;
    14321440        if (args.Has("autostart-host-players"))