Code Org - Moved more Unit movement things into UnitMovementAlgorithms
This commit is contained in:
parent
e78f408bff
commit
00e112e668
15 changed files with 198 additions and 201 deletions
|
@ -19,12 +19,12 @@ class SpecificUnitAutomation{
|
|||
|
||||
fun automateWorkBoats(unit: MapUnit) {
|
||||
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) }
|
||||
.firstOrNull { unit.movementAlgs().canReach(it) }
|
||||
.firstOrNull { unit.movement.canReach(it) }
|
||||
|
||||
if (closestReachableResource != null) {
|
||||
unit.movementAlgs().headTowards(closestReachableResource)
|
||||
unit.movement.headTowards(closestReachableResource)
|
||||
if (unit.currentMovement > 0 && unit.currentTile == closestReachableResource) {
|
||||
val createImprovementAction = UnitActions().getUnitActions(unit, UnCivGame.Current.worldScreen)
|
||||
.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"
|
||||
}
|
||||
}
|
||||
else UnitAutomation().tryExplore(unit, unit.getDistanceToTiles())
|
||||
else UnitAutomation().tryExplore(unit, unit.movement.getDistanceToTiles())
|
||||
}
|
||||
|
||||
fun automateGreatGeneral(unit: MapUnit){
|
||||
//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
|
||||
militant != null && militant.civInfo == unit.civInfo
|
||||
&& (it.civilianUnit == null || it.civilianUnit == unit)
|
||||
|
@ -49,17 +49,17 @@ class SpecificUnitAutomation{
|
|||
val militaryUnit = it.militaryUnit
|
||||
militaryUnit!=null && militaryUnit.civInfo==unit.civInfo
|
||||
} }
|
||||
unit.movementAlgs().headTowards(tilesSortedByAffectedTroops.first())
|
||||
unit.movement.headTowards(tilesSortedByAffectedTroops.first())
|
||||
return
|
||||
}
|
||||
|
||||
//if no unit to follow, take refuge in city.
|
||||
val cityToGarrison = unit.civInfo.cities.map {it.getCenterTile()}
|
||||
.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) {
|
||||
unit.movementAlgs().headTowards(cityToGarrison)
|
||||
unit.movement.headTowards(cityToGarrison)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -100,16 +100,16 @@ class SpecificUnitAutomation{
|
|||
.associateBy ( {it},{ Automation().rankTile(it,unit.civInfo) })
|
||||
|
||||
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
|
||||
.asSequence()
|
||||
.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(UnitAutomation().tryExplore(unit, unit.getDistanceToTiles())) return // try to find new areas
|
||||
UnitAutomation().wander(unit, unit.getDistanceToTiles()) // go around aimlessly
|
||||
if(UnitAutomation().tryExplore(unit, unit.movement.getDistanceToTiles())) return // try to find new areas
|
||||
UnitAutomation().wander(unit, unit.movement.getDistanceToTiles()) // go around aimlessly
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -119,7 +119,7 @@ class SpecificUnitAutomation{
|
|||
if (unit.getTile() == bestCityLocation)
|
||||
UnitActions().getUnitActions(unit, UnCivGame.Current.worldScreen).first { it.name == "Found city" }.action()
|
||||
else {
|
||||
unit.movementAlgs().headTowards(bestCityLocation)
|
||||
unit.movement.headTowards(bestCityLocation)
|
||||
if (unit.currentMovement > 0 && unit.getTile() == bestCityLocation)
|
||||
UnitActions().getUnitActions(unit, UnCivGame.Current.worldScreen).first { it.name == "Found city" }.action()
|
||||
}
|
||||
|
@ -136,24 +136,24 @@ class SpecificUnitAutomation{
|
|||
stats.toHashMap()[relatedStat]!!
|
||||
}
|
||||
for(city in citiesByStatBoost){
|
||||
val pathToCity =unit.movementAlgs().getShortestPath(city.getCenterTile())
|
||||
val pathToCity =unit.movement.getShortestPath(city.getCenterTile())
|
||||
if(pathToCity.isEmpty()) continue
|
||||
if(pathToCity.size>2){
|
||||
unit.movementAlgs().headTowards(city.getCenterTile())
|
||||
unit.movement.headTowards(city.getCenterTile())
|
||||
return
|
||||
}
|
||||
|
||||
// if we got here, we're pretty close, start looking!
|
||||
val tiles = city.getTiles().asSequence()
|
||||
.filter { (unit.canMoveTo(it) || unit.currentTile==it)
|
||||
.filter { (unit.movement.canMoveTo(it) || unit.currentTile==it)
|
||||
&& it.isLand
|
||||
&& !it.isCityCenter()
|
||||
&& it.resource==null }
|
||||
.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
|
||||
|
||||
unit.movementAlgs().headTowards(chosenTile)
|
||||
unit.movement.headTowards(chosenTile)
|
||||
if(unit.currentTile==chosenTile && unit.currentMovement>0)
|
||||
UnitActions().getUnitActions(unit, UnCivGame.Current.worldScreen)
|
||||
.first { it.name.startsWith("Create") }.action()
|
||||
|
@ -171,12 +171,12 @@ class SpecificUnitAutomation{
|
|||
if(UnitAutomation().tryAttackNearbyEnemy(unit)) return
|
||||
|
||||
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){
|
||||
if(city.getTilesInDistance(unit.getRange())
|
||||
.any { UnitAutomation().containsAttackableEnemy(it,MapUnitCombatant(unit)) }) {
|
||||
unit.moveToTile(city)
|
||||
unit.movement.moveToTile(city)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ class UnitAutomation{
|
|||
}
|
||||
|
||||
val unitActions = UnitActions().getUnitActions(unit,UnCivGame.Current.worldScreen)
|
||||
var unitDistanceToTiles = unit.getDistanceToTiles()
|
||||
var unitDistanceToTiles = unit.movement.getDistanceToTiles()
|
||||
|
||||
if(unit.civInfo.isBarbarianCivilization() &&
|
||||
unit.currentTile.improvement==Constants.barbarianEncampment && unit.type.isLandUnit()) {
|
||||
|
@ -52,7 +52,7 @@ class UnitAutomation{
|
|||
|
||||
if(tryGoToRuin(unit,unitDistanceToTiles)){
|
||||
if(unit.currentMovement==0f) return
|
||||
unitDistanceToTiles = unit.getDistanceToTiles()
|
||||
unitDistanceToTiles = unit.movement.getDistanceToTiles()
|
||||
}
|
||||
|
||||
if (tryUpgradeUnit(unit, unitActions)) return
|
||||
|
@ -104,15 +104,15 @@ class UnitAutomation{
|
|||
val encampmentsCloseToCities
|
||||
= knownEncampments.filter { cities.any { city -> city.getCenterTile().arialDistanceTo(it) < 6 } }
|
||||
.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
|
||||
unit.movementAlgs().headTowards(encampmentToHeadTowards)
|
||||
unit.movement.headTowards(encampmentToHeadTowards)
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
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...
|
||||
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
|
||||
val reachableCityTile = unit.civInfo.cities.map { it.getCenterTile() }
|
||||
.sortedBy { it.arialDistanceTo(unit.currentTile) }
|
||||
.firstOrNull{unit.movementAlgs().canReach(it)}
|
||||
if(reachableCityTile!=null) unit.movementAlgs().headTowards(reachableCityTile)
|
||||
.firstOrNull{unit.movement.canReach(it)}
|
||||
if(reachableCityTile!=null) unit.movement.headTowards(reachableCityTile)
|
||||
else wander(unit,unitDistanceToTiles)
|
||||
return true
|
||||
}
|
||||
|
@ -136,7 +136,7 @@ class UnitAutomation{
|
|||
if(bestTileForHealingRank == 0) return false // can't actually heal here...
|
||||
|
||||
if(unitTile!=bestTileForHealing && bestTileForHealingRank > unit.rankTileForHealing(unitTile))
|
||||
unit.moveToTile(bestTileForHealing)
|
||||
unit.movement.moveToTile(bestTileForHealing)
|
||||
|
||||
if(unit.currentMovement>0 && unit.canFortify()) unit.fortify()
|
||||
return true
|
||||
|
@ -145,12 +145,12 @@ class UnitAutomation{
|
|||
fun tryPillageImprovement(unit: MapUnit, unitDistanceToTiles: HashMap<TileInfo, Float>) : Boolean {
|
||||
if(unit.type.isCivilian()) return false
|
||||
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
|
||||
val tileToPillage = tilesInDistance.maxBy { it.getDefensiveBonus() }!!
|
||||
if (unit.getTile()!=tileToPillage)
|
||||
unit.moveToTile(tileToPillage)
|
||||
unit.movement.moveToTile(tileToPillage)
|
||||
|
||||
UnitActions().getUnitActions(unit, UnCivGame.Current.worldScreen)
|
||||
.first { it.name == "Pillage" }.action()
|
||||
|
@ -207,7 +207,7 @@ class UnitAutomation{
|
|||
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(...)
|
||||
.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
|
||||
val tilesInAttackRange =
|
||||
|
@ -227,14 +227,14 @@ class UnitAutomation{
|
|||
private fun tryAdvanceTowardsCloseEnemy(unit: MapUnit): Boolean {
|
||||
// this can be sped up if we check each layer separately
|
||||
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())
|
||||
closeEnemies = closeEnemies.filterNot { it.isCityCenter() && it.getCity()!!.health==1 }
|
||||
|
||||
val closestEnemy = closeEnemies.minBy { it.arialDistanceTo(unit.getTile()) }
|
||||
|
||||
if (closestEnemy != null) {
|
||||
unit.movementAlgs().headTowards(closestEnemy)
|
||||
unit.movement.headTowards(closestEnemy)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
@ -244,9 +244,9 @@ class UnitAutomation{
|
|||
val settlerOrGreatPersonToAccompany = unit.civInfo.getCivUnits()
|
||||
.firstOrNull { val tile = it.currentTile
|
||||
(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
|
||||
unit.movementAlgs().headTowards(settlerOrGreatPersonToAccompany.currentTile)
|
||||
unit.movement.headTowards(settlerOrGreatPersonToAccompany.currentTile)
|
||||
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!
|
||||
unit.civInfo.cities.asSequence().map { cityCenterTile.arialDistanceTo(it.getCenterTile()) }.min()!!
|
||||
}
|
||||
.firstOrNull { unit.movementAlgs().canReach(it) }
|
||||
.firstOrNull { unit.movement.canReach(it) }
|
||||
|
||||
if (closestReachableEnemyCity != null) {
|
||||
val unitDistanceToTiles = unit.getDistanceToTiles()
|
||||
val unitDistanceToTiles = unit.movement.getDistanceToTiles()
|
||||
val tilesInBombardRange = closestReachableEnemyCity.getTilesInDistance(2)
|
||||
val reachableTilesNotInBombardRange = unitDistanceToTiles.keys.filter { it !in tilesInBombardRange }
|
||||
val canMoveIntoBombardRange = tilesInBombardRange.any { unitDistanceToTiles.containsKey(it)}
|
||||
|
@ -293,7 +293,7 @@ class UnitAutomation{
|
|||
.filter { it.isLand }
|
||||
val closestReachableLandingGroundTile = suitableGatheringGroundTiles
|
||||
.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 -
|
||||
// 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
|
||||
unit.movementAlgs().headTowards(tileToHeadTo)
|
||||
unit.movement.headTowards(tileToHeadTo)
|
||||
|
||||
else{
|
||||
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
|
||||
if(tilesCanAttackFromButNotInBombardRange.any())
|
||||
unit.movementAlgs().headTowards(tilesCanAttackFromButNotInBombardRange.minBy { unitDistanceToTiles[it]!! }!!)
|
||||
unit.movement.headTowards(tilesCanAttackFromButNotInBombardRange.minBy { unitDistanceToTiles[it]!! }!!)
|
||||
}
|
||||
else {
|
||||
// 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)
|
||||
}
|
||||
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)
|
||||
|
||||
if (enemyTileToAttackNextTurn != null) {
|
||||
unit.moveToTile(enemyTileToAttackNextTurn.tileToAttackFrom)
|
||||
unit.movement.moveToTile(enemyTileToAttackNextTurn.tileToAttackFrom)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
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
|
||||
.filter {
|
||||
BattleDamage().calculateDamageToAttacker(MapUnitCombatant(unit),
|
||||
|
@ -418,7 +418,7 @@ class UnitAutomation{
|
|||
val citiesWithoutGarrison = unit.civInfo.cities.filter {
|
||||
val centerTile = it.getCenterTile()
|
||||
centerTile.militaryUnit==null
|
||||
&& unit.canMoveTo(centerTile)
|
||||
&& unit.movement.canMoveTo(centerTile)
|
||||
}
|
||||
|
||||
fun isCityThatNeedsDefendingInWartime(city: CityInfo): Boolean {
|
||||
|
@ -445,17 +445,17 @@ class UnitAutomation{
|
|||
|
||||
val closestReachableCityNeedsDefending =citiesToTry
|
||||
.sortedBy{ it.getCenterTile().arialDistanceTo(unit.currentTile) }
|
||||
.firstOrNull { unit.movementAlgs().canReach(it.getCenterTile()) }
|
||||
.firstOrNull { unit.movement.canReach(it.getCenterTile()) }
|
||||
if(closestReachableCityNeedsDefending==null) return false
|
||||
unit.movementAlgs().headTowards(closestReachableCityNeedsDefending.getCenterTile())
|
||||
unit.movement.headTowards(closestReachableCityNeedsDefending.getCenterTile())
|
||||
return true
|
||||
}
|
||||
|
||||
fun tryGoToRuin(unit:MapUnit, unitDistanceToTiles: HashMap<TileInfo, Float>): Boolean {
|
||||
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
|
||||
unit.moveToTile(tileWithRuin)
|
||||
unit.movement.moveToTile(tileWithRuin)
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -466,16 +466,16 @@ class UnitAutomation{
|
|||
}
|
||||
|
||||
for(tile in unit.currentTile.getTilesInDistance(5))
|
||||
if(unit.canMoveTo(tile) && tile.position !in unit.civInfo.exploredTiles
|
||||
&& unit.movementAlgs().canReach(tile)){
|
||||
unit.movementAlgs().headTowards(tile)
|
||||
if(unit.movement.canMoveTo(tile) && tile.position !in unit.civInfo.exploredTiles
|
||||
&& unit.movement.canReach(tile)){
|
||||
unit.movement.headTowards(tile)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun automatedExplore(unit:MapUnit){
|
||||
val unitDistanceToTiles = unit.getDistanceToTiles()
|
||||
val unitDistanceToTiles = unit.movement.getDistanceToTiles()
|
||||
if(tryGoToRuin(unit, unitDistanceToTiles) && unit.currentMovement==0f) return
|
||||
|
||||
if (unit.health < 80) {
|
||||
|
@ -485,10 +485,10 @@ class UnitAutomation{
|
|||
|
||||
for(i in 1..10){
|
||||
val unexploredTilesAtDistance = unit.getTile().getTilesAtDistance(i)
|
||||
.filter { unit.canMoveTo(it) && it.position !in unit.civInfo.exploredTiles
|
||||
&& unit.movementAlgs().canReach(it) }
|
||||
.filter { unit.movement.canMoveTo(it) && it.position !in unit.civInfo.exploredTiles
|
||||
&& unit.movement.canReach(it) }
|
||||
if(unexploredTilesAtDistance.isNotEmpty()){
|
||||
unit.movementAlgs().headTowards(unexploredTilesAtDistance.random())
|
||||
unit.movement.headTowards(unexploredTilesAtDistance.random())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -498,11 +498,11 @@ class UnitAutomation{
|
|||
|
||||
fun wander(unit: MapUnit, unitDistanceToTiles: HashMap<TileInfo, Float>) {
|
||||
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 }
|
||||
if (reachableTilesMaxWalkingDistance.any()) unit.moveToTile(reachableTilesMaxWalkingDistance.toList().random().first)
|
||||
else if (reachableTiles.any()) unit.moveToTile(reachableTiles.toList().random().first)
|
||||
if (reachableTilesMaxWalkingDistance.any()) unit.movement.moveToTile(reachableTilesMaxWalkingDistance.toList().random().first)
|
||||
else if (reachableTiles.any()) unit.movement.moveToTile(reachableTiles.toList().random().first)
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ import com.unciv.models.gamebasics.tile.TileImprovement
|
|||
class WorkerAutomation(val unit: MapUnit) {
|
||||
|
||||
fun automateWorkerAction() {
|
||||
val enemyUnitsInWalkingDistance = unit.getDistanceToTiles().keys
|
||||
val enemyUnitsInWalkingDistance = unit.movement.getDistanceToTiles().keys
|
||||
.filter { it.militaryUnit!=null && it.militaryUnit!!.civInfo!=unit.civInfo
|
||||
&& unit.civInfo.isAtWarWith(it.militaryUnit!!.civInfo) }
|
||||
|
||||
|
@ -26,7 +26,7 @@ class WorkerAutomation(val unit: MapUnit) {
|
|||
}
|
||||
|
||||
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!
|
||||
return
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ class WorkerAutomation(val unit: MapUnit) {
|
|||
if(citiesThatNeedConnecting.isEmpty()) return false // do nothing.
|
||||
|
||||
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()
|
||||
|
||||
val connectedCities = unit.civInfo.cities.filter { it.isCapital() || it.cityStats.isConnectedToCapital(targetRoad) }
|
||||
|
@ -77,10 +77,11 @@ class WorkerAutomation(val unit: MapUnit) {
|
|||
val tileToConstructRoadOn :TileInfo
|
||||
if(unit.currentTile in roadableTiles) tileToConstructRoadOn = unit.currentTile
|
||||
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
|
||||
tileToConstructRoadOn = reachableTiles.minBy { unit.movementAlgs().getShortestPath(it).size }!!
|
||||
unit.movementAlgs().headTowards(tileToConstructRoadOn)
|
||||
tileToConstructRoadOn = reachableTiles.minBy { unit.movement.getShortestPath(it).size }!!
|
||||
unit.movement.headTowards(tileToConstructRoadOn)
|
||||
}
|
||||
if(unit.currentMovement>0 && unit.currentTile==tileToConstructRoadOn
|
||||
&& 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,
|
||||
// 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.
|
||||
val selectedTile = workableTiles.firstOrNull{
|
||||
unit.movementAlgs().canReach(it) }
|
||||
val selectedTile = workableTiles.firstOrNull{unit.movement.canReach(it) }
|
||||
|
||||
if (selectedTile != null
|
||||
&& getPriority(selectedTile, unit.civInfo)>1
|
||||
|
|
|
@ -21,7 +21,7 @@ class Battle(val gameInfo:GameInfo) {
|
|||
|
||||
fun moveAndAttack(attacker: ICombatant, attackableTile: UnitAutomation.AttackableTile){
|
||||
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") {
|
||||
attacker.unit.action = "Set Up"
|
||||
attacker.unit.useMovementPoints(1f)
|
||||
|
@ -124,11 +124,11 @@ class Battle(val gameInfo:GameInfo) {
|
|||
else if (attacker.isMelee()
|
||||
&& (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
|
||||
&& (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
|
||||
if(attackedTile.civilianUnit!=null && attackedTile.civilianUnit!!.civInfo != attacker.getCivInfo())
|
||||
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? {
|
||||
|
|
|
@ -113,7 +113,7 @@ class CityExpansionManager {
|
|||
|
||||
for(unit in tileInfo.getUnits())
|
||||
if(!unit.civInfo.canEnterTiles(cityInfo.civInfo))
|
||||
unit.movementAlgs().teleportToClosestMoveableTile()
|
||||
unit.movement.teleportToClosestMoveableTile()
|
||||
|
||||
cityInfo.civInfo.updateViewableTiles()
|
||||
}
|
||||
|
|
|
@ -241,8 +241,8 @@ class CityInfo {
|
|||
// 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!
|
||||
for(unit in getCenterTile().getUnits())
|
||||
if(!unit.canPassThrough(getCenterTile()))
|
||||
unit.movementAlgs().teleportToClosestMoveableTile()
|
||||
if(!unit.movement.canPassThrough(getCenterTile()))
|
||||
unit.movement.teleportToClosestMoveableTile()
|
||||
|
||||
civInfo.cities = civInfo.cities.toMutableList().apply { remove(this@CityInfo) }
|
||||
getTiles().forEach { expansion.relinquishOwnership(it) }
|
||||
|
|
|
@ -212,7 +212,7 @@ class DiplomacyManager() {
|
|||
|
||||
if(hasOpenBorders && !newHasOpenBorders){ // borders were closed, get out!
|
||||
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()
|
||||
// We get out of their territory
|
||||
for(unit in civInfo.getCivUnits().filter { it.getTile().getOwner()== otherCiv})
|
||||
unit.movementAlgs().teleportToClosestMoveableTile()
|
||||
unit.movement.teleportToClosestMoveableTile()
|
||||
|
||||
// And we get out of theirs
|
||||
for(unit in otherCiv.getCivUnits().filter { it.getTile().getOwner()== civInfo})
|
||||
unit.movementAlgs().teleportToClosestMoveableTile()
|
||||
unit.movement.teleportToClosestMoveableTile()
|
||||
}
|
||||
|
||||
fun hasFlag(flag:DiplomacyFlags) = flagsCountdown.containsKey(flag.name)
|
||||
|
|
|
@ -14,8 +14,6 @@ import com.unciv.models.gamebasics.tile.TerrainType
|
|||
import com.unciv.models.gamebasics.unit.BaseUnit
|
||||
import com.unciv.models.gamebasics.unit.UnitType
|
||||
import java.text.DecimalFormat
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
class MapUnit {
|
||||
|
||||
|
@ -23,6 +21,8 @@ class MapUnit {
|
|||
@Transient lateinit var baseUnit: BaseUnit
|
||||
@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,
|
||||
// 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!
|
||||
|
@ -97,11 +97,6 @@ class MapUnit {
|
|||
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)
|
||||
// will not get counted twice!
|
||||
@Transient var tempUniques= ArrayList<String>()
|
||||
|
@ -160,63 +155,10 @@ class MapUnit {
|
|||
return action!!.split(" ")[1].toInt()
|
||||
}
|
||||
|
||||
fun movementAlgs() = UnitMovementAlgorithms(this)
|
||||
|
||||
override fun toString(): String {
|
||||
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 {
|
||||
if (currentMovement == 0f) return false
|
||||
|
@ -332,7 +274,7 @@ class MapUnit {
|
|||
val currentTile = getTile()
|
||||
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)}
|
||||
if(enemyUnitsInWalkingDistance.isNotEmpty()) {
|
||||
if (mapUnitAction?.shouldStopOnEnemyInSight()==true)
|
||||
|
@ -346,8 +288,8 @@ class MapUnit {
|
|||
val destination = action!!.replace("moveTo ", "").split(",").dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
val destinationVector = Vector2(Integer.parseInt(destination[0]).toFloat(), Integer.parseInt(destination[1]).toFloat())
|
||||
val destinationTile = currentTile.tileMap[destinationVector]
|
||||
if(!movementAlgs().canReach(destinationTile)) return // That tile that we were moving towards is now unreachable
|
||||
val gotTo = movementAlgs().headTowards(destinationTile)
|
||||
if(!movement.canReach(destinationTile)) return // That tile that we were moving towards is now unreachable
|
||||
val gotTo = movement.headTowards(destinationTile)
|
||||
if(gotTo==currentTile) // We didn't move at all
|
||||
return
|
||||
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() {
|
||||
doPostTurnAction()
|
||||
|
@ -464,7 +377,7 @@ class MapUnit {
|
|||
due = true
|
||||
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
|
||||
movementAlgs().teleportToClosestMoveableTile()
|
||||
movement.teleportToClosestMoveableTile()
|
||||
doPreTurnAction()
|
||||
}
|
||||
|
||||
|
@ -483,7 +396,7 @@ class MapUnit {
|
|||
|
||||
fun putInTile(tile:TileInfo){
|
||||
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.isCivilian() -> tile.civilianUnit=this
|
||||
else -> tile.militaryUnit=this
|
||||
|
|
|
@ -76,9 +76,9 @@ class TileMap {
|
|||
val unit = GameBasics.Units[unitName]!!.getMapUnit()
|
||||
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
|
||||
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)
|
||||
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.
|
||||
// only once we know the unit can be placed do we add it to the civ's unit list
|
||||
|
|
|
@ -5,8 +5,6 @@ import com.unciv.Constants
|
|||
import com.unciv.logic.civilization.CivilizationInfo
|
||||
|
||||
class UnitMovementAlgorithms(val unit:MapUnit) {
|
||||
val tileMap = unit.getTile().tileMap
|
||||
|
||||
private fun getMovementCostBetweenAdjacentTiles(from: TileInfo, to: TileInfo, civInfo: CivilizationInfo): Float {
|
||||
var cost = getMovementCostBetweenAdjacentTiles(from,to)
|
||||
|
||||
|
@ -47,7 +45,7 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
|
|||
fun getDistanceToTilesWithinTurn(origin: Vector2, unitMovement: Float): HashMap<TileInfo, Float> {
|
||||
if(unitMovement==0f) return hashMapOf()
|
||||
val distanceToTiles = LinkedHashMap<TileInfo, Float>()
|
||||
val unitTile = tileMap[origin]
|
||||
val unitTile = unit.getTile().tileMap[origin]
|
||||
distanceToTiles[unitTile] = 0f
|
||||
var tilesToCheck = listOf(unitTile)
|
||||
|
||||
|
@ -57,7 +55,7 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
|
|||
for (neighbor in tileToCheck.neighbors) {
|
||||
var totalDistanceToTile:Float
|
||||
|
||||
if (!unit.canPassThrough(neighbor))
|
||||
if (!canPassThrough(neighbor))
|
||||
totalDistanceToTile = unitMovement // Can't go here.
|
||||
// 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
|
||||
|
@ -106,7 +104,7 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
|
|||
distanceToDestination[tileToCheck] = distanceToTilesThisTurn[reachableTile]!!
|
||||
else {
|
||||
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
|
||||
newTilesToCheck.add(reachableTile)
|
||||
}
|
||||
|
@ -144,15 +142,15 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
|
|||
if (currentTile == destination) return currentTile
|
||||
|
||||
if(unit.type.isAirUnit()){
|
||||
unit.moveToTile(destination)
|
||||
moveToTile(destination)
|
||||
return destination
|
||||
}
|
||||
|
||||
val distanceToTiles = unit.getDistanceToTiles()
|
||||
val distanceToTiles = getDistanceToTiles()
|
||||
|
||||
val destinationTileThisTurn: TileInfo
|
||||
if (distanceToTiles.containsKey(destination)) { // we can get there this turn
|
||||
if (unit.canMoveTo(destination))
|
||||
if (canMoveTo(destination))
|
||||
destinationTileThisTurn = destination
|
||||
else // Someone is blocking to the path to the final tile...
|
||||
{
|
||||
|
@ -161,7 +159,7 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
|
|||
return currentTile
|
||||
|
||||
val reachableDestinationNeighbors = destinationNeighbors
|
||||
.filter { distanceToTiles.containsKey(it) && unit.canMoveTo(it) }
|
||||
.filter { distanceToTiles.containsKey(it) && canMoveTo(it) }
|
||||
if (reachableDestinationNeighbors.isEmpty()) // We can't get closer...
|
||||
return currentTile
|
||||
|
||||
|
@ -174,7 +172,7 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
|
|||
destinationTileThisTurn = path.first()
|
||||
}
|
||||
|
||||
unit.moveToTile(destinationTileThisTurn)
|
||||
moveToTile(destinationTileThisTurn)
|
||||
return destinationTileThisTurn
|
||||
}
|
||||
|
||||
|
@ -186,7 +184,7 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
|
|||
|
||||
fun getFullPathToCloseTile(destination: TileInfo): List<TileInfo> {
|
||||
val currentUnitTile = unit.getTile()
|
||||
val distanceToTiles = unit.getDistanceToTiles()
|
||||
val distanceToTiles = getDistanceToTiles()
|
||||
val reversedList = ArrayList<TileInfo>()
|
||||
var currentTile = destination
|
||||
while(currentTile != currentUnitTile){
|
||||
|
@ -209,7 +207,7 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
|
|||
while(allowedTile==null && distance<5){
|
||||
distance++
|
||||
allowedTile = unit.getTile().getTilesAtDistance(distance)
|
||||
.firstOrNull{unit.canMoveTo(it)}
|
||||
.firstOrNull{canMoveTo(it)}
|
||||
}
|
||||
|
||||
// No tile within 4 spaces? move him to a city.
|
||||
|
@ -217,7 +215,7 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
|
|||
if(allowedTile==null){
|
||||
for(city in unit.civInfo.cities){
|
||||
allowedTile = city.getCenterTile().getTilesInDistance(1)
|
||||
.firstOrNull { unit.canMoveTo(it) }
|
||||
.firstOrNull { canMoveTo(it) }
|
||||
if(allowedTile!=null) break
|
||||
}
|
||||
}
|
||||
|
@ -226,4 +224,90 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
|
|||
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)
|
||||
}
|
|
@ -52,13 +52,13 @@ class BuildLongRoadAction(
|
|||
// independent of movement costs, but should respect impassable terrain like water and enemy territory
|
||||
private fun stepForward(destination: TileInfo): Boolean {
|
||||
var success = false
|
||||
val tilesUnitCanCurrentlyReach = unit.getDistanceToTiles().keys
|
||||
val tilesUnitCanCurrentlyReach = unit.movement.getDistanceToTiles().keys
|
||||
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 (unit.currentMovement > 0f) {
|
||||
if(unit.canMoveTo(step)) {
|
||||
unit.moveToTile(step)
|
||||
if(unit.movement.canMoveTo(step)) {
|
||||
unit.movement.moveToTile(step)
|
||||
success = true
|
||||
// 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
|
||||
|
@ -83,7 +83,7 @@ class BuildLongRoadAction(
|
|||
.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 {
|
||||
val tile = unit.currentTile
|
||||
|
|
|
@ -94,7 +94,7 @@ class TradeLogic(val ourCivilization:CivilizationInfo, val otherCivilization: Ci
|
|||
if(offer.type== TradeType.City){
|
||||
val city = from.cities.first { it.name==offer.name }
|
||||
city.moveToCiv(to)
|
||||
city.getCenterTile().getUnits().forEach { it.movementAlgs().teleportToClosestMoveableTile() }
|
||||
city.getCenterTile().getUnits().forEach { it.movement.teleportToClosestMoveableTile() }
|
||||
to.updateViewableTiles()
|
||||
from.updateViewableTiles()
|
||||
}
|
||||
|
|
|
@ -102,7 +102,7 @@ class TileMapHolder(internal val worldScreen: WorldScreen, internal val tileMap:
|
|||
val newSelectedUnit = unitTable.selectedUnit
|
||||
|
||||
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
|
||||
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.
|
||||
*/
|
||||
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 {
|
||||
if(UnCivGame.Current.settings.singleTapMove && turnsToGetThere==1) {
|
||||
// single turn instant move
|
||||
selectedUnit.movementAlgs().headTowards(tileInfo)
|
||||
selectedUnit.movement.headTowards(tileInfo)
|
||||
worldScreen.bottomBar.unitTable.selectedUnit = selectedUnit // keep moved unit selected
|
||||
} else {
|
||||
// add "move to" button
|
||||
|
@ -287,14 +287,14 @@ class TileMapHolder(internal val worldScreen: WorldScreen, internal val tileMap:
|
|||
|
||||
val isAirUnit = unit.type.isAirUnit()
|
||||
val tilesInMoveRange = if(isAirUnit) unit.getTile().getTilesInDistance(unit.getRange())
|
||||
else unit.getDistanceToTiles().keys
|
||||
else unit.movement.getDistanceToTiles().keys
|
||||
|
||||
if(isAirUnit)
|
||||
for(tile in tilesInMoveRange)
|
||||
tileGroups[tile]!!.showCircle(Color.BLUE,0.3f)
|
||||
|
||||
for (tile: TileInfo in tilesInMoveRange)
|
||||
if (unit.canMoveTo(tile))
|
||||
if (unit.movement.canMoveTo(tile))
|
||||
tileGroups[tile]!!.showCircle(Color.WHITE,
|
||||
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 attackableTiles: List<TileInfo> = if (unitType.isCivilian()) listOf()
|
||||
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)) }
|
||||
}
|
||||
|
||||
|
|
|
@ -146,7 +146,7 @@ class BattleTable(val worldScreen: WorldScreen): Table() {
|
|||
if (attacker.canAttack()) {
|
||||
if (attacker is MapUnitCombatant) {
|
||||
attackableEnemy = UnitAutomation()
|
||||
.getAttackableEnemies(attacker.unit, attacker.unit.getDistanceToTiles())
|
||||
.getAttackableEnemies(attacker.unit, attacker.unit.movement.getDistanceToTiles())
|
||||
.firstOrNull{ it.tileToAttack == defender.getTile()}
|
||||
}
|
||||
else if (attacker is CityCombatant)
|
||||
|
|
|
@ -5,13 +5,13 @@ import com.badlogic.gdx.scenes.scene2d.ui.Button
|
|||
import com.badlogic.gdx.scenes.scene2d.ui.VerticalGroup
|
||||
import com.unciv.logic.map.MapUnit
|
||||
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.ImageGetter
|
||||
import com.unciv.ui.utils.Sounds
|
||||
import com.unciv.ui.utils.onClick
|
||||
import com.unciv.ui.worldscreen.TileMapHolder
|
||||
import com.unciv.logic.map.action.BuildLongRoadAction
|
||||
import com.unciv.logic.map.action.MapUnitAction
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
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() {
|
||||
// this can take a long time, because of the unit-to-tile calculation needed, so we put it in a different thread
|
||||
thread {
|
||||
if (selectedUnit.movementAlgs().canReach(targetTile)) {
|
||||
if (selectedUnit.movement.canReach(targetTile)) {
|
||||
try {
|
||||
// Because this is darned concurrent (as it MUST be to avoid ANRs),
|
||||
// there are edge cases where the canReach is true,
|
||||
// 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,
|
||||
// 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")
|
||||
if (selectedUnit.currentTile != targetTile)
|
||||
selectedUnit.action = "moveTo " + targetTile.position.x.toInt() + "," + targetTile.position.y.toInt()
|
||||
|
|
Loading…
Reference in a new issue