Forts and citadels (with AI) (#2325)
* Enabled Forts & Citadels * Friendly territory checks * Citadel damage & notifications * Sprites, Icons, Translation & Atlas * Obsolete tests are removed * NullReferenceException code is fixed * Refactoring: using the static object * AI for the forts and citadels * Display defence stats * Exclude enemies tiles as candidates Co-authored-by: r3versi <fluo392@gmail.com>
This commit is contained in:
parent
10762a3873
commit
29a077a803
19 changed files with 197 additions and 111 deletions
BIN
android/Images/ImprovementIcons/Citadel.png
Normal file
BIN
android/Images/ImprovementIcons/Citadel.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.6 KiB |
BIN
android/Images/ImprovementIcons/Fort.png
Normal file
BIN
android/Images/ImprovementIcons/Fort.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 951 B |
|
@ -79,7 +79,16 @@
|
||||||
"improvingTech": "Compass",
|
"improvingTech": "Compass",
|
||||||
"improvingTechStats": {"gold": 1}
|
"improvingTechStats": {"gold": 1}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Military improvement
|
||||||
|
{
|
||||||
|
name: "Fort",
|
||||||
|
terrainsCanBeBuiltOn: ["Plains","Grassland","Desert","Hill","Tundra","Snow"],
|
||||||
|
turnsToBuild: 6,
|
||||||
|
techRequired: "Engineering",
|
||||||
|
uniques: ["Gives a defensive bonus of 50%"]
|
||||||
|
},
|
||||||
|
|
||||||
// Transportation
|
// Transportation
|
||||||
{
|
{
|
||||||
"name": "Road",
|
"name": "Road",
|
||||||
|
@ -151,7 +160,12 @@
|
||||||
"improvingTech": "Economics",
|
"improvingTech": "Economics",
|
||||||
"improvingTechStats": {"gold": 1}
|
"improvingTechStats": {"gold": 1}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Citadel",
|
||||||
|
uniques: ["Gives a defensive bonus of 100%", "Deal 30 damage to adjacent enemy units"]
|
||||||
|
// TODO (G&K): adds every tile around it to your territory
|
||||||
|
},
|
||||||
|
|
||||||
//Civilization unique improvements
|
//Civilization unique improvements
|
||||||
{
|
{
|
||||||
"name": "Moai",
|
"name": "Moai",
|
||||||
|
|
|
@ -1324,8 +1324,7 @@
|
||||||
"name": "Great General",
|
"name": "Great General",
|
||||||
"unbuildable": true,
|
"unbuildable": true,
|
||||||
"unitType": "Civilian",
|
"unitType": "Civilian",
|
||||||
"uniques": ["Can start an 8-turn golden age","Bonus for units in 2 tile radius 15%"],
|
"uniques": ["Can start an 8-turn golden age","Bonus for units in 2 tile radius 15%", "Can build improvement: Citadel"],
|
||||||
//todo : should be able to build mega-fort
|
|
||||||
"movement": 2
|
"movement": 2
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1334,8 +1333,7 @@
|
||||||
"unitType": "Civilian",
|
"unitType": "Civilian",
|
||||||
"uniqueTo": "Mongolia",
|
"uniqueTo": "Mongolia",
|
||||||
"replaces": "Great General",
|
"replaces": "Great General",
|
||||||
"uniques": ["Can start an 8-turn golden age","Bonus for units in 2 tile radius 15%", "Heal adjacent units for an additional 15 HP per turn"],
|
"uniques": ["Can start an 8-turn golden age","Bonus for units in 2 tile radius 15%", "Heal adjacent units for an additional 15 HP per turn", "Can build improvement: Citadel"],
|
||||||
//todo : should be able to build mega-fort
|
|
||||||
"movement": 5
|
"movement": 5
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
|
|
||||||
# Tutorial tasks
|
# Tutorial tasks
|
||||||
|
|
||||||
Move a unit!\nClick on a unit > Click on a destination > Click the arrow popup = Sposta un'unità!\nClicca su un'unità > Clicca su una destinazione > Clicca sul popup con la freccia
|
Move a unit!\nClick on a unit > Click on a destination > Click the arrow popup = Sposta un'unità!\nClicca su un'unità > Clicca su una destinazione > Clicca sul popup con la freccia
|
||||||
|
|
|
@ -44,7 +44,7 @@ object Constants {
|
||||||
const val researchAgreement = "Research Agreement"
|
const val researchAgreement = "Research Agreement"
|
||||||
const val openBorders = "Open Borders"
|
const val openBorders = "Open Borders"
|
||||||
const val random = "Random"
|
const val random = "Random"
|
||||||
val greatImprovements = listOf("Academy", "Landmark", "Manufactory", "Customs house")
|
val greatImprovements = listOf("Academy", "Landmark", "Manufactory", "Customs house", "Citadel")
|
||||||
|
|
||||||
val unitActionSetUp = "Set Up"
|
val unitActionSetUp = "Set Up"
|
||||||
val unitActionSleep = "Sleep"
|
val unitActionSleep = "Sleep"
|
||||||
|
|
|
@ -212,7 +212,7 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
|
||||||
|
|
||||||
// If this city is the closest city to another civ, that makes it a likely candidate for attack
|
// If this city is the closest city to another civ, that makes it a likely candidate for attack
|
||||||
if (civInfo.getKnownCivs().filter { it.cities.isNotEmpty() }
|
if (civInfo.getKnownCivs().filter { it.cities.isNotEmpty() }
|
||||||
.any { NextTurnAutomation().getClosestCities(civInfo, it).city1 == cityInfo })
|
.any { NextTurnAutomation.getClosestCities(civInfo, it).city1 == cityInfo })
|
||||||
modifier *= 1.5f
|
modifier *= 1.5f
|
||||||
|
|
||||||
addChoice(relativeCostEffectiveness, defensiveBuilding.name, modifier)
|
addChoice(relativeCostEffectiveness, defensiveBuilding.name, modifier)
|
||||||
|
|
|
@ -282,21 +282,6 @@ class NextTurnAutomation{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getMinDistanceBetweenCities(civ1: CivilizationInfo, civ2: CivilizationInfo): Int {
|
|
||||||
return getClosestCities(civ1,civ2).aerialDistance
|
|
||||||
}
|
|
||||||
|
|
||||||
data class CityDistance(val city1:CityInfo, val city2:CityInfo, val aerialDistance: Int)
|
|
||||||
fun getClosestCities(civ1: CivilizationInfo, civ2: CivilizationInfo): CityDistance {
|
|
||||||
val cityDistances = arrayListOf<CityDistance>()
|
|
||||||
for (civ1city in civ1.cities)
|
|
||||||
for (civ2city in civ2.cities)
|
|
||||||
cityDistances.add(CityDistance(civ1city, civ2city,
|
|
||||||
civ1city.getCenterTile().aerialDistanceTo(civ2city.getCenterTile())))
|
|
||||||
|
|
||||||
return cityDistances.minBy { it.aerialDistance }!!
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun offerDeclarationOfFriendship(civInfo: CivilizationInfo) {
|
private fun offerDeclarationOfFriendship(civInfo: CivilizationInfo) {
|
||||||
val civsThatWeCanDeclareFriendshipWith = civInfo.getKnownCivs()
|
val civsThatWeCanDeclareFriendshipWith = civInfo.getKnownCivs()
|
||||||
.asSequence()
|
.asSequence()
|
||||||
|
@ -509,4 +494,22 @@ class NextTurnAutomation{
|
||||||
diplomacyManager.removeFlag(DiplomacyFlags.SettledCitiesNearUs)
|
diplomacyManager.removeFlag(DiplomacyFlags.SettledCitiesNearUs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object
|
||||||
|
{
|
||||||
|
fun getMinDistanceBetweenCities(civ1: CivilizationInfo, civ2: CivilizationInfo): Int {
|
||||||
|
return getClosestCities(civ1,civ2).aerialDistance
|
||||||
|
}
|
||||||
|
|
||||||
|
data class CityDistance(val city1:CityInfo, val city2:CityInfo, val aerialDistance: Int)
|
||||||
|
|
||||||
|
fun getClosestCities(civ1: CivilizationInfo, civ2: CivilizationInfo): CityDistance {
|
||||||
|
val cityDistances = arrayListOf<CityDistance>()
|
||||||
|
for (civ1city in civ1.cities)
|
||||||
|
for (civ2city in civ2.cities)
|
||||||
|
cityDistances.add(CityDistance(civ1city, civ2city,
|
||||||
|
civ1city.getCenterTile().aerialDistanceTo(civ2city.getCenterTile())))
|
||||||
|
|
||||||
|
return cityDistances.minBy { it.aerialDistance }!!
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,16 +61,27 @@ class SpecificUnitAutomation {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//if no unit to follow, take refuge in city.
|
// try to build a citadel
|
||||||
|
if (WorkerAutomation(unit).evaluateFortPlacement(unit.currentTile, unit.civInfo))
|
||||||
|
UnitActions.getGreatPersonBuildImprovementAction(unit)?.action?.invoke()
|
||||||
|
|
||||||
|
//if no unit to follow, take refuge in city or build citadel there.
|
||||||
|
val reachableTest : (TileInfo) -> Boolean = {it.civilianUnit == null &&
|
||||||
|
unit.movement.canMoveTo(it)
|
||||||
|
&& unit.movement.canReach(it)}
|
||||||
val cityToGarrison = unit.civInfo.cities.asSequence().map { it.getCenterTile() }
|
val cityToGarrison = unit.civInfo.cities.asSequence().map { it.getCenterTile() }
|
||||||
.sortedBy { it.aerialDistanceTo(unit.currentTile) }
|
.sortedBy { it.aerialDistanceTo(unit.currentTile) }
|
||||||
.firstOrNull {
|
.firstOrNull { reachableTest(it) }
|
||||||
it.civilianUnit == null && unit.movement.canMoveTo(it)
|
|
||||||
&& unit.movement.canReach(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cityToGarrison != null) {
|
if (cityToGarrison != null) {
|
||||||
unit.movement.headTowards(cityToGarrison)
|
// 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)
|
||||||
|
unit.movement.headTowards(tileForCitadel)
|
||||||
|
else
|
||||||
|
unit.movement.headTowards(cityToGarrison)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -191,6 +191,8 @@ class WorkerAutomation(val unit: MapUnit) {
|
||||||
tile.containsGreatImprovement() -> null
|
tile.containsGreatImprovement() -> null
|
||||||
tile.containsUnfinishedGreatImprovement() -> null
|
tile.containsUnfinishedGreatImprovement() -> null
|
||||||
|
|
||||||
|
// Defence is more important that civilian improvements
|
||||||
|
evaluateFortPlacement(tile,civInfo) -> "Fort"
|
||||||
// I think we can assume that the unique improvement is better
|
// I think we can assume that the unique improvement is better
|
||||||
uniqueImprovement!=null && tile.canBuildImprovement(uniqueImprovement,civInfo) -> uniqueImprovement.name
|
uniqueImprovement!=null && tile.canBuildImprovement(uniqueImprovement,civInfo) -> uniqueImprovement.name
|
||||||
|
|
||||||
|
@ -208,4 +210,73 @@ class WorkerAutomation(val unit: MapUnit) {
|
||||||
return unit.civInfo.gameInfo.ruleSet.tileImprovements[improvementString]!!
|
return unit.civInfo.gameInfo.ruleSet.tileImprovements[improvementString]!!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 == "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
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun evaluateFortPlacement(tile: TileInfo, civInfo: CivilizationInfo): Boolean {
|
||||||
|
// build on our land only
|
||||||
|
if ((tile.owningCity?.civInfo != civInfo) ||
|
||||||
|
!isAcceptableTileForFort(tile, civInfo)) return false
|
||||||
|
|
||||||
|
val isHills = tile.getBaseTerrain().name == Constants.hill
|
||||||
|
// if this place is not perfect, let's see if there is a better one
|
||||||
|
val nearestTiles = tile.getTilesInDistance(2).filter{it.owningCity?.civInfo == civInfo}.toList()
|
||||||
|
for (closeTile in nearestTiles) {
|
||||||
|
// don't build forts too close to the cities
|
||||||
|
if (closeTile.isCityCenter()) return false
|
||||||
|
// don't build forts too close to other forts
|
||||||
|
if (closeTile.improvement == "Fort" || closeTile.improvement == "Citadel"
|
||||||
|
|| closeTile.improvementInProgress == "Fort") return false
|
||||||
|
// there is another better tile for the fort
|
||||||
|
if (!isHills && tile.getBaseTerrain().name == Constants.hill &&
|
||||||
|
isAcceptableTileForFort(closeTile, civInfo)) return false
|
||||||
|
}
|
||||||
|
|
||||||
|
val enemyCivs = civInfo.getKnownCivs()
|
||||||
|
.filterNot { it == civInfo || it.cities.isEmpty() || !civInfo.getDiplomacyManager(it).canAttack() }
|
||||||
|
// no potential enemies
|
||||||
|
if (enemyCivs.isEmpty()) return false
|
||||||
|
|
||||||
|
val threatMapping : (CivilizationInfo) -> Int = {
|
||||||
|
// the war is already a good nudge to build forts
|
||||||
|
(if (civInfo.isAtWarWith(it)) 20 else 0) +
|
||||||
|
// let's check also the force of the enemy
|
||||||
|
when (Automation().threatAssessment(civInfo, it)) {
|
||||||
|
ThreatLevel.VeryLow -> 1 // do not build forts
|
||||||
|
ThreatLevel.Low -> 6 // too close, let's build until it is late
|
||||||
|
ThreatLevel.Medium -> 10
|
||||||
|
ThreatLevel.High -> 15 // they are strong, let's built until they reach us
|
||||||
|
ThreatLevel.VeryHigh -> 20
|
||||||
|
} }
|
||||||
|
val enemyCivsIsCloseEnough = enemyCivs.filter { NextTurnAutomation.getMinDistanceBetweenCities(civInfo, it) <= threatMapping(it) }
|
||||||
|
// no threat, let's not build fort
|
||||||
|
if (enemyCivsIsCloseEnough.isEmpty()) return false
|
||||||
|
|
||||||
|
// make list of enemy cities as sources of threat
|
||||||
|
val enemyCities = mutableListOf<TileInfo>()
|
||||||
|
enemyCivsIsCloseEnough.forEach { enemyCities.addAll(it.cities.map { city -> city.getCenterTile() } ) }
|
||||||
|
|
||||||
|
// find closest enemy city
|
||||||
|
val closestEnemyCity = enemyCities.minBy { it.aerialDistanceTo(tile) }!!
|
||||||
|
val distanceToEnemy = tile.aerialDistanceTo(closestEnemyCity)
|
||||||
|
|
||||||
|
// find closest our city to defend from this enemy city
|
||||||
|
val closestOurCity = tile.owningCity!!.getCenterTile()
|
||||||
|
val distanceBetweenCities = closestEnemyCity.aerialDistanceTo(closestOurCity)
|
||||||
|
|
||||||
|
// let's build fort on the front line, not behind the city
|
||||||
|
return distanceBetweenCities > distanceToEnemy
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -160,6 +160,7 @@ class BattleDamage{
|
||||||
|
|
||||||
fun getDefenceModifiers(attacker: ICombatant, defender: MapUnitCombatant): HashMap<String, Float> {
|
fun getDefenceModifiers(attacker: ICombatant, defender: MapUnitCombatant): HashMap<String, Float> {
|
||||||
val modifiers = HashMap<String, Float>()
|
val modifiers = HashMap<String, Float>()
|
||||||
|
val tile = defender.getTile()
|
||||||
|
|
||||||
if (defender.unit.isEmbarked()) {
|
if (defender.unit.isEmbarked()) {
|
||||||
// embarked units get no defensive modifiers apart from this unique
|
// embarked units get no defensive modifiers apart from this unique
|
||||||
|
@ -172,11 +173,17 @@ class BattleDamage{
|
||||||
|
|
||||||
modifiers.putAll(getGeneralModifiers(defender, attacker))
|
modifiers.putAll(getGeneralModifiers(defender, attacker))
|
||||||
|
|
||||||
modifiers.putAll(getTileSpecificModifiers(defender, defender.getTile()))
|
modifiers.putAll(getTileSpecificModifiers(defender, tile))
|
||||||
|
|
||||||
if (!defender.unit.hasUnique("No defensive terrain bonus")) {
|
if (!defender.unit.hasUnique("No defensive terrain bonus")) {
|
||||||
val tileDefenceBonus = defender.getTile().getDefensiveBonus()
|
val tileDefenceBonus = tile.getDefensiveBonus()
|
||||||
if (tileDefenceBonus > 0) modifiers["Terrain"] = tileDefenceBonus
|
if (tileDefenceBonus > 0)
|
||||||
|
modifiers["Terrain"] = tileDefenceBonus
|
||||||
|
|
||||||
|
val improvement = tile.getTileImprovement()
|
||||||
|
if (improvement != null && tile.isFriendlyTerritory(defender.getCivInfo()))
|
||||||
|
if (improvement.hasUnique("Gives a defensive bonus of 50%")) modifiers[improvement.name] = 0.50f
|
||||||
|
else if (improvement.hasUnique("Gives a defensive bonus of 100%")) modifiers[improvement.name] = 1.0f
|
||||||
}
|
}
|
||||||
|
|
||||||
if(attacker.isRanged()) {
|
if(attacker.isRanged()) {
|
||||||
|
@ -196,10 +203,9 @@ class BattleDamage{
|
||||||
|
|
||||||
private fun getTileSpecificModifiers(unit: MapUnitCombatant, tile: TileInfo): HashMap<String,Float> {
|
private fun getTileSpecificModifiers(unit: MapUnitCombatant, tile: TileInfo): HashMap<String,Float> {
|
||||||
val modifiers = HashMap<String,Float>()
|
val modifiers = HashMap<String,Float>()
|
||||||
val isFriendlyTerritory = tile.getOwner()!=null && !unit.getCivInfo().isAtWarWith(tile.getOwner()!!)
|
if(tile.isFriendlyTerritory(unit.getCivInfo()) && unit.getCivInfo().containsBuildingUnique("+15% combat strength for units fighting in friendly territory"))
|
||||||
if(isFriendlyTerritory && unit.getCivInfo().containsBuildingUnique("+15% combat strength for units fighting in friendly territory"))
|
|
||||||
modifiers["Himeji Castle"] = 0.15f
|
modifiers["Himeji Castle"] = 0.15f
|
||||||
if(!isFriendlyTerritory && unit.unit.hasUnique("+20% bonus outside friendly territory"))
|
if(!tile.isFriendlyTerritory(unit.getCivInfo()) && unit.unit.hasUnique("+20% bonus outside friendly territory"))
|
||||||
modifiers["Foreign Land"] = 0.2f
|
modifiers["Foreign Land"] = 0.2f
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -527,7 +527,7 @@ class CivilizationInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun giftMilitaryUnitTo(otherCiv: CivilizationInfo) {
|
fun giftMilitaryUnitTo(otherCiv: CivilizationInfo) {
|
||||||
val city = NextTurnAutomation().getClosestCities(this, otherCiv).city1
|
val city = NextTurnAutomation.getClosestCities(this, otherCiv).city1
|
||||||
val militaryUnit = city.cityConstructions.getConstructableUnits()
|
val militaryUnit = city.cityConstructions.getConstructableUnits()
|
||||||
.filter { !it.unitType.isCivilian() && it.unitType.isLandUnit() }
|
.filter { !it.unitType.isCivilian() && it.unitType.isLandUnit() }
|
||||||
.toList().random()
|
.toList().random()
|
||||||
|
|
|
@ -235,7 +235,7 @@ class DiplomacyManager() {
|
||||||
*
|
*
|
||||||
* This includes friendly and allied city-states and the open border treaties.
|
* This includes friendly and allied city-states and the open border treaties.
|
||||||
*/
|
*/
|
||||||
fun isConsideredAllyTerritory(): Boolean {
|
fun isConsideredFriendlyTerritory(): Boolean {
|
||||||
if(civInfo.isCityState() && relationshipLevel() >= RelationshipLevel.Friend)
|
if(civInfo.isCityState() && relationshipLevel() >= RelationshipLevel.Friend)
|
||||||
return true
|
return true
|
||||||
return hasOpenBorders
|
return hasOpenBorders
|
||||||
|
|
|
@ -411,25 +411,19 @@ class MapUnit {
|
||||||
|
|
||||||
/** Returns the health points [MapUnit] will receive if healing on [tileInfo] */
|
/** Returns the health points [MapUnit] will receive if healing on [tileInfo] */
|
||||||
fun rankTileForHealing(tileInfo: TileInfo): Int {
|
fun rankTileForHealing(tileInfo: TileInfo): Int {
|
||||||
val tileOwner = tileInfo.getOwner()
|
val isFriendlyTerritory = tileInfo.isFriendlyTerritory(civInfo)
|
||||||
val isAlliedTerritory = when {
|
|
||||||
tileOwner == null -> false
|
|
||||||
tileOwner == civInfo -> true
|
|
||||||
!civInfo.knows(tileOwner) -> false
|
|
||||||
else -> tileOwner.getDiplomacyManager(civInfo).isConsideredAllyTerritory()
|
|
||||||
}
|
|
||||||
|
|
||||||
var healing = when {
|
var healing = when {
|
||||||
tileInfo.isCityCenter() -> 20
|
tileInfo.isCityCenter() -> 20
|
||||||
tileInfo.isWater && isAlliedTerritory && type.isWaterUnit() -> 15 // Water unit on friendly water
|
tileInfo.isWater && isFriendlyTerritory && type.isWaterUnit() -> 15 // Water unit on friendly water
|
||||||
tileInfo.isWater -> 0 // All other water cases
|
tileInfo.isWater -> 0 // All other water cases
|
||||||
tileOwner == null -> 10 // Neutral territory
|
tileInfo.getOwner() == null -> 10 // Neutral territory
|
||||||
isAlliedTerritory -> 15 // Allied territory
|
isFriendlyTerritory -> 15 // Allied territory
|
||||||
else -> 5 // Enemy territory
|
else -> 5 // Enemy territory
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasUnique("This unit and all others in adjacent tiles heal 5 additional HP. This unit heals 5 additional HP outside of friendly territory.")
|
if (hasUnique("This unit and all others in adjacent tiles heal 5 additional HP. This unit heals 5 additional HP outside of friendly territory.")
|
||||||
&& !isAlliedTerritory
|
&& !isFriendlyTerritory
|
||||||
// Additional healing from medic is only applied when the unit is able to heal
|
// Additional healing from medic is only applied when the unit is able to heal
|
||||||
&& healing > 0)
|
&& healing > 0)
|
||||||
healing += 5
|
healing += 5
|
||||||
|
@ -439,7 +433,7 @@ class MapUnit {
|
||||||
|
|
||||||
fun endTurn() {
|
fun endTurn() {
|
||||||
doPostTurnAction()
|
doPostTurnAction()
|
||||||
if(currentMovement== getMaxMovement().toFloat() // didn't move this turn
|
if (currentMovement == getMaxMovement().toFloat() // didn't move this turn
|
||||||
|| getUniques().contains("Unit will heal every turn, even if it performs an action")){
|
|| getUniques().contains("Unit will heal every turn, even if it performs an action")){
|
||||||
heal()
|
heal()
|
||||||
}
|
}
|
||||||
|
@ -447,6 +441,8 @@ class MapUnit {
|
||||||
if (action!!.endsWith(" until healed")) {
|
if (action!!.endsWith(" until healed")) {
|
||||||
action = null // wake up when healed
|
action = null // wake up when healed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getCitadelDamage()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startTurn() {
|
fun startTurn() {
|
||||||
|
@ -662,5 +658,25 @@ class MapUnit {
|
||||||
return sum
|
return sum
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getCitadelDamage() {
|
||||||
|
// Check for Citadel damage
|
||||||
|
val applyCitadelDamage = currentTile.neighbors
|
||||||
|
.filter{ it.getOwner() != null && civInfo.isAtWarWith(it.getOwner()!!) }
|
||||||
|
.map{ it.getTileImprovement() }
|
||||||
|
.filter{ it != null && it.hasUnique("Deal 30 damage to adjacent enemy units") }
|
||||||
|
.any()
|
||||||
|
|
||||||
|
if (applyCitadelDamage) {
|
||||||
|
health -= 30
|
||||||
|
|
||||||
|
if (health <= 0) {
|
||||||
|
civInfo.addNotification("An enemy [Citadel] has destroyed our [$name]", currentTile.position, Color.RED)
|
||||||
|
destroy()
|
||||||
|
} else {
|
||||||
|
civInfo.addNotification("An enemy [Citadel] has attacked our [$name]", currentTile.position, Color.RED)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package com.unciv.logic.map
|
package com.unciv.logic.map
|
||||||
|
|
||||||
import com.badlogic.gdx.math.Vector2
|
import com.badlogic.gdx.math.Vector2
|
||||||
import com.unciv.Constants
|
import com.unciv.Constants
|
||||||
|
@ -132,9 +132,20 @@ open class TileInfo {
|
||||||
return containingCity.civInfo
|
return containingCity.civInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isFriendlyTerritory(civInfo: CivilizationInfo): Boolean {
|
||||||
|
val tileOwner = getOwner()
|
||||||
|
return when {
|
||||||
|
tileOwner == null -> false
|
||||||
|
tileOwner == civInfo -> true
|
||||||
|
!civInfo.knows(tileOwner) -> false
|
||||||
|
else -> tileOwner.getDiplomacyManager(civInfo).isConsideredFriendlyTerritory()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun getTerrainFeature(): Terrain? =
|
fun getTerrainFeature(): Terrain? =
|
||||||
if (terrainFeature == null) null else ruleset.terrains[terrainFeature!!]
|
if (terrainFeature == null) null else ruleset.terrains[terrainFeature!!]
|
||||||
|
|
||||||
|
|
||||||
fun isWorked(): Boolean {
|
fun isWorked(): Boolean {
|
||||||
val city = getCity()
|
val city = getCity()
|
||||||
return city!=null && city.workedTiles.contains(position)
|
return city!=null && city.workedTiles.contains(position)
|
||||||
|
@ -337,8 +348,17 @@ open class TileInfo {
|
||||||
milUnitString += " - "+militaryUnit!!.civInfo.civName.tr()
|
milUnitString += " - "+militaryUnit!!.civInfo.civName.tr()
|
||||||
lineList += milUnitString
|
lineList += milUnitString
|
||||||
}
|
}
|
||||||
if(getDefensiveBonus()!=0f){
|
var defenceBonus = getDefensiveBonus()
|
||||||
var defencePercentString = (getDefensiveBonus()*100).toInt().toString()+"%"
|
val tileImprovement = getTileImprovement()
|
||||||
|
if (tileImprovement != null) {
|
||||||
|
defenceBonus += when {
|
||||||
|
tileImprovement.hasUnique("Gives a defensive bonus of 50%") -> 0.5f
|
||||||
|
tileImprovement.hasUnique("Gives a defensive bonus of 100%") -> 1.0f
|
||||||
|
else -> 0.0f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(defenceBonus != 0.0f){
|
||||||
|
var defencePercentString = (defenceBonus*100).toInt().toString()+"%"
|
||||||
if(!defencePercentString.startsWith("-")) defencePercentString = "+$defencePercentString"
|
if(!defencePercentString.startsWith("-")) defencePercentString = "+$defencePercentString"
|
||||||
lineList += "[$defencePercentString] to unit defence".tr()
|
lineList += "[$defencePercentString] to unit defence".tr()
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,5 +64,7 @@ class TileImprovement : NamedStats() {
|
||||||
|
|
||||||
return stringBuilder.toString()
|
return stringBuilder.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun hasUnique(unique: String) = uniques.contains(unique)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,7 @@ class UnitActionsTable(val worldScreen: WorldScreen) : Table(){
|
||||||
"Construct Academy" -> return ImageGetter.getImprovementIcon("Academy")
|
"Construct Academy" -> return ImageGetter.getImprovementIcon("Academy")
|
||||||
"Start Golden Age" -> return ImageGetter.getUnitIcon("Great Artist")
|
"Start Golden Age" -> return ImageGetter.getUnitIcon("Great Artist")
|
||||||
"Construct Landmark" -> return ImageGetter.getImprovementIcon("Landmark")
|
"Construct Landmark" -> return ImageGetter.getImprovementIcon("Landmark")
|
||||||
|
"Construct Citadel" -> return ImageGetter.getImprovementIcon("Citadel")
|
||||||
"Hurry Wonder" -> return ImageGetter.getUnitIcon("Great Engineer")
|
"Hurry Wonder" -> return ImageGetter.getUnitIcon("Great Engineer")
|
||||||
"Construct Manufactory" -> return ImageGetter.getImprovementIcon("Manufactory")
|
"Construct Manufactory" -> return ImageGetter.getImprovementIcon("Manufactory")
|
||||||
"Conduct Trade Mission" -> return ImageGetter.getUnitIcon("Great Merchant")
|
"Conduct Trade Mission" -> return ImageGetter.getUnitIcon("Great Merchant")
|
||||||
|
|
|
@ -153,6 +153,8 @@ Unless otherwise specified, all the following are from [the Noun Project](https:
|
||||||
* [Ruins](https://thenounproject.com/term/ruins/3849/) By Paulo Volkova for City ruins
|
* [Ruins](https://thenounproject.com/term/ruins/3849/) By Paulo Volkova for City ruins
|
||||||
* [Fishing Net](https://thenounproject.com/term/fishing-net/1073133/) By Made for Fishing Boats
|
* [Fishing Net](https://thenounproject.com/term/fishing-net/1073133/) By Made for Fishing Boats
|
||||||
* [Moai](https://thenounproject.com/search/?q=moai&i=2878111) By Template
|
* [Moai](https://thenounproject.com/search/?q=moai&i=2878111) By Template
|
||||||
|
* [Fort](https://thenounproject.com/term/fort/1697645/) By Adrien Coquet
|
||||||
|
* [Citadel](https://thenounproject.com/term/fort/1697646/) By Adrien Coquet
|
||||||
|
|
||||||
## Buildings
|
## Buildings
|
||||||
|
|
||||||
|
|
|
@ -45,64 +45,6 @@ class TranslationTests {
|
||||||
allUnitActionsHaveTranslation)
|
allUnitActionsHaveTranslation)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun allTerrainsHaveTranslation() {
|
|
||||||
val strings: Set<String> = ruleset.terrains.keys
|
|
||||||
val allStringsHaveTranslation = allStringAreTranslated(strings)
|
|
||||||
Assert.assertTrue("This test will only pass when there is a translation for all buildings",
|
|
||||||
allStringsHaveTranslation)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun allTerrainUniquesHaveTranslation() {
|
|
||||||
val strings: MutableSet<String> = HashSet()
|
|
||||||
for (terrain in ruleset.terrains.values) {
|
|
||||||
strings.addAll(terrain.uniques)
|
|
||||||
}
|
|
||||||
val allStringsHaveTranslation = allStringAreTranslated(strings)
|
|
||||||
Assert.assertTrue("This test will only pass when there is a translation for all terrain uniques",
|
|
||||||
allStringsHaveTranslation)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun allImprovementsHaveTranslation() {
|
|
||||||
val strings: Set<String> = ruleset.tileImprovements.keys
|
|
||||||
val allStringsHaveTranslation = allStringAreTranslated(strings)
|
|
||||||
Assert.assertTrue("This test will only pass when there is a translation for all improvements",
|
|
||||||
allStringsHaveTranslation)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun allImprovementUniquesHaveTranslation() {
|
|
||||||
val strings: MutableSet<String> = HashSet()
|
|
||||||
for (improvement in ruleset.tileImprovements.values) {
|
|
||||||
strings.addAll(improvement.uniques)
|
|
||||||
}
|
|
||||||
val allStringsHaveTranslation = allStringAreTranslated(strings)
|
|
||||||
Assert.assertTrue("This test will only pass when there is a translation for all improvements uniques",
|
|
||||||
allStringsHaveTranslation)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun allTechnologiesHaveTranslation() {
|
|
||||||
val strings: Set<String> = ruleset.technologies.keys
|
|
||||||
val allStringsHaveTranslation = allStringAreTranslated(strings)
|
|
||||||
Assert.assertTrue("This test will only pass when there is a translation for all technologies",
|
|
||||||
allStringsHaveTranslation)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun allTechnologiesQuotesHaveTranslation() {
|
|
||||||
val strings: MutableSet<String> = HashSet()
|
|
||||||
for (tech in ruleset.technologies.values) {
|
|
||||||
strings.add(tech.quote)
|
|
||||||
}
|
|
||||||
val allStringsHaveTranslation = allStringAreTranslated(strings)
|
|
||||||
Assert.assertTrue("This test will only pass when there is a translation for all technologies quotes",
|
|
||||||
allStringsHaveTranslation)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private fun allStringAreTranslated(strings: Set<String>): Boolean {
|
private fun allStringAreTranslated(strings: Set<String>): Boolean {
|
||||||
var allStringsHaveTranslation = true
|
var allStringsHaveTranslation = true
|
||||||
for (key in strings) {
|
for (key in strings) {
|
||||||
|
|
Loading…
Reference in a new issue