Fix a few now flagged but working Unique targets (#9845)

* Allow a few working UniqueType-UniqueTarget combos in RulesetValidator

* RulesetValidator checks GlobalUniques

* Alternate Settlers and Workers recognized via Unique - same rules in RulesetValidator and GameStarter, wiki clarification
This commit is contained in:
SomeTroglodyte 2023-07-30 16:38:26 +02:00 committed by GitHub
parent ae7ac6a063
commit b992144ecd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 56 additions and 37 deletions

View File

@ -435,7 +435,7 @@ object GameStarter {
}
private fun getStartingUnitsForEraAndDifficulty(civ: Civilization, gameInfo: GameInfo, ruleset: Ruleset, startingEra: String): MutableList<String> {
val startingUnits = ruleset.eras[startingEra]!!.getStartingUnits().toMutableList()
val startingUnits = ruleset.eras[startingEra]!!.getStartingUnits(ruleset)
// Add extra units granted by difficulty
startingUnits.addAll(when {

View File

@ -40,6 +40,8 @@ class RulesetValidator(val ruleset: Ruleset) {
val rulesetInvariant = UniqueType.UniqueComplianceErrorSeverity.RulesetInvariant
val rulesetSpecific = UniqueType.UniqueComplianceErrorSeverity.RulesetSpecific
checkUniques(ruleset.globalUniques, lines, rulesetInvariant, tryFixUnknownUniques)
for (unit in ruleset.units.values) {
if (unit.upgradesTo == unit.name || (unit.upgradesTo != null && unit.upgradesTo == unit.replaces))
lines += "${unit.name} upgrades to itself!"
@ -145,6 +147,7 @@ class RulesetValidator(val ruleset: Ruleset) {
val vanillaRuleset = RulesetCache.getVanillaRuleset() // for UnitTypes fallback
checkUniques(ruleset.globalUniques, lines, rulesetSpecific, tryFixUnknownUniques)
if (ruleset.units.values.none { it.hasUnique(UniqueType.FoundCity, StateForConditionals.IgnoreConditionals) })
lines += "No city-founding units in ruleset!"
@ -313,13 +316,14 @@ class RulesetValidator(val ruleset: Ruleset) {
if (building !in ruleset.buildings)
lines += "Nonexistent building $building built by settlers when starting in ${era.name}"
// todo the whole 'starting unit' thing needs to be redone, there's no reason we can't have a single list containing all the starting units.
if (era.startingSettlerUnit !in ruleset.units && (era.startingSettlerUnit!= Constants.settler || ruleset.units.values.none { it.hasUnique(
UniqueType.FoundCity) }))
if (era.startingSettlerUnit !in ruleset.units
&& ruleset.units.values.none { it.hasUnique(UniqueType.FoundCity) })
lines += "Nonexistent unit ${era.startingSettlerUnit} marked as starting unit when starting in ${era.name}"
if (era.startingWorkerCount!=0 && era.startingWorkerUnit !in ruleset.units)
if (era.startingWorkerCount != 0 && era.startingWorkerUnit !in ruleset.units
&& ruleset.units.values.none { it.hasUnique(UniqueType.BuildImprovements) })
lines += "Nonexistent unit ${era.startingWorkerUnit} marked as starting unit when starting in ${era.name}"
if ((era.startingMilitaryUnitCount !=0 || allDifficultiesStartingUnits.contains(
if ((era.startingMilitaryUnitCount != 0 || allDifficultiesStartingUnits.contains(
Constants.eraSpecificUnit)) && era.startingMilitaryUnit !in ruleset.units)
lines += "Nonexistent unit ${era.startingMilitaryUnit} marked as starting unit when starting in ${era.name}"
if (era.researchAgreementCost < 0 || era.startingSettlerCount < 0 || era.startingWorkerCount < 0 || era.startingMilitaryUnitCount < 0 || era.startingGold < 0 || era.startingCulture < 0)

View File

@ -1,6 +1,7 @@
package com.unciv.models.ruleset.tech
import com.badlogic.gdx.graphics.Color
import com.unciv.logic.UncivShowableException
import com.unciv.models.ruleset.IRulesetObject
import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.RulesetObject
@ -82,10 +83,22 @@ class Era : RulesetObject() {
}.map { it.first }.distinct()
}
fun getStartingUnits(): List<String> {
fun getStartingUnits(ruleset: Ruleset): MutableList<String> {
val startingUnits = mutableListOf<String>()
repeat(startingSettlerCount) { startingUnits.add(startingSettlerUnit) }
repeat(startingWorkerCount) { startingUnits.add(startingWorkerUnit) }
val startingSettlerName: String =
if (startingSettlerUnit in ruleset.units) startingSettlerUnit
else ruleset.units.values
.firstOrNull { it.hasUnique(UniqueType.FoundCity) }
?.name
?: throw UncivShowableException("No Settler unit found for era $name")
val startingWorkerName: String =
if (startingWorkerCount == 0 || startingWorkerUnit in ruleset.units) startingWorkerUnit
else ruleset.units.values
.firstOrNull { it.hasUnique(UniqueType.BuildImprovements) }
?.name
?: throw UncivShowableException("No Worker unit found for era $name")
repeat(startingSettlerCount) { startingUnits.add(startingSettlerName) }
repeat(startingWorkerCount) { startingUnits.add(startingWorkerName) }
repeat(startingMilitaryUnitCount) { startingUnits.add(startingMilitaryUnit) }
return startingUnits
}

View File

@ -34,7 +34,7 @@ enum class UniqueTarget(
Tech(inheritsFrom = Global),
Policy(inheritsFrom = Global),
FounderBelief("Uniques for Founder and Enhancer type Beliefs, that will apply to the founder of this religion", inheritsFrom = Global),
FollowerBelief("Uniques for Pantheon and Follower type beliefs, that will apply to each city where the religion is the majority religion"),
FollowerBelief("Uniques for Pantheon and Follower type beliefs, that will apply to each city where the religion is the majority religion", inheritsFrom = Triggerable),
// City-specific
Building(inheritsFrom = Global),

View File

@ -184,7 +184,7 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags:
/// Resource production & consumption
ConsumesResources("Consumes [amount] [resource]", UniqueTarget.Improvement, UniqueTarget.Building, UniqueTarget.Unit),
ProvidesResources("Provides [amount] [resource]", UniqueTarget.Improvement, UniqueTarget.Global),
ProvidesResources("Provides [amount] [resource]", UniqueTarget.Global, UniqueTarget.Improvement, UniqueTarget.FollowerBelief),
CostsResources("Costs [amount] [stockpiledResource]", UniqueTarget.Improvement, UniqueTarget.Building, UniqueTarget.Unit),
// Todo: Get rid of forced sign (+[relativeAmount]) and unify these two, e.g.: "[relativeAmount]% [resource/resourceType] production"
// Note that the parameter type 'resourceType' (strategic, luxury, bonus) currently doesn't exist and should then be added as well
@ -497,7 +497,7 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags:
TerrainGrantsPromotion("Grants [promotion] ([comment]) to adjacent [mapUnitFilter] units for the rest of the game", UniqueTarget.Terrain),
GrantsCityStrength("[amount] Strength for cities built on this terrain", UniqueTarget.Terrain),
ProductionBonusWhenRemoved("Provides a one-time Production bonus to the closest city when cut down", UniqueTarget.Terrain),
Vegetation("Vegetation", UniqueTarget.Terrain, flags = UniqueFlag.setOfHiddenToUsers),
Vegetation("Vegetation", UniqueTarget.Terrain, UniqueTarget.Improvement, flags = UniqueFlag.setOfHiddenToUsers), // Improvement included because use as tileFilter works
TileProvidesYieldWithoutPopulation("Tile provides yield without assigned population", UniqueTarget.Terrain, UniqueTarget.Improvement),

View File

@ -8,9 +8,9 @@ This file defines the difficulty levels a player can choose when starting a new
Each difficulty level can have the following attributes:
| Attribute | Type | Optional | Notes |
| --------- | ---- | -------- | ----- |
| name | String | Required | Name of the difficulty level |
| Attribute | Type | Optional | Notes |
| --------- | ---- | -------- |--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| name | String | Required | Name of the difficulty level |
| baseHappiness | Integer | Default 0 |
| extraHappinessPerLuxury | Float | Default 0 |
| researchCostModifier | Float | Default 1 |
@ -19,7 +19,7 @@ Each difficulty level can have the following attributes:
| policyCostModifier | Float | Default 1 |
| unhappinessModifier | Float | Default 1 |
| barbarianBonus | Float | Default 0 |
| playerBonusStartingUnits | List of Units | Default empty | Can also be 'Era Starting Unit', maps to `startingMilitaryUnit` of the Eras file. All other units must be in [Units.json](4-Unit-related-JSON-files.md#Units.json)] |
| playerBonusStartingUnits | List of Units | Default empty | Can also be 'Era Starting Unit', maps to `startingMilitaryUnit` of the Eras file. All other units must be in [Units.json](4-Unit-related-JSON-files.md#Units.json)]. Applies only to human player civs |
| aiCityGrowthModifier | Float | Default 1 |
| aiUnitCostModifier | Float | Default 1 |
| aiBuildingCostModifier | Float | Default 1 |
@ -27,10 +27,10 @@ Each difficulty level can have the following attributes:
| aiBuildingMaintenanceModifier | Float | Default 1 |
| aiUnitMaintenanceModifier | Float | Default 1 |
| aiFreeTechs | List of Techs | Default empty |
| aiMajorCivBonusStartingUnits | List of Units | Default empty | See above |
| aiCityStateBonusStartingUnits | List of Units | Default empty | See above |
| aiMajorCivBonusStartingUnits | List of Units | Default empty | Same rules as playerBonusStartingUnits, See above. Applies only to AI major civs |
| aiCityStateBonusStartingUnits | List of Units | Default empty | Same rules as playerBonusStartingUnits, See above. Applies only to city-state civs |
| aiUnhappinessModifier | Float | Default 1 |
| aisExchangeTechs | Boolean | | Unimplemented |
| aisExchangeTechs | Boolean | | Unimplemented |
| turnBarbariansCanEnterPlayerTiles | Integer | Default 0 |
| clearBarbarianCampReward | Integer | Default 25 |
@ -42,23 +42,25 @@ This file should contain all the era's you want to use in your mod.
Each era can have the following attributes:
| Attribute | Type | Optional | Notes |
| --------- | ---- | -------- | ----- |
| name | String | required | Name of the era |
| researchAgreementCost | Integer (≥0) | defaults to 300 | Cost of research agreements were the most technologically advanced civ is in this era |
| iconRGB | List of 3 Integers | defaults to [255, 255, 255] | RGB color that icons for technologies of this era should have in the Tech screen |
| unitBaseBuyCost | Integer (≥0) | defaults to 200 | Base cost of buying units with Faith, Food, Science or Culture when no other cost is provided |
| startingSettlerCount | Integer (≥0) | defaults to 1 | Amount of settler units that should be spawned when starting a game in this era |
| startingSettlerUnit | String | defaults to "Settler" | Name of the unit that should be used for the previous field. Must be in [Units.json](4-Unit-related-JSON-files.md#unitsjson) |
| startingWorkerCount | Integer (≥0) | defaults to 0 | Amount of worker units that should be spawned when starting a game in this era |
| startingWorkerUnit | String | defaults to "Worker" | Name of the unit that should be used for the previous field. Must be in [Units.json](4-Unit-related-JSON-files.md#unitsjson) |
| startingMilitaryUnitCount | Integer (≥0) | defaults to 1 | Amount of military units that should be spawned when starting a game in this era |
| startingMilitaryUnit | String | defaults to "Warrior" | Name of the unit that should be used for the previous field. Must be in [Units.json](4-Unit-related-JSON-files.md#unitsjson)|
| startingGold | Integer (≥0) | defaults to 0 | Amount of gold each civ should receive when starting a game in this era |
| startingCulture | Integer (≥0) | defaults to 0 | Amount of culture each civ should receive when starting a game in this era |
| settlerPopulation | Integer (>0) | defaults to 1 | Default amount of population each city should have when settled when starting a game in this era |
| settlerBuildings | List of Strings | defaults to none | Buildings that should automatically be built whenever a city is settled when starting a game in this era |
| startingObsoleteWonders | List of Strings | defaults to none | Wonders (and technically buildings) that should be impossible to built when starting a game in this era. Used in the base game to remove all wonders older than 2 era's |
| Attribute | Type | Optional | Notes |
| --------- | ---- | -------- |----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| name | String | required | Name of the era |
| researchAgreementCost | Integer (≥0) | defaults to 300 | Cost of research agreements were the most technologically advanced civ is in this era |
| iconRGB | List of 3 Integers | defaults to [255, 255, 255] | RGB color that icons for technologies of this era should have in the Tech screen |
| unitBaseBuyCost | Integer (≥0) | defaults to 200 | Base cost of buying units with Faith, Food, Science or Culture when no other cost is provided |
| startingSettlerCount | Integer (≥0) | defaults to 1 | Amount of settler units that should be spawned when starting a game in this era (setting this to zero is discouraged [^1]) |
| startingSettlerUnit | String | defaults to "Settler" | Name of the unit that should be used for the previous field. Must be in [Units.json](4-Unit-related-JSON-files.md#unitsjson), or a unit with the "Founds a new city" unique must exist |
| startingWorkerCount | Integer (≥0) | defaults to 0 | Amount of worker units that should be spawned when starting a game in this era |
| startingWorkerUnit | String | defaults to "Worker" | Name of the unit that should be used for the previous field. If startingWorkerCount>0, then it must exist in [Units.json](4-Unit-related-JSON-files.md#unitsjson), or a unit with the "Can build [filter] improvements on tiles" unique must exist |
| startingMilitaryUnitCount | Integer (≥0) | defaults to 1 | Amount of military units that should be spawned when starting a game in this era |
| startingMilitaryUnit | String | defaults to "Warrior" | Name of the unit that should be used for the previous field. Must be in [Units.json](4-Unit-related-JSON-files.md#unitsjson) |
| startingGold | Integer (≥0) | defaults to 0 | Amount of gold each civ should receive when starting a game in this era |
| startingCulture | Integer (≥0) | defaults to 0 | Amount of culture each civ should receive when starting a game in this era |
| settlerPopulation | Integer (>0) | defaults to 1 | Default amount of population each city should have when settled when starting a game in this era |
| settlerBuildings | List of Strings | defaults to none | Buildings that should automatically be built whenever a city is settled when starting a game in this era |
| startingObsoleteWonders | List of Strings | defaults to none | Wonders (and technically buildings) that should be impossible to built when starting a game in this era. Used in the base game to remove all wonders older than 2 era's |
[^1]: Successfully setting startingSettlerCount to zero in a mod (idea: conquer or die) is not easy. Some player-controlled settings require at least one Settler, through any source (see difficulties for other possible settler sources), or you won't be able to start a game: Once City Challenge requires one for all players, and allowing any city-states requires one for those. Would also affect defeat rules.
## Speeds.json

View File

@ -606,7 +606,7 @@ Simple unique parameters are explained by mouseover. Complex parameters are expl
??? example "Provides [amount] [resource]"
Example: "Provides [3] [Iron]"
Applicable to: Global, Improvement
Applicable to: Global, FollowerBelief, Improvement
??? example "Quantity of strategic resources produced by the empire +[relativeAmount]%"
Example: "Quantity of strategic resources produced by the empire +[+20]%"
@ -1450,7 +1450,7 @@ Simple unique parameters are explained by mouseover. Complex parameters are expl
Applicable to: Terrain
??? example "Vegetation"
Applicable to: Terrain
Applicable to: Terrain, Improvement
??? example "Tile provides yield without assigned population"
Applicable to: Terrain, Improvement