Host on arbitrary UDP ports. Patch in cooperation with Imarok, fixes #3575.

This was SVN commit r18372.
This commit is contained in:
elexis 2016-06-13 16:56:14 +00:00
parent 7b655b7ea6
commit 62061557db
15 changed files with 130 additions and 50 deletions

View File

@ -43,6 +43,14 @@ var g_NetworkCommands = {
"/clear": argument => clearChatMessages()
};
function getValidPort(port)
{
if (isNaN(+port) || +port <= 0 || +port > 65535)
return Engine.GetDefaultPort();
return +port;
}
/**
* Must be kept in sync with source/network/NetHost.h
*/

View File

@ -160,6 +160,7 @@ var g_IsController;
* To report the game to the lobby bot.
*/
var g_ServerName;
var g_ServerPort;
/**
* States whether the GUI is currently updated in response to network messages instead of user input
@ -242,7 +243,8 @@ function init(attribs)
g_IsNetworked = attribs.type != "offline";
g_IsController = attribs.type != "client";
g_ServerName = attribs.serverName || undefined;
g_ServerName = attribs.serverName;
g_ServerPort = attribs.serverPort;
// Replace empty playername when entering a singleplayermatch for the first time
if (!g_IsNetworked)
@ -1937,6 +1939,7 @@ function sendRegisterGameStanza()
let stanza = {
"name": g_ServerName,
"port": g_ServerPort,
"mapName": g_GameAttributes.map,
"niceMapName": getMapDisplayName(g_GameAttributes.map),
"mapSize": mapSize,

View File

@ -13,6 +13,11 @@ var g_GameType;
*/
var g_ServerName = "";
/**
* Cached to pass it to the gamesetup of the controller to report the game to the lobby.
*/
var g_ServerPort;
var g_IsRejoining = false;
var g_GameAttributes; // used when rejoining
var g_PlayerAssignments; // used when rejoining
@ -28,7 +33,7 @@ function init(attribs)
{
if (Engine.HasXmppClient())
{
if (startJoin(attribs.name, attribs.ip))
if (startJoin(attribs.name, attribs.ip, getValidPort(attribs.port)))
switchSetupPage("pageConnecting");
}
else
@ -85,14 +90,18 @@ function confirmSetup()
{
let joinPlayerName = Engine.GetGUIObjectByName("joinPlayerName").caption;
let joinServer = Engine.GetGUIObjectByName("joinServer").caption;
if (startJoin(joinPlayerName, joinServer))
let joinPort = Engine.GetGUIObjectByName("joinPort").caption;
if (startJoin(joinPlayerName, joinServer, getValidPort(joinPort)))
switchSetupPage("pageConnecting");
}
else if (!Engine.GetGUIObjectByName("pageHost").hidden)
{
let hostPlayerName = Engine.GetGUIObjectByName("hostPlayerName").caption;
let hostServerName = Engine.GetGUIObjectByName("hostServerName").caption;
if (startHost(hostPlayerName, hostServerName))
let hostPort = Engine.GetGUIObjectByName("hostPort").caption;
if (startHost(hostPlayerName, hostServerName, getValidPort(hostPort)))
switchSetupPage("pageConnecting");
}
}
@ -202,7 +211,11 @@ function pollAndHandleNetworkClient()
}
else
{
Engine.SwitchGuiPage("page_gamesetup.xml", { "type": g_GameType, "serverName": g_ServerName });
Engine.SwitchGuiPage("page_gamesetup.xml", {
"type": g_GameType,
"serverName": g_ServerName,
"serverPort": g_ServerPort
});
return; // don't process any more messages - leave them for the game GUI loop
}
@ -242,7 +255,7 @@ function switchSetupPage(newPage)
Engine.GetGUIObjectByName("continueButton").hidden = newPage == "pageConnecting";
}
function startHost(playername, servername)
function startHost(playername, servername, port)
{
startConnectionStatus("server");
@ -250,6 +263,10 @@ function startHost(playername, servername)
Engine.ConfigDB_CreateValue("user", "playername.multiplayer", playername);
Engine.ConfigDB_WriteValueToFile("user", "playername.multiplayer", playername, "config/user.cfg");
// Save port
Engine.ConfigDB_CreateValue("user", "multiplayerhosting.port", port);
Engine.ConfigDB_WriteValueToFile("user", "multiplayerhosting.port", port, "config/user.cfg");
// Disallow identically named games in the multiplayer lobby
if (Engine.HasXmppClient() &&
Engine.GetGameList().some(game => game.name == servername))
@ -263,9 +280,9 @@ function startHost(playername, servername)
try
{
if (g_UserRating)
Engine.StartNetworkHost(playername + " (" + g_UserRating + ")");
Engine.StartNetworkHost(playername + " (" + g_UserRating + ")", port);
else
Engine.StartNetworkHost(playername);
Engine.StartNetworkHost(playername, port);
}
catch (e)
{
@ -279,6 +296,7 @@ function startHost(playername, servername)
}
g_ServerName = servername;
g_ServerPort = port;
if (Engine.HasXmppClient())
Engine.LobbySetPlayerPresence("playing");
@ -286,14 +304,14 @@ function startHost(playername, servername)
return true;
}
function startJoin(playername, ip)
function startJoin(playername, ip, port)
{
try
{
if (g_UserRating)
Engine.StartNetworkJoin(playername + " (" + g_UserRating + ")", ip);
Engine.StartNetworkJoin(playername + " (" + g_UserRating + ")", ip, port);
else
Engine.StartNetworkJoin(playername, ip);
Engine.StartNetworkJoin(playername, ip, port);
}
catch (e)
{
@ -317,6 +335,8 @@ function startJoin(playername, ip)
Engine.ConfigDB_WriteValueToFile("user", "playername.multiplayer", playername, "config/user.cfg");
Engine.ConfigDB_CreateValue("user", "multiplayerserver", ip);
Engine.ConfigDB_WriteValueToFile("user", "multiplayerserver", ip, "config/user.cfg");
Engine.ConfigDB_CreateValue("user", "multiplayerjoining.port", port);
Engine.ConfigDB_WriteValueToFile("user", "multiplayerjoining.port", port, "config/user.cfg");
}
return true;
}

