Allowed adding arbitrary global uniques to city state bonuses (#8028)

* Allowed adding arbitrary global uniques to city state bonuses

* Added conditional support for city-state uniques
This commit is contained in:
Yair Morgenstern 2022-11-19 19:16:24 +02:00 committed by GitHub
parent 694e0d33fc
commit 2d5536e8be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 58 additions and 66 deletions

View File

@ -491,6 +491,13 @@ class GameInfo : IsPartOfGameInfoSerialization, HasGameInfoSerializationVersion
for (religion in religions.values) religion.setTransients(this)
// must be done before updating tech manager bools, since that depends on allied city-states
for (civInfo in civilizations)
for (diplomacyManager in civInfo.diplomacy.values) {
diplomacyManager.civInfo = civInfo
diplomacyManager.updateHasOpenBorders()
}
for (civInfo in civilizations) civInfo.setTransients()
for (civInfo in civilizations) {
civInfo.thingsToFocusOnForVictory =

View File

@ -156,9 +156,8 @@ class CityStats(val cityInfo: CityInfo) {
if (otherCiv.cityStateType == CityStateType.Maritime && cityInfo.isCapital())
stats.food += 2
} else {
for (bonus in eraInfo.getCityStateBonuses(otherCiv.cityStateType, relationshipLevel)) {
if (bonus.isOfType(UniqueType.CityStateStatsPerCity)
&& cityInfo.matchesFilter(bonus.params[1])
for (bonus in eraInfo.getCityStateBonuses(otherCiv.cityStateType, relationshipLevel, UniqueType.CityStateStatsPerCity)) {
if (cityInfo.matchesFilter(bonus.params[1])
&& bonus.conditionalsApply(otherCiv, cityInfo)
) stats.add(bonus.stats)
}

View File

@ -2,18 +2,19 @@ package com.unciv.logic.civilization
import com.unciv.Constants
import com.unciv.logic.automation.civilization.NextTurnAutomation
import com.unciv.logic.civilization.diplomacy.*
import com.unciv.logic.civilization.diplomacy.DiplomacyFlags
import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers
import com.unciv.logic.civilization.diplomacy.DiplomaticStatus
import com.unciv.logic.civilization.diplomacy.RelationshipLevel
import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.tile.ResourceSupplyList
import com.unciv.models.ruleset.unique.StateForConditionals
import com.unciv.models.ruleset.unique.Unique
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.ruleset.unit.BaseUnit
import com.unciv.models.stats.Stat
import com.unciv.ui.victoryscreen.RankingType
import java.util.*
import kotlin.collections.HashMap
import kotlin.collections.HashSet
import kotlin.collections.LinkedHashMap
import kotlin.math.min
import kotlin.math.pow
@ -22,40 +23,29 @@ class CityStateFunctions(val civInfo: CivilizationInfo) {
/** Attempts to initialize the city state, returning true if successful. */
fun initCityState(ruleset: Ruleset, startingEra: String, unusedMajorCivs: Collection<String>): Boolean {
val cityStateType = ruleset.nations[civInfo.civName]?.cityStateType
?: return false
val startingTechs = ruleset.technologies.values.filter { it.hasUnique(UniqueType.StartingTech) }
for (tech in startingTechs)
civInfo.tech.techsResearched.add(tech.name) // can't be .addTechnology because the civInfo isn't assigned yet
val allMercantileResources = ruleset.tileResources.values.filter { it.hasUnique(UniqueType.CityStateOnlyResource) }.map { it.name }
val allPossibleBonuses = HashSet<Unique>() // We look through these to determine what kind of city state we are
var fallback = false
val uniqueTypes = HashSet<UniqueType>() // We look through these to determine what kind of city state we are
for (era in ruleset.eras.values) {
if (era.undefinedCityStateBonuses()) {
fallback = true
break
}
val allyBonuses = era.getCityStateBonuses(cityStateType, RelationshipLevel.Ally)
val friendBonuses = era.getCityStateBonuses(cityStateType, RelationshipLevel.Friend)
allPossibleBonuses.addAll(allyBonuses)
allPossibleBonuses.addAll(friendBonuses)
if (era.undefinedCityStateBonuses()) continue
for (unique in era.friendBonusObjects.values.map { it.getAllUniques() } + era.allyBonusObjects.values.map { it.getAllUniques() })
uniqueTypes.addAll(unique.mapNotNull { it.type })
}
// CS Personality
civInfo.cityStatePersonality = CityStatePersonality.values().random()
// Mercantile bonus resources
if (allPossibleBonuses.any { it.isOfType(UniqueType.CityStateUniqueLuxury) }
|| fallback && cityStateType == CityStateType.Mercantile) { // Fallback for badly defined Eras.json
if (uniqueTypes.contains(UniqueType.CityStateUniqueLuxury)) { // Fallback for badly defined Eras.json
civInfo.cityStateResource = allMercantileResources.randomOrNull()
}
// Unique unit for militaristic city-states
if (allPossibleBonuses.any { it.isOfType(UniqueType.CityStateMilitaryUnits) }
|| fallback && cityStateType == CityStateType.Militaristic // Fallback for badly defined Eras.json
) {
if (uniqueTypes.contains(UniqueType.CityStateMilitaryUnits)) {
val possibleUnits = ruleset.units.values.filter { it.requiredTech != null
&& ruleset.eras[ruleset.technologies[it.requiredTech!!]!!.era()]!!.eraNumber > ruleset.eras[startingEra]!!.eraNumber // Not from the start era or before
@ -438,15 +428,6 @@ class CityStateFunctions(val civInfo: CivilizationInfo) {
if (bonus.stats[statType] > 0 || (bonus.isOfType(UniqueType.CityStateHappiness) && statType == Stat.Happiness))
return true
}
} else {
// compatibility mode
return when {
civInfo.cityStateType == CityStateType.Mercantile && statType == Stat.Happiness -> true
civInfo.cityStateType == CityStateType.Cultured && statType == Stat.Culture -> true
civInfo.cityStateType == CityStateType.Maritime && statType == Stat.Food -> true
civInfo.cityStateType == CityStateType.Religious && statType == Stat.Faith ->true
else -> false
}
}
return false
@ -668,4 +649,19 @@ class CityStateFunctions(val civInfo: CivilizationInfo) {
addPositiveByResource(city.getCityResources(), Constants.cityStates)
}
}
// TODO: Optimize, update whenever status changes, otherwise retain the same list
fun getUniquesProvidedByCityStates(
uniqueType: UniqueType,
stateForConditionals: StateForConditionals
):Sequence<Unique> {
if (civInfo.isCityState()) return emptySequence()
val era = civInfo.getEra()
if (era.undefinedCityStateBonuses()) return emptySequence()
return civInfo.getKnownCivs().asSequence().filter { it.isCityState() }
.flatMap { era.getCityStateBonuses(it.cityStateType, civInfo.getDiplomacyManager(it).relationshipLevel(), uniqueType) }
.filter { it.conditionalsApply(stateForConditionals) }
}
}

View File

@ -147,21 +147,8 @@ class CivInfoStats(val civInfo: CivilizationInfo) {
val eraInfo = civInfo.getEra()
if (!eraInfo.undefinedCityStateBonuses()) {
for (bonus in eraInfo.getCityStateBonuses(otherCiv.cityStateType, relationshipLevel)) {
if (bonus.isOfType(UniqueType.CityStateStatsPerTurn) && bonus.conditionalsApply(otherCiv))
cityStateBonus.add(bonus.stats)
}
} else {
// Deprecated, assume Civ V values for compatibility
if (otherCiv.cityStateType == CityStateType.Cultured) {
cityStateBonus.culture =
when {
civInfo.getEraNumber() in 0..1 -> 3f
civInfo.getEraNumber() in 2..3 -> 6f
else -> 13f
}
if (relationshipLevel == RelationshipLevel.Ally)
cityStateBonus.culture *= 2f
for (bonus in eraInfo.getCityStateBonuses(otherCiv.cityStateType, relationshipLevel, UniqueType.CityStateStatsPerTurn)) {
cityStateBonus.add(bonus.stats)
}
}

View File

@ -463,6 +463,7 @@ class CivilizationInfo : IsPartOfGameInfoSerialization {
.filter { it.isOfType(uniqueType) && it.conditionalsApply(stateForConditionals) }
)
yieldAll(getEra().getMatchingUniques(uniqueType, stateForConditionals))
yieldAll(cityStateFunctions.getUniquesProvidedByCityStates(uniqueType, stateForConditionals))
if (religionManager.religion != null)
yieldAll(religionManager.religion!!.getFounderUniques()
.filter { it.isOfType(uniqueType) && it.conditionalsApply(stateForConditionals) })

View File

@ -646,11 +646,9 @@ class DiplomacyManager() : IsPartOfGameInfoSerialization {
if (eraInfo.undefinedCityStateBonuses()) return
for (bonus in eraInfo.getCityStateBonuses(otherCiv().cityStateType, relationshipLevel())) {
for (bonus in eraInfo.getCityStateBonuses(otherCiv().cityStateType, relationshipLevel(), UniqueType.CityStateMilitaryUnits)) {
// Reset the countdown if it has ended, or if we have longer to go than the current maximum (can happen when going from friend to ally)
if (bonus.isOfType(UniqueType.CityStateMilitaryUnits) &&
(!hasFlag(DiplomacyFlags.ProvideMilitaryUnit) || getFlag(DiplomacyFlags.ProvideMilitaryUnit) > bonus.params[0].toInt())
) {
if (!hasFlag(DiplomacyFlags.ProvideMilitaryUnit) || getFlag(DiplomacyFlags.ProvideMilitaryUnit) > bonus.params[0].toInt()) {
setFlag(DiplomacyFlags.ProvideMilitaryUnit, bonus.params[0].toInt() + variance)
}
}

View File

@ -3,9 +3,9 @@ package com.unciv.models.ruleset
import com.badlogic.gdx.graphics.Color
import com.unciv.logic.civilization.CityStateType
import com.unciv.logic.civilization.diplomacy.RelationshipLevel
import com.unciv.models.ruleset.unique.IHasUniques
import com.unciv.models.ruleset.unique.StateForConditionals
import com.unciv.models.ruleset.unique.Unique
import com.unciv.models.ruleset.unique.UniqueMap
import com.unciv.models.ruleset.unique.UniqueTarget
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.ui.civilopedia.FormattedLine
@ -34,9 +34,9 @@ class Era : RulesetObject() {
var friendBonus = HashMap<String, List<String>>()
var allyBonus = HashMap<String, List<String>>()
@Suppress("MemberVisibilityCanBePrivate")
val friendBonusObjects: Map<CityStateType, List<Unique>> by lazy { initBonuses(friendBonus) }
val friendBonusObjects: Map<CityStateType, UniqueMap> by lazy { initBonuses(friendBonus) }
@Suppress("MemberVisibilityCanBePrivate")
val allyBonusObjects: Map<CityStateType, List<Unique>> by lazy { initBonuses(allyBonus) }
val allyBonusObjects: Map<CityStateType, UniqueMap> by lazy { initBonuses(allyBonus) }
private var iconRGB: List<Int>? = null
@ -87,20 +87,24 @@ class Era : RulesetObject() {
}.map { it.first }.distinct()
}
private fun initBonuses(bonusMap: Map<String, List<String>>): Map<CityStateType, List<Unique>> {
val objectMap = HashMap<CityStateType, List<Unique>>()
private fun initBonuses(bonusMap: Map<String, List<String>>): Map<CityStateType, UniqueMap> {
val objectMap = HashMap<CityStateType, UniqueMap>()
for ((cityStateType, bonusList) in bonusMap) {
objectMap[CityStateType.valueOf(cityStateType)] = bonusList.map { Unique(it, UniqueTarget.CityState) }
val cityStateTypeUniqueMap = UniqueMap()
cityStateTypeUniqueMap.addUniques(bonusList.map { Unique(it, UniqueTarget.CityState)})
objectMap[CityStateType.valueOf(cityStateType)] = cityStateTypeUniqueMap
}
return objectMap
}
fun getCityStateBonuses(cityStateType: CityStateType, relationshipLevel: RelationshipLevel): List<Unique> {
return when (relationshipLevel) {
RelationshipLevel.Ally -> allyBonusObjects[cityStateType] ?: emptyList()
RelationshipLevel.Friend -> friendBonusObjects[cityStateType] ?: emptyList()
else -> emptyList()
}
fun getCityStateBonuses(cityStateType: CityStateType, relationshipLevel: RelationshipLevel, uniqueType:UniqueType?=null): Sequence<Unique> {
val cityStateUniqueMap = when (relationshipLevel) {
RelationshipLevel.Ally -> allyBonusObjects[cityStateType]
RelationshipLevel.Friend -> friendBonusObjects[cityStateType]
else -> null
} ?: return emptySequence()
return if (uniqueType == null) cityStateUniqueMap.getAllUniques()
else cityStateUniqueMap.getUniques(uniqueType)
}
/** Since 3.19.5 we have a warning for mods without bonuses, eventually we should treat such mods as providing no bonus */

View File

@ -288,7 +288,7 @@ class DiplomacyScreen(
/** Given a list of [bonuses], returns a list of pretty strings with updated values for Siam-like uniques
* Assumes that each bonus contains only one stat type */
private fun getAdjustedBonuses(bonuses: List<Unique>): List<String> {
private fun getAdjustedBonuses(bonuses: Sequence<Unique>): List<String> {
val bonusStrings = ArrayList<String>()
for (bonus in bonuses) {
var improved = false