Submarines are now visible to adjacent units, and once turned visible, can be attacked by all enemy units (#5001)

* Submarines are now visible to adjacent units, and once turned visible, can attack be all enemy units

* Deprecation & translation of unique saving
This commit is contained in:
Xander Lenstra 2021-08-27 16:00:12 +02:00 committed by GitHub
parent 7dd7e0b278
commit 85e4a68ea1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 55 additions and 39 deletions

View File

@ -50,7 +50,7 @@
{ {
"name": "Submarine", "name": "Submarine",
"movementType": "Water", "movementType": "Water",
"uniques": ["Can enter ice tiles", "Invisible to others"] "uniques": ["Can enter ice tiles", "Invisible to non-adjacent units", "Can see invisible [Submarine] units"]
}, },
{ {
"name": "Aircraft Carrier", "name": "Aircraft Carrier",

View File

@ -1034,7 +1034,7 @@
"cost": 325, "cost": 325,
"requiredTech": "Refrigeration", "requiredTech": "Refrigeration",
"upgradesTo": "Nuclear Submarine", "upgradesTo": "Nuclear Submarine",
"uniques": ["+[75]% Strength when attacking", "Can only attack [Water] tiles", "Can attack submarines"], "uniques": ["+[75]% Strength when attacking", "Can only attack [Water] tiles"],
"attackSound": "torpedo" "attackSound": "torpedo"
}, },
{ {
@ -1173,7 +1173,7 @@
"interceptRange": 2, "interceptRange": 2,
"cost": 375, "cost": 375,
"requiredTech": "Combustion", "requiredTech": "Combustion",
"uniques": ["Can attack submarines", "[40]% chance to intercept air attacks", "uniques": ["Can see invisible [Submarine] units", "[40]% chance to intercept air attacks",
"May withdraw before melee ([80]%)", "+[100]% Strength vs [submarine units]"], "May withdraw before melee ([80]%)", "+[100]% Strength vs [submarine units]"],
"attackSound": "shipguns" "attackSound": "shipguns"
}, },
@ -1391,8 +1391,7 @@
"rangedStrength": 85, "rangedStrength": 85,
"cost": 425, "cost": 425,
"requiredTech": "Telecommunications", "requiredTech": "Telecommunications",
"uniques": ["+[75]% Strength when attacking", "Can only attack [Water] tiles", "Can attack submarines", "uniques": ["+[75]% Strength when attacking", "Can only attack [Water] tiles", "[+1] Visibility Range", "Can carry [2] [Missile] units"],
"[+1] Visibility Range", "Can carry [2] [Missile] units"],
"attackSound": "torpedo" "attackSound": "torpedo"
}, },
{ {
@ -1414,7 +1413,7 @@
"interceptRange": 2, "interceptRange": 2,
"cost": 425, "cost": 425,
"requiredTech": "Robotics", "requiredTech": "Robotics",
"uniques": ["[100]% chance to intercept air attacks", "Can attack submarines", "uniques": ["[100]% chance to intercept air attacks", "Can see invisible [Submarine] units",
"Ranged attacks may be performed over obstacles", "Can carry [3] [Missile] units", "Ranged attacks may be performed over obstacles", "Can carry [3] [Missile] units",
"+[100]% Strength vs [submarine units]"], "+[100]% Strength vs [submarine units]"],
"attackSound": "shipguns" "attackSound": "shipguns"

View File

@ -1170,3 +1170,4 @@ Only available after [] turns =
This Unit upgrades for free = This Unit upgrades for free =
[stats] when a city adopts this religion for the first time = [stats] when a city adopts this religion for the first time =
Never destroyed when the city is captured = Never destroyed when the city is captured =
Invisible to others =

View File

@ -169,7 +169,7 @@ class GameInfo {
it.militaryUnit != null && it.militaryUnit!!.civInfo != thisPlayer it.militaryUnit != null && it.militaryUnit!!.civInfo != thisPlayer
&& thisPlayer.isAtWarWith(it.militaryUnit!!.civInfo) && thisPlayer.isAtWarWith(it.militaryUnit!!.civInfo)
&& (it.getOwner() == thisPlayer || it.neighbors.any { neighbor -> neighbor.getOwner() == thisPlayer } && (it.getOwner() == thisPlayer || it.neighbors.any { neighbor -> neighbor.getOwner() == thisPlayer }
&& (!it.militaryUnit!!.isInvisible() || viewableInvisibleTiles.contains(it.position))) && (!it.militaryUnit!!.isInvisible(thisPlayer) || viewableInvisibleTiles.contains(it.position)))
} }
// enemy units ON our territory // enemy units ON our territory

View File

@ -95,15 +95,11 @@ object BattleHelper {
) )
return false return false
//only submarine and destroyer can attack submarine // Only units with the right unique can view submarines (or other invisible units) from more then one tile away.
//garrisoned submarine can be attacked by anyone, or the city will be in invincible // Garrisoned invisible units can be attacked by anyone, as else the city will be in invincible.
if (tileCombatant.isInvisible() && !tile.isCityCenter()) { if (tileCombatant.isInvisible(combatant.getCivInfo()) && !tile.isCityCenter()) {
if (combatant is MapUnitCombatant return combatant is MapUnitCombatant
&& combatant.unit.hasUnique("Can attack submarines") && combatant.getCivInfo().viewableInvisibleUnitsTiles.map { it.position }.contains(tile.position)
&& combatant.getCivInfo().viewableInvisibleUnitsTiles.map { it.position }.contains(tile.position)) {
return true
}
return false
} }
return true return true
} }

