From 0d793268695e5fcbde9ba9305489354e1080c3bb Mon Sep 17 00:00:00 2001 From: Xander Lenstra <71121390+xlenstra@users.noreply.github.com> Date: Sun, 13 Jun 2021 07:14:31 +0200 Subject: [PATCH] Updated Tradition branch to G&K (#4106) * Updated Tradition branch to G&K * Small performance boost for calcualting maintenance; fix crash on next turn * Fixed a bug where maintenance-free buildings would still cost maintenance * Simplified some code * I am unable to read * Fixed a few broken uniques, including #4109 * Implemented requested changes --- .../jsons/Civ V - Vanilla/Buildings.json | 6 +- .../jsons/Civ V - Vanilla/Policies.json | 8 +- .../com/unciv/logic/battle/BattleDamage.kt | 11 +-- .../com/unciv/logic/city/CityConstructions.kt | 19 +++-- core/src/com/unciv/logic/city/CityInfo.kt | 2 +- core/src/com/unciv/logic/city/CityStats.kt | 8 +- .../unciv/logic/civilization/PolicyManager.kt | 78 ++++++++++++++++--- core/src/com/unciv/models/ruleset/Building.kt | 4 +- 8 files changed, 98 insertions(+), 38 deletions(-) diff --git a/android/assets/jsons/Civ V - Vanilla/Buildings.json b/android/assets/jsons/Civ V - Vanilla/Buildings.json index 5e92438d2e..9d4325675f 100644 --- a/android/assets/jsons/Civ V - Vanilla/Buildings.json +++ b/android/assets/jsons/Civ V - Vanilla/Buildings.json @@ -52,7 +52,7 @@ "name": "Library", "hurryCostModifier": 25, "maintenance": 1, - "uniques": ["[+1 Science] Per [2] Population in this city"], + "uniques": ["[+1 Science] Per [2] Population [in this city]"], "requiredTech": "Writing" }, { @@ -72,7 +72,7 @@ "uniqueTo": "China", "hurryCostModifier": 25, "gold": 2, - "uniques": ["[+1 Science] Per [2] Population in this city"], + "uniques": ["[+1 Science] Per [2] Population [in this city]"], "requiredTech": "Writing" }, { @@ -752,7 +752,7 @@ "requiredBuilding": "University", "maintenance": 3, "hurryCostModifier": 0, - "uniques": ["[+1 Science] Per [2] Population in this city"], + "uniques": ["[+1 Science] Per [2] Population [in this city]"], "requiredTech": "Scientific Theory" }, { diff --git a/android/assets/jsons/Civ V - Vanilla/Policies.json b/android/assets/jsons/Civ V - Vanilla/Policies.json index 7c3fbf04f1..65cdb6a91d 100644 --- a/android/assets/jsons/Civ V - Vanilla/Policies.json +++ b/android/assets/jsons/Civ V - Vanilla/Policies.json @@ -6,19 +6,19 @@ "policies": [ { "name": "Aristocracy", - "uniques": ["+[15]% Production when constructing [Wonders]", "[+1 Happiness] per [10] population in all cities"], + "uniques": ["+[15]% Production when constructing [Wonders]", "[+1 Happiness] per [10] population [in all cities]"], "row": 1, "column": 1 }, { "name": "Legalism", - "uniques":["Immediately creates a cheapest available cultural building in each of your first 4 cities for free"], + "uniques":["Immediately creates the cheapest available cultural building in each of your first [4] cities for free"], "row": 1, "column": 3 }, { "name": "Oligarchy", - "uniques": ["Units in cities cost no Maintenance", "+50% attacking strength for cities with garrisoned units"], + "uniques": ["Units in cities cost no Maintenance", "+[50]% attacking strength for cities with garrisoned units"], "row": 1, "column": 5 }, @@ -38,7 +38,7 @@ }, { "name": "Tradition Complete", - "uniques": ["+[15]% growth [in all cities]","[+2 Food] [in all cities]"] + "uniques": ["+[15]% growth [in all cities]","Immediately creates a [Aqueduct] in each of your first [4] cities for free"] } ] }, diff --git a/core/src/com/unciv/logic/battle/BattleDamage.kt b/core/src/com/unciv/logic/battle/BattleDamage.kt index 7c0219c499..8970b7a6ab 100644 --- a/core/src/com/unciv/logic/battle/BattleDamage.kt +++ b/core/src/com/unciv/logic/battle/BattleDamage.kt @@ -181,11 +181,12 @@ object BattleDamage { ) modifiers["Statue of Zeus"] = 15 } else if (attacker is CityCombatant) { - if (attacker.getCivInfo() - .hasUnique("+50% attacking strength for cities with garrisoned units") - && attacker.city.getCenterTile().militaryUnit != null - ) - modifiers["Oligarchy"] = 50 + if (attacker.city.getCenterTile().militaryUnit != null) { + val garrisonBonus = attacker.getCivInfo().getMatchingUniques("+[]% attacking strength for cities with garrisoned units") + .sumBy { it.params[0].toInt() } + if (garrisonBonus != 0) + modifiers["Garrisoned unit"] = garrisonBonus + } } return modifiers diff --git a/core/src/com/unciv/logic/city/CityConstructions.kt b/core/src/com/unciv/logic/city/CityConstructions.kt index fa31c5592c..c9d9bb577e 100644 --- a/core/src/com/unciv/logic/city/CityConstructions.kt +++ b/core/src/com/unciv/logic/city/CityConstructions.kt @@ -81,8 +81,12 @@ class CityConstructions { stats.add(building.getStats(cityInfo.civInfo)) for (unique in builtBuildingUniqueMap.getAllUniques()) when (unique.placeholderText) { - "[] Per [] Population in this city" -> stats.add(unique.stats.times(cityInfo.population.population / unique.params[1].toFloat())) + "[] per [] population []" -> if (cityInfo.matchesFilter(unique.params[2])) + stats.add(unique.stats.times(cityInfo.population.population / unique.params[1].toFloat())) "[] once [] is discovered" -> if (cityInfo.civInfo.tech.isResearched(unique.params[1])) stats.add(unique.stats) + // Deprecated since 3.14.17, left for modding compatibility + "[] Per [] Population in this city" -> + stats.add(unique.stats.times(cityInfo.population.population / unique.params[1].toFloat())) } return stats @@ -92,13 +96,14 @@ class CityConstructions { * @return Maintenance cost of all built buildings */ fun getMaintenanceCosts(): Int { - var maintenanceCost = getBuiltBuildings().sumBy { it.maintenance } - val policyManager = cityInfo.civInfo.policies - if (cityInfo.id in policyManager.legalismState) { - val buildingName = policyManager.legalismState[cityInfo.id] - maintenanceCost -= cityInfo.getRuleset().buildings[buildingName]!!.maintenance + var maintenanceCost = 0 + // We cache this to increase performance + val freeBuildings = cityInfo.civInfo.policies.getListOfFreeBuildings(cityInfo.id) + for (building in getBuiltBuildings()) { + if (building.name !in freeBuildings) { + maintenanceCost += building.maintenance + } } - return maintenanceCost } diff --git a/core/src/com/unciv/logic/city/CityInfo.kt b/core/src/com/unciv/logic/city/CityInfo.kt index 03ff0d9262..97a7d94cdb 100644 --- a/core/src/com/unciv/logic/city/CityInfo.kt +++ b/core/src/com/unciv/logic/city/CityInfo.kt @@ -88,7 +88,7 @@ class CityInfo { if (civInfo.cities.size == 1) cityConstructions.addBuilding(capitalCityIndicator()) - civInfo.policies.tryAddLegalismBuildings() + civInfo.policies.tryToAddPolicyBuildings() for (unique in civInfo.getMatchingUniques("Gain a free [] []")) { val freeBuildingName = unique.params[0] diff --git a/core/src/com/unciv/logic/city/CityStats.kt b/core/src/com/unciv/logic/city/CityStats.kt index 24f5682d9c..5126a27b68 100644 --- a/core/src/com/unciv/logic/city/CityStats.kt +++ b/core/src/com/unciv/logic/city/CityStats.kt @@ -158,13 +158,9 @@ class CityStats { fun getGrowthBonusFromPoliciesAndWonders(): Float { var bonus = 0f - // This requires more... complex navigation of the local uniques to merge into "+[amount]% growth [cityFilter]" - for (unique in cityInfo.civInfo.getMatchingUniques("+[]% growth in all cities")) - bonus += unique.params[0].toFloat() - // "+[amount]% growth [cityFilter]" for (unique in cityInfo.civInfo.getMatchingUniques("+[]% growth []")) - if (cityInfo.matchesFilter(unique.params[0])) + if (cityInfo.matchesFilter(unique.params[1])) bonus += unique.params[0].toFloat() return bonus / 100 } @@ -444,7 +440,7 @@ class CityStats { First we see how much food we generate. Then we apply production bonuses to it. Up till here, business as usual. Then, we deduct food eaten (from the total produced). - Now we have the excess food, whih has its own things. If we're unhappy, cut it by 1/4. + Now we have the excess food, which has its own things. If we're unhappy, cut it by 1/4. Some policies have bonuses for excess food only, not general food production. */ diff --git a/core/src/com/unciv/logic/civilization/PolicyManager.kt b/core/src/com/unciv/logic/civilization/PolicyManager.kt index 74aea52267..45bec65c2f 100644 --- a/core/src/com/unciv/logic/civilization/PolicyManager.kt +++ b/core/src/com/unciv/logic/civilization/PolicyManager.kt @@ -24,8 +24,14 @@ class PolicyManager { var numberOfAdoptedPolicies = 0 var shouldOpenPolicyPicker = false get() = field && canAdoptPolicy() - var legalismState = HashMap() + + private var cultureBuildingsAdded = HashMap() // Maps cities to buildings + private var specificBuildingsAdded = HashMap>() // Maps buildings to cities var autocracyCompletedTurns = 0 + + @Deprecated("Deprecated since 3.14.17") // Replaced with cultureBuildingsAdded + var legalismState = HashMap() // Maps cities to buildings + // We make it a reference copy of the original variable. This way, it can still works in older versions fun clone(): PolicyManager { val toReturn = PolicyManager() @@ -34,8 +40,13 @@ class PolicyManager { toReturn.freePolicies = freePolicies toReturn.shouldOpenPolicyPicker = shouldOpenPolicyPicker toReturn.storedCulture = storedCulture - toReturn.legalismState.putAll(legalismState) + toReturn.cultureBuildingsAdded.putAll(cultureBuildingsAdded) + toReturn.specificBuildingsAdded.putAll(specificBuildingsAdded) toReturn.autocracyCompletedTurns = autocracyCompletedTurns + + // Deprecated since 3.14.17, left for backwards compatibility + toReturn.legalismState.putAll(cultureBuildingsAdded) + return toReturn } @@ -44,6 +55,10 @@ class PolicyManager { fun setTransients() { for (policyName in adoptedPolicies) addPolicyToTransients(getPolicyByName(policyName)) + // Deprecated since 3.14.17, left for backwards compatibility + if (cultureBuildingsAdded.isEmpty() && legalismState.isNotEmpty()) { + cultureBuildingsAdded.putAll(legalismState) + } } fun addPolicyToTransients(policy: Policy) { @@ -52,7 +67,7 @@ class PolicyManager { } fun startTurn() { - tryAddLegalismBuildings() + tryToAddPolicyBuildings() } fun addCulture(culture: Int) { @@ -133,7 +148,7 @@ class PolicyManager { for (unique in policy.uniqueObjects) UniqueTriggerActivation.triggerCivwideUnique(unique, civInfo) - tryAddLegalismBuildings() + 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) @@ -141,22 +156,63 @@ class PolicyManager { if (!canAdoptPolicy()) shouldOpenPolicyPicker = false } + + fun tryToAddPolicyBuildings() { + tryAddCultureBuildings() + tryAddFreeBuildings() + } - fun tryAddLegalismBuildings() { - if (!civInfo.hasUnique("Immediately creates a cheapest available cultural building in each of your first 4 cities for free")) - return - if (legalismState.size >= 4) return + 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(4, civInfo.cities.size)) + .subList(0, min(citiesToReceiveCultureBuilding, civInfo.cities.size)) .filter { - it.id !in legalismState + it.id !in cultureBuildingsAdded && it.cityConstructions.hasBuildableCultureBuilding() } for (city in candidateCities) { val builtBuilding = city.cityConstructions.addCultureBuilding() - if (builtBuilding != null) legalismState[city.id] = builtBuilding!! + 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.sumBy {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).postBuildEvent(city.cityConstructions, false) + 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 + } } \ No newline at end of file diff --git a/core/src/com/unciv/models/ruleset/Building.kt b/core/src/com/unciv/models/ruleset/Building.kt index 8136a7c9c0..e76b3c298e 100644 --- a/core/src/com/unciv/models/ruleset/Building.kt +++ b/core/src/com/unciv/models/ruleset/Building.kt @@ -441,7 +441,9 @@ class Building : NamedStats(), IConstruction { fun isStatRelated(stat: Stat): Boolean { if (get(stat) > 0) return true if (getStatPercentageBonuses(null).get(stat) > 0) return true - if (uniqueObjects.any { it.placeholderText == "[] Per [] Population in this city" && it.stats.get(stat) > 0 }) return true + if (uniqueObjects.any { it.placeholderText == "[] per [] population []" && it.stats.get(stat) > 0 }) return true + // Deprecated since 3.14.17, left for modding compatibility + if (uniqueObjects.any { it.placeholderText == "[] Per [] Population in this city"}) return true return false }