diff --git a/core/src/com/unciv/logic/city/CityStats.kt b/core/src/com/unciv/logic/city/CityStats.kt index 57fc02bb27..ed3b515585 100644 --- a/core/src/com/unciv/logic/city/CityStats.kt +++ b/core/src/com/unciv/logic/city/CityStats.kt @@ -295,7 +295,7 @@ class CityStats(val city: City) { for (unique in city.getMatchingUniques(UniqueType.StatPercentFromReligionFollowers)) addUniqueStats(unique, Stat.valueOf(unique.params[1]), min( - unique.params[0].toFloat() * city.religion.getFollowersOfMajorityReligion(), + unique.params[0].toFloat() * city.religion.getFollowersOfOurReligion(), unique.params[2].toFloat() )) diff --git a/core/src/com/unciv/logic/city/managers/CityReligionManager.kt b/core/src/com/unciv/logic/city/managers/CityReligionManager.kt index 6b2da75c78..9b73ea7507 100644 --- a/core/src/com/unciv/logic/city/managers/CityReligionManager.kt +++ b/core/src/com/unciv/logic/city/managers/CityReligionManager.kt @@ -199,6 +199,11 @@ class CityReligionManager : IsPartOfGameInfoSerialization { return followers[majorityReligion] } + fun getFollowersOfOurReligion(): Int { + val ourReligion = city.civ.religionManager.religion ?: return 0 + return followers[ourReligion.name] + } + fun getFollowersOfOtherReligionsThan(religion: String): Int { return followers.filterNot { it.key == religion }.values.sum() } diff --git a/core/src/com/unciv/logic/map/tile/TileInfoImprovementFunctions.kt b/core/src/com/unciv/logic/map/tile/TileInfoImprovementFunctions.kt index 674ccc9e9a..594ecd8a82 100644 --- a/core/src/com/unciv/logic/map/tile/TileInfoImprovementFunctions.kt +++ b/core/src/com/unciv/logic/map/tile/TileInfoImprovementFunctions.kt @@ -11,8 +11,23 @@ import com.unciv.models.ruleset.unique.StateForConditionals import com.unciv.models.ruleset.unique.UniqueType -enum class ImprovementBuildingProblem { - WrongCiv, MissingTech, Unbuildable, NotJustOutsideBorders, OutsideBorders, UnmetConditional, Obsolete, MissingResources, Other +/** Reason why an Improvement cannot be built by a given civ */ +enum class ImprovementBuildingProblem( + /** `true` if this cannot change on the future of this game */ + val permanent: Boolean = false, + /** `true` if the ImprovementPicker should report this problem */ + val reportable: Boolean = false +) { + WrongCiv(permanent = true), + MissingTech(reportable = true), + Unbuildable(permanent = true), + ConditionallyUnbuildable, + NotJustOutsideBorders(reportable = true), + OutsideBorders(reportable = true), + UnmetConditional, + Obsolete(permanent = true), + MissingResources(reportable = true), + Other } class TileInfoImprovementFunctions(val tile: Tile) { @@ -30,8 +45,11 @@ class TileInfoImprovementFunctions(val tile: Tile) { yield(ImprovementBuildingProblem.WrongCiv) if (improvement.techRequired != null && !civInfo.tech.isResearched(improvement.techRequired!!)) yield(ImprovementBuildingProblem.MissingTech) - if (improvement.hasUnique(UniqueType.Unbuildable, stateForConditionals)) + if (improvement.getMatchingUniques(UniqueType.Unbuildable, StateForConditionals.IgnoreConditionals) + .any { it.conditionals.isEmpty() }) yield(ImprovementBuildingProblem.Unbuildable) + else if (improvement.hasUnique(UniqueType.Unbuildable, stateForConditionals)) + yield(ImprovementBuildingProblem.ConditionallyUnbuildable) if (tile.getOwner() != civInfo && !improvement.hasUnique(UniqueType.CanBuildOutsideBorders, stateForConditionals)) { if (!improvement.hasUnique(UniqueType.CanBuildJustOutsideBorders, stateForConditionals)) diff --git a/core/src/com/unciv/models/ruleset/unique/UniqueType.kt b/core/src/com/unciv/models/ruleset/unique/UniqueType.kt index 036bcb7a45..df2b2ed8d1 100644 --- a/core/src/com/unciv/models/ruleset/unique/UniqueType.kt +++ b/core/src/com/unciv/models/ruleset/unique/UniqueType.kt @@ -40,7 +40,7 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags: StatPercentBonusCities("[relativeAmount]% [stat] [cityFilter]", UniqueTarget.Global, UniqueTarget.FollowerBelief), StatPercentFromObject("[relativeAmount]% [stat] from every [tileFilter/buildingFilter]", UniqueTarget.Global, UniqueTarget.FollowerBelief), AllStatsPercentFromObject("[relativeAmount]% Yield from every [tileFilter/buildingFilter]", UniqueTarget.Global, UniqueTarget.FollowerBelief), - StatPercentFromReligionFollowers("[relativeAmount]% [stat] from every follower, up to [relativeAmount]%", UniqueTarget.FollowerBelief), + StatPercentFromReligionFollowers("[relativeAmount]% [stat] from every follower, up to [relativeAmount]%", UniqueTarget.FollowerBelief, UniqueTarget.FounderBelief), BonusStatsFromCityStates("[relativeAmount]% [stat] from City-States", UniqueTarget.Global), StatPercentFromTradeRoutes("[relativeAmount]% [stat] from Trade Routes", UniqueTarget.Global), diff --git a/core/src/com/unciv/models/ruleset/validation/UniqueValidator.kt b/core/src/com/unciv/models/ruleset/validation/UniqueValidator.kt index 649f2a952d..884d06ff26 100644 --- a/core/src/com/unciv/models/ruleset/validation/UniqueValidator.kt +++ b/core/src/com/unciv/models/ruleset/validation/UniqueValidator.kt @@ -216,7 +216,7 @@ class UniqueValidator(val ruleset: Ruleset) { private fun isFilteringUniqueAllowed(unique: Unique): Boolean { // Isolate this decision, to allow easy change of approach - // This says: Must have no conditionals or parameters, and is contained in GlobalUniques + // This says: Must have no conditionals or parameters, and is used in any "filtering" parameter of another Unique if (unique.conditionals.isNotEmpty() || unique.params.isNotEmpty()) return false return unique.text in allUniqueParameters // referenced at least once from elsewhere } diff --git a/core/src/com/unciv/ui/screens/pickerscreens/ImprovementPickerScreen.kt b/core/src/com/unciv/ui/screens/pickerscreens/ImprovementPickerScreen.kt index d55983f4ee..2ed2dcd17f 100644 --- a/core/src/com/unciv/ui/screens/pickerscreens/ImprovementPickerScreen.kt +++ b/core/src/com/unciv/ui/screens/pickerscreens/ImprovementPickerScreen.kt @@ -31,16 +31,8 @@ class ImprovementPickerScreen( ) : PickerScreen() { companion object { - /** Set of resolvable improvement building problems that this class knows how to report. */ - private val reportableProblems = setOf( - ImprovementBuildingProblem.MissingTech, - ImprovementBuildingProblem.NotJustOutsideBorders, - ImprovementBuildingProblem.OutsideBorders, - ImprovementBuildingProblem.MissingResources - ) - /** Return true if we can report improvements associated with the [problems] (or there are no problems for it at all). */ - fun canReport(problems: Collection) = problems.all { it in reportableProblems } + fun canReport(problems: Collection) = problems.all { it.reportable } } private var selectedImprovement: TileImprovement? = null 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 06f924bafa..dd142447c2 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 @@ -236,6 +236,11 @@ object UnitActionsFromUniques { val improvement = tile.ruleset.tileImprovements[improvementName] ?: continue + // Try to skip Improvements we can never build + // (getImprovementBuildingProblems catches those so the button is always disabled, but it nevertheless looks nicer) + if (tile.improvementFunctions.getImprovementBuildingProblems(improvement, unit.civ).any { it.permanent }) + continue + val resourcesAvailable = improvement.uniqueObjects.none { improvementUnique -> improvementUnique.isOfType(UniqueType.ConsumesResources) && diff --git a/docs/Modders/Mod-file-structure/1-Overview.md b/docs/Modders/Mod-file-structure/1-Overview.md index 05eee5d085..bad089bb06 100644 --- a/docs/Modders/Mod-file-structure/1-Overview.md +++ b/docs/Modders/Mod-file-structure/1-Overview.md @@ -95,7 +95,10 @@ Example: `"uniques":["[+1 Gold] "]` on a building - does almost All Unique "types" that have an implementation in Unciv are automatically documented in [uniques](../uniques.md). Note that file is entirely machine-generated from source code structures. Also kindly note the separate sections for [conditionals](../uniques.md#conditional-uniques) and [trigger conditions](../uniques.md#triggercondition-uniques). Uniques that do not correspond to any of those entries (verbatim including upper/lower case!) are called "untyped", will have no _direct_ effect, and may result in the "Ruleset Validator" showing warnings (see the Options Tab "Locate mod errors", it also runs when starting new games). -A legitimate use of "untyped" Uniques is their use as markers that can be recognized elsewhere in filters (example: "Aircraft" in the vanilla rulesets used as [Unit filter](../Unique-parameters.md#baseunitfilter)). To allow "Ruleset Validator" to warn about mistakes leading to untyped uniques, but still allow the "filtering Unique" use, those should be "declared" by including each in [GlobalUniques](5-Miscellaneous-JSON-files.md#globaluniquesjson), too. +A legitimate use of "untyped" Uniques is their use as markers that can be recognized elsewhere in **filters** (example: "Aircraft" in the vanilla rulesets used as [Unit filter](../Unique-parameters.md#baseunitfilter)). +This use is recognized by the "Ruleset Validator" and not flagged as invalid - but a filtering Unique must also use _no placeholders or conditionals_ to pass the test. +If you get the "not found in Unciv's unique types" warning, but are sure you are using a correct filtering Unique, please look for exactly identical spelling in all places, including upper/lower case. +Note: Currently some mods use untyped Uniques not for filtering purposes, but as purely informational tool. The team will try to think of an approach for that use that won't trigger validation warnings without reducing validation quality, but as of now, those are unavoidable. ## Information on JSON files used in the game diff --git a/docs/Modders/Mod-file-structure/5-Miscellaneous-JSON-files.md b/docs/Modders/Mod-file-structure/5-Miscellaneous-JSON-files.md index 416332e743..362e159d64 100644 --- a/docs/Modders/Mod-file-structure/5-Miscellaneous-JSON-files.md +++ b/docs/Modders/Mod-file-structure/5-Miscellaneous-JSON-files.md @@ -214,9 +214,6 @@ Defines uniques that apply globally. e.g. Vanilla rulesets define the effects of Only the `uniques` field is used, but a name must still be set (the Ruleset validator might display it). When extension rulesets define GlobalUniques, all uniques are merged. At the moment there is no way to change/remove uniques set by a base mod. -Note: Mods can use "arbitrary" Uniques as purely filtering uniques. They are not "Typed" by Unciv code and thus have no actual effect implementation - except by being filterable elsewhere. -In the near future, the ruleset validator will show warnings for all these, unless they are also included here, as validation that they are intentional (and - they **must** have **no** placeholders or conditionals). - ## Tutorials.json [link to original](https://github.com/yairm210/Unciv/tree/master/android/assets/jsons/Tutorials.json) diff --git a/docs/Modders/uniques.md b/docs/Modders/uniques.md index ff3775efaa..e9df5761e1 100644 --- a/docs/Modders/uniques.md +++ b/docs/Modders/uniques.md @@ -934,16 +934,16 @@ Simple unique parameters are explained by mouseover. Complex parameters are expl Applicable to: FounderBelief +??? example "[relativeAmount]% [stat] from every follower, up to [relativeAmount]%" + Example: "[+20]% [Culture] from every follower, up to [+20]%" + + Applicable to: FounderBelief, FollowerBelief + ## FollowerBelief uniques !!! note "" Uniques for Pantheon and Follower type beliefs, that will apply to each city where the religion is the majority religion -??? example "[relativeAmount]% [stat] from every follower, up to [relativeAmount]%" - Example: "[+20]% [Culture] from every follower, up to [+20]%" - - Applicable to: FollowerBelief - ??? example "Earn [amount]% of [mapUnitFilter] unit's [costOrStrength] as [civWideStat] when killed within 4 tiles of a city following this religion" Example: "Earn [3]% of [Wounded] unit's [Cost] as [Gold] when killed within 4 tiles of a city following this religion"