Removed Scenario Maps entirely - was not a well thought-out concept and caused more confusion than actual fun.

Case in point - 0 mods are Scenario mods.
This commit is contained in:
Yair Morgenstern
2021-02-11 22:33:52 +02:00
parent 74cc421b62
commit 8b1778eea3
23 changed files with 147 additions and 430 deletions

View File

@ -234,10 +234,6 @@ Reset to default =
HIGHLY EXPERIMENTAL - YOU HAVE BEEN WARNED! =
Online Multiplayer =
Scenario Editor =
Scenario file =
Scenario Map =
Scenario =
World Size =
Tiny =
@ -322,8 +318,6 @@ Delete save =
Saved at =
Load map =
Delete map =
Load Scenario Map =
Delete Scenario Map =
Are you sure you want to delete this map? =
Upload map =
Could not upload map! =
@ -775,11 +769,8 @@ Improvements =
Clear current map =
Save map =
Download map =
Toggle Scenario Map =
Loading... =
Filter: =
Create scenario map =
Edit scenario parameters =
OK =
Exit map editor =
[nation] starting location =

View File

@ -22,9 +22,7 @@ object GameStarter {
gameInfo.gameParameters = gameSetupInfo.gameParameters
val ruleset = RulesetCache.getComplexRuleset(gameInfo.gameParameters)
if (gameSetupInfo.mapParameters.type == MapType.scenarioMap)
gameInfo.tileMap = MapSaver.loadScenario(gameSetupInfo.mapParameters.name).tileMap
else if (gameSetupInfo.mapParameters.name != "") {
if (gameSetupInfo.mapParameters.name != "") {
gameInfo.tileMap = MapSaver.loadMap(gameSetupInfo.mapFile!!)
} else gameInfo.tileMap = MapGenerator(ruleset).generateMap(gameSetupInfo.mapParameters)
gameInfo.tileMap.mapParameters = gameSetupInfo.mapParameters
@ -51,8 +49,7 @@ object GameStarter {
addCivTechs(gameInfo, ruleset, gameSetupInfo)
// and only now do we add units for everyone, because otherwise both the gameInfo.setTransients() and the placeUnit will both add the unit to the civ's unit list!
if (gameSetupInfo.mapParameters.type != MapType.scenarioMap)
addCivStartingUnits(gameInfo)
addCivStartingUnits(gameInfo)
// remove starting locations once we're done
for (tile in gameInfo.tileMap.values) {

View File

@ -2,7 +2,6 @@ package com.unciv.logic
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.files.FileHandle
import com.unciv.logic.map.ScenarioMap
import com.unciv.logic.map.TileMap
import com.unciv.ui.saves.Gzip
@ -11,42 +10,20 @@ object MapSaver {
fun json() = GameSaver.json()
private const val mapsFolder = "maps"
private const val scenariosFolder = "scenarios"
private fun getMap(mapName:String) = Gdx.files.local("$mapsFolder/$mapName")
private fun getScenario(scenarioName:String) = Gdx.files.local("$scenariosFolder/$scenarioName")
fun saveMap(mapName: String,tileMap: TileMap) {
getMap(mapName).writeString(Gzip.zip(json().toJson(tileMap)), false)
}
fun saveScenario(scenarioName:String, scenarioMap: ScenarioMap) {
getScenario(scenarioName).writeString(Gzip.zip(json().toJson(scenarioMap)), false)
}
fun loadMap(mapName: String) = loadMap(getMap(mapName))
fun loadMap(mapFile:FileHandle):TileMap{
fun loadMap(mapFile:FileHandle):TileMap {
val gzippedString = mapFile.readString()
val unzippedJson = Gzip.unzip(gzippedString)
return json().fromJson(TileMap::class.java, unzippedJson)
}
fun loadScenario(scenarioName: String) = loadScenario(getScenario(scenarioName))
fun loadScenario(scenarioFile: FileHandle): ScenarioMap {
val gzippedString = scenarioFile.readString()
val unzippedJson = Gzip.unzip(gzippedString)
return json().fromJson(ScenarioMap::class.java, unzippedJson)
}
fun deleteMap(mapName: String) = getMap(mapName).delete()
fun deleteScenario(scenarioName: String) = getScenario(scenarioName).delete()
fun getMaps() = Gdx.files.local(mapsFolder).list()
fun getScenarios() = Gdx.files.local(scenariosFolder).list()
fun mapFromJson(json:String): TileMap = json().fromJson(TileMap::class.java, json)
}

View File

@ -192,7 +192,7 @@ object NextTurnAutomation {
VictoryType.Cultural -> listOf("Piety", "Freedom", "Tradition", "Rationalism", "Commerce")
VictoryType.Scientific -> listOf("Rationalism", "Commerce", "Liberty", "Freedom", "Piety")
VictoryType.Domination -> listOf("Autocracy", "Honor", "Liberty", "Rationalism", "Freedom")
VictoryType.Neutral, VictoryType.Scenario -> listOf()
VictoryType.Neutral -> listOf()
}
val policiesByPreference = adoptablePolicies
.groupBy {

View File

@ -333,15 +333,11 @@ class CivilizationInfo {
fun isDefeated(): Boolean {
// Dirty hack: exploredTiles are empty only before starting units are placed
if (exploredTiles.isEmpty() || isBarbarian() || isSpectator()) return false
// Scenarios are 'to the death'... for now
if (gameInfo.gameParameters.victoryTypes.contains(VictoryType.Scenario))
return cities.isEmpty() && getCivUnits().none()
else return cities.isEmpty() // No cities
&& (citiesCreated > 0 || !getCivUnits().any { it.hasUnique(Constants.settlerUnique) })
}
fun getEra(): String {
// For scenarios with no techs
if (gameInfo.ruleSet.technologies.isEmpty()) return "None"
if (tech.researchedTechnologies.isEmpty())
return gameInfo.ruleSet.getEras().first()
@ -353,9 +349,7 @@ class CivilizationInfo {
return maxEraOfTech
}
fun getEraNumber(): Int {
return gameInfo.ruleSet.getEraNumber(getEra())
}
fun getEraNumber(): Int = gameInfo.ruleSet.getEraNumber(getEra())
fun isAtWarWith(otherCiv: CivilizationInfo): Boolean {
if (otherCiv.civName == civName) return false // never at war with itself

View File

@ -39,8 +39,8 @@ class VictoryManager {
&& civInfo.policies.adoptedPolicies.count { it.endsWith("Complete") } > 4
fun hasWonDominationVictory(): Boolean {
return (hasVictoryType(VictoryType.Domination) || hasVictoryType(VictoryType.Scenario)) &&
civInfo.gameInfo.civilizations.all { it == civInfo || it.isDefeated() || !it.isMajorCiv() }
return hasVictoryType(VictoryType.Domination)
&& civInfo.gameInfo.civilizations.all { it == civInfo || it.isDefeated() || !it.isMajorCiv() }
}
fun hasWonVictoryType(): VictoryType? {

View File

@ -25,11 +25,6 @@ object MapType {
// Non-generated maps
const val custom = "Custom"
// Loaded scenario
const val scenarioMap = "Scenario Map"
const val scenario = "Scenario"
// All ocean tiles
const val empty = "Empty"
}

View File

@ -1,16 +0,0 @@
package com.unciv.logic.map
import com.unciv.models.metadata.GameParameters
class ScenarioMap {
lateinit var tileMap: TileMap
lateinit var gameParameters: GameParameters
/** for json parsing, we need to have a default constructor */
constructor()
constructor(tileMap:TileMap, gameParameters: GameParameters) {
this.tileMap = tileMap
this.gameParameters = gameParameters
}
}

View File

@ -477,6 +477,35 @@ open class TileInfo {
return lineList.joinToString("\n")
}
fun hasEnemyInvisibleUnit(viewingCiv: CivilizationInfo): Boolean {
val unitsInTile = getUnits()
if (unitsInTile.none()) return false
if (unitsInTile.first().civInfo != viewingCiv &&
unitsInTile.firstOrNull { it.isInvisible() } != null) {
return true
}
return false
}
fun hasConnection(civInfo: CivilizationInfo) =
roadStatus != RoadStatus.None || forestOrJungleAreRoads(civInfo)
private fun forestOrJungleAreRoads(civInfo: CivilizationInfo) =
civInfo.nation.forestsAndJunglesAreRoads
&& (terrainFeature == Constants.jungle || terrainFeature == Constants.forest)
&& isFriendlyTerritory(civInfo)
fun isRulesetCompatible(ruleset: Ruleset): Boolean {
if (!ruleset.terrains.containsKey(baseTerrain)) return false
if (terrainFeature != null && !ruleset.terrains.containsKey(terrainFeature)) return false
if (resource != null && !ruleset.tileResources.containsKey(resource)) return false
if (improvement != null && !ruleset.tileImprovements.containsKey(baseTerrain)) return false
return true
}
//endregion
//region state-changing functions
@ -527,23 +556,5 @@ open class TileInfo {
turnsToImprovement = 0
}
fun hasEnemyInvisibleUnit(viewingCiv: CivilizationInfo): Boolean {
val unitsInTile = getUnits()
if (unitsInTile.none()) return false
if (unitsInTile.first().civInfo != viewingCiv &&
unitsInTile.firstOrNull { it.isInvisible() } != null) {
return true
}
return false
}
fun hasConnection(civInfo: CivilizationInfo) =
roadStatus != RoadStatus.None || forestOrJungleAreRoads(civInfo)
private fun forestOrJungleAreRoads(civInfo: CivilizationInfo) =
civInfo.nation.forestsAndJunglesAreRoads
&& (terrainFeature == Constants.jungle || terrainFeature == Constants.forest)
&& isFriendlyTerritory(civInfo)
//endregion
}

View File

@ -14,16 +14,22 @@ class TileMap {
@Transient
lateinit var gameInfo: GameInfo
@Transient
var tileMatrix = ArrayList<ArrayList<TileInfo?>>() // this works several times faster than a hashmap, the performance difference is really astounding
@Transient
var leftX = 0
@Transient
var bottomY = 0
@delegate:Transient
val maxLatitude: Float by lazy { if (values.isEmpty()) 0f else values.map { abs(it.latitude) }.max()!! }
@delegate:Transient
val maxLongitude: Float by lazy { if (values.isEmpty()) 0f else values.map { abs(it.longitude) }.max()!! }
@delegate:Transient
val naturalWonders: List<String> by lazy { tileList.asSequence().filter { it.isNaturalWonder() }.map { it.naturalWonder!! }.distinct().toList() }
@ -62,9 +68,7 @@ class TileMap {
return toReturn
}
operator fun contains(vector: Vector2): Boolean {
return contains(vector.x.toInt(), vector.y.toInt())
}
operator fun contains(vector: Vector2) = contains(vector.x.toInt(), vector.y.toInt())
fun contains(x: Int, y: Int): Boolean {
val arrayXIndex = x - leftX
@ -270,10 +274,10 @@ class TileMap {
}
fun setTransients(ruleset: Ruleset, setUnitCivTransients: Boolean = true) { // In the map editor, no Civs or Game exist, so we won't set the unit transients
val topY = tileList.asSequence().map { it.position.y.toInt() }.max()!!
bottomY = tileList.asSequence().map { it.position.y.toInt() }.min()!!
val rightX = tileList.asSequence().map { it.position.x.toInt() }.max()!!
leftX = tileList.asSequence().map { it.position.x.toInt() }.min()!!
val topY = tileList.asSequence().map { it.position.y.toInt() }.maxOrNull()!!
bottomY = tileList.asSequence().map { it.position.y.toInt() }.minOrNull()!!
val rightX = tileList.asSequence().map { it.position.x.toInt() }.maxOrNull()!!
leftX = tileList.asSequence().map { it.position.x.toInt() }.minOrNull()!!
for (x in leftX..rightX) {
val row = ArrayList<TileInfo?>()

View File

@ -21,7 +21,7 @@ class GameSettings {
var soundEffectsVolume = 0.5f
var musicVolume = 0.5f
var turnsBetweenAutosaves = 1
var tileSet:String = "FantasyHex"
var tileSet: String = "FantasyHex"
var showTutorials: Boolean = true
var autoAssignCityProduction: Boolean = true
var autoBuildingRoads: Boolean = true
@ -38,7 +38,6 @@ class GameSettings {
var orderTradeOffersByAmount = true
var windowState = WindowState()
var isFreshlyCreated = false
var extendedMapEditor = false
init {
// 26 = Android Oreo. Versions below may display permanent icon in notification bar.
@ -47,14 +46,14 @@ class GameSettings {
}
}
fun save(){
fun save() {
if (!isFreshlyCreated && Gdx.app?.type == Application.ApplicationType.Desktop) {
windowState = WindowState( Gdx.graphics.width, Gdx.graphics.height)
windowState = WindowState(Gdx.graphics.width, Gdx.graphics.height)
}
GameSaver.setGeneralSettings(this)
}
fun addCompletedTutorialTask(tutorialTask:String){
fun addCompletedTutorialTask(tutorialTask: String) {
tutorialTasksCompleted.add(tutorialTask)
save()
}

View File

@ -13,8 +13,7 @@ enum class VictoryType {
Neutral,
Cultural,
Domination,
Scientific,
Scenario
Scientific
}
class Nation : INamed {

View File

@ -1,7 +1,6 @@
package com.unciv.ui.mapeditor
import com.unciv.UncivGame
import com.unciv.logic.map.ScenarioMap
import com.unciv.models.ruleset.RulesetCache
import com.unciv.models.translations.tr
import com.unciv.ui.newgamescreen.GameOptionsTable
@ -34,11 +33,10 @@ class GameParametersScreen(var mapEditorScreen: MapEditorScreen): IPreviousScree
rightSideButton.setText("OK".tr())
rightSideButton.onClick {
mapEditorScreen.gameSetupInfo = gameSetupInfo
mapEditorScreen.scenarioMap = ScenarioMap(mapEditorScreen.tileMap, mapEditorScreen.gameSetupInfo.gameParameters)
mapEditorScreen.ruleset.clear()
mapEditorScreen.ruleset.add(ruleset)
mapEditorScreen.tileEditorOptions.update()
// Remove resources that are not applicable to this scenario
// Remove resources that are not applicable to this ruleset
for(tile in mapEditorScreen.tileMap.values) {
if (tile.resource != null && !ruleset.tileResources.containsKey(tile.resource!!))
tile.resource = null

View File

@ -5,7 +5,6 @@ import com.badlogic.gdx.files.FileHandle
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.scenes.scene2d.ui.TextButton
import com.badlogic.gdx.utils.Align
import com.unciv.MainMenuScreen
import com.unciv.UncivGame
import com.unciv.logic.MapSaver
@ -17,45 +16,25 @@ import com.unciv.ui.utils.*
import kotlin.concurrent.thread
import com.unciv.ui.utils.AutoScrollPane as ScrollPane
class LoadMapScreen(previousMap: TileMap?) : PickerScreen(){
var chosenMap:FileHandle? = null
class LoadMapScreen(previousMap: TileMap?) : PickerScreen() {
var chosenMap: FileHandle? = null
val deleteButton = "Delete map".toTextButton()
var scenarioMap = false
val mapsTable = Table().apply { defaults().pad(10f) }
init {
if(UncivGame.Current.settings.extendedMapEditor) {
val toggleButton = "Toggle Scenario Map".toTextButton()
toggleButton.onClick {
scenarioMap = !scenarioMap
update()
}
toggleButton.centerX(stage)
toggleButton.setY(stage.height - 10f, Align.top)
stage.addActor(toggleButton)
}
rightSideButton.setText("Load map".tr())
rightSideButton.onClick {
thread {
if (scenarioMap) {
val scenario = MapSaver.loadScenario(chosenMap!!)
Gdx.app.postRunnable {
UncivGame.Current.setScreen(MapEditorScreen(scenario, chosenMap!!.name()))
dispose()
}
} else {
Gdx.app.postRunnable {
val popup = Popup(this)
popup.addGoodSizedLabel("Loading...")
popup.open()
}
val map = MapSaver.loadMap(chosenMap!!)
Gdx.app.postRunnable {
Gdx.input.inputProcessor = null // This is to stop ANRs happening here, until the map editor screen sets up.
UncivGame.Current.setScreen(MapEditorScreen(map))
dispose()
}
Gdx.app.postRunnable {
val popup = Popup(this)
popup.addGoodSizedLabel("Loading...")
popup.open()
}
val map = MapSaver.loadMap(chosenMap!!)
Gdx.app.postRunnable {
Gdx.input.inputProcessor = null // This is to stop ANRs happening here, until the map editor screen sets up.
UncivGame.Current.setScreen(MapEditorScreen(map))
dispose()
}
}
}
@ -108,38 +87,22 @@ class LoadMapScreen(previousMap: TileMap?) : PickerScreen(){
deleteButton.disable()
deleteButton.color = Color.RED
if (scenarioMap) {
deleteButton.setText("Delete Scenario Map".tr())
rightSideButton.setText("Load Scenario Map".tr())
deleteButton.setText("Delete map".tr())
rightSideButton.setText("Load map".tr())
mapsTable.clear()
for (scenario in MapSaver.getScenarios()) {
val loadScenarioButton = TextButton(scenario.name(), skin)
loadScenarioButton.onClick {
rightSideButton.enable()
chosenMap = scenario
deleteButton.enable()
deleteButton.color = Color.RED
}
mapsTable.add(loadScenarioButton).row()
}
} else {
deleteButton.setText("Delete map".tr())
rightSideButton.setText("Load map".tr())
mapsTable.clear()
for (map in MapSaver.getMaps()) {
val loadMapButton = TextButton(map.name(), skin)
loadMapButton.onClick {
rightSideButton.enable()
chosenMap = map
deleteButton.enable()
deleteButton.color = Color.RED
}
mapsTable.add(loadMapButton).row()
mapsTable.clear()
for (map in MapSaver.getMaps()) {
val loadMapButton = TextButton(map.name(), skin)
loadMapButton.onClick {
rightSideButton.enable()
chosenMap = map
deleteButton.enable()
deleteButton.color = Color.RED
}
mapsTable.add(loadMapButton).row()
}
}
}

View File

@ -9,9 +9,7 @@ import com.unciv.UncivGame
import com.unciv.logic.MapSaver
import com.unciv.logic.map.MapType
import com.unciv.logic.map.RoadStatus
import com.unciv.logic.map.ScenarioMap
import com.unciv.logic.map.TileMap
import com.unciv.models.translations.tr
import com.unciv.models.metadata.Player
import com.unciv.ui.saves.Gzip
import com.unciv.ui.utils.*
@ -34,7 +32,6 @@ class MapEditorMenuPopup(var mapEditorScreen: MapEditorScreen): Popup(mapEditorS
addCopyMapAsTextButton()
addLoadMapButton()
// addUploadMapButton()
if (UncivGame.Current.settings.extendedMapEditor) addScenarioButton()
addExitMapEditorButton()
addCloseOptionsButton()
}
@ -79,18 +76,10 @@ class MapEditorMenuPopup(var mapEditorScreen: MapEditorScreen): Popup(mapEditorS
saveMapButton.onClick {
mapEditorScreen.tileMap.mapParameters.name = mapEditorScreen.mapName
mapEditorScreen.tileMap.mapParameters.type = MapType.custom
thread(name = "SaveMap") { // this works for both scenarios and non-scenarios
thread(name = "SaveMap") {
try {
if(mapEditorScreen.hasScenario()) {
mapEditorScreen.tileMap.mapParameters.type = MapType.scenarioMap
mapEditorScreen.scenarioMap = ScenarioMap(mapEditorScreen.tileMap, mapEditorScreen.gameSetupInfo.gameParameters)
mapEditorScreen.scenarioMap!!.gameParameters.godMode = true // so we can edit this scenario when loading from the map
mapEditorScreen.scenarioName = mapNameEditor.text
MapSaver.saveScenario(mapNameEditor.text, mapEditorScreen.scenarioMap!!)
}
else {
MapSaver.saveMap(mapEditorScreen.mapName, mapEditorScreen.tileMap)
}
MapSaver.saveMap(mapEditorScreen.mapName, mapEditorScreen.tileMap)
close()
Gdx.app.postRunnable {
ToastPopup("Map saved", mapEditorScreen) // todo - add this text to translations
@ -162,25 +151,6 @@ class MapEditorMenuPopup(var mapEditorScreen: MapEditorScreen): Popup(mapEditorS
add(uploadMapButton).row()
}
private fun Popup.addScenarioButton() {
var scenarioButton = "".toTextButton()
if (mapEditorScreen.hasScenario()) {
scenarioButton.setText("Edit scenario parameters".tr())
} else {
scenarioButton.setText("Create scenario map".tr())
// for newly created scenarios read players from tileMap
val players = getPlayersFromMap(mapEditorScreen.tileMap)
mapEditorScreen.gameSetupInfo.gameParameters.players = players
}
add(scenarioButton).row()
scenarioButton.onClick {
close()
UncivGame.Current.setScreen(GameParametersScreen(mapEditorScreen).apply {
playerPickerTable.noRandom = true
})
}
}
private fun Popup.addExitMapEditorButton() {
val exitMapEditorButton = "Exit map editor".toTextButton()

View File

@ -5,7 +5,6 @@ import com.badlogic.gdx.math.Vector2
import com.badlogic.gdx.scenes.scene2d.InputEvent
import com.badlogic.gdx.scenes.scene2d.InputListener
import com.badlogic.gdx.scenes.scene2d.actions.Actions
import com.unciv.logic.map.ScenarioMap
import com.unciv.logic.map.TileInfo
import com.unciv.logic.map.TileMap
import com.unciv.models.ruleset.Ruleset
@ -16,9 +15,7 @@ import com.unciv.ui.utils.*
class MapEditorScreen(): CameraStageBaseScreen() {
var mapName = ""
var tileMap = TileMap()
var scenarioName = "" // when loading map: mapName is taken as default for scenarioName
var scenarioMap: ScenarioMap? = null // main indicator whether scenario information is present
var ruleset = Ruleset().apply { add(RulesetCache.getBaseRuleset()) } // Since we change this in scenarios, we can't take the base ruleset directly
var ruleset = Ruleset().apply { add(RulesetCache.getBaseRuleset()) }
var gameSetupInfo = GameSetupInfo()
lateinit var mapHolder: EditorMapHolder
@ -33,20 +30,6 @@ class MapEditorScreen(): CameraStageBaseScreen() {
initialize()
}
constructor(scenarioMap: ScenarioMap, scenarioName: String = "") : this() {
tileMap = scenarioMap.tileMap
mapName = scenarioName
this.scenarioMap = scenarioMap
this.scenarioName = scenarioName
gameSetupInfo.gameParameters = scenarioMap.gameParameters
// Since the ruleset is referenced directly from other places, we can't just replace it directly
ruleset.clear()
ruleset.add(RulesetCache.getComplexRuleset(scenarioMap.gameParameters))
initialize()
}
fun initialize() {
ImageGetter.setNewRuleset(ruleset)
tileMap.setTransients(ruleset,false)
@ -152,8 +135,6 @@ class MapEditorScreen(): CameraStageBaseScreen() {
game.setScreen(MapEditorScreen(mapHolder.tileMap))
}
}
fun hasScenario() = this.scenarioMap != null
}

View File

@ -56,12 +56,6 @@ class TileEditorOptionsTable(val mapEditorScreen: MapEditorScreen): Table(Camera
.onClick { setImprovements() }
tabPickerTable.add(improvementsButton)
// debug Scenario mode
if (UncivGame.Current.settings.extendedMapEditor && mapEditorScreen.hasScenario()) {
val unitsButton = "Units".toTextButton().onClick { setUnits() }
tabPickerTable.add(unitsButton)
}
tabPickerTable.pack()
val sliderTab = Table()
@ -160,59 +154,29 @@ class TileEditorOptionsTable(val mapEditorScreen: MapEditorScreen): Table(Camera
val nationTable = Table()
if (UncivGame.Current.settings.extendedMapEditor) {
/** new scenario/improvements functionality
* We shouldn't be able to edit random players or spectators
* */
for (player in gameParameters.players) {
val playerIndex = gameParameters.players.indexOf(player) + 1
if (player.chosenCiv == Constants.random || player.chosenCiv == Constants.spectator)
continue
val nation = ruleset.nations[player.chosenCiv]!!
val nationImage = ImageGetter.getNationIndicator(nation, 40f)
nationImage.onClick {
val improvementName = "StartingLocation " + nation.name
tileAction = {
it.improvement = improvementName
for (tileGroup in mapEditorScreen.mapHolder.tileGroups.values) {
val tile = tileGroup.tileInfo
if (tile.improvement == improvementName && tile != it)
tile.improvement = null
tile.setTerrainTransients()
tileGroup.update()
}
/** old way improvements for all civs
* */
for (nation in ruleset.nations.values) {
if (nation.isSpectator()) continue // no improvements for spectator
val nationImage = getHex(Color.WHITE, ImageGetter.getNationIndicator(nation, 40f))
nationImage.onClick {
val improvementName = "StartingLocation " + nation.name
tileAction = {
it.improvement = improvementName
for (tileGroup in mapEditorScreen.mapHolder.tileGroups.values) {
val tile = tileGroup.tileInfo
if (tile.improvement == improvementName && tile != it)
tile.improvement = null
tile.setTerrainTransients()
tileGroup.update()
}
val nationIcon = getHex(Color.WHITE, ImageGetter.getNationIndicator(nation, 40f))
setCurrentHex(nationIcon, "Player [$playerIndex] starting location")
}
nationTable.add(nationImage).row()
}
} else {
/** old way improvements for all civs
* */
for (nation in ruleset.nations.values) {
if (nation.isSpectator()) continue // no improvements for spectator
val nationImage = getHex(Color.WHITE, ImageGetter.getNationIndicator(nation, 40f))
nationImage.onClick {
val improvementName = "StartingLocation " + nation.name
tileAction = {
it.improvement = improvementName
for (tileGroup in mapEditorScreen.mapHolder.tileGroups.values) {
val tile = tileGroup.tileInfo
if (tile.improvement == improvementName && tile != it)
tile.improvement = null
tile.setTerrainTransients()
tileGroup.update()
}
}
val nationIcon = getHex(Color.WHITE, ImageGetter.getNationIndicator(nation, 40f))
setCurrentHex(nationIcon, "[${nation.name}] starting location")
}
nationTable.add(nationImage).row()
val nationIcon = getHex(Color.WHITE, ImageGetter.getNationIndicator(nation, 40f))
setCurrentHex(nationIcon, "[${nation.name}] starting location")
}
nationTable.add(nationImage).row()
}
editorPickTable.add(AutoScrollPane(nationTable).apply { setScrollingDisabled(true, false) }).height(scrollPanelHeight)

View File

@ -47,8 +47,6 @@ class GameOptionsTable(val previousScreen: IPreviousScreen, val updatePlayerPick
checkboxTable.addBarbariansCheckbox()
checkboxTable.addOneCityChallengeCheckbox()
checkboxTable.addNuclearWeaponsCheckbox()
if(UncivGame.Current.settings.extendedMapEditor)
checkboxTable.addGodmodeCheckbox()
checkboxTable.addIsOnlineMultiplayerCheckbox()
checkboxTable.addModCheckboxes()
add(checkboxTable).colspan(2).row()
@ -76,15 +74,13 @@ class GameOptionsTable(val previousScreen: IPreviousScreen, val updatePlayerPick
addCheckbox("Enable nuclear weapons", gameParameters.nuclearWeaponsEnabled)
{ gameParameters.nuclearWeaponsEnabled = it }
private fun Table.addGodmodeCheckbox() =
addCheckbox("Scenario Editor", gameParameters.godMode, lockable = false)
{ gameParameters.godMode = it }
private fun Table.addIsOnlineMultiplayerCheckbox() =
addCheckbox("Online Multiplayer", gameParameters.isOnlineMultiplayer)
{ gameParameters.isOnlineMultiplayer = it
updatePlayerPickerTable("") }
private fun Table.addIsOnlineMultiplayerCheckbox() =
addCheckbox("Online Multiplayer", gameParameters.isOnlineMultiplayer)
{
gameParameters.isOnlineMultiplayer = it
updatePlayerPickerTable("")
}
private fun addCityStatesSelectBox() {
add("{Number of City-States}:".toLabel())
@ -132,7 +128,7 @@ class GameOptionsTable(val previousScreen: IPreviousScreen, val updatePlayerPick
}
private fun Table.addEraSelectBox() {
if (ruleset.technologies.isEmpty()) return // scenario with no techs
if (ruleset.technologies.isEmpty()) return // mod with no techs
val eras = ruleset.technologies.values.filter { !it.uniques.contains("Starting tech") }.map { it.era() }.distinct()
addSelectBox("{Starting Era}:", eras, gameParameters.startingEra)
{ gameParameters.startingEra = it }
@ -147,7 +143,6 @@ class GameOptionsTable(val previousScreen: IPreviousScreen, val updatePlayerPick
val victoryConditionsTable = Table().apply { defaults().pad(5f) }
for (victoryType in VictoryType.values()) {
if (victoryType == VictoryType.Neutral) continue
if (previousScreen !is GameParametersScreen && victoryType == VictoryType.Scenario) continue // scenario victory is only available for scenarios
val victoryCheckbox = CheckBox(victoryType.name.tr(), CameraStageBaseScreen.skin)
victoryCheckbox.name = victoryType.name
victoryCheckbox.isChecked = gameParameters.victoryTypes.contains(victoryType)
@ -178,10 +173,7 @@ class GameOptionsTable(val previousScreen: IPreviousScreen, val updatePlayerPick
}
fun Table.addModCheckboxes() {
val modRulesets = RulesetCache.values.filter {
it.name != ""
&& (it.name in gameParameters.mods || !it.modOptions.uniques.contains("Scenario only"))
} // Don't allow scenario mods for a regular 'new game'
val modRulesets = RulesetCache.values.filter { it.name != "" }
val baseRulesetCheckboxes = ArrayList<CheckBox>()
val extentionRulesetModButtons = ArrayList<CheckBox>()

View File

@ -5,9 +5,6 @@ import com.badlogic.gdx.files.FileHandle
import com.badlogic.gdx.scenes.scene2d.ui.SelectBox
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.utils.Array
import com.unciv.UncivGame
import com.unciv.logic.GameInfo
import com.unciv.logic.GameSaver
import com.unciv.logic.MapSaver
import com.unciv.logic.map.MapType
import com.unciv.ui.utils.CameraStageBaseScreen
@ -20,9 +17,6 @@ class MapOptionsTable(val newGameScreen: NewGameScreen): Table() {
private var mapTypeSpecificTable = Table()
private val generatedMapOptionsTable = MapParametersTable(mapParameters)
private val savedMapOptionsTable = Table()
private val scenarioMapOptionsTable = Table()
private val scenarioOptionsTable = Table()
var selectedScenarioSaveGame: GameInfo? = null
lateinit var mapTypeSelectBox: TranslatedSelectBox
init {
@ -31,32 +25,10 @@ class MapOptionsTable(val newGameScreen: NewGameScreen): Table() {
addMapTypeSelection()
}
fun selectSavedGameAsScenario(gameFile: FileHandle){
val savedGame = GameSaver.loadGameFromFile(gameFile)
mapParameters.type = MapType.scenario
mapParameters.name = gameFile.name()
newGameScreen.gameSetupInfo.gameParameters = savedGame.gameParameters
newGameScreen.gameSetupInfo.mapParameters = savedGame.tileMap.mapParameters
newGameScreen.updateRuleset()
newGameScreen.updateTables()
selectedScenarioSaveGame = savedGame
}
fun getScenarioFiles(): Sequence<FileHandle> {
val localSaveScenarios = GameSaver.getSaves().filter { it.name().toLowerCase().endsWith("scenario") }
val modScenarios = Gdx.files.local("mods").list().asSequence()
.filter { it.child("scenarios").exists() }.flatMap { it.child("scenarios").list().asSequence() }
return localSaveScenarios + modScenarios
}
private fun addMapTypeSelection() {
add("{Map Type}:".toLabel())
val mapTypes = arrayListOf("Generated")
if (MapSaver.getMaps().isNotEmpty()) mapTypes.add(MapType.custom)
if (MapSaver.getScenarios().isNotEmpty() && UncivGame.Current.settings.extendedMapEditor)
mapTypes.add(MapType.scenarioMap)
if (getScenarioFiles().any())
mapTypes.add(MapType.scenario)
mapTypeSelectBox = TranslatedSelectBox(mapTypes, "Generated", CameraStageBaseScreen.skin)
val mapFileSelectBox = getMapFileSelectBox()
@ -67,31 +39,6 @@ class MapOptionsTable(val newGameScreen: NewGameScreen): Table() {
.right().row()
val scenarioMapSelectBox = getScenarioFileSelectBox()
scenarioMapOptionsTable.defaults().pad(5f)
scenarioMapOptionsTable.add("{Scenario file}:".toLabel()).left()
// because SOME people gotta give the hugest names to their maps
scenarioMapOptionsTable.add(scenarioMapSelectBox).maxWidth(newGameScreen.stage.width / 2)
.right().row()
val scenarioFiles = getScenarioFiles()
val scenarioSelectBox = SelectBox<FileHandleWrapper>(CameraStageBaseScreen.skin)
if (scenarioFiles.any()) {
for (savedGame in getScenarioFiles()) {
try { // Sometimes, we have scenarios that are dependent on mods that we don't have and so they can't be loaded.
GameSaver.loadGameFromFile(savedGame)
scenarioSelectBox.items.add(FileHandleWrapper(savedGame))
} catch (ex: Exception) {}
}
scenarioSelectBox.items = scenarioSelectBox.items // it doesn't register them until you do this.
scenarioSelectBox.selected = scenarioSelectBox.items.first()
// needs to be after the item change, so it doesn't activate before we choose the Scenario maptype
scenarioSelectBox.onChange { selectSavedGameAsScenario(scenarioSelectBox.selected.fileHandle) }
scenarioOptionsTable.add("{Scenario file}:".toLabel()).left()
scenarioOptionsTable.add(scenarioSelectBox)
}
fun updateOnMapTypeChange() {
mapTypeSpecificTable.clear()
@ -100,20 +47,6 @@ class MapOptionsTable(val newGameScreen: NewGameScreen): Table() {
mapParameters.name = mapFileSelectBox.selected.toString()
mapTypeSpecificTable.add(savedMapOptionsTable)
newGameScreen.unlockTables()
} else if (mapTypeSelectBox.selected.value == MapType.scenarioMap) {
mapParameters.type = MapType.scenarioMap
mapParameters.name = scenarioMapSelectBox.selected.toString()
mapTypeSpecificTable.add(scenarioMapOptionsTable)
val scenario = MapSaver.loadScenario(mapParameters.name)
newGameScreen.gameSetupInfo.gameParameters = scenario.gameParameters
newGameScreen.gameSetupInfo.mapParameters = mapParameters
newGameScreen.updateRuleset()
// update PlayerTable and GameOptionsTable
newGameScreen.lockTables()
} else if(mapTypeSelectBox.selected.value == MapType.scenario){
selectSavedGameAsScenario(scenarioSelectBox.selected.fileHandle)
mapTypeSpecificTable.add(scenarioOptionsTable)
newGameScreen.lockTables()
} else { // generated map
mapParameters.name = ""
mapParameters.type = generatedMapOptionsTable.mapTypeSelectBox.selected.value
@ -138,55 +71,34 @@ class MapOptionsTable(val newGameScreen: NewGameScreen): Table() {
val mapFiles = Array<FileHandleWrapper>()
for (mapFile in MapSaver.getMaps())
mapFiles.add(FileHandleWrapper(mapFile))
for(mod in Gdx.files.local("mods").list()){
for (mod in Gdx.files.local("mods").list()) {
val mapsFolder = mod.child("maps")
if(mapsFolder.exists())
for(map in mapsFolder.list())
if (mapsFolder.exists())
for (map in mapsFolder.list())
mapFiles.add(FileHandleWrapper(map))
}
mapFileSelectBox.items = mapFiles
val selectedItem = mapFiles.firstOrNull { it.fileHandle.name()==mapParameters.name }
val selectedItem = mapFiles.firstOrNull { it.fileHandle.name() == mapParameters.name }
if (selectedItem != null) {
mapFileSelectBox.selected = selectedItem
newGameScreen.gameSetupInfo.mapFile = mapFileSelectBox.selected.fileHandle
}
else if (!mapFiles.isEmpty) {
} else if (!mapFiles.isEmpty) {
mapFileSelectBox.selected = mapFiles.first()
newGameScreen.gameSetupInfo.mapFile = mapFileSelectBox.selected.fileHandle
}
mapFileSelectBox.onChange {
val mapFile = mapFileSelectBox.selected.fileHandle
val mapFile = mapFileSelectBox.selected.fileHandle
mapParameters.name = mapFile.name()
newGameScreen.gameSetupInfo.mapFile = mapFile
}
return mapFileSelectBox
}
private fun getScenarioFileSelectBox(): SelectBox<FileHandleWrapper> {
val scenarioFileSelectBox = SelectBox<FileHandleWrapper>(CameraStageBaseScreen.skin)
val scenarioFiles = Array<FileHandleWrapper>()
for (scenarioName in MapSaver.getScenarios()) scenarioFiles.add(FileHandleWrapper(scenarioName))
scenarioFileSelectBox.items = scenarioFiles
val selectedItem = scenarioFiles.firstOrNull { it.fileHandle.name()==mapParameters.name }
if(selectedItem!=null ) scenarioFileSelectBox.selected = selectedItem
scenarioFileSelectBox.onChange {
mapParameters.name = scenarioFileSelectBox.selected!!.fileHandle.name()
val scenario = MapSaver.loadScenario(mapParameters.name)
newGameScreen.apply {
gameSetupInfo.gameParameters = scenario.gameParameters
newGameOptionsTable.gameParameters = scenario.gameParameters
newGameOptionsTable.reloadRuleset()
updateTables()
}
}
return scenarioFileSelectBox
}
// The SelectBox auto displays the text a object.toString(), which on the FileHandle itself includes the folder path.
// So we wrap it in another object with a custom toString()
class FileHandleWrapper(val fileHandle: FileHandle){
class FileHandleWrapper(val fileHandle: FileHandle) {
override fun toString() = fileHandle.name()
}
}

View File

@ -1,6 +1,5 @@
package com.unciv.ui.newgamescreen
import com.unciv.ui.utils.AutoScrollPane as ScrollPane
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.files.FileHandle
import com.badlogic.gdx.scenes.scene2d.ui.SelectBox
@ -10,7 +9,6 @@ import com.unciv.UncivGame
import com.unciv.logic.*
import com.unciv.logic.civilization.PlayerType
import com.unciv.logic.map.MapParameters
import com.unciv.logic.map.MapType
import com.unciv.models.metadata.GameParameters
import com.unciv.models.ruleset.RulesetCache
import com.unciv.models.translations.tr
@ -19,6 +17,7 @@ import com.unciv.ui.utils.*
import com.unciv.ui.worldscreen.mainmenu.OnlineMultiplayer
import java.util.*
import kotlin.concurrent.thread
import com.unciv.ui.utils.AutoScrollPane as ScrollPane
class GameSetupInfo(var gameId:String, var gameParameters: GameParameters, var mapParameters: MapParameters) {
@ -37,9 +36,10 @@ class GameSetupInfo(var gameId:String, var gameParameters: GameParameters, var m
}
class NewGameScreen(previousScreen:CameraStageBaseScreen, _gameSetupInfo: GameSetupInfo?=null): IPreviousScreen, PickerScreen() {
override val gameSetupInfo = _gameSetupInfo ?: GameSetupInfo()
override val gameSetupInfo = _gameSetupInfo ?: GameSetupInfo()
override var ruleset = RulesetCache.getComplexRuleset(gameSetupInfo.gameParameters) // needs to be set because the gameoptionstable etc. depend on this
var newGameOptionsTable = GameOptionsTable(this) { desiredCiv: String -> playerPickerTable.update(desiredCiv) }
// Has to be defined before the mapOptionsTable, since the mapOptionsTable refers to it on init
var playerPickerTable = PlayerPickerTable(this, gameSetupInfo.gameParameters)
var mapOptionsTable = MapOptionsTable(this)
@ -103,19 +103,7 @@ class NewGameScreen(previousScreen:CameraStageBaseScreen, _gameSetupInfo: GameSe
private fun newGameThread() {
try {
if (mapOptionsTable.mapTypeSelectBox.selected.value == MapType.scenario) {
newGame = mapOptionsTable.selectedScenarioSaveGame
// to take the definition of which players are human and which are AI
for (player in gameSetupInfo.gameParameters.players) {
newGame!!.getCivilization(player.chosenCiv).playerType = player.playerType
}
if (newGame!!.getCurrentPlayerCivilization().playerType == PlayerType.AI) {
newGame!!.setTransients()
newGame!!.nextTurn() // can't start the game on an AI turn
}
newGame!!.gameParameters.godMode = false
}
else newGame = GameStarter.startNewGame(gameSetupInfo)
newGame = GameStarter.startNewGame(gameSetupInfo)
} catch (exception: Exception) {
Gdx.app.postRunnable {
val cantMakeThatMapPopup = Popup(this)
@ -155,7 +143,7 @@ class NewGameScreen(previousScreen:CameraStageBaseScreen, _gameSetupInfo: GameSe
Gdx.graphics.requestRendering()
}
fun updateRuleset(){
fun updateRuleset() {
ruleset.clear()
ruleset.add(RulesetCache.getComplexRuleset(gameSetupInfo.gameParameters))
}

View File

@ -22,7 +22,7 @@ import com.unciv.ui.utils.*
import java.util.*
/**
* This [Table] is used to pick or edit players information for new game/scenario creation.
* This [Table] is used to pick or edit players information for new game creation.
* Could be inserted to [NewGameScreen], [GameParametersScreen] or any other [Screen]
* which provides [GameSetupInfo] and [Ruleset].
* Upon player changes updates property [gameParameters]. Also updates available nations when mod changes.
@ -37,7 +37,7 @@ class PlayerPickerTable(val previousScreen: IPreviousScreen, var gameParameters:
val playerListTable = Table()
val civBlocksWidth = previousScreen.stage.width / 3
/** Locks player table for editing, used during new game creation with scenario.*/
/** Locks player table for editing, currently unused, was previously used for scenarios and could be useful in the future.*/
var locked = false
/** No random civilization is available, used during map editing.*/

View File

@ -28,7 +28,7 @@ class TechPickerScreen(internal val civInfo: CivilizationInfo, centerOnTech: Tec
/** We need this to be a separate table, and NOT the topTable, because *inhales*
* When call setConnectingLines we need to pack() the table so that the lines will align correctly, BUT
* this causes the table to be SMALLER THAN THE SCREEN for small tech trees e.g. scenarios,
* this causes the table to be SMALLER THAN THE SCREEN for small tech trees from mods,
* meaning the tech tree is in a crumpled heap at the lower-left corner of the screen
* Having this be a separate table allows us to leave the TopTable as is (that is: auto-width to fit the scrollPane)
* leaving us the juicy small tech tree right in the center.

View File

@ -42,7 +42,7 @@ class OptionsPopup(val previousScreen:CameraStageBaseScreen) : Popup(previousScr
add(scrollPane).maxHeight(screen.stage.height * 0.6f).row()
addCloseButton {
if(previousScreen is WorldScreen)
if (previousScreen is WorldScreen)
previousScreen.enableNextTurnButtonAfterOptions()
}
@ -50,11 +50,11 @@ class OptionsPopup(val previousScreen:CameraStageBaseScreen) : Popup(previousScr
center(previousScreen.stage)
}
private fun addHeader (text: String) {
private fun addHeader(text: String) {
innerTable2.add(text.toLabel(fontSize = 24)).colspan(2).padTop(if (innerTable2.cells.isEmpty) 0f else 20f).row()
}
private fun addYesNoRow (text: String, initialValue: Boolean, updateWorld: Boolean = false, action: ((Boolean) -> Unit)) {
private fun addYesNoRow(text: String, initialValue: Boolean, updateWorld: Boolean = false, action: ((Boolean) -> Unit)) {
innerTable2.add(text.toLabel())
val button = YesNoButton(initialValue, CameraStageBaseScreen.skin) {
action(it)
@ -82,13 +82,13 @@ class OptionsPopup(val previousScreen:CameraStageBaseScreen) : Popup(previousScr
addHeader("Display options")
addYesNoRow ("Show worked tiles", settings.showWorkedTiles, true) { settings.showWorkedTiles = it }
addYesNoRow ("Show resources and improvements", settings.showResourcesAndImprovements, true) { settings.showResourcesAndImprovements = it }
addYesNoRow ("Show tile yields", settings.showTileYields, true) { settings.showTileYields = it } // JN
addYesNoRow ("Show tutorials", settings.showTutorials, true) {settings.showTutorials = it }
addYesNoRow ("Show minimap", settings.showMinimap, true) { settings.showMinimap = it }
addYesNoRow ("Show pixel units", settings.showPixelUnits, true) { settings.showPixelUnits = it }
addYesNoRow ("Show pixel improvements", settings.showPixelImprovements, true) { settings.showPixelImprovements = it }
addYesNoRow("Show worked tiles", settings.showWorkedTiles, true) { settings.showWorkedTiles = it }
addYesNoRow("Show resources and improvements", settings.showResourcesAndImprovements, true) { settings.showResourcesAndImprovements = it }
addYesNoRow("Show tile yields", settings.showTileYields, true) { settings.showTileYields = it } // JN
addYesNoRow("Show tutorials", settings.showTutorials, true) { settings.showTutorials = it }
addYesNoRow("Show minimap", settings.showMinimap, true) { settings.showMinimap = it }
addYesNoRow("Show pixel units", settings.showPixelUnits, true) { settings.showPixelUnits = it }
addYesNoRow("Show pixel improvements", settings.showPixelImprovements, true) { settings.showPixelImprovements = it }
addLanguageSelectBox()
@ -96,7 +96,7 @@ class OptionsPopup(val previousScreen:CameraStageBaseScreen) : Popup(previousScr
addTileSetSelectBox()
addYesNoRow ("Continuous rendering", settings.continuousRendering) {
addYesNoRow("Continuous rendering", settings.continuousRendering) {
settings.continuousRendering = it
Gdx.graphics.isContinuousRendering = it
}
@ -106,22 +106,22 @@ class OptionsPopup(val previousScreen:CameraStageBaseScreen) : Popup(previousScr
addHeader("Gameplay options")
addYesNoRow ("Check for idle units", settings.checkForDueUnits, true) { settings.checkForDueUnits = it }
addYesNoRow ("Move units with a single tap", settings.singleTapMove) { settings.singleTapMove = it }
addYesNoRow ("Movement assumes unknown tiles to be passable", settings.unitMovementIncludesImpassibles)
{ settings.unitMovementIncludesImpassibles = it }
addYesNoRow ("Auto-assign city production", settings.autoAssignCityProduction, true) {
addYesNoRow("Check for idle units", settings.checkForDueUnits, true) { settings.checkForDueUnits = it }
addYesNoRow("Move units with a single tap", settings.singleTapMove) { settings.singleTapMove = it }
addYesNoRow("Movement assumes unknown tiles to be passable", settings.unitMovementIncludesImpassibles)
{ settings.unitMovementIncludesImpassibles = it }
addYesNoRow("Auto-assign city production", settings.autoAssignCityProduction, true) {
settings.autoAssignCityProduction = it
if (it && previousScreen is WorldScreen &&
previousScreen.viewingCiv.isCurrentPlayer() && previousScreen.viewingCiv.playerType == PlayerType.Human) {
previousScreen.viewingCiv.isCurrentPlayer() && previousScreen.viewingCiv.playerType == PlayerType.Human) {
previousScreen.gameInfo.currentPlayerCiv.cities.forEach { city ->
city.cityConstructions.chooseNextConstruction()
}
}
}
addYesNoRow ("Auto-build roads", settings.autoBuildingRoads) { settings.autoBuildingRoads = it }
addYesNoRow ("Automated workers replace improvements", settings.automatedWorkersReplaceImprovements) { settings.automatedWorkersReplaceImprovements = it }
addYesNoRow ("Order trade offers by amount", settings.orderTradeOffersByAmount) { settings.orderTradeOffersByAmount = it }
addYesNoRow("Auto-build roads", settings.autoBuildingRoads) { settings.autoBuildingRoads = it }
addYesNoRow("Automated workers replace improvements", settings.automatedWorkersReplaceImprovements) { settings.automatedWorkersReplaceImprovements = it }
addYesNoRow("Order trade offers by amount", settings.orderTradeOffersByAmount) { settings.orderTradeOffersByAmount = it }
addAutosaveTurnsSelectBox()
@ -130,8 +130,6 @@ class OptionsPopup(val previousScreen:CameraStageBaseScreen) : Popup(previousScr
addHeader("Other options")
addYesNoRow("Extended map editor", settings.extendedMapEditor) { settings.extendedMapEditor = it }
addSoundEffectsVolumeSlider()
addMusicVolumeSlider()
addTranslationGeneration()
@ -206,7 +204,7 @@ class OptionsPopup(val previousScreen:CameraStageBaseScreen) : Popup(previousScr
text += "\n\n" + mod.name + "\n\n" + modLinks
}
val popup = Popup(screen)
popup.add(ScrollPane(text.toLabel()).apply { setOverscroll(false,false) })
popup.add(ScrollPane(text.toLabel()).apply { setOverscroll(false, false) })
.maxHeight(screen.stage.height / 2).row()
popup.addCloseButton()
popup.open(true)