mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-09 15:29:32 +07:00
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:
@ -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] =
|
||||
|
@ -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()
|
||||
@ -287,6 +285,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.
|
||||
if (tiles.size < 3) {
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ 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"
|
||||
|
||||
@ -35,6 +35,8 @@ class GameParameters { // Default values are the default new game
|
||||
var baseRuleset: String = BaseRuleset.Civ_V_GnK.fullName
|
||||
var mods = LinkedHashSet<String>()
|
||||
|
||||
var maxTurns = 500
|
||||
|
||||
fun clone(): GameParameters {
|
||||
val parameters = GameParameters()
|
||||
parameters.difficulty = difficulty
|
||||
|
@ -20,6 +20,7 @@ enum class VictoryType {
|
||||
Diplomatic,
|
||||
Domination,
|
||||
Scientific,
|
||||
Time,
|
||||
}
|
||||
|
||||
class Nation : RulesetObject() {
|
||||
|
@ -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,6 +49,7 @@ class GameOptionsTable(
|
||||
addDifficultySelectBox()
|
||||
addGameSpeedSelectBox()
|
||||
addEraSelectBox()
|
||||
addMaxTurnsSelectBox()
|
||||
// align left and right edges with other SelectBoxes but allow independent dropdown width
|
||||
add(Table().apply {
|
||||
cityStateSlider = addCityStatesSlider()
|
||||
@ -57,6 +57,7 @@ class GameOptionsTable(
|
||||
}).row()
|
||||
addVictoryTypeCheckboxes()
|
||||
|
||||
|
||||
val checkboxTable = Table().apply { defaults().left().pad(2.5f) }
|
||||
checkboxTable.addNoBarbariansCheckbox()
|
||||
checkboxTable.addRagingBarbariansCheckbox()
|
||||
@ -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
|
||||
|
@ -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)
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.unciv.ui.victoryscreen
|
||||
|
||||
enum class RankingType {
|
||||
Score,
|
||||
Population,
|
||||
Crop_Yield,
|
||||
Production,
|
||||
|
@ -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!"
|
||||
|
Reference in New Issue
Block a user