From ae501c37507b31e6db80760e56e2986f5e34b2c1 Mon Sep 17 00:00:00 2001 From: Yair Morgenstern Date: Wed, 1 Mar 2023 16:09:44 +0200 Subject: [PATCH] 'create improvement' action, and modifiers --- .../jsons/Civ V - Gods & Kings/Units.json | 30 ++++++++---- .../assets/jsons/Civ V - Vanilla/Units.json | 46 +++++++++---------- .../automation/unit/SpecificUnitAutomation.kt | 3 +- .../logic/automation/unit/UnitAutomation.kt | 3 +- .../unciv/logic/map/mapunit/MapUnitCache.kt | 5 +- .../unciv/models/ruleset/RulesetValidator.kt | 5 +- .../models/ruleset/tile/TileImprovement.kt | 5 +- .../unciv/models/ruleset/unique/UniqueType.kt | 9 +++- .../worldscreen/unit/actions/UnitActions.kt | 27 ++++++++--- docs/Modders/uniques.md | 10 +++- 10 files changed, 95 insertions(+), 48 deletions(-) diff --git a/android/assets/jsons/Civ V - Gods & Kings/Units.json b/android/assets/jsons/Civ V - Gods & Kings/Units.json index e7cf30f215..2426ca4c37 100644 --- a/android/assets/jsons/Civ V - Gods & Kings/Units.json +++ b/android/assets/jsons/Civ V - Gods & Kings/Units.json @@ -1599,33 +1599,44 @@ { "name": "Great Artist", "unitType": "Civilian", - "uniques": ["Can start an [8]-turn golden age", "Can construct [Landmark]", "Great Person - [Culture]", "Unbuildable", "Uncapturable"], + "uniques": ["Can start an [8]-turn golden age", + "Can instantly construct a [Landmark] improvement ", + "Great Person - [Culture]", "Unbuildable", "Uncapturable"], "movement": 2 }, { "name": "Great Scientist", "unitType": "Civilian", - "uniques": ["Can hurry technology research", "Can construct [Academy]", "Great Person - [Science]", "Unbuildable", "Uncapturable"], + "uniques": ["Can hurry technology research", + "Can instantly construct a [Academy] improvement ", + "Great Person - [Science]", "Unbuildable", "Uncapturable"], "movement": 2 }, { "name": "Great Merchant", "unitType": "Civilian", "uniques": ["Can undertake a trade mission with City-State, giving a large sum of gold and [30] Influence", - "Can construct [Customs house]", "Great Person - [Gold]", "Unbuildable", "Uncapturable"], + "Can instantly construct a [Customs house] improvement ", + "Great Person - [Gold]", "Unbuildable", "Uncapturable"], "movement": 2 }, { "name": "Great Engineer", "unitType": "Civilian", - "uniques": ["Can speed up construction of a building", "Can construct [Manufactory]", "Great Person - [Production]", "Unbuildable", "Uncapturable"], + "uniques": ["Can speed up construction of a building", + "Can instantly construct a [Manufactory] improvement ", + "Great Person - [Production]", "Unbuildable", "Uncapturable"], "movement": 2 }, { "name": "Great Prophet", "unitType": "Civilian", - "uniques": ["Can construct [Holy site] ", "Can [Spread Religion] [4] times", - "Removes other religions when spreading religion", "May found a religion", "May enhance a religion", + "uniques": [ + "Can instantly construct a [Holy site] improvement ", + "Can [Spread Religion] [4] times", + "Removes other religions when spreading religion", + "May found a religion", + "May enhance a religion", "May enter foreign tiles without open borders", "[-1] Sight", "Great Person - [Faith]", "Unbuildable", "Religious Unit", "Hidden when religion is disabled", "Takes your religion over the one in their birth city"], @@ -1635,7 +1646,8 @@ { "name": "Great General", "unitType": "Civilian", - "uniques": ["Can start an [8]-turn golden age", "[+15]% Strength bonus for [Military] units within [2] tiles", "Can construct [Citadel]", + "uniques": ["Can start an [8]-turn golden age", "[+15]% Strength bonus for [Military] units within [2] tiles", + "Can instantly construct a [Citadel] improvement ", "Great Person - [War]", "Unbuildable", "Uncapturable"], "movement": 2 }, @@ -1645,7 +1657,9 @@ "uniqueTo": "Mongolia", "replaces": "Great General", "uniques": ["Can start an [8]-turn golden age","[+15]% Strength bonus for [Military] units within [2] tiles", - "All adjacent units heal [+15] HP when healing", "[+15] HP when healing", "Can construct [Citadel]", "Great Person - [War]", "Unbuildable", "Uncapturable"], + "All adjacent units heal [+15] HP when healing", "[+15] HP when healing", + "Can instantly construct a [Citadel] improvement ", + "Great Person - [War]", "Unbuildable", "Uncapturable"], "movement": 5 }, diff --git a/android/assets/jsons/Civ V - Vanilla/Units.json b/android/assets/jsons/Civ V - Vanilla/Units.json index 1e250517a3..94849caaed 100644 --- a/android/assets/jsons/Civ V - Vanilla/Units.json +++ b/android/assets/jsons/Civ V - Vanilla/Units.json @@ -380,22 +380,6 @@ "promotions": ["[Mohawk Warrior] ability"], "attackSound": "metalhit" }, - /* - { - "name": "Swordsman", - "unitType": "Sword", - "movement": 2, - "strength": 14, - "cost": 75, - "requiredTech": "Iron Working", - "upgradesTo": "Longswordsman", - "obsoleteTech": "Gunpowder", - "requiredResource": "Iron", - "hurryCostModifier": 20, - "attackSound": "metalhit" - // Barbarian unique Swordsman. Has same icon and name but slightly different 3d texture - }, - */ // Medieval Era { @@ -1274,33 +1258,44 @@ { "name": "Great Artist", "unitType": "Civilian", - "uniques": ["Can start an [8]-turn golden age", "Can construct [Landmark]", "Great Person - [Culture]", "Unbuildable", "Uncapturable"], + "uniques": ["Can start an [8]-turn golden age", + "Can instantly construct a [Landmark] improvement ", + "Great Person - [Culture]", "Unbuildable", "Uncapturable"], "movement": 2 }, { "name": "Great Scientist", "unitType": "Civilian", - "uniques": ["Can hurry technology research", "Can construct [Academy]", "Great Person - [Science]", "Unbuildable", "Uncapturable"], + "uniques": ["Can hurry technology research", + "Can instantly construct a [Academy] improvement ", + "Great Person - [Science]", "Unbuildable", "Uncapturable"], "movement": 2 }, { "name": "Great Merchant", "unitType": "Civilian", "uniques": ["Can undertake a trade mission with City-State, giving a large sum of gold and [30] Influence", - "Can construct [Customs house]", "Great Person - [Gold]", "Unbuildable", "Uncapturable"], + "Can instantly construct a [Customs house] improvement ", + "Great Person - [Gold]", "Unbuildable", "Uncapturable"], "movement": 2 }, { "name": "Great Engineer", "unitType": "Civilian", - "uniques": ["Can speed up construction of a building", "Can construct [Manufactory]", "Great Person - [Production]", "Unbuildable", "Uncapturable"], + "uniques": ["Can speed up construction of a building", + "Can instantly construct a [Manufactory] improvement ", + "Great Person - [Production]", "Unbuildable", "Uncapturable"], "movement": 2 }, { "name": "Great Prophet", "unitType": "Civilian", - "uniques": ["Can construct [Holy site] ", "Can [Spread Religion] [4] times", - "Removes other religions when spreading religion", "May found a religion", "May enhance a religion", + "uniques": [ + "Can instantly construct a [Holy site] improvement ", + "Can [Spread Religion] [4] times", + "Removes other religions when spreading religion", + "May found a religion", + "May enhance a religion", "May enter foreign tiles without open borders", "[-1] Sight", "Great Person - [Faith]", "Unbuildable", "Religious Unit", "Hidden when religion is disabled", "Takes your religion over the one in their birth city"], @@ -1310,7 +1305,8 @@ { "name": "Great General", "unitType": "Civilian", - "uniques": ["Can start an [8]-turn golden age", "[+15]% Strength bonus for [Military] units within [2] tiles", "Can construct [Citadel]", + "uniques": ["Can start an [8]-turn golden age", "[+15]% Strength bonus for [Military] units within [2] tiles", + "Can instantly construct a [Citadel] improvement ", "Great Person - [War]", "Unbuildable", "Uncapturable"], "movement": 2 }, @@ -1320,7 +1316,9 @@ "uniqueTo": "Mongolia", "replaces": "Great General", "uniques": ["Can start an [8]-turn golden age", "[+15]% Strength bonus for [Military] units within [2] tiles", - "All adjacent units heal [+15] HP when healing", "[+15] HP when healing", "Can construct [Citadel]", "Great Person - [War]", "Unbuildable", "Uncapturable"], + "All adjacent units heal [+15] HP when healing", "[+15] HP when healing", + "Can instantly construct a [Citadel] improvement ", + "Great Person - [War]", "Unbuildable", "Uncapturable"], "movement": 5 }, diff --git a/core/src/com/unciv/logic/automation/unit/SpecificUnitAutomation.kt b/core/src/com/unciv/logic/automation/unit/SpecificUnitAutomation.kt index d9d3aa00b0..aa620d90b8 100644 --- a/core/src/com/unciv/logic/automation/unit/SpecificUnitAutomation.kt +++ b/core/src/com/unciv/logic/automation/unit/SpecificUnitAutomation.kt @@ -248,7 +248,8 @@ object SpecificUnitAutomation { } fun automateImprovementPlacer(unit: MapUnit) { - val improvementBuildingUniques = unit.getMatchingUniques(UniqueType.ConstructImprovementConsumingUnit) + val improvementBuildingUniques = unit.getMatchingUniques(UniqueType.ConstructImprovementConsumingUnit) + + unit.getMatchingUniques(UniqueType.ConstructImprovementInstantly) val improvementName = improvementBuildingUniques.first().params[0] val improvement = unit.civ.gameInfo.ruleset.tileImprovements[improvementName] diff --git a/core/src/com/unciv/logic/automation/unit/UnitAutomation.kt b/core/src/com/unciv/logic/automation/unit/UnitAutomation.kt index 6e3666ad07..d1bdf39960 100644 --- a/core/src/com/unciv/logic/automation/unit/UnitAutomation.kt +++ b/core/src/com/unciv/logic/automation/unit/UnitAutomation.kt @@ -262,7 +262,8 @@ object UnitAutomation { if (unit.hasUnique(UniqueType.PreventSpreadingReligion) || unit.canDoReligiousAction(Constants.removeHeresy)) return SpecificUnitAutomation.automateInquisitor(unit) - if (unit.hasUnique(UniqueType.ConstructImprovementConsumingUnit)) + if (unit.hasUnique(UniqueType.ConstructImprovementConsumingUnit) + || unit.hasUnique(UniqueType.ConstructImprovementInstantly)) // catch great prophet for civs who can't found/enhance/spread religion return SpecificUnitAutomation.automateImprovementPlacer(unit) // includes great people plus moddable units diff --git a/core/src/com/unciv/logic/map/mapunit/MapUnitCache.kt b/core/src/com/unciv/logic/map/mapunit/MapUnitCache.kt index 3b2110cbc3..b9ffcc585e 100644 --- a/core/src/com/unciv/logic/map/mapunit/MapUnitCache.kt +++ b/core/src/com/unciv/logic/map/mapunit/MapUnitCache.kt @@ -121,7 +121,10 @@ class MapUnitCache(val mapUnit: MapUnit) { || mapUnit.hasUnique(UniqueType.CanEnterForeignTilesButLosesReligiousStrength) hasStrengthBonusInRadiusUnique = mapUnit.hasUnique(UniqueType.StrengthBonusInRadius) - hasCitadelPlacementUnique = mapUnit.getMatchingUniques(UniqueType.ConstructImprovementConsumingUnit) + hasCitadelPlacementUnique = ( + mapUnit.getMatchingUniques(UniqueType.ConstructImprovementConsumingUnit) + + mapUnit.getMatchingUniques(UniqueType.ConstructImprovementInstantly) + ) .mapNotNull { mapUnit.civ.gameInfo.ruleset.tileImprovements[it.params[0]] } .any { it.hasUnique(UniqueType.TakesOverAdjacentTiles) } } diff --git a/core/src/com/unciv/models/ruleset/RulesetValidator.kt b/core/src/com/unciv/models/ruleset/RulesetValidator.kt index ae18f57970..866b0855af 100644 --- a/core/src/com/unciv/models/ruleset/RulesetValidator.kt +++ b/core/src/com/unciv/models/ruleset/RulesetValidator.kt @@ -167,7 +167,8 @@ class RulesetValidator(val ruleset: Ruleset) { lines += "${unit.name} contains promotion $promotion which does not exist!" if (!ruleset.unitTypes.containsKey(unit.unitType) && (ruleset.unitTypes.isNotEmpty() || !vanillaRuleset.unitTypes.containsKey(unit.unitType))) lines += "${unit.name} is of type ${unit.unitType}, which does not exist!" - for (unique in unit.getMatchingUniques(UniqueType.ConstructImprovementConsumingUnit)) { + for (unique in unit.getMatchingUniques(UniqueType.ConstructImprovementConsumingUnit) + + unit.getMatchingUniques(UniqueType.ConstructImprovementInstantly)) { val improvementName = unique.params[0] if (ruleset.tileImprovements[improvementName]==null) continue // this will be caught in the checkUniques if ((ruleset.tileImprovements[improvementName] as Stats).none() && @@ -545,7 +546,7 @@ class RulesetValidator(val ruleset: Ruleset) { // 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 })) + && unique.conditionals.any { it.type == UniqueType.UnitActionConsumeUnit })) 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/tile/TileImprovement.kt b/core/src/com/unciv/models/ruleset/tile/TileImprovement.kt index ef1f33a103..88481c1005 100644 --- a/core/src/com/unciv/models/ruleset/tile/TileImprovement.kt +++ b/core/src/com/unciv/models/ruleset/tile/TileImprovement.kt @@ -13,9 +13,9 @@ import com.unciv.models.ruleset.unique.UniqueTarget import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.ruleset.unit.BaseUnit import com.unciv.models.translations.tr +import com.unciv.ui.components.extensions.toPercent import com.unciv.ui.screens.civilopediascreen.CivilopediaScreen.Companion.showReligionInCivilopedia import com.unciv.ui.screens.civilopediascreen.FormattedLine -import com.unciv.ui.components.extensions.toPercent import com.unciv.ui.screens.worldscreen.unit.actions.UnitActions import kotlin.math.roundToInt @@ -274,7 +274,8 @@ class TileImprovement : RulesetStatsObject() { private fun getCreatingUnits(ruleset: Ruleset): List { return ruleset.units.values.asSequence() .filter { unit -> - unit.getMatchingUniques(UniqueType.ConstructImprovementConsumingUnit, StateForConditionals.IgnoreConditionals) + (unit.getMatchingUniques(UniqueType.ConstructImprovementConsumingUnit, StateForConditionals.IgnoreConditionals) + + unit.getMatchingUniques(UniqueType.ConstructImprovementInstantly)) .any { it.params[0] == name } }.toList() } diff --git a/core/src/com/unciv/models/ruleset/unique/UniqueType.kt b/core/src/com/unciv/models/ruleset/unique/UniqueType.kt index 7deb29310f..53ec8381f2 100644 --- a/core/src/com/unciv/models/ruleset/unique/UniqueType.kt +++ b/core/src/com/unciv/models/ruleset/unique/UniqueType.kt @@ -347,7 +347,11 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags: ///////////////////////////////////////// region UNIT UNIQUES ///////////////////////////////////////// // Unit action uniques + // Unit actions should look like: "Can {action description}, to allow them to be combined with modifiers + FoundCity("Founds a new city", UniqueTarget.Unit), + ConstructImprovementInstantly("Can instantly construct a [improvementName] improvement", UniqueTarget.Unit), + @Deprecated("as of 4.5.2", ReplaceWith("Can instantly construct a [improvementName] improvement")) ConstructImprovementConsumingUnit("Can construct [improvementName]", UniqueTarget.Unit), BuildImprovements("Can build [improvementFilter/terrainFilter] improvements on tiles", UniqueTarget.Unit), CreateWaterImprovements("May create improvements on water resources", UniqueTarget.Unit), @@ -501,7 +505,10 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags: ///////////////////////////////////////// region UNIT ACTION MODIFIERS ///////////////////////////////////////// - ConditionalConsumeUnit("by consuming this unit", UniqueTarget.UnitActionModifier), + UnitActionConsumeUnit("consuming this unit", UniqueTarget.UnitActionModifier), + UnitActionTriggerUnitUnique("as an action", UniqueTarget.UnitActionModifier), + @Deprecated("as of 4.5.2", ReplaceWith("consuming this unit")) + UnitActionConsumeUnitOld("by consuming this unit", UniqueTarget.UnitActionModifier), // endregion diff --git a/core/src/com/unciv/ui/screens/worldscreen/unit/actions/UnitActions.kt b/core/src/com/unciv/ui/screens/worldscreen/unit/actions/UnitActions.kt index 557a67ddb8..e07fd2b1e7 100644 --- a/core/src/com/unciv/ui/screens/worldscreen/unit/actions/UnitActions.kt +++ b/core/src/com/unciv/ui/screens/worldscreen/unit/actions/UnitActions.kt @@ -18,8 +18,10 @@ import com.unciv.models.UncivSound import com.unciv.models.UnitAction 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.UniqueTriggerActivation import com.unciv.models.ruleset.unique.UniqueType +import com.unciv.models.translations.removeConditionals import com.unciv.models.translations.tr import com.unciv.ui.components.Fonts import com.unciv.ui.popups.ConfirmPopup @@ -453,7 +455,8 @@ object UnitActions { fun getImprovementConstructionActions(unit: MapUnit, tile: Tile): ArrayList { val finalActions = ArrayList() - val uniquesToCheck = unit.getMatchingUniques(UniqueType.ConstructImprovementConsumingUnit) + val uniquesToCheck = unit.getMatchingUniques(UniqueType.ConstructImprovementConsumingUnit) + + unit.getMatchingUniques(UniqueType.ConstructImprovementInstantly) val civResources = unit.civ.getCivResourcesByName() for (unique in uniquesToCheck) { @@ -474,7 +477,9 @@ object UnitActions { unitTile.changeImprovement(improvementName) unitTile.stopWorkingOnImprovement() improvement.handleImprovementCompletion(unit) - unit.consume() + + if (unique.type == UniqueType.ConstructImprovementConsumingUnit) unit.consume() + else activateSideEffects(unit, unique) }.takeIf { resourcesAvailable && unit.currentMovement > 0f @@ -637,10 +642,11 @@ object UnitActions { private 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.civ) - unit.consume() + if (!unique.conditionals.any { it.type == UniqueType.UnitActionTriggerUnitUnique }) continue + + val unitAction = UnitAction(type = UnitActionType.TriggerUnique, unique.text.removeConditionals()){ + UniqueTriggerActivation.triggerUnitwideUnique(unique, unit) + activateSideEffects(unit, unique) } actionList += unitAction } @@ -666,4 +672,13 @@ object UnitActions { } ) } + + fun activateSideEffects(unit: MapUnit, actionUnique: Unique){ + for (conditional in actionUnique.conditionals){ + when (conditional.type){ + UniqueType.UnitActionConsumeUnit -> unit.consume() + else -> unit.useMovementPoints(1f) + } + } + } } diff --git a/docs/Modders/uniques.md b/docs/Modders/uniques.md index e6ee0cd964..fa4815eed7 100644 --- a/docs/Modders/uniques.md +++ b/docs/Modders/uniques.md @@ -1039,8 +1039,8 @@ Simple unique parameters are explained by mouseover. Complex parameters are expl ??? example "Founds a new city" Applicable to: Unit -??? example "Can construct [improvementName]" - Example: "Can construct [Trading Post]" +??? example "Can instantly construct a [improvementName] improvement" + Example: "Can instantly construct a [Trading Post] improvement" Applicable to: Unit @@ -1975,6 +1975,12 @@ Simple unique parameters are explained by mouseover. Complex parameters are expl Applicable to: UnitTriggerCondition ## UnitActionModifier uniques +??? example "<consuming this unit>" + Applicable to: UnitActionModifier + +??? example "<as an action>" + Applicable to: UnitActionModifier + ??? example "<by consuming this unit>" Applicable to: UnitActionModifier