From 3eff497bd8a9b3bf4de838f9d0344024ae84576e Mon Sep 17 00:00:00 2001 From: Yair Morgenstern Date: Tue, 3 Oct 2023 11:53:50 +0300 Subject: [PATCH] chore: More luxury assignment into the new object --- .../LuxuryResourcePlacementLogic.kt | 265 +++++++++++++++++- .../map/mapgenerator/mapregions/MapRegions.kt | 134 +-------- 2 files changed, 264 insertions(+), 135 deletions(-) diff --git a/core/src/com/unciv/logic/map/mapgenerator/mapregions/LuxuryResourcePlacementLogic.kt b/core/src/com/unciv/logic/map/mapgenerator/mapregions/LuxuryResourcePlacementLogic.kt index 5068bd27eb..f6e0dca117 100644 --- a/core/src/com/unciv/logic/map/mapgenerator/mapregions/LuxuryResourcePlacementLogic.kt +++ b/core/src/com/unciv/logic/map/mapgenerator/mapregions/LuxuryResourcePlacementLogic.kt @@ -1,5 +1,8 @@ package com.unciv.logic.map.mapgenerator.mapregions +import com.unciv.logic.map.MapResources +import com.unciv.logic.map.TileMap +import com.unciv.logic.map.tile.Tile import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.tile.ResourceType import com.unciv.models.ruleset.tile.TerrainType @@ -7,8 +10,11 @@ import com.unciv.models.ruleset.tile.TileResource import com.unciv.models.ruleset.unique.StateForConditionals import com.unciv.models.ruleset.unique.UniqueType import com.unciv.ui.components.extensions.randomWeighted +import kotlin.math.abs +import kotlin.math.max import kotlin.math.min import kotlin.math.pow +import kotlin.random.Random object LuxuryResourcePlacementLogic { @@ -88,8 +94,7 @@ object LuxuryResourcePlacementLogic { 100 - min(tileData.size.toFloat().pow(0.2f) * 16, 100f).toInt() // Approximately val targetDisabledLuxuries = (ruleset.tileResources.values .count { it.resourceType == ResourceType.Luxury } * disabledPercent) / 100 - val randomLuxuries = remainingLuxuries.drop(targetDisabledLuxuries) - return randomLuxuries + return remainingLuxuries.drop(targetDisabledLuxuries) } private fun getCandidateLuxuries( @@ -160,4 +165,260 @@ object LuxuryResourcePlacementLogic { } return cityStateLuxuries } + + + /** Places all Luxuries onto [tileMap]. Assumes that assignLuxuries and placeMinorCivs have been called. */ + fun placeLuxuries( + regions: ArrayList, + tileMap: TileMap, + tileData: TileDataMap, + ruleset: Ruleset, + cityStateLuxuries: List, + randomLuxuries: List + ) { + + placeLuxuriesAtMajorCivStartLocations(regions, tileMap, ruleset, tileData, randomLuxuries) + placeLuxuriesAtMinorCivStartLocations(tileMap, ruleset, regions, randomLuxuries, cityStateLuxuries, tileData) + addRegionalLuxuries(tileData, regions, tileMap, ruleset) + addRandomLuxuries(randomLuxuries, tileData, tileMap, regions, ruleset) + + + val specialLuxuries = ruleset.tileResources.values.filter { + it.resourceType == ResourceType.Luxury && + it.hasUnique(UniqueType.LuxurySpecialPlacement) + } + val placedSpecials = HashMap() + specialLuxuries.forEach { placedSpecials[it.name] = 0 } // init map + + addExtraLuxuryToStarts( + tileMap, + regions, + randomLuxuries, + specialLuxuries, + cityStateLuxuries, + tileData, + ruleset, + placedSpecials + ) + + fillSpecialLuxuries(specialLuxuries, tileMap, regions, placedSpecials, tileData) + } + + /** top up marble-type specials if needed */ + private fun fillSpecialLuxuries( + specialLuxuries: List, + tileMap: TileMap, + regions: ArrayList, + placedSpecials: HashMap, + tileData: TileDataMap + ) { + for (special in specialLuxuries) { + val targetNumber = when (tileMap.mapParameters.mapResources) { + MapResources.sparse -> (regions.size * 0.5f).toInt() + MapResources.abundant -> (regions.size * 0.9f).toInt() + else -> (regions.size * 0.75f).toInt() + } + val numberToPlace = max(2, targetNumber - placedSpecials[special.name]!!) + MapRegionResources.tryAddingResourceToTiles( + tileData, special, numberToPlace, tileMap.values.asSequence().shuffled(), 1f, + true, 6, 0 + ) + } + } + + private fun addExtraLuxuryToStarts( + tileMap: TileMap, + regions: ArrayList, + randomLuxuries: List, + specialLuxuries: List, + cityStateLuxuries: List, + tileData: TileDataMap, + ruleset: Ruleset, + placedSpecials: HashMap + ) { + if (tileMap.mapParameters.mapResources == MapResources.sparse) return + for (region in regions) { + val tilesToCheck = tileMap[region.startPosition!!].getTilesInDistanceRange(1..2) + val candidateLuxuries = randomLuxuries.shuffled().toMutableList() + if (tileMap.mapParameters.mapResources != MapResources.strategicBalance) + candidateLuxuries += specialLuxuries.shuffled() + .map { it.name } // Include marble! + candidateLuxuries += cityStateLuxuries.shuffled() + candidateLuxuries += regions.mapNotNull { it.luxury }.shuffled() + for (luxury in candidateLuxuries) { + if (MapRegionResources.tryAddingResourceToTiles( + tileData, + ruleset.tileResources[luxury]!!, + 1, + tilesToCheck + ) > 0 + ) { + if (placedSpecials.containsKey(luxury)) // Keep track of marble-type specials as they may be placed now. + placedSpecials[luxury] = placedSpecials[luxury]!! + 1 + break + } + } + } + } + + private fun addRandomLuxuries( + randomLuxuries: List, + tileData: TileDataMap, + tileMap: TileMap, + regions: ArrayList, + ruleset: Ruleset + ) { + if (randomLuxuries.isEmpty()) return + var targetRandomLuxuries = tileData.size.toFloat().pow(0.45f).toInt() // Approximately + targetRandomLuxuries *= when (tileMap.mapParameters.mapResources) { + MapResources.sparse -> 80 + MapResources.abundant -> 133 + else -> 100 + } + targetRandomLuxuries /= 100 + targetRandomLuxuries += Random.nextInt(regions.size) // Add random number based on number of civs + val minimumRandomLuxuries = tileData.size.toFloat().pow(0.2f).toInt() // Approximately + val worldTiles = tileMap.values.asSequence().shuffled() + for ((index, luxury) in randomLuxuries.shuffled().withIndex()) { + val targetForThisLuxury = if (randomLuxuries.size > 8) targetRandomLuxuries / 10 + else { + val minimum = max(3, minimumRandomLuxuries - index) + max( + minimum, + (targetRandomLuxuries * MapRegions.randomLuxuryRatios[randomLuxuries.size]!![index] + 0.5f).toInt() + ) + } + MapRegionResources.tryAddingResourceToTiles( + tileData, + ruleset.tileResources[luxury]!!, + targetForThisLuxury, + worldTiles, + 0.25f, + true, + 4, + 2 + ) + } + } + + private fun addRegionalLuxuries( + tileData: TileDataMap, + regions: ArrayList, + tileMap: TileMap, + ruleset: Ruleset + ) { + val idealCivsForMapSize = max(2, tileData.size / 500) + var regionTargetNumber = + (tileData.size / 600) - (0.3f * abs(regions.size - idealCivsForMapSize)).toInt() + regionTargetNumber += when (tileMap.mapParameters.mapResources) { + MapResources.abundant -> 1 + MapResources.sparse -> -1 + else -> 0 + } + regionTargetNumber = max(1, regionTargetNumber) + for (region in regions) { + val resource = ruleset.tileResources[region.luxury] ?: continue + fun Tile.isShoreOfContinent(continent: Int) = + isWater && neighbors.any { it.getContinent() == continent } + + val candidates = if (isWaterOnlyResource(resource, ruleset)) + tileMap.getTilesInRectangle(region.rect) + .filter { it.isShoreOfContinent(region.continentID) } + else region.tiles.asSequence() + MapRegionResources.tryAddingResourceToTiles( + tileData, + resource, + regionTargetNumber, + candidates.shuffled(), + 0.4f, + true, + 4, + 2 + ) + } + } + + private fun placeLuxuriesAtMinorCivStartLocations( + tileMap: TileMap, + ruleset: Ruleset, + regions: ArrayList, + randomLuxuries: List, + cityStateLuxuries: List, + tileData: TileDataMap + ) { + for (startLocation in tileMap.startingLocationsByNation + .filterKeys { ruleset.nations[it]!!.isCityState }.map { it.value.first() }) { + val region = regions.firstOrNull { startLocation in it.tiles } + val tilesToCheck = startLocation.getTilesInDistanceRange(1..2) + // 75% probability that we first attempt to place a "city state" luxury, then a random or regional one + // 25% probability of going the other way around + val globalLuxuries = + if (region?.luxury != null) randomLuxuries + listOf(region.luxury) else randomLuxuries + val candidateLuxuries = if (Random.nextInt(100) >= 25) + cityStateLuxuries.shuffled() + globalLuxuries.shuffled() + else + globalLuxuries.shuffled() + cityStateLuxuries.shuffled() + // Now try adding one until we are successful + for (luxury in candidateLuxuries) { + if (MapRegionResources.tryAddingResourceToTiles( + tileData, + ruleset.tileResources[luxury]!!, + 1, + tilesToCheck + ) > 0 + ) break + } + } + } + + private fun placeLuxuriesAtMajorCivStartLocations( + regions: ArrayList, + tileMap: TileMap, + ruleset: Ruleset, + tileData: TileDataMap, + randomLuxuries: List + ) { + val averageFertilityDensity = + regions.sumOf { it.totalFertility } / regions.sumOf { it.tiles.size }.toFloat() + for (region in regions) { + var targetLuxuries = 1 + if (tileMap.mapParameters.mapResources == MapResources.legendaryStart) + targetLuxuries++ + if (region.totalFertility / region.tiles.size.toFloat() < averageFertilityDensity) { + targetLuxuries++ + } + + val luxuryToPlace = ruleset.tileResources[region.luxury] ?: continue + // First check 2 inner rings + val firstPass = tileMap[region.startPosition!!].getTilesInDistanceRange(1..2) + .shuffled().sortedBy { it.getTileFertility(false) } // Check bad tiles first + targetLuxuries -= MapRegionResources.tryAddingResourceToTiles( + tileData, + luxuryToPlace, + targetLuxuries, + firstPass, + 0.5f + ) // Skip every 2nd tile on first pass + + if (targetLuxuries > 0) { + val secondPass = firstPass + tileMap[region.startPosition!!].getTilesAtDistance(3) + .shuffled().sortedBy { it.getTileFertility(false) } // Check bad tiles first + targetLuxuries -= MapRegionResources.tryAddingResourceToTiles( + tileData, + luxuryToPlace, + targetLuxuries, + secondPass + ) + } + if (targetLuxuries > 0) { + // Try adding in 1 luxury from the random rotation as compensation + for (luxury in randomLuxuries) { + if (MapRegionResources.tryAddingResourceToTiles( + tileData, ruleset.tileResources[luxury]!!, 1, firstPass) > 0 + ) break + } + } + } + } + } diff --git a/core/src/com/unciv/logic/map/mapgenerator/mapregions/MapRegions.kt b/core/src/com/unciv/logic/map/mapgenerator/mapregions/MapRegions.kt index f2fe51bfc5..79b8dbad21 100644 --- a/core/src/com/unciv/logic/map/mapgenerator/mapregions/MapRegions.kt +++ b/core/src/com/unciv/logic/map/mapgenerator/mapregions/MapRegions.kt @@ -26,7 +26,6 @@ import com.unciv.utils.Tag import kotlin.math.abs import kotlin.math.max import kotlin.math.min -import kotlin.math.pow import kotlin.math.roundToInt import kotlin.random.Random @@ -660,7 +659,7 @@ class MapRegions (val ruleset: Ruleset){ val (cityStateLuxuries, randomLuxuries) = LuxuryResourcePlacementLogic.assignLuxuries(regions, tileData, ruleset) MinorCivPlacer.placeMinorCivs(regions, tileMap, minorCivs, usingArchipelagoRegions, tileData, ruleset) - placeLuxuries(tileMap, cityStateLuxuries, randomLuxuries) + LuxuryResourcePlacementLogic.placeLuxuries(regions, tileMap, tileData, ruleset, cityStateLuxuries, randomLuxuries) placeStrategicAndBonuses(tileMap) } @@ -675,137 +674,6 @@ class MapRegions (val ruleset: Ruleset){ } - /** Places all Luxuries onto [tileMap]. Assumes that assignLuxuries and placeMinorCivs have been called. */ - private fun placeLuxuries( - tileMap: TileMap, - cityStateLuxuries: List, - randomLuxuries: List - ) { - - // First place luxuries at major civ start locations - val averageFertilityDensity = regions.sumOf { it.totalFertility } / regions.sumOf { it.tiles.size }.toFloat() - for (region in regions) { - var targetLuxuries = 1 - if (tileMap.mapParameters.mapResources == MapResources.legendaryStart) - targetLuxuries++ - if (region.totalFertility / region.tiles.size.toFloat() < averageFertilityDensity) { - targetLuxuries++ - } - - val luxuryToPlace = ruleset.tileResources[region.luxury] ?: continue - // First check 2 inner rings - val firstPass = tileMap[region.startPosition!!].getTilesInDistanceRange(1..2) - .shuffled().sortedBy { it.getTileFertility(false) } // Check bad tiles first - targetLuxuries -= MapRegionResources.tryAddingResourceToTiles(tileData, luxuryToPlace, targetLuxuries, firstPass, 0.5f) // Skip every 2nd tile on first pass - - if (targetLuxuries > 0) { - val secondPass = firstPass + tileMap[region.startPosition!!].getTilesAtDistance(3) - .shuffled().sortedBy { it.getTileFertility(false) } // Check bad tiles first - targetLuxuries -= MapRegionResources.tryAddingResourceToTiles(tileData, luxuryToPlace, targetLuxuries, secondPass) - } - if (targetLuxuries > 0) { - // Try adding in 1 luxury from the random rotation as compensation - for (luxury in randomLuxuries) { - if (MapRegionResources.tryAddingResourceToTiles(tileData, ruleset.tileResources[luxury]!!, 1, firstPass) > 0) break - } - } - } - // Second place one (1) luxury at minor civ start locations - // Check only ones that got a start location - for (startLocation in tileMap.startingLocationsByNation - .filterKeys { ruleset.nations[it]!!.isCityState }.map { it.value.first() }) { - val region = regions.firstOrNull { startLocation in it.tiles } - val tilesToCheck = startLocation.getTilesInDistanceRange(1..2) - // 75% probability that we first attempt to place a "city state" luxury, then a random or regional one - // 25% probability of going the other way around - val globalLuxuries = if (region?.luxury != null) randomLuxuries + listOf(region.luxury) else randomLuxuries - val candidateLuxuries = if (Random.nextInt(100) >= 25) - cityStateLuxuries.shuffled() + globalLuxuries.shuffled() - else - globalLuxuries.shuffled() + cityStateLuxuries.shuffled() - // Now try adding one until we are successful - for (luxury in candidateLuxuries) { - if (MapRegionResources.tryAddingResourceToTiles(tileData, ruleset.tileResources[luxury]!!, 1, tilesToCheck) > 0) break - } - } - // Third add regional luxuries - // The target number depends on map size and how close we are to an "ideal number" of civs for the map - val idealCivs = max(2, tileData.size / 500) - var regionTargetNumber = (tileData.size / 600) - (0.3f * abs(regions.size - idealCivs)).toInt() - regionTargetNumber += when (tileMap.mapParameters.mapResources) { - MapResources.abundant -> 1 - MapResources.sparse -> -1 - else -> 0 - } - regionTargetNumber = max(1, regionTargetNumber) - for (region in regions) { - val resource = ruleset.tileResources[region.luxury] ?: continue - fun Tile.isShoreOfContinent(continent: Int) = isWater && neighbors.any { it.getContinent() == continent } - val candidates = if (isWaterOnlyResource(resource, ruleset)) - tileMap.getTilesInRectangle(region.rect).filter { it.isShoreOfContinent(region.continentID) } - else region.tiles.asSequence() - MapRegionResources.tryAddingResourceToTiles(tileData, resource, regionTargetNumber, candidates.shuffled(), 0.4f, true, 4, 2) - } - // Fourth add random luxuries - if (randomLuxuries.isNotEmpty()) { - var targetRandomLuxuries = tileData.size.toFloat().pow(0.45f).toInt() // Approximately - targetRandomLuxuries *= when (tileMap.mapParameters.mapResources) { - MapResources.sparse -> 80 - MapResources.abundant -> 133 - else -> 100 - } - targetRandomLuxuries /= 100 - targetRandomLuxuries += Random.nextInt(regions.size) // Add random number based on number of civs - val minimumRandomLuxuries = tileData.size.toFloat().pow(0.2f).toInt() // Approximately - val worldTiles = tileMap.values.asSequence().shuffled() - for ((index, luxury) in randomLuxuries.shuffled().withIndex()) { - val targetForThisLuxury = if (randomLuxuries.size > 8) targetRandomLuxuries / 10 - else { - val minimum = max(3, minimumRandomLuxuries - index) - max(minimum, (targetRandomLuxuries * randomLuxuryRatios[randomLuxuries.size]!![index] + 0.5f).toInt()) - } - MapRegionResources.tryAddingResourceToTiles(tileData, ruleset.tileResources[luxury]!!, targetForThisLuxury, worldTiles, 0.25f, - true, 4, 2) - } - } - val specialLuxuries = ruleset.tileResources.values.filter { - it.resourceType == ResourceType.Luxury && - it.hasUnique(UniqueType.LuxurySpecialPlacement) - } - val placedSpecials = HashMap() - specialLuxuries.forEach { placedSpecials[it.name] = 0 } // init map - - // Fifth, on resource settings other than sparse, add an extra luxury to starts - if (tileMap.mapParameters.mapResources != MapResources.sparse) { - for (region in regions) { - val tilesToCheck = tileMap[region.startPosition!!].getTilesInDistanceRange(1..2) - val candidateLuxuries = randomLuxuries.shuffled().toMutableList() - if (tileMap.mapParameters.mapResources != MapResources.strategicBalance) - candidateLuxuries += specialLuxuries.shuffled().map { it.name } // Include marble! - candidateLuxuries += cityStateLuxuries.shuffled() - candidateLuxuries += regions.mapNotNull { it.luxury }.shuffled() - for (luxury in candidateLuxuries) { - if (MapRegionResources.tryAddingResourceToTiles(tileData, ruleset.tileResources[luxury]!!, 1, tilesToCheck) > 0) { - if (placedSpecials.containsKey(luxury)) // Keep track of marble-type specials as they may be placed now. - placedSpecials[luxury] = placedSpecials[luxury]!! + 1 - break - } - } - } - } - // Sixth, top up marble-type specials if needed - for (special in specialLuxuries) { - val targetNumber = when (tileMap.mapParameters.mapResources) { - MapResources.sparse -> (regions.size * 0.5f).toInt() - MapResources.abundant -> (regions.size * 0.9f).toInt() - else -> (regions.size * 0.75f).toInt() - } - val numberToPlace = max(2, targetNumber - placedSpecials[special.name]!!) - MapRegionResources.tryAddingResourceToTiles(tileData, special, numberToPlace, tileMap.values.asSequence().shuffled(), 1f, - true, 6, 0) - } - } - private fun placeStrategicAndBonuses(tileMap: TileMap) { val strategicResources = ruleset.tileResources.values.filter { it.resourceType == ResourceType.Strategic } // As usual, if there are any relevant json definitions, assume they are complete