Merge remote-tracking branch 'origin/master'

This commit is contained in:
yairm210 2021-10-19 20:26:54 +03:00
commit 55bed3bf30
11 changed files with 71 additions and 40 deletions

View File

@ -6,6 +6,7 @@ import com.unciv.logic.civilization.NotificationIcon
import com.unciv.logic.map.TileInfo import com.unciv.logic.map.TileInfo
import com.unciv.logic.map.TileMap import com.unciv.logic.map.TileMap
import com.unciv.models.metadata.GameSpeed import com.unciv.models.metadata.GameSpeed
import com.unciv.models.ruleset.unique.UniqueType
import java.util.* import java.util.*
import kotlin.collections.HashMap import kotlin.collections.HashMap
import kotlin.math.max import kotlin.math.max
@ -210,7 +211,7 @@ class Encampment {
|| it.isCityCenter() || it.isCityCenter()
|| it.getFirstUnit() != null || it.getFirstUnit() != null
|| (it.isWater && !canSpawnBoats) || (it.isWater && !canSpawnBoats)
|| (it.hasUnique("Fresh water") && it.isWater) // No Lakes || (it.hasUnique(UniqueType.FreshWater) && it.isWater) // No Lakes
} }
if (validTiles.isEmpty()) return false if (validTiles.isEmpty()) return false

View File

@ -704,13 +704,13 @@ object Battle {
tile.turnsToImprovement = 0 tile.turnsToImprovement = 0
tile.roadStatus = RoadStatus.None tile.roadStatus = RoadStatus.None
if (tile.isLand && !tile.isImpassible() && !tile.terrainFeatures.contains("Fallout")) { if (tile.isLand && !tile.isImpassible() && !tile.terrainFeatures.contains("Fallout")) {
if (tile.terrainFeatures.any { attacker.getCivInfo().gameInfo.ruleSet.terrains[it]!!.uniques.contains("Resistant to nukes") }) { if (tile.terrainFeatures.any { attacker.getCivInfo().gameInfo.ruleSet.terrains[it]!!.hasUnique(UniqueType.ResistsNukes) }) {
if (Random().nextFloat() < 0.25f) { if (Random().nextFloat() < 0.25f) {
tile.terrainFeatures.removeAll { attacker.getCivInfo().gameInfo.ruleSet.terrains[it]!!.uniques.contains("Can be destroyed by nukes") } tile.terrainFeatures.removeAll { attacker.getCivInfo().gameInfo.ruleSet.terrains[it]!!.hasUnique(UniqueType.DestroyableByNukes) }
tile.terrainFeatures.add("Fallout") tile.terrainFeatures.add("Fallout")
} }
} else if (Random().nextFloat() < 0.5f) { } else if (Random().nextFloat() < 0.5f) {
tile.terrainFeatures.removeAll { attacker.getCivInfo().gameInfo.ruleSet.terrains[it]!!.uniques.contains("Can be destroyed by nukes") } tile.terrainFeatures.removeAll { attacker.getCivInfo().gameInfo.ruleSet.terrains[it]!!.hasUnique(UniqueType.DestroyableByNukes) }
tile.terrainFeatures.add("Fallout") tile.terrainFeatures.add("Fallout")
} }
} }
@ -767,13 +767,13 @@ object Battle {
tile.turnsToImprovement = 0 tile.turnsToImprovement = 0
tile.roadStatus = RoadStatus.None tile.roadStatus = RoadStatus.None
if (tile.isLand && !tile.isImpassible() && !tile.terrainFeatures.contains("Fallout")) { if (tile.isLand && !tile.isImpassible() && !tile.terrainFeatures.contains("Fallout")) {
if (tile.terrainFeatures.any { attacker.getCivInfo().gameInfo.ruleSet.terrains[it]!!.uniques.contains("Resistant to nukes") }) { if (tile.terrainFeatures.any { attacker.getCivInfo().gameInfo.ruleSet.terrains[it]!!.hasUnique(UniqueType.ResistsNukes) }) {
if (Random().nextFloat() < 0.25f) { if (Random().nextFloat() < 0.25f) {
tile.terrainFeatures.removeAll { attacker.getCivInfo().gameInfo.ruleSet.terrains[it]!!.uniques.contains("Can be destroyed by nukes") } tile.terrainFeatures.removeAll { attacker.getCivInfo().gameInfo.ruleSet.terrains[it]!!.hasUnique(UniqueType.DestroyableByNukes) }
tile.terrainFeatures.add("Fallout") tile.terrainFeatures.add("Fallout")
} }
} else if (Random().nextFloat() < 0.5f) { } else if (Random().nextFloat() < 0.5f) {
tile.terrainFeatures.removeAll { attacker.getCivInfo().gameInfo.ruleSet.terrains[it]!!.uniques.contains("Can be destroyed by nukes") } tile.terrainFeatures.removeAll { attacker.getCivInfo().gameInfo.ruleSet.terrains[it]!!.hasUnique(UniqueType.DestroyableByNukes) }
tile.terrainFeatures.add("Fallout") tile.terrainFeatures.add("Fallout")
} }
} }

View File

@ -4,6 +4,7 @@ import com.unciv.logic.city.CityInfo
import com.unciv.logic.civilization.CivilizationInfo import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.map.TileInfo import com.unciv.logic.map.TileInfo
import com.unciv.models.UncivSound import com.unciv.models.UncivSound
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.ruleset.unit.UnitType import com.unciv.models.ruleset.unit.UnitType
import kotlin.math.pow import kotlin.math.pow
import kotlin.math.roundToInt import kotlin.math.roundToInt
@ -39,9 +40,8 @@ class CityCombatant(val city: CityInfo) : ICombatant {
var strength = 8f var strength = 8f
strength += (city.population.population / 5) * 2 // Each 5 pop gives 2 defence strength += (city.population.population / 5) * 2 // Each 5 pop gives 2 defence
val cityTile = city.getCenterTile() val cityTile = city.getCenterTile()
for (unique in cityTile.getAllTerrains().flatMap { it.uniqueObjects }) for (unique in cityTile.getAllTerrains().flatMap { it.getMatchingUniques(UniqueType.GrantsCityStrength) })
if (unique.placeholderText == "[] Strength for cities built on this terrain") strength += unique.params[0].toInt()
strength += unique.params[0].toInt()
// as tech progresses so does city strength // as tech progresses so does city strength
val techCount = getCivInfo().gameInfo.ruleSet.technologies.count() val techCount = getCivInfo().gameInfo.ruleSet.technologies.count()
val techsPercentKnown: Float = if (techCount > 0) city.civInfo.tech.techsResearched.count().toFloat() / techCount else 0.5f // for mods with no tech val techsPercentKnown: Float = if (techCount > 0) city.civInfo.tech.techsResearched.count().toFloat() / techCount else 0.5f // for mods with no tech

View File

@ -294,6 +294,7 @@ class CityStateFunctions(val civInfo: CivilizationInfo) {
city.moveToCiv(otherCiv) city.moveToCiv(otherCiv)
city.isPuppet = true // Human players get a popup that allows them to annex instead city.isPuppet = true // Human players get a popup that allows them to annex instead
city.foundingCiv = "" // This is no longer a city-state city.foundingCiv = "" // This is no longer a city-state
city.isOriginalCapital = false // It's now an ordinary city and can be razed in later conquests
} }
civInfo.destroy() civInfo.destroy()
} }

