From d53766b7d751f960c1a487bce10b448a4156e512 Mon Sep 17 00:00:00 2001 From: SeventhM <127357473+SeventhM@users.noreply.github.com> Date: Sun, 28 Jan 2024 01:06:43 -0800 Subject: [PATCH] Unify unit and civ triggers (#11011) * Unify unit and civ triggers * Update conditionalsApply check * add tile in the city override * Add StateForConditionals for unit uniques when founding a city * Accidentally removed unit triggers from uponBuildingImprovement * Accidentally removed parenthesis * add "tile = tile" to improvement trigger's StateForContitionals * Add unit's tile to default tile Co-authored-by: Yair Morgenstern * Accidentally did getMatchingUniques instead of getTriggeredUniqued * Merge triggerUnitWideUniques into TrigerUniques * checkOnGameInfo Helper to shorten line length * StateForConditionals secondary constructors * accidentally deleted * also accidentally deleted --------- Co-authored-by: Yair Morgenstern --- core/src/com/unciv/logic/GameStarter.kt | 2 +- core/src/com/unciv/logic/battle/Battle.kt | 10 +- .../com/unciv/logic/city/CityConstructions.kt | 6 +- .../unciv/logic/city/managers/CityFounder.kt | 13 +- .../unciv/logic/civilization/Civilization.kt | 4 +- .../civilization/diplomacy/DeclareWar.kt | 2 +- .../diplomacy/DiplomacyManager.kt | 12 +- .../civilization/managers/GoldenAgeManager.kt | 2 +- .../civilization/managers/PolicyManager.kt | 4 +- .../civilization/managers/ReligionManager.kt | 10 +- .../civilization/managers/RuinsManager.kt | 3 +- .../civilization/managers/TechManager.kt | 8 +- .../civilization/managers/TurnManager.kt | 2 +- .../civilization/managers/UnitManager.kt | 4 +- .../transients/CivInfoTransientCache.kt | 2 +- .../com/unciv/logic/map/mapunit/MapUnit.kt | 2 +- .../unciv/logic/map/mapunit/UnitPromotions.kt | 4 +- .../logic/map/mapunit/UnitTurnManager.kt | 2 +- .../map/tile/TileInfoImprovementFunctions.kt | 25 +-- .../models/ruleset/unique/Conditionals.kt | 100 +++++---- .../ruleset/unique/StateForConditionals.kt | 16 ++ .../ruleset/unique/UniqueTriggerActivation.kt | 209 +++++++++--------- .../screens/devconsole/ConsoleCivCommands.kt | 2 +- .../unit/actions/UnitActionsFromUniques.kt | 4 +- tests/src/com/unciv/logic/map/UpgradeTests.kt | 8 +- 25 files changed, 249 insertions(+), 207 deletions(-) diff --git a/core/src/com/unciv/logic/GameStarter.kt b/core/src/com/unciv/logic/GameStarter.kt index 1810c735f3..5d73b76c69 100644 --- a/core/src/com/unciv/logic/GameStarter.kt +++ b/core/src/com/unciv/logic/GameStarter.kt @@ -430,7 +430,7 @@ object GameStarter { //We may need the starting location for some uniques, which is why we're doing it now val startingTriggers = (ruleset.globalUniques.uniqueObjects + civ.nation.uniqueObjects) for (unique in startingTriggers.filter { !it.hasTriggerConditional() && it.conditionalsApply(civ) }) - UniqueTriggerActivation.triggerCivwideUnique(unique, civ, tile = startingLocation) + UniqueTriggerActivation.triggerUnique(unique, civ, tile = startingLocation) } } diff --git a/core/src/com/unciv/logic/battle/Battle.kt b/core/src/com/unciv/logic/battle/Battle.kt index efcacf628c..4e021eae33 100644 --- a/core/src/com/unciv/logic/battle/Battle.kt +++ b/core/src/com/unciv/logic/battle/Battle.kt @@ -156,7 +156,7 @@ object Battle { for (unique in ourUnit.unit.getTriggeredUniques(UniqueType.TriggerUponDefeatingUnit, stateForConditionals)) if (unique.conditionals.any { it.type == UniqueType.TriggerUponDefeatingUnit && enemy.unit.matchesFilter(it.params[0]) }) - UniqueTriggerActivation.triggerUnitwideUnique(unique, ourUnit.unit, triggerNotificationText = "due to our [${ourUnit.getName()}] defeating a [${enemy.getName()}]") + UniqueTriggerActivation.triggerUnique(unique, ourUnit.unit, triggerNotificationText = "due to our [${ourUnit.getName()}] defeating a [${enemy.getName()}]") } // Add culture when defeating a barbarian when Honor policy is adopted, gold from enemy killed when honor is complete @@ -205,7 +205,7 @@ object Battle { val stateForConditionals = StateForConditionals(civInfo = ourUnit.getCivInfo(), ourCombatant = ourUnit, theirCombatant=enemy, tile = attackedTile) for (unique in ourUnit.unit.getTriggeredUniques(UniqueType.TriggerUponDefeat, stateForConditionals)) - UniqueTriggerActivation.triggerUnitwideUnique(unique, ourUnit.unit, triggerNotificationText = "due to our [${ourUnit.getName()}] being defeated by a [${enemy.getName()}]") + UniqueTriggerActivation.triggerUnique(unique, ourUnit.unit, triggerNotificationText = "due to our [${ourUnit.getName()}] being defeated by a [${enemy.getName()}]") } private fun tryEarnFromKilling(civUnit: ICombatant, defeatedUnit: MapUnitCombatant) { @@ -310,12 +310,12 @@ object Battle { if (attacker is MapUnitCombatant) for (unique in attacker.unit.getTriggeredUniques(UniqueType.TriggerUponLosingHealth)) if (unique.conditionals.any { it.params[0].toInt() <= defenderDamageDealt }) - UniqueTriggerActivation.triggerUnitwideUnique(unique, attacker.unit, triggerNotificationText = "due to losing [$defenderDamageDealt] HP") + UniqueTriggerActivation.triggerUnique(unique, attacker.unit, triggerNotificationText = "due to losing [$defenderDamageDealt] HP") if (defender is MapUnitCombatant) for (unique in defender.unit.getTriggeredUniques(UniqueType.TriggerUponLosingHealth)) if (unique.conditionals.any { it.params[0].toInt() <= attackerDamageDealt }) - UniqueTriggerActivation.triggerUnitwideUnique(unique, defender.unit, triggerNotificationText = "due to losing [$attackerDamageDealt] HP") + UniqueTriggerActivation.triggerUnique(unique, defender.unit, triggerNotificationText = "due to losing [$attackerDamageDealt] HP") plunderFromDamage(attacker, defender, attackerDamageDealt) return DamageDealt(attackerDamageDealt, defenderDamageDealt) @@ -555,7 +555,7 @@ object Battle { for (unique in attackerCiv.getTriggeredUniques(UniqueType.TriggerUponConqueringCity, stateForConditionals) + attacker.unit.getTriggeredUniques(UniqueType.TriggerUponConqueringCity, stateForConditionals)) - UniqueTriggerActivation.triggerUnitwideUnique(unique, attacker.unit) + UniqueTriggerActivation.triggerUnique(unique, attacker.unit) } /** Handle decision making after city conquest, namely whether the AI should liberate, puppet, diff --git a/core/src/com/unciv/logic/city/CityConstructions.kt b/core/src/com/unciv/logic/city/CityConstructions.kt index a4897b3649..c261f84b4f 100644 --- a/core/src/com/unciv/logic/city/CityConstructions.kt +++ b/core/src/com/unciv/logic/city/CityConstructions.kt @@ -552,17 +552,17 @@ class CityConstructions : IsPartOfGameInfoSerialization { for (unique in building.uniqueObjects) if (!unique.hasTriggerConditional() && unique.conditionalsApply(stateForConditionals)) - UniqueTriggerActivation.triggerCivwideUnique(unique, city.civ, city, triggerNotificationText = triggerNotificationText) + UniqueTriggerActivation.triggerUnique(unique, city, triggerNotificationText = triggerNotificationText) for (unique in city.civ.getTriggeredUniques(UniqueType.TriggerUponConstructingBuilding, stateForConditionals)) if (unique.conditionals.any {it.type == UniqueType.TriggerUponConstructingBuilding && building.matchesFilter(it.params[0])}) - UniqueTriggerActivation.triggerCivwideUnique(unique, city.civ, city, triggerNotificationText = triggerNotificationText) + UniqueTriggerActivation.triggerUnique(unique, city, triggerNotificationText = triggerNotificationText) for (unique in city.civ.getTriggeredUniques(UniqueType.TriggerUponConstructingBuildingCityFilter, stateForConditionals)) if (unique.conditionals.any {it.type == UniqueType.TriggerUponConstructingBuildingCityFilter && building.matchesFilter(it.params[0]) && city.matchesFilter(it.params[1])}) - UniqueTriggerActivation.triggerCivwideUnique(unique, city.civ, city, triggerNotificationText = triggerNotificationText) + UniqueTriggerActivation.triggerUnique(unique, city, triggerNotificationText = triggerNotificationText) } fun removeBuilding(buildingName: String) { diff --git a/core/src/com/unciv/logic/city/managers/CityFounder.kt b/core/src/com/unciv/logic/city/managers/CityFounder.kt index dc937116b3..aeace52b27 100644 --- a/core/src/com/unciv/logic/city/managers/CityFounder.kt +++ b/core/src/com/unciv/logic/city/managers/CityFounder.kt @@ -7,13 +7,14 @@ import com.unciv.logic.civilization.Civilization import com.unciv.logic.civilization.Proximity import com.unciv.logic.civilization.diplomacy.DiplomacyFlags import com.unciv.logic.civilization.managers.ReligionState +import com.unciv.logic.map.mapunit.MapUnit import com.unciv.models.ruleset.nation.Nation import com.unciv.models.ruleset.unique.StateForConditionals import com.unciv.models.ruleset.unique.UniqueTriggerActivation import com.unciv.models.ruleset.unique.UniqueType class CityFounder { - fun foundCity(civInfo: Civilization, cityLocation: Vector2): City { + fun foundCity(civInfo: Civilization, cityLocation: Vector2, unit: MapUnit? = null): City { val city = City() city.foundingCiv = civInfo.civName @@ -87,12 +88,14 @@ class CityFounder { triggerCitiesSettledNearOtherCiv(city) civInfo.gameInfo.cityDistances.setDirty() - for (unique in civInfo.getTriggeredUniques(UniqueType.TriggerUponFoundingCity, - StateForConditionals(civInfo, city) + StateForConditionals(civInfo, city, unit) )) - UniqueTriggerActivation.triggerCivwideUnique(unique, civInfo, city, triggerNotificationText = "due to founding a city") - + UniqueTriggerActivation.triggerUnique(unique, civInfo, city, unit, triggerNotificationText = "due to founding a city") + if (unit != null) + for (unique in unit.getTriggeredUniques(UniqueType.TriggerUponFoundingCity, + StateForConditionals(civInfo, city, unit))) + UniqueTriggerActivation.triggerUnique(unique, civInfo, city, unit, triggerNotificationText = "due to founding a city") return city } diff --git a/core/src/com/unciv/logic/civilization/Civilization.kt b/core/src/com/unciv/logic/civilization/Civilization.kt index 3495dbe769..32270e9c17 100644 --- a/core/src/com/unciv/logic/civilization/Civilization.kt +++ b/core/src/com/unciv/logic/civilization/Civilization.kt @@ -809,8 +809,8 @@ class Civilization : IsPartOfGameInfoSerialization { } // endregion - fun addCity(location: Vector2) { - val newCity = CityFounder().foundCity(this, location) + fun addCity(location: Vector2, unit: MapUnit? = null) { + val newCity = CityFounder().foundCity(this, location, unit) newCity.cityConstructions.chooseNextConstruction() } diff --git a/core/src/com/unciv/logic/civilization/diplomacy/DeclareWar.kt b/core/src/com/unciv/logic/civilization/diplomacy/DeclareWar.kt index 86aef4d928..a6ebd54c72 100644 --- a/core/src/com/unciv/logic/civilization/diplomacy/DeclareWar.kt +++ b/core/src/com/unciv/logic/civilization/diplomacy/DeclareWar.kt @@ -65,7 +65,7 @@ object DeclareWar { if (otherCiv.isMajorCiv()) for (unique in civInfo.getTriggeredUniques(UniqueType.TriggerUponDeclaringWar)) - UniqueTriggerActivation.triggerCivwideUnique(unique, civInfo) + UniqueTriggerActivation.triggerUnique(unique, civInfo) } private fun breakTreaties(diplomacyManager: DiplomacyManager) { diff --git a/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt b/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt index bb46cc10d0..c366b4f889 100644 --- a/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt +++ b/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt @@ -510,12 +510,12 @@ class DiplomacyManager() : IsPartOfGameInfoSerialization { thirdCiv.getDiplomacyManager(civInfo).setFriendshipBasedModifier() } - // Ignore contitionals as triggerCivwideUnique will check again, and that would break + // Ignore contitionals as triggerUnique will check again, and that would break // UniqueType.ConditionalChance - 25% declared chance would work as 6% actual chance for (unique in civInfo.getTriggeredUniques(UniqueType.TriggerUponDeclaringFriendship, StateForConditionals.IgnoreConditionals)) - UniqueTriggerActivation.triggerCivwideUnique(unique, civInfo) + UniqueTriggerActivation.triggerUnique(unique, civInfo) for (unique in otherCiv().getTriggeredUniques(UniqueType.TriggerUponDeclaringFriendship, StateForConditionals.IgnoreConditionals)) - UniqueTriggerActivation.triggerCivwideUnique(unique, otherCiv()) + UniqueTriggerActivation.triggerUnique(unique, otherCiv()) } internal fun setFriendshipBasedModifier() { @@ -557,12 +557,12 @@ class DiplomacyManager() : IsPartOfGameInfoSerialization { thirdCiv.getDiplomacyManager(civInfo).setDefensivePactBasedModifier() } - // Ignore contitionals as triggerCivwideUnique will check again, and that would break + // Ignore contitionals as triggerUnique will check again, and that would break // UniqueType.ConditionalChance - 25% declared chance would work as 6% actual chance for (unique in civInfo.getTriggeredUniques(UniqueType.TriggerUponSigningDefensivePact, StateForConditionals.IgnoreConditionals)) - UniqueTriggerActivation.triggerCivwideUnique(unique, civInfo) + UniqueTriggerActivation.triggerUnique(unique, civInfo) for (unique in otherCiv().getTriggeredUniques(UniqueType.TriggerUponSigningDefensivePact, StateForConditionals.IgnoreConditionals)) - UniqueTriggerActivation.triggerCivwideUnique(unique, otherCiv()) + UniqueTriggerActivation.triggerUnique(unique, otherCiv()) } internal fun setDefensivePactBasedModifier() { diff --git a/core/src/com/unciv/logic/civilization/managers/GoldenAgeManager.kt b/core/src/com/unciv/logic/civilization/managers/GoldenAgeManager.kt index 40a01d80af..6395d33a0c 100644 --- a/core/src/com/unciv/logic/civilization/managers/GoldenAgeManager.kt +++ b/core/src/com/unciv/logic/civilization/managers/GoldenAgeManager.kt @@ -52,7 +52,7 @@ class GoldenAgeManager : IsPartOfGameInfoSerialization { civInfo.popupAlerts.add(PopupAlert(AlertType.GoldenAge, "")) for (unique in civInfo.getTriggeredUniques(UniqueType.TriggerUponEnteringGoldenAge)) - UniqueTriggerActivation.triggerCivwideUnique(unique, civInfo) + UniqueTriggerActivation.triggerUnique(unique, civInfo) //Golden Age can happen mid turn with Great Artist effects for (city in civInfo.cities) city.cityStats.update() diff --git a/core/src/com/unciv/logic/civilization/managers/PolicyManager.kt b/core/src/com/unciv/logic/civilization/managers/PolicyManager.kt index bb61d0b19d..bc995c92a3 100644 --- a/core/src/com/unciv/logic/civilization/managers/PolicyManager.kt +++ b/core/src/com/unciv/logic/civilization/managers/PolicyManager.kt @@ -213,11 +213,11 @@ class PolicyManager : IsPartOfGameInfoSerialization { val triggerNotificationText = "due to adopting [${policy.name}]" for (unique in policy.uniqueObjects) if (!unique.hasTriggerConditional() && unique.conditionalsApply(StateForConditionals(civInfo))) - UniqueTriggerActivation.triggerCivwideUnique(unique, civInfo, triggerNotificationText = triggerNotificationText) + UniqueTriggerActivation.triggerUnique(unique, civInfo, triggerNotificationText = triggerNotificationText) for (unique in civInfo.getTriggeredUniques(UniqueType.TriggerUponAdoptingPolicyOrBelief)) if (unique.conditionals.any {it.type == UniqueType.TriggerUponAdoptingPolicyOrBelief && it.params[0] == policy.name}) - UniqueTriggerActivation.triggerCivwideUnique(unique, civInfo, triggerNotificationText = triggerNotificationText) + UniqueTriggerActivation.triggerUnique(unique, civInfo, triggerNotificationText = triggerNotificationText) civInfo.cache.updateCivResources() diff --git a/core/src/com/unciv/logic/civilization/managers/ReligionManager.kt b/core/src/com/unciv/logic/civilization/managers/ReligionManager.kt index 91e451cc70..10dacd3f3f 100644 --- a/core/src/com/unciv/logic/civilization/managers/ReligionManager.kt +++ b/core/src/com/unciv/logic/civilization/managers/ReligionManager.kt @@ -390,17 +390,17 @@ class ReligionManager : IsPartOfGameInfoSerialization { ReligionState.None -> { religionState = ReligionState.Pantheon for (unique in civInfo.getTriggeredUniques(UniqueType.TriggerUponFoundingPantheon)) - UniqueTriggerActivation.triggerCivwideUnique(unique, civInfo) + UniqueTriggerActivation.triggerUnique(unique, civInfo) } ReligionState.FoundingReligion -> { religionState = ReligionState.Religion for (unique in civInfo.getTriggeredUniques(UniqueType.TriggerUponFoundingReligion)) - UniqueTriggerActivation.triggerCivwideUnique(unique, civInfo) + UniqueTriggerActivation.triggerUnique(unique, civInfo) } ReligionState.EnhancingReligion -> { religionState = ReligionState.EnhancedReligion for (unique in civInfo.getTriggeredUniques(UniqueType.TriggerUponEnhancingReligion)) - UniqueTriggerActivation.triggerCivwideUnique(unique, civInfo) + UniqueTriggerActivation.triggerUnique(unique, civInfo) } else -> {} } @@ -408,12 +408,12 @@ class ReligionManager : IsPartOfGameInfoSerialization { for (unique in civInfo.getTriggeredUniques(UniqueType.TriggerUponAdoptingPolicyOrBelief)) for (belief in beliefs) if (unique.conditionals.any {it.type == UniqueType.TriggerUponAdoptingPolicyOrBelief && it.params[0] == belief.name}) - UniqueTriggerActivation.triggerCivwideUnique(unique, civInfo, + UniqueTriggerActivation.triggerUnique(unique, civInfo, triggerNotificationText = "due to adopting [${belief.name}]") for (belief in beliefs) for (unique in belief.uniqueObjects.filter { !it.hasTriggerConditional() && it.conditionalsApply(StateForConditionals(civInfo)) }) - UniqueTriggerActivation.triggerCivwideUnique(unique, civInfo) + UniqueTriggerActivation.triggerUnique(unique, civInfo) civInfo.updateStatsForNextTurn() // a belief can have an immediate effect on stats } diff --git a/core/src/com/unciv/logic/civilization/managers/RuinsManager.kt b/core/src/com/unciv/logic/civilization/managers/RuinsManager.kt index 98470041df..aa09372d1a 100644 --- a/core/src/com/unciv/logic/civilization/managers/RuinsManager.kt +++ b/core/src/com/unciv/logic/civilization/managers/RuinsManager.kt @@ -63,8 +63,7 @@ class RuinsManager( for (unique in possibleReward.uniqueObjects) { atLeastOneUniqueHadEffect = atLeastOneUniqueHadEffect - || UniqueTriggerActivation.triggerCivwideUnique(unique, civInfo, tile = triggeringUnit.getTile(), notification = possibleReward.notification, triggerNotificationText = "from the ruins") - || UniqueTriggerActivation.triggerUnitwideUnique(unique, triggeringUnit, notification = possibleReward.notification) + || UniqueTriggerActivation.triggerUnique(unique, triggeringUnit, notification = possibleReward.notification) } if (atLeastOneUniqueHadEffect) { rememberReward(possibleReward.name) diff --git a/core/src/com/unciv/logic/civilization/managers/TechManager.kt b/core/src/com/unciv/logic/civilization/managers/TechManager.kt index 04d7c0653f..f9974f4e33 100644 --- a/core/src/com/unciv/logic/civilization/managers/TechManager.kt +++ b/core/src/com/unciv/logic/civilization/managers/TechManager.kt @@ -299,11 +299,11 @@ class TechManager : IsPartOfGameInfoSerialization { val triggerNotificationText = "due to researching [$techName]" for (unique in newTech.uniqueObjects) if (!unique.hasTriggerConditional() && unique.conditionalsApply(StateForConditionals(civInfo))) - UniqueTriggerActivation.triggerCivwideUnique(unique, civInfo, triggerNotificationText = triggerNotificationText) + UniqueTriggerActivation.triggerUnique(unique, civInfo, triggerNotificationText = triggerNotificationText) for (unique in civInfo.getTriggeredUniques(UniqueType.TriggerUponResearch)) if (unique.conditionals.any {it.type == UniqueType.TriggerUponResearch && it.params[0] == techName}) - UniqueTriggerActivation.triggerCivwideUnique(unique, civInfo, triggerNotificationText = triggerNotificationText) + UniqueTriggerActivation.triggerUnique(unique, civInfo, triggerNotificationText = triggerNotificationText) val revealedResources = getRuleset().tileResources.values.filter { techName == it.revealedBy } @@ -440,7 +440,7 @@ class TechManager : IsPartOfGameInfoSerialization { for (era in erasPassed) for (unique in era.uniqueObjects) if (!unique.hasTriggerConditional() && unique.conditionalsApply(StateForConditionals(civInfo))) - UniqueTriggerActivation.triggerCivwideUnique( + UniqueTriggerActivation.triggerUnique( unique, civInfo, triggerNotificationText = "due to entering the [${era.name}]" @@ -450,7 +450,7 @@ class TechManager : IsPartOfGameInfoSerialization { for (unique in civInfo.getTriggeredUniques(UniqueType.TriggerUponEnteringEra)) for (eraName in eraNames) if (unique.conditionals.any { it.type == UniqueType.TriggerUponEnteringEra && it.params[0] == eraName }) - UniqueTriggerActivation.triggerCivwideUnique( + UniqueTriggerActivation.triggerUnique( unique, civInfo, triggerNotificationText = "due to entering the [$eraName]" diff --git a/core/src/com/unciv/logic/civilization/managers/TurnManager.kt b/core/src/com/unciv/logic/civilization/managers/TurnManager.kt index e9ec3b0405..5d1f1c564d 100644 --- a/core/src/com/unciv/logic/civilization/managers/TurnManager.kt +++ b/core/src/com/unciv/logic/civilization/managers/TurnManager.kt @@ -234,7 +234,7 @@ class TurnManager(val civInfo: Civilization) { NextTurnAutomation.automateCityBombardment(civInfo) // Bombard with all cities that haven't, maybe you missed one for (unique in civInfo.getTriggeredUniques(UniqueType.TriggerUponTurnEnd, StateForConditionals(civInfo))) - UniqueTriggerActivation.triggerCivwideUnique(unique, civInfo) + UniqueTriggerActivation.triggerUnique(unique, civInfo) val notificationsLog = civInfo.notificationsLog val notificationsThisTurn = Civilization.NotificationsLog(civInfo.gameInfo.turns) diff --git a/core/src/com/unciv/logic/civilization/managers/UnitManager.kt b/core/src/com/unciv/logic/civilization/managers/UnitManager.kt index 092496614e..74c797e6b3 100644 --- a/core/src/com/unciv/logic/civilization/managers/UnitManager.kt +++ b/core/src/com/unciv/logic/civilization/managers/UnitManager.kt @@ -82,11 +82,11 @@ class UnitManager(val civInfo:Civilization) { val triggerNotificationText = "due to gaining a [${unit.name}]" for (unique in unit.getUniques()) if (!unique.hasTriggerConditional() && unique.conditionalsApply(StateForConditionals(civInfo, unit = unit))) - UniqueTriggerActivation.triggerUnitwideUnique(unique, unit, triggerNotificationText = triggerNotificationText) + UniqueTriggerActivation.triggerUnique(unique, unit, triggerNotificationText = triggerNotificationText) for (unique in civInfo.getTriggeredUniques(UniqueType.TriggerUponGainingUnit)) if (unique.conditionals.any { it.type == UniqueType.TriggerUponGainingUnit && unit.matchesFilter(it.params[0]) }) - UniqueTriggerActivation.triggerCivwideUnique(unique, civInfo, triggerNotificationText = triggerNotificationText) + UniqueTriggerActivation.triggerUnique(unique, unit, triggerNotificationText = triggerNotificationText) if (unit.getResourceRequirementsPerTurn().isNotEmpty()) civInfo.cache.updateCivResources() diff --git a/core/src/com/unciv/logic/civilization/transients/CivInfoTransientCache.kt b/core/src/com/unciv/logic/civilization/transients/CivInfoTransientCache.kt index 559494e1ae..c2472258fd 100644 --- a/core/src/com/unciv/logic/civilization/transients/CivInfoTransientCache.kt +++ b/core/src/com/unciv/logic/civilization/transients/CivInfoTransientCache.kt @@ -237,7 +237,7 @@ class CivInfoTransientCache(val civInfo: Civilization) { for (unique in civInfo.getTriggeredUniques(UniqueType.TriggerUponDiscoveringNaturalWonder, StateForConditionals(civInfo, tile = tile) )) - UniqueTriggerActivation.triggerCivwideUnique(unique, civInfo, tile=tile, triggerNotificationText = "due to discovering a Natural Wonder") + UniqueTriggerActivation.triggerUnique(unique, civInfo, tile=tile, triggerNotificationText = "due to discovering a Natural Wonder") } } diff --git a/core/src/com/unciv/logic/map/mapunit/MapUnit.kt b/core/src/com/unciv/logic/map/mapunit/MapUnit.kt index 15a2d27e4d..3d4c59ef02 100644 --- a/core/src/com/unciv/logic/map/mapunit/MapUnit.kt +++ b/core/src/com/unciv/logic/map/mapunit/MapUnit.kt @@ -664,7 +664,7 @@ class MapUnit : IsPartOfGameInfoSerialization { && tile.matchesFilter(it.params[0], civ) } && unique.conditionalsApply(state) ) - UniqueTriggerActivation.triggerUnitwideUnique(unique, this) + UniqueTriggerActivation.triggerUnique(unique, this) } } } diff --git a/core/src/com/unciv/logic/map/mapunit/UnitPromotions.kt b/core/src/com/unciv/logic/map/mapunit/UnitPromotions.kt index 4fdbdeead2..2d23320425 100644 --- a/core/src/com/unciv/logic/map/mapunit/UnitPromotions.kt +++ b/core/src/com/unciv/logic/map/mapunit/UnitPromotions.kt @@ -73,7 +73,7 @@ class UnitPromotions : IsPartOfGameInfoSerialization { } for (unique in unit.getTriggeredUniques(UniqueType.TriggerUponPromotion)) - UniqueTriggerActivation.triggerUnitwideUnique(unique, unit) + UniqueTriggerActivation.triggerUnique(unique, unit) } if (!promotion.hasUnique(UniqueType.SkipPromotion)) @@ -106,7 +106,7 @@ class UnitPromotions : IsPartOfGameInfoSerialization { for (unique in promotion.uniqueObjects) if (unique.conditionalsApply(StateForConditionals(civInfo = unit.civ, unit = unit)) && !unique.hasTriggerConditional()) - UniqueTriggerActivation.triggerUnitwideUnique(unique, unit, triggerNotificationText = "due to our [${unit.name}] being promoted") + UniqueTriggerActivation.triggerUnique(unique, unit, triggerNotificationText = "due to our [${unit.name}] being promoted") } /** Gets all promotions this unit could currently "buy" with enough [XP] diff --git a/core/src/com/unciv/logic/map/mapunit/UnitTurnManager.kt b/core/src/com/unciv/logic/map/mapunit/UnitTurnManager.kt index 10de611b77..07320c3686 100644 --- a/core/src/com/unciv/logic/map/mapunit/UnitTurnManager.kt +++ b/core/src/com/unciv/logic/map/mapunit/UnitTurnManager.kt @@ -60,7 +60,7 @@ class UnitTurnManager(val unit: MapUnit) { for (unique in unit.getTriggeredUniques(UniqueType.TriggerUponEndingTurnInTile)) if (unique.conditionals.any { it.type == UniqueType.TriggerUponEndingTurnInTile && unit.getTile().matchesFilter(it.params[0], unit.civ) }) - UniqueTriggerActivation.triggerUnitwideUnique(unique, unit) + UniqueTriggerActivation.triggerUnique(unique, unit) } diff --git a/core/src/com/unciv/logic/map/tile/TileInfoImprovementFunctions.kt b/core/src/com/unciv/logic/map/tile/TileInfoImprovementFunctions.kt index 64f342c656..e9b38a687d 100644 --- a/core/src/com/unciv/logic/map/tile/TileInfoImprovementFunctions.kt +++ b/core/src/com/unciv/logic/map/tile/TileInfoImprovementFunctions.kt @@ -246,23 +246,20 @@ class TileInfoImprovementFunctions(val tile: Tile) { civ: Civilization, unit: MapUnit? = null ) { + val stateForConditionals = StateForConditionals(civ, unit = unit, tile = tile) + for (unique in improvement.uniqueObjects.filter { !it.hasTriggerConditional() - && it.conditionalsApply(StateForConditionals(civ)) }) - if (unit != null) { - UniqueTriggerActivation.triggerUnitwideUnique(unique, unit) - } - else UniqueTriggerActivation.triggerCivwideUnique(unique, civ, tile = tile) + && it.conditionalsApply(stateForConditionals) }) + UniqueTriggerActivation.triggerUnique(unique, civ, unit = unit, tile = tile) - if (unit != null){ - for (unique in unit.getTriggeredUniques(UniqueType.TriggerUponBuildingImprovement) - .filter { improvement.matchesFilter(it.params[0]) }) - UniqueTriggerActivation.triggerUnitwideUnique(unique, unit) - } - - for (unique in civ.getMatchingUniques( - UniqueType.TriggerUponBuildingImprovement, StateForConditionals(civInfo = civ, unit = unit)) + for (unique in civ.getTriggeredUniques(UniqueType.TriggerUponBuildingImprovement, stateForConditionals) .filter { improvement.matchesFilter(it.params[0]) }) - UniqueTriggerActivation.triggerCivwideUnique(unique, civ, tile = tile) + UniqueTriggerActivation.triggerUnique(unique, civ, unit = unit, tile = tile) + + if (unit == null) return + for (unique in unit.getTriggeredUniques(UniqueType.TriggerUponBuildingImprovement, stateForConditionals) + .filter { improvement.matchesFilter(it.params[0]) }) + UniqueTriggerActivation.triggerUnique(unique, civ, unit = unit, tile = tile) } private fun activateRemovalImprovement( diff --git a/core/src/com/unciv/models/ruleset/unique/Conditionals.kt b/core/src/com/unciv/models/ruleset/unique/Conditionals.kt index c6de7acdfe..d8189dbb7f 100644 --- a/core/src/com/unciv/models/ruleset/unique/Conditionals.kt +++ b/core/src/com/unciv/models/ruleset/unique/Conditionals.kt @@ -1,5 +1,6 @@ package com.unciv.models.ruleset.unique +import com.unciv.logic.GameInfo import com.unciv.logic.battle.CombatAction import com.unciv.logic.battle.MapUnitCombatant import com.unciv.logic.city.City @@ -36,18 +37,32 @@ object Conditionals { ?: relevantTile?.getCity() } + val relevantCiv by lazy { + state.civInfo ?: + relevantCity?.civ ?: + relevantUnit?.civ + } + + val gameInfo by lazy { relevantCiv?.gameInfo } + val stateBasedRandom by lazy { Random(state.hashCode()) } fun getResourceAmount(resourceName: String): Int { if (relevantCity != null) return relevantCity!!.getResourceAmount(resourceName) - if (state.civInfo != null) return state.civInfo.getResourceAmount(resourceName) + if (relevantCiv != null) return relevantCiv!!.getResourceAmount(resourceName) return 0 } + /** Helper to simplify conditional tests requiring gameInfo */ + fun checkOnGameInfo(predicate: (GameInfo.() -> Boolean)): Boolean { + if (gameInfo == null) return false + return gameInfo!!.predicate() + } + /** Helper to simplify conditional tests requiring a Civilization */ fun checkOnCiv(predicate: (Civilization.() -> Boolean)): Boolean { - if (state.civInfo == null) return false - return state.civInfo.predicate() + if (relevantCiv == null) return false + return relevantCiv!!.predicate() } /** Helper to simplify conditional tests requiring a City */ @@ -58,37 +73,37 @@ object Conditionals { /** Helper to simplify the "compare civ's current era with named era" conditions */ fun compareEra(eraParam: String, compare: (civEra: Int, paramEra: Int) -> Boolean): Boolean { - if (state.civInfo == null) return false - val era = state.civInfo.gameInfo.ruleset.eras[eraParam] ?: return false - return compare(state.civInfo.getEraNumber(), era.eraNumber) + if (gameInfo == null) return false + val era = gameInfo!!.ruleset.eras[eraParam] ?: return false + return compare(relevantCiv!!.getEraNumber(), era.eraNumber) } /** Helper for ConditionalWhenAboveAmountStatResource and its below counterpart */ fun checkResourceOrStatAmount(compare: (current: Int, limit: Int) -> Boolean): Boolean { - if (state.civInfo == null) return false + if (gameInfo == null) return false val limit = condition.params[0].toInt() val resourceOrStatName = condition.params[1] - if (state.civInfo.gameInfo.ruleset.tileResources.containsKey(resourceOrStatName)) + if (gameInfo!!.ruleset.tileResources.containsKey(resourceOrStatName)) return compare(getResourceAmount(resourceOrStatName), limit) val stat = Stat.safeValueOf(resourceOrStatName) ?: return false - return compare(state.civInfo.getStatReserve(stat), limit) + return compare(relevantCiv!!.getStatReserve(stat), limit) } /** Helper for ConditionalWhenAboveAmountStatSpeed and its below counterpart */ fun checkResourceOrStatAmountWithSpeed(compare: (current: Int, limit: Float) -> Boolean): Boolean { - if (state.civInfo == null) return false + if (gameInfo == null) return false val limit = condition.params[0].toInt() val resourceOrStatName = condition.params[1] - var gameSpeedModifier = state.civInfo.gameInfo.speed.modifier + var gameSpeedModifier = gameInfo!!.speed.modifier - if (state.civInfo.gameInfo.ruleset.tileResources.containsKey(resourceOrStatName)) + if (gameInfo!!.ruleset.tileResources.containsKey(resourceOrStatName)) return compare(getResourceAmount(resourceOrStatName), limit * gameSpeedModifier) val stat = Stat.safeValueOf(resourceOrStatName) ?: return false - gameSpeedModifier = state.civInfo.gameInfo.speed.statCostModifiers[stat]!! - return compare(state.civInfo.getStatReserve(stat), limit * gameSpeedModifier) + gameSpeedModifier = gameInfo!!.speed.statCostModifiers[stat]!! + return compare(relevantCiv!!.getStatReserve(stat), limit * gameSpeedModifier) } return when (condition.type) { @@ -97,9 +112,9 @@ object Conditionals { UniqueType.ModifierHiddenFromUsers -> true // allowed to be attached to any Unique to hide it, no-op otherwise UniqueType.ConditionalChance -> stateBasedRandom.nextFloat() < condition.params[0].toFloat() / 100f - UniqueType.ConditionalEveryTurns -> checkOnCiv { gameInfo.turns % condition.params[0].toInt() == 0} - UniqueType.ConditionalBeforeTurns -> checkOnCiv { gameInfo.turns < condition.params[0].toInt() } - UniqueType.ConditionalAfterTurns -> checkOnCiv { gameInfo.turns >= condition.params[0].toInt() } + UniqueType.ConditionalEveryTurns -> checkOnGameInfo { turns % condition.params[0].toInt() == 0 } + UniqueType.ConditionalBeforeTurns -> checkOnGameInfo { turns < condition.params[0].toInt() } + UniqueType.ConditionalAfterTurns -> checkOnGameInfo { turns >= condition.params[0].toInt() } UniqueType.ConditionalCivFilter -> checkOnCiv { matchesFilter(condition.params[0]) } UniqueType.ConditionalWar -> checkOnCiv { isAtWar() } @@ -125,7 +140,7 @@ object Conditionals { UniqueType.ConditionalBeforeEra -> compareEra(condition.params[0]) { current, param -> current < param } UniqueType.ConditionalStartingFromEra -> compareEra(condition.params[0]) { current, param -> current >= param } UniqueType.ConditionalDuringEra -> compareEra(condition.params[0]) { current, param -> current == param } - UniqueType.ConditionalIfStartingInEra -> checkOnCiv { gameInfo.gameParameters.startingEra == condition.params[0] } + UniqueType.ConditionalIfStartingInEra -> checkOnGameInfo { gameParameters.startingEra == condition.params[0] } UniqueType.ConditionalTech -> checkOnCiv { tech.isResearched(condition.params[0]) } UniqueType.ConditionalNoTech -> checkOnCiv { !tech.isResearched(condition.params[0]) } @@ -151,7 +166,7 @@ object Conditionals { UniqueType.ConditionalBuildingBuilt -> checkOnCiv { cities.any { it.cityConstructions.containsBuildingOrEquivalent(condition.params[0]) } } UniqueType.ConditionalBuildingBuiltByAnybody -> - checkOnCiv { gameInfo.getCities().any { it.cityConstructions.containsBuildingOrEquivalent(condition.params[0]) } } + checkOnGameInfo { getCities().any { it.cityConstructions.containsBuildingOrEquivalent(condition.params[0]) } } // Filtered via city.getMatchingUniques UniqueType.ConditionalInThisCity -> true @@ -182,23 +197,23 @@ object Conditionals { state.unit.abilityToTimesUsed.isEmpty() UniqueType.ConditionalInTiles -> - relevantTile?.matchesFilter(condition.params[0], state.civInfo) == true + relevantTile?.matchesFilter(condition.params[0], relevantCiv) == true UniqueType.ConditionalInTilesNot -> - relevantTile?.matchesFilter(condition.params[0], state.civInfo) == false - UniqueType.ConditionalAdjacentTo -> relevantTile?.isAdjacentTo(condition.params[0], state.civInfo) == true - UniqueType.ConditionalNotAdjacentTo -> relevantTile?.isAdjacentTo(condition.params[0], state.civInfo) == false + relevantTile?.matchesFilter(condition.params[0], relevantCiv) == false + UniqueType.ConditionalAdjacentTo -> relevantTile?.isAdjacentTo(condition.params[0], relevantCiv) == true + UniqueType.ConditionalNotAdjacentTo -> relevantTile?.isAdjacentTo(condition.params[0], relevantCiv) == false UniqueType.ConditionalFightingInTiles -> - state.attackedTile?.matchesFilter(condition.params[0], state.civInfo) == true + state.attackedTile?.matchesFilter(condition.params[0], relevantCiv) == true UniqueType.ConditionalInTilesAnd -> - relevantTile != null && relevantTile!!.matchesFilter(condition.params[0], state.civInfo) - && relevantTile!!.matchesFilter(condition.params[1], state.civInfo) + relevantTile != null && relevantTile!!.matchesFilter(condition.params[0], relevantCiv) + && relevantTile!!.matchesFilter(condition.params[1], relevantCiv) UniqueType.ConditionalNearTiles -> relevantTile != null && relevantTile!!.getTilesInDistance(condition.params[0].toInt()).any { it.matchesFilter(condition.params[1]) } UniqueType.ConditionalVsLargerCiv -> { - val yourCities = state.civInfo?.cities?.size ?: 1 + val yourCities = relevantCiv?.cities?.size ?: 1 val theirCities = state.theirCombatant?.getCivInfo()?.cities?.size ?: 0 yourCities < theirCities } @@ -209,25 +224,25 @@ object Conditionals { ) } UniqueType.ConditionalAdjacentUnit -> - state.civInfo != null + relevantCiv != null && relevantUnit != null && relevantTile!!.neighbors.any { it.militaryUnit != null && it.militaryUnit != relevantUnit - && it.militaryUnit!!.civ == state.civInfo + && it.militaryUnit!!.civ == relevantCiv && it.militaryUnit!!.matchesFilter(condition.params[0]) } UniqueType.ConditionalNeighborTiles -> relevantTile != null && relevantTile!!.neighbors.count { - it.matchesFilter(condition.params[2], state.civInfo) + it.matchesFilter(condition.params[2], relevantCiv) } in condition.params[0].toInt()..condition.params[1].toInt() UniqueType.ConditionalNeighborTilesAnd -> relevantTile != null && relevantTile!!.neighbors.count { - it.matchesFilter(condition.params[2], state.civInfo) - && it.matchesFilter(condition.params[3], state.civInfo) + it.matchesFilter(condition.params[2], relevantCiv) + && it.matchesFilter(condition.params[3], relevantCiv) } in condition.params[0].toInt()..condition.params[1].toInt() UniqueType.ConditionalOnWaterMaps -> state.region?.continentID == -1 @@ -235,17 +250,18 @@ object Conditionals { UniqueType.ConditionalInRegionExceptOfType -> state.region?.type != condition.params[0] UniqueType.ConditionalFirstCivToResearch -> - state.civInfo != null && unique.sourceObjectType == UniqueTarget.Tech - && state.civInfo.gameInfo.civilizations.none { - it != state.civInfo && it.isMajorCiv() - && it.tech.isResearched(unique.sourceObjectName!!) // guarded by the sourceObjectType check - } + unique.sourceObjectType == UniqueTarget.Tech + && checkOnGameInfo { civilizations.none { + it != relevantCiv && it.isMajorCiv() + && it.tech.isResearched(unique.sourceObjectName!!) // guarded by the sourceObjectType check + } } + UniqueType.ConditionalFirstCivToAdopt -> - state.civInfo != null && unique.sourceObjectType == UniqueTarget.Policy - && state.civInfo.gameInfo.civilizations.none { - it != state.civInfo && it.isMajorCiv() - && it.policies.isAdopted(unique.sourceObjectName!!) // guarded by the sourceObjectType check - } + unique.sourceObjectType == UniqueTarget.Policy + && checkOnGameInfo { civilizations.none { + it != relevantCiv && it.isMajorCiv() + && it.policies.isAdopted(unique.sourceObjectName!!) // guarded by the sourceObjectType check + } } else -> false } diff --git a/core/src/com/unciv/models/ruleset/unique/StateForConditionals.kt b/core/src/com/unciv/models/ruleset/unique/StateForConditionals.kt index e4aaa3015a..5345657e23 100644 --- a/core/src/com/unciv/models/ruleset/unique/StateForConditionals.kt +++ b/core/src/com/unciv/models/ruleset/unique/StateForConditionals.kt @@ -2,6 +2,7 @@ package com.unciv.models.ruleset.unique import com.unciv.logic.battle.CombatAction import com.unciv.logic.battle.ICombatant +import com.unciv.logic.battle.MapUnitCombatant import com.unciv.logic.city.City import com.unciv.logic.civilization.Civilization import com.unciv.logic.map.mapgenerator.mapregions.Region @@ -23,6 +24,21 @@ data class StateForConditionals( val ignoreConditionals: Boolean = false, ) { + constructor(city: City) : this(city.civ, city, tile = city.getCenterTile()) + constructor(unit: MapUnit) : this(unit.civ, unit = unit, tile = unit.currentTile) + constructor(ourCombatant: ICombatant) : this( + ourCombatant.getCivInfo(), + unit = (ourCombatant as MapUnitCombatant).unit, + tile = ourCombatant.getTile(), + ourCombatant = ourCombatant, + ) + constructor(ourCombatant: ICombatant, theirCombatant: ICombatant) : this( + ourCombatant.getCivInfo(), + unit = (ourCombatant as MapUnitCombatant).unit, + tile = ourCombatant.getTile(), + ourCombatant = ourCombatant, + theirCombatant = theirCombatant, + ) companion object { val IgnoreConditionals = StateForConditionals(ignoreConditionals = true) diff --git a/core/src/com/unciv/models/ruleset/unique/UniqueTriggerActivation.kt b/core/src/com/unciv/models/ruleset/unique/UniqueTriggerActivation.kt index 691c12763e..b68bd3f93c 100644 --- a/core/src/com/unciv/models/ruleset/unique/UniqueTriggerActivation.kt +++ b/core/src/com/unciv/models/ruleset/unique/UniqueTriggerActivation.kt @@ -31,12 +31,33 @@ import kotlin.random.Random // Buildings, techs, policies, ancient ruins and promotions can have 'triggered' effects object UniqueTriggerActivation { + + fun triggerUnique( + unique: Unique, + city: City, + notification: String? = null, + triggerNotificationText: String? = null + ): Boolean { + return triggerUnique(unique, city.civ, city, tile = city.getCenterTile(), + notification = notification, triggerNotificationText = triggerNotificationText) + } + fun triggerUnique( + unique: Unique, + unit: MapUnit, + notification: String? = null, + triggerNotificationText: String? = null + ): Boolean { + return triggerUnique(unique, unit.civ, unit = unit, tile = unit.currentTile, + notification = notification, triggerNotificationText = triggerNotificationText) + } + /** @return whether an action was successfully performed */ - fun triggerCivwideUnique( + fun triggerUnique( unique: Unique, civInfo: Civilization, city: City? = null, - tile: Tile? = city?.getCenterTile(), + unit: MapUnit? = null, + tile: Tile? = city?.getCenterTile() ?: unit?.currentTile, notification: String? = null, triggerNotificationText: String? = null ): Boolean { @@ -51,7 +72,7 @@ object UniqueTriggerActivation { return true } - if (!unique.conditionalsApply(civInfo, relevantCity)) return false + if (!unique.conditionalsApply(StateForConditionals(civInfo, city, unit, tile))) return false val chosenCity = relevantCity ?: civInfo.cities.firstOrNull { it.isCapital() } @@ -65,28 +86,28 @@ object UniqueTriggerActivation { UniqueType.OneTimeFreeUnit -> { val unitName = unique.params[0] val baseUnit = ruleSet.units[unitName] ?: return false - val unit = civInfo.getEquivalentUnit(baseUnit) - if (unit.isCityFounder() && civInfo.isOneCityChallenger()) + val civUnit = civInfo.getEquivalentUnit(baseUnit) + if (civUnit.isCityFounder() && civInfo.isOneCityChallenger()) return false - val limit = unit.getMatchingUniques(UniqueType.MaxNumberBuildable) + val limit = civUnit.getMatchingUniques(UniqueType.MaxNumberBuildable) .map { it.params[0].toInt() }.minOrNull() - if (limit != null && limit <= civInfo.units.getCivUnits().count { it.name == unit.name }) + if (limit != null && limit <= civInfo.units.getCivUnits().count { it.name == civUnit.name }) return false val placedUnit = when { // Set unit at city if there's an explict city or if there's no tile to set at relevantCity != null || (tile == null && civInfo.cities.isNotEmpty()) -> - civInfo.units.addUnit(unit, chosenCity) ?: return false + civInfo.units.addUnit(civUnit, chosenCity) ?: return false // Else set the unit at the given tile - tile != null -> civInfo.units.placeUnitNearTile(tile.position, unit) ?: return false + tile != null -> civInfo.units.placeUnitNearTile(tile.position, civUnit) ?: return false // Else set unit unit near other units if we have no cities civInfo.units.getCivUnits().any() -> - civInfo.units.placeUnitNearTile(civInfo.units.getCivUnits().first().currentTile.position, unit) ?: return false + civInfo.units.placeUnitNearTile(civInfo.units.getCivUnits().first().currentTile.position, civUnit) ?: return false else -> return false } val notificationText = getNotificationText(notification, triggerNotificationText, - "Gained [1] [${unit.name}] unit(s)") + "Gained [1] [${civUnit.name}] unit(s)") ?: return true civInfo.addNotification( @@ -100,13 +121,13 @@ object UniqueTriggerActivation { UniqueType.OneTimeAmountFreeUnits -> { val unitName = unique.params[1] val baseUnit = ruleSet.units[unitName] ?: return false - val unit = civInfo.getEquivalentUnit(baseUnit) - if (unit.isCityFounder() && civInfo.isOneCityChallenger()) + val civUnit = civInfo.getEquivalentUnit(baseUnit) + if (civUnit.isCityFounder() && civInfo.isOneCityChallenger()) return false - val limit = unit.getMatchingUniques(UniqueType.MaxNumberBuildable) + val limit = civUnit.getMatchingUniques(UniqueType.MaxNumberBuildable) .map { it.params[0].toInt() }.minOrNull() - val unitCount = civInfo.units.getCivUnits().count { it.name == unit.name } + val unitCount = civInfo.units.getCivUnits().count { it.name == civUnit.name } val amountFromTriggerable = unique.params[0].toInt() val actualAmount = when { limit == null -> amountFromTriggerable @@ -121,12 +142,12 @@ object UniqueTriggerActivation { val placedUnit = when { // Set unit at city if there's an explict city or if there's no tile to set at relevantCity != null || (tile == null && civInfo.cities.isNotEmpty()) -> - civInfo.units.addUnit(unit, chosenCity) + civInfo.units.addUnit(civUnit, chosenCity) // Else set the unit at the given tile - tile != null -> civInfo.units.placeUnitNearTile(tile.position, unit) + tile != null -> civInfo.units.placeUnitNearTile(tile.position, civUnit) // Else set unit unit near other units if we have no cities civInfo.units.getCivUnits().any() -> - civInfo.units.placeUnitNearTile(civInfo.units.getCivUnits().first().currentTile.position, unit) + civInfo.units.placeUnitNearTile(civInfo.units.getCivUnits().first().currentTile.position, civUnit) else -> null } if (placedUnit != null) @@ -135,32 +156,32 @@ object UniqueTriggerActivation { if (tilesUnitsWerePlacedOn.isEmpty()) return false val notificationText = getNotificationText(notification, triggerNotificationText, - "Gained [${tilesUnitsWerePlacedOn.size}] [${unit.name}] unit(s)") + "Gained [${tilesUnitsWerePlacedOn.size}] [${civUnit.name}] unit(s)") ?: return true civInfo.addNotification( notificationText, MapUnitAction(tilesUnitsWerePlacedOn), NotificationCategory.Units, - unit.name + civUnit.name ) return true } UniqueType.OneTimeFreeUnitRuins -> { - var unit = civInfo.getEquivalentUnit(unique.params[0]) - if ( unit.isCityFounder() && civInfo.isOneCityChallenger()) { + var civUnit = civInfo.getEquivalentUnit(unique.params[0]) + if ( civUnit.isCityFounder() && civInfo.isOneCityChallenger()) { val replacementUnit = ruleSet.units.values .firstOrNull { it.getMatchingUniques(UniqueType.BuildImprovements) .any { unique -> unique.params[0] == "Land" } } ?: return false - unit = civInfo.getEquivalentUnit(replacementUnit.name) + civUnit = civInfo.getEquivalentUnit(replacementUnit.name) } val placingTile = tile ?: civInfo.cities.random().getCenterTile() - val placedUnit = civInfo.units.placeUnitNearTile(placingTile.position, unit.name) + val placedUnit = civInfo.units.placeUnitNearTile(placingTile.position, civUnit.name) if (notification != null && placedUnit != null) { val notificationText = if (notification.hasPlaceholderParameters()) @@ -236,11 +257,11 @@ object UniqueTriggerActivation { // Anyone an idea for a good icon? if (notification != null) civInfo.addNotification(notification, NotificationCategory.General) - + if (civInfo.isAI() || UncivGame.Current.settings.autoPlay.isAutoPlayingAndFullAI()) { NextTurnAutomation.chooseGreatPerson(civInfo) } - + return true } @@ -395,14 +416,14 @@ object UniqueTriggerActivation { val promotion = unique.params[1] val promotedUnitLocations: MutableList = mutableListOf() - for (unit in civInfo.units.getCivUnits()) { - if (unit.matchesFilter(filter) + for (civUnit in civInfo.units.getCivUnits()) { + if (civUnit.matchesFilter(filter) && ruleSet.unitPromotions.values.any { - it.name == promotion && unit.type.name in it.unitTypes + it.name == promotion && civUnit.type.name in it.unitTypes } ) { - unit.promotions.addPromotion(promotion, isFree = true) - promotedUnitLocations.add(unit.getTile().position) + civUnit.promotions.addPromotion(promotion, isFree = true) + promotedUnitLocations.add(civUnit.getTile().position) } } @@ -697,7 +718,6 @@ object UniqueTriggerActivation { } UniqueType.RemoveBuilding -> { - val applicableCities = if (unique.params[1] == "in this city") sequenceOf(relevantCity!!) else civInfo.cities.asSequence().filter { it.matchesFilter(unique.params[1]) } @@ -706,7 +726,6 @@ object UniqueTriggerActivation { val buildingsToRemove = applicableCity.cityConstructions.getBuiltBuildings().filter { it.matchesFilter(unique.params[0]) }.toSet() - applicableCity.cityConstructions.removeBuildings(buildingsToRemove) } @@ -714,7 +733,6 @@ object UniqueTriggerActivation { } UniqueType.SellBuilding -> { - val applicableCities = if (unique.params[1] == "in this city") sequenceOf(relevantCity!!) else civInfo.cities.asSequence().filter { it.matchesFilter(unique.params[1]) } @@ -727,12 +745,69 @@ object UniqueTriggerActivation { for (building in buildingsToSell) { applicableCity.sellBuilding(building) } - } return true } + UniqueType.OneTimeUnitHeal -> { + if (unit == null) return false + unit.healBy(unique.params[0].toInt()) + if (notification != null) + unit.civ.addNotification(notification, unit.getTile().position, NotificationCategory.Units) // Do we have a heal icon? + return true + } + UniqueType.OneTimeUnitDamage -> { + if (unit == null) return false + MapUnitCombatant(unit).takeDamage(unique.params[0].toInt()) + if (notification != null) + unit.civ.addNotification(notification, unit.getTile().position, NotificationCategory.Units) // Do we have a heal icon? + return true + } + UniqueType.OneTimeUnitGainXP -> { + if (unit == null) return false + if (!unit.baseUnit.isMilitary()) return false + unit.promotions.XP += unique.params[0].toInt() + if (notification != null) + unit.civ.addNotification(notification, unit.getTile().position, NotificationCategory.Units) + return true + } + UniqueType.OneTimeUnitUpgrade -> { + if (unit == null) return false + val upgradeAction = UnitActionsUpgrade.getFreeUpgradeAction(unit) + if (upgradeAction.none()) return false + (upgradeAction.minBy { (it as UpgradeUnitAction).unitToUpgradeTo.cost }).action!!() + if (notification != null) + unit.civ.addNotification(notification, unit.getTile().position, NotificationCategory.Units) + return true + } + UniqueType.OneTimeUnitSpecialUpgrade -> { + if (unit == null) return false + val upgradeAction = UnitActionsUpgrade.getAncientRuinsUpgradeAction(unit) + if (upgradeAction.none()) return false + (upgradeAction.minBy { (it as UpgradeUnitAction).unitToUpgradeTo.cost }).action!!() + if (notification != null) + unit.civ.addNotification(notification, unit.getTile().position, NotificationCategory.Units) + return true + } + UniqueType.OneTimeUnitGainPromotion -> { + if (unit == null) return false + val promotion = unit.civ.gameInfo.ruleset.unitPromotions.keys + .firstOrNull { it == unique.params[0] } + ?: return false + unit.promotions.addPromotion(promotion, true) + if (notification != null) + unit.civ.addNotification(notification, unit.getTile().position, NotificationCategory.Units, unit.name) + return true + } + UniqueType.OneTimeUnitRemovePromotion -> { + if (unit == null) return false + val promotion = unit.civ.gameInfo.ruleset.unitPromotions.keys + .firstOrNull { it == unique.params[0]} + ?: return false + unit.promotions.removePromotion(promotion) + return true + } else -> {} } return false @@ -748,68 +823,4 @@ object UniqueTriggerActivation { } else null } - - /** @return boolean whether an action was successfully performed */ - fun triggerUnitwideUnique( - unique: Unique, - unit: MapUnit, - notification: String? = null, - triggerNotificationText: String? = null - ): Boolean { - when (unique.type) { - UniqueType.OneTimeUnitHeal -> { - unit.healBy(unique.params[0].toInt()) - if (notification != null) - unit.civ.addNotification(notification, unit.getTile().position, NotificationCategory.Units) // Do we have a heal icon? - return true - } - UniqueType.OneTimeUnitDamage -> { - MapUnitCombatant(unit).takeDamage(unique.params[0].toInt()) - if (notification != null) - unit.civ.addNotification(notification, unit.getTile().position, NotificationCategory.Units) // Do we have a heal icon? - return true - } - UniqueType.OneTimeUnitGainXP -> { - if (!unit.baseUnit.isMilitary()) return false - unit.promotions.XP += unique.params[0].toInt() - if (notification != null) - unit.civ.addNotification(notification, unit.getTile().position, NotificationCategory.Units) - return true - } - UniqueType.OneTimeUnitUpgrade -> { - val upgradeAction = UnitActionsUpgrade.getFreeUpgradeAction(unit) - if (upgradeAction.none()) return false - (upgradeAction.minBy { (it as UpgradeUnitAction).unitToUpgradeTo.cost }).action!!() - if (notification != null) - unit.civ.addNotification(notification, unit.getTile().position, NotificationCategory.Units) - return true - } - UniqueType.OneTimeUnitSpecialUpgrade -> { - val upgradeAction = UnitActionsUpgrade.getAncientRuinsUpgradeAction(unit) - if (upgradeAction.none()) return false - (upgradeAction.minBy { (it as UpgradeUnitAction).unitToUpgradeTo.cost }).action!!() - if (notification != null) - unit.civ.addNotification(notification, unit.getTile().position, NotificationCategory.Units) - return true - } - UniqueType.OneTimeUnitGainPromotion -> { - val promotion = unit.civ.gameInfo.ruleset.unitPromotions.keys - .firstOrNull { it == unique.params[0] } - ?: return false - unit.promotions.addPromotion(promotion, true) - if (notification != null) - unit.civ.addNotification(notification, unit.getTile().position, NotificationCategory.Units, unit.name) - return true - } - UniqueType.OneTimeUnitRemovePromotion -> { - val promotion = unit.civ.gameInfo.ruleset.unitPromotions.keys - .firstOrNull { it == unique.params[0]} - ?: return false - unit.promotions.removePromotion(promotion) - return true - } - - else -> return triggerCivwideUnique(unique, civInfo = unit.civ, tile=unit.currentTile, triggerNotificationText = triggerNotificationText) - } - } } diff --git a/core/src/com/unciv/ui/screens/devconsole/ConsoleCivCommands.kt b/core/src/com/unciv/ui/screens/devconsole/ConsoleCivCommands.kt index c3796a0a68..e6633fc66e 100644 --- a/core/src/com/unciv/ui/screens/devconsole/ConsoleCivCommands.kt +++ b/core/src/com/unciv/ui/screens/devconsole/ConsoleCivCommands.kt @@ -43,7 +43,7 @@ class ConsoleCivCommands : ConsoleCommandNode { if (unique.type == null) throw ConsoleErrorException("Unrecognized trigger") val tile = console.screen.mapHolder.selectedTile val city = tile?.getCity() - UniqueTriggerActivation.triggerCivwideUnique(unique, civ, city, tile) + UniqueTriggerActivation.triggerUnique(unique, civ, city, tile = tile) DevConsoleResponse.OK } ) diff --git a/core/src/com/unciv/ui/screens/worldscreen/unit/actions/UnitActionsFromUniques.kt b/core/src/com/unciv/ui/screens/worldscreen/unit/actions/UnitActionsFromUniques.kt index 4b16b06429..dab8b61299 100644 --- a/core/src/com/unciv/ui/screens/worldscreen/unit/actions/UnitActionsFromUniques.kt +++ b/core/src/com/unciv/ui/screens/worldscreen/unit/actions/UnitActionsFromUniques.kt @@ -54,7 +54,7 @@ object UnitActionsFromUniques { val foundAction = { if (unit.civ.playerType != PlayerType.AI) UncivGame.Current.settings.addCompletedTutorialTask("Found city") - unit.civ.addCity(tile.position) + unit.civ.addCity(tile.position, unit) if (hasActionModifiers) UnitActionModifiers.activateSideEffects(unit, unique) else unit.destroy() @@ -178,7 +178,7 @@ object UnitActionsFromUniques { val title = UnitActionModifiers.actionTextWithSideEffects(baseTitle, unique, unit) yield(UnitAction(UnitActionType.TriggerUnique, title) { - UniqueTriggerActivation.triggerUnitwideUnique(unique, unit) + UniqueTriggerActivation.triggerUnique(unique, unit) UnitActionModifiers.activateSideEffects(unit, unique) }) } diff --git a/tests/src/com/unciv/logic/map/UpgradeTests.kt b/tests/src/com/unciv/logic/map/UpgradeTests.kt index 241749e5f3..55e5aefdf1 100644 --- a/tests/src/com/unciv/logic/map/UpgradeTests.kt +++ b/tests/src/com/unciv/logic/map/UpgradeTests.kt @@ -31,7 +31,7 @@ class UpgradeTests { val civ = testGame.addCiv() var unit1 = testGame.addUnit(testUnit.name, civ, testGame.getTile(Vector2.Zero)) val triggerUnique = Unique("This Unit upgrades for free including special upgrades") - UniqueTriggerActivation.triggerUnitwideUnique(triggerUnique, unit1) + UniqueTriggerActivation.triggerUnique(triggerUnique, unit1) unit1 = testGame.getTile(Vector2.Zero).getFirstUnit()!! Assert.assertTrue("Unit should upgrade to special unit, not warrior", unit1.baseUnit == unitToUpgradeTo) @@ -46,7 +46,7 @@ class UpgradeTests { val civ = testGame.addCiv() var unit1 = testGame.addUnit(testUnit.name, civ, testGame.getTile(Vector2.Zero)) val triggerUnique = Unique("This Unit upgrades for free including special upgrades") - UniqueTriggerActivation.triggerUnitwideUnique(triggerUnique, unit1) + UniqueTriggerActivation.triggerUnique(triggerUnique, unit1) unit1 = testGame.getTile(Vector2.Zero).getFirstUnit()!! Assert.assertTrue("Unit should upgrade to Warrior without unique", unit1.baseUnit.name == "Warrior") @@ -67,7 +67,7 @@ class UpgradeTests { upgradeActions.any { (it as UpgradeUnitAction).unitToUpgradeTo == unitToUpgradeTo }) val triggerUnique = Unique("This Unit upgrades for free") - UniqueTriggerActivation.triggerUnitwideUnique(triggerUnique, unit1) + UniqueTriggerActivation.triggerUnique(triggerUnique, unit1) unit1 = testGame.getTile(Vector2.Zero).getFirstUnit()!! Assert.assertTrue(unit1.baseUnit.name == "Warrior") @@ -88,7 +88,7 @@ class UpgradeTests { Assert.assertTrue(upgradeActions.count() == 2) val triggerUnique = Unique("This Unit upgrades for free") - UniqueTriggerActivation.triggerUnitwideUnique(triggerUnique, unit1) + UniqueTriggerActivation.triggerUnique(triggerUnique, unit1) unit1 = testGame.getTile(Vector2.Zero).getFirstUnit()!! Assert.assertFalse(unit1.baseUnit == testUnit)