Battle damage calculations changed to ints, for simplicity

This commit is contained in:
Yair Morgenstern 2020-10-06 11:16:33 +03:00
parent 9ffa0bee54
commit bb5eff5250
3 changed files with 58 additions and 72 deletions

View File

@ -16,6 +16,8 @@ Minor UI fixes - By mrahimygk
Connect Resource Quest implemented - By r3versi Connect Resource Quest implemented - By r3versi
Translation updates
## 3.11.0 ## 3.11.0
City-States Quests introduced! - by r3versi City-States Quests introduced! - by r3versi

View File

@ -3,6 +3,7 @@ package com.unciv.logic.battle
import com.unciv.Constants import com.unciv.Constants
import com.unciv.logic.map.MapUnit import com.unciv.logic.map.MapUnit
import com.unciv.logic.map.TileInfo import com.unciv.logic.map.TileInfo
import com.unciv.models.Counter
import com.unciv.models.ruleset.unit.UnitType import com.unciv.models.ruleset.unit.UnitType
import java.util.* import java.util.*
import kotlin.collections.HashMap import kotlin.collections.HashMap
@ -11,7 +12,7 @@ import kotlin.math.max
import kotlin.math.pow import kotlin.math.pow
import kotlin.math.roundToInt import kotlin.math.roundToInt
class BattleDamageModifier(val vs:String,val modificationAmount:Float){ class BattleDamageModifier(val vs:String, val modificationAmount:Float){
fun getText(): String = "vs $vs" fun getText(): String = "vs $vs"
} }
@ -36,14 +37,10 @@ object BattleDamage {
} }
private fun getGeneralModifiers(combatant: ICombatant, enemy: ICombatant): HashMap<String, Float> { private fun getGeneralModifiers(combatant: ICombatant, enemy: ICombatant): Counter<String> {
val modifiers = HashMap<String, Float>() val modifiers = Counter<String>()
fun addToModifiers(text:String, amount:Float) {
if (!modifiers.containsKey(text)) modifiers[text] = 0f
modifiers[text] = modifiers[text]!! + amount
}
fun addToModifiers(BDM:BattleDamageModifier) = fun addToModifiers(BDM:BattleDamageModifier) =
addToModifiers(BDM.getText(), BDM.modificationAmount) modifiers.add(BDM.getText(), (BDM.modificationAmount*100).toInt())
val civInfo = combatant.getCivInfo() val civInfo = combatant.getCivInfo()
if (combatant is MapUnitCombatant) { if (combatant is MapUnitCombatant) {
@ -62,75 +59,71 @@ object BattleDamage {
for (unique in combatant.unit.getMatchingUniques("+[]% Strength vs []")) { for (unique in combatant.unit.getMatchingUniques("+[]% Strength vs []")) {
if (unique.params[1] == enemy.getName()) if (unique.params[1] == enemy.getName())
addToModifiers("vs [${unique.params[1]}]", unique.params[0].toFloat() / 100) modifiers.add("vs [${unique.params[1]}]", unique.params[0].toInt())
} }
for (unique in combatant.unit.getMatchingUniques("+[]% Combat Strength")) for (unique in combatant.unit.getMatchingUniques("+[]% Combat Strength"))
addToModifiers("Combat Strength", unique.params[0].toFloat() / 100) modifiers.add("Combat Strength", unique.params[0].toInt())
//https://www.carlsguides.com/strategy/civilization5/war/combatbonuses.php //https://www.carlsguides.com/strategy/civilization5/war/combatbonuses.php
val civHappiness = civInfo.getHappiness() val civHappiness = civInfo.getHappiness()
if (civHappiness < 0) if (civHappiness < 0)
modifiers["Unhappiness"] = max(0.02f * civHappiness, -0.9f) // otherwise it could exceed -100% and start healing enemy units... modifiers["Unhappiness"] = max(2 * civHappiness, -90) // otherwise it could exceed -100% and start healing enemy units...
if (civInfo.hasUnique("Wounded military units deal +25% damage") && combatant.getHealth() < 100) { if (civInfo.hasUnique("Wounded military units deal +25% damage") && combatant.getHealth() < 100) {
modifiers["Wounded unit"] = 0.25f modifiers["Wounded unit"] = 25
} }
if (civInfo.hasUnique("+15% combat strength for melee units which have another military unit in an adjacent tile") if (civInfo.hasUnique("+15% combat strength for melee units which have another military unit in an adjacent tile")
&& combatant.isMelee() && combatant.isMelee()
&& combatant.getTile().neighbors.flatMap { it.getUnits() } && combatant.getTile().neighbors.flatMap { it.getUnits() }
.any { it.civInfo == civInfo && !it.type.isCivilian() && !it.type.isAirUnit() }) .any { it.civInfo == civInfo && !it.type.isCivilian() && !it.type.isAirUnit() })
modifiers["Discipline"] = 0.15f modifiers["Discipline"] = 15
val requiredResource = combatant.unit.baseUnit.requiredResource val requiredResource = combatant.unit.baseUnit.requiredResource
if (requiredResource != null && civInfo.getCivResourcesByName()[requiredResource]!! < 0 if (requiredResource != null && civInfo.getCivResourcesByName()[requiredResource]!! < 0
&& !civInfo.isBarbarian()) { && !civInfo.isBarbarian()) {
modifiers["Missing resource"] = -0.25f modifiers["Missing resource"] = -25
} }
val nearbyCivUnits = combatant.unit.getTile().getTilesInDistance(2) val nearbyCivUnits = combatant.unit.getTile().getTilesInDistance(2)
.filter { it.civilianUnit?.civInfo == combatant.unit.civInfo } .filter { it.civilianUnit?.civInfo == combatant.unit.civInfo }
.map { it.civilianUnit } .map { it.civilianUnit }
if (nearbyCivUnits.any { it!!.hasUnique("Bonus for units in 2 tile radius 15%") }) { if (nearbyCivUnits.any { it!!.hasUnique("Bonus for units in 2 tile radius 15%") }) {
val greatGeneralModifier = if (combatant.unit.civInfo.hasUnique("Great General provides double combat bonus")) 0.3f else 0.15f val greatGeneralModifier = if (combatant.unit.civInfo.hasUnique("Great General provides double combat bonus")) 30 else 15
modifiers["Great General"] = greatGeneralModifier modifiers["Great General"] = greatGeneralModifier
} }
if(civInfo.goldenAges.isGoldenAge() && civInfo.hasUnique("+10% Strength for all units during Golden Age")) if(civInfo.goldenAges.isGoldenAge() && civInfo.hasUnique("+10% Strength for all units during Golden Age"))
modifiers["Golden Age"] = 0.1f modifiers["Golden Age"] = 10
if (enemy.getCivInfo().isCityState() && civInfo.hasUnique("+30% Strength when fighting City-State units and cities")) if (enemy.getCivInfo().isCityState() && civInfo.hasUnique("+30% Strength when fighting City-State units and cities"))
modifiers["vs [City-States]"] = 0.3f modifiers["vs [City-States]"] = 30
} }
if (enemy.getCivInfo().isBarbarian()) { if (enemy.getCivInfo().isBarbarian()) {
modifiers["Difficulty"] = civInfo.gameInfo.getDifficulty().barbarianBonus modifiers["Difficulty"] = (civInfo.gameInfo.getDifficulty().barbarianBonus*100).toInt()
if (civInfo.hasUnique("+25% bonus vs Barbarians")) if (civInfo.hasUnique("+25% bonus vs Barbarians"))
modifiers["vs Barbarians"] = 0.25f modifiers["vs Barbarians"] = 25
} }
return modifiers return modifiers
} }
fun getAttackModifiers(attacker: ICombatant, tileToAttackFrom:TileInfo?, defender: ICombatant): HashMap<String, Float> { fun getAttackModifiers(attacker: ICombatant, tileToAttackFrom:TileInfo?, defender: ICombatant): Counter<String> {
val modifiers = getGeneralModifiers(attacker, defender) val modifiers = getGeneralModifiers(attacker, defender)
if (attacker is MapUnitCombatant) { if (attacker is MapUnitCombatant) {
modifiers.putAll(getTileSpecificModifiers(attacker, defender.getTile())) modifiers.add(getTileSpecificModifiers(attacker, defender.getTile()))
for (ability in attacker.unit.getUniques()) { for (ability in attacker.unit.getUniques()) {
if(ability.placeholderText == "Bonus as Attacker []%") { if (ability.placeholderText == "Bonus as Attacker []%")
val bonus = ability.params[0].toFloat() / 100 modifiers.add("Attacker Bonus", ability.params[0].toInt())
if (modifiers.containsKey("Attacker Bonus"))
modifiers["Attacker Bonus"] = modifiers["Attacker Bonus"]!! + bonus
else modifiers["Attacker Bonus"] = bonus
}
} }
if (attacker.unit.isEmbarked() && !attacker.unit.hasUnique("Amphibious")) if (attacker.unit.isEmbarked() && !attacker.unit.hasUnique("Amphibious"))
modifiers["Landing"] = -0.5f modifiers["Landing"] = -50
if (attacker.isMelee()) { if (attacker.isMelee()) {
val numberOfAttackersSurroundingDefender = defender.getTile().neighbors.count { val numberOfAttackersSurroundingDefender = defender.getTile().neighbors.count {
@ -139,89 +132,82 @@ object BattleDamage {
&& MapUnitCombatant(it.militaryUnit!!).isMelee() && MapUnitCombatant(it.militaryUnit!!).isMelee()
} }
if (numberOfAttackersSurroundingDefender > 1) if (numberOfAttackersSurroundingDefender > 1)
modifiers["Flanking"] = 0.1f * (numberOfAttackersSurroundingDefender - 1) //https://www.carlsguides.com/strategy/civilization5/war/combatbonuses.php modifiers["Flanking"] = 10 * (numberOfAttackersSurroundingDefender - 1) //https://www.carlsguides.com/strategy/civilization5/war/combatbonuses.php
if (attacker.getTile().aerialDistanceTo(defender.getTile()) == 1 && attacker.getTile().isConnectedByRiver(defender.getTile()) if (attacker.getTile().aerialDistanceTo(defender.getTile()) == 1 && attacker.getTile().isConnectedByRiver(defender.getTile())
&& !attacker.unit.hasUnique("Amphibious")) { && !attacker.unit.hasUnique("Amphibious")) {
if (!attacker.getTile().hasConnection(attacker.getCivInfo()) // meaning, the tiles are not road-connected for this civ if (!attacker.getTile().hasConnection(attacker.getCivInfo()) // meaning, the tiles are not road-connected for this civ
|| !defender.getTile().hasConnection(attacker.getCivInfo()) || !defender.getTile().hasConnection(attacker.getCivInfo())
|| !attacker.getCivInfo().tech.roadsConnectAcrossRivers) { || !attacker.getCivInfo().tech.roadsConnectAcrossRivers) {
modifiers["Across river"] = -0.2f modifiers["Across river"] = -20
} }
} }
} }
if (attacker.getCivInfo().policies.autocracyCompletedTurns > 0) if (attacker.getCivInfo().policies.autocracyCompletedTurns > 0)
modifiers["Autocracy Complete"] = 0.2f modifiers["Autocracy Complete"] = 20
if (defender is CityCombatant && if (defender is CityCombatant &&
attacker.getCivInfo().hasUnique("+15% Combat Strength for all units when attacking Cities")) attacker.getCivInfo().hasUnique("+15% Combat Strength for all units when attacking Cities"))
modifiers["Statue of Zeus"] = 0.15f modifiers["Statue of Zeus"] = 15
} else if (attacker is CityCombatant) { } else if (attacker is CityCombatant) {
if (attacker.getCivInfo().hasUnique("+50% attacking strength for cities with garrisoned units") if (attacker.getCivInfo().hasUnique("+50% attacking strength for cities with garrisoned units")
&& attacker.city.getCenterTile().militaryUnit != null) && attacker.city.getCenterTile().militaryUnit != null)
modifiers["Oligarchy"] = 0.5f modifiers["Oligarchy"] = 50
} }
return modifiers return modifiers
} }
fun getDefenceModifiers(attacker: ICombatant, defender: MapUnitCombatant): HashMap<String, Float> { fun getDefenceModifiers(attacker: ICombatant, defender: MapUnitCombatant): Counter<String> {
val modifiers = HashMap<String, Float>() val modifiers = getGeneralModifiers(defender, attacker)
val tile = defender.getTile() val tile = defender.getTile()
if (defender.unit.isEmbarked()) { if (defender.unit.isEmbarked()) {
// embarked units get no defensive modifiers apart from this unique // embarked units get no defensive modifiers apart from this unique
if (defender.unit.hasUnique("Defense bonus when embarked") || if (defender.unit.hasUnique("Defense bonus when embarked") ||
defender.getCivInfo().hasUnique("Embarked units can defend themselves")) defender.getCivInfo().hasUnique("Embarked units can defend themselves"))
modifiers["Embarked"] = 1f modifiers["Embarked"] = 100
return modifiers return modifiers
} }
modifiers.putAll(getGeneralModifiers(defender, attacker))
modifiers.putAll(getTileSpecificModifiers(defender, tile)) modifiers.putAll(getTileSpecificModifiers(defender, tile))
val tileDefenceBonus = tile.getDefensiveBonus() val tileDefenceBonus = tile.getDefensiveBonus()
if (!defender.unit.hasUnique("No defensive terrain bonus") || tileDefenceBonus < 0) if (!defender.unit.hasUnique("No defensive terrain bonus") || tileDefenceBonus < 0)
modifiers["Tile"] = tileDefenceBonus modifiers["Tile"] = (tileDefenceBonus*100).toInt()
if (attacker.isRanged()) { if (attacker.isRanged()) {
val defenceVsRanged = 0.25f * defender.unit.getUniques().count { it.text == "+25% Defence against ranged attacks" } val defenceVsRanged = 25 * defender.unit.getUniques().count { it.text == "+25% Defence against ranged attacks" }
if (defenceVsRanged > 0) modifiers["defence vs ranged"] = defenceVsRanged if (defenceVsRanged > 0) modifiers["defence vs ranged"] = defenceVsRanged
} }
val carrierDefenceBonus = 0.25f * defender.unit.getUniques().count { it.text == "+25% Combat Bonus when defending" } val carrierDefenceBonus = 25 * defender.unit.getUniques().count { it.text == "+25% Combat Bonus when defending" }
if (carrierDefenceBonus > 0) modifiers["Armor Plating"] = carrierDefenceBonus if (carrierDefenceBonus > 0) modifiers["Armor Plating"] = carrierDefenceBonus
for(unique in defender.unit.getMatchingUniques("+[]% defence in [] tiles")) { for(unique in defender.unit.getMatchingUniques("+[]% defence in [] tiles")) {
if (tile.fitsUniqueFilter(unique.params[1])) if (tile.fitsUniqueFilter(unique.params[1]))
modifiers["[${unique.params[1]}] defence"] = unique.params[0].toFloat() / 100 modifiers["[${unique.params[1]}] defence"] = unique.params[0].toInt()
} }
if (defender.unit.isFortified()) if (defender.unit.isFortified())
modifiers["Fortification"] = 0.2f * defender.unit.getFortificationTurns() modifiers["Fortification"] = 20 * defender.unit.getFortificationTurns()
return modifiers return modifiers
} }
private fun addStackingModifier(modifiers : HashMap<String,Float>, text : String, modificationAmount : Float) { private fun getTileSpecificModifiers(unit: MapUnitCombatant, tile: TileInfo): Counter<String> {
if (modifiers.containsKey(text)) modifiers[text] = modifiers[text]!! + modificationAmount val modifiers = Counter<String>()
else modifiers[text] = modificationAmount
}
private fun getTileSpecificModifiers(unit: MapUnitCombatant, tile: TileInfo): HashMap<String,Float> {
val modifiers = HashMap<String,Float>()
// As of 3.11.0 This is to be deprecated and converted to "+[15]% combat bonus for units fighting in [Friendly Land]" - keeping it here to that mods with this can still work for now // As of 3.11.0 This is to be deprecated and converted to "+[15]% combat bonus for units fighting in [Friendly Land]" - keeping it here to that mods with this can still work for now
// Civ 5 does not use "Himeji Castle" // Civ 5 does not use "Himeji Castle"
if(tile.isFriendlyTerritory(unit.getCivInfo()) && unit.getCivInfo().hasUnique("+15% combat strength for units fighting in friendly territory")) if(tile.isFriendlyTerritory(unit.getCivInfo()) && unit.getCivInfo().hasUnique("+15% combat strength for units fighting in friendly territory"))
addStackingModifier(modifiers, "Friendly Land", 0.15f) modifiers.add("Friendly Land", 15)
// As of 3.11.0 This is to be deprecated and converted to "+[20]% combat bonus in [Foreign Land]" - keeping it here to that mods with this can still work for now // As of 3.11.0 This is to be deprecated and converted to "+[20]% combat bonus in [Foreign Land]" - keeping it here to that mods with this can still work for now
if(!tile.isFriendlyTerritory(unit.getCivInfo()) && unit.unit.hasUnique("+20% bonus outside friendly territory")) if(!tile.isFriendlyTerritory(unit.getCivInfo()) && unit.unit.hasUnique("+20% bonus outside friendly territory"))
addStackingModifier(modifiers, "Foreign Land", 0.2f) modifiers.add("Foreign Land", 20)
for (unique in unit.unit.getMatchingUniques("+[]% combat bonus in []") for (unique in unit.unit.getMatchingUniques("+[]% combat bonus in []")
+ unit.getCivInfo().getMatchingUniques("+[]% combat bonus for units fighting in []")) { + unit.getCivInfo().getMatchingUniques("+[]% combat bonus for units fighting in []")) {
@ -229,7 +215,7 @@ object BattleDamage {
if (filter == tile.getLastTerrain().name if (filter == tile.getLastTerrain().name
|| filter == "Foreign Land" && !tile.isFriendlyTerritory(unit.getCivInfo()) || filter == "Foreign Land" && !tile.isFriendlyTerritory(unit.getCivInfo())
|| filter == "Friendly Land" && tile.isFriendlyTerritory(unit.getCivInfo())) || filter == "Friendly Land" && tile.isFriendlyTerritory(unit.getCivInfo()))
addStackingModifier(modifiers, filter, unique.params[0].toFloat() / 100) modifiers.add(filter, unique.params[0].toInt())
} }
// As of 3.10.6 This is to be deprecated and converted to "+[]% combat bonus in []" - keeping it here to that mods with this can still work for now // As of 3.10.6 This is to be deprecated and converted to "+[]% combat bonus in []" - keeping it here to that mods with this can still work for now
@ -240,44 +226,42 @@ object BattleDamage {
// except when there is a vegetation // except when there is a vegetation
(tile.terrainFeature != Constants.forest (tile.terrainFeature != Constants.forest
|| tile.terrainFeature != Constants.jungle)) || tile.terrainFeature != Constants.jungle))
modifiers[tile.baseTerrain] = 0.25f modifiers[tile.baseTerrain] = 25
for(unique in unit.getCivInfo().getMatchingUniques("+[]% Strength if within [] tiles of a []")) { for(unique in unit.getCivInfo().getMatchingUniques("+[]% Strength if within [] tiles of a []")) {
if (tile.getTilesInDistance(unique.params[1].toInt()).any { it.improvement == unique.params[2] }) if (tile.getTilesInDistance(unique.params[1].toInt()).any { it.improvement == unique.params[2] })
modifiers[unique.params[2]] = unique.params[0].toFloat() / 100 modifiers[unique.params[2]] = unique.params[0].toInt()
} }
if(tile.neighbors.flatMap { it.getUnits() } if(tile.neighbors.flatMap { it.getUnits() }
.any { it.hasUnique("-10% combat strength for adjacent enemy units") && it.civInfo.isAtWarWith(unit.getCivInfo()) }) .any { it.hasUnique("-10% combat strength for adjacent enemy units") && it.civInfo.isAtWarWith(unit.getCivInfo()) })
modifiers["Haka War Dance"] = -0.1f modifiers["Haka War Dance"] = -10
// As of 3.10.6 This is to be deprecated and converted to "+[]% combat bonus in []" - keeping it here to that mods with this can still work for now // As of 3.10.6 This is to be deprecated and converted to "+[]% combat bonus in []" - keeping it here to that mods with this can still work for now
if(unit.unit.hasUnique("+33% combat bonus in Forest/Jungle") if(unit.unit.hasUnique("+33% combat bonus in Forest/Jungle")
&& (tile.terrainFeature== Constants.forest || tile.terrainFeature==Constants.jungle)) && (tile.terrainFeature== Constants.forest || tile.terrainFeature==Constants.jungle))
modifiers[tile.terrainFeature!!]=0.33f modifiers[tile.terrainFeature!!]=33
val isRoughTerrain = tile.isRoughTerrain() val isRoughTerrain = tile.isRoughTerrain()
for (BDM in getBattleDamageModifiersOfUnit(unit.unit)) { for (BDM in getBattleDamageModifiersOfUnit(unit.unit)) {
val text = BDM.getText() val text = BDM.getText()
if (BDM.vs == "units in open terrain" && !isRoughTerrain) { // this will change when we change over everything to ints
if (modifiers.containsKey(text)) if (BDM.vs == "units in open terrain" && !isRoughTerrain) modifiers.add(text, (BDM.modificationAmount*100).toInt())
modifiers[text] = modifiers[text]!! + BDM.modificationAmount if (BDM.vs == "units in rough terrain" && isRoughTerrain) modifiers.add(text, (BDM.modificationAmount*100).toInt())
else modifiers[text] = BDM.modificationAmount
}
if (BDM.vs == "units in rough terrain" && isRoughTerrain) {
if (modifiers.containsKey(text))
modifiers[text] = modifiers[text]!! + BDM.modificationAmount
else modifiers[text] = BDM.modificationAmount
}
} }
return modifiers return modifiers
} }
private fun modifiersToMultiplicationBonus(modifiers: HashMap<String, Float>): Float { fun Counter<String>.toOldModifiers(): HashMap<String, Float> {
// modifiers are like 0.1 for a 10% bonus, -0.1 for a 10% loss val modifiers = HashMap<String,Float>()
for((key,value) in this) modifiers[key] = value.toFloat()/100
return modifiers
}
private fun modifiersToMultiplicationBonus(modifiers: Counter<String>): Float {
var finalModifier = 1f var finalModifier = 1f
for (modifierValue in modifiers.values) finalModifier *= (1 + modifierValue) for (modifierValue in modifiers.values) finalModifier *= (1 + modifierValue/100) // so 25 will result in *= 1.25
return finalModifier return finalModifier
} }

View File

@ -123,14 +123,14 @@ class BattleTable(val worldScreen: WorldScreen): Table() {
val attackerModifiers = val attackerModifiers =
BattleDamage.getAttackModifiers(attacker,null,defender).map { BattleDamage.getAttackModifiers(attacker,null,defender).map {
val description = if(it.key.startsWith("vs ")) ("vs ["+it.key.replace("vs ","")+"]").tr() else it.key.tr() val description = if(it.key.startsWith("vs ")) ("vs ["+it.key.replace("vs ","")+"]").tr() else it.key.tr()
val percentage = (if(it.value>0)"+" else "")+(it.value*100).toInt()+"%" val percentage = (if(it.value>0)"+" else "")+ it.value +"%"
"$description: $percentage" "$description: $percentage"
} }
val defenderModifiers = val defenderModifiers =
if (defender is MapUnitCombatant) if (defender is MapUnitCombatant)
BattleDamage.getDefenceModifiers(attacker, defender).map { BattleDamage.getDefenceModifiers(attacker, defender).map {
val description = if(it.key.startsWith("vs ")) ("vs ["+it.key.replace("vs ","")+"]").tr() else it.key.tr() val description = if(it.key.startsWith("vs ")) ("vs ["+it.key.replace("vs ","")+"]").tr() else it.key.tr()
val percentage = (if(it.value>0)"+" else "")+(it.value*100).toInt()+"%" val percentage = (if(it.value>0)"+" else "")+ it.value +"%"
"$description: $percentage" "$description: $percentage"
} }
else listOf() else listOf()