View File

@ -26,7 +26,7 @@
<translatableAttribute id="caption">Joining an existing game.</translatableAttribute>
</object>
<object type="text" size="20 40 50% 70" style="ModernLabelText" text_align="right">
<object type="text" size="20 36 50% 66" style="ModernLabelText" text_align="right">
<translatableAttribute id="caption">Player name:</translatableAttribute>
</object>
@ -36,7 +36,7 @@
</action>
</object>
<object type="text" size="20 80 50% 110" style="ModernLabelText" text_align="right">
<object type="text" size="20 76 50% 106" style="ModernLabelText" text_align="right">
<translatableAttribute id="caption">Server Hostname or IP:</translatableAttribute>
</object>
@ -45,6 +45,20 @@
this.caption = Engine.ConfigDB_GetValue("user", "multiplayerserver");
</action>
</object>
<object type="text" size="20 116 50% 146" style="ModernLabelText" text_align="right">
<translatableAttribute id="caption">Server Port:</translatableAttribute>
</object>
<object name="joinPort" type="input" size="50%+10 120 100%-20 144" style="ModernInput">
<translatableAttribute id="tooltip">Leave blank to use the default port.</translatableAttribute>
<action on="Load">
this.caption = getValidPort(Engine.ConfigDB_GetValue("user", "multiplayerjoining.port"));
</action>
<action on="Press">
this.caption = getValidPort(this.caption);
</action>
</object>
</object>
<object name="pageHost" size="0 32 100% 100%" hidden="true">
@ -54,29 +68,45 @@
</object>
<object name="hostPlayerNameWrapper" hidden="true">
<object type="text" size="20 40 50% 70" style="ModernLabelText" text_align="right">
<translatableAttribute id="caption">Player name:</translatableAttribute>
</object>
<object type="text" size="20 36 50% 66" style="ModernLabelText" text_align="right">
<translatableAttribute id="caption">Player name:</translatableAttribute>
</object>
<object name="hostPlayerName" type="input" size="50%+10 40 100%-20 64" style="ModernInput">
<action on="Load">
this.caption = multiplayerName();
</action>
</object>
<object name="hostPlayerName" type="input" size="50%+10 40 100%-20 64" style="ModernInput">
<action on="Load">
this.caption = multiplayerName();
</action>
</object>
</object>
<!-- Host server name is only used on games started through the lobby. -->
<object name="hostServerNameWrapper" hidden="true">
<object type="text" size="20 80 50% 110" style="ModernLabelText" text_align="right">
<object type="text" size="20 36 50% 66" style="ModernLabelText" text_align="right">
<translatableAttribute id="caption">Server name:</translatableAttribute>
</object>
<object name="hostServerName" type="input" size="50%+10 80 100%-20 104" style="ModernInput">
<object name="hostServerName" type="input" size="50%+10 40 100%-20 64" style="ModernInput">
<action on="Load">
this.caption = getDefaultGameName();
</action>
</object>
</object>
<object>
<object type="text" size="20 66 50% 116" style="ModernLabelText" text_align="right">
<translatableAttribute id="caption">Server Port:</translatableAttribute>
</object>
<object name="hostPort" type="input" size="50%+10 80 100%-20 104" style="ModernInput">
<translatableAttribute id="tooltip">Leave blank to use the default port.</translatableAttribute>
<action on="Load">
this.caption = getValidPort(Engine.ConfigDB_GetValue("user", "multiplayerhosting.port"));
</action>
<action on="Press">
this.caption = getValidPort(this.caption);
</action>
</object>
</object>
</object>
<object name="hostFeedback" type="text" style="ModernLabelText" size="50 100%-90 100%-50 100%-50" textcolor="red" />

