diff --git a/android/assets/jsons/Civ V - Gods & Kings/GlobalUniques.json b/android/assets/jsons/Civ V - Gods & Kings/GlobalUniques.json new file mode 100644 index 0000000000..24c0c3d4f1 --- /dev/null +++ b/android/assets/jsons/Civ V - Gods & Kings/GlobalUniques.json @@ -0,0 +1,25 @@ +{ + "name": "Global uniques", + "uniques": [ + "[-75]% growth [in all cities] ", + "Nullifies Growth [in all cities] ", + "[-50]% [Production] [in all cities] ", + "[-33]% Strength ", + "Cannot build [Settler] units ", + "Rebel units may spawn " + + // TODO: Implement the uniques below + // "[+20]% [Culture] [in all cities] ", + // "[+20]% [Production] [in all cities] ", + + // "[+10]% growth [in all cities] ", + + // "Nullifies All Yield ", + + // "[-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 + ] +} \ No newline at end of file diff --git a/android/assets/jsons/Civ V - Vanilla/GlobalUniques.json b/android/assets/jsons/Civ V - Vanilla/GlobalUniques.json new file mode 100644 index 0000000000..0b60351c4a --- /dev/null +++ b/android/assets/jsons/Civ V - Vanilla/GlobalUniques.json @@ -0,0 +1,25 @@ +{ + "name": "Global uniques", + "uniques": [ + "[-75]% growth [in all cities] ", + "Nullifies Growth [in all cities] ", + "[-50]% [Production] [in all cities] ", + "[-33]% Strength ", + "Cannot build [Settler] units ", + "Rebel units may spawn " + + // TODO: Implement the uniques below + // "[+20]% [Culture] [in all cities] ", + // "[+20]% [Production] [in all cities] ", + + // "[+10]% growth [in all cities] ", + + // "Nullifies All Yield ", + + // "[-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 + ] +} \ No newline at end of file diff --git a/android/assets/jsons/Tutorials.json b/android/assets/jsons/Tutorials.json index d25b4d572c..a381b736fd 100644 --- a/android/assets/jsons/Tutorials.json +++ b/android/assets/jsons/Tutorials.json @@ -33,7 +33,7 @@ "This means that it is very difficult to expand quickly in Unciv.\nIt isn’t 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: [ - "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", "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." ], diff --git a/android/assets/jsons/translations/template.properties b/android/assets/jsons/translations/template.properties index f89a328b75..ea8ec13284 100644 --- a/android/assets/jsons/translations/template.properties +++ b/android/assets/jsons/translations/template.properties @@ -754,6 +754,7 @@ Force = GOLDEN AGE = Golden Age = We Love The King Day = +Global Effect = [year] BC = [year] AD = Civilopedia = @@ -811,7 +812,6 @@ Specialist Allocation = Specialists = [specialist] slots = Food eaten = -Growth bonus = Unassigned population = [turnsToExpansion] turns to expansion = Stopped expansion = diff --git a/core/src/com/unciv/MainMenuScreen.kt b/core/src/com/unciv/MainMenuScreen.kt index 8c0d81ff8b..0d0bb1bc44 100644 --- a/core/src/com/unciv/MainMenuScreen.kt +++ b/core/src/com/unciv/MainMenuScreen.kt @@ -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 // will not exist unless we reset the ruleset and images - ImageGetter.ruleset = RulesetCache.getBaseRuleset() + ImageGetter.ruleset = RulesetCache.getVanillaRuleset() crashHandlingThread(name = "ShowMapBackground") { - val newMap = MapGenerator(RulesetCache.getBaseRuleset()) + val newMap = MapGenerator(RulesetCache.getVanillaRuleset()) .generateMap(MapParameters().apply { mapSize = MapSizeNew(MapSize.Small); type = MapType.default }) postCrashHandlingRunnable { // for GL context - ImageGetter.setNewRuleset(RulesetCache.getBaseRuleset()) + ImageGetter.setNewRuleset(RulesetCache.getVanillaRuleset()) val mapHolder = EditorMapHolder(MapEditorScreen(), newMap) backgroundTable.addAction(Actions.sequence( Actions.fadeOut(0f), diff --git a/core/src/com/unciv/UncivGame.kt b/core/src/com/unciv/UncivGame.kt index 8302f38a15..97eab9055c 100644 --- a/core/src/com/unciv/UncivGame.kt +++ b/core/src/com/unciv/UncivGame.kt @@ -117,7 +117,7 @@ class UncivGame(parameters: UncivGameParameters) : Game() { postCrashHandlingRunnable { 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) { setScreen(LanguagePickerScreen()) diff --git a/core/src/com/unciv/logic/GameStarter.kt b/core/src/com/unciv/logic/GameStarter.kt index 7cb0ea6271..8be438caf1 100644 --- a/core/src/com/unciv/logic/GameStarter.kt +++ b/core/src/com/unciv/logic/GameStarter.kt @@ -40,7 +40,7 @@ object GameStarter { gameSetupInfo.gameParameters.baseRuleset = baseRulesetInMods if (!RulesetCache.containsKey(gameSetupInfo.gameParameters.baseRuleset)) - gameSetupInfo.gameParameters.baseRuleset = RulesetCache.getBaseRuleset().name + gameSetupInfo.gameParameters.baseRuleset = RulesetCache.getVanillaRuleset().name gameInfo.gameParameters = gameSetupInfo.gameParameters val ruleset = RulesetCache.getComplexRuleset(gameInfo.gameParameters.mods, gameInfo.gameParameters.baseRuleset) diff --git a/core/src/com/unciv/logic/battle/BattleDamage.kt b/core/src/com/unciv/logic/battle/BattleDamage.kt index 6355641376..cd4c05fff5 100644 --- a/core/src/com/unciv/logic/battle/BattleDamage.kt +++ b/core/src/com/unciv/logic/battle/BattleDamage.kt @@ -2,6 +2,7 @@ package com.unciv.logic.battle import com.unciv.logic.map.TileInfo import com.unciv.models.Counter +import com.unciv.models.ruleset.GlobalUniques import com.unciv.models.ruleset.unique.StateForConditionals import com.unciv.models.ruleset.unique.Unique import com.unciv.models.ruleset.unique.UniqueTarget @@ -17,9 +18,10 @@ import kotlin.math.roundToInt object BattleDamage { private fun getModifierStringFromUnique(unique: Unique): String { - val source = when (unique.sourceObjectType) { + val source = when (unique.sourceObjectType) { UniqueTarget.Unit -> "Unit ability" UniqueTarget.Nation -> "National ability" + UniqueTarget.Global -> GlobalUniques.getUniqueSourceDescription(unique) else -> "[${unique.sourceObjectName}] ([${unique.sourceObjectType?.name}])" } if (unique.conditionals.isEmpty()) return source @@ -58,19 +60,6 @@ object BattleDamage { } //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() } // Deprecated since 3.18.17 diff --git a/core/src/com/unciv/logic/city/CityInfo.kt b/core/src/com/unciv/logic/city/CityInfo.kt index 6f724ce2ed..01afe64422 100644 --- a/core/src/com/unciv/logic/city/CityInfo.kt +++ b/core/src/com/unciv/logic/city/CityInfo.kt @@ -282,7 +282,7 @@ class CityInfo { fun hasFlag(flag: CityFlags) = flagsCountdown.containsKey(flag.name) fun getFlag(flag: CityFlags) = flagsCountdown[flag.name]!! - fun isWeLoveTheKingDay() = hasFlag(CityFlags.WeLoveTheKing) + fun isWeLoveTheKingDayActive() = hasFlag(CityFlags.WeLoveTheKing) 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 */ diff --git a/core/src/com/unciv/logic/city/CityStats.kt b/core/src/com/unciv/logic/city/CityStats.kt index 113bdcab4e..359f5a5247 100644 --- a/core/src/com/unciv/logic/city/CityStats.kt +++ b/core/src/com/unciv/logic/city/CityStats.kt @@ -6,9 +6,9 @@ import com.unciv.logic.civilization.diplomacy.RelationshipLevel import com.unciv.logic.map.RoadStatus import com.unciv.models.Counter import com.unciv.models.ruleset.Building +import com.unciv.models.ruleset.GlobalUniques import com.unciv.models.ruleset.ModOptionsConstants -import com.unciv.models.ruleset.unique.Unique -import com.unciv.models.ruleset.unique.UniqueType +import com.unciv.models.ruleset.unique.* import com.unciv.models.ruleset.unit.BaseUnit import com.unciv.models.stats.Stat import com.unciv.models.stats.StatMap @@ -167,15 +167,21 @@ class CityStats(val cityInfo: CityInfo) { return stats } - private fun getGrowthBonusFromPoliciesAndWonders(): Float { - var bonus = 0f + private fun getGrowthBonus(totalFood: Float): StatMap { + val growthSources = StatMap() + val stateForConditionals = StateForConditionals(cityInfo.civInfo, cityInfo) // "[amount]% growth [cityFilter]" - for (unique in cityInfo.getMatchingUniques(UniqueType.GrowthPercentBonus)) { - if (!unique.conditionalsApply(cityInfo.civInfo, cityInfo)) continue - if (cityInfo.matchesFilter(unique.params[1])) - bonus += unique.params[0].toFloat() + for (unique in cityInfo.getMatchingUniques(UniqueType.GrowthPercentBonus, stateForConditionals = stateForConditionals)) { + if (!cityInfo.matchesFilter(unique.params[1])) continue + + 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 { @@ -206,8 +212,13 @@ class CityStats(val cityInfo: CityInfo) { private fun getStatsFromUniquesBySource(): StatTreeNode { val sourceToStats = StatTreeNode() - fun addUniqueStats(unique:Unique) = - sourceToStats.addStats(unique.stats, unique.sourceObjectType?.name ?: "", unique.sourceObjectName ?: "") + fun addUniqueStats(unique:Unique) { + 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)) addUniqueStats(unique) @@ -278,9 +289,14 @@ class CityStats(val cityInfo: CityInfo) { private fun getStatsPercentBonusesFromUniquesBySource(currentConstruction: IConstruction):StatMap { val sourceToStats = StatMap() - fun addUniqueStats(unique: Unique, stat:Stat, amount:Float) = - sourceToStats.add(unique.sourceObjectType?.name ?: "", - Stats().add(stat, amount)) + fun addUniqueStats(unique: Unique, stat:Stat, amount:Float) { + val uniqueSource = + 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)) { addUniqueStats(unique, Stat.valueOf(unique.params[1]), unique.params[0].toFloat()) @@ -294,14 +310,14 @@ class CityStats(val cityInfo: CityInfo) { val uniquesToCheck = - if (currentConstruction is Building && currentConstruction.isAnyWonder()) { - cityInfo.getMatchingUniques(UniqueType.PercentProductionWonders) - } else if (currentConstruction is Building && !currentConstruction.isAnyWonder()) { - cityInfo.getMatchingUniques(UniqueType.PercentProductionBuildings) - } else if (currentConstruction is BaseUnit) { - cityInfo.getMatchingUniques(UniqueType.PercentProductionUnits) - } else { // Science/Gold production - sequenceOf() + when { + currentConstruction is BaseUnit -> + cityInfo.getMatchingUniques(UniqueType.PercentProductionUnits) + currentConstruction is Building && currentConstruction.isAnyWonder() -> + cityInfo.getMatchingUniques(UniqueType.PercentProductionWonders) + currentConstruction is Building && !currentConstruction.isAnyWonder() -> + cityInfo.getMatchingUniques(UniqueType.PercentProductionBuildings) + else -> sequenceOf() // Science/Gold production } for (unique in uniquesToCheck) { @@ -321,9 +337,11 @@ class CityStats(val cityInfo: CityInfo) { if (currentConstruction is Building && cityInfo.civInfo.cities.isNotEmpty() - && 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) + && 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) + } renameStatmapKeys(sourceToStats) @@ -462,8 +480,8 @@ class CityStats(val cityInfo: CityInfo) { private fun updateBaseStatList(statsFromBuildings: StatTreeNode) { val newBaseStatTree = StatTreeNode() - val newBaseStatList = - StatMap() // we don't edit the existing baseStatList directly, in order to avoid concurrency exceptions + // We don't edit the existing baseStatList directly, in order to avoid concurrency exceptions + val newBaseStatList = StatMap() newBaseStatTree.addStats(Stats( science = cityInfo.population.population.toFloat(), @@ -576,6 +594,20 @@ class CityStats(val cityInfo: CityInfo) { 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. First we see how much food we generate. Then we apply production bonuses to it. Up till here, business as usual. @@ -592,15 +624,12 @@ class CityStats(val cityInfo: CityInfo) { if (totalFood > 0) { // 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 - val foodFromGrowthBonuses = Stats(food = getGrowthBonusFromPoliciesAndWonders() * totalFood) - newFinalStatList.add("Growth bonus", foodFromGrowthBonuses) - val happiness = cityInfo.civInfo.getHappiness() - if (happiness < 0) { - // Unhappiness -75% to -100% - val foodReducedByUnhappiness = if (happiness <= -10) Stats(food = totalFood * -1) - else Stats(food = (totalFood * -3) / 4) - newFinalStatList.add("Unhappiness", foodReducedByUnhappiness) - } else if (cityInfo.isWeLoveTheKingDay()) { + val growthBonuses = getGrowthBonus(totalFood) + renameStatmapKeys(growthBonuses) + for (growthBonus in growthBonuses) { + newFinalStatList.add("${growthBonus.key} (Growth)", growthBonus.value) + } + if (cityInfo.isWeLoveTheKingDayActive() && cityInfo.civInfo.getHappiness() >= 0) { // We Love The King Day +25%, only if not unhappy val weLoveTheKingFood = Stats(food = totalFood / 4) 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) } + + 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()) newFinalStatList.clear() // NOPE - + if (newFinalStatList.values.map { it.production }.sum() < 1) // Minimum production for things to progress newFinalStatList["Production"] = Stats(production = 1f) finalStatList = newFinalStatList diff --git a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt index da6e70f0d1..ce255823cc 100644 --- a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt +++ b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt @@ -382,7 +382,8 @@ class CivilizationInfo { 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() // Does not return local uniques, only global ones. @@ -402,6 +403,9 @@ class CivilizationInfo { yieldAll(getEra().getMatchingUniques(uniqueType, stateForConditionals)) if (religionManager.religion != null) yieldAll(religionManager.religion!!.getFounderUniques().filter { it.isOfType(uniqueType) }) + + yieldAll(gameInfo.ruleSet.globalUniques.getMatchingUniques(uniqueType, stateForConditionals)) + }.filter { it.conditionalsApply(stateForConditionals) } @@ -424,8 +428,10 @@ class CivilizationInfo { .asSequence() .filter { it.placeholderText == uniqueTemplate } ) + + yieldAll(gameInfo.ruleSet.globalUniques.getMatchingUniques(uniqueTemplate)) } - + //region Units fun getCivUnitsSize(): Int = units.size fun getCivUnits(): Sequence = units.asSequence() @@ -827,6 +833,7 @@ class CivilizationInfo { updateViewableTiles() // adds explored tiles so that the units will be able to perform automated actions better transients().updateCitiesConnectedToCapital() startTurnFlags() + updateRevolts() for (city in cities) city.startTurn() // Most expensive part of startTurn for (unit in getCivUnits()) unit.startTurn() @@ -921,6 +928,11 @@ class CivilizationInfo { if (flagsCountdown[flag]!! > 0) flagsCountdown[flag] = flagsCountdown[flag]!! - 1 + if (flagsCountdown[flag] != 0) continue + + when (flag) { + CivFlags.RevoltSpawning.name -> doRevoltSpawn() + } } handleDiplomaticVictoryFlags() } @@ -947,8 +959,8 @@ class CivilizationInfo { } fun addFlag(flag: String, count: Int) = flagsCountdown.set(flag, count) - 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 @@ -975,6 +987,65 @@ class CivilizationInfo { fun shouldCheckForDiplomaticVictory() = 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. * @param delta the amount to add (can be negative) */ @@ -1198,7 +1269,7 @@ class CivilizationInfo { return proximity } - + //////////////////////// City State wrapper functions //////////////////////// /** Gain a random great person from the city state */ @@ -1252,4 +1323,5 @@ enum class CivFlags { ShouldResetDiplomaticVotes, RecentlyBullied, TurnsTillCallForBarbHelp, + RevoltSpawning, } diff --git a/core/src/com/unciv/models/ruleset/GlobalUniques.kt b/core/src/com/unciv/models/ruleset/GlobalUniques.kt new file mode 100644 index 0000000000..cf4f1d00cc --- /dev/null +++ b/core/src/com/unciv/models/ruleset/GlobalUniques.kt @@ -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" + } + } + } +} \ No newline at end of file diff --git a/core/src/com/unciv/models/ruleset/Ruleset.kt b/core/src/com/unciv/models/ruleset/Ruleset.kt index 10161c8d39..c59b7f73b9 100644 --- a/core/src/com/unciv/models/ruleset/Ruleset.kt +++ b/core/src/com/unciv/models/ruleset/Ruleset.kt @@ -65,13 +65,12 @@ class Ruleset { private val jsonParser = JsonParser() - var modWithReligionLoaded = false - var name = "" val beliefs = LinkedHashMap() val buildings = LinkedHashMap() val difficulties = LinkedHashMap() val eras = LinkedHashMap() + var globalUniques = GlobalUniques() val nations = LinkedHashMap() val policies = LinkedHashMap() val policyBranches = LinkedHashMap() @@ -104,15 +103,16 @@ class Ruleset { } fun add(ruleset: Ruleset) { + beliefs.putAll(ruleset.beliefs) buildings.putAll(ruleset.buildings) for (buildingToRemove in ruleset.modOptions.buildingsToRemove) buildings.remove(buildingToRemove) difficulties.putAll(ruleset.difficulties) eras.putAll(ruleset.eras) + globalUniques = GlobalUniques().apply { uniques.addAll(globalUniques.uniques); uniques.addAll(ruleset.globalUniques.uniques) } nations.putAll(ruleset.nations) for (nationToRemove in ruleset.modOptions.nationsToRemove) nations.remove(nationToRemove) policyBranches.putAll(ruleset.policyBranches) policies.putAll(ruleset.policies) - beliefs.putAll(ruleset.beliefs) quests.putAll(ruleset.quests) religions.addAll(ruleset.religions) ruinRewards.putAll(ruleset.ruinRewards) @@ -127,7 +127,6 @@ class Ruleset { unitTypes.putAll(ruleset.unitTypes) for (unitToRemove in ruleset.modOptions.unitsToRemove) units.remove(unitToRemove) mods += ruleset.mods - modWithReligionLoaded = modWithReligionLoaded || ruleset.modWithReligionLoaded } fun clear() { @@ -135,14 +134,15 @@ class Ruleset { buildings.clear() difficulties.clear() eras.clear() - policyBranches.clear() - specialists.clear() + globalUniques = GlobalUniques() mods.clear() nations.clear() policies.clear() + policyBranches.clear() + quests.clear() religions.clear() ruinRewards.clear() - quests.clear() + specialists.clear() technologies.clear() terrains.clear() tileImprovements.clear() @@ -150,7 +150,6 @@ class Ruleset { unitPromotions.clear() units.clear() unitTypes.clear() - modWithReligionLoaded = false } @@ -201,7 +200,7 @@ class Ruleset { if (erasFile.exists()) eras += createHashmap(jsonParser.getFromJson(Array::class.java, erasFile)) // 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. - // Using a map sidesteps this problem + // Using map{} sidesteps this problem eras.map { it.value }.withIndex().forEach { it.value.eraNumber = it.index } val unitTypesFile = folderHandle.child("UnitTypes.json") @@ -254,8 +253,14 @@ class Ruleset { } val difficultiesFile = folderHandle.child("Difficulties.json") - if (difficultiesFile.exists()) difficulties += createHashmap(jsonParser.getFromJson(Array::class.java, difficultiesFile)) + if (difficultiesFile.exists()) + difficulties += createHashmap(jsonParser.getFromJson(Array::class.java, difficultiesFile)) + val globalUniquesFile = folderHandle.child("GlobalUniques.json") + if (globalUniquesFile.exists()) { + globalUniques = jsonParser.getFromJson(GlobalUniques::class.java, globalUniquesFile) + } + val gameBasicsLoadTime = System.currentTimeMillis() - gameBasicsStartTime 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 */ override fun toString() = when { name.isNotEmpty() -> name @@ -476,7 +479,7 @@ class Ruleset { // Quit here when no base ruleset is loaded - references cannot be checked if (!modOptions.isBaseRuleset) return lines - val baseRuleset = RulesetCache.getBaseRuleset() // for UnitTypes fallback + val baseRuleset = RulesetCache.getVanillaRuleset() // for UnitTypes fallback for (unit in units.values) { if (unit.requiredTech != null && !technologies.containsKey(unit.requiredTech!!)) @@ -697,7 +700,7 @@ object RulesetCache : HashMap() { } - 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 { val baseRulesets = values @@ -729,7 +732,7 @@ object RulesetCache : HashMap() { val baseRuleset = if (containsKey(optionalBaseRuleset) && this[optionalBaseRuleset]!!.modOptions.isBaseRuleset) this[optionalBaseRuleset]!! - else getBaseRuleset() + else getVanillaRuleset() val loadedMods = mods @@ -744,20 +747,20 @@ object RulesetCache : HashMap() { if (mod.modOptions.isBaseRuleset) { 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 // This one should be temporary 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()) { - newRuleset.ruinRewards.putAll(getBaseRuleset().ruinRewards) + newRuleset.ruinRewards.putAll(getVanillaRuleset().ruinRewards) + } + if (newRuleset.globalUniques.uniques.isEmpty()) { + newRuleset.globalUniques = getVanillaRuleset().globalUniques } return newRuleset diff --git a/core/src/com/unciv/models/ruleset/unique/Unique.kt b/core/src/com/unciv/models/ruleset/unique/Unique.kt index f262a0d5d7..fab428c929 100644 --- a/core/src/com/unciv/models/ruleset/unique/Unique.kt +++ b/core/src/com/unciv/models/ruleset/unique/Unique.kt @@ -58,6 +58,12 @@ class Unique(val text: String, val sourceObjectType: UniqueTarget? = null, val s UniqueType.ConditionalNotWar -> state.civInfo?.isAtWar() == false UniqueType.ConditionalHappy -> 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 -> state.civInfo != null && state.civInfo.goldenAges.isGoldenAge() UniqueType.ConditionalBeforeEra -> diff --git a/core/src/com/unciv/models/ruleset/unique/UniqueType.kt b/core/src/com/unciv/models/ruleset/unique/UniqueType.kt index ad2eda3ed2..74302bce8d 100644 --- a/core/src/com/unciv/models/ruleset/unique/UniqueType.kt +++ b/core/src/com/unciv/models/ruleset/unique/UniqueType.kt @@ -87,6 +87,7 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags: // Stat percentage boosts 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), @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), @@ -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), StatPercentFromReligionFollowers("[amount]% [stat] from every follower, up to [amount]%", UniqueTarget.FollowerBelief), 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), 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), ResearchableMultipleTimes("Can be continually researched", UniqueTarget.Global), + SpawnRebels("Rebel units may spawn", UniqueTarget.Global), + //endregion //endregion Global uniques @@ -514,8 +518,11 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags: /////// civ conditionals ConditionalWar("when 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), + + 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), ConditionalBeforeEra("before the [era]", UniqueTarget.Conditional), diff --git a/core/src/com/unciv/models/translations/TranslationFileWriter.kt b/core/src/com/unciv/models/translations/TranslationFileWriter.kt index f7f8c194d9..7a7981b1ca 100644 --- a/core/src/com/unciv/models/translations/TranslationFileWriter.kt +++ b/core/src/com/unciv/models/translations/TranslationFileWriter.kt @@ -238,7 +238,7 @@ object TranslationFileWriter { private fun generateStringsFromJSONs(jsonsFolder: FileHandle): LinkedHashMap> { // 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( "Friendly Land", "Foreign Land", @@ -422,6 +422,7 @@ object TranslationFileWriter { "Buildings" -> emptyArray().javaClass "Difficulties" -> emptyArray().javaClass "Eras" -> emptyArray().javaClass + "GlobalUniques" -> GlobalUniques().javaClass "Nations" -> emptyArray().javaClass "Policies" -> emptyArray().javaClass "Quests" -> emptyArray().javaClass diff --git a/core/src/com/unciv/ui/cityscreen/CityStatsTable.kt b/core/src/com/unciv/ui/cityscreen/CityStatsTable.kt index 8d525f28b5..1b410cd18a 100644 --- a/core/src/com/unciv/ui/cityscreen/CityStatsTable.kt +++ b/core/src/com/unciv/ui/cityscreen/CityStatsTable.kt @@ -87,7 +87,7 @@ class CityStatsTable(val cityScreen: CityScreen): Table() { tableWithIcons.add(ImageGetter.getImage("StatIcons/Resistance")).size(20f) 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("We Love The King Day for another [${cityInfo.getFlag(CityFlags.WeLoveTheKing)}] turns".toLabel()).row() } else if (cityInfo.demandedResource != "") { diff --git a/core/src/com/unciv/ui/civilopedia/CivilopediaScreen.kt b/core/src/com/unciv/ui/civilopedia/CivilopediaScreen.kt index 1a09ef6dff..1becb68084 100644 --- a/core/src/com/unciv/ui/civilopedia/CivilopediaScreen.kt +++ b/core/src/com/unciv/ui/civilopedia/CivilopediaScreen.kt @@ -196,7 +196,7 @@ class CivilopediaScreen( CivilopediaCategories.Technology -> ruleset.technologies.values CivilopediaCategories.Promotion -> ruleset.unitPromotions.values CivilopediaCategories.Policy -> ruleset.policies.values - CivilopediaCategories.Tutorial -> tutorialController.getCivilopediaTutorials() + CivilopediaCategories.Tutorial -> tutorialController.getCivilopediaTutorials(ruleset) CivilopediaCategories.Difficulty -> ruleset.difficulties.values CivilopediaCategories.Belief -> (ruleset.beliefs.values.asSequence() + Belief.getCivilopediaReligionEntry(ruleset)).toList() diff --git a/core/src/com/unciv/ui/mapeditor/MapEditorScreen.kt b/core/src/com/unciv/ui/mapeditor/MapEditorScreen.kt index c2d15e55a4..064781965c 100644 --- a/core/src/com/unciv/ui/mapeditor/MapEditorScreen.kt +++ b/core/src/com/unciv/ui/mapeditor/MapEditorScreen.kt @@ -22,7 +22,7 @@ import com.unciv.ui.utils.* class MapEditorScreen(): BaseScreen() { var mapName = "" var tileMap = TileMap() - var ruleset = Ruleset().apply { add(RulesetCache.getBaseRuleset()) } + var ruleset = Ruleset().apply { add(RulesetCache.getVanillaRuleset()) } var gameSetupInfo = GameSetupInfo() lateinit var mapHolder: EditorMapHolder diff --git a/core/src/com/unciv/ui/mapeditor/NewMapScreen.kt b/core/src/com/unciv/ui/mapeditor/NewMapScreen.kt index a749ce0c8d..4e631a79e6 100644 --- a/core/src/com/unciv/ui/mapeditor/NewMapScreen.kt +++ b/core/src/com/unciv/ui/mapeditor/NewMapScreen.kt @@ -23,7 +23,7 @@ import com.unciv.ui.utils.AutoScrollPane as ScrollPane /** New map generation screen */ class NewMapScreen(val mapParameters: MapParameters = getDefaultParameters()) : PickerScreen() { - private val ruleset = RulesetCache.getBaseRuleset() + private val ruleset = RulesetCache.getVanillaRuleset() private var generatedMap: TileMap? = null private val mapParametersTable: MapParametersTable private val modCheckBoxes: ModCheckboxTable diff --git a/core/src/com/unciv/ui/tutorials/TutorialController.kt b/core/src/com/unciv/ui/tutorials/TutorialController.kt index 30108b8ec7..98f2757cd0 100644 --- a/core/src/com/unciv/ui/tutorials/TutorialController.kt +++ b/core/src/com/unciv/ui/tutorials/TutorialController.kt @@ -4,6 +4,7 @@ import com.badlogic.gdx.utils.Array import com.unciv.JsonParser import com.unciv.UncivGame import com.unciv.models.Tutorial +import com.unciv.models.ruleset.Ruleset import com.unciv.models.stats.INamed import com.unciv.ui.civilopedia.FormattedLine import com.unciv.ui.civilopedia.SimpleCivilopediaText @@ -70,10 +71,13 @@ class TutorialController(screen: BaseScreen) { /** Get all Tutorials intended to be displayed in the Civilopedia * as a List of wrappers supporting INamed and ICivilopediaText */ - fun getCivilopediaTutorials() = - tutorials.filter { + fun getCivilopediaTutorials(ruleset: Ruleset): List { + val civilopediaTutorials = tutorials.filter { Tutorial.findByName(it.key)!!.isCivilopedia - }.map { - CivilopediaTutorial(it.key, it.value) + }.map { tutorial -> + val lines = tutorial.value + CivilopediaTutorial(tutorial.key, lines) } + return civilopediaTutorials + } } diff --git a/docs/uniques.md b/docs/uniques.md index 050556e9f0..8290146b46 100644 --- a/docs/uniques.md +++ b/docs/uniques.md @@ -76,6 +76,11 @@ Example: "[20]% [Culture]" Applicable to: Global, FollowerBelief +#### [amount]% [stat] [cityFilter] +Example: "[20]% [Culture] [in all cities]" + +Applicable to: Global, FollowerBelief + #### [amount]% [stat] 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 -#### [amount]% [stat] [cityFilter] -Example: "[20]% [Culture] [in all cities]" +#### Nullifies [stat] [cityFilter] +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] Example: "[20]% Production when constructing [Culture] wonders [in all cities]" @@ -388,6 +398,9 @@ Applicable to: Global #### Can be continually researched Applicable to: Global +#### Rebel units may spawn +Applicable to: Global + #### [amount]% Strength Example: "[20]% Strength" @@ -1206,10 +1219,20 @@ Applicable to: Conditional #### Applicable to: Conditional +#### +Applicable to: Conditional + #### Applicable to: Conditional -#### +#### +Example: "" + +Applicable to: Conditional + +#### +Example: "" + Applicable to: Conditional #### diff --git a/tests/src/com/unciv/logic/map/TileImprovementConstructionTests.kt b/tests/src/com/unciv/logic/map/TileImprovementConstructionTests.kt index f39ce0219b..00964d7090 100644 --- a/tests/src/com/unciv/logic/map/TileImprovementConstructionTests.kt +++ b/tests/src/com/unciv/logic/map/TileImprovementConstructionTests.kt @@ -31,7 +31,7 @@ class TileImprovementConstructionTests { @Before fun initTheWorld() { RulesetCache.loadRulesets() - ruleSet = RulesetCache.getBaseRuleset() + ruleSet = RulesetCache.getVanillaRuleset() civInfo.tech.researchedTechnologies.addAll(ruleSet.technologies.values) civInfo.tech.techsResearched.addAll(ruleSet.technologies.keys) city.civInfo = civInfo diff --git a/tests/src/com/unciv/logic/map/TileMapTests.kt b/tests/src/com/unciv/logic/map/TileMapTests.kt index ef736df94d..516c19cf14 100644 --- a/tests/src/com/unciv/logic/map/TileMapTests.kt +++ b/tests/src/com/unciv/logic/map/TileMapTests.kt @@ -22,7 +22,7 @@ class TileMapTests { @Before fun initTheWorld() { RulesetCache.loadRulesets() - ruleSet = RulesetCache.getBaseRuleset() + ruleSet = RulesetCache.getVanillaRuleset() map = TileMap() tile1.position = Vector2(0f, 0f) diff --git a/tests/src/com/unciv/logic/map/UnitMovementAlgorithmsTests.kt b/tests/src/com/unciv/logic/map/UnitMovementAlgorithmsTests.kt index 9922ceb916..243524ebcb 100644 --- a/tests/src/com/unciv/logic/map/UnitMovementAlgorithmsTests.kt +++ b/tests/src/com/unciv/logic/map/UnitMovementAlgorithmsTests.kt @@ -28,7 +28,7 @@ class UnitMovementAlgorithmsTests { @Before fun initTheWorld() { RulesetCache.loadRulesets() - ruleSet = RulesetCache.getBaseRuleset() + ruleSet = RulesetCache.getVanillaRuleset() tile.ruleset = ruleSet tile.baseTerrain = Constants.grassland civInfo.tech.techsResearched.addAll(ruleSet.technologies.keys) diff --git a/tests/src/com/unciv/testing/BasicTests.kt b/tests/src/com/unciv/testing/BasicTests.kt index 34096ae4c1..6eb130fe86 100644 --- a/tests/src/com/unciv/testing/BasicTests.kt +++ b/tests/src/com/unciv/testing/BasicTests.kt @@ -28,7 +28,7 @@ class BasicTests { @Before fun loadTranslations() { RulesetCache.loadRulesets() - ruleset = RulesetCache.getBaseRuleset() + ruleset = RulesetCache.getVanillaRuleset() } @Test diff --git a/tests/src/com/unciv/testing/TranslationTests.kt b/tests/src/com/unciv/testing/TranslationTests.kt index 36ba4d1916..2790186152 100644 --- a/tests/src/com/unciv/testing/TranslationTests.kt +++ b/tests/src/com/unciv/testing/TranslationTests.kt @@ -31,7 +31,7 @@ class TranslationTests { })) translations.readAllLanguagesTranslation() RulesetCache.loadRulesets() - ruleset = RulesetCache.getBaseRuleset() + ruleset = RulesetCache.getVanillaRuleset() System.setOut(outputChannel) } diff --git a/tests/src/com/unciv/uniques/GlobalUniquesTests.kt b/tests/src/com/unciv/uniques/GlobalUniquesTests.kt index 9ba13b247b..92464bc64b 100644 --- a/tests/src/com/unciv/uniques/GlobalUniquesTests.kt +++ b/tests/src/com/unciv/uniques/GlobalUniquesTests.kt @@ -41,7 +41,7 @@ class GlobalUniquesTests { // Create a new ruleset we can easily edit, and set the important variables of gameInfo RulesetCache.loadRulesets() - ruleSet = RulesetCache.getBaseRuleset() + ruleSet = RulesetCache.getVanillaRuleset() gameInfo.ruleSet = ruleSet gameInfo.difficultyObject = ruleSet.difficulties["Prince"]!!