Earlier version with changes mostly to use Sequences (#1993)

* Fixes Issue #1697 by adding information to the special production constructions.

* Get rid of extra $ sign in the SpecialConstruction tooltips

* Major refactor to use Sequences instead of List to try to improve logic whenever getting a list of tiles at a distance.

* Get rid of extraneous parameter

* get rid of extra exception. slight refactor placeUnitNearTile for readability

* Fix bug of doing intersection instead of union

* Add an extra method to get tiles in distance range

* Update based on comments
This commit is contained in:
Kentalot 2020-02-23 04:22:50 -08:00 committed by GitHub
parent 718473fb31
commit 3c2cb01169
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 309 additions and 289 deletions

View File

@ -164,11 +164,12 @@ class GameInfo {
// Barbarians will only spawn in places that no one can see // Barbarians will only spawn in places that no one can see
val allViewableTiles = civilizations.filterNot { it.isBarbarian() } val allViewableTiles = civilizations.filterNot { it.isBarbarian() }
.flatMap { it.viewableTiles }.toHashSet() .flatMap { it.viewableTiles }.toHashSet()
val tilesWithin3ofExistingEncampment = existingEncampments.flatMap { it.getTilesInDistance(3) } val tilesWithin3ofExistingEncampment = existingEncampments.asSequence()
.flatMap { it.getTilesInDistance(3) }.toSet()
val viableTiles = tileMap.values.filter { val viableTiles = tileMap.values.filter {
!it.getBaseTerrain().impassable && it.isLand !it.getBaseTerrain().impassable && it.isLand
&& it.terrainFeature==null && it.terrainFeature == null
&& it.naturalWonder==null && it.naturalWonder == null
&& it !in tilesWithin3ofExistingEncampment && it !in tilesWithin3ofExistingEncampment
&& it !in allViewableTiles && it !in allViewableTiles
} }

View File

@ -14,14 +14,6 @@ import kotlin.math.sqrt
class Automation { class Automation {
internal fun rankTile(tile: TileInfo?, civInfo: CivilizationInfo): Float {
if (tile == null) return 0f
val stats = tile.getTileStats(null, civInfo)
var rank = rankStatsValue(stats, civInfo)
if (tile.improvement == null) rank += 0.5f // improvement potential!
if (tile.hasViewableResource(civInfo)) rank += 1.0f
return rank
}
fun rankTileForCityWork(tile:TileInfo, city: CityInfo, foodWeight: Float = 1f): Float { fun rankTileForCityWork(tile:TileInfo, city: CityInfo, foodWeight: Float = 1f): Float {
val stats = tile.getTileStats(city, city.civInfo) val stats = tile.getTileStats(city, city.civInfo)
@ -63,20 +55,6 @@ class Automation {
return rank return rank
} }
fun rankStatsValue(stats: Stats, civInfo: CivilizationInfo): Float {
var rank = 0.0f
if (stats.food <= 2) rank += (stats.food * 1.2f) //food get more value to keep city growing
else rank += (2.4f + (stats.food - 2) / 2) // 1.2 point for each food up to 2, from there on half a point
if (civInfo.gold < 0 && civInfo.statsForNextTurn.gold <= 0) rank += stats.gold
else rank += stats.gold / 3 // 3 gold is much worse than 2 production
rank += stats.production
rank += stats.science
rank += stats.culture
return rank
}
fun trainMilitaryUnit(city: CityInfo) { fun trainMilitaryUnit(city: CityInfo) {
val name = chooseMilitaryUnit(city) val name = chooseMilitaryUnit(city)
city.cityConstructions.currentConstruction = name city.cityConstructions.currentConstruction = name
@ -128,6 +106,32 @@ class Automation {
} }
} }
companion object {
internal fun rankTile(tile: TileInfo?, civInfo: CivilizationInfo): Float {
if (tile == null) return 0f
val stats = tile.getTileStats(null, civInfo)
var rank = rankStatsValue(stats, civInfo)
if (tile.improvement == null) rank += 0.5f // improvement potential!
if (tile.hasViewableResource(civInfo)) rank += 1.0f
return rank
}
@JvmStatic
fun rankStatsValue(stats: Stats, civInfo: CivilizationInfo): Float {
var rank = 0.0f
if (stats.food <= 2) rank += (stats.food * 1.2f) //food get more value to keep city growing
else rank += (2.4f + (stats.food - 2) / 2) // 1.2 point for each food up to 2, from there on half a point
if (civInfo.gold < 0 && civInfo.statsForNextTurn.gold <= 0) rank += stats.gold
else rank += stats.gold / 3 // 3 gold is much worse than 2 production
rank += stats.production
rank += stats.science
rank += stats.culture
return rank
}
}
} }
enum class ThreatLevel{ enum class ThreatLevel{

View File

@ -86,7 +86,7 @@ class BarbarianAutomation(val civInfo: CivilizationInfo) {
if (unit.health < 100 && UnitAutomation().tryHealUnit(unit, unitDistanceToTiles)) return if (unit.health < 100 && UnitAutomation().tryHealUnit(unit, unitDistanceToTiles)) return
// 6 - wander // 6 - wander
UnitAutomation().wander(unit, unitDistanceToTiles) UnitAutomation.wander(unit, unitDistanceToTiles)
} }
private fun automateScout(unit: MapUnit) { private fun automateScout(unit: MapUnit) {
@ -114,7 +114,7 @@ class BarbarianAutomation(val civInfo: CivilizationInfo) {
if (unit.health < 100 && UnitAutomation().tryHealUnit(unit, unitDistanceToTiles)) return if (unit.health < 100 && UnitAutomation().tryHealUnit(unit, unitDistanceToTiles)) return
// 5 - wander // 5 - wander
UnitAutomation().wander(unit, unitDistanceToTiles) UnitAutomation.wander(unit, unitDistanceToTiles)
} }
private fun findFurthestTileCanMoveTo( private fun findFurthestTileCanMoveTo(

View File

@ -63,9 +63,10 @@ class BattleHelper {
val tilesInAttackRange = val tilesInAttackRange =
if (unit.hasUnique("Ranged attacks may be performed over obstacles") || unit.type.isAirUnit()) if (unit.hasUnique("Ranged attacks may be performed over obstacles") || unit.type.isAirUnit())
reachableTile.getTilesInDistance(rangeOfAttack) reachableTile.getTilesInDistance(rangeOfAttack)
else reachableTile.getViewableTiles(rangeOfAttack, unit.type.isWaterUnit()) else reachableTile.getViewableTilesList(rangeOfAttack, unit.type.isWaterUnit())
.asSequence()
attackableTiles += tilesInAttackRange.asSequence().filter { it in tilesWithEnemies } attackableTiles += tilesInAttackRange.filter { it in tilesWithEnemies }
.map { AttackableTile(reachableTile, it) } .map { AttackableTile(reachableTile, it) }
} }
return attackableTiles return attackableTiles

View File

@ -37,7 +37,7 @@ class SpecificUnitAutomation{
return createImprovementAction() return createImprovementAction()
} }
} }
else UnitAutomation().tryExplore(unit, unit.movement.getDistanceToTiles()) else UnitAutomation.tryExplore(unit, unit.movement.getDistanceToTiles())
} }
fun automateGreatGeneral(unit: MapUnit){ fun automateGreatGeneral(unit: MapUnit){
@ -72,14 +72,13 @@ class SpecificUnitAutomation{
fun rankTileAsCityCenter(tileInfo: TileInfo, nearbyTileRankings: Map<TileInfo, Float>, fun rankTileAsCityCenter(tileInfo: TileInfo, nearbyTileRankings: Map<TileInfo, Float>,
luxuryResourcesInCivArea: Sequence<TileResource>): Float { luxuryResourcesInCivArea: Sequence<TileResource>): Float {
val bestTilesFromOuterLayer = tileInfo.getTilesAtDistance(2) val bestTilesFromOuterLayer = tileInfo.getTilesAtDistance(2)
.asSequence()
.sortedByDescending { nearbyTileRankings[it] }.take(2) .sortedByDescending { nearbyTileRankings[it] }.take(2)
.toList() .toList()
val top5Tiles = tileInfo.neighbors.union(bestTilesFromOuterLayer) val top5Tiles = tileInfo.neighbors.union(bestTilesFromOuterLayer)
.asSequence() .asSequence()
.sortedByDescending { nearbyTileRankings[it] } .sortedByDescending { nearbyTileRankings[it] }
.take(5) .take(5)
var rank = top5Tiles.map { nearbyTileRankings[it]!! }.sum() var rank = top5Tiles.map { nearbyTileRankings.getValue(it) }.sum()
if (tileInfo.isCoastalTile()) rank += 5 if (tileInfo.isCoastalTile()) rank += 5
val luxuryResourcesInCityArea = tileInfo.getTilesAtDistance(2).filter { it.resource!=null } val luxuryResourcesInCityArea = tileInfo.getTilesAtDistance(2).filter { it.resource!=null }
@ -94,9 +93,9 @@ class SpecificUnitAutomation{
fun automateSettlerActions(unit: MapUnit) { fun automateSettlerActions(unit: MapUnit) {
if(unit.getTile().militaryUnit==null) return // Don't move until you're accompanied by a military unit if (unit.getTile().militaryUnit == null) return // Don't move until you're accompanied by a military unit
val tilesNearCities = unit.civInfo.gameInfo.getCities() val tilesNearCities = unit.civInfo.gameInfo.getCities().asSequence()
.flatMap { .flatMap {
val distanceAwayFromCity = val distanceAwayFromCity =
if (unit.civInfo.knows(it.civInfo) if (unit.civInfo.knows(it.civInfo)
@ -106,34 +105,36 @@ class SpecificUnitAutomation{
else 3 else 3
it.getCenterTile().getTilesInDistance(distanceAwayFromCity) it.getCenterTile().getTilesInDistance(distanceAwayFromCity)
} }
.toHashSet() .toSet()
// This is to improve performance - instead of ranking each tile in the area up to 19 times, do it once. // This is to improve performance - instead of ranking each tile in the area up to 19 times, do it once.
val nearbyTileRankings = unit.getTile().getTilesInDistance(7) val nearbyTileRankings = unit.getTile().getTilesInDistance(7)
.associateBy ( {it},{ Automation().rankTile(it,unit.civInfo) }) .associateBy({ it }, { Automation.rankTile(it, unit.civInfo) })
val possibleCityLocations = unit.getTile().getTilesInDistance(5) val possibleCityLocations = unit.getTile().getTilesInDistance(5)
.filter { val tileOwner=it.getOwner() .filter {
it.isLand && (tileOwner==null || tileOwner==unit.civInfo) && // don't allow settler to settle inside other civ's territory val tileOwner = it.getOwner()
(unit.movement.canMoveTo(it) || unit.currentTile==it) it.isLand && (tileOwner == null || tileOwner == unit.civInfo) && // don't allow settler to settle inside other civ's territory
&& it !in tilesNearCities } (unit.movement.canMoveTo(it) || unit.currentTile == it)
&& it !in tilesNearCities
}
val luxuryResourcesInCivArea = unit.civInfo.cities.asSequence() val luxuryResourcesInCivArea = unit.civInfo.cities.asSequence()
.flatMap { it.getTiles().asSequence() }.filter { it.resource!=null } .flatMap { it.getTiles().asSequence() }.filter { it.resource != null }
.map { it.getTileResource() }.filter { it.resourceType==ResourceType.Luxury } .map { it.getTileResource() }.filter { it.resourceType == ResourceType.Luxury }
.distinct() .distinct()
val bestCityLocation: TileInfo? = possibleCityLocations val bestCityLocation: TileInfo? = possibleCityLocations
.asSequence()
.sortedByDescending { rankTileAsCityCenter(it, nearbyTileRankings, luxuryResourcesInCivArea) } .sortedByDescending { rankTileAsCityCenter(it, nearbyTileRankings, luxuryResourcesInCivArea) }
.firstOrNull { unit.movement.canReach(it) } .firstOrNull { unit.movement.canReach(it) }
if(bestCityLocation==null) { // We got a badass over here, all tiles within 5 are taken? Screw it, random walk. if (bestCityLocation == null) { // We got a badass over here, all tiles within 5 are taken? Screw it, random walk.
if (UnitAutomation().tryExplore(unit, unit.movement.getDistanceToTiles())) return // try to find new areas val unitDistanceToTiles = unit.movement.getDistanceToTiles()
UnitAutomation().wander(unit, unit.movement.getDistanceToTiles()) // go around aimlessly if (UnitAutomation.tryExplore(unit, unitDistanceToTiles)) return // try to find new areas
UnitAutomation.wander(unit, unitDistanceToTiles) // go around aimlessly
return return
} }
if(bestCityLocation.getTilesInDistance(3).any { it.isCityCenter() }) if (bestCityLocation.getTilesInDistance(3).any { it.isCityCenter() })
throw Exception("City within distance") throw Exception("City within distance")
if (unit.getTile() == bestCityLocation) if (unit.getTile() == bestCityLocation)
@ -169,9 +170,9 @@ class SpecificUnitAutomation{
&& it.isLand && it.isLand
&& !it.isCityCenter() && !it.isCityCenter()
&& it.resource==null } && it.resource==null }
.sortedByDescending { Automation().rankTile(it,unit.civInfo) }.toList() .sortedByDescending { Automation.rankTile(it,unit.civInfo) }.toList()
val chosenTile = tiles.firstOrNull { unit.movement.canReach(it) } val chosenTile = tiles.firstOrNull { unit.movement.canReach(it) } ?: continue // to another city
if(chosenTile==null) continue // to another city
unit.movement.headTowards(chosenTile) unit.movement.headTowards(chosenTile)
if(unit.currentTile==chosenTile && unit.currentMovement > 0) if(unit.currentTile==chosenTile && unit.currentMovement > 0)
@ -185,35 +186,41 @@ class SpecificUnitAutomation{
fun automateFighter(unit: MapUnit) { fun automateFighter(unit: MapUnit) {
val tilesInRange = unit.currentTile.getTilesInDistance(unit.getRange()) val tilesInRange = unit.currentTile.getTilesInDistance(unit.getRange())
val enemyAirUnitsInRange = tilesInRange val enemyAirUnitsInRange = tilesInRange
.flatMap { it.airUnits }.filter { it.civInfo.isAtWarWith(unit.civInfo) } .flatMap { it.airUnits.asSequence() }.filter { it.civInfo.isAtWarWith(unit.civInfo) }
if(enemyAirUnitsInRange.isNotEmpty()) return // we need to be on standby in case they attack if (enemyAirUnitsInRange.any()) return // we need to be on standby in case they attack
if(battleHelper.tryAttackNearbyEnemy(unit)) return if (battleHelper.tryAttackNearbyEnemy(unit)) return
// TODO Implement consideration for landing on aircraft carrier // TODO Implement consideration for landing on aircraft carrier
val immediatelyReachableCities = tilesInRange val immediatelyReachableCities = tilesInRange
.filter { it.isCityCenter() && it.getOwner()==unit.civInfo && unit.movement.canMoveTo(it)} .filter { it.isCityCenter() && it.getOwner() == unit.civInfo && unit.movement.canMoveTo(it) }
for(city in immediatelyReachableCities){ for (city in immediatelyReachableCities) {
if(city.getTilesInDistance(unit.getRange()) if (city.getTilesInDistance(unit.getRange())
.any { battleHelper.containsAttackableEnemy(it,MapUnitCombatant(unit)) }) { .any { battleHelper.containsAttackableEnemy(it, MapUnitCombatant(unit)) }) {
unit.movement.moveToTile(city) unit.movement.moveToTile(city)
return return
} }
} }
val pathsToCities = unit.movement.getArialPathsToCities() val pathsToCities = unit.movement.getArialPathsToCities()
if(pathsToCities.isEmpty()) return // can't actually move anywhere else if (pathsToCities.isEmpty()) return // can't actually move anywhere else
val citiesByNearbyAirUnits = pathsToCities.keys val citiesByNearbyAirUnits = pathsToCities.keys
.groupBy { it.getTilesInDistance(unit.getRange()) .groupBy { key ->
.count{it.airUnits.size>0 && it.airUnits.first().civInfo.isAtWarWith(unit.civInfo)} } key.getTilesInDistance(unit.getRange())
.count {
val firstAirUnit = it.airUnits.firstOrNull()
firstAirUnit != null && firstAirUnit.civInfo.isAtWarWith(unit.civInfo)
}
}
if(citiesByNearbyAirUnits.keys.any { it!=0 }){ if (citiesByNearbyAirUnits.keys.any { it != 0 }) {
val citiesWithMostNeedOfAirUnits = citiesByNearbyAirUnits.maxBy { it.key }!!.value val citiesWithMostNeedOfAirUnits = citiesByNearbyAirUnits.maxBy { it.key }!!.value
val chosenCity = citiesWithMostNeedOfAirUnits.minBy { pathsToCities[it]!!.size }!! // city with min path = least turns to get there //todo: maybe groupby size and choose highest priority within the same size turns
val firstStepInPath = pathsToCities[chosenCity]!!.first() val chosenCity = citiesWithMostNeedOfAirUnits.minBy { pathsToCities.getValue(it).size }!! // city with min path = least turns to get there
val firstStepInPath = pathsToCities.getValue(chosenCity).first()
unit.movement.moveToTile(firstStepInPath) unit.movement.moveToTile(firstStepInPath)
return return
} }
@ -255,6 +262,8 @@ class SpecificUnitAutomation{
} }
if (citiesThatCanAttackFrom.isEmpty()) return if (citiesThatCanAttackFrom.isEmpty()) return
//todo: this logic looks similar to some parts of automateFighter, maybe pull out common code
//todo: maybe groupby size and choose highest priority within the same size turns
val closestCityThatCanAttackFrom = citiesThatCanAttackFrom.minBy { pathsToCities[it]!!.size }!! val closestCityThatCanAttackFrom = citiesThatCanAttackFrom.minBy { pathsToCities[it]!!.size }!!
val firstStepInPath = pathsToCities[closestCityThatCanAttackFrom]!!.first() val firstStepInPath = pathsToCities[closestCityThatCanAttackFrom]!!.first()
airUnit.movement.moveToTile(firstStepInPath) airUnit.movement.moveToTile(firstStepInPath)

View File

@ -3,10 +3,7 @@ package com.unciv.logic.automation
import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.graphics.Color
import com.unciv.Constants import com.unciv.Constants
import com.unciv.UncivGame import com.unciv.UncivGame
import com.unciv.logic.battle.Battle import com.unciv.logic.battle.*
import com.unciv.logic.battle.BattleDamage
import com.unciv.logic.battle.CityCombatant
import com.unciv.logic.battle.MapUnitCombatant
import com.unciv.logic.city.CityInfo import com.unciv.logic.city.CityInfo
import com.unciv.logic.civilization.GreatPersonManager import com.unciv.logic.civilization.GreatPersonManager
import com.unciv.logic.civilization.diplomacy.DiplomaticStatus import com.unciv.logic.civilization.diplomacy.DiplomaticStatus
@ -23,6 +20,41 @@ class UnitAutomation {
companion object { companion object {
const val CLOSE_ENEMY_TILES_AWAY_LIMIT = 5 const val CLOSE_ENEMY_TILES_AWAY_LIMIT = 5
const val CLOSE_ENEMY_TURNS_AWAY_LIMIT = 3f const val CLOSE_ENEMY_TURNS_AWAY_LIMIT = 3f
internal fun tryExplore(unit: MapUnit, unitDistanceToTiles: PathsToTilesWithinTurn): Boolean {
if (tryGoToRuin(unit, unitDistanceToTiles) && unit.currentMovement == 0f) return true
for (tile in unit.currentTile.getTilesInDistance(10))
if (unit.movement.canMoveTo(tile) && tile.position !in unit.civInfo.exploredTiles
&& unit.movement.canReach(tile)
&& (tile.getOwner() == null || !tile.getOwner()!!.isCityState())) {
unit.movement.headTowards(tile)
return true
}
return false
}
private fun tryGoToRuin(unit: MapUnit, unitDistanceToTiles: PathsToTilesWithinTurn): Boolean {
if (!unit.civInfo.isMajorCiv()) return false // barbs don't have anything to do in ruins
val tileWithRuin = unitDistanceToTiles.keys
.firstOrNull {
it.improvement == Constants.ancientRuins && unit.movement.canMoveTo(it)
}
if (tileWithRuin == null)
return false
unit.movement.moveToTile(tileWithRuin)
return true
}
@JvmStatic
fun wander(unit: MapUnit, unitDistanceToTiles: PathsToTilesWithinTurn) {
val reachableTiles = unitDistanceToTiles
.filter { unit.movement.canMoveTo(it.key) && unit.movement.canReach(it.key) }
val reachableTilesMaxWalkingDistance = reachableTiles.filter { it.value.totalDistance == unit.currentMovement }
if (reachableTilesMaxWalkingDistance.any()) unit.movement.moveToTile(reachableTilesMaxWalkingDistance.toList().random().first)
else if (reachableTiles.any()) unit.movement.moveToTile(reachableTiles.keys.random())
}
} }
private val battleHelper = BattleHelper() private val battleHelper = BattleHelper()
@ -162,10 +194,9 @@ class UnitAutomation {
return true return true
} }
fun getBombardTargets(city: CityInfo): List<TileInfo> { fun getBombardTargets(city: CityInfo): Sequence<TileInfo> =
return city.getCenterTile().getViewableTiles(city.range, true) city.getCenterTile().getTilesInDistance(city.range)
.filter { battleHelper.containsAttackableEnemy(it, CityCombatant(city)) } .filter { battleHelper.containsAttackableEnemy(it, CityCombatant(city)) }
}
/** Move towards the closest attackable enemy of the [unit]. /** Move towards the closest attackable enemy of the [unit].
* *
@ -180,7 +211,7 @@ class UnitAutomation {
var closeEnemies = battleHelper.getAttackableEnemies( var closeEnemies = battleHelper.getAttackableEnemies(
unit, unit,
unitDistanceToTiles, unitDistanceToTiles,
tilesToCheck = unit.getTile().getTilesInDistance(CLOSE_ENEMY_TILES_AWAY_LIMIT) tilesToCheck = unit.getTile().getTilesInDistance(CLOSE_ENEMY_TILES_AWAY_LIMIT).toList()
).filter { ).filter {
// Ignore units that would 1-shot you if you attacked // Ignore units that would 1-shot you if you attacked
BattleDamage().calculateDamageToAttacker(MapUnitCombatant(unit), BattleDamage().calculateDamageToAttacker(MapUnitCombatant(unit),
@ -246,16 +277,13 @@ class UnitAutomation {
if (closestReachableEnemyCity != null) { if (closestReachableEnemyCity != null) {
val unitDistanceToTiles = unit.movement.getDistanceToTiles() val unitDistanceToTiles = unit.movement.getDistanceToTiles()
val tilesInBombardRange = closestReachableEnemyCity.getTilesInDistance(2) val tilesInBombardRange = closestReachableEnemyCity.getTilesInDistance(2).toSet()
val reachableTilesNotInBombardRange = unitDistanceToTiles.keys.filter { it !in tilesInBombardRange }
val suitableGatheringGroundTiles = closestReachableEnemyCity.getTilesAtDistance(4)
.union(closestReachableEnemyCity.getTilesAtDistance(3))
.filter { it.isLand }
// don't head straight to the city, try to head to landing grounds - // don't head straight to the city, try to head to landing grounds -
// this is against tha AI's brilliant plan of having everyone embarked and attacking via sea when unnecessary. // this is against tha AI's brilliant plan of having everyone embarked and attacking via sea when unnecessary.
val tileToHeadTo = suitableGatheringGroundTiles val tileToHeadTo = closestReachableEnemyCity.getTilesInDistanceRange(3..4)
.filter { it.isLand }
.sortedBy { it.arialDistanceTo(unit.currentTile) } .sortedBy { it.arialDistanceTo(unit.currentTile) }
.firstOrNull { unit.movement.canReach(it) } ?: closestReachableEnemyCity .firstOrNull { unit.movement.canReach(it) } ?: closestReachableEnemyCity
@ -263,17 +291,21 @@ class UnitAutomation {
unit.movement.headTowards(tileToHeadTo) unit.movement.headTowards(tileToHeadTo)
else { else {
if (unit.getRange() > 2) { // should never be in a bombardable position if (unit.getRange() > 2) { // should never be in a bombardable position
val tilesCanAttackFromButNotInBombardRange = val tileToMoveTo =
reachableTilesNotInBombardRange.filter { it.arialDistanceTo(closestReachableEnemyCity) <= unit.getRange() } unitDistanceToTiles.asSequence()
.filter { it.key !in tilesInBombardRange
&& it.key.arialDistanceTo(closestReachableEnemyCity) <=
unit.getRange() }
.minBy { it.value.totalDistance }?.key
// move into position far away enough that the bombard doesn't hurt // move into position far away enough that the bombard doesn't hurt
if (tilesCanAttackFromButNotInBombardRange.any()) if (tileToMoveTo != null)
unit.movement.headTowards(tilesCanAttackFromButNotInBombardRange.minBy { unitDistanceToTiles[it]!!.totalDistance }!!) unit.movement.headTowards(tileToMoveTo)
} else { } else {
// calculate total damage of units in surrounding 4-spaces from enemy city (so we can attack a city from 2 directions at once) // calculate total damage of units in surrounding 4-spaces from enemy city (so we can attack a city from 2 directions at once)
val militaryUnitsAroundEnemyCity = closestReachableEnemyCity.getTilesInDistance(3) val militaryUnitsAroundEnemyCity = closestReachableEnemyCity.getTilesInDistance(3)
.filter { it.militaryUnit != null && it.militaryUnit!!.civInfo == unit.civInfo } .filter { it.militaryUnit != null && it.militaryUnit!!.civInfo == unit.civInfo }
.map { it.militaryUnit!! } .map { it.militaryUnit }.filterNotNull()
var totalAttackOnCityPerTurn = -20 // cities heal 20 per turn, so anything below that its useless var totalAttackOnCityPerTurn = -20 // cities heal 20 per turn, so anything below that its useless
val enemyCityCombatant = CityCombatant(closestReachableEnemyCity.getCity()!!) val enemyCityCombatant = CityCombatant(closestReachableEnemyCity.getCity()!!)
for (militaryUnit in militaryUnitsAroundEnemyCity) { for (militaryUnit in militaryUnitsAroundEnemyCity) {
@ -290,28 +322,29 @@ class UnitAutomation {
} }
fun tryBombardEnemy(city: CityInfo): Boolean { fun tryBombardEnemy(city: CityInfo): Boolean {
if (!city.attackedThisTurn) { return when {
val target = chooseBombardTarget(city) city.attackedThisTurn -> false
if (target == null) return false else -> {
val enemy = Battle.getMapCombatantOfTile(target)!! val enemy = chooseBombardTarget(city) ?: return false
Battle.attack(CityCombatant(city), enemy) Battle.attack(CityCombatant(city), enemy)
return true true
}
} }
return false
} }
private fun chooseBombardTarget(city: CityInfo): TileInfo? { private fun chooseBombardTarget(city: CityInfo): ICombatant? {
var targets = getBombardTargets(city) val mappedTargets = getBombardTargets(city).map { Battle.getMapCombatantOfTile(it)!! }
if (targets.isEmpty()) return null .filter {
val siegeUnits = targets val unitType = it.getUnitType()
.filter { Battle.getMapCombatantOfTile(it)!!.getUnitType() == UnitType.Siege } unitType == UnitType.Siege || unitType.isRanged()
if (siegeUnits.any()) targets = siegeUnits
else {
val rangedUnits = targets
.filter { Battle.getMapCombatantOfTile(it)!!.getUnitType().isRanged() }
if (rangedUnits.any()) targets = rangedUnits
} }
return targets.minBy { Battle.getMapCombatantOfTile(it)!!.getHealth() } .groupByTo(LinkedHashMap()) { it.getUnitType() }
var targets = mappedTargets[UnitType.Siege]?.asSequence()
if (targets == null)
targets = mappedTargets.values.asSequence().flatMap { it.asSequence() }
return targets.minBy { it.getHealth() }
} }
private fun tryGarrisoningUnit(unit: MapUnit): Boolean { private fun tryGarrisoningUnit(unit: MapUnit): Boolean {
@ -352,28 +385,6 @@ class UnitAutomation {
return true return true
} }
private fun tryGoToRuin(unit: MapUnit, unitDistanceToTiles: PathsToTilesWithinTurn): Boolean {
if (!unit.civInfo.isMajorCiv()) return false // barbs don't have anything to do in ruins
val tileWithRuin = unitDistanceToTiles.keys
.firstOrNull { it.improvement == Constants.ancientRuins && unit.movement.canMoveTo(it) }
if (tileWithRuin == null) return false
unit.movement.moveToTile(tileWithRuin)
return true
}
internal fun tryExplore(unit: MapUnit, unitDistanceToTiles: PathsToTilesWithinTurn): Boolean {
if (tryGoToRuin(unit, unitDistanceToTiles) && unit.currentMovement == 0f) return true
for (tile in unit.currentTile.getTilesInDistance(10))
if (unit.movement.canMoveTo(tile) && tile.position !in unit.civInfo.exploredTiles
&& unit.movement.canReach(tile)
&& (tile.getOwner()==null || !tile.getOwner()!!.isCityState())) {
unit.movement.headTowards(tile)
return true
}
return false
}
/** This is what a unit with the 'explore' action does. /** This is what a unit with the 'explore' action does.
It also explores, but also has other functions, like healing if necessary. */ It also explores, but also has other functions, like healing if necessary. */
fun automatedExplore(unit: MapUnit) { fun automatedExplore(unit: MapUnit) {
@ -383,13 +394,4 @@ class UnitAutomation {
if (tryExplore(unit, unit.movement.getDistanceToTiles())) return if (tryExplore(unit, unit.movement.getDistanceToTiles())) return
unit.civInfo.addNotification("[${unit.name}] finished exploring.", unit.currentTile.position, Color.GRAY) unit.civInfo.addNotification("[${unit.name}] finished exploring.", unit.currentTile.position, Color.GRAY)
} }
fun wander(unit: MapUnit, unitDistanceToTiles: PathsToTilesWithinTurn) {
val reachableTiles = unitDistanceToTiles
.filter { unit.movement.canMoveTo(it.key) && unit.movement.canReach(it.key) }
val reachableTilesMaxWalkingDistance = reachableTiles.filter { it.value.totalDistance == unit.currentMovement }
if (reachableTilesMaxWalkingDistance.any()) unit.movement.moveToTile(reachableTilesMaxWalkingDistance.toList().random().first)
else if (reachableTiles.any()) unit.movement.moveToTile(reachableTiles.toList().random().first)
}
} }

View File

@ -14,8 +14,7 @@ class WorkerAutomation(val unit: MapUnit) {
fun automateWorkerAction() { fun automateWorkerAction() {
val enemyUnitsInWalkingDistance = unit.movement.getDistanceToTiles().keys val enemyUnitsInWalkingDistance = unit.movement.getDistanceToTiles().keys
.filter { .filter {
it.militaryUnit != null && it.militaryUnit!!.civInfo != unit.civInfo it.militaryUnit != null && it.militaryUnit!!.civInfo.isAtWarWith(unit.civInfo)
&& unit.civInfo.isAtWarWith(it.militaryUnit!!.civInfo)
} }
if (enemyUnitsInWalkingDistance.isNotEmpty()) return // Don't you dare move. if (enemyUnitsInWalkingDistance.isNotEmpty()) return // Don't you dare move.
@ -118,24 +117,25 @@ class WorkerAutomation(val unit: MapUnit) {
* Returns the current tile if no tile to work was found * Returns the current tile if no tile to work was found
*/ */
private fun findTileToWork(): TileInfo { private fun findTileToWork(): TileInfo {
val currentTile=unit.getTile() val currentTile = unit.getTile()
val workableTiles = currentTile.getTilesInDistance(4) val workableTiles = currentTile.getTilesInDistance(4)
.filter { .filter {
(it.civilianUnit== null || it == currentTile) (it.civilianUnit == null || it == currentTile)
&& tileCanBeImproved(it, unit.civInfo) } && tileCanBeImproved(it, unit.civInfo)
.sortedByDescending { getPriority(it, unit.civInfo) }.toMutableList() }
.sortedByDescending { getPriority(it, unit.civInfo) }
// the tile needs to be actually reachable - more difficult than it seems, // the tile needs to be actually reachable - more difficult than it seems,
// which is why we DON'T calculate this for every possible tile in the radius, // which is why we DON'T calculate this for every possible tile in the radius,
// but only for the tile that's about to be chosen. // but only for the tile that's about to be chosen.
val selectedTile = workableTiles.firstOrNull{unit.movement.canReach(it) } val selectedTile = workableTiles.firstOrNull { unit.movement.canReach(it) }
if (selectedTile != null return if (selectedTile != null
&& getPriority(selectedTile, unit.civInfo)>1 && getPriority(selectedTile, unit.civInfo) > 1
&& (!workableTiles.contains(currentTile) && (!workableTiles.contains(currentTile)
|| getPriority(selectedTile, unit.civInfo) > getPriority(currentTile, unit.civInfo))) || getPriority(selectedTile, unit.civInfo) > getPriority(currentTile, unit.civInfo)))
return selectedTile selectedTile
else return currentTile else currentTile
} }
private fun tileCanBeImproved(tile: TileInfo, civInfo: CivilizationInfo): Boolean { private fun tileCanBeImproved(tile: TileInfo, civInfo: CivilizationInfo): Boolean {

View File

@ -62,9 +62,10 @@ class CityExpansionManager {
fun chooseNewTileToOwn(): TileInfo? { fun chooseNewTileToOwn(): TileInfo? {
for (i in 2..5) { for (i in 2..5) {
val tiles = cityInfo.getCenterTile().getTilesInDistance(i) val tiles = cityInfo.getCenterTile().getTilesInDistance(i)
.filter {it.getOwner() == null && it.neighbors.any { tile->tile.getOwner()==cityInfo.civInfo }} .filter { it.getOwner() == null
if (tiles.isEmpty()) continue && it.neighbors.any { tile -> tile.getOwner() == cityInfo.civInfo } }
val chosenTile = tiles.maxBy { Automation().rankTile(it,cityInfo.civInfo) } val chosenTile = tiles.maxBy { Automation.rankTile(it, cityInfo.civInfo) }
if (chosenTile != null)
return chosenTile return chosenTile
} }
return null return null
@ -72,7 +73,7 @@ class CityExpansionManager {
//region state-changing functions //region state-changing functions
fun reset() { fun reset() {
for(tile in cityInfo.getTiles()) for (tile in cityInfo.getTiles())
relinquishOwnership(tile) relinquishOwnership(tile)
// The only way to create a city inside an owned tile is if it's in your territory // The only way to create a city inside an owned tile is if it's in your territory
@ -80,10 +81,9 @@ class CityExpansionManager {
// It becomes an invisible city and weird shit starts happening // It becomes an invisible city and weird shit starts happening
takeOwnership(cityInfo.getCenterTile()) takeOwnership(cityInfo.getCenterTile())
cityInfo.getCenterTile().getTilesInDistance(1) for (tile in cityInfo.getCenterTile().getTilesInDistance(1)
.filter { it.getCity()==null } // can't take ownership of owned tiles (by other cities) .filter { it.getCity() == null }) // can't take ownership of owned tiles (by other cities)
.forEach { takeOwnership(it) } takeOwnership(tile)
} }
private fun addNewTileWithCulture() { private fun addNewTileWithCulture() {

View File

@ -277,7 +277,7 @@ class CityInfo {
fun setTransients() { fun setTransients() {
tileMap = civInfo.gameInfo.tileMap tileMap = civInfo.gameInfo.tileMap
centerTileInfo = tileMap[location] centerTileInfo = tileMap[location]
tilesInRange = getCenterTile().getTilesInDistance( 3).toHashSet() tilesInRange = getCenterTile().getTilesInDistance(3).toHashSet()
population.cityInfo = this population.cityInfo = this
expansion.cityInfo = this expansion.cityInfo = this
expansion.setTransients() expansion.setTransients()

View File

@ -292,8 +292,9 @@ class CivilizationInfo {
} }
fun isAtWarWith(otherCiv:CivilizationInfo): Boolean { fun isAtWarWith(otherCiv:CivilizationInfo): Boolean {
if(otherCiv.isBarbarian() || isBarbarian()) return true if (otherCiv.civName == civName) return false // never at war with itself
if(!diplomacy.containsKey(otherCiv.civName)) // not encountered yet if (otherCiv.isBarbarian() || isBarbarian()) return true
if (!diplomacy.containsKey(otherCiv.civName)) // not encountered yet
return false return false
return getDiplomacyManager(otherCiv).diplomaticStatus == DiplomaticStatus.War return getDiplomacyManager(otherCiv).diplomaticStatus == DiplomaticStatus.War
} }

View File

@ -141,10 +141,10 @@ class MapUnit {
} }
fun updateVisibleTiles() { fun updateVisibleTiles() {
if(type.isAirUnit()){ if(type.isAirUnit()) {
if(hasUnique("6 tiles in every direction always visible")) viewableTiles = if (hasUnique("6 tiles in every direction always visible"))
viewableTiles = getTile().getTilesInDistance(6) // it's that simple getTile().getTilesInDistance(6).toList() // it's that simple
else viewableTiles = listOf() // bomber units don't do recon else listOf() // bomber units don't do recon
} }
else { else {
var visibilityRange = 2 var visibilityRange = 2
@ -161,7 +161,7 @@ class MapUnit {
val tile = getTile() val tile = getTile()
if (tile.baseTerrain == Constants.hill && type.isLandUnit()) visibilityRange += 1 if (tile.baseTerrain == Constants.hill && type.isLandUnit()) visibilityRange += 1
viewableTiles = tile.getViewableTiles(visibilityRange, type.isWaterUnit()) viewableTiles = tile.getViewableTilesList(visibilityRange, type.isWaterUnit())
} }
civInfo.updateViewableTiles() // for the civ civInfo.updateViewableTiles() // for the civ
} }
@ -388,9 +388,10 @@ class MapUnit {
if (amountToHealBy == 0) return if (amountToHealBy == 0) return
if (hasUnique("+10 HP when healing")) amountToHealBy += 10 if (hasUnique("+10 HP when healing")) amountToHealBy += 10
val adjacentUnits = currentTile.getTilesInDistance(1).flatMap { it.getUnits() } val maxAdjacentHealingBonus = currentTile.getTilesInDistance(1)
if (adjacentUnits.isNotEmpty()) .flatMap { it.getUnits().asSequence() }.map { it.adjacentHealingBonus() }.max()
amountToHealBy += adjacentUnits.map { it.adjacentHealingBonus() }.max()!! if (maxAdjacentHealingBonus != null)
amountToHealBy += maxAdjacentHealingBonus
if (hasUnique("All healing effects doubled")) if (hasUnique("All healing effects doubled"))
amountToHealBy *= 2 amountToHealBy *= 2
healBy(amountToHealBy) healBy(amountToHealBy)
@ -441,19 +442,19 @@ class MapUnit {
} }
} }
fun startTurn(){ fun startTurn() {
currentMovement = getMaxMovement().toFloat() currentMovement = getMaxMovement().toFloat()
attacksThisTurn=0 attacksThisTurn = 0
due = true due = true
// Wake sleeping units if there's an enemy nearby // Wake sleeping units if there's an enemy nearby
if(isSleeping() && currentTile.getTilesInDistance(2).any { if (isSleeping() && currentTile.getTilesInDistance(2).any {
it.militaryUnit!=null && it.militaryUnit!!.civInfo.isAtWarWith(civInfo) it.militaryUnit != null && it.militaryUnit!!.civInfo.isAtWarWith(civInfo)
}) })
action=null action = null
val tileOwner = getTile().getOwner() val tileOwner = getTile().getOwner()
if(tileOwner!=null && !civInfo.canEnterTiles(tileOwner) && !tileOwner.isCityState()) // if an enemy city expanded onto this tile while I was in it if (tileOwner != null && !civInfo.canEnterTiles(tileOwner) && !tileOwner.isCityState()) // if an enemy city expanded onto this tile while I was in it
movement.teleportToClosestMoveableTile() movement.teleportToClosestMoveableTile()
doPreTurnAction() doPreTurnAction()
} }
@ -520,7 +521,7 @@ class MapUnit {
civInfo.addNotification("We have captured a barbarian encampment and recovered [${goldGained.toInt()}] gold!", tile.position, Color.RED) civInfo.addNotification("We have captured a barbarian encampment and recovered [${goldGained.toInt()}] gold!", tile.position, Color.RED)
} }
fun disband(){ fun disband() {
// evacuation of transported units before disbanding, if possible // evacuation of transported units before disbanding, if possible
if (type.isAircraftCarrierUnit() || type.isMissileCarrierUnit()) { if (type.isAircraftCarrierUnit() || type.isMissileCarrierUnit()) {
for(unit in currentTile.getUnits().filter { it.type.isAirUnit() && it.isTransported }) { for(unit in currentTile.getUnits().filter { it.type.isAirUnit() && it.isTransported }) {
@ -539,11 +540,17 @@ class MapUnit {
unit.disband() unit.disband()
} }
} }
destroy() destroy()
if(currentTile.getOwner()==civInfo) if (currentTile.getOwner() == civInfo)
civInfo.gold += baseUnit.getDisbandGold() civInfo.gold += baseUnit.getDisbandGold()
if (civInfo.isDefeated()) civInfo.destroy() if (civInfo.isDefeated()) civInfo.destroy()
for (unit in currentTile.getUnits().filter { it.type.isAirUnit() && it.isTransported }) {
if (unit.movement.canMoveTo(currentTile)) continue // we disbanded a carrier in a city, it can still stay in the city
val tileCanMoveTo = unit.currentTile.getTilesInDistance(unit.getRange())
.firstOrNull { unit.movement.canMoveTo(it) }
if (tileCanMoveTo != null) unit.movement.moveToTile(tileCanMoveTo)
else unit.disband()
}
} }
private fun getAncientRuinBonus(tile: TileInfo) { private fun getAncientRuinBonus(tile: TileInfo) {
@ -591,7 +598,7 @@ class MapUnit {
// Map of the surrounding area // Map of the surrounding area
actions.add { actions.add {
val revealCenter = tile.getTilesAtDistance(ANCIENT_RUIN_MAP_REVEAL_OFFSET).random(tileBasedRandom) val revealCenter = tile.getTilesAtDistance(ANCIENT_RUIN_MAP_REVEAL_OFFSET).toList().random(tileBasedRandom)
val tilesToReveal = revealCenter val tilesToReveal = revealCenter
.getTilesInDistance(ANCIENT_RUIN_MAP_REVEAL_RANGE) .getTilesInDistance(ANCIENT_RUIN_MAP_REVEAL_RANGE)
.filter { Random.nextFloat() < ANCIENT_RUIN_MAP_REVEAL_CHANCE } .filter { Random.nextFloat() < ANCIENT_RUIN_MAP_REVEAL_CHANCE }

View File

@ -68,9 +68,8 @@ open class TileInfo {
return false return false
} }
fun containsUnique(unique: String): Boolean { fun containsUnique(unique: String): Boolean =
return isNaturalWonder() && getNaturalWonder().uniques.contains(unique) isNaturalWonder() && getNaturalWonder().uniques.contains(unique)
}
//region pure functions //region pure functions
/** Returns military, civilian and air units in tile */ /** Returns military, civilian and air units in tile */
@ -104,13 +103,8 @@ open class TileInfo {
// This is for performance - since we access the neighbors of a tile ALL THE TIME, // This is for performance - since we access the neighbors of a tile ALL THE TIME,
// and the neighbors of a tile never change, it's much more efficient to save the list once and for all! // and the neighbors of a tile never change, it's much more efficient to save the list once and for all!
@Transient private var internalNeighbors : List<TileInfo>?=null @delegate:Transient
val neighbors: List<TileInfo> val neighbors: List<TileInfo> by lazy { getTilesAtDistance(1).toList() }
get(){
if(internalNeighbors==null)
internalNeighbors = getTilesAtDistance(1)
return internalNeighbors!!
}
fun getHeight(): Int { fun getHeight(): Int {
if (baseTerrain == Constants.mountain) return 4 if (baseTerrain == Constants.mountain) return 4
@ -127,18 +121,15 @@ open class TileInfo {
return containingCity.civInfo return containingCity.civInfo
} }
fun getTerrainFeature(): Terrain? { fun getTerrainFeature(): Terrain? =
return if (terrainFeature == null) null else ruleset.terrains[terrainFeature!!] if (terrainFeature == null) null else ruleset.terrains[terrainFeature!!]
}
fun isWorked(): Boolean { fun isWorked(): Boolean {
val city = getCity() val city = getCity()
return city!=null && city.workedTiles.contains(position) return city!=null && city.workedTiles.contains(position)
} }
fun getTileStats(observingCiv: CivilizationInfo): Stats { fun getTileStats(observingCiv: CivilizationInfo): Stats = getTileStats(getCity(), observingCiv)
return getTileStats(getCity(), observingCiv)
}
fun getTileStats(city: CityInfo?, observingCiv: CivilizationInfo): Stats { fun getTileStats(city: CityInfo?, observingCiv: CivilizationInfo): Stats {
var stats = getBaseTerrain().clone() var stats = getBaseTerrain().clone()
@ -264,21 +255,20 @@ open class TileInfo {
fun isCoastalTile() = neighbors.any { it.baseTerrain==Constants.coast } fun isCoastalTile() = neighbors.any { it.baseTerrain==Constants.coast }
fun hasViewableResource(civInfo: CivilizationInfo): Boolean { fun hasViewableResource(civInfo: CivilizationInfo): Boolean =
return resource != null && (getTileResource().revealedBy == null || civInfo.tech.isResearched(getTileResource().revealedBy!!)) resource != null && (getTileResource().revealedBy == null || civInfo.tech.isResearched(getTileResource().revealedBy!!))
}
fun getViewableTiles(distance:Int, ignoreCurrentTileHeight:Boolean = false): List<TileInfo> { fun getViewableTilesList(distance:Int, ignoreCurrentTileHeight: Boolean): List<TileInfo> =
return tileMap.getViewableTiles(this.position,distance,ignoreCurrentTileHeight) tileMap.getViewableTiles(position, distance, ignoreCurrentTileHeight)
}
fun getTilesInDistance(distance:Int): List<TileInfo> { fun getTilesInDistance(distance: Int): Sequence<TileInfo> =
return tileMap.getTilesInDistance(position,distance) tileMap.getTilesInDistance(position,distance)
}
fun getTilesAtDistance(distance:Int): List<TileInfo> { fun getTilesInDistanceRange(range: IntRange): Sequence<TileInfo> =
return tileMap.getTilesAtDistance(position,distance) tileMap.getTilesInDistanceRange(position, range)
}
fun getTilesAtDistance(distance:Int): Sequence<TileInfo> =
tileMap.getTilesAtDistance(position, distance)
fun getDefensiveBonus(): Float { fun getDefensiveBonus(): Float {
var bonus = getBaseTerrain().defenceBonus var bonus = getBaseTerrain().defenceBonus

View File

@ -73,22 +73,21 @@ class TileMap {
return get(vector.x.toInt(), vector.y.toInt()) return get(vector.x.toInt(), vector.y.toInt())
} }
fun getTilesInDistance(origin: Vector2, distance: Int): List<TileInfo> { fun getTilesInDistance(origin: Vector2, distance: Int): Sequence<TileInfo> =
val tilesToReturn = mutableListOf<TileInfo>() getTilesInDistanceRange(origin, 0..distance)
for (i in 0 .. distance) {
tilesToReturn += getTilesAtDistance(origin, i)
}
return tilesToReturn
}
fun getTilesAtDistance(origin: Vector2, distance: Int): List<TileInfo> { fun getTilesInDistanceRange(origin: Vector2, range: IntRange): Sequence<TileInfo> =
if(distance==0) return listOf(get(origin)) sequence {
val tilesToReturn = ArrayList<TileInfo>() for (i in range)
yield(getTilesAtDistance(origin, i))
}.flatMap { it }
fun addIfTileExists(x:Int,y:Int){ fun getTilesAtDistance(origin: Vector2, distance: Int): Sequence<TileInfo> =
if(contains(x,y)) if (distance <= 0) // silently take negatives.
tilesToReturn += get(x,y) sequenceOf(get(origin))
} else
sequence {
fun getIfTileExistsOrNull(x: Int, y: Int) = if (contains(x, y)) get(x, y) else null
val centerX = origin.x.toInt() val centerX = origin.x.toInt()
val centerY = origin.y.toInt() val centerY = origin.y.toInt()
@ -98,26 +97,25 @@ class TileMap {
var currentY = centerY - distance var currentY = centerY - distance
for (i in 0 until distance) { // From 6 to 8 for (i in 0 until distance) { // From 6 to 8
addIfTileExists(currentX,currentY) yield(getIfTileExistsOrNull(currentX, currentY))
// We want to get the tile on the other side of the clock, // We want to get the tile on the other side of the clock,
// so if we're at current = origin-delta we want to get to origin+delta. // so if we're at current = origin-delta we want to get to origin+delta.
// The simplest way to do this is 2*origin - current = 2*origin- (origin - delta) = origin+delta // The simplest way to do this is 2*origin - current = 2*origin- (origin - delta) = origin+delta
addIfTileExists(2*centerX - currentX, 2*centerY - currentY) yield(getIfTileExistsOrNull(2 * centerX - currentX, 2 * centerY - currentY))
currentX += 1 // we're going upwards to the left, towards 8 o'clock currentX += 1 // we're going upwards to the left, towards 8 o'clock
} }
for (i in 0 until distance) { // 8 to 10 for (i in 0 until distance) { // 8 to 10
addIfTileExists(currentX,currentY) yield(getIfTileExistsOrNull(currentX, currentY))
addIfTileExists(2*centerX - currentX, 2*centerY - currentY) yield(getIfTileExistsOrNull(2 * centerX - currentX, 2 * centerY - currentY))
currentX += 1 currentX += 1
currentY += 1 // we're going up the left side of the hexagon so we're going "up" - +1,+1 currentY += 1 // we're going up the left side of the hexagon so we're going "up" - +1,+1
} }
for (i in 0 until distance) { // 10 to 12 for (i in 0 until distance) { // 10 to 12
addIfTileExists(currentX,currentY) yield(getIfTileExistsOrNull(currentX, currentY))
addIfTileExists(2*centerX - currentX, 2*centerY - currentY) yield(getIfTileExistsOrNull(2 * centerX - currentX, 2 * centerY - currentY))
currentY += 1 // we're going up the top left side of the hexagon so we're heading "up and to the right" currentY += 1 // we're going up the top left side of the hexagon so we're heading "up and to the right"
} }
return tilesToReturn }.filterNotNull()
}
/** Tries to place the [unitName] into the [TileInfo] closest to the given the [position] /** Tries to place the [unitName] into the [TileInfo] closest to the given the [position]
* *
@ -134,24 +132,32 @@ class TileMap {
): MapUnit? { ): MapUnit? {
val unit = gameInfo.ruleSet.units[unitName]!!.getMapUnit(gameInfo.ruleSet) val unit = gameInfo.ruleSet.units[unitName]!!.getMapUnit(gameInfo.ruleSet)
fun isTileMovePotential(tileInfo:TileInfo): Boolean { fun isTileMovePotential(tileInfo: TileInfo): Boolean =
if(unit.type.isAirUnit()) return true when {
if(unit.type.isWaterUnit()) return tileInfo.isWater || tileInfo.isCityCenter() unit.type.isAirUnit() -> true
else return tileInfo.isLand unit.type.isWaterUnit() -> tileInfo.isWater || tileInfo.isCityCenter()
else -> tileInfo.isLand
} }
val viableTilesToPlaceUnitInAtDistance1 = getTilesInDistance(position, 1).filter { isTileMovePotential(it) } val viableTilesToPlaceUnitInAtDistance1 = getTilesAtDistance(position, 1)
.filter { isTileMovePotential(it) }.toSet()
// This is so that units don't skip over non-potential tiles to go elsewhere - // This is so that units don't skip over non-potential tiles to go elsewhere -
// e.g. a city 2 tiles away from a lake could spawn water units in the lake...Or spawn beyond a mountain range... // e.g. a city 2 tiles away from a lake could spawn water units in the lake...Or spawn beyond a mountain range...
val viableTilesToPlaceUnitInAtDistance2 = getTilesAtDistance(position, 2) val viableTilesToPlaceUnitIn = getTilesAtDistance(position, 2)
.filter { isTileMovePotential(it) && it.neighbors.any { n->n in viableTilesToPlaceUnitInAtDistance1 } } .filter {
isTileMovePotential(it)
&& it.neighbors.any { n -> n in viableTilesToPlaceUnitInAtDistance1 }
} + viableTilesToPlaceUnitInAtDistance1
val viableTilesToPlaceUnitIn = viableTilesToPlaceUnitInAtDistance1.union(viableTilesToPlaceUnitInAtDistance2)
unit.assignOwner(civInfo,false) // both the civ name and actual civ need to be in here in order to calculate the canMoveTo...Darn unit.assignOwner(civInfo, false) // both the civ name and actual civ need to be in here in order to calculate the canMoveTo...Darn
val unitToPlaceTile = viableTilesToPlaceUnitIn.firstOrNull { unit.movement.canMoveTo(it) } val unitToPlaceTile = viableTilesToPlaceUnitIn.firstOrNull { unit.movement.canMoveTo(it) }
if(unitToPlaceTile!=null) { if (unitToPlaceTile == null) {
civInfo.removeUnit(unit) // since we added it to the civ units in the previous assignOwner
return null // we didn't actually create a unit...
}
// Remove the tile improvement, e.g. when placing the starter units (so they don't spawn on ruins/encampments) // Remove the tile improvement, e.g. when placing the starter units (so they don't spawn on ruins/encampments)
if (removeImprovement) unitToPlaceTile.improvement = null if (removeImprovement) unitToPlaceTile.improvement = null
// only once we know the unit can be placed do we add it to the civ's unit list // only once we know the unit can be placed do we add it to the civ's unit list
@ -159,24 +165,20 @@ class TileMap {
unit.currentMovement = unit.getMaxMovement().toFloat() unit.currentMovement = unit.getMaxMovement().toFloat()
// Only once we add the unit to the civ we can activate addPromotion, because it will try to update civ viewable tiles // Only once we add the unit to the civ we can activate addPromotion, because it will try to update civ viewable tiles
for(promotion in unit.baseUnit.promotions) for (promotion in unit.baseUnit.promotions)
unit.promotions.addPromotion(promotion, true) unit.promotions.addPromotion(promotion, true)
// And update civ stats, since the new unit changes both unit upkeep and resource consumption // And update civ stats, since the new unit changes both unit upkeep and resource consumption
civInfo.updateStatsForNextTurn() civInfo.updateStatsForNextTurn()
civInfo.updateDetailedCivResources() civInfo.updateDetailedCivResources()
}
else {
civInfo.removeUnit(unit) // since we added it to the civ units in the previous assignOwner
return null // we didn't actually create a unit...
}
return unit return unit
} }
fun getViewableTiles(position: Vector2, sightDistance: Int, ignoreCurrentTileHeight: Boolean = false): List<TileInfo> { fun getViewableTiles(position: Vector2, sightDistance: Int, ignoreCurrentTileHeight: Boolean)
if (ignoreCurrentTileHeight) return getTilesInDistance(position, sightDistance) : List<TileInfo> {
if (ignoreCurrentTileHeight) return getTilesInDistance(position, sightDistance).toList()
val viewableTiles = getTilesInDistance(position, 1).toMutableList() val viewableTiles = getTilesInDistance(position, 1).toMutableList()
val currentTileHeight = get(position).getHeight() val currentTileHeight = get(position).getHeight()

View File

@ -43,9 +43,9 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
class ParentTileAndTotalDistance(val parentTile:TileInfo, val totalDistance: Float) class ParentTileAndTotalDistance(val parentTile:TileInfo, val totalDistance: Float)
fun getDistanceToTilesWithinTurn(origin: Vector2, unitMovement: Float): PathsToTilesWithinTurn { fun getDistanceToTilesWithinTurn(origin: Vector2, unitMovement: Float): PathsToTilesWithinTurn {
if(unitMovement==0f) return PathsToTilesWithinTurn()
val distanceToTiles = PathsToTilesWithinTurn() val distanceToTiles = PathsToTilesWithinTurn()
if(unitMovement==0f) return distanceToTiles
val unitTile = unit.getTile().tileMap[origin] val unitTile = unit.getTile().tileMap[origin]
distanceToTiles[unitTile] = ParentTileAndTotalDistance(unitTile,0f) distanceToTiles[unitTile] = ParentTileAndTotalDistance(unitTile,0f)
var tilesToCheck = listOf(unitTile) var tilesToCheck = listOf(unitTile)
@ -344,7 +344,8 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
return true return true
} }
fun getDistanceToTiles() = getDistanceToTilesWithinTurn(unit.currentTile.position,unit.currentMovement) fun getDistanceToTiles(): PathsToTilesWithinTurn
= getDistanceToTilesWithinTurn(unit.currentTile.position,unit.currentMovement)
fun getArialPathsToCities(): HashMap<TileInfo, ArrayList<TileInfo>> { fun getArialPathsToCities(): HashMap<TileInfo, ArrayList<TileInfo>> {
var tilesToCheck = ArrayList<TileInfo>() var tilesToCheck = ArrayList<TileInfo>()

View File

@ -98,7 +98,7 @@ class CityScreen(internal val city: CityInfo): CameraStageBaseScreen() {
updateAnnexAndRazeCityButton() updateAnnexAndRazeCityButton()
updateTileGroups() updateTileGroups()
if (city.getCenterTile().getTilesAtDistance(4).isNotEmpty()) if (city.getCenterTile().getTilesAtDistance(4).any())
displayTutorial(Tutorial.CityRange) displayTutorial(Tutorial.CityRange)
} }

View File

@ -251,18 +251,20 @@ class WorldMapHolder(internal val worldScreen: WorldScreen, internal val tileMap
tileGroups[unit.getTile()]!!.selectUnit(unit) tileGroups[unit.getTile()]!!.selectUnit(unit)
val isAirUnit = unit.type.isAirUnit() val isAirUnit = unit.type.isAirUnit()
val tilesInMoveRange = if(isAirUnit) unit.getTile().getTilesInDistance(unit.getRange()) val tilesInMoveRange =
else unit.movement.getDistanceToTiles().keys if (isAirUnit)
unit.getTile().getTilesInDistance(unit.getRange())
else
unit.movement.getDistanceToTiles().keys.asSequence()
if(isAirUnit) for (tile in tilesInMoveRange) {
for(tile in tilesInMoveRange) val tileToColor = tileGroups.getValue(tile)
tileGroups[tile]!!.showCircle(Color.BLUE,0.3f) if (isAirUnit)
tileToColor.showCircle(Color.BLUE, 0.3f)
for (tile: TileInfo in tilesInMoveRange)
if (unit.movement.canMoveTo(tile)) if (unit.movement.canMoveTo(tile))
tileGroups[tile]!!.showCircle(Color.WHITE, tileToColor.showCircle(Color.WHITE,
if (UncivGame.Current.settings.singleTapMove || isAirUnit) 0.7f else 0.3f) if (UncivGame.Current.settings.singleTapMove || isAirUnit) 0.7f else 0.3f)
}
val unitType = unit.type val unitType = unit.type
val attackableTiles: List<TileInfo> = if (unitType.isCivilian()) listOf() val attackableTiles: List<TileInfo> = if (unitType.isCivilian()) listOf()
@ -281,15 +283,15 @@ class WorldMapHolder(internal val worldScreen: WorldScreen, internal val tileMap
else 0.5f else 0.5f
for (tile in tileGroups.values) { for (tile in tileGroups.values) {
if (tile.icons.populationIcon != null) tile.icons.populationIcon!!.color.a = fadeout if (tile.icons.populationIcon != null) tile.icons.populationIcon!!.color.a = fadeout
if (tile.icons.improvementIcon != null && tile.tileInfo.improvement!=Constants.barbarianEncampment if (tile.icons.improvementIcon != null && tile.tileInfo.improvement != Constants.barbarianEncampment
&& tile.tileInfo.improvement!=Constants.ancientRuins) && tile.tileInfo.improvement != Constants.ancientRuins)
tile.icons.improvementIcon!!.color.a = fadeout tile.icons.improvementIcon!!.color.a = fadeout
if (tile.resourceImage != null) tile.resourceImage!!.color.a = fadeout if (tile.resourceImage != null) tile.resourceImage!!.color.a = fadeout
} }
} }
private fun updateTilegroupsForSelectedCity(city: CityInfo, playerViewableTilePositions: HashSet<Vector2>) { private fun updateTilegroupsForSelectedCity(city: CityInfo, playerViewableTilePositions: HashSet<Vector2>) {
val attackableTiles: List<TileInfo> = UnitAutomation().getBombardTargets(city) val attackableTiles = UnitAutomation().getBombardTargets(city)
.filter { (UncivGame.Current.viewEntireMapForDebug || playerViewableTilePositions.contains(it.position)) } .filter { (UncivGame.Current.viewEntireMapForDebug || playerViewableTilePositions.contains(it.position)) }
for (attackableTile in attackableTiles) { for (attackableTile in attackableTiles) {
tileGroups[attackableTile]!!.showCircle(colorFromRGB(237, 41, 57)) tileGroups[attackableTile]!!.showCircle(colorFromRGB(237, 41, 57))