diff --git a/android/assets/jsons/Civ V - Gods & Kings/VictoryTypes.json b/android/assets/jsons/Civ V - Gods & Kings/VictoryTypes.json new file mode 100644 index 0000000000..454d7caf32 --- /dev/null +++ b/android/assets/jsons/Civ V - Gods & Kings/VictoryTypes.json @@ -0,0 +1,42 @@ +[ + { + "name": "Scientific", + "victoryScreenHeader": "Complete all the spaceship parts\nto win!", + "milestones": [ + "Build [Apollo Program]", "Add all [spaceship parts] in capital" + ], + "requiredSpaceshipParts": [ + "SS Engine", "SS Stasis Chamber", "SS Cockpit", "SS Booster", "SS Booster", "SS Booster" + ], + "victoryString": "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!", + "defeatString": "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!" + }, + { + "name": "Cultural", + "victoryScreenHeader": "Complete 5 policy branches and\nbuild the Utopia Project to win!", + "milestones": ["Complete [5] Policy branches", "Build [Utopia Project]"], + "victoryString": "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.", + "defeatString": "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!" + }, + { + "name": "Domination", + "victoryScreenHeader": "Destroy all enemies\nto win!", + "milestones": ["Destroy all players"], + "victoryString": "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!", + "defeatString": "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!" + }, + { + "name": "Diplomatic", + "hiddenInVictoryScreen": true, + "milestones": ["[United Nations] build globally", "Win diplomatic vote"], + "victoryString": "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!", + "defeatString": "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!" + }, + { + "name": "Time", + "hiddenInVictoryScreen": true, + "milestones": ["Have highest score after max turns"], + "victoryString": "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!", + "defeatString": "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!" + } +] \ No newline at end of file diff --git a/android/assets/jsons/Civ V - Vanilla/Units.json b/android/assets/jsons/Civ V - Vanilla/Units.json index e0d7149c90..5a42668671 100644 --- a/android/assets/jsons/Civ V - Vanilla/Units.json +++ b/android/assets/jsons/Civ V - Vanilla/Units.json @@ -1356,7 +1356,7 @@ "cost": 500, "requiredTech": "Robotics", "requiredResource": "Aluminum", - "uniques": ["Spaceship part", "Cannot be purchased", "Only available ", "Uncapturable", "Can be added to [The Spaceship] in the Capital"] + "uniques": ["Spaceship part", "Cannot be purchased", "Only available ", "Uncapturable", "Can be added to [The Spaceship] in the Capital", "Limited to [3] per Civilization"] // costs 1500 in BNW }, { @@ -1366,7 +1366,7 @@ "cost": 750, "requiredTech": "Satellites", "requiredResource": "Aluminum", - "uniques": ["Spaceship part", "Cannot be purchased", "Only available ", "Uncapturable", "Can be added to [The Spaceship] in the Capital"] + "uniques": ["Spaceship part", "Cannot be purchased", "Only available ", "Uncapturable", "Can be added to [The Spaceship] in the Capital", "Limited to [1] per Civilization"] // costs 1500 in BNW }, { @@ -1376,7 +1376,7 @@ "cost": 750, "requiredTech": "Particle Physics", "requiredResource": "Aluminum", - "uniques": ["Spaceship part", "Cannot be purchased", "Only available ", "Uncapturable", "Can be added to [The Spaceship] in the Capital"] + "uniques": ["Spaceship part", "Cannot be purchased", "Only available ", "Uncapturable", "Can be added to [The Spaceship] in the Capital", "Limited to [1] per Civilization"] // costs 1500 in BNW }, { @@ -1386,7 +1386,7 @@ "cost": 750, "requiredTech": "Nanotechnology", "requiredResource": "Aluminum", - "uniques": ["Spaceship part", "Cannot be purchased", "Only available ", "Uncapturable", "Can be added to [The Spaceship] in the Capital"] + "uniques": ["Spaceship part", "Cannot be purchased", "Only available ", "Uncapturable", "Can be added to [The Spaceship] in the Capital", "Limited to [1] per Civilization"] // costs 1500 in BNW } ] diff --git a/android/assets/jsons/Civ V - Vanilla/VictoryTypes.json b/android/assets/jsons/Civ V - Vanilla/VictoryTypes.json new file mode 100644 index 0000000000..454d7caf32 --- /dev/null +++ b/android/assets/jsons/Civ V - Vanilla/VictoryTypes.json @@ -0,0 +1,42 @@ +[ + { + "name": "Scientific", + "victoryScreenHeader": "Complete all the spaceship parts\nto win!", + "milestones": [ + "Build [Apollo Program]", "Add all [spaceship parts] in capital" + ], + "requiredSpaceshipParts": [ + "SS Engine", "SS Stasis Chamber", "SS Cockpit", "SS Booster", "SS Booster", "SS Booster" + ], + "victoryString": "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!", + "defeatString": "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!" + }, + { + "name": "Cultural", + "victoryScreenHeader": "Complete 5 policy branches and\nbuild the Utopia Project to win!", + "milestones": ["Complete [5] Policy branches", "Build [Utopia Project]"], + "victoryString": "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.", + "defeatString": "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!" + }, + { + "name": "Domination", + "victoryScreenHeader": "Destroy all enemies\nto win!", + "milestones": ["Destroy all players"], + "victoryString": "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!", + "defeatString": "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!" + }, + { + "name": "Diplomatic", + "hiddenInVictoryScreen": true, + "milestones": ["[United Nations] build globally", "Win diplomatic vote"], + "victoryString": "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!", + "defeatString": "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!" + }, + { + "name": "Time", + "hiddenInVictoryScreen": true, + "milestones": ["Have highest score after max turns"], + "victoryString": "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!", + "defeatString": "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!" + } +] \ No newline at end of file diff --git a/android/assets/jsons/translations/template.properties b/android/assets/jsons/translations/template.properties index 8660ce66ea..2e6188c78d 100644 --- a/android/assets/jsons/translations/template.properties +++ b/android/assets/jsons/translations/template.properties @@ -415,6 +415,8 @@ Starting Era = It looks like we can't make a map with the parameters you requested! = Maybe you put too many players into too small a map? = No human players selected! = +Invalid Player ID! = +No victory conditions were selected! = Mods: = Extension mods: = Base ruleset: = @@ -1078,31 +1080,22 @@ Number of your cities\ndemanding this resource for\n'We Love The King Day' = # Victory -Science victory = -Cultural victory = -Conquest victory = -Diplomatic victory = -Complete all the spaceship parts\n to win! = -Complete 5 policy branches\n to win! = -Complete 5 policy branches and build\n the Utopia Project to win! = -Destroy all enemies\n to win! = +[victoryType] Victory = +Built [building] = +Add all spaceship parts in capital = +Destroy all players = +Capture all capitals = +Complete [amount] Policy branche = You have won a [victoryType] Victory! = -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. = -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! = -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! = +[civilization] has won a [victoryType] Victory! = 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] = +Capture [cityName] = Our status = Global status = Rankings = -Spaceship parts remaining = -Branches completed = -Undefeated civs = # The \n here means: put a newline (enter) here. If this is omitted, the sidebox in the diplomacy overview will become _really_ wide. # Feel free to replace it with a space and put it between other words in your translation Turns until the next\ndiplomacy victory vote: [amount] = diff --git a/core/src/com/unciv/Constants.kt b/core/src/com/unciv/Constants.kt index 637d95b52a..1024f8b24b 100644 --- a/core/src/com/unciv/Constants.kt +++ b/core/src/com/unciv/Constants.kt @@ -47,9 +47,11 @@ object Constants { const val citadel = "Citadel" const val futureTech = "Future Tech" - // Easter egg name. Hopefully is to hopefully avoid conflicts when later players can name their own religions. + // Easter egg name. Is to avoid conflicts when players name their own religions. // This religion name should never be displayed. const val noReligionName = "The religion of TheLegend27" + + const val neutralVictoryType = "Neutral" const val cancelImprovementOrder = "Cancel improvement order" const val tutorialPopupNamePrefix = "Tutorial: " diff --git a/core/src/com/unciv/JsonParser.kt b/core/src/com/unciv/JsonParser.kt index 4eed8d6e60..43da9ac8c9 100644 --- a/core/src/com/unciv/JsonParser.kt +++ b/core/src/com/unciv/JsonParser.kt @@ -12,7 +12,7 @@ class JsonParser { fun getFromJson(tClass: Class, filePath: String): T = getFromJson(tClass, Gdx.files.internal(filePath)) fun getFromJson(tClass: Class, file: FileHandle): T { - try{ + try { val jsonText = file.readString(Charsets.UTF_8.name()) return json.fromJson(tClass, jsonText) } catch (exception:Exception){ diff --git a/core/src/com/unciv/logic/GameInfo.kt b/core/src/com/unciv/logic/GameInfo.kt index c532dd1bf2..fa08051a03 100644 --- a/core/src/com/unciv/logic/GameInfo.kt +++ b/core/src/com/unciv/logic/GameInfo.kt @@ -37,6 +37,8 @@ class GameInfo { // Maps a civ to the civ they voted for var diplomaticVictoryVotesCast = HashMap() + // Set to false whenever the results still need te be processed + var diplomaticVictoryVotesProcessed = false /**Keep track of a custom location this game was saved to _or_ loaded from * @@ -205,7 +207,6 @@ class GameInfo { currentPlayerIndex = (currentPlayerIndex + 1) % civilizations.size if (currentPlayerIndex == 0) { turns++ - checkForTimeVictory() } thisPlayer = civilizations[currentPlayerIndex] thisPlayer.startTurn() @@ -266,7 +267,7 @@ class GameInfo { && (!it.militaryUnit!!.isInvisible(thisPlayer) || viewableInvisibleTiles.contains(it.position))) } - // enemy units ON our territory + // enemy units IN our territory addEnemyUnitNotification(thisPlayer, enemyUnitsCloseToTerritory.filter { it.getOwner() == thisPlayer }, "in" @@ -283,16 +284,17 @@ class GameInfo { } ) } + + fun getEnabledVictories() = ruleSet.victories.filter { !it.value.hiddenInVictoryScreen && gameParameters.victoryTypes.contains(it.key) } - 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.calculateTotalScore() } - ?: return // Are there no civs left? - - winningCiv.victoryManager.hasWonTimeVictory = true + fun processDiplomaticVictory() { + if (diplomaticVictoryVotesProcessed) return + for (civInfo in civilizations) { + if (civInfo.victoryManager.hasEnoughVotesForDiplomaticVictory()) { + civInfo.victoryManager.hasEverWonDiplomaticVote = true + } + } + diplomaticVictoryVotesProcessed = true } private fun addEnemyUnitNotification(thisPlayer: CivilizationInfo, tiles: List, inOrNear: String) { @@ -450,8 +452,10 @@ class GameInfo { } } + spaceResources.clear() spaceResources.addAll(ruleSet.buildings.values.filter { it.hasUnique(UniqueType.SpaceshipPart) } .flatMap { it.getResourceRequirements().keys } ) + spaceResources.addAll(ruleSet.victories.values.flatMap { it.requiredSpaceshipParts }) barbarians.setTransients(this) diff --git a/core/src/com/unciv/logic/automation/Automation.kt b/core/src/com/unciv/logic/automation/Automation.kt index 004d5b385b..482d1ed7a7 100644 --- a/core/src/com/unciv/logic/automation/Automation.kt +++ b/core/src/com/unciv/logic/automation/Automation.kt @@ -8,7 +8,8 @@ import com.unciv.logic.map.MapUnit import com.unciv.logic.map.TileInfo import com.unciv.logic.map.TileMap import com.unciv.models.ruleset.Building -import com.unciv.models.ruleset.VictoryType +import com.unciv.models.ruleset.MilestoneType +import com.unciv.models.ruleset.ThingToFocus import com.unciv.models.ruleset.tile.ResourceType import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.ruleset.unit.BaseUnit @@ -41,7 +42,7 @@ object Automation { rank += stats.production rank += stats.science - if (city.tiles.size < 12 || city.civInfo.victoryType() == VictoryType.Cultural) { + if (city.tiles.size < 12 || city.civInfo.wantsToFocusOn(ThingToFocus.Culture)) { rank += stats.culture } else rank += stats.culture / 2 } @@ -175,7 +176,7 @@ object Automation { return true // Spaceships are always allowed - if (construction.hasUnique(UniqueType.SpaceshipPart)) + if (construction.name in civInfo.gameInfo.spaceResources) return true val requiredResources = construction.getResourceRequirements() @@ -237,7 +238,7 @@ object Automation { } fun getReservedSpaceResourceAmount(civInfo: CivilizationInfo): Int { - return if (civInfo.nation.preferredVictoryType == VictoryType.Scientific) 3 else 2 + return if (civInfo.wantsToFocusOn(ThingToFocus.Science)) 3 else 2 } fun threatAssessment(assessor: CivilizationInfo, assessed: CivilizationInfo): ThreatLevel { diff --git a/core/src/com/unciv/logic/automation/ChooseBeliefsAutomation.kt b/core/src/com/unciv/logic/automation/ChooseBeliefsAutomation.kt index d7143bc114..4596bb41a9 100644 --- a/core/src/com/unciv/logic/automation/ChooseBeliefsAutomation.kt +++ b/core/src/com/unciv/logic/automation/ChooseBeliefsAutomation.kt @@ -5,7 +5,7 @@ import com.unciv.logic.civilization.CivilizationInfo import com.unciv.logic.map.TileInfo import com.unciv.models.ruleset.Belief import com.unciv.models.ruleset.BeliefType -import com.unciv.models.ruleset.VictoryType +import com.unciv.models.ruleset.ThingToFocus import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.stats.Stat import kotlin.math.min @@ -133,7 +133,7 @@ object ChooseBeliefsAutomation { score += modifier * when (unique.placeholderText) { "Earn []% of [] unit's [] as [] when killed within 4 tiles of a city following this religion" -> unique.params[0].toFloat() * 4f * - if (civInfo.victoryType() == VictoryType.Domination) 2f + if (civInfo.wantsToFocusOn(ThingToFocus.Military)) 2f else 1f "May buy [] buildings for [] [] []", "May buy [] units for [] [] []" -> if (civInfo.religionManager.religion != null @@ -152,7 +152,7 @@ object ChooseBeliefsAutomation { // what happens over there else civInfo.statsForNextTurn[Stat.valueOf(unique.params[1])] * 10f / civInfo.getEra().baseUnitBuyCost UniqueType.BuyUnitsByProductionCost.placeholderText -> - 15f * if (civInfo.victoryType() == VictoryType.Domination) 2f else 1f + 15f * if (civInfo.wantsToFocusOn(ThingToFocus.Military)) 2f else 1f "when a city adopts this religion for the first time (modified by game speed)" -> // Modified by personality unique.stats.values.sum() * 10f "When spreading religion to a city, gain [] times the amount of followers of other religions as []" -> diff --git a/core/src/com/unciv/logic/automation/ConstructionAutomation.kt b/core/src/com/unciv/logic/automation/ConstructionAutomation.kt index 8ef9182ac3..990841c534 100644 --- a/core/src/com/unciv/logic/automation/ConstructionAutomation.kt +++ b/core/src/com/unciv/logic/automation/ConstructionAutomation.kt @@ -8,7 +8,8 @@ import com.unciv.logic.civilization.NotificationIcon import com.unciv.logic.civilization.PlayerType import com.unciv.logic.map.BFS import com.unciv.models.ruleset.Building -import com.unciv.models.ruleset.VictoryType +import com.unciv.models.ruleset.MilestoneType +import com.unciv.models.ruleset.ThingToFocus import com.unciv.models.ruleset.unique.StateForConditionals import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.ruleset.unit.BaseUnit @@ -36,8 +37,14 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){ val allTechsAreResearched = civInfo.tech.getNumberOfTechsResearched() >= civInfo.gameInfo.ruleSet.technologies.size val isAtWar = civInfo.isAtWar() - val preferredVictoryType = civInfo.victoryType() + private val buildingsForVictory = civInfo.gameInfo.getEnabledVictories().values + .mapNotNull { civInfo.victoryManager.getNextMilestone(it.name) } + .filter { it.type == MilestoneType.BuiltBuilding || it.type == MilestoneType.BuildingBuiltGlobally } + .map { it.params[0] } + + private val spaceshipParts = civInfo.gameInfo.spaceResources + private val averageProduction = civInfo.cities.map { it.cityStats.currentCityStats.production }.average() val cityIsOverAverageProduction = cityInfo.cityStats.currentCityStats.production >= averageProduction @@ -45,9 +52,9 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){ private val faithConstruction = arrayListOf() - data class ConstructionChoice(val choice:String, var choiceModifier:Float,val remainingWork:Int) + data class ConstructionChoice(val choice: String, var choiceModifier: Float, val remainingWork: Int) - fun addChoice(choices:ArrayList, choice:String, choiceModifier: Float){ + private fun addChoice(choices: ArrayList, choice: String, choiceModifier: Float){ choices.add(ConstructionChoice(choice,choiceModifier,cityConstructions.getRemainingWork(choice))) } @@ -125,7 +132,7 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){ val unitsToCitiesRatio = cities.toFloat() / (militaryUnits + 1) // most buildings and civ units contribute the the civ's growth, military units are anti-growth var modifier = sqrt(unitsToCitiesRatio) / 2 - if (preferredVictoryType == VictoryType.Domination) modifier *= 3 + if (civInfo.wantsToFocusOn(ThingToFocus.Military)) modifier *= 3 else if (isAtWar) modifier *= unitsToCitiesRatio * 2 if (Automation.afraidOfBarbarians(civInfo)) modifier = 2f // military units are pro-growth if pressured by barbs @@ -193,16 +200,15 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){ var modifier = 0.5f if (cityInfo.cityStats.currentCityStats.culture == 0f) // It won't grow if we don't help it modifier = 0.8f - if (preferredVictoryType == VictoryType.Cultural) modifier = 1.6f + if (civInfo.wantsToFocusOn(ThingToFocus.Culture)) modifier = 1.6f addChoice(relativeCostEffectiveness, cultureBuilding.name, modifier) } } private fun addSpaceshipPartChoice() { - val spaceshipPart = (buildableNotWonders + buildableUnits).firstOrNull { it.hasUnique(UniqueType.SpaceshipPart) } + val spaceshipPart = (buildableNotWonders + buildableUnits).firstOrNull { it.name in spaceshipParts } if (spaceshipPart != null) { - var modifier = 1.5f - if (preferredVictoryType == VictoryType.Scientific) modifier = 2f + val modifier = 2f addChoice(relativeCostEffectiveness, spaceshipPart.name, modifier) } } @@ -217,23 +223,27 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){ } private fun getWonderPriority(wonder: Building): Float { - if (wonder.hasUnique(UniqueType.EnablesConstructionOfSpaceshipParts)) - return 2f - if (preferredVictoryType == VictoryType.Cultural + // Only start building if we are the city that would complete it the soonest + if (wonder.hasUnique(UniqueType.TriggersCulturalVictory) + && cityInfo == civInfo.cities.minByOrNull { + it.cityConstructions.turnsToConstruction(wonder.name) + }!! + ) { + return 10f + } + if (wonder.name in buildingsForVictory) + return 5f + if (civInfo.wantsToFocusOn(ThingToFocus.Culture) + // TODO: Moddability && wonder.name in listOf("Sistine Chapel", "Eiffel Tower", "Cristo Redentor", "Neuschwanstein", "Sydney Opera House")) return 3f - // Only start building if we are the city that would complete it the soonest - if (wonder.hasUnique(UniqueType.TriggersCulturalVictory) && cityInfo == civInfo.cities.minByOrNull { - it.cityConstructions.turnsToConstruction(wonder.name) - }!!) - return 10f if (wonder.isStatRelated(Stat.Science)) { if (allTechsAreResearched) return .5f - if (preferredVictoryType == VictoryType.Scientific) return 1.5f + if (civInfo.wantsToFocusOn(ThingToFocus.Science)) return 1.5f else return 1.3f } if (wonder.name == "Manhattan Project") { - if (preferredVictoryType == VictoryType.Domination) return 2f + if (civInfo.wantsToFocusOn(ThingToFocus.Military)) return 2f else return 1.3f } if (wonder.isStatRelated(Stat.Happiness)) return 1.2f @@ -259,10 +269,10 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){ val unitTrainingBuilding = buildableNotWonders.asSequence() .filter { it.hasUnique(UniqueType.UnitStartingExperience) && Automation.allowSpendingResource(civInfo, it)}.minByOrNull { it.cost } - if (unitTrainingBuilding != null && (preferredVictoryType != VictoryType.Cultural || isAtWar)) { + if (unitTrainingBuilding != null && (!civInfo.wantsToFocusOn(ThingToFocus.Culture) || isAtWar)) { var modifier = if (cityIsOverAverageProduction) 0.5f else 0.1f // You shouldn't be cranking out units anytime soon if (isAtWar) modifier *= 2 - if (preferredVictoryType == VictoryType.Domination) + if (civInfo.wantsToFocusOn(ThingToFocus.Military)) modifier *= 1.3f addChoice(relativeCostEffectiveness, unitTrainingBuilding.name, modifier) } @@ -272,7 +282,7 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){ val defensiveBuilding = buildableNotWonders.asSequence() .filter { it.cityStrength > 0 && Automation.allowSpendingResource(civInfo, it)}.minByOrNull { it.cost } - if (defensiveBuilding != null && (isAtWar || preferredVictoryType != VictoryType.Cultural)) { + if (defensiveBuilding != null && (isAtWar || !civInfo.wantsToFocusOn(ThingToFocus.Culture))) { var modifier = 0.2f if (isAtWar) modifier = 0.5f @@ -308,7 +318,7 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){ .minByOrNull { it.cost } if (scienceBuilding != null) { var modifier = 1.1f - if (preferredVictoryType == VictoryType.Scientific) + if (civInfo.wantsToFocusOn(ThingToFocus.Science)) modifier *= 1.4f addChoice(relativeCostEffectiveness, scienceBuilding.name, modifier) } @@ -352,7 +362,7 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){ // these 4 if conditions are used to determine if an AI should buy units to spread religion, or spend faith to buy things like new military units or new buildings. // currently this AI can only buy inquisitors and missionaries with faith // this system will have to be reengineered to support buying other stuff with faith - if (preferredVictoryType == VictoryType.Domination) return + if (civInfo.wantsToFocusOn(ThingToFocus.Military)) return if (civInfo.religionManager.religion?.name == null) return if (cityInfo.religion.getMajorityReligion()?.name != civInfo.religionManager.religion?.name) return // you don't want to build units of opposing religions. @@ -372,7 +382,7 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){ && it.canBePurchasedWithStat(cityInfo, Stat.Faith) } - if (preferredVictoryType == VictoryType.Cultural) modifier += 1 + if (civInfo.wantsToFocusOn(ThingToFocus.Culture)) modifier += 1 if (isAtWar) modifier -= 0.5f val citiesNotFollowingOurReligion = civInfo.cities.asSequence() diff --git a/core/src/com/unciv/logic/automation/NextTurnAutomation.kt b/core/src/com/unciv/logic/automation/NextTurnAutomation.kt index e47f6a0044..4652eabe4d 100644 --- a/core/src/com/unciv/logic/automation/NextTurnAutomation.kt +++ b/core/src/com/unciv/logic/automation/NextTurnAutomation.kt @@ -260,7 +260,7 @@ object NextTurnAutomation { } } - if (civInfo.victoryType() == VictoryType.Cultural) { + if (civInfo.wantsToFocusOn(ThingToFocus.Culture)) { for (cityState in civInfo.getKnownCivs() .filter { it.isCityState() && it.cityStateType == CityStateType.Cultured }) { val diploManager = cityState.getDiplomacyManager(civInfo) @@ -298,20 +298,20 @@ object NextTurnAutomation { if (!cityState.isAlive() || cityState.cities.isEmpty() || civInfo.cities.isEmpty()) return value - if (civInfo.victoryType() == VictoryType.Cultural && cityState.canGiveStat(Stat.Culture)) { + if (civInfo.wantsToFocusOn(ThingToFocus.Culture) && cityState.canGiveStat(Stat.Culture)) { value += 10 } - else if (civInfo.victoryType() == VictoryType.Scientific && cityState.canGiveStat(Stat.Science)) { + else if (civInfo.wantsToFocusOn(ThingToFocus.Science) && cityState.canGiveStat(Stat.Science)) { // In case someone mods this in value += 10 } - else if (civInfo.victoryType() == VictoryType.Domination) { + else if (civInfo.wantsToFocusOn(ThingToFocus.Military)) { // Don't ally close city-states, conquer them instead val distance = getMinDistanceBetweenCities(civInfo, cityState) if (distance < 20) value -= (20 - distance) / 4 } - else if (civInfo.victoryType() == VictoryType.Diplomatic) { + else if (civInfo.wantsToFocusOn(ThingToFocus.CityStates)) { value += 5 // Generally be friendly } if (civInfo.gold < 100) { @@ -658,7 +658,7 @@ object NextTurnAutomation { } private fun declareWar(civInfo: CivilizationInfo) { - if (civInfo.victoryType() == VictoryType.Cultural) return + if (civInfo.wantsToFocusOn(ThingToFocus.Culture)) return if (civInfo.cities.isEmpty() || civInfo.diplomacy.isEmpty()) return if (civInfo.isAtWar() || civInfo.getHappiness() <= 0) return @@ -848,7 +848,7 @@ object NextTurnAutomation { private fun trainSettler(civInfo: CivilizationInfo) { if (civInfo.isCityState()) return if (civInfo.isAtWar()) return // don't train settlers when you could be training troops. - if (civInfo.victoryType() == VictoryType.Cultural && civInfo.cities.size > 3) return + if (civInfo.wantsToFocusOn(ThingToFocus.Culture) && civInfo.cities.size > 3) return if (civInfo.cities.none() || civInfo.getHappiness() <= civInfo.cities.size + 5) return val settlerUnits = civInfo.gameInfo.ruleSet.units.values diff --git a/core/src/com/unciv/logic/city/IConstruction.kt b/core/src/com/unciv/logic/city/IConstruction.kt index ecb0eac16f..66efc8cefe 100644 --- a/core/src/com/unciv/logic/city/IConstruction.kt +++ b/core/src/com/unciv/logic/city/IConstruction.kt @@ -1,7 +1,7 @@ package com.unciv.logic.city import com.unciv.logic.civilization.CivilizationInfo -import com.unciv.models.ruleset.IHasUniques +import com.unciv.models.ruleset.unique.IHasUniques import com.unciv.models.ruleset.unique.StateForConditionals import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.stats.INamed @@ -20,6 +20,7 @@ interface IConstruction : INamed { interface INonPerpetualConstruction : IConstruction, INamed, IHasUniques { val hurryCostModifier: Int + var requiredTech: String? fun getProductionCost(civInfo: CivilizationInfo): Int fun getStatBuyCost(cityInfo: CityInfo, stat: Stat): Int? @@ -111,14 +112,14 @@ class RejectionReasons: HashSet() { RejectionReason.RequiresTech, RejectionReason.RequiresPolicy, RejectionReason.MorePolicyBranches, - RejectionReason.RequiresBuildingInSomeCity + RejectionReason.RequiresBuildingInSomeCity, ) private val reasonsToDefinitivelyRemoveFromQueue = hashSetOf( RejectionReason.Obsoleted, RejectionReason.WonderAlreadyBuilt, RejectionReason.NationalWonderAlreadyBuilt, RejectionReason.CannotBeBuiltWith, - RejectionReason.ReachedBuildCap + RejectionReason.MaxNumberBuildable, ) private val orderOfErrorMessages = listOf( RejectionReason.WonderBeingBuiltElsewhere, @@ -130,7 +131,7 @@ class RejectionReasons: HashSet() { RejectionReason.ConsumesResources, RejectionReason.CanOnlyBePurchased, RejectionReason.MaxNumberBuildable, - RejectionReason.NoPlaceToPutUnit + RejectionReason.NoPlaceToPutUnit, ) } } @@ -152,7 +153,7 @@ enum class RejectionReason(val shouldShow: Boolean, val errorMessage: String) { MustOwnTile(false, "Must own a specific tile close by"), WaterUnitsInCoastalCities(false, "May only built water units in coastal cities"), CanOnlyBeBuiltInSpecificCities(false, "Can only be built in specific cities"), - MaxNumberBuildable(true, "Maximum number have been built or are being constructed"), + MaxNumberBuildable(false, "Maximum number have been built or are being constructed"), UniqueToOtherNation(false, "Unique to another nation"), ReplacedByOurUnique(false, "Our unique replaces this"), @@ -179,8 +180,6 @@ enum class RejectionReason(val shouldShow: Boolean, val errorMessage: String) { CityStateNationalWonder(false, "No National Wonders for city-states"), WonderDisabledEra(false, "This Wonder is disabled when starting in this era"), - ReachedBuildCap(false, "Don't need to build any more of these!"), - ConsumesResources(true, "Consumes resources which you are lacking"), PopulationRequirement(true, "Requires more population"), diff --git a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt index 4ce037ed8e..92b7187a8f 100644 --- a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt +++ b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt @@ -1,13 +1,13 @@ package com.unciv.logic.civilization import com.badlogic.gdx.math.Vector2 +import com.unciv.Constants import com.unciv.UncivGame import com.unciv.logic.GameInfo import com.unciv.logic.UncivShowableException import com.unciv.logic.automation.NextTurnAutomation import com.unciv.logic.automation.WorkerAutomation import com.unciv.logic.city.CityInfo -import com.unciv.logic.city.IConstruction import com.unciv.logic.civilization.RuinsManager.RuinsManager import com.unciv.logic.civilization.diplomacy.DiplomacyFlags import com.unciv.logic.civilization.diplomacy.DiplomacyManager @@ -119,6 +119,9 @@ class CivilizationInfo { @Transient val lastEraResourceUsedForUnit = HashMap() + + @Transient + var thingsToFocusOnForVictory = setOf() var playerType = PlayerType.AI @@ -322,21 +325,33 @@ class CivilizationInfo { fun isAlive(): Boolean = !isDefeated() @Suppress("unused") //TODO remove if future use unlikely, including DiplomacyFlags.EverBeenFriends and 2 DiplomacyManager methods - see #3183 + // I'm willing to call this deprecated after so long fun hasEverBeenFriendWith(otherCiv: CivilizationInfo): Boolean = getDiplomacyManager(otherCiv).everBeenFriends() fun hasMetCivTerritory(otherCiv: CivilizationInfo): Boolean = otherCiv.getCivTerritory().any { it in exploredTiles } fun getCompletedPolicyBranchesCount(): Int = policies.adoptedPolicies.count { Policy.isBranchCompleteByName(it) } + fun originalMajorCapitalsOwned(): Int = cities.count { it.isOriginalCapital && it.foundingCiv != "" && gameInfo.getCivilization(it.foundingCiv).isMajorCiv() } private fun getCivTerritory() = cities.asSequence().flatMap { it.tiles.asSequence() } - fun victoryType(): VictoryType { + fun getPreferredVictoryType(): String { val victoryTypes = gameInfo.gameParameters.victoryTypes if (victoryTypes.size == 1) return victoryTypes.first() // That is the most relevant one val victoryType = nation.preferredVictoryType - return if (victoryType in victoryTypes) victoryType - else VictoryType.Neutral + return if (victoryType in gameInfo.ruleSet.victories) victoryType + else Constants.neutralVictoryType } - + + fun getPreferredVictoryTypeObject(): Victory? { + val preferredVictoryType = getPreferredVictoryType() + return if (preferredVictoryType == Constants.neutralVictoryType) null + else gameInfo.ruleSet.victories[getPreferredVictoryType()]!! + } + + fun wantsToFocusOn(thingToFocusOn: ThingToFocus): Boolean { + return thingsToFocusOnForVictory.contains(thingToFocusOn) + } + @Transient private val civInfoStats = CivInfoStats(this) fun stats() = civInfoStats @@ -751,6 +766,8 @@ class CivilizationInfo { } victoryManager.civInfo = this + + thingsToFocusOnForVictory = getPreferredVictoryTypeObject()?.getThingsToFocus(this) ?: setOf() for (cityInfo in cities) { cityInfo.civInfo = this // must be before the city's setTransients because it depends on the tilemap, that comes from the currentPlayerCivInfo @@ -928,16 +945,17 @@ class CivilizationInfo { private fun handleDiplomaticVictoryFlags() { if (flagsCountdown[CivFlags.ShouldResetDiplomaticVotes.name] == 0) { gameInfo.diplomaticVictoryVotesCast.clear() - removeFlag(CivFlags.ShouldResetDiplomaticVotes.name) removeFlag(CivFlags.ShowDiplomaticVotingResults.name) + removeFlag(CivFlags.ShouldResetDiplomaticVotes.name) } if (flagsCountdown[CivFlags.ShowDiplomaticVotingResults.name] == 0) { + gameInfo.processDiplomaticVictory() if (gameInfo.civilizations.any { it.victoryManager.hasWon() } ) { removeFlag(CivFlags.TurnsTillNextDiplomaticVote.name) } else { addFlag(CivFlags.ShouldResetDiplomaticVotes.name, 1) - addFlag(CivFlags.TurnsTillNextDiplomaticVote.name, getTurnsBetweenDiplomaticVotings()) + addFlag(CivFlags.TurnsTillNextDiplomaticVote.name, getTurnsBetweenDiplomaticVotes()) } } @@ -950,7 +968,7 @@ class CivilizationInfo { fun removeFlag(flag: String) = flagsCountdown.remove(flag) fun hasFlag(flag: String) = flagsCountdown.contains(flag) - fun getTurnsBetweenDiplomaticVotings() = (15 * gameInfo.gameParameters.gameSpeed.modifier).toInt() // Dunno the exact calculation, hidden in Lua files + fun getTurnsBetweenDiplomaticVotes() = (15 * gameInfo.gameParameters.gameSpeed.modifier).toInt() // Dunno the exact calculation, hidden in Lua files fun getTurnsTillNextDiplomaticVote() = flagsCountdown[CivFlags.TurnsTillNextDiplomaticVote.name] @@ -975,8 +993,8 @@ class CivilizationInfo { // to the user and thus the flag is set at -1/ fun shouldCheckForDiplomaticVictory() = (flagsCountdown[CivFlags.ShowDiplomaticVotingResults.name] == 0 - || flagsCountdown[CivFlags.ShowDiplomaticVotingResults.name] == -1) - && gameInfo.civilizations.any { it.isMajorCiv() && !it.isDefeated() && it != this } + || flagsCountdown[CivFlags.ShowDiplomaticVotingResults.name] == -1) + && gameInfo.civilizations.any { it.isMajorCiv() && !it.isDefeated() && it != this } private fun updateRevolts() { if (gameInfo.civilizations.none { it.isBarbarian() }) { diff --git a/core/src/com/unciv/logic/civilization/PolicyManager.kt b/core/src/com/unciv/logic/civilization/PolicyManager.kt index 55eb202ae0..1e5df054cb 100644 --- a/core/src/com/unciv/logic/civilization/PolicyManager.kt +++ b/core/src/com/unciv/logic/civilization/PolicyManager.kt @@ -38,7 +38,7 @@ class PolicyManager { get() { val value = HashMap() for (branch in branches) { - value[branch] = branch.priorities[civInfo.victoryType().name] ?: 0 + value[branch] = branch.priorities[civInfo.getPreferredVictoryType()] ?: 0 } return value } diff --git a/core/src/com/unciv/logic/civilization/VictoryManager.kt b/core/src/com/unciv/logic/civilization/VictoryManager.kt index 9c0766d564..deac60a261 100644 --- a/core/src/com/unciv/logic/civilization/VictoryManager.kt +++ b/core/src/com/unciv/logic/civilization/VictoryManager.kt @@ -1,39 +1,27 @@ package com.unciv.logic.civilization +import com.unciv.Constants import com.unciv.models.Counter -import com.unciv.models.ruleset.VictoryType +import com.unciv.models.ruleset.Milestone import com.unciv.models.ruleset.unique.UniqueType class VictoryManager { @Transient lateinit var civInfo: CivilizationInfo - var requiredSpaceshipParts = Counter() + // There is very likely a typo in this name (currents), but as its saved in save files, + // fixing it is non-trivial var currentsSpaceshipParts = Counter() - var hasWonTimeVictory = false - - init { - requiredSpaceshipParts.add("SS Booster", 3) - requiredSpaceshipParts.add("SS Cockpit", 1) - requiredSpaceshipParts.add("SS Engine", 1) - requiredSpaceshipParts.add("SS Stasis Chamber", 1) - } + var hasEverWonDiplomaticVote = false fun clone(): VictoryManager { val toReturn = VictoryManager() toReturn.currentsSpaceshipParts.putAll(currentsSpaceshipParts) + toReturn.hasEverWonDiplomaticVote = hasEverWonDiplomaticVote return toReturn } - fun unconstructedSpaceshipParts(): Counter { - val counter = requiredSpaceshipParts.clone() - counter.remove(currentsSpaceshipParts) - return counter - } - - fun spaceshipPartsRemaining() = requiredSpaceshipParts.values.sum() - currentsSpaceshipParts.values.sum() - - fun calculateDiplomaticVotingResults(votesCast: HashMap): Counter { + private fun calculateDiplomaticVotingResults(votesCast: HashMap): Counter { val results = Counter() for (castVote in votesCast) { results.add(castVote.value, 1) @@ -41,7 +29,7 @@ class VictoryManager { return results } - fun votesNeededForDiplomaticVictory(): Int { + private fun votesNeededForDiplomaticVictory(): Int { val civCount = civInfo.gameInfo.civilizations.count { !it.isDefeated() } // CvGame.cpp::DoUpdateDiploVictory() in the source code of the original @@ -54,44 +42,46 @@ class VictoryManager { fun hasEnoughVotesForDiplomaticVictory(): Boolean { val results = calculateDiplomaticVotingResults(civInfo.gameInfo.diplomaticVictoryVotesCast) val bestCiv = results.maxByOrNull { it.value } ?: return false - + // If we don't have the highest score, we have not won anyway if (bestCiv.key != civInfo.civName) return false + // If we don't have enough votes, we haven't won if (bestCiv.value < votesNeededForDiplomaticVictory()) return false // If there's a tie, we haven't won either return (results.none { it != bestCiv && it.value == bestCiv.value }) } - private fun hasVictoryType(victoryType: VictoryType) = civInfo.gameInfo.gameParameters.victoryTypes.contains(victoryType) - - fun hasWonScientificVictory() = hasVictoryType(VictoryType.Scientific) - && spaceshipPartsRemaining() == 0 - - fun hasWonCulturalVictory() = hasVictoryType(VictoryType.Cultural) - && civInfo.hasUnique(UniqueType.TriggersCulturalVictory) - - 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? { + fun getVictoryTypeAchieved(): String? { 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 - if (hasWonDiplomaticVictory()) return VictoryType.Diplomatic - if (civInfo.hasUnique(UniqueType.TriggersVictory)) return VictoryType.Neutral + for (victoryName in civInfo.gameInfo.ruleSet.victories.keys.filter { it != Constants.neutralVictoryType}) { + if (getNextMilestone(victoryName) == null) + return victoryName + } + if (civInfo.hasUnique(UniqueType.TriggersVictory)) + return Constants.neutralVictoryType return null } + + fun getNextMilestone(victory: String): Milestone? { + for (milestone in civInfo.gameInfo.ruleSet.victories[victory]!!.milestoneObjects) { + if (!milestone.hasBeenCompletedBy(civInfo)) + return milestone + } + return null + } + + fun amountMilestonesCompleted(victory: String): Int { + var completed = 0 + for (milestone in civInfo.gameInfo.ruleSet.victories[victory]!!.milestoneObjects) { + if (milestone.hasBeenCompletedBy(civInfo)) + ++completed + else + break + } + return completed + } - fun hasWon() = hasWonVictoryType() != null + fun hasWon() = getVictoryTypeAchieved() != null } \ No newline at end of file diff --git a/core/src/com/unciv/models/Counter.kt b/core/src/com/unciv/models/Counter.kt index 716dad4054..eb52263ae6 100644 --- a/core/src/com/unciv/models/Counter.kt +++ b/core/src/com/unciv/models/Counter.kt @@ -34,6 +34,10 @@ open class Counter : LinkedHashMap() { for (key in keys) newCounter[key] = this[key]!! * amount return newCounter } + + fun sumValues(): Int { + return this.map { it.value }.sum() + } override fun clone(): Counter { val newCounter = Counter() diff --git a/core/src/com/unciv/models/metadata/GameParameters.kt b/core/src/com/unciv/models/metadata/GameParameters.kt index 1304d87c94..95d6a39600 100644 --- a/core/src/com/unciv/models/metadata/GameParameters.kt +++ b/core/src/com/unciv/models/metadata/GameParameters.kt @@ -1,10 +1,6 @@ package com.unciv.models.metadata -import com.unciv.Constants import com.unciv.logic.civilization.PlayerType -import com.unciv.models.ruleset.Ruleset -import com.unciv.models.ruleset.RulesetCache -import com.unciv.models.ruleset.VictoryType enum class BaseRuleset(val fullName:String){ Civ_V_Vanilla("Civ V - Vanilla"), @@ -26,9 +22,8 @@ class GameParameters { // Default values are the default new game var godMode = false var nuclearWeaponsEnabled = true var religionEnabled = false - - // 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 victoryTypes: ArrayList = arrayListOf() var startingEra = "Ancient era" var isOnlineMultiplayer = false @@ -70,9 +65,7 @@ class GameParameters { // Default values are the default new game if (!nuclearWeaponsEnabled) yield("No nukes") if (religionEnabled) yield("Religion") if (godMode) yield("God mode") - for (victoryType in VictoryType.values()) { - if (victoryType !in victoryTypes) yield("No $victoryType Victory") - } + yield("Enabled Victories: " + victoryTypes.joinToString()) yield(baseRuleset) yield(if (mods.isEmpty()) "no mods" else mods.joinToString(",", "mods=(", ")", 6) ) }.joinToString(prefix = "(", postfix = ")") diff --git a/core/src/com/unciv/models/ruleset/Building.kt b/core/src/com/unciv/models/ruleset/Building.kt index 11dda619d1..51d9c9f685 100644 --- a/core/src/com/unciv/models/ruleset/Building.kt +++ b/core/src/com/unciv/models/ruleset/Building.kt @@ -18,7 +18,7 @@ import kotlin.math.pow class Building : RulesetStatsObject(), INonPerpetualConstruction { - var requiredTech: String? = null + override var requiredTech: String? = null var cost: Int = 0 var maintenance = 0 @@ -535,11 +535,10 @@ class Building : RulesetStatsObject(), INonPerpetualConstruction { } } + // To be replaced with `Only available ` UniqueType.SpaceshipPart -> { if (!civInfo.hasUnique(UniqueType.EnablesConstructionOfSpaceshipParts)) rejectionReasons.add(RejectionReason.RequiresBuildingInSomeCity.toInstance("Apollo project not built!")) - if (civInfo.victoryManager.unconstructedSpaceshipParts()[name] == 0) - rejectionReasons.add(RejectionReason.ReachedBuildCap) } UniqueType.RequiresAnotherBuilding -> { @@ -590,7 +589,7 @@ class Building : RulesetStatsObject(), INonPerpetualConstruction { } UniqueType.HiddenWithoutVictoryType -> { - if (!civInfo.gameInfo.gameParameters.victoryTypes.contains(VictoryType.valueOf(unique.params[0]))) + if (!civInfo.gameInfo.gameParameters.victoryTypes.contains(unique.params[0])) rejectionReasons.add(RejectionReason.HiddenWithoutVictory.toInstance(unique.text)) } } @@ -670,7 +669,7 @@ class Building : RulesetStatsObject(), INonPerpetualConstruction { override fun postBuildEvent(cityConstructions: CityConstructions, boughtWith: Stat?): Boolean { val civInfo = cityConstructions.cityInfo.civInfo - if (hasUnique(UniqueType.SpaceshipPart)) { + if (civInfo.gameInfo.spaceResources.contains(name)) { civInfo.victoryManager.currentsSpaceshipParts.add(name, 1) return true } diff --git a/core/src/com/unciv/models/ruleset/Era.kt b/core/src/com/unciv/models/ruleset/Era.kt index 4df2286776..d2b7950c1d 100644 --- a/core/src/com/unciv/models/ruleset/Era.kt +++ b/core/src/com/unciv/models/ruleset/Era.kt @@ -3,9 +3,9 @@ package com.unciv.models.ruleset import com.badlogic.gdx.graphics.Color import com.unciv.logic.civilization.CityStateType import com.unciv.logic.civilization.diplomacy.RelationshipLevel +import com.unciv.models.ruleset.unique.IHasUniques import com.unciv.models.ruleset.unique.Unique import com.unciv.models.ruleset.unique.UniqueTarget -import com.unciv.models.stats.INamed import com.unciv.ui.utils.colorFromRGB class Era : RulesetObject(), IHasUniques { diff --git a/core/src/com/unciv/models/ruleset/Nation.kt b/core/src/com/unciv/models/ruleset/Nation.kt index fdc33b95b6..7acdf00b4f 100644 --- a/core/src/com/unciv/models/ruleset/Nation.kt +++ b/core/src/com/unciv/models/ruleset/Nation.kt @@ -14,15 +14,6 @@ import com.unciv.ui.civilopedia.FormattedLine import com.unciv.ui.utils.Fonts import com.unciv.ui.utils.colorFromRGB -enum class VictoryType { - Neutral, - Cultural, - Diplomatic, - Domination, - Scientific, - Time, -} - class Nation : RulesetObject() { var leaderName = "" fun getLeaderDisplayName() = if (isCityState()) name @@ -30,7 +21,7 @@ class Nation : RulesetObject() { val style = "" var cityStateType: CityStateType? = null - var preferredVictoryType: VictoryType = VictoryType.Neutral + var preferredVictoryType: String = Constants.neutralVictoryType var declaringWar = "" var attacked = "" var defeated = "" diff --git a/core/src/com/unciv/models/ruleset/Ruleset.kt b/core/src/com/unciv/models/ruleset/Ruleset.kt index 7db7ba711a..a115d91931 100644 --- a/core/src/com/unciv/models/ruleset/Ruleset.kt +++ b/core/src/com/unciv/models/ruleset/Ruleset.kt @@ -16,6 +16,7 @@ import com.unciv.models.ruleset.tile.Terrain import com.unciv.models.ruleset.tile.TerrainType import com.unciv.models.ruleset.tile.TileImprovement import com.unciv.models.ruleset.tile.TileResource +import com.unciv.models.ruleset.unique.IHasUniques import com.unciv.models.ruleset.unique.Unique import com.unciv.models.ruleset.unique.UniqueTarget import com.unciv.models.ruleset.unique.UniqueType @@ -92,6 +93,7 @@ class Ruleset { val units = LinkedHashMap() val unitPromotions = LinkedHashMap() val unitTypes = LinkedHashMap() + var victories = LinkedHashMap() val mods = LinkedHashSet() var modOptions = ModOptions() @@ -132,6 +134,7 @@ class Ruleset { unitPromotions.putAll(ruleset.unitPromotions) units.putAll(ruleset.units) unitTypes.putAll(ruleset.unitTypes) + victories.putAll(ruleset.victories) for (unitToRemove in ruleset.modOptions.unitsToRemove) units.remove(unitToRemove) modOptions.uniques.addAll(ruleset.modOptions.uniques) modOptions.constants.merge(ruleset.modOptions.constants) @@ -159,6 +162,7 @@ class Ruleset { unitPromotions.clear() units.clear() unitTypes.clear() + victories.clear() } fun allRulesetObjects(): Sequence = @@ -181,6 +185,7 @@ class Ruleset { unitPromotions.values.asSequence() + units.values.asSequence() + unitTypes.values.asSequence() + // Victories is only INamed fun allIHasUniques(): Sequence = allRulesetObjects() + sequenceOf(modOptions) @@ -258,7 +263,7 @@ class Ruleset { // Setup this branch branch.requires = ArrayList() branch.branch = branch - for (victoryType in VictoryType.values()) { + for (victoryType in victories.values) { if (victoryType.name !in branch.priorities.keys) { branch.priorities[victoryType.name] = 0 } @@ -306,6 +311,33 @@ class Ruleset { if (globalUniquesFile.exists()) { globalUniques = jsonParser.getFromJson(GlobalUniques::class.java, globalUniquesFile) } + + val victoryTypesFiles = folderHandle.child("VictoryTypes.json") + if (victoryTypesFiles.exists()) { + victories += createHashmap(jsonParser.getFromJson(Array::class.java, victoryTypesFiles)) + } + + + + // Add objects that might not be present in base ruleset mods, but are required + if (modOptions.isBaseRuleset) { + // This one should be temporary + if (unitTypes.isEmpty()) { + unitTypes.putAll(RulesetCache.getVanillaRuleset().unitTypes) + } + + // These should be permanent + if (ruinRewards.isEmpty()) { + ruinRewards.putAll(RulesetCache.getVanillaRuleset().ruinRewards) + } + if (globalUniques.uniques.isEmpty()) { + globalUniques = RulesetCache.getVanillaRuleset().globalUniques + } + // If we have no victories, add all the default victories + if (victories.isEmpty()) { + victories.putAll(RulesetCache.getVanillaRuleset().victories) + } + } val gameBasicsLoadTime = System.currentTimeMillis() - gameBasicsStartTime if (printOutput) println("Loading ruleset - " + gameBasicsLoadTime + "ms") @@ -354,7 +386,7 @@ class Ruleset { forOptionsPopup: Boolean ) { val name = if (uniqueContainer is INamed) uniqueContainer.name else "" - + for (unique in uniqueContainer.uniqueObjects) { val errors = checkUnique( unique, @@ -771,6 +803,18 @@ class Ruleset { for (unitType in unitTypes.values) { checkUniques(unitType, lines, rulesetSpecific, forOptionsPopup) } + + for (victoryType in victories.values) { + for (requiredUnit in victoryType.requiredSpaceshipParts) + if (!units.contains(requiredUnit)) + lines.add("Victory type ${victoryType.name} requires adding the non-existant unit $requiredUnit to the capital to win!", RulesetErrorSeverity.Warning) + for (milestone in victoryType.milestoneObjects) + if (milestone.type == null) + lines.add("Victory type ${victoryType.name} has milestone ${milestone.uniqueDescription} that is of an unknown type!", RulesetErrorSeverity.Error) + for (victory in victories.values) + if (victory.name != victoryType.name && victory.milestones == victoryType.milestones) + lines.add("Victory types ${victoryType.name} and ${victory.name} have the same requirements!", RulesetErrorSeverity.Warning) + } for (difficulty in difficulties.values) { for (unitName in difficulty.aiCityStateBonusStartingUnits + difficulty.aiMajorCivBonusStartingUnits + difficulty.playerBonusStartingUnits) @@ -878,7 +922,6 @@ object RulesetCache : HashMap() { this[optionalBaseRuleset]!! else getVanillaRuleset() - val loadedMods = mods.asSequence() .filter { containsKey(it) } .map { this[it]!! } @@ -896,19 +939,9 @@ object RulesetCache : HashMap() { } newRuleset.updateBuildingCosts() // only after we've added all the mods can we calculate the building costs - // This one should be temporary - if (newRuleset.unitTypes.isEmpty()) { - newRuleset.unitTypes.putAll(getVanillaRuleset().unitTypes) - } - - // These should be permanent - if (newRuleset.ruinRewards.isEmpty()) { - newRuleset.ruinRewards.putAll(getVanillaRuleset().ruinRewards) - } - if (newRuleset.globalUniques.uniques.isEmpty()) { - newRuleset.globalUniques = getVanillaRuleset().globalUniques - } - + println(optionalBaseRuleset) + println(newRuleset.victories) + return newRuleset } diff --git a/core/src/com/unciv/models/ruleset/RulesetObject.kt b/core/src/com/unciv/models/ruleset/RulesetObject.kt index 42849a273a..7aba378231 100644 --- a/core/src/com/unciv/models/ruleset/RulesetObject.kt +++ b/core/src/com/unciv/models/ruleset/RulesetObject.kt @@ -1,12 +1,13 @@ package com.unciv.models.ruleset +import com.unciv.models.ruleset.unique.IHasUniques import com.unciv.models.ruleset.unique.Unique import com.unciv.models.stats.INamed import com.unciv.models.stats.NamedStats import com.unciv.ui.civilopedia.FormattedLine import com.unciv.ui.civilopedia.ICivilopediaText -interface IRulesetObject:INamed, IHasUniques, ICivilopediaText +interface IRulesetObject: INamed, IHasUniques, ICivilopediaText abstract class RulesetObject: IRulesetObject { override var name = "" diff --git a/core/src/com/unciv/models/ruleset/Victory.kt b/core/src/com/unciv/models/ruleset/Victory.kt new file mode 100644 index 0000000000..a12a98a4cb --- /dev/null +++ b/core/src/com/unciv/models/ruleset/Victory.kt @@ -0,0 +1,240 @@ +package com.unciv.models.ruleset + +import com.badlogic.gdx.graphics.Color +import com.badlogic.gdx.scenes.scene2d.ui.TextButton +import com.unciv.Constants +import com.unciv.models.stats.INamed +import com.unciv.logic.civilization.CivilizationInfo +import com.unciv.models.Counter +import com.unciv.models.translations.getPlaceholderParameters +import com.unciv.models.translations.getPlaceholderText +import com.unciv.ui.utils.toTextButton + + +enum class MilestoneType(val text: String) { + BuiltBuilding("Build [building]"), + BuildingBuiltGlobally("[building] build globally"), + AddedSSPartsInCapital("Add all [comment] in capital"), + DestroyAllPlayers("Destroy all players"), + CaptureAllCapitals("Capture all capitals"), + CompletePolicyBranches("Complete [amount] Policy branches"), + WinDiplomaticVote("Win diplomatic vote"), + ScoreAfterTimeOut("Have highest score after max turns"), +} + +enum class CompletionStatus { + Completed, + Partially, + Incomplete +} + +enum class ThingToFocus { + Production, + Gold, + Science, + Culture, + Military, + CityStates, + Score, +} + +class Victory : INamed { + override var name = "" + val victoryScreenHeader = "Do things to win!" + val hiddenInVictoryScreen = false + // Things to do to win + // Needs to be ordered, as the milestones are supposed to be obtained in a specific order + val milestones = ArrayList() + val milestoneObjects by lazy { milestones.map { Milestone(it, this) }} + val requiredSpaceshipParts = ArrayList() + + val requiredSpaceshipPartsAsCounter by lazy { + val parts = Counter() + for (spaceshipPart in requiredSpaceshipParts) + parts.add(spaceshipPart, 1) + parts + } + + val victoryString = "Your civilization stands above all others! The exploits of your people shall be remembered until the end of civilization itself!" + val defeatString = "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!" + + fun enablesMaxTurns(): Boolean = milestoneObjects.any { it.type == MilestoneType.ScoreAfterTimeOut } + fun getThingsToFocus(civInfo: CivilizationInfo): Set = milestoneObjects + .filter { !it.hasBeenCompletedBy(civInfo) } + .map { it.getThingToFocus(civInfo) } + .toSet() +} + +class Milestone(val uniqueDescription: String, private val accompaniedVictory: Victory) { + + val type: MilestoneType? = MilestoneType.values().firstOrNull { uniqueDescription.getPlaceholderText() == it.text.getPlaceholderText() } + val params by lazy { uniqueDescription.getPlaceholderParameters() } + + fun getIncompleteSpaceshipParts(civInfo: CivilizationInfo): Counter { + val incompleteSpaceshipParts = accompaniedVictory.requiredSpaceshipPartsAsCounter.clone() + incompleteSpaceshipParts.remove(civInfo.victoryManager.currentsSpaceshipParts) + return incompleteSpaceshipParts + } + + fun hasBeenCompletedBy(civInfo: CivilizationInfo): Boolean { + return when (type!!) { + MilestoneType.BuiltBuilding -> + civInfo.cities.any { it.cityConstructions.builtBuildings.contains(params[0])} + MilestoneType.AddedSSPartsInCapital -> { + getIncompleteSpaceshipParts(civInfo).isEmpty() + } + MilestoneType.DestroyAllPlayers -> + civInfo.gameInfo.getAliveMajorCivs() == listOf(civInfo) + MilestoneType.CaptureAllCapitals -> + civInfo.originalMajorCapitalsOwned() == civInfo.gameInfo.civilizations.count { it.isMajorCiv() } + MilestoneType.CompletePolicyBranches -> + civInfo.policies.completedBranches.size >= params[0].toInt() + MilestoneType.BuildingBuiltGlobally -> civInfo.gameInfo.getCities().any { + it.cityConstructions.builtBuildings.contains(params[0]) + } + MilestoneType.WinDiplomaticVote -> civInfo.victoryManager.hasEverWonDiplomaticVote + MilestoneType.ScoreAfterTimeOut -> { + civInfo.gameInfo.turns >= civInfo.gameInfo.gameParameters.maxTurns + && civInfo == civInfo.gameInfo.civilizations.maxByOrNull { it.calculateTotalScore() } + } + } + } + + private fun getMilestoneButton(text: String, achieved: Boolean): TextButton { + val textButton = text.toTextButton() + if (achieved) textButton.color = Color.GREEN + else textButton.color = Color.GRAY + return textButton + } + + fun getVictoryScreenButtonHeaderText(completed: Boolean, civInfo: CivilizationInfo): String { + return when (type!!) { + MilestoneType.BuildingBuiltGlobally, MilestoneType.WinDiplomaticVote, + MilestoneType.ScoreAfterTimeOut, MilestoneType.BuiltBuilding -> + uniqueDescription + MilestoneType.CompletePolicyBranches -> { + val amountToDo = params[0] + val amountDone = + if (completed) amountToDo + else civInfo.getCompletedPolicyBranchesCount() + "{$uniqueDescription} ($amountDone/$amountToDo)" + } + MilestoneType.CaptureAllCapitals -> { + val amountToDo = civInfo.gameInfo.civilizations.count { it.isMajorCiv() } + val amountDone = + if (completed) amountToDo + else civInfo.originalMajorCapitalsOwned() + "{$uniqueDescription} ($amountDone/$amountToDo)" + } + MilestoneType.DestroyAllPlayers -> { + val amountToDo = civInfo.gameInfo.civilizations.count { it.isMajorCiv() } - 1 // Don't count yourself + val amountDone = + if (completed) amountToDo + else amountToDo - (civInfo.gameInfo.getAliveMajorCivs().filter { it != civInfo }.count()) + "{$uniqueDescription} ($amountDone/$amountToDo)" + } + MilestoneType.AddedSSPartsInCapital -> { + val completeSpaceshipParts = civInfo.victoryManager.currentsSpaceshipParts + val incompleteSpaceshipParts = accompaniedVictory.requiredSpaceshipPartsAsCounter.clone() + val amountToDo = incompleteSpaceshipParts.sumValues() + incompleteSpaceshipParts.remove(completeSpaceshipParts) + + val amountDone = amountToDo - incompleteSpaceshipParts.sumValues() + + "{$uniqueDescription} ($amountDone/$amountToDo)" + } + } + } + + fun getVictoryScreenButtons(completionStatus: CompletionStatus, civInfo: CivilizationInfo): List { + val headerButton = getMilestoneButton( + getVictoryScreenButtonHeaderText(completionStatus == CompletionStatus.Completed, civInfo), + completionStatus == CompletionStatus.Completed + ) + if (completionStatus == CompletionStatus.Completed || completionStatus == CompletionStatus.Incomplete) { + // When done or not working on this milestone, only show the header button + return listOf(headerButton) + } + // Otherwise, append the partial buttons of each step + val buttons = mutableListOf(headerButton) + when (type) { + // No extra buttons necessary + MilestoneType.BuiltBuilding, MilestoneType.BuildingBuiltGlobally, + MilestoneType.ScoreAfterTimeOut, MilestoneType.WinDiplomaticVote -> {} + MilestoneType.AddedSSPartsInCapital -> { + val completedSpaceshipParts = civInfo.victoryManager.currentsSpaceshipParts + val incompleteSpaceshipParts = getIncompleteSpaceshipParts(civInfo) + + for (part in completedSpaceshipParts) { + repeat(part.value) { + buttons.add(getMilestoneButton(part.key, true)) + } + } + for (part in incompleteSpaceshipParts) { + repeat(part.value) { + buttons.add(getMilestoneButton(part.key, false)) + } + } + } + + MilestoneType.DestroyAllPlayers -> { + for (civ in civInfo.gameInfo.civilizations.filter { it != civInfo && it.isMajorCiv() && !it.isAlive() }) { + buttons.add(getMilestoneButton("Destroy [${civ.civName}]", true)) + } + for (civ in civInfo.gameInfo.getAliveMajorCivs().filter { it != civInfo}) { + buttons.add(getMilestoneButton("Destroy [${civ.civName}]", false)) + } + } + + MilestoneType.CaptureAllCapitals -> { + for (city in civInfo.gameInfo.getAliveMajorCivs().mapNotNull { + civ -> civ.cities.firstOrNull { it.isOriginalCapital && it.foundingCiv == civ.civName } + } + ) { + buttons.add(getMilestoneButton("Capture [${city.name}]", false)) + } + } + + MilestoneType.CompletePolicyBranches -> { + for (branch in civInfo.gameInfo.ruleSet.policyBranches.values) { + val finisher = branch.policies.last().name + buttons.add(getMilestoneButton(finisher, civInfo.policies.isAdopted(finisher))) + } + } + } + return buttons + } + + fun getThingToFocus(civInfo: CivilizationInfo): ThingToFocus { + val ruleset = civInfo.gameInfo.ruleSet + return when (type!!) { + MilestoneType.BuiltBuilding -> { + val building = ruleset.buildings[params[0]]!! + if (building.requiredTech != null && !civInfo.tech.isResearched(building.requiredTech!!)) ThingToFocus.Science +// if (building.hasUnique(UniqueType.Unbuildable)) Stat.Gold // Temporary, should be replaced with whatever is required to buy + ThingToFocus.Production + } + MilestoneType.BuildingBuiltGlobally -> { + val building = ruleset.buildings[params[0]]!! + if (building.requiredTech != null && !civInfo.tech.isResearched(building.requiredTech!!)) ThingToFocus.Science +// if (building.hasUnique(UniqueType.Unbuildable)) ThingToFocus.Gold + ThingToFocus.Production + } + MilestoneType.AddedSSPartsInCapital -> { + val constructions = + getIncompleteSpaceshipParts(civInfo).keys.map { + if (it in ruleset.buildings) + ruleset.buildings[it]!! + else ruleset.units[it]!! + } + if (constructions.any { it.requiredTech != null && !civInfo.tech.isResearched(it.requiredTech!!) } ) ThingToFocus.Science +// if (constructions.any { it.hasUnique(UniqueType.Unbuildable) } ) Stat.Gold + ThingToFocus.Production + } + MilestoneType.DestroyAllPlayers, MilestoneType.CaptureAllCapitals -> ThingToFocus.Military + MilestoneType.CompletePolicyBranches -> ThingToFocus.Culture + MilestoneType.WinDiplomaticVote -> ThingToFocus.CityStates + MilestoneType.ScoreAfterTimeOut -> ThingToFocus.Score + } + } +} \ No newline at end of file diff --git a/core/src/com/unciv/models/ruleset/IHasUniques.kt b/core/src/com/unciv/models/ruleset/unique/IHasUniques.kt similarity index 86% rename from core/src/com/unciv/models/ruleset/IHasUniques.kt rename to core/src/com/unciv/models/ruleset/unique/IHasUniques.kt index ed895c7ca5..c8ec677c4e 100644 --- a/core/src/com/unciv/models/ruleset/IHasUniques.kt +++ b/core/src/com/unciv/models/ruleset/unique/IHasUniques.kt @@ -1,9 +1,4 @@ -package com.unciv.models.ruleset - -import com.unciv.models.ruleset.unique.StateForConditionals -import com.unciv.models.ruleset.unique.Unique -import com.unciv.models.ruleset.unique.UniqueTarget -import com.unciv.models.ruleset.unique.UniqueType +package com.unciv.models.ruleset.unique /** * Common interface for all 'ruleset objects' that have Uniques, like BaseUnit, Nation, etc. diff --git a/core/src/com/unciv/models/ruleset/unique/UniqueParameterType.kt b/core/src/com/unciv/models/ruleset/unique/UniqueParameterType.kt index 61052409ce..061610510a 100644 --- a/core/src/com/unciv/models/ruleset/unique/UniqueParameterType.kt +++ b/core/src/com/unciv/models/ruleset/unique/UniqueParameterType.kt @@ -5,7 +5,6 @@ import com.unciv.models.metadata.BaseRuleset import com.unciv.models.ruleset.BeliefType import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.RulesetCache -import com.unciv.models.ruleset.VictoryType import com.unciv.models.ruleset.tile.ResourceType import com.unciv.models.stats.Stat import com.unciv.models.translations.TranslationFileWriter // for Kdoc only @@ -429,7 +428,7 @@ enum class UniqueParameterType( parameterText: String, ruleset: Ruleset ): UniqueType.UniqueComplianceErrorSeverity? { - return if (parameterText in VictoryType.values().map { it.name }) null + return if (parameterText in ruleset.victories) null else UniqueType.UniqueComplianceErrorSeverity.RulesetInvariant } }, diff --git a/core/src/com/unciv/models/ruleset/unique/UniqueTriggerActivation.kt b/core/src/com/unciv/models/ruleset/unique/UniqueTriggerActivation.kt index 2c143b390a..8b101bbcac 100644 --- a/core/src/com/unciv/models/ruleset/unique/UniqueTriggerActivation.kt +++ b/core/src/com/unciv/models/ruleset/unique/UniqueTriggerActivation.kt @@ -6,8 +6,8 @@ import com.unciv.logic.city.CityInfo import com.unciv.logic.civilization.* import com.unciv.logic.map.MapUnit import com.unciv.logic.map.TileInfo +import com.unciv.models.ruleset.ThingToFocus import com.unciv.models.ruleset.unique.UniqueType.* -import com.unciv.models.ruleset.VictoryType import com.unciv.models.stats.Stat import com.unciv.models.translations.fillPlaceholders import com.unciv.models.translations.hasPlaceholderParameters @@ -144,14 +144,12 @@ object UniqueTriggerActivation { if (greatPeople.isEmpty()) return false var greatPerson = greatPeople.random() - val preferredVictoryType = civInfo.victoryType() - - if (preferredVictoryType == VictoryType.Cultural) { + if (civInfo.wantsToFocusOn(ThingToFocus.Culture)) { val culturalGP = greatPeople.firstOrNull { it.uniques.contains("Great Person - [Culture]") } if (culturalGP != null) greatPerson = culturalGP } - if (preferredVictoryType == VictoryType.Scientific) { + if (civInfo.wantsToFocusOn(ThingToFocus.Science)) { val scientificGP = greatPeople.firstOrNull { it.uniques.contains("Great Person - [Science]") } if (scientificGP != null) greatPerson = scientificGP @@ -459,7 +457,7 @@ object UniqueTriggerActivation { if (!civ.isBarbarian() && !civ.isSpectator()) civ.addFlag( CivFlags.TurnsTillNextDiplomaticVote.name, - civInfo.getTurnsBetweenDiplomaticVotings() + civInfo.getTurnsBetweenDiplomaticVotes() ) if (notification != null) civInfo.addNotification(notification, NotificationIcon.Diplomacy) diff --git a/core/src/com/unciv/models/ruleset/unique/UniqueType.kt b/core/src/com/unciv/models/ruleset/unique/UniqueType.kt index 523175206e..736957b4c6 100644 --- a/core/src/com/unciv/models/ruleset/unique/UniqueType.kt +++ b/core/src/com/unciv/models/ruleset/unique/UniqueType.kt @@ -496,7 +496,7 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags: CannotBeBarbarian("Never appears as a Barbarian unit", UniqueTarget.Unit, flags = UniqueFlag.setOfHiddenToUsers), ReligiousUnit("Religious Unit", UniqueTarget.Unit), - SpaceshipPart("Spaceship part", UniqueTarget.Unit, UniqueTarget.Building), // Usage for buildings is deprecated + SpaceshipPart("Spaceship part", UniqueTarget.Unit, UniqueTarget.Building), // Should be deprecated in the near future AddInCapital("Can be added to [comment] in the Capital", UniqueTarget.Unit), diff --git a/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt b/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt index 65603888f0..9a0679e8d7 100644 --- a/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt +++ b/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt @@ -35,7 +35,7 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction { var interceptRange = 0 lateinit var unitType: String fun getType() = ruleset.unitTypes[unitType]!! - var requiredTech: String? = null + override var requiredTech: String? = null private var requiredResource: String? = null override fun getUniqueTarget() = UniqueTarget.Unit diff --git a/core/src/com/unciv/models/ruleset/unit/UnitType.kt b/core/src/com/unciv/models/ruleset/unit/UnitType.kt index 59cea5ded3..42d7c38d82 100644 --- a/core/src/com/unciv/models/ruleset/unit/UnitType.kt +++ b/core/src/com/unciv/models/ruleset/unit/UnitType.kt @@ -1,10 +1,7 @@ package com.unciv.models.ruleset.unit -import com.unciv.models.ruleset.IHasUniques import com.unciv.models.ruleset.RulesetObject -import com.unciv.models.ruleset.unique.Unique import com.unciv.models.ruleset.unique.UniqueTarget -import com.unciv.models.stats.INamed enum class UnitLayer { // The layer in which the unit moves diff --git a/core/src/com/unciv/models/simulation/Simulation.kt b/core/src/com/unciv/models/simulation/Simulation.kt index 7016aeea08..e86892f63f 100644 --- a/core/src/com/unciv/models/simulation/Simulation.kt +++ b/core/src/com/unciv/models/simulation/Simulation.kt @@ -1,9 +1,9 @@ package com.unciv.models.simulation import com.unciv.Constants +import com.unciv.UncivGame import com.unciv.logic.GameInfo import com.unciv.logic.GameStarter -import com.unciv.models.ruleset.VictoryType import com.unciv.models.metadata.GameSetupInfo import com.unciv.ui.crashhandling.crashHandlingThread import kotlin.time.Duration @@ -23,7 +23,7 @@ class Simulation( private var endTime: Long = 0 var steps = ArrayList() var winRate = mutableMapOf() - private var winRateByVictory = HashMap>() + private var winRateByVictory = HashMap>() private var avgSpeed = 0f private var avgDuration: Duration = Duration.ZERO private var totalTurns = 0 @@ -35,7 +35,7 @@ class Simulation( for (civ in civilizations) { this.winRate[civ] = MutableInt(0) winRateByVictory[civ] = mutableMapOf() - for (victory in VictoryType.values()) + for (victory in UncivGame.Current.gameInfo.ruleSet.victories.keys) winRateByVictory[civ]!![victory] = MutableInt(0) } } @@ -118,7 +118,7 @@ class Simulation( outString += "\n$civ:\n" val wins = winRate[civ]!!.value * 100 / max(steps.size, 1) outString += "$wins% total win rate \n" - for (victory in VictoryType.values()) { + for (victory in UncivGame.Current.gameInfo.ruleSet.victories.keys) { val winsVictory = winRateByVictory[civ]!![victory]!!.value * 100 / max(winRate[civ]!!.value, 1) outString += "$victory: $winsVictory% " } diff --git a/core/src/com/unciv/models/simulation/SimulationStep.kt b/core/src/com/unciv/models/simulation/SimulationStep.kt index 6855d6fd47..a48258e5f5 100644 --- a/core/src/com/unciv/models/simulation/SimulationStep.kt +++ b/core/src/com/unciv/models/simulation/SimulationStep.kt @@ -4,7 +4,7 @@ import com.unciv.logic.GameInfo class SimulationStep (gameInfo: GameInfo) { var turns = gameInfo.turns - var victoryType = gameInfo.currentPlayerCiv.victoryManager.hasWonVictoryType() + var victoryType = gameInfo.currentPlayerCiv.victoryManager.getVictoryTypeAchieved() var winner: String? = null val currentPlayer = gameInfo.currentPlayer // val durationString: String = formatDuration(Duration.ofMillis(System.currentTimeMillis() - startTime)) diff --git a/core/src/com/unciv/models/translations/TranslationFileWriter.kt b/core/src/com/unciv/models/translations/TranslationFileWriter.kt index c8214686a9..dc51cd9712 100644 --- a/core/src/com/unciv/models/translations/TranslationFileWriter.kt +++ b/core/src/com/unciv/models/translations/TranslationFileWriter.kt @@ -377,7 +377,9 @@ object TranslationFileWriter { when { // Promotion names are not uniques but since we did the "[unitName] ability" // they need the "parameters" treatment too - (field.name == "uniques" || field.name == "promotions") && (fieldValue is java.util.AbstractCollection<*>) -> + // Same for victory milestones + (field.name == "uniques" || field.name == "promotions" || field.name == "milestones") + && (fieldValue is java.util.AbstractCollection<*>) -> for (item in fieldValue) if (item is String) submitString(item, Unique(item)) else serializeElement(item!!) fieldValue is java.util.AbstractCollection<*> -> @@ -456,6 +458,7 @@ object TranslationFileWriter { "UnitPromotions" -> emptyArray().javaClass "Units" -> emptyArray().javaClass "UnitTypes" -> emptyArray().javaClass + "VictoryTypes" -> emptyArray().javaClass else -> this.javaClass // dummy value } } diff --git a/core/src/com/unciv/models/translations/Translations.kt b/core/src/com/unciv/models/translations/Translations.kt index 308ed7e2d5..5e24e9489a 100644 --- a/core/src/com/unciv/models/translations/Translations.kt +++ b/core/src/com/unciv/models/translations/Translations.kt @@ -322,6 +322,10 @@ fun String.tr(): String { return fullyTranslatedString } + if (contains('{')) { // Translating partial sentences + return curlyBraceRegex.replace(this) { it.groups[1]!!.value.tr() } + } + // There might still be optimization potential here! if (contains('[')) { // Placeholders! /** @@ -371,9 +375,6 @@ fun String.tr(): String { return languageSpecificPlaceholder // every component is already translated } - if (contains('{')) { // sentence - return curlyBraceRegex.replace(this) { it.groups[1]!!.value.tr() } - } if (Stats.isStats(this)) return Stats.parse(this).toString() diff --git a/core/src/com/unciv/ui/civilopedia/CivilopediaScreen.kt b/core/src/com/unciv/ui/civilopedia/CivilopediaScreen.kt index 8eadf582a1..401adc56a2 100644 --- a/core/src/com/unciv/ui/civilopedia/CivilopediaScreen.kt +++ b/core/src/com/unciv/ui/civilopedia/CivilopediaScreen.kt @@ -8,6 +8,7 @@ import com.badlogic.gdx.scenes.scene2d.ui.* import com.unciv.Constants import com.unciv.UncivGame import com.unciv.models.ruleset.* +import com.unciv.models.ruleset.unique.IHasUniques import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.stats.INamed import com.unciv.models.translations.tr @@ -177,13 +178,13 @@ class CivilopediaScreen( val religionEnabled = if (game.isGameInfoInitialized()) game.gameInfo.isReligionEnabled() else ruleset.beliefs.isNotEmpty() val victoryTypes = if (game.isGameInfoInitialized()) game.gameInfo.gameParameters.victoryTypes - else VictoryType.values().toList() + else listOf() fun shouldBeDisplayed(obj: IHasUniques): Boolean { return when { obj.hasUnique(UniqueType.HiddenFromCivilopedia) -> false (!religionEnabled && obj.hasUnique(UniqueType.HiddenWithoutReligion)) -> false - obj.getMatchingUniques(UniqueType.HiddenWithoutVictoryType).any { !victoryTypes.contains(VictoryType.valueOf(it.params[0])) } -> false + obj.getMatchingUniques(UniqueType.HiddenWithoutVictoryType).any { !victoryTypes.contains(it.params[0]) } -> false else -> true } } diff --git a/core/src/com/unciv/ui/newgamescreen/GameOptionsTable.kt b/core/src/com/unciv/ui/newgamescreen/GameOptionsTable.kt index 7d6cb63921..8245e14ad1 100644 --- a/core/src/com/unciv/ui/newgamescreen/GameOptionsTable.kt +++ b/core/src/com/unciv/ui/newgamescreen/GameOptionsTable.kt @@ -5,7 +5,6 @@ import com.unciv.UncivGame import com.unciv.logic.civilization.CityStateType import com.unciv.models.metadata.GameSpeed import com.unciv.models.ruleset.RulesetCache -import com.unciv.models.ruleset.VictoryType import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.translations.tr import com.unciv.ui.audio.MusicMood @@ -135,7 +134,8 @@ class GameOptionsTable( } private fun Table.addMaxTurnsSlider(): UncivSlider? { - if (!gameParameters.victoryTypes.contains(VictoryType.Time)) return null + if (gameParameters.victoryTypes.none { ruleset.victories[it]?.enablesMaxTurns() == true }) + return null add("{Max Turns}:".toLabel()).left().expandX() val slider = UncivSlider(250f, 1500f, 50f) { @@ -229,25 +229,23 @@ class GameOptionsTable( add("{Victory Conditions}:".toLabel()).colspan(2).row() // Create a checkbox for each VictoryType existing - var i = 0 val victoryConditionsTable = Table().apply { defaults().pad(5f) } - for (victoryType in VictoryType.values()) { - if (victoryType == VictoryType.Neutral) continue - val victoryCheckbox = victoryType.name.toCheckBox(gameParameters.victoryTypes.contains(victoryType)) { + for ((i, victoryType) in ruleset.victories.values.withIndex()) { + val victoryCheckbox = victoryType.name.toCheckBox(gameParameters.victoryTypes.contains(victoryType.name)) { // If the checkbox is checked, adds the victoryTypes else remove it if (it) { - gameParameters.victoryTypes.add(victoryType) + gameParameters.victoryTypes.add(victoryType.name) } else { - gameParameters.victoryTypes.remove(victoryType) + gameParameters.victoryTypes.remove(victoryType.name) } // show or hide the max turns select box - if (victoryType == VictoryType.Time) + if (victoryType.enablesMaxTurns()) update() } victoryCheckbox.name = victoryType.name victoryCheckbox.isDisabled = locked victoryConditionsTable.add(victoryCheckbox).left() - if (++i % 2 == 0) victoryConditionsTable.row() + if ((i + 1) % 2 == 0) victoryConditionsTable.row() } add(victoryConditionsTable).colspan(2).row() } diff --git a/core/src/com/unciv/ui/newgamescreen/NewGameScreen.kt b/core/src/com/unciv/ui/newgamescreen/NewGameScreen.kt index 92c0c3c22a..860f7076e0 100644 --- a/core/src/com/unciv/ui/newgamescreen/NewGameScreen.kt +++ b/core/src/com/unciv/ui/newgamescreen/NewGameScreen.kt @@ -41,6 +41,10 @@ class NewGameScreen( init { updateRuleset() // must come before playerPickerTable so mod nations from fromSettings // Has to be initialized before the mapOptionsTable, since the mapOptionsTable refers to it on init + + if (gameSetupInfo.gameParameters.victoryTypes.isEmpty()) + gameSetupInfo.gameParameters.victoryTypes.addAll(ruleset.victories.keys) + playerPickerTable = PlayerPickerTable( this, gameSetupInfo.gameParameters, if (isNarrowerThan4to3()) stage.width - 20f else 0f @@ -92,6 +96,14 @@ class NewGameScreen( noHumanPlayersPopup.open() return@onClick } + + if (gameSetupInfo.gameParameters.victoryTypes.isEmpty()) { + val noVictoryTypesPopup = Popup(this) + noVictoryTypesPopup.addGoodSizedLabel("No victory conditions were selected!".tr()).row() + noVictoryTypesPopup.addCloseButton() + noVictoryTypesPopup.open() + return@onClick + } Gdx.input.inputProcessor = null // remove input processing - nothing will be clicked! diff --git a/core/src/com/unciv/ui/overviewscreen/WonderOverviewTable.kt b/core/src/com/unciv/ui/overviewscreen/WonderOverviewTable.kt index 3608a157a7..b614081d4d 100644 --- a/core/src/com/unciv/ui/overviewscreen/WonderOverviewTable.kt +++ b/core/src/com/unciv/ui/overviewscreen/WonderOverviewTable.kt @@ -10,7 +10,6 @@ import com.unciv.logic.map.TileInfo import com.unciv.models.ruleset.Building import com.unciv.models.ruleset.Era import com.unciv.models.ruleset.QuestName -import com.unciv.models.ruleset.VictoryType import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.translations.tr import com.unciv.ui.civilopedia.CivilopediaCategories @@ -108,7 +107,7 @@ class WonderOverviewTab( wonder.name in startingObsolete -> false wonder.getMatchingUniques(UniqueType.HiddenWithoutVictoryType) .any { unique -> - !gameInfo.gameParameters.victoryTypes.contains(VictoryType.valueOf(unique.params[0])) + !gameInfo.gameParameters.victoryTypes.contains(unique.params[0]) } -> false else -> wonderEra <= viewerEra } diff --git a/core/src/com/unciv/ui/victoryscreen/VictoryScreen.kt b/core/src/com/unciv/ui/victoryscreen/VictoryScreen.kt index d2f71fdb88..145373e0b4 100644 --- a/core/src/com/unciv/ui/victoryscreen/VictoryScreen.kt +++ b/core/src/com/unciv/ui/victoryscreen/VictoryScreen.kt @@ -2,16 +2,12 @@ package com.unciv.ui.victoryscreen import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.scenes.scene2d.ui.Table -import com.badlogic.gdx.scenes.scene2d.ui.TextButton import com.badlogic.gdx.utils.Align import com.unciv.Constants import com.unciv.logic.civilization.CivilizationInfo -import com.unciv.models.ruleset.Policy -import com.unciv.models.ruleset.VictoryType -import com.unciv.models.translations.getPlaceholderParameters import com.unciv.models.translations.tr import com.unciv.models.metadata.GameSetupInfo -import com.unciv.models.ruleset.unique.UniqueType +import com.unciv.models.ruleset.CompletionStatus import com.unciv.ui.images.ImageGetter import com.unciv.ui.newgamescreen.NewGameScreen import com.unciv.ui.pickerscreens.PickerScreen @@ -22,10 +18,7 @@ class VictoryScreen(val worldScreen: WorldScreen) : PickerScreen() { val gameInfo = worldScreen.gameInfo private val playerCivInfo = worldScreen.viewingCiv - val victoryTypes = gameInfo.gameParameters.victoryTypes - private val scientificVictoryEnabled = victoryTypes.contains(VictoryType.Scientific) - private val culturalVictoryEnabled = victoryTypes.contains(VictoryType.Cultural) - private val dominationVictoryEnabled = victoryTypes.contains(VictoryType.Domination) + val enabledVictoryTypes = gameInfo.gameParameters.victoryTypes private val contentsTable = Table() @@ -35,7 +28,7 @@ class VictoryScreen(val worldScreen: WorldScreen) : PickerScreen() { stage.addActor(difficultyLabel) val tabsTable = Table().apply { defaults().pad(10f) } - val setMyVictoryButton = "Our status".toTextButton().onClick { setMyVictoryTable() } + val setMyVictoryButton = "Our status".toTextButton().onClick { setOurVictoryTable() } if (!playerCivInfo.isSpectator()) tabsTable.add(setMyVictoryButton) val setGlobalVictoryButton = "Global status".toTextButton().onClick { setGlobalVictoryTable() } tabsTable.add(setGlobalVictoryButton) @@ -48,28 +41,27 @@ class VictoryScreen(val worldScreen: WorldScreen) : PickerScreen() { if (playerCivInfo.isSpectator()) setGlobalVictoryTable() else - setMyVictoryTable() + setOurVictoryTable() rightSideButton.isVisible = false var someoneHasWon = false - val playerVictoryType = playerCivInfo.victoryManager.hasWonVictoryType() + val playerVictoryType = playerCivInfo.victoryManager.getVictoryTypeAchieved() if (playerVictoryType != null) { someoneHasWon = true - wonOrLost("You have won a [${playerVictoryType.name}] Victory!") + wonOrLost("You have won a [$playerVictoryType] Victory!", playerVictoryType, true) } for (civ in gameInfo.civilizations.filter { it.isMajorCiv() && it != playerCivInfo }) { - val civVictoryType = civ.victoryManager.hasWonVictoryType() + val civVictoryType = civ.victoryManager.getVictoryTypeAchieved() if (civVictoryType != null) { someoneHasWon = true - val winningCivName = civ.civName - wonOrLost("[$winningCivName] has won a [${civVictoryType.name}] Victory!") + wonOrLost("[${civ.civName}] has won a [$civVictoryType] Victory!", civVictoryType, false) } } if (playerCivInfo.isDefeated()) { - wonOrLost("") + wonOrLost("", null, false) } else if (!someoneHasWon) { setDefaultCloseAction() onBackButtonClicked { game.setWorldScreen() } @@ -77,18 +69,15 @@ 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!" - VictoryType.Diplomatic.name -> "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!" - VictoryType.Neutral.name -> "Your civilization stands above all others! The exploits of your people shall be remembered until the end of civilization itself!" - else -> "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!" - } - + private fun wonOrLost(description: String, victoryType: String?, hasWon: Boolean) { + val endGameMessage = + when { + hasWon && (victoryType == null || victoryType !in gameInfo.ruleSet.victories) -> "Your civilization stands above all others! The exploits of your people shall be remembered until the end of civilization itself!" + victoryType == null || victoryType !in gameInfo.ruleSet.victories -> "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!" + hasWon -> playerCivInfo.gameInfo.ruleSet.victories[victoryType]!!.victoryString + else -> playerCivInfo.gameInfo.ruleSet.victories[victoryType]!!.defeatString + } + descriptionLabel.setText(description.tr() + "\n" + endGameMessage.tr()) rightSideButton.setText("Start new game".tr()) @@ -107,134 +96,81 @@ class VictoryScreen(val worldScreen: WorldScreen) : PickerScreen() { } } - - private fun setMyVictoryTable() { - val myVictoryStatusTable = Table() - myVictoryStatusTable.defaults().pad(10f) - if (scientificVictoryEnabled) myVictoryStatusTable.add("Science victory".toLabel()) - if (culturalVictoryEnabled) myVictoryStatusTable.add("Cultural victory".toLabel()) - if (dominationVictoryEnabled) myVictoryStatusTable.add("Conquest victory".toLabel()) - myVictoryStatusTable.row() - if (scientificVictoryEnabled) myVictoryStatusTable.add(scienceVictoryColumn()) - if (culturalVictoryEnabled) myVictoryStatusTable.add(culturalVictoryColumn()) - if (dominationVictoryEnabled) myVictoryStatusTable.add(conquestVictoryColumn()) - myVictoryStatusTable.row() - if (scientificVictoryEnabled) myVictoryStatusTable.add("Complete all the spaceship parts\n to win!".toLabel()) - if (culturalVictoryEnabled) myVictoryStatusTable.add("Complete 5 policy branches and build\n the Utopia Project to win!".toLabel()) - if (dominationVictoryEnabled) myVictoryStatusTable.add("Destroy all enemies\n to win!".toLabel()) - - contentsTable.clear() - contentsTable.add(myVictoryStatusTable) - } - - private fun scienceVictoryColumn(): Table { - val t = Table() - t.defaults().pad(5f) - t.add(getMilestone("Built Apollo Program", - playerCivInfo.hasUnique(UniqueType.EnablesConstructionOfSpaceshipParts))).row() - - val victoryManager = playerCivInfo.victoryManager - - for (key in victoryManager.requiredSpaceshipParts.keys) - for (i in 0 until victoryManager.requiredSpaceshipParts[key]!!) - t.add(getMilestone(key, victoryManager.currentsSpaceshipParts[key]!! > i)).row() //(key, builtSpaceshipParts) - - return t - } - - private fun culturalVictoryColumn(): Table { - val t = Table() - t.defaults().pad(5f) - for (branch in playerCivInfo.gameInfo.ruleSet.policyBranches.values) { - val finisher = branch.policies.last().name - t.add(getMilestone(finisher, playerCivInfo.policies.isAdopted(finisher))).row() + private fun setOurVictoryTable() { + val ourVictoryStatusTable = Table() + ourVictoryStatusTable.defaults().pad(10f) + val victoriesToShow = gameInfo.getEnabledVictories() + + for (victory in victoriesToShow) { + ourVictoryStatusTable.add("[${victory.key}] Victory".toLabel()) } - return t + ourVictoryStatusTable.row() + + for (victory in victoriesToShow) { + ourVictoryStatusTable.add(getOurVictoryColumn(victory.key)) + } + ourVictoryStatusTable.row() + + for (victory in victoriesToShow) { + ourVictoryStatusTable.add(victory.value.victoryScreenHeader.toLabel()) + } + + contentsTable.clear() + contentsTable.add(ourVictoryStatusTable) } - private fun conquestVictoryColumn(): Table { + private fun getOurVictoryColumn(victory: String): Table { + val victoryObject = gameInfo.ruleSet.victories[victory]!! val table = Table() table.defaults().pad(5f) - for (civ in playerCivInfo.gameInfo.civilizations) { - if (civ.isCurrentPlayer() || !civ.isMajorCiv()) continue - val civName = - if (playerCivInfo.diplomacy.containsKey(civ.civName)) civ.civName - else Constants.unknownNationName - table.add(getMilestone("Destroy [$civName]", civ.isDefeated())).row() + var firstIncomplete: Boolean = true + for (milestone in victoryObject.milestoneObjects) { + val completionStatus = + when { + milestone.hasBeenCompletedBy(playerCivInfo) -> CompletionStatus.Completed + firstIncomplete -> { + firstIncomplete = false + CompletionStatus.Partially + } + else -> CompletionStatus.Incomplete + } + for (button in milestone.getVictoryScreenButtons(completionStatus, playerCivInfo)) { + table.add(button).row() + } } return table } - fun getMilestone(text: String, achieved: Boolean): TextButton { - val textButton = text.toTextButton() - if (achieved) textButton.color = Color.GREEN - else textButton.color = Color.GRAY - return textButton - } - - private fun setGlobalVictoryTable() { val majorCivs = gameInfo.civilizations.filter { it.isMajorCiv() } val globalVictoryTable = Table().apply { defaults().pad(10f) } - - if (scientificVictoryEnabled) globalVictoryTable.add(getGlobalScientificVictoryColumn(majorCivs)) - if (culturalVictoryEnabled) globalVictoryTable.add(getGlobalCulturalVictoryColumn(majorCivs)) - if (dominationVictoryEnabled) globalVictoryTable.add(getGlobalDominationVictoryColumn(majorCivs)) + val victoriesToShow = gameInfo.ruleSet.victories.filter { !it.value.hiddenInVictoryScreen && enabledVictoryTypes.contains(it.key) } + + for (victory in victoriesToShow) { + globalVictoryTable.add(getGlobalVictoryColumn(majorCivs, victory.key)) + } contentsTable.clear() contentsTable.add(globalVictoryTable) } + + private fun getGlobalVictoryColumn(majorCivs: List, victory: String): Table { + val victoryColumn = Table().apply { defaults().pad(10f) } + + victoryColumn.add("[$victory] Victory".toLabel()).row() + victoryColumn.addSeparator() - private fun getGlobalDominationVictoryColumn(majorCivs: List): Table { - val dominationVictoryColumn = Table().apply { defaults().pad(10f) } - - dominationVictoryColumn.add("Undefeated civs".toLabel()).row() - dominationVictoryColumn.addSeparator() - - for (civ in majorCivs.filter { !it.isDefeated() }) - dominationVictoryColumn.add(getCivGroup(civ, "", playerCivInfo)).fillX().row() - - for (civ in majorCivs.filter { it.isDefeated() }) - dominationVictoryColumn.add(getCivGroup(civ, "", playerCivInfo)).fillX().row() - - return dominationVictoryColumn - } - - private fun getGlobalCulturalVictoryColumn(majorCivs: List): Table { - val policyVictoryColumn = Table().apply { defaults().pad(10f) } - policyVictoryColumn.add("Branches completed".toLabel()).row() - policyVictoryColumn.addSeparator() - - data class CivToBranchesCompleted(val civ: CivilizationInfo, val branchesCompleted: Int) - - val civsToBranchesCompleted = majorCivs.map { - CivToBranchesCompleted(it, it.policies.adoptedPolicies.count { pol -> Policy.isBranchCompleteByName(pol) }) - }.sortedByDescending { it.branchesCompleted } - - for (entry in civsToBranchesCompleted) { - val civToBranchesHaveCompleted = getCivGroup(entry.civ, " - " + entry.branchesCompleted, playerCivInfo) - policyVictoryColumn.add(civToBranchesHaveCompleted).fillX().row() - } - return policyVictoryColumn - } - - private fun getGlobalScientificVictoryColumn(majorCivs: List): Table { - val scientificVictoryColumn = Table().apply { defaults().pad(10f) } - scientificVictoryColumn.add("Spaceship parts remaining".toLabel()).row() - scientificVictoryColumn.addSeparator() - - data class civToSpaceshipPartsRemaining(val civ: CivilizationInfo, val partsRemaining: Int) - - val civsToPartsRemaining = majorCivs.map { - civToSpaceshipPartsRemaining(it, - it.victoryManager.spaceshipPartsRemaining()) + for (civ in majorCivs.filter { !it.isDefeated() }.sortedByDescending { it.victoryManager.amountMilestonesCompleted(victory) }) { + val buttonText = civ.victoryManager.getNextMilestone(victory)?.getVictoryScreenButtonHeaderText(false, civ) ?: "Done!" + victoryColumn.add(getCivGroup(civ, "\n" + buttonText.tr(), playerCivInfo)).fillX().row() } - for (entry in civsToPartsRemaining) { - val civToPartsBeRemaining = (getCivGroup(entry.civ, " - " + entry.partsRemaining, playerCivInfo)) - scientificVictoryColumn.add(civToPartsBeRemaining).fillX().row() + for (civ in majorCivs.filter { it.isDefeated() }.sortedByDescending { it.victoryManager.amountMilestonesCompleted(victory) }) { + val buttonText = civ.victoryManager.getNextMilestone(victory)?.getVictoryScreenButtonHeaderText(false, civ) ?: "Done!" + victoryColumn.add(getCivGroup(civ, "\n" + buttonText.tr(), playerCivInfo)).fillX().row() } - return scientificVictoryColumn + + return victoryColumn } private fun setCivRankingsTable() { @@ -257,36 +193,38 @@ class VictoryScreen(val worldScreen: WorldScreen) : PickerScreen() { contentsTable.add(civRankingsTable) } - companion object { - fun getCivGroup(civ: CivilizationInfo, afterCivNameText:String, currentPlayer:CivilizationInfo): Table { - val civGroup = Table() + private fun getCivGroup(civ: CivilizationInfo, afterCivNameText: String, currentPlayer: CivilizationInfo): Table { + val civGroup = Table() - var labelText = civ.civName.tr()+afterCivNameText - var labelColor = Color.WHITE - val backgroundColor: Color + var labelText = "{${civ.civName.tr()}}{${afterCivNameText.tr()}}" + var labelColor = Color.WHITE + val backgroundColor: Color - if (civ.isDefeated()) { - civGroup.add(ImageGetter.getImage("OtherIcons/DisbandUnit")).size(30f) - backgroundColor = Color.LIGHT_GRAY - labelColor = Color.BLACK - } else if (currentPlayer == civ // || game.viewEntireMapForDebug - || currentPlayer.knows(civ) || currentPlayer.isDefeated() || currentPlayer.victoryManager.hasWon()) { - civGroup.add(ImageGetter.getNationIndicator(civ.nation, 30f)) - backgroundColor = civ.nation.getOuterColor() - labelColor = civ.nation.getInnerColor() - } else { - civGroup.add(ImageGetter.getRandomNationIndicator(30f)) - backgroundColor = Color.DARK_GRAY - labelText = Constants.unknownNationName - } - - civGroup.background = ImageGetter.getRoundedEdgeRectangle(backgroundColor) - val label = labelText.toLabel(labelColor) - label.setAlignment(Align.center) - - civGroup.add(label).padLeft(10f) - civGroup.pack() - return civGroup + if (civ.isDefeated()) { + civGroup.add(ImageGetter.getImage("OtherIcons/DisbandUnit")).size(30f) + backgroundColor = Color.LIGHT_GRAY + labelColor = Color.BLACK + } else if (currentPlayer == civ // || game.viewEntireMapForDebug + || currentPlayer.knows(civ) + || currentPlayer.isDefeated() + || currentPlayer.victoryManager.hasWon() + ) { + civGroup.add(ImageGetter.getNationIndicator(civ.nation, 30f)) + backgroundColor = civ.nation.getOuterColor() + labelColor = civ.nation.getInnerColor() + } else { + civGroup.add(ImageGetter.getRandomNationIndicator(30f)) + backgroundColor = Color.DARK_GRAY + labelText = Constants.unknownNationName } + + + civGroup.background = ImageGetter.getRoundedEdgeRectangle(backgroundColor) + val label = labelText.toLabel(labelColor) + label.setAlignment(Align.center) + + civGroup.add(label).padLeft(10f) + civGroup.pack() + return civGroup } }