diff --git a/binaries/data/mods/public/gui/common/functions_utility.js b/binaries/data/mods/public/gui/common/functions_utility.js index 0c531d54fd..25bf8cea3f 100644 --- a/binaries/data/mods/public/gui/common/functions_utility.js +++ b/binaries/data/mods/public/gui/common/functions_utility.js @@ -146,6 +146,20 @@ function rgbToGuiColor(color, alpha) return ret; } +function sameColor(color1, color2) +{ + return color1.r === color2.r && color1.g === color2.g && color1.b === color2.b; +} + +/** + * Computes the euclidian distance between the two colors. + * The smaller the return value, the close the colors. Zero if identical. + */ +function colorDistance(color1, color2) +{ + return Math.sqrt(Math.pow(color2.r - color1.r, 2) + Math.pow(color2.g - color1.g, 2) + Math.pow(color2.b - color1.b, 2)); +} + // ==================================================================== /** diff --git a/binaries/data/mods/public/gui/gamesetup/gamesetup.js b/binaries/data/mods/public/gui/gamesetup/gamesetup.js index bdba4d8bfa..51f0151737 100644 --- a/binaries/data/mods/public/gui/gamesetup/gamesetup.js +++ b/binaries/data/mods/public/gui/gamesetup/gamesetup.js @@ -10,6 +10,9 @@ const g_PopulationCapacities = prepareForDropdown(g_Settings ? g_Settings.Popula const g_StartingResources = prepareForDropdown(g_Settings ? g_Settings.StartingResources : undefined); const g_VictoryConditions = prepareForDropdown(g_Settings ? g_Settings.VictoryConditions : undefined); +// All colors except gaia +const g_PlayerColors = initPlayerDefaults().slice(1).map(pData => pData.Color); + //////////////////////////////////////////////////////////////////////////////////////////////// // Is this is a networked game, or offline @@ -366,6 +369,13 @@ function initMain() updateGameAttributes(); }; + // Populate color drop-down lists. + var colorPicker = Engine.GetGUIObjectByName("playerColorPicker["+i+"]"); + colorPicker.list = g_PlayerColors.map(color => '[color="' + color.r + ' ' + color.g + ' ' + color.b + '"] ■[/color]'); + colorPicker.list_data = g_PlayerColors.map((color, index) => index); + colorPicker.selected = -1; + colorPicker.onSelectionChange = function() { selectPlayerColor(playerSlot, this.selected); }; + // Set events var civ = Engine.GetGUIObjectByName("playerCiv["+i+"]"); civ.onSelectionChange = function() { @@ -877,6 +887,41 @@ function selectNumPlayers(num) updateGameAttributes(); } +/** + * Assigns the given color to that player. + */ +function selectPlayerColor(playerSlot, colorIndex) +{ + if (colorIndex == -1) + return; + + var playerData = g_GameAttributes.settings.PlayerData; + + // If someone else has that color, give that player the old color + var pData = playerData.find(pData => sameColor(g_PlayerColors[colorIndex], pData.Color)); + if (pData) + pData.Color = playerData[playerSlot].Color; + + // Assign the new color + playerData[playerSlot].Color = g_PlayerColors[colorIndex]; + + // Ensure colors are not used twice after increasing the number of players + ensureUniquePlayerColors(playerData); + + if (!g_IsInGuiUpdate) + updateGameAttributes(); +} + +function ensureUniquePlayerColors(playerData) +{ + for (let i = playerData.length - 1; i >= 0; --i) + { + // If someone else has that color, assign an unused color + if (playerData.some((pData, j) => i != j && sameColor(playerData[i].Color, pData.Color))) + playerData[i].Color = g_PlayerColors.find(color => playerData.every(pData => !sameColor(color, pData.Color))); + } +} + // Called when the user selects a map type from the list function selectMapType(type) { @@ -983,6 +1028,29 @@ function selectMap(name) g_GameAttributes.settings.VictoryScripts = g_VictoryConditions.Scripts[victoryIdx]; } + // Sanitize player data + if (mapSettings.PlayerData) + { + // Ignore gaia + if (mapSettings.PlayerData.length && !mapSettings.PlayerData[0]) + mapSettings.PlayerData.shift(); + + // Set default color if no color present + mapSettings.PlayerData.forEach((pData, index) => { + if (pData && !pData.Color) + pData.Color = g_PlayerColors[index]; + }); + + // Replace colors with the best matching color of PlayerDefaults + mapSettings.PlayerData.forEach((pData, index) => { + let colorDistances = g_PlayerColors.map(color => colorDistance(color, pData.Color)); + let smallestDistance = colorDistances.find(distance => colorDistances.every(distance2 => (distance2 >= distance))); + pData.Color = g_PlayerColors.find(color => colorDistance(color, pData.Color) == smallestDistance); + }); + + ensureUniquePlayerColors(mapSettings.PlayerData); + } + // Copy any new settings g_GameAttributes.map = name; g_GameAttributes.script = mapSettings.Script; @@ -990,10 +1058,6 @@ function selectMap(name) for (var prop in mapSettings) g_GameAttributes.settings[prop] = mapSettings[prop]; - // Ignore gaia - if (g_GameAttributes.settings.PlayerData.length && !g_GameAttributes.settings.PlayerData[0]) - g_GameAttributes.settings.PlayerData.shift(); - // Use default AI if the map doesn't specify any explicitly for (var i = 0; i < g_GameAttributes.settings.PlayerData.length; ++i) { @@ -1417,8 +1481,8 @@ function onGameAttributesChange() var pDefs = g_DefaultPlayerData ? g_DefaultPlayerData[i] : {}; // Common to all game types - var color = rgbToGuiColor(getSetting(pData, pDefs, "Color")); - pColor.sprite = "color:" + color + " 100"; + var color = getSetting(pData, pDefs, "Color"); + pColor.sprite = "color:" + rgbToGuiColor(color) + " 100"; pName.caption = translate(getSetting(pData, pDefs, "Name")); var team = getSetting(pData, pDefs, "Team"); @@ -1457,6 +1521,15 @@ function onGameAttributesChange() pCiv.selected = (civ ? pCiv.list_data.indexOf(civ) : 0); pTeam.selected = (team !== undefined && team >= 0) ? team+1 : 0; } + + // Allow host to chose player colors on non-scenario maps + const pColorPicker = Engine.GetGUIObjectByName("playerColorPicker["+i+"]"); + const pColorPickerHeading = Engine.GetGUIObjectByName("playerColorHeading"); + const canChangeColors = g_IsController && g_GameAttributes.mapType != "scenario"; + pColorPicker.hidden = !canChangeColors; + pColorPickerHeading.hidden = !canChangeColors; + if (canChangeColors) + pColorPicker.selected = g_PlayerColors.findIndex(col => sameColor(col, color)); } Engine.GetGUIObjectByName("mapInfoDescription").caption = playerString; diff --git a/binaries/data/mods/public/gui/gamesetup/gamesetup.xml b/binaries/data/mods/public/gui/gamesetup/gamesetup.xml index 7079438890..6d3fd10615 100644 --- a/binaries/data/mods/public/gui/gamesetup/gamesetup.xml +++ b/binaries/data/mods/public/gui/gamesetup/gamesetup.xml @@ -42,7 +42,10 @@ Player Name - + + Color + + Player Placement @@ -69,7 +72,10 @@