From 9674ddf04edb4c82bb0e1e760a76480bbead5a56 Mon Sep 17 00:00:00 2001 From: Yair Morgenstern Date: Tue, 1 Feb 2022 09:42:25 +0200 Subject: [PATCH] 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 --- core/src/com/unciv/logic/GameStarter.kt | 2 +- .../logic/civilization/CivilizationInfo.kt | 5 ++- .../logic/map/mapgenerator/MapGenerator.kt | 5 ++- .../map/mapgenerator/MapLandmassGenerator.kt | 11 ++++-- .../logic/map/mapgenerator/MapRegions.kt | 14 ++++--- core/src/com/unciv/models/ruleset/Ruleset.kt | 39 +++++++++++++++---- 6 files changed, 56 insertions(+), 20 deletions(-) diff --git a/core/src/com/unciv/logic/GameStarter.kt b/core/src/com/unciv/logic/GameStarter.kt index 8be438caf1..51803ff37f 100644 --- a/core/src/com/unciv/logic/GameStarter.kt +++ b/core/src/com/unciv/logic/GameStarter.kt @@ -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) diff --git a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt index 8e8cd45329..05b955370a 100644 --- a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt +++ b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt @@ -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) diff --git a/core/src/com/unciv/logic/map/mapgenerator/MapGenerator.kt b/core/src/com/unciv/logic/map/mapgenerator/MapGenerator.kt index 9d2cbbbfee..436acbf068 100644 --- a/core/src/com/unciv/logic/map/mapgenerator/MapGenerator.kt +++ b/core/src/com/unciv/logic/map/mapgenerator/MapGenerator.kt @@ -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 = 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() diff --git a/core/src/com/unciv/logic/map/mapgenerator/MapLandmassGenerator.kt b/core/src/com/unciv/logic/map/mapgenerator/MapLandmassGenerator.kt index 70f8cdcc90..f9aca44ce2 100644 --- a/core/src/com/unciv/logic/map/mapgenerator/MapLandmassGenerator.kt +++ b/core/src/com/unciv/logic/map/mapgenerator/MapLandmassGenerator.kt @@ -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 } } diff --git a/core/src/com/unciv/logic/map/mapgenerator/MapRegions.kt b/core/src/com/unciv/logic/map/mapgenerator/MapRegions.kt index ece0e94a7f..7bccab680d 100644 --- a/core/src/com/unciv/logic/map/mapgenerator/MapRegions.kt +++ b/core/src/com/unciv/logic/map/mapgenerator/MapRegions.kt @@ -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()) diff --git a/core/src/com/unciv/models/ruleset/Ruleset.kt b/core/src/com/unciv/models/ruleset/Ruleset.kt index 389df5d258..968550b4d6 100644 --- a/core/src/com/unciv/models/ruleset/Ruleset.kt +++ b/core/src/com/unciv/models/ruleset/Ruleset.kt @@ -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() + 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) }