Competition quests in progress display tied leaders (and your place if you're behind) (#11224)

* Minor Quest/QuestManager linting

* Cache Quest and QuestName references and use them

* Nicer randomWeighted and fix UniqueType.ResourceWeighting

* Integrate @soggerr's #10739 - show tied leaders and your position if you aren't leading

* One more lint
This commit is contained in:
SomeTroglodyte 2024-03-05 22:04:40 +01:00 committed by GitHub
parent 31931d3849
commit d5fda541ad
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 377 additions and 277 deletions

View File

@ -170,8 +170,10 @@ Ally = Allié
[questName] (+[influenceAmount] influence) = [questName] (+[influenceAmount] influence)
[remainingTurns] turns remaining = [remainingTurns] tours restants
Current leader is [civInfo] with [amount] [stat] generated. = [civInfo] est actuellement en tête et a généré [amount] [stat].
Current leader is [civInfo] with [amount] Technologies discovered. = [civInfo] est actuellement en tête avec [amount] Technologies découvertes.
Current leader(s): [leaders] = Actuellement en tête: [leaders]
Current leader(s): [leaders], you: [yourScore] = Ton résultat: [yourScore] est dépassé par: [leaders]
# In the two templates above, 'leaders' and 'yourScore' will use the following:
[civilizations] with [value] [valueType] = [civilizations] avec [value] [valueType]
Demands = Demandes
Please don't settle new cities near us. = Veuillez ne pas fonder de villes près de nous.

View File

@ -170,8 +170,10 @@ Ally =
[questName] (+[influenceAmount] influence) =
[remainingTurns] turns remaining =
Current leader is [civInfo] with [amount] [stat] generated. =
Current leader is [civInfo] with [amount] Technologies discovered. =
Current leader(s): [leaders] =
Current leader(s): [leaders], you: [yourScore] =
# In the two templates above, 'leaders' will be one or more of the following, and 'yourScore' one:
[civInfo] with [value] [valueType] =
Demands =
Please don't settle new cities near us. =

View File

@ -57,12 +57,11 @@ object LuxuryResourcePlacementLogic {
// Pick a luxury at random. Weight is reduced if the luxury has been picked before
val regionConditional = StateForConditionals(region = region)
val modifiedWeights = candidateLuxuries.map {
region.luxury = candidateLuxuries.randomWeighted {
val weightingUnique = it.getMatchingUniques(UniqueType.ResourceWeighting, regionConditional).firstOrNull()
val relativeWeight = if (weightingUnique == null) 1f else weightingUnique.params[0].toFloat()
relativeWeight / (1f + amountRegionsWithLuxury[it.name]!!)
}.shuffled()
region.luxury = candidateLuxuries.randomWeighted(modifiedWeights).name
}.name
amountRegionsWithLuxury[region.luxury!!] = amountRegionsWithLuxury[region.luxury]!! + 1
}
@ -150,15 +149,14 @@ object LuxuryResourcePlacementLogic {
}
if (candidateLuxuries.isEmpty()) return@repeat
val weights = candidateLuxuries.map {
val luxury = candidateLuxuries.randomWeighted {
val weightingUnique =
it.getMatchingUniques(UniqueType.LuxuryWeightingForCityStates).firstOrNull()
if (weightingUnique == null)
1f
else
weightingUnique.params[0].toFloat()
}
val luxury = candidateLuxuries.randomWeighted(weights).name
}.name
cityStateLuxuries.add(luxury)
amountRegionsWithLuxury[luxury] = 1
}

View File

