From e4b0afb7e37a5a74371ce252d41b009c4376a999 Mon Sep 17 00:00:00 2001 From: SomeTroglodyte <63000004+SomeTroglodyte@users.noreply.github.com> Date: Sun, 13 Mar 2022 21:43:23 +0100 Subject: [PATCH] Update ModConstants and start its documentation (#6309) * Update ModConstants * Update ModConstants - revert Ice, tweak formula * Update ModConstants - comment on predefined MapSizes --- core/src/com/unciv/logic/map/MapParameters.kt | 8 +++ .../logic/map/mapgenerator/MapGenerator.kt | 12 +++-- .../mapgenerator/NaturalWonderGenerator.kt | 7 ++- .../logic/map/mapgenerator/RiverGenerator.kt | 19 ++++--- core/src/com/unciv/models/ModConstants.kt | 22 +++++++- docs/Other/Miscellaneous-JSON-files.md | 51 ++++++++++++++++++- 6 files changed, 99 insertions(+), 20 deletions(-) diff --git a/core/src/com/unciv/logic/map/MapParameters.kt b/core/src/com/unciv/logic/map/MapParameters.kt index 3c042357b2..dc91db1a28 100644 --- a/core/src/com/unciv/logic/map/MapParameters.kt +++ b/core/src/com/unciv/logic/map/MapParameters.kt @@ -8,6 +8,14 @@ import com.unciv.models.metadata.BaseRuleset import com.unciv.models.ruleset.RulesetCache +/* Predefined Map Sizes - ours are a little lighter than the original values. For reference those are: + Civ5Duel(40,24,17), + Civ5Tiny(56,36,25), + Civ5Small(66,42,30), + Civ5Medium(80,52,37), + Civ5Large(104,64,47), + Civ5Huge(128,80,58), + */ enum class MapSize(val radius: Int, val width: Int, val height: Int) { Tiny(10, 23, 15), Small(15, 33, 21), diff --git a/core/src/com/unciv/logic/map/mapgenerator/MapGenerator.kt b/core/src/com/unciv/logic/map/mapgenerator/MapGenerator.kt index a9e637f727..0f1aeefe58 100644 --- a/core/src/com/unciv/logic/map/mapgenerator/MapGenerator.kt +++ b/core/src/com/unciv/logic/map/mapgenerator/MapGenerator.kt @@ -148,6 +148,8 @@ class MapGenerator(val ruleset: Ruleset) { val tilesInArea = ArrayList() val tilesToCheck = ArrayList() + val maxLakeSize = ruleset.modOptions.constants.maxLakeSize + while (waterTiles.isNotEmpty()) { val initialWaterTile = waterTiles.random(randomness.RNG) tilesInArea += initialWaterTile @@ -166,7 +168,7 @@ class MapGenerator(val ruleset: Ruleset) { tilesToCheck -= tileWeAreChecking } - if (tilesInArea.size <= 10) { + if (tilesInArea.size <= maxLakeSize) { for (tile in tilesInArea) { tile.baseTerrain = Constants.lakes tile.setTransients() @@ -194,8 +196,10 @@ class MapGenerator(val ruleset: Ruleset) { if (map.mapParameters.noRuins || ruinsEquivalents.isEmpty() ) return val suitableTiles = map.values.filter { it.isLand && !it.isImpassible() } - val locations = randomness.chooseSpreadOutLocations(suitableTiles.size / 50, - suitableTiles, map.mapParameters.mapSize.radius) + val locations = randomness.chooseSpreadOutLocations( + (suitableTiles.size * ruleset.modOptions.constants.ancientRuinCountMultiplier).roundToInt(), + suitableTiles, + map.mapParameters.mapSize.radius) for (tile in locations) tile.improvement = ruinsEquivalents.keys.random() } @@ -512,7 +516,7 @@ class MapGenerator(val ruleset: Ruleset) { val latitudeTemperature = 1.0 - 2.0 * abs(tile.latitude) / tileMap.maxLatitude var temperature = ((latitudeTemperature + randomTemperature) / 2.0) temperature = abs(temperature).pow(1.0 - tileMap.mapParameters.temperatureExtremeness) * temperature.sign - if (temperature < -0.8) + if (temperature < -0.8f) tile.addTerrainFeature(Constants.ice) } } diff --git a/core/src/com/unciv/logic/map/mapgenerator/NaturalWonderGenerator.kt b/core/src/com/unciv/logic/map/mapgenerator/NaturalWonderGenerator.kt index a85985ac40..b95514dd87 100644 --- a/core/src/com/unciv/logic/map/mapgenerator/NaturalWonderGenerator.kt +++ b/core/src/com/unciv/logic/map/mapgenerator/NaturalWonderGenerator.kt @@ -10,6 +10,7 @@ import com.unciv.models.ruleset.unique.Unique import com.unciv.models.ruleset.unique.UniqueType import kotlin.math.abs import kotlin.math.round +import kotlin.math.roundToInt class NaturalWonderGenerator(val ruleset: Ruleset, val randomness: MapGenerationRandomness) { @@ -25,8 +26,10 @@ class NaturalWonderGenerator(val ruleset: Ruleset, val randomness: MapGeneration if (tileMap.mapParameters.noNaturalWonders) return 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() + // number of Natural Wonders scales linearly with mapRadius + val numberToSpawn = ruleset.modOptions.constants.run { + mapRadius * naturalWonderCountMultiplier + naturalWonderCountAddedConstant + }.roundToInt() val spawned = mutableListOf() val allNaturalWonders = ruleset.terrains.values diff --git a/core/src/com/unciv/logic/map/mapgenerator/RiverGenerator.kt b/core/src/com/unciv/logic/map/mapgenerator/RiverGenerator.kt index ccfa34fdf0..d0cfb7d7ee 100644 --- a/core/src/com/unciv/logic/map/mapgenerator/RiverGenerator.kt +++ b/core/src/com/unciv/logic/map/mapgenerator/RiverGenerator.kt @@ -5,21 +5,20 @@ import com.unciv.Constants import com.unciv.logic.map.TileInfo import com.unciv.logic.map.TileMap import com.unciv.models.ruleset.Ruleset +import kotlin.math.roundToInt class RiverGenerator( private val tileMap: TileMap, private val randomness: MapGenerationRandomness, - private val ruleset: Ruleset + ruleset: Ruleset ) { - companion object{ - const val MAP_TILES_PER_RIVER = 100 - const val MIN_RIVER_LENGTH = 5 - const val MAX_RIVER_LENGTH = 666 // Do not set < max map radius - } + private val riverCountMultiplier = ruleset.modOptions.constants.riverCountMultiplier + private val minRiverLength = ruleset.modOptions.constants.minRiverLength + private val maxRiverLength = ruleset.modOptions.constants.maxRiverLength fun spawnRivers() { if (tileMap.values.none { it.isWater }) return - val numberOfRivers = tileMap.values.count { it.isLand } / MAP_TILES_PER_RIVER + val numberOfRivers = (tileMap.values.count { it.isLand } * riverCountMultiplier).roundToInt() var optionalTiles = tileMap.values.asSequence() .filter { it.baseTerrain == Constants.mountain && it.isFarEnoughFromWater() } @@ -37,14 +36,14 @@ class RiverGenerator( } private fun TileInfo.isFarEnoughFromWater(): Boolean { - for (distance in 1 until MIN_RIVER_LENGTH) { + for (distance in 1 until minRiverLength) { if (getTilesAtDistance(distance).any { it.isWater }) return false } return true } private fun getClosestWaterTile(tile: TileInfo): TileInfo { - for (distance in 1..MAX_RIVER_LENGTH) { + for (distance in 1..maxRiverLength) { val waterTiles = tile.getTilesAtDistance(distance).filter { it.isWater } if (waterTiles.any()) return waterTiles.toList().random(randomness.RNG) @@ -59,7 +58,7 @@ class RiverGenerator( var riverCoordinate = RiverCoordinate(initialPosition.position, RiverCoordinate.BottomRightOrLeft.values().random(randomness.RNG)) - for (step in 1..MAX_RIVER_LENGTH) { // Arbitrary max on river length, otherwise this will go in circles - rarely + for (step in 1..maxRiverLength) { // Arbitrary max on river length, otherwise this will go in circles - rarely if (riverCoordinate.getAdjacentTiles(tileMap).any { it.isWater }) return val possibleCoordinates = riverCoordinate.getAdjacentPositions(tileMap) if (possibleCoordinates.none()) return // end of the line diff --git a/core/src/com/unciv/models/ModConstants.kt b/core/src/com/unciv/models/ModConstants.kt index 9bbd4eb673..3f3b97fc76 100644 --- a/core/src/com/unciv/models/ModConstants.kt +++ b/core/src/com/unciv/models/ModConstants.kt @@ -25,11 +25,29 @@ class ModConstants { // unitSupplyBase and unitSupplyPerCity can be found in difficulties.json // unitSupplyBase, unitSupplyPerCity and unitSupplyPerPopulation can also be increased through uniques val unitSupplyPerPopulation = 0.5 - + // The minimal distance that must be between any two cities, not counting the tiles cities are on // The number is the amount of tiles between two cities, not counting the tiles the cities are on. // e.g. "C__C", where "C" is a tile with a city and "_" is a tile without a city, has a distance of 2. // First constant is for cities on the same landmass, the second is for cities on different continents. val minimalCityDistance = 3 val minimalCityDistanceOnDifferentContinents = 2 -} \ No newline at end of file + + // NaturalWonderGenerator uses these to determine the number of Natural Wonders to spawn for a given map size. + // With these values, radius * mul + add gives a 1-2-3-4-5 progression for Unciv predefined map sizes and a 2-3-4-5-6-7 progression for the original Civ5 map sizes. + // 0.124 = (Civ5.Huge.getHexagonalRadiusForArea(w*h) - Civ5.Duel.getHexagonalRadiusForArea(w*h)) / 5 (if you do not round in the radius function) + // The other constant is empiric to avoid an ugly jump in the progression. + val naturalWonderCountMultiplier = 0.124f + val naturalWonderCountAddedConstant = 0.1f + + // MapGenerator.spreadAncientRuins: number of ruins = suitable tile count * this + val ancientRuinCountMultiplier = 0.02f + // MapGenerator.spawnIce: spawn Ice where T < this, with T calculated from temperatureExtremeness, latitude and perlin noise. + val spawnIceBelowTemperature = -0.8f + // MapGenerator.spawnLakesAndCoasts: Water bodies up to this tile count become Lakes + val maxLakeSize = 10 + // RiverGenerator: river frequency and length bounds + val riverCountMultiplier = 0.01f + val minRiverLength = 5 + val maxRiverLength = 666 // Do not set < max map radius +} diff --git a/docs/Other/Miscellaneous-JSON-files.md b/docs/Other/Miscellaneous-JSON-files.md index db7cefbfac..c4149d031a 100644 --- a/docs/Other/Miscellaneous-JSON-files.md +++ b/docs/Other/Miscellaneous-JSON-files.md @@ -48,7 +48,7 @@ Each era can have the following attributes: | unitBaseBuyCost | Integer (≥0) | defaults to 200 | Base cost of buying units with Faith, Food, Science or Culture when no other cost is provided | | startingSettlerCount | Integer (≥0) | defaults to 1 | Amount of settler units that should be spawned when starting a game in this era | | startingSettlerUnit | String | defaults to "Settler" | Name of the unit that should be used for the previous field. Must be in [Units.json](Unit-related-JSON-files.md#unitsjson) | -| startingWokerCount | Integer (≥0) | defaults to 0 | Amount of worker units that should be spawned when starting a game in this era | +| startingWorkerCount | Integer (≥0) | defaults to 0 | Amount of worker units that should be spawned when starting a game in this era | | startingWorkerUnit | String | defaults to "Worker" | Name of the unit that should be used for the previous field. Must be in [Units.json](Unit-related-JSON-files.md#unitsjson) | | startingMilitaryUnitCount | Integer (≥0) | defaults to 1 | Amount of military units that should be spawned when starting a game in this era | | startingMilitaryUnit | String | defaults to "Warrior" | Name of the unit that should be used for the previous field. Must be in [Units.json](Unit-related-JSON-files.md#unitsjson)| @@ -69,7 +69,7 @@ The file can have the following attributes, including the values Unciv sets (no | Attribute | Type | Defaults | Notes | |-----------|------|-----------|-------| | isBaseRuleset | Boolean | false | Differentiates mods that change the vanilla ruleset or replace it | -| maxXPfromBarbarians | Integer | 30 | ...as the name says... | +| maxXPfromBarbarians | Integer | 30 | *Deprecated*, see [constants](#ModConstants) | | uniques | List | empty | Mod-wide specials, [see here](../Modders/Unique-parameter-types.md#modoptions-uniques) | | techsToRemove | List | empty | List of [Technologies](Civilization-related-JSON-files.md#techsjson) to remove (isBaseRuleset=false only) | | buildingsToRemove | List | empty | List of [Buildings or Wonders](Civilization-related-JSON-files.md#buildingsjson) to remove (isBaseRuleset=false only) | @@ -79,6 +79,53 @@ The file can have the following attributes, including the values Unciv sets (no | modUrl | String | empty | Set automatically after download - URL of repository | | author | String | empty | Set automatically after download - Owner of repository | | modSize | Integer | empty | Set automatically after download - kB in entire repository, not sum of default branch files | +| constants | Object | empty | see [constants](#ModOptions.constants) | + +### ModConstants +Stored in ModOptions.constants, this is a collection of constants used internally in Unciv. + +| Attribute | Type | Defaults | Notes | +|-----------|------|-----------|-------| +| maxXPfromBarbarians | Int | 30 | [^A] | +| cityStrengthBase| Float | 8.0 | [^B] | +| cityStrengthPerPop| Float | 0.4 | [^B] | +| cityStrengthFromTechsMultiplier| Float | 5.5 | [^B] | +| cityStrengthFromTechsExponent| Float | 2.8 | [^B] | +| cityStrengthFromTechsFullMultiplier| Float | 1.0 | [^B] | +| cityStrengthFromGarrison| Float | 0.2 | [^B] | +| unitSupplyPerPopulation| Float | 0.5 | [^C] | +| minimalCityDistance| Int | 3 | [^D] | +| minimalCityDistanceOnDifferentContinents| Int | 2 | [^D] | +| naturalWonderCountMultiplier| Float | 0.124 | [^E] | +| naturalWonderCountAddedConstant| Float | 0.1 | [^E] | +| ancientRuinCountMultiplier| Float | 0.02 | [^F] | +| maxLakeSize| Int | 10 | [^H] | +| riverCountMultiplier| Float | 0.01 | [^I] | +| minRiverLength| Int | 5 | [^I] | +| maxRiverLength| Int | 666 | [^I] | + +[^A]: Max amount of experience that can be gained from combat with barbarians +[^B]: Formula for city Strength: +Strength = baseStrength + strengthPerPop + strengthFromTiles + +((%techs * multiplier) ^ exponent) * fullMultiplier + +(garrisonBonus * garrisonUnitStrength * garrisonUnitHealth/100) + +defensiveBuildingStrength +where %techs is the percentage of techs in the tech tree that are complete +If no techs exist in this ruleset, %techs = 0.5 (=50%) +[^C]: Formula for Unit Supply: +Supply = unitSupplyBase (difficulties.json) +unitSupplyPerCity * amountOfCities + (difficulties.json) +unitSupplyPerPopulation * amountOfPopulationInAllCities +unitSupplyBase and unitSupplyPerCity can be found in difficulties.json +unitSupplyBase, unitSupplyPerCity and unitSupplyPerPopulation can also be increased through uniques +[^D]: The minimal distance that must be between any two cities, not counting the tiles cities are on +The number is the amount of tiles between two cities, not counting the tiles the cities are on. +e.g. "C__C", where "C" is a tile with a city and "_" is a tile without a city, has a distance of 2. +First constant is for cities on the same landmass, the second is for cities on different continents. +[^E]: NaturalWonderGenerator uses these to determine the number of Natural Wonders to spawn for a given map size. The number scales linearly with map radius: #wonders = radius * naturalWonderCountMultiplier + naturalWonderCountAddedConstant. The defaults effectively mean Tiny - 1, Small - 2, Medium - 3, Large - 4, Huge - 5, Custom radius >=109 - all G&K wonders. +[^F]: MapGenerator.spreadAncientRuins: number of ruins = suitable tile count * this +[^H]: MapGenerator.spawnLakesAndCoasts: Water bodies up to this tile count become Lakes +[^I]: RiverGenerator: river frequency and length bounds ## Civilopedia text