1
0
forked from 0ad/0ad

Implements ready status into gamesetup. Fixes #2447.

This was SVN commit r15006.
This commit is contained in:
scythetwirler 2014-04-26 18:34:34 +00:00
parent 4770e64449
commit d1d7afe46c
16 changed files with 359 additions and 43 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -271,11 +271,15 @@
<sprite name = "ModernDropDownArrow">
<image texture = "global/modern/dropdown-arrow.png"
real_texture_placement = "0 0 16 16"
texture_size="0 0 16 16"
size="0 6 16 22"
/>
</sprite>
<sprite name = "ModernDropDownArrowHighlight">
<image texture = "global/modern/dropdown-arrow.png"
real_texture_placement = "0 0 16 16"
texture_size="0 0 16 16"
size="0 6 16 22"
/>
</sprite>
<sprite name="ModernTickOn">
@ -296,4 +300,19 @@
size="0 0 22 22"
/>
</sprite>
<sprite name="ModernGear">
<image texture="global/modern/gear.png"
texture_size="0 0 24 24"
/>
</sprite>
<sprite name="ModernGearHover">
<image texture="global/modern/gear-hover.png"
texture_size="0 0 24 24"
/>
</sprite>
<sprite name="ModernGearPressed">
<image texture="global/modern/gear-press.png"
texture_size="0 0 24 24"
/>
</sprite>
</sprites>

View File

@ -46,7 +46,7 @@
sprite2_pressed="ModernDropDownArrowHighlight"
buffer_zone="8"
dropdown_size="216"
dropdown_size="224"
sprite_list="colour:12 12 12"
sprite_selectarea="ModernDarkBoxWhite"
textcolor_selected="white"

View File

