Merge 899735cf5fc40f7b9b3a77a4b02291019c0e44b7 into d51ef24c205b6b05330b3c4d7ce79c402db44447

This commit is contained in:
Ambeco 2025-09-18 20:13:58 -07:00 committed by GitHub
commit adb98489a4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 25 additions and 26 deletions

View File

@ -107,10 +107,6 @@ class RoadBetweenCitiesAutomation(val civInfo: Civilization, private val cachedF
if (roadsToBuildByCitiesCache.containsKey(city))
return roadsToBuildByCitiesCache[city]!!
// TODO: some better worker representative needs to be used here
val workerUnit = civInfo.gameInfo.ruleset.units.map { it.value }.firstOrNull { it.hasUnique(UniqueType.BuildImprovements) }
// This is a temporary unit only for AI purposes so it doesn't get a unique ID
?.newMapUnit(civInfo, Constants.NO_ID) ?: return listOf()
val roadToCapitalStatus = city.cityStats.getRoadTypeOfConnectionToCapital()
/** @return Rank 0, 1 or 2 of how important it is to build best available road to capital */
@ -144,8 +140,8 @@ class RoadBetweenCitiesAutomation(val civInfo: Civilization, private val cachedF
// Try to build a plan for the road to the city
// TODO: May return inconsistent paths across turns due to worker position, this makes it impossible to plan an exact road resulting in excessive roads built
val roadPath = if (civInfo.cities.indexOf(city) < civInfo.cities.indexOf(closeCity)) MapPathing.getRoadPath(workerUnit, city.getCenterTile(), closeCity.getCenterTile()) ?: continue
else MapPathing.getRoadPath(workerUnit, closeCity.getCenterTile(), city.getCenterTile()) ?: continue
val roadPath = if (civInfo.cities.indexOf(city) < civInfo.cities.indexOf(closeCity)) MapPathing.getRoadPath(civInfo, city.getCenterTile(), closeCity.getCenterTile()) ?: continue
else MapPathing.getRoadPath(civInfo, closeCity.getCenterTile(), city.getCenterTile()) ?: continue
val worstRoadStatus = getWorstRoadTypeInPath(roadPath)
if (worstRoadStatus == bestRoadAvailable) continue
@ -171,7 +167,7 @@ class RoadBetweenCitiesAutomation(val civInfo: Civilization, private val cachedF
// If and only if we have no roads to build to close-by cities then we check for a road to build to the capital
// The condition !city.isConnectedToCapital() is to avoid BFS for cities connected to capital with roads when railroads are unlocked
else if (roadPlans.isEmpty() && (roadToCapitalStatus < bestRoadAvailable) && !city.isConnectedToCapital()) {
val roadToCapital = getRoadToConnectCityToCapital(workerUnit, city)
val roadToCapital = getRoadToConnectCityToCapital(city)
if (roadToCapital != null) {
val worstRoadStatus = getWorstRoadTypeInPath(roadToCapital.second)
@ -243,10 +239,10 @@ class RoadBetweenCitiesAutomation(val civInfo: Civilization, private val cachedF
* @return a pair containing a list of tiles that resemble the road to build and the city that the road will connect to
*/
@Readonly
private fun getRoadToConnectCityToCapital(unit: MapUnit, city: City): Pair<City, List<Tile>>? {
private fun getRoadToConnectCityToCapital(city: City): Pair<City, List<Tile>>? {
if (tilesOfConnectedCities.isEmpty()) return null // In mods with no capital city indicator, there are no connected cities
val isCandidateTilePredicate: (Tile) -> Boolean = { it.isLand && unit.movement.canPassThrough(it) }
val isCandidateTilePredicate: (Tile) -> Boolean = { it.isLand && MapPathing.isValidRoadPathTile(city.civ, it) }
val toConnectTile = city.getCenterTile()
@LocalState val bfs: BFS = bfsCache[toConnectTile.position] ?: run {
val bfs = BFS(toConnectTile, isCandidateTilePredicate)

View File

@ -52,7 +52,7 @@ class RoadToAutomation(val civInfo: Civilization) {
// The path does not exist, create it
if (pathToDest == null) {
val foundPath: List<Tile>? = MapPathing.getRoadPath(unit, currentTile, destinationTile)
val foundPath: List<Tile>? = MapPathing.getRoadPath(unit.civ, unit.getTile(), destinationTile)
if (foundPath == null) {
Log.debug("WorkerAutomation: $unit -> connect road failed")
stopAndCleanAutomation(unit)

View File

@ -16,29 +16,29 @@ object MapPathing {
*/
@Suppress("UNUSED_PARAMETER") // While `from` is unused, this function should stay close to the signatures expected by the AStar and getPath `heuristic` parameter.
@Readonly
private fun roadPreferredMovementCost(unit: MapUnit, from: Tile, to: Tile): Float{
private fun roadPreferredMovementCost(civ: Civilization, from: Tile, to: Tile): Float{
// hasRoadConnection accounts for civs that treat jungle/forest as roads
// Ignore road over river penalties.
if ((to.hasRoadConnection(unit.civ, false) || to.hasRailroadConnection(false)))
if ((to.hasRoadConnection(civ, false) || to.hasRailroadConnection(false)))
return .5f
return 1f
}
@Readonly
fun isValidRoadPathTile(unit: MapUnit, tile: Tile): Boolean {
fun isValidRoadPathTile(civ: Civilization, tile: Tile): Boolean {
val roadImprovement = tile.ruleset.roadImprovement
val railRoadImprovement = tile.ruleset.railroadImprovement
if (tile.isWater) return false
if (tile.isImpassible()) return false
if (!unit.civ.hasExplored(tile)) return false
if (!tile.canCivPassThrough(unit.civ)) return false
if (!civ.hasExplored(tile)) return false
if (!tile.canCivPassThrough(civ)) return false
return tile.hasRoadConnection(unit.civ, false)
return tile.hasRoadConnection(civ, false)
|| tile.hasRailroadConnection(false)
|| roadImprovement != null && tile.improvementFunctions.canBuildImprovement(roadImprovement, unit.cache.state)
|| railRoadImprovement != null && tile.improvementFunctions.canBuildImprovement(railRoadImprovement, unit.cache.state)
|| roadImprovement != null && tile.improvementFunctions.canBuildImprovement(roadImprovement, civ.state)
|| railRoadImprovement != null && tile.improvementFunctions.canBuildImprovement(railRoadImprovement,civ.state)
}
/**
@ -46,14 +46,14 @@ object MapPathing {
*
* This function uses the A* search algorithm to find an optimal path for road construction between two specified tiles.
*
* @param unit The unit that will construct the road.
* @param civ The civlization that will construct the road.
* @param startTile The starting tile of the path.
* @param endTile The destination tile of the path.
* @return A sequence of tiles representing the path from startTile to endTile, or null if no valid path is found.
*/
@Readonly
fun getRoadPath(unit: MapUnit, startTile: Tile, endTile: Tile): List<Tile>?{
return getPath(unit,
fun getRoadPath(civ: Civilization, startTile: Tile, endTile: Tile): List<Tile>? {
return getConnection(civ,
startTile,
endTile,
::isValidRoadPathTile,
@ -112,13 +112,15 @@ object MapPathing {
fun getConnection(civ: Civilization,
startTile: Tile,
endTile: Tile,
predicate: (Civilization, Tile) -> Boolean
predicate: (Civilization, Tile) -> Boolean,
cost: (Civilization, Tile, Tile) -> Float = { _, _, _ -> 1f },
heuristic: (Civilization, Tile, Tile) -> Float = { _, from, to -> from.aerialDistanceTo(to).toFloat() }
): List<Tile>? {
val astar = AStar(
startTile,
predicate = { tile -> predicate(civ, tile) },
cost = { _, _ -> 1f },
heuristic = { from, to -> from.aerialDistanceTo(to).toFloat() }
cost = { from, to -> cost(civ, from, to) },
heuristic = { from, to -> heuristic(civ, from, to) }
)
while (true) {
if (astar.hasEnded()) {
@ -136,4 +138,5 @@ object MapPathing {
.reversed()
}
}
}

View File

@ -468,7 +468,7 @@ class WorldMapHolder(
selectedUnit.civ.hasExplored(tile)
if (validTile) {
val roadPath: List<Tile>? = MapPathing.getRoadPath(selectedUnit, selectedUnit.currentTile, tile)
val roadPath: List<Tile>? = MapPathing.getRoadPath(selectedUnit.civ, selectedUnit.getTile(), tile)
launchOnGLThread {
if (roadPath == null) { // give the regular tile overlays with no road connection
addTileOverlays(tile)

View File

@ -104,7 +104,7 @@ object WorldMapTileUpdater {
if (worldScreen.bottomUnitTable.selectedUnitIsConnectingRoad) {
if (unit.currentTile.ruleset.roadImprovement == null) return
val validTiles = unit.civ.gameInfo.tileMap.tileList.filter {
MapPathing.isValidRoadPathTile(unit, it)
MapPathing.isValidRoadPathTile(unit.civ, it)
}
val connectRoadTileOverlayColor = Color.RED
for (tile in validTiles) {