View File

@ -101,6 +101,11 @@ var g_SelectedPlayer = "";
*/
var g_SelectedGameIP = "";
/**
* Used to restore the selection after updating the gamelist.
*/
var g_SelectedGamePort = "";
/**
* Notifications sent by XmppClient.cpp
*/
@ -509,7 +514,10 @@ function updateGameList()
var sortOrder = gamesBox.selected_column_order || 1;
if (gamesBox.selected > -1)
{
g_SelectedGameIP = g_GameList[gamesBox.selected].ip;
g_SelectedGamePort = g_GameList[gamesBox.selected].port;
}
g_GameList = Engine.GetGameList().filter(game => !filterGame(game)).sort((a, b) => {
var sortA, sortB;
@ -556,7 +564,7 @@ function updateGameList()
let gameName = escapeText(game.name);
let mapTypeIdx = g_MapTypes.Name.indexOf(game.mapType);
if (game.ip == g_SelectedGameIP)
if (game.ip == g_SelectedGameIP && game.port == g_SelectedGamePort)
selectedGameIndex = +i;
list_name.push('[color="' + g_GameColors[game.state] + '"]' + gameName);
@ -676,6 +684,7 @@ function joinSelectedGame()
Engine.PushGuiPage("page_gamesetup_mp.xml", {
"multiplayerGameType": "join",
"ip": game.ip,
"port": game.port,
"name": g_Username,
"rating": g_UserRating
});

View File

@ -341,14 +341,14 @@ void SetNetworkGameAttributes(ScriptInterface::CxPrivate* pCxPrivate, JS::Handle
g_NetClient->SendGameSetupMessage(&attribs, *(pCxPrivate->pScriptInterface));
}
void StartNetworkHost(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& playerName)
void StartNetworkHost(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& playerName, const u16 serverPort)
{
ENSURE(!g_NetClient);
ENSURE(!g_NetServer);
ENSURE(!g_Game);
g_NetServer = new CNetServer();
if (!g_NetServer->SetupConnection())
if (!g_NetServer->SetupConnection(serverPort))
{
pCxPrivate->pScriptInterface->ReportError("Failed to start server");
SAFE_DELETE(g_NetServer);
@ -359,7 +359,7 @@ void StartNetworkHost(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring
g_NetClient = new CNetClient(g_Game, true);
g_NetClient->SetUserName(playerName);
if (!g_NetClient->SetupConnection("127.0.0.1"))
if (!g_NetClient->SetupConnection("127.0.0.1", serverPort))
{
pCxPrivate->pScriptInterface->ReportError("Failed to connect to server");
SAFE_DELETE(g_NetClient);
@ -367,7 +367,7 @@ void StartNetworkHost(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring
}
}
void StartNetworkJoin(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& playerName, const std::string& serverAddress)
void StartNetworkJoin(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& playerName, const CStr& serverAddress, u16 serverPort)
{
ENSURE(!g_NetClient);
ENSURE(!g_NetServer);
@ -376,7 +376,7 @@ void StartNetworkJoin(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring
g_Game = new CGame();
g_NetClient = new CNetClient(g_Game, false);
g_NetClient->SetUserName(playerName);
if (!g_NetClient->SetupConnection(serverAddress))
if (!g_NetClient->SetupConnection(serverAddress, serverPort))
{
pCxPrivate->pScriptInterface->ReportError("Failed to connect to server");
SAFE_DELETE(g_NetClient);
@ -384,6 +384,11 @@ void StartNetworkJoin(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring
}
}
u16 GetDefaultPort(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
return PS_DEFAULT_PORT;
}
void DisconnectNetworkGame(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
// TODO: we ought to do async reliable disconnections
@ -1027,8 +1032,9 @@ void GuiScriptingInit(ScriptInterface& scriptInterface)
scriptInterface.RegisterFunction<void, &StartNetworkGame>("StartNetworkGame");
scriptInterface.RegisterFunction<void, JS::HandleValue, int, &StartGame>("StartGame");
scriptInterface.RegisterFunction<void, &Script_EndGame>("EndGame");
scriptInterface.RegisterFunction<void, std::wstring, &StartNetworkHost>("StartNetworkHost");
scriptInterface.RegisterFunction<void, std::wstring, std::string, &StartNetworkJoin>("StartNetworkJoin");
scriptInterface.RegisterFunction<void, CStrW, u16, &StartNetworkHost>("StartNetworkHost");
scriptInterface.RegisterFunction<void, CStrW, CStr, u16, &StartNetworkJoin>("StartNetworkJoin");
scriptInterface.RegisterFunction<u16, &GetDefaultPort>("GetDefaultPort");
scriptInterface.RegisterFunction<void, &DisconnectNetworkGame>("DisconnectNetworkGame");
scriptInterface.RegisterFunction<std::string, &GetPlayerGUID>("GetPlayerGUID");
scriptInterface.RegisterFunction<void, CStrW, bool, &KickPlayer>("KickPlayer");

View File

@ -502,7 +502,7 @@ void XmppClient::GUIGetGameList(ScriptInterface& scriptInterface, JS::MutableHan
JSAutoRequest rq(cx);
scriptInterface.Eval("([])", ret);
const char* stats[] = { "name", "ip", "state", "nbp", "tnbp", "players", "mapName", "niceMapName", "mapSize", "mapType", "victoryCondition" };
const char* stats[] = { "name", "ip", "port", "state", "nbp", "tnbp", "players", "mapName", "niceMapName", "mapSize", "mapType", "victoryCondition" };
for(const glooxwrapper::Tag* const& t : m_GameList)
{
JS::RootedValue game(cx);

View File

@ -154,10 +154,10 @@ void CNetClient::SetUserName(const CStrW& username)
m_UserName = username;
}
bool CNetClient::SetupConnection(const CStr& server)
bool CNetClient::SetupConnection(const CStr& server, const u16 port)
{
CNetClientSession* session = new CNetClientSession(*this);
bool ok = session->Connect(PS_DEFAULT_PORT, server, m_IsLocalClient);
bool ok = session->Connect(server, port, m_IsLocalClient);
SetAndOwnSession(session);
return ok;
}

View File

@ -99,7 +99,7 @@ public:
* @param server IP address or host name to connect to
* @return true on success, false on connection failure
*/
bool SetupConnection(const CStr& server);
bool SetupConnection(const CStr& server, const u16 port);
/**
* Destroy the connection to the server.

View File

@ -169,7 +169,7 @@ CNetServerWorker::~CNetServerWorker()
delete m_ServerTurnManager;
}
bool CNetServerWorker::SetupConnection()
bool CNetServerWorker::SetupConnection(const u16 port)
{
ENSURE(m_State == SERVER_STATE_UNCONNECTED);
ENSURE(!m_Host);
@ -177,7 +177,7 @@ bool CNetServerWorker::SetupConnection()
// Bind to default host
ENetAddress addr;
addr.host = ENET_HOST_ANY;
addr.port = PS_DEFAULT_PORT;
addr.port = port;
// Create ENet server
m_Host = enet_host_create(&addr, MAX_CLIENTS, CHANNEL_COUNT, 0, 0);
@ -1427,9 +1427,9 @@ CNetServer::~CNetServer()
delete m_Worker;
}
bool CNetServer::SetupConnection()
bool CNetServer::SetupConnection(const u16 port)
{
return m_Worker->SetupConnection();
return m_Worker->SetupConnection(port);
}
void CNetServer::StartGame()

View File

@ -113,7 +113,7 @@ public:
* This function is synchronous (it won't return until the connection is established).
* @return true on success, false on error (e.g. port already in use)
*/
bool SetupConnection();
bool SetupConnection(const u16 port);
/**
* Call from the GUI to asynchronously notify all clients that they should start loading the game.
@ -184,7 +184,7 @@ private:
* Begin listening for network connections.
* @return true on success, false on error (e.g. port already in use)
*/
bool SetupConnection();
bool SetupConnection(const u16 port);
/**
* Call from the GUI to update the player assignments.

View File

@ -51,7 +51,7 @@ CNetClientSession::~CNetClientSession()
}
}
bool CNetClientSession::Connect(u16 port, const CStr& server, bool isLocalClient)
bool CNetClientSession::Connect(const CStr& server, const u16 port, const bool isLocalClient)
{
ENSURE(!m_Host);
ENSURE(!m_Server);

View File

@ -70,7 +70,7 @@ public:
CNetClientSession(CNetClient& client);
~CNetClientSession();
bool Connect(u16 port, const CStr& server, bool isLocalClient);
bool Connect(const CStr& server, const u16 port, const bool isLocalClient);
/**
* Process queued incoming messages.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2014 Wildfire Games.
/* Copyright (C) 2016 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -24,6 +24,8 @@
#include "network/NetServer.h"
#include "network/NetClient.h"
#include "network/NetTurnManager.h"
#include "network/NetMessage.h"
#include "network/NetMessages.h"
#include "ps/CLogger.h"
#include "ps/Game.h"
#include "ps/Filesystem.h"
@ -71,9 +73,9 @@ public:
void connect(CNetServer& server, const std::vector<CNetClient*>& clients)
{
TS_ASSERT(server.SetupConnection());
TS_ASSERT(server.SetupConnection(PS_DEFAULT_PORT));
for (size_t j = 0; j < clients.size(); ++j)
TS_ASSERT(clients[j]->SetupConnection("127.0.0.1"));
TS_ASSERT(clients[j]->SetupConnection("127.0.0.1", PS_DEFAULT_PORT));
for (size_t i = 0; ; ++i)
{
@ -273,7 +275,7 @@ public:
client2B.SetUserName(L"bob");
clients.push_back(&client2B);
TS_ASSERT(client2B.SetupConnection("127.0.0.1"));
TS_ASSERT(client2B.SetupConnection("127.0.0.1", PS_DEFAULT_PORT));
for (size_t i = 0; ; ++i)
{

View File

@ -50,6 +50,8 @@
#include "maths/MathUtil.h"
#include "network/NetServer.h"
#include "network/NetClient.h"
#include "network/NetMessage.h"
#include "network/NetMessages.h"
#include "ps/CConsole.h"
#include "ps/CLogger.h"
@ -1483,12 +1485,12 @@ bool Autostart(const CmdLineArgs& args)
g_NetServer->UpdateGameAttributes(&attrs, scriptInterface);
bool ok = g_NetServer->SetupConnection();
bool ok = g_NetServer->SetupConnection(PS_DEFAULT_PORT);
ENSURE(ok);
g_NetClient = new CNetClient(g_Game, true);
g_NetClient->SetUserName(userName);
g_NetClient->SetupConnection("127.0.0.1");
g_NetClient->SetupConnection("127.0.0.1", PS_DEFAULT_PORT);
}
else if (args.Has("autostart-client"))
{
@ -1501,7 +1503,7 @@ bool Autostart(const CmdLineArgs& args)
if (ip.empty())
ip = "127.0.0.1";
bool ok = g_NetClient->SetupConnection(ip);
bool ok = g_NetClient->SetupConnection(ip, PS_DEFAULT_PORT);
ENSURE(ok);
}
else