chore: Removed turn management functions from CivInfo

This commit is contained in:
Yair Morgenstern 2023-01-18 15:51:25 +02:00
parent e8f54cc2f5
commit e272a1408d
5 changed files with 295 additions and 265 deletions

View File

@ -18,16 +18,17 @@ import com.unciv.logic.civilization.NotificationCategory
import com.unciv.logic.civilization.NotificationIcon import com.unciv.logic.civilization.NotificationIcon
import com.unciv.logic.civilization.PlayerType import com.unciv.logic.civilization.PlayerType
import com.unciv.logic.civilization.managers.TechManager import com.unciv.logic.civilization.managers.TechManager
import com.unciv.logic.civilization.managers.TurnManager
import com.unciv.logic.map.CityDistanceData import com.unciv.logic.map.CityDistanceData
import com.unciv.logic.map.TileInfo import com.unciv.logic.map.TileInfo
import com.unciv.logic.map.TileMap import com.unciv.logic.map.TileMap
import com.unciv.models.Religion import com.unciv.models.Religion
import com.unciv.models.metadata.GameParameters import com.unciv.models.metadata.GameParameters
import com.unciv.models.ruleset.nation.Difficulty
import com.unciv.models.ruleset.ModOptionsConstants import com.unciv.models.ruleset.ModOptionsConstants
import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.RulesetCache import com.unciv.models.ruleset.RulesetCache
import com.unciv.models.ruleset.Speed import com.unciv.models.ruleset.Speed
import com.unciv.models.ruleset.nation.Difficulty
import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.ui.audio.MusicMood import com.unciv.ui.audio.MusicMood
import com.unciv.ui.audio.MusicTrackChooserFlags import com.unciv.ui.audio.MusicTrackChooserFlags
@ -273,7 +274,7 @@ class GameInfo : IsPartOfGameInfoSerialization, HasGameInfoSerializationVersion
// would skip a turn if an AI civ calls nextTurn // would skip a turn if an AI civ calls nextTurn
// this happens when resigning a multiplayer game) // this happens when resigning a multiplayer game)
if (player.isHuman()) { if (player.isHuman()) {
player.endTurn() TurnManager(player).endTurn()
setNextPlayer() setNextPlayer()
} }
@ -290,7 +291,7 @@ class GameInfo : IsPartOfGameInfoSerialization, HasGameInfoSerializationVersion
{ {
// Starting preparations // Starting preparations
player.startTurn() TurnManager(player).startTurn()
// Automation done here // Automation done here
player.doTurn() player.doTurn()
@ -302,7 +303,7 @@ class GameInfo : IsPartOfGameInfoSerialization, HasGameInfoSerializationVersion
} }
// Clean up // Clean up
player.endTurn() TurnManager(player).endTurn()
// To the next player // To the next player
setNextPlayer() setNextPlayer()
@ -317,7 +318,7 @@ class GameInfo : IsPartOfGameInfoSerialization, HasGameInfoSerializationVersion
currentPlayerCiv = getCivilization(currentPlayer) currentPlayerCiv = getCivilization(currentPlayer)
// Starting his turn // Starting his turn
player.startTurn() TurnManager(player).startTurn()
// No popups for spectators // No popups for spectators
if (currentPlayerCiv.isSpectator()) if (currentPlayerCiv.isSpectator())

View File

