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 @@ Match Setup - + @@ -32,19 +32,19 @@ onTick(); - + - + - + Number of players: - + - + - + Player Name - + Player Placement - + Civilization View civilization info @@ -90,18 +90,20 @@