Trade routes (Railroad) fixes (#2013)

* Changes:
- worker automation to build Railroad overseas (currently they do not improve roads/build new)
- recognize harbor connection and add Railroad production bonus
- extracted and refactored connections to capital lookup

* CR fixes
This commit is contained in:
Robert 2020-02-25 14:09:53 +01:00 committed by GitHub
parent 5cc39b1020
commit e062947e7e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 151 additions and 99 deletions

View File

@ -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<String>) -> 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) {

View File

@ -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

View File

@ -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<CityInfo, MutableSet<String>>()
private var citiesToCheck = mutableListOf(civInfo.getCapital())
private lateinit var newCitiesToCheck: MutableList<CityInfo>
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<CityInfo, Set<String>> {
// 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)
}
}

View File

@ -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<CityInfo, ArrayList<String>>()
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<allCivCities.size){
val newCitiesToCheck = mutableListOf<CityInfo>()
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())

View File

@ -44,8 +44,8 @@ class CivilizationInfo {
@Transient var viewableTiles = setOf<TileInfo>()
@Transient var viewableInvisibleUnitsTiles = setOf<TileInfo>()
/** Contains cities from ALL civilizations connected by trade routes to the capital */
@Transient var citiesConnectedToCapital = listOf<CityInfo>()
/** Contains mapping of cities to travel mediums from ALL civilizations connected by trade routes to the capital */
@Transient var citiesConnectedToCapitalToMediums = mapOf<CityInfo, Set<String>>()
/** This is for performance since every movement calculation depends on this, see MapUnit comment */
@Transient var hasActiveGreatWall = false

View File

@ -49,4 +49,7 @@ class BFS(val startingPoint: TileInfo, val predicate : (TileInfo) -> Boolean){
}
return path
}
fun hasReachedTile(tile: TileInfo) =
tilesReached.containsKey(tile)
}

View File

@ -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
}

View File

@ -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

View File

@ -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
}

View File

@ -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" +