1
0
forked from 0ad/0ad

Send a network message to update the GUI if another player pauses a multiplayer game. Patch by echotangoecho, fixes #1950.

Pause animations then and on disconnect.
Don't unpause unintentionally when closing a message box in
singleplayer.

This was SVN commit r18204.
This commit is contained in:
elexis 2016-05-19 22:10:38 +00:00
parent 856dc1c999
commit ec1696ded3
13 changed files with 207 additions and 38 deletions

View File

@ -139,8 +139,6 @@ function displayGamestateNotifications()
let messages = [];
let maxTextWidth = 0;
// TODO: Players who paused the game should be added here
// Add network warnings
if (Engine.ConfigDB_GetValue("user", "overlay.netwarnings") == "true")
{

View File

@ -666,25 +666,46 @@ function openStrucTree()
}
/**
* Pause the game in single player mode.
* Pause or resume the game.
*
* @param explicit - true if the player explicitly wants to pause or resume.
* If this argument isn't set, a multiplayer game won't be paused and the pause overlay
* won't be shown in single player.
*/
function pauseGame()
function pauseGame(pause = true, explicit = false)
{
if (g_IsNetworked)
if (g_IsNetworked && !explicit)
return;
Engine.GetGUIObjectByName("pauseButtonText").caption = translate("Resume");
Engine.GetGUIObjectByName("pauseOverlay").hidden = false;
Engine.SetPaused(true);
if (explicit)
g_Paused = pause;
Engine.SetPaused(g_Paused || pause, !!explicit);
if (g_IsNetworked)
{
setClientPauseState(Engine.GetPlayerGUID(), g_Paused);
return;
}
updatePauseOverlay();
}
function resumeGame()
/**
* Resume the game.
*
* @param explicit - true if the player explicitly wants to resume the game.
* If this argument isn't set, a multiplayer game won't be resumed and the pause overlay won't
* be closed in single player.
*/
function resumeGame(explicit = false)
{
Engine.GetGUIObjectByName("pauseButtonText").caption = translate("Pause");
Engine.GetGUIObjectByName("pauseOverlay").hidden = true;
Engine.SetPaused(false);
pauseGame(false, explicit);
}
/**
* Called when the current player toggles a pause button.
*/
function togglePause()
{
if (!Engine.GetGUIObjectByName("pauseButton").enabled)
@ -692,12 +713,40 @@ function togglePause()
closeOpenDialogs();
let pauseOverlay = Engine.GetGUIObjectByName("pauseOverlay");
pauseGame(!g_Paused, true);
}
Engine.SetPaused(pauseOverlay.hidden);
Engine.GetGUIObjectByName("pauseButtonText").caption = pauseOverlay.hidden ? translate("Resume") : translate("Pause");
/**
* Called when a client pauses or resumes in a multiplayer game.
*/
function setClientPauseState(guid, paused)
{
// Update the list of pausing clients.
let index = g_PausingClients.indexOf(guid)
if (paused && index == -1)
g_PausingClients.push(guid);
else if (!paused && index != -1)
g_PausingClients.splice(index, 1);
pauseOverlay.hidden = !pauseOverlay.hidden;
updatePauseOverlay();
Engine.SetPaused(!!g_PausingClients.length, false);
}
/**
* Update the pause overlay.
*/
function updatePauseOverlay()
{
Engine.GetGUIObjectByName("pauseButtonText").caption = g_Paused ? translate("Resume") : translate("Pause");
Engine.GetGUIObjectByName("resumeMessage").hidden = !g_Paused;
Engine.GetGUIObjectByName("pausedByText").hidden = !g_IsNetworked;
Engine.GetGUIObjectByName("pausedByText").caption = sprintf(translate("Paused by %(players)s"),
{ "players": g_PausingClients.map(guid => colorizePlayernameByGUID(guid)).join(translate(", ")) });
Engine.GetGUIObjectByName("pauseOverlay").hidden = !(g_Paused || g_PausingClients.length);
Engine.GetGUIObjectByName("pauseOverlay").onPress = g_Paused ? togglePause : function() {};
}
function openManual()

