From a6e61c120b7b30b7929ea63aabc6a032004833b7 Mon Sep 17 00:00:00 2001 From: SimonCeder Date: Sat, 30 Jul 2022 00:07:48 +0200 Subject: [PATCH] Fix NW placement --- .../logic/map/mapgenerator/MapGenerator.kt | 17 +++++-- .../logic/map/mapgenerator/MapRegions.kt | 11 +++++ .../mapgenerator/NaturalWonderGenerator.kt | 46 ++++++++++++++++--- 3 files changed, 63 insertions(+), 11 deletions(-) diff --git a/core/src/com/unciv/logic/map/mapgenerator/MapGenerator.kt b/core/src/com/unciv/logic/map/mapgenerator/MapGenerator.kt index 06ac192ce1..b0c88bc199 100644 --- a/core/src/com/unciv/logic/map/mapgenerator/MapGenerator.kt +++ b/core/src/com/unciv/logic/map/mapgenerator/MapGenerator.kt @@ -139,18 +139,22 @@ class MapGenerator(val ruleset: Ruleset) { runAndMeasure("assignRegions") { regions.assignRegions(map, civilizations.filter { ruleset.nations[it.civName]!!.isMajorCiv() }, gameParameters) } + // Natural wonders need to go before most resources since there is a minimum distance + runAndMeasure("NaturalWonderGenerator") { + NaturalWonderGenerator(ruleset, randomness).spawnNaturalWonders(map) + } runAndMeasure("placeResourcesAndMinorCivs") { regions.placeResourcesAndMinorCivs(map, civilizations.filter { ruleset.nations[it.civName]!!.isCityState() }) } } else { + runAndMeasure("NaturalWonderGenerator") { + NaturalWonderGenerator(ruleset, randomness).spawnNaturalWonders(map) + } // Fallback spread resources function - used when generating maps in map editor runAndMeasure("spreadResources") { spreadResources(map) } } - runAndMeasure("NaturalWonderGenerator") { - NaturalWonderGenerator(ruleset, randomness).spawnNaturalWonders(map) - } runAndMeasure("spreadAncientRuins") { spreadAncientRuins(map) } @@ -288,7 +292,9 @@ class MapGenerator(val ruleset: Ruleset) { private fun spreadStrategicResources(tileMap: TileMap, mapRadius: Int) { val strategicResources = ruleset.tileResources.values.filter { it.resourceType == ResourceType.Strategic } // passable land tiles (no mountains, no wonders) without resources yet - val candidateTiles = tileMap.values.filter { it.resource == null && !it.isImpassible() } + // can't be next to NW + val candidateTiles = tileMap.values.filter { it.resource == null && !it.isImpassible() + && it.neighbors.none { neighbor -> neighbor.isNaturalWonder() }} val totalNumberOfResources = candidateTiles.count { it.isLand } * tileMap.mapParameters.resourceRichness val resourcesPerType = (totalNumberOfResources/strategicResources.size).toInt() for (resource in strategicResources) { @@ -314,7 +320,8 @@ class MapGenerator(val ruleset: Ruleset) { val suitableTiles = tileMap.values .filterNot { it.baseTerrain == Constants.snow && it.isHill() } - .filter { it.resource == null && resourcesOfType.any { r -> r.terrainsCanBeFoundOn.contains(it.getLastTerrain().name) } } + .filter { it.resource == null && it.neighbors.none { neighbor -> neighbor.isNaturalWonder() } + && resourcesOfType.any { r -> r.terrainsCanBeFoundOn.contains(it.getLastTerrain().name) } } val numberOfResources = tileMap.values.count { it.isLand && !it.isImpassible() } * tileMap.mapParameters.resourceRichness val locations = randomness.chooseSpreadOutLocations(numberOfResources.toInt(), suitableTiles, mapRadius) diff --git a/core/src/com/unciv/logic/map/mapgenerator/MapRegions.kt b/core/src/com/unciv/logic/map/mapgenerator/MapRegions.kt index bbcccb9861..a3260e90ec 100644 --- a/core/src/com/unciv/logic/map/mapgenerator/MapRegions.kt +++ b/core/src/com/unciv/logic/map/mapgenerator/MapRegions.kt @@ -829,12 +829,23 @@ class MapRegions (val ruleset: Ruleset){ } fun placeResourcesAndMinorCivs(tileMap: TileMap, minorCivs: List) { + placeNaturalWonderImpacts(tileMap) assignLuxuries() placeMinorCivs(tileMap, minorCivs) placeLuxuries(tileMap) placeStrategicAndBonuses(tileMap) } + /** Places impacts from NWs that have been generated just prior to this step. */ + private fun placeNaturalWonderImpacts(tileMap: TileMap) { + for (tile in tileMap.values.filter { it.isNaturalWonder() }) { + placeImpact(ImpactType.Bonus, tile, 1) + placeImpact(ImpactType.Strategic, tile, 1) + placeImpact(ImpactType.Luxury, tile, 1) + placeImpact(ImpactType.MinorCiv, tile, 1) + } + } + /** Assigns a luxury to each region. No luxury can be assigned to too many regions. * Some luxuries are earmarked for city states. The rest are randomly distributed or * don't occur att all in the map */ diff --git a/core/src/com/unciv/logic/map/mapgenerator/NaturalWonderGenerator.kt b/core/src/com/unciv/logic/map/mapgenerator/NaturalWonderGenerator.kt index 496001e07e..52f6c7a4c1 100644 --- a/core/src/com/unciv/logic/map/mapgenerator/NaturalWonderGenerator.kt +++ b/core/src/com/unciv/logic/map/mapgenerator/NaturalWonderGenerator.kt @@ -18,9 +18,14 @@ class NaturalWonderGenerator(val ruleset: Ruleset, val randomness: MapGeneration .filter { it.type == TerrainType.TerrainFeature } .map { it.name }.toSet() + private val blockedTiles = HashSet() + /* 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/ + Above all, look in assignstartingplots.lua! The wonders are always attempted to be placed in order of + which has the least amount of candidate tiles. There is a minimum distance between wonders equal + to the map height / 5. */ fun spawnNaturalWonders(tileMap: TileMap) { if (tileMap.mapParameters.noNaturalWonders) @@ -31,31 +36,58 @@ class NaturalWonderGenerator(val ruleset: Ruleset, val randomness: MapGeneration mapRadius * naturalWonderCountMultiplier + naturalWonderCountAddedConstant }.roundToInt() - val spawned = mutableListOf() + val chosenWonders = mutableListOf() + val wonderCandidateTiles = mutableMapOf>() val allNaturalWonders = ruleset.terrains.values .filter { it.type == TerrainType.NaturalWonder }.toMutableList() + val spawned = mutableListOf() - while (allNaturalWonders.isNotEmpty() && spawned.size < numberToSpawn) { + while (allNaturalWonders.isNotEmpty() && chosenWonders.size < numberToSpawn) { val totalWeight = allNaturalWonders.sumOf { it.weight }.toFloat() val random = randomness.RNG.nextDouble() var sum = 0f for (wonder in allNaturalWonders) { sum += wonder.weight / totalWeight if (random <= sum) { - if (spawnSpecificWonder(tileMap, wonder)) - spawned.add(wonder) + chosenWonders.add(wonder) allNaturalWonders.remove(wonder) break } } } + // First attempt to spawn the chosen wonders in order of least candidate tiles + chosenWonders.forEach { + wonderCandidateTiles[it] = getCandidateTilesForWonder(tileMap, it) + } + chosenWonders.sortBy { wonderCandidateTiles[it]!!.size } + for (wonder in chosenWonders) { + if (trySpawnOnSuitableLocation(wonderCandidateTiles[wonder]!!.filter { it !in blockedTiles }.toList(), wonder)) + spawned.add(wonder) + } + + // If some wonders were not able to be spawned we will pull a wonder from the fallback list + if (spawned.size < numberToSpawn) { + // Now we have to do some more calculations. Unfortunately we have to calculate candidate tiles for everyone. + allNaturalWonders.forEach { + wonderCandidateTiles[it] = getCandidateTilesForWonder(tileMap, it) + } + allNaturalWonders.sortBy { wonderCandidateTiles[it]!!.size } + for (wonder in allNaturalWonders) { + if (trySpawnOnSuitableLocation(wonderCandidateTiles[wonder]!!.filter { it !in blockedTiles } + .toList(), wonder)) + spawned.add(wonder) + if (spawned.size >= numberToSpawn) + break + } + } + debug("Natural Wonders for this game: %s", spawned) } private fun Unique.getIntParam(index: Int) = params[index].toInt() - private fun spawnSpecificWonder(tileMap: TileMap, naturalWonder: Terrain): Boolean { + private fun getCandidateTilesForWonder(tileMap: TileMap, naturalWonder: Terrain): Collection { val continentsRelevant = naturalWonder.hasUnique(UniqueType.NaturalWonderLargerLandmass) || naturalWonder.hasUnique(UniqueType.NaturalWonderSmallerLandmass) val sortedContinents = if (continentsRelevant) @@ -98,7 +130,7 @@ class NaturalWonderGenerator(val ruleset: Ruleset, val randomness: MapGeneration } } - return trySpawnOnSuitableLocation(suitableLocations, naturalWonder) + return suitableLocations } private fun trySpawnOnSuitableLocation(suitableLocations: List, wonder: Terrain): Boolean { @@ -138,6 +170,8 @@ class NaturalWonderGenerator(val ruleset: Ruleset, val randomness: MapGeneration clearTile(it) it.naturalWonder = wonder.name it.baseTerrain = wonder.turnsInto!! + // Add all tiles within a certain distance to a blacklist so NW:s don't cluster + blockedTiles.addAll(it.getTilesInDistance(it.tileMap.mapParameters.mapSize.height / 5)) } if (convertNeighborsTo != null) { for (tile in location.neighbors) {