Random Nations game starter - reactive UI (#9127)

* Random Nations game starter - reactive UI

* Random Nations game starter - reactive UI continued

* Random Nations game starter - reactive UI - patch
This commit is contained in:
SomeTroglodyte 2023-04-07 08:53:04 +02:00 committed by GitHub
parent 21510a8455
commit 36667d9d18
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 396 additions and 295 deletions

View File

@ -344,12 +344,16 @@ object GameStarter {
for (player in chosenPlayers) { for (player in chosenPlayers) {
val civ = Civilization(player.chosenCiv) val civ = Civilization(player.chosenCiv)
if (player.chosenCiv in usedMajorCivs) { when (player.chosenCiv) {
Constants.spectator ->
civ.playerType = player.playerType
in usedMajorCivs -> {
for (tech in startingTechs) for (tech in startingTechs)
civ.tech.techsResearched.add(tech.name) // can't be .addTechnology because the civInfo isn't assigned yet civ.tech.techsResearched.add(tech.name) // can't be .addTechnology because the civInfo isn't assigned yet
civ.playerType = player.playerType civ.playerType = player.playerType
civ.playerId = player.playerId civ.playerId = player.playerId
} else { }
else ->
if (!civ.cityStateFunctions.initCityState(ruleset, newGameParameters.startingEra, unusedMajorCivs)) if (!civ.cityStateFunctions.initCityState(ruleset, newGameParameters.startingEra, unusedMajorCivs))
continue continue
} }

View File

@ -4,5 +4,6 @@ import com.unciv.logic.IsPartOfGameInfoSerialization
enum class PlayerType : IsPartOfGameInfoSerialization { enum class PlayerType : IsPartOfGameInfoSerialization {
AI, AI,
Human Human;
fun toggle() = if (this == AI) Human else AI
} }

View File

@ -1,4 +1,4 @@
package com.unciv.models.ruleset.nation package com.unciv.models.ruleset.nation
import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.graphics.Color
import com.unciv.Constants import com.unciv.Constants
@ -15,9 +15,9 @@ import com.unciv.ui.screens.civilopediascreen.CivilopediaScreen.Companion.showRe
import com.unciv.ui.screens.civilopediascreen.FormattedLine import com.unciv.ui.screens.civilopediascreen.FormattedLine
import kotlin.math.pow import kotlin.math.pow
class Nation : RulesetObject() { class Nation : RulesetObject() {
var leaderName = "" var leaderName = ""
fun getLeaderDisplayName() = if (isCityState) name fun getLeaderDisplayName() = if (isCityState || isSpectator) name
else "[$leaderName] of [$name]" else "[$leaderName] of [$name]"
val style = "" val style = ""
@ -36,7 +36,6 @@ import kotlin.math.pow
lateinit var outerColor: List<Int> lateinit var outerColor: List<Int>
var uniqueName = "" var uniqueName = ""
override fun getUniqueTarget() = UniqueTarget.Nation
var uniqueText = "" var uniqueText = ""
var innerColor: List<Int>? = null var innerColor: List<Int>? = null
var startBias = ArrayList<String>() var startBias = ArrayList<String>()
@ -52,6 +51,10 @@ import kotlin.math.pow
var favoredReligion: String? = null var favoredReligion: String? = null
var cities: ArrayList<String> = arrayListOf()
override fun getUniqueTarget() = UniqueTarget.Nation
@Transient @Transient
private lateinit var outerColorObject: Color private lateinit var outerColorObject: Color
fun getOuterColor(): Color = outerColorObject fun getOuterColor(): Color = outerColorObject
@ -84,8 +87,6 @@ import kotlin.math.pow
ignoreHillMovementCost = uniques.contains("Units ignore terrain costs when moving into any tile with Hills") ignoreHillMovementCost = uniques.contains("Units ignore terrain costs when moving into any tile with Hills")
} }
var cities: ArrayList<String> = arrayListOf()
override fun makeLink() = "Nation/$name" override fun makeLink() = "Nation/$name"
override fun getSortGroup(ruleset: Ruleset) = when { override fun getSortGroup(ruleset: Ruleset) = when {
@ -305,12 +306,12 @@ import kotlin.math.pow
else -> uniques.contains(filter) else -> uniques.contains(filter)
} }
} }
} }
/** All defined by https://www.w3.org/TR/WCAG20/#relativeluminancedef */ /** All defined by https://www.w3.org/TR/WCAG20/#relativeluminancedef */
fun getRelativeLuminance(color:Color):Double{ fun getRelativeLuminance(color: Color): Double {
fun getRelativeChannelLuminance(channel:Float):Double = fun getRelativeChannelLuminance(channel: Float): Double =
if (channel < 0.03928) channel / 12.92 if (channel < 0.03928) channel / 12.92
else ((channel + 0.055) / 1.055).pow(2.4) else ((channel + 0.055) / 1.055).pow(2.4)
@ -319,13 +320,14 @@ import kotlin.math.pow
val B = getRelativeChannelLuminance(color.b) val B = getRelativeChannelLuminance(color.b)
return 0.2126 * R + 0.7152 * G + 0.0722 * B return 0.2126 * R + 0.7152 * G + 0.0722 * B
} }
/** https://www.w3.org/TR/WCAG20/#contrast-ratiodef */ /** https://www.w3.org/TR/WCAG20/#contrast-ratiodef */
fun getContrastRatio(color1:Color, color2:Color): Double { // ratio can range from 1 to 21 fun getContrastRatio(color1: Color, color2: Color): Double { // ratio can range from 1 to 21
val innerColorLuminance = getRelativeLuminance(color1) val innerColorLuminance = getRelativeLuminance(color1)
val outerColorLuminance = getRelativeLuminance(color2) val outerColorLuminance = getRelativeLuminance(color2)
return if (innerColorLuminance > outerColorLuminance) (innerColorLuminance + 0.05) / (outerColorLuminance + 0.05) return if (innerColorLuminance > outerColorLuminance)
(innerColorLuminance + 0.05) / (outerColorLuminance + 0.05)
else (outerColorLuminance + 0.05) / (innerColorLuminance + 0.05) else (outerColorLuminance + 0.05) / (innerColorLuminance + 0.05)
} }

