Fix crashes when a civ does not have a capital (#6889)

This commit is contained in:
OptimizedForDensity 2022-05-22 11:00:42 -04:00 committed by GitHub
parent 740886c890
commit 39bbb2de1c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 172 additions and 168 deletions

View File

@ -100,8 +100,8 @@ class BarbarianManager {
if (campsToAdd <= 0) return if (campsToAdd <= 0) return
// Camps can't spawn within 7 tiles of each other or within 4 tiles of major civ capitals // Camps can't spawn within 7 tiles of each other or within 4 tiles of major civ capitals
val tooCloseToCapitals = gameInfo.civilizations.filterNot { it.isBarbarian() || it.isSpectator() || it.cities.isEmpty() || it.isCityState() } val tooCloseToCapitals = gameInfo.civilizations.filterNot { it.isBarbarian() || it.isSpectator() || it.cities.isEmpty() || it.isCityState() || it.getCapital() == null }
.flatMap { it.getCapital().getCenterTile().getTilesInDistance(4) }.toSet() .flatMap { it.getCapital()!!.getCenterTile().getTilesInDistance(4) }.toSet()
val tooCloseToCamps = camps val tooCloseToCamps = camps
.flatMap { tileMap[it.key].getTilesInDistance( .flatMap { tileMap[it.key].getTilesInDistance(
if (it.value.destroyed) 4 else 7 if (it.value.destroyed) 4 else 7
@ -132,7 +132,7 @@ class BarbarianManager {
tile = viableTiles.random() tile = viableTiles.random()
} else } else
tile = viableTiles.random() tile = viableTiles.random()
tile.improvement = Constants.barbarianEncampment tile.improvement = Constants.barbarianEncampment
val newCamp = Encampment(tile.position) val newCamp = Encampment(tile.position)
newCamp.gameInfo = gameInfo newCamp.gameInfo = gameInfo
@ -296,4 +296,4 @@ class Encampment() {
} }
countdown /= 100 countdown /= 100
} }
} }

View File

