diff --git a/android/assets/jsons/translations/template.properties b/android/assets/jsons/translations/template.properties index 546af8fe29..64067e2b85 100644 --- a/android/assets/jsons/translations/template.properties +++ b/android/assets/jsons/translations/template.properties @@ -336,8 +336,11 @@ Promotions = Load copied data = Reset to defaults = Select nations = +Select city states = Random nations pool = +Random city states pool = Blacklist random nations pool = +Blacklist random city states 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 7bd06302ca..0537a24dd3 100644 --- a/core/src/com/unciv/logic/GameStarter.kt +++ b/core/src/com/unciv/logic/GameStarter.kt @@ -291,16 +291,25 @@ object GameStarter { gameInfo.civilizations.add(playerCiv) } - val availableCityStatesNames = Stack() + var availableCityStatesNames = Stack() // since we shuffle and then order by, we end up with all the City-States with starting tiles first in a random order, // and then all the other City-States in a random order! Because the sortedBy function is stable! - availableCityStatesNames.addAll( ruleset.nations - .filter { - it.value.isCityState() && - !it.value.hasUnique(UniqueType.CityStateDeprecated) - }.keys - .shuffled() - .sortedBy { it in civNamesWithStartingLocations } ) // pop() gets the last item, so sort ascending + + val selectedCityStatesPoolNames = Stack() + for (selectedCityState in gameSetupInfo.gameParameters.randomCityStates) + selectedCityStatesPoolNames.add(selectedCityState.name) + if (gameSetupInfo.gameParameters.blacklistRandomCityStatesPool) { + availableCityStatesNames.addAll( ruleset.nations + .filter { + it.value.isCityState() && + !it.value.hasUnique(UniqueType.CityStateDeprecated) + }.keys + .shuffled() + .sortedBy { it in civNamesWithStartingLocations } ) // pop() gets the last item, so sort ascending + availableCityStatesNames.removeAll(selectedCityStatesPoolNames) + } else { + availableCityStatesNames = selectedCityStatesPoolNames + } val numberOfCityStates = if (newGameParameters.randomNumberOfCityStates) { // This swaps min and max if the user accidentally swapped min and max diff --git a/core/src/com/unciv/models/metadata/GameParameters.kt b/core/src/com/unciv/models/metadata/GameParameters.kt index 392eebd884..09b25b374d 100644 --- a/core/src/com/unciv/models/metadata/GameParameters.kt +++ b/core/src/com/unciv/models/metadata/GameParameters.kt @@ -29,8 +29,11 @@ class GameParameters : IsPartOfGameInfoSerialization { // Default values are the var numberOfCityStates = 6 var enableRandomNationsPool = false + var enableRandomCityStatesPool = false var blacklistRandomNationsPool = false + var blacklistRandomCityStatesPool = false var randomNations = arrayListOf() + var randomCityStates = arrayListOf() var noCityRazing = false var noBarbarians = false diff --git a/core/src/com/unciv/ui/newgamescreen/GameOptionsTable.kt b/core/src/com/unciv/ui/newgamescreen/GameOptionsTable.kt index c5362c441f..a902f9f7a5 100644 --- a/core/src/com/unciv/ui/newgamescreen/GameOptionsTable.kt +++ b/core/src/com/unciv/ui/newgamescreen/GameOptionsTable.kt @@ -1,34 +1,21 @@ 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 @@ -107,12 +94,17 @@ class GameOptionsTable( it.addEnableEspionageCheckbox() it.addNoStartBiasCheckbox() it.addRandomPlayersCheckbox() - it.addRandomCityStatesCheckbox() + it.addRandomNrCityStatesCheckbox() it.addRandomNationsPoolCheckbox() if (gameParameters.enableRandomNationsPool) { - it.addBlacklistRandomPool() + it.addBlacklistRandomNationsPool() it.addNationsSelectTextButton() } + it.addRandomCityStatesPoolCheckbox() + if (gameParameters.enableRandomCityStatesPool){ + it.addBlacklistRandomCityStatesPool() + it.addCityStatesSelectTextButton() + } } add(expander).pad(10f).padTop(10f).growX().row() @@ -177,19 +169,40 @@ class GameOptionsTable( update() } - private fun Table.addBlacklistRandomPool() = + private fun Table.addRandomCityStatesPoolCheckbox() = + addCheckbox("Random city states pool", gameParameters.enableRandomCityStatesPool) { + gameParameters.enableRandomCityStatesPool = it + update() + } + + private fun Table.addBlacklistRandomNationsPool() = addCheckbox("Blacklist random nations pool", gameParameters.blacklistRandomNationsPool) { gameParameters.blacklistRandomNationsPool = it } + private fun Table.addBlacklistRandomCityStatesPool() = + addCheckbox("Blacklist random city states pool", gameParameters.blacklistRandomCityStatesPool) { + gameParameters.blacklistRandomCityStatesPool = it + } + private fun Table.addNationsSelectTextButton() { val button = "Select nations".toTextButton() button.onClick { - val popup = RandomNationPickerPopup(previousScreen, gameParameters) + val popup = MultiNationSelectPopup(previousScreen, gameParameters, true) popup.open() popup.update() } - add(button) + add(button).row() + } + + private fun Table.addCityStatesSelectTextButton() { + val button = "Select city states".toTextButton() + button.onClick { + val popup = MultiNationSelectPopup(previousScreen, gameParameters, false) + popup.open() + popup.update() + } + add(button).row() } private fun numberOfPlayable() = ruleset.nations.values.count { @@ -212,7 +225,7 @@ class GameOptionsTable( update() } - private fun Table.addRandomCityStatesCheckbox() = + private fun Table.addRandomNrCityStatesCheckbox() = addCheckbox("Random number of City-States", gameParameters.randomNumberOfCityStates) { gameParameters.randomNumberOfCityStates = it @@ -439,136 +452,3 @@ class GameOptionsTable( updatePlayerPickerTable(desiredCiv) } } - -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/MultiNationSelectPopup.kt b/core/src/com/unciv/ui/newgamescreen/MultiNationSelectPopup.kt new file mode 100644 index 0000000000..484f6529f4 --- /dev/null +++ b/core/src/com/unciv/ui/newgamescreen/MultiNationSelectPopup.kt @@ -0,0 +1,170 @@ +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.nation.Nation +import com.unciv.models.translations.tr +import com.unciv.ui.images.ImageGetter +import com.unciv.ui.popup.Popup +import com.unciv.ui.utils.AutoScrollPane +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.KeyCharAndCode +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.onClick +import com.unciv.ui.utils.extensions.surroundWithCircle + + +class MultiNationSelectPopup( + previousScreen: IPreviousScreen, + val gameParameters: GameParameters, + val majorCivs: Boolean +) : 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 } + } + + private val blockWidth: Float = 0f + private 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 += if(majorCivs) { + previousScreen.ruleset.nations.values.asSequence() + .filter { it.isMajorCiv() } + } else { + previousScreen.ruleset.nations.values.asSequence() + .filter { it.isCityState() } + } + + 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 = if (majorCivs) { + gameParameters.randomNations + } else { + gameParameters.randomCityStates + } + 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() + if (majorCivs) + gameParameters.randomNations = selectedNations + else + gameParameters.randomCityStates = selectedNations + } +} + +