forked from 0ad/0ad
Allow continuing playing when a client leaves a multiplayer game (fixes #620).
Fix multiplayer autostart. Make NETTURN_LOG syntax differently ugly. This was SVN commit r8525.
This commit is contained in:
parent
786279fb95
commit
81f5e0ac5f
@ -45,7 +45,7 @@ function displayNotifications()
|
||||
getGUIObjectByName("notificationText").caption = messages.join("\n");
|
||||
}
|
||||
|
||||
//Messages
|
||||
// Messages
|
||||
function handleNetMessage(message)
|
||||
{
|
||||
log("Net message: "+uneval(message));
|
||||
@ -74,22 +74,39 @@ function handleNetMessage(message)
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case "players":
|
||||
// Find and report all leavings
|
||||
for (var host in g_PlayerAssignments)
|
||||
{
|
||||
if (! message.hosts[host])
|
||||
{
|
||||
var obj = getGUIObjectByName("netStatus");
|
||||
obj.caption = g_PlayerAssignments[host].name + " has disconnected.\n\nThe game has ended.";
|
||||
obj.hidden = false;
|
||||
getGUIObjectByName("disconnectedExitButton").hidden = false;
|
||||
// Tell the user about the disconnection
|
||||
addChatMessage({ "type": "disconnect", "guid": host });
|
||||
|
||||
// Update the cached player data, so we can display the disconnection status
|
||||
updatePlayerDataRemove(g_Players, host);
|
||||
}
|
||||
}
|
||||
|
||||
// Find and report all joinings
|
||||
for (var host in message.hosts)
|
||||
{
|
||||
if (! g_PlayerAssignments[host])
|
||||
{
|
||||
// Update the cached player data, so we can display the correct name
|
||||
updatePlayerDataAdd(g_Players, host, message.hosts[host]);
|
||||
}
|
||||
}
|
||||
|
||||
g_PlayerAssignments = message.hosts;
|
||||
|
||||
break;
|
||||
|
||||
case "chat":
|
||||
addChatMessage({ "type": "message", "guid": message.guid, "text": message.text });
|
||||
break;
|
||||
|
||||
default:
|
||||
error("Unrecognised net message type "+message.type);
|
||||
}
|
||||
@ -136,11 +153,9 @@ function addChatMessage(msg)
|
||||
|
||||
switch (msg.type)
|
||||
{
|
||||
/*
|
||||
case "disconnect":
|
||||
formatted = "<[color=\"" + playerColor + "\"]" + username + "[/color]> has left";
|
||||
formatted = "[color=\"" + playerColor + "\"]" + username + "[/color] has left the game.";
|
||||
break;
|
||||
*/
|
||||
case "message":
|
||||
console.write("<" + username + "> " + message);
|
||||
formatted = "<[color=\"" + playerColor + "\"]" + username + "[/color]> " + message;
|
||||
|
@ -38,6 +38,12 @@ function displaySingle(entState, template)
|
||||
var playerColor = g_Players[entState.player].color.r + " " + g_Players[entState.player].color.g + " " +
|
||||
g_Players[entState.player].color.b+ " " + g_Players[entState.player].color.a;
|
||||
|
||||
// Indicate disconnected players by prefixing their name
|
||||
if (g_Players[entState.player].offline)
|
||||
{
|
||||
playerName = "[OFFLINE] " + playerName;
|
||||
}
|
||||
|
||||
// Rank
|
||||
getGUIObjectByName("rankIcon").cell_id = getRankIconCellId(entState);
|
||||
|
||||
|
@ -66,7 +66,10 @@ function init(initData, hotloadData)
|
||||
{
|
||||
g_IsNetworked = initData.isNetworked; // Set network mode
|
||||
g_PlayerAssignments = initData.playerAssignments;
|
||||
g_Players = getPlayerData(initData.playerAssignments); // Cache the player data
|
||||
|
||||
// Cache the player data
|
||||
// (This may be updated at runtime by handleNetMessage)
|
||||
g_Players = getPlayerData(g_PlayerAssignments);
|
||||
}
|
||||
else // Needed for autostart loading option
|
||||
{
|
||||
|
@ -459,7 +459,7 @@
|
||||
<object size="50%-112 0 50%+112 100%" name="detailsAreaSingle">
|
||||
|
||||
<!-- Player Name -->
|
||||
<object size="0 10 100% 30" name="player" type="text" style="largeCenteredOutlinedText" tooltip_style="snToolTip"/>
|
||||
<object size="0 0 100% 40" name="player" type="text" style="largeCenteredOutlinedText" tooltip_style="snToolTip"/>
|
||||
|
||||
<!-- Stats -->
|
||||
<object size="6 36 50 100%" name="statsArea" type="image">
|
||||
|
@ -25,23 +25,54 @@ function getPlayerData(playerAssignments)
|
||||
var civ = playerState.civ;
|
||||
var color = {"r": playerState.colour.r*255, "g": playerState.colour.g*255, "b": playerState.colour.b*255, "a": playerState.colour.a*255};
|
||||
|
||||
var player = {"name": name, "civ": civ, "color": color, "team": playerState.team, "diplomacy": playerState.diplomacy, "state": playerState.state};
|
||||
var player = {
|
||||
"name": name,
|
||||
"civ": civ,
|
||||
"color": color,
|
||||
"team": playerState.team,
|
||||
"diplomacy": playerState.diplomacy,
|
||||
"state": playerState.state,
|
||||
"guid": undefined, // network guid for players controlled by hosts
|
||||
"disconnected": false, // flag for host-controlled players who have left the game
|
||||
};
|
||||
players.push(player);
|
||||
}
|
||||
|
||||
// Overwrite default player names with multiplayer names
|
||||
if (playerAssignments)
|
||||
{
|
||||
for each (var playerAssignment in playerAssignments)
|
||||
for (var playerGuid in playerAssignments)
|
||||
{
|
||||
var playerAssignment = playerAssignments[playerGuid];
|
||||
if (players[playerAssignment.player])
|
||||
{
|
||||
players[playerAssignment.player].guid = playerGuid;
|
||||
players[playerAssignment.player].name = playerAssignment.name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return players;
|
||||
}
|
||||
|
||||
// Update player data when a host has connected
|
||||
function updatePlayerDataAdd(players, hostGuid, playerAssignment)
|
||||
{
|
||||
if (players[playerAssignment.player])
|
||||
{
|
||||
players[playerAssignment.player].guid = hostGuid;
|
||||
players[playerAssignment.player].name = playerAssignment.name;
|
||||
}
|
||||
}
|
||||
|
||||
// Update player data when a host has disconnected
|
||||
function updatePlayerDataRemove(players, hostGuid)
|
||||
{
|
||||
for each (var player in players)
|
||||
if (player.guid == hostGuid)
|
||||
player.offline = true;
|
||||
}
|
||||
|
||||
function isUnit(entState)
|
||||
{
|
||||
if (entState.identity)
|
||||
|
@ -56,7 +56,6 @@ static CStr DebugName(CNetServerSession* session)
|
||||
/*
|
||||
* XXX: We use some non-threadsafe functions from the worker thread.
|
||||
* See http://trac.wildfiregames.com/ticket/654
|
||||
* and http://trac.wildfiregames.com/ticket/653
|
||||
*/
|
||||
|
||||
CNetServerWorker::CNetServerWorker(int autostartPlayers) :
|
||||
@ -399,6 +398,12 @@ void CNetServerWorker::OnUserJoin(CNetServerSession* session)
|
||||
void CNetServerWorker::OnUserLeave(CNetServerSession* session)
|
||||
{
|
||||
RemovePlayer(session->GetGUID());
|
||||
|
||||
if (m_ServerTurnManager)
|
||||
m_ServerTurnManager->UninitialiseClient(session->GetHostID()); // TODO: only for non-observers
|
||||
|
||||
// TODO: ought to switch the player controlled by that client
|
||||
// back to AI control, or something?
|
||||
}
|
||||
|
||||
void CNetServerWorker::AddPlayer(const CStr& guid, const CStrW& name)
|
||||
|
@ -40,7 +40,11 @@ static const int DEFAULT_TURN_LENGTH_SP = 200;
|
||||
|
||||
static const int COMMAND_DELAY = 2;
|
||||
|
||||
//#define NETTURN_LOG debug_printf
|
||||
#if 0
|
||||
#define NETTURN_LOG(args) debug_printf args
|
||||
#else
|
||||
#define NETTURN_LOG(args)
|
||||
#endif
|
||||
|
||||
static std::string Hexify(const std::string& s)
|
||||
{
|
||||
@ -77,9 +81,7 @@ bool CNetTurnManager::Update(float frameLength)
|
||||
if (m_DeltaTime < 0)
|
||||
return false;
|
||||
|
||||
#ifdef NETTURN_LOG
|
||||
NETTURN_LOG(L"Update current=%d ready=%d\n", m_CurrentTurn, m_ReadyTurn);
|
||||
#endif
|
||||
NETTURN_LOG((L"Update current=%d ready=%d\n", m_CurrentTurn, m_ReadyTurn));
|
||||
|
||||
// Check that the next turn is ready for execution
|
||||
if (m_ReadyTurn > m_CurrentTurn)
|
||||
@ -99,9 +101,7 @@ bool CNetTurnManager::Update(float frameLength)
|
||||
|
||||
m_Replay.Turn(m_CurrentTurn-1, m_TurnLength, commands);
|
||||
|
||||
#ifdef NETTURN_LOG
|
||||
NETTURN_LOG(L"Running %d cmds\n", commands.size());
|
||||
#endif
|
||||
NETTURN_LOG((L"Running %d cmds\n", commands.size()));
|
||||
|
||||
m_Simulation2.Update(m_TurnLength, commands);
|
||||
|
||||
@ -130,9 +130,7 @@ bool CNetTurnManager::Update(float frameLength)
|
||||
|
||||
void CNetTurnManager::OnSyncError(u32 turn, const std::string& expectedHash)
|
||||
{
|
||||
#ifdef NETTURN_LOG
|
||||
NETTURN_LOG(L"OnSyncError(%d, %hs)\n", turn, Hexify(expectedHash).c_str());
|
||||
#endif
|
||||
NETTURN_LOG((L"OnSyncError(%d, %hs)\n", turn, Hexify(expectedHash).c_str()));
|
||||
|
||||
// Only complain the first time
|
||||
if (m_HasSyncError)
|
||||
@ -166,9 +164,7 @@ void CNetTurnManager::Interpolate(float frameLength)
|
||||
|
||||
void CNetTurnManager::AddCommand(int client, int player, CScriptValRooted data, u32 turn)
|
||||
{
|
||||
#ifdef NETTURN_LOG
|
||||
NETTURN_LOG(L"AddCommand(client=%d player=%d turn=%d)\n", client, player, turn);
|
||||
#endif
|
||||
NETTURN_LOG((L"AddCommand(client=%d player=%d turn=%d)\n", client, player, turn));
|
||||
|
||||
if (!(m_CurrentTurn < turn && turn <= m_CurrentTurn + COMMAND_DELAY + 1))
|
||||
{
|
||||
@ -184,9 +180,7 @@ void CNetTurnManager::AddCommand(int client, int player, CScriptValRooted data,
|
||||
|
||||
void CNetTurnManager::FinishedAllCommands(u32 turn, u32 turnLength)
|
||||
{
|
||||
#ifdef NETTURN_LOG
|
||||
NETTURN_LOG(L"FinishedAllCommands(%d, %d)\n", turn, turnLength);
|
||||
#endif
|
||||
NETTURN_LOG((L"FinishedAllCommands(%d, %d)\n", turn, turnLength));
|
||||
|
||||
debug_assert(turn == m_ReadyTurn + 1);
|
||||
m_ReadyTurn = turn;
|
||||
@ -201,9 +195,7 @@ CNetClientTurnManager::CNetClientTurnManager(CSimulation2& simulation, CNetClien
|
||||
|
||||
void CNetClientTurnManager::PostCommand(CScriptValRooted data)
|
||||
{
|
||||
#ifdef NETTURN_LOG
|
||||
NETTURN_LOG(L"PostCommand()\n");
|
||||
#endif
|
||||
NETTURN_LOG((L"PostCommand()\n"));
|
||||
|
||||
// Transmit command to server
|
||||
CSimulationMessage msg(m_Simulation2.GetScriptInterface(), m_ClientId, m_PlayerId, m_CurrentTurn + COMMAND_DELAY, data.get());
|
||||
@ -216,9 +208,7 @@ void CNetClientTurnManager::PostCommand(CScriptValRooted data)
|
||||
|
||||
void CNetClientTurnManager::NotifyFinishedOwnCommands(u32 turn)
|
||||
{
|
||||
#ifdef NETTURN_LOG
|
||||
NETTURN_LOG(L"NotifyFinishedOwnCommands(%d)\n", turn);
|
||||
#endif
|
||||
NETTURN_LOG((L"NotifyFinishedOwnCommands(%d)\n", turn));
|
||||
|
||||
// Send message to the server
|
||||
CEndCommandBatchMessage msg;
|
||||
@ -229,10 +219,6 @@ void CNetClientTurnManager::NotifyFinishedOwnCommands(u32 turn)
|
||||
|
||||
void CNetClientTurnManager::NotifyFinishedUpdate(u32 turn)
|
||||
{
|
||||
#ifdef NETTURN_LOG
|
||||
NETTURN_LOG(L"NotifyFinishedUpdate(%d, %hs)\n", turn, Hexify(hash).c_str());
|
||||
#endif
|
||||
|
||||
std::string hash;
|
||||
{
|
||||
PROFILE("state hash check");
|
||||
@ -240,6 +226,8 @@ void CNetClientTurnManager::NotifyFinishedUpdate(u32 turn)
|
||||
debug_assert(ok);
|
||||
}
|
||||
|
||||
NETTURN_LOG((L"NotifyFinishedUpdate(%d, %hs)\n", turn, Hexify(hash).c_str()));
|
||||
|
||||
m_Replay.Hash(hash);
|
||||
|
||||
// Send message to the server
|
||||
@ -301,9 +289,7 @@ CNetServerTurnManager::CNetServerTurnManager(CNetServerWorker& server) :
|
||||
|
||||
void CNetServerTurnManager::NotifyFinishedClientCommands(int client, u32 turn)
|
||||
{
|
||||
#ifdef NETTURN_LOG
|
||||
NETTURN_LOG(L"NotifyFinishedClientCommands(client=%d, turn=%d)\n", client, turn);
|
||||
#endif
|
||||
NETTURN_LOG((L"NotifyFinishedClientCommands(client=%d, turn=%d)\n", client, turn));
|
||||
|
||||
// Must be a client we've already heard of
|
||||
debug_assert(m_ClientsReady.find(client) != m_ClientsReady.end());
|
||||
@ -312,23 +298,30 @@ void CNetServerTurnManager::NotifyFinishedClientCommands(int client, u32 turn)
|
||||
debug_assert(turn == m_ClientsReady[client] + 1);
|
||||
m_ClientsReady[client] = turn;
|
||||
|
||||
// Check whether this was the final client to become ready
|
||||
CheckClientsReady();
|
||||
}
|
||||
|
||||
void CNetServerTurnManager::CheckClientsReady()
|
||||
{
|
||||
// See if all clients (including self) are ready for a new turn
|
||||
for (std::map<int, u32>::iterator it = m_ClientsReady.begin(); it != m_ClientsReady.end(); ++it)
|
||||
{
|
||||
#ifdef NETTURN_LOG
|
||||
NETTURN_LOG(L" %d: %d <=? %d\n", it->first, it->second, m_ReadyTurn);
|
||||
#endif
|
||||
NETTURN_LOG((L" %d: %d <=? %d\n", it->first, it->second, m_ReadyTurn));
|
||||
if (it->second <= m_ReadyTurn)
|
||||
return;
|
||||
return; // wasn't ready for m_ReadyTurn+1
|
||||
}
|
||||
|
||||
// Advance the turn
|
||||
++m_ReadyTurn;
|
||||
|
||||
NETTURN_LOG((L"CheckClientsReady: ready for turn %d\n", m_ReadyTurn));
|
||||
|
||||
// Tell all clients that the next turn is ready
|
||||
CEndCommandBatchMessage msg;
|
||||
msg.m_TurnLength = m_TurnLength;
|
||||
msg.m_Turn = turn;
|
||||
msg.m_Turn = m_ReadyTurn;
|
||||
m_NetServer.Broadcast(&msg);
|
||||
|
||||
m_ReadyTurn = turn;
|
||||
}
|
||||
|
||||
void CNetServerTurnManager::NotifyFinishedClientUpdate(int client, u32 turn, const std::string& hash)
|
||||
@ -358,9 +351,7 @@ void CNetServerTurnManager::NotifyFinishedClientUpdate(int client, u32 turn, con
|
||||
|
||||
for (std::map<int, std::string>::iterator cit = it->second.begin(); cit != it->second.end(); ++cit)
|
||||
{
|
||||
#ifdef NETTURN_LOG
|
||||
NETTURN_LOG(L"sync check %d: %d = %hs\n", it->first, cit->first, Hexify(cit->second).c_str());
|
||||
#endif
|
||||
NETTURN_LOG((L"sync check %d: %d = %hs\n", it->first, cit->first, Hexify(cit->second).c_str()));
|
||||
if (cit->second != expected)
|
||||
{
|
||||
// Oh no, out of sync
|
||||
@ -382,11 +373,24 @@ void CNetServerTurnManager::NotifyFinishedClientUpdate(int client, u32 turn, con
|
||||
|
||||
void CNetServerTurnManager::InitialiseClient(int client)
|
||||
{
|
||||
NETTURN_LOG((L"InitialiseClient(client=%d)\n", client));
|
||||
|
||||
debug_assert(m_ClientsReady.find(client) == m_ClientsReady.end());
|
||||
m_ClientsReady[client] = 1;
|
||||
m_ClientsSimulated[client] = 0;
|
||||
}
|
||||
|
||||
// TODO: do we need some kind of UninitialiseClient in case they leave?
|
||||
void CNetServerTurnManager::UninitialiseClient(int client)
|
||||
{
|
||||
NETTURN_LOG((L"UninitialiseClient(client=%d)\n", client));
|
||||
|
||||
debug_assert(m_ClientsReady.find(client) != m_ClientsReady.end());
|
||||
m_ClientsReady.erase(client);
|
||||
m_ClientsSimulated.erase(client);
|
||||
|
||||
// Check whether we're ready for the next turn now that we're not
|
||||
// waiting for this client any more
|
||||
CheckClientsReady();
|
||||
}
|
||||
|
||||
void CNetServerTurnManager::SetTurnLength(u32 msecs)
|
||||
|
@ -197,11 +197,22 @@ public:
|
||||
|
||||
void NotifyFinishedClientUpdate(int client, u32 turn, const std::string& hash);
|
||||
|
||||
/**
|
||||
* Inform the turn manager of a new client who will be sending commands.
|
||||
*/
|
||||
void InitialiseClient(int client);
|
||||
|
||||
/**
|
||||
* Inform the turn manager that a previously-initialised client has left the game
|
||||
* and will no longer be sending commands.
|
||||
*/
|
||||
void UninitialiseClient(int client);
|
||||
|
||||
void SetTurnLength(u32 msecs);
|
||||
|
||||
protected:
|
||||
void CheckClientsReady();
|
||||
|
||||
/// The latest turn for which we have received all commands from all clients
|
||||
u32 m_ReadyTurn;
|
||||
|
||||
|
@ -342,7 +342,7 @@ void CLogger::Render()
|
||||
|
||||
void CLogger::PushRenderMessage(ELogMethod method, const wchar_t* message)
|
||||
{
|
||||
double now = timer_Time(); // XXX: this is not thread-safe (http://trac.wildfiregames.com/ticket/653)
|
||||
double now = timer_Time();
|
||||
|
||||
// Add each message line separately
|
||||
const wchar_t* pos = message;
|
||||
|
@ -458,7 +458,7 @@ static void InitVfs(const CmdLineArgs& args)
|
||||
}
|
||||
|
||||
|
||||
static void InitPs(bool setup_gui, const CStrW& gui_page)
|
||||
static void InitPs(bool setup_gui, const CStrW& gui_page, CScriptVal initData)
|
||||
{
|
||||
{
|
||||
// console
|
||||
@ -490,12 +490,12 @@ static void InitPs(bool setup_gui, const CStrW& gui_page)
|
||||
{
|
||||
// We do actually need *some* kind of GUI loaded, so use the
|
||||
// (currently empty) Atlas one
|
||||
g_GUI->SwitchPage(L"page_atlas.xml", JSVAL_VOID);
|
||||
g_GUI->SwitchPage(L"page_atlas.xml", initData);
|
||||
return;
|
||||
}
|
||||
|
||||
// GUI uses VFS, so this must come after VFS init.
|
||||
g_GUI->SwitchPage(gui_page, JSVAL_VOID);
|
||||
g_GUI->SwitchPage(gui_page, initData);
|
||||
|
||||
// Warn nicely about missing S3TC support
|
||||
if (!ogl_tex_has_s3tc())
|
||||
@ -887,7 +887,7 @@ void InitGraphics(const CmdLineArgs& args, int flags)
|
||||
if (!Autostart(args))
|
||||
{
|
||||
const bool setup_gui = ((flags & INIT_NO_GUI) == 0);
|
||||
InitPs(setup_gui, L"page_pregame.xml");
|
||||
InitPs(setup_gui, L"page_pregame.xml", JSVAL_VOID);
|
||||
}
|
||||
}
|
||||
|
||||
@ -918,9 +918,14 @@ static bool Autostart(const CmdLineArgs& args)
|
||||
scriptInterface.SetProperty(attrs.get(), "mapType", std::string("scenario"), false);
|
||||
scriptInterface.SetProperty(attrs.get(), "map", std::string(autostartMap), false);
|
||||
|
||||
CScriptValRooted mpInitData;
|
||||
g_GUI->GetScriptInterface().Eval("({isNetworked:true, playerAssignments:{}})", mpInitData);
|
||||
g_GUI->GetScriptInterface().SetProperty(mpInitData.get(), "attribs",
|
||||
CScriptVal(g_GUI->GetScriptInterface().CloneValueFromOtherContext(scriptInterface, attrs.get())), false);
|
||||
|
||||
if (args.Has("autostart-host"))
|
||||
{
|
||||
InitPs(true, L"page_loading.xml");
|
||||
InitPs(true, L"page_loading.xml", mpInitData.get());
|
||||
|
||||
size_t maxPlayers = 2;
|
||||
if (args.Has("autostart-players"))
|
||||
@ -939,7 +944,7 @@ static bool Autostart(const CmdLineArgs& args)
|
||||
}
|
||||
else if (args.Has("autostart-client"))
|
||||
{
|
||||
InitPs(true, L"page_loading.xml");
|
||||
InitPs(true, L"page_loading.xml", mpInitData.get());
|
||||
|
||||
g_NetClient = new CNetClient(g_Game);
|
||||
// TODO: player name, etc
|
||||
@ -959,7 +964,7 @@ static bool Autostart(const CmdLineArgs& args)
|
||||
PSRETURN ret = g_Game->ReallyStartGame();
|
||||
debug_assert(ret == PSRETURN_OK);
|
||||
|
||||
InitPs(true, L"page_session_new.xml");
|
||||
InitPs(true, L"page_session_new.xml", JSVAL_VOID);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
Loading…
Reference in New Issue
Block a user