mirror of
https://github.com/yairm210/Unciv.git
synced 2025-09-23 03:23:17 -04:00
chore(purity): QuestManager
This commit is contained in:
parent
10f0a79ac9
commit
0c4b9cbb85
@ -62,6 +62,8 @@ allprojects {
|
||||
"java.util.BitSet.get", // moved
|
||||
"kotlin.collections.getValue", // moved
|
||||
"kotlin.collections.randomOrNull",
|
||||
"kotlin.collections.Collection.isEmpty",
|
||||
"kotlin.collections.subtract"
|
||||
)
|
||||
wellKnownPureClasses = setOf<String>(
|
||||
)
|
||||
|
@ -266,6 +266,7 @@ class CityConstructions : IsPartOfGameInfoSerialization {
|
||||
else 0
|
||||
}
|
||||
|
||||
@Readonly
|
||||
fun getRemainingWork(constructionName: String, useStoredProduction: Boolean = true): Int {
|
||||
val constr = getConstruction(constructionName)
|
||||
return when {
|
||||
|
@ -380,11 +380,11 @@ class Civilization : IsPartOfGameInfoSerialization {
|
||||
var cityStatePersonality: CityStatePersonality = CityStatePersonality.Neutral
|
||||
var cityStateResource: String? = null
|
||||
var cityStateUniqueUnit: String? = null // Unique unit for militaristic city state. Might still be null if there are no appropriate units
|
||||
|
||||
fun hasMetCivTerritory(otherCiv: Civilization): Boolean =
|
||||
|
||||
@Readonly fun hasMetCivTerritory(otherCiv: Civilization): Boolean =
|
||||
otherCiv.getCivTerritory().any { gameInfo.tileMap[it].isExplored(this) }
|
||||
@Readonly fun getCompletedPolicyBranchesCount(): Int = policies.adoptedPolicies.count { Policy.isBranchCompleteByName(it) }
|
||||
private fun getCivTerritory() = cities.asSequence().flatMap { it.tiles.asSequence() }
|
||||
@Readonly private fun getCivTerritory() = cities.asSequence().flatMap { it.tiles.asSequence() }
|
||||
|
||||
@Readonly
|
||||
fun getPreferredVictoryTypes(): List<String> {
|
||||
@ -463,7 +463,7 @@ class Civilization : IsPartOfGameInfoSerialization {
|
||||
return newResourceSupplyList
|
||||
}
|
||||
|
||||
fun isCapitalConnectedToCity(city: City): Boolean = cache.citiesConnectedToCapitalToMediums.keys.contains(city)
|
||||
@Readonly fun isCapitalConnectedToCity(city: City): Boolean = cache.citiesConnectedToCapitalToMediums.keys.contains(city)
|
||||
|
||||
|
||||
/**
|
||||
|
@ -187,6 +187,7 @@ class CityStateFunctions(val civInfo: Civilization) {
|
||||
)
|
||||
}
|
||||
|
||||
@Readonly
|
||||
fun influenceGainedByGift(donorCiv: Civilization, giftAmount: Int): Int {
|
||||
// https://github.com/Gedemon/Civ5-DLL/blob/aa29e80751f541ae04858b6d2a2c7dcca454201e/CvGameCoreDLL_Expansion1/CvMinorCivAI.cpp
|
||||
// line 8681 and below
|
||||
|
@ -7,6 +7,7 @@ import com.unciv.logic.civilization.NotificationCategory
|
||||
import com.unciv.models.Counter
|
||||
import com.unciv.models.ruleset.unique.UniqueType
|
||||
import com.unciv.ui.components.MayaCalendar
|
||||
import yairm210.purity.annotations.Readonly
|
||||
|
||||
|
||||
// todo: Great Admiral?
|
||||
@ -46,11 +47,12 @@ class GreatPersonManager : IsPartOfGameInfoSerialization {
|
||||
return toReturn
|
||||
}
|
||||
|
||||
@Readonly
|
||||
private fun getPoolKey(greatPerson: String) = civInfo.getEquivalentUnit(greatPerson)
|
||||
.getMatchingUniques(UniqueType.GPPointPool)
|
||||
// An empty string is used to indicate the Unique wasn't found
|
||||
.firstOrNull()?.params?.get(0) ?: ""
|
||||
|
||||
|
||||
fun getPointsRequiredForGreatPerson(greatPerson: String): Int {
|
||||
val key = getPoolKey(greatPerson)
|
||||
if (pointsForNextGreatPersonCounter[key] == 0) {
|
||||
@ -102,12 +104,14 @@ class GreatPersonManager : IsPartOfGameInfoSerialization {
|
||||
}
|
||||
|
||||
/** Get Great People specific to this manager's Civilization, already filtered by `isHiddenBySettings` */
|
||||
@Readonly
|
||||
fun getGreatPeople() = civInfo.gameInfo.ruleset.units.values.asSequence()
|
||||
.filter { it.isGreatPerson }
|
||||
.map { civInfo.getEquivalentUnit(it.name) }
|
||||
.filterNot { it.isUnavailableBySettings(civInfo.gameInfo) }
|
||||
.toHashSet()
|
||||
|
||||
@Readonly
|
||||
fun getGreatPersonPointsForNextTurn(): Counter<String> {
|
||||
val greatPersonPoints = Counter<String>()
|
||||
for (city in civInfo.cities) greatPersonPoints.add(city.getGreatPersonPoints())
|
||||
|
@ -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.Readonly
|
||||
import kotlin.random.Random
|
||||
|
||||
class QuestManager : IsPartOfGameInfoSerialization {
|
||||
@ -80,21 +81,24 @@ class QuestManager : IsPartOfGameInfoSerialization {
|
||||
private var unitsKilledFromCiv: HashMap<String, HashMap<String, Int>> = HashMap()
|
||||
|
||||
/** Returns true if [civ] have active quests for [challenger] */
|
||||
fun haveQuestsFor(challenger: Civilization): Boolean = getAssignedQuestsFor(challenger.civName).any()
|
||||
@Readonly fun haveQuestsFor(challenger: Civilization): Boolean = getAssignedQuestsFor(challenger.civName).any()
|
||||
|
||||
/** Access all assigned Quests for [civName] */
|
||||
@Readonly
|
||||
fun getAssignedQuestsFor(civName: String) =
|
||||
assignedQuests.asSequence().filter { it.assignee == civName }
|
||||
|
||||
/** Access all assigned Quests of "type" [questName] */
|
||||
// Note if we decide to cache an index of these (such as `assignedQuests.groupBy { it.questNameInstance }`), this accessor would simplify the transition
|
||||
@Readonly
|
||||
private fun getAssignedQuestsOfName(questName: QuestName) =
|
||||
assignedQuests.asSequence().filter { it.questNameInstance == questName }
|
||||
|
||||
/** Returns true if [civ] has asked anyone to conquer [target] */
|
||||
fun wantsDead(target: String): Boolean = getAssignedQuestsOfName(QuestName.ConquerCityState).any { it.data1 == target }
|
||||
@Readonly fun wantsDead(target: String): Boolean = getAssignedQuestsOfName(QuestName.ConquerCityState).any { it.data1 == target }
|
||||
|
||||
/** Returns the influence multiplier for [donor] from a Investment quest that [civ] might have (assumes only one) */
|
||||
@Readonly
|
||||
fun getInvestmentMultiplier(donor: String): Float {
|
||||
val investmentQuest = getAssignedQuestsOfName(QuestName.Invest).firstOrNull { it.assignee == donor }
|
||||
?: return 1f
|
||||
@ -367,12 +371,14 @@ class QuestManager : IsPartOfGameInfoSerialization {
|
||||
}
|
||||
|
||||
/** Returns true if [civ] can assign a quest to [challenger] */
|
||||
@Readonly
|
||||
private fun canAssignAQuestTo(challenger: Civilization): Boolean {
|
||||
return !challenger.isDefeated() && challenger.isMajorCiv() &&
|
||||
civ.knows(challenger) && !civ.isAtWarWith(challenger)
|
||||
}
|
||||
|
||||
/** Returns true if the [quest] can be assigned to [challenger] */
|
||||
@Readonly
|
||||
private fun isQuestValid(quest: Quest, challenger: Civilization): Boolean {
|
||||
if (!canAssignAQuestTo(challenger))
|
||||
return false
|
||||
@ -403,6 +409,7 @@ class QuestManager : IsPartOfGameInfoSerialization {
|
||||
}
|
||||
}
|
||||
|
||||
@Readonly
|
||||
private fun isRouteQuestValid(challenger: Civilization): Boolean {
|
||||
if (challenger.cities.isEmpty()) return false
|
||||
if (challenger.isCapitalConnectedToCity(civ.getCapital()!!)) return false
|
||||
@ -414,6 +421,7 @@ class QuestManager : IsPartOfGameInfoSerialization {
|
||||
}
|
||||
}
|
||||
|
||||
@Readonly
|
||||
private fun isDenounceCivQuestValid(challenger: Civilization, mostRecentBully: String?): Boolean {
|
||||
return mostRecentBully != null
|
||||
&& challenger.knows(mostRecentBully)
|
||||
@ -424,6 +432,7 @@ class QuestManager : IsPartOfGameInfoSerialization {
|
||||
}
|
||||
|
||||
/** Returns true if the [assignedQuest] is successfully completed */
|
||||
@Readonly
|
||||
private fun isComplete(assignedQuest: AssignedQuest): Boolean {
|
||||
val assignee = civ.gameInfo.getCivilization(assignedQuest.assignee)
|
||||
return when (assignedQuest.questNameInstance) {
|
||||
@ -441,6 +450,7 @@ class QuestManager : IsPartOfGameInfoSerialization {
|
||||
}
|
||||
|
||||
/** Returns true if the [assignedQuest] request cannot be fulfilled anymore */
|
||||
@Readonly
|
||||
private fun isObsolete(assignedQuest: AssignedQuest): Boolean {
|
||||
val assignee = civ.gameInfo.getCivilization(assignedQuest.assignee)
|
||||
return when (assignedQuest.questNameInstance) {
|
||||
@ -489,6 +499,7 @@ class QuestManager : IsPartOfGameInfoSerialization {
|
||||
}
|
||||
|
||||
/** Returns the score for the [assignedQuest] */
|
||||
@Readonly
|
||||
private fun getScoreForQuest(assignedQuest: AssignedQuest): Int {
|
||||
val assignee = civ.gameInfo.getCivilization(assignedQuest.assignee)
|
||||
|
||||
@ -545,6 +556,7 @@ class QuestManager : IsPartOfGameInfoSerialization {
|
||||
* Tied leaders are separated by ", " - translators cannot influence this, sorry.
|
||||
* @param inquiringAssignedQuest Determines ["type"][AssignedQuest.questNameInstance] to find all competitors in [assignedQuests] and [viewing civ][AssignedQuest.assignee].
|
||||
*/
|
||||
@Readonly
|
||||
fun getScoreStringForGlobalQuest(inquiringAssignedQuest: AssignedQuest): String {
|
||||
require(inquiringAssignedQuest.assigner == civ.civName)
|
||||
require(inquiringAssignedQuest.isGlobal())
|
||||
@ -654,7 +666,7 @@ class QuestManager : IsPartOfGameInfoSerialization {
|
||||
|
||||
/** Gets notified when [killed]'s military unit was killed by [killer], for war with major pseudo-quest */
|
||||
fun militaryUnitKilledBy(killer: Civilization, killed: Civilization) {
|
||||
if (!warWithMajorActive(killed)) return
|
||||
if (!isWarWithMajorActive(killed)) return
|
||||
|
||||
// No credit if we're at war or haven't met
|
||||
if (!civ.knows(killer) || civ.isAtWarWith(killer)) return
|
||||
@ -705,14 +717,11 @@ class QuestManager : IsPartOfGameInfoSerialization {
|
||||
unitsKilledFromCiv.remove(attacker.civName)
|
||||
}
|
||||
|
||||
fun warWithMajorActive(target: Civilization): Boolean {
|
||||
return unitsToKillForCiv.containsKey(target.civName)
|
||||
}
|
||||
@Readonly fun isWarWithMajorActive(target: Civilization): Boolean = unitsToKillForCiv.containsKey(target.civName)
|
||||
|
||||
fun unitsToKill(target: Civilization): Int {
|
||||
return unitsToKillForCiv[target.civName] ?: 0
|
||||
}
|
||||
@Readonly fun unitsToKill(target: Civilization): Int = unitsToKillForCiv[target.civName] ?: 0
|
||||
|
||||
@Readonly
|
||||
fun unitsKilledSoFar(target: Civilization, viewingCiv: Civilization): Int {
|
||||
val killMap = unitsKilledFromCiv[target.civName] ?: return 0
|
||||
return killMap[viewingCiv.civName] ?: 0
|
||||
@ -734,6 +743,7 @@ class QuestManager : IsPartOfGameInfoSerialization {
|
||||
/**
|
||||
* Returns the weight of the [questName], depends on city state trait and personality
|
||||
*/
|
||||
@Readonly
|
||||
private fun getQuestWeight(questName: String): Float {
|
||||
var weight = 1f
|
||||
val quest = ruleset.quests[questName] ?: return 0f
|
||||
@ -751,6 +761,7 @@ class QuestManager : IsPartOfGameInfoSerialization {
|
||||
* Returns a random [Tile] containing a Barbarian encampment within 8 tiles of [civ]
|
||||
* to be destroyed
|
||||
*/
|
||||
@Readonly
|
||||
private fun getBarbarianEncampmentForQuest(): Tile? {
|
||||
val encampments = civ.getCapital()!!.getCenterTile().getTilesInDistance(8)
|
||||
.filter { it.improvement == Constants.barbarianEncampment }.toList()
|
||||
@ -764,6 +775,7 @@ class QuestManager : IsPartOfGameInfoSerialization {
|
||||
* by the [civ] and the [challenger], and must be viewable by the [challenger];
|
||||
* if none exists, it returns null.
|
||||
*/
|
||||
@Readonly
|
||||
private fun getResourceForQuest(challenger: Civilization): TileResource? {
|
||||
val ownedByCityStateResources = civ.detailedCivResources.map { it.resource }
|
||||
val ownedByMajorResources = challenger.detailedCivResources.map { it.resource }
|
||||
@ -781,8 +793,9 @@ class QuestManager : IsPartOfGameInfoSerialization {
|
||||
return notOwnedResources.randomOrNull()
|
||||
}
|
||||
|
||||
@Readonly
|
||||
private fun getWonderToBuildForQuest(challenger: Civilization): Building? {
|
||||
fun isMoreThanAQuarterDone(city: City, buildingName: String) =
|
||||
@Readonly fun isMoreThanAQuarterDone(city: City, buildingName: String) =
|
||||
city.cityConstructions.getWorkDone(buildingName) * 3 > city.cityConstructions.getRemainingWork(buildingName)
|
||||
val wonders = ruleset.buildings.values
|
||||
.filter { building ->
|
||||
@ -803,6 +816,7 @@ class QuestManager : IsPartOfGameInfoSerialization {
|
||||
/**
|
||||
* Returns a random Natural Wonder not yet discovered by [challenger].
|
||||
*/
|
||||
@Readonly
|
||||
private fun getNaturalWonderToFindForQuest(challenger: Civilization): String? {
|
||||
val naturalWondersToFind = civ.gameInfo.tileMap.naturalWonders.subtract(challenger.naturalWonders)
|
||||
|
||||
@ -812,6 +826,7 @@ class QuestManager : IsPartOfGameInfoSerialization {
|
||||
/**
|
||||
* Returns a Great Person [BaseUnit] that is not owned by both the [challenger] and the [civ]
|
||||
*/
|
||||
@Readonly
|
||||
private fun getGreatPersonForQuest(challenger: Civilization): BaseUnit? {
|
||||
val ruleset = ruleset // omit if the accessor should be converted to a transient field
|
||||
|
||||
@ -835,6 +850,7 @@ class QuestManager : IsPartOfGameInfoSerialization {
|
||||
* Returns a random [Civilization] (major) that [challenger] has met, but whose territory he
|
||||
* cannot see; if none exists, it returns null.
|
||||
*/
|
||||
@Readonly
|
||||
private fun getCivilizationToFindForQuest(challenger: Civilization): Civilization? {
|
||||
val civilizationsToFind = challenger.getKnownCivs()
|
||||
.filter { it.isAlive() && it.isMajorCiv() && !challenger.hasMetCivTerritory(it) }
|
||||
@ -846,6 +862,7 @@ class QuestManager : IsPartOfGameInfoSerialization {
|
||||
/**
|
||||
* Returns a city-state [Civilization] that [civ] wants to target for hostile quests
|
||||
*/
|
||||
@Readonly
|
||||
private fun getCityStateTarget(challenger: Civilization): Civilization? {
|
||||
val closestProximity = civ.gameInfo.getAliveCityStates()
|
||||
.mapNotNull { civ.proximity[it.civName] }.filter { it != Proximity.None }.minByOrNull { it.ordinal }
|
||||
@ -861,6 +878,7 @@ class QuestManager : IsPartOfGameInfoSerialization {
|
||||
|
||||
/** Returns a [Civilization] of the civ that most recently bullied [civ].
|
||||
* Note: forgets after 20 turns has passed! */
|
||||
@Readonly
|
||||
private fun getMostRecentBully(): String? {
|
||||
val bullies = civ.diplomacy.values.filter { it.hasFlag(DiplomacyFlags.Bullied) }
|
||||
return bullies.maxByOrNull { it.getFlag(DiplomacyFlags.Bullied) }?.otherCivName
|
||||
@ -892,17 +910,17 @@ class AssignedQuest(
|
||||
questObject = quest ?: gameInfo.ruleset.quests[questName]!!
|
||||
}
|
||||
|
||||
fun isIndividual(): Boolean = !isGlobal()
|
||||
fun isGlobal(): Boolean = questObject.isGlobal()
|
||||
@Readonly fun isIndividual(): Boolean = !isGlobal()
|
||||
@Readonly fun isGlobal(): Boolean = questObject.isGlobal()
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
fun doesExpire(): Boolean = questObject.duration > 0
|
||||
fun isExpired(): Boolean = doesExpire() && getRemainingTurns() == 0
|
||||
@Readonly fun doesExpire(): Boolean = questObject.duration > 0
|
||||
@Readonly fun isExpired(): Boolean = doesExpire() && getRemainingTurns() == 0
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
fun getDuration(): Int = (gameInfo.speed.modifier * questObject.duration).toInt()
|
||||
fun getRemainingTurns(): Int = (assignedOnTurn + getDuration() - gameInfo.turns).coerceAtLeast(0)
|
||||
fun getInfluence() = questObject.influence
|
||||
@Readonly fun getDuration(): Int = (gameInfo.speed.modifier * questObject.duration).toInt()
|
||||
@Readonly fun getRemainingTurns(): Int = (assignedOnTurn + getDuration() - gameInfo.turns).coerceAtLeast(0)
|
||||
@Readonly fun getInfluence() = questObject.influence
|
||||
|
||||
fun getDescription(): String = questObject.description.fillPlaceholders(data1)
|
||||
@Readonly fun getDescription(): String = questObject.description.fillPlaceholders(data1)
|
||||
|
||||
fun onClickAction() {
|
||||
when (questNameInstance) {
|
||||
|
@ -2,6 +2,7 @@ package com.unciv.models.ruleset
|
||||
|
||||
import com.unciv.logic.civilization.Civilization
|
||||
import com.unciv.models.stats.INamed
|
||||
import yairm210.purity.annotations.Readonly
|
||||
|
||||
enum class QuestName(val value: String) {
|
||||
Route("Route"),
|
||||
@ -24,7 +25,7 @@ enum class QuestName(val value: String) {
|
||||
None("")
|
||||
;
|
||||
companion object {
|
||||
fun find(value: String) = values().firstOrNull { it.value == value } ?: None
|
||||
fun find(value: String) = entries.firstOrNull { it.value == value } ?: None
|
||||
}
|
||||
}
|
||||
|
||||
@ -67,6 +68,6 @@ class Quest : INamed {
|
||||
var weightForCityStateType = HashMap<String, Float>()
|
||||
|
||||
/** Checks if `this` is a Global quest */
|
||||
fun isGlobal(): Boolean = type == QuestType.Global
|
||||
fun isIndividual(): Boolean = !isGlobal()
|
||||
@Readonly fun isGlobal(): Boolean = type == QuestType.Global
|
||||
@Readonly fun isIndividual(): Boolean = !isGlobal()
|
||||
}
|
||||
|
@ -371,6 +371,7 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction {
|
||||
|
||||
// This returns the name of the unit this tech upgrades this unit to,
|
||||
// or null if there is no automatic upgrade at that tech.
|
||||
@Readonly
|
||||
fun automaticallyUpgradedInProductionToUnitByTech(techName: String): String? {
|
||||
for (obsoleteTech: String in techsAtWhichAutoUpgradeInProduction())
|
||||
if (obsoleteTech == techName)
|
||||
@ -405,6 +406,7 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction {
|
||||
}
|
||||
}
|
||||
|
||||
@Readonly
|
||||
fun getReplacedUnit(ruleset: Ruleset): BaseUnit {
|
||||
return if (replaces == null) this
|
||||
else ruleset.units[replaces!!]!!
|
||||
|
@ -11,6 +11,7 @@ import com.unciv.models.ruleset.unique.UniqueParameterType
|
||||
import com.unciv.models.ruleset.unique.UniqueType
|
||||
import com.unciv.models.translations.fillPlaceholders
|
||||
import yairm210.purity.annotations.Pure
|
||||
import yairm210.purity.annotations.Readonly
|
||||
|
||||
/**
|
||||
* All public methods dealing with how Mod authors can suppress RulesetValidator output.
|
||||
@ -65,6 +66,7 @@ object Suppression {
|
||||
else -> true
|
||||
}
|
||||
|
||||
@Pure
|
||||
private fun matchesFilter(error: RulesetError, filter: String): Boolean {
|
||||
if (error.text == filter) return true
|
||||
if (!filter.endsWith('*') || !filter.startsWith('*')) return false
|
||||
@ -72,6 +74,7 @@ object Suppression {
|
||||
}
|
||||
|
||||
/** Determine if [error] matches any suppression Unique in [ModOptions] or the [sourceObject], or any suppression modifier in [sourceUnique] */
|
||||
@Readonly
|
||||
internal fun isErrorSuppressed(
|
||||
globalSuppressionFilters: Collection<String>,
|
||||
sourceObject: IHasUniques?,
|
||||
@ -81,6 +84,7 @@ object Suppression {
|
||||
if (error.errorSeverityToReport >= RulesetErrorSeverity.Error) return false
|
||||
if (sourceObject == null && globalSuppressionFilters.isEmpty()) return false
|
||||
|
||||
@Readonly
|
||||
fun getWildcardFilter(unique: Unique) = unique.params[0].let {
|
||||
if (it.startsWith('*')) it else "*$it*"
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ class CityStateDiplomacyTable(private val diplomacyScreen: DiplomacyScreen) {
|
||||
diplomacyTable.add(getQuestTable(assignedQuest)).row()
|
||||
}
|
||||
|
||||
for (target in otherCiv.getKnownCivs().filter { otherCiv.questManager.warWithMajorActive(it) && viewingCiv != it }) {
|
||||
for (target in otherCiv.getKnownCivs().filter { otherCiv.questManager.isWarWithMajorActive(it) && viewingCiv != it }) {
|
||||
diplomacyTable.addSeparator()
|
||||
diplomacyTable.add(getWarWithMajorTable(target, otherCiv)).row()
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user