Skip to content

New countable for adopted policies supports policyFilter #13191

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
May 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion core/src/com/unciv/models/ruleset/Policy.kt
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ open class Policy : RulesetObject() {
return when(filter) {
in Constants.all -> true
name -> true
"[all] branch" -> branch == this
"[${branch.name}] branch" -> true
else -> false
}
Expand Down
12 changes: 12 additions & 0 deletions core/src/com/unciv/models/ruleset/unique/Countables.kt
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,18 @@ enum class Countables(
override fun getKnownValuesForAutocomplete(ruleset: Ruleset) = setOf<String>()
},

FilteredPolicies("Adopted [policyFilter] Policies") {
override fun eval(parameterText: String, stateForConditionals: StateForConditionals): Int? {
val filter = parameterText.getPlaceholderParameters()[0]
val policyManager = stateForConditionals.civInfo?.policies ?: return null
return policyManager.getAdoptedPoliciesMatching(filter, stateForConditionals).size
}
override fun getErrorSeverity(parameterText: String, ruleset: Ruleset): UniqueType.UniqueParameterErrorSeverity? =
UniqueParameterType.PolicyFilter.getTranslatedErrorSeverity(parameterText, ruleset)
override fun getKnownValuesForAutocomplete(ruleset: Ruleset): Set<String> =
UniqueParameterType.PolicyFilter.getKnownValuesForAutocomplete(ruleset)
},

RemainingCivs("Remaining [civFilter] Civilizations") {
override fun eval(parameterText: String, stateForConditionals: StateForConditionals): Int? {
val filter = parameterText.getPlaceholderParameters()[0]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -561,10 +561,9 @@ enum class UniqueParameterType(
PolicyFilter("policyFilter", "Oligarchy",
"The name of any policy, a filtering Unique, any branch (matching only the branch itself)," +
" a branch name with \" Completed\" appended (matches if the branch is completed)," +
" a policy branch as `[branchName] branch` (matching all policies in that branch)," +
" or `[all] branch` which matches all branch starter policies."
" or a policy branch as `[branchName] branch` (matching all policies in that branch)."
) {
override val staticKnownValues = Constants.all + "[all] branch"
override val staticKnownValues = Constants.all
override fun isKnownValue(parameterText: String, ruleset: Ruleset) = when {
parameterText in staticKnownValues -> true
parameterText in ruleset.policies -> true
Expand Down
2 changes: 2 additions & 0 deletions docs/Modders/Unique-parameters.md
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,8 @@ Allowed values:
- Example: `Only available <when number of [[Wounded] Units] is more than [0]>`
- `[buildingFilter] Buildings`
- Example: `Only available <when number of [[Culture] Buildings] is more than [0]>`
- `Adopted [policyFilter] Policies`
- Example: `Only available <when number of [Adopted [Oligarchy] Policies] is more than [0]>`
- `Remaining [civFilter] Civilizations`
- Example: `Only available <when number of [Remaining [City-States] Civilizations] is more than [0]>`
- `Owned [tileFilter] Tiles`
Expand Down
2 changes: 1 addition & 1 deletion docs/Modders/uniques.md
Original file line number Diff line number Diff line change
Expand Up @@ -3556,7 +3556,7 @@ Simple unique parameters are explained by mouseover. Complex parameters are expl
*[modFilter]: A Mod name, case-sensitive _or_ a simple wildcard filter beginning and ending in an Asterisk, case-insensitive.
*[nonNegativeAmount]: This indicates a non-negative whole number, larger than or equal to zero, a '+' sign is optional.
*[policy]: The name of any policy.
*[policyFilter]: The name of any policy, a filtering Unique, any branch (matching only the branch itself), a branch name with " Completed" appended (matches if the branch is completed), a policy branch as `[branchName] branch` (matching all policies in that branch), or `[all] branch` which matches all branch starter policies.
*[policyFilter]: The name of any policy, a filtering Unique, any branch (matching only the branch itself), a branch name with " Completed" appended (matches if the branch is completed), or a policy branch as `[branchName] branch` (matching all policies in that branch).
*[positiveAmount]: This indicates a positive whole number, larger than zero, a '+' sign is optional.
*[promotion]: The name of any promotion.
*[relativeAmount]: This indicates a number, usually with a + or - sign, such as `+25` (this kind of parameter is often followed by '%' which is nevertheless not part of the value).
Expand Down
30 changes: 30 additions & 0 deletions tests/src/com/unciv/testing/TestGame.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import com.unciv.models.ruleset.BeliefType
import com.unciv.models.ruleset.Building
import com.unciv.models.ruleset.IRulesetObject
import com.unciv.models.ruleset.Policy
import com.unciv.models.ruleset.PolicyBranch
import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.RulesetCache
import com.unciv.models.ruleset.Specialist
Expand Down Expand Up @@ -263,8 +264,37 @@ class TestGame(vararg addGlobalUniques: String) {
createdBuilding.isWonder = true
return createdBuilding
}

/**
* Creates a new Policy.
* - This is internally inconsistent as it has no branch, and thus only supports simple tests.
* - Do not run policyFilter on a TestGame with such a Policy (or you'll get a lateinit not initialized exception).
* - Use [createPolicyBranch] to create a wrapping branch and a consistent state that supports testing filters.
*/
fun createPolicy(vararg uniques: String) =
createRulesetObject(ruleset.policies, *uniques) { Policy() }

/**
* Creates a new PolicyBranch, optionally including [policy] as member Policy,
* and including its own "... complete" Policy.
* @see createPolicy
*/
fun createPolicyBranch(vararg uniques: String, policy: Policy? = null): PolicyBranch {
val branch = createRulesetObject(ruleset.policyBranches, *uniques) { PolicyBranch() }
branch.branch = branch
ruleset.policies[branch.name] = branch
if (policy != null) {
policy.branch = branch
branch.policies.add(policy)
}
val complete = Policy()
complete.name = branch.name + Policy.branchCompleteSuffix
complete.branch = branch
branch.policies.add(complete)
ruleset.policies[complete.name] = complete
return branch
}

fun createTileImprovement(vararg uniques: String) =
createRulesetObject(ruleset.tileImprovements, *uniques) { TileImprovement() }
fun createUnitType(vararg uniques: String) =
Expand Down
33 changes: 33 additions & 0 deletions tests/src/com/unciv/uniques/CountableTests.kt
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,37 @@ class CountableTests {
assertEquals(10.5f, civCulture, 0.005f)
}

@Test
fun testPoliciesCountables() {
setupModdedGame()
civ.policies.run {
for (name in listOf(
"Tradition", "Aristocracy", "Legalism", "Oligarchy", "Landed Elite", "Monarchy",
"Liberty", "Citizenship", "Honor", "Piety"
)) {
freePolicies++
val policy = getPolicyByName(name)
adopt(policy)
}
// Don't use a fake Policy without a branch, the policyFilter would stumble over a lateinit.
val taggedPolicyBranch = game.createPolicyBranch("Some marker")
freePolicies++
adopt(taggedPolicyBranch) // Will be completed as it has no member policies
}
val tests = listOf(
"Completed Policy branches" to 2, // Tradition and taggedPolicyBranch
"Adopted [Tradition Complete] Policies" to 1,
"Adopted [[Tradition] branch] Policies" to 7, // Branch start and completion plus 5 members
"Adopted [Liberty Complete] Policies" to 0,
"Adopted [[Liberty] branch] Policies" to 2, // Liberty has only 1 member adopted
"Adopted [Some marker] Policies" to 1,
)
for ((test, expected) in tests) {
val actual = Countables.getCountableAmount(test, StateForConditionals(civ))
assertEquals("Testing `$test` countable:", expected, actual)
}
}

@Test
fun testRulesetValidation() {
/** These are `Pair<String, Int>` with the second being the expected number of parameters to fail UniqueParameterType validation */
Expand All @@ -205,6 +236,8 @@ class CountableTests {
"[+1 Food] <when number of [[Barbarian] Units] is between [[Japanese] Units] and [[Embarked] Units]>" to 1,
"[+1 Food] <when number of [[Science] Buildings] is between [[Wonder] Buildings] and [[All] Buildings]>" to 0,
"[+1 Food] <when number of [[42] Buildings] is between [[Universe] Buildings] and [[Library] Buildings]>" to 2,
"[+1 Food] <when number of [Adopted [Tradition] Policies] is between [Adopted [[Tradition] branch] Policies] and [Adopted [all] Policies]>" to 0,
"[+1 Food] <when number of [Adopted [[Legalism] branch] Policies] is between [Adopted [Folklore] Policies] and [Completed Policy branches]>" to 2,
"[+1 Food] <when number of [Remaining [Human player] Civilizations] is between [Remaining [City-State] Civilizations] and [Remaining [Major] Civilizations]>" to 0,
"[+1 Food] <when number of [Remaining [city-state] Civilizations] is between [Remaining [la la la] Civilizations] and [Remaining [all] Civilizations]>" to 2,
"[+1 Food] <when number of [Owned [Land] Tiles] is between [Owned [Desert] Tiles] and [Owned [All] Tiles]>" to 0,
Expand Down
Loading