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.PlayerType
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.TileInfo
import com.unciv.logic.map.TileMap
import com.unciv.models.Religion
import com.unciv.models.metadata.GameParameters
import com.unciv.models.ruleset.nation.Difficulty
import com.unciv.models.ruleset.ModOptionsConstants
import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.RulesetCache
import com.unciv.models.ruleset.Speed
import com.unciv.models.ruleset.nation.Difficulty
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.ui.audio.MusicMood
import com.unciv.ui.audio.MusicTrackChooserFlags
@ -273,7 +274,7 @@ class GameInfo : IsPartOfGameInfoSerialization, HasGameInfoSerializationVersion
// would skip a turn if an AI civ calls nextTurn
// this happens when resigning a multiplayer game)
if (player.isHuman()) {
player.endTurn()
TurnManager(player).endTurn()
setNextPlayer()
}
@ -290,7 +291,7 @@ class GameInfo : IsPartOfGameInfoSerialization, HasGameInfoSerializationVersion
{
// Starting preparations
player.startTurn()
TurnManager(player).startTurn()
// Automation done here
player.doTurn()
@ -302,7 +303,7 @@ class GameInfo : IsPartOfGameInfoSerialization, HasGameInfoSerializationVersion
}
// Clean up
player.endTurn()
TurnManager(player).endTurn()
// To the next player
setNextPlayer()
@ -317,7 +318,7 @@ class GameInfo : IsPartOfGameInfoSerialization, HasGameInfoSerializationVersion
currentPlayerCiv = getCivilization(currentPlayer)
// Starting his turn
player.startTurn()
TurnManager(player).startTurn()
// No popups for spectators
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.NotificationIcon
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.managers.ReligionState
import com.unciv.logic.map.RoadStatus
import com.unciv.logic.map.TileInfo
import com.unciv.logic.map.TileMap
@ -213,13 +213,13 @@ class CityInfo : IsPartOfGameInfoSerialization {
// Update proximity rankings for all civs
for (otherCiv in civInfo.gameInfo.getAliveMajorCivs()) {
if (civInfo.getProximity(otherCiv) != Proximity.Neighbors) // unless already neighbors
civInfo.updateProximity(otherCiv,
otherCiv.updateProximity(civInfo))
civInfo.cache.updateProximity(otherCiv,
otherCiv.cache.updateProximity(civInfo))
}
for (otherCiv in civInfo.gameInfo.getAliveCityStates()) {
if (civInfo.getProximity(otherCiv) != Proximity.Neighbors) // unless already neighbors
civInfo.updateProximity(otherCiv,
otherCiv.updateProximity(civInfo))
civInfo.cache.updateProximity(otherCiv,
otherCiv.cache.updateProximity(civInfo))
}
triggerCitiesSettledNearOtherCiv()

View File

@ -14,6 +14,9 @@ import com.unciv.logic.automation.unit.WorkerAutomation
import com.unciv.logic.city.CityInfo
import com.unciv.logic.civilization.diplomacy.CityStateFunctions
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.GoldenAgeManager
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.TechManager
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.CivInfoTransientCache
import com.unciv.logic.map.MapUnit
import com.unciv.logic.map.TileInfo
import com.unciv.logic.map.UnitMovementAlgorithms
import com.unciv.logic.trade.TradeEvaluation
import com.unciv.logic.trade.TradeRequest
import com.unciv.models.Counter
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.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.ResourceType
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.Unique
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.unit.BaseUnit
import com.unciv.models.stats.Stat
import com.unciv.models.stats.Stats
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.withItem
import com.unciv.ui.victoryscreen.RankingType
@ -136,7 +132,7 @@ class CivilizationInfo : IsPartOfGameInfoSerialization {
val cityStateFunctions = CityStateFunctions(this)
@Transient
private var cachedMilitaryMight = -1
var cachedMilitaryMight = -1
@Transient
var passThroughImpassableUnlocked = false // Cached Boolean equal to passableImpassables.isNotEmpty()
@ -194,7 +190,7 @@ class CivilizationInfo : IsPartOfGameInfoSerialization {
val tradeRequests = ArrayList<TradeRequest>()
/** 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
* 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 isMinorCivWarmonger() = numMinorCivsAttacked >= 4
private fun isLongCountActive(): Boolean {
fun isLongCountActive(): Boolean {
val unique = getMatchingUniques(UniqueType.MayanGainGreatPerson).firstOrNull()
?: return false
return tech.isResearched(unique.params[1])
@ -943,53 +939,6 @@ class CivilizationInfo : IsPartOfGameInfoSerialization {
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(){
if (gameInfo.victoryData == null) {
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 removeFlag(flag: String) = flagsCountdown.remove(flag)
fun hasFlag(flag: String) = flagsCountdown.contains(flag)
@ -1151,76 +977,6 @@ class CivilizationInfo : IsPartOfGameInfoSerialization {
&& 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.
* @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.unciv.logic.battle.BattleDamage
import com.unciv.logic.battle.MapUnitCombatant
import com.unciv.logic.civilization.managers.TurnManager
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.testing.GdxTestRunner
import org.junit.Assert
@ -38,7 +39,7 @@ class TriggeredUniquesTests {
@Test
fun testConditionalTimedUniqueExpires() {
civInfo.policies.adopt(policy, true)
civInfo.endTurn()
TurnManager(civInfo).endTurn()
val modifiers = BattleDamage.getAttackModifiers(attacker, defender)
Assert.assertTrue("Timed Strength should no longer work after endTurn", modifiers.sumValues() == 0)
}