diff --git a/android/assets/jsons/Civ V - Vanilla/Policies.json b/android/assets/jsons/Civ V - Vanilla/Policies.json index 47a6889629..6152dbf5a7 100644 --- a/android/assets/jsons/Civ V - Vanilla/Policies.json +++ b/android/assets/jsons/Civ V - Vanilla/Policies.json @@ -101,7 +101,7 @@ { "name": "Discipline", "uniques":["+[15]% Strength for [Melee] units which have another [Military] unit in an adjacent tile"], - "row": 1, + "row": 1, "column": 4 }, { @@ -393,7 +393,7 @@ { "name": "Autocracy", "era": "Industrial era", - "uniques": ["-33% unit upkeep costs"], + "uniques": ["-[33]% unit upkeep costs", "Upon capturing a city, receive [10] times its [Culture] production as [Culture] immediately"], "policies": [ { "name": "Populism", @@ -403,13 +403,13 @@ }, { "name": "Militarism", - "uniques": ["Gold cost of purchasing units -33%"], + "uniques": ["Gold cost of purchasing [All] units -[33]%"], "row": 1, "column": 5 }, { "name": "Fascism", - "uniques": ["Quantity of strategic resources produced by the empire increased by 100%"], + "uniques": ["Quantity of strategic resources produced by the empire +[100]%", "+[2] Movement for all [Great General] units"], "requires": ["Populism","Militarism"], "row": 2, "column": 3 @@ -417,20 +417,21 @@ { "name": "Police State", "uniques": ["[+3 Happiness] from every [Courthouse]", "+[100]% Production when constructing [Courthouse]"], + // There are also some uniques regarding espoinage, which as of this writing is not yet implemented "requires": ["Militarism"], "row": 2, "column": 5 }, { "name": "Total War", - "uniques": ["+[15]% Production when constructing [Military] units [in all cities]", "New [Military] units start with [15] Experience"], + "uniques": ["+[25]% Production when constructing [Military] units [in all cities]", "New [Military] units start with [15] Experience"], "requires": ["Police State","Fascism"], "row": 3, "column": 4 }, { "name": "Autocracy Complete", - "uniques": ["+20% attack bonus to all Military Units for 30 turns"] + "uniques": ["+[25]% attack strength to all [Military] units for [50] turns"] } ] } diff --git a/core/src/com/unciv/logic/battle/Battle.kt b/core/src/com/unciv/logic/battle/Battle.kt index e4c05c6184..96dd7922d6 100644 --- a/core/src/com/unciv/logic/battle/Battle.kt +++ b/core/src/com/unciv/logic/battle/Battle.kt @@ -10,6 +10,7 @@ import com.unciv.logic.map.TileInfo import com.unciv.models.AttackableTile import com.unciv.models.ruleset.Unique import com.unciv.models.ruleset.unit.UnitType +import com.unciv.models.stats.Stat import java.util.* import kotlin.math.max @@ -317,6 +318,13 @@ object Battle { return } + for (unique in attackerCiv.getMatchingUniques("Upon capturing a city, receive [] times its [] production as [] immediately")) { + attackerCiv.addStat( + Stat.valueOf(unique.params[2]), + unique.params[0].toInt() * city.cityStats.currentCityStats.get(Stat.valueOf(unique.params[1])).toInt() + ) + } + if (attackerCiv.isPlayerCivilization()) { attackerCiv.popupAlerts.add(PopupAlert(AlertType.CityConquered, city.id)) UncivGame.Current.settings.addCompletedTutorialTask("Conquer a city") diff --git a/core/src/com/unciv/logic/battle/BattleDamage.kt b/core/src/com/unciv/logic/battle/BattleDamage.kt index 50b4928404..fc06d53f60 100644 --- a/core/src/com/unciv/logic/battle/BattleDamage.kt +++ b/core/src/com/unciv/logic/battle/BattleDamage.kt @@ -147,8 +147,11 @@ object BattleDamage { } } - if (attacker.getCivInfo().policies.autocracyCompletedTurns > 0) - modifiers["Autocracy Complete"] = 20 + for (unique in attacker.getCivInfo().getMatchingUniques("+[]% attack strength to all [] units for [] turns")) { + if (attacker.matchesCategory(unique.params[1])) { + modifiers.add("Temporary Bonus", unique.params[0].toInt()) + } + } if (defender is CityCombatant && attacker.getCivInfo() diff --git a/core/src/com/unciv/logic/civilization/CivInfoStats.kt b/core/src/com/unciv/logic/civilization/CivInfoStats.kt index b26a441fcc..6886f50f38 100644 --- a/core/src/com/unciv/logic/civilization/CivInfoStats.kt +++ b/core/src/com/unciv/logic/civilization/CivInfoStats.kt @@ -34,7 +34,7 @@ class CivInfoStats(val civInfo: CivilizationInfo) { for (unique in civInfo.getMatchingUniques("-[]% [] unit maintenance costs")) { val numberOfUnitsWithDiscount = min(numberOfUnitsToPayFor, unitsToPayFor.count { it.matchesFilter(unique.params[1]) }.toFloat()) - numberOfUnitsToPayFor -= numberOfUnitsWithDiscount * unique.params[0].toFloat() / 100 + numberOfUnitsToPayFor -= numberOfUnitsWithDiscount * unique.params[0].toFloat() / 100f } val turnLimit = BASE_GAME_DURATION_TURNS * civInfo.gameInfo.gameParameters.gameSpeed.modifier @@ -43,11 +43,11 @@ class CivInfoStats(val civInfo: CivilizationInfo) { cost = cost.pow(1 + gameProgress / 3) // Why 3? To spread 1 to 1.33 if (!civInfo.isPlayerCivilization()) cost *= civInfo.gameInfo.getDifficulty().aiUnitMaintenanceModifier - + for (unique in civInfo.getMatchingUniques("-[]% unit upkeep costs")) { cost *= 1f - unique.params[0].toFloat() / 100f } - + // Deprecated since 3.15 if (civInfo.hasUnique("-33% unit upkeep costs")) cost *= 0.67f // diff --git a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt index f6b6267fc1..220c265bbd 100644 --- a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt +++ b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt @@ -19,6 +19,7 @@ import com.unciv.models.ruleset.tile.ResourceSupplyList import com.unciv.models.ruleset.tile.ResourceType import com.unciv.models.ruleset.tile.TileResource import com.unciv.models.ruleset.unit.BaseUnit +import com.unciv.models.stats.Stat import com.unciv.models.stats.Stats import com.unciv.models.translations.tr import com.unciv.ui.victoryscreen.RankingType @@ -92,6 +93,11 @@ class CivilizationInfo { /** See DiplomacyManager.flagsCountdown to why not eEnum */ private var flagsCountdown = HashMap() + /** Arraylist instead of HashMap as there might be doubles + * Pairs of Uniques and the amount of turns they are still active + * If the counter reaches 0 at the end of a turn, it is removed immediately + */ + var temporaryUniques = ArrayList>() // if we only use lists, and change the list each time the cities are changed, // we won't get concurrent modification exceptions. @@ -135,6 +141,7 @@ class CivilizationInfo { toReturn.naturalWonders.addAll(naturalWonders) toReturn.cityStatePersonality = cityStatePersonality toReturn.flagsCountdown.putAll(flagsCountdown) + toReturn.temporaryUniques.addAll(temporaryUniques) return toReturn } @@ -213,15 +220,21 @@ class CivilizationInfo { } fun getResourceModifier(resource: TileResource): Int { - var resourceModifier = 1 + var resourceModifier = 1f for (unique in getMatchingUniques("Double quantity of [] produced")) if (unique.params[0] == resource.name) - resourceModifier *= 2 + resourceModifier *= 2f if (resource.resourceType == ResourceType.Strategic) { - if (hasUnique("Quantity of strategic resources produced by the empire increased by 100%")) - resourceModifier *= 2 + resourceModifier *= 1f + getMatchingUniques("Quantity of strategic resources produced by the empire +[]%") + .map { it.params[0].toFloat() / 100f }.sum() + + // Deprecated since 3.15 + if (hasUnique("Quantity of strategic resources produced by the empire increased by 100%")) { + resourceModifier *= 2f + } + // } - return resourceModifier + return resourceModifier.toInt() } fun hasResource(resourceName: String): Boolean = getCivResourcesByName()[resourceName]!! > 0 @@ -242,7 +255,8 @@ class CivilizationInfo { } + policies.policyUniques.getUniques(uniqueTemplate) + tech.getTechUniques().filter { it.placeholderText == uniqueTemplate } + - religionManager.getUniques().filter { it.placeholderText == uniqueTemplate } + religionManager.getUniques().filter { it.placeholderText == uniqueTemplate } + + temporaryUniques.filter { it.first.placeholderText == uniqueTemplate }.map { it.first } } //region Units @@ -547,6 +561,12 @@ class CivilizationInfo { for (city in cities.toList()) { // a city can be removed while iterating (if it's being razed) so we need to iterate over a copy city.endTurn() } + + // Update turn counter for temporary uniques + for (unique in temporaryUniques.toList()) { + temporaryUniques.remove(unique) + if (unique.second > 1) temporaryUniques.add(Pair(unique.first, unique.second - 1)) + } goldenAges.endTurn(getHappiness()) getCivUnits().forEach { it.endTurn() } @@ -566,6 +586,18 @@ class CivilizationInfo { else -> gold + delta } } + + fun addStat(stat: Stat, amount: Int) { + when (stat) { + Stat.Culture -> policies.addCulture(amount) + Stat.Science -> tech.addScience(amount) + Stat.Gold -> addGold(amount) + Stat.Faith -> religionManager.storedFaith += amount + else -> {} + // Food and Production wouldn't make sense to be added nationwide + // Happiness cannot be added as it is recalculated again, use a unique instead + } + } fun getGreatPersonPointsForNextTurn(): Stats { val stats = Stats() diff --git a/core/src/com/unciv/logic/civilization/PolicyManager.kt b/core/src/com/unciv/logic/civilization/PolicyManager.kt index 1c21e6af5f..f15f15d7af 100644 --- a/core/src/com/unciv/logic/civilization/PolicyManager.kt +++ b/core/src/com/unciv/logic/civilization/PolicyManager.kt @@ -27,11 +27,13 @@ class PolicyManager { private var cultureBuildingsAdded = HashMap() // Maps cities to buildings private var specificBuildingsAdded = HashMap>() // Maps buildings to cities + + @Deprecated("Deprecated since 3.15") 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() @@ -42,11 +44,11 @@ class PolicyManager { toReturn.storedCulture = storedCulture toReturn.cultureBuildingsAdded.putAll(cultureBuildingsAdded) toReturn.specificBuildingsAdded.putAll(specificBuildingsAdded) - toReturn.autocracyCompletedTurns = autocracyCompletedTurns - - // Deprecated since 3.14.17, left for backwards compatibility - toReturn.legalismState.putAll(cultureBuildingsAdded) - + + // Deprecated since 3.15 left for backwards compatibility + toReturn.legalismState.putAll(cultureBuildingsAdded) + toReturn.autocracyCompletedTurns = autocracyCompletedTurns + // return toReturn } @@ -89,8 +91,6 @@ class PolicyManager { fun endTurn(culture: Int) { addCulture(culture) - if (autocracyCompletedTurns > 0) - autocracyCompletedTurns -= 1 } // from https://forums.civfanatics.com/threads/the-number-crunching-thread.389702/ @@ -166,7 +166,7 @@ class PolicyManager { if (!canAdoptPolicy()) shouldOpenPolicyPicker = false } - + fun tryToAddPolicyBuildings() { tryAddCultureBuildings() tryAddFreeBuildings() @@ -188,10 +188,10 @@ class PolicyManager { 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" @@ -200,7 +200,7 @@ class PolicyManager { 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] @@ -211,13 +211,13 @@ class PolicyManager { .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) + 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) }) { @@ -225,4 +225,4 @@ class PolicyManager { } return freeBuildings } -} \ No newline at end of file +} diff --git a/core/src/com/unciv/logic/map/MapUnit.kt b/core/src/com/unciv/logic/map/MapUnit.kt index b0838e7319..37cf177605 100644 --- a/core/src/com/unciv/logic/map/MapUnit.kt +++ b/core/src/com/unciv/logic/map/MapUnit.kt @@ -329,7 +329,7 @@ class MapUnit { val unitToUpgradeTo = getUnitToUpgradeTo() var goldCostOfUpgrade = (unitToUpgradeTo.cost - baseUnit().cost) * 2f + 10f for (unique in civInfo.getMatchingUniques("Gold cost of upgrading [] units reduced by []%")) { - if (matchesFilter(unique.params[0])) + if (matchesFilter(unique.params[0])) goldCostOfUpgrade *= (1 - unique.params[1].toFloat() / 100f) } // Deprecated since 3.14.17 @@ -337,7 +337,7 @@ class MapUnit { goldCostOfUpgrade *= 0.67f } // - + if (goldCostOfUpgrade < 0) return 0 // For instance, Landsknecht costs less than Spearman, so upgrading would cost negative gold return goldCostOfUpgrade.toInt() } diff --git a/core/src/com/unciv/models/ruleset/Unique.kt b/core/src/com/unciv/models/ruleset/Unique.kt index 16e24fe281..6d2a7f93e4 100644 --- a/core/src/com/unciv/models/ruleset/Unique.kt +++ b/core/src/com/unciv/models/ruleset/Unique.kt @@ -85,7 +85,10 @@ object UniqueTriggerActivation { "[] Free Technologies" -> if (!civInfo.isSpectator()) civInfo.tech.freeTechs += unique.params[0].toInt() "Quantity of strategic resources produced by the empire increased by 100%" -> civInfo.updateDetailedCivResources() - "+20% attack bonus to all Military Units for 30 turns" -> civInfo.policies.autocracyCompletedTurns = 30 + // Deprecated since 3.15 + "+20% attack bonus to all Military Units for 30 turns" -> civInfo.temporaryUniques.add(Pair(unique, 30)) + // + "+[]% attack strength to all [] Units for [] turns" -> civInfo.temporaryUniques.add(Pair(unique, unique.params[2].toInt())) "Reveals the entire map" -> civInfo.exploredTiles.addAll(civInfo.gameInfo.tileMap.values.asSequence().map { it.position }) @@ -94,12 +97,13 @@ object UniqueTriggerActivation { val promotion = unique.params[1] for (unit in civInfo.getCivUnits()) if (unit.matchesFilter(filter) - || civInfo.gameInfo.ruleSet.unitPromotions.values.any { - it.name == promotion && unit.type.name in it.unitTypes - }) + || civInfo.gameInfo.ruleSet.unitPromotions.values.any { + it.name == promotion && unit.type.name in it.unitTypes + } + ) { unit.promotions.addPromotion(promotion, isFree = true) + } } } } - } \ No newline at end of file diff --git a/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt b/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt index e33985f22d..6750cbcc75 100644 --- a/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt +++ b/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt @@ -112,10 +112,18 @@ class BaseUnit : INamed, IConstruction { override fun getGoldCost(civInfo: CivilizationInfo): Int { var cost = getBaseGoldCost(civInfo) - if (civInfo.hasUnique("Gold cost of purchasing units -33%")) cost *= 0.66f + for (unique in civInfo.getMatchingUniques("Gold cost of purchasing [] units -[]%")) { + if (matchesFilter(unique.params[0])) + cost *= 1f - unique.params[1].toFloat() / 100f + } + + // Deprecated since 3.15 + if (civInfo.hasUnique("Gold cost of purchasing units -33%")) cost *= 0.67f + // + for (unique in civInfo.getMatchingUniques("Cost of purchasing items in cities reduced by []%")) - cost *= 1 - (unique.params[0].toFloat() / 100) - return (cost / 10).toInt() * 10 // rounded down o nearest ten + cost *= 1f - (unique.params[0].toFloat() / 100f) + return (cost / 10).toInt() * 10 // rounded down to nearest ten } fun getDisbandGold(civInfo: CivilizationInfo) = getBaseGoldCost(civInfo).toInt() / 20