View File

@ -1,7 +1,6 @@
package com.unciv.logic.civilization package com.unciv.logic.civilization
import com.badlogic.gdx.math.Vector2 import com.badlogic.gdx.math.Vector2
import com.unciv.Constants
import com.unciv.UncivGame import com.unciv.UncivGame
import com.unciv.logic.GameInfo import com.unciv.logic.GameInfo
import com.unciv.logic.UncivShowableException import com.unciv.logic.UncivShowableException
@ -343,7 +342,7 @@ class CivilizationInfo {
cities.asSequence().flatMap { cities.asSequence().flatMap {
city -> city ->
if (cityItIsFor != null && city == cityItIsFor) if (cityItIsFor != null && city == cityItIsFor)
city.getAllUniquesWithNonLocalEffects().filter { it.params.none { param -> param == "in other cities" } } city.getAllUniquesWithNonLocalEffects().filter { it.params.none { param -> param == "in other cities" || param == "in all cities" } }
else city.getAllUniquesWithNonLocalEffects() else city.getAllUniquesWithNonLocalEffects()
} }
@ -714,7 +713,7 @@ class CivilizationInfo {
passThroughImpassableUnlocked = passableImpassables.isNotEmpty() passThroughImpassableUnlocked = passableImpassables.isNotEmpty()
// Cache whether this civ gets nonstandard terrain damage for performance reasons. // Cache whether this civ gets nonstandard terrain damage for performance reasons.
nonStandardTerrainDamage = getMatchingUniques("Units ending their turn on [] tiles take [] damage") nonStandardTerrainDamage = getMatchingUniques(UniqueType.DamagesContainingUnits)
.any { gameInfo.ruleSet.terrains[it.params[0]]!!.damagePerTurn != it.params[1].toInt() } .any { gameInfo.ruleSet.terrains[it.params[0]]!!.damagePerTurn != it.params[1].toInt() }
// Cache the last era each resource is used for buildings or units respectively for AI building evaluation // Cache the last era each resource is used for buildings or units respectively for AI building evaluation

View File

@ -614,9 +614,7 @@ class MapUnit {
tile.roadStatus = RoadStatus.None tile.roadStatus = RoadStatus.None
else { else {
val removedFeatureObject = tile.ruleset.terrains[removedFeatureName] val removedFeatureObject = tile.ruleset.terrains[removedFeatureName]
if (removedFeatureObject != null && removedFeatureObject.uniques if (removedFeatureObject != null && removedFeatureObject.hasUnique(UniqueType.ProductionBonusWhenRemoved)) {
.contains("Provides a one-time Production bonus to the closest city when cut down")
) {
tryProvideProductionToClosestCity(removedFeatureName) tryProvideProductionToClosestCity(removedFeatureName)
} }
tile.terrainFeatures.remove(removedFeatureName) tile.terrainFeatures.remove(removedFeatureName)
@ -1006,7 +1004,7 @@ class MapUnit {
fun getDamageFromTerrain(tile: TileInfo = currentTile): Int { fun getDamageFromTerrain(tile: TileInfo = currentTile): Int {
if (civInfo.nonStandardTerrainDamage) { if (civInfo.nonStandardTerrainDamage) {
for (unique in getMatchingUniques("Units ending their turn on [] tiles take [] damage")) { for (unique in getMatchingUniques(UniqueType.DamagesContainingUnits)) {
if (unique.params[0] in tile.getAllTerrains().map { it.name }) { if (unique.params[0] in tile.getAllTerrains().map { it.name }) {
return unique.params[1].toInt() // Use the damage from the unique return unique.params[1].toInt() // Use the damage from the unique
} }

View File

@ -436,7 +436,7 @@ open class TileInfo {
&& getTileImprovement().let { it != null && it.hasUnique("Irremovable") } -> false && getTileImprovement().let { it != null && it.hasUnique("Irremovable") } -> false
// Terrain blocks most improvements // Terrain blocks most improvements
getAllTerrains().any { it.getMatchingUniques("Only [] improvements may be built on this tile") getAllTerrains().any { it.getMatchingUniques(UniqueType.RestrictedBuildableImprovements)
.any { unique -> !improvement.matchesFilter(unique.params[0]) } } -> false .any { unique -> !improvement.matchesFilter(unique.params[0]) } } -> false
// Decide cancelImprovementOrder earlier, otherwise next check breaks it // Decide cancelImprovementOrder earlier, otherwise next check breaks it
@ -630,6 +630,17 @@ open class TileInfo {
FormattedLine("{$resource} ($resourceAmount)", link="Resource/$resource") FormattedLine("{$resource} ($resourceAmount)", link="Resource/$resource")
else else
FormattedLine(resource!!, link="Resource/$resource") FormattedLine(resource!!, link="Resource/$resource")
if (resource != null && viewingCiv != null && hasViewableResource(viewingCiv)) {
val tileImprovement = ruleset.tileImprovements[getTileResource().improvement]
if (tileImprovement?.techRequired != null
&& !viewingCiv.tech.isResearched(tileImprovement.techRequired!!)) {
lineList += FormattedLine(
"Requires [${tileImprovement.techRequired}]",
link="Technology/${tileImprovement.techRequired}",
color= "#FAA"
)
}
}
if (naturalWonder != null) if (naturalWonder != null)
lineList += FormattedLine(naturalWonder!!, link="Terrain/$naturalWonder") lineList += FormattedLine(naturalWonder!!, link="Terrain/$naturalWonder")
if (roadStatus !== RoadStatus.None && !isCityCenter()) if (roadStatus !== RoadStatus.None && !isCityCenter())

View File

@ -317,14 +317,17 @@ class TileMap {
This can all be summed up as "I can see c if a>b || c>b || (a==b && b !blocks same-elevation view)" This can all be summed up as "I can see c if a>b || c>b || (a==b && b !blocks same-elevation view)"
*/ */
val containsViewableNeighborThatCanSeeOver = cTile.neighbors.any { val containsViewableNeighborThatCanSeeOver = cTile.neighbors.any { bNeighbor: TileInfo ->
bNeighbor: TileInfo ->
val bNeighborHeight = bNeighbor.height val bNeighborHeight = bNeighbor.height
viewableTiles.contains(bNeighbor) && ( viewableTiles.contains(bNeighbor)
currentTileHeight > bNeighborHeight // a>b && (
|| cTileHeight > bNeighborHeight // c>b currentTileHeight > bNeighborHeight // a>b
|| currentTileHeight == bNeighborHeight // a==b || cTileHeight > bNeighborHeight // c>b
&& !bNeighbor.hasUnique("Blocks line-of-sight from tiles at same elevation")) || (
currentTileHeight == bNeighborHeight // a==b
&& !bNeighbor.hasUnique(UniqueType.BlocksLineOfSightAtSameElevation)
)
)
} }
if (containsViewableNeighborThatCanSeeOver) tilesToAddInDistanceI.add(cTile) if (containsViewableNeighborThatCanSeeOver) tilesToAddInDistanceI.add(cTile)
} }

View File

@ -9,6 +9,7 @@ import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.tile.ResourceType import com.unciv.models.ruleset.tile.ResourceType
import com.unciv.models.ruleset.tile.Terrain import com.unciv.models.ruleset.tile.Terrain
import com.unciv.models.ruleset.tile.TerrainType import com.unciv.models.ruleset.tile.TerrainType
import com.unciv.models.ruleset.unique.UniqueType
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.max import kotlin.math.max
import kotlin.math.pow import kotlin.math.pow
@ -223,9 +224,11 @@ class MapGenerator(val ruleset: Ruleset) {
* [MapParameters.elevationExponent] favors high elevation * [MapParameters.elevationExponent] favors high elevation
*/ */
private fun raiseMountainsAndHills(tileMap: TileMap) { private fun raiseMountainsAndHills(tileMap: TileMap) {
val mountain = ruleset.terrains.values.firstOrNull { it.uniques.contains("Occurs in chains at high elevations") }?.name val mountain = ruleset.terrains.values.firstOrNull { it.hasUnique(UniqueType.OccursInChains) }?.name
val hill = ruleset.terrains.values.firstOrNull { it.uniques.contains("Occurs in groups around high elevations") }?.name val hill = ruleset.terrains.values.firstOrNull { it.hasUnique(UniqueType.OccursInGroups) }?.name
val flat = ruleset.terrains.values.firstOrNull { !it.impassable && it.type == TerrainType.Land && !it.uniques.contains("Rough Terrain") }?.name val flat = ruleset.terrains.values.firstOrNull {
!it.impassable && it.type == TerrainType.Land && !it.hasUnique(UniqueType.RoughTerrain)
}?.name
if (flat == null) { if (flat == null) {
println("Ruleset seems to contain no flat terrain - can't generate heightmap") println("Ruleset seems to contain no flat terrain - can't generate heightmap")
@ -358,12 +361,11 @@ class MapGenerator(val ruleset: Ruleset) {
val tempFrom: Float, val tempTo: Float, val tempFrom: Float, val tempTo: Float,
val humidFrom: Float, val humidTo: Float val humidFrom: Float, val humidTo: Float
) )
// List is OK here as it's only sequentially scanned
val limitsMap: List<TerrainOccursRange> = val limitsMap: List<TerrainOccursRange> =
// List is OK here as it's only sequentially scanned ruleset.terrains.values.flatMap { terrain ->
ruleset.terrains.values.flatMap { terrain -> terrain.getMatchingUniques(UniqueType.TileGenerationConditions)
terrain.uniqueObjects.filter { .map { unique ->
it.placeholderText == "Occurs at temperature between [] and [] and humidity between [] and []"
}.map { unique ->
TerrainOccursRange(terrain, TerrainOccursRange(terrain,
unique.params[0].toFloat(), unique.params[1].toFloat(), unique.params[0].toFloat(), unique.params[1].toFloat(),
unique.params[2].toFloat(), unique.params[3].toFloat()) unique.params[2].toFloat(), unique.params[3].toFloat())
@ -432,7 +434,7 @@ class MapGenerator(val ruleset: Ruleset) {
*/ */
private fun spawnRareFeatures(tileMap: TileMap) { private fun spawnRareFeatures(tileMap: TileMap) {
val rareFeatures = ruleset.terrains.values.filter { val rareFeatures = ruleset.terrains.values.filter {
it.type == TerrainType.TerrainFeature && it.uniques.contains("Rare feature") it.type == TerrainType.TerrainFeature && it.hasUnique(UniqueType.RareFeature)
} }
for (tile in tileMap.values.asSequence().filter { it.terrainFeatures.isEmpty() }) { for (tile in tileMap.values.asSequence().filter { it.terrainFeatures.isEmpty() }) {
if (randomness.RNG.nextDouble() <= tileMap.mapParameters.rareFeaturesRichness) { if (randomness.RNG.nextDouble() <= tileMap.mapParameters.rareFeaturesRichness) {

View File

@ -40,7 +40,8 @@ class Terrain : RulesetStatsObject() {
@Transient @Transient
var damagePerTurn = 0 var damagePerTurn = 0
fun isRough(): Boolean = uniques.contains("Rough terrain") // Shouldn't this just be a lazy property so it's automatically cached?
fun isRough(): Boolean = hasUnique(UniqueType.RoughTerrain)
/** Tests base terrains, features and natural wonders whether they should be treated as Land/Water. /** Tests base terrains, features and natural wonders whether they should be treated as Land/Water.
* Currently only used for civilopedia display, as other code can test the tile itself. * Currently only used for civilopedia display, as other code can test the tile itself.
@ -141,8 +142,6 @@ class Terrain : RulesetStatsObject() {
} }
fun setTransients() { fun setTransients() {
damagePerTurn = uniqueObjects.sumOf { damagePerTurn = getMatchingUniques(UniqueType.DamagesContainingUnits).sumOf { it.params[0].toInt() }
if (it.placeholderText == "Units ending their turn on this terrain take [] damage") it.params[0].toInt() else 0
}
} }
} }

View File

@ -261,16 +261,33 @@ enum class UniqueType(val text:String, vararg targets: UniqueTarget) {
// The "Except [terrainFilter]" could theoretically be implemented with a conditional // The "Except [terrainFilter]" could theoretically be implemented with a conditional
NaturalWonderConvertNeighborsExcept("Neighboring tiles except [baseTerrain] will convert to [baseTerrain]", UniqueTarget.Terrain), NaturalWonderConvertNeighborsExcept("Neighboring tiles except [baseTerrain] will convert to [baseTerrain]", UniqueTarget.Terrain),
DamagesContainingUnits("Units ending their turn on this terrain take [amount] damage", UniqueTarget.Terrain),
TerrainGrantsPromotion("Grants [promotion] ([comment]) to adjacent [mapUnitFilter] units for the rest of the game", UniqueTarget.Terrain), TerrainGrantsPromotion("Grants [promotion] ([comment]) to adjacent [mapUnitFilter] units for the rest of the game", UniqueTarget.Terrain),
GrantsCityStrength("[amount] Strength for cities built on this terrain", UniqueTarget.Terrain),
ProductionBonusWhenRemoved("Provides a one-time Production bonus to the closest city when cut down", UniqueTarget.Terrain),
TileProvidesYieldWithoutPopulation("Tile provides yield without assigned population", UniqueTarget.Terrain, UniqueTarget.Improvement), TileProvidesYieldWithoutPopulation("Tile provides yield without assigned population", UniqueTarget.Terrain, UniqueTarget.Improvement),
NullifyYields("Nullifies all other stats this tile provides", UniqueTarget.Terrain), NullifyYields("Nullifies all other stats this tile provides", UniqueTarget.Terrain),
RestrictedBuildableImprovements("Only [improvementFilter] improvements may be built on this tile", UniqueTarget.Terrain),
BlocksLineOfSightAtSameElevation("Blocks line-of-sight from tiles at same elevation", UniqueTarget.Terrain),
VisibilityElevation("Has an elevation of [amount] for visibility calculations", UniqueTarget.Terrain), VisibilityElevation("Has an elevation of [amount] for visibility calculations", UniqueTarget.Terrain),
NoNaturalGeneration("Doesn't generate naturally", UniqueTarget.Terrain), NoNaturalGeneration("Doesn't generate naturally", UniqueTarget.Terrain),
TileGenerationConditions("Occurs at temperature between [amount] and [amount] and humidity between [amount] and [amount]", UniqueTarget.Terrain),
OccursInChains("Occurs in chains at high elevations", UniqueTarget.Terrain),
OccursInGroups("Occurs in groups around high elevations", UniqueTarget.Terrain),
RareFeature("Rare feature", UniqueTarget.Terrain),
ResistsNukes("Resistant to nukes", UniqueTarget.Terrain),
DestroyableByNukes("Can be destroyed by nukes", UniqueTarget.Terrain),
FreshWater("Fresh water", UniqueTarget.Terrain),
RoughTerrain("Rough terrain", UniqueTarget.Terrain),
// Resource uniques
OverrideDepositAmountOnTileFilter("Deposits in [tileFilter] tiles always provide [amount] resources", UniqueTarget.Resource), OverrideDepositAmountOnTileFilter("Deposits in [tileFilter] tiles always provide [amount] resources", UniqueTarget.Resource),
///////////////////////////////////////// CONDITIONALS ///////////////////////////////////////// ///////////////////////////////////////// CONDITIONALS /////////////////////////////////////////