mirror of
https://github.com/yairm210/Unciv.git
synced 2025-02-07 17:43:54 +07:00
Earlier version with changes mostly to use Sequences (#1993)
* Fixes Issue #1697 by adding information to the special production constructions. * Get rid of extra $ sign in the SpecialConstruction tooltips * Major refactor to use Sequences instead of List to try to improve logic whenever getting a list of tiles at a distance. * Get rid of extraneous parameter * get rid of extra exception. slight refactor placeUnitNearTile for readability * Fix bug of doing intersection instead of union * Add an extra method to get tiles in distance range * Update based on comments
This commit is contained in:
parent
718473fb31
commit
3c2cb01169
@ -164,11 +164,12 @@ class GameInfo {
|
||||
// Barbarians will only spawn in places that no one can see
|
||||
val allViewableTiles = civilizations.filterNot { it.isBarbarian() }
|
||||
.flatMap { it.viewableTiles }.toHashSet()
|
||||
val tilesWithin3ofExistingEncampment = existingEncampments.flatMap { it.getTilesInDistance(3) }
|
||||
val tilesWithin3ofExistingEncampment = existingEncampments.asSequence()
|
||||
.flatMap { it.getTilesInDistance(3) }.toSet()
|
||||
val viableTiles = tileMap.values.filter {
|
||||
!it.getBaseTerrain().impassable && it.isLand
|
||||
&& it.terrainFeature==null
|
||||
&& it.naturalWonder==null
|
||||
&& it.terrainFeature == null
|
||||
&& it.naturalWonder == null
|
||||
&& it !in tilesWithin3ofExistingEncampment
|
||||
&& it !in allViewableTiles
|
||||
}
|
||||
|
@ -14,14 +14,6 @@ import kotlin.math.sqrt
|
||||
|
||||
class Automation {
|
||||
|
||||
internal fun rankTile(tile: TileInfo?, civInfo: CivilizationInfo): Float {
|
||||
if (tile == null) return 0f
|
||||
val stats = tile.getTileStats(null, civInfo)
|
||||
var rank = rankStatsValue(stats, civInfo)
|
||||
if (tile.improvement == null) rank += 0.5f // improvement potential!
|
||||
if (tile.hasViewableResource(civInfo)) rank += 1.0f
|
||||
return rank
|
||||
}
|
||||
|
||||
fun rankTileForCityWork(tile:TileInfo, city: CityInfo, foodWeight: Float = 1f): Float {
|
||||
val stats = tile.getTileStats(city, city.civInfo)
|
||||
@ -63,20 +55,6 @@ class Automation {
|
||||
return rank
|
||||
}
|
||||
|
||||
fun rankStatsValue(stats: Stats, civInfo: CivilizationInfo): Float {
|
||||
var rank = 0.0f
|
||||
if (stats.food <= 2) rank += (stats.food * 1.2f) //food get more value to keep city growing
|
||||
else rank += (2.4f + (stats.food - 2) / 2) // 1.2 point for each food up to 2, from there on half a point
|
||||
|
||||
if (civInfo.gold < 0 && civInfo.statsForNextTurn.gold <= 0) rank += stats.gold
|
||||
else rank += stats.gold / 3 // 3 gold is much worse than 2 production
|
||||
|
||||
rank += stats.production
|
||||
rank += stats.science
|
||||
rank += stats.culture
|
||||
return rank
|
||||
}
|
||||
|
||||
fun trainMilitaryUnit(city: CityInfo) {
|
||||
val name = chooseMilitaryUnit(city)
|
||||
city.cityConstructions.currentConstruction = name
|
||||
@ -128,6 +106,32 @@ class Automation {
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
internal fun rankTile(tile: TileInfo?, civInfo: CivilizationInfo): Float {
|
||||
if (tile == null) return 0f
|
||||
val stats = tile.getTileStats(null, civInfo)
|
||||
var rank = rankStatsValue(stats, civInfo)
|
||||
if (tile.improvement == null) rank += 0.5f // improvement potential!
|
||||
if (tile.hasViewableResource(civInfo)) rank += 1.0f
|
||||
return rank
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun rankStatsValue(stats: Stats, civInfo: CivilizationInfo): Float {
|
||||
var rank = 0.0f
|
||||
if (stats.food <= 2) rank += (stats.food * 1.2f) //food get more value to keep city growing
|
||||
else rank += (2.4f + (stats.food - 2) / 2) // 1.2 point for each food up to 2, from there on half a point
|
||||
|
||||
if (civInfo.gold < 0 && civInfo.statsForNextTurn.gold <= 0) rank += stats.gold
|
||||
else rank += stats.gold / 3 // 3 gold is much worse than 2 production
|
||||
|
||||
rank += stats.production
|
||||
rank += stats.science
|
||||
rank += stats.culture
|
||||
return rank
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class ThreatLevel{
|
||||
|
@ -86,7 +86,7 @@ class BarbarianAutomation(val civInfo: CivilizationInfo) {
|
||||
if (unit.health < 100 && UnitAutomation().tryHealUnit(unit, unitDistanceToTiles)) return
|
||||
|
||||
// 6 - wander
|
||||
UnitAutomation().wander(unit, unitDistanceToTiles)
|
||||
UnitAutomation.wander(unit, unitDistanceToTiles)
|
||||
}
|
||||
|
||||
private fun automateScout(unit: MapUnit) {
|
||||
@ -114,7 +114,7 @@ class BarbarianAutomation(val civInfo: CivilizationInfo) {
|
||||
if (unit.health < 100 && UnitAutomation().tryHealUnit(unit, unitDistanceToTiles)) return
|
||||
|
||||
// 5 - wander
|
||||
UnitAutomation().wander(unit, unitDistanceToTiles)
|
||||
UnitAutomation.wander(unit, unitDistanceToTiles)
|
||||
}
|
||||
|
||||
private fun findFurthestTileCanMoveTo(
|
||||
|
@ -63,9 +63,10 @@ class BattleHelper {
|
||||
val tilesInAttackRange =
|
||||
if (unit.hasUnique("Ranged attacks may be performed over obstacles") || unit.type.isAirUnit())
|
||||
reachableTile.getTilesInDistance(rangeOfAttack)
|
||||
else reachableTile.getViewableTiles(rangeOfAttack, unit.type.isWaterUnit())
|
||||
else reachableTile.getViewableTilesList(rangeOfAttack, unit.type.isWaterUnit())
|
||||
.asSequence()
|
||||
|
||||
attackableTiles += tilesInAttackRange.asSequence().filter { it in tilesWithEnemies }
|
||||
attackableTiles += tilesInAttackRange.filter { it in tilesWithEnemies }
|
||||
.map { AttackableTile(reachableTile, it) }
|
||||
}
|
||||
return attackableTiles
|
||||
|
@ -37,7 +37,7 @@ class SpecificUnitAutomation{
|
||||
return createImprovementAction()
|
||||
}
|
||||
}
|
||||
else UnitAutomation().tryExplore(unit, unit.movement.getDistanceToTiles())
|
||||
else UnitAutomation.tryExplore(unit, unit.movement.getDistanceToTiles())
|
||||
}
|
||||
|
||||
fun automateGreatGeneral(unit: MapUnit){
|
||||
@ -72,14 +72,13 @@ class SpecificUnitAutomation{
|
||||
fun rankTileAsCityCenter(tileInfo: TileInfo, nearbyTileRankings: Map<TileInfo, Float>,
|
||||
luxuryResourcesInCivArea: Sequence<TileResource>): Float {
|
||||
val bestTilesFromOuterLayer = tileInfo.getTilesAtDistance(2)
|
||||
.asSequence()
|
||||
.sortedByDescending { nearbyTileRankings[it] }.take(2)
|
||||
.toList()
|
||||
val top5Tiles = tileInfo.neighbors.union(bestTilesFromOuterLayer)
|
||||
.asSequence()
|
||||
.sortedByDescending { nearbyTileRankings[it] }
|
||||
.take(5)
|
||||
var rank = top5Tiles.map { nearbyTileRankings[it]!! }.sum()
|
||||
var rank = top5Tiles.map { nearbyTileRankings.getValue(it) }.sum()
|
||||
if (tileInfo.isCoastalTile()) rank += 5
|
||||
|
||||
val luxuryResourcesInCityArea = tileInfo.getTilesAtDistance(2).filter { it.resource!=null }
|
||||
@ -94,9 +93,9 @@ class SpecificUnitAutomation{
|
||||
|
||||
|
||||
fun automateSettlerActions(unit: MapUnit) {
|
||||
if(unit.getTile().militaryUnit==null) return // Don't move until you're accompanied by a military unit
|
||||
if (unit.getTile().militaryUnit == null) return // Don't move until you're accompanied by a military unit
|
||||
|
||||
val tilesNearCities = unit.civInfo.gameInfo.getCities()
|
||||
val tilesNearCities = unit.civInfo.gameInfo.getCities().asSequence()
|
||||
.flatMap {
|
||||
val distanceAwayFromCity =
|
||||
if (unit.civInfo.knows(it.civInfo)
|
||||
@ -106,34 +105,36 @@ class SpecificUnitAutomation{
|
||||
else 3
|
||||
it.getCenterTile().getTilesInDistance(distanceAwayFromCity)
|
||||
}
|
||||
.toHashSet()
|
||||
.toSet()
|
||||
|
||||
// This is to improve performance - instead of ranking each tile in the area up to 19 times, do it once.
|
||||
val nearbyTileRankings = unit.getTile().getTilesInDistance(7)
|
||||
.associateBy ( {it},{ Automation().rankTile(it,unit.civInfo) })
|
||||
.associateBy({ it }, { Automation.rankTile(it, unit.civInfo) })
|
||||
|
||||
val possibleCityLocations = unit.getTile().getTilesInDistance(5)
|
||||
.filter { val tileOwner=it.getOwner()
|
||||
it.isLand && (tileOwner==null || tileOwner==unit.civInfo) && // don't allow settler to settle inside other civ's territory
|
||||
(unit.movement.canMoveTo(it) || unit.currentTile==it)
|
||||
&& it !in tilesNearCities }
|
||||
.filter {
|
||||
val tileOwner = it.getOwner()
|
||||
it.isLand && (tileOwner == null || tileOwner == unit.civInfo) && // don't allow settler to settle inside other civ's territory
|
||||
(unit.movement.canMoveTo(it) || unit.currentTile == it)
|
||||
&& it !in tilesNearCities
|
||||
}
|
||||
|
||||
val luxuryResourcesInCivArea = unit.civInfo.cities.asSequence()
|
||||
.flatMap { it.getTiles().asSequence() }.filter { it.resource!=null }
|
||||
.map { it.getTileResource() }.filter { it.resourceType==ResourceType.Luxury }
|
||||
.flatMap { it.getTiles().asSequence() }.filter { it.resource != null }
|
||||
.map { it.getTileResource() }.filter { it.resourceType == ResourceType.Luxury }
|
||||
.distinct()
|
||||
val bestCityLocation: TileInfo? = possibleCityLocations
|
||||
.asSequence()
|
||||
.sortedByDescending { rankTileAsCityCenter(it, nearbyTileRankings, luxuryResourcesInCivArea) }
|
||||
.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.movement.getDistanceToTiles())) return // try to find new areas
|
||||
UnitAutomation().wander(unit, unit.movement.getDistanceToTiles()) // go around aimlessly
|
||||
if (bestCityLocation == null) { // We got a badass over here, all tiles within 5 are taken? Screw it, random walk.
|
||||
val unitDistanceToTiles = unit.movement.getDistanceToTiles()
|
||||
if (UnitAutomation.tryExplore(unit, unitDistanceToTiles)) return // try to find new areas
|
||||
UnitAutomation.wander(unit, unitDistanceToTiles) // go around aimlessly
|
||||
return
|
||||
}
|
||||
|
||||
if(bestCityLocation.getTilesInDistance(3).any { it.isCityCenter() })
|
||||
if (bestCityLocation.getTilesInDistance(3).any { it.isCityCenter() })
|
||||
throw Exception("City within distance")
|
||||
|
||||
if (unit.getTile() == bestCityLocation)
|
||||
@ -169,9 +170,9 @@ class SpecificUnitAutomation{
|
||||
&& it.isLand
|
||||
&& !it.isCityCenter()
|
||||
&& it.resource==null }
|
||||
.sortedByDescending { Automation().rankTile(it,unit.civInfo) }.toList()
|
||||
val chosenTile = tiles.firstOrNull { unit.movement.canReach(it) }
|
||||
if(chosenTile==null) continue // to another city
|
||||
.sortedByDescending { Automation.rankTile(it,unit.civInfo) }.toList()
|
||||
val chosenTile = tiles.firstOrNull { unit.movement.canReach(it) } ?: continue // to another city
|
||||
|
||||
|
||||
unit.movement.headTowards(chosenTile)
|
||||
if(unit.currentTile==chosenTile && unit.currentMovement > 0)
|
||||
@ -185,35 +186,41 @@ class SpecificUnitAutomation{
|
||||
fun automateFighter(unit: MapUnit) {
|
||||
val tilesInRange = unit.currentTile.getTilesInDistance(unit.getRange())
|
||||
val enemyAirUnitsInRange = tilesInRange
|
||||
.flatMap { it.airUnits }.filter { it.civInfo.isAtWarWith(unit.civInfo) }
|
||||
.flatMap { it.airUnits.asSequence() }.filter { it.civInfo.isAtWarWith(unit.civInfo) }
|
||||
|
||||
if(enemyAirUnitsInRange.isNotEmpty()) return // we need to be on standby in case they attack
|
||||
if(battleHelper.tryAttackNearbyEnemy(unit)) return
|
||||
if (enemyAirUnitsInRange.any()) return // we need to be on standby in case they attack
|
||||
if (battleHelper.tryAttackNearbyEnemy(unit)) return
|
||||
|
||||
// TODO Implement consideration for landing on aircraft carrier
|
||||
|
||||
val immediatelyReachableCities = tilesInRange
|
||||
.filter { it.isCityCenter() && it.getOwner()==unit.civInfo && unit.movement.canMoveTo(it)}
|
||||
.filter { it.isCityCenter() && it.getOwner() == unit.civInfo && unit.movement.canMoveTo(it) }
|
||||
|
||||
for(city in immediatelyReachableCities){
|
||||
if(city.getTilesInDistance(unit.getRange())
|
||||
.any { battleHelper.containsAttackableEnemy(it,MapUnitCombatant(unit)) }) {
|
||||
for (city in immediatelyReachableCities) {
|
||||
if (city.getTilesInDistance(unit.getRange())
|
||||
.any { battleHelper.containsAttackableEnemy(it, MapUnitCombatant(unit)) }) {
|
||||
unit.movement.moveToTile(city)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
val pathsToCities = unit.movement.getArialPathsToCities()
|
||||
if(pathsToCities.isEmpty()) return // can't actually move anywhere else
|
||||
if (pathsToCities.isEmpty()) return // can't actually move anywhere else
|
||||
|
||||
val citiesByNearbyAirUnits = pathsToCities.keys
|
||||
.groupBy { it.getTilesInDistance(unit.getRange())
|
||||
.count{it.airUnits.size>0 && it.airUnits.first().civInfo.isAtWarWith(unit.civInfo)} }
|
||||
.groupBy { key ->
|
||||
key.getTilesInDistance(unit.getRange())
|
||||
.count {
|
||||
val firstAirUnit = it.airUnits.firstOrNull()
|
||||
firstAirUnit != null && firstAirUnit.civInfo.isAtWarWith(unit.civInfo)
|
||||
}
|
||||
}
|
||||
|
||||
if(citiesByNearbyAirUnits.keys.any { it!=0 }){
|
||||
if (citiesByNearbyAirUnits.keys.any { it != 0 }) {
|
||||
val citiesWithMostNeedOfAirUnits = citiesByNearbyAirUnits.maxBy { it.key }!!.value
|
||||
val chosenCity = citiesWithMostNeedOfAirUnits.minBy { pathsToCities[it]!!.size }!! // city with min path = least turns to get there
|
||||
val firstStepInPath = pathsToCities[chosenCity]!!.first()
|
||||
//todo: maybe groupby size and choose highest priority within the same size turns
|
||||
val chosenCity = citiesWithMostNeedOfAirUnits.minBy { pathsToCities.getValue(it).size }!! // city with min path = least turns to get there
|
||||
val firstStepInPath = pathsToCities.getValue(chosenCity).first()
|
||||
unit.movement.moveToTile(firstStepInPath)
|
||||
return
|
||||
}
|
||||
@ -255,6 +262,8 @@ class SpecificUnitAutomation{
|
||||
}
|
||||
if (citiesThatCanAttackFrom.isEmpty()) return
|
||||
|
||||
//todo: this logic looks similar to some parts of automateFighter, maybe pull out common code
|
||||
//todo: maybe groupby size and choose highest priority within the same size turns
|
||||
val closestCityThatCanAttackFrom = citiesThatCanAttackFrom.minBy { pathsToCities[it]!!.size }!!
|
||||
val firstStepInPath = pathsToCities[closestCityThatCanAttackFrom]!!.first()
|
||||
airUnit.movement.moveToTile(firstStepInPath)
|
||||
|
@ -3,10 +3,7 @@ package com.unciv.logic.automation
|
||||
import com.badlogic.gdx.graphics.Color
|
||||
import com.unciv.Constants
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.logic.battle.Battle
|
||||
import com.unciv.logic.battle.BattleDamage
|
||||
import com.unciv.logic.battle.CityCombatant
|
||||
import com.unciv.logic.battle.MapUnitCombatant
|
||||
import com.unciv.logic.battle.*
|
||||
import com.unciv.logic.city.CityInfo
|
||||
import com.unciv.logic.civilization.GreatPersonManager
|
||||
import com.unciv.logic.civilization.diplomacy.DiplomaticStatus
|
||||
@ -23,6 +20,41 @@ class UnitAutomation {
|
||||
companion object {
|
||||
const val CLOSE_ENEMY_TILES_AWAY_LIMIT = 5
|
||||
const val CLOSE_ENEMY_TURNS_AWAY_LIMIT = 3f
|
||||
|
||||
internal fun tryExplore(unit: MapUnit, unitDistanceToTiles: PathsToTilesWithinTurn): Boolean {
|
||||
if (tryGoToRuin(unit, unitDistanceToTiles) && unit.currentMovement == 0f) return true
|
||||
|
||||
for (tile in unit.currentTile.getTilesInDistance(10))
|
||||
if (unit.movement.canMoveTo(tile) && tile.position !in unit.civInfo.exploredTiles
|
||||
&& unit.movement.canReach(tile)
|
||||
&& (tile.getOwner() == null || !tile.getOwner()!!.isCityState())) {
|
||||
unit.movement.headTowards(tile)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun tryGoToRuin(unit: MapUnit, unitDistanceToTiles: PathsToTilesWithinTurn): Boolean {
|
||||
if (!unit.civInfo.isMajorCiv()) return false // barbs don't have anything to do in ruins
|
||||
val tileWithRuin = unitDistanceToTiles.keys
|
||||
.firstOrNull {
|
||||
it.improvement == Constants.ancientRuins && unit.movement.canMoveTo(it)
|
||||
}
|
||||
if (tileWithRuin == null)
|
||||
return false
|
||||
unit.movement.moveToTile(tileWithRuin)
|
||||
return true
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun wander(unit: MapUnit, unitDistanceToTiles: PathsToTilesWithinTurn) {
|
||||
val reachableTiles = unitDistanceToTiles
|
||||
.filter { unit.movement.canMoveTo(it.key) && unit.movement.canReach(it.key) }
|
||||
|
||||
val reachableTilesMaxWalkingDistance = reachableTiles.filter { it.value.totalDistance == unit.currentMovement }
|
||||
if (reachableTilesMaxWalkingDistance.any()) unit.movement.moveToTile(reachableTilesMaxWalkingDistance.toList().random().first)
|
||||
else if (reachableTiles.any()) unit.movement.moveToTile(reachableTiles.keys.random())
|
||||
}
|
||||
}
|
||||
|
||||
private val battleHelper = BattleHelper()
|
||||
@ -162,10 +194,9 @@ class UnitAutomation {
|
||||
return true
|
||||
}
|
||||
|
||||
fun getBombardTargets(city: CityInfo): List<TileInfo> {
|
||||
return city.getCenterTile().getViewableTiles(city.range, true)
|
||||
.filter { battleHelper.containsAttackableEnemy(it, CityCombatant(city)) }
|
||||
}
|
||||
fun getBombardTargets(city: CityInfo): Sequence<TileInfo> =
|
||||
city.getCenterTile().getTilesInDistance(city.range)
|
||||
.filter { battleHelper.containsAttackableEnemy(it, CityCombatant(city)) }
|
||||
|
||||
/** Move towards the closest attackable enemy of the [unit].
|
||||
*
|
||||
@ -180,7 +211,7 @@ class UnitAutomation {
|
||||
var closeEnemies = battleHelper.getAttackableEnemies(
|
||||
unit,
|
||||
unitDistanceToTiles,
|
||||
tilesToCheck = unit.getTile().getTilesInDistance(CLOSE_ENEMY_TILES_AWAY_LIMIT)
|
||||
tilesToCheck = unit.getTile().getTilesInDistance(CLOSE_ENEMY_TILES_AWAY_LIMIT).toList()
|
||||
).filter {
|
||||
// Ignore units that would 1-shot you if you attacked
|
||||
BattleDamage().calculateDamageToAttacker(MapUnitCombatant(unit),
|
||||
@ -246,34 +277,35 @@ class UnitAutomation {
|
||||
|
||||
if (closestReachableEnemyCity != null) {
|
||||
val unitDistanceToTiles = unit.movement.getDistanceToTiles()
|
||||
val tilesInBombardRange = closestReachableEnemyCity.getTilesInDistance(2)
|
||||
val reachableTilesNotInBombardRange = unitDistanceToTiles.keys.filter { it !in tilesInBombardRange }
|
||||
val tilesInBombardRange = closestReachableEnemyCity.getTilesInDistance(2).toSet()
|
||||
|
||||
val suitableGatheringGroundTiles = closestReachableEnemyCity.getTilesAtDistance(4)
|
||||
.union(closestReachableEnemyCity.getTilesAtDistance(3))
|
||||
.filter { it.isLand }
|
||||
|
||||
// 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.
|
||||
val tileToHeadTo = suitableGatheringGroundTiles
|
||||
.sortedBy { it.arialDistanceTo(unit.currentTile) }
|
||||
.firstOrNull { unit.movement.canReach(it) } ?: closestReachableEnemyCity
|
||||
val tileToHeadTo = closestReachableEnemyCity.getTilesInDistanceRange(3..4)
|
||||
.filter { it.isLand }
|
||||
.sortedBy { it.arialDistanceTo(unit.currentTile) }
|
||||
.firstOrNull { unit.movement.canReach(it) } ?: closestReachableEnemyCity
|
||||
|
||||
if (tileToHeadTo !in tilesInBombardRange) // no need to worry, keep going as the movement alg. says
|
||||
unit.movement.headTowards(tileToHeadTo)
|
||||
else {
|
||||
if (unit.getRange() > 2) { // should never be in a bombardable position
|
||||
val tilesCanAttackFromButNotInBombardRange =
|
||||
reachableTilesNotInBombardRange.filter { it.arialDistanceTo(closestReachableEnemyCity) <= unit.getRange() }
|
||||
val tileToMoveTo =
|
||||
unitDistanceToTiles.asSequence()
|
||||
.filter { it.key !in tilesInBombardRange
|
||||
&& it.key.arialDistanceTo(closestReachableEnemyCity) <=
|
||||
unit.getRange() }
|
||||
.minBy { it.value.totalDistance }?.key
|
||||
|
||||
// move into position far away enough that the bombard doesn't hurt
|
||||
if (tilesCanAttackFromButNotInBombardRange.any())
|
||||
unit.movement.headTowards(tilesCanAttackFromButNotInBombardRange.minBy { unitDistanceToTiles[it]!!.totalDistance }!!)
|
||||
if (tileToMoveTo != null)
|
||||
unit.movement.headTowards(tileToMoveTo)
|
||||
} else {
|
||||
// calculate total damage of units in surrounding 4-spaces from enemy city (so we can attack a city from 2 directions at once)
|
||||
val militaryUnitsAroundEnemyCity = closestReachableEnemyCity.getTilesInDistance(3)
|
||||
.filter { it.militaryUnit != null && it.militaryUnit!!.civInfo == unit.civInfo }
|
||||
.map { it.militaryUnit!! }
|
||||
.map { it.militaryUnit }.filterNotNull()
|
||||
var totalAttackOnCityPerTurn = -20 // cities heal 20 per turn, so anything below that its useless
|
||||
val enemyCityCombatant = CityCombatant(closestReachableEnemyCity.getCity()!!)
|
||||
for (militaryUnit in militaryUnitsAroundEnemyCity) {
|
||||
@ -290,28 +322,29 @@ class UnitAutomation {
|
||||
}
|
||||
|
||||
fun tryBombardEnemy(city: CityInfo): Boolean {
|
||||
if (!city.attackedThisTurn) {
|
||||
val target = chooseBombardTarget(city)
|
||||
if (target == null) return false
|
||||
val enemy = Battle.getMapCombatantOfTile(target)!!
|
||||
Battle.attack(CityCombatant(city), enemy)
|
||||
return true
|
||||
return when {
|
||||
city.attackedThisTurn -> false
|
||||
else -> {
|
||||
val enemy = chooseBombardTarget(city) ?: return false
|
||||
Battle.attack(CityCombatant(city), enemy)
|
||||
true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun chooseBombardTarget(city: CityInfo): TileInfo? {
|
||||
var targets = getBombardTargets(city)
|
||||
if (targets.isEmpty()) return null
|
||||
val siegeUnits = targets
|
||||
.filter { Battle.getMapCombatantOfTile(it)!!.getUnitType() == UnitType.Siege }
|
||||
if (siegeUnits.any()) targets = siegeUnits
|
||||
else {
|
||||
val rangedUnits = targets
|
||||
.filter { Battle.getMapCombatantOfTile(it)!!.getUnitType().isRanged() }
|
||||
if (rangedUnits.any()) targets = rangedUnits
|
||||
}
|
||||
return targets.minBy { Battle.getMapCombatantOfTile(it)!!.getHealth() }
|
||||
private fun chooseBombardTarget(city: CityInfo): ICombatant? {
|
||||
val mappedTargets = getBombardTargets(city).map { Battle.getMapCombatantOfTile(it)!! }
|
||||
.filter {
|
||||
val unitType = it.getUnitType()
|
||||
unitType == UnitType.Siege || unitType.isRanged()
|
||||
}
|
||||
.groupByTo(LinkedHashMap()) { it.getUnitType() }
|
||||
|
||||
var targets = mappedTargets[UnitType.Siege]?.asSequence()
|
||||
if (targets == null)
|
||||
targets = mappedTargets.values.asSequence().flatMap { it.asSequence() }
|
||||
|
||||
return targets.minBy { it.getHealth() }
|
||||
}
|
||||
|
||||
private fun tryGarrisoningUnit(unit: MapUnit): Boolean {
|
||||
@ -352,28 +385,6 @@ class UnitAutomation {
|
||||
return true
|
||||
}
|
||||
|
||||
private fun tryGoToRuin(unit: MapUnit, unitDistanceToTiles: PathsToTilesWithinTurn): Boolean {
|
||||
if (!unit.civInfo.isMajorCiv()) return false // barbs don't have anything to do in ruins
|
||||
val tileWithRuin = unitDistanceToTiles.keys
|
||||
.firstOrNull { it.improvement == Constants.ancientRuins && unit.movement.canMoveTo(it) }
|
||||
if (tileWithRuin == null) return false
|
||||
unit.movement.moveToTile(tileWithRuin)
|
||||
return true
|
||||
}
|
||||
|
||||
internal fun tryExplore(unit: MapUnit, unitDistanceToTiles: PathsToTilesWithinTurn): Boolean {
|
||||
if (tryGoToRuin(unit, unitDistanceToTiles) && unit.currentMovement == 0f) return true
|
||||
|
||||
for (tile in unit.currentTile.getTilesInDistance(10))
|
||||
if (unit.movement.canMoveTo(tile) && tile.position !in unit.civInfo.exploredTiles
|
||||
&& unit.movement.canReach(tile)
|
||||
&& (tile.getOwner()==null || !tile.getOwner()!!.isCityState())) {
|
||||
unit.movement.headTowards(tile)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/** This is what a unit with the 'explore' action does.
|
||||
It also explores, but also has other functions, like healing if necessary. */
|
||||
fun automatedExplore(unit: MapUnit) {
|
||||
@ -383,13 +394,4 @@ class UnitAutomation {
|
||||
if (tryExplore(unit, unit.movement.getDistanceToTiles())) return
|
||||
unit.civInfo.addNotification("[${unit.name}] finished exploring.", unit.currentTile.position, Color.GRAY)
|
||||
}
|
||||
|
||||
fun wander(unit: MapUnit, unitDistanceToTiles: PathsToTilesWithinTurn) {
|
||||
val reachableTiles = unitDistanceToTiles
|
||||
.filter { unit.movement.canMoveTo(it.key) && unit.movement.canReach(it.key) }
|
||||
|
||||
val reachableTilesMaxWalkingDistance = reachableTiles.filter { it.value.totalDistance == unit.currentMovement }
|
||||
if (reachableTilesMaxWalkingDistance.any()) unit.movement.moveToTile(reachableTilesMaxWalkingDistance.toList().random().first)
|
||||
else if (reachableTiles.any()) unit.movement.moveToTile(reachableTiles.toList().random().first)
|
||||
}
|
||||
}
|
@ -14,8 +14,7 @@ class WorkerAutomation(val unit: MapUnit) {
|
||||
fun automateWorkerAction() {
|
||||
val enemyUnitsInWalkingDistance = unit.movement.getDistanceToTiles().keys
|
||||
.filter {
|
||||
it.militaryUnit != null && it.militaryUnit!!.civInfo != unit.civInfo
|
||||
&& unit.civInfo.isAtWarWith(it.militaryUnit!!.civInfo)
|
||||
it.militaryUnit != null && it.militaryUnit!!.civInfo.isAtWarWith(unit.civInfo)
|
||||
}
|
||||
|
||||
if (enemyUnitsInWalkingDistance.isNotEmpty()) return // Don't you dare move.
|
||||
@ -118,24 +117,25 @@ class WorkerAutomation(val unit: MapUnit) {
|
||||
* Returns the current tile if no tile to work was found
|
||||
*/
|
||||
private fun findTileToWork(): TileInfo {
|
||||
val currentTile=unit.getTile()
|
||||
val currentTile = unit.getTile()
|
||||
val workableTiles = currentTile.getTilesInDistance(4)
|
||||
.filter {
|
||||
(it.civilianUnit== null || it == currentTile)
|
||||
&& tileCanBeImproved(it, unit.civInfo) }
|
||||
.sortedByDescending { getPriority(it, unit.civInfo) }.toMutableList()
|
||||
(it.civilianUnit == null || it == currentTile)
|
||||
&& tileCanBeImproved(it, unit.civInfo)
|
||||
}
|
||||
.sortedByDescending { getPriority(it, unit.civInfo) }
|
||||
|
||||
// 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.movement.canReach(it) }
|
||||
val selectedTile = workableTiles.firstOrNull { unit.movement.canReach(it) }
|
||||
|
||||
if (selectedTile != null
|
||||
&& getPriority(selectedTile, unit.civInfo)>1
|
||||
return if (selectedTile != null
|
||||
&& getPriority(selectedTile, unit.civInfo) > 1
|
||||
&& (!workableTiles.contains(currentTile)
|
||||
|| getPriority(selectedTile, unit.civInfo) > getPriority(currentTile, unit.civInfo)))
|
||||
return selectedTile
|
||||
else return currentTile
|
||||
selectedTile
|
||||
else currentTile
|
||||
}
|
||||
|
||||
private fun tileCanBeImproved(tile: TileInfo, civInfo: CivilizationInfo): Boolean {
|
||||
|
@ -62,17 +62,18 @@ class CityExpansionManager {
|
||||
fun chooseNewTileToOwn(): TileInfo? {
|
||||
for (i in 2..5) {
|
||||
val tiles = cityInfo.getCenterTile().getTilesInDistance(i)
|
||||
.filter {it.getOwner() == null && it.neighbors.any { tile->tile.getOwner()==cityInfo.civInfo }}
|
||||
if (tiles.isEmpty()) continue
|
||||
val chosenTile = tiles.maxBy { Automation().rankTile(it,cityInfo.civInfo) }
|
||||
return chosenTile
|
||||
.filter { it.getOwner() == null
|
||||
&& it.neighbors.any { tile -> tile.getOwner() == cityInfo.civInfo } }
|
||||
val chosenTile = tiles.maxBy { Automation.rankTile(it, cityInfo.civInfo) }
|
||||
if (chosenTile != null)
|
||||
return chosenTile
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
//region state-changing functions
|
||||
fun reset() {
|
||||
for(tile in cityInfo.getTiles())
|
||||
for (tile in cityInfo.getTiles())
|
||||
relinquishOwnership(tile)
|
||||
|
||||
// The only way to create a city inside an owned tile is if it's in your territory
|
||||
@ -80,10 +81,9 @@ class CityExpansionManager {
|
||||
// It becomes an invisible city and weird shit starts happening
|
||||
takeOwnership(cityInfo.getCenterTile())
|
||||
|
||||
cityInfo.getCenterTile().getTilesInDistance(1)
|
||||
.filter { it.getCity()==null } // can't take ownership of owned tiles (by other cities)
|
||||
.forEach { takeOwnership(it) }
|
||||
|
||||
for (tile in cityInfo.getCenterTile().getTilesInDistance(1)
|
||||
.filter { it.getCity() == null }) // can't take ownership of owned tiles (by other cities)
|
||||
takeOwnership(tile)
|
||||
}
|
||||
|
||||
private fun addNewTileWithCulture() {
|
||||
|
@ -277,7 +277,7 @@ class CityInfo {
|
||||
fun setTransients() {
|
||||
tileMap = civInfo.gameInfo.tileMap
|
||||
centerTileInfo = tileMap[location]
|
||||
tilesInRange = getCenterTile().getTilesInDistance( 3).toHashSet()
|
||||
tilesInRange = getCenterTile().getTilesInDistance(3).toHashSet()
|
||||
population.cityInfo = this
|
||||
expansion.cityInfo = this
|
||||
expansion.setTransients()
|
||||
|
@ -292,8 +292,9 @@ class CivilizationInfo {
|
||||
}
|
||||
|
||||
fun isAtWarWith(otherCiv:CivilizationInfo): Boolean {
|
||||
if(otherCiv.isBarbarian() || isBarbarian()) return true
|
||||
if(!diplomacy.containsKey(otherCiv.civName)) // not encountered yet
|
||||
if (otherCiv.civName == civName) return false // never at war with itself
|
||||
if (otherCiv.isBarbarian() || isBarbarian()) return true
|
||||
if (!diplomacy.containsKey(otherCiv.civName)) // not encountered yet
|
||||
return false
|
||||
return getDiplomacyManager(otherCiv).diplomaticStatus == DiplomaticStatus.War
|
||||
}
|
||||
|
@ -141,10 +141,10 @@ class MapUnit {
|
||||
}
|
||||
|
||||
fun updateVisibleTiles() {
|
||||
if(type.isAirUnit()){
|
||||
if(hasUnique("6 tiles in every direction always visible"))
|
||||
viewableTiles = getTile().getTilesInDistance(6) // it's that simple
|
||||
else viewableTiles = listOf() // bomber units don't do recon
|
||||
if(type.isAirUnit()) {
|
||||
viewableTiles = if (hasUnique("6 tiles in every direction always visible"))
|
||||
getTile().getTilesInDistance(6).toList() // it's that simple
|
||||
else listOf() // bomber units don't do recon
|
||||
}
|
||||
else {
|
||||
var visibilityRange = 2
|
||||
@ -161,7 +161,7 @@ class MapUnit {
|
||||
val tile = getTile()
|
||||
if (tile.baseTerrain == Constants.hill && type.isLandUnit()) visibilityRange += 1
|
||||
|
||||
viewableTiles = tile.getViewableTiles(visibilityRange, type.isWaterUnit())
|
||||
viewableTiles = tile.getViewableTilesList(visibilityRange, type.isWaterUnit())
|
||||
}
|
||||
civInfo.updateViewableTiles() // for the civ
|
||||
}
|
||||
@ -388,9 +388,10 @@ class MapUnit {
|
||||
if (amountToHealBy == 0) return
|
||||
|
||||
if (hasUnique("+10 HP when healing")) amountToHealBy += 10
|
||||
val adjacentUnits = currentTile.getTilesInDistance(1).flatMap { it.getUnits() }
|
||||
if (adjacentUnits.isNotEmpty())
|
||||
amountToHealBy += adjacentUnits.map { it.adjacentHealingBonus() }.max()!!
|
||||
val maxAdjacentHealingBonus = currentTile.getTilesInDistance(1)
|
||||
.flatMap { it.getUnits().asSequence() }.map { it.adjacentHealingBonus() }.max()
|
||||
if (maxAdjacentHealingBonus != null)
|
||||
amountToHealBy += maxAdjacentHealingBonus
|
||||
if (hasUnique("All healing effects doubled"))
|
||||
amountToHealBy *= 2
|
||||
healBy(amountToHealBy)
|
||||
@ -441,19 +442,19 @@ class MapUnit {
|
||||
}
|
||||
}
|
||||
|
||||
fun startTurn(){
|
||||
fun startTurn() {
|
||||
currentMovement = getMaxMovement().toFloat()
|
||||
attacksThisTurn=0
|
||||
attacksThisTurn = 0
|
||||
due = true
|
||||
|
||||
// Wake sleeping units if there's an enemy nearby
|
||||
if(isSleeping() && currentTile.getTilesInDistance(2).any {
|
||||
it.militaryUnit!=null && it.militaryUnit!!.civInfo.isAtWarWith(civInfo)
|
||||
if (isSleeping() && currentTile.getTilesInDistance(2).any {
|
||||
it.militaryUnit != null && it.militaryUnit!!.civInfo.isAtWarWith(civInfo)
|
||||
})
|
||||
action=null
|
||||
action = null
|
||||
|
||||
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
|
||||
if (tileOwner != null && !civInfo.canEnterTiles(tileOwner) && !tileOwner.isCityState()) // if an enemy city expanded onto this tile while I was in it
|
||||
movement.teleportToClosestMoveableTile()
|
||||
doPreTurnAction()
|
||||
}
|
||||
@ -520,7 +521,7 @@ class MapUnit {
|
||||
civInfo.addNotification("We have captured a barbarian encampment and recovered [${goldGained.toInt()}] gold!", tile.position, Color.RED)
|
||||
}
|
||||
|
||||
fun disband(){
|
||||
fun disband() {
|
||||
// evacuation of transported units before disbanding, if possible
|
||||
if (type.isAircraftCarrierUnit() || type.isMissileCarrierUnit()) {
|
||||
for(unit in currentTile.getUnits().filter { it.type.isAirUnit() && it.isTransported }) {
|
||||
@ -539,11 +540,17 @@ class MapUnit {
|
||||
unit.disband()
|
||||
}
|
||||
}
|
||||
|
||||
destroy()
|
||||
if(currentTile.getOwner()==civInfo)
|
||||
if (currentTile.getOwner() == civInfo)
|
||||
civInfo.gold += baseUnit.getDisbandGold()
|
||||
if (civInfo.isDefeated()) civInfo.destroy()
|
||||
for (unit in currentTile.getUnits().filter { it.type.isAirUnit() && it.isTransported }) {
|
||||
if (unit.movement.canMoveTo(currentTile)) continue // we disbanded a carrier in a city, it can still stay in the city
|
||||
val tileCanMoveTo = unit.currentTile.getTilesInDistance(unit.getRange())
|
||||
.firstOrNull { unit.movement.canMoveTo(it) }
|
||||
if (tileCanMoveTo != null) unit.movement.moveToTile(tileCanMoveTo)
|
||||
else unit.disband()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getAncientRuinBonus(tile: TileInfo) {
|
||||
@ -591,7 +598,7 @@ class MapUnit {
|
||||
|
||||
// Map of the surrounding area
|
||||
actions.add {
|
||||
val revealCenter = tile.getTilesAtDistance(ANCIENT_RUIN_MAP_REVEAL_OFFSET).random(tileBasedRandom)
|
||||
val revealCenter = tile.getTilesAtDistance(ANCIENT_RUIN_MAP_REVEAL_OFFSET).toList().random(tileBasedRandom)
|
||||
val tilesToReveal = revealCenter
|
||||
.getTilesInDistance(ANCIENT_RUIN_MAP_REVEAL_RANGE)
|
||||
.filter { Random.nextFloat() < ANCIENT_RUIN_MAP_REVEAL_CHANCE }
|
||||
|
@ -68,9 +68,8 @@ open class TileInfo {
|
||||
return false
|
||||
}
|
||||
|
||||
fun containsUnique(unique: String): Boolean {
|
||||
return isNaturalWonder() && getNaturalWonder().uniques.contains(unique)
|
||||
}
|
||||
fun containsUnique(unique: String): Boolean =
|
||||
isNaturalWonder() && getNaturalWonder().uniques.contains(unique)
|
||||
//region pure functions
|
||||
|
||||
/** Returns military, civilian and air units in tile */
|
||||
@ -104,13 +103,8 @@ open class TileInfo {
|
||||
|
||||
// This is for performance - since we access the neighbors of a tile ALL THE TIME,
|
||||
// and the neighbors of a tile never change, it's much more efficient to save the list once and for all!
|
||||
@Transient private var internalNeighbors : List<TileInfo>?=null
|
||||
val neighbors: List<TileInfo>
|
||||
get(){
|
||||
if(internalNeighbors==null)
|
||||
internalNeighbors = getTilesAtDistance(1)
|
||||
return internalNeighbors!!
|
||||
}
|
||||
@delegate:Transient
|
||||
val neighbors: List<TileInfo> by lazy { getTilesAtDistance(1).toList() }
|
||||
|
||||
fun getHeight(): Int {
|
||||
if (baseTerrain == Constants.mountain) return 4
|
||||
@ -127,18 +121,15 @@ open class TileInfo {
|
||||
return containingCity.civInfo
|
||||
}
|
||||
|
||||
fun getTerrainFeature(): Terrain? {
|
||||
return if (terrainFeature == null) null else ruleset.terrains[terrainFeature!!]
|
||||
}
|
||||
fun getTerrainFeature(): Terrain? =
|
||||
if (terrainFeature == null) null else ruleset.terrains[terrainFeature!!]
|
||||
|
||||
fun isWorked(): Boolean {
|
||||
val city = getCity()
|
||||
return city!=null && city.workedTiles.contains(position)
|
||||
}
|
||||
|
||||
fun getTileStats(observingCiv: CivilizationInfo): Stats {
|
||||
return getTileStats(getCity(), observingCiv)
|
||||
}
|
||||
fun getTileStats(observingCiv: CivilizationInfo): Stats = getTileStats(getCity(), observingCiv)
|
||||
|
||||
fun getTileStats(city: CityInfo?, observingCiv: CivilizationInfo): Stats {
|
||||
var stats = getBaseTerrain().clone()
|
||||
@ -264,21 +255,20 @@ open class TileInfo {
|
||||
|
||||
fun isCoastalTile() = neighbors.any { it.baseTerrain==Constants.coast }
|
||||
|
||||
fun hasViewableResource(civInfo: CivilizationInfo): Boolean {
|
||||
return resource != null && (getTileResource().revealedBy == null || civInfo.tech.isResearched(getTileResource().revealedBy!!))
|
||||
}
|
||||
fun hasViewableResource(civInfo: CivilizationInfo): Boolean =
|
||||
resource != null && (getTileResource().revealedBy == null || civInfo.tech.isResearched(getTileResource().revealedBy!!))
|
||||
|
||||
fun getViewableTiles(distance:Int, ignoreCurrentTileHeight:Boolean = false): List<TileInfo> {
|
||||
return tileMap.getViewableTiles(this.position,distance,ignoreCurrentTileHeight)
|
||||
}
|
||||
fun getViewableTilesList(distance:Int, ignoreCurrentTileHeight: Boolean): List<TileInfo> =
|
||||
tileMap.getViewableTiles(position, distance, ignoreCurrentTileHeight)
|
||||
|
||||
fun getTilesInDistance(distance:Int): List<TileInfo> {
|
||||
return tileMap.getTilesInDistance(position,distance)
|
||||
}
|
||||
fun getTilesInDistance(distance: Int): Sequence<TileInfo> =
|
||||
tileMap.getTilesInDistance(position,distance)
|
||||
|
||||
fun getTilesAtDistance(distance:Int): List<TileInfo> {
|
||||
return tileMap.getTilesAtDistance(position,distance)
|
||||
}
|
||||
fun getTilesInDistanceRange(range: IntRange): Sequence<TileInfo> =
|
||||
tileMap.getTilesInDistanceRange(position, range)
|
||||
|
||||
fun getTilesAtDistance(distance:Int): Sequence<TileInfo> =
|
||||
tileMap.getTilesAtDistance(position, distance)
|
||||
|
||||
fun getDefensiveBonus(): Float {
|
||||
var bonus = getBaseTerrain().defenceBonus
|
||||
|
@ -73,51 +73,49 @@ class TileMap {
|
||||
return get(vector.x.toInt(), vector.y.toInt())
|
||||
}
|
||||
|
||||
fun getTilesInDistance(origin: Vector2, distance: Int): List<TileInfo> {
|
||||
val tilesToReturn = mutableListOf<TileInfo>()
|
||||
for (i in 0 .. distance) {
|
||||
tilesToReturn += getTilesAtDistance(origin, i)
|
||||
}
|
||||
return tilesToReturn
|
||||
}
|
||||
fun getTilesInDistance(origin: Vector2, distance: Int): Sequence<TileInfo> =
|
||||
getTilesInDistanceRange(origin, 0..distance)
|
||||
|
||||
fun getTilesAtDistance(origin: Vector2, distance: Int): List<TileInfo> {
|
||||
if(distance==0) return listOf(get(origin))
|
||||
val tilesToReturn = ArrayList<TileInfo>()
|
||||
fun getTilesInDistanceRange(origin: Vector2, range: IntRange): Sequence<TileInfo> =
|
||||
sequence {
|
||||
for (i in range)
|
||||
yield(getTilesAtDistance(origin, i))
|
||||
}.flatMap { it }
|
||||
|
||||
fun addIfTileExists(x:Int,y:Int){
|
||||
if(contains(x,y))
|
||||
tilesToReturn += get(x,y)
|
||||
}
|
||||
fun getTilesAtDistance(origin: Vector2, distance: Int): Sequence<TileInfo> =
|
||||
if (distance <= 0) // silently take negatives.
|
||||
sequenceOf(get(origin))
|
||||
else
|
||||
sequence {
|
||||
fun getIfTileExistsOrNull(x: Int, y: Int) = if (contains(x, y)) get(x, y) else null
|
||||
|
||||
val centerX = origin.x.toInt()
|
||||
val centerY = origin.y.toInt()
|
||||
val centerX = origin.x.toInt()
|
||||
val centerY = origin.y.toInt()
|
||||
|
||||
// Start from 6 O'clock point which means (-distance, -distance) away from the center point
|
||||
var currentX = centerX - distance
|
||||
var currentY = centerY - distance
|
||||
// Start from 6 O'clock point which means (-distance, -distance) away from the center point
|
||||
var currentX = centerX - distance
|
||||
var currentY = centerY - distance
|
||||
|
||||
for (i in 0 until distance) { // From 6 to 8
|
||||
addIfTileExists(currentX,currentY)
|
||||
// We want to get the tile on the other side of the clock,
|
||||
// so if we're at current = origin-delta we want to get to origin+delta.
|
||||
// The simplest way to do this is 2*origin - current = 2*origin- (origin - delta) = origin+delta
|
||||
addIfTileExists(2*centerX - currentX, 2*centerY - currentY)
|
||||
currentX += 1 // we're going upwards to the left, towards 8 o'clock
|
||||
}
|
||||
for (i in 0 until distance) { // 8 to 10
|
||||
addIfTileExists(currentX,currentY)
|
||||
addIfTileExists(2*centerX - currentX, 2*centerY - currentY)
|
||||
currentX += 1
|
||||
currentY += 1 // we're going up the left side of the hexagon so we're going "up" - +1,+1
|
||||
}
|
||||
for (i in 0 until distance) { // 10 to 12
|
||||
addIfTileExists(currentX,currentY)
|
||||
addIfTileExists(2*centerX - currentX, 2*centerY - currentY)
|
||||
currentY += 1 // we're going up the top left side of the hexagon so we're heading "up and to the right"
|
||||
}
|
||||
return tilesToReturn
|
||||
}
|
||||
for (i in 0 until distance) { // From 6 to 8
|
||||
yield(getIfTileExistsOrNull(currentX, currentY))
|
||||
// We want to get the tile on the other side of the clock,
|
||||
// so if we're at current = origin-delta we want to get to origin+delta.
|
||||
// The simplest way to do this is 2*origin - current = 2*origin- (origin - delta) = origin+delta
|
||||
yield(getIfTileExistsOrNull(2 * centerX - currentX, 2 * centerY - currentY))
|
||||
currentX += 1 // we're going upwards to the left, towards 8 o'clock
|
||||
}
|
||||
for (i in 0 until distance) { // 8 to 10
|
||||
yield(getIfTileExistsOrNull(currentX, currentY))
|
||||
yield(getIfTileExistsOrNull(2 * centerX - currentX, 2 * centerY - currentY))
|
||||
currentX += 1
|
||||
currentY += 1 // we're going up the left side of the hexagon so we're going "up" - +1,+1
|
||||
}
|
||||
for (i in 0 until distance) { // 10 to 12
|
||||
yield(getIfTileExistsOrNull(currentX, currentY))
|
||||
yield(getIfTileExistsOrNull(2 * centerX - currentX, 2 * centerY - currentY))
|
||||
currentY += 1 // we're going up the top left side of the hexagon so we're heading "up and to the right"
|
||||
}
|
||||
}.filterNotNull()
|
||||
|
||||
/** Tries to place the [unitName] into the [TileInfo] closest to the given the [position]
|
||||
*
|
||||
@ -134,49 +132,53 @@ class TileMap {
|
||||
): MapUnit? {
|
||||
val unit = gameInfo.ruleSet.units[unitName]!!.getMapUnit(gameInfo.ruleSet)
|
||||
|
||||
fun isTileMovePotential(tileInfo:TileInfo): Boolean {
|
||||
if(unit.type.isAirUnit()) return true
|
||||
if(unit.type.isWaterUnit()) return tileInfo.isWater || tileInfo.isCityCenter()
|
||||
else return tileInfo.isLand
|
||||
}
|
||||
fun isTileMovePotential(tileInfo: TileInfo): Boolean =
|
||||
when {
|
||||
unit.type.isAirUnit() -> true
|
||||
unit.type.isWaterUnit() -> tileInfo.isWater || tileInfo.isCityCenter()
|
||||
else -> tileInfo.isLand
|
||||
}
|
||||
|
||||
val viableTilesToPlaceUnitInAtDistance1 = getTilesInDistance(position, 1).filter { isTileMovePotential(it) }
|
||||
val viableTilesToPlaceUnitInAtDistance1 = getTilesAtDistance(position, 1)
|
||||
.filter { isTileMovePotential(it) }.toSet()
|
||||
// This is so that units don't skip over non-potential tiles to go elsewhere -
|
||||
// e.g. a city 2 tiles away from a lake could spawn water units in the lake...Or spawn beyond a mountain range...
|
||||
val viableTilesToPlaceUnitInAtDistance2 = getTilesAtDistance(position, 2)
|
||||
.filter { isTileMovePotential(it) && it.neighbors.any { n->n in viableTilesToPlaceUnitInAtDistance1 } }
|
||||
val viableTilesToPlaceUnitIn = getTilesAtDistance(position, 2)
|
||||
.filter {
|
||||
isTileMovePotential(it)
|
||||
&& it.neighbors.any { n -> n in viableTilesToPlaceUnitInAtDistance1 }
|
||||
} + viableTilesToPlaceUnitInAtDistance1
|
||||
|
||||
val viableTilesToPlaceUnitIn = viableTilesToPlaceUnitInAtDistance1.union(viableTilesToPlaceUnitInAtDistance2)
|
||||
|
||||
unit.assignOwner(civInfo,false) // both the civ name and actual civ need to be in here in order to calculate the canMoveTo...Darn
|
||||
unit.assignOwner(civInfo, false) // both the civ name and actual civ need to be in here in order to calculate the canMoveTo...Darn
|
||||
val unitToPlaceTile = viableTilesToPlaceUnitIn.firstOrNull { unit.movement.canMoveTo(it) }
|
||||
|
||||
if(unitToPlaceTile!=null) {
|
||||
// Remove the tile improvement, e.g. when placing the starter units (so they don't spawn on ruins/encampments)
|
||||
if (removeImprovement) unitToPlaceTile.improvement = null
|
||||
// only once we know the unit can be placed do we add it to the civ's unit list
|
||||
unit.putInTile(unitToPlaceTile)
|
||||
unit.currentMovement = unit.getMaxMovement().toFloat()
|
||||
|
||||
// Only once we add the unit to the civ we can activate addPromotion, because it will try to update civ viewable tiles
|
||||
for(promotion in unit.baseUnit.promotions)
|
||||
unit.promotions.addPromotion(promotion, true)
|
||||
|
||||
// And update civ stats, since the new unit changes both unit upkeep and resource consumption
|
||||
civInfo.updateStatsForNextTurn()
|
||||
civInfo.updateDetailedCivResources()
|
||||
}
|
||||
else {
|
||||
if (unitToPlaceTile == null) {
|
||||
civInfo.removeUnit(unit) // since we added it to the civ units in the previous assignOwner
|
||||
return null // we didn't actually create a unit...
|
||||
}
|
||||
|
||||
// Remove the tile improvement, e.g. when placing the starter units (so they don't spawn on ruins/encampments)
|
||||
if (removeImprovement) unitToPlaceTile.improvement = null
|
||||
// only once we know the unit can be placed do we add it to the civ's unit list
|
||||
unit.putInTile(unitToPlaceTile)
|
||||
unit.currentMovement = unit.getMaxMovement().toFloat()
|
||||
|
||||
// Only once we add the unit to the civ we can activate addPromotion, because it will try to update civ viewable tiles
|
||||
for (promotion in unit.baseUnit.promotions)
|
||||
unit.promotions.addPromotion(promotion, true)
|
||||
|
||||
// And update civ stats, since the new unit changes both unit upkeep and resource consumption
|
||||
civInfo.updateStatsForNextTurn()
|
||||
civInfo.updateDetailedCivResources()
|
||||
|
||||
return unit
|
||||
}
|
||||
|
||||
|
||||
fun getViewableTiles(position: Vector2, sightDistance: Int, ignoreCurrentTileHeight: Boolean = false): List<TileInfo> {
|
||||
if (ignoreCurrentTileHeight) return getTilesInDistance(position, sightDistance)
|
||||
fun getViewableTiles(position: Vector2, sightDistance: Int, ignoreCurrentTileHeight: Boolean)
|
||||
: List<TileInfo> {
|
||||
if (ignoreCurrentTileHeight) return getTilesInDistance(position, sightDistance).toList()
|
||||
|
||||
val viewableTiles = getTilesInDistance(position, 1).toMutableList()
|
||||
val currentTileHeight = get(position).getHeight()
|
||||
|
@ -43,9 +43,9 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
|
||||
class ParentTileAndTotalDistance(val parentTile:TileInfo, val totalDistance: Float)
|
||||
|
||||
fun getDistanceToTilesWithinTurn(origin: Vector2, unitMovement: Float): PathsToTilesWithinTurn {
|
||||
if(unitMovement==0f) return PathsToTilesWithinTurn()
|
||||
|
||||
val distanceToTiles = PathsToTilesWithinTurn()
|
||||
if(unitMovement==0f) return distanceToTiles
|
||||
|
||||
val unitTile = unit.getTile().tileMap[origin]
|
||||
distanceToTiles[unitTile] = ParentTileAndTotalDistance(unitTile,0f)
|
||||
var tilesToCheck = listOf(unitTile)
|
||||
@ -344,7 +344,8 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
|
||||
return true
|
||||
}
|
||||
|
||||
fun getDistanceToTiles() = getDistanceToTilesWithinTurn(unit.currentTile.position,unit.currentMovement)
|
||||
fun getDistanceToTiles(): PathsToTilesWithinTurn
|
||||
= getDistanceToTilesWithinTurn(unit.currentTile.position,unit.currentMovement)
|
||||
|
||||
fun getArialPathsToCities(): HashMap<TileInfo, ArrayList<TileInfo>> {
|
||||
var tilesToCheck = ArrayList<TileInfo>()
|
||||
|
@ -98,7 +98,7 @@ class CityScreen(internal val city: CityInfo): CameraStageBaseScreen() {
|
||||
updateAnnexAndRazeCityButton()
|
||||
updateTileGroups()
|
||||
|
||||
if (city.getCenterTile().getTilesAtDistance(4).isNotEmpty())
|
||||
if (city.getCenterTile().getTilesAtDistance(4).any())
|
||||
displayTutorial(Tutorial.CityRange)
|
||||
}
|
||||
|
||||
|
@ -251,18 +251,20 @@ class WorldMapHolder(internal val worldScreen: WorldScreen, internal val tileMap
|
||||
tileGroups[unit.getTile()]!!.selectUnit(unit)
|
||||
|
||||
val isAirUnit = unit.type.isAirUnit()
|
||||
val tilesInMoveRange = if(isAirUnit) unit.getTile().getTilesInDistance(unit.getRange())
|
||||
else unit.movement.getDistanceToTiles().keys
|
||||
val tilesInMoveRange =
|
||||
if (isAirUnit)
|
||||
unit.getTile().getTilesInDistance(unit.getRange())
|
||||
else
|
||||
unit.movement.getDistanceToTiles().keys.asSequence()
|
||||
|
||||
if(isAirUnit)
|
||||
for(tile in tilesInMoveRange)
|
||||
tileGroups[tile]!!.showCircle(Color.BLUE,0.3f)
|
||||
|
||||
for (tile: TileInfo in tilesInMoveRange)
|
||||
for (tile in tilesInMoveRange) {
|
||||
val tileToColor = tileGroups.getValue(tile)
|
||||
if (isAirUnit)
|
||||
tileToColor.showCircle(Color.BLUE, 0.3f)
|
||||
if (unit.movement.canMoveTo(tile))
|
||||
tileGroups[tile]!!.showCircle(Color.WHITE,
|
||||
tileToColor.showCircle(Color.WHITE,
|
||||
if (UncivGame.Current.settings.singleTapMove || isAirUnit) 0.7f else 0.3f)
|
||||
|
||||
}
|
||||
|
||||
val unitType = unit.type
|
||||
val attackableTiles: List<TileInfo> = if (unitType.isCivilian()) listOf()
|
||||
@ -281,16 +283,16 @@ class WorldMapHolder(internal val worldScreen: WorldScreen, internal val tileMap
|
||||
else 0.5f
|
||||
for (tile in tileGroups.values) {
|
||||
if (tile.icons.populationIcon != null) tile.icons.populationIcon!!.color.a = fadeout
|
||||
if (tile.icons.improvementIcon != null && tile.tileInfo.improvement!=Constants.barbarianEncampment
|
||||
&& tile.tileInfo.improvement!=Constants.ancientRuins)
|
||||
if (tile.icons.improvementIcon != null && tile.tileInfo.improvement != Constants.barbarianEncampment
|
||||
&& tile.tileInfo.improvement != Constants.ancientRuins)
|
||||
tile.icons.improvementIcon!!.color.a = fadeout
|
||||
if (tile.resourceImage != null) tile.resourceImage!!.color.a = fadeout
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateTilegroupsForSelectedCity(city: CityInfo, playerViewableTilePositions: HashSet<Vector2>) {
|
||||
val attackableTiles: List<TileInfo> = UnitAutomation().getBombardTargets(city)
|
||||
.filter { (UncivGame.Current.viewEntireMapForDebug || playerViewableTilePositions.contains(it.position)) }
|
||||
val attackableTiles = UnitAutomation().getBombardTargets(city)
|
||||
.filter { (UncivGame.Current.viewEntireMapForDebug || playerViewableTilePositions.contains(it.position)) }
|
||||
for (attackableTile in attackableTiles) {
|
||||
tileGroups[attackableTile]!!.showCircle(colorFromRGB(237, 41, 57))
|
||||
tileGroups[attackableTile]!!.showCrosshair()
|
||||
|
Loading…
Reference in New Issue
Block a user