@ -31,6 +31,19 @@ var g_ServerName;
// (and therefore shouldn't send further messages to the network)
var g_IsInGuiUpdate;
// Is this user ready
var g_IsReady;
// There are some duplicate orders on init, we can ignore these [bool].
var g_ReadyInit = true;
// If no one has changed ready status, we have no need to spam the settings changed message.
// 2 - Host's initial ready, suppressed settings message, 1 - Will show settings message, <=0 - Suppressed settings message
var g_ReadyChanged = 2;
// Has the game started?
var g_GameStarted = false;
var g_PlayerAssignments = {};
// Default game setup attributes
@ -98,7 +111,6 @@ function init(attribs)
{
cancelButton.tooltip = translate("Return to the lobby.");
}
}
// Called after the map data is loaded and cached
@ -296,6 +308,7 @@ function initMain()
}
Engine.GetGUIObjectByName("numPlayersSelection").hidden = true;
Engine.GetGUIObjectByName("startGame").enabled = true;
}
// Set up multiplayer/singleplayer bits:
@ -414,19 +427,37 @@ function handleNetMessage(message)
break;
case "players":
var resetReady = false;
var newPlayer = "";
// Find and report all joinings/leavings
for (var host in message.hosts)
{
if (! g_PlayerAssignments[host])
{
addChatMessage({ "type": "connect", "username": message.hosts[host].name });
newPlayer = host;
}
}
for (var host in g_PlayerAssignments)
{
if (! message.hosts[host])
{
addChatMessage({ "type": "disconnect", "guid": host });
if (g_PlayerAssignments[host].player != -1)
resetReady = true; // Observers shouldn't reset ready.
}
}
// Update the player list
g_PlayerAssignments = message.hosts;
updatePlayerList();
if (g_PlayerAssignments[newPlayer] && g_PlayerAssignments[newPlayer].player != -1)
resetReady = true;
if (resetReady)
resetReadyData(); // Observers shouldn't reset ready.
updateReadyUI();
if (g_IsController)
sendRegisterGameStanza();
break;
@ -449,6 +480,18 @@ function handleNetMessage(message)
addChatMessage({ "type": "message", "guid": message.guid, "text": message.text });
break;
// Singular client to host message
case "ready":
g_ReadyChanged -= 1;
if (g_ReadyChanged < 1 && g_PlayerAssignments[message.guid].player != -1)
addChatMessage({ "type": "ready", "guid": message.guid, "ready": +message.status == 1 });
if (!g_IsController)
break;
g_PlayerAssignments[message.guid].status = +message.status == 1;
Engine.SetNetworkPlayerStatus(message.guid, +message.status);
updateReadyUI();
break;
default:
error("Unrecognised net message type "+message.type);
}
@ -597,17 +640,13 @@ function loadMapData(name)
case "scenario":
case "skirmish":
g_MapData[name] = Engine.LoadMapSettings(name);
translateObjectKeys(g_MapData[name], ["Name", "Description"]);
break;
case "random":
if (name == "random")
g_MapData[name] = { settings: { "Name": translateWithContext("map", "Random"), "Description": translate("Randomly selects a map from the list") } };
else
{
g_MapData[name] = parseJSONData(name+".json");
translateObjectKeys(g_MapData[name], ["Name", "Description"]);
}
break;
default:
@ -711,7 +750,7 @@ function selectNumPlayers(num)
if (g_IsNetworked)
Engine.AssignNetworkPlayer(player, "");
else
g_PlayerAssignments = { "local": { "name": translate("You"), "player": 1, "civ": "", "team": -1} };
g_PlayerAssignments = { "local": { "name": translate("You"), "player": 1, "civ": "", "team": -1, "ready": 0} };
}
}
@ -828,7 +867,7 @@ function selectMap(name)
// Reset player assignments on map change
if (!g_IsNetworked)
{ // Slot 1
g_PlayerAssignments = { "local": { "name": translate("You"), "player": 1, "civ": "", "team": -1} };
g_PlayerAssignments = { "local": { "name": translate("You"), "player": 1, "civ": "", "team": -1, "ready": 0} };
}
else
{
@ -861,7 +900,7 @@ function launchGame()
if (g_GameAttributes.map == "random")
selectMap(Engine.GetGUIObjectByName("mapSelection").list_data[Math.floor(Math.random() *
(Engine.GetGUIObjectByName("mapSelection").list.length - 1)) + 1]);
g_GameStarted = true;
g_GameAttributes.settings.mapType = g_GameAttributes.mapType;
var numPlayers = g_GameAttributes.settings.PlayerData.length;
// Assign random civilizations to players with that choice
@ -1244,6 +1283,9 @@ function onGameAttributesChange()
// Game attributes include AI settings, so update the player list
updatePlayerList();
// We should have everyone confirm that the new settings are acceptable.
resetReadyData();
}
function updateGameAttributes()
@ -1413,6 +1455,7 @@ function updatePlayerList()
swapPlayers(guid, playerSlot);
Engine.SetNetworkGameAttributes(g_GameAttributes);
updateReadyUI();
}
};
}
@ -1494,14 +1537,22 @@ function submitChatInput()
function addChatMessage(msg)
{
var username = escapeText(msg.username || g_PlayerAssignments[msg.guid].name);
var message = escapeText(msg.text);
var username = "";
if (msg.username)
username = escapeText(msg.username);
else if (msg.guid && g_PlayerAssignments[msg.guid])
username = escapeText(g_PlayerAssignments[msg.guid].name);
var message = "";
if (msg.text)
message = escapeText(msg.text);
// TODO: Maybe host should have distinct font/color?
var color = "white";
if (g_PlayerAssignments[msg.guid] && g_PlayerAssignments[msg.guid].player != -1)
{ // Valid player who has been assigned - get player colour
if (msg.guid && g_PlayerAssignments[msg.guid] && g_PlayerAssignments[msg.guid].player != -1)
{
// Valid player who has been assigned - get player colour
var player = g_PlayerAssignments[msg.guid].player - 1;
var mapName = g_GameAttributes.map;
var mapData = loadMapData(mapName);
@ -1516,13 +1567,13 @@ function addChatMessage(msg)
switch (msg.type)
{
case "connect":
var formattedUsername = '[font="sans-bold-13"][color="'+ color +'"]' + username + '[/color][/font][color="gold"]'
formatted = '[color="gold"]' + sprintf(translate("%(username)s has joined"), { username: formattedUsername });
var formattedUsername = '[font="sans-bold-13"][color="'+ color +'"]' + username + '[/color][/font]'
formatted = '[color="gold"]' + sprintf(translate("%(username)s has joined"), { username: formattedUsername }) + '[/color]';
break;
case "disconnect":
var formattedUsername = '[font="sans-bold-13"][color="'+ color +'"]' + username + '[/color][/font][color="gold"]'
formatted = '[color="gold"]' + sprintf(translate("%(username)s has left"), { username: formattedUsername });
var formattedUsername = '[font="sans-bold-13"][color="'+ color +'"]' + username + '[/color][/font]'
formatted = '[color="gold"]' + sprintf(translate("%(username)s has left"), { username: formattedUsername }) + '[/color]';
break;
case "message":
@ -1531,6 +1582,18 @@ function addChatMessage(msg)
formatted = sprintf(translate("%(username)s %(message)s"), { username: formattedUsernamePrefix, message: message });
break;
case "ready":
var formattedUsername = '[font="sans-bold-13"][color="'+ color +'"]' + username + '[/color][/font]'
if (msg.ready)
formatted = '[color="gold"]*' + sprintf(translate("%(username)s is ready!"), { username: formattedUsername }) + '[/color]';
else
formatted = '[color="gold"]*' + sprintf(translate("%(username)s is not ready."), { username: formattedUsername }) + '[/color]';
break;
case "settings":
formatted = '[color="gold"][font="sans-bold-13"]*' + translate('Game settings have been changed.') + '[/font][/color]';
break;
default:
error(sprintf("Invalid chat message '%(message)s'", { message: uneval(msg) }));
return;
@ -1547,6 +1610,80 @@ function toggleMoreOptions()
Engine.GetGUIObjectByName("moreOptions").hidden = !Engine.GetGUIObjectByName("moreOptions").hidden;
}
function toggleReady()
{
g_IsReady = !g_IsReady;
if (g_IsReady)
{
Engine.SendNetworkReady(1);
Engine.GetGUIObjectByName("startGame").caption = translate("I'm not ready.");
Engine.GetGUIObjectByName("startGame").tooltip = translate("State that you are not ready to play.");
}
else
{
Engine.SendNetworkReady(0);
Engine.GetGUIObjectByName("startGame").caption = translate("I'm ready!");
Engine.GetGUIObjectByName("startGame").tooltip = translate("State that you are ready to play!");
}
}
function updateReadyUI()
{
var allReady = true;
for (var guid in g_PlayerAssignments)
{
// We don't really care whether observers are ready.
if (g_PlayerAssignments[guid].player == -1 || !g_GameAttributes.settings.PlayerData[g_PlayerAssignments[guid].player - 1])
continue;
var pData = g_GameAttributes.settings.PlayerData ? g_GameAttributes.settings.PlayerData[g_PlayerAssignments[guid].player - 1] : {};
var pDefs = g_DefaultPlayerData ? g_DefaultPlayerData[g_PlayerAssignments[guid].player - 1] : {};
if (g_PlayerAssignments[guid].status || !g_IsNetworked)
Engine.GetGUIObjectByName("playerName[" + (g_PlayerAssignments[guid].player - 1) + "]").caption = '[color="0 255 0"]' + getSetting(pData, pDefs, "Name") + '[/color]';
else
{
Engine.GetGUIObjectByName("playerName[" + (g_PlayerAssignments[guid].player - 1) + "]").caption = getSetting(pData, pDefs, "Name");
allReady = false;
}
}
// AIs are always ready.
for (var playerid = 0; playerid < MAX_PLAYERS; playerid++)
{
if (!g_GameAttributes.settings.PlayerData[playerid])
continue;
var pData = g_GameAttributes.settings.PlayerData ? g_GameAttributes.settings.PlayerData[playerid] : {};
var pDefs = g_DefaultPlayerData ? g_DefaultPlayerData[playerid] : {};
if (g_GameAttributes.settings.PlayerData[playerid].AI != "" || g_GameAttributes.settings.PlayerData[playerid].Name == "Unassigned")
Engine.GetGUIObjectByName("playerName[" + playerid + "]").caption = '[color="0 255 0"]' + getSetting(pData, pDefs, "Name") + '[/color]';
}
// The host is not allowed to start until everyone is ready.
if (g_IsNetworked && g_IsController)
Engine.GetGUIObjectByName("startGame").enabled = allReady;
}
function resetReadyData()
{
if (g_GameStarted)
return;
if (g_ReadyChanged < 1)
addChatMessage({ "type": "settings"});
else if (g_ReadyChanged == 2 && !g_ReadyInit)
return; // duplicate calls on init
else
g_ReadyInit = false;
g_ReadyChanged = 2;
if (g_IsNetworked && g_IsController)
{
Engine.ClearAllPlayerReady();
g_IsReady = true;
Engine.SendNetworkReady(1);
}
else
{
g_IsReady = false;
Engine.GetGUIObjectByName("startGame").caption = translate("I'm ready!");
Engine.GetGUIObjectByName("startGame").tooltip = translate("State that you accept the current settings and are ready to play!");
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
// Basic map filters API

View File

@ -14,7 +14,7 @@
<object style="TitleText" type="text" size="50%-128 4 50%+128 36">
<translatableAttribute id="caption">Match Setup</translatableAttribute>
</object>
<object type="image" style="ModernDialog" size="50%-190 50%-80 50%+190 50%+80" name="loadingWindow">
<object type="text" style="TitleText" size="50%-128 0%-16 50%+128 16">
@ -32,19 +32,19 @@
<action on="Tick">
onTick();
</action>
<!-- Number of Players-->
<object size="24 26 224 54">
<object size="24 26 100%-24 54">
<!-- Number of Players-->
<object size="0 0 150 28">
<object size="0 0 50%-170 28">
<object size="0 0 100% 100%" type="text" style="ModernRightLabelText">
<translatableAttribute id="caption">Number of players:</translatableAttribute>
</object>
</object>
<!-- Number of Players-->
<object size="150 0 200 28">
<object size="50%-170 0 50%-120 28">
<object name="numPlayersText" size="0 0 100% 100%" type="text" style="ModernLeftLabelText"/>
<object name="numPlayersSelection"
type="dropdown"
@ -61,20 +61,20 @@
<!-- Player assignments -->
<object size="24 59 100%-440 358" type="image" sprite="ModernDarkBoxGold" name="playerAssignmentsPannel">
<object size="0 6 100% 30">
<object name="playerNameHeading" type="text" style="ModernLabelText" size="0 0 20% 100%">
<object name="playerNameHeading" type="text" style="ModernLabelText" size="0 0 22% 100%">
<translatableAttribute id="caption">Player Name</translatableAttribute>
</object>
<object name="playerPlacementHeading" type="text" style="ModernLabelText" size="20%+5 0 50% 100%">
<object name="playerPlacementHeading" type="text" style="ModernLabelText" size="22%+5 0 50%+35 100%">
<translatableAttribute id="caption">Player Placement</translatableAttribute>
</object>
<object name="playerCivHeading" type="text" style="ModernLabelText" size="50%+65 0 85% 100%">
<object name="playerCivHeading" type="text" style="ModernLabelText" size="50%+65 0 85%-26 100%">
<translatableAttribute id="caption">Civilization</translatableAttribute>
</object>
<object name="civInfoButton"
type="button"
sprite="iconInfoGold"
sprite_over="iconInfoWhite"
size="82%-8 0 82%+8 16"
size="67.5%+68 0 67.5%+84 16"
tooltip_style="onscreenToolTip"
>
<translatableAttribute id="tooltip">View civilization info</translatableAttribute>
@ -90,18 +90,20 @@
<repeat count="8">
<object name="playerBox[n]" size="0 0 100% 32" hidden="true">
<object name="playerColour[n]" type="image" size="0 0 100% 100%"/>
<object name="playerName[n]" type="text" style="ModernLabelText" size="0 2 20% 30"/>
<object name="playerAssignment[n]" type="dropdown" style="ModernDropDown" size="20%+5 2 50% 30" tooltip_style="onscreenToolTip">
<object name="playerName[n]" type="text" style="ModernLabelText" size="0 2 22% 30"/>
<object name="playerAssignment[n]" type="dropdown" style="ModernDropDown" size="22%+5 2 50%+35 30" tooltip_style="onscreenToolTip">
<translatableAttribute id="tooltip">Select player.</translatableAttribute>
</object>
<object name="playerConfig[n]" type="button" style="StoneButton" size="50%+5 6 50%+60 26"
<object name="playerConfig[n]" type="button" style="StoneButton" size="50%+40 4 50%+64 28"
tooltip_style="onscreenToolTip"
font="sans-bold-stroke-12"
sprite="ModernGear"
sprite_over="ModernGearHover"
sprite_pressed="ModernGearPressed"
>
<translatableAttribute id="caption">Settings</translatableAttribute>
<translatableAttribute id="tooltip">Configure AI settings.</translatableAttribute>
</object>
<object name="playerCiv[n]" type="dropdown" style="ModernDropDown" size="50%+65 2 85% 30" tooltip_style="onscreenToolTip">
<object name="playerCiv[n]" type="dropdown" style="ModernDropDown" size="50%+69 2 85% 30" tooltip_style="onscreenToolTip">
<translatableAttribute id="tooltip">Select player's civilization.</translatableAttribute>
</object>
<object name="playerCivText[n]" type="text" style="ModernLabelText" size="50%+65 0 85% 30"/>
@ -113,7 +115,7 @@
</repeat>
</object>
</object>
<object size="24 64 100%-460 358" type="image" sprite="CoverFillDark" name="playerAssignmentsPannelCover" hidden="true"/>
<object size="24 64 100%-460 358" type="image" sprite="CoverFillDark" name="playerAssignmentsPanelCover" hidden="true"/>
<!-- Map selection -->
@ -138,7 +140,7 @@
<object name="mapSelectionText" type="text" style="ModernLeftLabelText" size="0 64 100% 94" hidden="true"/>
<object name="mapSizeText" type="text" style="ModernLeftLabelText" size="0 96 100% 126" hidden="true"/>
</object>
<object name="mapTypeSelection"
type="dropdown"
style="ModernDropDown"
@ -169,7 +171,7 @@
</object>
</object>
<object name="mapSize" size="100%-325 459 100%-25 487" type="dropdown" style="ModernDropDown" hidden="true" tooltip_style="onscreenToolTip">
<object name="mapSize" size="100%-315 459 100%-25 487" type="dropdown" style="ModernDropDown" hidden="true" tooltip_style="onscreenToolTip">
<translatableAttribute id="tooltip">Select map size. (Larger sizes may reduce performance.)</translatableAttribute>
</object>
@ -187,7 +189,7 @@
</object>
<!-- Chat window -->
<object name="chatPanel" size="24 370 100%-440 100%-60" type="image" sprite="ModernDarkBoxGold">
<object name="chatPanel" size="24 370 100%-440 100%-58" type="image" sprite="ModernDarkBoxGold">
<object name="chatText" size="2 2 100%-2 100%-26" type="text" style="ChatPanel"/>
<object name="chatInput" size="4 100%-24 100%-76 100%-4" type="input" style="ModernInput">
@ -207,12 +209,12 @@
textcolor="white"
sprite="BackgroundTranslucent"
hidden="true"
size="100%-700 100%-56 100%-312 100%-24"
size="20 100%-56 100%-312 100%-24"
>
<translatableAttribute id="caption">[Tooltip text]</translatableAttribute>
</object>
<!-- Start Button -->
<!-- Start/Ready Button -->
<object
name="startGame"
type="button"
@ -223,7 +225,12 @@
>
<translatableAttribute id="caption">Start game!</translatableAttribute>
<translatableAttribute id="tooltip">Start a new game with the current settings.</translatableAttribute>
<action on="Press">launchGame();</action>
<action on="Press">
if (g_IsController)
launchGame();
else
toggleReady();
</action>
</object>
<!-- Cancel Button -->
@ -245,7 +252,7 @@
]]>
</action>
</object>
<!-- Options -->
<object name="gameOptionsBox" size="100%-425 497 100%-25 525">
<!-- More Options Button -->

View File

@ -344,6 +344,20 @@ void AssignNetworkPlayer(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), int pla
g_NetServer->AssignPlayer(playerID, guid);
}
void SetNetworkPlayerStatus(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::string guid, int ready)
{
ENSURE(g_NetServer);
g_NetServer->SetPlayerReady(guid, ready);
}
void ClearAllPlayerReady (ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
ENSURE(g_NetServer);
g_NetServer->ClearAllPlayerReady();
}
void SendNetworkChat(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::wstring message)
{
ENSURE(g_NetClient);
@ -351,6 +365,13 @@ void SendNetworkChat(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::wstrin
g_NetClient->SendChatMessage(message);
}
void SendNetworkReady(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), int message)
{
ENSURE(g_NetClient);
g_NetClient->SendReadyMessage(message);
}
std::vector<CScriptValRooted> GetAIs(ScriptInterface::CxPrivate* pCxPrivate)
{
return ICmpAIManager::GetAIs(*(pCxPrivate->pScriptInterface));
@ -861,7 +882,10 @@ void GuiScriptingInit(ScriptInterface& scriptInterface)
scriptInterface.RegisterFunction<CScriptVal, &PollNetworkClient>("PollNetworkClient");
scriptInterface.RegisterFunction<void, CScriptVal, &SetNetworkGameAttributes>("SetNetworkGameAttributes");
scriptInterface.RegisterFunction<void, int, std::string, &AssignNetworkPlayer>("AssignNetworkPlayer");
scriptInterface.RegisterFunction<void, std::string, int, &SetNetworkPlayerStatus>("SetNetworkPlayerStatus");
scriptInterface.RegisterFunction<void, &ClearAllPlayerReady>("ClearAllPlayerReady");
scriptInterface.RegisterFunction<void, std::wstring, &SendNetworkChat>("SendNetworkChat");
scriptInterface.RegisterFunction<void, int, &SendNetworkReady>("SendNetworkReady");
scriptInterface.RegisterFunction<std::vector<CScriptValRooted>, &GetAIs>("GetAIs");
scriptInterface.RegisterFunction<CScriptValRooted, &GetEngineInfo>("GetEngineInfo");

View File

@ -87,6 +87,7 @@ CNetClient::CNetClient(CGame* game) :
AddTransition(NCS_INITIAL_GAMESETUP, (uint)NMT_GAME_SETUP, NCS_PREGAME, (void*)&OnGameSetup, context);
AddTransition(NCS_PREGAME, (uint)NMT_CHAT, NCS_PREGAME, (void*)&OnChat, context);
AddTransition(NCS_PREGAME, (uint)NMT_READY, NCS_PREGAME, (void*)&OnReady, context);
AddTransition(NCS_PREGAME, (uint)NMT_GAME_SETUP, NCS_PREGAME, (void*)&OnGameSetup, context);
AddTransition(NCS_PREGAME, (uint)NMT_PLAYER_ASSIGNMENT, NCS_PREGAME, (void*)&OnPlayerAssignment, context);
AddTransition(NCS_PREGAME, (uint)NMT_GAME_START, NCS_LOADING, (void*)&OnGameStart, context);
@ -214,6 +215,7 @@ void CNetClient::PostPlayerAssignmentsToScript()
GetScriptInterface().Eval("({})", host);
GetScriptInterface().SetProperty(host.get(), "name", std::wstring(it->second.m_Name), false);
GetScriptInterface().SetProperty(host.get(), "player", it->second.m_PlayerID, false);
GetScriptInterface().SetProperty(host.get(), "status", it->second.m_Status, false);
GetScriptInterface().SetProperty(hosts.get(), it->first.c_str(), host, false);
}
@ -254,6 +256,13 @@ void CNetClient::SendChatMessage(const std::wstring& text)
SendMessage(&chat);
}
void CNetClient::SendReadyMessage(const int status)
{
CReadyMessage readyStatus;
readyStatus.m_Status = status;
SendMessage(&readyStatus);
}
bool CNetClient::HandleMessage(CNetMessage* message)
{
// Handle non-FSM messages first
@ -419,6 +428,23 @@ bool CNetClient::OnChat(void* context, CFsmEvent* event)
return true;
}
bool CNetClient::OnReady(void* context, CFsmEvent* event)
{
ENSURE(event->GetType() == (uint)NMT_READY);
CNetClient* client = (CNetClient*)context;
CReadyMessage* message = (CReadyMessage*)event->GetParamRef();
CScriptValRooted msg;
client->GetScriptInterface().Eval("({'type':'ready'})", msg);
client->GetScriptInterface().SetProperty(msg.get(), "guid", std::string(message->m_GUID), false);
client->GetScriptInterface().SetProperty(msg.get(), "status", int (message->m_Status), false);
client->PushGuiMessage(msg);
return true;
}
bool CNetClient::OnGameSetup(void* context, CFsmEvent* event)
{
ENSURE(event->GetType() == (uint)NMT_GAME_SETUP);
@ -453,6 +479,7 @@ bool CNetClient::OnPlayerAssignment(void* context, CFsmEvent* event)
assignment.m_Enabled = true;
assignment.m_Name = message->m_Hosts[i].m_Name;
assignment.m_PlayerID = message->m_Hosts[i].m_PlayerID;
assignment.m_Status = message->m_Hosts[i].m_Status;
newPlayerAssignments[message->m_Hosts[i].m_GUID] = assignment;
}

View File

@ -164,6 +164,8 @@ public:
void LoadFinished();
void SendChatMessage(const std::wstring& text);
void SendReadyMessage(const int status);
private:
// Net message / FSM transition handlers
@ -172,6 +174,7 @@ private:
static bool OnHandshakeResponse(void* context, CFsmEvent* event);
static bool OnAuthenticate(void* context, CFsmEvent* event);
static bool OnChat(void* context, CFsmEvent* event);
static bool OnReady(void* context, CFsmEvent* event);
static bool OnGameSetup(void* context, CFsmEvent* event);
static bool OnPlayerAssignment(void* context, CFsmEvent* event);
static bool OnInGame(void* context, CFsmEvent* event);

View File

@ -46,6 +46,9 @@ struct PlayerAssignment
/// The player that the given host controls, or -1 if none (observer)
i32 m_PlayerID;
/// Status - Ready or not: 0 for not ready, 1 for ready
u8 m_Status;
};
typedef std::map<CStr, PlayerAssignment> PlayerAssignmentMap; // map from GUID -> assignment

