Caught more mod failure conditions, removed certain assumptions from map creation (#6076)

* Caught more mod failure conditions, removed certain assumptions from map creation

* Military unit in Era can be "Era Specific Unit"

* Resolved #6078 - even more ruleset assumptions removed :)

* We now catch missing military units from the difficulties as well, as well as missing settler and worker units from eras
This commit is contained in:
Yair Morgenstern 2022-02-01 09:42:25 +02:00 committed by GitHub
parent 499b5e5b2f
commit 9674ddf04e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 56 additions and 20 deletions

View File

@ -306,7 +306,7 @@ object GameStarter {
fun getEquivalentUnit(civ: CivilizationInfo, unitParam: String): String? {
var unit = unitParam // We want to change it and this is the easiest way to do so
if (unit == Constants.eraSpecificUnit) unit = eraUnitReplacement
if (unit == "Settler" && "Settler" !in ruleSet.units) {
if (unit == Constants.settler && Constants.settler !in ruleSet.units) {
val buildableSettlerLikeUnits =
settlerLikeUnits.filter {
it.value.isBuildable(civ)

View File

@ -266,7 +266,10 @@ class CivilizationInfo {
//region pure functions
fun getDifficulty(): Difficulty {
if (isPlayerCivilization()) return gameInfo.getDifficulty()
return gameInfo.ruleSet.difficulties["Chieftain"]!!
// TODO We should be able to mark a difficulty as 'default AI difficulty' somehow
val chieftainDifficulty = gameInfo.ruleSet.difficulties["Chieftain"]
if (chieftainDifficulty != null) return chieftainDifficulty
return gameInfo.ruleSet.difficulties.values.first()
}
fun getDiplomacyManager(civInfo: CivilizationInfo) = getDiplomacyManager(civInfo.civName)

View File

@ -23,6 +23,7 @@ class MapGenerator(val ruleset: Ruleset) {
}
private var randomness = MapGenerationRandomness()
private val firstLandTerrain = ruleset.terrains.values.first { it.type==TerrainType.Land }
fun generateMap(mapParameters: MapParameters, civilizations: List<CivilizationInfo> = emptyList()): TileMap {
val mapSize = mapParameters.mapSize
@ -406,7 +407,7 @@ class MapGenerator(val ruleset: Ruleset) {
temperature <= 1.0 -> if (humidity < 0.7) Constants.desert else Constants.plains
else -> {
println("applyHumidityAndTemperature: Invalid temperature $temperature")
Constants.grassland
firstLandTerrain.name
}
}
if (ruleset.terrains.containsKey(autoTerrain)) tile.baseTerrain = autoTerrain
@ -421,7 +422,7 @@ class MapGenerator(val ruleset: Ruleset) {
if (matchingTerrain != null) tile.baseTerrain = matchingTerrain.terrain.name
else {
tile.baseTerrain = ruleset.terrains.values.firstOrNull { it.type == TerrainType.Land }?.name ?: Constants.grassland
tile.baseTerrain = firstLandTerrain.name
println("applyHumidityAndTemperature: No terrain found for temperature: $temperature, humidity: $humidity")
}
tile.setTerrainTransients()

View File

@ -11,11 +11,14 @@ import kotlin.math.pow
class MapLandmassGenerator(val ruleset: Ruleset, val randomness: MapGenerationRandomness) {
private val firstLandTerrain = ruleset.terrains.values.first { it.type==TerrainType.Land }
private val firstWaterTerrain = ruleset.terrains.values.firstOrNull { it.type==TerrainType.Water }
fun generateLand(tileMap: TileMap) {
// This is to accommodate land-only mods
if (ruleset.terrains.values.none { it.type == TerrainType.Water }) {
if (firstWaterTerrain==null) {
for (tile in tileMap.values)
tile.baseTerrain = ruleset.terrains.keys.first()
tile.baseTerrain = firstLandTerrain.name
return
}
@ -32,8 +35,8 @@ class MapLandmassGenerator(val ruleset: Ruleset, val randomness: MapGenerationRa
private fun spawnLandOrWater(tile: TileInfo, elevation: Double, threshold: Double) {
when {
elevation < threshold -> tile.baseTerrain = Constants.ocean
else -> tile.baseTerrain = Constants.grassland
elevation < threshold -> tile.baseTerrain = firstWaterTerrain!!.name
else -> tile.baseTerrain = firstLandTerrain.name
}
}

View File

@ -1449,7 +1449,12 @@ class MapRegions (val ruleset: Ruleset){
val resourceUnique = ruleset.terrains[terrain]!!.getMatchingUniques(UniqueType.RegionExtraResource).firstOrNull()
// If this region has an explicit "this is the bonus" unique go with that, else random appropriate
val resource = if (resourceUnique != null) ruleset.tileResources[resourceUnique.params[0]]!!
else ruleset.tileResources.values.filter { it.resourceType == ResourceType.Bonus && terrain in it.terrainsCanBeFoundOn }.random()
else {
val possibleResources =
ruleset.tileResources.values.filter { it.resourceType == ResourceType.Bonus && terrain in it.terrainsCanBeFoundOn }
if (possibleResources.isEmpty()) continue
possibleResources.random()
}
val candidateTiles = tileMap[region.startPosition!!].getTilesAtDistance(3).shuffled()
val amount = if (resourceUnique != null) 2 else 1 // Place an extra if the region type requests it
if (tryAddingResourceToTiles(resource, amount, candidateTiles) == 0) {
@ -1728,10 +1733,9 @@ class Region (val tileMap: TileMap, val rect: Rectangle, val continentID: Int =
for (tile in tileMap.getTilesInRectangle(rect, evenQ = true).filter {
continentID == -1 || it.getContinent() == continentID } ) {
val fertility = tile.getTileFertility(continentID != -1)
if (fertility != 0) { // If fertility is 0 this is candidate for trimming
tiles.add(tile)
totalFertility += fertility
}
tiles.add(tile)
totalFertility += fertility
if (affectedByWorldWrap)
columnHasTile.add(HexMath.hex2EvenQCoords(tile.position).x.toInt())

View File

@ -2,6 +2,7 @@ package com.unciv.models.ruleset
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.files.FileHandle
import com.unciv.Constants
import com.unciv.JsonParser
import com.unciv.logic.UncivShowableException
import com.unciv.models.Counter
@ -10,6 +11,7 @@ import com.unciv.models.metadata.BaseRuleset
import com.unciv.models.ruleset.tech.TechColumn
import com.unciv.models.ruleset.tech.Technology
import com.unciv.models.ruleset.tile.Terrain
import com.unciv.models.ruleset.tile.TerrainType
import com.unciv.models.ruleset.tile.TileImprovement
import com.unciv.models.ruleset.tile.TileResource
import com.unciv.models.ruleset.unique.Unique
@ -20,7 +22,6 @@ import com.unciv.models.ruleset.unit.Promotion
import com.unciv.models.ruleset.unit.UnitType
import com.unciv.models.stats.INamed
import com.unciv.models.stats.NamedStats
import com.unciv.models.stats.Stat
import com.unciv.models.stats.Stats
import com.unciv.models.translations.fillPlaceholders
import com.unciv.models.translations.tr
@ -481,7 +482,11 @@ class Ruleset {
// Quit here when no base ruleset is loaded - references cannot be checked
if (!modOptions.isBaseRuleset) return lines
val baseRuleset = RulesetCache.getVanillaRuleset() // for UnitTypes fallback
val vanillaRuleset = RulesetCache.getVanillaRuleset() // for UnitTypes fallback
if (units.values.none { it.uniques.contains(UniqueType.FoundCity.text) })
lines += "No city-founding units in ruleset!"
for (unit in units.values) {
if (unit.requiredTech != null && !technologies.containsKey(unit.requiredTech!!))
@ -498,7 +503,7 @@ class Ruleset {
for (promotion in unit.promotions)
if (!unitPromotions.containsKey(promotion))
lines += "${unit.name} contains promotion $promotion which does not exist!"
if (!unitTypes.containsKey(unit.unitType) && (unitTypes.isNotEmpty() || !baseRuleset.unitTypes.containsKey(unit.unitType)))
if (!unitTypes.containsKey(unit.unitType) && (unitTypes.isNotEmpty() || !vanillaRuleset.unitTypes.containsKey(unit.unitType)))
lines += "${unit.name} is of type ${unit.unitType}, which does not exist!"
for (unique in unit.getMatchingUniques(UniqueType.ConstructImprovementConsumingUnit)) {
val improvementName = unique.params[0]
@ -557,6 +562,8 @@ class Ruleset {
checkUniques(improvement, lines, UniqueType.UniqueComplianceErrorSeverity.RulesetSpecific)
}
if (terrains.values.none { it.type==TerrainType.Land && !it.impassable })
lines += "No passable land terrains exist!"
for (terrain in terrains.values) {
for (baseTerrain in terrain.occursOn)
if (!terrains.containsKey(baseTerrain))
@ -597,6 +604,13 @@ class Ruleset {
lines += "Eras file is empty! This will likely lead to crashes. Ask the mod maker to update this mod!"
}
val allDifficultiesStartingUnits = hashSetOf<String>()
for (difficulty in difficulties.values){
allDifficultiesStartingUnits.addAll(difficulty.aiCityStateBonusStartingUnits)
allDifficultiesStartingUnits.addAll(difficulty.aiMajorCivBonusStartingUnits)
allDifficultiesStartingUnits.addAll(difficulty.playerBonusStartingUnits)
}
for (era in eras.values) {
for (wonder in era.startingObsoleteWonders)
if (wonder !in buildings)
@ -604,7 +618,13 @@ class Ruleset {
for (building in era.settlerBuildings)
if (building !in buildings)
lines += "Nonexistent building $building built by settlers when starting in ${era.name}"
if (era.startingMilitaryUnit !in units)
// todo the whole 'starting unit' thing needs to be redone, there's no reason we can't have a single list containing all the starting units.
if (era.startingSettlerUnit !in units && (era.startingSettlerUnit!=Constants.settler || units.values.none { it.hasUnique(UniqueType.FoundCity) }))
lines += "Nonexistent unit ${era.startingSettlerUnit} marked as starting unit when starting in ${era.name}"
if (era.startingWorkerCount!=0 && era.startingWorkerUnit !in units)
lines += "Nonexistent unit ${era.startingWorkerUnit} marked as starting unit when starting in ${era.name}"
if ((era.startingMilitaryUnitCount !=0 || allDifficultiesStartingUnits.contains(Constants.eraSpecificUnit)) && era.startingMilitaryUnit !in units)
lines += "Nonexistent unit ${era.startingMilitaryUnit} marked as starting unit when starting in ${era.name}"
if (era.researchAgreementCost < 0 || era.startingSettlerCount < 0 || era.startingWorkerCount < 0 || era.startingMilitaryUnitCount < 0 || era.startingGold < 0 || era.startingCulture < 0)
lines += "Unexpected negative number found while parsing era ${era.name}"
@ -621,7 +641,7 @@ class Ruleset {
if (nation.favoredReligion != null && nation.favoredReligion !in religions)
lines += "${nation.name} has ${nation.favoredReligion} as their favored religion, which does not exist!"
}
for (policy in policies.values) {
if (policy.requires != null)
for (prereq in policy.requires!!)
@ -641,14 +661,19 @@ class Ruleset {
if (!unitPromotions.containsKey(prereq))
lines.add("${promotion.name} requires promotion $prereq which does not exist!", RulesetErrorSeverity.Warning)
for (unitType in promotion.unitTypes)
if (!unitTypes.containsKey(unitType) && (unitTypes.isNotEmpty() || !baseRuleset.unitTypes.containsKey(unitType)))
if (!unitTypes.containsKey(unitType) && (unitTypes.isNotEmpty() || !vanillaRuleset.unitTypes.containsKey(unitType)))
lines.add("${promotion.name} references unit type $unitType, which does not exist!", RulesetErrorSeverity.Warning)
checkUniques(promotion, lines, UniqueType.UniqueComplianceErrorSeverity.RulesetSpecific)
}
for (unitType in unitTypes.values) {
checkUniques(unitType, lines, UniqueType.UniqueComplianceErrorSeverity.RulesetSpecific)
}
for (difficulty in difficulties.values)
for (unitName in difficulty.aiCityStateBonusStartingUnits + difficulty.aiMajorCivBonusStartingUnits + difficulty.playerBonusStartingUnits)
if (unitName!=Constants.eraSpecificUnit && !units.containsKey(unitName))
lines += "Difficulty ${difficulty.name} contains starting unit $unitName which does not exist!"
if (modOptions.maxXPfromBarbarians != 30) {
lines.add("maxXPfromBarbarians is moved to the constants object, instead use: \nconstants: {\n maxXPfromBarbarians: ${modOptions.maxXPfromBarbarians},\n}", RulesetErrorSeverity.Warning)
}