Citadel improvements + improved AI for the forts (#2453)

* Allow building the citadels either next to or within friendly tiles only

* Citadel acquire the tiles around it

* AI uses the citadels to get the tiles back + improved AI for the forts
This commit is contained in:
Jack Rainy 2020-04-18 21:25:48 +03:00 committed by GitHub
parent d4ef5cb637
commit 77a63ce365
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 89 additions and 20 deletions

View File

@ -109,6 +109,7 @@ You fulfilled your promise to stop settling cities near us! =
You refused to stop settling cities near us =
Your arrogant demands are in bad taste =
Your use of nuclear weapons is disgusting! =
You have stolen our lands! =
Demands =
Please don't settle new cities near us. =
@ -240,6 +241,8 @@ Mongol Terror =
Units ignore terrain costs when moving into any tile with Hills. No maintenance costs for improvements in Hills; half cost elsewhere. =
Great Andean Road =
+1 Movement to all embarked units, units pay only 1 movement point to embark and disembark. Melee units pay no movement cost to pillage. =
Viking Fury =
# New game screen
@ -484,6 +487,7 @@ Our proposed trade request is no longer relevant! =
[defender] could not withdraw from a [attacker] - blocked. =
[defender] withdrew from a [attacker] =
[building] has provided [amount] Gold! =
[civName] has stolen your territory! =
# World Screen UI

View File

@ -1,9 +1,10 @@
package com.unciv.logic.automation
package com.unciv.logic.automation
import com.unciv.logic.battle.MapUnitCombatant
import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.civilization.GreatPersonManager
import com.unciv.logic.civilization.diplomacy.DiplomacyFlags
import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers
import com.unciv.logic.map.MapUnit
import com.unciv.logic.map.TileInfo
import com.unciv.models.ruleset.tile.ResourceType
@ -59,9 +60,37 @@ object SpecificUnitAutomation {
return
}
// try to build a citadel
if (WorkerAutomation(unit).evaluateFortPlacement(unit.currentTile, unit.civInfo))
// try to revenge and capture their tiles
val enemyCities = unit.civInfo.gameInfo.civilizations
.filter { unit.civInfo.knows(it) &&
unit.civInfo.getDiplomacyManager(it).hasModifier(DiplomaticModifiers.StealingTerritory) }
.flatMap { it.cities }.asSequence()
// find the suitable tiles (or their neigbours)
val tileToSteal = enemyCities.flatMap { it.getTiles() }.flatMap { it.neighbors.asSequence() }
.filter { it in unit.civInfo.viewableTiles // we can see them
&& unit.movement.canReach(it) // we can reach it
&& it.neighbors.any { tile -> tile.getOwner() == unit.civInfo} } // they are close to our borders
.sortedBy {
// get closest tiles
val distance = it.aerialDistanceTo(unit.currentTile)
// ...also get priorities to steal the most valuable for them
val owner = it.getOwner()
if (owner != null)
distance - WorkerAutomation(unit).getPriority(it, owner)
else distance }.firstOrNull()
// if there is a good tile to steal - go there
if (tileToSteal != null) {
unit.movement.headTowards(tileToSteal)
if (unit.currentMovement > 0 && unit.currentTile == tileToSteal)
UnitActions.getBuildImprovementAction(unit)?.action?.invoke()
return
}
// try to build a citadel
if (WorkerAutomation(unit).evaluateFortPlacement(unit.currentTile, unit.civInfo, true)) {
UnitActions.getBuildImprovementAction(unit)?.action?.invoke()
return
}
//if no unit to follow, take refuge in city or build citadel there.
val reachableTest : (TileInfo) -> Boolean = {it.civilianUnit == null &&
@ -75,10 +104,12 @@ object SpecificUnitAutomation {
// try to find a good place for citadel nearby
val potentialTilesNearCity = cityToGarrison.getTilesInDistanceRange(3..4)
val tileForCitadel = potentialTilesNearCity.firstOrNull { reachableTest(it) &&
WorkerAutomation(unit).evaluateFortPlacement(it, unit.civInfo) }
if (tileForCitadel != null)
WorkerAutomation(unit).evaluateFortPlacement(it, unit.civInfo, true) }
if (tileForCitadel != null) {
unit.movement.headTowards(tileForCitadel)
else
if (unit.currentMovement > 0 && unit.currentTile == tileForCitadel)
UnitActions.getBuildImprovementAction(unit)?.action?.invoke()
} else
unit.movement.headTowards(cityToGarrison)
return
}

View File

@ -160,7 +160,7 @@ class WorkerAutomation(val unit: MapUnit) {
return false // cou;dn't find anything to construct here
}
private fun getPriority(tileInfo: TileInfo, civInfo: CivilizationInfo): Int {
fun getPriority(tileInfo: TileInfo, civInfo: CivilizationInfo): Int {
var priority = 0
if (tileInfo.getOwner() == civInfo){
priority += 2
@ -195,7 +195,7 @@ class WorkerAutomation(val unit: MapUnit) {
// Defence is more important that civilian improvements
// While AI sucks in strategical placement of forts, allow a human does it manually
!civInfo.isPlayerCivilization() && evaluateFortPlacement(tile,civInfo) -> Constants.fort
!civInfo.isPlayerCivilization() && evaluateFortPlacement(tile,civInfo,false) -> Constants.fort
// I think we can assume that the unique improvement is better
uniqueImprovement!=null && tile.canBuildImprovement(uniqueImprovement,civInfo) -> uniqueImprovement.name
@ -226,21 +226,21 @@ class WorkerAutomation(val unit: MapUnit) {
private fun isAcceptableTileForFort(tile: TileInfo, civInfo: CivilizationInfo): Boolean
{
// don't build fort in the city
if (tile.isCityCenter()) return false
// don't build fort if it is already here
if (tile.improvement == Constants.fort) return false
// don't build on resource tiles
if (tile.hasViewableResource(civInfo)) return false
// don't build on great improvements
if (tile.containsGreatImprovement() || tile.containsUnfinishedGreatImprovement()) return false
if (tile.isCityCenter() // don't build fort in the city
|| !tile.isLand // don't build fort in the water
|| tile.improvement == Constants.fort // don't build fort if it is already here
|| tile.hasViewableResource(civInfo) // don't build on resource tiles
|| tile.containsGreatImprovement() // don't build on great improvements (including citadel)
|| tile.containsUnfinishedGreatImprovement()) return false
return true
}
fun evaluateFortPlacement(tile: TileInfo, civInfo: CivilizationInfo): Boolean {
fun evaluateFortPlacement(tile: TileInfo, civInfo: CivilizationInfo, isCitadel: Boolean): Boolean {
// build on our land only
if ((tile.owningCity?.civInfo != civInfo) ||
if ((tile.owningCity?.civInfo != civInfo &&
// except citadel which can be built near-by
(!isCitadel || tile.neighbors.all { it.getOwner() != civInfo })) ||
!isAcceptableTileForFort(tile, civInfo)) return false
val isHills = tile.getBaseTerrain().name == Constants.hill
@ -286,11 +286,14 @@ class WorkerAutomation(val unit: MapUnit) {
val distanceToEnemy = tile.aerialDistanceTo(closestEnemyCity)
// find closest our city to defend from this enemy city
val closestOurCity = tile.owningCity!!.getCenterTile()
val closestOurCity = civInfo.cities.minBy { it.getCenterTile().aerialDistanceTo(tile) }!!.getCenterTile()
val distanceToOurCity = tile.aerialDistanceTo(closestOurCity)
val distanceBetweenCities = closestEnemyCity.aerialDistanceTo(closestOurCity)
// let's build fort on the front line, not behind the city
return distanceBetweenCities > distanceToEnemy
// +2 is a acceptable deviation from the straight line between cities
return distanceBetweenCities + 2 > distanceToEnemy + distanceToOurCity
}
}

View File

@ -49,6 +49,7 @@ enum class DiplomaticModifiers{
BetrayedPromiseToNotSettleCitiesNearUs,
UnacceptableDemands,
UsedNuclearWeapons,
StealingTerritory,
YearsOfPeace,
SharedEnemy,

View File

@ -303,6 +303,7 @@ class DiplomacyScreen(val viewingCiv:CivilizationInfo):CameraStageBaseScreen() {
FulfilledPromiseToNotSettleCitiesNearUs -> "You fulfilled your promise to stop settling cities near us!"
UnacceptableDemands -> "Your arrogant demands are in bad taste"
UsedNuclearWeapons -> "Your use of nuclear weapons is disgusting!"
StealingTerritory -> "You have stolen our lands!"
}
text = text.tr() + " "
if (modifier.value > 0) text += "+"

View File

@ -7,6 +7,7 @@ import com.unciv.UniqueAbility
import com.unciv.logic.automation.UnitAutomation
import com.unciv.logic.automation.WorkerAutomation
import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers
import com.unciv.logic.map.MapUnit
import com.unciv.logic.map.RoadStatus
import com.unciv.logic.map.TileInfo
@ -364,6 +365,8 @@ object UnitActions {
unitTile.improvement = improvementName
unitTile.improvementInProgress = null
unitTile.turnsToImprovement = 0
if (improvementName == Constants.citadel)
takeOverTilesAround(unit)
val city = unitTile.getCity()
if (city != null) {
city.cityStats.update()
@ -373,11 +376,37 @@ object UnitActions {
unit.destroy()
}.takeIf { unit.currentMovement > 0f && !tile.isWater &&
!tile.isCityCenter() && !tile.getLastTerrain().impassable &&
tile.improvement != improvementName })
tile.improvement != improvementName &&
// citadel can be built only next to or within own borders
(improvementName != Constants.citadel ||
tile.neighbors.any { it.getOwner() == unit.civInfo })})
}
return null
}
private fun takeOverTilesAround(unit: MapUnit) {
// one of the neighbour tile must belong to unit's civ, so nearestCity will be never `null`
val nearestCity = unit.currentTile.neighbors.first { it.getOwner() == unit.civInfo }.getCity()
// capture all tiles which do not belong to unit's civ and are not enemy cities
// we use getTilesInDistance here, not neighbours to include the current tile as well
val tilesToTakeOver = unit.currentTile.getTilesInDistance(1)
.filter { !it.isCityCenter() && it.getOwner() != unit.civInfo }
// make a set of civs to be notified (a set - in order to not repeat notification on each tile)
val notifications = mutableSetOf<CivilizationInfo>()
// take over the ownership
for (tile in tilesToTakeOver) {
val otherCiv = tile.getOwner()
if (otherCiv != null) {
// decrease relations for -10 pt/tile
otherCiv.getDiplomacyManager(unit.civInfo).addModifier(DiplomaticModifiers.StealingTerritory, -10f)
notifications.add(otherCiv)
}
nearestCity!!.expansion.takeOwnership(tile)
}
for (otherCiv in notifications)
otherCiv.addNotification("${unit.civInfo} has stolen your territory!", unit.currentTile.position, Color.RED)
}
private fun addGoldPerGreatPersonUsage(civInfo: CivilizationInfo) {
val uniqueText = "Provides a sum of gold each time you spend a Great Person"
val cityWithMausoleum = civInfo.cities.firstOrNull { it.containsBuildingUnique(uniqueText) }