diff --git a/core/src/com/unciv/logic/city/City.kt b/core/src/com/unciv/logic/city/City.kt index 00844826b9..c9e6da9299 100644 --- a/core/src/com/unciv/logic/city/City.kt +++ b/core/src/com/unciv/logic/city/City.kt @@ -209,7 +209,7 @@ class City : IsPartOfGameInfoSerialization, INamed { @Readonly fun getRuleset() = civ.gameInfo.ruleset - fun getResourcesGeneratedByCity(civResourceModifiers: Map) = CityResources.getResourcesGeneratedByCity(this, civResourceModifiers) + @Readonly fun getResourcesGeneratedByCity(civResourceModifiers: Map) = CityResources.getResourcesGeneratedByCity(this, civResourceModifiers) @Readonly fun getAvailableResourceAmount(resourceName: String) = CityResources.getAvailableResourceAmount(this, resourceName) @Readonly fun isGrowing() = foodForNextTurn() > 0 diff --git a/core/src/com/unciv/logic/city/CityResources.kt b/core/src/com/unciv/logic/city/CityResources.kt index 7c186d4c7b..6abb211c7c 100644 --- a/core/src/com/unciv/logic/city/CityResources.kt +++ b/core/src/com/unciv/logic/city/CityResources.kt @@ -11,9 +11,10 @@ import yairm210.purity.annotations.Readonly object CityResources { /** Returns ALL resources, city-wide and civ-wide */ + @Readonly fun getResourcesGeneratedByCity(city: City, resourceModifiers: Map): ResourceSupplyList { - val cityResources = getResourcesGeneratedByCityNotIncludingBuildings(city, resourceModifiers) - addCityResourcesGeneratedFromUniqueBuildings(city, cityResources, resourceModifiers) + @LocalState val cityResources = getResourcesGeneratedByCityNotIncludingBuildings(city, resourceModifiers) + cityResources += getCityResourcesGeneratedFromUniqueBuildings(city, resourceModifiers) return cityResources } @@ -55,15 +56,18 @@ object CityResources { return cityResources } - private fun addCityResourcesGeneratedFromUniqueBuildings(city: City, cityResources: ResourceSupplyList, resourceModifer: Map) { + @Readonly + private fun getCityResourcesGeneratedFromUniqueBuildings(city: City, resourceModifer: Map): ResourceSupplyList { + val buildingResources = ResourceSupplyList() for (unique in city.getMatchingUniques(UniqueType.ProvidesResources, city.state, false)) { // E.G "Provides [1] [Iron]" val resource = city.getRuleset().tileResources[unique.params[1]] ?: continue - cityResources.add( + buildingResources.add( resource, unique.getSourceNameForUser(), (unique.params[0].toFloat() * resourceModifer[resource.name]!!).toInt() ) } + return buildingResources } /** Gets the number of resources available to this city diff --git a/core/src/com/unciv/logic/civilization/diplomacy/CityStateFunctions.kt b/core/src/com/unciv/logic/civilization/diplomacy/CityStateFunctions.kt index 0493788479..b8ca267e49 100644 --- a/core/src/com/unciv/logic/civilization/diplomacy/CityStateFunctions.kt +++ b/core/src/com/unciv/logic/civilization/diplomacy/CityStateFunctions.kt @@ -119,6 +119,7 @@ class CityStateFunctions(val civInfo: Civilization) { } } + @Readonly fun turnsForGreatPersonFromCityState(): Int = ((37 + Random.Default.nextInt(7)) * civInfo.gameInfo.speed.modifier).toInt() /** Gain a random great person from the city state */ @@ -143,6 +144,7 @@ class CityStateFunctions(val civInfo: Civilization) { val city = cities.city1 + @Readonly fun giftableUniqueUnit(): BaseUnit? { val uniqueUnit = civInfo.gameInfo.ruleset.units[civInfo.cityStateUniqueUnit] ?: return null @@ -152,6 +154,7 @@ class CityStateFunctions(val civInfo: Civilization) { return null return uniqueUnit } + @Readonly fun randomGiftableUnit() = city.cityConstructions.getConstructableUnits() .filter { !it.isCivilian() && it.isLandUnit && it.uniqueTo == null } @@ -160,6 +163,7 @@ class CityStateFunctions(val civInfo: Civilization) { it.value > 0 && receivingCiv.getResourceAmount(it.key) < it.value } } .toList().randomOrNull() + val militaryUnit = giftableUniqueUnit() // If the receiving civ has discovered the required tech and not the obsolete tech for our unique, always give them the unique ?: randomGiftableUnit() // Otherwise pick at random ?: return // That filter _can_ result in no candidates, if so, quit silently @@ -242,6 +246,7 @@ class CityStateFunctions(val civInfo: Civilization) { diplomacy.addInfluence(-20f) } + @Readonly fun otherCivCanPledgeProtection(otherCiv: Civilization): Boolean { // Must be a known city state if(!civInfo.isCityState || !otherCiv.isMajorCiv() || otherCiv.isDefeated() || !civInfo.knows(otherCiv)) @@ -262,6 +267,7 @@ class CityStateFunctions(val civInfo: Civilization) { return true } + @Readonly fun otherCivCanWithdrawProtection(otherCiv: Civilization): Boolean { // Must be a known city state if(!civInfo.isCityState || !otherCiv.isMajorCiv() || otherCiv.isDefeated() || !civInfo.knows(otherCiv)) @@ -334,6 +340,7 @@ class CityStateFunctions(val civInfo: Civilization) { } /** @return a Sequence of NotificationActions for use in addNotification, showing Capital on map if any, then opening diplomacy */ + @Readonly fun getNotificationActions() = sequence { // Notification click will first point to CS location, if any, then open diplomacy. // That's fine for the influence notifications and for afraid too. @@ -348,6 +355,7 @@ class CityStateFunctions(val civInfo: Civilization) { yield(DiplomacyAction(civInfo.civName)) } + @Readonly fun getDiplomaticMarriageCost(): Int { // https://github.com/Gedemon/Civ5-DLL/blob/master/CvGameCoreDLL_Expansion1/CvMinorCivAI.cpp, line 7812 var cost = (500 * civInfo.gameInfo.speed.goldCostModifier).toInt() @@ -362,6 +370,7 @@ class CityStateFunctions(val civInfo: Civilization) { return cost } + @Readonly fun canBeMarriedBy(otherCiv: Civilization): Boolean { return (!civInfo.isDefeated() && civInfo.isCityState @@ -488,6 +497,7 @@ class CityStateFunctions(val civInfo: Civilization) { return modifiers } + @Readonly fun goldGainedByTribute(): Int { // These values are close enough, linear increase throughout the game var gold = (10 * civInfo.gameInfo.speed.goldGiftModifier).toInt() * 5 // rounding down to nearest 5 @@ -565,6 +575,7 @@ class CityStateFunctions(val civInfo: Civilization) { return } + @Readonly fun getNumThreateningBarbarians(): Int { if (civInfo.gameInfo.gameParameters.noBarbarians) return 0 val barbarianCiv = civInfo.gameInfo.civilizations.firstOrNull { it.isBarbarian } @@ -785,13 +796,16 @@ class CityStateFunctions(val civInfo: Civilization) { } } - fun getCityStateResourcesForAlly() = ResourceSupplyList().apply { + @Readonly + fun getCityStateResourcesForAlly(): ResourceSupplyList { + val resourceSupplyList = ResourceSupplyList() // TODO: City-states don't give allies resources from civ-wide uniques! val civResourceModifiers = civInfo.getResourceModifiers() for (city in civInfo.cities) { // IGNORE the fact that they consume their own resources - #4769 - addPositiveByResource(city.getResourcesGeneratedByCity(civResourceModifiers), Constants.cityStates) + resourceSupplyList.addPositiveByResource(city.getResourcesGeneratedByCity(civResourceModifiers), Constants.cityStates) } + return resourceSupplyList } // TODO: Optimize, update whenever status changes, otherwise retain the same list diff --git a/core/src/com/unciv/logic/civilization/managers/QuestManager.kt b/core/src/com/unciv/logic/civilization/managers/QuestManager.kt index 62e32b162f..499dde664a 100644 --- a/core/src/com/unciv/logic/civilization/managers/QuestManager.kt +++ b/core/src/com/unciv/logic/civilization/managers/QuestManager.kt @@ -33,6 +33,7 @@ import com.unciv.models.translations.getPlaceholderParameters import com.unciv.models.translations.tr import com.unciv.ui.components.extensions.toPercent import com.unciv.utils.randomWeighted +import yairm210.purity.annotations.Pure import yairm210.purity.annotations.Readonly import kotlin.random.Random @@ -197,7 +198,7 @@ class QuestManager : IsPartOfGameInfoSerialization { } // Readability helper - No asSequence(): call frequency * data size is small - private fun getQuests(predicate: (Quest) -> Boolean) = ruleset.quests.values.filter(predicate) + @Readonly private fun getQuests(predicate: (Quest) -> Boolean) = ruleset.quests.values.filter(predicate) private fun tryStartNewGlobalQuest() { if (globalQuestCountdown != 0) @@ -206,7 +207,7 @@ class QuestManager : IsPartOfGameInfoSerialization { return val majorCivs = civ.getKnownCivs().filter { it.isMajorCiv() && !it.isAtWarWith(civ) } // A Sequence - fine because the count below can be different for each Quest - fun Quest.isAssignable() = majorCivs.count { civ -> isQuestValid(this, civ) } >= minimumCivs + @Readonly fun Quest.isAssignable() = majorCivs.count { civ -> isQuestValid(this, civ) } >= minimumCivs val assignableQuests = getQuests { it.isGlobal() && it.isAssignable() } @@ -574,7 +575,7 @@ class QuestManager : IsPartOfGameInfoSerialization { return "" val listOfLeadersAsTranslatedString = evaluation.winners.joinToString(separator = ", ") { it.assignee.tr() } - fun getScoreString(name: String, score: Int) = "[$name] with [$score] [$scoreDescriptor]".tr() + @Pure fun getScoreString(name: String, score: Int) = "[$name] with [$score] [$scoreDescriptor]".tr() val leadersString = getScoreString(listOfLeadersAsTranslatedString, evaluation.maxScore) if (inquiringAssignedQuest in evaluation.winners) diff --git a/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt b/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt index f1dbe25b15..f07feee360 100644 --- a/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt +++ b/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt @@ -194,7 +194,7 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction { override fun getStatBuyCost(city: City, stat: Stat): Int? = costFunctions.getStatBuyCost(city, stat) - fun getDisbandGold(civInfo: Civilization) = getBaseGoldCost(civInfo, null).toInt() / 20 + @Readonly fun getDisbandGold(civInfo: Civilization) = getBaseGoldCost(civInfo, null).toInt() / 20 override fun shouldBeDisplayed(cityConstructions: CityConstructions): Boolean { val rejectionReasons = getRejectionReasons(cityConstructions)