diff --git a/android/assets/jsons/Civ V - Vanilla/Policies.json b/android/assets/jsons/Civ V - Vanilla/Policies.json index 8e794e9cda..2e192e517e 100644 --- a/android/assets/jsons/Civ V - Vanilla/Policies.json +++ b/android/assets/jsons/Civ V - Vanilla/Policies.json @@ -12,7 +12,7 @@ }, { "name": "Legalism", - "uniques":["Immediately creates the cheapest available cultural building in each of your first [4] cities for free"], + "uniques":["Provides the cheapest [Culture] building in your first [4] cities for free"], "row": 1, "column": 3 }, @@ -38,7 +38,7 @@ }, { "name": "Tradition Complete", - "uniques": ["+[15]% growth [in all cities]","Immediately creates a [Aqueduct] in each of your first [4] cities for free"] + "uniques": ["+[15]% growth [in all cities]","Provides a [Aqueduct] in your first [4] cities for free"] } ] }, diff --git a/core/src/com/unciv/logic/city/CityConstructions.kt b/core/src/com/unciv/logic/city/CityConstructions.kt index e8c4646682..548422a80b 100644 --- a/core/src/com/unciv/logic/city/CityConstructions.kt +++ b/core/src/com/unciv/logic/city/CityConstructions.kt @@ -43,7 +43,8 @@ class CityConstructions { val inProgressConstructions = HashMap() var currentConstructionFromQueue: String get() { - if (constructionQueue.isEmpty()) return "" else return constructionQueue.first() + return if (constructionQueue.isEmpty()) "" + else constructionQueue.first() } set(value) { if (constructionQueue.isEmpty()) constructionQueue.add(value) else constructionQueue[0] = value @@ -53,6 +54,9 @@ class CityConstructions { var productionOverflow = 0 val queueMaxSize = 10 + // Maps cities to the buildings they received + val freeBuildingsProvidedFromThisCity: HashMap> = hashMapOf() + //region pure functions fun clone(): CityConstructions { val toReturn = CityConstructions() @@ -61,17 +65,22 @@ class CityConstructions { toReturn.currentConstructionIsUserSet = currentConstructionIsUserSet toReturn.constructionQueue.addAll(constructionQueue) toReturn.productionOverflow = productionOverflow + toReturn.freeBuildingsProvidedFromThisCity.putAll(freeBuildingsProvidedFromThisCity) return toReturn } internal fun getBuildableBuildings(): Sequence = cityInfo.getRuleset().buildings.values - .asSequence().filter { it.isBuildable(this) } + .asSequence().filter { it.isBuildable(this) } fun getConstructableUnits() = cityInfo.getRuleset().units.values - .asSequence().filter { it.isBuildable(this) } + .asSequence().filter { it.isBuildable(this) } fun getBasicCultureBuildings() = cityInfo.getRuleset().buildings.values - .asSequence().filter { it.culture > 0f && !it.isAnyWonder() && it.replaces == null } + .asSequence().filter { it.culture > 0f && !it.isAnyWonder() && it.replaces == null } + + fun getBasicStatBuildings(stat: Stat) = cityInfo.getRuleset().buildings.values + .asSequence() + .filter { !it.isAnyWonder() && it.replaces == null && it.getStats(null)[stat] > 0f } /** * @return [Stats] provided by all built buildings in city plus the bonus from Library @@ -121,13 +130,14 @@ class CityConstructions { */ fun getMaintenanceCosts(): Int { var maintenanceCost = 0 - // We cache this to increase performance - val freeBuildings = cityInfo.civInfo.policies.getListOfFreeBuildings(cityInfo.id) + val freeBuildings = cityInfo.civInfo.civConstructions.getFreeBuildings(cityInfo.id) + for (building in getBuiltBuildings()) { if (building.name !in freeBuildings) { maintenanceCost += building.maintenance } } + return maintenanceCost } @@ -151,7 +161,27 @@ class CityConstructions { } return result } + + fun addFreeBuildings() { + // "Provides a free [buildingName] [cityFilter]" + for (unique in cityInfo.getMatchingUniques("Provides a free [] []")) { + val freeBuildingName = cityInfo.civInfo.getEquivalentBuilding(unique.params[0]).name + val citiesThatApply = when (unique.params[1]) { + "in this city" -> listOf(cityInfo) + "in other cities" -> cityInfo.civInfo.cities.filter { it !== cityInfo } + else -> cityInfo.civInfo.cities.filter { it.matchesFilter(unique.params[1]) } + } + + for (city in citiesThatApply) { + if (city.cityConstructions.containsBuildingOrEquivalent(freeBuildingName)) continue + city.cityConstructions.addBuilding(freeBuildingName) + if (city.id !in freeBuildingsProvidedFromThisCity) + freeBuildingsProvidedFromThisCity[city.id] = hashSetOf() + freeBuildingsProvidedFromThisCity[city.id]!!.add(freeBuildingName) + } + } + } /** @constructionName needs to be a non-perpetual construction, else an empty string is returned */ internal fun getTurnsToConstructionString(constructionName: String, useStoredProduction:Boolean = true): String { @@ -173,20 +203,6 @@ class CityConstructions { return lines.joinToString("\n", "\n") } - // This function appears unused, can it be removed? - fun getProductionForTileInfo(): String { - /* this is because there were rare errors that I assume were caused because - currentConstruction changed on another thread */ - val currentConstructionSnapshot = currentConstructionFromQueue - var result = currentConstructionSnapshot.tr() - if (currentConstructionSnapshot != "" - && !PerpetualConstruction.perpetualConstructionsMap.containsKey(currentConstructionSnapshot)) { - val turnsLeft = turnsToConstruction(currentConstructionSnapshot) - result += " - $turnsLeft${Fonts.turn}" - } - return result - } - fun getProductionMarkup(ruleset: Ruleset): FormattedLine { val currentConstructionSnapshot = currentConstructionFromQueue if (currentConstructionSnapshot.isEmpty()) return FormattedLine() @@ -519,8 +535,7 @@ class CityConstructions { && it.params[2] == stat.name } ) { - cityInfo.civInfo.boughtConstructionsWithGloballyIncreasingPrice[constructionName] = - (cityInfo.civInfo.boughtConstructionsWithGloballyIncreasingPrice[constructionName] ?: 0) + 1 + cityInfo.civInfo.civConstructions.boughtItemsWithIncreasingPrice.add(constructionName, 1) } } @@ -530,26 +545,26 @@ class CityConstructions { return true } - - fun hasBuildableCultureBuilding(): Boolean { - return getBasicCultureBuildings() - .map { cityInfo.civInfo.getEquivalentBuilding(it.name) } - .filter { it.isBuildable(this) || isBeingConstructedOrEnqueued(it.name) } - .any() + + fun hasBuildableStatBuildings(stat: Stat): Boolean { + return getBasicStatBuildings(stat) + .map { cityInfo.civInfo.getEquivalentBuilding(it.name) } + .filter { it.isBuildable(this) || isBeingConstructedOrEnqueued(it.name) } + .any() } - fun addCultureBuilding(): String? { - val buildableCultureBuildings = getBasicCultureBuildings() - .map { cityInfo.civInfo.getEquivalentBuilding(it.name) } - .filter { it.isBuildable(this) || isBeingConstructedOrEnqueued(it.name) } + fun addCheapestBuildableStatBuilding(stat: Stat): String? { + val cheapestBuildableStatBuilding = getBasicStatBuildings(stat) + .map { cityInfo.civInfo.getEquivalentBuilding(it.name) } + .filter { it.isBuildable(this) || isBeingConstructedOrEnqueued(it.name) } + .minByOrNull { it.cost }?.name - if (!buildableCultureBuildings.any()) + if (cheapestBuildableStatBuilding == null) return null - val cultureBuildingToBuild = buildableCultureBuildings.minByOrNull { it.cost }!!.name - constructionComplete(getConstruction(cultureBuildingToBuild) as INonPerpetualConstruction) + constructionComplete(getConstruction(cheapestBuildableStatBuilding) as INonPerpetualConstruction) - return cultureBuildingToBuild + return cheapestBuildableStatBuilding } private fun removeCurrentConstruction() = removeFromQueue(0, true) diff --git a/core/src/com/unciv/logic/city/CityInfo.kt b/core/src/com/unciv/logic/city/CityInfo.kt index 2e63950e4e..9faf36a71e 100644 --- a/core/src/com/unciv/logic/city/CityInfo.kt +++ b/core/src/com/unciv/logic/city/CityInfo.kt @@ -144,7 +144,7 @@ class CityInfo { cityConstructions.addBuilding(uniqueBuilding.name) } - civInfo.policies.tryToAddPolicyBuildings() + civInfo.civConstructions.tryAddFreeBuildings() for (unique in getMatchingUniques("Gain a free [] []")) { val freeBuildingName = unique.params[0] @@ -467,6 +467,7 @@ class CityInfo { // Construct units at the beginning of the turn, // so they won't be generated out in the open and vulnerable to enemy attacks before you can control them cityConstructions.constructIfEnough() + cityConstructions.addFreeBuildings() cityStats.update() tryUpdateRoadStatus() attackedThisTurn = false @@ -632,8 +633,8 @@ class CityInfo { fun matchesFilter(filter: String, viewingCiv: CivilizationInfo = civInfo): Boolean { return when (filter) { "in this city" -> true - "in all cities" -> true // Filtered by the way uniques our found - "in other cities" -> true // Filtered by the way uniques our found + "in all cities" -> true // Filtered by the way uniques are found + "in other cities" -> true // Filtered by the way uniques are found "in all coastal cities" -> isCoastal() "in capital" -> isCapital() "in all non-occupied cities" -> !cityStats.hasExtraAnnexUnhappiness() || isPuppet diff --git a/core/src/com/unciv/logic/city/CityInfoConquestFunctions.kt b/core/src/com/unciv/logic/city/CityInfoConquestFunctions.kt index 0cd25b56b7..9ecc936c00 100644 --- a/core/src/com/unciv/logic/city/CityInfoConquestFunctions.kt +++ b/core/src/com/unciv/logic/city/CityInfoConquestFunctions.kt @@ -32,7 +32,8 @@ class CityInfoConquestFunctions(val city: CityInfo){ } private fun destroyBuildingsOnCapture() { - city.apply { + city.apply { + // Possibly remove other buildings for (building in cityConstructions.getBuiltBuildings()) { when { building.hasUnique("Never destroyed when the city is captured") || building.isWonder -> continue @@ -47,6 +48,32 @@ class CityInfoConquestFunctions(val city: CityInfo){ } } } + + private fun removeBuildingsOnMoveToCiv(oldCiv: CivilizationInfo) { + city.apply { + // Remove all buildings provided for free to this city + for (building in civInfo.civConstructions.getFreeBuildings(id)) { + cityConstructions.removeBuilding(building) + } + + // Remove all buildings provided for free from here to other cities (e.g. CN Tower) + println("Removing buildings: ${cityConstructions.freeBuildingsProvidedFromThisCity}") + for ((cityId, buildings) in cityConstructions.freeBuildingsProvidedFromThisCity) { + val city = oldCiv.cities.firstOrNull { it.id == cityId } ?: continue + println("Removing buildings $buildings from city ${city.name}") + for (building in buildings) { + city.cityConstructions.removeBuilding(building) + } + } + cityConstructions.freeBuildingsProvidedFromThisCity.clear() + + // Remove national wonders + for (building in cityConstructions.getBuiltBuildings()) { + if (building.isNationalWonder && !building.hasUnique("Never destroyed when the city is captured")) + cityConstructions.removeBuilding(building.name) + } + } + } /** Function for stuff that should happen on any capture, be it puppet, annex or liberate. * Stuff that should happen any time a city is moved between civs, so also when trading, @@ -223,11 +250,11 @@ class CityInfoConquestFunctions(val city: CityInfo){ oldCiv.cities.first().cityConstructions.addBuilding(capitalCityIndicator) // relocate palace } } - - for (building in cityConstructions.getBuiltBuildings()) { - if (building.isNationalWonder && !building.hasUnique("Never destroyed when the city is captured")) - cityConstructions.removeBuilding(building.name) - } + + // Remove their free buildings from this city and remove free buildings provided by the city from their cities + removeBuildingsOnMoveToCiv(oldCiv) + // Add our free buildings to this city and add free buildings provided by the city to other cities + civInfo.civConstructions.tryAddFreeBuildings() // Place palace for newCiv if this is the only city they have if (newCivInfo.cities.count() == 1) { diff --git a/core/src/com/unciv/logic/civilization/CivConstructions.kt b/core/src/com/unciv/logic/civilization/CivConstructions.kt new file mode 100644 index 0000000000..997d507e5c --- /dev/null +++ b/core/src/com/unciv/logic/civilization/CivConstructions.kt @@ -0,0 +1,165 @@ +package com.unciv.logic.civilization + +import com.unciv.logic.city.INonPerpetualConstruction +import com.unciv.models.Counter +import com.unciv.models.stats.Stat +import java.util.* +import kotlin.collections.HashMap + +class CivConstructions() { + + @Transient + lateinit var civInfo: CivilizationInfo + + // Maps objects to the amount of times bought + val boughtItemsWithIncreasingPrice: Counter = Counter() + + // Maps to cities to all free buildings they contain + private val freeBuildings: HashMap> = hashMapOf() + + // Maps stats to the cities that have received a building of that stat + // Android Studio says an EnumMap would be better, but that thing isn't serializable. + // I don't know how to suppress that hint :( + private val freeStatBuildingsProvided: HashMap> = hashMapOf() + + // Maps buildings to the cities that have received that building + private val freeSpecificBuildingsProvided: HashMap> = hashMapOf() + + init { + for (stat in Stat.values()) { + freeStatBuildingsProvided[stat] = hashSetOf() + } + } + + fun clone(): CivConstructions { + val toReturn = CivConstructions() + toReturn.civInfo = civInfo + toReturn.freeBuildings.putAll(freeBuildings) + toReturn.freeStatBuildingsProvided.putAll(freeStatBuildingsProvided) + toReturn.freeSpecificBuildingsProvided.putAll(freeSpecificBuildingsProvided) + toReturn.boughtItemsWithIncreasingPrice.add(boughtItemsWithIncreasingPrice.clone()) + return toReturn + } + + fun setTransients(civInfo: CivilizationInfo) { + this.civInfo = civInfo + + // civInfo.boughtConstructionsWithGloballyIncreasingPrice deprecated since 3.16.15, this is replacement code + if (civInfo.boughtConstructionsWithGloballyIncreasingPrice.isNotEmpty()) { + for (item in civInfo.boughtConstructionsWithGloballyIncreasingPrice) { + boughtItemsWithIncreasingPrice.add(item.key, item.value) + } + civInfo.boughtConstructionsWithGloballyIncreasingPrice.clear() + } + // + + // Deprecated variables in civ.policies since 3.16.15, this is replacement code + if (civInfo.policies.specificBuildingsAdded.isNotEmpty()) { + for ((building, cities) in civInfo.policies.specificBuildingsAdded) { + for (cityId in cities) { + if (building !in freeSpecificBuildingsProvided) + freeSpecificBuildingsProvided[building] = hashSetOf() + freeSpecificBuildingsProvided[building]!!.add(cityId) + + if (cityId !in freeBuildings) + freeBuildings[cityId] = hashSetOf() + freeBuildings[cityId]!!.add(building) + } + } + civInfo.policies.specificBuildingsAdded.clear() + } + + if (civInfo.policies.cultureBuildingsAdded.isNotEmpty()) { + for ((cityId, building) in civInfo.policies.cultureBuildingsAdded) { + freeStatBuildingsProvided[Stat.Culture]!!.add(cityId) + + if (cityId !in freeBuildings) + freeBuildings[cityId] = hashSetOf() + freeBuildings[cityId]!!.add(building) + } + civInfo.policies.cultureBuildingsAdded.clear() + } + // + } + + fun startTurn() { + tryAddFreeBuildings() + } + + fun tryAddFreeBuildings() { + addFreeStatsBuildings() + addFreeSpecificBuildings() + } + + fun getFreeBuildings(cityId: String): HashSet { + val toReturn = freeBuildings[cityId] ?: hashSetOf() + for (city in civInfo.cities) { + toReturn.addAll(city.cityConstructions.freeBuildingsProvidedFromThisCity[city.id] ?: hashSetOf()) + } + return toReturn + } + + private fun addFreeBuilding(cityId: String, building: String) { + if (!freeBuildings.containsKey(cityId)) + freeBuildings[cityId] = hashSetOf() + freeBuildings[cityId]!!.add(building) + } + + private fun addFreeStatsBuildings() { + val statUniquesData = civInfo.getMatchingUniques("Provides the cheapest [] building in your first [] cities for free") + .groupBy { it.params[0] } + .mapKeys { Stat.valueOf(it.key) } + .mapValues { unique -> unique.value.sumOf { it.params[1].toInt() } } + .toMutableMap() + + // Deprecated since 3.16.15 + statUniquesData[Stat.Culture] = (statUniquesData[Stat.Culture] ?: 0) + + civInfo.getMatchingUniques("Immediately creates the cheapest available cultural building in each of your first [] cities for free") + .sumOf { it.params[1].toInt() } + // + + + for ((stat, amount) in statUniquesData) { + addFreeStatBuildings(stat, amount) + } + } + + private fun addFreeStatBuildings(stat: Stat, amount: Int) { + for (city in civInfo.cities.take(amount)) { + if (freeStatBuildingsProvided[stat]!!.contains(city.id) || !city.cityConstructions.hasBuildableStatBuildings(stat)) continue + + val builtBuilding = city.cityConstructions.addCheapestBuildableStatBuilding(stat) + if (builtBuilding != null) { + freeStatBuildingsProvided[stat]!!.add(city.id) + addFreeBuilding(city.id, builtBuilding) + } + } + } + + private fun addFreeSpecificBuildings() { + val buildingsUniquesData = (civInfo.getMatchingUniques("Provides a [] in your first [] cities for free") + // Deprecated since 3.16.15 + + civInfo.getMatchingUniques("Immediately creates a [] in each of your first [] cities for free") + // + ).groupBy { it.params[0] } + .mapValues { unique -> unique.value.sumOf { it.params[1].toInt() } } + + for ((building, amount) in buildingsUniquesData) { + addFreeBuildings(building, amount) + } + } + + private fun addFreeBuildings(building: String, amount: Int) { + for (city in civInfo.cities.take(amount)) { + if (freeSpecificBuildingsProvided[building]?.contains(city.id) == true || city.cityConstructions.containsBuildingOrEquivalent(building)) continue + + (city.cityConstructions.getConstruction(building) as INonPerpetualConstruction).postBuildEvent(city.cityConstructions) + + if (!freeSpecificBuildingsProvided.containsKey(building)) + freeSpecificBuildingsProvided[building] = hashSetOf() + freeSpecificBuildingsProvided[building]!!.add(city.id) + + addFreeBuilding(city.id, building) + } + } +} \ No newline at end of file diff --git a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt index 725164eb96..43b8277a56 100644 --- a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt +++ b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt @@ -101,6 +101,7 @@ class CivilizationInfo { var civName = "" var tech = TechManager() var policies = PolicyManager() + var civConstructions = CivConstructions() var questManager = QuestManager() var religionManager = ReligionManager() var goldenAges = GoldenAgeManager() @@ -124,8 +125,11 @@ class CivilizationInfo { */ val temporaryUniques = ArrayList>() - /** Maps the name of the construction to the amount of times bought */ - val boughtConstructionsWithGloballyIncreasingPrice = HashMap() + // Deprecated since 3.16.15 + /** Maps the name of the construction to the amount of times bought */ + @Deprecated("Deprecated since 3.16.15", replaceWith = ReplaceWith("civWideConstructions.boughtItemsWithIncreasingPrice")) + val boughtConstructionsWithGloballyIncreasingPrice = HashMap() + // // if we only use lists, and change the list each time the cities are changed, // we won't get concurrent modification exceptions. @@ -153,6 +157,7 @@ class CivilizationInfo { toReturn.civName = civName toReturn.tech = tech.clone() toReturn.policies = policies.clone() + toReturn.civConstructions = civConstructions.clone() toReturn.religionManager = religionManager.clone() toReturn.questManager = questManager.clone() toReturn.goldenAges = goldenAges.clone() @@ -178,7 +183,9 @@ class CivilizationInfo { toReturn.cityStateUniqueUnit = cityStateUniqueUnit toReturn.flagsCountdown.putAll(flagsCountdown) toReturn.temporaryUniques.addAll(temporaryUniques) - toReturn.boughtConstructionsWithGloballyIncreasingPrice.putAll(boughtConstructionsWithGloballyIncreasingPrice) + // Deprecated since 3.16.15 + toReturn.boughtConstructionsWithGloballyIncreasingPrice.putAll(boughtConstructionsWithGloballyIncreasingPrice) + // toReturn.hasEverOwnedOriginalCapital = hasEverOwnedOriginalCapital return toReturn } @@ -557,6 +564,8 @@ class CivilizationInfo { fun setTransients() { goldenAges.civInfo = this + civConstructions.setTransients(civInfo = this) + policies.civInfo = this if (policies.adoptedPolicies.size > 0 && policies.numberOfAdoptedPolicies == 0) policies.numberOfAdoptedPolicies = policies.adoptedPolicies.count { !Policy.isBranchCompleteByName(it) } @@ -602,7 +611,7 @@ class CivilizationInfo { fun updateDetailedCivResources() = transients().updateDetailedCivResources() fun startTurn() { - policies.startTurn() + civConstructions.startTurn() updateStatsForNextTurn() // for things that change when turn passes e.g. golden age, city state influence // Generate great people at the start of the turn, diff --git a/core/src/com/unciv/logic/civilization/PolicyManager.kt b/core/src/com/unciv/logic/civilization/PolicyManager.kt index eb15e84c4e..998a1d9504 100644 --- a/core/src/com/unciv/logic/civilization/PolicyManager.kt +++ b/core/src/com/unciv/logic/civilization/PolicyManager.kt @@ -30,8 +30,12 @@ class PolicyManager { var shouldOpenPolicyPicker = false get() = field && canAdoptPolicy() - private var cultureBuildingsAdded = HashMap() // Maps cities to buildings - private var specificBuildingsAdded = HashMap>() // Maps buildings to cities + // Deprecated since 3.16.15 + @Deprecated("Deprecated since 3.16.15", ReplaceWith("civInfo.civWideConstructions.freeStatBuildingsProvided[Stat.Culture]")) + var cultureBuildingsAdded = HashMap() // Maps cities to buildings + @Deprecated("Deprecated since 3.16.15", ReplaceWith("civInfo.civWideConstructions.freeSpecificBuildingsProvided")) + var specificBuildingsAdded = HashMap>() // Maps buildings to cities + // fun clone(): PolicyManager { @@ -41,8 +45,10 @@ class PolicyManager { toReturn.freePolicies = freePolicies toReturn.shouldOpenPolicyPicker = shouldOpenPolicyPicker toReturn.storedCulture = storedCulture - toReturn.cultureBuildingsAdded.putAll(cultureBuildingsAdded) - toReturn.specificBuildingsAdded.putAll(specificBuildingsAdded) + // Deprecated since 3.16.15 + toReturn.cultureBuildingsAdded.putAll(cultureBuildingsAdded) + toReturn.specificBuildingsAdded.putAll(specificBuildingsAdded) + // return toReturn } @@ -70,10 +76,6 @@ class PolicyManager { policyUniques.addUnique(unique) } - fun startTurn() { - tryToAddPolicyBuildings() - } - fun addCulture(culture: Int) { val couldAdoptPolicyBefore = canAdoptPolicy() storedCulture += culture @@ -171,8 +173,6 @@ class PolicyManager { for (unique in policy.uniqueObjects) UniqueTriggerActivation.triggerCivwideUnique(unique, civInfo) - tryToAddPolicyBuildings() - // This ALSO has the side-effect of updating the CivInfo statForNextTurn so we don't need to call it explicitly for (cityInfo in civInfo.cities) cityInfo.cityStats.update() @@ -180,65 +180,6 @@ class PolicyManager { if (!canAdoptPolicy()) shouldOpenPolicyPicker = false } - fun tryToAddPolicyBuildings() { - tryAddCultureBuildings() - tryAddFreeBuildings() - } - - private fun tryAddCultureBuildings() { - val cultureBuildingUniques = civInfo.getMatchingUniques("Immediately creates the cheapest available cultural building in each of your first [] cities for free") - val citiesToReceiveCultureBuilding = cultureBuildingUniques.sumOf { it.params[0].toInt() } - if (!cultureBuildingUniques.any()) return - if (cultureBuildingsAdded.size >= citiesToReceiveCultureBuilding) return - - val candidateCities = civInfo.cities - .sortedBy { it.turnAcquired } - .subList(0, min(citiesToReceiveCultureBuilding, civInfo.cities.size)) - .filter { - it.id !in cultureBuildingsAdded - && it.cityConstructions.hasBuildableCultureBuilding() - } - for (city in candidateCities) { - val builtBuilding = city.cityConstructions.addCultureBuilding() - if (builtBuilding != null) cultureBuildingsAdded[city.id] = builtBuilding - - } - } - - private fun tryAddFreeBuildings() { - val matchingUniques = civInfo.getMatchingUniques("Immediately creates a [] in each of your first [] cities for free") - // If we have "create a free aqueduct in first 3 cities" and "create free aqueduct in first 4 cities", we do: "create free aqueduct in first 3+4=7 cities" - val sortedUniques = matchingUniques.groupBy {it.params[0]} - for (unique in sortedUniques) { - tryAddSpecificBuilding(unique.key, unique.value.sumOf {it.params[1].toInt()}) - } - } - - private fun tryAddSpecificBuilding(building: String, cityCount: Int) { - if (specificBuildingsAdded[building] == null) specificBuildingsAdded[building] = mutableSetOf() - val citiesAlreadyGivenBuilding = specificBuildingsAdded[building] - if (citiesAlreadyGivenBuilding!!.size >= cityCount) return - val candidateCities = civInfo.cities - .sortedBy { it.turnAcquired } - .subList(0, min(cityCount, civInfo.cities.size)) - .filter { - it.id !in citiesAlreadyGivenBuilding && !it.cityConstructions.containsBuildingOrEquivalent(building) - } - - for (city in candidateCities) { - (city.cityConstructions.getConstruction(building) as INonPerpetualConstruction).postBuildEvent(city.cityConstructions) - citiesAlreadyGivenBuilding.add(city.id) - } - } - - fun getListOfFreeBuildings(cityId: String): MutableSet { - val freeBuildings = cultureBuildingsAdded.filter { it.key == cityId }.values.toMutableSet() - for (building in specificBuildingsAdded.filter { it.value.contains(cityId) }) { - freeBuildings.add(building.key) - } - return freeBuildings - } - private fun triggerGlobalAlerts(policy: Policy, extraNotificationText: String = "") { var extraNotificationTextCopy = extraNotificationText if (extraNotificationText != "") { diff --git a/core/src/com/unciv/logic/civilization/ReligionManager.kt b/core/src/com/unciv/logic/civilization/ReligionManager.kt index 18d435693e..70bf161cdc 100644 --- a/core/src/com/unciv/logic/civilization/ReligionManager.kt +++ b/core/src/com/unciv/logic/civilization/ReligionManager.kt @@ -28,7 +28,7 @@ class ReligionManager { // contain the master list, and the ReligionManagers retrieve it from there every time the game loads. // Deprecated since 3.16.13 - @Deprecated("Replace by adding to `civInfo.boughtConstructionsWithGloballyIncreasingPrice`") + @Deprecated("Replace by adding to `civInfo.civWideConstructions.boughtItemsWithIncreasingPrice`") var greatProphetsEarned = 0 private set // @@ -67,7 +67,7 @@ class ReligionManager { // greatProphetsEarned deprecated since 3.16.13, replacement code if (greatProphetsEarned != 0) { - civInfo.boughtConstructionsWithGloballyIncreasingPrice[getGreatProphetEquivalent()!!] = greatProphetsEarned + civInfo.civConstructions.boughtItemsWithIncreasingPrice[getGreatProphetEquivalent()!!] = greatProphetsEarned greatProphetsEarned = 0 } // @@ -115,7 +115,7 @@ class ReligionManager { // https://www.reddit.com/r/civ/comments/2m82wu/can_anyone_detail_the_finer_points_of_great/ // Game files (globaldefines.xml) fun faithForNextGreatProphet(): Int { - val greatProphetsEarned = civInfo.boughtConstructionsWithGloballyIncreasingPrice[getGreatProphetEquivalent()!!] ?: 0 + val greatProphetsEarned = civInfo.civConstructions.boughtItemsWithIncreasingPrice[getGreatProphetEquivalent()!!] ?: 0 var faithCost = (200 + 100 * greatProphetsEarned * (greatProphetsEarned + 1) / 2f) * @@ -152,8 +152,7 @@ class ReligionManager { val prophet = civInfo.addUnit(prophetUnitName, birthCity) ?: return prophet.religion = religion!!.name storedFaith -= faithForNextGreatProphet() - civInfo.boughtConstructionsWithGloballyIncreasingPrice[prophetUnitName] = - (civInfo.boughtConstructionsWithGloballyIncreasingPrice[prophetUnitName] ?: 0) + 1 + civInfo.civConstructions.boughtItemsWithIncreasingPrice.add(prophetUnitName, 1) } } diff --git a/core/src/com/unciv/logic/civilization/RuinsManager/RuinsManager.kt b/core/src/com/unciv/logic/civilization/RuinsManager/RuinsManager.kt index e9c8873803..d249571cbc 100644 --- a/core/src/com/unciv/logic/civilization/RuinsManager/RuinsManager.kt +++ b/core/src/com/unciv/logic/civilization/RuinsManager/RuinsManager.kt @@ -43,7 +43,7 @@ class RuinsManager { if (civInfo.gameInfo.difficulty in possibleReward.excludedDifficulties) continue if (Constants.hiddenWithoutReligionUnique in possibleReward.uniques && !civInfo.gameInfo.hasReligionEnabled()) continue if ("Hidden after generating a Great Prophet" in possibleReward.uniques - && civInfo.boughtConstructionsWithGloballyIncreasingPrice[civInfo.religionManager.getGreatProphetEquivalent()] ?: 0 > 0 + && civInfo.civConstructions.boughtItemsWithIncreasingPrice[civInfo.religionManager.getGreatProphetEquivalent()] ?: 0 > 0 ) continue if (possibleReward.uniqueObjects.any { unique -> unique.placeholderText == "Only available after [] turns" diff --git a/core/src/com/unciv/models/ruleset/Building.kt b/core/src/com/unciv/models/ruleset/Building.kt index ceb05b11c6..74e61ecf71 100644 --- a/core/src/com/unciv/models/ruleset/Building.kt +++ b/core/src/com/unciv/models/ruleset/Building.kt @@ -151,7 +151,8 @@ class Building : NamedStats(), INonPerpetualConstruction, ICivilopediaText { } fun getStats(city: CityInfo?): Stats { - val stats = this.clone() + // Calls the clone function of the NamedStats this class is derived from, not a clone function of this class + val stats = this.clone() if (city == null) return stats val civInfo = city.civInfo @@ -651,20 +652,7 @@ class Building : NamedStats(), INonPerpetualConstruction, ICivilopediaText { } // "Provides a free [buildingName] [cityFilter]" - val freeBuildingUniques = uniqueObjects.asSequence().filter { it.placeholderText=="Provides a free [] []" } - - for (unique in freeBuildingUniques) { - val affectedCities = - if (unique.params[1] == "in this city") sequenceOf(cityConstructions.cityInfo) - else civInfo.cities.asSequence().filter { it.matchesFilter(unique.params[1]) } - - val freeBuildingName = civInfo.getEquivalentBuilding(unique.params[0]).name - - for (city in affectedCities) { - if (cityConstructions.containsBuildingOrEquivalent(freeBuildingName)) continue - cityConstructions.addBuilding(freeBuildingName) - } - } + cityConstructions.addFreeBuildings() for (unique in uniqueObjects) UniqueTriggerActivation.triggerCivwideUnique(unique, civInfo, cityConstructions.cityInfo) diff --git a/core/src/com/unciv/models/ruleset/Unique.kt b/core/src/com/unciv/models/ruleset/Unique.kt index 6936981962..2ee4adbcab 100644 --- a/core/src/com/unciv/models/ruleset/Unique.kt +++ b/core/src/com/unciv/models/ruleset/Unique.kt @@ -457,6 +457,10 @@ object UniqueTriggerActivation { civInfo.addNotification(notification, NotificationIcon.Diplomacy) return true } + + "Provides the cheapest [] building in your first [] cities for free", + "Provides a [] in your first [] cities for free" -> + civInfo.civConstructions.tryAddFreeBuildings() } return false } diff --git a/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt b/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt index b7724dfd2c..7408430ff2 100644 --- a/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt +++ b/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt @@ -14,7 +14,6 @@ import com.unciv.ui.civilopedia.FormattedLine import com.unciv.ui.civilopedia.ICivilopediaText import com.unciv.ui.utils.Fonts import com.unciv.ui.utils.toPercent -import java.util.* import kotlin.collections.ArrayList import kotlin.collections.HashMap import kotlin.collections.HashSet @@ -254,7 +253,7 @@ class BaseUnit : INamed, INonPerpetualConstruction, ICivilopediaText { getCostForConstructionsIncreasingInPrice( it.params[1].toInt(), it.params[5].toInt(), - cityInfo.civInfo.boughtConstructionsWithGloballyIncreasingPrice[name] ?: 0 + cityInfo.civInfo.civConstructions.boughtItemsWithIncreasingPrice[name] ?: 0 ) } ) @@ -267,7 +266,7 @@ class BaseUnit : INamed, INonPerpetualConstruction, ICivilopediaText { getCostForConstructionsIncreasingInPrice( it.params[1].toInt(), it.params[4].toInt(), - cityInfo.civInfo.boughtConstructionsWithGloballyIncreasingPrice[name] ?: 0 + cityInfo.civInfo.civConstructions.boughtItemsWithIncreasingPrice[name] ?: 0 ) } )