@ -345,7 +345,7 @@ class GameInfo {
getAliveCityStates() getAliveCityStates()
.asSequence() .asSequence()
.filter { it.cityStateResource == resourceName } .filter { it.cityStateResource == resourceName }
.map { it.getCapital().getCenterTile() } .map { it.getCapital()!!.getCenterTile() }
} else { } else {
tileMap.values tileMap.values
.asSequence() .asSequence()

View File

@ -213,7 +213,7 @@ object Automation {
// If we have vision of our entire starting continent (ish) we are not afraid // If we have vision of our entire starting continent (ish) we are not afraid
civInfo.gameInfo.tileMap.assignContinents(TileMap.AssignContinentsMode.Ensure) civInfo.gameInfo.tileMap.assignContinents(TileMap.AssignContinentsMode.Ensure)
val startingContinent = civInfo.getCapital().getCenterTile().getContinent() val startingContinent = civInfo.getCapital()!!.getCenterTile().getContinent()
val startingContinentSize = civInfo.gameInfo.tileMap.continentSizes[startingContinent] val startingContinentSize = civInfo.gameInfo.tileMap.continentSizes[startingContinent]
if (startingContinentSize != null && startingContinentSize < civInfo.viewableTiles.size * multiplier) if (startingContinentSize != null && startingContinentSize < civInfo.viewableTiles.size * multiplier)
return false return false

View File

@ -473,7 +473,7 @@ object NextTurnAutomation {
for (resource in civInfo.gameInfo.spaceResources) { for (resource in civInfo.gameInfo.spaceResources) {
// Have enough resources already // Have enough resources already
val resourceCount = civInfo.getCivResourcesByName()[resource] ?: 0 val resourceCount = civInfo.getCivResourcesByName()[resource] ?: 0
if (resourceCount >= Automation.getReservedSpaceResourceAmount(civInfo)) if (resourceCount >= Automation.getReservedSpaceResourceAmount(civInfo))
continue continue
@ -691,7 +691,7 @@ object NextTurnAutomation {
private fun motivationToAttack(civInfo: CivilizationInfo, otherCiv: CivilizationInfo): Int { private fun motivationToAttack(civInfo: CivilizationInfo, otherCiv: CivilizationInfo): Int {
if(civInfo.cities.isEmpty() || otherCiv.cities.isEmpty()) return 0 if(civInfo.cities.isEmpty() || otherCiv.cities.isEmpty()) return 0
val baseForce = 30f val baseForce = 30f
val ourCombatStrength = civInfo.getStatForRanking(RankingType.Force).toFloat() + baseForce val ourCombatStrength = civInfo.getStatForRanking(RankingType.Force).toFloat() + baseForce
var theirCombatStrength = otherCiv.getStatForRanking(RankingType.Force).toFloat() + baseForce var theirCombatStrength = otherCiv.getStatForRanking(RankingType.Force).toFloat() + baseForce
@ -705,9 +705,9 @@ object NextTurnAutomation {
val closestCities = getClosestCities(civInfo, otherCiv) val closestCities = getClosestCities(civInfo, otherCiv)
val ourCity = closestCities.city1 val ourCity = closestCities.city1
val theirCity = closestCities.city2 val theirCity = closestCities.city2
if (civInfo.getCivUnits().filter { it.isMilitary() }.none { if (civInfo.getCivUnits().filter { it.isMilitary() }.none {
val damageRecievedWhenAttacking = val damageRecievedWhenAttacking =
BattleDamage.calculateDamageToAttacker( BattleDamage.calculateDamageToAttacker(
MapUnitCombatant(it), MapUnitCombatant(it),
CityCombatant(theirCity) CityCombatant(theirCity)
@ -722,7 +722,7 @@ object NextTurnAutomation {
&& (owner == otherCiv || owner == null || civInfo.canPassThroughTiles(owner)) && (owner == otherCiv || owner == null || civInfo.canPassThroughTiles(owner))
} }
val reachableEnemyCitiesBfs = BFS(civInfo.getCapital().getCenterTile()) { isTileCanMoveThrough(it) } val reachableEnemyCitiesBfs = BFS(civInfo.getCapital()!!.getCenterTile()) { isTileCanMoveThrough(it) }
reachableEnemyCitiesBfs.stepToEnd() reachableEnemyCitiesBfs.stepToEnd()
val reachableEnemyCities = otherCiv.cities.filter { reachableEnemyCitiesBfs.hasReachedTile(it.getCenterTile()) } val reachableEnemyCities = otherCiv.cities.filter { reachableEnemyCitiesBfs.hasReachedTile(it.getCenterTile()) }
if (reachableEnemyCities.isEmpty()) return 0 // Can't even reach the enemy city, no point in war. if (reachableEnemyCities.isEmpty()) return 0 // Can't even reach the enemy city, no point in war.

View File

@ -279,7 +279,8 @@ object SpecificUnitAutomation {
} }
fun automateAddInCapital(unit: MapUnit) { fun automateAddInCapital(unit: MapUnit) {
val capitalTile = unit.civInfo.getCapital().getCenterTile() if (unit.civInfo.getCapital() == null) return // safeguard
val capitalTile = unit.civInfo.getCapital()!!.getCenterTile()
if (unit.movement.canReach(capitalTile)) if (unit.movement.canReach(capitalTile))
unit.movement.headTowards(capitalTile) unit.movement.headTowards(capitalTile)
if (unit.getTile() == capitalTile) { if (unit.getTile() == capitalTile) {
@ -287,7 +288,7 @@ object SpecificUnitAutomation {
return return
} }
} }
fun automateMissionary(unit: MapUnit) { fun automateMissionary(unit: MapUnit) {
if (unit.religion != unit.civInfo.religionManager.religion?.name) if (unit.religion != unit.civInfo.religionManager.religion?.name)
return unit.destroy() return unit.destroy()
@ -425,7 +426,7 @@ object SpecificUnitAutomation {
val firstStepInPath = pathsToCities[closestCityThatCanAttackFrom]!!.first() val firstStepInPath = pathsToCities[closestCityThatCanAttackFrom]!!.first()
airUnit.movement.moveToTile(firstStepInPath) airUnit.movement.moveToTile(firstStepInPath)
} }
fun automateNukes(unit: MapUnit) { fun automateNukes(unit: MapUnit) {
val tilesInRange = unit.currentTile.getTilesInDistance(unit.getRange()) val tilesInRange = unit.currentTile.getTilesInDistance(unit.getRange())
for (tile in tilesInRange) { for (tile in tilesInRange) {
@ -443,7 +444,7 @@ object SpecificUnitAutomation {
if (BattleHelper.tryAttackNearbyEnemy(unit)) return if (BattleHelper.tryAttackNearbyEnemy(unit)) return
tryRelocateToNearbyAttackableCities(unit) tryRelocateToNearbyAttackableCities(unit)
} }
private fun tryRelocateToNearbyAttackableCities(unit: MapUnit) { private fun tryRelocateToNearbyAttackableCities(unit: MapUnit) {
val tilesInRange = unit.currentTile.getTilesInDistance(unit.getRange()) val tilesInRange = unit.currentTile.getTilesInDistance(unit.getRange())
val immediatelyReachableCities = tilesInRange val immediatelyReachableCities = tilesInRange
@ -455,7 +456,7 @@ object SpecificUnitAutomation {
unit.movement.moveToTile(city) unit.movement.moveToTile(city)
return return
} }
if (unit.baseUnit.isAirUnit()) { if (unit.baseUnit.isAirUnit()) {
val pathsToCities = unit.movement.getAerialPathsToCities() val pathsToCities = unit.movement.getAerialPathsToCities()
if (pathsToCities.isEmpty()) return // can't actually move anywhere else if (pathsToCities.isEmpty()) return // can't actually move anywhere else
@ -479,7 +480,7 @@ object SpecificUnitAutomation {
fun foundReligion(unit: MapUnit) { fun foundReligion(unit: MapUnit) {
val cityToFoundReligionAt = val cityToFoundReligionAt =
if (unit.getTile().isCityCenter() && !unit.getTile().owningCity!!.isHolyCity()) unit.getTile().owningCity if (unit.getTile().isCityCenter() && !unit.getTile().owningCity!!.isHolyCity()) unit.getTile().owningCity
else unit.civInfo.cities.firstOrNull { else unit.civInfo.cities.firstOrNull {
!it.isHolyCity() !it.isHolyCity()
&& unit.movement.canMoveTo(it.getCenterTile()) && unit.movement.canMoveTo(it.getCenterTile())
@ -493,16 +494,16 @@ object SpecificUnitAutomation {
UnitActions.getFoundReligionAction(unit)() UnitActions.getFoundReligionAction(unit)()
} }
fun enhanceReligion(unit: MapUnit) { fun enhanceReligion(unit: MapUnit) {
// Try go to a nearby city // Try go to a nearby city
if (!unit.getTile().isCityCenter()) if (!unit.getTile().isCityCenter())
UnitAutomation.tryEnterOwnClosestCity(unit) UnitAutomation.tryEnterOwnClosestCity(unit)
// If we were unable to go there this turn, unable to do anything else // If we were unable to go there this turn, unable to do anything else
if (!unit.getTile().isCityCenter()) if (!unit.getTile().isCityCenter())
return return
UnitActions.getEnhanceReligionAction(unit)() UnitActions.getEnhanceReligionAction(unit)()
} }

View File

@ -28,7 +28,7 @@ private object WorkerAutomationConst {
* Contains the logic for worker automation. * Contains the logic for worker automation.
* *
* This is instantiated from [CivilizationInfo.getWorkerAutomation] and cached there. * This is instantiated from [CivilizationInfo.getWorkerAutomation] and cached there.
* *
* @param civInfo The Civilization - data common to all automated workers is cached once per Civ * @param civInfo The Civilization - data common to all automated workers is cached once per Civ
* @param cachedForTurn The turn number this was created for - a recreation of the instance is forced on different turn numbers * @param cachedForTurn The turn number this was created for - a recreation of the instance is forced on different turn numbers
*/ */
@ -57,7 +57,7 @@ class WorkerAutomation(
&& !it.isCapital() && !it.isBeingRazed // Cities being razed should not be connected. && !it.isCapital() && !it.isBeingRazed // Cities being razed should not be connected.
&& !it.cityStats.isConnectedToCapital(bestRoadAvailable) && !it.cityStats.isConnectedToCapital(bestRoadAvailable)
}.sortedBy { }.sortedBy {
it.getCenterTile().aerialDistanceTo(civInfo.getCapital().getCenterTile()) it.getCenterTile().aerialDistanceTo(civInfo.getCapital()!!.getCenterTile())
}.toList() }.toList()
if (WorkerAutomationConst.consoleOutput) { if (WorkerAutomationConst.consoleOutput) {
println("WorkerAutomation citiesThatNeedConnecting for ${civInfo.civName} turn $cachedForTurn:") println("WorkerAutomation citiesThatNeedConnecting for ${civInfo.civName} turn $cachedForTurn:")
@ -88,9 +88,9 @@ class WorkerAutomation(
} }
/** Caches BFS by city locations (cities needing connecting). /** Caches BFS by city locations (cities needing connecting).
* *
* key: The city to connect from as [hex position][Vector2]. * key: The city to connect from as [hex position][Vector2].
* *
* value: The [BFS] searching from that city, whether successful or not. * value: The [BFS] searching from that city, whether successful or not.
*/ */
//todo: If BFS were to deal in vectors instead of TileInfos, we could copy this on cloning //todo: If BFS were to deal in vectors instead of TileInfos, we could copy this on cloning
@ -116,7 +116,7 @@ class WorkerAutomation(
fun evaluateFortPlacement(tile: TileInfo, civInfo: CivilizationInfo, isCitadel: Boolean): Boolean { fun evaluateFortPlacement(tile: TileInfo, civInfo: CivilizationInfo, isCitadel: Boolean): Boolean {
return civInfo.getWorkerAutomation().evaluateFortPlacement(tile, isCitadel) return civInfo.getWorkerAutomation().evaluateFortPlacement(tile, isCitadel)
} }
/** For console logging only */ /** For console logging only */
private fun MapUnit.label() = toString() + " " + getTile().position.toString() private fun MapUnit.label() = toString() + " " + getTile().position.toString()
} }
@ -297,7 +297,7 @@ class WorkerAutomation(
if (tile.improvementInProgress != null && unit.canBuildImprovement(tile.getTileImprovementInProgress()!!, tile)) return true if (tile.improvementInProgress != null && unit.canBuildImprovement(tile.getTileImprovementInProgress()!!, tile)) return true
val chosenImprovement = chooseImprovement(unit, tile) val chosenImprovement = chooseImprovement(unit, tile)
if (chosenImprovement != null && tile.canBuildImprovement(chosenImprovement, civInfo) && unit.canBuildImprovement(chosenImprovement, tile)) return true if (chosenImprovement != null && tile.canBuildImprovement(chosenImprovement, civInfo) && unit.canBuildImprovement(chosenImprovement, tile)) return true
} else if (!tile.containsGreatImprovement() && tile.hasViewableResource(civInfo) } else if (!tile.containsGreatImprovement() && tile.hasViewableResource(civInfo)
&& tile.tileResource.isImprovedBy(tile.improvement!!) && tile.tileResource.isImprovedBy(tile.improvement!!)
&& (chooseImprovement(unit, tile) // if the chosen improvement is not null and buildable && (chooseImprovement(unit, tile) // if the chosen improvement is not null and buildable

View File

@ -16,7 +16,7 @@ import kotlin.math.pow
import kotlin.math.roundToInt import kotlin.math.roundToInt
object BattleDamage { object BattleDamage {
private fun getModifierStringFromUnique(unique: Unique): String { private fun getModifierStringFromUnique(unique: Unique): String {
val source = when (unique.sourceObjectType) { val source = when (unique.sourceObjectType) {
UniqueTarget.Unit -> "Unit ability" UniqueTarget.Unit -> "Unit ability"
@ -49,9 +49,9 @@ object BattleDamage {
for (unique in combatant.getMatchingUniques( for (unique in combatant.getMatchingUniques(
UniqueType.StrengthNearCapital, conditionalState, true UniqueType.StrengthNearCapital, conditionalState, true
)) { )) {
if (civInfo.cities.isEmpty()) break if (civInfo.cities.isEmpty() || civInfo.getCapital() == null) break
val distance = val distance =
combatant.getTile().aerialDistanceTo(civInfo.getCapital().getCenterTile()) combatant.getTile().aerialDistanceTo(civInfo.getCapital()!!.getCenterTile())
// https://steamcommunity.com/sharedfiles/filedetails/?id=326411722#464287 // https://steamcommunity.com/sharedfiles/filedetails/?id=326411722#464287
val effect = unique.params[0].toInt() - 3 * distance val effect = unique.params[0].toInt() - 3 * distance
if (effect <= 0) continue if (effect <= 0) continue
@ -168,7 +168,7 @@ object BattleDamage {
fun getDefenceModifiers(attacker: ICombatant, defender: ICombatant): Counter<String> { fun getDefenceModifiers(attacker: ICombatant, defender: ICombatant): Counter<String> {
val modifiers = getGeneralModifiers(defender, attacker, CombatAction.Defend) val modifiers = getGeneralModifiers(defender, attacker, CombatAction.Defend)
val tile = defender.getTile() val tile = defender.getTile()
if (defender is MapUnitCombatant) { if (defender is MapUnitCombatant) {
if (defender.unit.isEmbarked()) { if (defender.unit.isEmbarked()) {
@ -196,7 +196,7 @@ object BattleDamage {
return modifiers return modifiers
} }
@Deprecated("As of 4.0.3", level=DeprecationLevel.WARNING) @Deprecated("As of 4.0.3", level=DeprecationLevel.WARNING)
private fun getTileSpecificModifiers(unit: MapUnitCombatant, tile: TileInfo): Counter<String> { private fun getTileSpecificModifiers(unit: MapUnitCombatant, tile: TileInfo): Counter<String> {
val modifiers = Counter<String>() val modifiers = Counter<String>()
@ -225,7 +225,7 @@ object BattleDamage {
1f 1f
} }
// Each 3 points of health reduces damage dealt by 1% // Each 3 points of health reduces damage dealt by 1%
else 1 - (100 - combatant.getHealth()) / 300f else 1 - (100 - combatant.getHealth()) / 300f
} }

View File

@ -55,12 +55,12 @@ class StatTreeNode {
} }
/** Holds and calculates [Stats] for a city. /** Holds and calculates [Stats] for a city.
* *
* No field needs to be saved, all are calculated on the fly, * No field needs to be saved, all are calculated on the fly,
* so its field in [CityInfo] is @Transient and no such annotation is needed here. * so its field in [CityInfo] is @Transient and no such annotation is needed here.
*/ */
class CityStats(val cityInfo: CityInfo) { class CityStats(val cityInfo: CityInfo) {
//region Fields, Transient //region Fields, Transient
var baseStatTree = StatTreeNode() var baseStatTree = StatTreeNode()
@ -83,7 +83,7 @@ class CityStats(val cityInfo: CityInfo) {
val stats = Stats() val stats = Stats()
if (!cityInfo.isCapital() && cityInfo.isConnectedToCapital()) { if (!cityInfo.isCapital() && cityInfo.isConnectedToCapital()) {
val civInfo = cityInfo.civInfo val civInfo = cityInfo.civInfo
stats.gold = civInfo.getCapital().population.population * 0.15f + cityInfo.population.population * 1.1f - 1 // Calculated by http://civilization.wikia.com/wiki/Trade_route_(Civ5) stats.gold = civInfo.getCapital()!!.population.population * 0.15f + cityInfo.population.population * 1.1f - 1 // Calculated by http://civilization.wikia.com/wiki/Trade_route_(Civ5)
for (unique in cityInfo.getMatchingUniques(UniqueType.StatsFromTradeRoute)) for (unique in cityInfo.getMatchingUniques(UniqueType.StatsFromTradeRoute))
stats.add(unique.stats) stats.add(unique.stats)
val percentageStats = Stats() val percentageStats = Stats()
@ -161,7 +161,7 @@ class CityStats(val cityInfo: CityInfo) {
} }
} }
} }
for (unique in cityInfo.civInfo.getMatchingUniques(UniqueType.BonusStatsFromCityStates)) { for (unique in cityInfo.civInfo.getMatchingUniques(UniqueType.BonusStatsFromCityStates)) {
stats[Stat.valueOf(unique.params[1])] *= unique.params[0].toPercent() stats[Stat.valueOf(unique.params[1])] *= unique.params[0].toPercent()
} }
@ -314,10 +314,11 @@ class CityStats(val cityInfo: CityInfo) {
unique.params[0].toFloat() * cityInfo.religion.getFollowersOfMajorityReligion(), unique.params[0].toFloat() * cityInfo.religion.getFollowersOfMajorityReligion(),
unique.params[2].toFloat() unique.params[2].toFloat()
)) ))
if (currentConstruction is Building if (currentConstruction is Building
&& cityInfo.civInfo.cities.isNotEmpty() && cityInfo.civInfo.cities.isNotEmpty()
&& cityInfo.civInfo.getCapital().cityConstructions.builtBuildings.contains(currentConstruction.name) && cityInfo.civInfo.getCapital() != null
&& cityInfo.civInfo.getCapital()!!.cityConstructions.builtBuildings.contains(currentConstruction.name)
) { ) {
for (unique in cityInfo.getMatchingUniques(UniqueType.PercentProductionBuildingsInCapital)) for (unique in cityInfo.getMatchingUniques(UniqueType.PercentProductionBuildingsInCapital))
addUniqueStats(unique, Stat.Production, unique.params[0].toFloat()) addUniqueStats(unique, Stat.Production, unique.params[0].toFloat())
@ -399,19 +400,19 @@ class CityStats(val cityInfo: CityInfo) {
unhappinessFromCity *= 2f //doubled for the Indian unhappinessFromCity *= 2f //doubled for the Indian
newHappinessList["Cities"] = unhappinessFromCity * unhappinessModifier newHappinessList["Cities"] = unhappinessFromCity * unhappinessModifier
var unhappinessFromCitizens = cityInfo.population.population.toFloat() var unhappinessFromCitizens = cityInfo.population.population.toFloat()
for (unique in cityInfo.getMatchingUniques(UniqueType.UnhappinessFromPopulationTypePercentageChange)) for (unique in cityInfo.getMatchingUniques(UniqueType.UnhappinessFromPopulationTypePercentageChange))
if (cityInfo.matchesFilter(unique.params[2])) if (cityInfo.matchesFilter(unique.params[2]))
unhappinessFromCitizens += (unique.params[0].toFloat() / 100f) * cityInfo.population.getPopulationFilterAmount(unique.params[1]) unhappinessFromCitizens += (unique.params[0].toFloat() / 100f) * cityInfo.population.getPopulationFilterAmount(unique.params[1])
// Deprecated as of 3.19.19 // Deprecated as of 3.19.19
for (unique in cityInfo.getMatchingUniques(UniqueType.UnhappinessFromSpecialistsPercentageChange)) { for (unique in cityInfo.getMatchingUniques(UniqueType.UnhappinessFromSpecialistsPercentageChange)) {
if (cityInfo.matchesFilter(unique.params[1])) if (cityInfo.matchesFilter(unique.params[1]))
unhappinessFromCitizens += unique.params[0].toFloat() / 100f * cityInfo.population.getNumberOfSpecialists() unhappinessFromCitizens += unique.params[0].toFloat() / 100f * cityInfo.population.getNumberOfSpecialists()
} }
for (unique in cityInfo.getMatchingUniques(UniqueType.UnhappinessFromPopulationPercentageChange)) for (unique in cityInfo.getMatchingUniques(UniqueType.UnhappinessFromPopulationPercentageChange))
if (cityInfo.matchesFilter(unique.params[1])) if (cityInfo.matchesFilter(unique.params[1]))
unhappinessFromCitizens += unique.params[0].toFloat() / 100f * cityInfo.population.population unhappinessFromCitizens += unique.params[0].toFloat() / 100f * cityInfo.population.population
@ -423,7 +424,7 @@ class CityStats(val cityInfo: CityInfo) {
unhappinessFromCitizens *= 2f unhappinessFromCitizens *= 2f
if (unhappinessFromCitizens < 0) unhappinessFromCitizens = 0f if (unhappinessFromCitizens < 0) unhappinessFromCitizens = 0f
newHappinessList["Population"] = -unhappinessFromCitizens * unhappinessModifier newHappinessList["Population"] = -unhappinessFromCitizens * unhappinessModifier
if (hasExtraAnnexUnhappiness()) newHappinessList["Occupied City"] = -2f //annexed city if (hasExtraAnnexUnhappiness()) newHappinessList["Occupied City"] = -2f //annexed city
@ -453,7 +454,7 @@ class CityStats(val cityInfo: CityInfo) {
val newBaseStatTree = StatTreeNode() val newBaseStatTree = StatTreeNode()
// We don't edit the existing baseStatList directly, in order to avoid concurrency exceptions // We don't edit the existing baseStatList directly, in order to avoid concurrency exceptions
val newBaseStatList = StatMap() val newBaseStatList = StatMap()
newBaseStatTree.addStats(Stats( newBaseStatTree.addStats(Stats(
science = cityInfo.population.population.toFloat(), science = cityInfo.population.population.toFloat(),
@ -605,7 +606,7 @@ class CityStats(val cityInfo: CityInfo) {
newFinalStatList["Excess food to production"] = newFinalStatList["Excess food to production"] =
Stats(production = getProductionFromExcessiveFood(totalFood), food = -totalFood) Stats(production = getProductionFromExcessiveFood(totalFood), food = -totalFood)
} }
val growthNullifyingUnique = cityInfo.getMatchingUniques(UniqueType.NullifiesGrowth).firstOrNull() val growthNullifyingUnique = cityInfo.getMatchingUniques(UniqueType.NullifiesGrowth).firstOrNull()
if (growthNullifyingUnique != null) { if (growthNullifyingUnique != null) {
val amountToRemove = -newFinalStatList.values.sumOf { it[Stat.Food].toDouble() } val amountToRemove = -newFinalStatList.values.sumOf { it[Stat.Food].toDouble() }
@ -615,7 +616,7 @@ class CityStats(val cityInfo: CityInfo) {
if (cityInfo.isInResistance()) if (cityInfo.isInResistance())
newFinalStatList.clear() // NOPE newFinalStatList.clear() // NOPE
if (newFinalStatList.values.map { it.production }.sum() < 1) // Minimum production for things to progress if (newFinalStatList.values.map { it.production }.sum() < 1) // Minimum production for things to progress
newFinalStatList["Production"] = Stats(production = 1f) newFinalStatList["Production"] = Stats(production = 1f)
finalStatList = newFinalStatList finalStatList = newFinalStatList

View File

@ -8,7 +8,7 @@ import kotlin.collections.set
class CapitalConnectionsFinder(private val civInfo: CivilizationInfo) { class CapitalConnectionsFinder(private val civInfo: CivilizationInfo) {
private val citiesReachedToMediums = HashMap<CityInfo, MutableSet<String>>() private val citiesReachedToMediums = HashMap<CityInfo, MutableSet<String>>()
private var citiesToCheck = mutableListOf(civInfo.getCapital()) private var citiesToCheck = mutableListOf(civInfo.getCapital()!!)
private lateinit var newCitiesToCheck: MutableList<CityInfo> private lateinit var newCitiesToCheck: MutableList<CityInfo>
private val openBordersCivCities = civInfo.gameInfo.getCities().filter { civInfo.canEnterBordersOf(it.civInfo) } private val openBordersCivCities = civInfo.gameInfo.getCities().filter { civInfo.canEnterBordersOf(it.civInfo) }
@ -24,7 +24,7 @@ class CapitalConnectionsFinder(private val civInfo: CivilizationInfo) {
private val railroadIsResearched = ruleset.tileImprovements.containsKey(railroad) && civInfo.tech.isResearched(ruleset.tileImprovements[railroad]!!.techRequired!!) private val railroadIsResearched = ruleset.tileImprovements.containsKey(railroad) && civInfo.tech.isResearched(ruleset.tileImprovements[railroad]!!.techRequired!!)
init { init {
citiesReachedToMediums[civInfo.getCapital()] = hashSetOf("Start") citiesReachedToMediums[civInfo.getCapital()!!] = hashSetOf("Start")
} }
fun find(): Map<CityInfo, Set<String>> { fun find(): Map<CityInfo, Set<String>> {
@ -124,4 +124,4 @@ class CapitalConnectionsFinder(private val civInfo: CivilizationInfo) {
citiesReachedToMediums[this]!!.add(transportType) citiesReachedToMediums[this]!!.add(transportType)
} }
} }

View File

@ -99,7 +99,7 @@ class CityStateFunctions(val civInfo: CivilizationInfo) {
return null return null
return uniqueUnit return uniqueUnit
} }
fun randomGiftableUnit() = fun randomGiftableUnit() =
city.cityConstructions.getConstructableUnits() city.cityConstructions.getConstructableUnits()
.filter { !it.isCivilian() && it.isLandUnit() && it.uniqueTo == null } .filter { !it.isCivilian() && it.isLandUnit() && it.uniqueTo == null }
.toList().randomOrNull() .toList().randomOrNull()
@ -111,7 +111,7 @@ class CityStateFunctions(val civInfo: CivilizationInfo) {
val placedUnit = receivingCiv.placeUnitNearTile(city.location, militaryUnit.name) ?: return val placedUnit = receivingCiv.placeUnitNearTile(city.location, militaryUnit.name) ?: return
// The unit should have bonuses from Barracks, Alhambra etc as if it was built in the CS capital // The unit should have bonuses from Barracks, Alhambra etc as if it was built in the CS capital
militaryUnit.addConstructionBonuses(placedUnit, civInfo.getCapital().cityConstructions) militaryUnit.addConstructionBonuses(placedUnit, civInfo.getCapital()!!.cityConstructions)
// Siam gets +10 XP for all CS units // Siam gets +10 XP for all CS units
for (unique in receivingCiv.getMatchingUniques(UniqueType.CityStateGiftedUnitsStartWithXp)) { for (unique in receivingCiv.getMatchingUniques(UniqueType.CityStateGiftedUnitsStartWithXp)) {
@ -236,7 +236,7 @@ class CityStateFunctions(val civInfo: CivilizationInfo) {
// If the city-state is captured by a civ, it stops being the ally of the civ it was previously an ally of. // If the city-state is captured by a civ, it stops being the ally of the civ it was previously an ally of.
// This means that it will NOT HAVE a capital at that time, so if we run getCapital we'll get a crash! // This means that it will NOT HAVE a capital at that time, so if we run getCapital we'll get a crash!
val capitalLocation = if (civInfo.cities.isNotEmpty()) civInfo.getCapital().location else null val capitalLocation = if (civInfo.cities.isNotEmpty() && civInfo.getCapital() != null) civInfo.getCapital()!!.location else null
if (newAllyName != null) { if (newAllyName != null) {
val newAllyCiv = civInfo.gameInfo.getCivilization(newAllyName) val newAllyCiv = civInfo.gameInfo.getCivilization(newAllyName)
@ -300,10 +300,10 @@ class CityStateFunctions(val civInfo: CivilizationInfo) {
otherCiv.addGold(-getDiplomaticMarriageCost()) otherCiv.addGold(-getDiplomaticMarriageCost())
otherCiv.addNotification("We have married into the ruling family of [${civInfo.civName}], bringing them under our control.", otherCiv.addNotification("We have married into the ruling family of [${civInfo.civName}], bringing them under our control.",
civInfo.getCapital().location, civInfo.civName, NotificationIcon.Diplomacy, otherCiv.civName) civInfo.getCapital()!!.location, civInfo.civName, NotificationIcon.Diplomacy, otherCiv.civName)
for (civ in civInfo.gameInfo.civilizations.filter { it != otherCiv }) for (civ in civInfo.gameInfo.civilizations.filter { it != otherCiv })
civ.addNotification("[${otherCiv.civName}] has married into the ruling family of [${civInfo.civName}], bringing them under their control.", civ.addNotification("[${otherCiv.civName}] has married into the ruling family of [${civInfo.civName}], bringing them under their control.",
civInfo.getCapital().location, civInfo.civName, NotificationIcon.Diplomacy, otherCiv.civName) civInfo.getCapital()!!.location, civInfo.civName, NotificationIcon.Diplomacy, otherCiv.civName)
for (unit in civInfo.getCivUnits()) for (unit in civInfo.getCivUnits())
unit.gift(otherCiv) unit.gift(otherCiv)
for (city in civInfo.cities) { for (city in civInfo.cities) {
@ -326,7 +326,7 @@ class CityStateFunctions(val civInfo: CivilizationInfo) {
modifiers["Major Civ"] = -999 modifiers["Major Civ"] = -999
return modifiers return modifiers
} }
if (civInfo.cities.isEmpty()) { if (civInfo.cities.isEmpty() || civInfo.getCapital() == null) {
modifiers["No Cities"] = -999 modifiers["No Cities"] = -999
return modifiers return modifiers
} }
@ -343,7 +343,7 @@ class CityStateFunctions(val civInfo: CivilizationInfo) {
modifiers["Has Protector"] = -20 modifiers["Has Protector"] = -20
if (demandingWorker) if (demandingWorker)
modifiers["Demanding a Worker"] = -30 modifiers["Demanding a Worker"] = -30
if (demandingWorker && civInfo.getCapital().population.population < 4) if (demandingWorker && civInfo.getCapital()!!.population.population < 4)
modifiers["Demanding a Worker from small City-State"] = -300 modifiers["Demanding a Worker from small City-State"] = -300
val recentBullying = civInfo.getRecentBullyingCountdown() val recentBullying = civInfo.getRecentBullyingCountdown()
if (recentBullying != null && recentBullying > 10) if (recentBullying != null && recentBullying > 10)
@ -365,13 +365,13 @@ class CityStateFunctions(val civInfo: CivilizationInfo) {
return modifiers return modifiers
val bullyRange = (civInfo.gameInfo.tileMap.tileMatrix.size / 10).coerceIn(5, 10) // Longer range for larger maps val bullyRange = (civInfo.gameInfo.tileMap.tileMatrix.size / 10).coerceIn(5, 10) // Longer range for larger maps
val inRangeTiles = civInfo.getCapital().getCenterTile().getTilesInDistanceRange(1..bullyRange) val inRangeTiles = civInfo.getCapital()!!.getCenterTile().getTilesInDistanceRange(1..bullyRange)
val forceNearCity = inRangeTiles val forceNearCity = inRangeTiles
.sumOf { if (it.militaryUnit?.civInfo == demandingCiv) .sumOf { if (it.militaryUnit?.civInfo == demandingCiv)
it.militaryUnit!!.getForceEvaluation() it.militaryUnit!!.getForceEvaluation()
else 0 else 0
} }
val csForce = civInfo.getCapital().getForceEvaluation() + inRangeTiles val csForce = civInfo.getCapital()!!.getForceEvaluation() + inRangeTiles
.sumOf { if (it.militaryUnit?.civInfo == civInfo) .sumOf { if (it.militaryUnit?.civInfo == civInfo)
it.militaryUnit!!.getForceEvaluation() it.militaryUnit!!.getForceEvaluation()
else 0 else 0
@ -426,7 +426,7 @@ class CityStateFunctions(val civInfo: CivilizationInfo) {
it.value.isCivilian() && it.value.isBuildable(civInfo) it.value.isCivilian() && it.value.isBuildable(civInfo)
} }
if (buildableWorkerLikeUnits.isEmpty()) return // Bad luck? if (buildableWorkerLikeUnits.isEmpty()) return // Bad luck?
demandingCiv.placeUnitNearTile(civInfo.getCapital().location, buildableWorkerLikeUnits.keys.random()) demandingCiv.placeUnitNearTile(civInfo.getCapital()!!.location, buildableWorkerLikeUnits.keys.random())
civInfo.getDiplomacyManager(demandingCiv).addInfluence(-50f) civInfo.getDiplomacyManager(demandingCiv).addInfluence(-50f)
cityStateBullied(demandingCiv) cityStateBullied(demandingCiv)
@ -660,7 +660,7 @@ class CityStateFunctions(val civInfo: CivilizationInfo) {
) { ) {
thirdCiv.addNotification( thirdCiv.addNotification(
"[${civInfo.civName}] is being attacked by [${attacker.civName}] and asks all major civilizations to help them out by gifting them military units.", "[${civInfo.civName}] is being attacked by [${attacker.civName}] and asks all major civilizations to help them out by gifting them military units.",
civInfo.getCapital().location, civInfo.getCapital()!!.location,
civInfo.civName, civInfo.civName,
"OtherIcons/Present", "OtherIcons/Present",
) )
@ -676,4 +676,4 @@ class CityStateFunctions(val civInfo: CivilizationInfo) {
} }
return newDetailedCivResources return newDetailedCivResources
} }
} }

View File

@ -145,13 +145,13 @@ class CivInfoTransientUpdater(val civInfo: CivilizationInfo) {
} }
fun updateCitiesConnectedToCapital(initialSetup: Boolean = false) { fun updateCitiesConnectedToCapital(initialSetup: Boolean = false) {
if (civInfo.cities.isEmpty()) return // eg barbarians if (civInfo.cities.isEmpty() || civInfo.getCapital() == null) return // eg barbarians
val citiesReachedToMediums = CapitalConnectionsFinder(civInfo).find() val citiesReachedToMediums = CapitalConnectionsFinder(civInfo).find()
if (!initialSetup) { // In the initial setup we're loading an old game state, so it doesn't really count if (!initialSetup) { // In the initial setup we're loading an old game state, so it doesn't really count
for (city in citiesReachedToMediums.keys) for (city in citiesReachedToMediums.keys)
if (city !in civInfo.citiesConnectedToCapitalToMediums && city.civInfo == civInfo && city != civInfo.getCapital()) if (city !in civInfo.citiesConnectedToCapitalToMediums && city.civInfo == civInfo && city != civInfo.getCapital()!!)
civInfo.addNotification("[${city.name}] has been connected to your capital!", city.location, NotificationIcon.Gold) civInfo.addNotification("[${city.name}] has been connected to your capital!", city.location, NotificationIcon.Gold)
// This may still contain cities that have just been destroyed by razing - thus the population test // This may still contain cities that have just been destroyed by razing - thus the population test
@ -166,7 +166,7 @@ class CivInfoTransientUpdater(val civInfo: CivilizationInfo) {
fun updateCivResources() { fun updateCivResources() {
val newDetailedCivResources = ResourceSupplyList() val newDetailedCivResources = ResourceSupplyList()
for (city in civInfo.cities) newDetailedCivResources.add(city.getCityResources()) for (city in civInfo.cities) newDetailedCivResources.add(city.getCityResources())
if (!civInfo.isCityState()) { if (!civInfo.isCityState()) {
// First we get all these resources of each city state separately // First we get all these resources of each city state separately
val cityStateProvidedResources = ResourceSupplyList() val cityStateProvidedResources = ResourceSupplyList()
@ -192,10 +192,10 @@ class CivInfoTransientUpdater(val civInfo: CivilizationInfo) {
for (unit in civInfo.getCivUnits()) for (unit in civInfo.getCivUnits())
for ((resource, amount) in unit.baseUnit.getResourceRequirements()) for ((resource, amount) in unit.baseUnit.getResourceRequirements())
newDetailedCivResources.add(civInfo.gameInfo.ruleSet.tileResources[resource]!!, -amount, "Units") newDetailedCivResources.add(civInfo.gameInfo.ruleSet.tileResources[resource]!!, -amount, "Units")
// Check if anything has actually changed so we don't update stats for no reason - this uses List equality which means it checks the elements // Check if anything has actually changed so we don't update stats for no reason - this uses List equality which means it checks the elements
if (civInfo.detailedCivResources == newDetailedCivResources) return if (civInfo.detailedCivResources == newDetailedCivResources) return
civInfo.detailedCivResources = newDetailedCivResources civInfo.detailedCivResources = newDetailedCivResources
val newSummarizedCivResources = ResourceSupplyList() val newSummarizedCivResources = ResourceSupplyList()
@ -206,4 +206,4 @@ class CivInfoTransientUpdater(val civInfo: CivilizationInfo) {
civInfo.updateStatsForNextTurn() // More or less resources = more or less happiness, with potential domino effects civInfo.updateStatsForNextTurn() // More or less resources = more or less happiness, with potential domino effects
} }
} }

View File

@ -150,13 +150,13 @@ class CivilizationInfo {
/** See DiplomacyManager.flagsCountdown for why this does not map Enums to ints */ /** See DiplomacyManager.flagsCountdown for why this does not map Enums to ints */
private var flagsCountdown = HashMap<String, Int>() private var flagsCountdown = HashMap<String, Int>()
/** Arraylist instead of HashMap as the same unique might appear multiple times /** Arraylist instead of HashMap as the same unique might appear multiple times
* We don't use pairs, as these cannot be serialized due to having no no-arg constructor * We don't use pairs, as these cannot be serialized due to having no no-arg constructor
* This can also contain NON-temporary uniques but I can't be bothered to do the deprecation dance with this one * This can also contain NON-temporary uniques but I can't be bothered to do the deprecation dance with this one
*/ */
val temporaryUniques = ArrayList<TemporaryUnique>() val temporaryUniques = ArrayList<TemporaryUnique>()
// if we only use lists, and change the list each time the cities are changed, // if we only use lists, and change the list each time the cities are changed,
// we won't get concurrent modification exceptions. // we won't get concurrent modification exceptions.
// This is basically a way to ensure our lists are immutable. // This is basically a way to ensure our lists are immutable.
@ -303,7 +303,7 @@ class CivilizationInfo {
compareByDescending<CivilizationInfo> { it.isMajorCiv() } compareByDescending<CivilizationInfo> { it.isMajorCiv() }
.thenBy (UncivGame.Current.settings.getCollatorFromLocale()) { it.civName.tr() } .thenBy (UncivGame.Current.settings.getCollatorFromLocale()) { it.civName.tr() }
) )
fun getCapital() = cities.first { it.isCapital() } fun getCapital() = cities.firstOrNull { it.isCapital() }
fun isPlayerCivilization() = playerType == PlayerType.Human fun isPlayerCivilization() = playerType == PlayerType.Human
fun isOneCityChallenger() = ( fun isOneCityChallenger() = (
playerType == PlayerType.Human && playerType == PlayerType.Human &&
@ -344,11 +344,11 @@ class CivilizationInfo {
return if (preferredVictoryType == Constants.neutralVictoryType) null return if (preferredVictoryType == Constants.neutralVictoryType) null
else gameInfo.ruleSet.victories[getPreferredVictoryType()]!! else gameInfo.ruleSet.victories[getPreferredVictoryType()]!!
} }
fun wantsToFocusOn(focus: Victory.Focus): Boolean { fun wantsToFocusOn(focus: Victory.Focus): Boolean {
return thingsToFocusOnForVictory.contains(focus) return thingsToFocusOnForVictory.contains(focus)
} }
@Transient @Transient
private val civInfoStats = CivInfoStats(this) private val civInfoStats = CivInfoStats(this)
fun stats() = civInfoStats fun stats() = civInfoStats
@ -413,7 +413,7 @@ class CivilizationInfo {
fun hasUnique(uniqueType: UniqueType, stateForConditionals: StateForConditionals = fun hasUnique(uniqueType: UniqueType, stateForConditionals: StateForConditionals =
StateForConditionals(this)) = getMatchingUniques(uniqueType, stateForConditionals).any() StateForConditionals(this)) = getMatchingUniques(uniqueType, stateForConditionals).any()
// Does not return local uniques, only global ones. // Does not return local uniques, only global ones.
/** Destined to replace getMatchingUniques, gradually, as we fill the enum */ /** Destined to replace getMatchingUniques, gradually, as we fill the enum */
fun getMatchingUniques(uniqueType: UniqueType, stateForConditionals: StateForConditionals = StateForConditionals(this), cityToIgnore: CityInfo? = null) = sequence { fun getMatchingUniques(uniqueType: UniqueType, stateForConditionals: StateForConditionals = StateForConditionals(this), cityToIgnore: CityInfo? = null) = sequence {
@ -433,16 +433,16 @@ class CivilizationInfo {
if (religionManager.religion != null) if (religionManager.religion != null)
yieldAll(religionManager.religion!!.getFounderUniques() yieldAll(religionManager.religion!!.getFounderUniques()
.filter { it.isOfType(uniqueType) && it.conditionalsApply(stateForConditionals) }) .filter { it.isOfType(uniqueType) && it.conditionalsApply(stateForConditionals) })
yieldAll(getCivResources().asSequence() yieldAll(getCivResources().asSequence()
.filter { it.amount > 0 } .filter { it.amount > 0 }
.flatMap { it.resource.getMatchingUniques(uniqueType, stateForConditionals) } .flatMap { it.resource.getMatchingUniques(uniqueType, stateForConditionals) }
) )
yieldAll(gameInfo.ruleSet.globalUniques.getMatchingUniques(uniqueType, stateForConditionals)) yieldAll(gameInfo.ruleSet.globalUniques.getMatchingUniques(uniqueType, stateForConditionals))
} }
//region Units //region Units
fun getCivUnitsSize(): Int = units.size fun getCivUnitsSize(): Int = units.size
fun getCivUnits(): Sequence<MapUnit> = units.asSequence() fun getCivUnits(): Sequence<MapUnit> = units.asSequence()
@ -507,7 +507,7 @@ class CivilizationInfo {
val baseUnit = gameInfo.ruleSet.units[baseUnitName] val baseUnit = gameInfo.ruleSet.units[baseUnitName]
?: throw UncivShowableException("Unit $baseUnitName doesn't seem to exist!") ?: throw UncivShowableException("Unit $baseUnitName doesn't seem to exist!")
return getEquivalentUnit(baseUnit) return getEquivalentUnit(baseUnit)
} }
fun getEquivalentUnit(baseUnit: BaseUnit): BaseUnit { fun getEquivalentUnit(baseUnit: BaseUnit): BaseUnit {
if (baseUnit.replaces != null) if (baseUnit.replaces != null)
return getEquivalentUnit(baseUnit.replaces!!) // Equivalent of unique unit is the equivalent of the replaced unit return getEquivalentUnit(baseUnit.replaces!!) // Equivalent of unique unit is the equivalent of the replaced unit
@ -536,7 +536,7 @@ class CivilizationInfo {
if (!(isCityState() && otherCiv.isMajorCiv())) return if (!(isCityState() && otherCiv.isMajorCiv())) return
if (warOnContact || otherCiv.isMinorCivAggressor()) return // No gift if they are bad people, or we are just about to be at war if (warOnContact || otherCiv.isMinorCivAggressor()) return // No gift if they are bad people, or we are just about to be at war
val cityStateLocation = if (cities.isEmpty()) null else getCapital().location val cityStateLocation = if (cities.isEmpty()) null else getCapital()!!.location
val giftAmount = Stats(gold = 15f) val giftAmount = Stats(gold = 15f)
val faithAmount = Stats(faith = 4f) val faithAmount = Stats(faith = 4f)
@ -561,10 +561,10 @@ class CivilizationInfo {
} }
for ((key, value) in giftAmount) for ((key, value) in giftAmount)
otherCiv.addStat(key, value.toInt()) otherCiv.addStat(key, value.toInt())
if (cities.isNotEmpty()) if (cities.isNotEmpty())
otherCiv.exploredTiles = otherCiv.exploredTiles.withItem(getCapital().location) otherCiv.exploredTiles = otherCiv.exploredTiles.withItem(getCapital()!!.location)
questManager.justMet(otherCiv) // Include them in war with major pseudo-quest questManager.justMet(otherCiv) // Include them in war with major pseudo-quest
} }
@ -987,7 +987,7 @@ class CivilizationInfo {
fun getTurnsTillCallForBarbHelp() = flagsCountdown[CivFlags.TurnsTillCallForBarbHelp.name] fun getTurnsTillCallForBarbHelp() = flagsCountdown[CivFlags.TurnsTillCallForBarbHelp.name]
fun mayVoteForDiplomaticVictory() = fun mayVoteForDiplomaticVictory() =
getTurnsTillNextDiplomaticVote() == 0 getTurnsTillNextDiplomaticVote() == 0
&& civName !in gameInfo.diplomaticVictoryVotesCast.keys && civName !in gameInfo.diplomaticVictoryVotesCast.keys
// Only vote if there is someone to vote for, may happen in one-more-turn mode // Only vote if there is someone to vote for, may happen in one-more-turn mode
&& gameInfo.civilizations.any { it.isMajorCiv() && !it.isDefeated() && it != this } && gameInfo.civilizations.any { it.isMajorCiv() && !it.isDefeated() && it != this }
@ -1000,6 +1000,7 @@ class CivilizationInfo {
flagsCountdown[CivFlags.ShowDiplomaticVotingResults.name] == 0 flagsCountdown[CivFlags.ShowDiplomaticVotingResults.name] == 0
&& gameInfo.civilizations.any { it.isMajorCiv() && !it.isDefeated() && it != this } && gameInfo.civilizations.any { it.isMajorCiv() && !it.isDefeated() && it != this }
private fun updateRevolts() { private fun updateRevolts() {
if (gameInfo.civilizations.none { it.isBarbarian() }) { if (gameInfo.civilizations.none { it.isBarbarian() }) {
// Can't spawn revolts without barbarians ¯\_(ツ)_/¯ // Can't spawn revolts without barbarians ¯\_(ツ)_/¯
@ -1031,7 +1032,7 @@ class CivilizationInfo {
val spawnCity = cities.maxByOrNull { random.nextInt(it.population.population + 10) } ?: return val spawnCity = cities.maxByOrNull { random.nextInt(it.population.population + 10) } ?: return
val spawnTile = spawnCity.getTiles().maxByOrNull { rateTileForRevoltSpawn(it) } ?: return val spawnTile = spawnCity.getTiles().maxByOrNull { rateTileForRevoltSpawn(it) } ?: return
val unitToSpawn = gameInfo.ruleSet.units.values.asSequence().filter { val unitToSpawn = gameInfo.ruleSet.units.values.asSequence().filter {
it.uniqueTo == null && it.isMelee() && it.isLandUnit() it.uniqueTo == null && it.isMelee() && it.isLandUnit()
&& !it.hasUnique(UniqueType.CannotAttack) && it.isBuildable(this) && !it.hasUnique(UniqueType.CannotAttack) && it.isBuildable(this)
}.maxByOrNull { }.maxByOrNull {
random.nextInt(1000) random.nextInt(1000)
@ -1046,14 +1047,14 @@ class CivilizationInfo {
} }
// Will be automatically added again as long as unhappiness is still low enough // Will be automatically added again as long as unhappiness is still low enough
removeFlag(CivFlags.RevoltSpawning.name) removeFlag(CivFlags.RevoltSpawning.name)
addNotification("Your citizens are revolting due to very high unhappiness!", spawnTile.position, unitToSpawn.name, "StatIcons/Malcontent") addNotification("Your citizens are revolting due to very high unhappiness!", spawnTile.position, unitToSpawn.name, "StatIcons/Malcontent")
} }
// Higher is better // Higher is better
private fun rateTileForRevoltSpawn(tile: TileInfo): Int { private fun rateTileForRevoltSpawn(tile: TileInfo): Int {
if (tile.isWater || tile.militaryUnit != null || tile.civilianUnit != null || tile.isCityCenter() || tile.isImpassible()) if (tile.isWater || tile.militaryUnit != null || tile.civilianUnit != null || tile.isCityCenter() || tile.isImpassible())
return -1 return -1
var score = 10 var score = 10
if (tile.improvement == null) { if (tile.improvement == null) {
@ -1164,7 +1165,7 @@ class CivilizationInfo {
} }
if (placedUnit.hasUnique(UniqueType.ReligiousUnit) && gameInfo.isReligionEnabled()) { if (placedUnit.hasUnique(UniqueType.ReligiousUnit) && gameInfo.isReligionEnabled()) {
placedUnit.religion = placedUnit.religion =
when { when {
placedUnit.hasUnique(UniqueType.TakeReligionOverBirthCity) placedUnit.hasUnique(UniqueType.TakeReligionOverBirthCity)
&& religionManager.religion?.isMajorReligion() == true -> && religionManager.religion?.isMajorReligion() == true ->
@ -1181,7 +1182,7 @@ class CivilizationInfo {
passableImpassables.add(unique.params[0]) // Add to list of passable impassables passableImpassables.add(unique.params[0]) // Add to list of passable impassables
} }
} }
return placedUnit return placedUnit
} }
@ -1272,7 +1273,7 @@ class CivilizationInfo {
// Check if different continents (unless already max distance, or water map) // Check if different continents (unless already max distance, or water map)
if (connections > 0 && proximity != Proximity.Distant && !gameInfo.tileMap.isWaterMap() if (connections > 0 && proximity != Proximity.Distant && !gameInfo.tileMap.isWaterMap()
&& getCapital().getCenterTile().getContinent() != otherCiv.getCapital().getCenterTile().getContinent() && getCapital()!!.getCenterTile().getContinent() != otherCiv.getCapital()!!.getCenterTile().getContinent()
) { ) {
// Different continents - increase separation by one step // Different continents - increase separation by one step
proximity = when (proximity) { proximity = when (proximity) {
@ -1299,8 +1300,9 @@ class CivilizationInfo {
* Removes current capital then moves capital to argument city if not null * Removes current capital then moves capital to argument city if not null
*/ */
fun moveCapitalTo(city: CityInfo?) { fun moveCapitalTo(city: CityInfo?) {
if (cities.isNotEmpty()) { if (cities.isNotEmpty() && getCapital() != null) {
getCapital().cityConstructions.removeBuilding(getCapital().capitalCityIndicator()) val oldCapital = getCapital()!!
oldCapital.cityConstructions.removeBuilding(oldCapital.capitalCityIndicator())
} }
if (city == null) return // can't move a non-existent city but we can always remove our old capital if (city == null) return // can't move a non-existent city but we can always remove our old capital
@ -1311,7 +1313,7 @@ class CivilizationInfo {
fun moveCapitalToNextLargest() { fun moveCapitalToNextLargest() {
moveCapitalTo(cities moveCapitalTo(cities
.filterNot { it == getCapital() } .filterNot { it.isCapital() }
.maxByOrNull { it.population.population}) .maxByOrNull { it.population.population})
} }

View File

@ -117,7 +117,7 @@ class QuestManager {
tryStartNewGlobalQuest() tryStartNewGlobalQuest()
tryStartNewIndividualQuests() tryStartNewIndividualQuests()
tryBarbarianInvasion() tryBarbarianInvasion()
tryEndWarWithMajorQuests() tryEndWarWithMajorQuests()
} }
@ -226,7 +226,7 @@ class QuestManager {
&& !it.isAtWarWith(civInfo) && !it.isAtWarWith(civInfo)
&& it.getProximity(civInfo) <= Proximity.Far }) { && it.getProximity(civInfo) <= Proximity.Far }) {
otherCiv.addNotification("[${civInfo.civName}] is being invaded by Barbarians! Destroy Barbarians near their territory to earn Influence.", otherCiv.addNotification("[${civInfo.civName}] is being invaded by Barbarians! Destroy Barbarians near their territory to earn Influence.",
civInfo.getCapital().location, civInfo.civName, NotificationIcon.War) civInfo.getCapital()!!.location, civInfo.civName, NotificationIcon.War)
} }
civInfo.addFlag(CivFlags.TurnsTillCallForBarbHelp.name, 30) civInfo.addFlag(CivFlags.TurnsTillCallForBarbHelp.name, 30)
} }
@ -358,10 +358,10 @@ class QuestManager {
return when (quest.name) { return when (quest.name) {
QuestName.ClearBarbarianCamp.value -> getBarbarianEncampmentForQuest() != null QuestName.ClearBarbarianCamp.value -> getBarbarianEncampmentForQuest() != null
QuestName.Route.value -> !challenger.cities.none() QuestName.Route.value -> !challenger.cities.none()
&& !challenger.isCapitalConnectedToCity(civInfo.getCapital()) && !challenger.isCapitalConnectedToCity(civInfo.getCapital()!!)
// Need to have a city within 7 tiles on the same continent // Need to have a city within 7 tiles on the same continent
&& challenger.cities.any { it.getCenterTile().aerialDistanceTo(civInfo.getCapital().getCenterTile()) <= 7 && challenger.cities.any { it.getCenterTile().aerialDistanceTo(civInfo.getCapital()!!.getCenterTile()) <= 7
&& it.getCenterTile().getContinent() == civInfo.getCapital().getCenterTile().getContinent() } && it.getCenterTile().getContinent() == civInfo.getCapital()!!.getCenterTile().getContinent() }
QuestName.ConnectResource.value -> getResourceForQuest(challenger) != null QuestName.ConnectResource.value -> getResourceForQuest(challenger) != null
QuestName.ConstructWonder.value -> getWonderToBuildForQuest(challenger) != null QuestName.ConstructWonder.value -> getWonderToBuildForQuest(challenger) != null
QuestName.GreatPerson.value -> getGreatPersonForQuest(challenger) != null QuestName.GreatPerson.value -> getGreatPersonForQuest(challenger) != null
@ -373,7 +373,7 @@ class QuestManager {
&& !challenger.getDiplomacyManager(mostRecentBully).hasFlag(DiplomacyFlags.Denunciation) && !challenger.getDiplomacyManager(mostRecentBully).hasFlag(DiplomacyFlags.Denunciation)
&& challenger.getDiplomacyManager(mostRecentBully).diplomaticStatus != DiplomaticStatus.War && challenger.getDiplomacyManager(mostRecentBully).diplomaticStatus != DiplomaticStatus.War
&& !( challenger.playerType == PlayerType.Human && civInfo.gameInfo.getCivilization(mostRecentBully).playerType == PlayerType.Human) && !( challenger.playerType == PlayerType.Human && civInfo.gameInfo.getCivilization(mostRecentBully).playerType == PlayerType.Human)
QuestName.SpreadReligion.value -> playerReligion != null && civInfo.getCapital().religion.getMajorityReligion()?.name != playerReligion QuestName.SpreadReligion.value -> playerReligion != null && civInfo.getCapital()!!.religion.getMajorityReligion()?.name != playerReligion
QuestName.ConquerCityState.value -> getCityStateTarget(challenger) != null && civInfo.cityStatePersonality != CityStatePersonality.Friendly QuestName.ConquerCityState.value -> getCityStateTarget(challenger) != null && civInfo.cityStatePersonality != CityStatePersonality.Friendly
QuestName.BullyCityState.value -> getCityStateTarget(challenger) != null QuestName.BullyCityState.value -> getCityStateTarget(challenger) != null
QuestName.ContestFaith.value -> civInfo.gameInfo.isReligionEnabled() QuestName.ContestFaith.value -> civInfo.gameInfo.isReligionEnabled()
@ -385,7 +385,7 @@ class QuestManager {
private fun isComplete(assignedQuest: AssignedQuest): Boolean { private fun isComplete(assignedQuest: AssignedQuest): Boolean {
val assignee = civInfo.gameInfo.getCivilization(assignedQuest.assignee) val assignee = civInfo.gameInfo.getCivilization(assignedQuest.assignee)
return when (assignedQuest.questName) { return when (assignedQuest.questName) {
QuestName.Route.value -> assignee.isCapitalConnectedToCity(civInfo.getCapital()) QuestName.Route.value -> assignee.isCapitalConnectedToCity(civInfo.getCapital()!!)
QuestName.ConnectResource.value -> assignee.detailedCivResources.map { it.resource }.contains(civInfo.gameInfo.ruleSet.tileResources[assignedQuest.data1]) QuestName.ConnectResource.value -> assignee.detailedCivResources.map { it.resource }.contains(civInfo.gameInfo.ruleSet.tileResources[assignedQuest.data1])
QuestName.ConstructWonder.value -> assignee.cities.any { it.cityConstructions.isBuilt(assignedQuest.data1) } QuestName.ConstructWonder.value -> assignee.cities.any { it.cityConstructions.isBuilt(assignedQuest.data1) }
QuestName.GreatPerson.value -> assignee.getCivGreatPeople().any { it.baseUnit.getReplacedUnit(civInfo.gameInfo.ruleSet).name == assignedQuest.data1 } QuestName.GreatPerson.value -> assignee.getCivGreatPeople().any { it.baseUnit.getReplacedUnit(civInfo.gameInfo.ruleSet).name == assignedQuest.data1 }
@ -393,7 +393,7 @@ class QuestManager {
QuestName.FindNaturalWonder.value -> assignee.naturalWonders.contains(assignedQuest.data1) QuestName.FindNaturalWonder.value -> assignee.naturalWonders.contains(assignedQuest.data1)
QuestName.PledgeToProtect.value -> assignee in civInfo.getProtectorCivs() QuestName.PledgeToProtect.value -> assignee in civInfo.getProtectorCivs()
QuestName.DenounceCiv.value -> assignee.getDiplomacyManager(assignedQuest.data1).hasFlag(DiplomacyFlags.Denunciation) QuestName.DenounceCiv.value -> assignee.getDiplomacyManager(assignedQuest.data1).hasFlag(DiplomacyFlags.Denunciation)
QuestName.SpreadReligion.value -> civInfo.getCapital().religion.getMajorityReligion() == civInfo.gameInfo.religions[assignedQuest.data2] QuestName.SpreadReligion.value -> civInfo.getCapital()!!.religion.getMajorityReligion() == civInfo.gameInfo.religions[assignedQuest.data2]
else -> false else -> false
} }
} }
@ -421,7 +421,7 @@ class QuestManager {
if (rewardInfluence > 0) if (rewardInfluence > 0)
assignee.addNotification( assignee.addNotification(
"[${civInfo.civName}] rewarded you with [${rewardInfluence.toInt()}] influence for completing the [${assignedQuest.questName}] quest.", "[${civInfo.civName}] rewarded you with [${rewardInfluence.toInt()}] influence for completing the [${assignedQuest.questName}] quest.",
civInfo.getCapital().location, civInfo.civName, "OtherIcons/Quest" civInfo.getCapital()!!.location, civInfo.civName, "OtherIcons/Quest"
) )
// We may have received bonuses from city-state friend-ness or ally-ness // We may have received bonuses from city-state friend-ness or ally-ness
@ -436,11 +436,11 @@ class QuestManager {
if (winners.isEmpty()) { if (winners.isEmpty()) {
assignee.addNotification( assignee.addNotification(
"[${civInfo.civName}] no longer needs your help with the [${assignedQuest.questName}] quest.", "[${civInfo.civName}] no longer needs your help with the [${assignedQuest.questName}] quest.",
civInfo.getCapital().location, civInfo.civName, "OtherIcons/Quest") civInfo.getCapital()!!.location, civInfo.civName, "OtherIcons/Quest")
} else { } else {
assignee.addNotification( assignee.addNotification(
"The [${assignedQuest.questName}] quest for [${civInfo.civName}] has ended. It was won by [${winners.joinToString { it.assignee.tr() }}].", "The [${assignedQuest.questName}] quest for [${civInfo.civName}] has ended. It was won by [${winners.joinToString { it.assignee.tr() }}].",
civInfo.getCapital().location, civInfo.civName, "OtherIcons/Quest") civInfo.getCapital()!!.location, civInfo.civName, "OtherIcons/Quest")
} }
} }
@ -530,10 +530,10 @@ class QuestManager {
val totalMilitaryUnits = attacker.getCivUnits().count { !it.isCivilian() } val totalMilitaryUnits = attacker.getCivUnits().count { !it.isCivilian() }
val unitsToKill = max(3, totalMilitaryUnits / 4) val unitsToKill = max(3, totalMilitaryUnits / 4)
unitsToKillForCiv[attacker.civName] = unitsToKill unitsToKillForCiv[attacker.civName] = unitsToKill
val location = if (civInfo.cities.isEmpty()) null
else civInfo.getCapital().location val location = if (civInfo.cities.isEmpty() || civInfo.getCapital() == null) null
else civInfo.getCapital()!!.location
// Ask for assistance // Ask for assistance
for (thirdCiv in civInfo.getKnownCivs().filter { it.isAlive() && !it.isAtWarWith(civInfo) && it.isMajorCiv() }) { for (thirdCiv in civInfo.getKnownCivs().filter { it.isAlive() && !it.isAtWarWith(civInfo) && it.isMajorCiv() }) {
@ -568,12 +568,12 @@ class QuestManager {
endWarWithMajorQuest(killed) endWarWithMajorQuest(killed)
} }
} }
/** Called when a major civ meets the city-state for the first time. Mainly for war with major pseudo-quest. */ /** Called when a major civ meets the city-state for the first time. Mainly for war with major pseudo-quest. */
fun justMet(otherCiv: CivilizationInfo) { fun justMet(otherCiv: CivilizationInfo) {
val location = if (civInfo.cities.isEmpty()) null val location = if (civInfo.cities.isEmpty() || civInfo.getCapital() == null) null
else civInfo.getCapital().location else civInfo.getCapital()!!.location
for ((attackerName, unitsToKill) in unitsToKillForCiv) { for ((attackerName, unitsToKill) in unitsToKillForCiv) {
if (location != null) if (location != null)
otherCiv.addNotification("[${civInfo.civName}] is being attacked by [$attackerName]! Kill [$unitsToKill] of the attacker's military units and they will be immensely grateful.", otherCiv.addNotification("[${civInfo.civName}] is being attacked by [$attackerName]! Kill [$unitsToKill] of the attacker's military units and they will be immensely grateful.",
@ -604,15 +604,15 @@ class QuestManager {
unitsToKillForCiv.remove(attacker.civName) unitsToKillForCiv.remove(attacker.civName)
unitsKilledFromCiv.remove(attacker.civName) unitsKilledFromCiv.remove(attacker.civName)
} }
fun warWithMajorActive(target: CivilizationInfo): Boolean { fun warWithMajorActive(target: CivilizationInfo): Boolean {
return unitsToKillForCiv.containsKey(target.civName) return unitsToKillForCiv.containsKey(target.civName)
} }
fun unitsToKill(target: CivilizationInfo): Int { fun unitsToKill(target: CivilizationInfo): Int {
return unitsToKillForCiv[target.civName] ?: 0 return unitsToKillForCiv[target.civName] ?: 0
} }
fun unitsKilledSoFar(target: CivilizationInfo, viewingCiv: CivilizationInfo): Int { fun unitsKilledSoFar(target: CivilizationInfo, viewingCiv: CivilizationInfo): Int {
val killMap = unitsKilledFromCiv[target.civName] ?: return 0 val killMap = unitsKilledFromCiv[target.civName] ?: return 0
return killMap[viewingCiv.civName] ?: 0 return killMap[viewingCiv.civName] ?: 0
@ -755,7 +755,7 @@ class QuestManager {
* to be destroyed * to be destroyed
*/ */
private fun getBarbarianEncampmentForQuest(): TileInfo? { private fun getBarbarianEncampmentForQuest(): TileInfo? {
val encampments = civInfo.getCapital().getCenterTile().getTilesInDistance(8) val encampments = civInfo.getCapital()!!.getCenterTile().getTilesInDistance(8)
.filter { it.improvement == Constants.barbarianEncampment }.toList() .filter { it.improvement == Constants.barbarianEncampment }.toList()
if (encampments.isNotEmpty()) if (encampments.isNotEmpty())
@ -923,7 +923,7 @@ class AssignedQuest(val questName: String = "",
} }
QuestName.Route.value -> { QuestName.Route.value -> {
game.resetToWorldScreen() game.resetToWorldScreen()
game.worldScreen.mapHolder.setCenterPosition(gameInfo.getCivilization(assigner).getCapital().location, selectUnit = false) game.worldScreen.mapHolder.setCenterPosition(gameInfo.getCivilization(assigner).getCapital()!!.location, selectUnit = false)
} }
} }
} }

View File

@ -14,7 +14,7 @@ import kotlin.math.max
import kotlin.math.min import kotlin.math.min
enum class RelationshipLevel(val color: Color) { enum class RelationshipLevel(val color: Color) {
// War is tested separately for the Diplomacy Screen. Colored RED. // War is tested separately for the Diplomacy Screen. Colored RED.
Unforgivable(Color.FIREBRICK), Unforgivable(Color.FIREBRICK),
Afraid(Color(0x5300ffff)), // HSV(260,100,100) Afraid(Color(0x5300ffff)), // HSV(260,100,100)
Enemy(Color.YELLOW), Enemy(Color.YELLOW),
@ -116,7 +116,7 @@ class DiplomacyManager() {
/** For city-states. Influence is saved in the CITY STATE -> major civ Diplomacy, NOT in the major civ -> city state diplomacy. /** For city-states. Influence is saved in the CITY STATE -> major civ Diplomacy, NOT in the major civ -> city state diplomacy.
* Access via getInfluence() and setInfluence() unless you know what you're doing. * Access via getInfluence() and setInfluence() unless you know what you're doing.
* Note that not using the setter skips recalculating the ally and bounds checks, * Note that not using the setter skips recalculating the ally and bounds checks,
* and skipping the getter bypasses the modified value when at war */ * and skipping the getter bypasses the modified value when at war */
private var influence = 0f private var influence = 0f
@ -230,7 +230,7 @@ class DiplomacyManager() {
influence = max(amount, MINIMUM_INFLUENCE) influence = max(amount, MINIMUM_INFLUENCE)
civInfo.updateAllyCivForCityState() civInfo.updateAllyCivForCityState()
} }
fun getInfluence() = if (civInfo.isAtWarWith(otherCiv())) MINIMUM_INFLUENCE else influence fun getInfluence() = if (civInfo.isAtWarWith(otherCiv())) MINIMUM_INFLUENCE else influence
// To be run from City-State DiplomacyManager, which holds the influence. Resting point for every major civ can be different. // To be run from City-State DiplomacyManager, which holds the influence. Resting point for every major civ can be different.
@ -240,9 +240,9 @@ class DiplomacyManager() {
for (unique in otherCiv().getMatchingUniques(UniqueType.CityStateRestingPoint)) for (unique in otherCiv().getMatchingUniques(UniqueType.CityStateRestingPoint))
restingPoint += unique.params[0].toInt() restingPoint += unique.params[0].toInt()
if (civInfo.cities.any()) // no capital if no cities if (civInfo.cities.any() && civInfo.getCapital() != null)
for (unique in otherCiv().getMatchingUniques(UniqueType.RestingPointOfCityStatesFollowingReligionChange)) for (unique in otherCiv().getMatchingUniques(UniqueType.RestingPointOfCityStatesFollowingReligionChange))
if (otherCiv().religionManager.religion?.name == civInfo.getCapital().religion.getMajorityReligionName()) if (otherCiv().religionManager.religion?.name == civInfo.getCapital()!!.religion.getMajorityReligionName())
restingPoint += unique.params[0].toInt() restingPoint += unique.params[0].toInt()
if (diplomaticStatus == DiplomaticStatus.Protector) restingPoint += 10 if (diplomaticStatus == DiplomaticStatus.Protector) restingPoint += 10
@ -266,8 +266,8 @@ class DiplomacyManager() {
for (unique in otherCiv().getMatchingUniques(UniqueType.CityStateInfluenceDegradation)) for (unique in otherCiv().getMatchingUniques(UniqueType.CityStateInfluenceDegradation))
modifierPercent += unique.params[0].toFloat() modifierPercent += unique.params[0].toFloat()
val religion = if (civInfo.cities.isEmpty()) null val religion = if (civInfo.cities.isEmpty() || civInfo.getCapital() == null) null
else civInfo.getCapital().religion.getMajorityReligionName() else civInfo.getCapital()!!.religion.getMajorityReligionName()
if (religion != null && religion == otherCiv().religionManager.religion?.name) if (religion != null && religion == otherCiv().religionManager.religion?.name)
modifierPercent -= 25f // 25% slower degrade when sharing a religion modifierPercent -= 25f // 25% slower degrade when sharing a religion
@ -291,8 +291,8 @@ class DiplomacyManager() {
if (otherCiv().hasUnique(UniqueType.CityStateInfluenceRecoversTwiceNormalRate)) if (otherCiv().hasUnique(UniqueType.CityStateInfluenceRecoversTwiceNormalRate))
modifierPercent += 100f modifierPercent += 100f
val religion = if (civInfo.cities.isEmpty()) null val religion = if (civInfo.cities.isEmpty() || civInfo.getCapital() == null) null
else civInfo.getCapital().religion.getMajorityReligionName() else civInfo.getCapital()!!.religion.getMajorityReligionName()
if (religion != null && religion == otherCiv().religionManager.religion?.name) if (religion != null && religion == otherCiv().religionManager.religion?.name)
modifierPercent += 50f // 50% quicker recovery when sharing a religion modifierPercent += 50f // 50% quicker recovery when sharing a religion
@ -373,7 +373,7 @@ class DiplomacyManager() {
if (offer.type in listOf(TradeType.Luxury_Resource, TradeType.Strategic_Resource) if (offer.type in listOf(TradeType.Luxury_Resource, TradeType.Strategic_Resource)
&& (offer.name in negativeCivResources || !civInfo.gameInfo.ruleSet.tileResources.containsKey(offer.name)) && (offer.name in negativeCivResources || !civInfo.gameInfo.ruleSet.tileResources.containsKey(offer.name))
) { ) {
trades.remove(trade) trades.remove(trade)
val otherCivTrades = otherCiv().getDiplomacyManager(civInfo).trades val otherCivTrades = otherCiv().getDiplomacyManager(civInfo).trades
otherCivTrades.removeAll { it.equalTrade(trade.reverse()) } otherCivTrades.removeAll { it.equalTrade(trade.reverse()) }
@ -382,7 +382,7 @@ class DiplomacyManager() {
if (trade.theirOffers.any { it.name == Constants.peaceTreaty }) { if (trade.theirOffers.any { it.name == Constants.peaceTreaty }) {
remakePeaceTreaty(trade.theirOffers.first { it.name == Constants.peaceTreaty }.duration) remakePeaceTreaty(trade.theirOffers.first { it.name == Constants.peaceTreaty }.duration)
} }
civInfo.addNotification("One of our trades with [$otherCivName] has been cut short", NotificationIcon.Trade, otherCivName) civInfo.addNotification("One of our trades with [$otherCivName] has been cut short", NotificationIcon.Trade, otherCivName)
otherCiv().addNotification("One of our trades with [${civInfo.civName}] has been cut short", NotificationIcon.Trade, civInfo.civName) otherCiv().addNotification("One of our trades with [${civInfo.civName}] has been cut short", NotificationIcon.Trade, civInfo.civName)
civInfo.updateDetailedCivResources() civInfo.updateDetailedCivResources()
@ -390,7 +390,7 @@ class DiplomacyManager() {
} }
} }
} }
private fun remakePeaceTreaty(durationLeft: Int) { private fun remakePeaceTreaty(durationLeft: Int) {
val treaty = Trade() val treaty = Trade()
treaty.ourOffers.add( treaty.ourOffers.add(
@ -444,7 +444,7 @@ class DiplomacyManager() {
val initialRelationshipLevel = relationshipLevel() val initialRelationshipLevel = relationshipLevel()
val restingPoint = getCityStateInfluenceRestingPoint() val restingPoint = getCityStateInfluenceRestingPoint()
// We don't use `getInfluence()` here, as then during war with the ally of this CS, // We don't use `getInfluence()` here, as then during war with the ally of this CS,
// our influence would be set to -59, overwriting the old value, which we want to keep // our influence would be set to -59, overwriting the old value, which we want to keep
// as it should be restored once the war ends (though we keep influence degradation from time during the war) // as it should be restored once the war ends (though we keep influence degradation from time during the war)
if (influence > restingPoint) { if (influence > restingPoint) {
@ -456,7 +456,7 @@ class DiplomacyManager() {
} }
if (!civInfo.isDefeated()) { // don't display city state relationship notifications when the city state is currently defeated if (!civInfo.isDefeated()) { // don't display city state relationship notifications when the city state is currently defeated
val civCapitalLocation = if (civInfo.cities.isNotEmpty()) civInfo.getCapital().location else null val civCapitalLocation = if (civInfo.cities.isNotEmpty() || civInfo.getCapital() != null) civInfo.getCapital()!!.location else null
if (getTurnsToRelationshipChange() == 1) { if (getTurnsToRelationshipChange() == 1) {
val text = "Your relationship with [${civInfo.civName}] is about to degrade" val text = "Your relationship with [${civInfo.civName}] is about to degrade"
if (civCapitalLocation != null) otherCiv().addNotification(text, civCapitalLocation, civInfo.civName, NotificationIcon.Diplomacy) if (civCapitalLocation != null) otherCiv().addNotification(text, civCapitalLocation, civInfo.civName, NotificationIcon.Diplomacy)
@ -612,17 +612,17 @@ class DiplomacyManager() {
revertToZero(DiplomaticModifiers.DeclarationOfFriendship, 1 / 2f) //decreases slowly and will revert to full if it is declared later revertToZero(DiplomaticModifiers.DeclarationOfFriendship, 1 / 2f) //decreases slowly and will revert to full if it is declared later
if (!otherCiv().isCityState()) return if (!otherCiv().isCityState()) return
val eraInfo = civInfo.getEra() val eraInfo = civInfo.getEra()
if (relationshipLevel() < RelationshipLevel.Friend) { if (relationshipLevel() < RelationshipLevel.Friend) {
if (hasFlag(DiplomacyFlags.ProvideMilitaryUnit)) if (hasFlag(DiplomacyFlags.ProvideMilitaryUnit))
removeFlag(DiplomacyFlags.ProvideMilitaryUnit) removeFlag(DiplomacyFlags.ProvideMilitaryUnit)
return return
} }
val variance = listOf(-1, 0, 1).random() val variance = listOf(-1, 0, 1).random()
if (eraInfo.undefinedCityStateBonuses() && otherCiv().cityStateType == CityStateType.Militaristic) { if (eraInfo.undefinedCityStateBonuses() && otherCiv().cityStateType == CityStateType.Militaristic) {
// Deprecated, assume Civ V values for compatibility // Deprecated, assume Civ V values for compatibility
if (!hasFlag(DiplomacyFlags.ProvideMilitaryUnit) && relationshipLevel() == RelationshipLevel.Friend) if (!hasFlag(DiplomacyFlags.ProvideMilitaryUnit) && relationshipLevel() == RelationshipLevel.Friend)
@ -632,7 +632,7 @@ class DiplomacyManager() {
&& relationshipLevel() == RelationshipLevel.Ally) && relationshipLevel() == RelationshipLevel.Ally)
setFlag(DiplomacyFlags.ProvideMilitaryUnit, 17 + variance) setFlag(DiplomacyFlags.ProvideMilitaryUnit, 17 + variance)
} }
if (eraInfo.undefinedCityStateBonuses()) return if (eraInfo.undefinedCityStateBonuses()) return
for (bonus in eraInfo.getCityStateBonuses(otherCiv().cityStateType, relationshipLevel())) { for (bonus in eraInfo.getCityStateBonuses(otherCiv().cityStateType, relationshipLevel())) {
@ -657,7 +657,7 @@ class DiplomacyManager() {
if (civInfo.isCityState() && civInfo.getProtectorCivs().contains(otherCiv())) { if (civInfo.isCityState() && civInfo.getProtectorCivs().contains(otherCiv())) {
civInfo.removeProtectorCiv(otherCiv(), forced = true) civInfo.removeProtectorCiv(otherCiv(), forced = true)
} }
diplomaticStatus = DiplomaticStatus.War diplomaticStatus = DiplomaticStatus.War
removeModifier(DiplomaticModifiers.YearsOfPeace) removeModifier(DiplomaticModifiers.YearsOfPeace)
@ -666,12 +666,12 @@ class DiplomacyManager() {
removeFlag(DiplomacyFlags.BorderConflict) removeFlag(DiplomacyFlags.BorderConflict)
} }
/** Declares war with the other civ in this diplomacy manager. /** Declares war with the other civ in this diplomacy manager.
* Handles all war effects and diplomatic changes with other civs and such. * Handles all war effects and diplomatic changes with other civs and such.
* *
* @param indirectCityStateAttack Influence with city states should only be set to -60 * @param indirectCityStateAttack Influence with city states should only be set to -60
* when they are attacked directly, not when their ally is attacked. * when they are attacked directly, not when their ally is attacked.
* When @indirectCityStateAttack is set to true, we thus don't reset the influence with this city state. * When @indirectCityStateAttack is set to true, we thus don't reset the influence with this city state.
* Should only ever be set to true for calls originating from within this function. * Should only ever be set to true for calls originating from within this function.
*/ */
fun declareWar(indirectCityStateAttack: Boolean = false) { fun declareWar(indirectCityStateAttack: Boolean = false) {

View File

@ -230,7 +230,7 @@ class TradeEvaluation {
val city = civInfo.cities.firstOrNull { it.id == offer.name } val city = civInfo.cities.firstOrNull { it.id == offer.name }
?: throw Exception("Got an offer to sell city id " + offer.name + " which does't seem to exist for this civ!") ?: throw Exception("Got an offer to sell city id " + offer.name + " which does't seem to exist for this civ!")
val capitalcity = civInfo.getCapital() val capitalcity = civInfo.getCapital()!!
val distanceCost = distanceCityTradeModifier(civInfo, capitalcity, city) val distanceCost = distanceCityTradeModifier(civInfo, capitalcity, city)
val stats = city.cityStats.currentCityStats val stats = city.cityStats.currentCityStats
val sumOfStats = val sumOfStats =
@ -280,4 +280,4 @@ class TradeEvaluation {
?: return 0 ?: return 0
return unique.params[0].toInt() return unique.params[0].toInt()
} }
} }

View File

@ -105,7 +105,7 @@ class Unique(val text: String, val sourceObjectType: UniqueTarget? = null, val s
if (finalPossibleUniques.size == 1) return finalPossibleUniques.first() if (finalPossibleUniques.size == 1) return finalPossibleUniques.first()
// filter out possible replacements that are obviously wrong // filter out possible replacements that are obviously wrong
val uniquesWithNoErrors = finalPossibleUniques.filter { val uniquesWithNoErrors = finalPossibleUniques.filter {
val unique = Unique(it) val unique = Unique(it)
val errors = ruleset.checkUnique(unique, true, "", val errors = ruleset.checkUnique(unique, true, "",
UniqueType.UniqueComplianceErrorSeverity.RulesetSpecific, unique.type!!.targetTypes.first()) UniqueType.UniqueComplianceErrorSeverity.RulesetSpecific, unique.type!!.targetTypes.first())
@ -132,7 +132,7 @@ class Unique(val text: String, val sourceObjectType: UniqueTarget? = null, val s
if (state.ourCombatant != null && state.ourCombatant is MapUnitCombatant) state.ourCombatant.unit if (state.ourCombatant != null && state.ourCombatant is MapUnitCombatant) state.ourCombatant.unit
else state.unit else state.unit
} }
val stateBasedRandom by lazy { Random(state.hashCode()) } val stateBasedRandom by lazy { Random(state.hashCode()) }
return when (condition.type) { return when (condition.type) {
@ -151,8 +151,8 @@ class Unique(val text: String, val sourceObjectType: UniqueTarget? = null, val s
state.civInfo != null state.civInfo != null
&& condition.params[0].toInt() <= state.civInfo.happinessForNextTurn && condition.params[0].toInt() <= state.civInfo.happinessForNextTurn
&& state.civInfo.happinessForNextTurn < condition.params[1].toInt() && state.civInfo.happinessForNextTurn < condition.params[1].toInt()
UniqueType.ConditionalBelowHappiness -> UniqueType.ConditionalBelowHappiness ->
state.civInfo != null && state.civInfo.happinessForNextTurn < condition.params[0].toInt() state.civInfo != null && state.civInfo.happinessForNextTurn < condition.params[0].toInt()
UniqueType.ConditionalGoldenAge -> UniqueType.ConditionalGoldenAge ->
state.civInfo != null && state.civInfo.goldenAges.isGoldenAge() state.civInfo != null && state.civInfo.goldenAges.isGoldenAge()
UniqueType.ConditionalBeforeEra -> UniqueType.ConditionalBeforeEra ->
@ -169,12 +169,12 @@ class Unique(val text: String, val sourceObjectType: UniqueTarget? = null, val s
state.civInfo != null && state.civInfo.policies.isAdopted(condition.params[0]) state.civInfo != null && state.civInfo.policies.isAdopted(condition.params[0])
UniqueType.ConditionalNoPolicy -> UniqueType.ConditionalNoPolicy ->
state.civInfo != null && !state.civInfo.policies.isAdopted(condition.params[0]) state.civInfo != null && !state.civInfo.policies.isAdopted(condition.params[0])
UniqueType.ConditionalBuildingBuilt -> UniqueType.ConditionalBuildingBuilt ->
state.civInfo != null && state.civInfo.cities.any { it.cityConstructions.containsBuildingOrEquivalent(condition.params[0]) } state.civInfo != null && state.civInfo.cities.any { it.cityConstructions.containsBuildingOrEquivalent(condition.params[0]) }
UniqueType.ConditionalCityWithBuilding -> UniqueType.ConditionalCityWithBuilding ->
state.cityInfo != null && state.cityInfo.cityConstructions.containsBuildingOrEquivalent(condition.params[0]) state.cityInfo != null && state.cityInfo.cityConstructions.containsBuildingOrEquivalent(condition.params[0])
UniqueType.ConditionalCityWithoutBuilding -> UniqueType.ConditionalCityWithoutBuilding ->
state.cityInfo != null && !state.cityInfo.cityConstructions.containsBuildingOrEquivalent(condition.params[0]) state.cityInfo != null && !state.cityInfo.cityConstructions.containsBuildingOrEquivalent(condition.params[0])
UniqueType.ConditionalPopulationFilter -> UniqueType.ConditionalPopulationFilter ->
state.cityInfo != null && state.cityInfo.population.getPopulationFilterAmount(condition.params[1]) >= condition.params[0].toInt() state.cityInfo != null && state.cityInfo.population.getPopulationFilterAmount(condition.params[1]) >= condition.params[0].toInt()
@ -211,7 +211,7 @@ class Unique(val text: String, val sourceObjectType: UniqueTarget? = null, val s
relevantTile != null && relevantTile!!.getTilesInDistance(condition.params[0].toInt()).any { relevantTile != null && relevantTile!!.getTilesInDistance(condition.params[0].toInt()).any {
it.matchesFilter(condition.params[1]) it.matchesFilter(condition.params[1])
} }
UniqueType.ConditionalVsLargerCiv -> { UniqueType.ConditionalVsLargerCiv -> {
val yourCities = state.civInfo?.cities?.size ?: 1 val yourCities = state.civInfo?.cities?.size ?: 1
val theirCities = state.theirCombatant?.getCivInfo()?.cities?.size ?: 0 val theirCities = state.theirCombatant?.getCivInfo()?.cities?.size ?: 0
@ -219,17 +219,17 @@ class Unique(val text: String, val sourceObjectType: UniqueTarget? = null, val s
} }
UniqueType.ConditionalForeignContinent -> UniqueType.ConditionalForeignContinent ->
state.civInfo != null && relevantTile != null state.civInfo != null && relevantTile != null
&& (state.civInfo.cities.isEmpty() && (state.civInfo.cities.isEmpty() || state.civInfo.getCapital() == null
|| state.civInfo.getCapital().getCenterTile().getContinent() || state.civInfo.getCapital()!!.getCenterTile().getContinent()
!= relevantTile!!.getContinent() != relevantTile!!.getContinent()
) )
UniqueType.ConditionalAdjacentUnit -> UniqueType.ConditionalAdjacentUnit ->
state.civInfo != null state.civInfo != null
&& relevantUnit != null && relevantUnit != null
&& relevantTile!!.neighbors.any { && relevantTile!!.neighbors.any {
it.militaryUnit != null it.militaryUnit != null
&& it.militaryUnit != relevantUnit && it.militaryUnit != relevantUnit
&& it.militaryUnit!!.civInfo == state.civInfo && it.militaryUnit!!.civInfo == state.civInfo
&& it.militaryUnit!!.matchesFilter(condition.params[0]) && it.militaryUnit!!.matchesFilter(condition.params[0])
} }
@ -241,7 +241,7 @@ class Unique(val text: String, val sourceObjectType: UniqueTarget? = null, val s
UniqueType.ConditionalNeighborTilesAnd -> UniqueType.ConditionalNeighborTilesAnd ->
relevantTile != null relevantTile != null
&& relevantTile!!.neighbors.count { && relevantTile!!.neighbors.count {
it.matchesFilter(condition.params[2], state.civInfo) it.matchesFilter(condition.params[2], state.civInfo)
&& it.matchesFilter(condition.params[3], state.civInfo) && it.matchesFilter(condition.params[3], state.civInfo)
} in (condition.params[0].toInt())..(condition.params[1].toInt()) } in (condition.params[0].toInt())..(condition.params[1].toInt())
@ -304,7 +304,7 @@ class UniqueMap: HashMap<String, ArrayList<Unique>>() {
fun getMatchingUniques(uniqueType: UniqueType, state: StateForConditionals) = getUniques(uniqueType) fun getMatchingUniques(uniqueType: UniqueType, state: StateForConditionals) = getUniques(uniqueType)
.filter { it.conditionalsApply(state) } .filter { it.conditionalsApply(state) }
fun getAllUniques() = this.asSequence().flatMap { it.value.asSequence() } fun getAllUniques() = this.asSequence().flatMap { it.value.asSequence() }
} }

View File

@ -215,7 +215,7 @@ class DiplomacyScreen(
} }
val atWar = otherCiv.isAtWarWith(viewingCiv) val atWar = otherCiv.isAtWarWith(viewingCiv)
val nextLevelString = when { val nextLevelString = when {
atWar -> "" atWar -> ""
otherCivDiplomacyManager.getInfluence().toInt() < 30 -> "Reach 30 for friendship." otherCivDiplomacyManager.getInfluence().toInt() < 30 -> "Reach 30 for friendship."
@ -397,7 +397,7 @@ class DiplomacyScreen(
diplomacyTable.add(declareWarButton).row() diplomacyTable.add(declareWarButton).row()
} }
} }
diplomacyTable.add(getGoToOnMapButton(otherCiv)).row() diplomacyTable.add(getGoToOnMapButton(otherCiv)).row()
val diplomaticMarriageButton = getDiplomaticMarriageButton(otherCiv) val diplomaticMarriageButton = getDiplomaticMarriageButton(otherCiv)
@ -428,7 +428,7 @@ class DiplomacyScreen(
for (improvableTile in improvableResourceTiles) for (improvableTile in improvableResourceTiles)
for (tileImprovement in improvements.values) for (tileImprovement in improvements.values)
if (improvableTile.tileResource.isImprovedBy(tileImprovement.name) if (improvableTile.tileResource.isImprovedBy(tileImprovement.name)
&& improvableTile.canBuildImprovement(tileImprovement, otherCiv) && improvableTile.canBuildImprovement(tileImprovement, otherCiv)
) )
needsImprovements = true needsImprovements = true
@ -491,8 +491,8 @@ class DiplomacyScreen(
return diplomacyTable return diplomacyTable
} }
fun getImprovableResourceTiles(otherCiv:CivilizationInfo) = otherCiv.getCapital().getTiles().filter { fun getImprovableResourceTiles(otherCiv:CivilizationInfo) = otherCiv.getCapital()!!.getTiles().filter {
it.hasViewableResource(otherCiv) it.hasViewableResource(otherCiv)
&& it.tileResource.resourceType != ResourceType.Bonus && it.tileResource.resourceType != ResourceType.Bonus
&& (it.improvement == null || !it.tileResource.isImprovedBy(it.improvement!!)) && (it.improvement == null || !it.tileResource.isImprovedBy(it.improvement!!))
} }
@ -751,9 +751,9 @@ class DiplomacyScreen(
diplomacyTable.add(demandsButton).row() diplomacyTable.add(demandsButton).row()
if (isNotPlayersTurn()) demandsButton.disable() if (isNotPlayersTurn()) demandsButton.disable()
if (otherCiv.cities.isNotEmpty() && otherCiv.getCapital().location in viewingCiv.exploredTiles) if (otherCiv.cities.isNotEmpty() && otherCiv.getCapital() != null && otherCiv.getCapital()!!.location in viewingCiv.exploredTiles)
diplomacyTable.add(getGoToOnMapButton(otherCiv)).row() diplomacyTable.add(getGoToOnMapButton(otherCiv)).row()
if (!otherCiv.isPlayerCivilization()) { // human players make their own choices if (!otherCiv.isPlayerCivilization()) { // human players make their own choices
diplomacyTable.add(getRelationshipTable(otherCivDiplomacyManager)).row() diplomacyTable.add(getRelationshipTable(otherCivDiplomacyManager)).row()
diplomacyTable.add(getDiplomacyModifiersTable(otherCivDiplomacyManager)).row() diplomacyTable.add(getDiplomacyModifiersTable(otherCivDiplomacyManager)).row()
@ -938,10 +938,10 @@ class DiplomacyScreen(
val goToOnMapButton = "Go to on map".toTextButton() val goToOnMapButton = "Go to on map".toTextButton()
goToOnMapButton.onClick { goToOnMapButton.onClick {
UncivGame.Current.resetToWorldScreen() UncivGame.Current.resetToWorldScreen()
UncivGame.Current.worldScreen.mapHolder.setCenterPosition(civilization.getCapital().location, selectUnit = false) UncivGame.Current.worldScreen.mapHolder.setCenterPosition(civilization.getCapital()!!.location, selectUnit = false)
} }
return goToOnMapButton return goToOnMapButton
} }
override fun resize(width: Int, height: Int) { override fun resize(width: Int, height: Int) {
super.resize(width, height) super.resize(width, height)

View File

@ -155,7 +155,7 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas
stage.addActor(mapHolder) stage.addActor(mapHolder)
stage.scrollFocus = mapHolder stage.scrollFocus = mapHolder
stage.addActor(notificationsScroll) // very low in z-order, so we're free to let it extend _below_ tile info and minimap if we want stage.addActor(notificationsScroll) // very low in z-order, so we're free to let it extend _below_ tile info and minimap if we want
stage.addActor(minimapWrapper) stage.addActor(minimapWrapper)
stage.addActor(topBar) stage.addActor(topBar)
stage.addActor(nextTurnButton) stage.addActor(nextTurnButton)
@ -176,7 +176,7 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas
val tileToCenterOn: Vector2 = val tileToCenterOn: Vector2 =
when { when {
viewingCiv.cities.isNotEmpty() -> viewingCiv.getCapital().location viewingCiv.cities.isNotEmpty() && viewingCiv.getCapital() != null -> viewingCiv.getCapital()!!.location
viewingCiv.getCivUnits().any() -> viewingCiv.getCivUnits().first().getTile().position viewingCiv.getCivUnits().any() -> viewingCiv.getCivUnits().first().getTile().position
else -> Vector2.Zero else -> Vector2.Zero
} }
@ -282,7 +282,7 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas
keyPressDispatcher[Input.Keys.F12] = quickLoad // Quick Load keyPressDispatcher[Input.Keys.F12] = quickLoad // Quick Load
keyPressDispatcher[Input.Keys.HOME] = { // Capital City View keyPressDispatcher[Input.Keys.HOME] = { // Capital City View
val capital = gameInfo.currentPlayerCiv.getCapital() val capital = gameInfo.currentPlayerCiv.getCapital()
if (!mapHolder.setCenterPosition(capital.location)) if (capital != null && !mapHolder.setCenterPosition(capital.location))
game.setScreen(CityScreen(capital)) game.setScreen(CityScreen(capital))
} }
keyPressDispatcher[KeyCharAndCode.ctrl('O')] = { // Game Options keyPressDispatcher[KeyCharAndCode.ctrl('O')] = { // Game Options