Moddable victories (#6161)

* Added a moddable (but not yet functional) file for victories

* Spaceship parts are units now

* Fixed tests

* Added milestone objects

* Made 'our status' table in VictoryScreen dependend on file

* Updated VictoryManager to use the new Milestone system

* Fixed bug where in vanilla too many spaceship parts could beb uild

* Whoops

* Updated global victory table to use the jsons

* Updated the new game screen to show the new victory types
Also started with the deprecation of VictorType

* Did some translation stuff, also finally fixed the tests

* Removed VictoryType and reworked AI to use Milestones instead

* Add some checks for the victory file; tested that custom victories work
Also moves some code to a better spot and fixes compilation errors

* Fixed some things I thought about while falling asleep
Most notably: built -> build; fixed spaceship part construction 
priority; removed more code for the old system

* Fixed translation issues on the victory screen
This commit is contained in:
Xander Lenstra 2022-04-24 21:45:38 +02:00 committed by GitHub
parent 382707a6a5
commit 34105efdda
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 691 additions and 388 deletions

View File

@ -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!"
}
]

View File

@ -1356,7 +1356,7 @@
"cost": 500, "cost": 500,
"requiredTech": "Robotics", "requiredTech": "Robotics",
"requiredResource": "Aluminum", "requiredResource": "Aluminum",
"uniques": ["Spaceship part", "Cannot be purchased", "Only available <if [Apollo Program] is constructed>", "Uncapturable", "Can be added to [The Spaceship] in the Capital"] "uniques": ["Spaceship part", "Cannot be purchased", "Only available <if [Apollo Program] is constructed>", "Uncapturable", "Can be added to [The Spaceship] in the Capital", "Limited to [3] per Civilization"]
// costs 1500 in BNW // costs 1500 in BNW
}, },
{ {
@ -1366,7 +1366,7 @@
"cost": 750, "cost": 750,
"requiredTech": "Satellites", "requiredTech": "Satellites",
"requiredResource": "Aluminum", "requiredResource": "Aluminum",
"uniques": ["Spaceship part", "Cannot be purchased", "Only available <if [Apollo Program] is constructed>", "Uncapturable", "Can be added to [The Spaceship] in the Capital"] "uniques": ["Spaceship part", "Cannot be purchased", "Only available <if [Apollo Program] is constructed>", "Uncapturable", "Can be added to [The Spaceship] in the Capital", "Limited to [1] per Civilization"]
// costs 1500 in BNW // costs 1500 in BNW
}, },
{ {
@ -1376,7 +1376,7 @@
"cost": 750, "cost": 750,
"requiredTech": "Particle Physics", "requiredTech": "Particle Physics",
"requiredResource": "Aluminum", "requiredResource": "Aluminum",
"uniques": ["Spaceship part", "Cannot be purchased", "Only available <if [Apollo Program] is constructed>", "Uncapturable", "Can be added to [The Spaceship] in the Capital"] "uniques": ["Spaceship part", "Cannot be purchased", "Only available <if [Apollo Program] is constructed>", "Uncapturable", "Can be added to [The Spaceship] in the Capital", "Limited to [1] per Civilization"]
// costs 1500 in BNW // costs 1500 in BNW
}, },
{ {
@ -1386,7 +1386,7 @@
"cost": 750, "cost": 750,
"requiredTech": "Nanotechnology", "requiredTech": "Nanotechnology",
"requiredResource": "Aluminum", "requiredResource": "Aluminum",
"uniques": ["Spaceship part", "Cannot be purchased", "Only available <if [Apollo Program] is constructed>", "Uncapturable", "Can be added to [The Spaceship] in the Capital"] "uniques": ["Spaceship part", "Cannot be purchased", "Only available <if [Apollo Program] is constructed>", "Uncapturable", "Can be added to [The Spaceship] in the Capital", "Limited to [1] per Civilization"]
// costs 1500 in BNW // costs 1500 in BNW
} }
] ]

View File

@ -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!"
}
]

View File

@ -415,6 +415,8 @@ Starting Era =
It looks like we can't make a map with the parameters you requested! = 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? = Maybe you put too many players into too small a map? =
No human players selected! = No human players selected! =
Invalid Player ID! =
No victory conditions were selected! =
Mods: = Mods: =
Extension mods: = Extension mods: =
Base ruleset: = Base ruleset: =
@ -1078,31 +1080,22 @@ Number of your cities\ndemanding this resource for\n'We Love The King Day' =
# Victory # Victory
Science victory = [victoryType] Victory =
Cultural victory = Built [building] =
Conquest victory = Add all spaceship parts in capital =
Diplomatic victory = Destroy all players =
Complete all the spaceship parts\n to win! = Capture all capitals =
Complete 5 policy branches\n to win! = Complete [amount] Policy branche =
Complete 5 policy branches and build\n the Utopia Project to win! =
Destroy all enemies\n to win! =
You have won a [victoryType] Victory! = 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. = [civilization] has won a [victoryType] Victory! =
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! =
Your civilization stands above all others! The exploits of your people shall be remembered until the end of civilization itself! = 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 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...! = One more turn...! =
Built Apollo Program =
Destroy [civName] = Destroy [civName] =
Capture [cityName] =
Our status = Our status =
Global status = Global status =
Rankings = 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. # 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 # 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] = Turns until the next\ndiplomacy victory vote: [amount] =

View File

