diff --git a/core/src/com/unciv/logic/map/mapgenerator/NaturalWonderGenerator.kt b/core/src/com/unciv/logic/map/mapgenerator/NaturalWonderGenerator.kt index 16f7ee1ac5..5c3489b251 100644 --- a/core/src/com/unciv/logic/map/mapgenerator/NaturalWonderGenerator.kt +++ b/core/src/com/unciv/logic/map/mapgenerator/NaturalWonderGenerator.kt @@ -151,18 +151,10 @@ class NaturalWonderGenerator(val ruleset: Ruleset, val randomness: MapGeneration val targetGroupSize = if (minGroupSize == maxGroupSize) maxGroupSize else (minGroupSize..maxGroupSize).random(randomness.RNG) - var convertNeighborsExcept: String? = null - var convertUnique = wonder.getMatchingUniques(UniqueType.NaturalWonderConvertNeighbors).firstOrNull() - var convertNeighborsTo = convertUnique?.params?.get(0) - if (convertNeighborsTo == null) { - convertUnique = wonder.getMatchingUniques(UniqueType.NaturalWonderConvertNeighborsExcept).firstOrNull() - convertNeighborsExcept = convertUnique?.params?.get(0) - convertNeighborsTo = convertUnique?.params?.get(1) - } - if (suitableLocations.size >= minGroupSize) { val location = suitableLocations.random(randomness.RNG) val list = mutableListOf(location) + while (list.size < targetGroupSize) { val allNeighbors = list.flatMap { it.neighbors }.minus(list).toHashSet() val candidates = suitableLocations.filter { it in allNeighbors } @@ -171,30 +163,11 @@ class NaturalWonderGenerator(val ruleset: Ruleset, val randomness: MapGeneration } if (list.size >= minGroupSize) { list.forEach { - clearTile(it) - it.naturalWonder = wonder.name - if (wonder.turnsInto != null) - it.baseTerrain = wonder.turnsInto!! + placeNaturalWonder(wonder, location) // 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) { - if (tile.baseTerrain == convertNeighborsTo) continue - if (tile.baseTerrain == convertNeighborsExcept) continue - if (convertNeighborsTo == Constants.coast) - for (neighbor in tile.neighbors) { - // This is so we don't have this tile turn into Coast, and then it's touching a Lake tile. - // We just turn the lake tiles into this kind of tile. - if (neighbor.baseTerrain == Constants.lakes) { - neighbor.baseTerrain = tile.baseTerrain - neighbor.setTerrainTransients() - } - } - tile.baseTerrain = convertNeighborsTo - clearTile(tile) - } - } + debug("Natural Wonder %s @%s", wonder.name, location.position) return true @@ -205,11 +178,47 @@ class NaturalWonderGenerator(val ruleset: Ruleset, val randomness: MapGeneration return false } - private fun clearTile(tile: Tile) { - tile.setTerrainFeatures(listOf()) - tile.resource = null - tile.removeImprovement() - tile.setTerrainTransients() + companion object { + fun placeNaturalWonder(wonder: Terrain, location: Tile) { + clearTile(location) + location.naturalWonder = wonder.name + if (wonder.turnsInto != null) + location.baseTerrain = wonder.turnsInto!! + + var convertNeighborsExcept: String? = null + var convertUnique = wonder.getMatchingUniques(UniqueType.NaturalWonderConvertNeighbors).firstOrNull() + var convertNeighborsTo = convertUnique?.params?.get(0) + if (convertNeighborsTo == null) { + convertUnique = wonder.getMatchingUniques(UniqueType.NaturalWonderConvertNeighborsExcept).firstOrNull() + convertNeighborsExcept = convertUnique?.params?.get(0) + convertNeighborsTo = convertUnique?.params?.get(1) + } + + if (convertNeighborsTo != null) { + for (tile in location.neighbors) { + if (tile.baseTerrain == convertNeighborsTo) continue + if (tile.baseTerrain == convertNeighborsExcept) continue + if (convertNeighborsTo == Constants.coast) + for (neighbor in tile.neighbors) { + // This is so we don't have this tile turn into Coast, and then it's touching a Lake tile. + // We just turn the lake tiles into this kind of tile. + if (neighbor.baseTerrain == Constants.lakes) { + neighbor.baseTerrain = tile.baseTerrain + neighbor.setTerrainTransients() + } + } + tile.baseTerrain = convertNeighborsTo + clearTile(tile) + } + } + } + + private fun clearTile(tile: Tile) { + tile.setTerrainFeatures(listOf()) + tile.resource = null + tile.removeImprovement() + tile.setTerrainTransients() + } } /** Implements [UniqueParameterType.SimpleTerrain][com.unciv.models.ruleset.unique.UniqueParameterType.SimpleTerrain] */ diff --git a/core/src/com/unciv/logic/map/tile/Tile.kt b/core/src/com/unciv/logic/map/tile/Tile.kt index 325c834930..663151382e 100644 --- a/core/src/com/unciv/logic/map/tile/Tile.kt +++ b/core/src/com/unciv/logic/map/tile/Tile.kt @@ -26,6 +26,7 @@ import com.unciv.models.ruleset.unique.UniqueMap import com.unciv.models.ruleset.unique.UniqueType import com.unciv.ui.components.extensions.withItem import com.unciv.ui.components.extensions.withoutItem +import com.unciv.ui.screens.mapeditorscreen.TileInfoNormalizer import com.unciv.utils.DebugUtils import com.unciv.utils.Log import kotlin.math.abs @@ -832,6 +833,14 @@ open class Tile : IsPartOfGameInfoSerialization { else unitHeight } + fun setBaseTerrain(baseTerrainObject: Terrain){ + baseTerrain = baseTerrainObject.name + this.baseTerrainObject = baseTerrainObject + TileInfoNormalizer.normalizeToRuleset(this, ruleset) + setTerrainFeatures(terrainFeatures) + setTerrainTransients() + } + private fun updateUniqueMap() { if (!::tileMap.isInitialized) return // This tile is a fake tile, for visual display only (e.g. map editor, civilopedia) val terrainNameList = allTerrains.map { it.name }.toList() diff --git a/core/src/com/unciv/models/ruleset/unique/UniqueParameterType.kt b/core/src/com/unciv/models/ruleset/unique/UniqueParameterType.kt index a86f1ec573..8b2ed87f5f 100644 --- a/core/src/com/unciv/models/ruleset/unique/UniqueParameterType.kt +++ b/core/src/com/unciv/models/ruleset/unique/UniqueParameterType.kt @@ -375,7 +375,7 @@ enum class UniqueParameterType( /** Used by [NaturalWonderGenerator.trySpawnOnSuitableLocation][com.unciv.logic.map.mapgenerator.NaturalWonderGenerator.trySpawnOnSuitableLocation], only tests base terrain */ BaseTerrain("baseTerrain", Constants.grassland, "The name of any terrain that is a base terrain according to the json file") { override fun getErrorSeverity(parameterText: String, ruleset: Ruleset): - UniqueType.UniqueParameterErrorSeverity? { + UniqueType.UniqueParameterErrorSeverity? { if (ruleset.terrains[parameterText]?.type?.isBaseTerrain == true) return null return UniqueType.UniqueParameterErrorSeverity.RulesetSpecific } diff --git a/core/src/com/unciv/models/ruleset/unique/UniqueTriggerActivation.kt b/core/src/com/unciv/models/ruleset/unique/UniqueTriggerActivation.kt index 71c7199ba3..7207235c05 100644 --- a/core/src/com/unciv/models/ruleset/unique/UniqueTriggerActivation.kt +++ b/core/src/com/unciv/models/ruleset/unique/UniqueTriggerActivation.kt @@ -16,15 +16,18 @@ import com.unciv.logic.civilization.NotificationIcon import com.unciv.logic.civilization.PolicyAction import com.unciv.logic.civilization.TechAction import com.unciv.logic.civilization.managers.ReligionState +import com.unciv.logic.map.mapgenerator.NaturalWonderGenerator import com.unciv.logic.map.mapunit.MapUnit import com.unciv.logic.map.tile.Tile import com.unciv.models.UpgradeUnitAction import com.unciv.models.ruleset.BeliefType +import com.unciv.models.ruleset.tile.TerrainType import com.unciv.models.stats.Stat import com.unciv.models.stats.Stats import com.unciv.models.translations.fillPlaceholders import com.unciv.models.translations.hasPlaceholderParameters import com.unciv.ui.components.extensions.addToMapOfSets +import com.unciv.ui.screens.mapeditorscreen.TileInfoNormalizer import com.unciv.ui.screens.worldscreen.unit.actions.UnitActionsUpgrade import kotlin.math.roundToInt import kotlin.random.Random @@ -926,6 +929,28 @@ object UniqueTriggerActivation { true } } + + UniqueType.OneTimeChangeTerrain -> { + if (tile == null) return null + val terrain = ruleSet.terrains[unique.params[0]] ?: return null + if (terrain.type == TerrainType.TerrainFeature && !terrain.occursOn.contains(tile.lastTerrain.name)) + return null + if (tile.terrainFeatures.contains(terrain.name)) return null + if (tile.isCityCenter() && terrain.type != TerrainType.Land) return null + if (terrain.type.isBaseTerrain && tile.baseTerrain == terrain.name) return null + + return { + when (terrain.type) { + TerrainType.Land, TerrainType.Water -> tile.setBaseTerrain(terrain) + TerrainType.TerrainFeature -> tile.addTerrainFeature(terrain.name) + TerrainType.NaturalWonder -> NaturalWonderGenerator.placeNaturalWonder(terrain, tile) + } + TileInfoNormalizer.normalizeToRuleset(tile, ruleSet) + tile.getUnits().filter { !it.movement.canPassThrough(tile) }.toList() + .forEach { it.movement.teleportToClosestMoveableTile() } + true + } + } else -> return null } } diff --git a/core/src/com/unciv/models/ruleset/unique/UniqueType.kt b/core/src/com/unciv/models/ruleset/unique/UniqueType.kt index cbdb508eb3..16dd7c2dde 100644 --- a/core/src/com/unciv/models/ruleset/unique/UniqueType.kt +++ b/core/src/com/unciv/models/ruleset/unique/UniqueType.kt @@ -761,6 +761,8 @@ enum class UniqueType( SkipPromotion("Doing so will consume this opportunity to choose a Promotion", UniqueTarget.Promotion), FreePromotion("This Promotion is free", UniqueTarget.Promotion), + OneTimeChangeTerrain("Turn this tile into a [terrainName] tile", UniqueTarget.UnitTriggerable), + UnitsGainPromotion("[mapUnitFilter] units gain the [promotion] promotion", UniqueTarget.Triggerable), // Not used in Vanilla FreeStatBuildings("Provides the cheapest [stat] building in your first [positiveAmount] cities for free", UniqueTarget.Triggerable), // used in Policy FreeSpecificBuildings("Provides a [buildingName] in your first [positiveAmount] cities for free", UniqueTarget.Triggerable), // used in Policy diff --git a/docs/Modders/uniques.md b/docs/Modders/uniques.md index 94c96f1662..b87220809f 100644 --- a/docs/Modders/uniques.md +++ b/docs/Modders/uniques.md @@ -190,6 +190,11 @@ Simple unique parameters are explained by mouseover. Complex parameters are expl Applicable to: UnitTriggerable +??? example "Turn this tile into a [terrain] tile" + Example: "Turn this tile into a [Unknown] tile" + + Applicable to: UnitTriggerable + ## Global uniques !!! note "" diff --git a/tests/src/com/unciv/testing/BasicTests.kt b/tests/src/com/unciv/testing/BasicTests.kt index 063cab72ea..a5b4e2e19c 100644 --- a/tests/src/com/unciv/testing/BasicTests.kt +++ b/tests/src/com/unciv/testing/BasicTests.kt @@ -110,7 +110,7 @@ class BasicTests { for (paramType in entry.value) { if (paramType == UniqueParameterType.Unknown) { val badParam = uniqueType.text.getPlaceholderParameters()[entry.index] - debug("%s param[%s] type \"%s\" is unknown", uniqueType.name, entry.index, badParam) + println("${uniqueType.name} param[${entry.index}] type \"$badParam\" is unknown") noUnknownParameters = false } }