diff --git a/android/assets/jsons/translations/template.properties b/android/assets/jsons/translations/template.properties index bc8628e1b1..544e3b8d75 100644 --- a/android/assets/jsons/translations/template.properties +++ b/android/assets/jsons/translations/template.properties @@ -230,6 +230,7 @@ Rectangular = Show advanced settings = Hide advanced settings = +RNG Seed = Map Height = Temperature extremeness = Resource richness = diff --git a/core/src/com/unciv/logic/map/MapParameters.kt b/core/src/com/unciv/logic/map/MapParameters.kt index eadad355bc..04593ce674 100644 --- a/core/src/com/unciv/logic/map/MapParameters.kt +++ b/core/src/com/unciv/logic/map/MapParameters.kt @@ -130,7 +130,7 @@ class MapParameters { /** This is used mainly for the map editor, so you can continue editing a map under the ame ruleset you started with */ var mods = LinkedHashSet() - var seed: Long = 0 + var seed: Long = System.currentTimeMillis() var tilesPerBiomeArea = 6 var maxCoastExtension = 2 var elevationExponent = 0.7f @@ -141,6 +141,7 @@ class MapParameters { var waterThreshold = 0f fun resetAdvancedSettings() { + seed = System.currentTimeMillis() tilesPerBiomeArea = 6 maxCoastExtension = 2 elevationExponent = 0.7f diff --git a/core/src/com/unciv/logic/map/mapgenerator/MapGenerator.kt b/core/src/com/unciv/logic/map/mapgenerator/MapGenerator.kt index 544a27b4fd..16984646b7 100644 --- a/core/src/com/unciv/logic/map/mapgenerator/MapGenerator.kt +++ b/core/src/com/unciv/logic/map/mapgenerator/MapGenerator.kt @@ -18,17 +18,21 @@ import kotlin.random.Random class MapGenerator(val ruleset: Ruleset) { private var randomness = MapGenerationRandomness() - fun generateMap(mapParameters: MapParameters, seed: Long = System.currentTimeMillis()): TileMap { + fun generateMap(mapParameters: MapParameters): TileMap { val mapSize = mapParameters.mapSize val mapType = mapParameters.type + if (mapParameters.seed == 0L) + mapParameters.seed = System.currentTimeMillis() + + randomness.seedRNG(mapParameters.seed) + val map: TileMap = if (mapParameters.shape == MapShape.rectangular) TileMap(mapSize.width, mapSize.height, ruleset, mapParameters.worldWrap) else TileMap(mapSize.radius, ruleset, mapParameters.worldWrap) map.mapParameters = mapParameters - map.mapParameters.seed = seed if (mapType == MapType.empty) { for (tile in map.values) { @@ -39,15 +43,14 @@ class MapGenerator(val ruleset: Ruleset) { return map } - seedRNG(seed) - MapLandmassGenerator(randomness).generateLand(map,ruleset) + MapLandmassGenerator(ruleset, randomness).generateLand(map) raiseMountainsAndHills(map) applyHumidityAndTemperature(map) spawnLakesAndCoasts(map) spawnVegetation(map) spawnRareFeatures(map) spawnIce(map) - NaturalWonderGenerator(ruleset).spawnNaturalWonders(map, randomness) + NaturalWonderGenerator(ruleset, randomness).spawnNaturalWonders(map) RiverGenerator(randomness).spawnRivers(map) spreadResources(map) spreadAncientRuins(map) @@ -303,6 +306,10 @@ class MapGenerator(val ruleset: Ruleset) { class MapGenerationRandomness{ var RNG = Random(42) + fun seedRNG(seed: Long = 42) { + RNG = Random(seed) + } + /** * Generates a perlin noise channel combining multiple octaves * diff --git a/core/src/com/unciv/logic/map/mapgenerator/MapLandmassGenerator.kt b/core/src/com/unciv/logic/map/mapgenerator/MapLandmassGenerator.kt index 0565c1304d..a9a68efb3f 100644 --- a/core/src/com/unciv/logic/map/mapgenerator/MapLandmassGenerator.kt +++ b/core/src/com/unciv/logic/map/mapgenerator/MapLandmassGenerator.kt @@ -9,9 +9,9 @@ import kotlin.math.abs import kotlin.math.min import kotlin.math.pow -class MapLandmassGenerator(val randomness: MapGenerationRandomness) { +class MapLandmassGenerator(val ruleset: Ruleset, val randomness: MapGenerationRandomness) { - fun generateLand(tileMap: TileMap, ruleset: Ruleset) { + fun generateLand(tileMap: TileMap) { // This is to accommodate land-only mods if (ruleset.terrains.values.none { it.type == TerrainType.Water }) { for (tile in tileMap.values) diff --git a/core/src/com/unciv/logic/map/mapgenerator/NaturalWonderGenerator.kt b/core/src/com/unciv/logic/map/mapgenerator/NaturalWonderGenerator.kt index 7334784c85..a5da1ef3b8 100644 --- a/core/src/com/unciv/logic/map/mapgenerator/NaturalWonderGenerator.kt +++ b/core/src/com/unciv/logic/map/mapgenerator/NaturalWonderGenerator.kt @@ -9,13 +9,13 @@ import com.unciv.models.ruleset.tile.TerrainType import kotlin.math.abs import kotlin.math.round -class NaturalWonderGenerator(val ruleset: Ruleset) { +class NaturalWonderGenerator(val ruleset: Ruleset, val randomness: MapGenerationRandomness) { /* https://gaming.stackexchange.com/questions/95095/do-natural-wonders-spawn-more-closely-to-city-states/96479 https://www.reddit.com/r/civ/comments/1jae5j/information_on_the_occurrence_of_natural_wonders/ */ - fun spawnNaturalWonders(tileMap: TileMap, randomness: MapGenerationRandomness) { + fun spawnNaturalWonders(tileMap: TileMap) { if (tileMap.mapParameters.noNaturalWonders) return val mapRadius = tileMap.mapParameters.mapSize.radius @@ -60,7 +60,7 @@ class NaturalWonderGenerator(val ruleset: Ruleset) { private fun trySpawnOnSuitableLocation(suitableLocations: List, wonder: Terrain): TileInfo? { if (suitableLocations.isNotEmpty()) { - val location = suitableLocations.random() + val location = suitableLocations.random(randomness.RNG) clearTile(location) location.naturalWonder = wonder.name location.baseTerrain = wonder.turnsInto!! diff --git a/core/src/com/unciv/ui/newgamescreen/MapParametersTable.kt b/core/src/com/unciv/ui/newgamescreen/MapParametersTable.kt index 1c6a42aaf2..85a0775fa3 100644 --- a/core/src/com/unciv/ui/newgamescreen/MapParametersTable.kt +++ b/core/src/com/unciv/ui/newgamescreen/MapParametersTable.kt @@ -5,6 +5,7 @@ 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.badlogic.gdx.scenes.scene2d.ui.TextField.TextFieldFilter.DigitsOnlyFilter import com.unciv.Constants import com.unciv.UncivGame import com.unciv.logic.map.* @@ -40,23 +41,22 @@ class MapParametersTable(val mapParameters: MapParameters, val isEmptyMapAllowed addWorldSizeTable() addNoRuinsCheckbox() addNoNaturalWondersCheckbox() - if (UncivGame.Current.settings.showExperimentalWorldWrap) { + if (UncivGame.Current.settings.showExperimentalWorldWrap) addWorldWrapCheckbox() - } addAdvancedSettings() } private fun addMapShapeSelectBox() { val mapShapes = listOfNotNull( - MapShape.hexagonal, - MapShape.rectangular + MapShape.hexagonal, + MapShape.rectangular ) val mapShapeSelectBox = - TranslatedSelectBox(mapShapes, mapParameters.shape, skin) + TranslatedSelectBox(mapShapes, mapParameters.shape, skin) mapShapeSelectBox.onChange { - mapParameters.shape = mapShapeSelectBox.selected.value - updateWorldSizeTable() - } + mapParameters.shape = mapShapeSelectBox.selected.value + updateWorldSizeTable() + } add ("{Map Shape}:".toLabel()).left() add(mapShapeSelectBox).fillX().row() @@ -77,12 +77,12 @@ class MapParametersTable(val mapParameters: MapParameters, val isEmptyMapAllowed mapTypeSelectBox = TranslatedSelectBox(mapTypes, mapParameters.type, skin) mapTypeSelectBox.onChange { - mapParameters.type = mapTypeSelectBox.selected.value + mapParameters.type = mapTypeSelectBox.selected.value - // If the map won't be generated, these options are irrelevant and are hidden - noRuinsCheckbox.isVisible = mapParameters.type != MapType.empty - noNaturalWondersCheckbox.isVisible = mapParameters.type != MapType.empty - } + // If the map won't be generated, these options are irrelevant and are hidden + noRuinsCheckbox.isVisible = mapParameters.type != MapType.empty + noNaturalWondersCheckbox.isVisible = mapParameters.type != MapType.empty + } add("{Map Generation Type}:".toLabel()).left() add(mapTypeSelectBox).fillX().row() @@ -106,7 +106,7 @@ class MapParametersTable(val mapParameters: MapParameters, val isEmptyMapAllowed private fun addHexagonalSizeTable() { val defaultRadius = mapParameters.mapSize.radius.toString() customMapSizeRadius = TextField(defaultRadius, skin).apply { - textFieldFilter = TextField.TextFieldFilter.DigitsOnlyFilter() + textFieldFilter = DigitsOnlyFilter() } customMapSizeRadius.onChange { mapParameters.mapSize = MapSizeNew(customMapSizeRadius.text.toIntOrNull() ?: 0 ) @@ -114,18 +114,18 @@ class MapParametersTable(val mapParameters: MapParameters, val isEmptyMapAllowed 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) + .apply { wrap=true }).width(prefWidth).colspan(hexagonalSizeTable.columns) } private fun addRectangularSizeTable() { val defaultWidth = mapParameters.mapSize.width.toString() customMapWidth = TextField(defaultWidth, skin).apply { - textFieldFilter = TextField.TextFieldFilter.DigitsOnlyFilter() + textFieldFilter = DigitsOnlyFilter() } val defaultHeight = mapParameters.mapSize.height.toString() customMapHeight = TextField(defaultHeight, skin).apply { - textFieldFilter = TextField.TextFieldFilter.DigitsOnlyFilter() + textFieldFilter = DigitsOnlyFilter() } customMapWidth.onChange { @@ -141,7 +141,7 @@ class MapParametersTable(val mapParameters: MapParameters, val isEmptyMapAllowed 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) + .apply { wrap = true }).width(prefWidth).colspan(hexagonalSizeTable.columns) } private fun updateWorldSizeTable() { @@ -159,7 +159,7 @@ class MapParametersTable(val mapParameters: MapParameters, val isEmptyMapAllowed noRuinsCheckbox = CheckBox("No Ancient Ruins".tr(), skin) noRuinsCheckbox.isChecked = mapParameters.noRuins noRuinsCheckbox.onChange { mapParameters.noRuins = noRuinsCheckbox.isChecked } - add(noRuinsCheckbox).colspan(2).row() + add(noRuinsCheckbox).colspan(2).left().row() } private fun addNoNaturalWondersCheckbox() { @@ -168,7 +168,7 @@ class MapParametersTable(val mapParameters: MapParameters, val isEmptyMapAllowed noNaturalWondersCheckbox.onChange { mapParameters.noNaturalWonders = noNaturalWondersCheckbox.isChecked } - add(noNaturalWondersCheckbox).colspan(2).row() + add(noNaturalWondersCheckbox).colspan(2).left().row() } private fun addWorldWrapCheckbox() { @@ -177,7 +177,7 @@ class MapParametersTable(val mapParameters: MapParameters, val isEmptyMapAllowed worldWrapCheckbox.onChange { mapParameters.worldWrap = worldWrapCheckbox.isChecked } - add(worldWrapCheckbox).colspan(2).row() + add(worldWrapCheckbox).colspan(2).left().row() add("World wrap maps are very memory intensive - creating large world wrap maps on Android can lead to crashes!" .toLabel(fontSize = 14).apply { wrap=true }).colspan(2).fillX().row() } @@ -209,11 +209,26 @@ class MapParametersTable(val mapParameters: MapParameters, val isEmptyMapAllowed private fun getAdvancedSettingsTable(): Table { val advancedSettingsTable = Table() - .apply {isVisible = false; defaults().pad(5f)} + .apply {isVisible = false; defaults().pad(5f)} + + val seedTextField = TextField(mapParameters.seed.toString(), skin) + seedTextField.textFieldFilter = DigitsOnlyFilter() + + // If the field is empty, fallback seed value to 0 + seedTextField.onChange { + mapParameters.seed = try { + seedTextField.text.toLong() + } catch (e: Exception) { + 0L + } + } + + advancedSettingsTable.add("RNG Seed".toLabel()).left() + advancedSettingsTable.add(seedTextField).fillX().row() val sliders = HashMapFloat>() - fun addSlider(text:String, getValue:()->Float, min:Float, max:Float, onChange: (value:Float)->Unit): Slider { + fun addSlider(text: String, getValue:()->Float, min:Float, max:Float, onChange: (value:Float)->Unit): Slider { val slider = Slider(min, max, (max - min) / 20, false, skin) slider.value = getValue() slider.onChange { onChange(slider.value) } @@ -250,6 +265,7 @@ class MapParametersTable(val mapParameters: MapParameters, val isEmptyMapAllowed val resetToDefaultButton = "Reset to default".toTextButton() resetToDefaultButton.onClick { mapParameters.resetAdvancedSettings() + seedTextField.text = mapParameters.seed.toString() for(entry in sliders) entry.key.value = entry.value() }