mirror of
https://github.com/yairm210/Unciv.git
synced 2025-08-03 04:27:56 -04:00
chore(purity): BattleDamage
This commit is contained in:
parent
d26a38a77d
commit
d074612c0d
@ -55,12 +55,13 @@ allprojects {
|
||||
"kotlin.error",
|
||||
)
|
||||
wellKnownReadonlyFunctions = setOf(
|
||||
// Looks like the Collection.contains is not considered overridden :thunk:
|
||||
"com.badlogic.gdx.math.Vector2.len",
|
||||
"com.badlogic.gdx.math.Vector2.cpy",
|
||||
"com.badlogic.gdx.math.Vector2.hashCode",
|
||||
"java.lang.reflect.Field.getAnnotation", // not sure if generic enough to be useful globally
|
||||
"java.lang.Class.getField",
|
||||
|
||||
// Looks like the Collection.contains is not considered overridden :thunk:
|
||||
"kotlin.collections.Iterable.iterator", // moved
|
||||
"kotlin.collections.Collection.containsAll", // moved
|
||||
"kotlin.collections.filterKeys", // moved
|
||||
|
@ -9,6 +9,8 @@ import com.unciv.models.ruleset.unique.UniqueTarget
|
||||
import com.unciv.models.ruleset.unique.UniqueType
|
||||
import com.unciv.models.translations.tr
|
||||
import com.unciv.ui.components.extensions.toPercent
|
||||
import yairm210.purity.annotations.LocalState
|
||||
import yairm210.purity.annotations.Readonly
|
||||
import kotlin.collections.set
|
||||
import kotlin.math.max
|
||||
import kotlin.math.pow
|
||||
@ -17,6 +19,7 @@ import kotlin.random.Random
|
||||
|
||||
object BattleDamage {
|
||||
|
||||
@Readonly
|
||||
private fun getModifierStringFromUnique(unique: Unique): String {
|
||||
val source = when (unique.sourceObjectType) {
|
||||
UniqueTarget.Unit -> "Unit ability"
|
||||
@ -30,18 +33,23 @@ object BattleDamage {
|
||||
return "$source - $conditionalsText"
|
||||
}
|
||||
|
||||
@Readonly
|
||||
private fun getGeneralModifiers(combatant: ICombatant, enemy: ICombatant, combatAction: CombatAction, tileToAttackFrom: Tile): Counter<String> {
|
||||
val modifiers = Counter<String>()
|
||||
@LocalState val modifiers = Counter<String>()
|
||||
|
||||
val conditionalState = getStateForConditionals(combatAction, combatant, enemy)
|
||||
val civInfo = combatant.getCivInfo()
|
||||
|
||||
if (combatant is MapUnitCombatant) {
|
||||
|
||||
addUnitUniqueModifiers(combatant, enemy, conditionalState, tileToAttackFrom, modifiers)
|
||||
|
||||
addResourceLackingMalus(combatant, modifiers)
|
||||
val unitUniqueModifiers = getUnitUniqueModifiers(combatant, enemy, conditionalState, tileToAttackFrom)
|
||||
modifiers.add(unitUniqueModifiers)
|
||||
|
||||
val civResources = civInfo.getCivResourcesByName()
|
||||
for (resource in combatant.unit.getResourceRequirementsPerTurn().keys)
|
||||
if (civResources[resource]!! < 0 && !civInfo.isBarbarian)
|
||||
modifiers["Missing resource"] = BattleConstants.MISSING_RESOURCES_MALUS
|
||||
|
||||
val (greatGeneralName, greatGeneralBonus) = GreatGeneralImplementation.getGreatGeneralBonus(combatant, enemy, combatAction)
|
||||
if (greatGeneralBonus != 0)
|
||||
modifiers[greatGeneralName] = greatGeneralBonus
|
||||
@ -68,6 +76,7 @@ object BattleDamage {
|
||||
return modifiers
|
||||
}
|
||||
|
||||
@Readonly
|
||||
private fun getStateForConditionals(
|
||||
combatAction: CombatAction,
|
||||
combatant: ICombatant,
|
||||
@ -88,9 +97,11 @@ object BattleDamage {
|
||||
return conditionalState
|
||||
}
|
||||
|
||||
private fun addUnitUniqueModifiers(combatant: MapUnitCombatant, enemy: ICombatant, conditionalState: GameContext,
|
||||
tileToAttackFrom: Tile, modifiers: Counter<String>) {
|
||||
@Readonly
|
||||
private fun getUnitUniqueModifiers(combatant: MapUnitCombatant, enemy: ICombatant, conditionalState: GameContext,
|
||||
tileToAttackFrom: Tile): Counter<String> {
|
||||
val civInfo = combatant.getCivInfo()
|
||||
@LocalState val modifiers = Counter<String>()
|
||||
|
||||
for (unique in combatant.getMatchingUniques(UniqueType.Strength, conditionalState, true)) {
|
||||
modifiers.add(getModifierStringFromUnique(unique), unique.params[0].toInt())
|
||||
@ -124,25 +135,20 @@ object BattleDamage {
|
||||
if (strengthMalus != null) {
|
||||
modifiers.add("Adjacent enemy units", strengthMalus.params[0].toInt())
|
||||
}
|
||||
return modifiers
|
||||
}
|
||||
|
||||
private fun addResourceLackingMalus(combatant: MapUnitCombatant, modifiers: Counter<String>) {
|
||||
val civInfo = combatant.getCivInfo()
|
||||
val civResources = civInfo.getCivResourcesByName()
|
||||
for (resource in combatant.unit.getResourceRequirementsPerTurn().keys)
|
||||
if (civResources[resource]!! < 0 && !civInfo.isBarbarian)
|
||||
modifiers["Missing resource"] = BattleConstants.MISSING_RESOURCES_MALUS
|
||||
}
|
||||
|
||||
@Readonly
|
||||
fun getAttackModifiers(
|
||||
attacker: ICombatant,
|
||||
defender: ICombatant, tileToAttackFrom: Tile
|
||||
): Counter<String> {
|
||||
val modifiers = getGeneralModifiers(attacker, defender, CombatAction.Attack, tileToAttackFrom)
|
||||
@LocalState val modifiers = getGeneralModifiers(attacker, defender, CombatAction.Attack, tileToAttackFrom)
|
||||
|
||||
if (attacker is MapUnitCombatant) {
|
||||
|
||||
addTerrainAttackModifiers(attacker, defender, tileToAttackFrom, modifiers)
|
||||
val terrainAttackModifiers = getTerrainAttackModifiers(attacker, defender, tileToAttackFrom)
|
||||
modifiers.add(terrainAttackModifiers)
|
||||
|
||||
// Air unit attacking with Air Sweep
|
||||
if (attacker.unit.isPreparingAirSweep())
|
||||
@ -171,8 +177,9 @@ object BattleDamage {
|
||||
return modifiers
|
||||
}
|
||||
|
||||
private fun addTerrainAttackModifiers(attacker: MapUnitCombatant, defender: ICombatant,
|
||||
tileToAttackFrom: Tile, modifiers: Counter<String>) {
|
||||
@Readonly
|
||||
private fun getTerrainAttackModifiers(attacker: MapUnitCombatant, defender: ICombatant, tileToAttackFrom: Tile): Counter<String> {
|
||||
@LocalState val modifiers = Counter<String>()
|
||||
if (attacker.unit.isEmbarked() && defender.getTile().isLand
|
||||
&& !attacker.unit.hasUnique(UniqueType.AttackAcrossCoast)
|
||||
)
|
||||
@ -192,8 +199,10 @@ object BattleDamage {
|
||||
|
||||
if (isMeleeAttackingAcrossRiverWithNoBridge(attacker, tileToAttackFrom, defender))
|
||||
modifiers["Across river"] = BattleConstants.ATTACKING_ACROSS_RIVER_MALUS
|
||||
return modifiers
|
||||
}
|
||||
|
||||
@Readonly
|
||||
private fun isMeleeAttackingAcrossRiverWithNoBridge(attacker: MapUnitCombatant, tileToAttackFrom: Tile, defender: ICombatant) = (
|
||||
attacker.isMelee()
|
||||
&&
|
||||
@ -206,10 +215,11 @@ object BattleDamage {
|
||||
|| !attacker.getCivInfo().tech.roadsConnectAcrossRivers)
|
||||
)
|
||||
|
||||
@Readonly
|
||||
fun getAirSweepAttackModifiers(
|
||||
attacker: ICombatant
|
||||
): Counter<String> {
|
||||
val modifiers = Counter<String>()
|
||||
@LocalState val modifiers = Counter<String>()
|
||||
|
||||
if (attacker is MapUnitCombatant) {
|
||||
for (unique in attacker.unit.getMatchingUniques(UniqueType.StrengthWhenAirsweep)) {
|
||||
@ -220,8 +230,9 @@ object BattleDamage {
|
||||
return modifiers
|
||||
}
|
||||
|
||||
@Readonly
|
||||
fun getDefenceModifiers(attacker: ICombatant, defender: ICombatant, tileToAttackFrom: Tile): Counter<String> {
|
||||
val modifiers = getGeneralModifiers(defender, attacker, CombatAction.Defend, tileToAttackFrom)
|
||||
@LocalState val modifiers = getGeneralModifiers(defender, attacker, CombatAction.Defend, tileToAttackFrom)
|
||||
val tile = defender.getTile()
|
||||
|
||||
if (defender is MapUnitCombatant && !defender.unit.isEmbarked()) { // Embarked units get no terrain defensive bonuses
|
||||
@ -239,14 +250,15 @@ object BattleDamage {
|
||||
|
||||
return modifiers
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Readonly
|
||||
private fun modifiersToFinalBonus(modifiers: Counter<String>): Float {
|
||||
var finalModifier = 1f
|
||||
for (modifierValue in modifiers.values) finalModifier += modifierValue / 100f
|
||||
return finalModifier
|
||||
}
|
||||
|
||||
@Readonly
|
||||
private fun getHealthDependantDamageRatio(combatant: ICombatant): Float {
|
||||
return if (combatant !is MapUnitCombatant
|
||||
|| combatant.unit.hasUnique(UniqueType.NoDamagePenaltyWoundedUnits, checkCivInfoUniques = true)
|
||||
@ -259,6 +271,7 @@ object BattleDamage {
|
||||
/**
|
||||
* Includes attack modifiers
|
||||
*/
|
||||
@Readonly
|
||||
fun getAttackingStrength(
|
||||
attacker: ICombatant,
|
||||
defender: ICombatant,
|
||||
@ -272,11 +285,13 @@ object BattleDamage {
|
||||
/**
|
||||
* Includes defence modifiers
|
||||
*/
|
||||
@Readonly
|
||||
fun getDefendingStrength(attacker: ICombatant, defender: ICombatant, tileToAttackFrom: Tile): Float {
|
||||
val defenceModifier = modifiersToFinalBonus(getDefenceModifiers(attacker, defender, tileToAttackFrom))
|
||||
return max(1f, defender.getDefendingStrength(attacker.isRanged()) * defenceModifier)
|
||||
}
|
||||
|
||||
@Readonly
|
||||
fun calculateDamageToAttacker(
|
||||
attacker: ICombatant,
|
||||
defender: ICombatant,
|
||||
@ -291,6 +306,7 @@ object BattleDamage {
|
||||
return (damageModifier(ratio, true, randomnessFactor) * getHealthDependantDamageRatio(defender)).roundToInt()
|
||||
}
|
||||
|
||||
@Readonly
|
||||
fun calculateDamageToDefender(
|
||||
attacker: ICombatant,
|
||||
defender: ICombatant,
|
||||
@ -305,6 +321,7 @@ object BattleDamage {
|
||||
return (damageModifier(ratio, false, randomnessFactor) * getHealthDependantDamageRatio(attacker)).roundToInt()
|
||||
}
|
||||
|
||||
@Readonly
|
||||
private fun damageModifier(
|
||||
attackerToDefenderRatio: Float,
|
||||
damageToAttacker: Boolean,
|
||||
|
@ -6,6 +6,7 @@ import com.unciv.logic.map.tile.Tile
|
||||
import com.unciv.models.ruleset.unique.GameContext
|
||||
import com.unciv.models.ruleset.unique.Unique
|
||||
import com.unciv.models.ruleset.unique.UniqueType
|
||||
import yairm210.purity.annotations.Readonly
|
||||
|
||||
|
||||
object GreatGeneralImplementation {
|
||||
@ -26,6 +27,7 @@ object GreatGeneralImplementation {
|
||||
*
|
||||
* @return A pair of unit's name and bonus (percentage) as Int (typically 15), or 0 if no applicable Great General equivalents found
|
||||
*/
|
||||
@Readonly
|
||||
fun getGreatGeneralBonus(
|
||||
ourUnitCombatant: MapUnitCombatant,
|
||||
enemy: ICombatant,
|
||||
|
@ -8,6 +8,7 @@ import com.unciv.models.ruleset.unique.GameContext
|
||||
import com.unciv.models.ruleset.unique.Unique
|
||||
import com.unciv.models.ruleset.unique.UniqueType
|
||||
import com.unciv.models.ruleset.unit.UnitType
|
||||
import yairm210.purity.annotations.Readonly
|
||||
|
||||
class MapUnitCombatant(val unit: MapUnit) : ICombatant {
|
||||
override fun getHealth(): Int = unit.health
|
||||
@ -46,9 +47,11 @@ class MapUnitCombatant(val unit: MapUnit) : ICombatant {
|
||||
return unit.name+" of "+unit.civ.civName
|
||||
}
|
||||
|
||||
@Readonly
|
||||
fun getMatchingUniques(uniqueType: UniqueType, gameContext: GameContext, checkCivUniques: Boolean): Sequence<Unique> =
|
||||
unit.getMatchingUniques(uniqueType, gameContext, checkCivUniques)
|
||||
|
||||
@Readonly
|
||||
fun hasUnique(uniqueType: UniqueType, conditionalState: GameContext? = null): Boolean =
|
||||
if (conditionalState == null) unit.hasUnique(uniqueType)
|
||||
else unit.hasUnique(uniqueType, conditionalState)
|
||||
|
@ -10,6 +10,7 @@ import com.unciv.models.Religion
|
||||
import com.unciv.models.ruleset.unique.Unique
|
||||
import com.unciv.models.ruleset.unique.UniqueType
|
||||
import com.unciv.ui.components.extensions.toPercent
|
||||
import yairm210.purity.annotations.LocalState
|
||||
import yairm210.purity.annotations.Readonly
|
||||
|
||||
class CityReligionManager : IsPartOfGameInfoSerialization {
|
||||
@ -65,7 +66,7 @@ class CityReligionManager : IsPartOfGameInfoSerialization {
|
||||
}
|
||||
|
||||
|
||||
fun getPressures(): Counter<String> = pressures.clone()
|
||||
@Readonly fun getPressures(): Counter<String> = pressures.clone()
|
||||
|
||||
private fun clearAllPressures() {
|
||||
pressures.clear()
|
||||
@ -261,6 +262,7 @@ class CityReligionManager : IsPartOfGameInfoSerialization {
|
||||
updateNumberOfFollowers()
|
||||
}
|
||||
|
||||
@Readonly
|
||||
private fun getSpreadRange(): Int {
|
||||
var spreadRange = 10
|
||||
|
||||
@ -278,8 +280,9 @@ class CityReligionManager : IsPartOfGameInfoSerialization {
|
||||
}
|
||||
|
||||
/** Doesn't update the pressures, only returns what they are if the update were to happen right now */
|
||||
@Readonly
|
||||
fun getPressuresFromSurroundingCities(): Counter<String> {
|
||||
val addedPressure = Counter<String>()
|
||||
@LocalState val addedPressure = Counter<String>()
|
||||
if (city.isHolyCity()) {
|
||||
addedPressure[religionThisIsTheHolyCityOf!!] = 5 * pressureFromAdjacentCities
|
||||
}
|
||||
@ -299,6 +302,7 @@ class CityReligionManager : IsPartOfGameInfoSerialization {
|
||||
return addedPressure
|
||||
}
|
||||
|
||||
@Readonly
|
||||
fun isProtectedByInquisitor(fromReligion: String? = null): Boolean {
|
||||
for (tile in city.getCenterTile().getTilesInDistance(1)) {
|
||||
for (unit in listOf(tile.civilianUnit, tile.militaryUnit)) {
|
||||
@ -311,6 +315,7 @@ class CityReligionManager : IsPartOfGameInfoSerialization {
|
||||
return false
|
||||
}
|
||||
|
||||
@Readonly
|
||||
private fun pressureAmountToAdjacentCities(pressuredCity: City): Int {
|
||||
var pressure = pressureFromAdjacentCities.toFloat()
|
||||
|
||||
@ -331,7 +336,10 @@ class CityReligionManager : IsPartOfGameInfoSerialization {
|
||||
return pressure.toInt()
|
||||
}
|
||||
|
||||
/** Calculates how much pressure this religion is lacking compared to the majority religion */
|
||||
@Readonly
|
||||
fun getPressureDeficit(otherReligion: String?): Int {
|
||||
return (getPressures()[getMajorityReligionName()] ?: 0) - (getPressures()[otherReligion] ?: 0)
|
||||
val pressures = getPressures()
|
||||
return (pressures[getMajorityReligionName()] ?: 0) - (pressures[otherReligion] ?: 0)
|
||||
}
|
||||
}
|
||||
|
@ -318,6 +318,7 @@ object TranslationActiveModsCache {
|
||||
* defaults to the input string if no translation is available,
|
||||
* but with placeholder or sentence brackets removed.
|
||||
*/
|
||||
@Readonly @Suppress("purity")
|
||||
fun String.tr(hideIcons: Boolean = false, hideStats: Boolean = false): String {
|
||||
val language: String = UncivGame.Current.settings.language
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user