Major update - enemies can now take over cities (and do!)

This commit is contained in:
Yair Morgenstern 2018-05-17 12:24:33 +03:00
parent 64f8b3fef8
commit e1018371b3
8 changed files with 102 additions and 63 deletions

View File

@ -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
}

View File

@ -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<TileInfo> {
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<TileInfo, Float>): Float {
@ -156,4 +197,5 @@ class UnitAutomation{
}
}
}
}

View File

@ -13,8 +13,6 @@ import kotlin.math.max
*/
class Battle(val gameInfo:GameInfo) {
private fun getGeneralModifiers(combatant: ICombatant, enemy: ICombatant): HashMap<String, Float> {
val modifiers = HashMap<String, Float>()
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)

View File

@ -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"

View File

@ -34,13 +34,17 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
val updatedTiles = ArrayList<TileInfo>()
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

View File

@ -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!!)

View File

@ -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()

View File

@ -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()
}