mirror of
https://github.com/yairm210/Unciv.git
synced 2025-09-24 03:53:12 -04:00
Preparation for Tactical AI Rework: analysis map, domination zones (#8381)
* Preparations for AI Rework: tactical analysis map, tactical domination zones, city distances * Iteration whole tilemap -> iteration 4-tile rings around cities * Optimize iteration Co-authored-by: tunerzinc@gmail.com <vfylfhby>
This commit is contained in:
parent
864145acbb
commit
280d3da933
124
core/src/com/unciv/logic/CityDistance.kt
Normal file
124
core/src/com/unciv/logic/CityDistance.kt
Normal file
@ -0,0 +1,124 @@
|
||||
package com.unciv.logic
|
||||
|
||||
import com.badlogic.gdx.math.Vector2
|
||||
import com.unciv.logic.city.CityInfo
|
||||
import com.unciv.logic.civilization.CivilizationInfo
|
||||
import com.unciv.logic.map.TileInfo
|
||||
|
||||
class CityDistance(
|
||||
val city: CityInfo,
|
||||
val distance: Int) {
|
||||
|
||||
companion object {
|
||||
fun compare(a: CityDistance?, b: CityDistance?) : CityDistance? {
|
||||
|
||||
if (a == null && b != null)
|
||||
return b
|
||||
else if (a != null && b == null)
|
||||
return a
|
||||
else if (a == null && b == null)
|
||||
return null
|
||||
|
||||
if (a!!.distance < b!!.distance)
|
||||
return a
|
||||
else if (a.distance > b.distance)
|
||||
return b
|
||||
|
||||
if (a.city.civInfo.isMajorCiv() && b.city.civInfo.isMinorCiv())
|
||||
return a
|
||||
else if (b.city.civInfo.isMajorCiv() && a.city.civInfo.isMinorCiv())
|
||||
return b
|
||||
|
||||
return a
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** This class holds information about distance from every tile to the nearest city */
|
||||
class CityDistanceData {
|
||||
|
||||
@Transient
|
||||
lateinit var game: GameInfo
|
||||
|
||||
companion object {
|
||||
const val IDENTIFIER_ALL_CIVS = "ALL_CIVS"
|
||||
const val IDENTIFIER_MAJOR_CIVS = "MAJOR_CIVS"
|
||||
}
|
||||
|
||||
private var shouldUpdate: Boolean = true
|
||||
|
||||
/** Identifier -> Map (Tile position -> Distance)
|
||||
* Identifier is either: Civ name, ALL_CIVS or MAJOR_CIVS */
|
||||
private var data: HashMap<String, HashMap<Vector2, CityDistance?>> = HashMap()
|
||||
|
||||
private fun reset() {
|
||||
data = HashMap()
|
||||
data[IDENTIFIER_ALL_CIVS]= HashMap()
|
||||
data[IDENTIFIER_MAJOR_CIVS] = HashMap()
|
||||
}
|
||||
|
||||
private fun resetPlayer(identifier: String) {
|
||||
data[identifier] = HashMap()
|
||||
}
|
||||
|
||||
private fun updateDistanceIfLower(identifier: String, position: Vector2, city: CityInfo, distance: Int) {
|
||||
val currentDistance = data[identifier]!![position]
|
||||
val newDistance = CityDistance(city, distance)
|
||||
data[identifier]!![position] = CityDistance.compare(currentDistance, newDistance)
|
||||
}
|
||||
|
||||
private fun updateDistances(thisTile: TileInfo, city: CityInfo, owner: CivilizationInfo, isMajor: Boolean) {
|
||||
|
||||
val cityTile = city.getCenterTile()
|
||||
val distance = thisTile.aerialDistanceTo(cityTile)
|
||||
|
||||
updateDistanceIfLower(IDENTIFIER_ALL_CIVS, thisTile.position, city, distance)
|
||||
if (isMajor) {
|
||||
updateDistanceIfLower(IDENTIFIER_MAJOR_CIVS, thisTile.position, city, distance)
|
||||
updateDistanceIfLower(owner.civName, thisTile.position, city, distance)
|
||||
}
|
||||
}
|
||||
|
||||
private fun update() {
|
||||
|
||||
// Clear previous info
|
||||
reset()
|
||||
|
||||
for (player in game.civilizations) {
|
||||
|
||||
// Not interested in defeated players
|
||||
if (player.isDefeated())
|
||||
continue
|
||||
|
||||
val isMajor = player.isMajorCiv()
|
||||
if (isMajor)
|
||||
resetPlayer(player.civName)
|
||||
|
||||
// Update distances for each tile inside radius 4 around each city
|
||||
for (city in player.cities.asSequence())
|
||||
for (otherTile in city.getCenterTile().getTilesInDistance(4))
|
||||
updateDistances(otherTile, city, player, isMajor)
|
||||
}
|
||||
|
||||
shouldUpdate = false
|
||||
|
||||
}
|
||||
|
||||
fun getClosestCityDistance(tile: TileInfo, player: CivilizationInfo? = null, majorsOnly: Boolean = false) : CityDistance? {
|
||||
|
||||
if (shouldUpdate)
|
||||
update()
|
||||
|
||||
val identifier = when {
|
||||
player != null && player.isMajorCiv() -> player.civName
|
||||
majorsOnly -> IDENTIFIER_MAJOR_CIVS
|
||||
else -> IDENTIFIER_ALL_CIVS
|
||||
}
|
||||
return data[identifier]!![tile.position]
|
||||
}
|
||||
|
||||
fun setDirty() {
|
||||
shouldUpdate = true
|
||||
}
|
||||
|
||||
}
|
@ -139,6 +139,9 @@ class GameInfo : IsPartOfGameInfoSerialization, HasGameInfoSerializationVersion
|
||||
@Transient
|
||||
var spaceResources = HashSet<String>()
|
||||
|
||||
@Transient
|
||||
var cityDistances: CityDistanceData = CityDistanceData()
|
||||
|
||||
//endregion
|
||||
//region Pure functions
|
||||
|
||||
@ -578,6 +581,8 @@ class GameInfo : IsPartOfGameInfoSerialization, HasGameInfoSerializationVersion
|
||||
|
||||
barbarians.setTransients(this)
|
||||
|
||||
cityDistances.game = this
|
||||
|
||||
guaranteeUnitPromotions()
|
||||
}
|
||||
|
||||
|
57
core/src/com/unciv/logic/automation/ai/TacticalAI.kt
Normal file
57
core/src/com/unciv/logic/automation/ai/TacticalAI.kt
Normal file
@ -0,0 +1,57 @@
|
||||
package com.unciv.logic.automation.ai
|
||||
|
||||
import com.badlogic.gdx.graphics.Color
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.logic.IsPartOfGameInfoSerialization
|
||||
import com.unciv.logic.civilization.CivilizationInfo
|
||||
import com.unciv.logic.map.TileInfo
|
||||
import com.unciv.ui.images.ImageGetter
|
||||
import com.unciv.ui.utils.extensions.toGroup
|
||||
import com.unciv.utils.Log
|
||||
|
||||
class TacticalAI : IsPartOfGameInfoSerialization {
|
||||
|
||||
private val debug: Boolean = false
|
||||
|
||||
@Transient private val tacticalAnalysisMap = TacticalAnalysisMap()
|
||||
@Transient private var player: CivilizationInfo? = null
|
||||
|
||||
fun init(player: CivilizationInfo) {
|
||||
this.player = player
|
||||
tacticalAnalysisMap.reset(player)
|
||||
}
|
||||
|
||||
fun showZonesDebug(tile: TileInfo) {
|
||||
|
||||
if (!debug)
|
||||
return
|
||||
|
||||
val zone = tacticalAnalysisMap.getZoneByTile(tile)
|
||||
val zoneId = zone?.id
|
||||
|
||||
Log.debug("MYTAG Zone $zoneId City: ${zone?.city} Area: ${zone?.area} Area size: ${
|
||||
tile.tileMap.continentSizes[tile.getContinent()]} Zone size: ${zone?.tileCount}")
|
||||
|
||||
val mapHolder = UncivGame.Current.worldScreen!!.mapHolder
|
||||
|
||||
for (otherTile in mapHolder.tileMap.values.asSequence()) {
|
||||
|
||||
val otherZoneId = tacticalAnalysisMap.plotPositionToZoneId[otherTile.position]
|
||||
if (otherZoneId == zoneId) {
|
||||
mapHolder.tileGroups[otherTile]?.forEach {
|
||||
mapHolder.addOverlayOnTileGroup(it, ImageGetter.getCircle().apply {
|
||||
color = when (zone?.territoryType) {
|
||||
TacticalTerritoryType.FRIENDLY -> Color.GREEN
|
||||
TacticalTerritoryType.ENEMY -> Color.RED
|
||||
else -> Color.WHITE
|
||||
}
|
||||
}.toGroup(20f)) }
|
||||
}
|
||||
|
||||
if (zone?.neighboringZones?.contains(otherZoneId) == true) {
|
||||
mapHolder.tileGroups[otherTile]?.forEach {
|
||||
mapHolder.addOverlayOnTileGroup(it, ImageGetter.getCircle().apply { color = Color.GRAY }.toGroup(20f)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
347
core/src/com/unciv/logic/automation/ai/TacticalAnalysisMap.kt
Normal file
347
core/src/com/unciv/logic/automation/ai/TacticalAnalysisMap.kt
Normal file
@ -0,0 +1,347 @@
|
||||
package com.unciv.logic.automation.ai
|
||||
|
||||
import com.badlogic.gdx.math.Vector2
|
||||
import com.unciv.Constants
|
||||
import com.unciv.logic.GameInfo
|
||||
import com.unciv.logic.city.CityInfo
|
||||
import com.unciv.logic.civilization.CivilizationInfo
|
||||
import com.unciv.logic.map.TileInfo
|
||||
import com.unciv.logic.map.TileMap
|
||||
import com.unciv.utils.Log
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.collections.HashMap
|
||||
|
||||
enum class TacticalTerritoryType {
|
||||
NONE,
|
||||
FRIENDLY,
|
||||
ENEMY,
|
||||
NEUTRAL
|
||||
}
|
||||
|
||||
class TacticalDominanceZone {
|
||||
var id = "UNKNOWN"
|
||||
var territoryType = TacticalTerritoryType.NONE
|
||||
var owner: CivilizationInfo? = null
|
||||
var city: CityInfo? = null
|
||||
var area: Int = -1
|
||||
var tileCount: Int = 0
|
||||
|
||||
var neighboringZones: HashSet<String> = HashSet()
|
||||
|
||||
var friendlyMeleeStrength = 0
|
||||
var friendlyRangeStrength = 0
|
||||
var friendlyNavalMeleeStrength = 0
|
||||
var friendlyNavalRangeStrength = 0
|
||||
var friendlyUnitCount = 0
|
||||
var friendlyNavalUnitCount = 0
|
||||
|
||||
var enemyMeleeStrength = 0
|
||||
var enemyRangeStrength = 0
|
||||
var enemyNavalMeleeStrength = 0
|
||||
var enemyNavalRangeStrength = 0
|
||||
var enemyUnitCount = 0
|
||||
var enemyNavalUnitCount = 0
|
||||
|
||||
val neutralUnitStrength = 0
|
||||
val neutralUnitCount = 0
|
||||
|
||||
var zoneValue = 0
|
||||
|
||||
fun getOverallFriendlyStrength(): Int {
|
||||
return friendlyMeleeStrength*4/3 + friendlyNavalMeleeStrength + friendlyRangeStrength + friendlyNavalRangeStrength
|
||||
}
|
||||
|
||||
fun getOverallEnemyStrength(): Int {
|
||||
return enemyMeleeStrength*4/3 + enemyNavalMeleeStrength + enemyRangeStrength + enemyNavalRangeStrength
|
||||
}
|
||||
|
||||
fun isWater(): Boolean {
|
||||
return id.startsWith('-')
|
||||
}
|
||||
|
||||
fun extend(tile: TileInfo) {
|
||||
tileCount += 1
|
||||
}
|
||||
}
|
||||
|
||||
class TacticalAnalysisMap {
|
||||
|
||||
lateinit var game: GameInfo // Current game
|
||||
lateinit var player: CivilizationInfo // Current player
|
||||
|
||||
var lastUpdate: Int = -1
|
||||
|
||||
val zones: ArrayList<TacticalDominanceZone> = ArrayList()
|
||||
val zoneIdToZoneIndex: HashMap<String, Int> = HashMap()
|
||||
val plotPositionToZoneId: HashMap<Vector2, String> = HashMap()
|
||||
|
||||
companion object {
|
||||
const val maxRange = 4
|
||||
const val maxZoneSize = 30
|
||||
}
|
||||
|
||||
fun reset(player: CivilizationInfo) {
|
||||
this.player = player
|
||||
this.game = player.gameInfo
|
||||
this.lastUpdate = -1
|
||||
|
||||
this.zones.clear()
|
||||
this.zoneIdToZoneIndex.clear()
|
||||
this.plotPositionToZoneId.clear()
|
||||
}
|
||||
|
||||
fun isUpToDate() : Boolean {
|
||||
if (lastUpdate == -1)
|
||||
return false
|
||||
if (player != game.currentPlayerCiv)
|
||||
return true
|
||||
return lastUpdate == game.turns
|
||||
}
|
||||
|
||||
fun invalidate() {
|
||||
lastUpdate = -1
|
||||
}
|
||||
|
||||
private fun refreshIfOutdated() {
|
||||
|
||||
if (isUpToDate())
|
||||
return
|
||||
|
||||
Log.debug("Refreshing Tactical Analysis Map...")
|
||||
|
||||
// This is where creation and separation of zones occur
|
||||
createDominanceZones()
|
||||
establishZoneNeighborhood()
|
||||
|
||||
// This is workaround for absent "Area" mechanics.
|
||||
// We glue small leftovers to bigger zones
|
||||
glueSmallZonesToBig()
|
||||
|
||||
// TODO: calculateMilitaryStrength
|
||||
// TODO: prioritizeZones
|
||||
// TODO: updatePostures
|
||||
}
|
||||
|
||||
fun getZoneByTile(tile: TileInfo): TacticalDominanceZone? {
|
||||
refreshIfOutdated()
|
||||
val zoneId = plotPositionToZoneId[tile.position]?: return null
|
||||
return getZoneById(zoneId)
|
||||
}
|
||||
|
||||
fun getZoneById(id: String): TacticalDominanceZone? {
|
||||
refreshIfOutdated()
|
||||
val index = zoneIdToZoneIndex[id]
|
||||
if (index != null)
|
||||
return getZoneByIndex(index)
|
||||
return null
|
||||
}
|
||||
|
||||
fun getZoneByIndex(index: Int): TacticalDominanceZone? {
|
||||
refreshIfOutdated()
|
||||
if (index < 0 || index >= zones.size)
|
||||
return null
|
||||
return zones[index]
|
||||
}
|
||||
|
||||
fun createDominanceZones() {
|
||||
|
||||
lastUpdate = game.turns
|
||||
zones.clear()
|
||||
zoneIdToZoneIndex.clear()
|
||||
plotPositionToZoneId.clear()
|
||||
|
||||
val unknownZone = TacticalDominanceZone()
|
||||
|
||||
zones.add(unknownZone)
|
||||
zoneIdToZoneIndex[unknownZone.id] = 0
|
||||
|
||||
val nonCityTiles = ArrayList<TileInfo>()
|
||||
|
||||
var zone: TacticalDominanceZone? = null
|
||||
|
||||
for (tile in game.tileMap.values.asSequence()) {
|
||||
|
||||
// Unexplored plot go into their own zone
|
||||
if (!player.hasExplored(tile)) {
|
||||
plotPositionToZoneId[tile.position] = unknownZone.id
|
||||
continue
|
||||
}
|
||||
|
||||
// Is this plot close to a city?
|
||||
val cityDistance = game.cityDistances.getClosestCityDistance(tile, null, false)
|
||||
if (cityDistance == null) {
|
||||
// Non-city tiles processed separately
|
||||
nonCityTiles.add(tile)
|
||||
continue
|
||||
}
|
||||
|
||||
val city: CityInfo? = when {
|
||||
cityDistance.distance < 3 -> cityDistance.city
|
||||
else -> tile.getCity()
|
||||
}
|
||||
|
||||
if (city == null) {
|
||||
nonCityTiles.add(tile)
|
||||
continue
|
||||
}
|
||||
|
||||
val zoneId = if (tile.isWater) "-${city.id}" else city.id
|
||||
|
||||
// Chances are it's the same zone as before
|
||||
if (zone == null || zone.id != zoneId) {
|
||||
zone = getZoneById(zoneId)
|
||||
// Still not found? Create new
|
||||
if (zone == null) {
|
||||
|
||||
val newZone = TacticalDominanceZone()
|
||||
newZone.id = zoneId
|
||||
newZone.city = city
|
||||
newZone.owner = city.civInfo
|
||||
newZone.area = tile.getContinent()
|
||||
|
||||
if (newZone.owner == player)
|
||||
newZone.territoryType = TacticalTerritoryType.FRIENDLY
|
||||
else if (newZone.owner?.isAtWarWith(player) == true)
|
||||
newZone.territoryType = TacticalTerritoryType.ENEMY
|
||||
else
|
||||
newZone.territoryType = TacticalTerritoryType.NEUTRAL
|
||||
|
||||
zoneIdToZoneIndex[zoneId] = zones.size
|
||||
zones.add(newZone)
|
||||
|
||||
zone = zones.last()
|
||||
}
|
||||
}
|
||||
plotPositionToZoneId[tile.position] = zoneId
|
||||
zone.extend(tile)
|
||||
}
|
||||
|
||||
// Ensure that continents sizes are calculated
|
||||
game.tileMap.assignContinents(TileMap.AssignContinentsMode.Ensure)
|
||||
|
||||
while (nonCityTiles.isNotEmpty()) {
|
||||
|
||||
var count = maxZoneSize
|
||||
val stack: ArrayList<TileInfo> = ArrayList()
|
||||
stack.add(nonCityTiles.removeFirst())
|
||||
|
||||
val randomId = UUID.randomUUID().toString()
|
||||
val newId = if (stack.last().isWater) "-$randomId" else randomId
|
||||
|
||||
val newZone = TacticalDominanceZone()
|
||||
newZone.id = newId
|
||||
newZone.city = null
|
||||
newZone.area = stack.last().getContinent()
|
||||
newZone.territoryType = TacticalTerritoryType.NEUTRAL
|
||||
|
||||
while (stack.isNotEmpty() || count > 0) {
|
||||
val tile = stack.removeLastOrNull() ?: break
|
||||
val tileContinentSize = tile.tileMap.continentSizes[tile.getContinent()] ?: Int.MAX_VALUE
|
||||
plotPositionToZoneId[tile.position] = newId
|
||||
newZone.extend(tile)
|
||||
|
||||
for (neighbor in tile.neighbors) {
|
||||
|
||||
// We don't want lakes and mountains to be separate zones - should attach them too
|
||||
val isLake = neighbor.matchesTerrainFilter(Constants.lakes)
|
||||
val isMountain = neighbor.matchesTerrainFilter(Constants.mountain)
|
||||
val neighborContinentSize = neighbor.tileMap.continentSizes[neighbor.getContinent()] ?: Int.MAX_VALUE
|
||||
|
||||
val isSameZone = neighbor.getContinent() == tile.getContinent()
|
||||
|| isLake || (isMountain && neighbor.isLand)
|
||||
|| neighborContinentSize < 4 || tileContinentSize < 4
|
||||
|
||||
if (isSameZone && nonCityTiles.contains(neighbor) && count > 0) {
|
||||
nonCityTiles.remove(neighbor)
|
||||
stack.add(neighbor)
|
||||
count -= 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
zoneIdToZoneIndex[newZone.id] = zones.size
|
||||
zones.add(newZone)
|
||||
}
|
||||
}
|
||||
|
||||
private fun glueSmallZonesToBig() {
|
||||
|
||||
val toRemove = HashSet<TacticalDominanceZone>()
|
||||
|
||||
for (zone in zones) {
|
||||
if (zone.tileCount < 5 && zone.city == null) {
|
||||
val biggerZone = zones.asSequence()
|
||||
.filter { it.isWater() == zone.isWater() && it.neighboringZones.contains(zone.id) }
|
||||
.firstOrNull()
|
||||
if (biggerZone != null) {
|
||||
plotPositionToZoneId.asSequence()
|
||||
.filter { it.value == zone.id }
|
||||
.forEach { plotPositionToZoneId[it.key] = biggerZone.id }
|
||||
toRemove.add(zone)
|
||||
}
|
||||
}
|
||||
}
|
||||
zones.removeAll(toRemove)
|
||||
zoneIdToZoneIndex.clear()
|
||||
|
||||
for (i in 0 until zones.size) {
|
||||
val zoneId = zones[i].id
|
||||
zoneIdToZoneIndex[zoneId] = i
|
||||
}
|
||||
}
|
||||
|
||||
private fun establishZoneNeighborhood() {
|
||||
|
||||
for (zone in zones)
|
||||
zone.neighboringZones.clear()
|
||||
|
||||
val tileMatrix = game.tileMap.tileMatrix
|
||||
|
||||
val gridH = tileMatrix.size-1
|
||||
val gridW = tileMatrix.size-1
|
||||
|
||||
for (y in 0 until gridH) {
|
||||
for (x in 0 until gridW) {
|
||||
val tileA = tileMatrix[y][x]
|
||||
val tileB = tileMatrix[y+1][x]
|
||||
val tileC = tileMatrix[y][x+1]
|
||||
val tileD = tileMatrix[y+1][x+1]
|
||||
|
||||
val zoneA = if (tileA == null) getZoneById("UNKNOWN") else getZoneByTile(tileA)
|
||||
val zoneB = if (tileB == null) getZoneById("UNKNOWN") else getZoneByTile(tileB)
|
||||
val zoneC = if (tileC == null) getZoneById("UNKNOWN") else getZoneByTile(tileC)
|
||||
val zoneD = if (tileD == null) getZoneById("UNKNOWN") else getZoneByTile(tileD)
|
||||
|
||||
val zoneAId = zoneA!!.id
|
||||
val zoneBId = zoneB!!.id
|
||||
val zoneCId = zoneC!!.id
|
||||
val zoneDId = zoneD!!.id
|
||||
|
||||
if (zoneAId != "UNKNOWN" && zoneBId != "UNKNOWN" && zoneAId != zoneBId) {
|
||||
zoneA.neighboringZones.add(zoneB.id)
|
||||
zoneB.neighboringZones.add(zoneA.id)
|
||||
}
|
||||
|
||||
if (zoneAId != "UNKNOWN" && zoneCId != "UNKNOWN" && zoneAId != zoneCId) {
|
||||
zoneA.neighboringZones.add(zoneC.id)
|
||||
zoneC.neighboringZones.add(zoneA.id)
|
||||
}
|
||||
|
||||
if (zoneAId != "UNKNOWN" && zoneDId != "UNKNOWN" && zoneAId != zoneDId) {
|
||||
zoneA.neighboringZones.add(zoneD.id)
|
||||
zoneD.neighboringZones.add(zoneA.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun debugOutput() {
|
||||
Log.debug("MYTAG: Total tactical zones: ${zones.size}")
|
||||
for (zone in zones) {
|
||||
Log.debug("MYTAG: Zone: ${zone.id} City: ${zone.city} Territory: ${zone.territoryType} Neighbors: ${zone.neighboringZones}")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -153,7 +153,7 @@ object UnitAutomation {
|
||||
val tilesCanMoveTo = unit.movement.getDistanceToTiles()
|
||||
.filter { unit.movement.canMoveTo(it.key) }
|
||||
if (tilesCanMoveTo.isNotEmpty())
|
||||
unit.movement.moveToTile(tilesCanMoveTo.minBy { it.value.totalDistance }.key)
|
||||
unit.movement.moveToTile(tilesCanMoveTo.minByOrNull { it.value.totalDistance }!!.key)
|
||||
}
|
||||
|
||||
|
||||
|
@ -218,6 +218,8 @@ class CityInfo : IsPartOfGameInfoSerialization {
|
||||
}
|
||||
|
||||
triggerCitiesSettledNearOtherCiv()
|
||||
|
||||
civInfo.gameInfo.cityDistances.setDirty()
|
||||
}
|
||||
|
||||
private fun addStartingBuildings(civInfo: CivilizationInfo, startingEra: String) {
|
||||
@ -807,6 +809,8 @@ class CityInfo : IsPartOfGameInfoSerialization {
|
||||
civInfo.updateProximity(otherCiv,
|
||||
otherCiv.updateProximity(civInfo))
|
||||
}
|
||||
|
||||
civInfo.gameInfo.cityDistances.setDirty()
|
||||
}
|
||||
|
||||
fun annexCity() = CityInfoConquestFunctions(this).annexCity()
|
||||
|
@ -8,6 +8,7 @@ import com.unciv.logic.GameInfo
|
||||
import com.unciv.logic.IsPartOfGameInfoSerialization
|
||||
import com.unciv.logic.UncivShowableException
|
||||
import com.unciv.logic.VictoryData
|
||||
import com.unciv.logic.automation.ai.TacticalAI
|
||||
import com.unciv.logic.automation.civilization.NextTurnAutomation
|
||||
import com.unciv.logic.automation.unit.WorkerAutomation
|
||||
import com.unciv.logic.city.CityInfo
|
||||
@ -173,6 +174,9 @@ class CivilizationInfo : IsPartOfGameInfoSerialization {
|
||||
private var allyCivName: String? = null
|
||||
var naturalWonders = ArrayList<String>()
|
||||
|
||||
/* AI section */
|
||||
val tacticalAI = TacticalAI()
|
||||
|
||||
var notifications = ArrayList<Notification>()
|
||||
|
||||
var notificationsLog = ArrayList<NotificationsLog>()
|
||||
@ -364,6 +368,7 @@ class CivilizationInfo : IsPartOfGameInfoSerialization {
|
||||
var cityStateResource: String? = null
|
||||
var cityStateUniqueUnit: String? = null // Unique unit for militaristic city state. Might still be null if there are no appropriate units
|
||||
fun isMajorCiv() = nation.isMajorCiv()
|
||||
fun isMinorCiv() = nation.isCityState() || nation.isBarbarian()
|
||||
fun isAlive(): Boolean = !isDefeated()
|
||||
|
||||
fun hasMetCivTerritory(otherCiv: CivilizationInfo): Boolean = otherCiv.getCivTerritory().any { hasExplored(it) }
|
||||
@ -894,6 +899,8 @@ class CivilizationInfo : IsPartOfGameInfoSerialization {
|
||||
|
||||
hasLongCountDisplayUnique = hasUnique(UniqueType.MayanCalendarDisplay)
|
||||
|
||||
tacticalAI.init(this)
|
||||
|
||||
}
|
||||
|
||||
fun updateSightAndResources() {
|
||||
|
@ -218,7 +218,7 @@ class WorldMapHolder(
|
||||
unitTable.citySelected(previousSelectedCity)
|
||||
}
|
||||
}
|
||||
|
||||
worldScreen.viewingCiv.tacticalAI.showZonesDebug(tileInfo)
|
||||
worldScreen.shouldUpdate = true
|
||||
}
|
||||
|
||||
@ -502,7 +502,7 @@ class WorldMapHolder(
|
||||
}
|
||||
|
||||
|
||||
private fun addOverlayOnTileGroup(group: TileGroup, actor: Actor) {
|
||||
fun addOverlayOnTileGroup(group: TileGroup, actor: Actor) {
|
||||
|
||||
actor.center(group)
|
||||
actor.x += group.x
|
||||
|
Loading…
x
Reference in New Issue
Block a user