Added score and time victory (#5842)

* Added a score calculation

* Added the score in multiple places of the UI

* Added time victory and a max turns

* Made time victory functional

* Added translation files
This commit is contained in:
Xander Lenstra
2021-12-24 13:31:36 +01:00
committed by GitHub
parent 730a3d8482
commit 759366c912
12 changed files with 102 additions and 22 deletions

View File

@ -319,6 +319,7 @@ Game Options =
Civilizations =
Map Type =
Map file =
Max Turns =
Could not load map! =
Invalid map: Area ([area]) does not match saved dimensions ([dimensions]). =
The dimensions have now been fixed for you. =
@ -344,6 +345,7 @@ Scientific =
Domination =
Cultural =
Diplomatic =
Time =
Map Shape =
Hexagonal =
@ -978,6 +980,7 @@ You have achieved victory through mastery of Science! You have conquered the mys
Your civilization stands above all others! The exploits of your people shall be remembered until the end of civilization itself! =
You have been defeated. Your civilization has been overwhelmed by its many foes. But your people do not despair, for they know that one day you shall return - and lead them forward to victory! =
You have triumphed over your foes through the art of diplomacy! Your cunning and wisdom have earned you great friends - and divided and sown confusion among your enemies! Forever will you be remembered as the leader who brought peace to this weary world! =
The world has been convulsed by war. Many great and powerful civilizations have fallen, but you have survived - and emerged victorious! The world will long remember your glorious triumph! =
One more turn...! =
Built Apollo Program =
Destroy [civName] =

View File

@ -13,10 +13,7 @@ import com.unciv.logic.map.TileMap
import com.unciv.models.Religion
import com.unciv.models.metadata.GameParameters
import com.unciv.models.metadata.GameSpeed
import com.unciv.models.ruleset.Difficulty
import com.unciv.models.ruleset.ModOptionsConstants
import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.RulesetCache
import com.unciv.models.ruleset.*
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.ui.audio.MusicMood
import com.unciv.ui.audio.MusicTrackChooserFlags
@ -209,6 +206,7 @@ class GameInfo {
currentPlayerIndex = (currentPlayerIndex + 1) % civilizations.size
if (currentPlayerIndex == 0) {
turns++
checkForTimeVictory()
}
thisPlayer = civilizations[currentPlayerIndex]
thisPlayer.startTurn()
@ -286,6 +284,17 @@ class GameInfo {
}
)
}
private fun checkForTimeVictory() {
if (turns != gameParameters.maxTurns || !gameParameters.victoryTypes.contains(VictoryType.Time)) return
val winningCiv = civilizations
.filter { it.isMajorCiv() && !it.isSpectator() && !it.isBarbarian() }
.maxByOrNull { it.calculateScore() }
?: return // Are there no civs left?
winningCiv.victoryManager.hasWonTimeVictory = true
}
private fun addEnemyUnitNotification(thisPlayer: CivilizationInfo, tiles: List<TileInfo>, inOrNear: String) {
// don't flood the player with similar messages. instead cycle through units by clicking the message multiple times.

View File

@ -613,6 +613,7 @@ class CivilizationInfo {
fun getStatForRanking(category: RankingType): Int {
return when (category) {
RankingType.Score -> calculateScore()
RankingType.Population -> cities.sumOf { it.population.population }
RankingType.Crop_Yield -> statsForNextTurn.food.roundToInt()
RankingType.Production -> statsForNextTurn.production.roundToInt()
@ -667,6 +668,27 @@ class CivilizationInfo {
}
fun isLongCountDisplay() = hasLongCountDisplayUnique && isLongCountActive()
fun calculateScore(): Int {
// 1276 is the number of tiles in a medium sized map. The original uses 4160 for this,
// but they have bigger maps
var mapSizeModifier = 1276.0 / gameInfo.tileMap.mapParameters.numberOfTiles()
if (mapSizeModifier > 1)
mapSizeModifier = (mapSizeModifier - 1) / 3 + 1
var score = 0.0
score += cities.count() * 10 * mapSizeModifier
score += cities.sumOf { it.population.population } * 3 * mapSizeModifier
score += cities.sumOf { city -> city.getTiles().filter { !it.isWater}.count() } * 1 * mapSizeModifier
score += 40 * cities
.sumOf { city -> city.cityConstructions.builtBuildings
.filter { gameInfo.ruleSet.buildings[it]!!.isWonder }.count()
}
score += tech.getNumberOfTechsResearched() * 4
score += tech.repeatingTechsResearched * 10
return score.toInt()
}
//endregion
//region state-changing functions

View File

@ -43,6 +43,8 @@ class TechManager {
var roadsConnectAcrossRivers = false
var freeTechs = 0
// For calculating score
var repeatingTechsResearched = 0
/** For calculating Great Scientist yields - see https://civilization.fandom.com/wiki/Great_Scientist_(Civ5) */
var scienceOfLast8Turns = IntArray(8) { 0 }
@ -63,6 +65,7 @@ class TechManager {
val toReturn = TechManager()
toReturn.techsResearched.addAll(techsResearched)
toReturn.freeTechs = freeTechs
toReturn.repeatingTechsResearched = repeatingTechsResearched
toReturn.techsInProgress.putAll(techsInProgress)
toReturn.techsToResearch.addAll(techsToResearch)
toReturn.scienceOfLast8Turns = scienceOfLast8Turns.clone()
@ -239,6 +242,8 @@ class TechManager {
val newTech = getRuleset().technologies[techName]!!
if (!newTech.isContinuallyResearchable())
techsToResearch.remove(techName)
else
repeatingTechsResearched++
researchedTechnologies = researchedTechnologies.withItem(newTech)
addTechToTransients(newTech)
for (unique in newTech.uniqueObjects) {

View File

@ -10,7 +10,7 @@ class VictoryManager {
var requiredSpaceshipParts = Counter<String>()
var currentsSpaceshipParts = Counter<String>()
var hasWonDiplomaticVictory = false
var hasWonTimeVictory = false
init {
requiredSpaceshipParts.add("SS Booster", 3)
@ -66,22 +66,25 @@ class VictoryManager {
private fun hasVictoryType(victoryType: VictoryType) = civInfo.gameInfo.gameParameters.victoryTypes.contains(victoryType)
fun hasWonScientificVictory() = hasVictoryType(VictoryType.Scientific) && spaceshipPartsRemaining() == 0
fun hasWonScientificVictory() = hasVictoryType(VictoryType.Scientific)
&& spaceshipPartsRemaining() == 0
fun hasWonCulturalVictory() = hasVictoryType(VictoryType.Cultural)
&& civInfo.hasUnique(UniqueType.TriggersCulturalVictory)
fun hasWonDominationVictory(): Boolean {
return hasVictoryType(VictoryType.Domination)
&& civInfo.gameInfo.civilizations.all { it == civInfo || it.isDefeated() || !it.isMajorCiv() }
}
fun hasWonDominationVictory()= hasVictoryType(VictoryType.Domination)
&& civInfo.gameInfo.civilizations.all { it == civInfo || it.isDefeated() || !it.isMajorCiv() }
fun hasWonDiplomaticVictory() = hasVictoryType(VictoryType.Diplomatic)
&& civInfo.shouldCheckForDiplomaticVictory()
&& hasEnoughVotesForDiplomaticVictory()
fun hasWonTimeVictory() = hasVictoryType(VictoryType.Time)
&& hasWonTimeVictory
fun hasWonVictoryType(): VictoryType? {
if (!civInfo.isMajorCiv()) return null
if (hasWonTimeVictory()) return VictoryType.Time
if (hasWonDominationVictory()) return VictoryType.Domination
if (hasWonScientificVictory()) return VictoryType.Scientific
if (hasWonCulturalVictory()) return VictoryType.Cultural

View File

@ -229,4 +229,11 @@ class MapParameters {
yield("$elevationExponent/$temperatureExtremeness/$resourceRichness/$vegetationRichness/")
yield("$rareFeaturesRichness/$maxCoastExtension/$tilesPerBiomeArea/$waterThreshold")
}.joinToString("", postfix = ")")
fun numberOfTiles() =
if (shape == MapShape.hexagonal) {
1 + 3 * mapSize.radius * (mapSize.radius - 1)
} else {
mapSize.width * mapSize.height
}
}

View File

@ -27,13 +27,15 @@ class GameParameters { // Default values are the default new game
var nuclearWeaponsEnabled = true
var religionEnabled = false
// By default, all victory types except Diplomacy as it is quite new
// By default, all victory types except Diplomacy and time as they are quite new
var victoryTypes: ArrayList<VictoryType> = arrayListOf(VictoryType.Cultural, VictoryType.Domination, VictoryType.Scientific)
var startingEra = "Ancient era"
var isOnlineMultiplayer = false
var baseRuleset: String = BaseRuleset.Civ_V_GnK.fullName
var mods = LinkedHashSet<String>()
var maxTurns = 500
fun clone(): GameParameters {
val parameters = GameParameters()

View File

@ -20,6 +20,7 @@ enum class VictoryType {
Diplomatic,
Domination,
Scientific,
Time,
}
class Nation : RulesetObject() {

View File

@ -38,11 +38,10 @@ class GameOptionsTable(
defaults().pad(5f)
// We assign this first to make sure addBaseRulesetSelectBox doesn't reference a null object
if (isPortrait) {
modCheckboxes = getModCheckboxes(isPortrait = true)
} else {
modCheckboxes = getModCheckboxes()
}
modCheckboxes =
if (isPortrait)
getModCheckboxes(isPortrait = true)
else getModCheckboxes()
add(Table().apply {
defaults().pad(5f)
@ -50,12 +49,14 @@ class GameOptionsTable(
addDifficultySelectBox()
addGameSpeedSelectBox()
addEraSelectBox()
addMaxTurnsSelectBox()
// align left and right edges with other SelectBoxes but allow independent dropdown width
add(Table().apply {
cityStateSlider = addCityStatesSlider()
}).colspan(2).fillX().row()
}).row()
addVictoryTypeCheckboxes()
val checkboxTable = Table().apply { defaults().left().pad(2.5f) }
checkboxTable.addNoBarbariansCheckbox()
@ -205,6 +206,13 @@ class GameOptionsTable(
{ gameParameters.startingEra = it; null }
}
private fun Table.addMaxTurnsSelectBox() {
if (!gameParameters.victoryTypes.contains(VictoryType.Time)) return
val maxTurns = listOf(250,300,350,400,450,500,550,600,650,700,750,800,900,1000,1250,1500,2000).map { it.toString() }
addSelectBox( "{Max Turns}:", maxTurns, gameParameters.maxTurns.toString())
{ gameParameters.maxTurns = it.toInt(); null }
}
private fun addVictoryTypeCheckboxes() {
add("{Victory Conditions}:".toLabel()).colspan(2).row()
@ -221,6 +229,9 @@ class GameOptionsTable(
} else {
gameParameters.victoryTypes.remove(victoryType)
}
// show or hide the max turns select box
if (victoryType == VictoryType.Time)
update()
}
victoryCheckbox.name = victoryType.name
victoryCheckbox.isDisabled = locked

View File

@ -31,12 +31,13 @@ class DiplomacyOverviewTable (
val playerKnowsAndUndefeatedCivs = relevantCivs.filter { diplomacyGroup.playerKnows(it) && !it.isDefeated() }
val playerKnowsAndDefeatedCivs = relevantCivs.filter { diplomacyGroup.playerKnows(it) && it.isDefeated() }
if (playerKnowsAndUndefeatedCivs.size > 1)
add(diplomacyGroup)
add(diplomacyGroup).top()
val titleTable = Table()
titleTable.add("Our Civilization:".toLabel())
titleTable.add("Our Civilization:".toLabel()).colspan(2).row()
titleTable.add(ImageGetter.getNationIndicator(viewingPlayer.nation, 25f)).pad(5f)
titleTable.add(viewingPlayer.civName.toLabel()).left().row()
titleTable.add(viewingPlayer.civName.toLabel()).left().padRight(10f)
titleTable.add(viewingPlayer.calculateScore().toLabel()).row()
val civTableScrollPane = getCivTableScroll(relevantCivs, titleTable, playerKnowsAndUndefeatedCivs, playerKnowsAndDefeatedCivs)
@ -55,7 +56,7 @@ class DiplomacyOverviewTable (
private fun getCivMiniTable(civInfo: CivilizationInfo): Table {
val table = Table()
table.add(ImageGetter.getNationIndicator(civInfo.nation, 25f)).pad(5f)
table.add(civInfo.civName.toLabel()).left()
table.add(civInfo.civName.toLabel()).left().padRight(10f)
table.touchable = Touchable.enabled
table.onClick {
if (civInfo.isDefeated() || viewingPlayer.isSpectator() || civInfo == viewingPlayer) return@onClick
@ -80,9 +81,16 @@ class DiplomacyOverviewTable (
.pad(5f).colspan(2).row()
if (playerKnowsAndUndefeatedCivs.size > 1) {
civTable.addSeparator()
var cityStatesParsed = 0
playerKnowsAndUndefeatedCivs.filter { it != viewingPlayer }.forEach {
civTable.add(getCivMiniTable(it)).left()
if (playerKnowsAndUndefeatedCivs.indexOf(it) % 2 == 0) civTable.row()
if (it.isCityState()) {
cityStatesParsed++
} else {
civTable.add(it.calculateScore().toLabel()).left()
}
if (!it.isCityState() || cityStatesParsed % 2 == 0)
civTable.row()
}
}
civTable.addSeparator()
@ -90,9 +98,16 @@ class DiplomacyOverviewTable (
.pad(5f).colspan(2).row()
if (playerKnowsAndDefeatedCivs.isNotEmpty()) {
civTable.addSeparator()
var cityStatesParsed = 0
playerKnowsAndDefeatedCivs.forEach {
civTable.add(getCivMiniTable(it)).left()
if (playerKnowsAndDefeatedCivs.indexOf(it) % 2 == 0) civTable.row()
if (it.isCityState()) {
cityStatesParsed++
} else {
civTable.add(it.calculateScore().toLabel()).left()
}
if (!it.isCityState() || cityStatesParsed % 2 == 0)
civTable.row()
}
}
val civTableScrollPane = AutoScrollPane(civTable)

View File

@ -1,6 +1,7 @@
package com.unciv.ui.victoryscreen
enum class RankingType {
Score,
Population,
Crop_Yield,
Production,

View File

@ -79,6 +79,7 @@ class VictoryScreen(val worldScreen: WorldScreen) : PickerScreen() {
private fun wonOrLost(description: String) {
// description will be empty when the player loses - no parameters - so this will be when(null) and end up in the else branch:
val endGameMessage = when (description.getPlaceholderParameters().firstOrNull()) {
VictoryType.Time.name -> "The world has been convulsed by war. Many great and powerful civilizations have fallen, but you have survived - and emerged victorious! The world will long remember your glorious triumph!"
VictoryType.Cultural.name -> "You have achieved victory through the awesome power of your Culture. Your civilization's greatness - the magnificence of its monuments and the power of its artists - have astounded the world! Poets will honor you as long as beauty brings gladness to a weary heart."
VictoryType.Domination.name -> "The world has been convulsed by war. Many great and powerful civilizations have fallen, but you have survived - and emerged victorious! The world will long remember your glorious triumph!"
VictoryType.Scientific.name -> "You have achieved victory through mastery of Science! You have conquered the mysteries of nature and led your people on a voyage to a brave new world! Your triumph will be remembered as long as the stars burn in the night sky!"