@ -47,9 +47,11 @@ object Constants {
const val citadel = "Citadel" const val citadel = "Citadel"
const val futureTech = "Future Tech" 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. // This religion name should never be displayed.
const val noReligionName = "The religion of TheLegend27" const val noReligionName = "The religion of TheLegend27"
const val neutralVictoryType = "Neutral"
const val cancelImprovementOrder = "Cancel improvement order" const val cancelImprovementOrder = "Cancel improvement order"
const val tutorialPopupNamePrefix = "Tutorial: " const val tutorialPopupNamePrefix = "Tutorial: "

View File

@ -12,7 +12,7 @@ class JsonParser {
fun <T> getFromJson(tClass: Class<T>, filePath: String): T = getFromJson(tClass, Gdx.files.internal(filePath)) fun <T> getFromJson(tClass: Class<T>, filePath: String): T = getFromJson(tClass, Gdx.files.internal(filePath))
fun <T> getFromJson(tClass: Class<T>, file: FileHandle): T { fun <T> getFromJson(tClass: Class<T>, file: FileHandle): T {
try{ try {
val jsonText = file.readString(Charsets.UTF_8.name()) val jsonText = file.readString(Charsets.UTF_8.name())
return json.fromJson(tClass, jsonText) return json.fromJson(tClass, jsonText)
} catch (exception:Exception){ } catch (exception:Exception){

View File

@ -37,6 +37,8 @@ class GameInfo {
// Maps a civ to the civ they voted for // Maps a civ to the civ they voted for
var diplomaticVictoryVotesCast = HashMap<String, String>() var diplomaticVictoryVotesCast = HashMap<String, String>()
// 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 /**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 currentPlayerIndex = (currentPlayerIndex + 1) % civilizations.size
if (currentPlayerIndex == 0) { if (currentPlayerIndex == 0) {
turns++ turns++
checkForTimeVictory()
} }
thisPlayer = civilizations[currentPlayerIndex] thisPlayer = civilizations[currentPlayerIndex]
thisPlayer.startTurn() thisPlayer.startTurn()
@ -266,7 +267,7 @@ class GameInfo {
&& (!it.militaryUnit!!.isInvisible(thisPlayer) || viewableInvisibleTiles.contains(it.position))) && (!it.militaryUnit!!.isInvisible(thisPlayer) || viewableInvisibleTiles.contains(it.position)))
} }
// enemy units ON our territory // enemy units IN our territory
addEnemyUnitNotification(thisPlayer, addEnemyUnitNotification(thisPlayer,
enemyUnitsCloseToTerritory.filter { it.getOwner() == thisPlayer }, enemyUnitsCloseToTerritory.filter { it.getOwner() == thisPlayer },
"in" "in"
@ -283,16 +284,17 @@ class GameInfo {
} }
) )
} }
fun getEnabledVictories() = ruleSet.victories.filter { !it.value.hiddenInVictoryScreen && gameParameters.victoryTypes.contains(it.key) }
private fun checkForTimeVictory() { fun processDiplomaticVictory() {
if (turns != gameParameters.maxTurns || !gameParameters.victoryTypes.contains(VictoryType.Time)) return if (diplomaticVictoryVotesProcessed) return
for (civInfo in civilizations) {
val winningCiv = civilizations if (civInfo.victoryManager.hasEnoughVotesForDiplomaticVictory()) {
.filter { it.isMajorCiv() && !it.isSpectator() && !it.isBarbarian() } civInfo.victoryManager.hasEverWonDiplomaticVote = true
.maxByOrNull { it.calculateTotalScore() } }
?: return // Are there no civs left? }
diplomaticVictoryVotesProcessed = true
winningCiv.victoryManager.hasWonTimeVictory = true
} }
private fun addEnemyUnitNotification(thisPlayer: CivilizationInfo, tiles: List<TileInfo>, inOrNear: String) { private fun addEnemyUnitNotification(thisPlayer: CivilizationInfo, tiles: List<TileInfo>, inOrNear: String) {
@ -450,8 +452,10 @@ class GameInfo {
} }
} }
spaceResources.clear()
spaceResources.addAll(ruleSet.buildings.values.filter { it.hasUnique(UniqueType.SpaceshipPart) } spaceResources.addAll(ruleSet.buildings.values.filter { it.hasUnique(UniqueType.SpaceshipPart) }
.flatMap { it.getResourceRequirements().keys } ) .flatMap { it.getResourceRequirements().keys } )
spaceResources.addAll(ruleSet.victories.values.flatMap { it.requiredSpaceshipParts })
barbarians.setTransients(this) barbarians.setTransients(this)

View File

@ -8,7 +8,8 @@ import com.unciv.logic.map.MapUnit
import com.unciv.logic.map.TileInfo import com.unciv.logic.map.TileInfo
import com.unciv.logic.map.TileMap import com.unciv.logic.map.TileMap
import com.unciv.models.ruleset.Building 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.tile.ResourceType
import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.ruleset.unit.BaseUnit import com.unciv.models.ruleset.unit.BaseUnit
@ -41,7 +42,7 @@ object Automation {
rank += stats.production rank += stats.production
rank += stats.science 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 rank += stats.culture
} else rank += stats.culture / 2 } else rank += stats.culture / 2
} }
@ -175,7 +176,7 @@ object Automation {
return true return true
// Spaceships are always allowed // Spaceships are always allowed
if (construction.hasUnique(UniqueType.SpaceshipPart)) if (construction.name in civInfo.gameInfo.spaceResources)
return true return true
val requiredResources = construction.getResourceRequirements() val requiredResources = construction.getResourceRequirements()
@ -237,7 +238,7 @@ object Automation {
} }
fun getReservedSpaceResourceAmount(civInfo: CivilizationInfo): Int { 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 { fun threatAssessment(assessor: CivilizationInfo, assessed: CivilizationInfo): ThreatLevel {

View File

@ -5,7 +5,7 @@ import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.map.TileInfo import com.unciv.logic.map.TileInfo
import com.unciv.models.ruleset.Belief import com.unciv.models.ruleset.Belief
import com.unciv.models.ruleset.BeliefType 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.ruleset.unique.UniqueType
import com.unciv.models.stats.Stat import com.unciv.models.stats.Stat
import kotlin.math.min import kotlin.math.min
@ -133,7 +133,7 @@ object ChooseBeliefsAutomation {
score += modifier * when (unique.placeholderText) { score += modifier * when (unique.placeholderText) {
"Earn []% of [] unit's [] as [] when killed within 4 tiles of a city following this religion" -> "Earn []% of [] unit's [] as [] when killed within 4 tiles of a city following this religion" ->
unique.params[0].toFloat() * 4f * unique.params[0].toFloat() * 4f *
if (civInfo.victoryType() == VictoryType.Domination) 2f if (civInfo.wantsToFocusOn(ThingToFocus.Military)) 2f
else 1f else 1f
"May buy [] buildings for [] [] []", "May buy [] units for [] [] []" -> "May buy [] buildings for [] [] []", "May buy [] units for [] [] []" ->
if (civInfo.religionManager.religion != null if (civInfo.religionManager.religion != null
@ -152,7 +152,7 @@ object ChooseBeliefsAutomation {
// what happens over there // what happens over there
else civInfo.statsForNextTurn[Stat.valueOf(unique.params[1])] * 10f / civInfo.getEra().baseUnitBuyCost else civInfo.statsForNextTurn[Stat.valueOf(unique.params[1])] * 10f / civInfo.getEra().baseUnitBuyCost
UniqueType.BuyUnitsByProductionCost.placeholderText -> 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 "when a city adopts this religion for the first time (modified by game speed)" -> // Modified by personality
unique.stats.values.sum() * 10f unique.stats.values.sum() * 10f
"When spreading religion to a city, gain [] times the amount of followers of other religions as []" -> "When spreading religion to a city, gain [] times the amount of followers of other religions as []" ->

View File

@ -8,7 +8,8 @@ import com.unciv.logic.civilization.NotificationIcon
import com.unciv.logic.civilization.PlayerType import com.unciv.logic.civilization.PlayerType
import com.unciv.logic.map.BFS import com.unciv.logic.map.BFS
import com.unciv.models.ruleset.Building 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.StateForConditionals
import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.ruleset.unit.BaseUnit 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 allTechsAreResearched = civInfo.tech.getNumberOfTechsResearched() >= civInfo.gameInfo.ruleSet.technologies.size
val isAtWar = civInfo.isAtWar() 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() private val averageProduction = civInfo.cities.map { it.cityStats.currentCityStats.production }.average()
val cityIsOverAverageProduction = cityInfo.cityStats.currentCityStats.production >= averageProduction val cityIsOverAverageProduction = cityInfo.cityStats.currentCityStats.production >= averageProduction
@ -45,9 +52,9 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
private val faithConstruction = arrayListOf<BaseUnit>() private val faithConstruction = arrayListOf<BaseUnit>()
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<ConstructionChoice>, choice:String, choiceModifier: Float){ private fun addChoice(choices: ArrayList<ConstructionChoice>, choice: String, choiceModifier: Float){
choices.add(ConstructionChoice(choice,choiceModifier,cityConstructions.getRemainingWork(choice))) choices.add(ConstructionChoice(choice,choiceModifier,cityConstructions.getRemainingWork(choice)))
} }
@ -125,7 +132,7 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
val unitsToCitiesRatio = cities.toFloat() / (militaryUnits + 1) val unitsToCitiesRatio = cities.toFloat() / (militaryUnits + 1)
// most buildings and civ units contribute the the civ's growth, military units are anti-growth // most buildings and civ units contribute the the civ's growth, military units are anti-growth
var modifier = sqrt(unitsToCitiesRatio) / 2 var modifier = sqrt(unitsToCitiesRatio) / 2
if (preferredVictoryType == VictoryType.Domination) modifier *= 3 if (civInfo.wantsToFocusOn(ThingToFocus.Military)) modifier *= 3
else if (isAtWar) modifier *= unitsToCitiesRatio * 2 else if (isAtWar) modifier *= unitsToCitiesRatio * 2
if (Automation.afraidOfBarbarians(civInfo)) modifier = 2f // military units are pro-growth if pressured by barbs 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 var modifier = 0.5f
if (cityInfo.cityStats.currentCityStats.culture == 0f) // It won't grow if we don't help it if (cityInfo.cityStats.currentCityStats.culture == 0f) // It won't grow if we don't help it
modifier = 0.8f modifier = 0.8f
if (preferredVictoryType == VictoryType.Cultural) modifier = 1.6f if (civInfo.wantsToFocusOn(ThingToFocus.Culture)) modifier = 1.6f
addChoice(relativeCostEffectiveness, cultureBuilding.name, modifier) addChoice(relativeCostEffectiveness, cultureBuilding.name, modifier)
} }
} }
private fun addSpaceshipPartChoice() { private fun addSpaceshipPartChoice() {
val spaceshipPart = (buildableNotWonders + buildableUnits).firstOrNull { it.hasUnique(UniqueType.SpaceshipPart) } val spaceshipPart = (buildableNotWonders + buildableUnits).firstOrNull { it.name in spaceshipParts }
if (spaceshipPart != null) { if (spaceshipPart != null) {
var modifier = 1.5f val modifier = 2f
if (preferredVictoryType == VictoryType.Scientific) modifier = 2f
addChoice(relativeCostEffectiveness, spaceshipPart.name, modifier) addChoice(relativeCostEffectiveness, spaceshipPart.name, modifier)
} }
} }
@ -217,23 +223,27 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
} }
private fun getWonderPriority(wonder: Building): Float { private fun getWonderPriority(wonder: Building): Float {
if (wonder.hasUnique(UniqueType.EnablesConstructionOfSpaceshipParts)) // Only start building if we are the city that would complete it the soonest
return 2f if (wonder.hasUnique(UniqueType.TriggersCulturalVictory)
if (preferredVictoryType == VictoryType.Cultural && 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")) && wonder.name in listOf("Sistine Chapel", "Eiffel Tower", "Cristo Redentor", "Neuschwanstein", "Sydney Opera House"))
return 3f 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 (wonder.isStatRelated(Stat.Science)) {
if (allTechsAreResearched) return .5f if (allTechsAreResearched) return .5f
if (preferredVictoryType == VictoryType.Scientific) return 1.5f if (civInfo.wantsToFocusOn(ThingToFocus.Science)) return 1.5f
else return 1.3f else return 1.3f
} }
if (wonder.name == "Manhattan Project") { if (wonder.name == "Manhattan Project") {
if (preferredVictoryType == VictoryType.Domination) return 2f if (civInfo.wantsToFocusOn(ThingToFocus.Military)) return 2f
else return 1.3f else return 1.3f
} }
if (wonder.isStatRelated(Stat.Happiness)) return 1.2f if (wonder.isStatRelated(Stat.Happiness)) return 1.2f
@ -259,10 +269,10 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
val unitTrainingBuilding = buildableNotWonders.asSequence() val unitTrainingBuilding = buildableNotWonders.asSequence()
.filter { it.hasUnique(UniqueType.UnitStartingExperience) .filter { it.hasUnique(UniqueType.UnitStartingExperience)
&& Automation.allowSpendingResource(civInfo, it)}.minByOrNull { it.cost } && 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 var modifier = if (cityIsOverAverageProduction) 0.5f else 0.1f // You shouldn't be cranking out units anytime soon
if (isAtWar) modifier *= 2 if (isAtWar) modifier *= 2
if (preferredVictoryType == VictoryType.Domination) if (civInfo.wantsToFocusOn(ThingToFocus.Military))
modifier *= 1.3f modifier *= 1.3f
addChoice(relativeCostEffectiveness, unitTrainingBuilding.name, modifier) addChoice(relativeCostEffectiveness, unitTrainingBuilding.name, modifier)
} }
@ -272,7 +282,7 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
val defensiveBuilding = buildableNotWonders.asSequence() val defensiveBuilding = buildableNotWonders.asSequence()
.filter { it.cityStrength > 0 .filter { it.cityStrength > 0
&& Automation.allowSpendingResource(civInfo, it)}.minByOrNull { it.cost } && 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 var modifier = 0.2f
if (isAtWar) modifier = 0.5f if (isAtWar) modifier = 0.5f
@ -308,7 +318,7 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
.minByOrNull { it.cost } .minByOrNull { it.cost }
if (scienceBuilding != null) { if (scienceBuilding != null) {
var modifier = 1.1f var modifier = 1.1f
if (preferredVictoryType == VictoryType.Scientific) if (civInfo.wantsToFocusOn(ThingToFocus.Science))
modifier *= 1.4f modifier *= 1.4f
addChoice(relativeCostEffectiveness, scienceBuilding.name, modifier) 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. // 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 // 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 // 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 (civInfo.religionManager.religion?.name == null) return
if (cityInfo.religion.getMajorityReligion()?.name != civInfo.religionManager.religion?.name) if (cityInfo.religion.getMajorityReligion()?.name != civInfo.religionManager.religion?.name)
return // you don't want to build units of opposing religions. 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) } && it.canBePurchasedWithStat(cityInfo, Stat.Faith) }
if (preferredVictoryType == VictoryType.Cultural) modifier += 1 if (civInfo.wantsToFocusOn(ThingToFocus.Culture)) modifier += 1
if (isAtWar) modifier -= 0.5f if (isAtWar) modifier -= 0.5f
val citiesNotFollowingOurReligion = civInfo.cities.asSequence() val citiesNotFollowingOurReligion = civInfo.cities.asSequence()

View File

@ -260,7 +260,7 @@ object NextTurnAutomation {
} }
} }
if (civInfo.victoryType() == VictoryType.Cultural) { if (civInfo.wantsToFocusOn(ThingToFocus.Culture)) {
for (cityState in civInfo.getKnownCivs() for (cityState in civInfo.getKnownCivs()
.filter { it.isCityState() && it.cityStateType == CityStateType.Cultured }) { .filter { it.isCityState() && it.cityStateType == CityStateType.Cultured }) {
val diploManager = cityState.getDiplomacyManager(civInfo) val diploManager = cityState.getDiplomacyManager(civInfo)
@ -298,20 +298,20 @@ object NextTurnAutomation {
if (!cityState.isAlive() || cityState.cities.isEmpty() || civInfo.cities.isEmpty()) if (!cityState.isAlive() || cityState.cities.isEmpty() || civInfo.cities.isEmpty())
return value return value
if (civInfo.victoryType() == VictoryType.Cultural && cityState.canGiveStat(Stat.Culture)) { if (civInfo.wantsToFocusOn(ThingToFocus.Culture) && cityState.canGiveStat(Stat.Culture)) {
value += 10 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 // In case someone mods this in
value += 10 value += 10
} }
else if (civInfo.victoryType() == VictoryType.Domination) { else if (civInfo.wantsToFocusOn(ThingToFocus.Military)) {
// Don't ally close city-states, conquer them instead // Don't ally close city-states, conquer them instead
val distance = getMinDistanceBetweenCities(civInfo, cityState) val distance = getMinDistanceBetweenCities(civInfo, cityState)
if (distance < 20) if (distance < 20)
value -= (20 - distance) / 4 value -= (20 - distance) / 4
} }
else if (civInfo.victoryType() == VictoryType.Diplomatic) { else if (civInfo.wantsToFocusOn(ThingToFocus.CityStates)) {
value += 5 // Generally be friendly value += 5 // Generally be friendly
} }
if (civInfo.gold < 100) { if (civInfo.gold < 100) {
@ -658,7 +658,7 @@ object NextTurnAutomation {
} }
private fun declareWar(civInfo: CivilizationInfo) { 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.cities.isEmpty() || civInfo.diplomacy.isEmpty()) return
if (civInfo.isAtWar() || civInfo.getHappiness() <= 0) return if (civInfo.isAtWar() || civInfo.getHappiness() <= 0) return
@ -848,7 +848,7 @@ object NextTurnAutomation {
private fun trainSettler(civInfo: CivilizationInfo) { private fun trainSettler(civInfo: CivilizationInfo) {
if (civInfo.isCityState()) return if (civInfo.isCityState()) return
if (civInfo.isAtWar()) return // don't train settlers when you could be training troops. 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 if (civInfo.cities.none() || civInfo.getHappiness() <= civInfo.cities.size + 5) return
val settlerUnits = civInfo.gameInfo.ruleSet.units.values val settlerUnits = civInfo.gameInfo.ruleSet.units.values

View File

@ -1,7 +1,7 @@
package com.unciv.logic.city package com.unciv.logic.city
import com.unciv.logic.civilization.CivilizationInfo 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.StateForConditionals
import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.stats.INamed import com.unciv.models.stats.INamed
@ -20,6 +20,7 @@ interface IConstruction : INamed {
interface INonPerpetualConstruction : IConstruction, INamed, IHasUniques { interface INonPerpetualConstruction : IConstruction, INamed, IHasUniques {
val hurryCostModifier: Int val hurryCostModifier: Int
var requiredTech: String?
fun getProductionCost(civInfo: CivilizationInfo): Int fun getProductionCost(civInfo: CivilizationInfo): Int
fun getStatBuyCost(cityInfo: CityInfo, stat: Stat): Int? fun getStatBuyCost(cityInfo: CityInfo, stat: Stat): Int?
@ -111,14 +112,14 @@ class RejectionReasons: HashSet<RejectionReasonInstance>() {
RejectionReason.RequiresTech, RejectionReason.RequiresTech,
RejectionReason.RequiresPolicy, RejectionReason.RequiresPolicy,
RejectionReason.MorePolicyBranches, RejectionReason.MorePolicyBranches,
RejectionReason.RequiresBuildingInSomeCity RejectionReason.RequiresBuildingInSomeCity,
) )
private val reasonsToDefinitivelyRemoveFromQueue = hashSetOf( private val reasonsToDefinitivelyRemoveFromQueue = hashSetOf(
RejectionReason.Obsoleted, RejectionReason.Obsoleted,
RejectionReason.WonderAlreadyBuilt, RejectionReason.WonderAlreadyBuilt,
RejectionReason.NationalWonderAlreadyBuilt, RejectionReason.NationalWonderAlreadyBuilt,
RejectionReason.CannotBeBuiltWith, RejectionReason.CannotBeBuiltWith,
RejectionReason.ReachedBuildCap RejectionReason.MaxNumberBuildable,
) )
private val orderOfErrorMessages = listOf( private val orderOfErrorMessages = listOf(
RejectionReason.WonderBeingBuiltElsewhere, RejectionReason.WonderBeingBuiltElsewhere,
@ -130,7 +131,7 @@ class RejectionReasons: HashSet<RejectionReasonInstance>() {
RejectionReason.ConsumesResources, RejectionReason.ConsumesResources,
RejectionReason.CanOnlyBePurchased, RejectionReason.CanOnlyBePurchased,
RejectionReason.MaxNumberBuildable, 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"), MustOwnTile(false, "Must own a specific tile close by"),
WaterUnitsInCoastalCities(false, "May only built water units in coastal cities"), WaterUnitsInCoastalCities(false, "May only built water units in coastal cities"),
CanOnlyBeBuiltInSpecificCities(false, "Can only be built in specific 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"), UniqueToOtherNation(false, "Unique to another nation"),
ReplacedByOurUnique(false, "Our unique replaces this"), 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"), CityStateNationalWonder(false, "No National Wonders for city-states"),
WonderDisabledEra(false, "This Wonder is disabled when starting in this era"), 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"), ConsumesResources(true, "Consumes resources which you are lacking"),
PopulationRequirement(true, "Requires more population"), PopulationRequirement(true, "Requires more population"),

View File

@ -1,13 +1,13 @@
package com.unciv.logic.civilization package com.unciv.logic.civilization
import com.badlogic.gdx.math.Vector2 import com.badlogic.gdx.math.Vector2
import com.unciv.Constants
import com.unciv.UncivGame import com.unciv.UncivGame
import com.unciv.logic.GameInfo import com.unciv.logic.GameInfo
import com.unciv.logic.UncivShowableException import com.unciv.logic.UncivShowableException
import com.unciv.logic.automation.NextTurnAutomation import com.unciv.logic.automation.NextTurnAutomation
import com.unciv.logic.automation.WorkerAutomation import com.unciv.logic.automation.WorkerAutomation
import com.unciv.logic.city.CityInfo import com.unciv.logic.city.CityInfo
import com.unciv.logic.city.IConstruction
import com.unciv.logic.civilization.RuinsManager.RuinsManager import com.unciv.logic.civilization.RuinsManager.RuinsManager
import com.unciv.logic.civilization.diplomacy.DiplomacyFlags import com.unciv.logic.civilization.diplomacy.DiplomacyFlags
import com.unciv.logic.civilization.diplomacy.DiplomacyManager import com.unciv.logic.civilization.diplomacy.DiplomacyManager
@ -119,6 +119,9 @@ class CivilizationInfo {
@Transient @Transient
val lastEraResourceUsedForUnit = HashMap<String, Int>() val lastEraResourceUsedForUnit = HashMap<String, Int>()
@Transient
var thingsToFocusOnForVictory = setOf<ThingToFocus>()
var playerType = PlayerType.AI var playerType = PlayerType.AI
@ -322,21 +325,33 @@ class CivilizationInfo {
fun isAlive(): Boolean = !isDefeated() fun isAlive(): Boolean = !isDefeated()
@Suppress("unused") //TODO remove if future use unlikely, including DiplomacyFlags.EverBeenFriends and 2 DiplomacyManager methods - see #3183 @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 hasEverBeenFriendWith(otherCiv: CivilizationInfo): Boolean = getDiplomacyManager(otherCiv).everBeenFriends()
fun hasMetCivTerritory(otherCiv: CivilizationInfo): Boolean = otherCiv.getCivTerritory().any { it in exploredTiles } fun hasMetCivTerritory(otherCiv: CivilizationInfo): Boolean = otherCiv.getCivTerritory().any { it in exploredTiles }
fun getCompletedPolicyBranchesCount(): Int = policies.adoptedPolicies.count { Policy.isBranchCompleteByName(it) } 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() } private fun getCivTerritory() = cities.asSequence().flatMap { it.tiles.asSequence() }
fun victoryType(): VictoryType { fun getPreferredVictoryType(): String {
val victoryTypes = gameInfo.gameParameters.victoryTypes val victoryTypes = gameInfo.gameParameters.victoryTypes
if (victoryTypes.size == 1) if (victoryTypes.size == 1)
return victoryTypes.first() // That is the most relevant one return victoryTypes.first() // That is the most relevant one
val victoryType = nation.preferredVictoryType val victoryType = nation.preferredVictoryType
return if (victoryType in victoryTypes) victoryType return if (victoryType in gameInfo.ruleSet.victories) victoryType
else VictoryType.Neutral 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 @Transient
private val civInfoStats = CivInfoStats(this) private val civInfoStats = CivInfoStats(this)
fun stats() = civInfoStats fun stats() = civInfoStats
@ -751,6 +766,8 @@ class CivilizationInfo {
} }
victoryManager.civInfo = this victoryManager.civInfo = this
thingsToFocusOnForVictory = getPreferredVictoryTypeObject()?.getThingsToFocus(this) ?: setOf()
for (cityInfo in cities) { 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 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() { private fun handleDiplomaticVictoryFlags() {
if (flagsCountdown[CivFlags.ShouldResetDiplomaticVotes.name] == 0) { if (flagsCountdown[CivFlags.ShouldResetDiplomaticVotes.name] == 0) {
gameInfo.diplomaticVictoryVotesCast.clear() gameInfo.diplomaticVictoryVotesCast.clear()
removeFlag(CivFlags.ShouldResetDiplomaticVotes.name)
removeFlag(CivFlags.ShowDiplomaticVotingResults.name) removeFlag(CivFlags.ShowDiplomaticVotingResults.name)
removeFlag(CivFlags.ShouldResetDiplomaticVotes.name)
} }
if (flagsCountdown[CivFlags.ShowDiplomaticVotingResults.name] == 0) { if (flagsCountdown[CivFlags.ShowDiplomaticVotingResults.name] == 0) {
gameInfo.processDiplomaticVictory()
if (gameInfo.civilizations.any { it.victoryManager.hasWon() } ) { if (gameInfo.civilizations.any { it.victoryManager.hasWon() } ) {
removeFlag(CivFlags.TurnsTillNextDiplomaticVote.name) removeFlag(CivFlags.TurnsTillNextDiplomaticVote.name)
} else { } else {
addFlag(CivFlags.ShouldResetDiplomaticVotes.name, 1) 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 removeFlag(flag: String) = flagsCountdown.remove(flag)
fun hasFlag(flag: String) = flagsCountdown.contains(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] fun getTurnsTillNextDiplomaticVote() = flagsCountdown[CivFlags.TurnsTillNextDiplomaticVote.name]
@ -975,8 +993,8 @@ class CivilizationInfo {
// to the user and thus the flag is set at -1/ // to the user and thus the flag is set at -1/
fun shouldCheckForDiplomaticVictory() = fun shouldCheckForDiplomaticVictory() =
(flagsCountdown[CivFlags.ShowDiplomaticVotingResults.name] == 0 (flagsCountdown[CivFlags.ShowDiplomaticVotingResults.name] == 0
|| flagsCountdown[CivFlags.ShowDiplomaticVotingResults.name] == -1) || flagsCountdown[CivFlags.ShowDiplomaticVotingResults.name] == -1)
&& gameInfo.civilizations.any { it.isMajorCiv() && !it.isDefeated() && it != this } && gameInfo.civilizations.any { it.isMajorCiv() && !it.isDefeated() && it != this }
private fun updateRevolts() { private fun updateRevolts() {
if (gameInfo.civilizations.none { it.isBarbarian() }) { if (gameInfo.civilizations.none { it.isBarbarian() }) {

View File

@ -38,7 +38,7 @@ class PolicyManager {
get() { get() {
val value = HashMap<PolicyBranch, Int>() val value = HashMap<PolicyBranch, Int>()
for (branch in branches) { for (branch in branches) {
value[branch] = branch.priorities[civInfo.victoryType().name] ?: 0 value[branch] = branch.priorities[civInfo.getPreferredVictoryType()] ?: 0
} }
return value return value
} }

View File

@ -1,39 +1,27 @@
package com.unciv.logic.civilization package com.unciv.logic.civilization
import com.unciv.Constants
import com.unciv.models.Counter import com.unciv.models.Counter
import com.unciv.models.ruleset.VictoryType import com.unciv.models.ruleset.Milestone
import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.ruleset.unique.UniqueType
class VictoryManager { class VictoryManager {
@Transient @Transient
lateinit var civInfo: CivilizationInfo lateinit var civInfo: CivilizationInfo
var requiredSpaceshipParts = Counter<String>() // 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<String>() var currentsSpaceshipParts = Counter<String>()
var hasWonTimeVictory = false var hasEverWonDiplomaticVote = false
init {
requiredSpaceshipParts.add("SS Booster", 3)
requiredSpaceshipParts.add("SS Cockpit", 1)
requiredSpaceshipParts.add("SS Engine", 1)
requiredSpaceshipParts.add("SS Stasis Chamber", 1)
}
fun clone(): VictoryManager { fun clone(): VictoryManager {
val toReturn = VictoryManager() val toReturn = VictoryManager()
toReturn.currentsSpaceshipParts.putAll(currentsSpaceshipParts) toReturn.currentsSpaceshipParts.putAll(currentsSpaceshipParts)
toReturn.hasEverWonDiplomaticVote = hasEverWonDiplomaticVote
return toReturn return toReturn
} }
fun unconstructedSpaceshipParts(): Counter<String> { private fun calculateDiplomaticVotingResults(votesCast: HashMap<String, String>): Counter<String> {
val counter = requiredSpaceshipParts.clone()
counter.remove(currentsSpaceshipParts)
return counter
}
fun spaceshipPartsRemaining() = requiredSpaceshipParts.values.sum() - currentsSpaceshipParts.values.sum()
fun calculateDiplomaticVotingResults(votesCast: HashMap<String, String>): Counter<String> {
val results = Counter<String>() val results = Counter<String>()
for (castVote in votesCast) { for (castVote in votesCast) {
results.add(castVote.value, 1) results.add(castVote.value, 1)
@ -41,7 +29,7 @@ class VictoryManager {
return results return results
} }
fun votesNeededForDiplomaticVictory(): Int { private fun votesNeededForDiplomaticVictory(): Int {
val civCount = civInfo.gameInfo.civilizations.count { !it.isDefeated() } val civCount = civInfo.gameInfo.civilizations.count { !it.isDefeated() }
// CvGame.cpp::DoUpdateDiploVictory() in the source code of the original // CvGame.cpp::DoUpdateDiploVictory() in the source code of the original
@ -54,44 +42,46 @@ class VictoryManager {
fun hasEnoughVotesForDiplomaticVictory(): Boolean { fun hasEnoughVotesForDiplomaticVictory(): Boolean {
val results = calculateDiplomaticVotingResults(civInfo.gameInfo.diplomaticVictoryVotesCast) val results = calculateDiplomaticVotingResults(civInfo.gameInfo.diplomaticVictoryVotesCast)
val bestCiv = results.maxByOrNull { it.value } ?: return false val bestCiv = results.maxByOrNull { it.value } ?: return false
// If we don't have the highest score, we have not won anyway // If we don't have the highest score, we have not won anyway
if (bestCiv.key != civInfo.civName) return false 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 (bestCiv.value < votesNeededForDiplomaticVictory()) return false
// If there's a tie, we haven't won either // If there's a tie, we haven't won either
return (results.none { it != bestCiv && it.value == bestCiv.value }) return (results.none { it != bestCiv && it.value == bestCiv.value })
} }
private fun hasVictoryType(victoryType: VictoryType) = civInfo.gameInfo.gameParameters.victoryTypes.contains(victoryType) fun getVictoryTypeAchieved(): String? {
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? {
if (!civInfo.isMajorCiv()) return null if (!civInfo.isMajorCiv()) return null
if (hasWonTimeVictory()) return VictoryType.Time for (victoryName in civInfo.gameInfo.ruleSet.victories.keys.filter { it != Constants.neutralVictoryType}) {
if (hasWonDominationVictory()) return VictoryType.Domination if (getNextMilestone(victoryName) == null)
if (hasWonScientificVictory()) return VictoryType.Scientific return victoryName
if (hasWonCulturalVictory()) return VictoryType.Cultural }
if (hasWonDiplomaticVictory()) return VictoryType.Diplomatic if (civInfo.hasUnique(UniqueType.TriggersVictory))
if (civInfo.hasUnique(UniqueType.TriggersVictory)) return VictoryType.Neutral return Constants.neutralVictoryType
return null 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
} }

View File

@ -34,6 +34,10 @@ open class Counter<K> : LinkedHashMap<K, Int>() {
for (key in keys) newCounter[key] = this[key]!! * amount for (key in keys) newCounter[key] = this[key]!! * amount
return newCounter return newCounter
} }
fun sumValues(): Int {
return this.map { it.value }.sum()
}
override fun clone(): Counter<K> { override fun clone(): Counter<K> {
val newCounter = Counter<K>() val newCounter = Counter<K>()

View File

@ -1,10 +1,6 @@
package com.unciv.models.metadata package com.unciv.models.metadata
import com.unciv.Constants
import com.unciv.logic.civilization.PlayerType 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){ enum class BaseRuleset(val fullName:String){
Civ_V_Vanilla("Civ V - Vanilla"), Civ_V_Vanilla("Civ V - Vanilla"),
@ -26,9 +22,8 @@ class GameParameters { // Default values are the default new game
var godMode = false var godMode = false
var nuclearWeaponsEnabled = true var nuclearWeaponsEnabled = true
var religionEnabled = false var religionEnabled = false
// By default, all victory types except Diplomacy and time as they are quite new var victoryTypes: ArrayList<String> = arrayListOf()
var victoryTypes: ArrayList<VictoryType> = arrayListOf(VictoryType.Cultural, VictoryType.Domination, VictoryType.Scientific)
var startingEra = "Ancient era" var startingEra = "Ancient era"
var isOnlineMultiplayer = false var isOnlineMultiplayer = false
@ -70,9 +65,7 @@ class GameParameters { // Default values are the default new game
if (!nuclearWeaponsEnabled) yield("No nukes") if (!nuclearWeaponsEnabled) yield("No nukes")
if (religionEnabled) yield("Religion") if (religionEnabled) yield("Religion")
if (godMode) yield("God mode") if (godMode) yield("God mode")
for (victoryType in VictoryType.values()) { yield("Enabled Victories: " + victoryTypes.joinToString())
if (victoryType !in victoryTypes) yield("No $victoryType Victory")
}
yield(baseRuleset) yield(baseRuleset)
yield(if (mods.isEmpty()) "no mods" else mods.joinToString(",", "mods=(", ")", 6) ) yield(if (mods.isEmpty()) "no mods" else mods.joinToString(",", "mods=(", ")", 6) )
}.joinToString(prefix = "(", postfix = ")") }.joinToString(prefix = "(", postfix = ")")

View File

@ -18,7 +18,7 @@ import kotlin.math.pow
class Building : RulesetStatsObject(), INonPerpetualConstruction { class Building : RulesetStatsObject(), INonPerpetualConstruction {
var requiredTech: String? = null override var requiredTech: String? = null
var cost: Int = 0 var cost: Int = 0
var maintenance = 0 var maintenance = 0
@ -535,11 +535,10 @@ class Building : RulesetStatsObject(), INonPerpetualConstruction {
} }
} }
// To be replaced with `Only available <after [Apollo Project] has been build>`
UniqueType.SpaceshipPart -> { UniqueType.SpaceshipPart -> {
if (!civInfo.hasUnique(UniqueType.EnablesConstructionOfSpaceshipParts)) if (!civInfo.hasUnique(UniqueType.EnablesConstructionOfSpaceshipParts))
rejectionReasons.add(RejectionReason.RequiresBuildingInSomeCity.toInstance("Apollo project not built!")) rejectionReasons.add(RejectionReason.RequiresBuildingInSomeCity.toInstance("Apollo project not built!"))
if (civInfo.victoryManager.unconstructedSpaceshipParts()[name] == 0)
rejectionReasons.add(RejectionReason.ReachedBuildCap)
} }
UniqueType.RequiresAnotherBuilding -> { UniqueType.RequiresAnotherBuilding -> {
@ -590,7 +589,7 @@ class Building : RulesetStatsObject(), INonPerpetualConstruction {
} }
UniqueType.HiddenWithoutVictoryType -> { 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)) rejectionReasons.add(RejectionReason.HiddenWithoutVictory.toInstance(unique.text))
} }
} }
@ -670,7 +669,7 @@ class Building : RulesetStatsObject(), INonPerpetualConstruction {
override fun postBuildEvent(cityConstructions: CityConstructions, boughtWith: Stat?): Boolean { override fun postBuildEvent(cityConstructions: CityConstructions, boughtWith: Stat?): Boolean {
val civInfo = cityConstructions.cityInfo.civInfo val civInfo = cityConstructions.cityInfo.civInfo
if (hasUnique(UniqueType.SpaceshipPart)) { if (civInfo.gameInfo.spaceResources.contains(name)) {
civInfo.victoryManager.currentsSpaceshipParts.add(name, 1) civInfo.victoryManager.currentsSpaceshipParts.add(name, 1)
return true return true
} }

View File

@ -3,9 +3,9 @@ package com.unciv.models.ruleset
import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.graphics.Color
import com.unciv.logic.civilization.CityStateType import com.unciv.logic.civilization.CityStateType
import com.unciv.logic.civilization.diplomacy.RelationshipLevel 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.Unique
import com.unciv.models.ruleset.unique.UniqueTarget import com.unciv.models.ruleset.unique.UniqueTarget
import com.unciv.models.stats.INamed
import com.unciv.ui.utils.colorFromRGB import com.unciv.ui.utils.colorFromRGB
class Era : RulesetObject(), IHasUniques { class Era : RulesetObject(), IHasUniques {

View File

@ -14,15 +14,6 @@ import com.unciv.ui.civilopedia.FormattedLine
import com.unciv.ui.utils.Fonts import com.unciv.ui.utils.Fonts
import com.unciv.ui.utils.colorFromRGB import com.unciv.ui.utils.colorFromRGB
enum class VictoryType {
Neutral,
Cultural,
Diplomatic,
Domination,
Scientific,
Time,
}
class Nation : RulesetObject() { class Nation : RulesetObject() {
var leaderName = "" var leaderName = ""
fun getLeaderDisplayName() = if (isCityState()) name fun getLeaderDisplayName() = if (isCityState()) name
@ -30,7 +21,7 @@ class Nation : RulesetObject() {
val style = "" val style = ""
var cityStateType: CityStateType? = null var cityStateType: CityStateType? = null
var preferredVictoryType: VictoryType = VictoryType.Neutral var preferredVictoryType: String = Constants.neutralVictoryType
var declaringWar = "" var declaringWar = ""
var attacked = "" var attacked = ""
var defeated = "" var defeated = ""

View File

@ -16,6 +16,7 @@ import com.unciv.models.ruleset.tile.Terrain
import com.unciv.models.ruleset.tile.TerrainType import com.unciv.models.ruleset.tile.TerrainType
import com.unciv.models.ruleset.tile.TileImprovement import com.unciv.models.ruleset.tile.TileImprovement
import com.unciv.models.ruleset.tile.TileResource 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.Unique
import com.unciv.models.ruleset.unique.UniqueTarget import com.unciv.models.ruleset.unique.UniqueTarget
import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.ruleset.unique.UniqueType
@ -92,6 +93,7 @@ class Ruleset {
val units = LinkedHashMap<String, BaseUnit>() val units = LinkedHashMap<String, BaseUnit>()
val unitPromotions = LinkedHashMap<String, Promotion>() val unitPromotions = LinkedHashMap<String, Promotion>()
val unitTypes = LinkedHashMap<String, UnitType>() val unitTypes = LinkedHashMap<String, UnitType>()
var victories = LinkedHashMap<String, Victory>()
val mods = LinkedHashSet<String>() val mods = LinkedHashSet<String>()
var modOptions = ModOptions() var modOptions = ModOptions()
@ -132,6 +134,7 @@ class Ruleset {
unitPromotions.putAll(ruleset.unitPromotions) unitPromotions.putAll(ruleset.unitPromotions)
units.putAll(ruleset.units) units.putAll(ruleset.units)
unitTypes.putAll(ruleset.unitTypes) unitTypes.putAll(ruleset.unitTypes)
victories.putAll(ruleset.victories)
for (unitToRemove in ruleset.modOptions.unitsToRemove) units.remove(unitToRemove) for (unitToRemove in ruleset.modOptions.unitsToRemove) units.remove(unitToRemove)
modOptions.uniques.addAll(ruleset.modOptions.uniques) modOptions.uniques.addAll(ruleset.modOptions.uniques)
modOptions.constants.merge(ruleset.modOptions.constants) modOptions.constants.merge(ruleset.modOptions.constants)
@ -159,6 +162,7 @@ class Ruleset {
unitPromotions.clear() unitPromotions.clear()
units.clear() units.clear()
unitTypes.clear() unitTypes.clear()
victories.clear()
} }
fun allRulesetObjects(): Sequence<IRulesetObject> = fun allRulesetObjects(): Sequence<IRulesetObject> =
@ -181,6 +185,7 @@ class Ruleset {
unitPromotions.values.asSequence() + unitPromotions.values.asSequence() +
units.values.asSequence() + units.values.asSequence() +
unitTypes.values.asSequence() unitTypes.values.asSequence()
// Victories is only INamed
fun allIHasUniques(): Sequence<IHasUniques> = fun allIHasUniques(): Sequence<IHasUniques> =
allRulesetObjects() + sequenceOf(modOptions) allRulesetObjects() + sequenceOf(modOptions)
@ -258,7 +263,7 @@ class Ruleset {
// Setup this branch // Setup this branch
branch.requires = ArrayList() branch.requires = ArrayList()
branch.branch = branch branch.branch = branch
for (victoryType in VictoryType.values()) { for (victoryType in victories.values) {
if (victoryType.name !in branch.priorities.keys) { if (victoryType.name !in branch.priorities.keys) {
branch.priorities[victoryType.name] = 0 branch.priorities[victoryType.name] = 0
} }
@ -306,6 +311,33 @@ class Ruleset {
if (globalUniquesFile.exists()) { if (globalUniquesFile.exists()) {
globalUniques = jsonParser.getFromJson(GlobalUniques::class.java, globalUniquesFile) globalUniques = jsonParser.getFromJson(GlobalUniques::class.java, globalUniquesFile)
} }
val victoryTypesFiles = folderHandle.child("VictoryTypes.json")
if (victoryTypesFiles.exists()) {
victories += createHashmap(jsonParser.getFromJson(Array<Victory>::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 val gameBasicsLoadTime = System.currentTimeMillis() - gameBasicsStartTime
if (printOutput) println("Loading ruleset - " + gameBasicsLoadTime + "ms") if (printOutput) println("Loading ruleset - " + gameBasicsLoadTime + "ms")
@ -354,7 +386,7 @@ class Ruleset {
forOptionsPopup: Boolean forOptionsPopup: Boolean
) { ) {
val name = if (uniqueContainer is INamed) uniqueContainer.name else "" val name = if (uniqueContainer is INamed) uniqueContainer.name else ""
for (unique in uniqueContainer.uniqueObjects) { for (unique in uniqueContainer.uniqueObjects) {
val errors = checkUnique( val errors = checkUnique(
unique, unique,
@ -771,6 +803,18 @@ class Ruleset {
for (unitType in unitTypes.values) { for (unitType in unitTypes.values) {
checkUniques(unitType, lines, rulesetSpecific, forOptionsPopup) 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 (difficulty in difficulties.values) {
for (unitName in difficulty.aiCityStateBonusStartingUnits + difficulty.aiMajorCivBonusStartingUnits + difficulty.playerBonusStartingUnits) for (unitName in difficulty.aiCityStateBonusStartingUnits + difficulty.aiMajorCivBonusStartingUnits + difficulty.playerBonusStartingUnits)
@ -878,7 +922,6 @@ object RulesetCache : HashMap<String,Ruleset>() {
this[optionalBaseRuleset]!! this[optionalBaseRuleset]!!
else getVanillaRuleset() else getVanillaRuleset()
val loadedMods = mods.asSequence() val loadedMods = mods.asSequence()
.filter { containsKey(it) } .filter { containsKey(it) }
.map { this[it]!! } .map { this[it]!! }
@ -896,19 +939,9 @@ object RulesetCache : HashMap<String,Ruleset>() {
} }
newRuleset.updateBuildingCosts() // only after we've added all the mods can we calculate the building costs newRuleset.updateBuildingCosts() // only after we've added all the mods can we calculate the building costs
// This one should be temporary println(optionalBaseRuleset)
if (newRuleset.unitTypes.isEmpty()) { println(newRuleset.victories)
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
}
return newRuleset return newRuleset
} }

View File

@ -1,12 +1,13 @@
package com.unciv.models.ruleset package com.unciv.models.ruleset
import com.unciv.models.ruleset.unique.IHasUniques
import com.unciv.models.ruleset.unique.Unique import com.unciv.models.ruleset.unique.Unique
import com.unciv.models.stats.INamed import com.unciv.models.stats.INamed
import com.unciv.models.stats.NamedStats import com.unciv.models.stats.NamedStats
import com.unciv.ui.civilopedia.FormattedLine import com.unciv.ui.civilopedia.FormattedLine
import com.unciv.ui.civilopedia.ICivilopediaText import com.unciv.ui.civilopedia.ICivilopediaText
interface IRulesetObject:INamed, IHasUniques, ICivilopediaText interface IRulesetObject: INamed, IHasUniques, ICivilopediaText
abstract class RulesetObject: IRulesetObject { abstract class RulesetObject: IRulesetObject {
override var name = "" override var name = ""

View File

@ -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<String>()
val milestoneObjects by lazy { milestones.map { Milestone(it, this) }}
val requiredSpaceshipParts = ArrayList<String>()
val requiredSpaceshipPartsAsCounter by lazy {
val parts = Counter<String>()
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<ThingToFocus> = 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<String> {
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<TextButton> {
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
}
}
}

View File

@ -1,9 +1,4 @@
package com.unciv.models.ruleset package com.unciv.models.ruleset.unique
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
/** /**
* Common interface for all 'ruleset objects' that have Uniques, like BaseUnit, Nation, etc. * Common interface for all 'ruleset objects' that have Uniques, like BaseUnit, Nation, etc.

View File

@ -5,7 +5,6 @@ import com.unciv.models.metadata.BaseRuleset
import com.unciv.models.ruleset.BeliefType import com.unciv.models.ruleset.BeliefType
import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.RulesetCache import com.unciv.models.ruleset.RulesetCache
import com.unciv.models.ruleset.VictoryType
import com.unciv.models.ruleset.tile.ResourceType import com.unciv.models.ruleset.tile.ResourceType
import com.unciv.models.stats.Stat import com.unciv.models.stats.Stat
import com.unciv.models.translations.TranslationFileWriter // for Kdoc only import com.unciv.models.translations.TranslationFileWriter // for Kdoc only
@ -429,7 +428,7 @@ enum class UniqueParameterType(
parameterText: String, parameterText: String,
ruleset: Ruleset ruleset: Ruleset
): UniqueType.UniqueComplianceErrorSeverity? { ): UniqueType.UniqueComplianceErrorSeverity? {
return if (parameterText in VictoryType.values().map { it.name }) null return if (parameterText in ruleset.victories) null
else UniqueType.UniqueComplianceErrorSeverity.RulesetInvariant else UniqueType.UniqueComplianceErrorSeverity.RulesetInvariant
} }
}, },

View File

@ -6,8 +6,8 @@ import com.unciv.logic.city.CityInfo
import com.unciv.logic.civilization.* import com.unciv.logic.civilization.*
import com.unciv.logic.map.MapUnit import com.unciv.logic.map.MapUnit
import com.unciv.logic.map.TileInfo import com.unciv.logic.map.TileInfo
import com.unciv.models.ruleset.ThingToFocus
import com.unciv.models.ruleset.unique.UniqueType.* import com.unciv.models.ruleset.unique.UniqueType.*
import com.unciv.models.ruleset.VictoryType
import com.unciv.models.stats.Stat import com.unciv.models.stats.Stat
import com.unciv.models.translations.fillPlaceholders import com.unciv.models.translations.fillPlaceholders
import com.unciv.models.translations.hasPlaceholderParameters import com.unciv.models.translations.hasPlaceholderParameters
@ -144,14 +144,12 @@ object UniqueTriggerActivation {
if (greatPeople.isEmpty()) return false if (greatPeople.isEmpty()) return false
var greatPerson = greatPeople.random() var greatPerson = greatPeople.random()
val preferredVictoryType = civInfo.victoryType() if (civInfo.wantsToFocusOn(ThingToFocus.Culture)) {
if (preferredVictoryType == VictoryType.Cultural) {
val culturalGP = val culturalGP =
greatPeople.firstOrNull { it.uniques.contains("Great Person - [Culture]") } greatPeople.firstOrNull { it.uniques.contains("Great Person - [Culture]") }
if (culturalGP != null) greatPerson = culturalGP if (culturalGP != null) greatPerson = culturalGP
} }
if (preferredVictoryType == VictoryType.Scientific) { if (civInfo.wantsToFocusOn(ThingToFocus.Science)) {
val scientificGP = val scientificGP =
greatPeople.firstOrNull { it.uniques.contains("Great Person - [Science]") } greatPeople.firstOrNull { it.uniques.contains("Great Person - [Science]") }
if (scientificGP != null) greatPerson = scientificGP if (scientificGP != null) greatPerson = scientificGP
@ -459,7 +457,7 @@ object UniqueTriggerActivation {
if (!civ.isBarbarian() && !civ.isSpectator()) if (!civ.isBarbarian() && !civ.isSpectator())
civ.addFlag( civ.addFlag(
CivFlags.TurnsTillNextDiplomaticVote.name, CivFlags.TurnsTillNextDiplomaticVote.name,
civInfo.getTurnsBetweenDiplomaticVotings() civInfo.getTurnsBetweenDiplomaticVotes()
) )
if (notification != null) if (notification != null)
civInfo.addNotification(notification, NotificationIcon.Diplomacy) civInfo.addNotification(notification, NotificationIcon.Diplomacy)

View File

@ -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), CannotBeBarbarian("Never appears as a Barbarian unit", UniqueTarget.Unit, flags = UniqueFlag.setOfHiddenToUsers),
ReligiousUnit("Religious Unit", UniqueTarget.Unit), 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), AddInCapital("Can be added to [comment] in the Capital", UniqueTarget.Unit),

View File

@ -35,7 +35,7 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction {
var interceptRange = 0 var interceptRange = 0
lateinit var unitType: String lateinit var unitType: String
fun getType() = ruleset.unitTypes[unitType]!! fun getType() = ruleset.unitTypes[unitType]!!
var requiredTech: String? = null override var requiredTech: String? = null
private var requiredResource: String? = null private var requiredResource: String? = null
override fun getUniqueTarget() = UniqueTarget.Unit override fun getUniqueTarget() = UniqueTarget.Unit

View File

@ -1,10 +1,7 @@
package com.unciv.models.ruleset.unit package com.unciv.models.ruleset.unit
import com.unciv.models.ruleset.IHasUniques
import com.unciv.models.ruleset.RulesetObject import com.unciv.models.ruleset.RulesetObject
import com.unciv.models.ruleset.unique.Unique
import com.unciv.models.ruleset.unique.UniqueTarget import com.unciv.models.ruleset.unique.UniqueTarget
import com.unciv.models.stats.INamed
enum class UnitLayer { // The layer in which the unit moves enum class UnitLayer { // The layer in which the unit moves

View File

@ -1,9 +1,9 @@
package com.unciv.models.simulation package com.unciv.models.simulation
import com.unciv.Constants import com.unciv.Constants
import com.unciv.UncivGame
import com.unciv.logic.GameInfo import com.unciv.logic.GameInfo
import com.unciv.logic.GameStarter import com.unciv.logic.GameStarter
import com.unciv.models.ruleset.VictoryType
import com.unciv.models.metadata.GameSetupInfo import com.unciv.models.metadata.GameSetupInfo
import com.unciv.ui.crashhandling.crashHandlingThread import com.unciv.ui.crashhandling.crashHandlingThread
import kotlin.time.Duration import kotlin.time.Duration
@ -23,7 +23,7 @@ class Simulation(
private var endTime: Long = 0 private var endTime: Long = 0
var steps = ArrayList<SimulationStep>() var steps = ArrayList<SimulationStep>()
var winRate = mutableMapOf<String, MutableInt>() var winRate = mutableMapOf<String, MutableInt>()
private var winRateByVictory = HashMap<String, MutableMap<VictoryType, MutableInt>>() private var winRateByVictory = HashMap<String, MutableMap<String, MutableInt>>()
private var avgSpeed = 0f private var avgSpeed = 0f
private var avgDuration: Duration = Duration.ZERO private var avgDuration: Duration = Duration.ZERO
private var totalTurns = 0 private var totalTurns = 0
@ -35,7 +35,7 @@ class Simulation(
for (civ in civilizations) { for (civ in civilizations) {
this.winRate[civ] = MutableInt(0) this.winRate[civ] = MutableInt(0)
winRateByVictory[civ] = mutableMapOf() winRateByVictory[civ] = mutableMapOf()
for (victory in VictoryType.values()) for (victory in UncivGame.Current.gameInfo.ruleSet.victories.keys)
winRateByVictory[civ]!![victory] = MutableInt(0) winRateByVictory[civ]!![victory] = MutableInt(0)
} }
} }
@ -118,7 +118,7 @@ class Simulation(
outString += "\n$civ:\n" outString += "\n$civ:\n"
val wins = winRate[civ]!!.value * 100 / max(steps.size, 1) val wins = winRate[civ]!!.value * 100 / max(steps.size, 1)
outString += "$wins% total win rate \n" 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) val winsVictory = winRateByVictory[civ]!![victory]!!.value * 100 / max(winRate[civ]!!.value, 1)
outString += "$victory: $winsVictory% " outString += "$victory: $winsVictory% "
} }

View File

@ -4,7 +4,7 @@ import com.unciv.logic.GameInfo
class SimulationStep (gameInfo: GameInfo) { class SimulationStep (gameInfo: GameInfo) {
var turns = gameInfo.turns var turns = gameInfo.turns
var victoryType = gameInfo.currentPlayerCiv.victoryManager.hasWonVictoryType() var victoryType = gameInfo.currentPlayerCiv.victoryManager.getVictoryTypeAchieved()
var winner: String? = null var winner: String? = null
val currentPlayer = gameInfo.currentPlayer val currentPlayer = gameInfo.currentPlayer
// val durationString: String = formatDuration(Duration.ofMillis(System.currentTimeMillis() - startTime)) // val durationString: String = formatDuration(Duration.ofMillis(System.currentTimeMillis() - startTime))

View File

@ -377,7 +377,9 @@ object TranslationFileWriter {
when { when {
// Promotion names are not uniques but since we did the "[unitName] ability" // Promotion names are not uniques but since we did the "[unitName] ability"
// they need the "parameters" treatment too // 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) for (item in fieldValue)
if (item is String) submitString(item, Unique(item)) else serializeElement(item!!) if (item is String) submitString(item, Unique(item)) else serializeElement(item!!)
fieldValue is java.util.AbstractCollection<*> -> fieldValue is java.util.AbstractCollection<*> ->
@ -456,6 +458,7 @@ object TranslationFileWriter {
"UnitPromotions" -> emptyArray<Promotion>().javaClass "UnitPromotions" -> emptyArray<Promotion>().javaClass
"Units" -> emptyArray<BaseUnit>().javaClass "Units" -> emptyArray<BaseUnit>().javaClass
"UnitTypes" -> emptyArray<UnitType>().javaClass "UnitTypes" -> emptyArray<UnitType>().javaClass
"VictoryTypes" -> emptyArray<Victory>().javaClass
else -> this.javaClass // dummy value else -> this.javaClass // dummy value
} }
} }

View File

@ -322,6 +322,10 @@ fun String.tr(): String {
return fullyTranslatedString return fullyTranslatedString
} }
if (contains('{')) { // Translating partial sentences
return curlyBraceRegex.replace(this) { it.groups[1]!!.value.tr() }
}
// There might still be optimization potential here! // There might still be optimization potential here!
if (contains('[')) { // Placeholders! if (contains('[')) { // Placeholders!
/** /**
@ -371,9 +375,6 @@ fun String.tr(): String {
return languageSpecificPlaceholder // every component is already translated 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() if (Stats.isStats(this)) return Stats.parse(this).toString()

View File

@ -8,6 +8,7 @@ import com.badlogic.gdx.scenes.scene2d.ui.*
import com.unciv.Constants import com.unciv.Constants
import com.unciv.UncivGame import com.unciv.UncivGame
import com.unciv.models.ruleset.* import com.unciv.models.ruleset.*
import com.unciv.models.ruleset.unique.IHasUniques
import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.stats.INamed import com.unciv.models.stats.INamed
import com.unciv.models.translations.tr import com.unciv.models.translations.tr
@ -177,13 +178,13 @@ class CivilopediaScreen(
val religionEnabled = if (game.isGameInfoInitialized()) game.gameInfo.isReligionEnabled() val religionEnabled = if (game.isGameInfoInitialized()) game.gameInfo.isReligionEnabled()
else ruleset.beliefs.isNotEmpty() else ruleset.beliefs.isNotEmpty()
val victoryTypes = if (game.isGameInfoInitialized()) game.gameInfo.gameParameters.victoryTypes val victoryTypes = if (game.isGameInfoInitialized()) game.gameInfo.gameParameters.victoryTypes
else VictoryType.values().toList() else listOf()
fun shouldBeDisplayed(obj: IHasUniques): Boolean { fun shouldBeDisplayed(obj: IHasUniques): Boolean {
return when { return when {
obj.hasUnique(UniqueType.HiddenFromCivilopedia) -> false obj.hasUnique(UniqueType.HiddenFromCivilopedia) -> false
(!religionEnabled && obj.hasUnique(UniqueType.HiddenWithoutReligion)) -> 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 else -> true
} }
} }

View File

@ -5,7 +5,6 @@ import com.unciv.UncivGame
import com.unciv.logic.civilization.CityStateType import com.unciv.logic.civilization.CityStateType
import com.unciv.models.metadata.GameSpeed import com.unciv.models.metadata.GameSpeed
import com.unciv.models.ruleset.RulesetCache import com.unciv.models.ruleset.RulesetCache
import com.unciv.models.ruleset.VictoryType
import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.translations.tr import com.unciv.models.translations.tr
import com.unciv.ui.audio.MusicMood import com.unciv.ui.audio.MusicMood
@ -135,7 +134,8 @@ class GameOptionsTable(
} }
private fun Table.addMaxTurnsSlider(): UncivSlider? { 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() add("{Max Turns}:".toLabel()).left().expandX()
val slider = UncivSlider(250f, 1500f, 50f) { val slider = UncivSlider(250f, 1500f, 50f) {
@ -229,25 +229,23 @@ class GameOptionsTable(
add("{Victory Conditions}:".toLabel()).colspan(2).row() add("{Victory Conditions}:".toLabel()).colspan(2).row()
// Create a checkbox for each VictoryType existing // Create a checkbox for each VictoryType existing
var i = 0
val victoryConditionsTable = Table().apply { defaults().pad(5f) } val victoryConditionsTable = Table().apply { defaults().pad(5f) }
for (victoryType in VictoryType.values()) { for ((i, victoryType) in ruleset.victories.values.withIndex()) {
if (victoryType == VictoryType.Neutral) continue val victoryCheckbox = victoryType.name.toCheckBox(gameParameters.victoryTypes.contains(victoryType.name)) {
val victoryCheckbox = victoryType.name.toCheckBox(gameParameters.victoryTypes.contains(victoryType)) {
// If the checkbox is checked, adds the victoryTypes else remove it // If the checkbox is checked, adds the victoryTypes else remove it
if (it) { if (it) {
gameParameters.victoryTypes.add(victoryType) gameParameters.victoryTypes.add(victoryType.name)
} else { } else {
gameParameters.victoryTypes.remove(victoryType) gameParameters.victoryTypes.remove(victoryType.name)
} }
// show or hide the max turns select box // show or hide the max turns select box
if (victoryType == VictoryType.Time) if (victoryType.enablesMaxTurns())
update() update()
} }
victoryCheckbox.name = victoryType.name victoryCheckbox.name = victoryType.name
victoryCheckbox.isDisabled = locked victoryCheckbox.isDisabled = locked
victoryConditionsTable.add(victoryCheckbox).left() victoryConditionsTable.add(victoryCheckbox).left()
if (++i % 2 == 0) victoryConditionsTable.row() if ((i + 1) % 2 == 0) victoryConditionsTable.row()
} }
add(victoryConditionsTable).colspan(2).row() add(victoryConditionsTable).colspan(2).row()
} }

View File

@ -41,6 +41,10 @@ class NewGameScreen(
init { init {
updateRuleset() // must come before playerPickerTable so mod nations from fromSettings 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 // 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( playerPickerTable = PlayerPickerTable(
this, gameSetupInfo.gameParameters, this, gameSetupInfo.gameParameters,
if (isNarrowerThan4to3()) stage.width - 20f else 0f if (isNarrowerThan4to3()) stage.width - 20f else 0f
@ -92,6 +96,14 @@ class NewGameScreen(
noHumanPlayersPopup.open() noHumanPlayersPopup.open()
return@onClick 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! Gdx.input.inputProcessor = null // remove input processing - nothing will be clicked!

View File

@ -10,7 +10,6 @@ import com.unciv.logic.map.TileInfo
import com.unciv.models.ruleset.Building import com.unciv.models.ruleset.Building
import com.unciv.models.ruleset.Era import com.unciv.models.ruleset.Era
import com.unciv.models.ruleset.QuestName import com.unciv.models.ruleset.QuestName
import com.unciv.models.ruleset.VictoryType
import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.translations.tr import com.unciv.models.translations.tr
import com.unciv.ui.civilopedia.CivilopediaCategories import com.unciv.ui.civilopedia.CivilopediaCategories
@ -108,7 +107,7 @@ class WonderOverviewTab(
wonder.name in startingObsolete -> false wonder.name in startingObsolete -> false
wonder.getMatchingUniques(UniqueType.HiddenWithoutVictoryType) wonder.getMatchingUniques(UniqueType.HiddenWithoutVictoryType)
.any { unique -> .any { unique ->
!gameInfo.gameParameters.victoryTypes.contains(VictoryType.valueOf(unique.params[0])) !gameInfo.gameParameters.victoryTypes.contains(unique.params[0])
} -> false } -> false
else -> wonderEra <= viewerEra else -> wonderEra <= viewerEra
} }

View File

@ -2,16 +2,12 @@ package com.unciv.ui.victoryscreen
import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.scenes.scene2d.ui.TextButton
import com.badlogic.gdx.utils.Align import com.badlogic.gdx.utils.Align
import com.unciv.Constants import com.unciv.Constants
import com.unciv.logic.civilization.CivilizationInfo 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.translations.tr
import com.unciv.models.metadata.GameSetupInfo 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.images.ImageGetter
import com.unciv.ui.newgamescreen.NewGameScreen import com.unciv.ui.newgamescreen.NewGameScreen
import com.unciv.ui.pickerscreens.PickerScreen import com.unciv.ui.pickerscreens.PickerScreen
@ -22,10 +18,7 @@ class VictoryScreen(val worldScreen: WorldScreen) : PickerScreen() {
val gameInfo = worldScreen.gameInfo val gameInfo = worldScreen.gameInfo
private val playerCivInfo = worldScreen.viewingCiv private val playerCivInfo = worldScreen.viewingCiv
val victoryTypes = gameInfo.gameParameters.victoryTypes val enabledVictoryTypes = gameInfo.gameParameters.victoryTypes
private val scientificVictoryEnabled = victoryTypes.contains(VictoryType.Scientific)
private val culturalVictoryEnabled = victoryTypes.contains(VictoryType.Cultural)
private val dominationVictoryEnabled = victoryTypes.contains(VictoryType.Domination)
private val contentsTable = Table() private val contentsTable = Table()
@ -35,7 +28,7 @@ class VictoryScreen(val worldScreen: WorldScreen) : PickerScreen() {
stage.addActor(difficultyLabel) stage.addActor(difficultyLabel)
val tabsTable = Table().apply { defaults().pad(10f) } 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) if (!playerCivInfo.isSpectator()) tabsTable.add(setMyVictoryButton)
val setGlobalVictoryButton = "Global status".toTextButton().onClick { setGlobalVictoryTable() } val setGlobalVictoryButton = "Global status".toTextButton().onClick { setGlobalVictoryTable() }
tabsTable.add(setGlobalVictoryButton) tabsTable.add(setGlobalVictoryButton)
@ -48,28 +41,27 @@ class VictoryScreen(val worldScreen: WorldScreen) : PickerScreen() {
if (playerCivInfo.isSpectator()) if (playerCivInfo.isSpectator())
setGlobalVictoryTable() setGlobalVictoryTable()
else else
setMyVictoryTable() setOurVictoryTable()
rightSideButton.isVisible = false rightSideButton.isVisible = false
var someoneHasWon = false var someoneHasWon = false
val playerVictoryType = playerCivInfo.victoryManager.hasWonVictoryType() val playerVictoryType = playerCivInfo.victoryManager.getVictoryTypeAchieved()
if (playerVictoryType != null) { if (playerVictoryType != null) {
someoneHasWon = true 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 }) { for (civ in gameInfo.civilizations.filter { it.isMajorCiv() && it != playerCivInfo }) {
val civVictoryType = civ.victoryManager.hasWonVictoryType() val civVictoryType = civ.victoryManager.getVictoryTypeAchieved()
if (civVictoryType != null) { if (civVictoryType != null) {
someoneHasWon = true someoneHasWon = true
val winningCivName = civ.civName wonOrLost("[${civ.civName}] has won a [$civVictoryType] Victory!", civVictoryType, false)
wonOrLost("[$winningCivName] has won a [${civVictoryType.name}] Victory!")
} }
} }
if (playerCivInfo.isDefeated()) { if (playerCivInfo.isDefeated()) {
wonOrLost("") wonOrLost("", null, false)
} else if (!someoneHasWon) { } else if (!someoneHasWon) {
setDefaultCloseAction() setDefaultCloseAction()
onBackButtonClicked { game.setWorldScreen() } onBackButtonClicked { game.setWorldScreen() }
@ -77,18 +69,15 @@ class VictoryScreen(val worldScreen: WorldScreen) : PickerScreen() {
} }
private fun wonOrLost(description: String) { private fun wonOrLost(description: String, victoryType: String?, hasWon: Boolean) {
// 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 =
val endGameMessage = when (description.getPlaceholderParameters().firstOrNull()) { when {
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!" 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.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 == 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!"
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!" hasWon -> playerCivInfo.gameInfo.ruleSet.victories[victoryType]!!.victoryString
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!" else -> playerCivInfo.gameInfo.ruleSet.victories[victoryType]!!.defeatString
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!"
}
descriptionLabel.setText(description.tr() + "\n" + endGameMessage.tr()) descriptionLabel.setText(description.tr() + "\n" + endGameMessage.tr())
rightSideButton.setText("Start new game".tr()) rightSideButton.setText("Start new game".tr())
@ -107,134 +96,81 @@ class VictoryScreen(val worldScreen: WorldScreen) : PickerScreen() {
} }
} }
private fun setOurVictoryTable() {
private fun setMyVictoryTable() { val ourVictoryStatusTable = Table()
val myVictoryStatusTable = Table() ourVictoryStatusTable.defaults().pad(10f)
myVictoryStatusTable.defaults().pad(10f) val victoriesToShow = gameInfo.getEnabledVictories()
if (scientificVictoryEnabled) myVictoryStatusTable.add("Science victory".toLabel())
if (culturalVictoryEnabled) myVictoryStatusTable.add("Cultural victory".toLabel()) for (victory in victoriesToShow) {
if (dominationVictoryEnabled) myVictoryStatusTable.add("Conquest victory".toLabel()) ourVictoryStatusTable.add("[${victory.key}] 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()
} }
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() val table = Table()
table.defaults().pad(5f) table.defaults().pad(5f)
for (civ in playerCivInfo.gameInfo.civilizations) { var firstIncomplete: Boolean = true
if (civ.isCurrentPlayer() || !civ.isMajorCiv()) continue for (milestone in victoryObject.milestoneObjects) {
val civName = val completionStatus =
if (playerCivInfo.diplomacy.containsKey(civ.civName)) civ.civName when {
else Constants.unknownNationName milestone.hasBeenCompletedBy(playerCivInfo) -> CompletionStatus.Completed
table.add(getMilestone("Destroy [$civName]", civ.isDefeated())).row() firstIncomplete -> {
firstIncomplete = false
CompletionStatus.Partially
}
else -> CompletionStatus.Incomplete
}
for (button in milestone.getVictoryScreenButtons(completionStatus, playerCivInfo)) {
table.add(button).row()
}
} }
return table 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() { private fun setGlobalVictoryTable() {
val majorCivs = gameInfo.civilizations.filter { it.isMajorCiv() } val majorCivs = gameInfo.civilizations.filter { it.isMajorCiv() }
val globalVictoryTable = Table().apply { defaults().pad(10f) } val globalVictoryTable = Table().apply { defaults().pad(10f) }
val victoriesToShow = gameInfo.ruleSet.victories.filter { !it.value.hiddenInVictoryScreen && enabledVictoryTypes.contains(it.key) }
if (scientificVictoryEnabled) globalVictoryTable.add(getGlobalScientificVictoryColumn(majorCivs))
if (culturalVictoryEnabled) globalVictoryTable.add(getGlobalCulturalVictoryColumn(majorCivs)) for (victory in victoriesToShow) {
if (dominationVictoryEnabled) globalVictoryTable.add(getGlobalDominationVictoryColumn(majorCivs)) globalVictoryTable.add(getGlobalVictoryColumn(majorCivs, victory.key))
}
contentsTable.clear() contentsTable.clear()
contentsTable.add(globalVictoryTable) contentsTable.add(globalVictoryTable)
} }
private fun getGlobalVictoryColumn(majorCivs: List<CivilizationInfo>, victory: String): Table {
val victoryColumn = Table().apply { defaults().pad(10f) }
victoryColumn.add("[$victory] Victory".toLabel()).row()
victoryColumn.addSeparator()
private fun getGlobalDominationVictoryColumn(majorCivs: List<CivilizationInfo>): Table { for (civ in majorCivs.filter { !it.isDefeated() }.sortedByDescending { it.victoryManager.amountMilestonesCompleted(victory) }) {
val dominationVictoryColumn = Table().apply { defaults().pad(10f) } val buttonText = civ.victoryManager.getNextMilestone(victory)?.getVictoryScreenButtonHeaderText(false, civ) ?: "Done!"
victoryColumn.add(getCivGroup(civ, "\n" + buttonText.tr(), playerCivInfo)).fillX().row()
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<CivilizationInfo>): 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<CivilizationInfo>): 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 (entry in civsToPartsRemaining) { for (civ in majorCivs.filter { it.isDefeated() }.sortedByDescending { it.victoryManager.amountMilestonesCompleted(victory) }) {
val civToPartsBeRemaining = (getCivGroup(entry.civ, " - " + entry.partsRemaining, playerCivInfo)) val buttonText = civ.victoryManager.getNextMilestone(victory)?.getVictoryScreenButtonHeaderText(false, civ) ?: "Done!"
scientificVictoryColumn.add(civToPartsBeRemaining).fillX().row() victoryColumn.add(getCivGroup(civ, "\n" + buttonText.tr(), playerCivInfo)).fillX().row()
} }
return scientificVictoryColumn
return victoryColumn
} }
private fun setCivRankingsTable() { private fun setCivRankingsTable() {
@ -257,36 +193,38 @@ class VictoryScreen(val worldScreen: WorldScreen) : PickerScreen() {
contentsTable.add(civRankingsTable) contentsTable.add(civRankingsTable)
} }
companion object { private fun getCivGroup(civ: CivilizationInfo, afterCivNameText: String, currentPlayer: CivilizationInfo): Table {
fun getCivGroup(civ: CivilizationInfo, afterCivNameText:String, currentPlayer:CivilizationInfo): Table { val civGroup = Table()
val civGroup = Table()
var labelText = civ.civName.tr()+afterCivNameText var labelText = "{${civ.civName.tr()}}{${afterCivNameText.tr()}}"
var labelColor = Color.WHITE var labelColor = Color.WHITE
val backgroundColor: Color val backgroundColor: Color
if (civ.isDefeated()) { if (civ.isDefeated()) {
civGroup.add(ImageGetter.getImage("OtherIcons/DisbandUnit")).size(30f) civGroup.add(ImageGetter.getImage("OtherIcons/DisbandUnit")).size(30f)
backgroundColor = Color.LIGHT_GRAY backgroundColor = Color.LIGHT_GRAY
labelColor = Color.BLACK labelColor = Color.BLACK
} else if (currentPlayer == civ // || game.viewEntireMapForDebug } else if (currentPlayer == civ // || game.viewEntireMapForDebug
|| currentPlayer.knows(civ) || currentPlayer.isDefeated() || currentPlayer.victoryManager.hasWon()) { || currentPlayer.knows(civ)
civGroup.add(ImageGetter.getNationIndicator(civ.nation, 30f)) || currentPlayer.isDefeated()
backgroundColor = civ.nation.getOuterColor() || currentPlayer.victoryManager.hasWon()
labelColor = civ.nation.getInnerColor() ) {
} else { civGroup.add(ImageGetter.getNationIndicator(civ.nation, 30f))
civGroup.add(ImageGetter.getRandomNationIndicator(30f)) backgroundColor = civ.nation.getOuterColor()
backgroundColor = Color.DARK_GRAY labelColor = civ.nation.getInnerColor()
labelText = Constants.unknownNationName } else {
} civGroup.add(ImageGetter.getRandomNationIndicator(30f))
backgroundColor = Color.DARK_GRAY
civGroup.background = ImageGetter.getRoundedEdgeRectangle(backgroundColor) labelText = Constants.unknownNationName
val label = labelText.toLabel(labelColor)
label.setAlignment(Align.center)
civGroup.add(label).padLeft(10f)
civGroup.pack()
return civGroup
} }
civGroup.background = ImageGetter.getRoundedEdgeRectangle(backgroundColor)
val label = labelText.toLabel(labelColor)
label.setAlignment(Align.center)
civGroup.add(label).padLeft(10f)
civGroup.pack()
return civGroup
} }
} }