chore(purity): CityStateFunctions

This commit is contained in:
yairm210 2025-08-15 10:05:56 +03:00
parent ca44be05d5
commit 885e62e501
5 changed files with 30 additions and 11 deletions

View File

@ -209,7 +209,7 @@ class City : IsPartOfGameInfoSerialization, INamed {
@Readonly fun getRuleset() = civ.gameInfo.ruleset
fun getResourcesGeneratedByCity(civResourceModifiers: Map<String, Float>) = CityResources.getResourcesGeneratedByCity(this, civResourceModifiers)
@Readonly fun getResourcesGeneratedByCity(civResourceModifiers: Map<String, Float>) = CityResources.getResourcesGeneratedByCity(this, civResourceModifiers)
@Readonly fun getAvailableResourceAmount(resourceName: String) = CityResources.getAvailableResourceAmount(this, resourceName)
@Readonly fun isGrowing() = foodForNextTurn() > 0

View File

@ -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<String, Float>): 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<String, Float>) {
@Readonly
private fun getCityResourcesGeneratedFromUniqueBuildings(city: City, resourceModifer: Map<String, Float>): 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

View File

@ -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

View File

@ -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)

View File

@ -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)