diff --git a/core/src/com/unciv/logic/automation/Automation.kt b/core/src/com/unciv/logic/automation/Automation.kt index 2e31beaf86..411606a6e6 100644 --- a/core/src/com/unciv/logic/automation/Automation.kt +++ b/core/src/com/unciv/logic/automation/Automation.kt @@ -67,7 +67,11 @@ class Automation { if(city.civInfo.cities.any { it.getCenterTile().unit==null} && combatUnits.any { it.unitType==UnitType.Archery }) // this is for city defence so get an archery unit if we can chosenUnit = combatUnits.filter { it.unitType==UnitType.Archery }.maxBy { it.cost }!! - else chosenUnit = combatUnits.maxBy { it.cost }!! + + else{ // randomize type of unit and takee the most expensive of its kind + val chosenUnitType = combatUnits.map { it.unitType }.distinct().getRandom() + chosenUnit = combatUnits.filter { it.unitType==chosenUnitType }.maxBy { it.cost }!! + } city.cityConstructions.currentConstruction = chosenUnit.name } diff --git a/core/src/com/unciv/logic/automation/UnitAutomation.kt b/core/src/com/unciv/logic/automation/UnitAutomation.kt index f60420edf1..079ab169d8 100644 --- a/core/src/com/unciv/logic/automation/UnitAutomation.kt +++ b/core/src/com/unciv/logic/automation/UnitAutomation.kt @@ -2,7 +2,10 @@ package com.unciv.logic.automation import com.unciv.UnCivGame import com.unciv.logic.battle.Battle +import com.unciv.logic.battle.CityCombatant +import com.unciv.logic.battle.ICombatant import com.unciv.logic.battle.MapUnitCombatant +import com.unciv.logic.civilization.CivilizationInfo import com.unciv.logic.map.MapUnit import com.unciv.logic.map.TileInfo import com.unciv.logic.map.UnitType @@ -32,6 +35,36 @@ class UnitAutomation{ } } + fun containsAttackableEnemy(tile: TileInfo, civInfo: CivilizationInfo): Boolean { + return (tile.unit != null && tile.unit!!.owner != civInfo.civName) + || (tile.isCityCenter() && tile.getCity()!!.civInfo!=civInfo) + } + + fun getAttackableEnemies(unit: MapUnit): List { + val attackableTiles = unit.civInfo.getViewableTiles() + .filter { containsAttackableEnemy(it,unit.civInfo) } + + if(MapUnitCombatant(unit).isMelee()) { + val distanceToTiles = unit.getDistanceToTiles() + // If we're conducting a melee attack, + // then there needs to be a tile adjacent to the enemy that we can get to, + // AND STILL HAVE MOVEMENT POINTS REMAINING, + return attackableTiles.filter { + it.neighbors.any { + unit.getTile()==it || // We're already right nearby + it.unit == null + && distanceToTiles.containsKey(it) + && distanceToTiles[it]!! < unit.currentMovement // We can get there + } + } + } + + else { // Range attack, so enemy needs to be in range + return attackableTiles.filter { unit.getTile().getTilesInDistance(2).contains(it) } + } + + } + fun automateUnitMoves(unit: MapUnit) { if (unit.name == "Settler") { @@ -52,26 +85,32 @@ class UnitAutomation{ } // do nothing but heal // if there is an attackable unit in the vicinity, attack! - val attackableTiles = unit.civInfo.getViewableTiles() - .filter { it.unit != null && it.unit!!.owner != unit.civInfo.civName && !it.isCityCenter() }.toHashSet() - val distanceToTiles = unit.getDistanceToTiles() - val unitTileToAttack = distanceToTiles.keys.firstOrNull { attackableTiles.contains(it) } + val enemyTileToAttack = getAttackableEnemies(unit).firstOrNull() - if (unitTileToAttack != null) { - val unitToAttack = unitTileToAttack.unit!! - 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.movementAlgs().headTowards(unitTileToAttack) - return + if (enemyTileToAttack != null) { + + val enemy:ICombatant + if(enemyTileToAttack.isCityCenter()){ + enemy = CityCombatant(enemyTileToAttack.getCity()!!) } - val damageToAttacker = Battle(unit.civInfo.gameInfo).calculateDamageToAttacker(MapUnitCombatant(unit), MapUnitCombatant(unitToAttack)) + else { + val unitToAttack = enemyTileToAttack.unit!! + if (unitToAttack.getBaseUnit().unitType == UnitType.Civilian) { // kill + unitToAttack.civInfo.addNotification("Our " + unitToAttack.name + " was destroyed by an enemy " + unit.name + "!", enemyTileToAttack.position) + enemyTileToAttack.unit = null + unit.movementAlgs().headTowards(enemyTileToAttack) + return + } + enemy=MapUnitCombatant(unitToAttack) + } + + val damageToAttacker = Battle(unit.civInfo.gameInfo).calculateDamageToAttacker(MapUnitCombatant(unit), enemy) if (damageToAttacker < unit.health) { // don't attack if we'll die from the attack if(MapUnitCombatant(unit).isMelee()) - unit.movementAlgs().headTowards(unitTileToAttack) - Battle(unit.civInfo.gameInfo).attack(MapUnitCombatant(unit), MapUnitCombatant(unitToAttack)) + unit.movementAlgs().headTowards(enemyTileToAttack) + Battle(unit.civInfo.gameInfo).attack(MapUnitCombatant(unit), enemy) return } } @@ -98,11 +137,11 @@ class UnitAutomation{ // todo // else, find the closest enemy unit that we know of within 5 spaces and advance towards it - val closestUnit = unit.getTile().getTilesInDistance(5) - .firstOrNull { attackableTiles.contains(it) } + val closestEnemy = unit.getTile().getTilesInDistance(5) + .firstOrNull { containsAttackableEnemy(it,unit.civInfo) && unit.movementAlgs().canReach(it) } - if (closestUnit != null) { - unit.movementAlgs().headTowards(closestUnit) + if (closestEnemy != null) { + unit.movementAlgs().headTowards(closestEnemy) return } @@ -112,7 +151,9 @@ class UnitAutomation{ } // else, go to a random space - unit.moveToTile(distanceToTiles.keys.filter { it.unit == null }.toList().getRandom()) + unit.moveToTile(unit.getDistanceToTiles() + .filter { it.key.unit == null && it.value==unit.currentMovement } // at edge of walking distance + .toList().getRandom().first) } fun rankTileAsCityCenter(tileInfo: TileInfo, nearbyTileRankings: Map): Float { @@ -156,4 +197,5 @@ class UnitAutomation{ } } -} \ No newline at end of file +} + diff --git a/core/src/com/unciv/logic/battle/Battle.kt b/core/src/com/unciv/logic/battle/Battle.kt index 0c9ee85741..ce3336608a 100644 --- a/core/src/com/unciv/logic/battle/Battle.kt +++ b/core/src/com/unciv/logic/battle/Battle.kt @@ -13,8 +13,6 @@ import kotlin.math.max */ class Battle(val gameInfo:GameInfo) { - - private fun getGeneralModifiers(combatant: ICombatant, enemy: ICombatant): HashMap { val modifiers = HashMap() if (combatant is MapUnitCombatant) { @@ -140,7 +138,7 @@ class Battle(val gameInfo:GameInfo) { if (attacker.isDefeated()) " was destroyed while attacking" else " has " + (if (defender.isDefeated()) "destroyed" else "attacked") val defenderString = - if (defender.getUnitType() == UnitType.City) defender.getName() + if (defender.getUnitType() == UnitType.City) " "+defender.getName() else " our " + defender.getName() val notificationString = "An enemy " + attacker.getName() + whatHappenedString + defenderString gameInfo.getPlayerCivilization().addNotification(notificationString, attackedTile.position) diff --git a/core/src/com/unciv/logic/map/MapUnit.kt b/core/src/com/unciv/logic/map/MapUnit.kt index c6ee92f50f..c7c54e9972 100644 --- a/core/src/com/unciv/logic/map/MapUnit.kt +++ b/core/src/com/unciv/logic/map/MapUnit.kt @@ -83,12 +83,18 @@ class MapUnit { if(health>100) health=100 } + fun canMove(tile: TileInfo): Boolean { + if(tile.unit!=null) return false + if(tile.isCityCenter() && tile.getOwner()!=civInfo) return false + return true + } fun moveToTile(otherTile: TileInfo) { val distanceToTiles = getDistanceToTiles() if (!distanceToTiles.containsKey(otherTile)) throw Exception("You can't get there from here!") if (otherTile.unit != null ) throw Exception("Tile already contains a unit!") + 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" diff --git a/core/src/com/unciv/logic/map/UnitMovementAlgorithms.kt b/core/src/com/unciv/logic/map/UnitMovementAlgorithms.kt index 5564552b7a..386918a7e6 100644 --- a/core/src/com/unciv/logic/map/UnitMovementAlgorithms.kt +++ b/core/src/com/unciv/logic/map/UnitMovementAlgorithms.kt @@ -34,13 +34,17 @@ class UnitMovementAlgorithms(val unit:MapUnit) { val updatedTiles = ArrayList() for (tileToCheck in tilesToCheck) for (neighbor in tileToCheck.neighbors) { - if (neighbor.getOwner() != null && neighbor.getOwner() != unit.civInfo + + var totalDistanceToTile:Float + if (neighbor.getOwner() != unit.civInfo && neighbor.isCityCenter()) - continue // Enemy city, can't move through it! + totalDistanceToTile = unitMovement // Enemy city, can't move through it - we'll be "stuck" there - val distanceBetweenTiles = getMovementCostBetweenAdjacentTiles(tileToCheck, neighbor) + else { + val distanceBetweenTiles = getMovementCostBetweenAdjacentTiles(tileToCheck, neighbor) + totalDistanceToTile = distanceToTiles[tileToCheck]!! + distanceBetweenTiles + } - var totalDistanceToTile = distanceToTiles[tileToCheck]!! + distanceBetweenTiles if (!distanceToTiles.containsKey(neighbor) || distanceToTiles[neighbor]!! > totalDistanceToTile) { if (totalDistanceToTile < unitMovement) updatedTiles += neighbor @@ -76,7 +80,7 @@ class UnitMovementAlgorithms(val unit:MapUnit) { 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! + if (!unit.canMove(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) } @@ -111,12 +115,13 @@ class UnitMovementAlgorithms(val unit:MapUnit) { */ fun headTowards(destination: TileInfo): TileInfo { val currentTile = unit.getTile() + if(currentTile==destination) return currentTile val distanceToTiles = unit.getDistanceToTiles() val destinationTileThisTurn: TileInfo if (distanceToTiles.containsKey(destination)) { // we can get there this turn - if (destination.unit == null) + if (unit.canMove(destination)) destinationTileThisTurn = destination else // Someone is blocking to the path to the final tile... { @@ -125,7 +130,7 @@ class UnitMovementAlgorithms(val unit:MapUnit) { return currentTile val reachableDestinationNeighbors = destinationNeighbors - .filter { distanceToTiles.containsKey(it) && it.unit == null } + .filter { distanceToTiles.containsKey(it) && unit.canMove(it)} if (reachableDestinationNeighbors.isEmpty()) // We can't get closer... return currentTile diff --git a/core/src/com/unciv/ui/tilegroups/TileGroup.kt b/core/src/com/unciv/ui/tilegroups/TileGroup.kt index 759b67d485..a7751c4401 100644 --- a/core/src/com/unciv/ui/tilegroups/TileGroup.kt +++ b/core/src/com/unciv/ui/tilegroups/TileGroup.kt @@ -12,6 +12,12 @@ import com.unciv.ui.utils.center import com.unciv.ui.utils.colorFromRGB open class TileGroup(var tileInfo: TileInfo) : Group() { + /** + * This exists so that when debugging we can see the entire map. + * Remember to turn this to false before commit and upload! + */ + protected val viewEntireMapForDebug = false + protected val hexagon = ImageGetter.getImage("TerrainIcons/Hexagon.png") protected var terrainFeatureImage:Image?=null @@ -67,7 +73,8 @@ open class TileGroup(var tileInfo: TileInfo) : Group() { open fun update(isViewable: Boolean) { hideCircle() - if (!tileInfo.tileMap.gameInfo.getPlayerCivilization().exploredTiles.contains(tileInfo.position)) { + if (!tileInfo.tileMap.gameInfo.getPlayerCivilization().exploredTiles.contains(tileInfo.position) + && !viewEntireMapForDebug) { hexagon.color = Color.BLACK return } @@ -205,7 +212,7 @@ open class TileGroup(var tileInfo: TileInfo) : Group() { unitImage = null } - if (tileInfo.unit != null && isViewable) { // Tile is visible + if (tileInfo.unit != null && (isViewable || viewEntireMapForDebug)) { // Tile is visible val unit = tileInfo.unit!! unitImage = getUnitImage(unit.name, unit.civInfo.getCivilization().getColor()) addActor(unitImage!!) diff --git a/core/src/com/unciv/ui/tilegroups/WorldTileGroup.kt b/core/src/com/unciv/ui/tilegroups/WorldTileGroup.kt index a351615606..3a7c003091 100644 --- a/core/src/com/unciv/ui/tilegroups/WorldTileGroup.kt +++ b/core/src/com/unciv/ui/tilegroups/WorldTileGroup.kt @@ -27,7 +27,8 @@ class WorldTileGroup(tileInfo: TileInfo) : TileGroup(tileInfo) { override fun update(isViewable: Boolean) { super.update(isViewable) - if (!tileInfo.tileMap.gameInfo.getPlayerCivilization().exploredTiles.contains(tileInfo.position)) return + if (!tileInfo.tileMap.gameInfo.getPlayerCivilization().exploredTiles.contains(tileInfo.position) + && !viewEntireMapForDebug) return if (populationImage != null) removePopulationIcon() diff --git a/core/src/com/unciv/ui/worldscreen/BattleTable.kt b/core/src/com/unciv/ui/worldscreen/BattleTable.kt index cb0fb4e42a..93a83af916 100644 --- a/core/src/com/unciv/ui/worldscreen/BattleTable.kt +++ b/core/src/com/unciv/ui/worldscreen/BattleTable.kt @@ -4,16 +4,15 @@ import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.scenes.scene2d.ui.Label import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.scenes.scene2d.ui.TextButton +import com.unciv.logic.automation.UnitAutomation import com.unciv.logic.battle.Battle import com.unciv.logic.battle.CityCombatant import com.unciv.logic.battle.ICombatant import com.unciv.logic.battle.MapUnitCombatant -import com.unciv.logic.map.TileInfo import com.unciv.logic.map.UnitType import com.unciv.ui.cityscreen.addClickListener import com.unciv.ui.utils.CameraStageBaseScreen import com.unciv.ui.utils.ImageGetter -import com.unciv.ui.utils.centerX import com.unciv.ui.utils.disable import kotlin.math.max @@ -121,37 +120,14 @@ class BattleTable(val worldScreen: WorldScreen): Table() { val attackerDistanceToTiles = attacker.unit.getDistanceToTiles() - val attackerCanReachDefender:Boolean - var tileToMoveTo:TileInfo? = null - - if(attacker.isMelee()){ - if(attacker.getTile().neighbors.contains(defender.getTile())){ - attackerCanReachDefender=true - } - else { - val tilesCanAttackFrom = attackerDistanceToTiles.filter { - attacker.unit.currentMovement - it.value > 0 // once we reach it we'll still have energy to attack - && it.key.unit == null - && it.key.neighbors.contains(defender.getTile()) - } - - if (tilesCanAttackFrom.isEmpty()) attackerCanReachDefender=false - else { - tileToMoveTo = tilesCanAttackFrom.minBy { it.value }!!.key // travel least distance - attackerCanReachDefender=true - } - } - } - - else { // ranged - val tilesInRange = attacker.getTile().getTilesInDistance(2) - attackerCanReachDefender = tilesInRange.contains(defender.getTile()) - } + val attackerCanReachDefender = UnitAutomation().getAttackableEnemies(attacker.unit) + .contains(defender.getTile()) if(!attackerCanReachDefender || attacker.unit.currentMovement==0f) attackButton.disable() else { attackButton.addClickListener { - if(tileToMoveTo!=null) attacker.unit.moveToTile(tileToMoveTo) + if(attacker.isMelee()) + attacker.unit.movementAlgs().headTowards(defender.getTile()) battle.attack(attacker, defender) worldScreen.update() }