Organized unit algorithms
Combat units now head towards the closest city with no garrison if exists
This commit is contained in:
parent
afa6478745
commit
4cfbf18178
5 changed files with 125 additions and 121 deletions
|
@ -62,7 +62,7 @@ class UnitAutomation{
|
|||
if (unitToAttack.getBaseUnit().unitType == UnitType.Civilian) { // kill
|
||||
unitToAttack.civInfo.addNotification("Our " + unitToAttack.name + " was destroyed by an enemy " + unit.name + "!", unitTileToAttack.position)
|
||||
unitTileToAttack.unit = null
|
||||
unit.headTowards(unitTileToAttack.position)
|
||||
unit.movementAlgs().headTowards(unitTileToAttack)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -70,7 +70,7 @@ class UnitAutomation{
|
|||
|
||||
if (damageToAttacker < unit.health) { // don't attack if we'll die from the attack
|
||||
if(MapUnitCombatant(unit).isMelee())
|
||||
unit.headTowards(unitTileToAttack.position)
|
||||
unit.movementAlgs().headTowards(unitTileToAttack)
|
||||
Battle(unit.civInfo.gameInfo).attack(MapUnitCombatant(unit), MapUnitCombatant(unitToAttack))
|
||||
return
|
||||
}
|
||||
|
@ -78,6 +78,13 @@ class UnitAutomation{
|
|||
|
||||
if(unit.getTile().isCityCenter()) return // It's always good to have a unit in the city center, so if you havn't found annyonw aroud to attack, forget it.
|
||||
|
||||
val reachableCitiesWithoutUnits = unit.civInfo.cities.filter { it.getCenterTile().unit==null
|
||||
&& unit.movementAlgs().canReach(it.getCenterTile()) }
|
||||
if(reachableCitiesWithoutUnits.isNotEmpty()){
|
||||
val closestCityWithoutUnit = reachableCitiesWithoutUnits
|
||||
.minBy { unit.movementAlgs().getShortestPath(it.getCenterTile()).size }!!
|
||||
unit.movementAlgs().headTowards(closestCityWithoutUnit.getCenterTile())
|
||||
}
|
||||
|
||||
if (unit.health < 80) {
|
||||
healUnit(unit)
|
||||
|
@ -94,7 +101,7 @@ class UnitAutomation{
|
|||
.firstOrNull { attackableTiles.contains(it) }
|
||||
|
||||
if (closestUnit != null) {
|
||||
unit.headTowards(closestUnit.position)
|
||||
unit.movementAlgs().headTowards(closestUnit)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -132,7 +139,7 @@ class UnitAutomation{
|
|||
if (unit.getTile() == bestCityLocation)
|
||||
UnitActions().getUnitActions(unit, UnCivGame.Current.worldScreen!!).first { it.name == "Found city" }.action()
|
||||
else {
|
||||
unit.headTowards(bestCityLocation.position)
|
||||
unit.movementAlgs().headTowards(bestCityLocation)
|
||||
if (unit.currentMovement > 0 && unit.getTile() == bestCityLocation)
|
||||
UnitActions().getUnitActions(unit, UnCivGame.Current.worldScreen!!).first { it.name == "Found city" }.action()
|
||||
}
|
||||
|
|
|
@ -3,18 +3,17 @@ package com.unciv.logic.automation
|
|||
import com.unciv.logic.civilization.CivilizationInfo
|
||||
import com.unciv.logic.map.MapUnit
|
||||
import com.unciv.logic.map.TileInfo
|
||||
import com.unciv.logic.map.UnitMovementAlgorithms
|
||||
import com.unciv.models.gamebasics.GameBasics
|
||||
import com.unciv.models.gamebasics.TileImprovement
|
||||
|
||||
class WorkerAutomation {
|
||||
|
||||
fun automateWorkerAction(unit: MapUnit) {
|
||||
var tile = unit.getTile()
|
||||
val tile = unit.getTile()
|
||||
val tileToWork = findTileToWork(unit)
|
||||
if (tileToWork != tile) {
|
||||
tile = unit.headTowards(tileToWork.position)
|
||||
unit.doPreTurnAction(tile)
|
||||
unit.movementAlgs().headTowards(tileToWork)
|
||||
unit.doPreTurnAction()
|
||||
return
|
||||
}
|
||||
if (tile.improvementInProgress == null) {
|
||||
|
@ -39,8 +38,8 @@ class WorkerAutomation {
|
|||
// 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{
|
||||
UnitMovementAlgorithms(currentTile.tileMap)
|
||||
.getShortestPath(currentTile.position, workableTiles.first().position,worker)
|
||||
worker.movementAlgs()
|
||||
.getShortestPath(workableTiles.first())
|
||||
.isNotEmpty()}
|
||||
|
||||
if (selectedTile != null
|
||||
|
|
|
@ -26,31 +26,32 @@ class MapUnit {
|
|||
|
||||
fun getDistanceToTiles(): HashMap<TileInfo, Float> {
|
||||
val tile = getTile()
|
||||
return UnitMovementAlgorithms(tile.tileMap).getDistanceToTilesWithinTurn(tile.position,currentMovement,
|
||||
this)
|
||||
return movementAlgs().getDistanceToTilesWithinTurn(tile.position,currentMovement)
|
||||
}
|
||||
|
||||
fun doPreTurnAction(tile: TileInfo) {
|
||||
fun doPreTurnAction() {
|
||||
val currentTile = getTile()
|
||||
if (currentMovement == 0f) return // We've already done stuff this turn, and can't do any more stuff
|
||||
if (action != null && action!!.startsWith("moveTo")) {
|
||||
val destination = action!!.replace("moveTo ", "").split(",").dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
val destinationVector = Vector2(Integer.parseInt(destination[0]).toFloat(), Integer.parseInt(destination[1]).toFloat())
|
||||
val gotTo = headTowards(destinationVector)
|
||||
if(gotTo==tile) // We didn't move at all
|
||||
val gotTo = movementAlgs().headTowards(currentTile.tileMap[destinationVector])
|
||||
if(gotTo==currentTile) // We didn't move at all
|
||||
return
|
||||
if (gotTo.position == destinationVector) action = null
|
||||
if (currentMovement != 0f) doPreTurnAction(gotTo)
|
||||
if (currentMovement != 0f) doPreTurnAction()
|
||||
return
|
||||
}
|
||||
|
||||
if (action == "automation") WorkerAutomation().automateWorkerAction(this)
|
||||
}
|
||||
|
||||
private fun doPostTurnAction(tile: TileInfo) {
|
||||
if (name == "Worker" && tile.improvementInProgress != null) workOnImprovement(tile)
|
||||
private fun doPostTurnAction() {
|
||||
if (name == "Worker" && getTile().improvementInProgress != null) workOnImprovement()
|
||||
}
|
||||
|
||||
private fun workOnImprovement(tile: TileInfo) {
|
||||
private fun workOnImprovement() {
|
||||
val tile=getTile()
|
||||
tile.turnsToImprovement -= 1
|
||||
if (tile.turnsToImprovement != 0) return
|
||||
when {
|
||||
|
@ -65,9 +66,6 @@ class MapUnit {
|
|||
/**
|
||||
* @return The tile that we reached this turn
|
||||
*/
|
||||
fun headTowards(destination: Vector2): TileInfo {
|
||||
return UnitMovementAlgorithms(getTile().tileMap).headTowards(this,destination)
|
||||
}
|
||||
|
||||
private fun heal(){
|
||||
val tile = getTile()
|
||||
|
@ -93,13 +91,12 @@ class MapUnit {
|
|||
}
|
||||
|
||||
fun nextTurn() {
|
||||
val tile = getTile()
|
||||
doPostTurnAction(tile)
|
||||
doPostTurnAction()
|
||||
if(currentMovement==maxMovement.toFloat()){ // didn't move this turn
|
||||
heal()
|
||||
}
|
||||
currentMovement = maxMovement.toFloat()
|
||||
doPreTurnAction(tile)
|
||||
doPreTurnAction()
|
||||
}
|
||||
|
||||
fun hasUnique(unique:String): Boolean {
|
||||
|
@ -107,4 +104,5 @@ class MapUnit {
|
|||
return baseUnit.uniques!=null && baseUnit.uniques!!.contains(unique)
|
||||
}
|
||||
|
||||
fun movementAlgs() = UnitMovementAlgorithms(this)
|
||||
}
|
|
@ -2,136 +2,136 @@ package com.unciv.logic.map
|
|||
|
||||
import com.badlogic.gdx.math.Vector2
|
||||
|
||||
class UnitMovementAlgorithms(val tileMap: TileMap){
|
||||
private fun getMovementCostBetweenAdjacentTiles(from:TileInfo, to:TileInfo, unit:MapUnit): Float {
|
||||
class UnitMovementAlgorithms(val unit:MapUnit) {
|
||||
val tileMap = unit.getTile().tileMap
|
||||
|
||||
private fun getMovementCostBetweenAdjacentTiles(from: TileInfo, to: TileInfo): Float {
|
||||
if (from.roadStatus === RoadStatus.Railroad && to.roadStatus === RoadStatus.Railroad)
|
||||
return 1 / 10f
|
||||
|
||||
if (from.roadStatus !== RoadStatus.None && to.roadStatus !== RoadStatus.None) //Road
|
||||
{
|
||||
if(unit.civInfo.tech.isResearched("Machinery")) return 1/3f
|
||||
else return 1/2f
|
||||
if (unit.civInfo.tech.isResearched("Machinery")) return 1 / 3f
|
||||
else return 1 / 2f
|
||||
}
|
||||
if(unit.hasUnique("Ignores terrain cost")) return 1f
|
||||
if (unit.hasUnique("Ignores terrain cost")) return 1f
|
||||
|
||||
if(unit.hasUnique("Rough terrain penalty")
|
||||
&& (to.baseTerrain=="Hill" || to.terrainFeature=="Forest" || to.terrainFeature=="Jungle"))
|
||||
if (unit.hasUnique("Rough terrain penalty")
|
||||
&& (to.baseTerrain == "Hill" || to.terrainFeature == "Forest" || to.terrainFeature == "Jungle"))
|
||||
return 4f
|
||||
|
||||
return to.lastTerrain.movementCost.toFloat() // no road
|
||||
}
|
||||
|
||||
fun getDistanceToTilesWithinTurn(origin: Vector2, unitMovement:Float, unit: MapUnit): HashMap<TileInfo, Float> {
|
||||
val distanceToTiles = HashMap<TileInfo, Float>()
|
||||
val unitTile = tileMap[origin]
|
||||
distanceToTiles[unitTile] = 0f
|
||||
var tilesToCheck = listOf(unitTile)
|
||||
fun getDistanceToTilesWithinTurn(origin: Vector2, unitMovement: Float): HashMap<TileInfo, Float> {
|
||||
val distanceToTiles = HashMap<TileInfo, Float>()
|
||||
val unitTile = tileMap[origin]
|
||||
distanceToTiles[unitTile] = 0f
|
||||
var tilesToCheck = listOf(unitTile)
|
||||
|
||||
while (!tilesToCheck.isEmpty()) {
|
||||
val updatedTiles = ArrayList<TileInfo>()
|
||||
for (tileToCheck in tilesToCheck)
|
||||
for (neighbor in tileToCheck.neighbors) {
|
||||
if(neighbor.getOwner() != null && neighbor.getOwner() != unit.civInfo
|
||||
&& neighbor.isCityCenter())
|
||||
continue // Enemy city, can't move through it!
|
||||
while (!tilesToCheck.isEmpty()) {
|
||||
val updatedTiles = ArrayList<TileInfo>()
|
||||
for (tileToCheck in tilesToCheck)
|
||||
for (neighbor in tileToCheck.neighbors) {
|
||||
if (neighbor.getOwner() != null && neighbor.getOwner() != unit.civInfo
|
||||
&& neighbor.isCityCenter())
|
||||
continue // Enemy city, can't move through it!
|
||||
|
||||
val distanceBetweenTiles = getMovementCostBetweenAdjacentTiles(tileToCheck,neighbor,unit)
|
||||
val distanceBetweenTiles = getMovementCostBetweenAdjacentTiles(tileToCheck, neighbor)
|
||||
|
||||
var totalDistanceToTile = distanceToTiles[tileToCheck]!! + distanceBetweenTiles
|
||||
if (!distanceToTiles.containsKey(neighbor) || distanceToTiles[neighbor]!! > totalDistanceToTile) {
|
||||
if (totalDistanceToTile < unitMovement)
|
||||
updatedTiles += neighbor
|
||||
else
|
||||
totalDistanceToTile = unitMovement
|
||||
distanceToTiles[neighbor] = totalDistanceToTile
|
||||
}
|
||||
}
|
||||
var totalDistanceToTile = distanceToTiles[tileToCheck]!! + distanceBetweenTiles
|
||||
if (!distanceToTiles.containsKey(neighbor) || distanceToTiles[neighbor]!! > totalDistanceToTile) {
|
||||
if (totalDistanceToTile < unitMovement)
|
||||
updatedTiles += neighbor
|
||||
else
|
||||
totalDistanceToTile = unitMovement
|
||||
distanceToTiles[neighbor] = totalDistanceToTile
|
||||
}
|
||||
}
|
||||
|
||||
tilesToCheck = updatedTiles
|
||||
}
|
||||
tilesToCheck = updatedTiles
|
||||
}
|
||||
|
||||
return distanceToTiles
|
||||
}
|
||||
return distanceToTiles
|
||||
}
|
||||
|
||||
fun getShortestPath(origin: Vector2, destination: Vector2, unit:MapUnit): List<TileInfo> {
|
||||
if(origin.equals(destination)) return listOf(tileMap[origin]) // edge case that's needed, so that workers will know that they can reach their own tile. *sig
|
||||
fun getShortestPath(destination: TileInfo): List<TileInfo> {
|
||||
val currentTile = unit.getTile()
|
||||
if (currentTile.position.equals(destination)) return listOf(currentTile) // edge case that's needed, so that workers will know that they can reach their own tile. *sig
|
||||
|
||||
var tilesToCheck: List<TileInfo> = listOf(tileMap[origin])
|
||||
val movementTreeParents = HashMap<TileInfo, TileInfo?>() // contains a map of "you can get from X to Y in that turn"
|
||||
movementTreeParents[tileMap[origin]] = null
|
||||
var tilesToCheck: List<TileInfo> = listOf(currentTile)
|
||||
val movementTreeParents = HashMap<TileInfo, TileInfo?>() // contains a map of "you can get from X to Y in that turn"
|
||||
movementTreeParents[currentTile] = null
|
||||
|
||||
var distance = 1
|
||||
while (true) {
|
||||
val newTilesToCheck = ArrayList<TileInfo>()
|
||||
val distanceToDestination = HashMap<TileInfo, Float>()
|
||||
val movementThisTurn = if (distance == 1) unit.currentMovement else unit.maxMovement.toFloat()
|
||||
for (tileToCheck in tilesToCheck) {
|
||||
val distanceToTilesThisTurn = getDistanceToTilesWithinTurn(tileToCheck.position, movementThisTurn, unit)
|
||||
for (reachableTile in distanceToTilesThisTurn.keys) {
|
||||
if(reachableTile.position == destination)
|
||||
distanceToDestination[tileToCheck] = distanceToTilesThisTurn[reachableTile]!!
|
||||
else {
|
||||
if (movementTreeParents.containsKey(reachableTile)) continue // We cannot be faster than anything existing...
|
||||
if (reachableTile.position != destination && reachableTile.unit != null) continue // This is an intermediary tile that contains a unit - we can't go there!
|
||||
movementTreeParents[reachableTile] = tileToCheck
|
||||
newTilesToCheck.add(reachableTile)
|
||||
}
|
||||
}
|
||||
}
|
||||
var distance = 1
|
||||
while (true) {
|
||||
val newTilesToCheck = ArrayList<TileInfo>()
|
||||
val distanceToDestination = HashMap<TileInfo, Float>()
|
||||
val movementThisTurn = if (distance == 1) unit.currentMovement else unit.maxMovement.toFloat()
|
||||
for (tileToCheck in tilesToCheck) {
|
||||
val distanceToTilesThisTurn = getDistanceToTilesWithinTurn(tileToCheck.position, movementThisTurn)
|
||||
for (reachableTile in distanceToTilesThisTurn.keys) {
|
||||
if (reachableTile == destination)
|
||||
distanceToDestination[tileToCheck] = distanceToTilesThisTurn[reachableTile]!!
|
||||
else {
|
||||
if (movementTreeParents.containsKey(reachableTile)) continue // We cannot be faster than anything existing...
|
||||
if (reachableTile != destination && reachableTile.unit != null) continue // This is an intermediary tile that contains a unit - we can't go there!
|
||||
movementTreeParents[reachableTile] = tileToCheck
|
||||
newTilesToCheck.add(reachableTile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (distanceToDestination.isNotEmpty()) {
|
||||
val path = mutableListOf(tileMap[destination]) // Traverse the tree upwards to get the list of tiles leading to the destination,
|
||||
var currentTile = distanceToDestination.minBy { it.value }!!.key
|
||||
while (currentTile.position != origin) {
|
||||
path.add(currentTile)
|
||||
currentTile = movementTreeParents[currentTile]!!
|
||||
}
|
||||
return path.reversed() // and reverse in order to get the list in chronological order
|
||||
}
|
||||
if (distanceToDestination.isNotEmpty()) {
|
||||
val path = mutableListOf(destination) // Traverse the tree upwards to get the list of tiles leading to the destination,
|
||||
// Get the tile from which the distance to the final tile in least -
|
||||
// this is so that when we finally get there, we'll have as many movement points as possible
|
||||
var intermediateTile = distanceToDestination.minBy { it.value }!!.key
|
||||
while (intermediateTile != currentTile) {
|
||||
path.add(intermediateTile)
|
||||
intermediateTile = movementTreeParents[intermediateTile]!!
|
||||
}
|
||||
return path.reversed() // and reverse in order to get the list in chronological order
|
||||
}
|
||||
|
||||
if(newTilesToCheck.isEmpty()) return emptyList() // there is NO PATH (eg blocked by enemy units)
|
||||
if (newTilesToCheck.isEmpty()) return emptyList() // there is NO PATH (eg blocked by enemy units)
|
||||
|
||||
// no need to check tiles that are surrounded by reachable tiles, only need to check the edgemost tiles.
|
||||
// Because anything we can reach from intermediate tiles, can be more easily reached by the edgemost tiles,
|
||||
// since we'll have to pass through an edgemost tile in order to reach the diestination anyway
|
||||
tilesToCheck = newTilesToCheck.filterNot {tile -> tile.neighbors.all{newTilesToCheck.contains(it) || tilesToCheck.contains(it) } }
|
||||
// no need to check tiles that are surrounded by reachable tiles, only need to check the edgemost tiles.
|
||||
// Because anything we can reach from intermediate tiles, can be more easily reached by the edgemost tiles,
|
||||
// since we'll have to pass through an edgemost tile in order to reach the diestination anyway
|
||||
tilesToCheck = newTilesToCheck.filterNot { tile -> tile.neighbors.all { newTilesToCheck.contains(it) || tilesToCheck.contains(it) } }
|
||||
|
||||
distance++
|
||||
}
|
||||
}
|
||||
distance++
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param origin
|
||||
* @param destination
|
||||
* @return The tile that we reached this turn
|
||||
*/
|
||||
fun headTowards(unit:MapUnit,destination: Vector2): TileInfo {
|
||||
fun headTowards(destination: TileInfo): TileInfo {
|
||||
val currentTile = unit.getTile()
|
||||
val tileMap = currentTile.tileMap
|
||||
|
||||
val finalDestinationTile = tileMap[destination]
|
||||
val distanceToTiles = unit.getDistanceToTiles()
|
||||
|
||||
val destinationTileThisTurn:TileInfo
|
||||
if (distanceToTiles.containsKey(finalDestinationTile)) { // we can get there this turn
|
||||
if (finalDestinationTile.unit == null)
|
||||
destinationTileThisTurn = finalDestinationTile
|
||||
val destinationTileThisTurn: TileInfo
|
||||
if (distanceToTiles.containsKey(destination)) { // we can get there this turn
|
||||
if (destination.unit == null)
|
||||
destinationTileThisTurn = destination
|
||||
else // Someone is blocking to the path to the final tile...
|
||||
{
|
||||
val destinationNeighbors = tileMap[destination].neighbors
|
||||
if(destinationNeighbors.contains(currentTile)) // We're right nearby anyway, no need to move
|
||||
val destinationNeighbors = destination.neighbors
|
||||
if (destinationNeighbors.contains(currentTile)) // We're right nearby anyway, no need to move
|
||||
return currentTile
|
||||
|
||||
val reachableDestinationNeighbors = destinationNeighbors.filter { distanceToTiles.containsKey(it) && it.unit==null }
|
||||
if(reachableDestinationNeighbors.isEmpty()) // We can't get closer...
|
||||
val reachableDestinationNeighbors = destinationNeighbors
|
||||
.filter { distanceToTiles.containsKey(it) && it.unit == null }
|
||||
if (reachableDestinationNeighbors.isEmpty()) // We can't get closer...
|
||||
return currentTile
|
||||
|
||||
destinationTileThisTurn = reachableDestinationNeighbors.minBy { distanceToTiles[it]!! }!!
|
||||
}
|
||||
}
|
||||
|
||||
else { // If the tile is far away, we need to build a path how to get there, and then take the first step
|
||||
val path = getShortestPath(currentTile.position, destination, unit)
|
||||
} else { // If the tile is far away, we need to build a path how to get there, and then take the first step
|
||||
val path = getShortestPath(destination)
|
||||
destinationTileThisTurn = path.first()
|
||||
}
|
||||
|
||||
|
@ -139,7 +139,8 @@ class UnitMovementAlgorithms(val tileMap: TileMap){
|
|||
return destinationTileThisTurn
|
||||
}
|
||||
|
||||
fun canReach(destination: TileInfo): Boolean {
|
||||
return getShortestPath(destination).isNotEmpty()
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -5,7 +5,6 @@ import com.badlogic.gdx.scenes.scene2d.ui.Label
|
|||
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
||||
import com.unciv.logic.map.MapUnit
|
||||
import com.unciv.logic.map.TileInfo
|
||||
import com.unciv.logic.map.UnitMovementAlgorithms
|
||||
import com.unciv.logic.map.UnitType
|
||||
import com.unciv.ui.utils.CameraStageBaseScreen
|
||||
import com.unciv.ui.utils.ImageGetter
|
||||
|
@ -66,8 +65,8 @@ class UnitTable(val worldScreen: WorldScreen) : Table(){
|
|||
fun tileSelected(selectedTile: TileInfo) {
|
||||
if(currentlyExecutingAction=="moveTo"){
|
||||
|
||||
if(UnitMovementAlgorithms(selectedTile.tileMap)
|
||||
.getShortestPath(selectedTile.position, selectedTile.position, selectedUnit!!).isEmpty())
|
||||
if(selectedUnit!!.movementAlgs()
|
||||
.getShortestPath(selectedUnit!!.getTile().position, selectedTile.position).isEmpty())
|
||||
return // can't reach there with the selected unit, watcha want me to do?
|
||||
|
||||
val reachedTile = selectedUnit!!.headTowards(selectedTile.position)
|
||||
|
|
Loading…
Reference in a new issue