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 <yairm210@hotmail.com>
This commit is contained in:
Alexander Korolyov
2021-05-06 17:39:28 +02:00
committed by GitHub
parent b8f9b99bd1
commit 453f5588ac
13 changed files with 170 additions and 29 deletions

View File

@ -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

View File

@ -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"
}

View File

@ -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)

View File

@ -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

View File

@ -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<Vector2> {
val vectors = arrayListOf(
Vector2(1f, 0f),

View File

@ -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]

View File

@ -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

View File

@ -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

View File

@ -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) {

View File

@ -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()

View File

@ -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() {

View File

@ -132,7 +132,6 @@ class MinimapHolder(mapHolder: WorldMapHolder): Table() {
pack()
}
private fun getWrappedMinimap(): Table {
val internalMinimapWrapper = Table()
internalMinimapWrapper.add(minimap)

View File

@ -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
}