Made unhappiness effects moddable by adding a global uniques json; added revolts when < -20 happiness (#5932)

* Added a json file for unhappiness effects

* Change existing code to handle these effects

* Made a weird and unexpendable way to add unhappiness effects to the civilopedia

* Add the default unhappinesseffects to mods without the json

* Added revolts when at very low happiness

* Renamed a few often-used functions

* Added a file for uniques that are always active

* Fixed tests

* Nullifies [Food] -> Nullifies Growth
This commit is contained in:
Xander Lenstra 2022-01-24 18:19:51 +01:00 committed by GitHub
parent 40cd2ba24b
commit f6cb2bd0d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 325 additions and 106 deletions

View File

@ -0,0 +1,25 @@
{
"name": "Global uniques",
"uniques": [
"[-75]% growth [in all cities] <when between [-10] and [0] Happiness>",
"Nullifies Growth [in all cities] <when below [-10] Happiness>",
"[-50]% [Production] [in all cities] <when below [-10] Happiness>",
"[-33]% Strength <for [All] units> <when below [-10] Happiness>",
"Cannot build [Settler] units <when below [-10] Happiness>",
"Rebel units may spawn <when below [-20] Happiness>"
// TODO: Implement the uniques below
// "[+20]% [Culture] [in all cities] <during a golden age>",
// "[+20]% [Production] [in all cities] <during a golden age>",
// "[+10]% growth [in all cities] <during We Love The King Day>",
// "Nullifies All Yield <while is in resistance>",
// "[-25]% [Science] [in pupetted cities]" -- Imo cityFilters should ideally become conditionals anyway
// "[-25]% [Culture] [in pupetted cities]"
// "[+20]% [Production] [in cities connected via railroad]"
// something something unit supply
]
}

View File

@ -0,0 +1,25 @@
{
"name": "Global uniques",
"uniques": [
"[-75]% growth [in all cities] <when between [-10] and [0] Happiness>",
"Nullifies Growth [in all cities] <when below [-10] Happiness>",
"[-50]% [Production] [in all cities] <when below [-10] Happiness>",
"[-33]% Strength <for [All] units> <when below [-10] Happiness>",
"Cannot build [Settler] units <when below [-10] Happiness>",
"Rebel units may spawn <when below [-20] Happiness>"
// TODO: Implement the uniques below
// "[+20]% [Culture] [in all cities] <during a golden age>",
// "[+20]% [Production] [in all cities] <during a golden age>",
// "[+10]% growth [in all cities] <during We Love The King Day>",
// "Nullifies All Yield <while is in resistance>",
// "[-25]% [Science] [in pupetted cities]" -- Imo cityFilters should ideally become conditionals anyway
// "[-25]% [Culture] [in pupetted cities]"
// "[+20]% [Production] [in cities connected via railroad]"
// something something unit supply
]
}

View File

@ -33,7 +33,7 @@
"This means that it is very difficult to expand quickly in Unciv.\nIt isnt impossible, but as a new player you probably shouldn't do it.\nSo what should you do? Chill out, scout, and improve the land that you do have by building Workers.\nOnly build new cities once you have found a spot that you believe is appropriate." "This means that it is very difficult to expand quickly in Unciv.\nIt isnt impossible, but as a new player you probably shouldn't do it.\nSo what should you do? Chill out, scout, and improve the land that you do have by building Workers.\nOnly build new cities once you have found a spot that you believe is appropriate."
], ],
Unhappiness: [ Unhappiness: [
"It seems that your citizens are unhappy!\nWhile unhappy, cities will grow at 1/4 the speed, and your units will suffer a 2% penalty for each unhappiness", "It seems that your citizens are unhappy!\nWhile unhappy, your civilization will suffer many detrimental effects, increasing in severity as unhappiness gets higher.",
"Unhappiness has two main causes: Population and cities.\n Each city causes 3 unhappiness, and each population, 1", "Unhappiness has two main causes: Population and cities.\n Each city causes 3 unhappiness, and each population, 1",
"There are 2 main ways to combat unhappiness:\n by building happiness buildings for your population\n or by having improved luxury resources within your borders." "There are 2 main ways to combat unhappiness:\n by building happiness buildings for your population\n or by having improved luxury resources within your borders."
], ],

View File

@ -754,6 +754,7 @@ Force =
GOLDEN AGE = GOLDEN AGE =
Golden Age = Golden Age =
We Love The King Day = We Love The King Day =
Global Effect =
[year] BC = [year] BC =
[year] AD = [year] AD =
Civilopedia = Civilopedia =
@ -811,7 +812,6 @@ Specialist Allocation =
Specialists = Specialists =
[specialist] slots = [specialist] slots =
Food eaten = Food eaten =
Growth bonus =
Unassigned population = Unassigned population =
[turnsToExpansion] turns to expansion = [turnsToExpansion] turns to expansion =
Stopped expansion = Stopped expansion =

View File

