Step 1 in the Glorious UnitType Moddability Revolution - isRanged(), isMelee(), isMilitary(), isCivilian() are no longer unittype-dependent

Unified all isAir || isMissile to BaseUnit.MovesLikeAirUnit()
This commit is contained in:
Yair Morgenstern 2021-07-26 00:15:18 +03:00
parent bc1455de2a
commit 8a1c728438
27 changed files with 118 additions and 143 deletions

View File

@ -232,7 +232,7 @@ class GameInfo {
val barbarianCiv = getBarbarianCivilization() val barbarianCiv = getBarbarianCivilization()
barbarianCiv.tech.techsResearched = allResearchedTechs.toHashSet() barbarianCiv.tech.techsResearched = allResearchedTechs.toHashSet()
val unitList = ruleSet.units.values val unitList = ruleSet.units.values
.filter { !it.unitType.isCivilian() } .filter { it.isMilitary() }
.filter { it.isBuildable(barbarianCiv) } .filter { it.isBuildable(barbarianCiv) }
val landUnits = unitList.filter { it.unitType.isLandUnit() } val landUnits = unitList.filter { it.unitType.isLandUnit() }

View File

@ -14,7 +14,6 @@ import com.unciv.models.ruleset.RulesetCache
import com.unciv.models.ruleset.tile.ResourceType import com.unciv.models.ruleset.tile.ResourceType
import com.unciv.ui.newgamescreen.GameSetupInfo import com.unciv.ui.newgamescreen.GameSetupInfo
import java.util.* import java.util.*
import kotlin.NoSuchElementException
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
import kotlin.collections.HashMap import kotlin.collections.HashMap
import kotlin.math.max import kotlin.math.max
@ -240,10 +239,10 @@ object GameStarter {
else -> gameInfo.getDifficulty().aiCityStateStartingUnits else -> gameInfo.getDifficulty().aiCityStateStartingUnits
}).toMutableList() }).toMutableList()
val warriorEquivalent = ruleSet.units val warriorEquivalent = ruleSet.units.values
.filter { it.value.unitType.isLandUnit() && it.value.unitType.isMilitary() && it.value.isBuildable(civ) } .filter { it.unitType.isLandUnit() && it.isMilitary() && it.isBuildable(civ) }
.maxByOrNull {max(it.value.strength, it.value.rangedStrength)} .maxByOrNull {max(it.strength, it.rangedStrength)}
?.key ?.name
for (unit in startingUnits) { for (unit in startingUnits) {
val unitToAdd = if (unit == "Warrior") warriorEquivalent else unit val unitToAdd = if (unit == "Warrior") warriorEquivalent else unit
@ -277,7 +276,7 @@ object GameStarter {
val buildableSettlerLikeUnits = val buildableSettlerLikeUnits =
settlerLikeUnits.filter { settlerLikeUnits.filter {
it.value.isBuildable(civ) it.value.isBuildable(civ)
&& it.value.unitType.isCivilian() && it.value.isCivilian()
} }
if (buildableSettlerLikeUnits.isEmpty()) return null // No settlers in this mod if (buildableSettlerLikeUnits.isEmpty()) return null // No settlers in this mod
return civ.getEquivalentUnit(buildableSettlerLikeUnits.keys.random()).name return civ.getEquivalentUnit(buildableSettlerLikeUnits.keys.random()).name
@ -286,7 +285,7 @@ object GameStarter {
val buildableWorkerLikeUnits = ruleSet.units.filter { val buildableWorkerLikeUnits = ruleSet.units.filter {
it.value.uniqueObjects.any { it.placeholderText == Constants.canBuildImprovements } it.value.uniqueObjects.any { it.placeholderText == Constants.canBuildImprovements }
&& it.value.isBuildable(civ) && it.value.isBuildable(civ)
&& it.value.unitType.isCivilian() && it.value.isCivilian()
} }
if (buildableWorkerLikeUnits.isEmpty()) return null // No workers in this mod if (buildableWorkerLikeUnits.isEmpty()) return null // No workers in this mod
return civ.getEquivalentUnit(buildableWorkerLikeUnits.keys.random()).name return civ.getEquivalentUnit(buildableWorkerLikeUnits.keys.random()).name

View File

@ -3,7 +3,6 @@ package com.unciv.logic.automation
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.BFS import com.unciv.logic.map.BFS
import com.unciv.logic.map.RoadStatus
import com.unciv.logic.map.TileInfo import com.unciv.logic.map.TileInfo
import com.unciv.models.ruleset.VictoryType import com.unciv.models.ruleset.VictoryType
import com.unciv.models.ruleset.tile.ResourceType import com.unciv.models.ruleset.tile.ResourceType
@ -60,7 +59,7 @@ object Automation {
fun chooseMilitaryUnit(city: CityInfo): String? { fun chooseMilitaryUnit(city: CityInfo): String? {
var militaryUnits = var militaryUnits =
city.cityConstructions.getConstructableUnits().filter { !it.unitType.isCivilian() } city.cityConstructions.getConstructableUnits().filter { !it.isCivilian() }
if (militaryUnits.map { it.name } if (militaryUnits.map { it.name }
.contains(city.cityConstructions.currentConstructionFromQueue)) .contains(city.cityConstructions.currentConstructionFromQueue))
return city.cityConstructions.currentConstructionFromQueue return city.cityConstructions.currentConstructionFromQueue

View File

@ -8,9 +8,9 @@ class BarbarianAutomation(val civInfo: CivilizationInfo) {
fun automate() { fun automate() {
// ranged go first, after melee and then everyone else // ranged go first, after melee and then everyone else
civInfo.getCivUnits().filter { it.type.isRanged() }.forEach { automateUnit(it) } civInfo.getCivUnits().filter { it.baseUnit.isRanged() }.forEach { automateUnit(it) }
civInfo.getCivUnits().filter { it.type.isMelee() }.forEach { automateUnit(it) } civInfo.getCivUnits().filter { it.baseUnit.isMelee() }.forEach { automateUnit(it) }
civInfo.getCivUnits().filter { !it.type.isRanged() && !it.type.isMelee() }.forEach { automateUnit(it) } civInfo.getCivUnits().filter { !it.baseUnit.isRanged() && !it.baseUnit.isMelee() }.forEach { automateUnit(it) }
} }
private fun automateUnit(unit: MapUnit) { private fun automateUnit(unit: MapUnit) {
@ -39,7 +39,7 @@ class BarbarianAutomation(val civInfo: CivilizationInfo) {
// 3 - trying to attack enemy // 3 - trying to attack enemy
// if a embarked melee unit can land and attack next turn, do not attack from water. // if a embarked melee unit can land and attack next turn, do not attack from water.
if (BattleHelper.tryDisembarkUnitToAttackPosition(unit)) return if (BattleHelper.tryDisembarkUnitToAttackPosition(unit)) return
if (!unit.type.isCivilian() && BattleHelper.tryAttackNearbyEnemy(unit)) return if (!unit.isCivilian() && BattleHelper.tryAttackNearbyEnemy(unit)) return
// 4 - trying to pillage tile or route // 4 - trying to pillage tile or route
if (UnitAutomation.tryPillageImprovement(unit)) return if (UnitAutomation.tryPillageImprovement(unit)) return

View File

@ -122,7 +122,7 @@ object BattleHelper {
fun tryDisembarkUnitToAttackPosition(unit: MapUnit): Boolean { fun tryDisembarkUnitToAttackPosition(unit: MapUnit): Boolean {
val unitDistanceToTiles = unit.movement.getDistanceToTiles() val unitDistanceToTiles = unit.movement.getDistanceToTiles()
if (!unit.type.isMelee() || !unit.type.isLandUnit() || !unit.isEmbarked()) return false if (!unit.baseUnit.isMelee() || !unit.type.isLandUnit() || !unit.isEmbarked()) return false
val attackableEnemiesNextTurn = getAttackableEnemies(unit, unitDistanceToTiles) val attackableEnemiesNextTurn = getAttackableEnemies(unit, unitDistanceToTiles)
// Only take enemies we can fight without dying // Only take enemies we can fight without dying
@ -154,7 +154,7 @@ object BattleHelper {
cityTilesToAttack.filter { it.tileToAttack.getCity()!!.health != 1 } // don't want ranged units to attack defeated cities cityTilesToAttack.filter { it.tileToAttack.getCity()!!.health != 1 } // don't want ranged units to attack defeated cities
.minByOrNull { it.tileToAttack.getCity()!!.health } .minByOrNull { it.tileToAttack.getCity()!!.health }
if (unit.type.isMelee() && capturableCity != null) if (unit.baseUnit.isMelee() && capturableCity != null)
enemyTileToAttack = capturableCity // enter it quickly, top priority! enemyTileToAttack = capturableCity // enter it quickly, top priority!
else if (nonCityTilesToAttack.isNotEmpty()) // second priority, units else if (nonCityTilesToAttack.isNotEmpty()) // second priority, units

View File

@ -26,9 +26,9 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
.filter { it.isAnyWonder() } .filter { it.isAnyWonder() }
val civUnits = civInfo.getCivUnits() val civUnits = civInfo.getCivUnits()
val militaryUnits = civUnits.count { !it.type.isCivilian() } val militaryUnits = civUnits.count { it.baseUnit.isMilitary() }
// Constants.workerUnique deprecated since 3.15.5 // Constants.workerUnique deprecated since 3.15.5
val workers = civUnits.count { (it.hasUnique(Constants.canBuildImprovements) || it.hasUnique(Constants.workerUnique)) && it.type.isCivilian() }.toFloat() val workers = civUnits.count { (it.hasUnique(Constants.canBuildImprovements) || it.hasUnique(Constants.workerUnique)) && it.isCivilian() }.toFloat()
val cities = civInfo.cities.size val cities = civInfo.cities.size
val allTechsAreResearched = civInfo.tech.getNumberOfTechsResearched() >= civInfo.gameInfo.ruleSet.technologies.size val allTechsAreResearched = civInfo.tech.getNumberOfTechsResearched() >= civInfo.gameInfo.ruleSet.technologies.size

View File

@ -340,7 +340,7 @@ object NextTurnAutomation {
if (civInfo.isAtWarWith(otherCiv)) continue if (civInfo.isAtWarWith(otherCiv)) continue
val diplomacy = civInfo.getDiplomacyManager(otherCiv) val diplomacy = civInfo.getDiplomacyManager(otherCiv)
val unitsInBorder = otherCiv.getCivUnits().count { !it.type.isCivilian() && it.getTile().getOwner() == civInfo } val unitsInBorder = otherCiv.getCivUnits().count { !it.isCivilian() && it.getTile().getOwner() == civInfo }
if (unitsInBorder > 0 && diplomacy.relationshipLevel() < RelationshipLevel.Friend) { if (unitsInBorder > 0 && diplomacy.relationshipLevel() < RelationshipLevel.Friend) {
diplomacy.influence -= 10f diplomacy.influence -= 10f
if (!diplomacy.hasFlag(DiplomacyFlags.BorderConflict)) { if (!diplomacy.hasFlag(DiplomacyFlags.BorderConflict)) {
@ -356,7 +356,7 @@ object NextTurnAutomation {
if (civInfo.cities.isEmpty() || civInfo.diplomacy.isEmpty()) return if (civInfo.cities.isEmpty() || civInfo.diplomacy.isEmpty()) return
if (civInfo.isAtWar() || civInfo.getHappiness() <= 0) return if (civInfo.isAtWar() || civInfo.getHappiness() <= 0) return
val ourMilitaryUnits = civInfo.getCivUnits().filter { !it.type.isCivilian() }.count() val ourMilitaryUnits = civInfo.getCivUnits().filter { !it.isCivilian() }.count()
if (ourMilitaryUnits < civInfo.cities.size) return if (ourMilitaryUnits < civInfo.cities.size) return
//evaluate war //evaluate war
@ -505,8 +505,8 @@ object NextTurnAutomation {
} }
when { when {
unit.type.isRanged() -> rangedUnits.add(unit) unit.baseUnit.isRanged() -> rangedUnits.add(unit)
unit.type.isMelee() -> meleeUnits.add(unit) unit.baseUnit.isMelee() -> meleeUnits.add(unit)
unit.hasUnique("Bonus for units in 2 tile radius 15%") unit.hasUnique("Bonus for units in 2 tile radius 15%")
-> generals.add(unit) //generals move after military units -> generals.add(unit) //generals move after military units
else -> civilianUnits.add(unit) else -> civilianUnits.add(unit)

View File

@ -86,7 +86,7 @@ object UnitAutomation {
if (unit.civInfo.isBarbarian()) if (unit.civInfo.isBarbarian())
throw IllegalStateException("Barbarians is not allowed here.") throw IllegalStateException("Barbarians is not allowed here.")
if (unit.type.isCivilian()) { if (unit.isCivilian()) {
if (unit.hasUnique(Constants.settlerUnique)) if (unit.hasUnique(Constants.settlerUnique))
return SpecificUnitAutomation.automateSettlerActions(unit) return SpecificUnitAutomation.automateSettlerActions(unit)
@ -171,7 +171,7 @@ object UnitAutomation {
} }
fun tryHealUnit(unit: MapUnit): Boolean { fun tryHealUnit(unit: MapUnit): Boolean {
if (unit.type.isRanged() && unit.hasUnique("Unit will heal every turn, even if it performs an action")) if (unit.baseUnit.isRanged() && unit.hasUnique("Unit will heal every turn, even if it performs an action"))
return false // will heal anyway, and attacks don't hurt return false // will heal anyway, and attacks don't hurt
val unitDistanceToTiles = unit.movement.getDistanceToTiles() val unitDistanceToTiles = unit.movement.getDistanceToTiles()
@ -222,7 +222,7 @@ object UnitAutomation {
} }
fun tryPillageImprovement(unit: MapUnit): Boolean { fun tryPillageImprovement(unit: MapUnit): Boolean {
if (unit.type.isCivilian()) return false if (unit.isCivilian()) return false
val unitDistanceToTiles = unit.movement.getDistanceToTiles() val unitDistanceToTiles = unit.movement.getDistanceToTiles()
val tilesThatCanWalkToAndThenPillage = unitDistanceToTiles val tilesThatCanWalkToAndThenPillage = unitDistanceToTiles
.filter { it.value.totalDistance < unit.currentMovement }.keys .filter { it.value.totalDistance < unit.currentMovement }.keys
@ -263,7 +263,7 @@ object UnitAutomation {
Battle.getMapCombatantOfTile(it.tileToAttack)!!) < unit.health Battle.getMapCombatantOfTile(it.tileToAttack)!!) < unit.health
} }
if (unit.type.isRanged()) if (unit.baseUnit.isRanged())
closeEnemies = closeEnemies.filterNot { it.tileToAttack.isCityCenter() && it.tileToAttack.getCity()!!.health == 1 } closeEnemies = closeEnemies.filterNot { it.tileToAttack.isCityCenter() && it.tileToAttack.getCity()!!.health == 1 }
val closestEnemy = closeEnemies.minByOrNull { it.tileToAttack.aerialDistanceTo(unit.getTile()) } val closestEnemy = closeEnemies.minByOrNull { it.tileToAttack.aerialDistanceTo(unit.getTile()) }
@ -317,7 +317,7 @@ object UnitAutomation {
.flatMap { it.cities }.asSequence() .flatMap { it.cities }.asSequence()
.filter { it.location in unit.civInfo.exploredTiles } .filter { it.location in unit.civInfo.exploredTiles }
if (unit.type.isRanged()) // ranged units don't harm capturable cities, waste of a turn if (unit.baseUnit.isRanged()) // ranged units don't harm capturable cities, waste of a turn
enemyCities = enemyCities.filterNot { it.health == 1 } enemyCities = enemyCities.filterNot { it.health == 1 }
val closestReachableEnemyCity = enemyCities val closestReachableEnemyCity = enemyCities
@ -396,7 +396,7 @@ object UnitAutomation {
if (siegeUnits.any()) targets = siegeUnits if (siegeUnits.any()) targets = siegeUnits
else { else {
val rangedUnits = targets val rangedUnits = targets
.filter { it.getUnitType().isRanged() } .filter { it.isRanged() }
if (rangedUnits.any()) targets = rangedUnits if (rangedUnits.any()) targets = rangedUnits
} }
return targets.minByOrNull { it: ICombatant -> it.getHealth() } return targets.minByOrNull { it: ICombatant -> it.getHealth() }
@ -413,7 +413,7 @@ object UnitAutomation {
} //Most likely just been captured } //Most likely just been captured
if (unit.type.isRanged()) // ranged units don't harm capturable cities, waste of a turn if (unit.baseUnit.isRanged()) // ranged units don't harm capturable cities, waste of a turn
capturedCities = capturedCities.filterNot { it.health == 1 } capturedCities = capturedCities.filterNot { it.health == 1 }
val closestReachableCapturedCity = capturedCities val closestReachableCapturedCity = capturedCities
@ -429,7 +429,7 @@ object UnitAutomation {
} }
private fun tryGarrisoningUnit(unit: MapUnit): Boolean { private fun tryGarrisoningUnit(unit: MapUnit): Boolean {
if (unit.type.isMelee() || unit.type.isWaterUnit()) return false // don't garrison melee units, they're not that good at it if (unit.baseUnit.isMelee() || unit.type.isWaterUnit()) return false // don't garrison melee units, they're not that good at it
val citiesWithoutGarrison = unit.civInfo.cities.filter { val citiesWithoutGarrison = unit.civInfo.cities.filter {
val centerTile = it.getCenterTile() val centerTile = it.getCenterTile()
centerTile.militaryUnit == null centerTile.militaryUnit == null

View File

@ -15,7 +15,7 @@ class WorkerAutomation(val unit: MapUnit) {
val enemyUnitsInWalkingDistance = unit.movement.getDistanceToTiles().keys val enemyUnitsInWalkingDistance = unit.movement.getDistanceToTiles().keys
.filter { UnitAutomation.containsEnemyMilitaryUnit(unit, it) } .filter { UnitAutomation.containsEnemyMilitaryUnit(unit, it) }
if (enemyUnitsInWalkingDistance.isNotEmpty() && !unit.type.isMilitary()) return UnitAutomation.runAway(unit) if (enemyUnitsInWalkingDistance.isNotEmpty() && !unit.baseUnit.isMilitary()) return UnitAutomation.runAway(unit)
val currentTile = unit.getTile() val currentTile = unit.getTile()
val tileToWork = findTileToWork() val tileToWork = findTileToWork()

View File

@ -75,11 +75,11 @@ object Battle {
// Add culture when defeating a barbarian when Honor policy is adopted, gold from enemy killed when honor is complete // Add culture when defeating a barbarian when Honor policy is adopted, gold from enemy killed when honor is complete
// or any enemy military unit with Sacrificial captives unique (can be either attacker or defender!) // or any enemy military unit with Sacrificial captives unique (can be either attacker or defender!)
// or check if unit is captured by the attacker (prize ships unique) // or check if unit is captured by the attacker (prize ships unique)
if (defender.isDefeated() && defender is MapUnitCombatant && !defender.getUnitType().isCivilian()) { if (defender.isDefeated() && defender is MapUnitCombatant && !defender.unit.isCivilian()) {
tryEarnFromKilling(attacker, defender) tryEarnFromKilling(attacker, defender)
tryCaptureUnit(attacker, defender) tryCaptureUnit(attacker, defender)
tryHealAfterKilling(attacker) tryHealAfterKilling(attacker)
} else if (attacker.isDefeated() && attacker is MapUnitCombatant && !attacker.getUnitType().isCivilian()) { } else if (attacker.isDefeated() && attacker is MapUnitCombatant && !attacker.unit.isCivilian()) {
tryEarnFromKilling(defender, attacker) tryEarnFromKilling(defender, attacker)
tryCaptureUnit(defender, attacker) tryCaptureUnit(defender, attacker)
tryHealAfterKilling(defender) tryHealAfterKilling(defender)
@ -140,12 +140,11 @@ object Battle {
} }
} }
private fun tryCaptureUnit(attacker: ICombatant, defender: ICombatant) { private fun tryCaptureUnit(attacker: ICombatant, defender: MapUnitCombatant) {
// https://forums.civfanatics.com/threads/prize-ships-for-land-units.650196/ // https://forums.civfanatics.com/threads/prize-ships-for-land-units.650196/
// https://civilization.fandom.com/wiki/Module:Data/Civ5/GK/Defines // https://civilization.fandom.com/wiki/Module:Data/Civ5/GK/Defines
if (!defender.isDefeated()) return if (!defender.isDefeated()) return
if (attacker !is MapUnitCombatant) return if (attacker !is MapUnitCombatant) return
if (defender is MapUnitCombatant && !defender.getUnitType().isMilitary()) return
if (attacker.unit.getMatchingUniques("May capture killed [] units").none { defender.matchesCategory(it.params[0]) }) return if (attacker.unit.getMatchingUniques("May capture killed [] units").none { defender.matchesCategory(it.params[0]) }) return
var captureChance = 10 + attacker.getAttackingStrength().toFloat() / defender.getDefendingStrength().toFloat() * 40 var captureChance = 10 + attacker.getAttackingStrength().toFloat() / defender.getDefendingStrength().toFloat() * 40
@ -167,8 +166,8 @@ object Battle {
var damageToAttacker = attacker.getHealth() // These variables names don't make any sense as of yet ... var damageToAttacker = attacker.getHealth() // These variables names don't make any sense as of yet ...
var damageToDefender = defender.getHealth() var damageToDefender = defender.getHealth()
if (defender.getUnitType().isCivilian() && attacker.isMelee()) { if (defender is MapUnitCombatant && defender.unit.isCivilian() && attacker.isMelee()) {
captureCivilianUnit(attacker, defender as MapUnitCombatant) captureCivilianUnit(attacker, defender)
} else if (attacker.isRanged()) { } else if (attacker.isRanged()) {
defender.takeDamage(potentialDamageToDefender) // straight up defender.takeDamage(potentialDamageToDefender) // straight up
} else { } else {
@ -302,7 +301,7 @@ object Battle {
private fun postBattleAddXp(attacker: ICombatant, defender: ICombatant) { private fun postBattleAddXp(attacker: ICombatant, defender: ICombatant) {
if (attacker.isMelee()) { if (attacker.isMelee()) {
if (!defender.getUnitType().isCivilian()) // unit was not captured but actually attacked if (!defender.isCivilian()) // unit was not captured but actually attacked
{ {
addXp(attacker, 5, defender) addXp(attacker, 5, defender)
addXp(defender, 4, attacker) addXp(defender, 4, attacker)
@ -321,7 +320,7 @@ object Battle {
// if it was a melee attack and we won, then the unit ALREADY got movement points deducted, // if it was a melee attack and we won, then the unit ALREADY got movement points deducted,
// for the movement to the enemy's tile! // for the movement to the enemy's tile!
// and if it's an air unit, it only has 1 movement anyway, so... // and if it's an air unit, it only has 1 movement anyway, so...
if (!attacker.unit.baseUnit.movesLikeAirUnits() && !(attacker.getUnitType().isMelee() && defender.isDefeated())) if (!attacker.unit.baseUnit.movesLikeAirUnits() && !(attacker.isMelee() && defender.isDefeated()))
unit.useMovementPoints(1f) unit.useMovementPoints(1f)
} else unit.currentMovement = 0f } else unit.currentMovement = 0f
if (unit.isFortified() || unit.isSleeping()) if (unit.isFortified() || unit.isSleeping())
@ -554,7 +553,7 @@ object Battle {
// Damage and/or destroy units on the tile // Damage and/or destroy units on the tile
for (unit in tile.getUnits().toList()) { // tolist so if it's destroyed there's no concurrent modification for (unit in tile.getUnits().toList()) { // tolist so if it's destroyed there's no concurrent modification
val defender = MapUnitCombatant(unit) val defender = MapUnitCombatant(unit)
if (defender.unit.baseUnit.unitType.isCivilian()) { if (defender.unit.isCivilian()) {
unit.destroy() // destroy the unit unit.destroy() // destroy the unit
} else { } else {
defender.takeDamage(((40 + Random().nextInt(60)) * damageModifierFromMissingResource).toInt()) defender.takeDamage(((40 + Random().nextInt(60)) * damageModifierFromMissingResource).toInt())

View File

@ -2,7 +2,6 @@ package com.unciv.logic.battle
import com.unciv.logic.map.TileInfo import com.unciv.logic.map.TileInfo
import com.unciv.models.Counter import com.unciv.models.Counter
import com.unciv.models.ruleset.unit.UnitType
import java.util.* import java.util.*
import kotlin.collections.set import kotlin.collections.set
import kotlin.math.max import kotlin.math.max
@ -269,11 +268,10 @@ object BattleDamage {
} }
private fun getHealthDependantDamageRatio(combatant: ICombatant): Float { private fun getHealthDependantDamageRatio(combatant: ICombatant): Float {
return if (combatant.getUnitType() == UnitType.City return if (combatant !is MapUnitCombatant // is city
|| combatant.getCivInfo() || combatant.getCivInfo()
.hasUnique("Units fight as though they were at full strength even when damaged") .hasUnique("Units fight as though they were at full strength even when damaged")
&& !combatant.getUnitType().isAirUnit() && !combatant.unit.baseUnit.movesLikeAirUnits()
&& !combatant.getUnitType().isMissile()
) )
1f 1f
else 1 - (100 - combatant.getHealth()) / 300f// Each 3 points of health reduces damage dealt by 1% like original game else 1 - (100 - combatant.getHealth()) / 300f// Each 3 points of health reduces damage dealt by 1% like original game
@ -307,7 +305,7 @@ object BattleDamage {
defender: ICombatant defender: ICombatant
): Int { ): Int {
if (attacker.isRanged()) return 0 if (attacker.isRanged()) return 0
if (defender.getUnitType().isCivilian()) return 0 if (defender.isCivilian()) return 0
val ratio = val ratio =
getAttackingStrength(attacker, tileToAttackFrom, defender) / getDefendingStrength( getAttackingStrength(attacker, tileToAttackFrom, defender) / getDefendingStrength(
attacker, attacker,

View File

@ -21,10 +21,10 @@ interface ICombatant{
fun matchesCategory(category:String): Boolean fun matchesCategory(category:String): Boolean
fun getAttackSound(): UncivSound fun getAttackSound(): UncivSound
fun isMelee(): Boolean { fun isMelee(): Boolean = !isRanged()
return getUnitType().isMelee()
}
fun isRanged(): Boolean { fun isRanged(): Boolean {
return getUnitType().isRanged() if (this is CityCombatant) return true
return (this as MapUnitCombatant).unit.baseUnit.isRanged()
} }
fun isCivilian() = this is MapUnitCombatant && this.unit.isCivilian()
} }

View File

@ -31,7 +31,7 @@ class MapUnitCombatant(val unit: MapUnit) : ICombatant {
} }
override fun getDefendingStrength(): Int { override fun getDefendingStrength(): Int {
return if (unit.isEmbarked() && !unit.type.isCivilian()) 5 * getCivInfo().getEraNumber() return if (unit.isEmbarked() && !isCivilian()) 5 * getCivInfo().getEraNumber()
else unit.baseUnit().strength else unit.baseUnit().strength
} }

View File

@ -543,9 +543,9 @@ class CityInfo {
fun canPurchase(construction: IConstruction): Boolean { fun canPurchase(construction: IConstruction): Boolean {
if (construction is BaseUnit) { if (construction is BaseUnit) {
val tile = getCenterTile() val tile = getCenterTile()
if (construction.unitType.isCivilian()) if (construction.isCivilian())
return tile.civilianUnit == null return tile.civilianUnit == null
if (construction.unitType.isAirUnit() || construction.unitType.isMissile()) if (construction.movesLikeAirUnits())
return tile.airUnits.filter { !it.isTransported }.size < 6 return tile.airUnits.filter { !it.isTransported }.size < 6
else return tile.militaryUnit == null else return tile.militaryUnit == null
} }

View File

@ -563,7 +563,7 @@ class CivilizationInfo {
// disband units until there are none left OR the gold values are normal // disband units until there are none left OR the gold values are normal
if (!isBarbarian() && gold < -100 && nextTurnStats.gold.toInt() < 0) { if (!isBarbarian() && gold < -100 && nextTurnStats.gold.toInt() < 0) {
for (i in 1 until (gold / -100)) { for (i in 1 until (gold / -100)) {
var civMilitaryUnits = getCivUnits().filter { !it.type.isCivilian() } var civMilitaryUnits = getCivUnits().filter { it.baseUnit.isMilitary() }
if (civMilitaryUnits.any()) { if (civMilitaryUnits.any()) {
val unitToDisband = civMilitaryUnits.first() val unitToDisband = civMilitaryUnits.first()
unitToDisband.disband() unitToDisband.disband()
@ -765,7 +765,7 @@ class CivilizationInfo {
val cities = NextTurnAutomation.getClosestCities(this, otherCiv) val cities = NextTurnAutomation.getClosestCities(this, otherCiv)
val city = cities.city1 val city = cities.city1
val militaryUnit = city.cityConstructions.getConstructableUnits() val militaryUnit = city.cityConstructions.getConstructableUnits()
.filter { !it.unitType.isCivilian() && it.unitType.isLandUnit() && it.uniqueTo==null } .filter { !it.isCivilian() && it.unitType.isLandUnit() && it.uniqueTo==null }
.toList().random() .toList().random()
// placing the unit may fail - in that case stay quiet // placing the unit may fail - in that case stay quiet
val placedUnit = placeUnitNearTile(city.location, militaryUnit.name) ?: return val placedUnit = placeUnitNearTile(city.location, militaryUnit.name) ?: return

View File

@ -223,7 +223,7 @@ class MapUnit {
if (hasUnique("Limited Visibility")) visibilityRange -= 1 if (hasUnique("Limited Visibility")) visibilityRange -= 1
// Deprecated since 3.15.1 // Deprecated since 3.15.1
if (civInfo.hasUnique("+1 Sight for all land military units") && type.isMilitary() && type.isLandUnit()) if (civInfo.hasUnique("+1 Sight for all land military units") && baseUnit.isMilitary() && type.isLandUnit())
visibilityRange += 1 visibilityRange += 1
// //
@ -251,17 +251,19 @@ class MapUnit {
} }
fun isFortified() = action?.startsWith("Fortify") == true fun isFortified() = action?.startsWith("Fortify") == true
fun isFortifyingUntilHealed() = isFortified() && action?.endsWith("until healed") == true fun isFortifyingUntilHealed() = isFortified() && action?.endsWith("until healed") == true
fun isSleeping() = action?.startsWith("Sleep") == true fun isSleeping() = action?.startsWith("Sleep") == true
fun isSleepingUntilHealed() = isSleeping() && action?.endsWith("until healed") == true fun isSleepingUntilHealed() = isSleeping() && action?.endsWith("until healed") == true
fun isMoving() = action?.startsWith("moveTo") == true fun isMoving() = action?.startsWith("moveTo") == true
fun isAutomaticallyBuildingImprovements() = action != null && action == Constants.unitActionAutomation fun isAutomaticallyBuildingImprovements() = action != null && action == Constants.unitActionAutomation
fun isCivilian() = baseUnit.isCivilian()
fun getFortificationTurns(): Int { fun getFortificationTurns(): Int {
if (!isFortified()) return 0 if (!isFortified()) return 0
return action!!.split(" ")[1].toInt() return action!!.split(" ")[1].toInt()
@ -301,7 +303,7 @@ class MapUnit {
} }
fun getRange(): Int { fun getRange(): Int {
if (type.isMelee()) return 1 if (baseUnit.isMelee()) return 1
var range = baseUnit().range var range = baseUnit().range
// Deprecated since 3.15.6 // Deprecated since 3.15.6
if (hasUnique("+1 Range")) range++ if (hasUnique("+1 Range")) range++
@ -370,9 +372,8 @@ class MapUnit {
fun canFortify(): Boolean { fun canFortify(): Boolean {
if (type.isWaterUnit()) return false if (type.isWaterUnit()) return false
if (type.isCivilian()) return false if (isCivilian()) return false
if (type.isAirUnit()) return false if (baseUnit.movesLikeAirUnits()) return false
if (type.isMissile()) return false
if (isEmbarked()) return false if (isEmbarked()) return false
if (hasUnique("No defensive terrain bonus")) return false if (hasUnique("No defensive terrain bonus")) return false
if (isFortified()) return false if (isFortified()) return false
@ -395,7 +396,7 @@ class MapUnit {
return getMatchingUniques("All adjacent units heal [] HP when healing").sumBy { it.params[0].toInt() } return getMatchingUniques("All adjacent units heal [] HP when healing").sumBy { it.params[0].toInt() }
} }
fun canGarrison() = type.isMilitary() && type.isLandUnit() fun canGarrison() = baseUnit.isMilitary() && type.isLandUnit()
fun isGreatPerson() = baseUnit.isGreatPerson() fun isGreatPerson() = baseUnit.isGreatPerson()
@ -634,7 +635,7 @@ class MapUnit {
// Wake sleeping units if there's an enemy in vision range: // Wake sleeping units if there's an enemy in vision range:
// Military units always but civilians only if not protected. // Military units always but civilians only if not protected.
if (isSleeping() && (!type.isCivilian() || currentTile.militaryUnit == null) && if (isSleeping() && (baseUnit.isMilitary() || currentTile.militaryUnit == null) &&
this.viewableTiles.any { this.viewableTiles.any {
it.militaryUnit != null && it.militaryUnit!!.civInfo.isAtWarWith(civInfo) it.militaryUnit != null && it.militaryUnit!!.civInfo.isAtWarWith(civInfo)
} }
@ -679,7 +680,7 @@ class MapUnit {
if (tile.improvement == Constants.barbarianEncampment && !civInfo.isBarbarian()) if (tile.improvement == Constants.barbarianEncampment && !civInfo.isBarbarian())
clearEncampment(tile) clearEncampment(tile)
if (!hasUnique("All healing effects doubled") && type.isLandUnit() && type.isMilitary()) { if (!hasUnique("All healing effects doubled") && type.isLandUnit() && baseUnit.isMilitary()) {
val gainDoubleHealPromotion = tile.neighbors val gainDoubleHealPromotion = tile.neighbors
.any { it.hasUnique("Grants Rejuvenation (all healing effects doubled) to adjacent military land units for the rest of the game") } .any { it.hasUnique("Grants Rejuvenation (all healing effects doubled) to adjacent military land units for the rest of the game") }
if (gainDoubleHealPromotion && civInfo.gameInfo.ruleSet.unitPromotions.containsKey("Rejuvenation")) if (gainDoubleHealPromotion && civInfo.gameInfo.ruleSet.unitPromotions.containsKey("Rejuvenation"))
@ -693,13 +694,12 @@ class MapUnit {
when { when {
!movement.canMoveTo(tile) -> !movement.canMoveTo(tile) ->
throw Exception("I can't go there!") throw Exception("I can't go there!")
type.isAirUnit() || type.isMissile() -> tile.airUnits.add(this) baseUnit.movesLikeAirUnits() -> tile.airUnits.add(this)
type.isCivilian() -> tile.civilianUnit = this isCivilian() -> tile.civilianUnit = this
else -> tile.militaryUnit = this else -> tile.militaryUnit = this
} }
// this check is here in order to not load the fresh built unit into carrier right after the build // this check is here in order to not load the fresh built unit into carrier right after the build
isTransported = !tile.isCityCenter() && isTransported = !tile.isCityCenter() && baseUnit.movesLikeAirUnits() // not moving civilians
(type.isAirUnit() || type.isMissile()) // not moving civilians
moveThroughTile(tile) moveThroughTile(tile)
} }
@ -816,7 +816,7 @@ class MapUnit {
} }
} }
if (!type.isCivilian()) if (!isCivilian())
actions.add { actions.add {
promotions.XP += 10 promotions.XP += 10
civInfo.addNotification( civInfo.addNotification(
@ -881,7 +881,7 @@ class MapUnit {
fun isTransportTypeOf(mapUnit: MapUnit): Boolean { fun isTransportTypeOf(mapUnit: MapUnit): Boolean {
// Currently, only missiles and airplanes can be carried // Currently, only missiles and airplanes can be carried
if (!mapUnit.type.isMissile() && !mapUnit.type.isAirUnit()) return false if (!mapUnit.baseUnit.movesLikeAirUnits()) return false
return getMatchingUniques("Can carry [] [] units").any { mapUnit.matchesFilter(it.params[1]) } return getMatchingUniques("Can carry [] [] units").any { mapUnit.matchesFilter(it.params[1]) }
} }
@ -941,13 +941,12 @@ class MapUnit {
private fun getCitadelDamage() { private fun getCitadelDamage() {
// Check for Citadel damage - note: 'Damage does not stack with other Citadels' // Check for Citadel damage - note: 'Damage does not stack with other Citadels'
val citadelTile = currentTile.neighbors val citadelTile = currentTile.neighbors
.filter { .firstOrNull {
it.getOwner() != null && civInfo.isAtWarWith(it.getOwner()!!) && it.getOwner() != null && civInfo.isAtWarWith(it.getOwner()!!) &&
with(it.getTileImprovement()) { with(it.getTileImprovement()) {
this != null && this.hasUnique("Deal 30 damage to adjacent enemy units") this != null && this.hasUnique("Deal 30 damage to adjacent enemy units")
} }
} }
.firstOrNull()
if (citadelTile != null) { if (citadelTile != null) {
health -= 30 health -= 30

View File

@ -179,7 +179,7 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
if (currentTile == finalDestination) return currentTile if (currentTile == finalDestination) return currentTile
// If we can fly, head there directly // If we can fly, head there directly
if (unit.type.isAirUnit() || unit.type.isMissile() || unit.action == Constants.unitActionParadrop) return finalDestination if (unit.baseUnit.movesLikeAirUnits() || unit.action == Constants.unitActionParadrop) return finalDestination
val distanceToTiles = getDistanceToTiles() val distanceToTiles = getDistanceToTiles()
@ -217,13 +217,13 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
/** This is performance-heavy - use as last resort, only after checking everything else! /** This is performance-heavy - use as last resort, only after checking everything else!
* Also note that REACHABLE tiles are not necessarily tiles that the unit CAN ENTER */ * Also note that REACHABLE tiles are not necessarily tiles that the unit CAN ENTER */
fun canReach(destination: TileInfo): Boolean { fun canReach(destination: TileInfo): Boolean {
if (unit.type.isAirUnit() || unit.type.isMissile() || unit.action == Constants.unitActionParadrop) if (unit.baseUnit.movesLikeAirUnits() || unit.action == Constants.unitActionParadrop)
return canReachInCurrentTurn(destination) return canReachInCurrentTurn(destination)
return getShortestPath(destination).any() return getShortestPath(destination).any()
} }
fun canReachInCurrentTurn(destination: TileInfo): Boolean { fun canReachInCurrentTurn(destination: TileInfo): Boolean {
if (unit.type.isAirUnit() || unit.type.isMissile()) if (unit.baseUnit.movesLikeAirUnits())
return unit.currentTile.aerialDistanceTo(destination) <= unit.getRange()*2 return unit.currentTile.aerialDistanceTo(destination) <= unit.getRange()*2
if (unit.action == Constants.unitActionParadrop) if (unit.action == Constants.unitActionParadrop)
return getDistance(unit.currentTile.position, destination.position) <= unit.paradropRange && canParadropOn(destination) return getDistance(unit.currentTile.position, destination.position) <= unit.paradropRange && canParadropOn(destination)
@ -232,7 +232,7 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
fun getReachableTilesInCurrentTurn(): Sequence<TileInfo> { fun getReachableTilesInCurrentTurn(): Sequence<TileInfo> {
return when { return when {
unit.type.isAirUnit() || unit.type.isMissile() -> unit.baseUnit.movesLikeAirUnits() ->
unit.getTile().getTilesInDistanceRange(IntRange(1, unit.getRange() * 2)) unit.getTile().getTilesInDistanceRange(IntRange(1, unit.getRange() * 2))
unit.action == Constants.unitActionParadrop -> unit.action == Constants.unitActionParadrop ->
unit.getTile().getTilesInDistance(unit.paradropRange) unit.getTile().getTilesInDistance(unit.paradropRange)
@ -259,13 +259,13 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
*/ */
private fun canUnitSwapToReachableTile(reachableTile: TileInfo): Boolean { private fun canUnitSwapToReachableTile(reachableTile: TileInfo): Boolean {
// Air units cannot swap // Air units cannot swap
if (unit.type.isAirUnit() || unit.type.isMissile()) return false if (unit.baseUnit.movesLikeAirUnits()) return false
// We can't swap with ourself // We can't swap with ourself
if (reachableTile == unit.getTile()) return false if (reachableTile == unit.getTile()) return false
// Check whether the tile contains a unit of the same type as us that we own and that can // Check whether the tile contains a unit of the same type as us that we own and that can
// also reach our tile in its current turn. // also reach our tile in its current turn.
val otherUnit = ( val otherUnit = (
if (unit.type.isCivilian()) if (unit.isCivilian())
reachableTile.civilianUnit reachableTile.civilianUnit
else else
reachableTile.militaryUnit reachableTile.militaryUnit
@ -322,7 +322,7 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
fun moveToTile(destination: TileInfo) { fun moveToTile(destination: TileInfo) {
if (destination == unit.getTile()) return // already here! if (destination == unit.getTile()) return // already here!
if (unit.type.isAirUnit() || unit.type.isMissile()) { // air units move differently from all other units if (unit.baseUnit.movesLikeAirUnits()) { // air units move differently from all other units
unit.action = null unit.action = null
unit.removeFromTile() unit.removeFromTile()
unit.isTransported = false // it has left the carrier by own means unit.isTransported = false // it has left the carrier by own means
@ -398,7 +398,7 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
*/ */
fun swapMoveToTile(destination: TileInfo) { fun swapMoveToTile(destination: TileInfo) {
val otherUnit = ( val otherUnit = (
if (unit.type.isCivilian()) if (unit.isCivilian())
destination.civilianUnit destination.civilianUnit
else else
destination.militaryUnit destination.militaryUnit
@ -421,7 +421,7 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
* DOES NOT designate whether we can reach that tile in the current turn * DOES NOT designate whether we can reach that tile in the current turn
*/ */
fun canMoveTo(tile: TileInfo): Boolean { fun canMoveTo(tile: TileInfo): Boolean {
if (unit.type.isAirUnit() || unit.type.isMissile()) if (unit.baseUnit.movesLikeAirUnits())
return canAirUnitMoveTo(tile, unit) return canAirUnitMoveTo(tile, unit)
if (!canPassThrough(tile)) if (!canPassThrough(tile))
@ -429,7 +429,7 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
if (tile.isCityCenter() && tile.getOwner() != unit.civInfo) return false // even if they'll let us pass through, we can't enter their city if (tile.isCityCenter() && tile.getOwner() != unit.civInfo) return false // even if they'll let us pass through, we can't enter their city
if (unit.type.isCivilian()) if (unit.isCivilian())
return tile.civilianUnit == null && (tile.militaryUnit == null || tile.militaryUnit!!.owner == unit.owner) return tile.civilianUnit == null && (tile.militaryUnit == null || tile.militaryUnit!!.owner == unit.owner)
else return tile.militaryUnit == null && (tile.civilianUnit == null || tile.civilianUnit!!.owner == unit.owner) else return tile.militaryUnit == null && (tile.civilianUnit == null || tile.civilianUnit!!.owner == unit.owner)
} }

View File

@ -277,9 +277,9 @@ class Ruleset {
for (unit in units.values) { for (unit in units.values) {
if (unit.upgradesTo == unit.name) if (unit.upgradesTo == unit.name)
lines += "${unit.name} upgrades to itself!" lines += "${unit.name} upgrades to itself!"
if (!unit.unitType.isCivilian() && unit.strength == 0) if (!unit.isCivilian() && unit.strength == 0)
lines += "${unit.name} is a military unit but has no assigned strength!" lines += "${unit.name} is a military unit but has no assigned strength!"
if (unit.unitType.isRanged() && unit.rangedStrength == 0 && "Cannot attack" !in unit.uniques) if (unit.isRanged() && unit.rangedStrength == 0 && "Cannot attack" !in unit.uniques)
lines += "${unit.name} is a ranged unit but has no assigned rangedStrength!" lines += "${unit.name} is a ranged unit but has no assigned rangedStrength!"
} }

View File

@ -7,8 +7,8 @@ import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.map.MapUnit import com.unciv.logic.map.MapUnit
import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.Unique import com.unciv.models.ruleset.Unique
import com.unciv.models.translations.tr
import com.unciv.models.stats.INamed import com.unciv.models.stats.INamed
import com.unciv.models.translations.tr
import com.unciv.ui.civilopedia.CivilopediaText import com.unciv.ui.civilopedia.CivilopediaText
import com.unciv.ui.civilopedia.FormattedLine import com.unciv.ui.civilopedia.FormattedLine
import com.unciv.ui.utils.Fonts import com.unciv.ui.utils.Fonts
@ -277,7 +277,7 @@ class BaseUnit : INamed, IConstruction, CivilopediaText() {
if (wasBought && !civInfo.gameInfo.gameParameters.godMode && !unit.hasUnique("Can move immediately once bought")) if (wasBought && !civInfo.gameInfo.gameParameters.godMode && !unit.hasUnique("Can move immediately once bought"))
unit.currentMovement = 0f unit.currentMovement = 0f
if (this.unitType.isCivilian()) return true // tiny optimization makes save files a few bytes smaller if (this.isCivilian()) return true // tiny optimization makes save files a few bytes smaller
var XP = cityConstructions.getBuiltBuildings().sumBy { it.xpForNewUnits } var XP = cityConstructions.getBuiltBuildings().sumBy { it.xpForNewUnits }
@ -339,24 +339,20 @@ class BaseUnit : INamed, IConstruction, CivilopediaText() {
name -> true name -> true
"All" -> true "All" -> true
"Melee" -> unitType.isMelee() "Melee" -> isMelee()
"Ranged" -> unitType.isRanged() "Ranged" -> isRanged()
"Land", "land units" -> unitType.isLandUnit() "Land", "land units" -> unitType.isLandUnit()
"Civilian" -> unitType.isCivilian() "Civilian" -> isCivilian()
"Military", "military units" -> unitType.isMilitary() "Military", "military units" -> isMilitary()
"Water", "water units" -> unitType.isWaterUnit() "Water", "water units" -> unitType.isWaterUnit()
"Air", "air units" -> unitType.isAirUnit() "Air", "air units" -> unitType.isAirUnit()
"non-air" -> !unitType.isAirUnit() && !unitType.isMissile() "non-air" -> !movesLikeAirUnits()
"Missile" -> unitType.isMissile()
"Submarine", "submarine units" -> unitType == UnitType.WaterSubmarine "Submarine", "submarine units" -> unitType == UnitType.WaterSubmarine
"Nuclear Weapon" -> isNuclearWeapon() "Nuclear Weapon" -> isNuclearWeapon()
// Deprecated as of 3.15.2 // Deprecated as of 3.15.2
"military water" -> unitType.isMilitary() && unitType.isWaterUnit() "military water" -> isMilitary() && unitType.isWaterUnit()
else -> { else -> return uniques.contains(filter)
if (uniques.contains(filter)) return true
return false
}
} }
} }
@ -365,7 +361,7 @@ class BaseUnit : INamed, IConstruction, CivilopediaText() {
// "Nuclear Weapon" unique deprecated since 3.15.4 // "Nuclear Weapon" unique deprecated since 3.15.4
fun isNuclearWeapon() = uniqueObjects.any { it.placeholderText == "Nuclear Weapon" || it.placeholderText == "Nuclear weapon of Strength []" } fun isNuclearWeapon() = uniqueObjects.any { it.placeholderText == "Nuclear Weapon" || it.placeholderText == "Nuclear weapon of Strength []" }
fun movesLikeAirUnits() = unitType.isAirUnit() || unitType.isMissile() fun movesLikeAirUnits() = unitType.isAirUnit() || unitType == UnitType.Missile
override fun getResourceRequirements(): HashMap<String, Int> { override fun getResourceRequirements(): HashMap<String, Int> {
val resourceRequirements = HashMap<String, Int>() val resourceRequirements = HashMap<String, Int>()
@ -375,4 +371,9 @@ class BaseUnit : INamed, IConstruction, CivilopediaText() {
resourceRequirements[unique.params[1]] = unique.params[0].toInt() resourceRequirements[unique.params[1]] = unique.params[0].toInt()
return resourceRequirements return resourceRequirements
} }
fun isRanged() = rangedStrength > 0
fun isMelee() = !isRanged() && strength > 0
fun isMilitary() = isRanged() || isMelee()
fun isCivilian() = !isMilitary()
} }

View File

@ -21,23 +21,6 @@ enum class UnitType{
AtomicBomber, AtomicBomber,
Missile; Missile;
fun isMelee() =
this == Melee
|| this == Mounted
|| this == Armor
|| this == Scout
|| this == WaterMelee
fun isRanged() =
this == Ranged
|| this == Siege
|| this == WaterRanged
|| this == WaterSubmarine
|| this == WaterAircraftCarrier
|| this == City
|| this.isAirUnit()
|| this.isMissile()
fun isLandUnit() = fun isLandUnit() =
this == Civilian this == Civilian
|| this == Melee || this == Melee
@ -47,10 +30,6 @@ enum class UnitType{
|| this == Ranged || this == Ranged
|| this == Siege || this == Siege
fun isCivilian() = this == Civilian || this == WaterCivilian
fun isMilitary() = this != Civilian && this != WaterCivilian
fun isWaterUnit() = fun isWaterUnit() =
this == WaterSubmarine this == WaterSubmarine
|| this == WaterRanged || this == WaterRanged
@ -62,7 +41,5 @@ enum class UnitType{
this == Bomber this == Bomber
|| this == Fighter || this == Fighter
|| this == AtomicBomber || this == AtomicBomber
fun isMissile() =
this == Missile
} }

View File

@ -214,7 +214,7 @@ class MapEditorOptionsTable(val mapEditorScreen: MapEditorScreen): Table(CameraS
it.airUnits.add(unit) it.airUnits.add(unit)
if (!it.isCityCenter()) unit.isTransported = true // if not city - air unit enters carrier if (!it.isCityCenter()) unit.isTransported = true // if not city - air unit enters carrier
} }
unit.type.isCivilian() -> it.civilianUnit = unit unit.isCivilian() -> it.civilianUnit = unit
else -> it.militaryUnit = unit else -> it.militaryUnit = unit
} }
unit.currentTile = it // needed for unit icon - unit needs to know if it's embarked or not... unit.currentTile = it // needed for unit icon - unit needs to know if it's embarked or not...

View File

@ -14,8 +14,8 @@ class WorldTileGroup(internal val worldScreen: WorldScreen, tileInfo: TileInfo,
private var cityButton: CityButton? = null private var cityButton: CityButton? = null
fun selectUnit(unit: MapUnit) { fun selectUnit(unit: MapUnit) {
if(unit.type.isAirUnit() || unit.type.isMissile()) return // doesn't appear on map so nothing to select if(unit.baseUnit.movesLikeAirUnits()) return // doesn't appear on map so nothing to select
val unitImage = if (unit.type.isCivilian()) icons.civilianUnitIcon val unitImage = if (unit.isCivilian()) icons.civilianUnitIcon
else icons.militaryUnitIcon else icons.militaryUnitIcon
unitImage?.selectUnit() unitImage?.selectUnit()
} }

View File

@ -488,7 +488,7 @@ class WorldMapHolder(internal val worldScreen: WorldScreen, internal val tileMap
} }
// Fade out less relevant images if a military unit is selected // Fade out less relevant images if a military unit is selected
val fadeout = if (unit.type.isCivilian()) 1f val fadeout = if (unit.isCivilian()) 1f
else 0.5f else 0.5f
for (tile in allWorldTileGroups) { for (tile in allWorldTileGroups) {
if (tile.icons.populationIcon != null) tile.icons.populationIcon!!.color.a = fadeout if (tile.icons.populationIcon != null) tile.icons.populationIcon!!.color.a = fadeout
@ -545,7 +545,7 @@ class WorldMapHolder(internal val worldScreen: WorldScreen, internal val tileMap
tileGroup.showCircle(Color.WHITE, 0.7f) tileGroup.showCircle(Color.WHITE, 0.7f)
} }
val attackableTiles: List<AttackableTile> = if (unit.type.isCivilian()) listOf() val attackableTiles: List<AttackableTile> = if (unit.isCivilian()) listOf()
else { else {
BattleHelper.getAttackableEnemies(unit, unit.movement.getDistanceToTiles()) BattleHelper.getAttackableEnemies(unit, unit.movement.getDistanceToTiles())
.filter { .filter {

View File

@ -483,7 +483,7 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Cam
displayTutorial(Tutorial.Workers) { displayTutorial(Tutorial.Workers) {
gameInfo.getCurrentPlayerCivilization().getCivUnits().any { gameInfo.getCurrentPlayerCivilization().getCivUnits().any {
(it.hasUnique(Constants.canBuildImprovements) || it.hasUnique(Constants.workerUnique)) (it.hasUnique(Constants.canBuildImprovements) || it.hasUnique(Constants.workerUnique))
&& it.type.isCivilian() && it.isCivilian()
} }
} }
} }

View File

@ -58,7 +58,7 @@ class BattleTable(val worldScreen: WorldScreen): Table() {
private fun tryGetAttacker(): ICombatant? { private fun tryGetAttacker(): ICombatant? {
val unitTable = worldScreen.bottomUnitTable val unitTable = worldScreen.bottomUnitTable
return if (unitTable.selectedUnit != null return if (unitTable.selectedUnit != null
&& !unitTable.selectedUnit!!.type.isCivilian() && !unitTable.selectedUnit!!.isCivilian()
&& !unitTable.selectedUnit!!.hasUnique("Cannot attack")) && !unitTable.selectedUnit!!.hasUnique("Cannot attack"))
MapUnitCombatant(unitTable.selectedUnit!!) MapUnitCombatant(unitTable.selectedUnit!!)
else if (unitTable.selectedCity != null) else if (unitTable.selectedCity != null)
@ -159,13 +159,16 @@ class BattleTable(val worldScreen: WorldScreen): Table() {
else if (damageToDefender>defender.getHealth()) damageToDefender=defender.getHealth() else if (damageToDefender>defender.getHealth()) damageToDefender=defender.getHealth()
if(attacker.isMelee() && (defender.getUnitType().isCivilian() if(attacker.isMelee() && (defender.isCivilian()
|| defender.getUnitType()==UnitType.City && defender.isDefeated())) { || defender is CityCombatant && defender.isDefeated())) {
add("") add("")
add(if(defender.getUnitType().isCivilian() add(
&& (defender as MapUnitCombatant).unit.hasUnique("Uncapturable")) "" if (defender.isCivilian()
else if(defender.getUnitType().isCivilian()) "Captured!".tr() && (defender as MapUnitCombatant).unit.hasUnique("Uncapturable")
else "Occupied!".tr()) ) ""
else if (defender.isCivilian()) "Captured!".tr()
else "Occupied!".tr()
)
} }

View File

@ -82,7 +82,7 @@ object UnitActions {
private fun addSwapAction(unit: MapUnit, actionList: ArrayList<UnitAction>, worldScreen: WorldScreen) { private fun addSwapAction(unit: MapUnit, actionList: ArrayList<UnitAction>, worldScreen: WorldScreen) {
// Air units cannot swap // Air units cannot swap
if (unit.type.isAirUnit() || unit.type.isMissile()) return if (unit.baseUnit.movesLikeAirUnits()) return
// Disable unit swapping if multiple units are selected. It would make little sense. // Disable unit swapping if multiple units are selected. It would make little sense.
// In principle, the unit swapping mode /will/ function with multiselect: it will simply // In principle, the unit swapping mode /will/ function with multiselect: it will simply
// only consider the first selected unit, and ignore the other selections. However, it does // only consider the first selected unit, and ignore the other selections. However, it does
@ -208,7 +208,7 @@ object UnitActions {
} }
private fun addPromoteAction(unit: MapUnit, actionList: ArrayList<UnitAction>) { private fun addPromoteAction(unit: MapUnit, actionList: ArrayList<UnitAction>) {
if (unit.type.isCivilian() || !unit.promotions.canBePromoted()) return if (unit.isCivilian() || !unit.promotions.canBePromoted()) return
// promotion does not consume movement points, so we can do it always // promotion does not consume movement points, so we can do it always
actionList += UnitAction(UnitActionType.Promote, actionList += UnitAction(UnitActionType.Promote,
uncivSound = UncivSound.Promote, uncivSound = UncivSound.Promote,
@ -264,7 +264,7 @@ object UnitActions {
fun getPillageAction(unit: MapUnit): UnitAction? { fun getPillageAction(unit: MapUnit): UnitAction? {
val tile = unit.currentTile val tile = unit.currentTile
if (unit.type.isCivilian() || tile.improvement == null) return null if (unit.isCivilian() || tile.improvement == null) return null
return UnitAction(UnitActionType.Pillage, return UnitAction(UnitActionType.Pillage,
action = { action = {
@ -278,7 +278,7 @@ object UnitActions {
if (tile.resource != null) tile.getOwner()?.updateDetailedCivResources() // this might take away a resource if (tile.resource != null) tile.getOwner()?.updateDetailedCivResources() // this might take away a resource
val freePillage = unit.hasUnique("No movement cost to pillage") || val freePillage = unit.hasUnique("No movement cost to pillage") ||
(unit.type.isMelee() && unit.civInfo.hasUnique("Melee units pay no movement cost to pillage")) (unit.baseUnit.isMelee() && unit.civInfo.hasUnique("Melee units pay no movement cost to pillage"))
if (!freePillage) unit.useMovementPoints(1f) if (!freePillage) unit.useMovementPoints(1f)
unit.healBy(25) unit.healBy(25)

View File

@ -137,7 +137,7 @@ class UnitTable(val worldScreen: WorldScreen) : Table(){
unitDescriptionTable.add(ImageGetter.getStatIcon("Movement")).size(20f) unitDescriptionTable.add(ImageGetter.getStatIcon("Movement")).size(20f)
unitDescriptionTable.add(unit.getMovementString()).padRight(10f) unitDescriptionTable.add(unit.getMovementString()).padRight(10f)
if (!unit.type.isCivilian()) { if (!unit.isCivilian()) {
unitDescriptionTable.add(ImageGetter.getStatIcon("Strength")).size(20f) unitDescriptionTable.add(ImageGetter.getStatIcon("Strength")).size(20f)
unitDescriptionTable.add(unit.baseUnit().strength.toString()).padRight(10f) unitDescriptionTable.add(unit.baseUnit().strength.toString()).padRight(10f)
} }
@ -147,18 +147,18 @@ class UnitTable(val worldScreen: WorldScreen) : Table(){
unitDescriptionTable.add(unit.baseUnit().rangedStrength.toString()).padRight(10f) unitDescriptionTable.add(unit.baseUnit().rangedStrength.toString()).padRight(10f)
} }
if (unit.type.isRanged()) { if (unit.baseUnit.isRanged()) {
unitDescriptionTable.add(ImageGetter.getStatIcon("Range")).size(20f) unitDescriptionTable.add(ImageGetter.getStatIcon("Range")).size(20f)
unitDescriptionTable.add(unit.getRange().toString()).padRight(10f) unitDescriptionTable.add(unit.getRange().toString()).padRight(10f)
} }
if (unit.baseUnit.interceptRange > 0) { if (unit.baseUnit.interceptRange > 0) {
unitDescriptionTable.add(ImageGetter.getStatIcon("InterceptRange")).size(20f) unitDescriptionTable.add(ImageGetter.getStatIcon("InterceptRange")).size(20f)
val range = if (unit.type.isRanged()) unit.getRange() else unit.baseUnit.interceptRange val range = if (unit.baseUnit.isRanged()) unit.getRange() else unit.baseUnit.interceptRange
unitDescriptionTable.add(range.toString()).padRight(10f) unitDescriptionTable.add(range.toString()).padRight(10f)
} }
if (!unit.type.isCivilian()) { if (!unit.isCivilian()) {
unitDescriptionTable.add("XP") unitDescriptionTable.add("XP")
unitDescriptionTable.add(unit.promotions.XP.toString() + "/" + unit.promotions.xpForNextPromotion()) unitDescriptionTable.add(unit.promotions.XP.toString() + "/" + unit.promotions.xpForNextPromotion())
} }