Code Org - Moved more Unit movement things into UnitMovementAlgorithms

This commit is contained in:
Yair Morgenstern 2019-07-11 21:43:03 +03:00
parent e78f408bff
commit 00e112e668
15 changed files with 198 additions and 201 deletions

View File

@ -19,12 +19,12 @@ class SpecificUnitAutomation{
fun automateWorkBoats(unit: MapUnit) { fun automateWorkBoats(unit: MapUnit) {
val seaResourcesInCities = unit.civInfo.cities.flatMap { it.getTilesInRange() }.asSequence() val seaResourcesInCities = unit.civInfo.cities.flatMap { it.getTilesInRange() }.asSequence()
.filter { hasWorkableSeaResource(it, unit.civInfo) && (unit.canMoveTo(it) || unit.currentTile == it) } .filter { hasWorkableSeaResource(it, unit.civInfo) && (unit.movement.canMoveTo(it) || unit.currentTile == it) }
val closestReachableResource = seaResourcesInCities.sortedBy { it.arialDistanceTo(unit.currentTile) } val closestReachableResource = seaResourcesInCities.sortedBy { it.arialDistanceTo(unit.currentTile) }
.firstOrNull { unit.movementAlgs().canReach(it) } .firstOrNull { unit.movement.canReach(it) }
if (closestReachableResource != null) { if (closestReachableResource != null) {
unit.movementAlgs().headTowards(closestReachableResource) unit.movement.headTowards(closestReachableResource)
if (unit.currentMovement > 0 && unit.currentTile == closestReachableResource) { if (unit.currentMovement > 0 && unit.currentTile == closestReachableResource) {
val createImprovementAction = UnitActions().getUnitActions(unit, UnCivGame.Current.worldScreen) val createImprovementAction = UnitActions().getUnitActions(unit, UnCivGame.Current.worldScreen)
.firstOrNull { it.name.startsWith("Create") } // could be either fishing boats or oil well .firstOrNull { it.name.startsWith("Create") } // could be either fishing boats or oil well
@ -32,12 +32,12 @@ class SpecificUnitAutomation{
return createImprovementAction.action() // unit is already gone, can't "Explore" return createImprovementAction.action() // unit is already gone, can't "Explore"
} }
} }
else UnitAutomation().tryExplore(unit, unit.getDistanceToTiles()) else UnitAutomation().tryExplore(unit, unit.movement.getDistanceToTiles())
} }
fun automateGreatGeneral(unit: MapUnit){ fun automateGreatGeneral(unit: MapUnit){
//try to follow nearby units. Do not garrison in city if possible //try to follow nearby units. Do not garrison in city if possible
val militaryUnitTilesInDistance = unit.getDistanceToTiles().map { it.key } val militaryUnitTilesInDistance = unit.movement.getDistanceToTiles().map { it.key }
.filter {val militant = it.militaryUnit .filter {val militant = it.militaryUnit
militant != null && militant.civInfo == unit.civInfo militant != null && militant.civInfo == unit.civInfo
&& (it.civilianUnit == null || it.civilianUnit == unit) && (it.civilianUnit == null || it.civilianUnit == unit)
@ -49,17 +49,17 @@ class SpecificUnitAutomation{
val militaryUnit = it.militaryUnit val militaryUnit = it.militaryUnit
militaryUnit!=null && militaryUnit.civInfo==unit.civInfo militaryUnit!=null && militaryUnit.civInfo==unit.civInfo
} } } }
unit.movementAlgs().headTowards(tilesSortedByAffectedTroops.first()) unit.movement.headTowards(tilesSortedByAffectedTroops.first())
return return
} }
//if no unit to follow, take refuge in city. //if no unit to follow, take refuge in city.
val cityToGarrison = unit.civInfo.cities.map {it.getCenterTile()} val cityToGarrison = unit.civInfo.cities.map {it.getCenterTile()}
.sortedBy { it.arialDistanceTo(unit.currentTile) } .sortedBy { it.arialDistanceTo(unit.currentTile) }
.firstOrNull { it.civilianUnit == null && unit.canMoveTo(it) && unit.movementAlgs().canReach(it)} .firstOrNull { it.civilianUnit == null && unit.movement.canMoveTo(it) && unit.movement.canReach(it)}
if (cityToGarrison != null) { if (cityToGarrison != null) {
unit.movementAlgs().headTowards(cityToGarrison) unit.movement.headTowards(cityToGarrison)
return return
} }
} }
@ -100,16 +100,16 @@ class SpecificUnitAutomation{
.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 { (unit.canMoveTo(it) || unit.currentTile==it) && it !in tilesNearCities && it.isLand } .filter { (unit.movement.canMoveTo(it) || unit.currentTile==it) && it !in tilesNearCities && it.isLand }
val bestCityLocation: TileInfo? = possibleCityLocations val bestCityLocation: TileInfo? = possibleCityLocations
.asSequence() .asSequence()
.sortedByDescending { rankTileAsCityCenter(it, nearbyTileRankings) } .sortedByDescending { rankTileAsCityCenter(it, nearbyTileRankings) }
.firstOrNull { unit.movementAlgs().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.getDistanceToTiles())) return // try to find new areas if(UnitAutomation().tryExplore(unit, unit.movement.getDistanceToTiles())) return // try to find new areas
UnitAutomation().wander(unit, unit.getDistanceToTiles()) // go around aimlessly UnitAutomation().wander(unit, unit.movement.getDistanceToTiles()) // go around aimlessly
return return
} }
@ -119,7 +119,7 @@ class SpecificUnitAutomation{
if (unit.getTile() == bestCityLocation) if (unit.getTile() == bestCityLocation)
UnitActions().getUnitActions(unit, UnCivGame.Current.worldScreen).first { it.name == "Found city" }.action() UnitActions().getUnitActions(unit, UnCivGame.Current.worldScreen).first { it.name == "Found city" }.action()
else { else {
unit.movementAlgs().headTowards(bestCityLocation) unit.movement.headTowards(bestCityLocation)
if (unit.currentMovement > 0 && unit.getTile() == bestCityLocation) if (unit.currentMovement > 0 && unit.getTile() == bestCityLocation)
UnitActions().getUnitActions(unit, UnCivGame.Current.worldScreen).first { it.name == "Found city" }.action() UnitActions().getUnitActions(unit, UnCivGame.Current.worldScreen).first { it.name == "Found city" }.action()
} }
@ -136,24 +136,24 @@ class SpecificUnitAutomation{
stats.toHashMap()[relatedStat]!! stats.toHashMap()[relatedStat]!!
} }
for(city in citiesByStatBoost){ for(city in citiesByStatBoost){
val pathToCity =unit.movementAlgs().getShortestPath(city.getCenterTile()) val pathToCity =unit.movement.getShortestPath(city.getCenterTile())
if(pathToCity.isEmpty()) continue if(pathToCity.isEmpty()) continue
if(pathToCity.size>2){ if(pathToCity.size>2){
unit.movementAlgs().headTowards(city.getCenterTile()) unit.movement.headTowards(city.getCenterTile())
return return
} }
// if we got here, we're pretty close, start looking! // if we got here, we're pretty close, start looking!
val tiles = city.getTiles().asSequence() val tiles = city.getTiles().asSequence()
.filter { (unit.canMoveTo(it) || unit.currentTile==it) .filter { (unit.movement.canMoveTo(it) || unit.currentTile==it)
&& 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.movementAlgs().canReach(it) } val chosenTile = tiles.firstOrNull { unit.movement.canReach(it) }
if(chosenTile==null) continue // to another city if(chosenTile==null) continue // to another city
unit.movementAlgs().headTowards(chosenTile) unit.movement.headTowards(chosenTile)
if(unit.currentTile==chosenTile && unit.currentMovement>0) if(unit.currentTile==chosenTile && unit.currentMovement>0)
UnitActions().getUnitActions(unit, UnCivGame.Current.worldScreen) UnitActions().getUnitActions(unit, UnCivGame.Current.worldScreen)
.first { it.name.startsWith("Create") }.action() .first { it.name.startsWith("Create") }.action()
@ -171,12 +171,12 @@ class SpecificUnitAutomation{
if(UnitAutomation().tryAttackNearbyEnemy(unit)) return if(UnitAutomation().tryAttackNearbyEnemy(unit)) return
val reachableCities = tilesInRange val reachableCities = tilesInRange
.filter { it.isCityCenter() && it.getOwner()==unit.civInfo && unit.canMoveTo(it)} .filter { it.isCityCenter() && it.getOwner()==unit.civInfo && unit.movement.canMoveTo(it)}
for(city in reachableCities){ for(city in reachableCities){
if(city.getTilesInDistance(unit.getRange()) if(city.getTilesInDistance(unit.getRange())
.any { UnitAutomation().containsAttackableEnemy(it,MapUnitCombatant(unit)) }) { .any { UnitAutomation().containsAttackableEnemy(it,MapUnitCombatant(unit)) }) {
unit.moveToTile(city) unit.movement.moveToTile(city)
return return
} }
} }

View File

@ -42,7 +42,7 @@ class UnitAutomation{
} }
val unitActions = UnitActions().getUnitActions(unit,UnCivGame.Current.worldScreen) val unitActions = UnitActions().getUnitActions(unit,UnCivGame.Current.worldScreen)
var unitDistanceToTiles = unit.getDistanceToTiles() var unitDistanceToTiles = unit.movement.getDistanceToTiles()
if(unit.civInfo.isBarbarianCivilization() && if(unit.civInfo.isBarbarianCivilization() &&
unit.currentTile.improvement==Constants.barbarianEncampment && unit.type.isLandUnit()) { unit.currentTile.improvement==Constants.barbarianEncampment && unit.type.isLandUnit()) {
@ -52,7 +52,7 @@ class UnitAutomation{
if(tryGoToRuin(unit,unitDistanceToTiles)){ if(tryGoToRuin(unit,unitDistanceToTiles)){
if(unit.currentMovement==0f) return if(unit.currentMovement==0f) return
unitDistanceToTiles = unit.getDistanceToTiles() unitDistanceToTiles = unit.movement.getDistanceToTiles()
} }
if (tryUpgradeUnit(unit, unitActions)) return if (tryUpgradeUnit(unit, unitActions)) return
@ -104,15 +104,15 @@ class UnitAutomation{
val encampmentsCloseToCities val encampmentsCloseToCities
= knownEncampments.filter { cities.any { city -> city.getCenterTile().arialDistanceTo(it) < 6 } } = knownEncampments.filter { cities.any { city -> city.getCenterTile().arialDistanceTo(it) < 6 } }
.sortedBy { it.arialDistanceTo(unit.currentTile) } .sortedBy { it.arialDistanceTo(unit.currentTile) }
val encampmentToHeadTowards = encampmentsCloseToCities.firstOrNull { unit.movementAlgs().canReach(it) } val encampmentToHeadTowards = encampmentsCloseToCities.firstOrNull { unit.movement.canReach(it) }
if(encampmentToHeadTowards==null) return false if(encampmentToHeadTowards==null) return false
unit.movementAlgs().headTowards(encampmentToHeadTowards) unit.movement.headTowards(encampmentToHeadTowards)
return true return true
} }
fun tryHealUnit(unit: MapUnit, unitDistanceToTiles: HashMap<TileInfo, Float>):Boolean { fun tryHealUnit(unit: MapUnit, unitDistanceToTiles: HashMap<TileInfo, Float>):Boolean {
val tilesInDistance = unitDistanceToTiles.keys.filter { unit.canMoveTo(it) } val tilesInDistance = unitDistanceToTiles.keys.filter { unit.movement.canMoveTo(it) }
if(unitDistanceToTiles.isEmpty()) return true // can't move, so... if(unitDistanceToTiles.isEmpty()) return true // can't move, so...
val unitTile = unit.getTile() val unitTile = unit.getTile()
@ -123,8 +123,8 @@ class UnitAutomation{
if(tilesByHealingRate.keys.none { it!=0 }){// We can't heal here at all! We're probably embarked if(tilesByHealingRate.keys.none { it!=0 }){// We can't heal here at all! We're probably embarked
val reachableCityTile = unit.civInfo.cities.map { it.getCenterTile() } val reachableCityTile = unit.civInfo.cities.map { it.getCenterTile() }
.sortedBy { it.arialDistanceTo(unit.currentTile) } .sortedBy { it.arialDistanceTo(unit.currentTile) }
.firstOrNull{unit.movementAlgs().canReach(it)} .firstOrNull{unit.movement.canReach(it)}
if(reachableCityTile!=null) unit.movementAlgs().headTowards(reachableCityTile) if(reachableCityTile!=null) unit.movement.headTowards(reachableCityTile)
else wander(unit,unitDistanceToTiles) else wander(unit,unitDistanceToTiles)
return true return true
} }
@ -136,7 +136,7 @@ class UnitAutomation{
if(bestTileForHealingRank == 0) return false // can't actually heal here... if(bestTileForHealingRank == 0) return false // can't actually heal here...
if(unitTile!=bestTileForHealing && bestTileForHealingRank > unit.rankTileForHealing(unitTile)) if(unitTile!=bestTileForHealing && bestTileForHealingRank > unit.rankTileForHealing(unitTile))
unit.moveToTile(bestTileForHealing) unit.movement.moveToTile(bestTileForHealing)
if(unit.currentMovement>0 && unit.canFortify()) unit.fortify() if(unit.currentMovement>0 && unit.canFortify()) unit.fortify()
return true return true
@ -145,12 +145,12 @@ class UnitAutomation{
fun tryPillageImprovement(unit: MapUnit, unitDistanceToTiles: HashMap<TileInfo, Float>) : Boolean { fun tryPillageImprovement(unit: MapUnit, unitDistanceToTiles: HashMap<TileInfo, Float>) : Boolean {
if(unit.type.isCivilian()) return false if(unit.type.isCivilian()) return false
val tilesInDistance = unitDistanceToTiles.filter {it.value < unit.currentMovement}.keys val tilesInDistance = unitDistanceToTiles.filter {it.value < unit.currentMovement}.keys
.filter { unit.canMoveTo(it) && UnitActions().canPillage(unit,it) } .filter { unit.movement.canMoveTo(it) && UnitActions().canPillage(unit,it) }
if (tilesInDistance.isEmpty()) return false if (tilesInDistance.isEmpty()) return false
val tileToPillage = tilesInDistance.maxBy { it.getDefensiveBonus() }!! val tileToPillage = tilesInDistance.maxBy { it.getDefensiveBonus() }!!
if (unit.getTile()!=tileToPillage) if (unit.getTile()!=tileToPillage)
unit.moveToTile(tileToPillage) unit.movement.moveToTile(tileToPillage)
UnitActions().getUnitActions(unit, UnCivGame.Current.worldScreen) UnitActions().getUnitActions(unit, UnCivGame.Current.worldScreen)
.first { it.name == "Pillage" }.action() .first { it.name == "Pillage" }.action()
@ -207,7 +207,7 @@ class UnitAutomation{
val movementPointsToExpendBeforeAttack = if(it.key==unit.currentTile) movementPointsToExpendHere else movementPointsToExpendAfterMovement val movementPointsToExpendBeforeAttack = if(it.key==unit.currentTile) movementPointsToExpendHere else movementPointsToExpendAfterMovement
unit.currentMovement - it.value - movementPointsToExpendBeforeAttack > 0.1 } // still got leftover movement points after all that, to attack (0.1 is because of Float nensense, see MapUnit.moveToTile(...) unit.currentMovement - it.value - movementPointsToExpendBeforeAttack > 0.1 } // still got leftover movement points after all that, to attack (0.1 is because of Float nensense, see MapUnit.moveToTile(...)
.map { it.key } .map { it.key }
.filter { unit.canMoveTo(it) || it==unit.getTile() } .filter { unit.movement.canMoveTo(it) || it==unit.getTile() }
for(reachableTile in tilesToAttackFrom){ // tiles we'll still have energy after we reach there for(reachableTile in tilesToAttackFrom){ // tiles we'll still have energy after we reach there
val tilesInAttackRange = val tilesInAttackRange =
@ -227,14 +227,14 @@ class UnitAutomation{
private fun tryAdvanceTowardsCloseEnemy(unit: MapUnit): Boolean { private fun tryAdvanceTowardsCloseEnemy(unit: MapUnit): Boolean {
// this can be sped up if we check each layer separately // this can be sped up if we check each layer separately
var closeEnemies = unit.getTile().getTilesInDistance(5) var closeEnemies = unit.getTile().getTilesInDistance(5)
.filter{ containsAttackableEnemy(it, MapUnitCombatant(unit)) && unit.movementAlgs().canReach(it)} .filter{ containsAttackableEnemy(it, MapUnitCombatant(unit)) && unit.movement.canReach(it)}
if(unit.type.isRanged()) if(unit.type.isRanged())
closeEnemies = closeEnemies.filterNot { it.isCityCenter() && it.getCity()!!.health==1 } closeEnemies = closeEnemies.filterNot { it.isCityCenter() && it.getCity()!!.health==1 }
val closestEnemy = closeEnemies.minBy { it.arialDistanceTo(unit.getTile()) } val closestEnemy = closeEnemies.minBy { it.arialDistanceTo(unit.getTile()) }
if (closestEnemy != null) { if (closestEnemy != null) {
unit.movementAlgs().headTowards(closestEnemy) unit.movement.headTowards(closestEnemy)
return true return true
} }
return false return false
@ -244,9 +244,9 @@ class UnitAutomation{
val settlerOrGreatPersonToAccompany = unit.civInfo.getCivUnits() val settlerOrGreatPersonToAccompany = unit.civInfo.getCivUnits()
.firstOrNull { val tile = it.currentTile .firstOrNull { val tile = it.currentTile
(it.name== Constants.settler || it.name in GreatPersonManager().statToGreatPersonMapping.values) (it.name== Constants.settler || it.name in GreatPersonManager().statToGreatPersonMapping.values)
&& tile.militaryUnit==null && unit.canMoveTo(tile) && unit.movementAlgs().canReach(tile) } && tile.militaryUnit==null && unit.movement.canMoveTo(tile) && unit.movement.canReach(tile) }
if(settlerOrGreatPersonToAccompany==null) return false if(settlerOrGreatPersonToAccompany==null) return false
unit.movementAlgs().headTowards(settlerOrGreatPersonToAccompany.currentTile) unit.movement.headTowards(settlerOrGreatPersonToAccompany.currentTile)
return true return true
} }
@ -280,10 +280,10 @@ class UnitAutomation{
.sortedBy { cityCenterTile -> // sort enemy cities by closeness to our cities, and only then choose the first reachable - checking canReach is comparatively very time-intensive! .sortedBy { cityCenterTile -> // sort enemy cities by closeness to our cities, and only then choose the first reachable - checking canReach is comparatively very time-intensive!
unit.civInfo.cities.asSequence().map { cityCenterTile.arialDistanceTo(it.getCenterTile()) }.min()!! unit.civInfo.cities.asSequence().map { cityCenterTile.arialDistanceTo(it.getCenterTile()) }.min()!!
} }
.firstOrNull { unit.movementAlgs().canReach(it) } .firstOrNull { unit.movement.canReach(it) }
if (closestReachableEnemyCity != null) { if (closestReachableEnemyCity != null) {
val unitDistanceToTiles = unit.getDistanceToTiles() val unitDistanceToTiles = unit.movement.getDistanceToTiles()
val tilesInBombardRange = closestReachableEnemyCity.getTilesInDistance(2) val tilesInBombardRange = closestReachableEnemyCity.getTilesInDistance(2)
val reachableTilesNotInBombardRange = unitDistanceToTiles.keys.filter { it !in tilesInBombardRange } val reachableTilesNotInBombardRange = unitDistanceToTiles.keys.filter { it !in tilesInBombardRange }
val canMoveIntoBombardRange = tilesInBombardRange.any { unitDistanceToTiles.containsKey(it)} val canMoveIntoBombardRange = tilesInBombardRange.any { unitDistanceToTiles.containsKey(it)}
@ -293,7 +293,7 @@ class UnitAutomation{
.filter { it.isLand } .filter { it.isLand }
val closestReachableLandingGroundTile = suitableGatheringGroundTiles val closestReachableLandingGroundTile = suitableGatheringGroundTiles
.sortedBy { it.arialDistanceTo(unit.currentTile) } .sortedBy { it.arialDistanceTo(unit.currentTile) }
.firstOrNull { unit.movementAlgs().canReach(it) } .firstOrNull { unit.movement.canReach(it) }
// 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.
@ -302,7 +302,7 @@ class UnitAutomation{
if(tileToHeadTo !in tilesInBombardRange) // no need to worry, keep going as the movement alg. says if(tileToHeadTo !in tilesInBombardRange) // no need to worry, keep going as the movement alg. says
unit.movementAlgs().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
@ -311,7 +311,7 @@ class UnitAutomation{
// 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(tilesCanAttackFromButNotInBombardRange.any())
unit.movementAlgs().headTowards(tilesCanAttackFromButNotInBombardRange.minBy { unitDistanceToTiles[it]!! }!!) unit.movement.headTowards(tilesCanAttackFromButNotInBombardRange.minBy { unitDistanceToTiles[it]!! }!!)
} }
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)
@ -324,7 +324,7 @@ class UnitAutomation{
totalAttackOnCityPerTurn += BattleDamage().calculateDamageToDefender(MapUnitCombatant(militaryUnit), enemyCityCombatant) totalAttackOnCityPerTurn += BattleDamage().calculateDamageToDefender(MapUnitCombatant(militaryUnit), enemyCityCombatant)
} }
if(totalAttackOnCityPerTurn * 3 > closestReachableEnemyCity.getCity()!!.health) // if we can defeat it in 3 turns with the current units, if(totalAttackOnCityPerTurn * 3 > closestReachableEnemyCity.getCity()!!.health) // if we can defeat it in 3 turns with the current units,
unit.movementAlgs().headTowards(closestReachableEnemyCity) // go for it! unit.movement.headTowards(closestReachableEnemyCity) // go for it!
} }
} }
@ -346,14 +346,14 @@ class UnitAutomation{
val enemyTileToAttackNextTurn = chooseAttackTarget(unit, attackableEnemiesNextTurn) val enemyTileToAttackNextTurn = chooseAttackTarget(unit, attackableEnemiesNextTurn)
if (enemyTileToAttackNextTurn != null) { if (enemyTileToAttackNextTurn != null) {
unit.moveToTile(enemyTileToAttackNextTurn.tileToAttackFrom) unit.movement.moveToTile(enemyTileToAttackNextTurn.tileToAttackFrom)
return true return true
} }
return false return false
} }
fun tryAttackNearbyEnemy(unit: MapUnit): Boolean { fun tryAttackNearbyEnemy(unit: MapUnit): Boolean {
val attackableEnemies = getAttackableEnemies(unit, unit.getDistanceToTiles()) val attackableEnemies = getAttackableEnemies(unit, unit.movement.getDistanceToTiles())
// Only take enemies we can fight without dying // Only take enemies we can fight without dying
.filter { .filter {
BattleDamage().calculateDamageToAttacker(MapUnitCombatant(unit), BattleDamage().calculateDamageToAttacker(MapUnitCombatant(unit),
@ -418,7 +418,7 @@ class UnitAutomation{
val citiesWithoutGarrison = unit.civInfo.cities.filter { val citiesWithoutGarrison = unit.civInfo.cities.filter {
val centerTile = it.getCenterTile() val centerTile = it.getCenterTile()
centerTile.militaryUnit==null centerTile.militaryUnit==null
&& unit.canMoveTo(centerTile) && unit.movement.canMoveTo(centerTile)
} }
fun isCityThatNeedsDefendingInWartime(city: CityInfo): Boolean { fun isCityThatNeedsDefendingInWartime(city: CityInfo): Boolean {
@ -445,17 +445,17 @@ class UnitAutomation{
val closestReachableCityNeedsDefending =citiesToTry val closestReachableCityNeedsDefending =citiesToTry
.sortedBy{ it.getCenterTile().arialDistanceTo(unit.currentTile) } .sortedBy{ it.getCenterTile().arialDistanceTo(unit.currentTile) }
.firstOrNull { unit.movementAlgs().canReach(it.getCenterTile()) } .firstOrNull { unit.movement.canReach(it.getCenterTile()) }
if(closestReachableCityNeedsDefending==null) return false if(closestReachableCityNeedsDefending==null) return false
unit.movementAlgs().headTowards(closestReachableCityNeedsDefending.getCenterTile()) unit.movement.headTowards(closestReachableCityNeedsDefending.getCenterTile())
return true return true
} }
fun tryGoToRuin(unit:MapUnit, unitDistanceToTiles: HashMap<TileInfo, Float>): Boolean { fun tryGoToRuin(unit:MapUnit, unitDistanceToTiles: HashMap<TileInfo, Float>): Boolean {
if(!unit.civInfo.isMajorCiv()) return false // barbs don't have anything to do in ruins if(!unit.civInfo.isMajorCiv()) return false // barbs don't have anything to do in ruins
val tileWithRuin = unitDistanceToTiles.keys.firstOrNull{unit.canMoveTo(it) && it.improvement == Constants.ancientRuins} val tileWithRuin = unitDistanceToTiles.keys.firstOrNull{unit.movement.canMoveTo(it) && it.improvement == Constants.ancientRuins}
if(tileWithRuin==null) return false if(tileWithRuin==null) return false
unit.moveToTile(tileWithRuin) unit.movement.moveToTile(tileWithRuin)
return true return true
} }
@ -466,16 +466,16 @@ class UnitAutomation{
} }
for(tile in unit.currentTile.getTilesInDistance(5)) for(tile in unit.currentTile.getTilesInDistance(5))
if(unit.canMoveTo(tile) && tile.position !in unit.civInfo.exploredTiles if(unit.movement.canMoveTo(tile) && tile.position !in unit.civInfo.exploredTiles
&& unit.movementAlgs().canReach(tile)){ && unit.movement.canReach(tile)){
unit.movementAlgs().headTowards(tile) unit.movement.headTowards(tile)
return true return true
} }
return false return false
} }
fun automatedExplore(unit:MapUnit){ fun automatedExplore(unit:MapUnit){
val unitDistanceToTiles = unit.getDistanceToTiles() val unitDistanceToTiles = unit.movement.getDistanceToTiles()
if(tryGoToRuin(unit, unitDistanceToTiles) && unit.currentMovement==0f) return if(tryGoToRuin(unit, unitDistanceToTiles) && unit.currentMovement==0f) return
if (unit.health < 80) { if (unit.health < 80) {
@ -485,10 +485,10 @@ class UnitAutomation{
for(i in 1..10){ for(i in 1..10){
val unexploredTilesAtDistance = unit.getTile().getTilesAtDistance(i) val unexploredTilesAtDistance = unit.getTile().getTilesAtDistance(i)
.filter { unit.canMoveTo(it) && it.position !in unit.civInfo.exploredTiles .filter { unit.movement.canMoveTo(it) && it.position !in unit.civInfo.exploredTiles
&& unit.movementAlgs().canReach(it) } && unit.movement.canReach(it) }
if(unexploredTilesAtDistance.isNotEmpty()){ if(unexploredTilesAtDistance.isNotEmpty()){
unit.movementAlgs().headTowards(unexploredTilesAtDistance.random()) unit.movement.headTowards(unexploredTilesAtDistance.random())
return return
} }
} }
@ -498,11 +498,11 @@ class UnitAutomation{
fun wander(unit: MapUnit, unitDistanceToTiles: HashMap<TileInfo, Float>) { fun wander(unit: MapUnit, unitDistanceToTiles: HashMap<TileInfo, Float>) {
val reachableTiles= unitDistanceToTiles val reachableTiles= unitDistanceToTiles
.filter { unit.canMoveTo(it.key) && unit.movementAlgs().canReach(it.key) } .filter { unit.movement.canMoveTo(it.key) && unit.movement.canReach(it.key) }
val reachableTilesMaxWalkingDistance = reachableTiles.filter { it.value == unit.currentMovement } val reachableTilesMaxWalkingDistance = reachableTiles.filter { it.value == unit.currentMovement }
if (reachableTilesMaxWalkingDistance.any()) unit.moveToTile(reachableTilesMaxWalkingDistance.toList().random().first) if (reachableTilesMaxWalkingDistance.any()) unit.movement.moveToTile(reachableTilesMaxWalkingDistance.toList().random().first)
else if (reachableTiles.any()) unit.moveToTile(reachableTiles.toList().random().first) else if (reachableTiles.any()) unit.movement.moveToTile(reachableTiles.toList().random().first)
} }

View File

@ -12,7 +12,7 @@ import com.unciv.models.gamebasics.tile.TileImprovement
class WorkerAutomation(val unit: MapUnit) { class WorkerAutomation(val unit: MapUnit) {
fun automateWorkerAction() { fun automateWorkerAction() {
val enemyUnitsInWalkingDistance = unit.getDistanceToTiles().keys val enemyUnitsInWalkingDistance = unit.movement.getDistanceToTiles().keys
.filter { it.militaryUnit!=null && it.militaryUnit!!.civInfo!=unit.civInfo .filter { it.militaryUnit!=null && it.militaryUnit!!.civInfo!=unit.civInfo
&& unit.civInfo.isAtWarWith(it.militaryUnit!!.civInfo) } && unit.civInfo.isAtWarWith(it.militaryUnit!!.civInfo) }
@ -26,7 +26,7 @@ class WorkerAutomation(val unit: MapUnit) {
} }
if (tileToWork != tile) { if (tileToWork != tile) {
val reachedTile = unit.movementAlgs().headTowards(tileToWork) val reachedTile = unit.movement.headTowards(tileToWork)
if(reachedTile!=tile) unit.doPreTurnAction() // otherwise, we get a situation where the worker is automated, so it tries to move but doesn't, then tries to automate, then move, etc, forever. Stack overflow exception! if(reachedTile!=tile) unit.doPreTurnAction() // otherwise, we get a situation where the worker is automated, so it tries to move but doesn't, then tries to automate, then move, etc, forever. Stack overflow exception!
return return
} }
@ -57,7 +57,7 @@ class WorkerAutomation(val unit: MapUnit) {
if(citiesThatNeedConnecting.isEmpty()) return false // do nothing. if(citiesThatNeedConnecting.isEmpty()) return false // do nothing.
val citiesThatNeedConnectingBfs = citiesThatNeedConnecting val citiesThatNeedConnectingBfs = citiesThatNeedConnecting
.map { city -> BFS(city.getCenterTile()){it.isLand && unit.canPassThrough(it)} } .map { city -> BFS(city.getCenterTile()){it.isLand && unit.movement.canPassThrough(it)} }
.toMutableList() .toMutableList()
val connectedCities = unit.civInfo.cities.filter { it.isCapital() || it.cityStats.isConnectedToCapital(targetRoad) } val connectedCities = unit.civInfo.cities.filter { it.isCapital() || it.cityStats.isConnectedToCapital(targetRoad) }
@ -77,10 +77,11 @@ class WorkerAutomation(val unit: MapUnit) {
val tileToConstructRoadOn :TileInfo val tileToConstructRoadOn :TileInfo
if(unit.currentTile in roadableTiles) tileToConstructRoadOn = unit.currentTile if(unit.currentTile in roadableTiles) tileToConstructRoadOn = unit.currentTile
else{ else{
val reachableTiles = roadableTiles.filter { unit.canMoveTo(it)&& unit.movementAlgs().canReach(it)} val reachableTiles = roadableTiles
.filter { unit.movement.canMoveTo(it)&& unit.movement.canReach(it)}
if(reachableTiles.isEmpty()) continue if(reachableTiles.isEmpty()) continue
tileToConstructRoadOn = reachableTiles.minBy { unit.movementAlgs().getShortestPath(it).size }!! tileToConstructRoadOn = reachableTiles.minBy { unit.movement.getShortestPath(it).size }!!
unit.movementAlgs().headTowards(tileToConstructRoadOn) unit.movement.headTowards(tileToConstructRoadOn)
} }
if(unit.currentMovement>0 && unit.currentTile==tileToConstructRoadOn if(unit.currentMovement>0 && unit.currentTile==tileToConstructRoadOn
&& unit.currentTile.improvementInProgress!=targetRoad.name) && unit.currentTile.improvementInProgress!=targetRoad.name)
@ -110,8 +111,7 @@ class WorkerAutomation(val unit: MapUnit) {
// 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{ val selectedTile = workableTiles.firstOrNull{unit.movement.canReach(it) }
unit.movementAlgs().canReach(it) }
if (selectedTile != null if (selectedTile != null
&& getPriority(selectedTile, unit.civInfo)>1 && getPriority(selectedTile, unit.civInfo)>1

View File

@ -21,7 +21,7 @@ class Battle(val gameInfo:GameInfo) {
fun moveAndAttack(attacker: ICombatant, attackableTile: UnitAutomation.AttackableTile){ fun moveAndAttack(attacker: ICombatant, attackableTile: UnitAutomation.AttackableTile){
if (attacker is MapUnitCombatant) { if (attacker is MapUnitCombatant) {
attacker.unit.moveToTile(attackableTile.tileToAttackFrom) attacker.unit.movement.moveToTile(attackableTile.tileToAttackFrom)
if (attacker.unit.hasUnique("Must set up to ranged attack") && attacker.unit.action != "Set Up") { if (attacker.unit.hasUnique("Must set up to ranged attack") && attacker.unit.action != "Set Up") {
attacker.unit.action = "Set Up" attacker.unit.action = "Set Up"
attacker.unit.useMovementPoints(1f) attacker.unit.useMovementPoints(1f)
@ -124,11 +124,11 @@ class Battle(val gameInfo:GameInfo) {
else if (attacker.isMelee() else if (attacker.isMelee()
&& (defender.isDefeated() || defender.getCivInfo()==attacker.getCivInfo()) && (defender.isDefeated() || defender.getCivInfo()==attacker.getCivInfo())
// This is so that if we attack e.g. a barbarian in enemy territory that we can't enter, we won't enter it // This is so that if we attack e.g. a barbarian in enemy territory that we can't enter, we won't enter it
&& (attacker as MapUnitCombatant).unit.canMoveTo(attackedTile)) { && (attacker as MapUnitCombatant).unit.movement.canMoveTo(attackedTile)) {
// we destroyed an enemy military unit and there was a civilian unit in the same tile as well // we destroyed an enemy military unit and there was a civilian unit in the same tile as well
if(attackedTile.civilianUnit!=null && attackedTile.civilianUnit!!.civInfo != attacker.getCivInfo()) if(attackedTile.civilianUnit!=null && attackedTile.civilianUnit!!.civInfo != attacker.getCivInfo())
captureCivilianUnit(attacker,MapUnitCombatant(attackedTile.civilianUnit!!)) captureCivilianUnit(attacker,MapUnitCombatant(attackedTile.civilianUnit!!))
attacker.unit.moveToTile(attackedTile) attacker.unit.movement.moveToTile(attackedTile)
} }
@ -262,7 +262,7 @@ class Battle(val gameInfo:GameInfo) {
} }
} }
(attacker as MapUnitCombatant).unit.moveToTile(city.getCenterTile()) (attacker as MapUnitCombatant).unit.movement.moveToTile(city.getCenterTile())
} }
fun getMapCombatantOfTile(tile:TileInfo): ICombatant? { fun getMapCombatantOfTile(tile:TileInfo): ICombatant? {

View File

@ -113,7 +113,7 @@ class CityExpansionManager {
for(unit in tileInfo.getUnits()) for(unit in tileInfo.getUnits())
if(!unit.civInfo.canEnterTiles(cityInfo.civInfo)) if(!unit.civInfo.canEnterTiles(cityInfo.civInfo))
unit.movementAlgs().teleportToClosestMoveableTile() unit.movement.teleportToClosestMoveableTile()
cityInfo.civInfo.updateViewableTiles() cityInfo.civInfo.updateViewableTiles()
} }

View File

@ -241,8 +241,8 @@ class CityInfo {
// Edge case! What if a water unit is in a city, and you raze the city? // Edge case! What if a water unit is in a city, and you raze the city?
// Well, the water unit has to return to the water! // Well, the water unit has to return to the water!
for(unit in getCenterTile().getUnits()) for(unit in getCenterTile().getUnits())
if(!unit.canPassThrough(getCenterTile())) if(!unit.movement.canPassThrough(getCenterTile()))
unit.movementAlgs().teleportToClosestMoveableTile() unit.movement.teleportToClosestMoveableTile()
civInfo.cities = civInfo.cities.toMutableList().apply { remove(this@CityInfo) } civInfo.cities = civInfo.cities.toMutableList().apply { remove(this@CityInfo) }
getTiles().forEach { expansion.relinquishOwnership(it) } getTiles().forEach { expansion.relinquishOwnership(it) }

View File

@ -212,7 +212,7 @@ class DiplomacyManager() {
if(hasOpenBorders && !newHasOpenBorders){ // borders were closed, get out! if(hasOpenBorders && !newHasOpenBorders){ // borders were closed, get out!
for(unit in civInfo.getCivUnits().filter { it.currentTile.getOwner()?.civName == otherCivName }){ for(unit in civInfo.getCivUnits().filter { it.currentTile.getOwner()?.civName == otherCivName }){
unit.movementAlgs().teleportToClosestMoveableTile() unit.movement.teleportToClosestMoveableTile()
} }
} }
@ -343,11 +343,11 @@ class DiplomacyManager() {
val otherCiv = otherCiv() val otherCiv = otherCiv()
// We get out of their territory // We get out of their territory
for(unit in civInfo.getCivUnits().filter { it.getTile().getOwner()== otherCiv}) for(unit in civInfo.getCivUnits().filter { it.getTile().getOwner()== otherCiv})
unit.movementAlgs().teleportToClosestMoveableTile() unit.movement.teleportToClosestMoveableTile()
// And we get out of theirs // And we get out of theirs
for(unit in otherCiv.getCivUnits().filter { it.getTile().getOwner()== civInfo}) for(unit in otherCiv.getCivUnits().filter { it.getTile().getOwner()== civInfo})
unit.movementAlgs().teleportToClosestMoveableTile() unit.movement.teleportToClosestMoveableTile()
} }
fun hasFlag(flag:DiplomacyFlags) = flagsCountdown.containsKey(flag.name) fun hasFlag(flag:DiplomacyFlags) = flagsCountdown.containsKey(flag.name)

View File

@ -14,8 +14,6 @@ import com.unciv.models.gamebasics.tile.TerrainType
import com.unciv.models.gamebasics.unit.BaseUnit import com.unciv.models.gamebasics.unit.BaseUnit
import com.unciv.models.gamebasics.unit.UnitType import com.unciv.models.gamebasics.unit.UnitType
import java.text.DecimalFormat import java.text.DecimalFormat
import java.util.*
import kotlin.collections.ArrayList
class MapUnit { class MapUnit {
@ -23,6 +21,8 @@ class MapUnit {
@Transient lateinit var baseUnit: BaseUnit @Transient lateinit var baseUnit: BaseUnit
@Transient internal lateinit var currentTile :TileInfo @Transient internal lateinit var currentTile :TileInfo
@Transient val movement = UnitMovementAlgorithms(this)
// This is saved per each unit because if we need to recalculate viewable tiles every time a unit moves, // This is saved per each unit because if we need to recalculate viewable tiles every time a unit moves,
// and we need to go over ALL the units, that's a lot of time spent on updating information we should already know! // and we need to go over ALL the units, that's a lot of time spent on updating information we should already know!
// About 10% of total NextTurn performance time, at the time of this change! // About 10% of total NextTurn performance time, at the time of this change!
@ -97,11 +97,6 @@ class MapUnit {
return movement return movement
} }
fun getDistanceToTiles(): HashMap<TileInfo, Float> {
val tile = getTile()
return movementAlgs().getDistanceToTilesWithinTurn(tile.position,currentMovement)
}
// This SHOULD NOT be a hashset, because if it is, thenn promotions with the same text (e.g. barrage I, barrage II) // This SHOULD NOT be a hashset, because if it is, thenn promotions with the same text (e.g. barrage I, barrage II)
// will not get counted twice! // will not get counted twice!
@Transient var tempUniques= ArrayList<String>() @Transient var tempUniques= ArrayList<String>()
@ -160,63 +155,10 @@ class MapUnit {
return action!!.split(" ")[1].toInt() return action!!.split(" ")[1].toInt()
} }
fun movementAlgs() = UnitMovementAlgorithms(this)
override fun toString(): String { override fun toString(): String {
return "$name - $owner" return "$name - $owner"
} }
// This is the most called function in the entire game,
// so multiple callees of this function have been optimized,
// because optimization on this function results in massive benefits!
fun canPassThrough(tile: TileInfo):Boolean{
if(tile.getBaseTerrain().impassable) return false
if(tile.isLand && type.isWaterUnit() && !tile.isCityCenter())
return false
if(tile.isWater && type.isLandUnit()){
if(!civInfo.tech.unitsCanEmbark) return false
if(tile.isOcean && !civInfo.tech.embarkedUnitsCanEnterOcean)
return false
}
if(tile.isOcean && baseUnit.uniques.contains("Cannot enter ocean tiles")) return false
if(tile.isOcean && baseUnit.uniques.contains("Cannot enter ocean tiles until Astronomy")
&& !civInfo.tech.isResearched("Astronomy"))
return false
val tileOwner = tile.getOwner()
if(tileOwner!=null && tileOwner.civName!=owner) {
if (tile.isCityCenter()) return false
if (!civInfo.canEnterTiles(tileOwner)
&& !(civInfo.isPlayerCivilization() && tileOwner.isCityState())) return false
// AIs won't enter city-state's border.
}
val unitsInTile = tile.getUnits()
if(unitsInTile.isNotEmpty()){
val firstUnit = unitsInTile.first()
if(firstUnit.civInfo != civInfo && civInfo.isAtWarWith(firstUnit.civInfo))
return false
}
return true
}
/**
* Designates whether we can enter the tile - without attacking
* DOES NOT designate whether we can reach that tile in the current turn
*/
fun canMoveTo(tile: TileInfo): Boolean {
if(type.isAirUnit())
return tile.airUnits.size<6 && tile.isCityCenter() && tile.getCity()?.civInfo==civInfo
if(!canPassThrough(tile)) return false
if (type.isCivilian())
return tile.civilianUnit==null && (tile.militaryUnit==null || tile.militaryUnit!!.owner==owner)
else return tile.militaryUnit==null && (tile.civilianUnit==null || tile.civilianUnit!!.owner==owner)
}
fun isIdle(): Boolean { fun isIdle(): Boolean {
if (currentMovement == 0f) return false if (currentMovement == 0f) return false
@ -332,7 +274,7 @@ class MapUnit {
val currentTile = getTile() val currentTile = getTile()
if (currentMovement == 0f) return // We've already done stuff this turn, and can't do any more stuff if (currentMovement == 0f) return // We've already done stuff this turn, and can't do any more stuff
val enemyUnitsInWalkingDistance = getDistanceToTiles().keys val enemyUnitsInWalkingDistance = movement.getDistanceToTiles().keys
.filter { it.militaryUnit!=null && civInfo.isAtWarWith(it.militaryUnit!!.civInfo)} .filter { it.militaryUnit!=null && civInfo.isAtWarWith(it.militaryUnit!!.civInfo)}
if(enemyUnitsInWalkingDistance.isNotEmpty()) { if(enemyUnitsInWalkingDistance.isNotEmpty()) {
if (mapUnitAction?.shouldStopOnEnemyInSight()==true) if (mapUnitAction?.shouldStopOnEnemyInSight()==true)
@ -346,8 +288,8 @@ class MapUnit {
val destination = action!!.replace("moveTo ", "").split(",").dropLastWhile { it.isEmpty() }.toTypedArray() val destination = action!!.replace("moveTo ", "").split(",").dropLastWhile { it.isEmpty() }.toTypedArray()
val destinationVector = Vector2(Integer.parseInt(destination[0]).toFloat(), Integer.parseInt(destination[1]).toFloat()) val destinationVector = Vector2(Integer.parseInt(destination[0]).toFloat(), Integer.parseInt(destination[1]).toFloat())
val destinationTile = currentTile.tileMap[destinationVector] val destinationTile = currentTile.tileMap[destinationVector]
if(!movementAlgs().canReach(destinationTile)) return // That tile that we were moving towards is now unreachable if(!movement.canReach(destinationTile)) return // That tile that we were moving towards is now unreachable
val gotTo = movementAlgs().headTowards(destinationTile) val gotTo = movement.headTowards(destinationTile)
if(gotTo==currentTile) // We didn't move at all if(gotTo==currentTile) // We didn't move at all
return return
if (gotTo.position == destinationVector) action = null if (gotTo.position == destinationVector) action = null
@ -420,35 +362,6 @@ class MapUnit {
} }
} }
fun moveToTile(otherTile: TileInfo) {
if(otherTile==getTile()) return // already here!
class CantEnterThisTileException(msg: String) : Exception(msg)
if(!canMoveTo(otherTile))
throw CantEnterThisTileException("$this can't enter $otherTile")
if(type.isAirUnit()){ // they move differently from all other units
action=null
removeFromTile()
putInTile(otherTile)
currentMovement=0f
return
}
val distanceToTiles = getDistanceToTiles()
class YouCantGetThereFromHereException(msg: String) : Exception(msg)
if (!distanceToTiles.containsKey(otherTile))
throw YouCantGetThereFromHereException("$this can't get from ${currentTile.position} to ${otherTile.position}.")
if(otherTile.isCityCenter() && otherTile.getOwner()!=civInfo)
throw Exception("This is an enemy city, you can't go here!")
currentMovement -= distanceToTiles[otherTile]!!
if (currentMovement < 0.1) currentMovement = 0f // silly floats which are "almost zero"
if(isFortified() || action=="Set Up" || action=="Sleep") action=null // unfortify/setup after moving
removeFromTile()
putInTile(otherTile)
}
fun endTurn() { fun endTurn() {
doPostTurnAction() doPostTurnAction()
@ -464,7 +377,7 @@ class MapUnit {
due = true due = true
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
movementAlgs().teleportToClosestMoveableTile() movement.teleportToClosestMoveableTile()
doPreTurnAction() doPreTurnAction()
} }
@ -483,7 +396,7 @@ class MapUnit {
fun putInTile(tile:TileInfo){ fun putInTile(tile:TileInfo){
when { when {
!canMoveTo(tile) -> throw Exception("I can't go there!") !movement.canMoveTo(tile) -> throw Exception("I can't go there!")
type.isAirUnit() -> tile.airUnits.add(this) type.isAirUnit() -> tile.airUnits.add(this)
type.isCivilian() -> tile.civilianUnit=this type.isCivilian() -> tile.civilianUnit=this
else -> tile.militaryUnit=this else -> tile.militaryUnit=this

View File

@ -76,9 +76,9 @@ class TileMap {
val unit = GameBasics.Units[unitName]!!.getMapUnit() val unit = GameBasics.Units[unitName]!!.getMapUnit()
val tilesInDistance = getTilesInDistance(position, 2) val tilesInDistance = getTilesInDistance(position, 2)
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
var unitToPlaceTile = tilesInDistance.firstOrNull { unit.canMoveTo(it) && (unit.type.isWaterUnit() || it.isLand) } var unitToPlaceTile = tilesInDistance.firstOrNull { unit.movement.canMoveTo(it) && (unit.type.isWaterUnit() || it.isLand) }
if (unitToPlaceTile==null) if (unitToPlaceTile==null)
unitToPlaceTile = tilesInDistance.firstOrNull { unit.canMoveTo(it) } unitToPlaceTile = tilesInDistance.firstOrNull { unit.movement.canMoveTo(it) }
if(unitToPlaceTile!=null) { //see if a land unit can be placed on land. if impossible, put it on water. if(unitToPlaceTile!=null) { //see if a land unit can be placed on land. if impossible, put it on water.
// 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

View File

@ -5,8 +5,6 @@ import com.unciv.Constants
import com.unciv.logic.civilization.CivilizationInfo import com.unciv.logic.civilization.CivilizationInfo
class UnitMovementAlgorithms(val unit:MapUnit) { class UnitMovementAlgorithms(val unit:MapUnit) {
val tileMap = unit.getTile().tileMap
private fun getMovementCostBetweenAdjacentTiles(from: TileInfo, to: TileInfo, civInfo: CivilizationInfo): Float { private fun getMovementCostBetweenAdjacentTiles(from: TileInfo, to: TileInfo, civInfo: CivilizationInfo): Float {
var cost = getMovementCostBetweenAdjacentTiles(from,to) var cost = getMovementCostBetweenAdjacentTiles(from,to)
@ -47,7 +45,7 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
fun getDistanceToTilesWithinTurn(origin: Vector2, unitMovement: Float): HashMap<TileInfo, Float> { fun getDistanceToTilesWithinTurn(origin: Vector2, unitMovement: Float): HashMap<TileInfo, Float> {
if(unitMovement==0f) return hashMapOf() if(unitMovement==0f) return hashMapOf()
val distanceToTiles = LinkedHashMap<TileInfo, Float>() val distanceToTiles = LinkedHashMap<TileInfo, Float>()
val unitTile = tileMap[origin] val unitTile = unit.getTile().tileMap[origin]
distanceToTiles[unitTile] = 0f distanceToTiles[unitTile] = 0f
var tilesToCheck = listOf(unitTile) var tilesToCheck = listOf(unitTile)
@ -57,7 +55,7 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
for (neighbor in tileToCheck.neighbors) { for (neighbor in tileToCheck.neighbors) {
var totalDistanceToTile:Float var totalDistanceToTile:Float
if (!unit.canPassThrough(neighbor)) if (!canPassThrough(neighbor))
totalDistanceToTile = unitMovement // Can't go here. totalDistanceToTile = unitMovement // Can't go here.
// The reason that we don't just "return" is so that when calculating how to reach an enemy, // The reason that we don't just "return" is so that when calculating how to reach an enemy,
// You need to assume his tile is reachable, otherwise all movement algs on reaching enemy // You need to assume his tile is reachable, otherwise all movement algs on reaching enemy
@ -106,7 +104,7 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
distanceToDestination[tileToCheck] = distanceToTilesThisTurn[reachableTile]!! distanceToDestination[tileToCheck] = distanceToTilesThisTurn[reachableTile]!!
else { else {
if (movementTreeParents.containsKey(reachableTile)) continue // We cannot be faster than anything existing... if (movementTreeParents.containsKey(reachableTile)) continue // We cannot be faster than anything existing...
if (!unit.canMoveTo(reachableTile)) continue // This is a tile that we can''t actually enter - either an intermediary tile containing our unit, or an enemy unit/city if (!canMoveTo(reachableTile)) continue // This is a tile that we can''t actually enter - either an intermediary tile containing our unit, or an enemy unit/city
movementTreeParents[reachableTile] = tileToCheck movementTreeParents[reachableTile] = tileToCheck
newTilesToCheck.add(reachableTile) newTilesToCheck.add(reachableTile)
} }
@ -144,15 +142,15 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
if (currentTile == destination) return currentTile if (currentTile == destination) return currentTile
if(unit.type.isAirUnit()){ if(unit.type.isAirUnit()){
unit.moveToTile(destination) moveToTile(destination)
return destination return destination
} }
val distanceToTiles = unit.getDistanceToTiles() val distanceToTiles = getDistanceToTiles()
val destinationTileThisTurn: TileInfo val destinationTileThisTurn: TileInfo
if (distanceToTiles.containsKey(destination)) { // we can get there this turn if (distanceToTiles.containsKey(destination)) { // we can get there this turn
if (unit.canMoveTo(destination)) if (canMoveTo(destination))
destinationTileThisTurn = destination destinationTileThisTurn = destination
else // Someone is blocking to the path to the final tile... else // Someone is blocking to the path to the final tile...
{ {
@ -161,7 +159,7 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
return currentTile return currentTile
val reachableDestinationNeighbors = destinationNeighbors val reachableDestinationNeighbors = destinationNeighbors
.filter { distanceToTiles.containsKey(it) && unit.canMoveTo(it) } .filter { distanceToTiles.containsKey(it) && canMoveTo(it) }
if (reachableDestinationNeighbors.isEmpty()) // We can't get closer... if (reachableDestinationNeighbors.isEmpty()) // We can't get closer...
return currentTile return currentTile
@ -174,7 +172,7 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
destinationTileThisTurn = path.first() destinationTileThisTurn = path.first()
} }
unit.moveToTile(destinationTileThisTurn) moveToTile(destinationTileThisTurn)
return destinationTileThisTurn return destinationTileThisTurn
} }
@ -186,7 +184,7 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
fun getFullPathToCloseTile(destination: TileInfo): List<TileInfo> { fun getFullPathToCloseTile(destination: TileInfo): List<TileInfo> {
val currentUnitTile = unit.getTile() val currentUnitTile = unit.getTile()
val distanceToTiles = unit.getDistanceToTiles() val distanceToTiles = getDistanceToTiles()
val reversedList = ArrayList<TileInfo>() val reversedList = ArrayList<TileInfo>()
var currentTile = destination var currentTile = destination
while(currentTile != currentUnitTile){ while(currentTile != currentUnitTile){
@ -209,7 +207,7 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
while(allowedTile==null && distance<5){ while(allowedTile==null && distance<5){
distance++ distance++
allowedTile = unit.getTile().getTilesAtDistance(distance) allowedTile = unit.getTile().getTilesAtDistance(distance)
.firstOrNull{unit.canMoveTo(it)} .firstOrNull{canMoveTo(it)}
} }
// No tile within 4 spaces? move him to a city. // No tile within 4 spaces? move him to a city.
@ -217,7 +215,7 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
if(allowedTile==null){ if(allowedTile==null){
for(city in unit.civInfo.cities){ for(city in unit.civInfo.cities){
allowedTile = city.getCenterTile().getTilesInDistance(1) allowedTile = city.getCenterTile().getTilesInDistance(1)
.firstOrNull { unit.canMoveTo(it) } .firstOrNull { canMoveTo(it) }
if(allowedTile!=null) break if(allowedTile!=null) break
} }
} }
@ -226,4 +224,90 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
unit.putInTile(allowedTile) unit.putInTile(allowedTile)
} }
fun moveToTile(otherTile: TileInfo) {
if(otherTile==unit.getTile()) return // already here!
class CantEnterThisTileException(msg: String) : Exception(msg)
if(!canMoveTo(otherTile))
throw CantEnterThisTileException("$this can't enter $otherTile")
if(unit.type.isAirUnit()){ // they move differently from all other units
unit.action=null
unit.removeFromTile()
unit.putInTile(otherTile)
unit.currentMovement=0f
return
}
val distanceToTiles = getDistanceToTiles()
class YouCantGetThereFromHereException(msg: String) : Exception(msg)
if (!distanceToTiles.containsKey(otherTile))
throw YouCantGetThereFromHereException("$unit can't get from ${unit.currentTile.position} to ${otherTile.position}.")
if(otherTile.isCityCenter() && otherTile.getOwner()!=unit.civInfo)
throw Exception("This is an enemy city, you can't go here!")
unit.currentMovement -= distanceToTiles[otherTile]!!
if (unit.currentMovement < 0.1) unit.currentMovement = 0f // silly floats which are "almost zero"
if(unit.isFortified() || unit.action=="Set Up" || unit.action=="Sleep")
unit.action=null // unfortify/setup after moving
unit.removeFromTile()
unit.putInTile(otherTile)
}
/**
* Designates whether we can enter the tile - without attacking
* DOES NOT designate whether we can reach that tile in the current turn
*/
fun canMoveTo(tile: TileInfo): Boolean {
if(unit.type.isAirUnit())
return tile.airUnits.size<6 && tile.isCityCenter() && tile.getCity()?.civInfo==unit.civInfo
if(!canPassThrough(tile)) return false
if (unit.type.isCivilian())
return tile.civilianUnit==null && (tile.militaryUnit==null || tile.militaryUnit!!.owner==unit.owner)
else return tile.militaryUnit==null && (tile.civilianUnit==null || tile.civilianUnit!!.owner==unit.owner)
}
// This is the most called function in the entire game,
// so multiple callees of this function have been optimized,
// because optimization on this function results in massive benefits!
fun canPassThrough(tile: TileInfo):Boolean{
if(tile.getBaseTerrain().impassable) return false
if(tile.isLand && unit.type.isWaterUnit() && !tile.isCityCenter())
return false
if(tile.isWater && unit.type.isLandUnit()){
if(!unit.civInfo.tech.unitsCanEmbark) return false
if(tile.isOcean && !unit.civInfo.tech.embarkedUnitsCanEnterOcean)
return false
}
if(tile.isOcean && unit.baseUnit.uniques.contains("Cannot enter ocean tiles")) return false
if(tile.isOcean && unit.baseUnit.uniques.contains("Cannot enter ocean tiles until Astronomy")
&& !unit.civInfo.tech.isResearched("Astronomy"))
return false
val tileOwner = tile.getOwner()
if(tileOwner!=null && tileOwner.civName!=unit.owner) {
if (tile.isCityCenter()) return false
if (!unit.civInfo.canEnterTiles(tileOwner)
&& !(unit.civInfo.isPlayerCivilization() && tileOwner.isCityState())) return false
// AIs won't enter city-state's border.
}
val unitsInTile = tile.getUnits()
if(unitsInTile.isNotEmpty()){
val firstUnit = unitsInTile.first()
if(firstUnit.civInfo != unit.civInfo && unit.civInfo.isAtWarWith(firstUnit.civInfo))
return false
}
return true
}
fun getDistanceToTiles() = getDistanceToTilesWithinTurn(unit.currentTile.position,unit.currentMovement)
} }

View File

@ -52,13 +52,13 @@ class BuildLongRoadAction(
// independent of movement costs, but should respect impassable terrain like water and enemy territory // independent of movement costs, but should respect impassable terrain like water and enemy territory
private fun stepForward(destination: TileInfo): Boolean { private fun stepForward(destination: TileInfo): Boolean {
var success = false var success = false
val tilesUnitCanCurrentlyReach = unit.getDistanceToTiles().keys val tilesUnitCanCurrentlyReach = unit.movement.getDistanceToTiles().keys
for (step in getPath(destination).drop(1)) { for (step in getPath(destination).drop(1)) {
if(step !in tilesUnitCanCurrentlyReach) return false // we're out of tiles in reachable distance, no need to check any further if(step !in tilesUnitCanCurrentlyReach) return false // we're out of tiles in reachable distance, no need to check any further
if (unit.currentMovement > 0f) { if (unit.currentMovement > 0f) {
if(unit.canMoveTo(step)) { if(unit.movement.canMoveTo(step)) {
unit.moveToTile(step) unit.movement.moveToTile(step)
success = true success = true
// if there is a road already, take multiple steps, otherwise this is where we're going to build a road // if there is a road already, take multiple steps, otherwise this is where we're going to build a road
if (!isRoadFinished(step)) return true if (!isRoadFinished(step)) return true
@ -83,7 +83,7 @@ class BuildLongRoadAction(
.getPathTo(destination).reversed() .getPathTo(destination).reversed()
} }
private fun isRoadableTile(it: TileInfo) = it.isLand && unit.canPassThrough(it) private fun isRoadableTile(it: TileInfo) = it.isLand && unit.movement.canPassThrough(it)
private fun startWorkingOnRoad(): Boolean { private fun startWorkingOnRoad(): Boolean {
val tile = unit.currentTile val tile = unit.currentTile

View File

@ -94,7 +94,7 @@ class TradeLogic(val ourCivilization:CivilizationInfo, val otherCivilization: Ci
if(offer.type== TradeType.City){ if(offer.type== TradeType.City){
val city = from.cities.first { it.name==offer.name } val city = from.cities.first { it.name==offer.name }
city.moveToCiv(to) city.moveToCiv(to)
city.getCenterTile().getUnits().forEach { it.movementAlgs().teleportToClosestMoveableTile() } city.getCenterTile().getUnits().forEach { it.movement.teleportToClosestMoveableTile() }
to.updateViewableTiles() to.updateViewableTiles()
from.updateViewableTiles() from.updateViewableTiles()
} }

View File

@ -102,7 +102,7 @@ class TileMapHolder(internal val worldScreen: WorldScreen, internal val tileMap:
val newSelectedUnit = unitTable.selectedUnit val newSelectedUnit = unitTable.selectedUnit
if (previousSelectedUnit != null && previousSelectedUnit.getTile() != tileInfo if (previousSelectedUnit != null && previousSelectedUnit.getTile() != tileInfo
&& previousSelectedUnit.canMoveTo(tileInfo) && previousSelectedUnit.movementAlgs().canReach(tileInfo)) { && previousSelectedUnit.movement.canMoveTo(tileInfo) && previousSelectedUnit.movement.canReach(tileInfo)) {
// this can take a long time, because of the unit-to-tile calculation needed, so we put it in a different thread // this can take a long time, because of the unit-to-tile calculation needed, so we put it in a different thread
addTileOverlaysWithUnitMovement(previousSelectedUnit, tileInfo) addTileOverlaysWithUnitMovement(previousSelectedUnit, tileInfo)
} }
@ -132,12 +132,12 @@ class TileMapHolder(internal val worldScreen: WorldScreen, internal val tileMap:
* so that and that alone will be relegated to the concurrent thread. * so that and that alone will be relegated to the concurrent thread.
*/ */
val turnsToGetThere = if(selectedUnit.type.isAirUnit()) 1 val turnsToGetThere = if(selectedUnit.type.isAirUnit()) 1
else selectedUnit.movementAlgs().getShortestPath(tileInfo).size // this is what takes the most time, tbh else selectedUnit.movement.getShortestPath(tileInfo).size // this is what takes the most time, tbh
Gdx.app.postRunnable { Gdx.app.postRunnable {
if(UnCivGame.Current.settings.singleTapMove && turnsToGetThere==1) { if(UnCivGame.Current.settings.singleTapMove && turnsToGetThere==1) {
// single turn instant move // single turn instant move
selectedUnit.movementAlgs().headTowards(tileInfo) selectedUnit.movement.headTowards(tileInfo)
worldScreen.bottomBar.unitTable.selectedUnit = selectedUnit // keep moved unit selected worldScreen.bottomBar.unitTable.selectedUnit = selectedUnit // keep moved unit selected
} else { } else {
// add "move to" button // add "move to" button
@ -287,14 +287,14 @@ class TileMapHolder(internal val worldScreen: WorldScreen, internal val tileMap:
val isAirUnit = unit.type.isAirUnit() val isAirUnit = unit.type.isAirUnit()
val tilesInMoveRange = if(isAirUnit) unit.getTile().getTilesInDistance(unit.getRange()) val tilesInMoveRange = if(isAirUnit) unit.getTile().getTilesInDistance(unit.getRange())
else unit.getDistanceToTiles().keys else unit.movement.getDistanceToTiles().keys
if(isAirUnit) if(isAirUnit)
for(tile in tilesInMoveRange) for(tile in tilesInMoveRange)
tileGroups[tile]!!.showCircle(Color.BLUE,0.3f) tileGroups[tile]!!.showCircle(Color.BLUE,0.3f)
for (tile: TileInfo in tilesInMoveRange) for (tile: TileInfo in tilesInMoveRange)
if (unit.canMoveTo(tile)) if (unit.movement.canMoveTo(tile))
tileGroups[tile]!!.showCircle(Color.WHITE, tileGroups[tile]!!.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)
@ -302,7 +302,7 @@ class TileMapHolder(internal val worldScreen: WorldScreen, internal val tileMap:
val unitType = unit.type val unitType = unit.type
val attackableTiles: List<TileInfo> = if (unitType.isCivilian()) listOf() val attackableTiles: List<TileInfo> = if (unitType.isCivilian()) listOf()
else { else {
val tiles = UnitAutomation().getAttackableEnemies(unit, unit.getDistanceToTiles()).map { it.tileToAttack } val tiles = UnitAutomation().getAttackableEnemies(unit, unit.movement.getDistanceToTiles()).map { it.tileToAttack }
tiles.filter { (UnCivGame.Current.viewEntireMapForDebug || playerViewableTilePositions.contains(it.position)) } tiles.filter { (UnCivGame.Current.viewEntireMapForDebug || playerViewableTilePositions.contains(it.position)) }
} }

View File

@ -146,7 +146,7 @@ class BattleTable(val worldScreen: WorldScreen): Table() {
if (attacker.canAttack()) { if (attacker.canAttack()) {
if (attacker is MapUnitCombatant) { if (attacker is MapUnitCombatant) {
attackableEnemy = UnitAutomation() attackableEnemy = UnitAutomation()
.getAttackableEnemies(attacker.unit, attacker.unit.getDistanceToTiles()) .getAttackableEnemies(attacker.unit, attacker.unit.movement.getDistanceToTiles())
.firstOrNull{ it.tileToAttack == defender.getTile()} .firstOrNull{ it.tileToAttack == defender.getTile()}
} }
else if (attacker is CityCombatant) else if (attacker is CityCombatant)

View File

@ -5,13 +5,13 @@ import com.badlogic.gdx.scenes.scene2d.ui.Button
import com.badlogic.gdx.scenes.scene2d.ui.VerticalGroup import com.badlogic.gdx.scenes.scene2d.ui.VerticalGroup
import com.unciv.logic.map.MapUnit import com.unciv.logic.map.MapUnit
import com.unciv.logic.map.TileInfo import com.unciv.logic.map.TileInfo
import com.unciv.logic.map.action.BuildLongRoadAction
import com.unciv.logic.map.action.MapUnitAction
import com.unciv.ui.utils.CameraStageBaseScreen import com.unciv.ui.utils.CameraStageBaseScreen
import com.unciv.ui.utils.ImageGetter import com.unciv.ui.utils.ImageGetter
import com.unciv.ui.utils.Sounds import com.unciv.ui.utils.Sounds
import com.unciv.ui.utils.onClick import com.unciv.ui.utils.onClick
import com.unciv.ui.worldscreen.TileMapHolder import com.unciv.ui.worldscreen.TileMapHolder
import com.unciv.logic.map.action.BuildLongRoadAction
import com.unciv.logic.map.action.MapUnitAction
import kotlin.concurrent.thread import kotlin.concurrent.thread
class UnitContextMenu(val tileMapHolder: TileMapHolder, val selectedUnit: MapUnit, val targetTile: TileInfo) : VerticalGroup() { class UnitContextMenu(val tileMapHolder: TileMapHolder, val selectedUnit: MapUnit, val targetTile: TileInfo) : VerticalGroup() {
@ -57,14 +57,14 @@ class UnitContextMenu(val tileMapHolder: TileMapHolder, val selectedUnit: MapUni
fun onMoveButtonClick() { fun onMoveButtonClick() {
// this can take a long time, because of the unit-to-tile calculation needed, so we put it in a different thread // this can take a long time, because of the unit-to-tile calculation needed, so we put it in a different thread
thread { thread {
if (selectedUnit.movementAlgs().canReach(targetTile)) { if (selectedUnit.movement.canReach(targetTile)) {
try { try {
// Because this is darned concurrent (as it MUST be to avoid ANRs), // Because this is darned concurrent (as it MUST be to avoid ANRs),
// there are edge cases where the canReach is true, // there are edge cases where the canReach is true,
// but until it reaches the headTowards the board has changed and so the headTowards fails. // but until it reaches the headTowards the board has changed and so the headTowards fails.
// I can't think of any way to avoid this, // I can't think of any way to avoid this,
// but it's so rare and edge-case-y that ignoring its failure is actually acceptable, hence the empty catch // but it's so rare and edge-case-y that ignoring its failure is actually acceptable, hence the empty catch
selectedUnit.movementAlgs().headTowards(targetTile) selectedUnit.movement.headTowards(targetTile)
Sounds.play("whoosh") Sounds.play("whoosh")
if (selectedUnit.currentTile != targetTile) if (selectedUnit.currentTile != targetTile)
selectedUnit.action = "moveTo " + targetTile.position.x.toInt() + "," + targetTile.position.y.toInt() selectedUnit.action = "moveTo " + targetTile.position.x.toInt() + "," + targetTile.position.y.toInt()