diff --git a/android/assets/jsons/translations/template.properties b/android/assets/jsons/translations/template.properties index b9648bd81c..79bd23b4c9 100644 --- a/android/assets/jsons/translations/template.properties +++ b/android/assets/jsons/translations/template.properties @@ -360,6 +360,12 @@ Three Continents = Four Corners = Archipelago = Inner Sea = +Random number of Civilizations = +Min number of Civilizations = +Max number of Civilizations = +Random number of City-States = +Min number of City-States = +Max number of City-States = Number of City-States = One City Challenge = No City Razing = diff --git a/core/src/com/unciv/logic/GameStarter.kt b/core/src/com/unciv/logic/GameStarter.kt index 96de438ba0..3535997cf0 100644 --- a/core/src/com/unciv/logic/GameStarter.kt +++ b/core/src/com/unciv/logic/GameStarter.kt @@ -13,6 +13,7 @@ import com.unciv.logic.map.TileMap import com.unciv.logic.map.mapgenerator.MapGenerator import com.unciv.models.metadata.GameParameters import com.unciv.models.metadata.GameSetupInfo +import com.unciv.models.metadata.Player import com.unciv.models.ruleset.ModOptionsConstants import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.RulesetCache @@ -236,6 +237,33 @@ object GameStarter { val presetMajors = Stack() presetMajors.addAll(availableCivNames.filter { it in civNamesWithStartingLocations }) + if (newGameParameters.randomNumberOfPlayers) { + // This swaps min and max if the user accidentally swapped min and max + val min = newGameParameters.minNumberOfPlayers.coerceAtMost(newGameParameters.maxNumberOfPlayers) + val max = newGameParameters.maxNumberOfPlayers.coerceAtLeast(newGameParameters.minNumberOfPlayers) + var playerCount = (min..max).random() + + val humanPlayerCount = newGameParameters.players.filter { + it.playerType === PlayerType.Human + }.count() + val spectatorCount = newGameParameters.players.filter { + it.chosenCiv === Constants.spectator + }.count() + playerCount = playerCount.coerceAtLeast(humanPlayerCount + spectatorCount) + + if (newGameParameters.players.size < playerCount) { + val neededPlayers = playerCount - newGameParameters.players.size + for (i in 1..neededPlayers) newGameParameters.players.add(Player()) + } else if (newGameParameters.players.size > playerCount) { + val extraPlayers = newGameParameters.players.size - playerCount + val playersToRemove = newGameParameters.players.filter { + it.playerType === PlayerType.AI + }.shuffled().subList(0, extraPlayers) + + newGameParameters.players.removeAll(playersToRemove) + } + } + for (player in newGameParameters.players.sortedBy { it.chosenCiv == Constants.random }) { val nationName = when { player.chosenCiv != Constants.random -> player.chosenCiv @@ -263,9 +291,17 @@ object GameStarter { .shuffled() .sortedBy { it in civNamesWithStartingLocations } ) // pop() gets the last item, so sort ascending + val numberOfCityStates = if (newGameParameters.randomNumberOfCityStates) { + // This swaps min and max if the user accidentally swapped min and max + val min = newGameParameters.minNumberOfCityStates.coerceAtMost(newGameParameters.maxNumberOfCityStates) + val max = newGameParameters.maxNumberOfCityStates.coerceAtLeast(newGameParameters.minNumberOfCityStates) + (min..max).random() + } else { + newGameParameters.numberOfCityStates + } var addedCityStates = 0 // Keep trying to add city states until we reach the target number. - while (addedCityStates < newGameParameters.numberOfCityStates) { + while (addedCityStates < numberOfCityStates) { if (availableCityStatesNames.isEmpty()) // We ran out of city-states somehow break diff --git a/core/src/com/unciv/models/metadata/GameParameters.kt b/core/src/com/unciv/models/metadata/GameParameters.kt index f945335e59..9b5dac7d4f 100644 --- a/core/src/com/unciv/models/metadata/GameParameters.kt +++ b/core/src/com/unciv/models/metadata/GameParameters.kt @@ -15,10 +15,16 @@ class GameParameters : IsPartOfGameInfoSerialization { // Default values are the @Deprecated("Since 4.1.11") var gameSpeed = "" + var randomNumberOfPlayers = false + var minNumberOfPlayers = 3 + var maxNumberOfPlayers = 3 var players = ArrayList().apply { add(Player().apply { playerType = PlayerType.Human }) for (i in 1..3) add(Player()) } + var randomNumberOfCityStates = false + var minNumberOfCityStates = 6 + var maxNumberOfCityStates = 6 var numberOfCityStates = 6 var noCityRazing = false @@ -47,6 +53,12 @@ class GameParameters : IsPartOfGameInfoSerialization { // Default values are the parameters.difficulty = difficulty parameters.speed = speed parameters.players = ArrayList(players) + parameters.randomNumberOfPlayers = randomNumberOfPlayers + parameters.minNumberOfPlayers = minNumberOfPlayers + parameters.maxNumberOfPlayers = maxNumberOfPlayers + parameters.randomNumberOfCityStates = randomNumberOfCityStates + parameters.minNumberOfCityStates = minNumberOfCityStates + parameters.maxNumberOfCityStates = maxNumberOfCityStates parameters.numberOfCityStates = numberOfCityStates parameters.noBarbarians = noBarbarians parameters.ragingBarbarians = ragingBarbarians @@ -67,7 +79,11 @@ class GameParameters : IsPartOfGameInfoSerialization { // Default values are the yield("$difficulty $speed $startingEra") yield("${players.count { it.playerType == PlayerType.Human }} ${PlayerType.Human}") yield("${players.count { it.playerType == PlayerType.AI }} ${PlayerType.AI}") + yield("$minNumberOfCityStates Min CS") + yield("$maxNumberOfCityStates Max CS") yield("$numberOfCityStates CS") + if (randomNumberOfPlayers) yield("Random number of Players") + if (randomNumberOfCityStates) yield("Random number of City-States") if (isOnlineMultiplayer) yield("Online Multiplayer") if (noBarbarians) yield("No barbs") if (ragingBarbarians) yield("Raging barbs") diff --git a/core/src/com/unciv/ui/newgamescreen/GameOptionsTable.kt b/core/src/com/unciv/ui/newgamescreen/GameOptionsTable.kt index f282275cdf..213504f393 100644 --- a/core/src/com/unciv/ui/newgamescreen/GameOptionsTable.kt +++ b/core/src/com/unciv/ui/newgamescreen/GameOptionsTable.kt @@ -60,7 +60,16 @@ class GameOptionsTable( val turnSlider = addMaxTurnsSlider() if (turnSlider != null) add(turnSlider).padTop(10f).row() - addCityStatesSlider() + if (gameParameters.randomNumberOfPlayers) { + addMinPlayersSlider() + addMaxPlayersSlider() + } + if (gameParameters.randomNumberOfCityStates) { + addMinCityStatesSlider() + addMaxCityStatesSlider() + } else { + addCityStatesSlider() + } }).colspan(2).fillX().row() }).row() addVictoryTypeCheckboxes() @@ -82,6 +91,8 @@ class GameOptionsTable( if (UncivGame.Current.settings.enableEspionageOption) it.addEnableEspionageCheckbox() it.addNoStartBiasCheckbox() + it.addRandomPlayersCheckbox() + it.addRandomCityStatesCheckbox() } add(expander).pad(10f).padTop(10f).growX().row() @@ -141,6 +152,10 @@ class GameOptionsTable( { gameParameters.espionageEnabled = it } + private fun numberOfPlayable() = ruleset.nations.values.count { + it.isMajorCiv() + } + private fun numberOfCityStates() = ruleset.nations.values.count { it.isCityState() && !it.hasUnique(UniqueType.CityStateDeprecated) @@ -150,12 +165,78 @@ class GameOptionsTable( addCheckbox("Disable starting bias", gameParameters.noStartBias) { gameParameters.noStartBias = it } + private fun Table.addRandomPlayersCheckbox() = + addCheckbox("Random number of Civilizations", gameParameters.randomNumberOfPlayers) + { + gameParameters.randomNumberOfPlayers = it + update() + } + + private fun Table.addRandomCityStatesCheckbox() = + addCheckbox("Random number of City-States", gameParameters.randomNumberOfCityStates) + { + gameParameters.randomNumberOfCityStates = it + update() + } + + private fun Table.addMinPlayersSlider() { + val playableAvailable = numberOfPlayable() + if (playableAvailable == 0) return + + add("{Min number of Civilizations}:".toLabel()).left().expandX() + val slider = UncivSlider(2f, playableAvailable.toFloat(), 1f, initial = gameParameters.minNumberOfPlayers.toFloat()) { + gameParameters.minNumberOfPlayers = it.toInt() + } + slider.permanentTip = true + slider.isDisabled = locked + add(slider).padTop(10f).row() + } + + private fun Table.addMaxPlayersSlider() { + val playableAvailable = numberOfPlayable() + if (playableAvailable == 0) return + + add("{Max number of Civilizations}:".toLabel()).left().expandX() + val slider = UncivSlider(2f, playableAvailable.toFloat(), 1f, initial = gameParameters.maxNumberOfPlayers.toFloat()) { + gameParameters.maxNumberOfPlayers = it.toInt() + } + slider.permanentTip = true + slider.isDisabled = locked + add(slider).padTop(10f).row() + } + + private fun Table.addMinCityStatesSlider() { + val cityStatesAvailable = numberOfCityStates() + if (cityStatesAvailable == 0) return + + add("{Min number of City-States}:".toLabel()).left().expandX() + val slider = UncivSlider(0f, cityStatesAvailable.toFloat(), 1f, initial = gameParameters.minNumberOfCityStates.toFloat()) { + gameParameters.minNumberOfCityStates = it.toInt() + } + slider.permanentTip = true + slider.isDisabled = locked + add(slider).padTop(10f).row() + } + + private fun Table.addMaxCityStatesSlider() { + val cityStatesAvailable = numberOfCityStates() + if (cityStatesAvailable == 0) return + + add("{Max number of City-States}:".toLabel()).left().expandX() + val slider = UncivSlider(0f, cityStatesAvailable.toFloat(), 1f, initial = gameParameters.maxNumberOfCityStates.toFloat()) { + gameParameters.maxNumberOfCityStates = it.toInt() + } + slider.permanentTip = true + slider.isDisabled = locked + add(slider).padTop(10f).row() + } + private fun Table.addCityStatesSlider() { - val maxCityStates = numberOfCityStates() - if (maxCityStates == 0) return + val cityStatesAvailable = numberOfCityStates() + if (cityStatesAvailable == 0) return add("{Number of City-States}:".toLabel()).left().expandX() - val slider = UncivSlider(0f, maxCityStates.toFloat(), 1f, initial = gameParameters.numberOfCityStates.toFloat()) { + val slider = UncivSlider(0f, cityStatesAvailable.toFloat(), 1f, initial = gameParameters.numberOfCityStates.toFloat()) { gameParameters.numberOfCityStates = it.toInt() } slider.permanentTip = true