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