diff --git a/core/src/com/unciv/logic/city/CityInfo.kt b/core/src/com/unciv/logic/city/CityInfo.kt index e707d324dc..310e66b106 100644 --- a/core/src/com/unciv/logic/city/CityInfo.kt +++ b/core/src/com/unciv/logic/city/CityInfo.kt @@ -128,7 +128,10 @@ class CityInfo { fun getWorkableTiles() = getTiles().filter { it in tilesInRange } fun isCapital() = cityConstructions.isBuilt("Palace") - fun isConnectedToCapital() = civInfo.citiesConnectedToCapital.contains(this) + fun isConnectedToCapital(connectionTypePredicate: (Set) -> Boolean = {true}): Boolean { + val mediumTypes = civInfo.citiesConnectedToCapitalToMediums[this] ?: return false + return connectionTypePredicate(mediumTypes) + } fun isInResistance() = resistanceCounter>0 @@ -396,7 +399,7 @@ class CityInfo { // The city could be producing something that puppets shouldn't, like units cityConstructions.currentConstructionIsUserSet = false cityConstructions.constructionQueue.clear() - cityConstructions.chooseNextConstruction() + cityConstructions.chooseNextConstruction() } private fun diplomaticRepercussionsForConqueringCity(oldCiv: CivilizationInfo, conqueringCiv: CivilizationInfo) { diff --git a/core/src/com/unciv/logic/city/CityStats.kt b/core/src/com/unciv/logic/city/CityStats.kt index cca98fdca1..9764c8ef1d 100644 --- a/core/src/com/unciv/logic/city/CityStats.kt +++ b/core/src/com/unciv/logic/city/CityStats.kt @@ -353,14 +353,7 @@ class CityStats { fun isConnectedToCapital(roadType: RoadStatus): Boolean { if (cityInfo.civInfo.cities.count() < 2) return false// first city! - if (roadType == RoadStatus.Road) return cityInfo.isConnectedToCapital() // this transient is not applicable to connection via railroad. - - val capitalTile = cityInfo.civInfo.getCapital().getCenterTile() - val bfs = BFS(capitalTile) { it.roadStatus == roadType } - - val cityTile = cityInfo.getCenterTile() - bfs.stepUntilDestination(cityTile) - return bfs.tilesReached.containsKey(cityTile) + return cityInfo.isConnectedToCapital { it.contains(roadType.name) || it.contains("Harbor") } } //endregion diff --git a/core/src/com/unciv/logic/civilization/CapitalConnectionsFinder.kt b/core/src/com/unciv/logic/civilization/CapitalConnectionsFinder.kt new file mode 100644 index 0000000000..5ebf95e6ec --- /dev/null +++ b/core/src/com/unciv/logic/civilization/CapitalConnectionsFinder.kt @@ -0,0 +1,112 @@ +package com.unciv.logic.civilization + +import com.unciv.logic.city.CityInfo +import com.unciv.logic.map.BFS +import com.unciv.logic.map.TileInfo +import kotlin.collections.set + +class CapitalConnectionsFinder(private val civInfo: CivilizationInfo) { + private val citiesReachedToMediums = HashMap>() + private var citiesToCheck = mutableListOf(civInfo.getCapital()) + private lateinit var newCitiesToCheck: MutableList + + private val allCivCities = civInfo.gameInfo.getCities() + + private val theWheelIsResearched = civInfo.tech.isResearched("The Wheel") + private val railroadIsResearched = civInfo.tech.isResearched("Railroad") + + private val road = "Road" + private val railroad = "Railroad" + private val harbor = "Harbor" + + init { + citiesReachedToMediums[civInfo.getCapital()] = hashSetOf("Start") + } + + fun find(): Map> { + // We map which cities we've reached, to the mediums they've been reached by - + // this is so we know that if we've seen which cities can be connected by port A, and one + // of those is city B, then we don't need to check the cities that B can connect to by port, + // since we'll get the same cities we got from A, since they're connected to the same sea. + while (citiesToCheck.isNotEmpty() && citiesReachedToMediums.size < allCivCities.size) { + newCitiesToCheck = mutableListOf() + for (cityToConnectFrom in citiesToCheck) { + if (cityToConnectFrom.containsHarbor()) { + checkHarbor(cityToConnectFrom) + } + if (railroadIsResearched) { + checkRailroad(cityToConnectFrom) + } + if (theWheelIsResearched) { + checkRoad(cityToConnectFrom) + } + } + citiesToCheck = newCitiesToCheck + } + return citiesReachedToMediums + } + + private fun checkRoad(cityToConnectFrom: CityInfo) { + check( + cityToConnectFrom = cityToConnectFrom, + transportType = road, + overridingTransportType = railroad, + tileFilter = { tile -> tile.hasRoad(civInfo) || tile.hasRailroad() || tile.isCityCenter() } + ) + } + + private fun checkRailroad(cityToConnectFrom: CityInfo) { + check( + cityToConnectFrom = cityToConnectFrom, + transportType = railroad, + tileFilter = { tile -> tile.hasRailroad() || tile.isCityCenter() } + ) + } + + private fun checkHarbor(cityToConnectFrom: CityInfo) { + check( + cityToConnectFrom = cityToConnectFrom, + transportType = harbor, + tileFilter = { tile -> tile.isWater || tile.isCityCenter() }, + cityFilter = { city -> city.containsHarbor() } + ) + } + + private fun CityInfo.containsHarbor() = + this.cityConstructions.containsBuildingOrEquivalent(harbor) + + private fun check(cityToConnectFrom: CityInfo, + transportType: String, + overridingTransportType: String? = null, + tileFilter: (TileInfo) -> Boolean, + cityFilter: (CityInfo) -> Boolean = { true }) { + val bfs = BFS(cityToConnectFrom.getCenterTile(), tileFilter) + bfs.stepToEnd() + val reachedCities = allCivCities.filter { + bfs.hasReachedTile(it.getCenterTile()) && cityFilter(it) + } + for (reachedCity in reachedCities) { + addCityIfFirstEncountered(reachedCity) + if (reachedCity == cityToConnectFrom) continue + if (reachedCity.wasNotPreviouslyReached(transportType, overridingTransportType)) + reachedCity.addMedium(transportType) + } + } + + private fun addCityIfFirstEncountered(reachedCity: CityInfo) { + if (!citiesReachedToMediums.containsKey(reachedCity)) { + newCitiesToCheck.add(reachedCity) + citiesReachedToMediums[reachedCity] = mutableSetOf() + } + } + + private fun CityInfo.wasNotPreviouslyReached(transportType: String, overridingTransportType: String?): Boolean { + val mediums = citiesReachedToMediums[this]!! + return !mediums.contains(transportType) && !mediums.contains(overridingTransportType) + } + + private fun CityInfo.addMedium(transportType: String) { + citiesReachedToMediums[this]!!.add(transportType) + } + +} \ 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 1c034d0255..be01a748df 100644 --- a/core/src/com/unciv/logic/civilization/CivInfoTransientUpdater.kt +++ b/core/src/com/unciv/logic/civilization/CivInfoTransientUpdater.kt @@ -1,17 +1,11 @@ package com.unciv.logic.civilization import com.badlogic.gdx.graphics.Color -import com.unciv.logic.city.CityInfo -import com.unciv.logic.map.BFS import com.unciv.logic.map.TileInfo import com.unciv.models.ruleset.tile.ResourceSupplyList -import java.util.* -import kotlin.collections.HashMap -import kotlin.collections.HashSet -import kotlin.collections.set /** CivInfo class was getting too crowded */ -class CivInfoTransientUpdater(val civInfo: CivilizationInfo){ +class CivInfoTransientUpdater(val civInfo: CivilizationInfo) { // This is a big performance fun updateViewableTiles() { @@ -112,89 +106,30 @@ class CivInfoTransientUpdater(val civInfo: CivilizationInfo){ } } - fun updateHasActiveGreatWall(){ + fun updateHasActiveGreatWall() { civInfo.hasActiveGreatWall = !civInfo.tech.isResearched("Dynamite") && civInfo.containsBuildingUnique("Enemy land units must spend 1 extra movement point when inside your territory (obsolete upon Dynamite)") } - fun setCitiesConnectedToCapitalTransients(initialSetup:Boolean=false){ - if(civInfo.cities.isEmpty()) return // eg barbarians + fun setCitiesConnectedToCapitalTransients(initialSetup: Boolean = false) { + if (civInfo.cities.isEmpty()) return // eg barbarians - // We map which cities we've reached, to the mediums they've been reached by - - // this is so we know that if we've seen which cities can be connected by port A, and one - // of those is city B, then we don't need to check the cities that B can connect to by port, - // since we'll get the same cities we got from A, since they're connected to the same sea. - val citiesReachedToMediums = HashMap>() - var citiesToCheck = mutableListOf(civInfo.getCapital()) - citiesReachedToMediums[civInfo.getCapital()] = arrayListOf("Start") - val allCivCities = civInfo.gameInfo.getCities() + val citiesReachedToMediums = CapitalConnectionsFinder(civInfo).find() - val theWheelIsResearched = civInfo.tech.isResearched("The Wheel") + 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()) + civInfo.addNotification("[${city.name}] has been connected to your capital!", city.location, Color.GOLD) - val road = "Road" - val harbor = "Harbor" - - while(citiesToCheck.isNotEmpty() && citiesReachedToMediums.size() - for(cityToConnectFrom in citiesToCheck){ - val reachedMediums = citiesReachedToMediums[cityToConnectFrom]!! - - // This is copypasta and can be cleaned up - if(theWheelIsResearched && !reachedMediums.contains(road)){ - - val roadBfs = BFS(cityToConnectFrom.getCenterTile()) { it.hasRoad(civInfo) } - roadBfs.stepToEnd() - val reachedCities = allCivCities.filter { roadBfs.tilesReached.containsKey(it.getCenterTile())} - for(reachedCity in reachedCities){ - if(!citiesReachedToMediums.containsKey(reachedCity)){ - newCitiesToCheck.add(reachedCity) - citiesReachedToMediums[reachedCity] = arrayListOf() - } - val cityReachedByMediums = citiesReachedToMediums[reachedCity]!! - if(!cityReachedByMediums.contains(road)) - cityReachedByMediums.add(road) - } - citiesReachedToMediums[cityToConnectFrom]!!.add(road) - } - - if(!reachedMediums.contains(harbor) - && cityToConnectFrom.cityConstructions.containsBuildingOrEquivalent(harbor)){ - val seaBfs = BFS(cityToConnectFrom.getCenterTile()) { it.isWater || it.isCityCenter() } - seaBfs.stepToEnd() - val reachedCities = allCivCities.filter { - seaBfs.tilesReached.containsKey(it.getCenterTile()) - && it.cityConstructions.containsBuildingOrEquivalent(harbor) - } - for(reachedCity in reachedCities){ - if(!citiesReachedToMediums.containsKey(reachedCity)){ - newCitiesToCheck.add(reachedCity) - citiesReachedToMediums[reachedCity] = arrayListOf() - } - val cityReachedByMediums = citiesReachedToMediums[reachedCity]!! - if(!cityReachedByMediums.contains(harbor)) - cityReachedByMediums.add(harbor) - } - citiesReachedToMediums[cityToConnectFrom]!!.add(harbor) - } - } - citiesToCheck = newCitiesToCheck + for (city in civInfo.citiesConnectedToCapitalToMediums.keys) + if (!citiesReachedToMediums.containsKey(city) && city.civInfo == civInfo) + civInfo.addNotification("[${city.name}] has been disconnected from your capital!", city.location, Color.GOLD) } - 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.citiesConnectedToCapital && city.civInfo == civInfo && city != civInfo.getCapital()) - civInfo.addNotification("[${city.name}] has been connected to your capital!",city.location, Color.GOLD) - - for(city in civInfo.citiesConnectedToCapital) - if(!citiesReachedToMediums.containsKey(city) && city.civInfo==civInfo) - civInfo.addNotification("[${city.name}] has been disconnected from your capital!",city.location, Color.GOLD) - } - - civInfo.citiesConnectedToCapital = citiesReachedToMediums.keys.toList() + civInfo.citiesConnectedToCapitalToMediums = citiesReachedToMediums } - fun updateDetailedCivResources() { val newDetailedCivResources = ResourceSupplyList() for (city in civInfo.cities) newDetailedCivResources.add(city.getCityResources()) diff --git a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt index 7f9644ee4d..e3f6fef95d 100644 --- a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt +++ b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt @@ -44,8 +44,8 @@ class CivilizationInfo { @Transient var viewableTiles = setOf() @Transient var viewableInvisibleUnitsTiles = setOf() - /** Contains cities from ALL civilizations connected by trade routes to the capital */ - @Transient var citiesConnectedToCapital = listOf() + /** Contains mapping of cities to travel mediums from ALL civilizations connected by trade routes to the capital */ + @Transient var citiesConnectedToCapitalToMediums = mapOf>() /** This is for performance since every movement calculation depends on this, see MapUnit comment */ @Transient var hasActiveGreatWall = false diff --git a/core/src/com/unciv/logic/map/BFS.kt b/core/src/com/unciv/logic/map/BFS.kt index 3622b85580..d07c226193 100644 --- a/core/src/com/unciv/logic/map/BFS.kt +++ b/core/src/com/unciv/logic/map/BFS.kt @@ -49,4 +49,7 @@ class BFS(val startingPoint: TileInfo, val predicate : (TileInfo) -> Boolean){ } return path } + + fun hasReachedTile(tile: TileInfo) = + tilesReached.containsKey(tile) } \ No newline at end of file diff --git a/core/src/com/unciv/logic/map/TileInfo.kt b/core/src/com/unciv/logic/map/TileInfo.kt index 2c368a3b26..bb2ba23db3 100644 --- a/core/src/com/unciv/logic/map/TileInfo.kt +++ b/core/src/com/unciv/logic/map/TileInfo.kt @@ -354,11 +354,17 @@ open class TileInfo { return false } - fun hasRoad(civInfo: CivilizationInfo): Boolean { - if(roadStatus != RoadStatus.None) return true - if(civInfo.nation.forestsAndJunglesAreRoads && (terrainFeature==Constants.jungle || terrainFeature==Constants.forest)) - return true - return false - } + fun hasConnection(civInfo: CivilizationInfo) = + roadStatus != RoadStatus.None || forestOrJungleAreRoads(civInfo) + + fun hasRoad(civInfo: CivilizationInfo) = + roadStatus == RoadStatus.Road || forestOrJungleAreRoads(civInfo) + + fun hasRailroad() = + roadStatus == RoadStatus.Railroad + + private fun forestOrJungleAreRoads(civInfo: CivilizationInfo) = + civInfo.nation.forestsAndJunglesAreRoads + && (terrainFeature == Constants.jungle || terrainFeature == Constants.forest) //endregion } \ No newline at end of file diff --git a/core/src/com/unciv/logic/map/UnitMovementAlgorithms.kt b/core/src/com/unciv/logic/map/UnitMovementAlgorithms.kt index 4fc19e09a0..117ab7e51f 100644 --- a/core/src/com/unciv/logic/map/UnitMovementAlgorithms.kt +++ b/core/src/com/unciv/logic/map/UnitMovementAlgorithms.kt @@ -21,7 +21,7 @@ class UnitMovementAlgorithms(val unit:MapUnit) { if (from.roadStatus === RoadStatus.Railroad && to.roadStatus === RoadStatus.Railroad) return 1 / 10f + extraCost - if (from.hasRoad(civInfo) && to.hasRoad(civInfo)) + if (from.hasConnection(civInfo) && to.hasConnection(civInfo)) { if (unit.civInfo.tech.movementSpeedOnRoadsImproved) return 1 / 3f + extraCost else return 1 / 2f + extraCost diff --git a/core/src/com/unciv/models/ruleset/Building.kt b/core/src/com/unciv/models/ruleset/Building.kt index c7eceb3287..c59ccb68a8 100644 --- a/core/src/com/unciv/models/ruleset/Building.kt +++ b/core/src/com/unciv/models/ruleset/Building.kt @@ -180,7 +180,7 @@ class Building : NamedStats(), IConstruction{ stats.science = 50f if(uniques.contains("+5% Production for every Trade Route with a City-State in the empire")) - stats.production += 5*civInfo.citiesConnectedToCapital.count { it.civInfo.isCityState() } + stats.production += 5*civInfo.citiesConnectedToCapitalToMediums.count { it.key.civInfo.isCityState() } return stats } diff --git a/core/src/com/unciv/ui/worldscreen/WorldScreen.kt b/core/src/com/unciv/ui/worldscreen/WorldScreen.kt index b06730c33b..c5aa7b9e1a 100644 --- a/core/src/com/unciv/ui/worldscreen/WorldScreen.kt +++ b/core/src/com/unciv/ui/worldscreen/WorldScreen.kt @@ -247,7 +247,7 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() { "\n Click 'Create improvement' (above the unit table, bottom left)" + "\n > Choose the farm > \n Leave the worker there until it's finished" if(!completedTasks.contains("Create a trade route") - && viewingCiv.citiesConnectedToCapital.any { it.civInfo==viewingCiv }) + && viewingCiv.citiesConnectedToCapitalToMediums.any { it.key.civInfo==viewingCiv }) game.settings.addCompletedTutorialTask("Create a trade route") if(viewingCiv.cities.size>1 && !completedTasks.contains("Create a trade route")) return "Create a trade route!\nConstruct roads between your capital and another city" +