diff --git a/android/assets/jsons/Civ V - Vanilla/UnitPromotions.json b/android/assets/jsons/Civ V - Vanilla/UnitPromotions.json index 10e15e6f03..da826dc9c8 100644 --- a/android/assets/jsons/Civ V - Vanilla/UnitPromotions.json +++ b/android/assets/jsons/Civ V - Vanilla/UnitPromotions.json @@ -174,13 +174,13 @@ }, { "name": "Survivalism I", - "uniques": ["[+5] HP when healing in [Foreign Land] tiles", "+[25]% Strength when defending"], + "uniques": ["[+5] HP when healing in [Foreign Land] tiles", "[+25]% Strength "], "unitTypes": ["Scout"] }, { "name": "Survivalism II", "prerequisites": ["Survivalism I"], - "uniques": ["[+5] HP when healing in [Foreign Land] tiles", "+[25]% Strength when defending"], + "uniques": ["[+5] HP when healing in [Foreign Land] tiles", "[+25]% Strength "], "unitTypes": ["Scout"] }, { @@ -250,38 +250,38 @@ // Submarine { "name": "Wolfpack I", - "uniques": ["+[25]% Strength when attacking"], + "uniques": ["[+25]% Strength "], "unitTypes": ["Submarine"] }, { "name": "Wolfpack II", "prerequisites": ["Wolfpack I"], - "uniques": ["+[25]% Strength when attacking"], + "uniques": ["[+25]% Strength "], "unitTypes": ["Submarine"] }, { "name": "Wolfpack III", "prerequisites": ["Wolfpack II"], - "uniques": ["+[25]% Strength when attacking"], + "uniques": ["[+25]% Strength "], "unitTypes": ["Submarine"] }, // Aircraft Carrier { "name": "Armor Plating I", - "uniques": ["+[25]% Strength when defending"], + "uniques": ["[+25]% Strength "], "unitTypes": ["Aircraft Carrier"] }, { "name": "Armor Plating II", "prerequisites": ["Armor Plating I"], - "uniques": ["+[25]% Strength when defending"], + "uniques": ["[+25]% Strength "], "unitTypes": ["Aircraft Carrier"] }, { "name": "Armor Plating III", "prerequisites": ["Armor Plating II"], - "uniques": ["+[25]% Strength when defending"], + "uniques": ["[+25]% Strength "], "unitTypes": ["Aircraft Carrier"] }, { @@ -433,13 +433,13 @@ // Mixed { "name": "Cover I", - "uniques": ["[+33]% Strength when defending vs [Ranged] units"], + "uniques": ["[+33]% Strength "], "unitTypes": ["Sword","Gunpowder","Archery","Ranged Gunpowder","Siege"] }, { "name": "Cover II", "prerequisites": ["Cover I"], - "uniques": ["[+33]% Strength when defending vs [Ranged] units"], + "uniques": ["[+33]% Strength "], "unitTypes": ["Sword","Gunpowder","Archery","Ranged Gunpowder","Siege"] }, diff --git a/android/assets/jsons/Civ V - Vanilla/Units.json b/android/assets/jsons/Civ V - Vanilla/Units.json index 60ffd3100e..049561fda3 100644 --- a/android/assets/jsons/Civ V - Vanilla/Units.json +++ b/android/assets/jsons/Civ V - Vanilla/Units.json @@ -315,7 +315,7 @@ "requiredTech": "Bronze Working", "obsoleteTech": "Physics", "upgradesTo": "Trebuchet", - "uniques": ["[+300]% Strength ", "No defensive terrain bonus", "+[-33]% Strength when defending", + "uniques": ["[+300]% Strength ", "No defensive terrain bonus", "[-33]% Strength ", "-[1] Visibility Range", "Can only attack [City] units"], "promotions": ["Cover I"], "attackSound": "throw" @@ -838,7 +838,7 @@ "requiredTech": "Gunpowder", "upgradesTo": "Rifleman", "obsoleteTech": "Rifling", - "uniques": ["Heals [50] damage if it kills a unit", "+[25]% Strength when attacking"], + "uniques": ["Heals [50] damage if it kills a unit", "[+25]% Strength "], "attackSound": "shot" }, { @@ -1132,7 +1132,7 @@ "cost": 325, "requiredTech": "Refrigeration", "upgradesTo": "Nuclear Submarine", - "uniques": ["+[75]% Strength when attacking", "Can only attack [Water] tiles"], + "uniques": ["[+75]% Strength ", "Can only attack [Water] tiles"], "attackSound": "torpedo" }, { @@ -1490,7 +1490,7 @@ "rangedStrength": 85, "cost": 425, "requiredTech": "Telecommunications", - "uniques": ["+[75]% Strength when attacking", "Can only attack [Water] tiles", "[+1] Visibility Range", "Can carry [2] [Missile] units"], + "uniques": ["[+75]% Strength ", "Can only attack [Water] tiles", "[+1] Visibility Range", "Can carry [2] [Missile] units"], "attackSound": "torpedo" }, { diff --git a/android/assets/jsons/translations/template.properties b/android/assets/jsons/translations/template.properties index 9a9e6ce8be..e5001d24d3 100644 --- a/android/assets/jsons/translations/template.properties +++ b/android/assets/jsons/translations/template.properties @@ -814,6 +814,9 @@ defence vs [unitType] = Defensive Bonus = Stacked with [unitType] = +Unit ability = +National ability = + The following improvements [stats]: = The following improvements on [tileType] tiles [stats]: = @@ -1302,6 +1305,10 @@ Invisible to others = when not at war = when at war = if this city has at least [amount] specialists = +vs cities = +vs [mapUnitFilter] units = +when attacking = +when defending = # In English we just paste all these conditionals at the end of each unique, but in your language that # may not turn into valid sentences. Therefore we have the following two translations to determine diff --git a/core/src/com/unciv/logic/battle/BattleDamage.kt b/core/src/com/unciv/logic/battle/BattleDamage.kt index 29754baf57..20c0319806 100644 --- a/core/src/com/unciv/logic/battle/BattleDamage.kt +++ b/core/src/com/unciv/logic/battle/BattleDamage.kt @@ -3,6 +3,8 @@ package com.unciv.logic.battle import com.unciv.logic.map.TileInfo import com.unciv.models.Counter import com.unciv.models.ruleset.unique.StateForConditionals +import com.unciv.models.ruleset.unique.Unique +import com.unciv.models.ruleset.unique.UniqueTarget import com.unciv.models.ruleset.unique.UniqueType import com.unciv.ui.utils.toPercent import java.util.* @@ -17,17 +19,25 @@ class BattleDamageModifier(val vs:String, val modificationAmount:Float){ } object BattleDamage { + + private fun getModifierStringFromUnique(unique: Unique): String { + return when (unique.sourceObjectType) { + UniqueTarget.Unit -> "Unit ability" + UniqueTarget.Nation -> "National ability" + else -> "[${unique.sourceObjectName}] ([${unique.sourceObjectType?.name}])" + } + } - private fun getGeneralModifiers(combatant: ICombatant, enemy: ICombatant): Counter { + private fun getGeneralModifiers(combatant: ICombatant, enemy: ICombatant, combatAction: CombatAction): Counter { val modifiers = Counter() val civInfo = combatant.getCivInfo() if (combatant is MapUnitCombatant) { for (unique in combatant.unit.getMatchingUniques( UniqueType.Strength, - StateForConditionals(civInfo, defender = enemy)) + StateForConditionals(civInfo, defender = enemy, combatAction = combatAction)) ) { - modifiers.add("${unique.sourceObjectName} (${unique.sourceObjectType})", unique.params[0].toInt()) + modifiers.add(getModifierStringFromUnique(unique), unique.params[0].toInt()) } // Deprecated since 3.17.3 @@ -124,14 +134,16 @@ object BattleDamage { attacker: ICombatant, defender: ICombatant ): Counter { - val modifiers = getGeneralModifiers(attacker, defender) + val modifiers = getGeneralModifiers(attacker, defender, CombatAction.Attack) if (attacker is MapUnitCombatant) { modifiers.add(getTileSpecificModifiers(attacker, defender.getTile())) - for (unique in attacker.unit.getMatchingUniques("+[]% Strength when attacking")) { - modifiers.add("Attacker Bonus", unique.params[0].toInt()) - } + // Deprecated since 3.17.4 + for (unique in attacker.unit.getMatchingUniques(UniqueType.StrengthAttacking)) { + modifiers.add("Attacker Bonus", unique.params[0].toInt()) + } + // if (attacker.unit.isEmbarked() && !attacker.unit.hasUnique("Eliminates combat penalty for attacking from the sea")) modifiers["Landing"] = -50 @@ -192,7 +204,7 @@ object BattleDamage { } fun getDefenceModifiers(attacker: ICombatant, defender: ICombatant): Counter { - val modifiers = getGeneralModifiers(defender, attacker) + val modifiers = getGeneralModifiers(defender, attacker, CombatAction.Defend) val tile = defender.getTile() if (defender is MapUnitCombatant) { @@ -215,14 +227,16 @@ object BattleDamage { ) modifiers["Tile"] = (tileDefenceBonus * 100).toInt() - for (unique in defender.unit.getMatchingUniques("[]% Strength when defending vs [] units")) { - if (attacker.matchesCategory(unique.params[1])) - modifiers.add("defence vs [${unique.params[1]}] ", unique.params[0].toInt()) - } - - for (unique in defender.unit.getMatchingUniques("+[]% Strength when defending")) { - modifiers.add("Defender Bonus", unique.params[0].toInt()) - } + // Deprecated since 3.17.4 + for (unique in defender.unit.getMatchingUniques(UniqueType.StrengthDefendingUnitFilter)) { + if (attacker.matchesCategory(unique.params[1])) + modifiers.add("defence vs [${unique.params[1]}] ", unique.params[0].toInt()) + } + + for (unique in defender.unit.getMatchingUniques(UniqueType.StrengthDefending)) { + modifiers.add("Defender Bonus", unique.params[0].toInt()) + } + // for (unique in defender.unit.getMatchingUniques("+[]% defence in [] tiles")) { if (tile.matchesFilter(unique.params[1])) @@ -341,4 +355,10 @@ object BattleDamage { val randomCenteredAround30 = 24 + 12 * Random().nextFloat() return randomCenteredAround30 * ratioModifier } +} + +enum class CombatAction { + Attack, + Defend, + Intercept, } \ No newline at end of file diff --git a/core/src/com/unciv/models/ruleset/unique/StateForConditionals.kt b/core/src/com/unciv/models/ruleset/unique/StateForConditionals.kt index 9edf5baffd..c8a1e83c0b 100644 --- a/core/src/com/unciv/models/ruleset/unique/StateForConditionals.kt +++ b/core/src/com/unciv/models/ruleset/unique/StateForConditionals.kt @@ -1,5 +1,6 @@ package com.unciv.models.ruleset.unique +import com.unciv.logic.battle.CombatAction import com.unciv.logic.battle.ICombatant import com.unciv.logic.city.CityInfo import com.unciv.logic.civilization.CivilizationInfo @@ -8,14 +9,8 @@ import com.unciv.logic.map.TileInfo data class StateForConditionals( val civInfo: CivilizationInfo? = null, val cityInfo: CityInfo? = null, - val defender: ICombatant? = null, // val attacker: ICombatant? = null, + val defender: ICombatant? = null, // val attackedTile: TileInfo? = null, -// val combatAction: CombatAction? = null, -) - -//enum class CombatAction() { -// Attack, -// Defend, -// Intercept, -//} \ No newline at end of file + val combatAction: CombatAction? = null, +) \ No newline at end of file diff --git a/core/src/com/unciv/models/ruleset/unique/Unique.kt b/core/src/com/unciv/models/ruleset/unique/Unique.kt index 4565d4155d..bc66443ded 100644 --- a/core/src/com/unciv/models/ruleset/unique/Unique.kt +++ b/core/src/com/unciv/models/ruleset/unique/Unique.kt @@ -1,5 +1,6 @@ package com.unciv.models.ruleset.unique +import com.unciv.logic.battle.CombatAction import com.unciv.logic.city.CityInfo import com.unciv.models.stats.Stats import com.unciv.models.translations.* @@ -54,6 +55,8 @@ class Unique(val text: String, val sourceObjectType: UniqueTarget? = null, val s state.defender != null && state.defender.matchesCategory("City") UniqueType.ConditionalVsUnits -> state.defender != null && state.defender.matchesCategory(condition.params[0]) + UniqueType.ConditionalAttacking -> state.combatAction == CombatAction.Attack + UniqueType.ConditionalDefending -> state.combatAction == CombatAction.Defend UniqueType.ConditionalNeighborTiles -> state.cityInfo != null && state.cityInfo.getCenterTile().neighbors.count { diff --git a/core/src/com/unciv/models/ruleset/unique/UniqueType.kt b/core/src/com/unciv/models/ruleset/unique/UniqueType.kt index 9d1a689b6c..1c21316cb6 100644 --- a/core/src/com/unciv/models/ruleset/unique/UniqueType.kt +++ b/core/src/com/unciv/models/ruleset/unique/UniqueType.kt @@ -123,12 +123,18 @@ enum class UniqueType(val text:String, vararg targets: UniqueTarget) { StrengthPlus("+[amount]% Strength", UniqueTarget.Unit), @Deprecated("As of 3.17.3", ReplaceWith("[amount]% Strength"), DeprecationLevel.WARNING) StrengthMin("-[amount]% Strength", UniqueTarget.Unit), - @Deprecated("As of 3.17.3", ReplaceWith("[amount]% Strength OR [amount]% Strength "), DeprecationLevel.WARNING) + @Deprecated("As of 3.17.3", ReplaceWith("[amount]% Strength OR [amount]% Strength "), DeprecationLevel.WARNING) StrengthPlusVs("+[amount]% Strength vs [combatantFilter]", UniqueTarget.Unit), - @Deprecated("As of 3.17.3", ReplaceWith("[amount]% Strength OR [amount]% Strength "), DeprecationLevel.WARNING) + @Deprecated("As of 3.17.3", ReplaceWith("[amount]% Strength OR [amount]% Strength "), DeprecationLevel.WARNING) StrengthMinVs("-[amount]% Strength vs [combatantFilter]", UniqueTarget.Unit), @Deprecated("As of 3.17.3", ReplaceWith("[amount]% Strength"), DeprecationLevel.WARNING) CombatBonus("+[amount]% Combat Strength", UniqueTarget.Unit), + @Deprecated("As of 3.17.5", ReplaceWith("[amount]% Strength "), DeprecationLevel.WARNING) + StrengthAttacking("+[amount]% Strength when attacking", UniqueTarget.Unit), + @Deprecated("As of 3.17.5", ReplaceWith("[amount]% Strength "), DeprecationLevel.WARNING) + StrengthDefending("+[amount]% Strength when defending", UniqueTarget.Unit), + @Deprecated("As of 3.17.5", ReplaceWith("[amount]% Strength "), DeprecationLevel.WARNING) + StrengthDefendingUnitFilter("[amount]% Strength when defending vs [mapUnitFilter] units", UniqueTarget.Unit), // The following block gets cached in MapUnit for faster getMovementCostBetweenAdjacentTiles DoubleMovementOnTerrain("Double movement in [terrainFilter]", UniqueTarget.Unit), @@ -175,10 +181,10 @@ enum class UniqueType(val text:String, vararg targets: UniqueTarget) { ConditionalHappy("while the empire is happy", UniqueTarget.Conditional), ConditionalVsCity("vs cities", UniqueTarget.Conditional), ConditionalVsUnits("vs [mapUnitFilter] units", UniqueTarget.Conditional), -// ConditionalInTiles("fighting in [tileFilter] tiles", UniqueTarget.Conditional), -// ConditionalAttacking("when attacking", UniqueTarget.Conditional), -// ConditionalDefending("when defending", UniqueTarget.Conditional), + ConditionalAttacking("when attacking", UniqueTarget.Conditional), + ConditionalDefending("when defending", UniqueTarget.Conditional), // ConditionalIntercepting("when intercepting", UniqueTarget.Conditional), +// ConditionalInTiles("fighting in [tileFilter] tiles", UniqueTarget.Conditional), ConditionalNeighborTiles("with [amount] to [amount] neighboring [tileFilter] tiles", UniqueTarget.Conditional), ConditionalNeighborTilesAnd("with [amount] to [amount] neighboring [tileFilter] [tileFilter] tiles", UniqueTarget.Conditional), diff --git a/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt b/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt index 43f4d8a17d..9bbfb1ca0d 100644 --- a/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt +++ b/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt @@ -573,8 +573,8 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction { return } - var power = strength.toFloat().pow(1.5f).toInt() - var rangedPower = rangedStrength.toFloat().pow(1.45f).toInt() + var power = strength.toFloat().pow(1.5f) + var rangedPower = rangedStrength.toFloat().pow(1.45f) // Value ranged naval units less if (isWaterUnit()) { @@ -585,7 +585,7 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction { // Replicates the formula from civ V, which is a lower multiplier than probably intended, because math // They did fix it in BNW so it was completely bugged and always 1, again math - power = (power * movement.toFloat().pow(0.3f)).toInt() + power = (power * movement.toFloat().pow(0.3f)) if (uniqueObjects.any { it.placeholderText =="Self-destructs when attacking" } ) power /= 2 @@ -597,21 +597,28 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction { when { unique.isOfType(UniqueType.Strength) && unique.params[0].toInt() > 0 -> { - if (unique.conditionals.any { it.isOfType(UniqueType.ConditionalVsCity) }) - power += (power * unique.params[0].toInt()) / 200 - else if (unique.conditionals.any { it.isOfType(UniqueType.ConditionalVsUnits) }) - power += (power * unique.params[0].toInt()) / 400 + if (unique.conditionals.any { it.isOfType(UniqueType.ConditionalVsUnits) } ) { // Bonus vs some units - a quarter of the bonus + power *= (unique.params[0].toInt() / 4f).toPercent() + } else if ( + unique.conditionals.any { it.isOfType(UniqueType.ConditionalVsCity) } || // City Attack - half the bonus + unique.conditionals.any { it.isOfType(UniqueType.ConditionalAttacking) } || // Attack - half the bonus + unique.conditionals.any { it.isOfType(UniqueType.ConditionalDefending) } // Defense - half the bonus + ) { + power *= (unique.params[0].toInt() / 2f).toPercent() + } } // Deprecated since 3.17.3 - unique.placeholderText == "+[]% Strength vs []" && unique.params[1] == "City" // City Attack - half the bonus + unique.isOfType(UniqueType.StrengthPlusVs) && unique.params[1] == "City" // City Attack - half the bonus -> power += (power * unique.params[0].toInt()) / 200 - unique.placeholderText == "+[]% Strength vs []" && unique.params[1] != "City" // Bonus vs something else - a quarter of the bonus + unique.isOfType(UniqueType.StrengthPlusVs) && unique.params[1] != "City" // Bonus vs something else - a quarter of the bonus -> power += (power * unique.params[0].toInt()) / 400 // - unique.placeholderText == "+[]% Strength when attacking" // Attack - half the bonus - -> power += (power * unique.params[0].toInt()) / 200 - unique.placeholderText == "+[]% Strength when defending" // Defense - half the bonus - -> power += (power * unique.params[0].toInt()) / 200 + // Deprecated since 3.17.4 + unique.isOfType(UniqueType.StrengthAttacking) // Attack - half the bonus + -> power += (power * unique.params[0].toInt()) / 200 + unique.isOfType(UniqueType.StrengthDefending) // Defense - half the bonus + -> power += (power * unique.params[0].toInt()) / 200 + // unique.placeholderText == "May Paradrop up to [] tiles from inside friendly territory" // Paradrop - 25% bonus -> power += power / 4 unique.placeholderText == "Must set up to ranged attack" // Must set up - 20 % penalty @@ -626,21 +633,28 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction { for (unique in ruleset.unitPromotions[promotionName]!!.uniqueObjects) { when { unique.isOfType(UniqueType.Strength) && unique.params[0].toInt() > 0 -> { - if (unique.conditionals.any { it.isOfType(UniqueType.ConditionalVsCity) }) - power += (power * unique.params[0].toInt()) / 200 - else if (unique.conditionals.any { it.isOfType(UniqueType.ConditionalVsUnits) }) - power += (power * unique.params[0].toInt()) / 400 + if (unique.conditionals.any { it.isOfType(UniqueType.ConditionalVsUnits) } ) { // Bonus vs some units - a quarter of the bonus + power *= (unique.params[0].toInt() / 4f).toPercent() + } else if ( + unique.conditionals.any { it.isOfType(UniqueType.ConditionalVsCity) } || // City Attack - half the bonus + unique.conditionals.any { it.isOfType(UniqueType.ConditionalAttacking) } || // Attack - half the bonus + unique.conditionals.any { it.isOfType(UniqueType.ConditionalDefending) } // Defense - half the bonus + ) { + power *= (unique.params[0].toInt() / 2f).toPercent() + } } // Deprecated since 3.17.3 - unique.placeholderText == "+[]% Strength vs []" && unique.params[1] == "City" // City Attack - half the bonus - -> power += (power * unique.params[0].toInt()) / 200 - unique.placeholderText == "+[]% Strength vs []" && unique.params[1] != "City" // Bonus vs something else - a quarter of the bonus - -> power += (power * unique.params[0].toInt()) / 400 + unique.isOfType(UniqueType.StrengthPlusVs) && unique.params[1] == "City" // City Attack - half the bonus + -> power += (power * unique.params[0].toInt()) / 200 + unique.isOfType(UniqueType.StrengthPlusVs) && unique.params[1] != "City" // Bonus vs something else - a quarter of the bonus + -> power += (power * unique.params[0].toInt()) / 400 + // + // Deprecated since 3.17.4 + unique.isOfType(UniqueType.StrengthAttacking) // Attack - half the bonus + -> power += (power * unique.params[0].toInt()) / 200 + unique.isOfType(UniqueType.StrengthDefending) // Defense - half the bonus + -> power += (power * unique.params[0].toInt()) / 200 // - unique.placeholderText == "+[]% Strength when attacking" // Attack - half the bonus - -> power += (power * unique.params[0].toInt()) / 200 - unique.placeholderText == "+[]% Strength when defending" // Defense - half the bonus - -> power += (power * unique.params[0].toInt()) / 200 unique.placeholderText == "[] additional attacks per turn" // Extra attacks - 20% bonus per extra attack -> power += (power * unique.params[0].toInt()) / 5 unique.placeholderText == "+[]% Strength in []" // Bonus in terrain or feature - half the bonus @@ -649,6 +663,6 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction { } } - cachedForceEvaluation = power + cachedForceEvaluation = power.toInt() } }