From 6d48f99206fd12dc9eef6331128b55ce45702e1a Mon Sep 17 00:00:00 2001 From: Yair Morgenstern Date: Sat, 19 Feb 2022 19:38:39 +0200 Subject: [PATCH] Trigger uniques by sacrificing units with conditional (#6157) * Triggered uniques currently come from researching techs, adopting policies, and building buildings. This adds a third way of triggering uniques, by attaching them to units with a "by consuming this unit" conditional, which will be added as a possible unit action. So for example, "[amount] Free Technologies ", "Reveals the entire map " etc. * Added a new uniquetype for triggerable uniques, to make them separate from regular global uniques, so that triggerable uniques can become unit uniques when necessary * And added translations so the tests will pass --- .../jsons/translations/template.properties | 1 + core/src/com/unciv/models/UnitAction.kt | 2 + core/src/com/unciv/models/ruleset/Ruleset.kt | 6 ++- .../com/unciv/models/ruleset/unique/Unique.kt | 6 +++ .../unciv/models/ruleset/unique/UniqueType.kt | 37 ++++++++++--------- .../unciv/ui/worldscreen/unit/UnitActions.kt | 15 +++++++- .../src/com/unciv/testing/TranslationTests.kt | 2 +- 7 files changed, 49 insertions(+), 20 deletions(-) diff --git a/android/assets/jsons/translations/template.properties b/android/assets/jsons/translations/template.properties index 2e73863226..1d7156e2f5 100644 --- a/android/assets/jsons/translations/template.properties +++ b/android/assets/jsons/translations/template.properties @@ -740,6 +740,7 @@ Pillage = Are you sure you want to pillage this [improvement]? = Create [improvement] = Start Golden Age = +Trigger unique = Show more = Yes = No = diff --git a/core/src/com/unciv/models/UnitAction.kt b/core/src/com/unciv/models/UnitAction.kt index 32dabb03eb..7f4cb361a0 100644 --- a/core/src/com/unciv/models/UnitAction.kt +++ b/core/src/com/unciv/models/UnitAction.kt @@ -124,6 +124,8 @@ enum class UnitActionType( { ImageGetter.getUnitIcon("Great Merchant") }, 'g', UncivSound.Chimes), FoundReligion("Found a Religion", { ImageGetter.getUnitIcon("Great Prophet") }, 'g', UncivSound.Choir), + TriggerUnique("Trigger unique", + { ImageGetter.getImage("OtherIcons/Star") }, 'g', UncivSound.Chimes), SpreadReligion("Spread Religion", null, 'g', UncivSound.Choir), RemoveHeresy("Remove Heresy", diff --git a/core/src/com/unciv/models/ruleset/Ruleset.kt b/core/src/com/unciv/models/ruleset/Ruleset.kt index 2dce9adae2..8b7f05c709 100644 --- a/core/src/com/unciv/models/ruleset/Ruleset.kt +++ b/core/src/com/unciv/models/ruleset/Ruleset.kt @@ -417,7 +417,11 @@ class Ruleset { rulesetErrors.add(deprecationText, severity) } - if (unique.type.targetTypes.none { uniqueTarget.canAcceptUniqueTarget(it) }) + if (unique.type.targetTypes.none { uniqueTarget.canAcceptUniqueTarget(it) } + // the 'consume unit' conditional causes a triggerable unique to become a unit action + && !(uniqueTarget==UniqueTarget.Unit + && unique.isTriggerable + && unique.conditionals.any { it.type == UniqueType.ConditionalConsumeUnit })) rulesetErrors.add( "$name's unique \"${unique.text}\" cannot be put on this type of object!", RulesetErrorSeverity.Warning diff --git a/core/src/com/unciv/models/ruleset/unique/Unique.kt b/core/src/com/unciv/models/ruleset/unique/Unique.kt index 00cdac9da9..f48d349d9d 100644 --- a/core/src/com/unciv/models/ruleset/unique/Unique.kt +++ b/core/src/com/unciv/models/ruleset/unique/Unique.kt @@ -24,6 +24,9 @@ class Unique(val text: String, val sourceObjectType: UniqueTarget? = null, val s else Stats.parse(firstStatParam) } val conditionals: List = text.getConditionals() + val isTriggerable = type != null && type.targetTypes.contains(UniqueTarget.Triggerable) + // in effect makes any unique become a triggerable unique + || conditionals.any { it.type == UniqueType.ConditionalTimedUnique } val allParams = params + conditionals.flatMap { it.params } @@ -86,7 +89,10 @@ class Unique(val text: String, val sourceObjectType: UniqueTarget? = null, val s } return when (condition.type) { + // These are 'what to do' and not 'when to do' conditionals UniqueType.ConditionalTimedUnique -> true + UniqueType.ConditionalConsumeUnit -> true + UniqueType.ConditionalWar -> state.civInfo?.isAtWar() == true UniqueType.ConditionalNotWar -> state.civInfo?.isAtWar() == false UniqueType.ConditionalWithResource -> state.civInfo?.hasResource(condition.params[0]) == true diff --git a/core/src/com/unciv/models/ruleset/unique/UniqueType.kt b/core/src/com/unciv/models/ruleset/unique/UniqueType.kt index b421a8e915..a82d4ebd88 100644 --- a/core/src/com/unciv/models/ruleset/unique/UniqueType.kt +++ b/core/src/com/unciv/models/ruleset/unique/UniqueType.kt @@ -10,9 +10,11 @@ import kotlin.collections.HashSet * For example, all Global uniques are acceptable for Nations, Eras, etc. */ enum class UniqueTarget(val inheritsFrom: UniqueTarget? = null) { + /** Only includes uniques that have immediate effects, caused by UniqueTriggerActivation */ + Triggerable, /** Buildings, units, nations, policies, religions, techs etc. * Basically anything caught by CivInfo.getMatchingUniques. */ - Global, + Global(Triggerable), // Civilization-specific Nation(Global), @@ -41,7 +43,7 @@ enum class UniqueTarget(val inheritsFrom: UniqueTarget? = null) { Terrain, Improvement, Resource(Global), - Ruins, + Ruins(Triggerable), // Other CityState, @@ -595,6 +597,7 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags: ConditionalNoPolicy("before adopting [policy]", UniqueTarget.Conditional), ConditionalTimedUnique("for [amount] turns", UniqueTarget.Conditional), + ConditionalConsumeUnit("by consuming this unit", UniqueTarget.Conditional), /////// city conditionals ConditionalCityWithBuilding("in cities with a [buildingFilter]", UniqueTarget.Conditional), @@ -635,19 +638,19 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags: ///////////////////////////////////////// region TRIGGERED ONE-TIME ///////////////////////////////////////// - OneTimeFreeUnit("Free [baseUnitFilter] appears", UniqueTarget.Global), // used in Policies, Buildings - OneTimeAmountFreeUnits("[amount] free [baseUnitFilter] units appear", UniqueTarget.Global), // used in Buildings + OneTimeFreeUnit("Free [baseUnitFilter] appears", UniqueTarget.Triggerable), // used in Policies, Buildings + OneTimeAmountFreeUnits("[amount] free [baseUnitFilter] units appear", UniqueTarget.Triggerable), // used in Buildings OneTimeFreeUnitRuins("Free [baseUnitFilter] found in the ruins", UniqueTarget.Ruins), // Differs from "Free [] appears" in that it spawns near the ruins instead of in a city - OneTimeFreePolicy("Free Social Policy", UniqueTarget.Global), // used in Buildings - OneTimeAmountFreePolicies("[amount] Free Social Policies", UniqueTarget.Global), // Not used in Vanilla - OneTimeEnterGoldenAge("Empire enters golden age", UniqueTarget.Global), // used in Policies, Buildings - OneTimeFreeGreatPerson("Free Great Person", UniqueTarget.Global), // used in Policies, Buildings - OneTimeGainPopulation("[amount] population [cityFilter]", UniqueTarget.Global), // used in CN tower + OneTimeFreePolicy("Free Social Policy", UniqueTarget.Triggerable), // used in Buildings + OneTimeAmountFreePolicies("[amount] Free Social Policies", UniqueTarget.Triggerable), // Not used in Vanilla + OneTimeEnterGoldenAge("Empire enters golden age", UniqueTarget.Triggerable), // used in Policies, Buildings + OneTimeFreeGreatPerson("Free Great Person", UniqueTarget.Triggerable), // used in Policies, Buildings + OneTimeGainPopulation("[amount] population [cityFilter]", UniqueTarget.Triggerable), // used in CN tower OneTimeGainPopulationRandomCity("[amount] population in a random city", UniqueTarget.Ruins), - OneTimeFreeTech("Free Technology", UniqueTarget.Global), // used in Buildings - OneTimeAmountFreeTechs("[amount] Free Technologies", UniqueTarget.Global), // used in Policy + OneTimeFreeTech("Free Technology", UniqueTarget.Triggerable), // used in Buildings + OneTimeAmountFreeTechs("[amount] Free Technologies", UniqueTarget.Triggerable), // used in Policy OneTimeFreeTechRuins("[amount] free random researchable Tech(s) from the [era]", UniqueTarget.Ruins), // todo: Not picked up by TranslationFileWriter? - OneTimeRevealEntireMap("Reveals the entire map", UniqueTarget.Global), // used in tech + OneTimeRevealEntireMap("Reveals the entire map", UniqueTarget.Triggerable), // used in tech OneTimeGainStat("Gain [amount] [stat]", UniqueTarget.Ruins), OneTimeGainStatRange("Gain [amount]-[amount] [stat]", UniqueTarget.Ruins), OneTimeGainPantheon("Gain enough Faith for a Pantheon", UniqueTarget.Ruins), @@ -655,20 +658,20 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags: // todo: The "up to [All]" used in vanilla json is not nice to read. Split? OneTimeRevealSpecificMapTiles("Reveal up to [amount/'all'] [tileFilter] within a [amount] tile radius", UniqueTarget.Ruins), OneTimeRevealCrudeMap("From a randomly chosen tile [amount] tiles away from the ruins, reveal tiles up to [amount] tiles away with [amount]% chance", UniqueTarget.Ruins), - OneTimeTriggerVoting("Triggers voting for the Diplomatic Victory", UniqueTarget.Global), // used in Building + OneTimeTriggerVoting("Triggers voting for the Diplomatic Victory", UniqueTarget.Triggerable), // used in Building OneTimeUnitHeal("Heal this unit by [amount] HP", UniqueTarget.Promotion), OneTimeUnitGainXP("This Unit gains [amount] XP", UniqueTarget.Ruins), OneTimeUnitUpgrade("This Unit upgrades for free", UniqueTarget.Global), // Not used in Vanilla OneTimeUnitSpecialUpgrade("This Unit upgrades for free including special upgrades", UniqueTarget.Ruins), - OneTimeUnitGainPromotion("This Unit gains the [promotion] promotion", UniqueTarget.Global), // Not used in Vanilla + OneTimeUnitGainPromotion("This Unit gains the [promotion] promotion", UniqueTarget.Triggerable), // Not used in Vanilla - UnitsGainPromotion("[mapUnitFilter] units gain the [promotion] promotion", UniqueTarget.Global), // Not used in Vanilla + UnitsGainPromotion("[mapUnitFilter] units gain the [promotion] promotion", UniqueTarget.Triggerable), // Not used in Vanilla // todo: remove forced sign @Deprecated("as of 3.19.8", ReplaceWith("[+amount]% Strength ")) TimedAttackStrength("+[amount]% attack strength to all [mapUnitFilter] units for [amount2] turns", UniqueTarget.Global), // used in Policy - FreeStatBuildings("Provides the cheapest [stat] building in your first [amount] cities for free", UniqueTarget.Global), // used in Policy - FreeSpecificBuildings("Provides a [buildingName] in your first [amount] cities for free", UniqueTarget.Global), // used in Policy + FreeStatBuildings("Provides the cheapest [stat] building in your first [amount] cities for free", UniqueTarget.Triggerable), // used in Policy + FreeSpecificBuildings("Provides a [buildingName] in your first [amount] cities for free", UniqueTarget.Triggerable), // used in Policy //endregion diff --git a/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt b/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt index bf3ef32b39..c2309de3df 100644 --- a/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt +++ b/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt @@ -16,6 +16,7 @@ import com.unciv.models.UncivSound import com.unciv.models.UnitAction import com.unciv.models.UnitActionType import com.unciv.models.ruleset.Building +import com.unciv.models.ruleset.unique.UniqueTriggerActivation import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.stats.Stat import com.unciv.models.stats.Stats @@ -64,7 +65,7 @@ object UnitActions { addActionsWithLimitedUses(unit, actionList, tile) addExplorationActions(unit, actionList) addAutomateBuildingImprovementsAction(unit, actionList) - + addTriggerUniqueActions(unit, actionList) addToggleActionsAction(unit, actionList, unitTable) @@ -813,6 +814,18 @@ object UnitActions { return UnitAction(UnitActionType.GiftUnit, action = giftAction) } + + fun addTriggerUniqueActions(unit: MapUnit, actionList: ArrayList){ + for (unique in unit.getUniques()) { + if (!unique.conditionals.any { it.type == UniqueType.ConditionalConsumeUnit }) continue + val unitAction = UnitAction(type = UnitActionType.TriggerUnique, unique.text){ + UniqueTriggerActivation.triggerCivwideUnique(unique, unit.civInfo) + addStatsPerGreatPersonUsage(unit) + unit.destroy() + } + actionList += unitAction + } + } private fun addToggleActionsAction(unit: MapUnit, actionList: ArrayList, unitTable: UnitTable) { actionList += UnitAction( diff --git a/tests/src/com/unciv/testing/TranslationTests.kt b/tests/src/com/unciv/testing/TranslationTests.kt index 2790186152..632ea973e0 100644 --- a/tests/src/com/unciv/testing/TranslationTests.kt +++ b/tests/src/com/unciv/testing/TranslationTests.kt @@ -65,7 +65,7 @@ class TranslationTests { val key = if (entry.contains('[')) entry.replace(squareBraceRegex, "[]") else entry if (!translations.containsKey(key)) { allStringsHaveTranslation = false - println(entry) + println("$entry not translated!") } } return allStringsHaveTranslation