@ -50,7 +50,7 @@ object MapRegionResources {
fallbackTiles.add(tile) // Taken but might be a viable fallback tile
} else {
// Add a resource to the tile
val resourceToPlace = possibleResourcesForTile.randomWeighted(possibleResourcesForTile.map { weightings[it] ?: 0f })
val resourceToPlace = possibleResourcesForTile.randomWeighted { weightings[it] ?: 0f }
tile.setTileResource(resourceToPlace, majorDeposit)
tileData.placeImpact(impactType, tile, baseImpact + Random.nextInt(randomImpact + 1))
amountPlaced++
@ -66,7 +66,7 @@ object MapRegionResources {
val bestTile = fallbackTiles.minByOrNull { tileData[it.position]!!.impacts[impactType]!! }!!
fallbackTiles.remove(bestTile)
val possibleResourcesForTile = resourceOptions.filter { it.generatesNaturallyOn(bestTile) }
val resourceToPlace = possibleResourcesForTile.randomWeighted(possibleResourcesForTile.map { weightings[it] ?: 0f })
val resourceToPlace = possibleResourcesForTile.randomWeighted { weightings[it] ?: 0f }
bestTile.setTileResource(resourceToPlace, majorDeposit)
tileData.placeImpact(impactType, bestTile, baseImpact + Random.nextInt(randomImpact + 1))
amountPlaced++

View File

@ -1,7 +1,7 @@
package com.unciv.models.ruleset
import com.unciv.logic.civilization.Civilization
import com.unciv.models.stats.INamed
import com.unciv.logic.civilization.Civilization // for Kdoc
enum class QuestName(val value: String) {
Route("Route"),
@ -22,6 +22,10 @@ enum class QuestName(val value: String) {
DenounceCiv("Denounce Civilization"),
SpreadReligion("Spread Religion"),
None("")
;
companion object {
fun find(value: String) = values().firstOrNull { it.value == value } ?: None
}
}
enum class QuestType {
@ -33,12 +37,14 @@ enum class QuestType {
// Notes: This is **not** `IsPartOfGameInfoSerialization`, only Ruleset.
// Saves contain [QuestManager]s instead, which contain lists of [AssignedQuest] instances.
// These are matched to this Quest **by name**.
// Note [name] must match one of the [QuestName] _values_ above for the Quest to have any functionality.
class Quest : INamed {
/** Unique identifier name of the quest, it is also shown */
/** Unique identifier name of the quest, it is also shown.
* Must match a [QuestName.value] for the Quest to have any functionality. */
override var name: String = ""
val questNameInstance by lazy { QuestName.find(name) } // lazy only ensures evaluation happens after deserialization, all will be 'triggered'
/** Description of the quest shown to players */
var description: String = ""

View File

@ -23,6 +23,13 @@ fun <T> List<T>.randomWeighted(weights: List<Float>, random: Random = Random): T
return this.last()
}
/** Get one random element of a given List.
*
* The probability for each element is proportional to the result of [getWeight] (evaluated only once).
*/
fun <T> List<T>.randomWeighted(random: Random = Random, getWeight: (T) -> Float): T =
randomWeighted(map(getWeight), random)
/** Gets a clone of an [ArrayList] with an additional item
*
* Solves concurrent modification problems - everyone who had a reference to the previous arrayList can keep using it because it hasn't changed

View File

@ -81,7 +81,7 @@ class CityStateDiplomacyTable(private val diplomacyScreen: DiplomacyScreen) {
val diplomaticMarriageButton = getDiplomaticMarriageButton(otherCiv)
if (diplomaticMarriageButton != null) diplomacyTable.add(diplomaticMarriageButton).row()
for (assignedQuest in otherCiv.questManager.assignedQuests.filter { it.assignee == viewingCiv.civName }) {
for (assignedQuest in otherCiv.questManager.getAssignedQuestsFor(viewingCiv.civName)) {
diplomacyTable.addSeparator()
diplomacyTable.add(getQuestTable(assignedQuest)).row()
}
@ -464,8 +464,8 @@ class CityStateDiplomacyTable(private val diplomacyScreen: DiplomacyScreen) {
if (quest.duration > 0)
questTable.add("[${remainingTurns}] turns remaining".toLabel()).row()
if (quest.isGlobal()) {
val leaderString = viewingCiv.gameInfo.getCivilization(assignedQuest.assigner).questManager.getLeaderStringForQuest(assignedQuest.questName)
if (leaderString != "")
val leaderString = viewingCiv.gameInfo.getCivilization(assignedQuest.assigner).questManager.getScoreStringForGlobalQuest(assignedQuest)
if (leaderString.isNotEmpty())
questTable.add(leaderString.toLabel()).row()
}

View File

@ -162,8 +162,7 @@ class WonderInfo {
private fun knownFromQuest(viewingPlayer: Civilization, name: String): Boolean {
// No, *your* civInfo's QuestManager has no idea about your quests
for (civ in gameInfo.civilizations) {
for (quest in civ.questManager.assignedQuests) {
if (quest.assignee != viewingPlayer.civName) continue
for (quest in civ.questManager.getAssignedQuestsFor(viewingPlayer.civName)) {
if (quest.questName == QuestName.FindNaturalWonder.value && quest.data1 == name)
return true
}