View File

@ -174,6 +174,10 @@ CNetMessage* CNetMessageFactory::CreateMessage(const void* pData,
case NMT_CHAT:
pNewMessage = new CChatMessage;
break;
case NMT_READY:
pNewMessage = new CReadyMessage;
break;
case NMT_SIMULATION_COMMAND:
pNewMessage = new CSimulationMessage(scriptInterface);

View File

@ -46,6 +46,7 @@ enum NetMessageType
NMT_AUTHENTICATE, // Authentication stage
NMT_AUTHENTICATE_RESULT,
NMT_CHAT, // Common chat message
NMT_READY,
NMT_GAME_SETUP,
NMT_PLAYER_ASSIGNMENT,
@ -118,11 +119,17 @@ START_NMT_CLASS_(Chat, NMT_CHAT)
NMT_FIELD(CStrW, m_Message)
END_NMT_CLASS()
START_NMT_CLASS_(Ready, NMT_READY)
NMT_FIELD(CStr8, m_GUID)
NMT_FIELD_INT(m_Status, u8, 1)
END_NMT_CLASS()
START_NMT_CLASS_(PlayerAssignment, NMT_PLAYER_ASSIGNMENT)
NMT_START_ARRAY(m_Hosts)
NMT_FIELD(CStr8, m_GUID)
NMT_FIELD(CStrW, m_Name)
NMT_FIELD_INT(m_PlayerID, i8, 1)
NMT_FIELD_INT(m_Status, u8, 1)
NMT_END_ARRAY()
END_NMT_CLASS()

View File

@ -376,6 +376,8 @@ bool CNetServerWorker::RunStep()
std::vector<std::pair<int, CStr> > newAssignPlayer;
std::vector<bool> newStartGame;
std::vector<std::pair<CStr, int> > newPlayerReady;
std::vector<bool> newPlayerResetReady;
std::vector<std::string> newGameAttributes;
std::vector<u32> newTurnLength;
@ -386,6 +388,8 @@ bool CNetServerWorker::RunStep()
return false;
newStartGame.swap(m_StartGameQueue);
newPlayerReady.swap(m_PlayerReadyQueue);
newPlayerResetReady.swap(m_PlayerResetReadyQueue);
newAssignPlayer.swap(m_AssignPlayerQueue);
newGameAttributes.swap(m_GameAttributesQueue);
newTurnLength.swap(m_TurnLengthQueue);
@ -394,6 +398,12 @@ bool CNetServerWorker::RunStep()
for (size_t i = 0; i < newAssignPlayer.size(); ++i)
AssignPlayer(newAssignPlayer[i].first, newAssignPlayer[i].second);
for (size_t i = 0; i < newPlayerReady.size(); ++i)
SetPlayerReady(newPlayerReady[i].first, newPlayerReady[i].second);
if (!newPlayerResetReady.empty())
ClearAllPlayerReady();
if (!newGameAttributes.empty())
UpdateGameAttributes(GetScriptInterface().ParseJSON(newGameAttributes.back()));
@ -548,6 +558,7 @@ void CNetServerWorker::SetupSession(CNetServerSession* session)
session->AddTransition(NSS_PREGAME, (uint)NMT_CONNECTION_LOST, NSS_UNCONNECTED, (void*)&OnDisconnect, context);
session->AddTransition(NSS_PREGAME, (uint)NMT_CHAT, NSS_PREGAME, (void*)&OnChat, context);
session->AddTransition(NSS_PREGAME, (uint)NMT_READY, NSS_PREGAME, (void*)&OnReady, context);
session->AddTransition(NSS_PREGAME, (uint)NMT_LOADED_GAME, NSS_INGAME, (void*)&OnLoadedGame, context);
session->AddTransition(NSS_JOIN_SYNCING, (uint)NMT_LOADED_GAME, NSS_INGAME, (void*)&OnJoinSyncingLoadedGame, context);
@ -649,6 +660,7 @@ void CNetServerWorker::AddPlayer(const CStr& guid, const CStrW& name)
assignment.m_Enabled = true;
assignment.m_Name = name;
assignment.m_PlayerID = playerID;
assignment.m_Status = 0;
m_PlayerAssignments[guid] = assignment;
// Send the new assignments to all currently active players
@ -663,6 +675,21 @@ void CNetServerWorker::RemovePlayer(const CStr& guid)
SendPlayerAssignments();
}
void CNetServerWorker::SetPlayerReady(const CStr& guid, const int ready)
{
m_PlayerAssignments[guid].m_Status = ready;
SendPlayerAssignments();
}
void CNetServerWorker::ClearAllPlayerReady()
{
for (PlayerAssignmentMap::iterator it = m_PlayerAssignments.begin(); it != m_PlayerAssignments.end(); ++it)
it->second.m_Status = 0;
SendPlayerAssignments();
}
void CNetServerWorker::AssignPlayer(int playerID, const CStr& guid)
{
// Remove anyone who's already assigned to this player
@ -690,6 +717,7 @@ void CNetServerWorker::ConstructPlayerAssignmentMessage(CPlayerAssignmentMessage
h.m_GUID = it->first;
h.m_Name = it->second.m_Name;
h.m_PlayerID = it->second.m_PlayerID;
h.m_Status = it->second.m_Status;
message.m_Hosts.push_back(h);
}
}
@ -860,6 +888,22 @@ bool CNetServerWorker::OnChat(void* context, CFsmEvent* event)
return true;
}
bool CNetServerWorker::OnReady(void* context, CFsmEvent* event)
{
ENSURE(event->GetType() == (uint)NMT_READY);
CNetServerSession* session = (CNetServerSession*)context;
CNetServerWorker& server = session->GetServer();
CReadyMessage* message = (CReadyMessage*)event->GetParamRef();
message->m_GUID = session->GetGUID();
server.Broadcast(message);
return true;
}
bool CNetServerWorker::OnLoadedGame(void* context, CFsmEvent* event)
{
ENSURE(event->GetType() == (uint)NMT_LOADED_GAME);
@ -1053,6 +1097,18 @@ void CNetServer::AssignPlayer(int playerID, const CStr& guid)
m_Worker->m_AssignPlayerQueue.push_back(std::make_pair(playerID, guid));
}
void CNetServer::SetPlayerReady(const CStr& guid, int ready)
{
CScopeLock lock(m_Worker->m_WorkerMutex);
m_Worker->m_PlayerReadyQueue.push_back(std::make_pair(guid, ready));
}
void CNetServer::ClearAllPlayerReady()
{
CScopeLock lock(m_Worker->m_WorkerMutex);
m_Worker->m_PlayerResetReadyQueue.push_back(false);
}
void CNetServer::StartGame()
{
CScopeLock lock(m_Worker->m_WorkerMutex);

View File

@ -122,7 +122,19 @@ public:
* The changes will be asynchronously propagated to all clients.
*/
void AssignPlayer(int playerID, const CStr& guid);
/**
* Call from the GUI to update the player readiness.
* The changes will be asynchronously propagated to all clients.
*/
void SetPlayerReady(const CStr& guid, int ready);
/**
* Call from the GUI to set the all player readiness to 0.
* The changes will be asynchronously propagated to all clients.
*/
void ClearAllPlayerReady();
/**
* Call from the GUI to asynchronously notify all clients that they should start loading the game.
*/
@ -233,7 +245,9 @@ private:
void AddPlayer(const CStr& guid, const CStrW& name);
void RemovePlayer(const CStr& guid);
void SetPlayerReady(const CStr& guid, const int ready);
void SendPlayerAssignments();
void ClearAllPlayerReady();
void SetupSession(CNetServerSession* session);
bool HandleConnect(CNetServerSession* session);
@ -245,6 +259,7 @@ private:
static bool OnAuthenticate(void* context, CFsmEvent* event);
static bool OnInGame(void* context, CFsmEvent* event);
static bool OnChat(void* context, CFsmEvent* event);
static bool OnReady(void* context, CFsmEvent* event);
static bool OnLoadedGame(void* context, CFsmEvent* event);
static bool OnJoinSyncingLoadedGame(void* context, CFsmEvent* event);
static bool OnDisconnect(void* context, CFsmEvent* event);
@ -320,6 +335,8 @@ private:
// Queues for messages sent by the game thread:
std::vector<std::pair<int, CStr> > m_AssignPlayerQueue; // protected by m_WorkerMutex
std::vector<bool> m_StartGameQueue; // protected by m_WorkerMutex
std::vector<std::pair<CStr, int> > m_PlayerReadyQueue; // protected by m_WorkerMutex
std::vector<bool> m_PlayerResetReadyQueue; // protected by m_WorkerMutex
std::vector<std::string> m_GameAttributesQueue; // protected by m_WorkerMutex
std::vector<u32> m_TurnLengthQueue; // protected by m_WorkerMutex
};

View File

@ -402,7 +402,8 @@ bool CFsm::Update( unsigned int eventType, void* pEventParam )
// Lookup transition
CFsmTransition* pTransition = GetTransition( m_CurrState, eventType );
if ( !pTransition ) return false;
if ( !pTransition )
return false;
// Setup event parameter
EventMap::iterator it = m_Events.find( eventType );
@ -413,14 +414,16 @@ bool CFsm::Update( unsigned int eventType, void* pEventParam )
}
// Valid transition?
if ( !pTransition->ApplyConditions() ) return false;
if ( !pTransition->ApplyConditions() )
return false;
// Save the default state transition (actions might call SetNextState
// to override this)
SetNextState( pTransition->GetNextState() );
// Run transition actions
if ( !pTransition->RunActions() ) return false;
if ( !pTransition->RunActions() )
return false;
// Switch state
SetCurrState( GetNextState() );