Moved tile ranking and construction picking to Automation
This commit is contained in:
parent
5eec4deb84
commit
c6136acbec
5 changed files with 98 additions and 45 deletions
|
@ -3,11 +3,13 @@ package com.unciv.logic
|
||||||
import com.unciv.UnCivGame
|
import com.unciv.UnCivGame
|
||||||
import com.unciv.logic.battle.Battle
|
import com.unciv.logic.battle.Battle
|
||||||
import com.unciv.logic.battle.MapUnitCombatant
|
import com.unciv.logic.battle.MapUnitCombatant
|
||||||
|
import com.unciv.logic.city.CityConstructions
|
||||||
import com.unciv.logic.city.CityInfo
|
import com.unciv.logic.city.CityInfo
|
||||||
import com.unciv.logic.civilization.CivilizationInfo
|
import com.unciv.logic.civilization.CivilizationInfo
|
||||||
import com.unciv.logic.map.MapUnit
|
import com.unciv.logic.map.MapUnit
|
||||||
import com.unciv.logic.map.TileInfo
|
import com.unciv.logic.map.TileInfo
|
||||||
import com.unciv.logic.map.UnitType
|
import com.unciv.logic.map.UnitType
|
||||||
|
import com.unciv.models.gamebasics.Building
|
||||||
import com.unciv.models.gamebasics.GameBasics
|
import com.unciv.models.gamebasics.GameBasics
|
||||||
import com.unciv.models.gamebasics.TileImprovement
|
import com.unciv.models.gamebasics.TileImprovement
|
||||||
import com.unciv.ui.utils.getRandom
|
import com.unciv.ui.utils.getRandom
|
||||||
|
@ -20,6 +22,7 @@ class Automation {
|
||||||
.filter {
|
.filter {
|
||||||
(it.unit == null || it == currentTile)
|
(it.unit == null || it == currentTile)
|
||||||
&& it.improvement == null
|
&& it.improvement == null
|
||||||
|
&& (it.getCity()==null || it.getCity()!!.civInfo == civInfo) // don't work tiles belonging to another civ
|
||||||
&& it.canBuildImprovement(chooseImprovement(it), civInfo)
|
&& it.canBuildImprovement(chooseImprovement(it), civInfo)
|
||||||
}
|
}
|
||||||
.maxBy { getPriority(it, civInfo) }
|
.maxBy { getPriority(it, civInfo) }
|
||||||
|
@ -27,6 +30,21 @@ class Automation {
|
||||||
else return currentTile
|
else return currentTile
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal fun rankTile(tile: TileInfo, civInfo: CivilizationInfo): Float {
|
||||||
|
val stats = tile.getTileStats(null, civInfo)
|
||||||
|
var rank = 0.0f
|
||||||
|
if (stats.food <= 2) rank += stats.food
|
||||||
|
else rank += (2 + (stats.food - 2) / 2f) // 1 point for each food up to 2, from there on half a point
|
||||||
|
rank += (stats.gold / 2)
|
||||||
|
rank += stats.production
|
||||||
|
rank += stats.science
|
||||||
|
rank += stats.culture
|
||||||
|
if (tile.improvement == null) rank += 0.5f // improvement potential!
|
||||||
|
if (tile.resource != null) rank += 1.0f
|
||||||
|
return rank
|
||||||
|
}
|
||||||
|
|
||||||
private fun getPriority(tileInfo: TileInfo, civInfo: CivilizationInfo): Int {
|
private fun getPriority(tileInfo: TileInfo, civInfo: CivilizationInfo): Int {
|
||||||
var priority = 0
|
var priority = 0
|
||||||
if (tileInfo.isWorked()) priority += 3
|
if (tileInfo.isWorked()) priority += 3
|
||||||
|
@ -67,6 +85,15 @@ class Automation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun rankTileAsCityCenter(tileInfo: TileInfo, civInfo: CivilizationInfo): Float {
|
||||||
|
val bestTilesFromOuterLayer = tileInfo.tileMap.getTilesAtDistance(tileInfo.position,2)
|
||||||
|
.sortedByDescending { rankTile(it,civInfo) }.take(2)
|
||||||
|
val top5Tiles = tileInfo.neighbors.union(bestTilesFromOuterLayer)
|
||||||
|
.sortedByDescending { rankTile(it,civInfo) }
|
||||||
|
.take(5)
|
||||||
|
return top5Tiles.map { rankTile(it,civInfo) }.sum()
|
||||||
|
}
|
||||||
|
|
||||||
fun automateCivMoves(civInfo: CivilizationInfo) {
|
fun automateCivMoves(civInfo: CivilizationInfo) {
|
||||||
if (civInfo.tech.techsToResearch.isEmpty()) {
|
if (civInfo.tech.techsToResearch.isEmpty()) {
|
||||||
val researchableTechs = GameBasics.Technologies.values.filter { civInfo.tech.canBeResearched(it.name) }
|
val researchableTechs = GameBasics.Technologies.values.filter { civInfo.tech.canBeResearched(it.name) }
|
||||||
|
@ -74,13 +101,25 @@ class Automation {
|
||||||
civInfo.tech.techsResearched.add(techToResearch!!.name)
|
civInfo.tech.techsResearched.add(techToResearch!!.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (unit in civInfo.getCivUnits()) {
|
||||||
|
automateUnitMoves(unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// train settler?
|
||||||
|
if (civInfo.cities.any()
|
||||||
|
&& civInfo.happiness > 2*civInfo.cities.size +5
|
||||||
|
&& civInfo.getCivUnits().none { it.name == "Settler" }
|
||||||
|
&& civInfo.cities.none { it.cityConstructions.currentConstruction == "Settler" }) {
|
||||||
|
|
||||||
|
val bestCity = civInfo.cities.maxBy { it.cityStats.currentCityStats.production }!!
|
||||||
|
if(bestCity.cityConstructions.builtBuildings.size > 1) // 2 buildings or more, otherwisse focus on self first
|
||||||
|
bestCity.cityConstructions.currentConstruction = "Settler"
|
||||||
|
}
|
||||||
|
|
||||||
for (city in civInfo.cities) {
|
for (city in civInfo.cities) {
|
||||||
if (city.health < city.getMaxHealth()) trainCombatUnit(city)
|
if (city.health < city.getMaxHealth()) trainCombatUnit(city)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (unit in civInfo.getCivUnits()) {
|
|
||||||
automateUnitMoves(unit)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun trainCombatUnit(city: CityInfo) {
|
private fun trainCombatUnit(city: CityInfo) {
|
||||||
|
@ -90,7 +129,24 @@ class Automation {
|
||||||
fun automateUnitMoves(unit: MapUnit) {
|
fun automateUnitMoves(unit: MapUnit) {
|
||||||
|
|
||||||
if (unit.name == "Settler") {
|
if (unit.name == "Settler") {
|
||||||
|
|
||||||
|
val tileMap = unit.civInfo.gameInfo.tileMap
|
||||||
|
|
||||||
|
// find best city location within 5 tiles
|
||||||
|
val bestCityLocation = tileMap.getTilesInDistance(unit.getTile().position, 5)
|
||||||
|
.filterNot { it.isCityCenter || it.neighbors.any { it.isCityCenter } }
|
||||||
|
.sortedByDescending { rankTileAsCityCenter(it, unit.civInfo) }
|
||||||
|
.first()
|
||||||
|
|
||||||
|
if(unit.getTile() == bestCityLocation)
|
||||||
UnitActions().getUnitActions(unit, UnCivGame.Current.worldScreen!!).first { it.name == "Found city" }.action()
|
UnitActions().getUnitActions(unit, UnCivGame.Current.worldScreen!!).first { it.name == "Found city" }.action()
|
||||||
|
|
||||||
|
else {
|
||||||
|
unit.headTowards(bestCityLocation.position)
|
||||||
|
if(unit.currentMovement>0 && unit.getTile()==bestCityLocation)
|
||||||
|
UnitActions().getUnitActions(unit, UnCivGame.Current.worldScreen!!).first { it.name == "Found city" }.action()
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,4 +234,24 @@ class Automation {
|
||||||
// else, go to a random space
|
// else, go to a random space
|
||||||
unit.moveToTile(distanceToTiles.keys.filter { it.unit == null }.toList().getRandom())
|
unit.moveToTile(distanceToTiles.keys.filter { it.unit == null }.toList().getRandom())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun chooseNextConstruction(cityConstructions: CityConstructions) {
|
||||||
|
cityConstructions.run {
|
||||||
|
val buildableNotWonders = getBuildableBuildings().filterNot { (getConstruction(it) as Building).isWonder }
|
||||||
|
if (!buildableNotWonders.isEmpty()) {
|
||||||
|
currentConstruction = buildableNotWonders.first()
|
||||||
|
} else {
|
||||||
|
val militaryUnits = cityInfo.civInfo.getCivUnits().filter { it.getBaseUnit().unitType != UnitType.Civilian }.size
|
||||||
|
if (cityInfo.civInfo.getCivUnits().none { it.name == CityConstructions.Worker } || militaryUnits > cityInfo.civInfo.cities.size * 2) {
|
||||||
|
currentConstruction = CityConstructions.Worker
|
||||||
|
} else {
|
||||||
|
trainCombatUnit(cityInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cityInfo.civInfo == cityInfo.civInfo.gameInfo.getPlayerCivilization())
|
||||||
|
cityInfo.civInfo.addNotification("Work has started on $currentConstruction", cityInfo.location)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
package com.unciv.logic.city
|
package com.unciv.logic.city
|
||||||
|
|
||||||
import com.unciv.logic.map.UnitType
|
import com.unciv.logic.Automation
|
||||||
import com.unciv.models.gamebasics.Building
|
import com.unciv.models.gamebasics.Building
|
||||||
import com.unciv.models.gamebasics.GameBasics
|
import com.unciv.models.gamebasics.GameBasics
|
||||||
import com.unciv.models.stats.Stats
|
import com.unciv.models.stats.Stats
|
||||||
|
@ -16,7 +16,7 @@ class CityConstructions {
|
||||||
var currentConstruction: String = "Monument" // default starting building!
|
var currentConstruction: String = "Monument" // default starting building!
|
||||||
|
|
||||||
|
|
||||||
private fun getBuildableBuildings(): List<String> = GameBasics.Buildings.values
|
internal fun getBuildableBuildings(): List<String> = GameBasics.Buildings.values
|
||||||
.filter { it.isBuildable(this) }.map { it.name }
|
.filter { it.isBuildable(this) }.map { it.name }
|
||||||
|
|
||||||
// Library and public school unique (not actualy unique, though...hmm)
|
// Library and public school unique (not actualy unique, though...hmm)
|
||||||
|
@ -62,7 +62,7 @@ class CityConstructions {
|
||||||
|
|
||||||
fun isBuilding(buildingName: String): Boolean = currentConstruction == buildingName
|
fun isBuilding(buildingName: String): Boolean = currentConstruction == buildingName
|
||||||
|
|
||||||
private fun getConstruction(constructionName: String): IConstruction {
|
internal fun getConstruction(constructionName: String): IConstruction {
|
||||||
if (GameBasics.Buildings.containsKey(constructionName))
|
if (GameBasics.Buildings.containsKey(constructionName))
|
||||||
return GameBasics.Buildings[constructionName]!!
|
return GameBasics.Buildings[constructionName]!!
|
||||||
else if (GameBasics.Units.containsKey(constructionName))
|
else if (GameBasics.Units.containsKey(constructionName))
|
||||||
|
@ -87,7 +87,7 @@ class CityConstructions {
|
||||||
if (!construction.isBuildable(this)) {
|
if (!construction.isBuildable(this)) {
|
||||||
// We can't build this building anymore! (Wonder has been built / resource is gone / etc.)
|
// We can't build this building anymore! (Wonder has been built / resource is gone / etc.)
|
||||||
cityInfo.civInfo.addNotification("Cannot continue work on $saveCurrentConstruction", cityInfo.location)
|
cityInfo.civInfo.addNotification("Cannot continue work on $saveCurrentConstruction", cityInfo.location)
|
||||||
chooseNextConstruction()
|
Automation().chooseNextConstruction(this)
|
||||||
construction = getConstruction(currentConstruction)
|
construction = getConstruction(currentConstruction)
|
||||||
} else
|
} else
|
||||||
currentConstruction = saveCurrentConstruction
|
currentConstruction = saveCurrentConstruction
|
||||||
|
@ -99,28 +99,11 @@ class CityConstructions {
|
||||||
inProgressConstructions.remove(currentConstruction)
|
inProgressConstructions.remove(currentConstruction)
|
||||||
cityInfo.civInfo.addNotification(currentConstruction + " has been built in " + cityInfo.name, cityInfo.location)
|
cityInfo.civInfo.addNotification(currentConstruction + " has been built in " + cityInfo.name, cityInfo.location)
|
||||||
|
|
||||||
chooseNextConstruction()
|
Automation().chooseNextConstruction(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun chooseNextConstruction() {
|
|
||||||
val buildableNotWonders = getBuildableBuildings().filterNot{(getConstruction(it) as Building).isWonder}
|
|
||||||
if(!buildableNotWonders.isEmpty()){
|
|
||||||
currentConstruction = buildableNotWonders.first()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
val militaryUnits = cityInfo.civInfo.getCivUnits().filter { it.getBaseUnit().unitType != UnitType.Civilian }.size
|
|
||||||
if (cityInfo.civInfo.getCivUnits().none { it.name== Worker } || militaryUnits > cityInfo.civInfo.cities.size*2) {
|
|
||||||
currentConstruction = Worker
|
|
||||||
} else {
|
|
||||||
currentConstruction = "Archer"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(cityInfo.civInfo == cityInfo.civInfo.gameInfo.getPlayerCivilization())
|
|
||||||
cityInfo.civInfo.addNotification("Work has started on $currentConstruction", cityInfo.location)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun workDone(constructionName: String): Int {
|
private fun workDone(constructionName: String): Int {
|
||||||
if (inProgressConstructions.containsKey(constructionName)) return inProgressConstructions[constructionName]!!
|
if (inProgressConstructions.containsKey(constructionName)) return inProgressConstructions[constructionName]!!
|
||||||
else return 0
|
else return 0
|
||||||
|
@ -141,7 +124,7 @@ class CityConstructions {
|
||||||
fun purchaseBuilding(buildingName: String) {
|
fun purchaseBuilding(buildingName: String) {
|
||||||
cityInfo.civInfo.gold -= getConstruction(buildingName).getGoldCost(cityInfo.civInfo.policies.adoptedPolicies)
|
cityInfo.civInfo.gold -= getConstruction(buildingName).getGoldCost(cityInfo.civInfo.policies.adoptedPolicies)
|
||||||
getConstruction(buildingName).postBuildEvent(this)
|
getConstruction(buildingName).postBuildEvent(this)
|
||||||
if (currentConstruction == buildingName) chooseNextConstruction()
|
if (currentConstruction == buildingName) Automation().chooseNextConstruction(this)
|
||||||
cityInfo.cityStats.update()
|
cityInfo.cityStats.update()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,11 +133,15 @@ class CityConstructions {
|
||||||
if (cultureBuildingToBuild == null) return
|
if (cultureBuildingToBuild == null) return
|
||||||
builtBuildings.add(cultureBuildingToBuild)
|
builtBuildings.add(cultureBuildingToBuild)
|
||||||
if (currentConstruction == cultureBuildingToBuild)
|
if (currentConstruction == cultureBuildingToBuild)
|
||||||
chooseNextConstruction()
|
Automation().chooseNextConstruction(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
internal const val Worker = "Worker"
|
internal const val Worker = "Worker"
|
||||||
internal const val Settler = "Settler"
|
internal const val Settler = "Settler"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun chooseNextConstruction() {
|
||||||
|
Automation().chooseNextConstruction(this)
|
||||||
|
}
|
||||||
} // for json parsing, we need to have a default constructor
|
} // for json parsing, we need to have a default constructor
|
|
@ -1,5 +1,7 @@
|
||||||
package com.unciv.logic.city
|
package com.unciv.logic.city
|
||||||
|
|
||||||
|
import com.unciv.logic.Automation
|
||||||
|
|
||||||
class CityExpansionManager {
|
class CityExpansionManager {
|
||||||
|
|
||||||
@Transient
|
@Transient
|
||||||
|
@ -16,11 +18,11 @@ class CityExpansionManager {
|
||||||
// https://www.reddit.com/r/civ/comments/58rxkk/how_in_gods_name_do_borders_expand_in_civ_vi/ has it
|
// https://www.reddit.com/r/civ/comments/58rxkk/how_in_gods_name_do_borders_expand_in_civ_vi/ has it
|
||||||
// (per game XML files) at 6*(t+0.4813)^1.3
|
// (per game XML files) at 6*(t+0.4813)^1.3
|
||||||
// The second seems to be more based, so I'll go with that
|
// The second seems to be more based, so I'll go with that
|
||||||
//Speciality of Angkor Wat
|
|
||||||
fun getCultureToNextTile(): Int {
|
fun getCultureToNextTile(): Int {
|
||||||
val numTilesClaimed = cityInfo.tiles.size - 7
|
val numTilesClaimed = cityInfo.tiles.size - 7
|
||||||
var cultureToNextTile = 6 * Math.pow(numTilesClaimed + 1.4813, 1.3)
|
var cultureToNextTile = 6 * Math.pow(numTilesClaimed + 1.4813, 1.3)
|
||||||
if (cityInfo.civInfo.buildingUniques.contains("NewTileCostReduction")) cultureToNextTile *= 0.75
|
if (cityInfo.civInfo.buildingUniques.contains("NewTileCostReduction")) cultureToNextTile *= 0.75 //Speciality of Angkor Wat
|
||||||
if (cityInfo.civInfo.policies.isAdopted("Tradition")) cultureToNextTile *= 0.75
|
if (cityInfo.civInfo.policies.isAdopted("Tradition")) cultureToNextTile *= 0.75
|
||||||
return Math.round(cultureToNextTile).toInt()
|
return Math.round(cultureToNextTile).toInt()
|
||||||
}
|
}
|
||||||
|
@ -31,7 +33,7 @@ class CityExpansionManager {
|
||||||
for (i in 2..3) {
|
for (i in 2..3) {
|
||||||
val tiles = cityInfo.civInfo.gameInfo.tileMap.getTilesInDistance(cityInfo.location, i).filter { it.getOwner() == null }
|
val tiles = cityInfo.civInfo.gameInfo.tileMap.getTilesInDistance(cityInfo.location, i).filter { it.getOwner() == null }
|
||||||
if (tiles.isEmpty()) continue
|
if (tiles.isEmpty()) continue
|
||||||
val chosenTile = tiles.maxBy { cityInfo.rankTile(it) }
|
val chosenTile = tiles.maxBy { Automation().rankTile(it,cityInfo.civInfo) }
|
||||||
cityInfo.tiles.add(chosenTile!!.position)
|
cityInfo.tiles.add(chosenTile!!.position)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -127,19 +127,6 @@ class CityInfo {
|
||||||
health = min(health+maxHealth/10, maxHealth)
|
health = min(health+maxHealth/10, maxHealth)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun rankTile(tile: TileInfo): Float {
|
|
||||||
val stats = tile.getTileStats(this, civInfo)
|
|
||||||
var rank = 0.0f
|
|
||||||
if (stats.food <= 2) rank += stats.food
|
|
||||||
else rank += (2 + (stats.food - 2) / 2f) // 1 point for each food up to 2, from there on half a point
|
|
||||||
rank += (stats.gold / 2)
|
|
||||||
rank += stats.production
|
|
||||||
rank += stats.science
|
|
||||||
rank += stats.culture
|
|
||||||
if (tile.improvement == null) rank += 0.5f // improvement potential!
|
|
||||||
if (tile.resource != null) rank += 1.0f
|
|
||||||
return rank
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun getMaxHealth(): Int {
|
internal fun getMaxHealth(): Int {
|
||||||
return 200 // add more later when walls citadl etc.
|
return 200 // add more later when walls citadl etc.
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.unciv.logic.city
|
package com.unciv.logic.city
|
||||||
|
|
||||||
|
import com.unciv.logic.Automation
|
||||||
import com.unciv.logic.map.TileInfo
|
import com.unciv.logic.map.TileInfo
|
||||||
import com.unciv.models.stats.Stats
|
import com.unciv.models.stats.Stats
|
||||||
|
|
||||||
|
@ -61,7 +62,7 @@ class PopulationManager {
|
||||||
internal fun autoAssignWorker() {
|
internal fun autoAssignWorker() {
|
||||||
val toWork: TileInfo? = cityInfo.getTiles()
|
val toWork: TileInfo? = cityInfo.getTiles()
|
||||||
.filterNot { cityInfo.workedTiles.contains(it.position) || cityInfo.location==it.position}
|
.filterNot { cityInfo.workedTiles.contains(it.position) || cityInfo.location==it.position}
|
||||||
.maxBy { cityInfo.rankTile(it) }
|
.maxBy { Automation().rankTile(it,cityInfo.civInfo) }
|
||||||
if (toWork != null) // This is when we've run out of tiles!
|
if (toWork != null) // This is when we've run out of tiles!
|
||||||
cityInfo.workedTiles.add(toWork.position)
|
cityInfo.workedTiles.add(toWork.position)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue