mirror of
https://github.com/yairm210/Unciv.git
synced 2025-09-23 03:23:17 -04:00
chore: Removed turn management functions from CivInfo
This commit is contained in:
parent
e8f54cc2f5
commit
e272a1408d
@ -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())
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
*/
|
||||
|
272
core/src/com/unciv/logic/civilization/managers/TurnManager.kt
Normal file
272
core/src/com/unciv/logic/civilization/managers/TurnManager.kt
Normal 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
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user