@ -66,13 +66,13 @@ class MainMenuScreen: BaseScreen() {
// If we were in a mod, some of the resource images for the background map we're creating // If we were in a mod, some of the resource images for the background map we're creating
// will not exist unless we reset the ruleset and images // will not exist unless we reset the ruleset and images
ImageGetter.ruleset = RulesetCache.getBaseRuleset() ImageGetter.ruleset = RulesetCache.getVanillaRuleset()
crashHandlingThread(name = "ShowMapBackground") { crashHandlingThread(name = "ShowMapBackground") {
val newMap = MapGenerator(RulesetCache.getBaseRuleset()) val newMap = MapGenerator(RulesetCache.getVanillaRuleset())
.generateMap(MapParameters().apply { mapSize = MapSizeNew(MapSize.Small); type = MapType.default }) .generateMap(MapParameters().apply { mapSize = MapSizeNew(MapSize.Small); type = MapType.default })
postCrashHandlingRunnable { // for GL context postCrashHandlingRunnable { // for GL context
ImageGetter.setNewRuleset(RulesetCache.getBaseRuleset()) ImageGetter.setNewRuleset(RulesetCache.getVanillaRuleset())
val mapHolder = EditorMapHolder(MapEditorScreen(), newMap) val mapHolder = EditorMapHolder(MapEditorScreen(), newMap)
backgroundTable.addAction(Actions.sequence( backgroundTable.addAction(Actions.sequence(
Actions.fadeOut(0f), Actions.fadeOut(0f),

View File

@ -117,7 +117,7 @@ class UncivGame(parameters: UncivGameParameters) : Game() {
postCrashHandlingRunnable { postCrashHandlingRunnable {
musicController.chooseTrack(suffix = MusicMood.Menu) musicController.chooseTrack(suffix = MusicMood.Menu)
ImageGetter.ruleset = RulesetCache.getBaseRuleset() // so that we can enter the map editor without having to load a game first ImageGetter.ruleset = RulesetCache.getVanillaRuleset() // so that we can enter the map editor without having to load a game first
if (settings.isFreshlyCreated) { if (settings.isFreshlyCreated) {
setScreen(LanguagePickerScreen()) setScreen(LanguagePickerScreen())

View File

@ -40,7 +40,7 @@ object GameStarter {
gameSetupInfo.gameParameters.baseRuleset = baseRulesetInMods gameSetupInfo.gameParameters.baseRuleset = baseRulesetInMods
if (!RulesetCache.containsKey(gameSetupInfo.gameParameters.baseRuleset)) if (!RulesetCache.containsKey(gameSetupInfo.gameParameters.baseRuleset))
gameSetupInfo.gameParameters.baseRuleset = RulesetCache.getBaseRuleset().name gameSetupInfo.gameParameters.baseRuleset = RulesetCache.getVanillaRuleset().name
gameInfo.gameParameters = gameSetupInfo.gameParameters gameInfo.gameParameters = gameSetupInfo.gameParameters
val ruleset = RulesetCache.getComplexRuleset(gameInfo.gameParameters.mods, gameInfo.gameParameters.baseRuleset) val ruleset = RulesetCache.getComplexRuleset(gameInfo.gameParameters.mods, gameInfo.gameParameters.baseRuleset)

View File

@ -2,6 +2,7 @@ package com.unciv.logic.battle
import com.unciv.logic.map.TileInfo import com.unciv.logic.map.TileInfo
import com.unciv.models.Counter import com.unciv.models.Counter
import com.unciv.models.ruleset.GlobalUniques
import com.unciv.models.ruleset.unique.StateForConditionals import com.unciv.models.ruleset.unique.StateForConditionals
import com.unciv.models.ruleset.unique.Unique import com.unciv.models.ruleset.unique.Unique
import com.unciv.models.ruleset.unique.UniqueTarget import com.unciv.models.ruleset.unique.UniqueTarget
@ -17,9 +18,10 @@ import kotlin.math.roundToInt
object BattleDamage { object BattleDamage {
private fun getModifierStringFromUnique(unique: Unique): String { private fun getModifierStringFromUnique(unique: Unique): String {
val source = when (unique.sourceObjectType) { val source = when (unique.sourceObjectType) {
UniqueTarget.Unit -> "Unit ability" UniqueTarget.Unit -> "Unit ability"
UniqueTarget.Nation -> "National ability" UniqueTarget.Nation -> "National ability"
UniqueTarget.Global -> GlobalUniques.getUniqueSourceDescription(unique)
else -> "[${unique.sourceObjectName}] ([${unique.sourceObjectType?.name}])" else -> "[${unique.sourceObjectName}] ([${unique.sourceObjectType?.name}])"
} }
if (unique.conditionals.isEmpty()) return source if (unique.conditionals.isEmpty()) return source
@ -58,19 +60,6 @@ object BattleDamage {
} }
//https://www.carlsguides.com/strategy/civilization5/war/combatbonuses.php //https://www.carlsguides.com/strategy/civilization5/war/combatbonuses.php
val civHappiness = if (civInfo.isCityState() && civInfo.getAllyCiv() != null)
// If we are a city state with an ally we are vulnerable to their unhappiness.
min(
civInfo.gameInfo.getCivilization(civInfo.getAllyCiv()!!).getHappiness(),
civInfo.getHappiness()
)
else civInfo.getHappiness()
if (civHappiness < 0)
modifiers["Unhappiness"] = max(
2 * civHappiness,
-90
) // otherwise it could exceed -100% and start healing enemy units...
val adjacentUnits = combatant.getTile().neighbors.flatMap { it.getUnits() } val adjacentUnits = combatant.getTile().neighbors.flatMap { it.getUnits() }
// Deprecated since 3.18.17 // Deprecated since 3.18.17

View File

@ -282,7 +282,7 @@ class CityInfo {
fun hasFlag(flag: CityFlags) = flagsCountdown.containsKey(flag.name) fun hasFlag(flag: CityFlags) = flagsCountdown.containsKey(flag.name)
fun getFlag(flag: CityFlags) = flagsCountdown[flag.name]!! fun getFlag(flag: CityFlags) = flagsCountdown[flag.name]!!
fun isWeLoveTheKingDay() = hasFlag(CityFlags.WeLoveTheKing) fun isWeLoveTheKingDayActive() = hasFlag(CityFlags.WeLoveTheKing)
fun isInResistance() = hasFlag(CityFlags.Resistance) fun isInResistance() = hasFlag(CityFlags.Resistance)
/** @return the number of tiles 4 out from this city that could hold a city, ie how lonely this city is */ /** @return the number of tiles 4 out from this city that could hold a city, ie how lonely this city is */

View File

@ -6,9 +6,9 @@ import com.unciv.logic.civilization.diplomacy.RelationshipLevel
import com.unciv.logic.map.RoadStatus import com.unciv.logic.map.RoadStatus
import com.unciv.models.Counter import com.unciv.models.Counter
import com.unciv.models.ruleset.Building import com.unciv.models.ruleset.Building
import com.unciv.models.ruleset.GlobalUniques
import com.unciv.models.ruleset.ModOptionsConstants import com.unciv.models.ruleset.ModOptionsConstants
import com.unciv.models.ruleset.unique.Unique import com.unciv.models.ruleset.unique.*
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.ruleset.unit.BaseUnit import com.unciv.models.ruleset.unit.BaseUnit
import com.unciv.models.stats.Stat import com.unciv.models.stats.Stat
import com.unciv.models.stats.StatMap import com.unciv.models.stats.StatMap
@ -167,15 +167,21 @@ class CityStats(val cityInfo: CityInfo) {
return stats return stats
} }
private fun getGrowthBonusFromPoliciesAndWonders(): Float { private fun getGrowthBonus(totalFood: Float): StatMap {
var bonus = 0f val growthSources = StatMap()
val stateForConditionals = StateForConditionals(cityInfo.civInfo, cityInfo)
// "[amount]% growth [cityFilter]" // "[amount]% growth [cityFilter]"
for (unique in cityInfo.getMatchingUniques(UniqueType.GrowthPercentBonus)) { for (unique in cityInfo.getMatchingUniques(UniqueType.GrowthPercentBonus, stateForConditionals = stateForConditionals)) {
if (!unique.conditionalsApply(cityInfo.civInfo, cityInfo)) continue if (!cityInfo.matchesFilter(unique.params[1])) continue
if (cityInfo.matchesFilter(unique.params[1]))
bonus += unique.params[0].toFloat() val uniqueSource =
if (unique.sourceObjectType == UniqueTarget.Global && unique.conditionals.any())
GlobalUniques.getUniqueSourceDescription(unique)
else unique.sourceObjectType?.name ?: ""
growthSources.add(uniqueSource, Stats(food = unique.params[0].toFloat()/100f * totalFood))
} }
return bonus / 100 return growthSources
} }
fun hasExtraAnnexUnhappiness(): Boolean { fun hasExtraAnnexUnhappiness(): Boolean {
@ -206,8 +212,13 @@ class CityStats(val cityInfo: CityInfo) {
private fun getStatsFromUniquesBySource(): StatTreeNode { private fun getStatsFromUniquesBySource(): StatTreeNode {
val sourceToStats = StatTreeNode() val sourceToStats = StatTreeNode()
fun addUniqueStats(unique:Unique) = fun addUniqueStats(unique:Unique) {
sourceToStats.addStats(unique.stats, unique.sourceObjectType?.name ?: "", unique.sourceObjectName ?: "") val uniqueSource =
if (unique.sourceObjectType == UniqueTarget.Global && unique.conditionals.any())
GlobalUniques.getUniqueSourceDescription(unique)
else unique.sourceObjectType?.name ?: ""
sourceToStats.addStats(unique.stats, uniqueSource, unique.sourceObjectName ?: "")
}
for (unique in cityInfo.getMatchingUniques(UniqueType.Stats)) for (unique in cityInfo.getMatchingUniques(UniqueType.Stats))
addUniqueStats(unique) addUniqueStats(unique)
@ -278,9 +289,14 @@ class CityStats(val cityInfo: CityInfo) {
private fun getStatsPercentBonusesFromUniquesBySource(currentConstruction: IConstruction):StatMap { private fun getStatsPercentBonusesFromUniquesBySource(currentConstruction: IConstruction):StatMap {
val sourceToStats = StatMap() val sourceToStats = StatMap()
fun addUniqueStats(unique: Unique, stat:Stat, amount:Float) = fun addUniqueStats(unique: Unique, stat:Stat, amount:Float) {
sourceToStats.add(unique.sourceObjectType?.name ?: "", val uniqueSource =
Stats().add(stat, amount)) if (unique.sourceObjectType == UniqueTarget.Global && unique.conditionals.any())
GlobalUniques.getUniqueSourceDescription(unique)
else unique.sourceObjectType?.name ?: ""
sourceToStats.add(uniqueSource, Stats().add(stat, amount))
}
for (unique in cityInfo.getMatchingUniques(UniqueType.StatPercentBonus)) { for (unique in cityInfo.getMatchingUniques(UniqueType.StatPercentBonus)) {
addUniqueStats(unique, Stat.valueOf(unique.params[1]), unique.params[0].toFloat()) addUniqueStats(unique, Stat.valueOf(unique.params[1]), unique.params[0].toFloat())
@ -294,14 +310,14 @@ class CityStats(val cityInfo: CityInfo) {
val uniquesToCheck = val uniquesToCheck =
if (currentConstruction is Building && currentConstruction.isAnyWonder()) { when {
cityInfo.getMatchingUniques(UniqueType.PercentProductionWonders) currentConstruction is BaseUnit ->
} else if (currentConstruction is Building && !currentConstruction.isAnyWonder()) { cityInfo.getMatchingUniques(UniqueType.PercentProductionUnits)
cityInfo.getMatchingUniques(UniqueType.PercentProductionBuildings) currentConstruction is Building && currentConstruction.isAnyWonder() ->
} else if (currentConstruction is BaseUnit) { cityInfo.getMatchingUniques(UniqueType.PercentProductionWonders)
cityInfo.getMatchingUniques(UniqueType.PercentProductionUnits) currentConstruction is Building && !currentConstruction.isAnyWonder() ->
} else { // Science/Gold production cityInfo.getMatchingUniques(UniqueType.PercentProductionBuildings)
sequenceOf() else -> sequenceOf() // Science/Gold production
} }
for (unique in uniquesToCheck) { for (unique in uniquesToCheck) {
@ -321,9 +337,11 @@ class CityStats(val cityInfo: CityInfo) {
if (currentConstruction is Building if (currentConstruction is Building
&& cityInfo.civInfo.cities.isNotEmpty() && cityInfo.civInfo.cities.isNotEmpty()
&& cityInfo.civInfo.getCapital().cityConstructions.builtBuildings.contains(currentConstruction.name)) && cityInfo.civInfo.getCapital().cityConstructions.builtBuildings.contains(currentConstruction.name)
for(unique in cityInfo.getMatchingUniques("+25% Production towards any buildings that already exist in the Capital")) ) {
addUniqueStats(unique, Stat.Production, 25f) for (unique in cityInfo.getMatchingUniques("+25% Production towards any buildings that already exist in the Capital"))
addUniqueStats(unique, Stat.Production, 25f)
}
renameStatmapKeys(sourceToStats) renameStatmapKeys(sourceToStats)
@ -462,8 +480,8 @@ class CityStats(val cityInfo: CityInfo) {
private fun updateBaseStatList(statsFromBuildings: StatTreeNode) { private fun updateBaseStatList(statsFromBuildings: StatTreeNode) {
val newBaseStatTree = StatTreeNode() val newBaseStatTree = StatTreeNode()
val newBaseStatList = // We don't edit the existing baseStatList directly, in order to avoid concurrency exceptions
StatMap() // we don't edit the existing baseStatList directly, in order to avoid concurrency exceptions val newBaseStatList = StatMap()
newBaseStatTree.addStats(Stats( newBaseStatTree.addStats(Stats(
science = cityInfo.population.population.toFloat(), science = cityInfo.population.population.toFloat(),
@ -576,6 +594,20 @@ class CityStats(val cityInfo: CityInfo) {
entry.science *= statPercentBonusesSum.science.toPercent() entry.science *= statPercentBonusesSum.science.toPercent()
} }
for ((unique, statToBeRemoved) in cityInfo.getMatchingUniques(UniqueType.NullifiesStat)
.map { it to Stat.valueOf(it.params[0]) }
.distinct()
) {
val removedAmount = newFinalStatList.values.sumOf { it[statToBeRemoved].toDouble() }
val uniqueSource =
if (unique.sourceObjectType == UniqueTarget.Global && unique.conditionals.any())
GlobalUniques.getUniqueSourceDescription(unique)
else unique.sourceObjectType?.name ?: ""
newFinalStatList.add(uniqueSource, Stats().apply { this[statToBeRemoved] = -removedAmount.toFloat() })
}
/* Okay, food calculation is complicated. /* Okay, food calculation is complicated.
First we see how much food we generate. Then we apply production bonuses to it. First we see how much food we generate. Then we apply production bonuses to it.
Up till here, business as usual. Up till here, business as usual.
@ -592,15 +624,12 @@ class CityStats(val cityInfo: CityInfo) {
if (totalFood > 0) { if (totalFood > 0) {
// Since growth bonuses are special, (applied afterwards) they will be displayed separately in the user interface as well. // Since growth bonuses are special, (applied afterwards) they will be displayed separately in the user interface as well.
// All bonuses except We Love The King do apply even when unhappy // All bonuses except We Love The King do apply even when unhappy
val foodFromGrowthBonuses = Stats(food = getGrowthBonusFromPoliciesAndWonders() * totalFood) val growthBonuses = getGrowthBonus(totalFood)
newFinalStatList.add("Growth bonus", foodFromGrowthBonuses) renameStatmapKeys(growthBonuses)
val happiness = cityInfo.civInfo.getHappiness() for (growthBonus in growthBonuses) {
if (happiness < 0) { newFinalStatList.add("${growthBonus.key} (Growth)", growthBonus.value)
// Unhappiness -75% to -100% }
val foodReducedByUnhappiness = if (happiness <= -10) Stats(food = totalFood * -1) if (cityInfo.isWeLoveTheKingDayActive() && cityInfo.civInfo.getHappiness() >= 0) {
else Stats(food = (totalFood * -3) / 4)
newFinalStatList.add("Unhappiness", foodReducedByUnhappiness)
} else if (cityInfo.isWeLoveTheKingDay()) {
// We Love The King Day +25%, only if not unhappy // We Love The King Day +25%, only if not unhappy
val weLoveTheKingFood = Stats(food = totalFood / 4) val weLoveTheKingFood = Stats(food = totalFood / 4)
newFinalStatList.add("We Love The King Day", weLoveTheKingFood) newFinalStatList.add("We Love The King Day", weLoveTheKingFood)
@ -619,10 +648,20 @@ class CityStats(val cityInfo: CityInfo) {
) { ) {
newFinalStatList["Excess food to production"] = Stats(production = totalFood, food = -totalFood) newFinalStatList["Excess food to production"] = Stats(production = totalFood, food = -totalFood)
} }
val growthNullifyingUnique = cityInfo.getMatchingUniques(UniqueType.NullifiesGrwoth).firstOrNull()
if (growthNullifyingUnique != null) {
val uniqueSource =
if (growthNullifyingUnique.sourceObjectType == UniqueTarget.Global && growthNullifyingUnique.conditionals.any())
GlobalUniques.getUniqueSourceDescription(growthNullifyingUnique)
else growthNullifyingUnique.sourceObjectType?.name ?: ""
val amountToRemove = -newFinalStatList.values.sumOf { it[Stat.Food].toDouble() }
newFinalStatList[uniqueSource] = Stats().apply { this[Stat.Food] = amountToRemove.toFloat() }
}
if (cityInfo.isInResistance()) if (cityInfo.isInResistance())
newFinalStatList.clear() // NOPE newFinalStatList.clear() // NOPE
if (newFinalStatList.values.map { it.production }.sum() < 1) // Minimum production for things to progress if (newFinalStatList.values.map { it.production }.sum() < 1) // Minimum production for things to progress
newFinalStatList["Production"] = Stats(production = 1f) newFinalStatList["Production"] = Stats(production = 1f)
finalStatList = newFinalStatList finalStatList = newFinalStatList

View File

@ -382,7 +382,8 @@ class CivilizationInfo {
else city.getAllUniquesWithNonLocalEffects() else city.getAllUniquesWithNonLocalEffects()
} }
fun hasUnique(uniqueType: UniqueType, stateForConditionals: StateForConditionals? = null) = getMatchingUniques(uniqueType, stateForConditionals).any() fun hasUnique(uniqueType: UniqueType, stateForConditionals: StateForConditionals? =
StateForConditionals(this)) = getMatchingUniques(uniqueType, stateForConditionals).any()
fun hasUnique(unique: String) = getMatchingUniques(unique).any() fun hasUnique(unique: String) = getMatchingUniques(unique).any()
// Does not return local uniques, only global ones. // Does not return local uniques, only global ones.
@ -402,6 +403,9 @@ class CivilizationInfo {
yieldAll(getEra().getMatchingUniques(uniqueType, stateForConditionals)) yieldAll(getEra().getMatchingUniques(uniqueType, stateForConditionals))
if (religionManager.religion != null) if (religionManager.religion != null)
yieldAll(religionManager.religion!!.getFounderUniques().filter { it.isOfType(uniqueType) }) yieldAll(religionManager.religion!!.getFounderUniques().filter { it.isOfType(uniqueType) })
yieldAll(gameInfo.ruleSet.globalUniques.getMatchingUniques(uniqueType, stateForConditionals))
}.filter { }.filter {
it.conditionalsApply(stateForConditionals) it.conditionalsApply(stateForConditionals)
} }
@ -424,8 +428,10 @@ class CivilizationInfo {
.asSequence() .asSequence()
.filter { it.placeholderText == uniqueTemplate } .filter { it.placeholderText == uniqueTemplate }
) )
yieldAll(gameInfo.ruleSet.globalUniques.getMatchingUniques(uniqueTemplate))
} }
//region Units //region Units
fun getCivUnitsSize(): Int = units.size fun getCivUnitsSize(): Int = units.size
fun getCivUnits(): Sequence<MapUnit> = units.asSequence() fun getCivUnits(): Sequence<MapUnit> = units.asSequence()
@ -827,6 +833,7 @@ class CivilizationInfo {
updateViewableTiles() // adds explored tiles so that the units will be able to perform automated actions better updateViewableTiles() // adds explored tiles so that the units will be able to perform automated actions better
transients().updateCitiesConnectedToCapital() transients().updateCitiesConnectedToCapital()
startTurnFlags() startTurnFlags()
updateRevolts()
for (city in cities) city.startTurn() // Most expensive part of startTurn for (city in cities) city.startTurn() // Most expensive part of startTurn
for (unit in getCivUnits()) unit.startTurn() for (unit in getCivUnits()) unit.startTurn()
@ -921,6 +928,11 @@ class CivilizationInfo {
if (flagsCountdown[flag]!! > 0) if (flagsCountdown[flag]!! > 0)
flagsCountdown[flag] = flagsCountdown[flag]!! - 1 flagsCountdown[flag] = flagsCountdown[flag]!! - 1
if (flagsCountdown[flag] != 0) continue
when (flag) {
CivFlags.RevoltSpawning.name -> doRevoltSpawn()
}
} }
handleDiplomaticVictoryFlags() handleDiplomaticVictoryFlags()
} }
@ -947,8 +959,8 @@ class CivilizationInfo {
} }
fun addFlag(flag: String, count: Int) = flagsCountdown.set(flag, count) fun addFlag(flag: String, count: Int) = flagsCountdown.set(flag, count)
fun removeFlag(flag: String) = flagsCountdown.remove(flag) fun removeFlag(flag: String) = flagsCountdown.remove(flag)
fun hasFlag(flag: String) = flagsCountdown.contains(flag)
fun getTurnsBetweenDiplomaticVotings() = (15 * gameInfo.gameParameters.gameSpeed.modifier).toInt() // Dunno the exact calculation, hidden in Lua files fun getTurnsBetweenDiplomaticVotings() = (15 * gameInfo.gameParameters.gameSpeed.modifier).toInt() // Dunno the exact calculation, hidden in Lua files
@ -975,6 +987,65 @@ class CivilizationInfo {
fun shouldCheckForDiplomaticVictory() = fun shouldCheckForDiplomaticVictory() =
shouldShowDiplomaticVotingResults() shouldShowDiplomaticVotingResults()
private fun updateRevolts() {
if (!hasUnique(UniqueType.SpawnRebels)) {
removeFlag(CivFlags.RevoltSpawning.name)
return
}
if (!hasFlag(CivFlags.RevoltSpawning.name)) {
addFlag(CivFlags.RevoltSpawning.name, max(getTurnsBeforeRevolt(),1))
return
}
}
private fun doRevoltSpawn() {
val random = Random()
val rebelCount = 1 + random.nextInt(100 + 20 * (cities.size - 1)) / 100
val spawnCity = cities.maxByOrNull { random.nextInt(it.population.population + 10) } ?: return
val spawnTile = spawnCity.getTiles().maxByOrNull { rateTileForRevoltSpawn(it) } ?: return
val unitToSpawn = gameInfo.ruleSet.units.values.asSequence().filter {
it.uniqueTo == null && it.isMelee() && it.isLandUnit()
&& !it.hasUnique(UniqueType.CannotAttack) && it.isBuildable(this)
}.maxByOrNull {
random.nextInt(1000)
} ?: return
repeat(rebelCount) {
gameInfo.tileMap.placeUnitNearTile(
spawnTile.position,
unitToSpawn.name,
gameInfo.getBarbarianCivilization()
)
}
// Will be automatically added again as long as unhappiness is still low enough
removeFlag(CivFlags.RevoltSpawning.name)
addNotification("Your citizens are revolting due to very high unhappiness!", spawnTile.position, unitToSpawn.name, "StatIcons/Malcontent")
}
// Higher is better
private fun rateTileForRevoltSpawn(tile: TileInfo): Int {
if (tile.isWater || tile.militaryUnit != null || tile.civilianUnit != null || tile.isCityCenter() || tile.isImpassible())
return -1;
var score = 10
if (tile.improvement == null) {
score += 4
if (tile.resource != null) {
score += 3
}
}
if (tile.getDefensiveBonus() > 0)
score += 4
return score
}
private fun getTurnsBeforeRevolt(): Int {
val score = ((4 + Random().nextInt(3)) * max(gameInfo.gameParameters.gameSpeed.modifier, 1f)).toInt()
return score
}
/** Modify gold by a given amount making sure it does neither overflow nor underflow. /** Modify gold by a given amount making sure it does neither overflow nor underflow.
* @param delta the amount to add (can be negative) * @param delta the amount to add (can be negative)
*/ */
@ -1198,7 +1269,7 @@ class CivilizationInfo {
return proximity return proximity
} }
//////////////////////// City State wrapper functions //////////////////////// //////////////////////// City State wrapper functions ////////////////////////
/** Gain a random great person from the city state */ /** Gain a random great person from the city state */
@ -1252,4 +1323,5 @@ enum class CivFlags {
ShouldResetDiplomaticVotes, ShouldResetDiplomaticVotes,
RecentlyBullied, RecentlyBullied,
TurnsTillCallForBarbHelp, TurnsTillCallForBarbHelp,
RevoltSpawning,
} }

View File

@ -0,0 +1,25 @@
package com.unciv.models.ruleset
import com.unciv.models.ruleset.unique.Unique
import com.unciv.models.ruleset.unique.UniqueTarget
import com.unciv.models.ruleset.unique.UniqueType
class GlobalUniques: RulesetObject() {
override var name = ""
override fun getUniqueTarget() = UniqueTarget.Global
override fun makeLink() = "" // No own category on Civilopedia screen
companion object {
fun getUniqueSourceDescription(unique: Unique): String {
if (unique.conditionals.none())
return "Global Effect"
return when (unique.conditionals.first().type) {
UniqueType.ConditionalGoldenAge -> "Golden Age"
UniqueType.ConditionalHappy -> "Happiness"
UniqueType.ConditionalBetweenHappiness, UniqueType.ConditionalBelowHappiness -> "Unhappiness"
else -> "Global Effect"
}
}
}
}

View File

@ -65,13 +65,12 @@ class Ruleset {
private val jsonParser = JsonParser() private val jsonParser = JsonParser()
var modWithReligionLoaded = false
var name = "" var name = ""
val beliefs = LinkedHashMap<String, Belief>() val beliefs = LinkedHashMap<String, Belief>()
val buildings = LinkedHashMap<String, Building>() val buildings = LinkedHashMap<String, Building>()
val difficulties = LinkedHashMap<String, Difficulty>() val difficulties = LinkedHashMap<String, Difficulty>()
val eras = LinkedHashMap<String, Era>() val eras = LinkedHashMap<String, Era>()
var globalUniques = GlobalUniques()
val nations = LinkedHashMap<String, Nation>() val nations = LinkedHashMap<String, Nation>()
val policies = LinkedHashMap<String, Policy>() val policies = LinkedHashMap<String, Policy>()
val policyBranches = LinkedHashMap<String, PolicyBranch>() val policyBranches = LinkedHashMap<String, PolicyBranch>()
@ -104,15 +103,16 @@ class Ruleset {
} }
fun add(ruleset: Ruleset) { fun add(ruleset: Ruleset) {
beliefs.putAll(ruleset.beliefs)
buildings.putAll(ruleset.buildings) buildings.putAll(ruleset.buildings)
for (buildingToRemove in ruleset.modOptions.buildingsToRemove) buildings.remove(buildingToRemove) for (buildingToRemove in ruleset.modOptions.buildingsToRemove) buildings.remove(buildingToRemove)
difficulties.putAll(ruleset.difficulties) difficulties.putAll(ruleset.difficulties)
eras.putAll(ruleset.eras) eras.putAll(ruleset.eras)
globalUniques = GlobalUniques().apply { uniques.addAll(globalUniques.uniques); uniques.addAll(ruleset.globalUniques.uniques) }
nations.putAll(ruleset.nations) nations.putAll(ruleset.nations)
for (nationToRemove in ruleset.modOptions.nationsToRemove) nations.remove(nationToRemove) for (nationToRemove in ruleset.modOptions.nationsToRemove) nations.remove(nationToRemove)
policyBranches.putAll(ruleset.policyBranches) policyBranches.putAll(ruleset.policyBranches)
policies.putAll(ruleset.policies) policies.putAll(ruleset.policies)
beliefs.putAll(ruleset.beliefs)
quests.putAll(ruleset.quests) quests.putAll(ruleset.quests)
religions.addAll(ruleset.religions) religions.addAll(ruleset.religions)
ruinRewards.putAll(ruleset.ruinRewards) ruinRewards.putAll(ruleset.ruinRewards)
@ -127,7 +127,6 @@ class Ruleset {
unitTypes.putAll(ruleset.unitTypes) unitTypes.putAll(ruleset.unitTypes)
for (unitToRemove in ruleset.modOptions.unitsToRemove) units.remove(unitToRemove) for (unitToRemove in ruleset.modOptions.unitsToRemove) units.remove(unitToRemove)
mods += ruleset.mods mods += ruleset.mods
modWithReligionLoaded = modWithReligionLoaded || ruleset.modWithReligionLoaded
} }
fun clear() { fun clear() {
@ -135,14 +134,15 @@ class Ruleset {
buildings.clear() buildings.clear()
difficulties.clear() difficulties.clear()
eras.clear() eras.clear()
policyBranches.clear() globalUniques = GlobalUniques()
specialists.clear()
mods.clear() mods.clear()
nations.clear() nations.clear()
policies.clear() policies.clear()
policyBranches.clear()
quests.clear()
religions.clear() religions.clear()
ruinRewards.clear() ruinRewards.clear()
quests.clear() specialists.clear()
technologies.clear() technologies.clear()
terrains.clear() terrains.clear()
tileImprovements.clear() tileImprovements.clear()
@ -150,7 +150,6 @@ class Ruleset {
unitPromotions.clear() unitPromotions.clear()
units.clear() units.clear()
unitTypes.clear() unitTypes.clear()
modWithReligionLoaded = false
} }
@ -201,7 +200,7 @@ class Ruleset {
if (erasFile.exists()) eras += createHashmap(jsonParser.getFromJson(Array<Era>::class.java, erasFile)) if (erasFile.exists()) eras += createHashmap(jsonParser.getFromJson(Array<Era>::class.java, erasFile))
// While `eras.values.toList()` might seem more logical, eras.values is a MutableCollection and // While `eras.values.toList()` might seem more logical, eras.values is a MutableCollection and
// therefore does not guarantee keeping the order of elements like a LinkedHashMap does. // therefore does not guarantee keeping the order of elements like a LinkedHashMap does.
// Using a map sidesteps this problem // Using map{} sidesteps this problem
eras.map { it.value }.withIndex().forEach { it.value.eraNumber = it.index } eras.map { it.value }.withIndex().forEach { it.value.eraNumber = it.index }
val unitTypesFile = folderHandle.child("UnitTypes.json") val unitTypesFile = folderHandle.child("UnitTypes.json")
@ -254,8 +253,14 @@ class Ruleset {
} }
val difficultiesFile = folderHandle.child("Difficulties.json") val difficultiesFile = folderHandle.child("Difficulties.json")
if (difficultiesFile.exists()) difficulties += createHashmap(jsonParser.getFromJson(Array<Difficulty>::class.java, difficultiesFile)) if (difficultiesFile.exists())
difficulties += createHashmap(jsonParser.getFromJson(Array<Difficulty>::class.java, difficultiesFile))
val globalUniquesFile = folderHandle.child("GlobalUniques.json")
if (globalUniquesFile.exists()) {
globalUniques = jsonParser.getFromJson(GlobalUniques::class.java, globalUniquesFile)
}
val gameBasicsLoadTime = System.currentTimeMillis() - gameBasicsStartTime val gameBasicsLoadTime = System.currentTimeMillis() - gameBasicsStartTime
if (printOutput) println("Loading ruleset - " + gameBasicsLoadTime + "ms") if (printOutput) println("Loading ruleset - " + gameBasicsLoadTime + "ms")
} }
@ -274,9 +279,7 @@ class Ruleset {
} }
} }
} }
fun hasReligion() = beliefs.any() && modWithReligionLoaded
/** Used for displaying a RuleSet's name */ /** Used for displaying a RuleSet's name */
override fun toString() = when { override fun toString() = when {
name.isNotEmpty() -> name name.isNotEmpty() -> name
@ -476,7 +479,7 @@ class Ruleset {
// Quit here when no base ruleset is loaded - references cannot be checked // Quit here when no base ruleset is loaded - references cannot be checked
if (!modOptions.isBaseRuleset) return lines if (!modOptions.isBaseRuleset) return lines
val baseRuleset = RulesetCache.getBaseRuleset() // for UnitTypes fallback val baseRuleset = RulesetCache.getVanillaRuleset() // for UnitTypes fallback
for (unit in units.values) { for (unit in units.values) {
if (unit.requiredTech != null && !technologies.containsKey(unit.requiredTech!!)) if (unit.requiredTech != null && !technologies.containsKey(unit.requiredTech!!))
@ -697,7 +700,7 @@ object RulesetCache : HashMap<String,Ruleset>() {
} }
fun getBaseRuleset() = this[BaseRuleset.Civ_V_Vanilla.fullName]!!.clone() // safeguard, so no-one edits the base ruleset by mistake fun getVanillaRuleset() = this[BaseRuleset.Civ_V_Vanilla.fullName]!!.clone() // safeguard, so no-one edits the base ruleset by mistake
fun getSortedBaseRulesets(): List<String> { fun getSortedBaseRulesets(): List<String> {
val baseRulesets = values val baseRulesets = values
@ -729,7 +732,7 @@ object RulesetCache : HashMap<String,Ruleset>() {
val baseRuleset = val baseRuleset =
if (containsKey(optionalBaseRuleset) && this[optionalBaseRuleset]!!.modOptions.isBaseRuleset) this[optionalBaseRuleset]!! if (containsKey(optionalBaseRuleset) && this[optionalBaseRuleset]!!.modOptions.isBaseRuleset) this[optionalBaseRuleset]!!
else getBaseRuleset() else getVanillaRuleset()
val loadedMods = mods val loadedMods = mods
@ -744,20 +747,20 @@ object RulesetCache : HashMap<String,Ruleset>() {
if (mod.modOptions.isBaseRuleset) { if (mod.modOptions.isBaseRuleset) {
newRuleset.modOptions = mod.modOptions newRuleset.modOptions = mod.modOptions
} }
if (mod.beliefs.any()) {
newRuleset.modWithReligionLoaded = true
}
} }
newRuleset.updateBuildingCosts() // only after we've added all the mods can we calculate the building costs newRuleset.updateBuildingCosts() // only after we've added all the mods can we calculate the building costs
// This one should be temporary // This one should be temporary
if (newRuleset.unitTypes.isEmpty()) { if (newRuleset.unitTypes.isEmpty()) {
newRuleset.unitTypes.putAll(getBaseRuleset().unitTypes) newRuleset.unitTypes.putAll(getVanillaRuleset().unitTypes)
} }
// This one should be permanent // These should be permanent
if (newRuleset.ruinRewards.isEmpty()) { if (newRuleset.ruinRewards.isEmpty()) {
newRuleset.ruinRewards.putAll(getBaseRuleset().ruinRewards) newRuleset.ruinRewards.putAll(getVanillaRuleset().ruinRewards)
}
if (newRuleset.globalUniques.uniques.isEmpty()) {
newRuleset.globalUniques = getVanillaRuleset().globalUniques
} }
return newRuleset return newRuleset

View File

@ -58,6 +58,12 @@ class Unique(val text: String, val sourceObjectType: UniqueTarget? = null, val s
UniqueType.ConditionalNotWar -> state.civInfo?.isAtWar() == false UniqueType.ConditionalNotWar -> state.civInfo?.isAtWar() == false
UniqueType.ConditionalHappy -> UniqueType.ConditionalHappy ->
state.civInfo != null && state.civInfo.statsForNextTurn.happiness >= 0 state.civInfo != null && state.civInfo.statsForNextTurn.happiness >= 0
UniqueType.ConditionalBetweenHappiness ->
state.civInfo != null
&& condition.params[0].toInt() <= state.civInfo.happinessForNextTurn
&& state.civInfo.happinessForNextTurn < condition.params[1].toInt()
UniqueType.ConditionalBelowHappiness ->
state.civInfo != null && state.civInfo.happinessForNextTurn < condition.params[0].toInt()
UniqueType.ConditionalGoldenAge -> UniqueType.ConditionalGoldenAge ->
state.civInfo != null && state.civInfo.goldenAges.isGoldenAge() state.civInfo != null && state.civInfo.goldenAges.isGoldenAge()
UniqueType.ConditionalBeforeEra -> UniqueType.ConditionalBeforeEra ->

View File

@ -87,6 +87,7 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags:
// Stat percentage boosts // Stat percentage boosts
StatPercentBonus("[amount]% [stat]", UniqueTarget.Global, UniqueTarget.FollowerBelief), StatPercentBonus("[amount]% [stat]", UniqueTarget.Global, UniqueTarget.FollowerBelief),
StatPercentBonusCities("[amount]% [stat] [cityFilter]", UniqueTarget.Global, UniqueTarget.FollowerBelief),
StatPercentFromObject("[amount]% [stat] from every [tileFilter/specialist/buildingName]", UniqueTarget.Global, UniqueTarget.FollowerBelief), StatPercentFromObject("[amount]% [stat] from every [tileFilter/specialist/buildingName]", UniqueTarget.Global, UniqueTarget.FollowerBelief),
@Deprecated("As of 3.18.17", ReplaceWith("[amount]% [stat] from every [tileFilter/specialist/buildingName]")) @Deprecated("As of 3.18.17", ReplaceWith("[amount]% [stat] from every [tileFilter/specialist/buildingName]"))
StatPercentSignedFromObject("+[amount]% [stat] from every [tileFilter/specialist/buildingName]", UniqueTarget.Global, UniqueTarget.FollowerBelief), StatPercentSignedFromObject("+[amount]% [stat] from every [tileFilter/specialist/buildingName]", UniqueTarget.Global, UniqueTarget.FollowerBelief),
@ -95,7 +96,8 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags:
AllStatsSignedPercentFromObject("+[amount]% yield from every [tileFilter]", UniqueTarget.Global, UniqueTarget.FollowerBelief), AllStatsSignedPercentFromObject("+[amount]% yield from every [tileFilter]", UniqueTarget.Global, UniqueTarget.FollowerBelief),
StatPercentFromReligionFollowers("[amount]% [stat] from every follower, up to [amount]%", UniqueTarget.FollowerBelief), StatPercentFromReligionFollowers("[amount]% [stat] from every follower, up to [amount]%", UniqueTarget.FollowerBelief),
BonusStatsFromCityStates("[amount]% [stat] from City-States", UniqueTarget.Global), BonusStatsFromCityStates("[amount]% [stat] from City-States", UniqueTarget.Global),
StatPercentBonusCities("[amount]% [stat] [cityFilter]", UniqueTarget.Global, UniqueTarget.FollowerBelief), NullifiesStat("Nullifies [stat] [cityFilter]", UniqueTarget.Global),
NullifiesGrwoth("Nullifies Growth [cityFilter]", UniqueTarget.Global),
PercentProductionWonders("[amount]% Production when constructing [buildingFilter] wonders [cityFilter]", UniqueTarget.Global, UniqueTarget.Resource, UniqueTarget.FollowerBelief), PercentProductionWonders("[amount]% Production when constructing [buildingFilter] wonders [cityFilter]", UniqueTarget.Global, UniqueTarget.Resource, UniqueTarget.FollowerBelief),
PercentProductionBuildings("[amount]% Production when constructing [buildingFilter] buildings [cityFilter]", UniqueTarget.Global, UniqueTarget.FollowerBelief), PercentProductionBuildings("[amount]% Production when constructing [buildingFilter] buildings [cityFilter]", UniqueTarget.Global, UniqueTarget.FollowerBelief),
@ -266,6 +268,8 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags:
StartsWithTech("Starts with [tech]", UniqueTarget.Nation), StartsWithTech("Starts with [tech]", UniqueTarget.Nation),
ResearchableMultipleTimes("Can be continually researched", UniqueTarget.Global), ResearchableMultipleTimes("Can be continually researched", UniqueTarget.Global),
SpawnRebels("Rebel units may spawn", UniqueTarget.Global),
//endregion //endregion
//endregion Global uniques //endregion Global uniques
@ -514,8 +518,11 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags:
/////// civ conditionals /////// civ conditionals
ConditionalWar("when at war", UniqueTarget.Conditional), ConditionalWar("when at war", UniqueTarget.Conditional),
ConditionalNotWar("when not at war", UniqueTarget.Conditional), ConditionalNotWar("when not at war", UniqueTarget.Conditional),
ConditionalHappy("while the empire is happy", UniqueTarget.Conditional),
ConditionalGoldenAge("during a Golden Age", UniqueTarget.Conditional), ConditionalGoldenAge("during a Golden Age", UniqueTarget.Conditional),
ConditionalHappy("while the empire is happy", UniqueTarget.Conditional),
ConditionalBetweenHappiness("when between [amount] and [amount] Happiness", UniqueTarget.Conditional),
ConditionalBelowHappiness("when below [amount] Happiness", UniqueTarget.Conditional),
ConditionalDuringEra("during the [era]", UniqueTarget.Conditional), ConditionalDuringEra("during the [era]", UniqueTarget.Conditional),
ConditionalBeforeEra("before the [era]", UniqueTarget.Conditional), ConditionalBeforeEra("before the [era]", UniqueTarget.Conditional),

View File

@ -238,7 +238,7 @@ object TranslationFileWriter {
private fun generateStringsFromJSONs(jsonsFolder: FileHandle): LinkedHashMap<String, MutableSet<String>> { private fun generateStringsFromJSONs(jsonsFolder: FileHandle): LinkedHashMap<String, MutableSet<String>> {
// build maps identifying parameters as certain types of filters - unitFilter etc // build maps identifying parameters as certain types of filters - unitFilter etc
val ruleset = RulesetCache.getBaseRuleset() val ruleset = RulesetCache.getVanillaRuleset()
val tileFilterMap = ruleset.terrains.keys.toMutableSet().apply { addAll(sequenceOf( val tileFilterMap = ruleset.terrains.keys.toMutableSet().apply { addAll(sequenceOf(
"Friendly Land", "Friendly Land",
"Foreign Land", "Foreign Land",
@ -422,6 +422,7 @@ object TranslationFileWriter {
"Buildings" -> emptyArray<Building>().javaClass "Buildings" -> emptyArray<Building>().javaClass
"Difficulties" -> emptyArray<Difficulty>().javaClass "Difficulties" -> emptyArray<Difficulty>().javaClass
"Eras" -> emptyArray<Era>().javaClass "Eras" -> emptyArray<Era>().javaClass
"GlobalUniques" -> GlobalUniques().javaClass
"Nations" -> emptyArray<Nation>().javaClass "Nations" -> emptyArray<Nation>().javaClass
"Policies" -> emptyArray<PolicyBranch>().javaClass "Policies" -> emptyArray<PolicyBranch>().javaClass
"Quests" -> emptyArray<Quest>().javaClass "Quests" -> emptyArray<Quest>().javaClass

View File

@ -87,7 +87,7 @@ class CityStatsTable(val cityScreen: CityScreen): Table() {
tableWithIcons.add(ImageGetter.getImage("StatIcons/Resistance")).size(20f) tableWithIcons.add(ImageGetter.getImage("StatIcons/Resistance")).size(20f)
tableWithIcons.add("In resistance for another [${cityInfo.getFlag(CityFlags.Resistance)}] turns".toLabel()).row() tableWithIcons.add("In resistance for another [${cityInfo.getFlag(CityFlags.Resistance)}] turns".toLabel()).row()
} }
if (cityInfo.isWeLoveTheKingDay()) { if (cityInfo.isWeLoveTheKingDayActive()) {
tableWithIcons.add(ImageGetter.getStatIcon("Food")).size(20f) tableWithIcons.add(ImageGetter.getStatIcon("Food")).size(20f)
tableWithIcons.add("We Love The King Day for another [${cityInfo.getFlag(CityFlags.WeLoveTheKing)}] turns".toLabel()).row() tableWithIcons.add("We Love The King Day for another [${cityInfo.getFlag(CityFlags.WeLoveTheKing)}] turns".toLabel()).row()
} else if (cityInfo.demandedResource != "") { } else if (cityInfo.demandedResource != "") {

View File

@ -196,7 +196,7 @@ class CivilopediaScreen(
CivilopediaCategories.Technology -> ruleset.technologies.values CivilopediaCategories.Technology -> ruleset.technologies.values
CivilopediaCategories.Promotion -> ruleset.unitPromotions.values CivilopediaCategories.Promotion -> ruleset.unitPromotions.values
CivilopediaCategories.Policy -> ruleset.policies.values CivilopediaCategories.Policy -> ruleset.policies.values
CivilopediaCategories.Tutorial -> tutorialController.getCivilopediaTutorials() CivilopediaCategories.Tutorial -> tutorialController.getCivilopediaTutorials(ruleset)
CivilopediaCategories.Difficulty -> ruleset.difficulties.values CivilopediaCategories.Difficulty -> ruleset.difficulties.values
CivilopediaCategories.Belief -> (ruleset.beliefs.values.asSequence() + CivilopediaCategories.Belief -> (ruleset.beliefs.values.asSequence() +
Belief.getCivilopediaReligionEntry(ruleset)).toList() Belief.getCivilopediaReligionEntry(ruleset)).toList()

View File

@ -22,7 +22,7 @@ import com.unciv.ui.utils.*
class MapEditorScreen(): BaseScreen() { class MapEditorScreen(): BaseScreen() {
var mapName = "" var mapName = ""
var tileMap = TileMap() var tileMap = TileMap()
var ruleset = Ruleset().apply { add(RulesetCache.getBaseRuleset()) } var ruleset = Ruleset().apply { add(RulesetCache.getVanillaRuleset()) }
var gameSetupInfo = GameSetupInfo() var gameSetupInfo = GameSetupInfo()
lateinit var mapHolder: EditorMapHolder lateinit var mapHolder: EditorMapHolder

View File

@ -23,7 +23,7 @@ import com.unciv.ui.utils.AutoScrollPane as ScrollPane
/** New map generation screen */ /** New map generation screen */
class NewMapScreen(val mapParameters: MapParameters = getDefaultParameters()) : PickerScreen() { class NewMapScreen(val mapParameters: MapParameters = getDefaultParameters()) : PickerScreen() {
private val ruleset = RulesetCache.getBaseRuleset() private val ruleset = RulesetCache.getVanillaRuleset()
private var generatedMap: TileMap? = null private var generatedMap: TileMap? = null
private val mapParametersTable: MapParametersTable private val mapParametersTable: MapParametersTable
private val modCheckBoxes: ModCheckboxTable private val modCheckBoxes: ModCheckboxTable

View File

@ -4,6 +4,7 @@ import com.badlogic.gdx.utils.Array
import com.unciv.JsonParser import com.unciv.JsonParser
import com.unciv.UncivGame import com.unciv.UncivGame
import com.unciv.models.Tutorial import com.unciv.models.Tutorial
import com.unciv.models.ruleset.Ruleset
import com.unciv.models.stats.INamed import com.unciv.models.stats.INamed
import com.unciv.ui.civilopedia.FormattedLine import com.unciv.ui.civilopedia.FormattedLine
import com.unciv.ui.civilopedia.SimpleCivilopediaText import com.unciv.ui.civilopedia.SimpleCivilopediaText
@ -70,10 +71,13 @@ class TutorialController(screen: BaseScreen) {
/** Get all Tutorials intended to be displayed in the Civilopedia /** Get all Tutorials intended to be displayed in the Civilopedia
* as a List of wrappers supporting INamed and ICivilopediaText * as a List of wrappers supporting INamed and ICivilopediaText
*/ */
fun getCivilopediaTutorials() = fun getCivilopediaTutorials(ruleset: Ruleset): List<CivilopediaTutorial> {
tutorials.filter { val civilopediaTutorials = tutorials.filter {
Tutorial.findByName(it.key)!!.isCivilopedia Tutorial.findByName(it.key)!!.isCivilopedia
}.map { }.map { tutorial ->
CivilopediaTutorial(it.key, it.value) val lines = tutorial.value
CivilopediaTutorial(tutorial.key, lines)
} }
return civilopediaTutorials
}
} }

View File

@ -76,6 +76,11 @@ Example: "[20]% [Culture]"
Applicable to: Global, FollowerBelief Applicable to: Global, FollowerBelief
#### [amount]% [stat] [cityFilter]
Example: "[20]% [Culture] [in all cities]"
Applicable to: Global, FollowerBelief
#### [amount]% [stat] from every [tileFilter/specialist/buildingName] #### [amount]% [stat] from every [tileFilter/specialist/buildingName]
Example: "[20]% [Culture] from every [tileFilter/specialist/buildingName]" Example: "[20]% [Culture] from every [tileFilter/specialist/buildingName]"
@ -91,10 +96,15 @@ Example: "[20]% [Culture] from City-States"
Applicable to: Global Applicable to: Global
#### [amount]% [stat] [cityFilter] #### Nullifies [stat] [cityFilter]
Example: "[20]% [Culture] [in all cities]" Example: "Nullifies [Culture] [in all cities]"
Applicable to: Global, FollowerBelief Applicable to: Global
#### Nullifies Growth [cityFilter]
Example: "Nullifies Growth [in all cities]"
Applicable to: Global
#### [amount]% Production when constructing [buildingFilter] wonders [cityFilter] #### [amount]% Production when constructing [buildingFilter] wonders [cityFilter]
Example: "[20]% Production when constructing [Culture] wonders [in all cities]" Example: "[20]% Production when constructing [Culture] wonders [in all cities]"
@ -388,6 +398,9 @@ Applicable to: Global
#### Can be continually researched #### Can be continually researched
Applicable to: Global Applicable to: Global
#### Rebel units may spawn
Applicable to: Global
#### [amount]% Strength #### [amount]% Strength
Example: "[20]% Strength" Example: "[20]% Strength"
@ -1206,10 +1219,20 @@ Applicable to: Conditional
#### <when not at war> #### <when not at war>
Applicable to: Conditional Applicable to: Conditional
#### <during a Golden Age>
Applicable to: Conditional
#### <while the empire is happy> #### <while the empire is happy>
Applicable to: Conditional Applicable to: Conditional
#### <during a Golden Age> #### <when between [amount] and [amount] Happiness>
Example: "<when between [20] and [20] Happiness>"
Applicable to: Conditional
#### <when below [amount] Happiness>
Example: "<when below [20] Happiness>"
Applicable to: Conditional Applicable to: Conditional
#### <during the [era]> #### <during the [era]>

View File

@ -31,7 +31,7 @@ class TileImprovementConstructionTests {
@Before @Before
fun initTheWorld() { fun initTheWorld() {
RulesetCache.loadRulesets() RulesetCache.loadRulesets()
ruleSet = RulesetCache.getBaseRuleset() ruleSet = RulesetCache.getVanillaRuleset()
civInfo.tech.researchedTechnologies.addAll(ruleSet.technologies.values) civInfo.tech.researchedTechnologies.addAll(ruleSet.technologies.values)
civInfo.tech.techsResearched.addAll(ruleSet.technologies.keys) civInfo.tech.techsResearched.addAll(ruleSet.technologies.keys)
city.civInfo = civInfo city.civInfo = civInfo

View File

@ -22,7 +22,7 @@ class TileMapTests {
@Before @Before
fun initTheWorld() { fun initTheWorld() {
RulesetCache.loadRulesets() RulesetCache.loadRulesets()
ruleSet = RulesetCache.getBaseRuleset() ruleSet = RulesetCache.getVanillaRuleset()
map = TileMap() map = TileMap()
tile1.position = Vector2(0f, 0f) tile1.position = Vector2(0f, 0f)

View File

@ -28,7 +28,7 @@ class UnitMovementAlgorithmsTests {
@Before @Before
fun initTheWorld() { fun initTheWorld() {
RulesetCache.loadRulesets() RulesetCache.loadRulesets()
ruleSet = RulesetCache.getBaseRuleset() ruleSet = RulesetCache.getVanillaRuleset()
tile.ruleset = ruleSet tile.ruleset = ruleSet
tile.baseTerrain = Constants.grassland tile.baseTerrain = Constants.grassland
civInfo.tech.techsResearched.addAll(ruleSet.technologies.keys) civInfo.tech.techsResearched.addAll(ruleSet.technologies.keys)

View File

@ -28,7 +28,7 @@ class BasicTests {
@Before @Before
fun loadTranslations() { fun loadTranslations() {
RulesetCache.loadRulesets() RulesetCache.loadRulesets()
ruleset = RulesetCache.getBaseRuleset() ruleset = RulesetCache.getVanillaRuleset()
} }
@Test @Test

View File

@ -31,7 +31,7 @@ class TranslationTests {
})) }))
translations.readAllLanguagesTranslation() translations.readAllLanguagesTranslation()
RulesetCache.loadRulesets() RulesetCache.loadRulesets()
ruleset = RulesetCache.getBaseRuleset() ruleset = RulesetCache.getVanillaRuleset()
System.setOut(outputChannel) System.setOut(outputChannel)
} }

View File

@ -41,7 +41,7 @@ class GlobalUniquesTests {
// Create a new ruleset we can easily edit, and set the important variables of gameInfo // Create a new ruleset we can easily edit, and set the important variables of gameInfo
RulesetCache.loadRulesets() RulesetCache.loadRulesets()
ruleSet = RulesetCache.getBaseRuleset() ruleSet = RulesetCache.getVanillaRuleset()
gameInfo.ruleSet = ruleSet gameInfo.ruleSet = ruleSet
gameInfo.difficultyObject = ruleSet.difficulties["Prince"]!! gameInfo.difficultyObject = ruleSet.difficulties["Prince"]!!