View File

@ -48,15 +48,15 @@ private class RestorableTextButtonStyle(
val restoreStyle: ButtonStyle val restoreStyle: ButtonStyle
) : TextButtonStyle(baseStyle) ) : TextButtonStyle(baseStyle)
/** Disable a [Button] by setting its [touchable][Button.touchable] and [color][Button.color] properties. */ /** Disable a [Button] by setting its [touchable][Button.touchable] and [style][Button.style] properties. */
fun Button.disable() { fun Button.disable() {
touchable = Touchable.disabled touchable = Touchable.disabled
val oldStyle = style val oldStyle = style
if (oldStyle is RestorableTextButtonStyle) return
val disabledStyle = BaseScreen.skin.get("disabled", TextButtonStyle::class.java) val disabledStyle = BaseScreen.skin.get("disabled", TextButtonStyle::class.java)
if (oldStyle !is RestorableTextButtonStyle)
style = RestorableTextButtonStyle(disabledStyle, oldStyle) style = RestorableTextButtonStyle(disabledStyle, oldStyle)
} }
/** Enable a [Button] by setting its [touchable][Button.touchable] and [color][Button.color] properties. */ /** Enable a [Button] by setting its [touchable][Button.touchable] and [style][Button.style] properties. */
fun Button.enable() { fun Button.enable() {
val oldStyle = style val oldStyle = style
if (oldStyle is RestorableTextButtonStyle) { if (oldStyle is RestorableTextButtonStyle) {
@ -64,7 +64,7 @@ fun Button.enable() {
} }
touchable = Touchable.enabled touchable = Touchable.enabled
} }
/** Enable or disable a [Button] by setting its [touchable][Button.touchable] and [color][Button.color] properties, /** Enable or disable a [Button] by setting its [touchable][Button.touchable] and [style][Button.style] properties,
* or returns the corresponding state. * or returns the corresponding state.
* *
* Do not confuse with Gdx' builtin [isDisabled][Button.isDisabled] property, * Do not confuse with Gdx' builtin [isDisabled][Button.isDisabled] property,

View File

@ -299,7 +299,7 @@ class MapEditorEditStartsTab(
} }
private fun allowedNations() = ruleset.nations.values.asSequence() private fun allowedNations() = ruleset.nations.values.asSequence()
.filter { it.name !in disallowNations } .filter { it.name !in disallowNations && !it.hasUnique(UniqueType.CityStateDeprecated) }
private fun getNations() = allowedNations() private fun getNations() = allowedNations()
.sortedWith(compareBy<Nation>{ it.isCityState }.thenBy(collator) { it.name.tr() }) .sortedWith(compareBy<Nation>{ it.isCityState }.thenBy(collator) { it.name.tr() })
.map { FormattedLine("[${it.name}] starting location", it.name, "Nation/${it.name}", size = 24) } .map { FormattedLine("[${it.name}] starting location", it.name, "Nation/${it.name}", size = 24) }

View File

@ -5,8 +5,11 @@ import com.badlogic.gdx.scenes.scene2d.Touchable
import com.badlogic.gdx.scenes.scene2d.ui.CheckBox import com.badlogic.gdx.scenes.scene2d.ui.CheckBox
import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.utils.Align import com.badlogic.gdx.utils.Align
import com.unciv.Constants
import com.unciv.UncivGame import com.unciv.UncivGame
import com.unciv.logic.civilization.PlayerType
import com.unciv.models.metadata.GameParameters import com.unciv.models.metadata.GameParameters
import com.unciv.models.metadata.Player
import com.unciv.models.ruleset.RulesetCache import com.unciv.models.ruleset.RulesetCache
import com.unciv.models.ruleset.nation.Nation import com.unciv.models.ruleset.nation.Nation
import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.ruleset.unique.UniqueType
@ -31,11 +34,13 @@ import com.unciv.ui.popups.Popup
import com.unciv.ui.popups.ToastPopup import com.unciv.ui.popups.ToastPopup
import com.unciv.ui.screens.basescreen.BaseScreen import com.unciv.ui.screens.basescreen.BaseScreen
import com.unciv.ui.screens.multiplayerscreens.MultiplayerHelpers import com.unciv.ui.screens.multiplayerscreens.MultiplayerHelpers
import kotlin.reflect.KMutableProperty0
class GameOptionsTable( class GameOptionsTable(
private val previousScreen: IPreviousScreen, private val previousScreen: IPreviousScreen,
private val isPortrait: Boolean = false, private val isPortrait: Boolean = false,
private val updatePlayerPickerTable: (desiredCiv:String)->Unit private val updatePlayerPickerTable: (desiredCiv: String) -> Unit,
private val updatePlayerPickerRandomLabel: () -> Unit
) : Table(BaseScreen.skin) { ) : Table(BaseScreen.skin) {
var gameParameters = previousScreen.gameSetupInfo.gameParameters var gameParameters = previousScreen.gameSetupInfo.gameParameters
val ruleset = previousScreen.ruleset val ruleset = previousScreen.ruleset
@ -77,12 +82,10 @@ class GameOptionsTable(
if (turnSlider != null) if (turnSlider != null)
add(turnSlider).padTop(10f).row() add(turnSlider).padTop(10f).row()
if (gameParameters.randomNumberOfPlayers) { if (gameParameters.randomNumberOfPlayers) {
addMinPlayersSlider() addMinMaxPlayersSliders()
addMaxPlayersSlider()
} }
if (gameParameters.randomNumberOfCityStates) { if (gameParameters.randomNumberOfCityStates) {
addMinCityStatesSlider() addMinMaxCityStatesSliders()
addMaxCityStatesSlider()
} else { } else {
addCityStatesSlider() addCityStatesSlider()
} }
@ -204,7 +207,7 @@ class GameOptionsTable(
add(button) add(button)
} }
private fun numberOfPlayable() = ruleset.nations.values.count { private fun numberOfMajorCivs() = ruleset.nations.values.count {
it.isMajorCiv it.isMajorCiv
} }
@ -218,64 +221,101 @@ class GameOptionsTable(
private fun Table.addRandomPlayersCheckbox() = private fun Table.addRandomPlayersCheckbox() =
addCheckbox("Random number of Civilizations", gameParameters.randomNumberOfPlayers) addCheckbox("Random number of Civilizations", gameParameters.randomNumberOfPlayers)
{ {newRandomNumberOfPlayers ->
gameParameters.randomNumberOfPlayers = it gameParameters.randomNumberOfPlayers = newRandomNumberOfPlayers
if (newRandomNumberOfPlayers) {
// remove all random AI from player picker
val newPlayers = gameParameters.players.asSequence()
.filterNot { it.playerType == PlayerType.AI && it.chosenCiv == Constants.random }
.toCollection(ArrayList(gameParameters.players.size))
if (newPlayers.size != gameParameters.players.size) {
gameParameters.players = newPlayers
updatePlayerPickerTable("")
}
} else {
// Fill up player picker with random AI until previously active min reached
val additionalRandom = gameParameters.minNumberOfPlayers - gameParameters.players.size
if (additionalRandom > 0) {
repeat(additionalRandom) {
gameParameters.players.add(Player(Constants.random))
}
updatePlayerPickerTable("")
}
}
update() // To see the new sliders update() // To see the new sliders
} }
private fun Table.addRandomCityStatesCheckbox() = private fun Table.addRandomCityStatesCheckbox() =
addCheckbox("Random number of City-States", gameParameters.randomNumberOfCityStates) addCheckbox("Random number of City-States", gameParameters.randomNumberOfCityStates)
{ {
gameParameters.randomNumberOfCityStates = it gameParameters.run {
randomNumberOfCityStates = it
if (it) {
if (numberOfCityStates > maxNumberOfCityStates)
maxNumberOfCityStates = numberOfCityStates
if (numberOfCityStates < minNumberOfCityStates)
minNumberOfCityStates = numberOfCityStates
} else {
if (numberOfCityStates > maxNumberOfCityStates)
numberOfCityStates = maxNumberOfCityStates
if (numberOfCityStates < minNumberOfCityStates)
numberOfCityStates = minNumberOfCityStates
}
}
update() // To see the changed sliders update() // To see the changed sliders
} }
private fun Table.addMinPlayersSlider() { private fun Table.addLinkedMinMaxSliders(
val playableAvailable = numberOfPlayable() minValue: Int, maxValue: Int,
if (playableAvailable == 0) return minText: String, maxText: String,
minField: KMutableProperty0<Int>,
maxField: KMutableProperty0<Int>,
onChangeCallback: (() -> Unit)? = null
) {
if (maxValue < minValue) return
add("{Min number of Civilizations}:".toLabel()).left().expandX() @Suppress("JoinDeclarationAndAssignment") // it's a forward declaration!
val slider = UncivSlider(2f, playableAvailable.toFloat(), 1f, initial = gameParameters.minNumberOfPlayers.toFloat()) { lateinit var maxSlider: UncivSlider // lateinit safe because the closure won't use it until the user operates a slider
gameParameters.minNumberOfPlayers = it.toInt() val minSlider = UncivSlider(minValue.toFloat(), maxValue.toFloat(), 1f, initial = minField.get().toFloat()) {
val newMin = it.toInt()
minField.set(newMin)
if (newMin > maxSlider.value.toInt()) {
maxSlider.value = it
maxField.set(newMin)
} }
slider.isDisabled = locked onChangeCallback?.invoke()
add(slider).padTop(10f).row() }
minSlider.isDisabled = locked
maxSlider = UncivSlider(minValue.toFloat(), maxValue.toFloat(), 1f, initial = maxField.get().toFloat()) {
val newMax = it.toInt()
maxField.set(newMax)
if (newMax < minSlider.value.toInt()) {
minSlider.value = it
minField.set(newMax)
}
onChangeCallback?.invoke()
}
maxSlider.isDisabled = locked
add(minText.toLabel()).left().expandX()
add(minSlider).padTop(10f).row()
add(maxText.toLabel()).left().expandX()
add(maxSlider).padTop(10f).row()
} }
private fun Table.addMaxPlayersSlider() { private fun Table.addMinMaxPlayersSliders() {
val playableAvailable = numberOfPlayable() addLinkedMinMaxSliders(2, numberOfMajorCivs(),
if (playableAvailable == 0) return "{Min number of Civilizations}:", "{Max number of Civilizations}:",
gameParameters::minNumberOfPlayers, gameParameters::maxNumberOfPlayers,
add("{Max number of Civilizations}:".toLabel()).left().expandX() updatePlayerPickerRandomLabel
val slider = UncivSlider(2f, playableAvailable.toFloat(), 1f, initial = gameParameters.maxNumberOfPlayers.toFloat()) { )
gameParameters.maxNumberOfPlayers = it.toInt()
}
slider.isDisabled = locked
add(slider).padTop(10f).row()
} }
private fun Table.addMinCityStatesSlider() { private fun Table.addMinMaxCityStatesSliders() {
val cityStatesAvailable = numberOfCityStates() addLinkedMinMaxSliders( 0, numberOfCityStates(),
if (cityStatesAvailable == 0) return "{Min number of City-States}:", "{Max number of City-States}:",
gameParameters::minNumberOfCityStates, gameParameters::maxNumberOfCityStates
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.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.isDisabled = locked
add(slider).padTop(10f).row()
} }
private fun Table.addCityStatesSlider() { private fun Table.addCityStatesSlider() {

View File

@ -11,22 +11,15 @@ import com.unciv.UncivGame
import com.unciv.logic.GameInfo import com.unciv.logic.GameInfo
import com.unciv.logic.GameStarter import com.unciv.logic.GameStarter
import com.unciv.logic.IdChecker import com.unciv.logic.IdChecker
import com.unciv.logic.files.MapSaver
import com.unciv.logic.civilization.PlayerType import com.unciv.logic.civilization.PlayerType
import com.unciv.logic.files.MapSaver
import com.unciv.logic.map.MapGeneratedMainType import com.unciv.logic.map.MapGeneratedMainType
import com.unciv.logic.multiplayer.OnlineMultiplayer import com.unciv.logic.multiplayer.OnlineMultiplayer
import com.unciv.logic.multiplayer.storage.FileStorageRateLimitReached import com.unciv.logic.multiplayer.storage.FileStorageRateLimitReached
import com.unciv.models.metadata.GameSetupInfo import com.unciv.models.metadata.GameSetupInfo
import com.unciv.models.ruleset.RulesetCache import com.unciv.models.ruleset.RulesetCache
import com.unciv.models.translations.tr import com.unciv.models.translations.tr
import com.unciv.ui.images.ImageGetter
import com.unciv.ui.screens.pickerscreens.PickerScreen
import com.unciv.ui.popups.ConfirmPopup
import com.unciv.ui.popups.Popup
import com.unciv.ui.popups.ToastPopup
import com.unciv.ui.screens.basescreen.BaseScreen
import com.unciv.ui.components.ExpanderTab import com.unciv.ui.components.ExpanderTab
import com.unciv.ui.screens.basescreen.RecreateOnResize
import com.unciv.ui.components.extensions.addSeparator import com.unciv.ui.components.extensions.addSeparator
import com.unciv.ui.components.extensions.addSeparatorVertical import com.unciv.ui.components.extensions.addSeparatorVertical
import com.unciv.ui.components.extensions.disable import com.unciv.ui.components.extensions.disable
@ -35,12 +28,19 @@ import com.unciv.ui.components.extensions.onClick
import com.unciv.ui.components.extensions.pad import com.unciv.ui.components.extensions.pad
import com.unciv.ui.components.extensions.toLabel import com.unciv.ui.components.extensions.toLabel
import com.unciv.ui.components.extensions.toTextButton import com.unciv.ui.components.extensions.toTextButton
import com.unciv.ui.images.ImageGetter
import com.unciv.ui.popups.ConfirmPopup
import com.unciv.ui.popups.Popup
import com.unciv.ui.popups.ToastPopup
import com.unciv.ui.screens.basescreen.BaseScreen
import com.unciv.ui.screens.basescreen.RecreateOnResize
import com.unciv.ui.screens.pickerscreens.PickerScreen
import com.unciv.utils.Log import com.unciv.utils.Log
import com.unciv.utils.concurrency.Concurrency import com.unciv.utils.concurrency.Concurrency
import com.unciv.utils.concurrency.launchOnGLThread import com.unciv.utils.concurrency.launchOnGLThread
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import java.net.URL import java.net.URL
import java.util.* import java.util.UUID
import com.unciv.ui.components.AutoScrollPane as ScrollPane import com.unciv.ui.components.AutoScrollPane as ScrollPane
class NewGameScreen( class NewGameScreen(
@ -54,6 +54,8 @@ class NewGameScreen(
private val mapOptionsTable: MapOptionsTable private val mapOptionsTable: MapOptionsTable
init { init {
val isPortrait = isNarrowerThan4to3()
updateRuleset() // must come before playerPickerTable so mod nations from fromSettings updateRuleset() // must come before playerPickerTable so mod nations from fromSettings
// Has to be initialized before the mapOptionsTable, since the mapOptionsTable refers to it on init // Has to be initialized before the mapOptionsTable, since the mapOptionsTable refers to it on init
@ -65,13 +67,17 @@ class NewGameScreen(
playerPickerTable = PlayerPickerTable( playerPickerTable = PlayerPickerTable(
this, gameSetupInfo.gameParameters, this, gameSetupInfo.gameParameters,
if (isNarrowerThan4to3()) stage.width - 20f else 0f if (isPortrait) stage.width - 20f else 0f
)
newGameOptionsTable = GameOptionsTable(
this, isPortrait,
updatePlayerPickerTable = { desiredCiv -> playerPickerTable.update(desiredCiv) },
updatePlayerPickerRandomLabel = { playerPickerTable.updateRandomNumberLabel() }
) )
newGameOptionsTable = GameOptionsTable(this, isNarrowerThan4to3()) { desiredCiv: String -> playerPickerTable.update(desiredCiv) }
mapOptionsTable = MapOptionsTable(this) mapOptionsTable = MapOptionsTable(this)
setDefaultCloseAction() setDefaultCloseAction()
if (isNarrowerThan4to3()) initPortrait() if (isPortrait) initPortrait()
else initLandscape() else initLandscape()
pickerPane.bottomTable.background = skinStrings.getUiBackground("NewGameScreen/BottomTable", tintColor = skinStrings.skinConfig.clearColor) pickerPane.bottomTable.background = skinStrings.getUiBackground("NewGameScreen/BottomTable", tintColor = skinStrings.skinConfig.clearColor)
@ -94,7 +100,10 @@ class NewGameScreen(
rightSideButton.enable() rightSideButton.enable()
rightSideButton.setText("Start game!".tr()) rightSideButton.setText("Start game!".tr())
rightSideButton.onClick { rightSideButton.onClick(this::onStartGameClicked)
}
private fun onStartGameClicked() {
if (gameSetupInfo.gameParameters.isOnlineMultiplayer) { if (gameSetupInfo.gameParameters.isOnlineMultiplayer) {
if (!checkConnectionToMultiplayerServer()) { if (!checkConnectionToMultiplayerServer()) {
val noInternetConnectionPopup = Popup(this) val noInternetConnectionPopup = Popup(this)
@ -102,7 +111,7 @@ class NewGameScreen(
noInternetConnectionPopup.addGoodSizedLabel(label.tr()).row() noInternetConnectionPopup.addGoodSizedLabel(label.tr()).row()
noInternetConnectionPopup.addCloseButton() noInternetConnectionPopup.addCloseButton()
noInternetConnectionPopup.open() noInternetConnectionPopup.open()
return@onClick return
} }
for (player in gameSetupInfo.gameParameters.players.filter { it.playerType == PlayerType.Human }) { for (player in gameSetupInfo.gameParameters.players.filter { it.playerType == PlayerType.Human }) {
@ -113,7 +122,7 @@ class NewGameScreen(
invalidPlayerIdPopup.addGoodSizedLabel("Invalid player ID!".tr()).row() invalidPlayerIdPopup.addGoodSizedLabel("Invalid player ID!".tr()).row()
invalidPlayerIdPopup.addCloseButton() invalidPlayerIdPopup.addCloseButton()
invalidPlayerIdPopup.open() invalidPlayerIdPopup.open()
return@onClick return
} }
} }
@ -123,7 +132,7 @@ class NewGameScreen(
notAllowedToSpectate.addGoodSizedLabel("You are not allowed to spectate!".tr()).row() notAllowedToSpectate.addGoodSizedLabel("You are not allowed to spectate!".tr()).row()
notAllowedToSpectate.addCloseButton() notAllowedToSpectate.addCloseButton()
notAllowedToSpectate.open() notAllowedToSpectate.open()
return@onClick return
} }
} }
} }
@ -138,7 +147,7 @@ class NewGameScreen(
noHumanPlayersPopup.addGoodSizedLabel("No human players selected!".tr()).row() noHumanPlayersPopup.addGoodSizedLabel("No human players selected!".tr()).row()
noHumanPlayersPopup.addCloseButton() noHumanPlayersPopup.addCloseButton()
noHumanPlayersPopup.open() noHumanPlayersPopup.open()
return@onClick return
} }
if (gameSetupInfo.gameParameters.victoryTypes.isEmpty()) { if (gameSetupInfo.gameParameters.victoryTypes.isEmpty()) {
@ -146,7 +155,7 @@ class NewGameScreen(
noVictoryTypesPopup.addGoodSizedLabel("No victory conditions were selected!".tr()).row() noVictoryTypesPopup.addGoodSizedLabel("No victory conditions were selected!".tr()).row()
noVictoryTypesPopup.addCloseButton() noVictoryTypesPopup.addCloseButton()
noVictoryTypesPopup.open() noVictoryTypesPopup.open()
return@onClick return
} }
Gdx.input.inputProcessor = null // remove input processing - nothing will be clicked! Gdx.input.inputProcessor = null // remove input processing - nothing will be clicked!
@ -157,7 +166,7 @@ class NewGameScreen(
} catch (ex: Throwable) { } catch (ex: Throwable) {
Gdx.input.inputProcessor = stage Gdx.input.inputProcessor = stage
ToastPopup("Could not load map!", this) ToastPopup("Could not load map!", this)
return@onClick return
} }
val rulesetIncompatibilities = map.getRulesetIncompatibility(ruleset) val rulesetIncompatibilities = map.getRulesetIncompatibility(ruleset)
@ -169,7 +178,7 @@ class NewGameScreen(
incompatibleMap.addCloseButton() incompatibleMap.addCloseButton()
incompatibleMap.open() incompatibleMap.open()
Gdx.input.inputProcessor = stage Gdx.input.inputProcessor = stage
return@onClick return
} }
} else { } else {
// Generated map - check for sensible dimensions and if exceeded correct them and notify user // Generated map - check for sensible dimensions and if exceeded correct them and notify user
@ -183,7 +192,7 @@ class NewGameScreen(
customMapHeight.text = mapSize.height.toString() customMapHeight.text = mapSize.height.toString()
} }
Gdx.input.inputProcessor = stage Gdx.input.inputProcessor = stage
return@onClick return
} }
} }
@ -196,7 +205,6 @@ class NewGameScreen(
startNewGame() startNewGame()
} }
} }
}
private fun initLandscape() { private fun initLandscape() {
scrollPane.setScrollingDisabled(true,true) scrollPane.setScrollingDisabled(true,true)

View File

@ -11,6 +11,7 @@ import com.unciv.logic.IdChecker
import com.unciv.logic.civilization.PlayerType import com.unciv.logic.civilization.PlayerType
import com.unciv.logic.multiplayer.FriendList import com.unciv.logic.multiplayer.FriendList
import com.unciv.models.metadata.GameParameters import com.unciv.models.metadata.GameParameters
import com.unciv.models.metadata.GameSetupInfo
import com.unciv.models.metadata.Player import com.unciv.models.metadata.Player
import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.nation.Nation import com.unciv.models.ruleset.nation.Nation
@ -19,6 +20,7 @@ import com.unciv.ui.audio.MusicMood
import com.unciv.ui.audio.MusicTrackChooserFlags import com.unciv.ui.audio.MusicTrackChooserFlags
import com.unciv.ui.components.KeyCharAndCode import com.unciv.ui.components.KeyCharAndCode
import com.unciv.ui.components.UncivTextField import com.unciv.ui.components.UncivTextField
import com.unciv.ui.components.WrappableLabel
import com.unciv.ui.components.extensions.* import com.unciv.ui.components.extensions.*
import com.unciv.ui.images.ImageGetter import com.unciv.ui.images.ImageGetter
import com.unciv.ui.popups.Popup import com.unciv.ui.popups.Popup
@ -44,12 +46,13 @@ class PlayerPickerTable(
blockWidth: Float = 0f blockWidth: Float = 0f
): Table() { ): Table() {
val playerListTable = Table() val playerListTable = Table()
val civBlocksWidth = if(blockWidth <= 10f) previousScreen.stage.width / 3 - 5f else blockWidth val civBlocksWidth = if (blockWidth <= 10f) previousScreen.stage.width / 3 - 5f else blockWidth
private var randomNumberLabel: WrappableLabel? = null
/** Locks player table for editing, currently unused, was previously used for scenarios and could be useful in the future.*/ /** Locks player table for editing, currently unused, was previously used for scenarios and could be useful in the future. */
var locked = false var locked = false
/** No random civilization is available, used during map editing.*/ /** No random civilization is available, potentially used in the future during map editing. */
var noRandom = false var noRandom = false
private val friendList = FriendList() private val friendList = FriendList()
@ -82,27 +85,47 @@ class PlayerPickerTable(
for (player in gameParameters.players) { for (player in gameParameters.players) {
playerListTable.add(getPlayerTable(player)).width(civBlocksWidth).padBottom(20f).row() playerListTable.add(getPlayerTable(player)).width(civBlocksWidth).padBottom(20f).row()
} }
val isRandomNumberOfPlayers = gameParameters.randomNumberOfPlayers
if (isRandomNumberOfPlayers) {
randomNumberLabel = WrappableLabel("", civBlocksWidth - 20f, Color.GOLD)
playerListTable.add(randomNumberLabel).fillX().pad(0f, 10f, 20f, 10f).row()
updateRandomNumberLabel()
}
if (!locked && gameParameters.players.size < gameBasics.nations.values.count { it.isMajorCiv }) { if (!locked && gameParameters.players.size < gameBasics.nations.values.count { it.isMajorCiv }) {
val addPlayerButton = "+".toLabel(Color.BLACK, 30) val addPlayerButton = "+".toLabel(Color.BLACK, 30)
.apply { this.setAlignment(Align.center) } .apply { this.setAlignment(Align.center) }
.surroundWithCircle(50f) .surroundWithCircle(50f)
.onClick { .onClick {
var player = Player()
// no random mode - add first not spectator civ if still available // no random mode - add first not spectator civ if still available
if (noRandom) { val player = if (noRandom || isRandomNumberOfPlayers) {
val availableCiv = getAvailablePlayerCivs().firstOrNull() val availableCiv = getAvailablePlayerCivs().firstOrNull()
if (availableCiv != null) player = Player(availableCiv.name) if (availableCiv != null) Player(availableCiv.name)
// Spectators only Humans // Spectators can only be Humans
else player = Player(Constants.spectator, PlayerType.Human) else Player(Constants.spectator, PlayerType.Human)
} } else Player() // normal: add random AI
gameParameters.players.add(player) gameParameters.players.add(player)
update() update()
} }
playerListTable.add(addPlayerButton).pad(10f) playerListTable.add(addPlayerButton).pad(10f)
} }
// enable start game when more than 1 active player
val moreThanOnePlayer = 1 < gameParameters.players.count { it.chosenCiv != Constants.spectator } // enable start game when at least one human player and they're not alone
(previousScreen as? PickerScreen)?.setRightSideButtonEnabled(moreThanOnePlayer) val humanPlayerCount = gameParameters.players.count { it.playerType == PlayerType.Human }
val isValid = humanPlayerCount >= 2 || humanPlayerCount >= 1 && isRandomNumberOfPlayers
(previousScreen as? PickerScreen)?.setRightSideButtonEnabled(isValid)
}
fun updateRandomNumberLabel() {
randomNumberLabel?.run {
val text = "These [${gameParameters.players.size}] players will be adjusted to [${gameParameters.minNumberOfPlayers}" +
"]-[${gameParameters.maxNumberOfPlayers}] actual players by adding random AI's or by randomly omitting AI's."
wrap = false
align(Align.center)
setText(text.tr())
wrap = true
}
} }
/** /**
@ -145,29 +168,56 @@ class PlayerPickerTable(
playerTable.add(nationTable).left() playerTable.add(nationTable).left()
val playerTypeTextButton = player.playerType.name.toTextButton() val playerTypeTextButton = player.playerType.name.toTextButton()
playerTypeTextButton.onClick { playerTable.add(playerTypeTextButton).width(100f).pad(5f).right()
if (player.playerType == PlayerType.AI) fun updatePlayerTypeButtonEnabled() {
player.playerType = PlayerType.Human // This could be written much shorter with logical operators - I think this is readable
playerTypeTextButton.isEnabled = when {
// Can always change AI to Human
player.playerType == PlayerType.AI -> true
// we cannot change Spectator player to AI type, robots not allowed to spectate :( // we cannot change Spectator player to AI type, robots not allowed to spectate :(
else if (player.chosenCiv != Constants.spectator) player.chosenCiv == Constants.spectator -> false
player.playerType = PlayerType.AI // In randomNumberOfPlayers mode, don't let the user choose random AI's
gameParameters.randomNumberOfPlayers && player.chosenCiv == Constants.random -> false
else -> true
}
}
updatePlayerTypeButtonEnabled()
nationTable.onClick {
if (locked) return@onClick
val noRandom = noRandom ||
gameParameters.randomNumberOfPlayers && player.playerType == PlayerType.AI
popupNationPicker(player, noRandom)
updatePlayerTypeButtonEnabled()
}
playerTypeTextButton.onClick {
player.playerType = player.playerType.toggle()
update() update()
} }
playerTable.add(playerTypeTextButton).width(100f).pad(5f).right()
if (!locked) { if (!locked) {
playerTable.add("-".toLabel(Color.BLACK, 30).apply { this.setAlignment(Align.center) } playerTable.add("-".toLabel(Color.BLACK, 30, Align.center)
.surroundWithCircle(40f) .surroundWithCircle(40f)
.onClick { .onClick {
gameParameters.players.remove(player) gameParameters.players.remove(player)
update() update()
}).pad(5f).right().row()
} }
if (gameParameters.isOnlineMultiplayer && player.playerType == PlayerType.Human) { ).pad(5f).right()
}
if (gameParameters.isOnlineMultiplayer && player.playerType == PlayerType.Human)
playerTable.addPlayerTableMultiplayerControls(player)
return playerTable
}
private fun Table.addPlayerTableMultiplayerControls(player: Player) {
row()
val playerIdTextField = UncivTextField.create("Please input Player ID!", player.playerId) val playerIdTextField = UncivTextField.create("Please input Player ID!", player.playerId)
playerTable.add(playerIdTextField).colspan(2).fillX().pad(5f) add(playerIdTextField).colspan(2).fillX().pad(5f)
val errorLabel = "".toLabel(Color.RED) val errorLabel = "".toLabel(Color.RED)
playerTable.add(errorLabel).pad(5f).row() add(errorLabel).pad(5f).row()
fun onPlayerIdTextUpdated() { fun onPlayerIdTextUpdated() {
try { try {
@ -179,22 +229,22 @@ class PlayerPickerTable(
} }
} }
onPlayerIdTextUpdated() onPlayerIdTextUpdated()
playerIdTextField.addListener { onPlayerIdTextUpdated(); true } playerIdTextField.addListener { onPlayerIdTextUpdated(); true }
val currentUserId = UncivGame.Current.settings.multiplayer.userId val currentUserId = UncivGame.Current.settings.multiplayer.userId
val setCurrentUserButton = "Set current user".toTextButton() val setCurrentUserButton = "Set current user".toTextButton()
setCurrentUserButton.onClick { setCurrentUserButton.onClick {
playerIdTextField.text = currentUserId playerIdTextField.text = currentUserId
onPlayerIdTextUpdated() onPlayerIdTextUpdated()
} }
playerTable.add(setCurrentUserButton).colspan(3).fillX().pad(5f).row() add(setCurrentUserButton).colspan(3).fillX().pad(5f).row()
val copyFromClipboardButton = "Player ID from clipboard".toTextButton() val copyFromClipboardButton = "Player ID from clipboard".toTextButton()
copyFromClipboardButton.onClick { copyFromClipboardButton.onClick {
playerIdTextField.text = Gdx.app.clipboard.contents playerIdTextField.text = Gdx.app.clipboard.contents
onPlayerIdTextUpdated() onPlayerIdTextUpdated()
} }
playerTable.add(copyFromClipboardButton).right().colspan(3).fillX().pad(5f).row() add(copyFromClipboardButton).right().colspan(3).fillX().pad(5f).row()
//check if friends list is empty before adding the select friend button //check if friends list is empty before adding the select friend button
if (friendList.friendList.isNotEmpty()) { if (friendList.friendList.isNotEmpty()) {
@ -202,31 +252,25 @@ class PlayerPickerTable(
selectPlayerFromFriendsList.onClick { selectPlayerFromFriendsList.onClick {
popupFriendPicker(player) popupFriendPicker(player)
} }
playerTable.add(selectPlayerFromFriendsList).left().colspan(3).fillX().pad(5f) add(selectPlayerFromFriendsList).left().colspan(3).fillX().pad(5f)
} }
} }
return playerTable
}
/** /**
* Creates clickable icon and nation name for some [Player] * Creates clickable icon and nation name for some [Player].
* as a [Table]. Clicking creates [popupNationPicker] to choose new nation.
* @param player [Player] for which generated * @param player [Player] for which generated
* @return [Table] containing nation icon and name * @return [Table] containing nation icon and name
*/ */
private fun getNationTable(player: Player): Table { private fun getNationTable(player: Player): Table {
val nationTable = Table() val nationTable = Table()
val nationImageName = previousScreen.ruleset.nations[player.chosenCiv]
val nationImage = val nationImage =
if (player.chosenCiv == Constants.random) if (nationImageName == null)
ImageGetter.getRandomNationPortrait(40f) ImageGetter.getRandomNationPortrait(40f)
else ImageGetter.getNationPortrait(previousScreen.ruleset.nations[player.chosenCiv]!!, 40f) else ImageGetter.getNationPortrait(nationImageName, 40f)
nationTable.add(nationImage).pad(5f) nationTable.add(nationImage).pad(5f)
nationTable.add(player.chosenCiv.toLabel()).pad(5f) nationTable.add(player.chosenCiv.toLabel()).pad(5f)
nationTable.touchable = Touchable.enabled nationTable.touchable = Touchable.enabled
nationTable.onClick {
if (!locked) popupNationPicker(player)
}
return nationTable return nationTable
} }
@ -247,8 +291,8 @@ class PlayerPickerTable(
* ruleset and other players nation choice. * ruleset and other players nation choice.
* @param player current player * @param player current player
*/ */
private fun popupNationPicker(player: Player) { private fun popupNationPicker(player: Player, noRandom: Boolean) {
NationPickerPopup(this, player).open() NationPickerPopup(this, player, noRandom).open()
update() update()
} }
@ -288,7 +332,7 @@ class FriendSelectionPopup(
screen: BaseScreen, screen: BaseScreen,
) : Popup(screen) { ) : Popup(screen) {
val pickerPane = PickerPane() private val pickerPane = PickerPane()
private var selectedFriendId: String? = null private var selectedFriendId: String? = null
init { init {
@ -327,7 +371,8 @@ class FriendSelectionPopup(
private class NationPickerPopup( private class NationPickerPopup(
private val playerPicker: PlayerPickerTable, private val playerPicker: PlayerPickerTable,
private val player: Player private val player: Player,
noRandom: Boolean
) : Popup(playerPicker.previousScreen as BaseScreen) { ) : Popup(playerPicker.previousScreen as BaseScreen) {
companion object { companion object {
// These are used for the Close/OK buttons in the lower left/right corners: // These are used for the Close/OK buttons in the lower left/right corners:
@ -357,26 +402,23 @@ private class NationPickerPopup(
nationDetailsScroll.setOverscroll(false, false) nationDetailsScroll.setOverscroll(false, false)
add(nationDetailsScroll).size(civBlocksWidth + 10f, partHeight) // Same here, see above add(nationDetailsScroll).size(civBlocksWidth + 10f, partHeight) // Same here, see above
val randomNation = Nation().apply { val nationSequence = sequence {
if (!noRandom) yield(Nation().apply {
name = Constants.random name = Constants.random
innerColor = listOf(255, 255, 255) innerColor = listOf(255, 255, 255)
outerColor = listOf(0, 0, 0) outerColor = listOf(0, 0, 0)
setTransients() setTransients()
} })
val nations = ArrayList<Nation>()
if (!playerPicker.noRandom) nations += randomNation
val spectator = previousScreen.ruleset.nations[Constants.spectator] val spectator = previousScreen.ruleset.nations[Constants.spectator]
if (spectator != null) nations += spectator if (spectator != null && player.playerType != PlayerType.AI) // only humans can spectate, sorry robots
yield(spectator)
nations += playerPicker.getAvailablePlayerCivs(player.chosenCiv) } + playerPicker.getAvailablePlayerCivs(player.chosenCiv)
.sortedWith(compareBy(UncivGame.Current.settings.getCollatorFromLocale()) { it.name.tr() }) .sortedWith(compareBy(UncivGame.Current.settings.getCollatorFromLocale()) { it.name.tr() })
val nations = nationSequence.toCollection(ArrayList<Nation>(previousScreen.ruleset.nations.size))
var nationListScrollY = 0f var nationListScrollY = 0f
var currentY = 0f var currentY = 0f
for (nation in nations) { for (nation in nations) {
// only humans can spectate, sorry robots
if (player.playerType == PlayerType.AI && nation.isSpectator)
continue
if (player.chosenCiv == nation.name) if (player.chosenCiv == nation.name)
nationListScrollY = currentY nationListScrollY = currentY
val nationTable = NationTable(nation, civBlocksWidth, 0f) // no need for min height val nationTable = NationTable(nation, civBlocksWidth, 0f) // no need for min height
@ -386,6 +428,10 @@ private class NationPickerPopup(
nationTable.onClick { nationTable.onClick {
setNationDetails(nation) setNationDetails(nation)
} }
nationTable.onDoubleClick {
selectedNation = nation
returnSelected()
}
if (player.chosenCiv == nation.name) if (player.chosenCiv == nation.name)
setNationDetails(nation) setNationDetails(nation)
} }