diff --git a/binaries/data/mods/public/art/textures/ui/global/modern/gear-hover.png b/binaries/data/mods/public/art/textures/ui/global/modern/gear-hover.png
new file mode 100644
index 0000000000..cbc7fd744f
--- /dev/null
+++ b/binaries/data/mods/public/art/textures/ui/global/modern/gear-hover.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:21134c1ca42e276bf0368fe95d0c48dd6e00ee9d7ebaffa069935c6efccd123f
+size 19343
diff --git a/binaries/data/mods/public/art/textures/ui/global/modern/gear-press.png b/binaries/data/mods/public/art/textures/ui/global/modern/gear-press.png
new file mode 100644
index 0000000000..ec6932e005
--- /dev/null
+++ b/binaries/data/mods/public/art/textures/ui/global/modern/gear-press.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b7b1d31f94156c735f1d20729bc8c95fc1baa2565d71f6c41625c6d8697832d4
+size 20232
diff --git a/binaries/data/mods/public/art/textures/ui/global/modern/gear.png b/binaries/data/mods/public/art/textures/ui/global/modern/gear.png
new file mode 100644
index 0000000000..5b3dfc282b
--- /dev/null
+++ b/binaries/data/mods/public/art/textures/ui/global/modern/gear.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:e9a7491f799ac5f26346eea436407ed1b858591ad7a9421f8f55a0ec0109adf3
+size 20290
diff --git a/binaries/data/mods/public/gui/common/modern/sprites.xml b/binaries/data/mods/public/gui/common/modern/sprites.xml
index c2309bdbb1..c5d56b5a82 100644
--- a/binaries/data/mods/public/gui/common/modern/sprites.xml
+++ b/binaries/data/mods/public/gui/common/modern/sprites.xml
@@ -271,11 +271,15 @@
@@ -296,4 +300,19 @@
size="0 0 22 22"
/>
+
+
+
+
+
+
+
+
+
diff --git a/binaries/data/mods/public/gui/common/modern/styles.xml b/binaries/data/mods/public/gui/common/modern/styles.xml
index 47c7f53b11..337ead286d 100644
--- a/binaries/data/mods/public/gui/common/modern/styles.xml
+++ b/binaries/data/mods/public/gui/common/modern/styles.xml
@@ -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"
diff --git a/binaries/data/mods/public/gui/gamesetup/gamesetup.js b/binaries/data/mods/public/gui/gamesetup/gamesetup.js
index 5dc29ebdbb..b907a2a764 100644
--- a/binaries/data/mods/public/gui/gamesetup/gamesetup.js
+++ b/binaries/data/mods/public/gui/gamesetup/gamesetup.js
@@ -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
diff --git a/binaries/data/mods/public/gui/gamesetup/gamesetup.xml b/binaries/data/mods/public/gui/gamesetup/gamesetup.xml
index ce1b443df0..f24756d20d 100644
--- a/binaries/data/mods/public/gui/gamesetup/gamesetup.xml
+++ b/binaries/data/mods/public/gui/gamesetup/gamesetup.xml
@@ -14,7 +14,7 @@
-
+