diff --git a/android/assets/jsons/translations/template.properties b/android/assets/jsons/translations/template.properties index bd130572ff..546af8fe29 100644 --- a/android/assets/jsons/translations/template.properties +++ b/android/assets/jsons/translations/template.properties @@ -335,6 +335,9 @@ Uniques = Promotions = Load copied data = Reset to defaults = +Select nations = +Random nations pool = +Blacklist random nations pool = Are you sure you want to reset all game options to defaults? = Start game! = Map Options = diff --git a/core/src/com/unciv/logic/GameStarter.kt b/core/src/com/unciv/logic/GameStarter.kt index e773d9ce26..7bd06302ca 100644 --- a/core/src/com/unciv/logic/GameStarter.kt +++ b/core/src/com/unciv/logic/GameStarter.kt @@ -222,7 +222,18 @@ object GameStarter { private fun addCivilizations(newGameParameters: GameParameters, gameInfo: GameInfo, ruleset: Ruleset, existingMap: Boolean) { val availableCivNames = Stack() // CityState or Spectator civs are not available for Random pick - availableCivNames.addAll(ruleset.nations.filter { it.value.isMajorCiv() }.keys.shuffled()) + if (gameSetupInfo.gameParameters.enableRandomNationsPool) { + if (gameSetupInfo.gameParameters.blacklistRandomNationsPool) { + availableCivNames.addAll(ruleset.nations.filter { it.value.isMajorCiv() }.keys.shuffled()) + for (nation in gameSetupInfo.gameParameters.randomNations) + availableCivNames.remove(nation.name) + } else { + for (nation in gameSetupInfo.gameParameters.randomNations) + availableCivNames.add(nation.name) + } + } else + availableCivNames.addAll(ruleset.nations.filter { it.value.isMajorCiv() }.keys.shuffled()) + availableCivNames.removeAll(newGameParameters.players.map { it.chosenCiv }.toSet()) val startingTechs = ruleset.technologies.values.filter { it.hasUnique(UniqueType.StartingTech) } diff --git a/core/src/com/unciv/models/metadata/GameParameters.kt b/core/src/com/unciv/models/metadata/GameParameters.kt index 45fab9dc0d..392eebd884 100644 --- a/core/src/com/unciv/models/metadata/GameParameters.kt +++ b/core/src/com/unciv/models/metadata/GameParameters.kt @@ -3,6 +3,7 @@ package com.unciv.models.metadata import com.unciv.logic.IsPartOfGameInfoSerialization import com.unciv.logic.civilization.PlayerType import com.unciv.models.ruleset.Speed +import com.unciv.models.ruleset.nation.Nation enum class BaseRuleset(val fullName:String){ Civ_V_Vanilla("Civ V - Vanilla"), @@ -27,6 +28,10 @@ class GameParameters : IsPartOfGameInfoSerialization { // Default values are the var maxNumberOfCityStates = 6 var numberOfCityStates = 6 + var enableRandomNationsPool = false + var blacklistRandomNationsPool = false + var randomNations = arrayListOf() + var noCityRazing = false var noBarbarians = false var ragingBarbarians = false diff --git a/core/src/com/unciv/ui/mapeditor/tabs/MapEditorGenerateTab.kt b/core/src/com/unciv/ui/mapeditor/tabs/MapEditorGenerateTab.kt index 6bd59b0ebc..0ba1b2d213 100644 --- a/core/src/com/unciv/ui/mapeditor/tabs/MapEditorGenerateTab.kt +++ b/core/src/com/unciv/ui/mapeditor/tabs/MapEditorGenerateTab.kt @@ -168,7 +168,7 @@ class MapEditorGenerateTab( private val parent: MapEditorGenerateTab ): Table(BaseScreen.skin) { val generateButton = "".toTextButton() - val mapParametersTable = MapParametersTable(parent.editorScreen.newMapParameters, MapGeneratedMainType.generated, forMapEditor = true) { + val mapParametersTable = MapParametersTable(null, parent.editorScreen.newMapParameters, MapGeneratedMainType.generated, forMapEditor = true) { parent.replacePage(0, this) // A kludge to get the ScrollPanes to recognize changes in vertical layout?? } diff --git a/core/src/com/unciv/ui/newgamescreen/GameOptionsTable.kt b/core/src/com/unciv/ui/newgamescreen/GameOptionsTable.kt index 213504f393..c5362c441f 100644 --- a/core/src/com/unciv/ui/newgamescreen/GameOptionsTable.kt +++ b/core/src/com/unciv/ui/newgamescreen/GameOptionsTable.kt @@ -1,22 +1,37 @@ package com.unciv.ui.newgamescreen +import com.badlogic.gdx.graphics.Color +import com.badlogic.gdx.scenes.scene2d.Group +import com.badlogic.gdx.scenes.scene2d.Touchable +import com.badlogic.gdx.scenes.scene2d.ui.ImageButton import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.utils.Align import com.unciv.UncivGame +import com.unciv.models.metadata.GameParameters import com.unciv.models.ruleset.RulesetCache +import com.unciv.models.ruleset.nation.Nation import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.translations.tr import com.unciv.ui.audio.MusicMood import com.unciv.ui.audio.MusicTrackChooserFlags import com.unciv.ui.images.ImageGetter import com.unciv.ui.multiplayer.MultiplayerHelpers +import com.unciv.ui.popup.Popup import com.unciv.ui.popup.ToastPopup +import com.unciv.ui.utils.AutoScrollPane import com.unciv.ui.utils.BaseScreen import com.unciv.ui.utils.ExpanderTab +import com.unciv.ui.utils.KeyCharAndCode import com.unciv.ui.utils.UncivSlider +import com.unciv.ui.utils.extensions.isNarrowerThan4to3 +import com.unciv.ui.utils.extensions.keyShortcuts +import com.unciv.ui.utils.extensions.onActivation import com.unciv.ui.utils.extensions.onChange +import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.surroundWithCircle import com.unciv.ui.utils.extensions.toCheckBox import com.unciv.ui.utils.extensions.toLabel +import com.unciv.ui.utils.extensions.toTextButton class GameOptionsTable( val previousScreen: IPreviousScreen, @@ -93,6 +108,11 @@ class GameOptionsTable( it.addNoStartBiasCheckbox() it.addRandomPlayersCheckbox() it.addRandomCityStatesCheckbox() + it.addRandomNationsPoolCheckbox() + if (gameParameters.enableRandomNationsPool) { + it.addBlacklistRandomPool() + it.addNationsSelectTextButton() + } } add(expander).pad(10f).padTop(10f).growX().row() @@ -151,6 +171,26 @@ class GameOptionsTable( addCheckbox("Enable Espionage", gameParameters.espionageEnabled) { gameParameters.espionageEnabled = it } + private fun Table.addRandomNationsPoolCheckbox() = + addCheckbox("Random nations pool", gameParameters.enableRandomNationsPool) { + gameParameters.enableRandomNationsPool = it + update() + } + + private fun Table.addBlacklistRandomPool() = + addCheckbox("Blacklist random nations pool", gameParameters.blacklistRandomNationsPool) { + gameParameters.blacklistRandomNationsPool = it + } + + private fun Table.addNationsSelectTextButton() { + val button = "Select nations".toTextButton() + button.onClick { + val popup = RandomNationPickerPopup(previousScreen, gameParameters) + popup.open() + popup.update() + } + add(button) + } private fun numberOfPlayable() = ruleset.nations.values.count { it.isMajorCiv() @@ -400,3 +440,135 @@ class GameOptionsTable( } } +private class RandomNationPickerPopup( + previousScreen: IPreviousScreen, + val gameParameters: GameParameters +) : Popup(previousScreen as BaseScreen) { + companion object { + // These are used for the Close/OK buttons in the lower left/right corners: + const val buttonsCircleSize = 70f + const val buttonsIconSize = 50f + const val buttonsOffsetFromEdge = 5f + val buttonsBackColor: Color = Color.BLACK.cpy().apply { a = 0.67f } + } + + val blockWidth: Float = 0f + val civBlocksWidth = if(blockWidth <= 10f) previousScreen.stage.width / 3 - 5f else blockWidth + + // This Popup's body has two halves of same size, either side by side or arranged vertically + // depending on screen proportions - determine height for one of those + private val partHeight = stageToShowOn.height * (if (stageToShowOn.isNarrowerThan4to3()) 0.45f else 0.8f) + private val nationListTable = Table() + private val nationListScroll = AutoScrollPane(nationListTable) + private val selectedNationsListTable = Table() + private val selectedNationsListScroll = AutoScrollPane(selectedNationsListTable) + private var selectedNations = gameParameters.randomNations + var nations = arrayListOf() + + + init { + var nationListScrollY = 0f + nations += previousScreen.ruleset.nations.values.asSequence() + .filter { it.isMajorCiv() } + nationListScroll.setOverscroll(false, false) + add(nationListScroll).size( civBlocksWidth + 10f, partHeight ) + // +10, because the nation table has a 5f pad, for a total of +10f + if (stageToShowOn.isNarrowerThan4to3()) row() + selectedNationsListScroll.setOverscroll(false, false) + add(selectedNationsListScroll).size(civBlocksWidth + 10f, partHeight) // Same here, see above + + update() + + nationListScroll.layout() + pack() + if (nationListScrollY > 0f) { + // center the selected nation vertically, getRowHeight safe because nationListScrollY > 0f ensures at least 1 row + nationListScrollY -= (nationListScroll.height - nationListTable.getRowHeight(0)) / 2 + nationListScroll.scrollY = nationListScrollY.coerceIn(0f, nationListScroll.maxY) + } + + val closeButton = "OtherIcons/Close".toImageButton(Color.FIREBRICK) + closeButton.onActivation { close() } + closeButton.keyShortcuts.add(KeyCharAndCode.BACK) + closeButton.setPosition(buttonsOffsetFromEdge, buttonsOffsetFromEdge, Align.bottomLeft) + innerTable.addActor(closeButton) + + val okButton = "OtherIcons/Checkmark".toImageButton(Color.LIME) + okButton.onClick { returnSelected() } + okButton.setPosition(innerTable.width - buttonsOffsetFromEdge, buttonsOffsetFromEdge, Align.bottomRight) + innerTable.addActor(okButton) + + selectedNationsListTable.touchable = Touchable.enabled + } + + fun update() { + nationListTable.clear() + selectedNations = gameParameters.randomNations + nations -= selectedNations.toSet() + nations = nations.sortedWith(compareBy(UncivGame.Current.settings.getCollatorFromLocale()) { it.name.tr() }).toMutableList() as ArrayList + + var currentY = 0f + for (nation in nations) { + val nationTable = NationTable(nation, civBlocksWidth, 0f) // no need for min height + val cell = nationListTable.add(nationTable) + currentY += cell.padBottom + cell.prefHeight + cell.padTop + cell.row() + nationTable.onClick { + addNationToPool(nation) + } + } + + if (selectedNations.isNotEmpty()) { + selectedNationsListTable.clear() + + for (currentNation in selectedNations) { + val nationTable = NationTable(currentNation, civBlocksWidth, 0f) + nationTable.onClick { removeNationFromPool(currentNation) } + selectedNationsListTable.add(nationTable).row() + } + } + } + + private fun String.toImageButton(overColor: Color): Group { + val style = ImageButton.ImageButtonStyle() + val image = ImageGetter.getDrawable(this) + style.imageUp = image + style.imageOver = image.tint(overColor) + val button = ImageButton(style) + button.setSize(buttonsIconSize, buttonsIconSize) + + return button.surroundWithCircle(buttonsCircleSize, false, buttonsBackColor) + } + + private fun updateNationListTable() { + selectedNationsListTable.clear() + + for (currentNation in selectedNations) { + val nationTable = NationTable(currentNation, civBlocksWidth, 0f) + nationTable.onClick { removeNationFromPool(currentNation) } + selectedNationsListTable.add(nationTable).row() + } + } + + private fun addNationToPool(nation: Nation) { + selectedNations.add(nation) + + update() + updateNationListTable() + } + + private fun removeNationFromPool(nation: Nation) { + nations.add(nation) + selectedNations.remove(nation) + + update() + updateNationListTable() + } + + private fun returnSelected() { + close() + gameParameters.randomNations = selectedNations + } +} + + diff --git a/core/src/com/unciv/ui/newgamescreen/MapOptionsTable.kt b/core/src/com/unciv/ui/newgamescreen/MapOptionsTable.kt index 6a90dc8563..53a4d1836c 100644 --- a/core/src/com/unciv/ui/newgamescreen/MapOptionsTable.kt +++ b/core/src/com/unciv/ui/newgamescreen/MapOptionsTable.kt @@ -18,8 +18,8 @@ class MapOptionsTable(private val newGameScreen: NewGameScreen): Table() { private val mapParameters = newGameScreen.gameSetupInfo.mapParameters private var mapTypeSpecificTable = Table() - val generatedMapOptionsTable = MapParametersTable(mapParameters, MapGeneratedMainType.generated) - private val randomMapOptionsTable = MapParametersTable(mapParameters, MapGeneratedMainType.randomGenerated) + val generatedMapOptionsTable = MapParametersTable(newGameScreen, mapParameters, MapGeneratedMainType.generated) + private val randomMapOptionsTable = MapParametersTable(newGameScreen, mapParameters, MapGeneratedMainType.randomGenerated) private val savedMapOptionsTable = Table() lateinit var mapTypeSelectBox: TranslatedSelectBox private val mapFileSelectBox = createMapFileSelectBox() diff --git a/core/src/com/unciv/ui/newgamescreen/MapParametersTable.kt b/core/src/com/unciv/ui/newgamescreen/MapParametersTable.kt index fdce3192c7..314382aab5 100644 --- a/core/src/com/unciv/ui/newgamescreen/MapParametersTable.kt +++ b/core/src/com/unciv/ui/newgamescreen/MapParametersTable.kt @@ -31,6 +31,7 @@ import com.unciv.ui.utils.extensions.toTextButton * @param forMapEditor whether the [MapType.empty] option should be present. Is used by the Map Editor, but should **never** be used with the New Game * */ class MapParametersTable( + private val previousScreen: IPreviousScreen? = null, private val mapParameters: MapParameters, private val mapGeneratedMainType: String, private val forMapEditor: Boolean = false, @@ -67,6 +68,12 @@ class MapParametersTable( private val advancedSliders = HashMapFloat>() init { + update() + } + + fun update() { + clear() + skin = BaseScreen.skin defaults().pad(5f, 10f) if (mapGeneratedMainType == MapGeneratedMainType.randomGenerated) { @@ -354,6 +361,13 @@ class MapParametersTable( return slider } + fun addTextButton(text: String, shouldAddToTable: Boolean = false, action: ((Boolean) -> Unit),) { + val button = text.toTextButton() + button.onClick { action.invoke(true) } + if (shouldAddToTable) + table.add(button).colspan(2).padTop(10f).row() + } + addSlider("Map Elevation", {mapParameters.elevationExponent}, 0.6f, 0.8f) { mapParameters.elevationExponent = it } @@ -381,13 +395,11 @@ class MapParametersTable( addSlider("Water level", {mapParameters.waterThreshold}, -0.1f, 0.1f) { mapParameters.waterThreshold = it } - val resetToDefaultButton = "Reset to defaults".toTextButton() - resetToDefaultButton.onClick { + addTextButton("Reset to defaults", true) { mapParameters.resetAdvancedSettings() seedTextField.text = mapParameters.seed.toString() for (entry in advancedSliders) entry.key.value = entry.value() } - table.add(resetToDefaultButton).colspan(2).padTop(10f).row() } }