Typed some uniques and fixed a policy not working (#5848)

* Typed some uniques, fixing a policy not working

* Repurposed an unused function to remove .unit in some places

* Fixed compilation errors
This commit is contained in:
Xander Lenstra
2021-12-25 16:55:14 +01:00
committed by GitHub
parent ea51c7155b
commit ecadfb53fa
11 changed files with 136 additions and 52 deletions

View File

@ -106,7 +106,7 @@
},
{
"name": "Military Tradition",
"uniques":["[Military] units gain [50]% more Experience from combat"],
"uniques":["[+50]% XP gained from combat <for [Military] units>"],
"requires": ["Warrior Code"],
"row": 2,
"column": 2

View File

@ -517,7 +517,7 @@
},
{
"name": "Quick Study", // only for Keshik and subsequent upgrades
"uniques": ["[50]% Bonus XP gain"]
"uniques": ["[+50]% XP gained from combat"]
},
{
"name": "Haka War Dance", // only for Maori Warrior and subsequent upgrades

View File

@ -107,7 +107,7 @@
},
{
"name": "Military Tradition",
"uniques":["[Military] units gain [50]% more Experience from combat"],
"uniques":["[+50]% XP gained from combat <for [Military] units>"],
"requires": ["Warrior Code"],
"row": 2,
"column": 2

View File

@ -517,7 +517,7 @@
},
{
"name": "Quick Study", // only for Keshik and subsequent upgrades
"uniques": ["[50]% Bonus XP gain"]
"uniques": ["[+50]% XP gained from combat"]
},
{
"name": "Haka War Dance", // only for Maori Warrior and subsequent upgrades

View File

@ -10,6 +10,7 @@ import com.unciv.logic.map.RoadStatus
import com.unciv.logic.map.TileInfo
import com.unciv.models.AttackableTile
import com.unciv.models.UnitActionType
import com.unciv.models.ruleset.unique.StateForConditionals
import com.unciv.models.ruleset.unique.Unique
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.stats.Stat
@ -39,7 +40,7 @@ object Battle {
*/
if (attacker.unit.currentMovement == 0f)
return
if (attacker.unit.hasUnique(UniqueType.MustSetUp) && !attacker.unit.isSetUpForSiege()) {
if (attacker.hasUnique(UniqueType.MustSetUp) && !attacker.unit.isSetUpForSiege()) {
attacker.unit.action = UnitActionType.SetUp.value
attacker.unit.useMovementPoints(1f)
}
@ -131,25 +132,22 @@ object Battle {
private fun tryEarnFromKilling(civUnit: ICombatant, defeatedUnit: MapUnitCombatant) {
val unitStr = max(defeatedUnit.unit.baseUnit.strength, defeatedUnit.unit.baseUnit.rangedStrength)
val unitCost = defeatedUnit.unit.baseUnit.cost
var bonusUniquePlaceholderText = "Earn []% of killed [] unit's [] as []"
val bonusUniques = ArrayList<Unique>()
val stateForConditionals = StateForConditionals(civInfo = civUnit.getCivInfo(), ourCombatant = civUnit, theirCombatant = defeatedUnit)
if (civUnit is MapUnitCombatant) {
bonusUniques.addAll(civUnit.getMatchingUniques(bonusUniquePlaceholderText))
bonusUniques.addAll(civUnit.getCivInfo().getMatchingUniques(bonusUniquePlaceholderText))
bonusUniques.addAll(civUnit.getMatchingUniques(UniqueType.KillUnitPlunder, stateForConditionals, true))
} else {
bonusUniques.addAll(civUnit.getCivInfo().getMatchingUniques(bonusUniquePlaceholderText))
bonusUniques.addAll(civUnit.getCivInfo().getMatchingUniques(UniqueType.KillUnitPlunder, stateForConditionals))
}
bonusUniquePlaceholderText = "Earn []% of [] unit's [] as [] when killed within 4 tiles of a city following this religion"
val cityWithReligion =
civUnit.getTile().getTilesInDistance(4).firstOrNull {
it.isCityCenter() && it.getCity()!!.getMatchingUniques(bonusUniquePlaceholderText).any()
it.isCityCenter() && it.getCity()!!.getLocalMatchingUniques(UniqueType.KillUnitPlunderNearCity, stateForConditionals).any()
}?.getCity()
if (cityWithReligion != null) {
bonusUniques.addAll(cityWithReligion.getLocalMatchingUniques(bonusUniquePlaceholderText))
bonusUniques.addAll(cityWithReligion.getLocalMatchingUniques(UniqueType.KillUnitPlunderNearCity, stateForConditionals))
}
for (unique in bonusUniques) {
@ -405,29 +403,43 @@ object Battle {
// XP!
private fun addXp(thisCombatant: ICombatant, amount: Int, otherCombatant: ICombatant) {
var baseXP = amount
if (thisCombatant !is MapUnitCombatant) return
if (thisCombatant.unit.promotions.totalXpProduced() >= thisCombatant.unit.civInfo.gameInfo.ruleSet.modOptions.maxXPfromBarbarians
&& otherCombatant.getCivInfo().isBarbarian())
&& otherCombatant.getCivInfo().isBarbarian()
) {
return
}
val stateForConditionals = StateForConditionals(civInfo = thisCombatant.getCivInfo(), ourCombatant = thisCombatant, theirCombatant = otherCombatant)
for (unique in thisCombatant.getMatchingUniques(UniqueType.FlatXPGain, stateForConditionals, true))
baseXP += unique.params[0].toInt()
var xpModifier = 1f
for (unique in thisCombatant.getCivInfo().getMatchingUniques("[] units gain []% more Experience from combat")) {
if (thisCombatant.unit.matchesFilter(unique.params[0]))
xpModifier += unique.params[1].toFloat() / 100
}
for (unique in thisCombatant.unit.getMatchingUniques("[]% Bonus XP gain"))
// Deprecated since 3.18.12
for (unique in thisCombatant.getCivInfo().getMatchingUniques(UniqueType.BonusXPGainForUnits, stateForConditionals)) {
if (thisCombatant.unit.matchesFilter(unique.params[0]))
xpModifier += unique.params[1].toFloat() / 100
}
for (unique in thisCombatant.getMatchingUniques(UniqueType.BonuxXPGain, stateForConditionals, true))
xpModifier += unique.params[0].toFloat() / 100
//
for (unique in thisCombatant.getMatchingUniques(UniqueType.PercentageXPGain, stateForConditionals, true))
xpModifier += unique.params[0].toFloat() / 100
val xpGained = (amount * xpModifier).toInt()
val xpGained = (baseXP * xpModifier).toInt()
thisCombatant.unit.promotions.XP += xpGained
if (thisCombatant.getCivInfo().isMajorCiv() && !otherCombatant.getCivInfo().isBarbarian()) { // Can't get great generals from Barbarians
var greatGeneralPointsModifier = 1f
for (unique in thisCombatant.getMatchingUniques("[] is earned []% faster")) {
for (unique in thisCombatant.getMatchingUniques(UniqueType.GreatPersonEarnedFaster, stateForConditionals, true)) {
val unitName = unique.params[0]
val unit = thisCombatant.getCivInfo().gameInfo.ruleSet.units[unitName]
if (unit != null && unit.uniques.contains("Great Person - [War]"))
// From the unique we know this unit exists
val unit = thisCombatant.getCivInfo().gameInfo.ruleSet.units[unitName]!!
if (unit.uniques.contains("Great Person - [War]"))
greatGeneralPointsModifier += unique.params[1].toFloat() / 100
}
@ -438,7 +450,8 @@ object Battle {
private fun conquerCity(city: CityInfo, attacker: MapUnitCombatant) {
val attackerCiv = attacker.getCivInfo()
attackerCiv.addNotification("We have conquered the city of [${city.name}]!", city.location, NotificationIcon.War)
city.hasJustBeenConquered = true
@ -448,7 +461,8 @@ object Battle {
for (airUnit in airUnits.toList()) airUnit.destroy()
}
for (unique in attacker.getMatchingUniques("Upon capturing a city, receive [] times its [] production as [] immediately")) {
val stateForConditionals = StateForConditionals(civInfo = attackerCiv, unit = attacker.unit, ourCombatant = attacker, attackedTile = city.getCenterTile())
for (unique in attacker.getMatchingUniques(UniqueType.CaptureCityPlunder, stateForConditionals, true)) {
attackerCiv.addStat(
Stat.valueOf(unique.params[2]),
unique.params[0].toInt() * city.cityStats.currentCityStats[Stat.valueOf(unique.params[1])].toInt()
@ -548,9 +562,10 @@ object Battle {
fun mayUseNuke(nuke: MapUnitCombatant, targetTile: TileInfo): Boolean {
val blastRadius =
if (!nuke.unit.hasUnique(UniqueType.BlastRadius)) 2
if (!nuke.hasUnique(UniqueType.BlastRadius)) 2
// Don't check conditionals as these are not supported
else nuke.unit.getMatchingUniques(UniqueType.BlastRadius).first().params[0].toInt()
var canNuke = true
val attackerCiv = nuke.getCivInfo()
for (tile in targetTile.getTilesInDistance(blastRadius)) {
@ -582,7 +597,8 @@ object Battle {
}
val blastRadius =
if (!attacker.unit.hasUnique(UniqueType.BlastRadius)) 2
if (!attacker.hasUnique(UniqueType.BlastRadius)) 2
// Don't check conditionals as there are not supported
else attacker.unit.getMatchingUniques(UniqueType.BlastRadius).first().params[0].toInt()
val strength = when {

View File

@ -42,14 +42,13 @@ object BattleDamage {
combatAction = combatAction, attackedTile = attackedTile
)
for (unique in combatant.unit.getMatchingUniques(
for (unique in combatant.getMatchingUniques(
UniqueType.Strength, conditionalState, true
)) {
modifiers.add(getModifierStringFromUnique(unique), unique.params[0].toInt())
}
for (unique in combatant.unit.getMatchingUniques(
UniqueType.StrengthNearCapital,
checkCivInfoUniques = true
for (unique in combatant.getMatchingUniques(
UniqueType.StrengthNearCapital, conditionalState, true
)) {
if (civInfo.cities.isEmpty()) break
val distance =

View File

@ -4,7 +4,9 @@ import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.map.MapUnit
import com.unciv.logic.map.TileInfo
import com.unciv.models.UncivSound
import com.unciv.models.ruleset.unique.StateForConditionals
import com.unciv.models.ruleset.unique.Unique
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.ruleset.unit.UnitType
class MapUnitCombatant(val unit: MapUnit) : ICombatant {
@ -44,6 +46,10 @@ class MapUnitCombatant(val unit: MapUnit) : ICombatant {
return unit.name+" of "+unit.civInfo.civName
}
fun getMatchingUniques(uniqueTemplate: String): Sequence<Unique> = unit.getMatchingUniques(uniqueTemplate)
fun getMatchingUniques(uniqueType: UniqueType, conditionalState: StateForConditionals, checkCivUniques: Boolean): Sequence<Unique> =
unit.getMatchingUniques(uniqueType, conditionalState, checkCivUniques)
fun hasUnique(uniqueType: UniqueType, conditionalState: StateForConditionals? = null): Boolean =
if (conditionalState == null) unit.hasUnique(uniqueType)
else unit.hasUnique(uniqueType, conditionalState)
}

View File

@ -425,8 +425,9 @@ class CityInfo {
buildingsCounter.add(building.greatPersonPoints)
sourceToGPP["Buildings"] = buildingsCounter
val stateForConditionals = StateForConditionals(civInfo = civInfo, cityInfo = this)
for ((_, gppCounter) in sourceToGPP) {
for (unique in civInfo.getMatchingUniques("[] is earned []% faster")) {
for (unique in civInfo.getMatchingUniques(UniqueType.GreatPersonEarnedFaster, stateForConditionals)) {
val unitName = unique.params[0]
if (!gppCounter.containsKey(unitName)) continue
gppCounter.add(unitName, gppCounter[unitName]!! * unique.params[1].toInt() / 100)
@ -440,9 +441,8 @@ class CityInfo {
// Sweden UP
for (otherCiv in civInfo.getKnownCivs()) {
if (!civInfo.getDiplomacyManager(otherCiv)
.hasFlag(DiplomacyFlags.DeclarationOfFriendship)
) continue
if (!civInfo.getDiplomacyManager(otherCiv).hasFlag(DiplomacyFlags.DeclarationOfFriendship))
continue
for (ourUnique in civInfo.getMatchingUniques("When declaring friendship, both parties gain a []% boost to great person generation"))
allGppPercentageBonus += ourUnique.params[0].toInt()

View File

@ -60,6 +60,15 @@ enum class UniqueParameterType(val parameterName:String) {
return UniqueType.UniqueComplianceErrorSeverity.WarningOnly
}
},
GreatPerson("greatPerson") {
override fun getErrorSeverity(
parameterText: String,
ruleset: Ruleset
): UniqueType.UniqueComplianceErrorSeverity? {
return if (parameterText in ruleset.units && ruleset.units[parameterText]!!.hasUnique("Great Person - []")) null
else UniqueType.UniqueComplianceErrorSeverity.RulesetSpecific
}
},
Stats("stats") {
override fun getErrorSeverity(parameterText: String, ruleset: Ruleset):
UniqueType.UniqueComplianceErrorSeverity? {
@ -265,6 +274,16 @@ enum class UniqueParameterType(val parameterName:String) {
else UniqueType.UniqueComplianceErrorSeverity.RulesetInvariant
}
},
CostOrStrength("costOrStrength") {
private val knownValues = setOf("Cost", "Strength")
override fun getErrorSeverity(
parameterText: String,
ruleset: Ruleset
): UniqueType.UniqueComplianceErrorSeverity? {
return if (parameterText in knownValues) null
else UniqueType.UniqueComplianceErrorSeverity.RulesetInvariant
}
},
/** Behaves like [Unknown], but states explicitly the parameter is OK and its contents are ignored */
Comment("comment") {
override fun getErrorSeverity(parameterText: String, ruleset: Ruleset):

View File

@ -223,7 +223,7 @@ enum class UniqueType(val text:String, vararg targets: UniqueTarget, val flags:
CanSeeInvisibleUnits("Can see invisible [mapUnitFilter] units", UniqueTarget.Unit),
Strength("[amount]% Strength", UniqueTarget.Unit, UniqueTarget.Global),
StrengthNearCapital("[amount]% Strength decreasing with distance from the capital", UniqueTarget.Unit),
StrengthNearCapital("[amount]% Strength decreasing with distance from the capital", UniqueTarget.Unit, UniqueTarget.Global),
Movement("[amount] Movement", UniqueTarget.Unit, UniqueTarget.Global),
@ -244,8 +244,16 @@ enum class UniqueType(val text:String, vararg targets: UniqueTarget, val flags:
CarryExtraAirUnits("Can carry [amount] extra [mapUnitFilter] units", UniqueTarget.Unit),
CannotBeCarriedBy("Cannot be carried by [mapUnitFilter] units", UniqueTarget.Unit),
UnitMaintenanceDiscount("[amount]% maintenance costs", UniqueTarget.Unit),
UnitMaintenanceDiscount("[amount]% maintenance costs", UniqueTarget.Unit, UniqueTarget.Global),
GreatPersonEarnedFaster("[greatPerson] is earned [amount]% faster", UniqueTarget.Unit, UniqueTarget.Global),
CaptureCityPlunder("Upon capturing a city, receive [amount] times its [stat] production as [stat] immediately", UniqueTarget.Unit, UniqueTarget.Global),
KillUnitPlunder("Earn [amount]% of killed [mapUnitFilter] unit's [costOrStrength] as [stat]", UniqueTarget.Unit, UniqueTarget.Global),
KillUnitPlunderNearCity("Earn [amount]% of [mapUnitFilter] unit's [costOrStrength] as [stat] when killed within 4 tiles of a city following this religion", UniqueTarget.FollowerBelief),
FlatXPGain("[amount] XP gained from combat", UniqueTarget.Unit, UniqueTarget.Global),
PercentageXPGain("[amount]% XP gained from combat", UniqueTarget.Unit, UniqueTarget.Global),
// The following block gets cached in MapUnit for faster getMovementCostBetweenAdjacentTiles
DoubleMovementOnTerrain("Double movement in [terrainFilter]", UniqueTarget.Unit),
AllTilesCost1Move("All tiles cost 1 movement", UniqueTarget.Unit),
@ -554,7 +562,11 @@ enum class UniqueType(val text:String, vararg targets: UniqueTarget, val flags:
@Deprecated("As of 3.16.16 - removed 3.17.11", ReplaceWith("[stats] <if this city has at least [amount] specialists>"), DeprecationLevel.ERROR)
StatBonusForNumberOfSpecialists("[stats] if this city has at least [amount] specialists", UniqueTarget.Global),
@Deprecated("As of 3.18.12", ReplaceWith("[amount]% XP gained from combat"))
BonuxXPGain("[amount]% Bonus XP gain", UniqueTarget.Unit),
@Deprecated("As of 3.18.12", ReplaceWith("[amount]% XP gained from combat <for [mapUnitFilter] units>"))
BonusXPGainForUnits("[mapUnitFilter] units gain [amount]% more Experience from combat", UniqueTarget.Global),
// endregion
;

View File

@ -220,6 +220,11 @@ Example: "[20]% Strength"
Applicable to: Global, Unit
#### [amount]% Strength decreasing with distance from the capital
Example: "[20]% Strength decreasing with distance from the capital"
Applicable to: Global, Unit
#### [amount] Movement
Example: "[20] Movement"
@ -238,6 +243,36 @@ Applicable to: Global, Unit
#### Normal vision when embarked
Applicable to: Global, Unit
#### [amount]% maintenance costs
Example: "[20]% maintenance costs"
Applicable to: Global, Unit
#### [greatPerson] is earned [amount]% faster
Example: "[greatPerson] is earned [20]% faster"
Applicable to: Global, Unit
#### Upon capturing a city, receive [amount] times its [stat] production as [stat] immediately
Example: "Upon capturing a city, receive [20] times its [Culture] production as [Culture] immediately"
Applicable to: Global, Unit
#### Earn [amount]% of killed [mapUnitFilter] unit's [costOrStrength] as [stat]
Example: "Earn [20]% of killed [Wounded] unit's [costOrStrength] as [Culture]"
Applicable to: Global, Unit
#### [amount] XP gained from combat
Example: "[20] XP gained from combat"
Applicable to: Global, Unit
#### [amount]% XP gained from combat
Example: "[20]% XP gained from combat"
Applicable to: Global, Unit
#### Free [baseUnitFilter] appears
Example: "Free [Melee] appears"
@ -337,6 +372,11 @@ Example: "[20]% [Culture] from every follower, up to [20]%"
Applicable to: FollowerBelief
#### Earn [amount]% of [mapUnitFilter] unit's [costOrStrength] as [stat] when killed within 4 tiles of a city following this religion
Example: "Earn [20]% of [Wounded] unit's [costOrStrength] as [Culture] when killed within 4 tiles of a city following this religion"
Applicable to: FollowerBelief
## Building uniques
#### Remove extra unhappiness from annexed cities
Applicable to: Building
@ -450,11 +490,6 @@ Example: "Can see invisible [Wounded] units"
Applicable to: Unit
#### [amount]% Strength decreasing with distance from the capital
Example: "[20]% Strength decreasing with distance from the capital"
Applicable to: Unit
#### May found a religion
Applicable to: Unit
@ -490,11 +525,6 @@ Example: "Cannot be carried by [Wounded] units"
Applicable to: Unit
#### [amount]% maintenance costs
Example: "[20]% maintenance costs"
Applicable to: Unit
#### Double movement in [terrainFilter]
Example: "Double movement in [Grassland]"
@ -1051,6 +1081,7 @@ Applicable to: Conditional
- "+[amount]% Production when constructing [constructionFilter] [cityFilter]" - Deprecated As of 3.17.10 - removed 3.18.5, replace with "[amount]% Production when constructing [buildingFilter] buildings [cityFilter]"
- "[stats] from every specialist" - Deprecated As of 3.16.16 - removed 3.17.11, replace with "[stats] from every specialist [in all cities]"
- "[stats] if this city has at least [amount] specialists" - Deprecated As of 3.16.16 - removed 3.17.11, replace with "[stats] <if this city has at least [amount] specialists>"
- "[mapUnitFilter] units gain [amount]% more Experience from combat" - Deprecated As of 3.18.12, replace with "[amount]% XP gained from combat <for [mapUnitFilter] units>"
- "Not displayed as an available construction unless [buildingName] is built" - Deprecated As of 3.16.11, replace with "Not displayed as an available construction without [buildingName]"
- "[stats] once [tech] is discovered" - Deprecated As of 3.17.10, replace with "[stats] <after discovering [tech]>"
- "Double movement in coast" - Deprecated As of 3.17.1 - removed 3.17.13, replace with "Double movement in [terrainFilter]"
@ -1068,5 +1099,6 @@ Applicable to: Conditional
- "+[amount]% Strength in [tileFilter]" - Deprecated As of 3.17.5 - removed 3.18.5, replace with "[amount]% Strength <when fighting in [tileFilter] tiles>"
- "[amount] Visibility Range" - Deprecated As of 3.17.5 - removed 3.18.5, replace with "[amount] Sight"
- "Limited Visibility" - Deprecated As of 3.17.5 - removed 3.18.5, replace with "[-1] Sight"
- "[amount]% Bonus XP gain" - Deprecated As of 3.18.12, replace with "[amount]% XP gained from combat"
- "[stats] on [tileFilter] tiles once [tech] is discovered" - Deprecated As of 3.17.10, replace with "[stats] from [tileFilter] tiles <after discovering [tech]>"
- "Deal 30 damage to adjacent enemy units" - Deprecated As of 3.17.10, replace with "Adjacent enemy units ending their turn take [30] damage"