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 <by consuming this unit>", "Reveals the entire map <by consuming this unit>" 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
This commit is contained in:
Yair Morgenstern 2022-02-19 19:38:39 +02:00 committed by GitHub
parent a8be359ed2
commit 6d48f99206
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 49 additions and 20 deletions

View File

@ -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 =

View File

@ -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",

View File

@ -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

View File

@ -24,6 +24,9 @@ class Unique(val text: String, val sourceObjectType: UniqueTarget? = null, val s
else Stats.parse(firstStatParam)
}
val conditionals: List<Unique> = text.getConditionals()
val isTriggerable = type != null && type.targetTypes.contains(UniqueTarget.Triggerable)
// <for [amount] turns]> 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

View File

@ -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 <when attacking> <for [mapUnitFilter] units> <for [amount2] turns>"))
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

View File

@ -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<UnitAction>){
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<UnitAction>, unitTable: UnitTable) {
actionList += UnitAction(

View File

@ -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