View File

@ -18,7 +18,7 @@ class CityCombatant(val city: CityInfo) : ICombatant {
override fun getTile(): TileInfo = city.getCenterTile() override fun getTile(): TileInfo = city.getCenterTile()
override fun getName(): String = city.name override fun getName(): String = city.name
override fun isDefeated(): Boolean = city.health == 1 override fun isDefeated(): Boolean = city.health == 1
override fun isInvisible(): Boolean = false override fun isInvisible(to: CivilizationInfo): Boolean = false
override fun canAttack(): Boolean = city.canBombard() override fun canAttack(): Boolean = city.canBombard()
override fun matchesCategory(category: String) = category == "City" || category == "All" override fun matchesCategory(category: String) = category == "City" || category == "All"
override fun getAttackSound() = UncivSound.Bombard override fun getAttackSound() = UncivSound.Bombard

View File

@ -16,7 +16,7 @@ interface ICombatant{
fun isDefeated():Boolean fun isDefeated():Boolean
fun getCivInfo(): CivilizationInfo fun getCivInfo(): CivilizationInfo
fun getTile(): TileInfo fun getTile(): TileInfo
fun isInvisible(): Boolean fun isInvisible(to: CivilizationInfo): Boolean
fun canAttack(): Boolean fun canAttack(): Boolean
fun matchesCategory(category:String): Boolean fun matchesCategory(category:String): Boolean
fun getAttackSound(): UncivSound fun getAttackSound(): UncivSound

View File

@ -13,7 +13,7 @@ class MapUnitCombatant(val unit: MapUnit) : ICombatant {
override fun getTile(): TileInfo = unit.getTile() override fun getTile(): TileInfo = unit.getTile()
override fun getName(): String = unit.name override fun getName(): String = unit.name
override fun isDefeated(): Boolean = unit.health <= 0 override fun isDefeated(): Boolean = unit.health <= 0
override fun isInvisible(): Boolean = unit.isInvisible() override fun isInvisible(to: CivilizationInfo): Boolean = unit.isInvisible(to)
override fun canAttack(): Boolean = unit.canAttack() override fun canAttack(): Boolean = unit.canAttack()
override fun matchesCategory(category:String) = unit.matchesFilter(category) override fun matchesCategory(category:String) = unit.matchesFilter(category)
override fun getAttackSound() = unit.baseUnit.attackSound.let { override fun getAttackSound() = unit.baseUnit.attackSound.let {
@ -36,7 +36,7 @@ class MapUnitCombatant(val unit: MapUnit) : ICombatant {
} }
override fun getUnitType(): UnitType { override fun getUnitType(): UnitType {
return unit.type!! return unit.type
} }
override fun toString(): String { override fun toString(): String {

View File

@ -12,12 +12,28 @@ class CivInfoTransientUpdater(val civInfo: CivilizationInfo) {
val newViewableInvisibleTiles = HashSet<TileInfo>() val newViewableInvisibleTiles = HashSet<TileInfo>()
newViewableInvisibleTiles.addAll(civInfo.getCivUnits() newViewableInvisibleTiles.addAll(civInfo.getCivUnits()
.filter { it.hasUnique("Can attack submarines") } // "Can attack submarines" unique deprecated since 3.16.9
.flatMap { it.viewableTiles.asSequence() }) .filter { attacker -> attacker.hasUnique("Can see invisible [] units") || attacker.hasUnique("Can attack submarines") }
.flatMap { attacker ->
attacker.viewableTiles
.asSequence()
.filter { tile ->
( tile.militaryUnit != null
&& attacker.getMatchingUniques("Can see invisible [] units")
.any { unique -> tile.militaryUnit!!.matchesFilter(unique.params[0]) }
) || (
tile.militaryUnit != null
// "Can attack submarines" unique deprecated since 3.16.9
&& attacker.hasUnique("Can attack submarines")
&& tile.militaryUnit!!.matchesFilter("Submarine")
)
}
}
)
civInfo.viewableInvisibleUnitsTiles = newViewableInvisibleTiles civInfo.viewableInvisibleUnitsTiles = newViewableInvisibleTiles
// updating the viewable tiles also affects the explored tiles, obvs // updating the viewable tiles also affects the explored tiles, obviously.
// So why don't we play switcharoo with the explored tiles as well? // So why don't we play switcharoo with the explored tiles as well?
// Well, because it gets REALLY LARGE so it's a lot of memory space, // Well, because it gets REALLY LARGE so it's a lot of memory space,
// and we never actually iterate on the explored tiles (only check contains()), // and we never actually iterate on the explored tiles (only check contains()),

View File

@ -354,9 +354,13 @@ class MapUnit {
return currentTile.isWater return currentTile.isWater
} }
fun isInvisible(): Boolean { fun isInvisible(to: CivilizationInfo): Boolean {
if (hasUnique("Invisible to others")) if (hasUnique("Invisible to others"))
return true return true
if (hasUnique("Invisible to non-adjacent units"))
return getTile().getTilesInDistance(1).none {
it.getOwner() == to || it.getUnits().any { unit -> unit.owner == to.civName }
}
return false return false
} }

View File

@ -637,7 +637,7 @@ open class TileInfo {
val unitsInTile = getUnits() val unitsInTile = getUnits()
if (unitsInTile.none()) return false if (unitsInTile.none()) return false
if (unitsInTile.first().civInfo != viewingCiv && if (unitsInTile.first().civInfo != viewingCiv &&
unitsInTile.firstOrNull { it.isInvisible() } != null) { unitsInTile.firstOrNull { it.isInvisible(viewingCiv) } != null) {
return true return true
} }
return false return false

View File

@ -74,14 +74,14 @@ class BattleTable(val worldScreen: WorldScreen): Table() {
val attackerCiv = worldScreen.viewingCiv val attackerCiv = worldScreen.viewingCiv
val defender: ICombatant? = Battle.getMapCombatantOfTile(selectedTile) val defender: ICombatant? = Battle.getMapCombatantOfTile(selectedTile)
if(defender==null || if (defender == null || (!includeFriendly && defender.getCivInfo() == attackerCiv))
!includeFriendly && defender.getCivInfo()==attackerCiv )
return null // no enemy combatant in tile return null // no enemy combatant in tile
val canSeeDefender = if(UncivGame.Current.viewEntireMapForDebug) true val canSeeDefender =
if (UncivGame.Current.viewEntireMapForDebug) true
else { else {
when { when {
defender.isInvisible() -> attackerCiv.viewableInvisibleUnitsTiles.contains(selectedTile) defender.isInvisible(attackerCiv) -> attackerCiv.viewableInvisibleUnitsTiles.contains(selectedTile)
defender.isCity() -> attackerCiv.exploredTiles.contains(selectedTile.position) defender.isCity() -> attackerCiv.exploredTiles.contains(selectedTile.position)
else -> attackerCiv.viewableTiles.contains(selectedTile) else -> attackerCiv.viewableTiles.contains(selectedTile)
} }