diff --git a/android/assets/jsons/translations/template.properties b/android/assets/jsons/translations/template.properties index fd7fda5490..874e1acfea 100644 --- a/android/assets/jsons/translations/template.properties +++ b/android/assets/jsons/translations/template.properties @@ -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] = diff --git a/core/src/com/unciv/logic/GameInfo.kt b/core/src/com/unciv/logic/GameInfo.kt index b722dcd130..5a493e968b 100644 --- a/core/src/com/unciv/logic/GameInfo.kt +++ b/core/src/com/unciv/logic/GameInfo.kt @@ -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, inOrNear: String) { // don't flood the player with similar messages. instead cycle through units by clicking the message multiple times. diff --git a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt index 986f2e5e1f..d4c9e101fa 100644 --- a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt +++ b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt @@ -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 diff --git a/core/src/com/unciv/logic/civilization/TechManager.kt b/core/src/com/unciv/logic/civilization/TechManager.kt index ed868f6751..0ee29f3ebf 100644 --- a/core/src/com/unciv/logic/civilization/TechManager.kt +++ b/core/src/com/unciv/logic/civilization/TechManager.kt @@ -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) { diff --git a/core/src/com/unciv/logic/civilization/VictoryManager.kt b/core/src/com/unciv/logic/civilization/VictoryManager.kt index beabb951a5..9c0766d564 100644 --- a/core/src/com/unciv/logic/civilization/VictoryManager.kt +++ b/core/src/com/unciv/logic/civilization/VictoryManager.kt @@ -10,7 +10,7 @@ class VictoryManager { var requiredSpaceshipParts = Counter() var currentsSpaceshipParts = Counter() - 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 diff --git a/core/src/com/unciv/logic/map/MapParameters.kt b/core/src/com/unciv/logic/map/MapParameters.kt index d41461c27b..3c042357b2 100644 --- a/core/src/com/unciv/logic/map/MapParameters.kt +++ b/core/src/com/unciv/logic/map/MapParameters.kt @@ -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 + } } diff --git a/core/src/com/unciv/models/metadata/GameParameters.kt b/core/src/com/unciv/models/metadata/GameParameters.kt index 8667dafa95..1e804f0f60 100644 --- a/core/src/com/unciv/models/metadata/GameParameters.kt +++ b/core/src/com/unciv/models/metadata/GameParameters.kt @@ -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 = 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() + + var maxTurns = 500 fun clone(): GameParameters { val parameters = GameParameters() diff --git a/core/src/com/unciv/models/ruleset/Nation.kt b/core/src/com/unciv/models/ruleset/Nation.kt index d9fe628d71..90cffcb447 100644 --- a/core/src/com/unciv/models/ruleset/Nation.kt +++ b/core/src/com/unciv/models/ruleset/Nation.kt @@ -20,6 +20,7 @@ enum class VictoryType { Diplomatic, Domination, Scientific, + Time, } class Nation : RulesetObject() { diff --git a/core/src/com/unciv/ui/newgamescreen/GameOptionsTable.kt b/core/src/com/unciv/ui/newgamescreen/GameOptionsTable.kt index 13e26fc03f..ca2c634a37 100644 --- a/core/src/com/unciv/ui/newgamescreen/GameOptionsTable.kt +++ b/core/src/com/unciv/ui/newgamescreen/GameOptionsTable.kt @@ -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 diff --git a/core/src/com/unciv/ui/overviewscreen/DiplomacyOverviewTable.kt b/core/src/com/unciv/ui/overviewscreen/DiplomacyOverviewTable.kt index 6227d37bff..a165dd093b 100644 --- a/core/src/com/unciv/ui/overviewscreen/DiplomacyOverviewTable.kt +++ b/core/src/com/unciv/ui/overviewscreen/DiplomacyOverviewTable.kt @@ -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) diff --git a/core/src/com/unciv/ui/victoryscreen/RankingType.kt b/core/src/com/unciv/ui/victoryscreen/RankingType.kt index 943f939d32..452f39298a 100644 --- a/core/src/com/unciv/ui/victoryscreen/RankingType.kt +++ b/core/src/com/unciv/ui/victoryscreen/RankingType.kt @@ -1,6 +1,7 @@ package com.unciv.ui.victoryscreen enum class RankingType { + Score, Population, Crop_Yield, Production, diff --git a/core/src/com/unciv/ui/victoryscreen/VictoryScreen.kt b/core/src/com/unciv/ui/victoryscreen/VictoryScreen.kt index e9c37ce144..632376a3f9 100644 --- a/core/src/com/unciv/ui/victoryscreen/VictoryScreen.kt +++ b/core/src/com/unciv/ui/victoryscreen/VictoryScreen.kt @@ -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!"