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()
barbarianCiv.tech.techsResearched = allResearchedTechs.toHashSet()
val unitList = ruleSet.units.values
.filter { !it.unitType.isCivilian() }
.filter { it.isMilitary() }
.filter { it.isBuildable(barbarianCiv) }
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.ui.newgamescreen.GameSetupInfo
import java.util.*
import kotlin.NoSuchElementException
import kotlin.collections.ArrayList
import kotlin.collections.HashMap
import kotlin.math.max
@ -240,10 +239,10 @@ object GameStarter {
else -> gameInfo.getDifficulty().aiCityStateStartingUnits
}).toMutableList()
val warriorEquivalent = ruleSet.units
.filter { it.value.unitType.isLandUnit() && it.value.unitType.isMilitary() && it.value.isBuildable(civ) }
.maxByOrNull {max(it.value.strength, it.value.rangedStrength)}
?.key
val warriorEquivalent = ruleSet.units.values
.filter { it.unitType.isLandUnit() && it.isMilitary() && it.isBuildable(civ) }
.maxByOrNull {max(it.strength, it.rangedStrength)}
?.name
for (unit in startingUnits) {
val unitToAdd = if (unit == "Warrior") warriorEquivalent else unit
@ -277,7 +276,7 @@ object GameStarter {
val buildableSettlerLikeUnits =
settlerLikeUnits.filter {
it.value.isBuildable(civ)
&& it.value.unitType.isCivilian()
&& it.value.isCivilian()
}
if (buildableSettlerLikeUnits.isEmpty()) return null // No settlers in this mod
return civ.getEquivalentUnit(buildableSettlerLikeUnits.keys.random()).name
@ -286,7 +285,7 @@ object GameStarter {
val buildableWorkerLikeUnits = ruleSet.units.filter {
it.value.uniqueObjects.any { it.placeholderText == Constants.canBuildImprovements }
&& it.value.isBuildable(civ)
&& it.value.unitType.isCivilian()
&& it.value.isCivilian()
}
if (buildableWorkerLikeUnits.isEmpty()) return null // No workers in this mod
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.civilization.CivilizationInfo
import com.unciv.logic.map.BFS
import com.unciv.logic.map.RoadStatus
import com.unciv.logic.map.TileInfo
import com.unciv.models.ruleset.VictoryType
import com.unciv.models.ruleset.tile.ResourceType
@ -60,7 +59,7 @@ object Automation {
fun chooseMilitaryUnit(city: CityInfo): String? {
var militaryUnits =
city.cityConstructions.getConstructableUnits().filter { !it.unitType.isCivilian() }
city.cityConstructions.getConstructableUnits().filter { !it.isCivilian() }
if (militaryUnits.map { it.name }
.contains(city.cityConstructions.currentConstructionFromQueue))
return city.cityConstructions.currentConstructionFromQueue

View File

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

View File

@ -122,7 +122,7 @@ object BattleHelper {
fun tryDisembarkUnitToAttackPosition(unit: MapUnit): Boolean {
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)
// 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
.minByOrNull { it.tileToAttack.getCity()!!.health }
if (unit.type.isMelee() && capturableCity != null)
if (unit.baseUnit.isMelee() && capturableCity != null)
enemyTileToAttack = capturableCity // enter it quickly, top priority!
else if (nonCityTilesToAttack.isNotEmpty()) // second priority, units

View File

@ -26,9 +26,9 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
.filter { it.isAnyWonder() }
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
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 allTechsAreResearched = civInfo.tech.getNumberOfTechsResearched() >= civInfo.gameInfo.ruleSet.technologies.size

View File

@ -340,7 +340,7 @@ object NextTurnAutomation {
if (civInfo.isAtWarWith(otherCiv)) continue
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) {
diplomacy.influence -= 10f
if (!diplomacy.hasFlag(DiplomacyFlags.BorderConflict)) {
@ -356,7 +356,7 @@ object NextTurnAutomation {
if (civInfo.cities.isEmpty() || civInfo.diplomacy.isEmpty()) 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
//evaluate war
@ -505,8 +505,8 @@ object NextTurnAutomation {
}
when {
unit.type.isRanged() -> rangedUnits.add(unit)
unit.type.isMelee() -> meleeUnits.add(unit)
unit.baseUnit.isRanged() -> rangedUnits.add(unit)
unit.baseUnit.isMelee() -> meleeUnits.add(unit)
unit.hasUnique("Bonus for units in 2 tile radius 15%")
-> generals.add(unit) //generals move after military units
else -> civilianUnits.add(unit)

View File

@ -86,7 +86,7 @@ object UnitAutomation {
if (unit.civInfo.isBarbarian())
throw IllegalStateException("Barbarians is not allowed here.")
if (unit.type.isCivilian()) {
if (unit.isCivilian()) {
if (unit.hasUnique(Constants.settlerUnique))
return SpecificUnitAutomation.automateSettlerActions(unit)
@ -171,7 +171,7 @@ object UnitAutomation {
}
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
val unitDistanceToTiles = unit.movement.getDistanceToTiles()
@ -222,7 +222,7 @@ object UnitAutomation {
}
fun tryPillageImprovement(unit: MapUnit): Boolean {
if (unit.type.isCivilian()) return false
if (unit.isCivilian()) return false
val unitDistanceToTiles = unit.movement.getDistanceToTiles()
val tilesThatCanWalkToAndThenPillage = unitDistanceToTiles
.filter { it.value.totalDistance < unit.currentMovement }.keys
@ -263,7 +263,7 @@ object UnitAutomation {
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 }
val closestEnemy = closeEnemies.minByOrNull { it.tileToAttack.aerialDistanceTo(unit.getTile()) }
@ -317,7 +317,7 @@ object UnitAutomation {
.flatMap { it.cities }.asSequence()
.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 }
val closestReachableEnemyCity = enemyCities
@ -396,7 +396,7 @@ object UnitAutomation {
if (siegeUnits.any()) targets = siegeUnits
else {
val rangedUnits = targets
.filter { it.getUnitType().isRanged() }
.filter { it.isRanged() }
if (rangedUnits.any()) targets = rangedUnits
}
return targets.minByOrNull { it: ICombatant -> it.getHealth() }
@ -413,7 +413,7 @@ object UnitAutomation {
} //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 }
val closestReachableCapturedCity = capturedCities
@ -429,7 +429,7 @@ object UnitAutomation {
}
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 centerTile = it.getCenterTile()
centerTile.militaryUnit == null

View File

@ -15,7 +15,7 @@ class WorkerAutomation(val unit: MapUnit) {
val enemyUnitsInWalkingDistance = unit.movement.getDistanceToTiles().keys
.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 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
// 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)
if (defender.isDefeated() && defender is MapUnitCombatant && !defender.getUnitType().isCivilian()) {
if (defender.isDefeated() && defender is MapUnitCombatant && !defender.unit.isCivilian()) {
tryEarnFromKilling(attacker, defender)
tryCaptureUnit(attacker, defender)
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)
tryCaptureUnit(defender, attacker)
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://civilization.fandom.com/wiki/Module:Data/Civ5/GK/Defines
if (!defender.isDefeated()) 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
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 damageToDefender = defender.getHealth()
if (defender.getUnitType().isCivilian() && attacker.isMelee()) {
captureCivilianUnit(attacker, defender as MapUnitCombatant)
if (defender is MapUnitCombatant && defender.unit.isCivilian() && attacker.isMelee()) {
captureCivilianUnit(attacker, defender)
} else if (attacker.isRanged()) {
defender.takeDamage(potentialDamageToDefender) // straight up
} else {
@ -302,7 +301,7 @@ object Battle {
private fun postBattleAddXp(attacker: ICombatant, defender: ICombatant) {
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(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,
// for the movement to the enemy's tile!
// 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)
} else unit.currentMovement = 0f
if (unit.isFortified() || unit.isSleeping())
@ -554,7 +553,7 @@ object Battle {
// 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
val defender = MapUnitCombatant(unit)
if (defender.unit.baseUnit.unitType.isCivilian()) {
if (defender.unit.isCivilian()) {
unit.destroy() // destroy the unit
} else {
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.models.Counter
import com.unciv.models.ruleset.unit.UnitType
import java.util.*
import kotlin.collections.set
import kotlin.math.max
@ -269,11 +268,10 @@ object BattleDamage {
}
private fun getHealthDependantDamageRatio(combatant: ICombatant): Float {
return if (combatant.getUnitType() == UnitType.City
return if (combatant !is MapUnitCombatant // is city
|| combatant.getCivInfo()
.hasUnique("Units fight as though they were at full strength even when damaged")
&& !combatant.getUnitType().isAirUnit()
&& !combatant.getUnitType().isMissile()
&& !combatant.unit.baseUnit.movesLikeAirUnits()
)
1f
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
): Int {
if (attacker.isRanged()) return 0
if (defender.getUnitType().isCivilian()) return 0
if (defender.isCivilian()) return 0
val ratio =
getAttackingStrength(attacker, tileToAttackFrom, defender) / getDefendingStrength(
attacker,

View File

@ -21,10 +21,10 @@ interface ICombatant{
fun matchesCategory(category:String): Boolean
fun getAttackSound(): UncivSound
fun isMelee(): Boolean {
return getUnitType().isMelee()
}
fun isMelee(): Boolean = !isRanged()
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 {
return if (unit.isEmbarked() && !unit.type.isCivilian()) 5 * getCivInfo().getEraNumber()
return if (unit.isEmbarked() && !isCivilian()) 5 * getCivInfo().getEraNumber()
else unit.baseUnit().strength
}

View File

@ -543,9 +543,9 @@ class CityInfo {
fun canPurchase(construction: IConstruction): Boolean {
if (construction is BaseUnit) {
val tile = getCenterTile()
if (construction.unitType.isCivilian())
if (construction.isCivilian())
return tile.civilianUnit == null
if (construction.unitType.isAirUnit() || construction.unitType.isMissile())
if (construction.movesLikeAirUnits())
return tile.airUnits.filter { !it.isTransported }.size < 6
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
if (!isBarbarian() && gold < -100 && nextTurnStats.gold.toInt() < 0) {
for (i in 1 until (gold / -100)) {
var civMilitaryUnits = getCivUnits().filter { !it.type.isCivilian() }
var civMilitaryUnits = getCivUnits().filter { it.baseUnit.isMilitary() }
if (civMilitaryUnits.any()) {
val unitToDisband = civMilitaryUnits.first()
unitToDisband.disband()
@ -765,7 +765,7 @@ class CivilizationInfo {
val cities = NextTurnAutomation.getClosestCities(this, otherCiv)
val city = cities.city1
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()
// placing the unit may fail - in that case stay quiet
val placedUnit = placeUnitNearTile(city.location, militaryUnit.name) ?: return

View File

@ -223,7 +223,7 @@ class MapUnit {
if (hasUnique("Limited Visibility")) visibilityRange -= 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
//
@ -251,17 +251,19 @@ class MapUnit {
}
fun isFortified() = action?.startsWith("Fortify") == true
fun isFortifyingUntilHealed() = isFortified() && action?.endsWith("until healed") == true
fun isSleeping() = action?.startsWith("Sleep") == true
fun isSleepingUntilHealed() = isSleeping() && action?.endsWith("until healed") == true
fun isMoving() = action?.startsWith("moveTo") == true
fun isAutomaticallyBuildingImprovements() = action != null && action == Constants.unitActionAutomation
fun isCivilian() = baseUnit.isCivilian()
fun getFortificationTurns(): Int {
if (!isFortified()) return 0
return action!!.split(" ")[1].toInt()
@ -301,7 +303,7 @@ class MapUnit {
}
fun getRange(): Int {
if (type.isMelee()) return 1
if (baseUnit.isMelee()) return 1
var range = baseUnit().range
// Deprecated since 3.15.6
if (hasUnique("+1 Range")) range++
@ -370,9 +372,8 @@ class MapUnit {
fun canFortify(): Boolean {
if (type.isWaterUnit()) return false
if (type.isCivilian()) return false
if (type.isAirUnit()) return false
if (type.isMissile()) return false
if (isCivilian()) return false
if (baseUnit.movesLikeAirUnits()) return false
if (isEmbarked()) return false
if (hasUnique("No defensive terrain bonus")) 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() }
}
fun canGarrison() = type.isMilitary() && type.isLandUnit()
fun canGarrison() = baseUnit.isMilitary() && type.isLandUnit()
fun isGreatPerson() = baseUnit.isGreatPerson()
@ -634,7 +635,7 @@ class MapUnit {
// Wake sleeping units if there's an enemy in vision range:
// 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 {
it.militaryUnit != null && it.militaryUnit!!.civInfo.isAtWarWith(civInfo)
}
@ -679,7 +680,7 @@ class MapUnit {
if (tile.improvement == Constants.barbarianEncampment && !civInfo.isBarbarian())
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
.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"))
@ -693,13 +694,12 @@ class MapUnit {
when {
!movement.canMoveTo(tile) ->
throw Exception("I can't go there!")
type.isAirUnit() || type.isMissile() -> tile.airUnits.add(this)
type.isCivilian() -> tile.civilianUnit = this
baseUnit.movesLikeAirUnits() -> tile.airUnits.add(this)
isCivilian() -> tile.civilianUnit = this
else -> tile.militaryUnit = this
}
// this check is here in order to not load the fresh built unit into carrier right after the build
isTransported = !tile.isCityCenter() &&
(type.isAirUnit() || type.isMissile()) // not moving civilians
isTransported = !tile.isCityCenter() && baseUnit.movesLikeAirUnits() // not moving civilians
moveThroughTile(tile)
}
@ -816,7 +816,7 @@ class MapUnit {
}
}
if (!type.isCivilian())
if (!isCivilian())
actions.add {
promotions.XP += 10
civInfo.addNotification(
@ -881,7 +881,7 @@ class MapUnit {
fun isTransportTypeOf(mapUnit: MapUnit): Boolean {
// 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]) }
}
@ -941,13 +941,12 @@ class MapUnit {
private fun getCitadelDamage() {
// Check for Citadel damage - note: 'Damage does not stack with other Citadels'
val citadelTile = currentTile.neighbors
.filter {
.firstOrNull {
it.getOwner() != null && civInfo.isAtWarWith(it.getOwner()!!) &&
with(it.getTileImprovement()) {
this != null && this.hasUnique("Deal 30 damage to adjacent enemy units")
}
}
.firstOrNull()
if (citadelTile != null) {
health -= 30

View File

@ -179,7 +179,7 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
if (currentTile == finalDestination) return currentTile
// 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()
@ -217,13 +217,13 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
/** 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 */
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 getShortestPath(destination).any()
}
fun canReachInCurrentTurn(destination: TileInfo): Boolean {
if (unit.type.isAirUnit() || unit.type.isMissile())
if (unit.baseUnit.movesLikeAirUnits())
return unit.currentTile.aerialDistanceTo(destination) <= unit.getRange()*2
if (unit.action == Constants.unitActionParadrop)
return getDistance(unit.currentTile.position, destination.position) <= unit.paradropRange && canParadropOn(destination)
@ -232,7 +232,7 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
fun getReachableTilesInCurrentTurn(): Sequence<TileInfo> {
return when {
unit.type.isAirUnit() || unit.type.isMissile() ->
unit.baseUnit.movesLikeAirUnits() ->
unit.getTile().getTilesInDistanceRange(IntRange(1, unit.getRange() * 2))
unit.action == Constants.unitActionParadrop ->
unit.getTile().getTilesInDistance(unit.paradropRange)
@ -259,13 +259,13 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
*/
private fun canUnitSwapToReachableTile(reachableTile: TileInfo): Boolean {
// 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
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
// also reach our tile in its current turn.
val otherUnit = (
if (unit.type.isCivilian())
if (unit.isCivilian())
reachableTile.civilianUnit
else
reachableTile.militaryUnit
@ -322,7 +322,7 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
fun moveToTile(destination: TileInfo) {
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.removeFromTile()
unit.isTransported = false // it has left the carrier by own means
@ -398,7 +398,7 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
*/
fun swapMoveToTile(destination: TileInfo) {
val otherUnit = (
if (unit.type.isCivilian())
if (unit.isCivilian())
destination.civilianUnit
else
destination.militaryUnit
@ -421,7 +421,7 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
* DOES NOT designate whether we can reach that tile in the current turn
*/
fun canMoveTo(tile: TileInfo): Boolean {
if (unit.type.isAirUnit() || unit.type.isMissile())
if (unit.baseUnit.movesLikeAirUnits())
return canAirUnitMoveTo(tile, unit)
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 (unit.type.isCivilian())
if (unit.isCivilian())
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)
}

View File

@ -277,9 +277,9 @@ class Ruleset {
for (unit in units.values) {
if (unit.upgradesTo == unit.name)
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!"
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!"
}

View File

@ -7,8 +7,8 @@ import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.map.MapUnit
import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.Unique
import com.unciv.models.translations.tr
import com.unciv.models.stats.INamed
import com.unciv.models.translations.tr
import com.unciv.ui.civilopedia.CivilopediaText
import com.unciv.ui.civilopedia.FormattedLine
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"))
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 }
@ -339,24 +339,20 @@ class BaseUnit : INamed, IConstruction, CivilopediaText() {
name -> true
"All" -> true
"Melee" -> unitType.isMelee()
"Ranged" -> unitType.isRanged()
"Melee" -> isMelee()
"Ranged" -> isRanged()
"Land", "land units" -> unitType.isLandUnit()
"Civilian" -> unitType.isCivilian()
"Military", "military units" -> unitType.isMilitary()
"Civilian" -> isCivilian()
"Military", "military units" -> isMilitary()
"Water", "water units" -> unitType.isWaterUnit()
"Air", "air units" -> unitType.isAirUnit()
"non-air" -> !unitType.isAirUnit() && !unitType.isMissile()
"Missile" -> unitType.isMissile()
"non-air" -> !movesLikeAirUnits()
"Submarine", "submarine units" -> unitType == UnitType.WaterSubmarine
"Nuclear Weapon" -> isNuclearWeapon()
// Deprecated as of 3.15.2
"military water" -> unitType.isMilitary() && unitType.isWaterUnit()
else -> {
if (uniques.contains(filter)) return true
return false
}
"military water" -> isMilitary() && unitType.isWaterUnit()
else -> return uniques.contains(filter)
}
}
@ -365,7 +361,7 @@ class BaseUnit : INamed, IConstruction, CivilopediaText() {
// "Nuclear Weapon" unique deprecated since 3.15.4
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> {
val resourceRequirements = HashMap<String, Int>()
@ -375,4 +371,9 @@ class BaseUnit : INamed, IConstruction, CivilopediaText() {
resourceRequirements[unique.params[1]] = unique.params[0].toInt()
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,
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() =
this == Civilian
|| this == Melee
@ -47,10 +30,6 @@ enum class UnitType{
|| this == Ranged
|| this == Siege
fun isCivilian() = this == Civilian || this == WaterCivilian
fun isMilitary() = this != Civilian && this != WaterCivilian
fun isWaterUnit() =
this == WaterSubmarine
|| this == WaterRanged
@ -62,7 +41,5 @@ enum class UnitType{
this == Bomber
|| this == Fighter
|| this == AtomicBomber
fun isMissile() =
this == Missile
}

View File

@ -214,7 +214,7 @@ class MapEditorOptionsTable(val mapEditorScreen: MapEditorScreen): Table(CameraS
it.airUnits.add(unit)
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
}
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
fun selectUnit(unit: MapUnit) {
if(unit.type.isAirUnit() || unit.type.isMissile()) return // doesn't appear on map so nothing to select
val unitImage = if (unit.type.isCivilian()) icons.civilianUnitIcon
if(unit.baseUnit.movesLikeAirUnits()) return // doesn't appear on map so nothing to select
val unitImage = if (unit.isCivilian()) icons.civilianUnitIcon
else icons.militaryUnitIcon
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
val fadeout = if (unit.type.isCivilian()) 1f
val fadeout = if (unit.isCivilian()) 1f
else 0.5f
for (tile in allWorldTileGroups) {
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)
}
val attackableTiles: List<AttackableTile> = if (unit.type.isCivilian()) listOf()
val attackableTiles: List<AttackableTile> = if (unit.isCivilian()) listOf()
else {
BattleHelper.getAttackableEnemies(unit, unit.movement.getDistanceToTiles())
.filter {

View File

@ -483,7 +483,7 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Cam
displayTutorial(Tutorial.Workers) {
gameInfo.getCurrentPlayerCivilization().getCivUnits().any {
(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? {
val unitTable = worldScreen.bottomUnitTable
return if (unitTable.selectedUnit != null
&& !unitTable.selectedUnit!!.type.isCivilian()
&& !unitTable.selectedUnit!!.isCivilian()
&& !unitTable.selectedUnit!!.hasUnique("Cannot attack"))
MapUnitCombatant(unitTable.selectedUnit!!)
else if (unitTable.selectedCity != null)
@ -159,13 +159,16 @@ class BattleTable(val worldScreen: WorldScreen): Table() {
else if (damageToDefender>defender.getHealth()) damageToDefender=defender.getHealth()
if(attacker.isMelee() && (defender.getUnitType().isCivilian()
|| defender.getUnitType()==UnitType.City && defender.isDefeated())) {
if(attacker.isMelee() && (defender.isCivilian()
|| defender is CityCombatant && defender.isDefeated())) {
add("")
add(if(defender.getUnitType().isCivilian()
&& (defender as MapUnitCombatant).unit.hasUnique("Uncapturable")) ""
else if(defender.getUnitType().isCivilian()) "Captured!".tr()
else "Occupied!".tr())
add(
if (defender.isCivilian()
&& (defender as MapUnitCombatant).unit.hasUnique("Uncapturable")
) ""
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) {
// 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.
// 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
@ -208,7 +208,7 @@ object UnitActions {
}
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
actionList += UnitAction(UnitActionType.Promote,
uncivSound = UncivSound.Promote,
@ -264,7 +264,7 @@ object UnitActions {
fun getPillageAction(unit: MapUnit): UnitAction? {
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,
action = {
@ -278,7 +278,7 @@ object UnitActions {
if (tile.resource != null) tile.getOwner()?.updateDetailedCivResources() // this might take away a resource
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)
unit.healBy(25)

View File

@ -137,7 +137,7 @@ class UnitTable(val worldScreen: WorldScreen) : Table(){
unitDescriptionTable.add(ImageGetter.getStatIcon("Movement")).size(20f)
unitDescriptionTable.add(unit.getMovementString()).padRight(10f)
if (!unit.type.isCivilian()) {
if (!unit.isCivilian()) {
unitDescriptionTable.add(ImageGetter.getStatIcon("Strength")).size(20f)
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)
}
if (unit.type.isRanged()) {
if (unit.baseUnit.isRanged()) {
unitDescriptionTable.add(ImageGetter.getStatIcon("Range")).size(20f)
unitDescriptionTable.add(unit.getRange().toString()).padRight(10f)
}
if (unit.baseUnit.interceptRange > 0) {
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)
}
if (!unit.type.isCivilian()) {
if (!unit.isCivilian()) {
unitDescriptionTable.add("XP")
unitDescriptionTable.add(unit.promotions.XP.toString() + "/" + unit.promotions.xpForNextPromotion())
}