diff --git a/core/src/com/unciv/logic/BarbarianManager.kt b/core/src/com/unciv/logic/BarbarianManager.kt index 0ae2ca54c0..e5abc55200 100644 --- a/core/src/com/unciv/logic/BarbarianManager.kt +++ b/core/src/com/unciv/logic/BarbarianManager.kt @@ -100,8 +100,8 @@ class BarbarianManager { if (campsToAdd <= 0) return // 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() } - .flatMap { it.getCapital().getCenterTile().getTilesInDistance(4) }.toSet() + val tooCloseToCapitals = gameInfo.civilizations.filterNot { it.isBarbarian() || it.isSpectator() || it.cities.isEmpty() || it.isCityState() || it.getCapital() == null } + .flatMap { it.getCapital()!!.getCenterTile().getTilesInDistance(4) }.toSet() val tooCloseToCamps = camps .flatMap { tileMap[it.key].getTilesInDistance( if (it.value.destroyed) 4 else 7 @@ -132,7 +132,7 @@ class BarbarianManager { tile = viableTiles.random() } else tile = viableTiles.random() - + tile.improvement = Constants.barbarianEncampment val newCamp = Encampment(tile.position) newCamp.gameInfo = gameInfo @@ -296,4 +296,4 @@ class Encampment() { } countdown /= 100 } -} \ No newline at end of file +} diff --git a/core/src/com/unciv/logic/GameInfo.kt b/core/src/com/unciv/logic/GameInfo.kt index 601915f244..c03f073ba7 100644 --- a/core/src/com/unciv/logic/GameInfo.kt +++ b/core/src/com/unciv/logic/GameInfo.kt @@ -345,7 +345,7 @@ class GameInfo { getAliveCityStates() .asSequence() .filter { it.cityStateResource == resourceName } - .map { it.getCapital().getCenterTile() } + .map { it.getCapital()!!.getCenterTile() } } else { tileMap.values .asSequence() diff --git a/core/src/com/unciv/logic/automation/Automation.kt b/core/src/com/unciv/logic/automation/Automation.kt index a7289e5719..5da809bce7 100644 --- a/core/src/com/unciv/logic/automation/Automation.kt +++ b/core/src/com/unciv/logic/automation/Automation.kt @@ -213,7 +213,7 @@ object Automation { // If we have vision of our entire starting continent (ish) we are not afraid 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] if (startingContinentSize != null && startingContinentSize < civInfo.viewableTiles.size * multiplier) return false diff --git a/core/src/com/unciv/logic/automation/NextTurnAutomation.kt b/core/src/com/unciv/logic/automation/NextTurnAutomation.kt index daa3207508..0088283f36 100644 --- a/core/src/com/unciv/logic/automation/NextTurnAutomation.kt +++ b/core/src/com/unciv/logic/automation/NextTurnAutomation.kt @@ -473,7 +473,7 @@ object NextTurnAutomation { for (resource in civInfo.gameInfo.spaceResources) { // Have enough resources already - val resourceCount = civInfo.getCivResourcesByName()[resource] ?: 0 + val resourceCount = civInfo.getCivResourcesByName()[resource] ?: 0 if (resourceCount >= Automation.getReservedSpaceResourceAmount(civInfo)) continue @@ -691,7 +691,7 @@ object NextTurnAutomation { private fun motivationToAttack(civInfo: CivilizationInfo, otherCiv: CivilizationInfo): Int { if(civInfo.cities.isEmpty() || otherCiv.cities.isEmpty()) return 0 val baseForce = 30f - + val ourCombatStrength = civInfo.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 ourCity = closestCities.city1 val theirCity = closestCities.city2 - + if (civInfo.getCivUnits().filter { it.isMilitary() }.none { - val damageRecievedWhenAttacking = + val damageRecievedWhenAttacking = BattleDamage.calculateDamageToAttacker( MapUnitCombatant(it), CityCombatant(theirCity) @@ -722,7 +722,7 @@ object NextTurnAutomation { && (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() 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. diff --git a/core/src/com/unciv/logic/automation/SpecificUnitAutomation.kt b/core/src/com/unciv/logic/automation/SpecificUnitAutomation.kt index 15dff29774..c61120c277 100644 --- a/core/src/com/unciv/logic/automation/SpecificUnitAutomation.kt +++ b/core/src/com/unciv/logic/automation/SpecificUnitAutomation.kt @@ -279,7 +279,8 @@ object SpecificUnitAutomation { } 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)) unit.movement.headTowards(capitalTile) if (unit.getTile() == capitalTile) { @@ -287,7 +288,7 @@ object SpecificUnitAutomation { return } } - + fun automateMissionary(unit: MapUnit) { if (unit.religion != unit.civInfo.religionManager.religion?.name) return unit.destroy() @@ -425,7 +426,7 @@ object SpecificUnitAutomation { val firstStepInPath = pathsToCities[closestCityThatCanAttackFrom]!!.first() airUnit.movement.moveToTile(firstStepInPath) } - + fun automateNukes(unit: MapUnit) { val tilesInRange = unit.currentTile.getTilesInDistance(unit.getRange()) for (tile in tilesInRange) { @@ -443,7 +444,7 @@ object SpecificUnitAutomation { if (BattleHelper.tryAttackNearbyEnemy(unit)) return tryRelocateToNearbyAttackableCities(unit) } - + private fun tryRelocateToNearbyAttackableCities(unit: MapUnit) { val tilesInRange = unit.currentTile.getTilesInDistance(unit.getRange()) val immediatelyReachableCities = tilesInRange @@ -455,7 +456,7 @@ object SpecificUnitAutomation { unit.movement.moveToTile(city) return } - + if (unit.baseUnit.isAirUnit()) { val pathsToCities = unit.movement.getAerialPathsToCities() if (pathsToCities.isEmpty()) return // can't actually move anywhere else @@ -479,7 +480,7 @@ object SpecificUnitAutomation { fun foundReligion(unit: MapUnit) { 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 { !it.isHolyCity() && unit.movement.canMoveTo(it.getCenterTile()) @@ -493,16 +494,16 @@ object SpecificUnitAutomation { UnitActions.getFoundReligionAction(unit)() } - + fun enhanceReligion(unit: MapUnit) { // Try go to a nearby city if (!unit.getTile().isCityCenter()) UnitAutomation.tryEnterOwnClosestCity(unit) - + // If we were unable to go there this turn, unable to do anything else if (!unit.getTile().isCityCenter()) return - + UnitActions.getEnhanceReligionAction(unit)() } diff --git a/core/src/com/unciv/logic/automation/WorkerAutomation.kt b/core/src/com/unciv/logic/automation/WorkerAutomation.kt index 59a2eb1704..1133847d21 100644 --- a/core/src/com/unciv/logic/automation/WorkerAutomation.kt +++ b/core/src/com/unciv/logic/automation/WorkerAutomation.kt @@ -28,7 +28,7 @@ private object WorkerAutomationConst { * Contains the logic for worker automation. * * 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 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.cityStats.isConnectedToCapital(bestRoadAvailable) }.sortedBy { - it.getCenterTile().aerialDistanceTo(civInfo.getCapital().getCenterTile()) + it.getCenterTile().aerialDistanceTo(civInfo.getCapital()!!.getCenterTile()) }.toList() if (WorkerAutomationConst.consoleOutput) { println("WorkerAutomation citiesThatNeedConnecting for ${civInfo.civName} turn $cachedForTurn:") @@ -88,9 +88,9 @@ class WorkerAutomation( } /** Caches BFS by city locations (cities needing connecting). - * + * * key: The city to connect from as [hex position][Vector2]. - * + * * 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 @@ -116,7 +116,7 @@ class WorkerAutomation( fun evaluateFortPlacement(tile: TileInfo, civInfo: CivilizationInfo, isCitadel: Boolean): Boolean { return civInfo.getWorkerAutomation().evaluateFortPlacement(tile, isCitadel) } - + /** For console logging only */ 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 val chosenImprovement = chooseImprovement(unit, tile) if (chosenImprovement != null && tile.canBuildImprovement(chosenImprovement, civInfo) && unit.canBuildImprovement(chosenImprovement, tile)) return true - + } else if (!tile.containsGreatImprovement() && tile.hasViewableResource(civInfo) && tile.tileResource.isImprovedBy(tile.improvement!!) && (chooseImprovement(unit, tile) // if the chosen improvement is not null and buildable diff --git a/core/src/com/unciv/logic/battle/BattleDamage.kt b/core/src/com/unciv/logic/battle/BattleDamage.kt index 635bd1c6f7..1ae1881cdb 100644 --- a/core/src/com/unciv/logic/battle/BattleDamage.kt +++ b/core/src/com/unciv/logic/battle/BattleDamage.kt @@ -16,7 +16,7 @@ import kotlin.math.pow import kotlin.math.roundToInt object BattleDamage { - + private fun getModifierStringFromUnique(unique: Unique): String { val source = when (unique.sourceObjectType) { UniqueTarget.Unit -> "Unit ability" @@ -49,9 +49,9 @@ object BattleDamage { for (unique in combatant.getMatchingUniques( UniqueType.StrengthNearCapital, conditionalState, true )) { - if (civInfo.cities.isEmpty()) break + if (civInfo.cities.isEmpty() || civInfo.getCapital() == null) break val distance = - combatant.getTile().aerialDistanceTo(civInfo.getCapital().getCenterTile()) + combatant.getTile().aerialDistanceTo(civInfo.getCapital()!!.getCenterTile()) // https://steamcommunity.com/sharedfiles/filedetails/?id=326411722#464287 val effect = unique.params[0].toInt() - 3 * distance if (effect <= 0) continue @@ -168,7 +168,7 @@ object BattleDamage { fun getDefenceModifiers(attacker: ICombatant, defender: ICombatant): Counter { val modifiers = getGeneralModifiers(defender, attacker, CombatAction.Defend) val tile = defender.getTile() - + if (defender is MapUnitCombatant) { if (defender.unit.isEmbarked()) { @@ -196,7 +196,7 @@ object BattleDamage { return modifiers } - + @Deprecated("As of 4.0.3", level=DeprecationLevel.WARNING) private fun getTileSpecificModifiers(unit: MapUnitCombatant, tile: TileInfo): Counter { val modifiers = Counter() @@ -225,7 +225,7 @@ object BattleDamage { 1f } // Each 3 points of health reduces damage dealt by 1% - else 1 - (100 - combatant.getHealth()) / 300f + else 1 - (100 - combatant.getHealth()) / 300f } diff --git a/core/src/com/unciv/logic/city/CityStats.kt b/core/src/com/unciv/logic/city/CityStats.kt index 598b37ba94..4c4206c615 100644 --- a/core/src/com/unciv/logic/city/CityStats.kt +++ b/core/src/com/unciv/logic/city/CityStats.kt @@ -55,12 +55,12 @@ class StatTreeNode { } /** Holds and calculates [Stats] for a city. - * + * * 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. */ class CityStats(val cityInfo: CityInfo) { - //region Fields, Transient + //region Fields, Transient var baseStatTree = StatTreeNode() @@ -83,7 +83,7 @@ class CityStats(val cityInfo: CityInfo) { val stats = Stats() if (!cityInfo.isCapital() && cityInfo.isConnectedToCapital()) { 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)) stats.add(unique.stats) val percentageStats = Stats() @@ -161,7 +161,7 @@ class CityStats(val cityInfo: CityInfo) { } } } - + for (unique in cityInfo.civInfo.getMatchingUniques(UniqueType.BonusStatsFromCityStates)) { 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[2].toFloat() )) - + if (currentConstruction is Building && 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)) addUniqueStats(unique, Stat.Production, unique.params[0].toFloat()) @@ -399,19 +400,19 @@ class CityStats(val cityInfo: CityInfo) { unhappinessFromCity *= 2f //doubled for the Indian newHappinessList["Cities"] = unhappinessFromCity * unhappinessModifier - + var unhappinessFromCitizens = cityInfo.population.population.toFloat() - + for (unique in cityInfo.getMatchingUniques(UniqueType.UnhappinessFromPopulationTypePercentageChange)) if (cityInfo.matchesFilter(unique.params[2])) unhappinessFromCitizens += (unique.params[0].toFloat() / 100f) * cityInfo.population.getPopulationFilterAmount(unique.params[1]) - + // Deprecated as of 3.19.19 for (unique in cityInfo.getMatchingUniques(UniqueType.UnhappinessFromSpecialistsPercentageChange)) { if (cityInfo.matchesFilter(unique.params[1])) unhappinessFromCitizens += unique.params[0].toFloat() / 100f * cityInfo.population.getNumberOfSpecialists() } - + for (unique in cityInfo.getMatchingUniques(UniqueType.UnhappinessFromPopulationPercentageChange)) if (cityInfo.matchesFilter(unique.params[1])) unhappinessFromCitizens += unique.params[0].toFloat() / 100f * cityInfo.population.population @@ -423,7 +424,7 @@ class CityStats(val cityInfo: CityInfo) { unhappinessFromCitizens *= 2f if (unhappinessFromCitizens < 0) unhappinessFromCitizens = 0f - + newHappinessList["Population"] = -unhappinessFromCitizens * unhappinessModifier if (hasExtraAnnexUnhappiness()) newHappinessList["Occupied City"] = -2f //annexed city @@ -453,7 +454,7 @@ class CityStats(val cityInfo: CityInfo) { val newBaseStatTree = StatTreeNode() // We don't edit the existing baseStatList directly, in order to avoid concurrency exceptions - val newBaseStatList = StatMap() + val newBaseStatList = StatMap() newBaseStatTree.addStats(Stats( science = cityInfo.population.population.toFloat(), @@ -605,7 +606,7 @@ class CityStats(val cityInfo: CityInfo) { newFinalStatList["Excess food to production"] = Stats(production = getProductionFromExcessiveFood(totalFood), food = -totalFood) } - + val growthNullifyingUnique = cityInfo.getMatchingUniques(UniqueType.NullifiesGrowth).firstOrNull() if (growthNullifyingUnique != null) { val amountToRemove = -newFinalStatList.values.sumOf { it[Stat.Food].toDouble() } @@ -615,7 +616,7 @@ class CityStats(val cityInfo: CityInfo) { if (cityInfo.isInResistance()) newFinalStatList.clear() // NOPE - + if (newFinalStatList.values.map { it.production }.sum() < 1) // Minimum production for things to progress newFinalStatList["Production"] = Stats(production = 1f) finalStatList = newFinalStatList diff --git a/core/src/com/unciv/logic/civilization/CapitalConnectionsFinder.kt b/core/src/com/unciv/logic/civilization/CapitalConnectionsFinder.kt index 33b1c056e1..89a551d584 100644 --- a/core/src/com/unciv/logic/civilization/CapitalConnectionsFinder.kt +++ b/core/src/com/unciv/logic/civilization/CapitalConnectionsFinder.kt @@ -8,7 +8,7 @@ import kotlin.collections.set class CapitalConnectionsFinder(private val civInfo: CivilizationInfo) { private val citiesReachedToMediums = HashMap>() - private var citiesToCheck = mutableListOf(civInfo.getCapital()) + private var citiesToCheck = mutableListOf(civInfo.getCapital()!!) private lateinit var newCitiesToCheck: MutableList 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!!) init { - citiesReachedToMediums[civInfo.getCapital()] = hashSetOf("Start") + citiesReachedToMediums[civInfo.getCapital()!!] = hashSetOf("Start") } fun find(): Map> { @@ -124,4 +124,4 @@ class CapitalConnectionsFinder(private val civInfo: CivilizationInfo) { citiesReachedToMediums[this]!!.add(transportType) } -} \ No newline at end of file +} diff --git a/core/src/com/unciv/logic/civilization/CityStateFunctions.kt b/core/src/com/unciv/logic/civilization/CityStateFunctions.kt index 5c9eae728e..e6bb7aeea1 100644 --- a/core/src/com/unciv/logic/civilization/CityStateFunctions.kt +++ b/core/src/com/unciv/logic/civilization/CityStateFunctions.kt @@ -99,7 +99,7 @@ class CityStateFunctions(val civInfo: CivilizationInfo) { return null return uniqueUnit } - fun randomGiftableUnit() = + fun randomGiftableUnit() = city.cityConstructions.getConstructableUnits() .filter { !it.isCivilian() && it.isLandUnit() && it.uniqueTo == null } .toList().randomOrNull() @@ -111,7 +111,7 @@ class CityStateFunctions(val civInfo: CivilizationInfo) { 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 - militaryUnit.addConstructionBonuses(placedUnit, civInfo.getCapital().cityConstructions) + militaryUnit.addConstructionBonuses(placedUnit, civInfo.getCapital()!!.cityConstructions) // Siam gets +10 XP for all CS units 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. // 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) { val newAllyCiv = civInfo.gameInfo.getCivilization(newAllyName) @@ -300,10 +300,10 @@ class CityStateFunctions(val civInfo: CivilizationInfo) { otherCiv.addGold(-getDiplomaticMarriageCost()) 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 }) 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()) unit.gift(otherCiv) for (city in civInfo.cities) { @@ -326,7 +326,7 @@ class CityStateFunctions(val civInfo: CivilizationInfo) { modifiers["Major Civ"] = -999 return modifiers } - if (civInfo.cities.isEmpty()) { + if (civInfo.cities.isEmpty() || civInfo.getCapital() == null) { modifiers["No Cities"] = -999 return modifiers } @@ -343,7 +343,7 @@ class CityStateFunctions(val civInfo: CivilizationInfo) { modifiers["Has Protector"] = -20 if (demandingWorker) 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 val recentBullying = civInfo.getRecentBullyingCountdown() if (recentBullying != null && recentBullying > 10) @@ -365,13 +365,13 @@ class CityStateFunctions(val civInfo: CivilizationInfo) { return modifiers 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 .sumOf { if (it.militaryUnit?.civInfo == demandingCiv) it.militaryUnit!!.getForceEvaluation() else 0 } - val csForce = civInfo.getCapital().getForceEvaluation() + inRangeTiles + val csForce = civInfo.getCapital()!!.getForceEvaluation() + inRangeTiles .sumOf { if (it.militaryUnit?.civInfo == civInfo) it.militaryUnit!!.getForceEvaluation() else 0 @@ -426,7 +426,7 @@ class CityStateFunctions(val civInfo: CivilizationInfo) { it.value.isCivilian() && it.value.isBuildable(civInfo) } 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) cityStateBullied(demandingCiv) @@ -660,7 +660,7 @@ class CityStateFunctions(val civInfo: CivilizationInfo) { ) { 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.getCapital().location, + civInfo.getCapital()!!.location, civInfo.civName, "OtherIcons/Present", ) @@ -676,4 +676,4 @@ class CityStateFunctions(val civInfo: CivilizationInfo) { } return newDetailedCivResources } -} \ No newline at end of file +} diff --git a/core/src/com/unciv/logic/civilization/CivInfoTransientUpdater.kt b/core/src/com/unciv/logic/civilization/CivInfoTransientUpdater.kt index afc0f551d4..cddcae3b1b 100644 --- a/core/src/com/unciv/logic/civilization/CivInfoTransientUpdater.kt +++ b/core/src/com/unciv/logic/civilization/CivInfoTransientUpdater.kt @@ -145,13 +145,13 @@ class CivInfoTransientUpdater(val civInfo: CivilizationInfo) { } 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() if (!initialSetup) { // In the initial setup we're loading an old game state, so it doesn't really count 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) // 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() { val newDetailedCivResources = ResourceSupplyList() for (city in civInfo.cities) newDetailedCivResources.add(city.getCityResources()) - + if (!civInfo.isCityState()) { // First we get all these resources of each city state separately val cityStateProvidedResources = ResourceSupplyList() @@ -192,10 +192,10 @@ class CivInfoTransientUpdater(val civInfo: CivilizationInfo) { for (unit in civInfo.getCivUnits()) for ((resource, amount) in unit.baseUnit.getResourceRequirements()) 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 if (civInfo.detailedCivResources == newDetailedCivResources) return - + civInfo.detailedCivResources = newDetailedCivResources 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 } -} \ No newline at end of file +} diff --git a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt index 41c6810547..ddce02bd75 100644 --- a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt +++ b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt @@ -150,13 +150,13 @@ class CivilizationInfo { /** See DiplomacyManager.flagsCountdown for why this does not map Enums to ints */ private var flagsCountdown = HashMap() - + /** 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 * This can also contain NON-temporary uniques but I can't be bothered to do the deprecation dance with this one */ val temporaryUniques = ArrayList() - + // if we only use lists, and change the list each time the cities are changed, // we won't get concurrent modification exceptions. // This is basically a way to ensure our lists are immutable. @@ -303,7 +303,7 @@ class CivilizationInfo { compareByDescending { it.isMajorCiv() } .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 isOneCityChallenger() = ( playerType == PlayerType.Human && @@ -344,11 +344,11 @@ class CivilizationInfo { return if (preferredVictoryType == Constants.neutralVictoryType) null else gameInfo.ruleSet.victories[getPreferredVictoryType()]!! } - + fun wantsToFocusOn(focus: Victory.Focus): Boolean { return thingsToFocusOnForVictory.contains(focus) } - + @Transient private val civInfoStats = CivInfoStats(this) fun stats() = civInfoStats @@ -413,7 +413,7 @@ class CivilizationInfo { fun hasUnique(uniqueType: UniqueType, stateForConditionals: StateForConditionals = StateForConditionals(this)) = getMatchingUniques(uniqueType, stateForConditionals).any() - + // Does not return local uniques, only global ones. /** Destined to replace getMatchingUniques, gradually, as we fill the enum */ fun getMatchingUniques(uniqueType: UniqueType, stateForConditionals: StateForConditionals = StateForConditionals(this), cityToIgnore: CityInfo? = null) = sequence { @@ -433,16 +433,16 @@ class CivilizationInfo { if (religionManager.religion != null) yieldAll(religionManager.religion!!.getFounderUniques() .filter { it.isOfType(uniqueType) && it.conditionalsApply(stateForConditionals) }) - + yieldAll(getCivResources().asSequence() .filter { it.amount > 0 } .flatMap { it.resource.getMatchingUniques(uniqueType, stateForConditionals) } ) - + yieldAll(gameInfo.ruleSet.globalUniques.getMatchingUniques(uniqueType, stateForConditionals)) } - - + + //region Units fun getCivUnitsSize(): Int = units.size fun getCivUnits(): Sequence = units.asSequence() @@ -507,7 +507,7 @@ class CivilizationInfo { val baseUnit = gameInfo.ruleSet.units[baseUnitName] ?: throw UncivShowableException("Unit $baseUnitName doesn't seem to exist!") return getEquivalentUnit(baseUnit) - } + } fun getEquivalentUnit(baseUnit: BaseUnit): BaseUnit { if (baseUnit.replaces != null) 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 (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 faithAmount = Stats(faith = 4f) @@ -561,10 +561,10 @@ class CivilizationInfo { } for ((key, value) in giftAmount) otherCiv.addStat(key, value.toInt()) - + 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 } @@ -987,7 +987,7 @@ class CivilizationInfo { fun getTurnsTillCallForBarbHelp() = flagsCountdown[CivFlags.TurnsTillCallForBarbHelp.name] fun mayVoteForDiplomaticVictory() = - getTurnsTillNextDiplomaticVote() == 0 + getTurnsTillNextDiplomaticVote() == 0 && civName !in gameInfo.diplomaticVictoryVotesCast.keys // 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 } @@ -1000,6 +1000,7 @@ class CivilizationInfo { flagsCountdown[CivFlags.ShowDiplomaticVotingResults.name] == 0 && gameInfo.civilizations.any { it.isMajorCiv() && !it.isDefeated() && it != this } + private fun updateRevolts() { if (gameInfo.civilizations.none { it.isBarbarian() }) { // Can't spawn revolts without barbarians ¯\_(ツ)_/¯ @@ -1031,7 +1032,7 @@ class CivilizationInfo { val spawnCity = cities.maxByOrNull { random.nextInt(it.population.population + 10) } ?: return val spawnTile = spawnCity.getTiles().maxByOrNull { rateTileForRevoltSpawn(it) } ?: return 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) }.maxByOrNull { random.nextInt(1000) @@ -1046,14 +1047,14 @@ class CivilizationInfo { } // 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") } // Higher is better 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 var score = 10 if (tile.improvement == null) { @@ -1164,7 +1165,7 @@ class CivilizationInfo { } if (placedUnit.hasUnique(UniqueType.ReligiousUnit) && gameInfo.isReligionEnabled()) { - placedUnit.religion = + placedUnit.religion = when { placedUnit.hasUnique(UniqueType.TakeReligionOverBirthCity) && religionManager.religion?.isMajorReligion() == true -> @@ -1181,7 +1182,7 @@ class CivilizationInfo { passableImpassables.add(unique.params[0]) // Add to list of passable impassables } } - + return placedUnit } @@ -1272,7 +1273,7 @@ class CivilizationInfo { // Check if different continents (unless already max distance, or water map) 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 proximity = when (proximity) { @@ -1299,8 +1300,9 @@ class CivilizationInfo { * Removes current capital then moves capital to argument city if not null */ fun moveCapitalTo(city: CityInfo?) { - if (cities.isNotEmpty()) { - getCapital().cityConstructions.removeBuilding(getCapital().capitalCityIndicator()) + if (cities.isNotEmpty() && getCapital() != null) { + 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 @@ -1311,7 +1313,7 @@ class CivilizationInfo { fun moveCapitalToNextLargest() { moveCapitalTo(cities - .filterNot { it == getCapital() } + .filterNot { it.isCapital() } .maxByOrNull { it.population.population}) } diff --git a/core/src/com/unciv/logic/civilization/QuestManager.kt b/core/src/com/unciv/logic/civilization/QuestManager.kt index cf41aea3ca..a5a6f52f23 100644 --- a/core/src/com/unciv/logic/civilization/QuestManager.kt +++ b/core/src/com/unciv/logic/civilization/QuestManager.kt @@ -117,7 +117,7 @@ class QuestManager { tryStartNewGlobalQuest() tryStartNewIndividualQuests() - + tryBarbarianInvasion() tryEndWarWithMajorQuests() } @@ -226,7 +226,7 @@ class QuestManager { && !it.isAtWarWith(civInfo) && it.getProximity(civInfo) <= Proximity.Far }) { 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) } @@ -358,10 +358,10 @@ class QuestManager { return when (quest.name) { QuestName.ClearBarbarianCamp.value -> getBarbarianEncampmentForQuest() != null 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 - && challenger.cities.any { it.getCenterTile().aerialDistanceTo(civInfo.getCapital().getCenterTile()) <= 7 - && it.getCenterTile().getContinent() == civInfo.getCapital().getCenterTile().getContinent() } + && challenger.cities.any { it.getCenterTile().aerialDistanceTo(civInfo.getCapital()!!.getCenterTile()) <= 7 + && it.getCenterTile().getContinent() == civInfo.getCapital()!!.getCenterTile().getContinent() } QuestName.ConnectResource.value -> getResourceForQuest(challenger) != null QuestName.ConstructWonder.value -> getWonderToBuildForQuest(challenger) != null QuestName.GreatPerson.value -> getGreatPersonForQuest(challenger) != null @@ -373,7 +373,7 @@ class QuestManager { && !challenger.getDiplomacyManager(mostRecentBully).hasFlag(DiplomacyFlags.Denunciation) && challenger.getDiplomacyManager(mostRecentBully).diplomaticStatus != DiplomaticStatus.War && !( 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.BullyCityState.value -> getCityStateTarget(challenger) != null QuestName.ContestFaith.value -> civInfo.gameInfo.isReligionEnabled() @@ -385,7 +385,7 @@ class QuestManager { private fun isComplete(assignedQuest: AssignedQuest): Boolean { val assignee = civInfo.gameInfo.getCivilization(assignedQuest.assignee) 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.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 } @@ -393,7 +393,7 @@ class QuestManager { QuestName.FindNaturalWonder.value -> assignee.naturalWonders.contains(assignedQuest.data1) QuestName.PledgeToProtect.value -> assignee in civInfo.getProtectorCivs() 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 } } @@ -421,7 +421,7 @@ class QuestManager { if (rewardInfluence > 0) assignee.addNotification( "[${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 @@ -436,11 +436,11 @@ class QuestManager { if (winners.isEmpty()) { assignee.addNotification( "[${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 { assignee.addNotification( "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 unitsToKill = max(3, totalMilitaryUnits / 4) 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 for (thirdCiv in civInfo.getKnownCivs().filter { it.isAlive() && !it.isAtWarWith(civInfo) && it.isMajorCiv() }) { @@ -568,12 +568,12 @@ class QuestManager { endWarWithMajorQuest(killed) } } - + /** Called when a major civ meets the city-state for the first time. Mainly for war with major pseudo-quest. */ fun justMet(otherCiv: CivilizationInfo) { - 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 + for ((attackerName, unitsToKill) in unitsToKillForCiv) { 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.", @@ -604,15 +604,15 @@ class QuestManager { unitsToKillForCiv.remove(attacker.civName) unitsKilledFromCiv.remove(attacker.civName) } - + fun warWithMajorActive(target: CivilizationInfo): Boolean { return unitsToKillForCiv.containsKey(target.civName) } - + fun unitsToKill(target: CivilizationInfo): Int { return unitsToKillForCiv[target.civName] ?: 0 } - + fun unitsKilledSoFar(target: CivilizationInfo, viewingCiv: CivilizationInfo): Int { val killMap = unitsKilledFromCiv[target.civName] ?: return 0 return killMap[viewingCiv.civName] ?: 0 @@ -755,7 +755,7 @@ class QuestManager { * to be destroyed */ private fun getBarbarianEncampmentForQuest(): TileInfo? { - val encampments = civInfo.getCapital().getCenterTile().getTilesInDistance(8) + val encampments = civInfo.getCapital()!!.getCenterTile().getTilesInDistance(8) .filter { it.improvement == Constants.barbarianEncampment }.toList() if (encampments.isNotEmpty()) @@ -923,7 +923,7 @@ class AssignedQuest(val questName: String = "", } QuestName.Route.value -> { game.resetToWorldScreen() - game.worldScreen.mapHolder.setCenterPosition(gameInfo.getCivilization(assigner).getCapital().location, selectUnit = false) + game.worldScreen.mapHolder.setCenterPosition(gameInfo.getCivilization(assigner).getCapital()!!.location, selectUnit = false) } } } diff --git a/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt b/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt index 6b4298446e..a8bad5a91b 100644 --- a/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt +++ b/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt @@ -14,7 +14,7 @@ import kotlin.math.max import kotlin.math.min 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), Afraid(Color(0x5300ffff)), // HSV(260,100,100) 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. * 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 */ private var influence = 0f @@ -230,7 +230,7 @@ class DiplomacyManager() { influence = max(amount, MINIMUM_INFLUENCE) civInfo.updateAllyCivForCityState() } - + 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. @@ -240,9 +240,9 @@ class DiplomacyManager() { for (unique in otherCiv().getMatchingUniques(UniqueType.CityStateRestingPoint)) 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)) - if (otherCiv().religionManager.religion?.name == civInfo.getCapital().religion.getMajorityReligionName()) + if (otherCiv().religionManager.religion?.name == civInfo.getCapital()!!.religion.getMajorityReligionName()) restingPoint += unique.params[0].toInt() if (diplomaticStatus == DiplomaticStatus.Protector) restingPoint += 10 @@ -266,8 +266,8 @@ class DiplomacyManager() { for (unique in otherCiv().getMatchingUniques(UniqueType.CityStateInfluenceDegradation)) modifierPercent += unique.params[0].toFloat() - val religion = if (civInfo.cities.isEmpty()) null - else civInfo.getCapital().religion.getMajorityReligionName() + val religion = if (civInfo.cities.isEmpty() || civInfo.getCapital() == null) null + else civInfo.getCapital()!!.religion.getMajorityReligionName() if (religion != null && religion == otherCiv().religionManager.religion?.name) modifierPercent -= 25f // 25% slower degrade when sharing a religion @@ -291,8 +291,8 @@ class DiplomacyManager() { if (otherCiv().hasUnique(UniqueType.CityStateInfluenceRecoversTwiceNormalRate)) modifierPercent += 100f - val religion = if (civInfo.cities.isEmpty()) null - else civInfo.getCapital().religion.getMajorityReligionName() + val religion = if (civInfo.cities.isEmpty() || civInfo.getCapital() == null) null + else civInfo.getCapital()!!.religion.getMajorityReligionName() if (religion != null && religion == otherCiv().religionManager.religion?.name) 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) && (offer.name in negativeCivResources || !civInfo.gameInfo.ruleSet.tileResources.containsKey(offer.name)) ) { - + trades.remove(trade) val otherCivTrades = otherCiv().getDiplomacyManager(civInfo).trades otherCivTrades.removeAll { it.equalTrade(trade.reverse()) } @@ -382,7 +382,7 @@ class DiplomacyManager() { if (trade.theirOffers.any { it.name == Constants.peaceTreaty }) { remakePeaceTreaty(trade.theirOffers.first { it.name == Constants.peaceTreaty }.duration) } - + 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) civInfo.updateDetailedCivResources() @@ -390,7 +390,7 @@ class DiplomacyManager() { } } } - + private fun remakePeaceTreaty(durationLeft: Int) { val treaty = Trade() treaty.ourOffers.add( @@ -444,7 +444,7 @@ class DiplomacyManager() { val initialRelationshipLevel = relationshipLevel() 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 // as it should be restored once the war ends (though we keep influence degradation from time during the war) 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 - 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) { val text = "Your relationship with [${civInfo.civName}] is about to degrade" 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 if (!otherCiv().isCityState()) return - + val eraInfo = civInfo.getEra() if (relationshipLevel() < RelationshipLevel.Friend) { - if (hasFlag(DiplomacyFlags.ProvideMilitaryUnit)) + if (hasFlag(DiplomacyFlags.ProvideMilitaryUnit)) removeFlag(DiplomacyFlags.ProvideMilitaryUnit) return } - + val variance = listOf(-1, 0, 1).random() - + if (eraInfo.undefinedCityStateBonuses() && otherCiv().cityStateType == CityStateType.Militaristic) { // Deprecated, assume Civ V values for compatibility if (!hasFlag(DiplomacyFlags.ProvideMilitaryUnit) && relationshipLevel() == RelationshipLevel.Friend) @@ -632,7 +632,7 @@ class DiplomacyManager() { && relationshipLevel() == RelationshipLevel.Ally) setFlag(DiplomacyFlags.ProvideMilitaryUnit, 17 + variance) } - + if (eraInfo.undefinedCityStateBonuses()) return for (bonus in eraInfo.getCityStateBonuses(otherCiv().cityStateType, relationshipLevel())) { @@ -657,7 +657,7 @@ class DiplomacyManager() { if (civInfo.isCityState() && civInfo.getProtectorCivs().contains(otherCiv())) { civInfo.removeProtectorCiv(otherCiv(), forced = true) } - + diplomaticStatus = DiplomaticStatus.War removeModifier(DiplomaticModifiers.YearsOfPeace) @@ -666,12 +666,12 @@ class DiplomacyManager() { 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. - * - * @param indirectCityStateAttack Influence with city states should only be set to -60 - * 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. + * + * @param indirectCityStateAttack Influence with city states should only be set to -60 + * 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. * Should only ever be set to true for calls originating from within this function. */ fun declareWar(indirectCityStateAttack: Boolean = false) { diff --git a/core/src/com/unciv/logic/trade/TradeEvaluation.kt b/core/src/com/unciv/logic/trade/TradeEvaluation.kt index 64135812e0..d02824d914 100644 --- a/core/src/com/unciv/logic/trade/TradeEvaluation.kt +++ b/core/src/com/unciv/logic/trade/TradeEvaluation.kt @@ -230,7 +230,7 @@ class TradeEvaluation { 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!") - val capitalcity = civInfo.getCapital() + val capitalcity = civInfo.getCapital()!! val distanceCost = distanceCityTradeModifier(civInfo, capitalcity, city) val stats = city.cityStats.currentCityStats val sumOfStats = @@ -280,4 +280,4 @@ class TradeEvaluation { ?: return 0 return unique.params[0].toInt() } -} \ No newline at end of file +} diff --git a/core/src/com/unciv/models/ruleset/unique/Unique.kt b/core/src/com/unciv/models/ruleset/unique/Unique.kt index 0b72bab6ca..29de13a5fb 100644 --- a/core/src/com/unciv/models/ruleset/unique/Unique.kt +++ b/core/src/com/unciv/models/ruleset/unique/Unique.kt @@ -105,7 +105,7 @@ class Unique(val text: String, val sourceObjectType: UniqueTarget? = null, val s if (finalPossibleUniques.size == 1) return finalPossibleUniques.first() // filter out possible replacements that are obviously wrong - val uniquesWithNoErrors = finalPossibleUniques.filter { + val uniquesWithNoErrors = finalPossibleUniques.filter { val unique = Unique(it) val errors = ruleset.checkUnique(unique, true, "", 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 else state.unit } - + val stateBasedRandom by lazy { Random(state.hashCode()) } return when (condition.type) { @@ -151,8 +151,8 @@ class Unique(val text: String, val sourceObjectType: UniqueTarget? = null, val s state.civInfo != null && condition.params[0].toInt() <= state.civInfo.happinessForNextTurn && state.civInfo.happinessForNextTurn < condition.params[1].toInt() - UniqueType.ConditionalBelowHappiness -> - state.civInfo != null && state.civInfo.happinessForNextTurn < condition.params[0].toInt() + UniqueType.ConditionalBelowHappiness -> + state.civInfo != null && state.civInfo.happinessForNextTurn < condition.params[0].toInt() UniqueType.ConditionalGoldenAge -> state.civInfo != null && state.civInfo.goldenAges.isGoldenAge() 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]) UniqueType.ConditionalNoPolicy -> 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]) } - UniqueType.ConditionalCityWithBuilding -> + UniqueType.ConditionalCityWithBuilding -> state.cityInfo != null && state.cityInfo.cityConstructions.containsBuildingOrEquivalent(condition.params[0]) - UniqueType.ConditionalCityWithoutBuilding -> + UniqueType.ConditionalCityWithoutBuilding -> state.cityInfo != null && !state.cityInfo.cityConstructions.containsBuildingOrEquivalent(condition.params[0]) UniqueType.ConditionalPopulationFilter -> 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 { it.matchesFilter(condition.params[1]) } - + UniqueType.ConditionalVsLargerCiv -> { val yourCities = state.civInfo?.cities?.size ?: 1 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 -> state.civInfo != null && relevantTile != null - && (state.civInfo.cities.isEmpty() - || state.civInfo.getCapital().getCenterTile().getContinent() + && (state.civInfo.cities.isEmpty() || state.civInfo.getCapital() == null + || state.civInfo.getCapital()!!.getCenterTile().getContinent() != relevantTile!!.getContinent() ) UniqueType.ConditionalAdjacentUnit -> - state.civInfo != null + state.civInfo != null && relevantUnit != null && relevantTile!!.neighbors.any { it.militaryUnit != null && it.militaryUnit != relevantUnit - && it.militaryUnit!!.civInfo == state.civInfo + && it.militaryUnit!!.civInfo == state.civInfo && it.militaryUnit!!.matchesFilter(condition.params[0]) } @@ -241,7 +241,7 @@ class Unique(val text: String, val sourceObjectType: UniqueTarget? = null, val s UniqueType.ConditionalNeighborTilesAnd -> relevantTile != null && relevantTile!!.neighbors.count { - it.matchesFilter(condition.params[2], state.civInfo) + it.matchesFilter(condition.params[2], state.civInfo) && it.matchesFilter(condition.params[3], state.civInfo) } in (condition.params[0].toInt())..(condition.params[1].toInt()) @@ -304,7 +304,7 @@ class UniqueMap: HashMap>() { fun getMatchingUniques(uniqueType: UniqueType, state: StateForConditionals) = getUniques(uniqueType) .filter { it.conditionalsApply(state) } - + fun getAllUniques() = this.asSequence().flatMap { it.value.asSequence() } } diff --git a/core/src/com/unciv/ui/trade/DiplomacyScreen.kt b/core/src/com/unciv/ui/trade/DiplomacyScreen.kt index 64fb2de205..b4ddacecb7 100644 --- a/core/src/com/unciv/ui/trade/DiplomacyScreen.kt +++ b/core/src/com/unciv/ui/trade/DiplomacyScreen.kt @@ -215,7 +215,7 @@ class DiplomacyScreen( } val atWar = otherCiv.isAtWarWith(viewingCiv) - + val nextLevelString = when { atWar -> "" otherCivDiplomacyManager.getInfluence().toInt() < 30 -> "Reach 30 for friendship." @@ -397,7 +397,7 @@ class DiplomacyScreen( diplomacyTable.add(declareWarButton).row() } } - + diplomacyTable.add(getGoToOnMapButton(otherCiv)).row() val diplomaticMarriageButton = getDiplomaticMarriageButton(otherCiv) @@ -428,7 +428,7 @@ class DiplomacyScreen( for (improvableTile in improvableResourceTiles) for (tileImprovement in improvements.values) - if (improvableTile.tileResource.isImprovedBy(tileImprovement.name) + if (improvableTile.tileResource.isImprovedBy(tileImprovement.name) && improvableTile.canBuildImprovement(tileImprovement, otherCiv) ) needsImprovements = true @@ -491,8 +491,8 @@ class DiplomacyScreen( return diplomacyTable } - fun getImprovableResourceTiles(otherCiv:CivilizationInfo) = otherCiv.getCapital().getTiles().filter { - it.hasViewableResource(otherCiv) + fun getImprovableResourceTiles(otherCiv:CivilizationInfo) = otherCiv.getCapital()!!.getTiles().filter { + it.hasViewableResource(otherCiv) && it.tileResource.resourceType != ResourceType.Bonus && (it.improvement == null || !it.tileResource.isImprovedBy(it.improvement!!)) } @@ -751,9 +751,9 @@ class DiplomacyScreen( diplomacyTable.add(demandsButton).row() 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() - + if (!otherCiv.isPlayerCivilization()) { // human players make their own choices diplomacyTable.add(getRelationshipTable(otherCivDiplomacyManager)).row() diplomacyTable.add(getDiplomacyModifiersTable(otherCivDiplomacyManager)).row() @@ -938,10 +938,10 @@ class DiplomacyScreen( val goToOnMapButton = "Go to on map".toTextButton() goToOnMapButton.onClick { 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 - } + } override fun resize(width: Int, height: Int) { super.resize(width, height) diff --git a/core/src/com/unciv/ui/worldscreen/WorldScreen.kt b/core/src/com/unciv/ui/worldscreen/WorldScreen.kt index b8814eb58f..9d79255827 100644 --- a/core/src/com/unciv/ui/worldscreen/WorldScreen.kt +++ b/core/src/com/unciv/ui/worldscreen/WorldScreen.kt @@ -155,7 +155,7 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas stage.addActor(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(topBar) stage.addActor(nextTurnButton) @@ -176,7 +176,7 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas val tileToCenterOn: Vector2 = when { - viewingCiv.cities.isNotEmpty() -> viewingCiv.getCapital().location + viewingCiv.cities.isNotEmpty() && viewingCiv.getCapital() != null -> viewingCiv.getCapital()!!.location viewingCiv.getCivUnits().any() -> viewingCiv.getCivUnits().first().getTile().position 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.HOME] = { // Capital City View val capital = gameInfo.currentPlayerCiv.getCapital() - if (!mapHolder.setCenterPosition(capital.location)) + if (capital != null && !mapHolder.setCenterPosition(capital.location)) game.setScreen(CityScreen(capital)) } keyPressDispatcher[KeyCharAndCode.ctrl('O')] = { // Game Options