@ -12,8 +12,8 @@ import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.civilization.NotificationCategory import com.unciv.logic.civilization.NotificationCategory
import com.unciv.logic.civilization.NotificationIcon import com.unciv.logic.civilization.NotificationIcon
import com.unciv.logic.civilization.Proximity import com.unciv.logic.civilization.Proximity
import com.unciv.logic.civilization.managers.ReligionState
import com.unciv.logic.civilization.diplomacy.DiplomacyFlags import com.unciv.logic.civilization.diplomacy.DiplomacyFlags
import com.unciv.logic.civilization.managers.ReligionState
import com.unciv.logic.map.RoadStatus import com.unciv.logic.map.RoadStatus
import com.unciv.logic.map.TileInfo import com.unciv.logic.map.TileInfo
import com.unciv.logic.map.TileMap import com.unciv.logic.map.TileMap
@ -213,13 +213,13 @@ class CityInfo : IsPartOfGameInfoSerialization {
// Update proximity rankings for all civs // Update proximity rankings for all civs
for (otherCiv in civInfo.gameInfo.getAliveMajorCivs()) { for (otherCiv in civInfo.gameInfo.getAliveMajorCivs()) {
if (civInfo.getProximity(otherCiv) != Proximity.Neighbors) // unless already neighbors if (civInfo.getProximity(otherCiv) != Proximity.Neighbors) // unless already neighbors
civInfo.updateProximity(otherCiv, civInfo.cache.updateProximity(otherCiv,
otherCiv.updateProximity(civInfo)) otherCiv.cache.updateProximity(civInfo))
} }
for (otherCiv in civInfo.gameInfo.getAliveCityStates()) { for (otherCiv in civInfo.gameInfo.getAliveCityStates()) {
if (civInfo.getProximity(otherCiv) != Proximity.Neighbors) // unless already neighbors if (civInfo.getProximity(otherCiv) != Proximity.Neighbors) // unless already neighbors
civInfo.updateProximity(otherCiv, civInfo.cache.updateProximity(otherCiv,
otherCiv.updateProximity(civInfo)) otherCiv.cache.updateProximity(civInfo))
} }
triggerCitiesSettledNearOtherCiv() triggerCitiesSettledNearOtherCiv()

View File

@ -14,6 +14,9 @@ import com.unciv.logic.automation.unit.WorkerAutomation
import com.unciv.logic.city.CityInfo import com.unciv.logic.city.CityInfo
import com.unciv.logic.civilization.diplomacy.CityStateFunctions import com.unciv.logic.civilization.diplomacy.CityStateFunctions
import com.unciv.logic.civilization.diplomacy.CityStatePersonality import com.unciv.logic.civilization.diplomacy.CityStatePersonality
import com.unciv.logic.civilization.diplomacy.DiplomacyFlags
import com.unciv.logic.civilization.diplomacy.DiplomacyManager
import com.unciv.logic.civilization.diplomacy.DiplomaticStatus
import com.unciv.logic.civilization.managers.EspionageManager import com.unciv.logic.civilization.managers.EspionageManager
import com.unciv.logic.civilization.managers.GoldenAgeManager import com.unciv.logic.civilization.managers.GoldenAgeManager
import com.unciv.logic.civilization.managers.GreatPersonManager import com.unciv.logic.civilization.managers.GreatPersonManager
@ -23,25 +26,20 @@ import com.unciv.logic.civilization.managers.ReligionManager
import com.unciv.logic.civilization.managers.RuinsManager import com.unciv.logic.civilization.managers.RuinsManager
import com.unciv.logic.civilization.managers.TechManager import com.unciv.logic.civilization.managers.TechManager
import com.unciv.logic.civilization.managers.VictoryManager import com.unciv.logic.civilization.managers.VictoryManager
import com.unciv.logic.civilization.diplomacy.DiplomacyFlags
import com.unciv.logic.civilization.diplomacy.DiplomacyManager
import com.unciv.logic.civilization.diplomacy.DiplomaticStatus
import com.unciv.logic.civilization.transients.CivInfoStatsForNextTurn import com.unciv.logic.civilization.transients.CivInfoStatsForNextTurn
import com.unciv.logic.civilization.transients.CivInfoTransientCache import com.unciv.logic.civilization.transients.CivInfoTransientCache
import com.unciv.logic.map.MapUnit import com.unciv.logic.map.MapUnit
import com.unciv.logic.map.TileInfo import com.unciv.logic.map.TileInfo
import com.unciv.logic.map.UnitMovementAlgorithms import com.unciv.logic.map.UnitMovementAlgorithms
import com.unciv.logic.trade.TradeEvaluation
import com.unciv.logic.trade.TradeRequest import com.unciv.logic.trade.TradeRequest
import com.unciv.models.Counter import com.unciv.models.Counter
import com.unciv.models.ruleset.Building import com.unciv.models.ruleset.Building
import com.unciv.models.ruleset.nation.CityStateType
import com.unciv.models.ruleset.nation.Difficulty
import com.unciv.models.ruleset.tech.Era
import com.unciv.models.ruleset.ModOptionsConstants
import com.unciv.models.ruleset.nation.Nation
import com.unciv.models.ruleset.Policy import com.unciv.models.ruleset.Policy
import com.unciv.models.ruleset.Victory import com.unciv.models.ruleset.Victory
import com.unciv.models.ruleset.nation.CityStateType
import com.unciv.models.ruleset.nation.Difficulty
import com.unciv.models.ruleset.nation.Nation
import com.unciv.models.ruleset.tech.Era
import com.unciv.models.ruleset.tile.ResourceSupplyList import com.unciv.models.ruleset.tile.ResourceSupplyList
import com.unciv.models.ruleset.tile.ResourceType import com.unciv.models.ruleset.tile.ResourceType
import com.unciv.models.ruleset.tile.TileResource import com.unciv.models.ruleset.tile.TileResource
@ -49,13 +47,11 @@ import com.unciv.models.ruleset.unique.StateForConditionals
import com.unciv.models.ruleset.unique.TemporaryUnique import com.unciv.models.ruleset.unique.TemporaryUnique
import com.unciv.models.ruleset.unique.Unique import com.unciv.models.ruleset.unique.Unique
import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.ruleset.unique.endTurn
import com.unciv.models.ruleset.unique.getMatchingUniques import com.unciv.models.ruleset.unique.getMatchingUniques
import com.unciv.models.ruleset.unit.BaseUnit import com.unciv.models.ruleset.unit.BaseUnit
import com.unciv.models.stats.Stat import com.unciv.models.stats.Stat
import com.unciv.models.stats.Stats import com.unciv.models.stats.Stats
import com.unciv.models.translations.tr import com.unciv.models.translations.tr
import com.unciv.ui.utils.MayaCalendar
import com.unciv.ui.utils.extensions.toPercent import com.unciv.ui.utils.extensions.toPercent
import com.unciv.ui.utils.extensions.withItem import com.unciv.ui.utils.extensions.withItem
import com.unciv.ui.victoryscreen.RankingType import com.unciv.ui.victoryscreen.RankingType
@ -136,7 +132,7 @@ class CivilizationInfo : IsPartOfGameInfoSerialization {
val cityStateFunctions = CityStateFunctions(this) val cityStateFunctions = CityStateFunctions(this)
@Transient @Transient
private var cachedMilitaryMight = -1 var cachedMilitaryMight = -1
@Transient @Transient
var passThroughImpassableUnlocked = false // Cached Boolean equal to passableImpassables.isNotEmpty() var passThroughImpassableUnlocked = false // Cached Boolean equal to passableImpassables.isNotEmpty()
@ -194,7 +190,7 @@ class CivilizationInfo : IsPartOfGameInfoSerialization {
val tradeRequests = ArrayList<TradeRequest>() val tradeRequests = ArrayList<TradeRequest>()
/** See DiplomacyManager.flagsCountdown for why this does not map Enums to ints */ /** See DiplomacyManager.flagsCountdown for why this does not map Enums to ints */
private var flagsCountdown = HashMap<String, Int>() var flagsCountdown = HashMap<String, Int>()
/** Arraylist instead of HashMap as the same unique might appear multiple times /** Arraylist instead of HashMap as the same unique might appear multiple times
* We don't use pairs, as these cannot be serialized due to having no no-arg constructor * We don't use pairs, as these cannot be serialized due to having no no-arg constructor
@ -806,7 +802,7 @@ class CivilizationInfo : IsPartOfGameInfoSerialization {
fun isMinorCivAggressor() = numMinorCivsAttacked >= 2 fun isMinorCivAggressor() = numMinorCivsAttacked >= 2
fun isMinorCivWarmonger() = numMinorCivsAttacked >= 4 fun isMinorCivWarmonger() = numMinorCivsAttacked >= 4
private fun isLongCountActive(): Boolean { fun isLongCountActive(): Boolean {
val unique = getMatchingUniques(UniqueType.MayanGainGreatPerson).firstOrNull() val unique = getMatchingUniques(UniqueType.MayanGainGreatPerson).firstOrNull()
?: return false ?: return false
return tech.isResearched(unique.params[1]) return tech.isResearched(unique.params[1])
@ -943,53 +939,6 @@ class CivilizationInfo : IsPartOfGameInfoSerialization {
gameInfo.barbarians.updateEncampments() gameInfo.barbarians.updateEncampments()
} }
fun startTurn() {
civConstructions.startTurn()
attacksSinceTurnStart.clear()
updateStatsForNextTurn() // for things that change when turn passes e.g. golden age, city state influence
// Do this after updateStatsForNextTurn but before cities.startTurn
if (playerType == PlayerType.AI && gameInfo.ruleSet.modOptions.uniques.contains(ModOptionsConstants.convertGoldToScience))
NextTurnAutomation.automateGoldToSciencePercentage(this)
// Generate great people at the start of the turn,
// so they won't be generated out in the open and vulnerable to enemy attacks before you can control them
if (cities.isNotEmpty()) { //if no city available, addGreatPerson will throw exception
val greatPerson = greatPeople.getNewGreatPerson()
if (greatPerson != null && gameInfo.ruleSet.units.containsKey(greatPerson)) addUnit(greatPerson)
religionManager.startTurn()
if (isLongCountActive())
MayaCalendar.startTurnForMaya(this)
}
cache.updateViewableTiles() // adds explored tiles so that the units will be able to perform automated actions better
cache.updateCitiesConnectedToCapital()
startTurnFlags()
updateRevolts()
for (city in cities) city.startTurn() // Most expensive part of startTurn
for (unit in getCivUnits()) unit.startTurn()
if (playerType == PlayerType.Human && UncivGame.Current.settings.automatedUnitsMoveOnTurnStart) {
hasMovedAutomatedUnits = true
for (unit in getCivUnits())
unit.doAction()
} else hasMovedAutomatedUnits = false
cache.updateCivResources() // If you offered a trade last turn, this turn it will have been accepted/declined
for (tradeRequest in tradeRequests.toList()) { // remove trade requests where one of the sides can no longer supply
val offeringCiv = gameInfo.getCivilization(tradeRequest.requestingCiv)
if (offeringCiv.isDefeated() || !TradeEvaluation().isTradeValid(tradeRequest.trade, this, offeringCiv)) {
tradeRequests.remove(tradeRequest)
// Yes, this is the right direction. I checked.
offeringCiv.addNotification("Our proposed trade is no longer relevant!", NotificationCategory.Trade, NotificationIcon.Trade)
}
}
updateWinningCiv()
}
fun updateWinningCiv(){ fun updateWinningCiv(){
if (gameInfo.victoryData == null) { if (gameInfo.victoryData == null) {
val victoryType = victoryManager.getVictoryTypeAchieved() val victoryType = victoryManager.getVictoryTypeAchieved()
@ -1002,129 +951,6 @@ class CivilizationInfo : IsPartOfGameInfoSerialization {
} }
} }
fun endTurn() {
val notificationsThisTurn = NotificationsLog(gameInfo.turns)
notificationsThisTurn.notifications.addAll(notifications)
while (notificationsLog.size >= UncivGame.Current.settings.notificationsLogMaxTurns) {
notificationsLog.removeFirst()
}
if (notificationsThisTurn.notifications.isNotEmpty())
notificationsLog.add(notificationsThisTurn)
notifications.clear()
updateStatsForNextTurn()
val nextTurnStats = stats.statsForNextTurn
policies.endTurn(nextTurnStats.culture.toInt())
totalCultureForContests += nextTurnStats.culture.toInt()
if (isCityState())
questManager.endTurn()
// disband units until there are none left OR the gold values are normal
if (!isBarbarian() && gold < -100 && nextTurnStats.gold.toInt() < 0) {
for (i in 1 until (gold / -100)) {
var civMilitaryUnits = getCivUnits().filter { it.baseUnit.isMilitary() }
if (civMilitaryUnits.any()) {
val unitToDisband = civMilitaryUnits.first()
unitToDisband.disband()
civMilitaryUnits -= unitToDisband
val unitName = unitToDisband.shortDisplayName()
addNotification("Cannot provide unit upkeep for $unitName - unit has been disbanded!", NotificationCategory.Units, unitName, NotificationIcon.Death)
}
}
}
addGold( nextTurnStats.gold.toInt() )
if (cities.isNotEmpty() && gameInfo.ruleSet.technologies.isNotEmpty())
tech.endTurn(nextTurnStats.science.toInt())
religionManager.endTurn(nextTurnStats.faith.toInt())
totalFaithForContests += nextTurnStats.faith.toInt()
espionageManager.endTurn()
if (isMajorCiv()) greatPeople.addGreatPersonPoints(getGreatPersonPointsForNextTurn()) // City-states don't get great people!
// To handle tile's owner issue (#8246), we need to run being razed city first.
for (city in sequence {
yieldAll(cities.filter { it.isBeingRazed })
yieldAll(cities.filterNot { it.isBeingRazed })
}.toList()) { // a city can be removed while iterating (if it's being razed) so we need to iterate over a copy
city.endTurn()
}
temporaryUniques.endTurn()
goldenAges.endTurn(getHappiness())
getCivUnits().forEach { it.endTurn() } // This is the most expensive part of endTurn
diplomacy.values.toList().forEach { it.nextTurn() } // we copy the diplomacy values so if it changes in-loop we won't crash
cache.updateHasActiveEnemyMovementPenalty()
cachedMilitaryMight = -1 // Reset so we don't use a value from a previous turn
updateWinningCiv() // Maybe we did something this turn to win
}
private fun startTurnFlags() {
for (flag in flagsCountdown.keys.toList()) {
// In case we remove flags while iterating
if (!flagsCountdown.containsKey(flag)) continue
if (flag == CivFlags.CityStateGreatPersonGift.name) {
val cityStateAllies: List<CivilizationInfo> =
getKnownCivs().filter { it.isCityState() && it.getAllyCiv() == civName }
val givingCityState = cityStateAllies.filter { it.cities.isNotEmpty() }.randomOrNull()
if (cityStateAllies.isNotEmpty()) flagsCountdown[flag] = flagsCountdown[flag]!! - 1
if (flagsCountdown[flag]!! < min(cityStateAllies.size, 10) && cities.isNotEmpty()
&& givingCityState != null
) {
givingCityState.cityStateFunctions.giveGreatPersonToPatron(this)
flagsCountdown[flag] = cityStateFunctions.turnsForGreatPersonFromCityState()
}
continue
}
if (flagsCountdown[flag]!! > 0)
flagsCountdown[flag] = flagsCountdown[flag]!! - 1
if (flagsCountdown[flag] != 0) continue
when (flag) {
CivFlags.RevoltSpawning.name -> doRevoltSpawn()
}
}
handleDiplomaticVictoryFlags()
}
private fun handleDiplomaticVictoryFlags() {
if (flagsCountdown[CivFlags.ShouldResetDiplomaticVotes.name] == 0) {
gameInfo.diplomaticVictoryVotesCast.clear()
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, getTurnsBetweenDiplomaticVotes())
}
}
if (flagsCountdown[CivFlags.TurnsTillNextDiplomaticVote.name] == 0) {
addFlag(CivFlags.ShowDiplomaticVotingResults.name, 1)
}
}
fun addFlag(flag: String, count: Int) = flagsCountdown.set(flag, count) fun addFlag(flag: String, count: Int) = flagsCountdown.set(flag, count)
fun removeFlag(flag: String) = flagsCountdown.remove(flag) fun removeFlag(flag: String) = flagsCountdown.remove(flag)
fun hasFlag(flag: String) = flagsCountdown.contains(flag) fun hasFlag(flag: String) = flagsCountdown.contains(flag)
@ -1151,76 +977,6 @@ class CivilizationInfo : IsPartOfGameInfoSerialization {
&& gameInfo.civilizations.any { it.isMajorCiv() && !it.isDefeated() && it != this } && gameInfo.civilizations.any { it.isMajorCiv() && !it.isDefeated() && it != this }
private fun updateRevolts() {
if (gameInfo.civilizations.none { it.isBarbarian() }) {
// Can't spawn revolts without barbarians ¯\_(ツ)_/¯
return
}
if (!hasUnique(UniqueType.SpawnRebels)) {
removeFlag(CivFlags.RevoltSpawning.name)
return
}
if (!hasFlag(CivFlags.RevoltSpawning.name)) {
addFlag(CivFlags.RevoltSpawning.name, max(getTurnsBeforeRevolt(),1))
return
}
}
private fun doRevoltSpawn() {
val barbarians = try {
// The first test in `updateRevolts` should prevent getting here in a no-barbarians game, but it has been shown to still occur
gameInfo.getBarbarianCivilization()
} catch (ex: NoSuchElementException) {
removeFlag(CivFlags.RevoltSpawning.name)
return
}
val random = Random()
val rebelCount = 1 + random.nextInt(100 + 20 * (cities.size - 1)) / 100
val spawnCity = cities.maxByOrNull { random.nextInt(it.population.population + 10) } ?: return
val spawnTile = spawnCity.getTiles().maxByOrNull { rateTileForRevoltSpawn(it) } ?: return
val unitToSpawn = gameInfo.ruleSet.units.values.asSequence().filter {
it.uniqueTo == null && it.isMelee() && it.isLandUnit()
&& !it.hasUnique(UniqueType.CannotAttack) && it.isBuildable(this)
}.maxByOrNull {
random.nextInt(1000)
} ?: return
repeat(rebelCount) {
gameInfo.tileMap.placeUnitNearTile(
spawnTile.position,
unitToSpawn.name,
barbarians
)
}
// Will be automatically added again as long as unhappiness is still low enough
removeFlag(CivFlags.RevoltSpawning.name)
addNotification("Your citizens are revolting due to very high unhappiness!", spawnTile.position, NotificationCategory.General, unitToSpawn.name, "StatIcons/Malcontent")
}
// Higher is better
private fun rateTileForRevoltSpawn(tile: TileInfo): Int {
if (tile.isWater || tile.militaryUnit != null || tile.civilianUnit != null || tile.isCityCenter() || tile.isImpassible())
return -1
var score = 10
if (tile.improvement == null) {
score += 4
if (tile.resource != null) {
score += 3
}
}
if (tile.getDefensiveBonus() > 0)
score += 4
return score
}
private fun getTurnsBeforeRevolt() =
((4 + Random().nextInt(3)) * max(gameInfo.speed.modifier, 1f)).toInt()
/** Modify gold by a given amount making sure it does neither overflow nor underflow. /** Modify gold by a given amount making sure it does neither overflow nor underflow.
* @param delta the amount to add (can be negative) * @param delta the amount to add (can be negative)
*/ */

View File

@ -0,0 +1,272 @@
package com.unciv.logic.civilization.managers
import com.unciv.UncivGame
import com.unciv.logic.automation.civilization.NextTurnAutomation
import com.unciv.logic.civilization.CivFlags
import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.civilization.NotificationCategory
import com.unciv.logic.civilization.NotificationIcon
import com.unciv.logic.civilization.PlayerType
import com.unciv.logic.map.TileInfo
import com.unciv.logic.trade.TradeEvaluation
import com.unciv.models.ruleset.ModOptionsConstants
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.ruleset.unique.endTurn
import com.unciv.ui.utils.MayaCalendar
import java.util.*
import kotlin.math.max
import kotlin.math.min
class TurnManager(val civInfo: CivilizationInfo) {
fun startTurn() {
civInfo.civConstructions.startTurn()
civInfo.attacksSinceTurnStart.clear()
civInfo.updateStatsForNextTurn() // for things that change when turn passes e.g. golden age, city state influence
// Do this after updateStatsForNextTurn but before cities.startTurn
if (civInfo.playerType == PlayerType.AI && civInfo.gameInfo.ruleSet.modOptions.uniques.contains(
ModOptionsConstants.convertGoldToScience))
NextTurnAutomation.automateGoldToSciencePercentage(civInfo)
// Generate great people at the start of the turn,
// so they won't be generated out in the open and vulnerable to enemy attacks before you can control them
if (civInfo.cities.isNotEmpty()) { //if no city available, addGreatPerson will throw exception
val greatPerson = civInfo.greatPeople.getNewGreatPerson()
if (greatPerson != null && civInfo.gameInfo.ruleSet.units.containsKey(greatPerson))
civInfo.addUnit(greatPerson)
civInfo.religionManager.startTurn()
if (civInfo.isLongCountActive())
MayaCalendar.startTurnForMaya(civInfo)
}
civInfo.cache.updateViewableTiles() // adds explored tiles so that the units will be able to perform automated actions better
civInfo.cache.updateCitiesConnectedToCapital()
startTurnFlags()
updateRevolts()
for (city in civInfo.cities) city.startTurn() // Most expensive part of startTurn
for (unit in civInfo.getCivUnits()) unit.startTurn()
if (civInfo.playerType == PlayerType.Human && UncivGame.Current.settings.automatedUnitsMoveOnTurnStart) {
civInfo.hasMovedAutomatedUnits = true
for (unit in civInfo.getCivUnits())
unit.doAction()
} else civInfo.hasMovedAutomatedUnits = false
civInfo.cache.updateCivResources() // If you offered a trade last turn, this turn it will have been accepted/declined
for (tradeRequest in civInfo.tradeRequests.toList()) { // remove trade requests where one of the sides can no longer supply
val offeringCiv = civInfo.gameInfo.getCivilization(tradeRequest.requestingCiv)
if (offeringCiv.isDefeated() || !TradeEvaluation().isTradeValid(tradeRequest.trade, civInfo, offeringCiv)) {
civInfo.tradeRequests.remove(tradeRequest)
// Yes, this is the right direction. I checked.
offeringCiv.addNotification("Our proposed trade is no longer relevant!", NotificationCategory.Trade, NotificationIcon.Trade)
}
}
civInfo.updateWinningCiv()
}
private fun startTurnFlags() {
for (flag in civInfo.flagsCountdown.keys.toList()) {
// In case we remove flags while iterating
if (!civInfo.flagsCountdown.containsKey(flag)) continue
if (flag == CivFlags.CityStateGreatPersonGift.name) {
val cityStateAllies: List<CivilizationInfo> =
civInfo.getKnownCivs().filter { it.isCityState() && it.getAllyCiv() == civInfo.civName }
val givingCityState = cityStateAllies.filter { it.cities.isNotEmpty() }.randomOrNull()
if (cityStateAllies.isNotEmpty()) civInfo.flagsCountdown[flag] = civInfo.flagsCountdown[flag]!! - 1
if (civInfo.flagsCountdown[flag]!! < min(cityStateAllies.size, 10) && civInfo.cities.isNotEmpty()
&& givingCityState != null
) {
givingCityState.cityStateFunctions.giveGreatPersonToPatron(civInfo)
civInfo.flagsCountdown[flag] = civInfo.cityStateFunctions.turnsForGreatPersonFromCityState()
}
continue
}
if (civInfo.flagsCountdown[flag]!! > 0)
civInfo.flagsCountdown[flag] = civInfo.flagsCountdown[flag]!! - 1
if (civInfo.flagsCountdown[flag] != 0) continue
when (flag) {
CivFlags.RevoltSpawning.name -> doRevoltSpawn()
}
}
handleDiplomaticVictoryFlags()
}
private fun handleDiplomaticVictoryFlags() {
if (civInfo.flagsCountdown[CivFlags.ShouldResetDiplomaticVotes.name] == 0) {
civInfo.gameInfo.diplomaticVictoryVotesCast.clear()
civInfo.removeFlag(CivFlags.ShowDiplomaticVotingResults.name)
civInfo.removeFlag(CivFlags.ShouldResetDiplomaticVotes.name)
}
if (civInfo.flagsCountdown[CivFlags.ShowDiplomaticVotingResults.name] == 0) {
civInfo.gameInfo.processDiplomaticVictory()
if (civInfo.gameInfo.civilizations.any { it.victoryManager.hasWon() } ) {
civInfo.removeFlag(CivFlags.TurnsTillNextDiplomaticVote.name)
} else {
civInfo.addFlag(CivFlags.ShouldResetDiplomaticVotes.name, 1)
civInfo.addFlag(CivFlags.TurnsTillNextDiplomaticVote.name, civInfo.getTurnsBetweenDiplomaticVotes())
}
}
if (civInfo.flagsCountdown[CivFlags.TurnsTillNextDiplomaticVote.name] == 0) {
civInfo.addFlag(CivFlags.ShowDiplomaticVotingResults.name, 1)
}
}
private fun updateRevolts() {
if (civInfo.gameInfo.civilizations.none { it.isBarbarian() }) {
// Can't spawn revolts without barbarians ¯\_(ツ)_/¯
return
}
if (!civInfo.hasUnique(UniqueType.SpawnRebels)) {
civInfo.removeFlag(CivFlags.RevoltSpawning.name)
return
}
if (!civInfo.hasFlag(CivFlags.RevoltSpawning.name)) {
civInfo.addFlag(CivFlags.RevoltSpawning.name, max(getTurnsBeforeRevolt(),1))
return
}
}
private fun doRevoltSpawn() {
val barbarians = try {
// The first test in `updateRevolts` should prevent getting here in a no-barbarians game, but it has been shown to still occur
civInfo.gameInfo.getBarbarianCivilization()
} catch (ex: NoSuchElementException) {
civInfo.removeFlag(CivFlags.RevoltSpawning.name)
return
}
val random = Random()
val rebelCount = 1 + random.nextInt(100 + 20 * (civInfo.cities.size - 1)) / 100
val spawnCity = civInfo.cities.maxByOrNull { random.nextInt(it.population.population + 10) } ?: return
val spawnTile = spawnCity.getTiles().maxByOrNull { rateTileForRevoltSpawn(it) } ?: return
val unitToSpawn = civInfo.gameInfo.ruleSet.units.values.asSequence().filter {
it.uniqueTo == null && it.isMelee() && it.isLandUnit()
&& !it.hasUnique(UniqueType.CannotAttack) && it.isBuildable(civInfo)
}.maxByOrNull {
random.nextInt(1000)
} ?: return
repeat(rebelCount) {
civInfo.gameInfo.tileMap.placeUnitNearTile(
spawnTile.position,
unitToSpawn.name,
barbarians
)
}
// Will be automatically added again as long as unhappiness is still low enough
civInfo.removeFlag(CivFlags.RevoltSpawning.name)
civInfo.addNotification("Your citizens are revolting due to very high unhappiness!", spawnTile.position, NotificationCategory.General, unitToSpawn.name, "StatIcons/Malcontent")
}
// Higher is better
private fun rateTileForRevoltSpawn(tile: TileInfo): Int {
if (tile.isWater || tile.militaryUnit != null || tile.civilianUnit != null || tile.isCityCenter() || tile.isImpassible())
return -1
var score = 10
if (tile.improvement == null) {
score += 4
if (tile.resource != null) {
score += 3
}
}
if (tile.getDefensiveBonus() > 0)
score += 4
return score
}
private fun getTurnsBeforeRevolt() =
((4 + Random().nextInt(3)) * max(civInfo.gameInfo.speed.modifier, 1f)).toInt()
fun endTurn() {
val notificationsLog = civInfo.notificationsLog
val notificationsThisTurn = CivilizationInfo.NotificationsLog(civInfo.gameInfo.turns)
notificationsThisTurn.notifications.addAll(civInfo.notifications)
while (notificationsLog.size >= UncivGame.Current.settings.notificationsLogMaxTurns) {
notificationsLog.removeFirst()
}
if (notificationsThisTurn.notifications.isNotEmpty())
notificationsLog.add(notificationsThisTurn)
civInfo.notifications.clear()
civInfo.updateStatsForNextTurn()
val nextTurnStats = civInfo.stats.statsForNextTurn
civInfo.policies.endTurn(nextTurnStats.culture.toInt())
civInfo.totalCultureForContests += nextTurnStats.culture.toInt()
if (civInfo.isCityState())
civInfo.questManager.endTurn()
// disband units until there are none left OR the gold values are normal
if (!civInfo.isBarbarian() && civInfo.gold < -100 && nextTurnStats.gold.toInt() < 0) {
for (i in 1 until (civInfo.gold / -100)) {
var civMilitaryUnits = civInfo.getCivUnits().filter { it.baseUnit.isMilitary() }
if (civMilitaryUnits.any()) {
val unitToDisband = civMilitaryUnits.first()
unitToDisband.disband()
civMilitaryUnits -= unitToDisband
val unitName = unitToDisband.shortDisplayName()
civInfo.addNotification("Cannot provide unit upkeep for $unitName - unit has been disbanded!", NotificationCategory.Units, unitName, NotificationIcon.Death)
}
}
}
civInfo.addGold( nextTurnStats.gold.toInt() )
if (civInfo.cities.isNotEmpty() && civInfo.gameInfo.ruleSet.technologies.isNotEmpty())
civInfo.tech.endTurn(nextTurnStats.science.toInt())
civInfo.religionManager.endTurn(nextTurnStats.faith.toInt())
civInfo.totalFaithForContests += nextTurnStats.faith.toInt()
civInfo.espionageManager.endTurn()
if (civInfo.isMajorCiv())
civInfo.greatPeople.addGreatPersonPoints(civInfo.getGreatPersonPointsForNextTurn()) // City-states don't get great people!
// To handle tile's owner issue (#8246), we need to run being razed city first.
for (city in sequence {
yieldAll(civInfo.cities.filter { it.isBeingRazed })
yieldAll(civInfo.cities.filterNot { it.isBeingRazed })
}.toList()) { // a city can be removed while iterating (if it's being razed) so we need to iterate over a copy
city.endTurn()
}
civInfo.temporaryUniques.endTurn()
civInfo.goldenAges.endTurn(civInfo.getHappiness())
civInfo.getCivUnits().forEach { it.endTurn() } // This is the most expensive part of endTurn
civInfo.diplomacy.values.toList().forEach { it.nextTurn() } // we copy the diplomacy values so if it changes in-loop we won't crash
civInfo.cache.updateHasActiveEnemyMovementPenalty()
civInfo.cachedMilitaryMight = -1 // Reset so we don't use a value from a previous turn
civInfo.updateWinningCiv() // Maybe we did something this turn to win
}
}

View File

@ -3,6 +3,7 @@ package com.unciv.uniques
import com.badlogic.gdx.math.Vector2 import com.badlogic.gdx.math.Vector2
import com.unciv.logic.battle.BattleDamage import com.unciv.logic.battle.BattleDamage
import com.unciv.logic.battle.MapUnitCombatant import com.unciv.logic.battle.MapUnitCombatant
import com.unciv.logic.civilization.managers.TurnManager
import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.testing.GdxTestRunner import com.unciv.testing.GdxTestRunner
import org.junit.Assert import org.junit.Assert
@ -38,7 +39,7 @@ class TriggeredUniquesTests {
@Test @Test
fun testConditionalTimedUniqueExpires() { fun testConditionalTimedUniqueExpires() {
civInfo.policies.adopt(policy, true) civInfo.policies.adopt(policy, true)
civInfo.endTurn() TurnManager(civInfo).endTurn()
val modifiers = BattleDamage.getAttackModifiers(attacker, defender) val modifiers = BattleDamage.getAttackModifiers(attacker, defender)
Assert.assertTrue("Timed Strength should no longer work after endTurn", modifiers.sumValues() == 0) Assert.assertTrue("Timed Strength should no longer work after endTurn", modifiers.sumValues() == 0)
} }