Revamped nukes again to closer match the original (#5892)

This commit is contained in:
Xander Lenstra
2022-01-02 13:04:43 +01:00
committed by GitHub
parent d8a08a156d
commit cdbdda366b
2 changed files with 74 additions and 135 deletions

View File

@ -652,11 +652,7 @@ object Battle {
for (tile in hitTiles) {
// Handle complicated effects
when (strength) {
1 -> nukeStrength1Effect(attacker, tile)
2 -> nukeStrength2Effect(attacker, tile)
else -> nukeStrength1Effect(attacker, tile)
}
doNukeExplosion(attacker, tile, strength)
}
// Instead of postBattleAction() just destroy the unit, all other functions are not relevant
@ -672,66 +668,48 @@ object Battle {
attacker.unit.attacksThisTurn += 1
}
}
// todo: reduce extreme code duplication, parameterize probabilities where an unique already used
private fun nukeStrength1Effect(attacker: MapUnitCombatant, tile: TileInfo) {
private fun doNukeExplosion(attacker: MapUnitCombatant, tile: TileInfo, nukeStrength: Int) {
// https://forums.civfanatics.com/resources/unit-guide-modern-future-units-g-k.25628/
// https://www.carlsguides.com/strategy/civilization5/units/aircraft-nukes.ph
// Testing done by Ravignir
// original source code: GenerateNuclearExplosionDamage(), ApplyNuclearExplosionDamage()
var damageModifierFromMissingResource = 1f
val civResources = attacker.getCivInfo().getCivResourcesByName()
for (resource in attacker.unit.baseUnit.getResourceRequirements().keys) {
if (civResources[resource]!! < 0 && !attacker.getCivInfo().isBarbarian())
damageModifierFromMissingResource *= 0.5f // I could not find a source for this number, but this felt about right
}
// Decrease health & population of a hit city
// Damage city and reduce its population
val city = tile.getCity()
if (city != null && tile.position == city.location) {
var populationLoss = city.population.population * (0.3 + Random().nextFloat() * 0.4)
var populationLossReduced = false
// Deprecated since 3.16.11
for (unique in city.getLocalMatchingUniques("Population loss from nuclear attacks -[]%")) {
populationLoss *= 1 - unique.params[0].toFloat() / 100f
populationLossReduced = true
}
//
for (unique in city.getMatchingUniques("Population loss from nuclear attacks []% []")) {
if (!city.matchesFilter(unique.params[1])) continue
populationLoss *= unique.params[0].toPercent()
populationLossReduced = true
}
if (city.population.population < 5 && !populationLossReduced) {
city.population.setPopulation(1) // For cities that cannot be destroyed, such as original capitals
city.destroyCity()
} else {
city.population.addPopulation(-populationLoss.toInt())
if (city.population.population < 1) city.population.setPopulation(1)
city.population.unassignExtraPopulation()
city.health -= ((0.5 + 0.25 * Random().nextFloat()) * city.health * damageModifierFromMissingResource).toInt()
if (city.health < 1) city.health = 1
}
doNukeExplosionDamageToCity(city, nukeStrength, damageModifierFromMissingResource)
postBattleNotifications(attacker, CityCombatant(city), city.getCenterTile())
destroyIfDefeated(city.civInfo, attacker.getCivInfo())
}
// 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.isCivilian()) {
unit.destroy() // destroy the unit
} else {
if (defender.unit.isCivilian() || nukeStrength >= 2) {
unit.destroy()
} else if (nukeStrength == 1) {
defender.takeDamage(((40 + Random().nextInt(60)) * damageModifierFromMissingResource).toInt())
} else if (nukeStrength == 0) {
defender.takeDamage(((20 + Random().nextInt(30)) * damageModifierFromMissingResource).toInt())
}
postBattleNotifications(attacker, defender, defender.getTile())
destroyIfDefeated(defender.getCivInfo(), attacker.getCivInfo())
}
// Remove improvements, add fallout
// Pillage improvements, remove roads, add fallout
if (tile.improvement != null && !tile.getTileImprovement()!!.hasUnique(UniqueType.Indestructible)) {
tile.turnsToImprovement = 2
tile.improvementInProgress = tile.improvement
tile.improvement = null
}
tile.improvementInProgress = null
tile.turnsToImprovement = 0
tile.roadStatus = RoadStatus.None
if (tile.isLand && !tile.isImpassible() && !tile.terrainFeatures.contains("Fallout")) {
if (tile.terrainFeatures.any { attacker.getCivInfo().gameInfo.ruleSet.terrains[it]!!.hasUnique(UniqueType.ResistsNukes) }) {
@ -745,68 +723,33 @@ object Battle {
}
}
}
private fun nukeStrength2Effect(attacker: MapUnitCombatant, tile: TileInfo) {
// https://forums.civfanatics.com/threads/unit-guide-modern-future-units-g-k.429987/#2
// https://www.carlsguides.com/strategy/civilization5/units/aircraft-nukes.php
// Testing done by Ravignir
var damageModifierFromMissingResource = 1f
val civResources = attacker.getCivInfo().getCivResourcesByName()
for (resource in attacker.unit.baseUnit.getResourceRequirements().keys) {
if (civResources[resource]!! < 0 && !attacker.getCivInfo().isBarbarian())
damageModifierFromMissingResource *= 0.5f // I could not find a source for this number, but this felt about right
private fun doNukeExplosionDamageToCity(targetedCity: CityInfo, nukeStrength: Int, damageModifierFromMissingResource: Float) {
if (nukeStrength > 1 && targetedCity.population.population < 5 && targetedCity.canBeDestroyed(true)) {
targetedCity.destroyCity()
return
}
val cityCombatant = CityCombatant(targetedCity)
cityCombatant.takeDamage((cityCombatant.getHealth() * 0.5f * damageModifierFromMissingResource).toInt())
// Damage and/or destroy cities
val city = tile.getCity()
if (city != null && city.location == tile.position) {
if (city.population.population < 5) {
city.population.setPopulation(1) // For cities that cannot be destroyed, such as original capitals
city.destroyCity()
} else {
var populationLoss = city.population.population * (0.6 + Random().nextFloat() * 0.2)
var populationLossReduced = false
for (unique in city.getMatchingUniques("Population loss from nuclear attacks []% []")) {
if (!city.matchesFilter(unique.params[1]))
populationLoss *= unique.params[0].toPercent()
populationLossReduced = true
}
city.population.addPopulation(-populationLoss.toInt())
if (city.population.population < 5 && populationLossReduced) city.population.setPopulation(5)
if (city.population.population < 1) city.population.setPopulation(1)
city.population.unassignExtraPopulation()
city.health -= (0.5 * city.getMaxHealth() * damageModifierFromMissingResource).toInt()
if (city.health < 1) city.health = 1
var populationLoss = targetedCity.population.population *
when (nukeStrength) {
0 -> 0f
1 -> (30 + Random().nextInt(40)) / 100f
2 -> (60 + Random().nextInt(20)) / 100f
else -> 1f
}
postBattleNotifications(attacker, CityCombatant(city), city.getCenterTile())
destroyIfDefeated(city.civInfo, attacker.getCivInfo())
}
// Destroy all hit units
for (defender in tile.getUnits().toList()) { // toList to avoid concurrent modification exceptions
defender.destroy()
postBattleNotifications(attacker, MapUnitCombatant(defender), defender.currentTile)
destroyIfDefeated(defender.civInfo, attacker.getCivInfo())
}
// Remove improvements
if (tile.improvement != null && !tile.getTileImprovement()!!.hasUnique(UniqueType.Indestructible)) {
tile.improvement = null
}
tile.improvementInProgress = null
tile.turnsToImprovement = 0
tile.roadStatus = RoadStatus.None
if (tile.isLand && !tile.isImpassible() && !tile.terrainFeatures.contains("Fallout")) {
if (tile.terrainFeatures.any { attacker.getCivInfo().gameInfo.ruleSet.terrains[it]!!.hasUnique(UniqueType.ResistsNukes) }) {
if (Random().nextFloat() < 0.25f) {
tile.terrainFeatures.removeAll { attacker.getCivInfo().gameInfo.ruleSet.terrains[it]!!.hasUnique(UniqueType.DestroyableByNukes) }
tile.terrainFeatures.add("Fallout")
}
} else if (Random().nextFloat() < 0.5f) {
tile.terrainFeatures.removeAll { attacker.getCivInfo().gameInfo.ruleSet.terrains[it]!!.hasUnique(UniqueType.DestroyableByNukes) }
tile.terrainFeatures.add("Fallout")
// Deprecated since 3.16.11
for (unique in targetedCity.getLocalMatchingUniques("Population loss from nuclear attacks -[]%")) {
populationLoss *= 1 - unique.params[0].toFloat() / 100f
}
//
for (unique in targetedCity.getMatchingUniques("Population loss from nuclear attacks []% []")) {
if (!targetedCity.matchesFilter(unique.params[1])) continue
populationLoss *= unique.params[0].toPercent()
}
targetedCity.population.addPopulation(-populationLoss.toInt())
if (targetedCity.population.population < 1) targetedCity.population.setPopulation(1)
}
private fun tryInterceptAirAttack(attacker: MapUnitCombatant, attackedTile:TileInfo, interceptingCiv:CivilizationInfo) {

View File

@ -23,17 +23,17 @@ Applicable to: Global, FollowerBelief, Improvement
#### [stats] [cityFilter]
Example: "[+1 Gold, +2 Production] [in all cities]"
Applicable to: Global
Applicable to: Global, FollowerBelief
#### [stats] from every specialist [cityFilter]
Example: "[+1 Gold, +2 Production] from every specialist [in all cities]"
Applicable to: Global
Applicable to: Global, FollowerBelief
#### [stats] per [amount] population [cityFilter]
Example: "[+1 Gold, +2 Production] per [20] population [in all cities]"
Applicable to: Global
Applicable to: Global, FollowerBelief
#### [stats] in cities with [amount] or more population
Example: "[+1 Gold, +2 Production] in cities with [20] or more population"
@ -45,11 +45,6 @@ Example: "[+1 Gold, +2 Production] in cities on [Grassland] tiles"
Applicable to: Global, FollowerBelief
#### [stats] per turn from cities before [tech/policy]
Example: "[+1 Gold, +2 Production] per turn from cities before [tech/policy]"
Applicable to: Global
#### [stats] whenever a Great Person is expended
Example: "[+1 Gold, +2 Production] whenever a Great Person is expended"
@ -58,7 +53,7 @@ Applicable to: Global
#### [stats] from [tileFilter] tiles [cityFilter]
Example: "[+1 Gold, +2 Production] from [Farm] tiles [in all cities]"
Applicable to: Global
Applicable to: Global, FollowerBelief
#### [stats] from [tileFilter] tiles without [tileFilter] [cityFilter]
Example: "[+1 Gold, +2 Production] from [Farm] tiles without [Farm] [in all cities]"
@ -73,7 +68,7 @@ Applicable to: Global, FollowerBelief
#### [amount]% [stat]
Example: "[20]% [Culture]"
Applicable to: Global
Applicable to: Global, FollowerBelief
#### [amount]% [stat] from City-States
Example: "[20]% [Culture] from City-States"
@ -83,7 +78,7 @@ Applicable to: Global
#### [amount]% [stat] [cityFilter]
Example: "[20]% [Culture] [in all cities]"
Applicable to: Global
Applicable to: Global, FollowerBelief
#### [amount]% Production when constructing [buildingFilter] wonders [cityFilter]
Example: "[20]% Production when constructing [buildingFilter] wonders [in all cities]"
@ -93,17 +88,17 @@ Applicable to: Global, FollowerBelief, Resource
#### [amount]% Production when constructing [buildingFilter] buildings [cityFilter]
Example: "[20]% Production when constructing [buildingFilter] buildings [in all cities]"
Applicable to: Global
Applicable to: Global, FollowerBelief
#### [amount]% Production when constructing [baseUnitFilter] units [cityFilter]
Example: "[20]% Production when constructing [Melee] units [in all cities]"
Applicable to: Global
Applicable to: Global, FollowerBelief
#### [amount]% unhappiness from population [cityFilter]
Example: "[20]% unhappiness from population [in all cities]"
Applicable to: Global
Applicable to: Global, FollowerBelief
#### Military Units gifted from City-States start with [amount] XP
Example: "Military Units gifted from City-States start with [20] XP"
@ -154,7 +149,7 @@ Applicable to: Global
#### [amount]% food consumption by specialists [cityFilter]
Example: "[20]% food consumption by specialists [in all cities]"
Applicable to: Global
Applicable to: Global, FollowerBelief
#### [amount]% of excess happiness converted to [stat]
Example: "[20]% of excess happiness converted to [Culture]"
@ -164,12 +159,12 @@ Applicable to: Global
#### May buy [baseUnitFilter] units for [amount] [stat] [cityFilter] at an increasing price ([amount])
Example: "May buy [Melee] units for [20] [Culture] [in all cities] at an increasing price ([20])"
Applicable to: Global
Applicable to: Global, FollowerBelief
#### May buy [buildingFilter] buildings for [amount] [stat] [cityFilter] at an increasing price ([amount])
Example: "May buy [buildingFilter] buildings for [20] [Culture] [in all cities] at an increasing price ([20])"
Applicable to: Global
Applicable to: Global, FollowerBelief
#### May buy [baseUnitFilter] units for [amount] [stat] [cityFilter]
Example: "May buy [Melee] units for [20] [Culture] [in all cities]"
@ -201,6 +196,19 @@ Example: "May buy [buildingFilter] buildings with [Culture] for [20] times their
Applicable to: Global, FollowerBelief
#### Receive a free Great Person at the end of every [comment] (every 394 years), after researching [tech]. Each bonus person can only be chosen once.
Example: "Receive a free Great Person at the end of every [comment] (every 394 years), after researching [tech]. Each bonus person can only be chosen once."
Applicable to: Global
#### Once The Long Count activates, the year on the world screen displays as the traditional Mayan Long Count.
Applicable to: Global
#### Retain [amount]% of the happiness from a luxury after the last copy has been traded away
Example: "Retain [20]% of the happiness from a luxury after the last copy has been traded away"
Applicable to: Global
#### Enables Research agreements
Applicable to: Global
@ -210,6 +218,11 @@ Applicable to: Global
#### Triggers a Cultural Victory upon completion
Applicable to: Global
#### Cannot build [baseUnitFilter] units
Example: "Cannot build [Melee] units"
Applicable to: Global
#### [amount]% Strength
Example: "[20]% Strength"
@ -348,24 +361,6 @@ Applicable to: Global
#### Will not be chosen for new games
Applicable to: Nation
#### Receive a free Great Person at the end of every [comment] (every 394 years), after researching [tech]. Each bonus person can only be chosen once.
Example: "Receive a free Great Person at the end of every [comment] (every 394 years), after researching [tech]. Each bonus person can only be chosen once."
Applicable to: Nation
#### Once The Long Count activates, the year on the world screen displays as the traditional Mayan Long Count.
Applicable to: Nation
#### Retain [amount]% of the happiness from a luxury after the last copy has been traded away
Example: "Retain [20]% of the happiness from a luxury after the last copy has been traded away"
Applicable to: Nation
#### Cannot build [baseUnitFilter] units
Example: "Cannot build [Melee] units"
Applicable to: Nation
## FollowerBelief uniques
#### [amount]% [stat] from every follower, up to [amount]%
Example: "[20]% [Culture] from every follower, up to [20]%"
@ -378,9 +373,6 @@ Example: "Earn [20]% of [Wounded] unit's [costOrStrength] as [Culture] when kill
Applicable to: FollowerBelief
## Building uniques
#### Remove extra unhappiness from annexed cities
Applicable to: Building
#### Consumes [amount] [resource]
Example: "Consumes [20] [Iron]"
@ -465,6 +457,9 @@ Applicable to: Building
#### Unsellable
Applicable to: Building
#### Remove extra unhappiness from annexed cities
Applicable to: Building
#### Spaceship part
Applicable to: Building, Unit
@ -1044,6 +1039,7 @@ Example: "<in all except [regionType] Regions>"
Applicable to: Conditional
## Deprecated uniques
- "[stats] per turn from cities before [tech/policy]" - Deprecated As of 3.18.14, replace with "[stats] [in all cities] <before discovering [tech]> OR [stats] [in all cities] <before adopting [policy]>"
- "+[amount]% [stat] [cityFilter]" - Deprecated As of 3.17.10, replace with "[+amount]% [stat] [cityFilter]"
- "+[amount]% [stat] in all cities" - Deprecated As of 3.17.10, replace with "[+amount]% [stat] [in all cities]"
- "[amount]% [stat] while the empire is happy" - Deprecated As of 3.17.1, replace with "[amount]% [stat] [in all cities] <while the empire is happy>"