View File

@ -31,6 +31,7 @@ var g_NetMessageTypes = {
"netstatus": msg => handleNetStatusMessage(msg),
"netwarn": msg => addNetworkWarning(msg),
"players": msg => handlePlayerAssignmentsMessage(msg),
"paused": msg => setClientPauseState(msg.guid, msg.pause),
"rejoined": msg => addChatMessage({ "type": "rejoined", "guid": msg.guid }),
"kicked": msg => addChatMessage({ "type": "system", "text": sprintf(translate("%(username)s has been kicked"), { "username": msg.username }) }),
"banned": msg => addChatMessage({ "type": "system", "text": sprintf(translate("%(username)s has been banned"), { "username": msg.username }) }),
@ -449,6 +450,10 @@ function handleNetStatusMessage(message)
if (message.status == "disconnected")
{
// Hide the pause overlay, and pause animations.
Engine.GetGUIObjectByName("pauseOverlay").hidden = true;
Engine.SetPaused(true, false);
g_Disconnected = true;
closeOpenDialogs();
}
@ -462,6 +467,8 @@ function handlePlayerAssignmentsMessage(message)
if (message.hosts[guid])
continue;
setClientPauseState(guid, false);
addChatMessage({ "type": "disconnect", "guid": guid });
for (let id in g_Players)

View File

@ -37,6 +37,16 @@ var g_IsObserver = false;
*/
var g_HasRejoined = false;
/**
* True if the current player has paused the game explicitly.
*/
var g_Paused = false;
/**
* The list of GUIDs of players who have currently paused the game, if the game is networked.
*/
var g_PausingClients = [];
/**
* The playerID selected in the change perspective tool.
*/
@ -440,7 +450,7 @@ function resignGame(leaveGameAfterResign)
global.music.setState(global.music.states.DEFEAT);
if (!leaveGameAfterResign)
resumeGame();
resumeGame(true);
}
/**

View File

@ -174,20 +174,15 @@
<!-- ================================ ================================ -->
<!-- Pause Overlay -->
<!-- ================================ ================================ -->
<object type="button"
name="pauseOverlay"
size="0 0 100% 100%"
tooltip_style="sessionToolTip"
hidden="true"
z="0"
>
<object type="button" name="pauseOverlay" size="0 0 100% 100%" tooltip_style="sessionToolTip" hidden="true" z="0">
<object size="0 0 100% 100%" type="image" sprite="devCommandsBackground" ghost="true" z="0"/>
<object size="50%-128 50%-20 50%+128 50%+20" type="text" style="PauseText" ghost="true" z="0">
<object size="50%-128 40%-20 50%+128 40%+20" type="text" style="PauseText" ghost="true" z="0">
<translatableAttribute id="caption">Game Paused</translatableAttribute>
</object>
<object size="50%-128 50%+20 50%+128 50%+30" type="text" style="PauseMessageText" ghost="true" z="0">
<object name="resumeMessage" size="50%-128 40%+20 50%+128 40%+40" type="text" style="ResumeMessageText" ghost="true" z="0">
<translatableAttribute id="caption">Click to Resume Game</translatableAttribute>
</object>
<object name="pausedByText" size="30% 40%+50 70% 100%" type="text" style="PausedByText" ghost="true" hidden="true" z="0"/>
<action on="Press">togglePause();</action>
</object>
@ -261,7 +256,7 @@
<!-- Trade Window -->
<!-- ================================ ================================ -->
<include file="gui/session/trade_window.xml"/>
<!-- ================================ ================================ -->
<!-- Top Panel -->
<!-- ================================ ================================ -->
@ -305,7 +300,7 @@
<!-- ================================ ================================ -->
<!-- Supplemental Details Panel (Left of Selection Details) -->
<!-- ================================ ================================ -->
<object
<object
size="50%-304 100%-170 50%-110 100%"
name="supplementalSelectionDetails"
type="image"

View File

@ -12,13 +12,20 @@
text_valign="center"
/>
<style name="PauseMessageText"
<style name="ResumeMessageText"
font="sans-bold-12"
textcolor="white"
text_align="center"
text_valign="center"
/>
<style name="PausedByText"
font="sans-bold-16"
textcolor="white"
text_align="center"
text_valign="top"
/>
<style name="BuildNameText"
font="sans-stroke-12"
textcolor="white"
@ -116,7 +123,7 @@
text_valign="center"
ghost="true"
/>
<style name="StatsTextCentered"
font="sans-stroke-12"
textcolor="white"
@ -124,7 +131,7 @@
text_valign="center"
ghost="true"
/>
<style name="StatsTextRight"
font="sans-stroke-12"
textcolor="white"
@ -132,7 +139,7 @@
text_valign="center"
ghost="true"
/>
<style name="CarryingTextRight"
font="sans-bold-stroke-13"
textcolor="white"
@ -140,7 +147,7 @@
text_valign="center"
ghost="true"
/>
<style name="SpecificNameCentered"
font="sans-bold-stroke-13"
textcolor="gold"
@ -148,7 +155,7 @@
text_valign="center"
ghost="true"
/>
<style name="GenericNameCentered"
font="sans-stroke-12"
textcolor="white"
@ -156,7 +163,7 @@
text_valign="center"
ghost="true"
/>
<style name="SettingsText"
font="sans-stroke-16"
textcolor="white"

View File

@ -769,7 +769,7 @@ bool IsPaused(ScriptInterface::CxPrivate* pCxPrivate)
}
// Pause/unpause the game
void SetPaused(ScriptInterface::CxPrivate* pCxPrivate, bool pause)
void SetPaused(ScriptInterface::CxPrivate* pCxPrivate, bool pause, bool sendMessage)
{
if (!g_Game)
{
@ -781,6 +781,9 @@ void SetPaused(ScriptInterface::CxPrivate* pCxPrivate, bool pause)
if (g_SoundManager)
g_SoundManager->Pause(pause);
#endif
if (g_NetClient && sendMessage)
g_NetClient->SendPausedMessage(pause);
}
// Return the global frames-per-second value.
@ -1083,7 +1086,7 @@ void GuiScriptingInit(ScriptInterface& scriptInterface)
scriptInterface.RegisterFunction<JS::Value, &GetProfilerState>("GetProfilerState");
scriptInterface.RegisterFunction<void, &ExitProgram>("Exit");
scriptInterface.RegisterFunction<bool, &IsPaused>("IsPaused");
scriptInterface.RegisterFunction<void, bool, &SetPaused>("SetPaused");
scriptInterface.RegisterFunction<void, bool, bool, &SetPaused>("SetPaused");
scriptInterface.RegisterFunction<int, &GetFps>("GetFPS");
scriptInterface.RegisterFunction<std::wstring, int, &GetBuildTimestamp>("GetBuildTimestamp");
scriptInterface.RegisterFunction<JS::Value, std::wstring, &ReadJSONFile>("ReadJSONFile");

View File

@ -123,6 +123,7 @@ CNetClient::CNetClient(CGame* game, bool isLocalClient) :
AddTransition(NCS_INGAME, (uint)NMT_KICKED, NCS_INGAME, (void*)&OnKicked, context);
AddTransition(NCS_INGAME, (uint)NMT_CLIENT_TIMEOUT, NCS_INGAME, (void*)&OnClientTimeout, context);
AddTransition(NCS_INGAME, (uint)NMT_CLIENT_PERFORMANCE, NCS_INGAME, (void*)&OnClientPerformance, context);
AddTransition(NCS_INGAME, (uint)NMT_CLIENT_PAUSED, NCS_INGAME, (void*)&OnClientPaused, context);
AddTransition(NCS_INGAME, (uint)NMT_CHAT, NCS_INGAME, (void*)&OnChat, context);
AddTransition(NCS_INGAME, (uint)NMT_GAME_SETUP, NCS_INGAME, (void*)&OnGameSetup, context);
AddTransition(NCS_INGAME, (uint)NMT_PLAYER_ASSIGNMENT, NCS_INGAME, (void*)&OnPlayerAssignment, context);
@ -342,6 +343,13 @@ void CNetClient::SendRejoinedMessage()
SendMessage(&rejoinedMessage);
}
void CNetClient::SendPausedMessage(bool pause)
{
CClientPausedMessage pausedMessage;
pausedMessage.m_Pause = pause;
SendMessage(&pausedMessage);
}
bool CNetClient::HandleMessage(CNetMessage* message)
{
// Handle non-FSM messages first
@ -727,6 +735,23 @@ bool CNetClient::OnClientPerformance(void *context, CFsmEvent* event)
return true;
}
bool CNetClient::OnClientPaused(void *context, CFsmEvent *event)
{
ENSURE(event->GetType() == (uint)NMT_CLIENT_PAUSED);
CNetClient* client = (CNetClient*)context;
JSContext* cx = client->GetScriptInterface().GetContext();
CClientPausedMessage* message = (CClientPausedMessage*)event->GetParamRef();
JS::RootedValue msg(cx);
client->GetScriptInterface().Eval("({ 'type':'paused' })", &msg);
client->GetScriptInterface().SetProperty(msg, "pause", message->m_Pause != 0);
client->GetScriptInterface().SetProperty(msg, "guid", message->m_GUID);
client->PushGuiMessage(msg);
return true;
}
bool CNetClient::OnLoadedGame(void* context, CFsmEvent* event)
{
ENSURE(event->GetType() == (uint)NMT_LOADED_GAME);

View File

@ -197,6 +197,11 @@ public:
*/
void SendRejoinedMessage();
/**
* Call when the client has paused or unpaused the game.
*/
void SendPausedMessage(bool pause);
private:
// Net message / FSM transition handlers
static bool OnConnect(void* context, CFsmEvent* event);
@ -215,6 +220,7 @@ private:
static bool OnKicked(void* context, CFsmEvent* event);
static bool OnClientTimeout(void* context, CFsmEvent* event);
static bool OnClientPerformance(void* context, CFsmEvent* event);
static bool OnClientPaused(void* context, CFsmEvent* event);
static bool OnLoadedGame(void* context, CFsmEvent* event);
/**

View File

@ -147,6 +147,10 @@ CNetMessage* CNetMessageFactory::CreateMessage(const void* pData,
pNewMessage = new CClientPerformanceMessage;
break;
case NMT_CLIENT_PAUSED:
pNewMessage = new CClientPausedMessage;
break;
case NMT_LOADED_GAME:
pNewMessage = new CLoadedGameMessage;
break;

View File

@ -28,7 +28,7 @@
#define PS_PROTOCOL_MAGIC 0x5073013f // 'P', 's', 0x01, '?'
#define PS_PROTOCOL_MAGIC_RESPONSE 0x50630121 // 'P', 'c', 0x01, '!'
#define PS_PROTOCOL_VERSION 0x01010012 // Arbitrary protocol
#define PS_PROTOCOL_VERSION 0x01010013 // Arbitrary protocol
#define PS_DEFAULT_PORT 0x5073 // 'P', 's'
// Defines the list of message types. The order of the list must not change.
@ -62,6 +62,7 @@ enum NetMessageType
NMT_CLIENT_TIMEOUT,
NMT_CLIENT_PERFORMANCE,
NMT_CLIENT_PAUSED,
NMT_LOADED_GAME,
NMT_GAME_START,
@ -183,6 +184,11 @@ START_NMT_CLASS_(ClientPerformance, NMT_CLIENT_PERFORMANCE)
NMT_END_ARRAY()
END_NMT_CLASS()
START_NMT_CLASS_(ClientPaused, NMT_CLIENT_PAUSED)
NMT_FIELD(CStr, m_GUID)
NMT_FIELD_INT(m_Pause, u8, 1)
END_NMT_CLASS()
START_NMT_CLASS_(LoadedGame, NMT_LOADED_GAME)
NMT_FIELD_INT(m_CurrentTurn, u32, 4)
END_NMT_CLASS()

View File

@ -655,6 +655,7 @@ void CNetServerWorker::SetupSession(CNetServerSession* session)
session->AddTransition(NSS_JOIN_SYNCING, (uint)NMT_LOADED_GAME, NSS_INGAME, (void*)&OnJoinSyncingLoadedGame, context);
session->AddTransition(NSS_INGAME, (uint)NMT_REJOINED, NSS_INGAME, (void*)&OnRejoined, context);
session->AddTransition(NSS_INGAME, (uint)NMT_CLIENT_PAUSED, NSS_INGAME, (void*)&OnClientPaused, context);
session->AddTransition(NSS_INGAME, (uint)NMT_CONNECTION_LOST, NSS_UNCONNECTED, (void*)&OnDisconnect, context);
session->AddTransition(NSS_INGAME, (uint)NMT_CHAT, NSS_INGAME, (void*)&OnChat, context);
session->AddTransition(NSS_INGAME, (uint)NMT_SIMULATION_COMMAND, NSS_INGAME, (void*)&OnInGame, context);
@ -698,6 +699,10 @@ void CNetServerWorker::OnUserJoin(CNetServerSession* session)
void CNetServerWorker::OnUserLeave(CNetServerSession* session)
{
std::vector<CStr>::iterator pausing = std::find(m_PausingPlayers.begin(), m_PausingPlayers.end(), session->GetGUID());
if (pausing != m_PausingPlayers.end())
m_PausingPlayers.erase(pausing);
RemovePlayer(session->GetGUID());
if (m_ServerTurnManager && session->GetCurrState() != NSS_JOIN_SYNCING)
@ -1186,6 +1191,15 @@ bool CNetServerWorker::OnRejoined(void* context, CFsmEvent* event)
server.Broadcast(message);
// Send all pausing players to the rejoined client.
for (const CStr& guid : server.m_PausingPlayers)
{
CClientPausedMessage pausedMessage;
pausedMessage.m_GUID = guid;
pausedMessage.m_Pause = true;
session->SendMessage(&pausedMessage);
}
return true;
}
@ -1201,6 +1215,45 @@ bool CNetServerWorker::OnDisconnect(void* context, CFsmEvent* event)
return true;
}
bool CNetServerWorker::OnClientPaused(void *context, CFsmEvent *event)
{
ENSURE(event->GetType() == (uint)NMT_CLIENT_PAUSED);
CNetServerSession* session = (CNetServerSession*)context;
CNetServerWorker& server = session->GetServer();
CClientPausedMessage* message = (CClientPausedMessage*)event->GetParamRef();
message->m_GUID = session->GetGUID();
// Update the list of pausing players.
std::vector<CStr>::iterator player = std::find(server.m_PausingPlayers.begin(), server.m_PausingPlayers.end(), session->GetGUID());
if (message->m_Pause)
{
if (player != server.m_PausingPlayers.end())
return true;
server.m_PausingPlayers.push_back(session->GetGUID());
}
else
{
if (player == server.m_PausingPlayers.end())
return true;
server.m_PausingPlayers.erase(player);
}
// Send messages to clients that are in game, and are not the client who paused.
for (CNetServerSession* session : server.m_Sessions)
{
if (session->GetCurrState() == NSS_INGAME && message->m_GUID != session->GetGUID())
session->SendMessage(message);
}
return true;
}
void CNetServerWorker::CheckGameLoadStatus(CNetServerSession* changedSession)
{
for (const CNetServerSession* session : m_Sessions)

View File

@ -274,6 +274,7 @@ private:
static bool OnJoinSyncingLoadedGame(void* context, CFsmEvent* event);
static bool OnRejoined(void* context, CFsmEvent* event);
static bool OnDisconnect(void* context, CFsmEvent* event);
static bool OnClientPaused(void* context, CFsmEvent* event);
void CheckGameLoadStatus(CNetServerSession* changedSession);
@ -316,6 +317,11 @@ private:
std::vector<u32> m_BannedIPs;
std::vector<CStrW> m_BannedPlayers;
/**
* Holds the GUIDs of all currently paused players.
*/
std::vector<CStr> m_PausingPlayers;
u32 m_NextHostID;
CNetServerTurnManager* m_ServerTurnManager;