From 453f5588ac90ef4d6bfb33a53c23a005e6bff505 Mon Sep 17 00:00:00 2001 From: Alexander Korolyov <49795502+alkorolyov@users.noreply.github.com> Date: Thu, 6 May 2021 17:39:28 +0200 Subject: [PATCH] Custom map size (#2876) * Adding custom map sizes. Initial commit * Custom map sizes UI update * Custom map size with rectangular shape * Added compatibility with older Maps and Game saves * Fixed build errors and added warning messages Co-authored-by: Yair Morgenstern --- .../jsons/translations/template.properties | 2 + core/src/com/unciv/Constants.kt | 7 ++ core/src/com/unciv/MainMenuScreen.kt | 4 +- core/src/com/unciv/logic/GameInfo.kt | 7 ++ core/src/com/unciv/logic/HexMath.kt | 10 +++ .../unciv/logic/civilization/TechManager.kt | 10 +-- core/src/com/unciv/logic/map/MapParameters.kt | 47 +++++++++- .../logic/map/mapgenerator/MapGenerator.kt | 9 +- .../map/mapgenerator/MapLandmassGenerator.kt | 6 +- .../mapgenerator/NaturalWonderGenerator.kt | 2 +- .../ui/newgamescreen/MapParametersTable.kt | 89 ++++++++++++++++--- core/src/com/unciv/ui/worldscreen/Minimap.kt | 1 - .../com/unciv/app/desktop/ConsoleLauncher.kt | 5 +- 13 files changed, 170 insertions(+), 29 deletions(-) diff --git a/android/assets/jsons/translations/template.properties b/android/assets/jsons/translations/template.properties index 84336812c5..ff0a881207 100644 --- a/android/assets/jsons/translations/template.properties +++ b/android/assets/jsons/translations/template.properties @@ -270,6 +270,8 @@ Extension mods: = World Wrap = World wrap maps are very memory intensive - creating large world wrap maps on Android can lead to crashes! = +Anything above 80 by 50 may work very slowly on Android! = +Anything above 40 may work very slowly on Android! = # Multiplayer diff --git a/core/src/com/unciv/Constants.kt b/core/src/com/unciv/Constants.kt index 6b56a6fdf8..26aa3db5cc 100644 --- a/core/src/com/unciv/Constants.kt +++ b/core/src/com/unciv/Constants.kt @@ -76,4 +76,11 @@ object Constants { const val futureEra = "Future era" const val barbarians = "Barbarians" const val spectator = "Spectator" + + const val tiny = "Tiny" + const val small = "Small" + const val medium = "Medium" + const val large = "Large" + const val huge = "Huge" + const val custom = "Custom" } \ No newline at end of file diff --git a/core/src/com/unciv/MainMenuScreen.kt b/core/src/com/unciv/MainMenuScreen.kt index 91029af82b..41a4457afc 100644 --- a/core/src/com/unciv/MainMenuScreen.kt +++ b/core/src/com/unciv/MainMenuScreen.kt @@ -10,7 +10,7 @@ import com.unciv.logic.GameSaver import com.unciv.logic.GameStarter import com.unciv.logic.map.mapgenerator.MapGenerator import com.unciv.logic.map.MapParameters -import com.unciv.logic.map.MapSize +import com.unciv.logic.map.MapSizeNew import com.unciv.logic.map.MapType import com.unciv.models.ruleset.RulesetCache import com.unciv.models.translations.tr @@ -50,7 +50,7 @@ class MainMenuScreen: CameraStageBaseScreen() { thread(name = "ShowMapBackground") { val newMap = MapGenerator(RulesetCache.getBaseRuleset()) - .generateMap(MapParameters().apply { size = MapSize.Small; type = MapType.default }) + .generateMap(MapParameters().apply { mapSize = MapSizeNew(Constants.small); type = MapType.default }) Gdx.app.postRunnable { // for GL context ImageGetter.setNewRuleset(RulesetCache.getBaseRuleset()) val mapHolder = EditorMapHolder(MapEditorScreen(), newMap) diff --git a/core/src/com/unciv/logic/GameInfo.kt b/core/src/com/unciv/logic/GameInfo.kt index 45da844c2b..8bc25bf99d 100644 --- a/core/src/com/unciv/logic/GameInfo.kt +++ b/core/src/com/unciv/logic/GameInfo.kt @@ -7,6 +7,7 @@ import com.unciv.logic.automation.NextTurnAutomation import com.unciv.logic.city.CityConstructions import com.unciv.logic.city.PerpetualConstruction import com.unciv.logic.civilization.* +import com.unciv.logic.map.MapSizeNew import com.unciv.logic.map.TileInfo import com.unciv.logic.map.TileMap import com.unciv.models.metadata.GameParameters @@ -272,6 +273,12 @@ class GameInfo { if (currentPlayer == "") currentPlayer = civilizations.first { it.isPlayerCivilization() }.civName currentPlayerCiv = getCivilization(currentPlayer) + // as of version 3.9.18, added new custom map size + // empty mapSize name and non-custom map type means - it is old style map size, + // therefore we need to create new mapSize property + if (tileMap.mapParameters.mapSize.name == "" && tileMap.mapParameters.type != Constants.custom) + tileMap.mapParameters.mapSize = MapSizeNew(tileMap.mapParameters.size.name) + // this is separated into 2 loops because when we activate updateVisibleTiles in civ.setTransients, // we try to find new civs, and we check if civ is barbarian, which we can't know unless the gameInfo is already set. for (civInfo in civilizations) civInfo.gameInfo = this diff --git a/core/src/com/unciv/logic/HexMath.kt b/core/src/com/unciv/logic/HexMath.kt index 6832f227d6..fdfff1ef12 100644 --- a/core/src/com/unciv/logic/HexMath.kt +++ b/core/src/com/unciv/logic/HexMath.kt @@ -41,6 +41,16 @@ object HexMath { return Vector2(width, height) } + /** Returns a radius of a hexagonal map that have approximately the same number of + * tiles as a rectangular map of a given width/height + */ + fun getEquivalentHexagonalRadius(width: Int, height: Int): Int { + val nTiles = width * height.toFloat() + if (nTiles < 1) return 0 + val radius = ((sqrt(12*nTiles - 3) - 3) / 6).roundToInt() + return radius + } + fun getAdjacentVectors(origin: Vector2): ArrayList { val vectors = arrayListOf( Vector2(1f, 0f), diff --git a/core/src/com/unciv/logic/civilization/TechManager.kt b/core/src/com/unciv/logic/civilization/TechManager.kt index c59b046e57..0f8a012561 100644 --- a/core/src/com/unciv/logic/civilization/TechManager.kt +++ b/core/src/com/unciv/logic/civilization/TechManager.kt @@ -1,7 +1,7 @@ package com.unciv.logic.civilization +import com.unciv.Constants import com.unciv.logic.city.CityInfo -import com.unciv.logic.map.MapSize import com.unciv.logic.map.RoadStatus import com.unciv.logic.map.TileInfo import com.unciv.models.ruleset.Unique @@ -84,10 +84,10 @@ class TechManager { // https://forums.civfanatics.com/threads/the-mechanics-of-overflow-inflation.517970/ techCost /= 1 + techsResearchedKnownCivs / undefeatedCivs.toFloat() * 0.3f // http://www.civclub.net/bbs/forum.php?mod=viewthread&tid=123976 - val worldSizeModifier = when (civInfo.gameInfo.tileMap.mapParameters.size) { - MapSize.Medium -> floatArrayOf(1.1f, 0.05f) - MapSize.Large -> floatArrayOf(1.2f, 0.03f) - MapSize.Huge -> floatArrayOf(1.3f, 0.02f) + val worldSizeModifier = when (civInfo.gameInfo.tileMap.mapParameters.mapSize.name) { + Constants.medium -> floatArrayOf(1.1f, 0.05f) + Constants.large -> floatArrayOf(1.2f, 0.03f) + Constants.huge -> floatArrayOf(1.3f, 0.02f) else -> floatArrayOf(1f, 0.05f) } techCost *= worldSizeModifier[0] diff --git a/core/src/com/unciv/logic/map/MapParameters.kt b/core/src/com/unciv/logic/map/MapParameters.kt index 41fb9ac99c..daccdb3797 100644 --- a/core/src/com/unciv/logic/map/MapParameters.kt +++ b/core/src/com/unciv/logic/map/MapParameters.kt @@ -1,5 +1,10 @@ package com.unciv.logic.map +import com.unciv.Constants +import com.unciv.logic.HexMath.getEquivalentHexagonalRadius +import com.unciv.logic.HexMath.getEquivalentRectangularSize + + enum class MapSize(val radius: Int) { Tiny(10), Small(15), @@ -8,6 +13,44 @@ enum class MapSize(val radius: Int) { Huge(40) } +class MapSizeNew { + var radius = 0 + var width = 0 + var height = 0 + var name = "" + + /** Needed for Json parsing */ + constructor() + + constructor(name: String) { + /** Hard coded values from getEquivalentRectangularSize() */ + when (name) { + Constants.tiny -> { radius = 10; width = 23; height = 15 } + Constants.small -> { radius = 15; width = 33; height = 21 } + Constants.medium -> { radius = 20; width = 44; height = 29 } + Constants.large -> { radius = 30; width = 66; height = 43 } + Constants.huge -> { radius = 40; width = 87; height = 57 } + } + } + constructor(radius: Int) { + name = Constants.custom + this.radius = radius + val size = getEquivalentRectangularSize(radius) + this.width = size.x.toInt() + this.height = size.y.toInt() + } + + constructor(width: Int, height: Int) { + name = Constants.custom + this.width = width + this.height = height + this.radius = getEquivalentHexagonalRadius(width, height) + + } + + +} + object MapShape { const val hexagonal = "Hexagonal" const val rectangular = "Rectangular" @@ -33,7 +76,9 @@ class MapParameters { var name = "" var type = MapType.pangaea var shape = MapShape.hexagonal - var size: MapSize = MapSize.Medium + @Deprecated("replaced by mapSize since 3.19.18") + var size = MapSize.Medium + var mapSize = MapSizeNew(Constants.medium) var noRuins = false var noNaturalWonders = false var worldWrap = false diff --git a/core/src/com/unciv/logic/map/mapgenerator/MapGenerator.kt b/core/src/com/unciv/logic/map/mapgenerator/MapGenerator.kt index 70e0bd82a4..7f703d6b5e 100644 --- a/core/src/com/unciv/logic/map/mapgenerator/MapGenerator.kt +++ b/core/src/com/unciv/logic/map/mapgenerator/MapGenerator.kt @@ -21,16 +21,15 @@ class MapGenerator(val ruleset: Ruleset) { private var randomness = MapGenerationRandomness() fun generateMap(mapParameters: MapParameters, seed: Long = System.currentTimeMillis()): TileMap { - val mapRadius = mapParameters.size.radius + val mapSize = mapParameters.mapSize val mapType = mapParameters.type val map: TileMap if (mapParameters.shape == MapShape.rectangular) { - val size = HexMath.getEquivalentRectangularSize(mapRadius) + val size = HexMath.getEquivalentRectangularSize(mapSize.radius) map = TileMap(size.x.toInt(), size.y.toInt(), ruleset, mapParameters.worldWrap) } - else - map = TileMap(mapRadius, ruleset, mapParameters.worldWrap) + else map = TileMap(mapSize.radius, ruleset, mapParameters.worldWrap) map.mapParameters = mapParameters map.mapParameters.seed = seed @@ -120,7 +119,7 @@ class MapGenerator(val ruleset: Ruleset) { } private fun spreadResources(tileMap: TileMap) { - val distance = tileMap.mapParameters.size.radius + val distance = tileMap.mapParameters.mapSize.radius for (tile in tileMap.values) tile.resource = null diff --git a/core/src/com/unciv/logic/map/mapgenerator/MapLandmassGenerator.kt b/core/src/com/unciv/logic/map/mapgenerator/MapLandmassGenerator.kt index f37f4d1eb8..619331c12e 100644 --- a/core/src/com/unciv/logic/map/mapgenerator/MapLandmassGenerator.kt +++ b/core/src/com/unciv/logic/map/mapgenerator/MapLandmassGenerator.kt @@ -108,7 +108,7 @@ class MapLandmassGenerator(val randomness: MapGenerationRandomness) { } private fun percentualDistanceToCenter(tileInfo: TileInfo, tileMap: TileMap): Double { - val mapRadius = tileMap.mapParameters.size.radius + val mapRadius = tileMap.mapParameters.mapSize.radius if (tileMap.mapParameters.shape == MapShape.hexagonal) return HexMath.getDistance(Vector2.Zero, tileInfo.position).toDouble() / mapRadius else { @@ -132,6 +132,8 @@ class MapLandmassGenerator(val randomness: MapGenerationRandomness) { // region Cellular automata private fun generateLandCellularAutomata(tileMap: TileMap) { + val mapRadius = tileMap.mapParameters.mapSize.radius + val mapType = tileMap.mapParameters.type val numSmooth = 4 // init @@ -161,7 +163,7 @@ class MapLandmassGenerator(val randomness: MapGenerationRandomness) { } private fun getInitialTerrainCellularAutomata(tileInfo: TileInfo, mapParameters: MapParameters): TerrainType { - val mapRadius = mapParameters.size.radius + val mapRadius = mapParameters.mapSize.radius // default if (HexMath.getDistance(Vector2.Zero, tileInfo.position) > 0.9f * mapRadius) { diff --git a/core/src/com/unciv/logic/map/mapgenerator/NaturalWonderGenerator.kt b/core/src/com/unciv/logic/map/mapgenerator/NaturalWonderGenerator.kt index 102962ba92..7334784c85 100644 --- a/core/src/com/unciv/logic/map/mapgenerator/NaturalWonderGenerator.kt +++ b/core/src/com/unciv/logic/map/mapgenerator/NaturalWonderGenerator.kt @@ -18,7 +18,7 @@ class NaturalWonderGenerator(val ruleset: Ruleset) { fun spawnNaturalWonders(tileMap: TileMap, randomness: MapGenerationRandomness) { if (tileMap.mapParameters.noNaturalWonders) return - val mapRadius = tileMap.mapParameters.size.radius + val mapRadius = tileMap.mapParameters.mapSize.radius // number of Natural Wonders scales linearly with mapRadius as #wonders = mapRadius * 0.13133208 - 0.56128831 val numberToSpawn = round(mapRadius * 0.13133208f - 0.56128831f).toInt() diff --git a/core/src/com/unciv/ui/newgamescreen/MapParametersTable.kt b/core/src/com/unciv/ui/newgamescreen/MapParametersTable.kt index ec3f4238a9..d64cc9b4e5 100644 --- a/core/src/com/unciv/ui/newgamescreen/MapParametersTable.kt +++ b/core/src/com/unciv/ui/newgamescreen/MapParametersTable.kt @@ -1,8 +1,12 @@ package com.unciv.ui.newgamescreen +import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.scenes.scene2d.ui.CheckBox import com.badlogic.gdx.scenes.scene2d.ui.Slider import com.badlogic.gdx.scenes.scene2d.ui.Table +import com.badlogic.gdx.scenes.scene2d.ui.TextField +import com.unciv.Constants +import com.unciv.logic.map.* import com.unciv.UncivGame import com.unciv.logic.map.MapParameters import com.unciv.logic.map.MapShape @@ -20,16 +24,21 @@ import com.unciv.ui.utils.* class MapParametersTable(val mapParameters: MapParameters, val isEmptyMapAllowed: Boolean = false): Table() { lateinit var mapTypeSelectBox: TranslatedSelectBox + lateinit var worldSizeSelectBox: TranslatedSelectBox + private var customWorldSizeTable = Table () + private var hexagonalSizeTable = Table() + private var rectangularSizeTable = Table() lateinit var noRuinsCheckbox: CheckBox lateinit var noNaturalWondersCheckbox: CheckBox lateinit var worldWrapCheckbox: CheckBox + init { skin = CameraStageBaseScreen.skin defaults().pad(5f) addMapShapeSelectBox() addMapTypeSelectBox() - addWorldSizeSelectBox() + addWorldSizeTable() addNoRuinsCheckbox() addNoNaturalWondersCheckbox() if (UncivGame.Current.settings.showExperimentalWorldWrap) { @@ -47,6 +56,7 @@ class MapParametersTable(val mapParameters: MapParameters, val isEmptyMapAllowed TranslatedSelectBox(mapShapes, mapParameters.shape, skin) mapShapeSelectBox.onChange { mapParameters.shape = mapShapeSelectBox.selected.value + updateWorldSizeTable() } add ("{Map Shape}:".toLabel()).left() @@ -78,20 +88,79 @@ class MapParametersTable(val mapParameters: MapParameters, val isEmptyMapAllowed add(mapTypeSelectBox).fillX().row() } - - private fun addWorldSizeSelectBox() { - val worldSizeSelectBox = TranslatedSelectBox( - MapSize.values().map { it.name }, - mapParameters.size.name, - skin + private fun addWorldSizeTable() { + val mapSizes = listOfNotNull( + Constants.tiny, + Constants.small, + Constants.medium, + Constants.large, + Constants.huge, + Constants.custom ) - worldSizeSelectBox.onChange { - mapParameters.size = MapSize.valueOf(worldSizeSelectBox.selected.value) - } + worldSizeSelectBox = TranslatedSelectBox(mapSizes, mapParameters.mapSize.name, skin) + worldSizeSelectBox.onChange { updateWorldSizeTable() } + + addHexagonalSizeTable() + addRectangularSizeTable() add("{World Size}:".toLabel()).left() add(worldSizeSelectBox).fillX().row() + add(customWorldSizeTable).colspan(2).grow().row() + + updateWorldSizeTable() + } + + private fun addHexagonalSizeTable() { + val defaultRadius = mapParameters.mapSize.radius.toString() + val customMapSizeRadius = TextField(defaultRadius, skin).apply { + textFieldFilter = TextField.TextFieldFilter.DigitsOnlyFilter() + } + customMapSizeRadius.onChange { + mapParameters.mapSize = MapSizeNew(customMapSizeRadius.text.toIntOrNull() ?: 0 ) + } + hexagonalSizeTable.add("{Radius}:".toLabel()).grow().left() + hexagonalSizeTable.add(customMapSizeRadius).right().row() + hexagonalSizeTable.add("Anything above 40 may work very slowly on Android!".toLabel(Color.RED) + .apply { wrap=true }).width(prefWidth).colspan(hexagonalSizeTable.columns) + } + + private fun addRectangularSizeTable() { + val defaultWidth = mapParameters.mapSize.width.toString() + val customMapWidth = TextField(defaultWidth, skin).apply { + textFieldFilter = TextField.TextFieldFilter.DigitsOnlyFilter() + } + + val defaultHeight = mapParameters.mapSize.height.toString() + val customMapHeight = TextField(defaultHeight, skin).apply { + textFieldFilter = TextField.TextFieldFilter.DigitsOnlyFilter() + } + + customMapWidth.onChange { + mapParameters.mapSize = MapSizeNew(customMapWidth.text.toIntOrNull() ?: 0, customMapHeight.text.toIntOrNull() ?: 0) + } + customMapHeight.onChange { + mapParameters.mapSize = MapSizeNew(customMapWidth.text.toIntOrNull() ?: 0, customMapHeight.text.toIntOrNull() ?: 0) + } + + rectangularSizeTable.defaults().pad(5f) + rectangularSizeTable.add("{Width}:".toLabel()).grow().left() + rectangularSizeTable.add(customMapWidth).right().row() + rectangularSizeTable.add("{Height}:".toLabel()).grow().left() + rectangularSizeTable.add(customMapHeight).right().row() + rectangularSizeTable.add("Anything above 80 by 50 may work very slowly on Android!".toLabel(Color.RED) + .apply { wrap=true }).width(prefWidth).colspan(hexagonalSizeTable.columns) + } + + private fun updateWorldSizeTable() { + customWorldSizeTable.clear() + + if (mapParameters.shape == MapShape.hexagonal && worldSizeSelectBox.selected.value == Constants.custom) + customWorldSizeTable.add(hexagonalSizeTable).grow().row() + else if (mapParameters.shape == MapShape.rectangular && worldSizeSelectBox.selected.value == Constants.custom) + customWorldSizeTable.add(rectangularSizeTable).grow().row() + else + mapParameters.mapSize = MapSizeNew(worldSizeSelectBox.selected.value) } private fun addNoRuinsCheckbox() { diff --git a/core/src/com/unciv/ui/worldscreen/Minimap.kt b/core/src/com/unciv/ui/worldscreen/Minimap.kt index 13ff03d51b..ca07d7a2ef 100644 --- a/core/src/com/unciv/ui/worldscreen/Minimap.kt +++ b/core/src/com/unciv/ui/worldscreen/Minimap.kt @@ -132,7 +132,6 @@ class MinimapHolder(mapHolder: WorldMapHolder): Table() { pack() } - private fun getWrappedMinimap(): Table { val internalMinimapWrapper = Table() internalMinimapWrapper.add(minimap) diff --git a/desktop/src/com/unciv/app/desktop/ConsoleLauncher.kt b/desktop/src/com/unciv/app/desktop/ConsoleLauncher.kt index 402adc5b88..7fc532d400 100644 --- a/desktop/src/com/unciv/app/desktop/ConsoleLauncher.kt +++ b/desktop/src/com/unciv/app/desktop/ConsoleLauncher.kt @@ -1,11 +1,12 @@ package com.unciv.app.desktop +import com.unciv.Constants import com.unciv.UncivGame import com.unciv.UncivGameParameters import com.unciv.logic.GameStarter import com.unciv.logic.civilization.PlayerType import com.unciv.logic.map.MapParameters -import com.unciv.logic.map.MapSize +import com.unciv.logic.map.MapSizeNew import com.unciv.models.metadata.GameParameters import com.unciv.models.metadata.GameSettings import com.unciv.models.metadata.GameSpeed @@ -54,7 +55,7 @@ internal object ConsoleLauncher { private fun getMapParameters(): MapParameters { return MapParameters().apply { - size = MapSize.Tiny + mapSize = MapSizeNew(Constants.tiny) noRuins = true noNaturalWonders = true }