Added Caching for civ Resources, Stats and city Happiness information for massive performance improvements in some late games

This commit is contained in:
Yair Morgenstern 2019-06-20 23:03:28 +03:00
parent beedcc5896
commit e0f72af06d
19 changed files with 86 additions and 53 deletions

View File

@ -211,7 +211,10 @@ class GameInfo {
for(unit in civInfo.getCivUnits()) for(unit in civInfo.getCivUnits())
unit.updateViewableTiles() // this needs to be done after all the units are assigned to their civs and all other transients are set unit.updateViewableTiles() // this needs to be done after all the units are assigned to their civs and all other transients are set
} }
for (civInfo in civilizations){ for (civInfo in civilizations){
// We need to determine the GLOBAL happiness state in order to determine the city stats
for(cityInfo in civInfo.cities) cityInfo.cityStats.updateCityHappiness()
for (cityInfo in civInfo.cities) cityInfo.cityStats.update() for (cityInfo in civInfo.cities) cityInfo.cityStats.update()
} }
} }

View File

@ -43,7 +43,7 @@ class Automation {
if (stats.food <= 2) rank += (stats.food * 1.2f) //food get more value to keep city growing if (stats.food <= 2) rank += (stats.food * 1.2f) //food get more value to keep city growing
else rank += (2.4f + (stats.food - 2) / 2) // 1.2 point for each food up to 2, from there on half a point else rank += (2.4f + (stats.food - 2) / 2) // 1.2 point for each food up to 2, from there on half a point
if (civInfo.gold < 0 && civInfo.getStatsForNextTurn().gold <= 0) rank += stats.gold if (civInfo.gold < 0 && civInfo.statsForNextTurn.gold <= 0) rank += stats.gold
else rank += stats.gold / 2 else rank += stats.gold / 2
rank += stats.production rank += stats.production
@ -129,7 +129,7 @@ class Automation {
.minBy{it.cost} .minBy{it.cost}
if (goldBuilding!=null) { if (goldBuilding!=null) {
val choice = ConstructionChoice(goldBuilding.name,1.2f) val choice = ConstructionChoice(goldBuilding.name,1.2f)
if (cityInfo.civInfo.getStatsForNextTurn().gold<0) { if (cityInfo.civInfo.statsForNextTurn.gold<0) {
choice.choiceModifier=3f choice.choiceModifier=3f
} }
relativeCostEffectiveness.add(choice) relativeCostEffectiveness.add(choice)
@ -151,8 +151,9 @@ class Automation {
.minBy{it.cost} .minBy{it.cost}
if (happinessBuilding!=null) { if (happinessBuilding!=null) {
val choice = ConstructionChoice(happinessBuilding.name,1f) val choice = ConstructionChoice(happinessBuilding.name,1f)
if (cityInfo.civInfo.happiness > 5) choice.choiceModifier = 1/2f // less desperate val civHappiness = cityInfo.civInfo.getHappiness()
if (cityInfo.civInfo.happiness < 0) choice.choiceModifier = 3f // more desperate if (civHappiness > 5) choice.choiceModifier = 1/2f // less desperate
if (civHappiness < 0) choice.choiceModifier = 3f // more desperate
relativeCostEffectiveness.add(choice) relativeCostEffectiveness.add(choice)
} }
@ -217,7 +218,7 @@ class Automation {
} }
//Army //Army
if((!isAtWar && cityInfo.civInfo.getStatsForNextTurn().gold>0 && militaryUnits<cities*2) if((!isAtWar && cityInfo.civInfo.statsForNextTurn.gold>0 && militaryUnits<cities*2)
|| (isAtWar && cityInfo.civInfo.gold > -50)) { || (isAtWar && cityInfo.civInfo.gold > -50)) {
val militaryUnit = chooseMilitaryUnit(cityInfo) val militaryUnit = chooseMilitaryUnit(cityInfo)
val unitsToCitiesRatio = cities.toFloat() / (militaryUnits + 1) val unitsToCitiesRatio = cities.toFloat() / (militaryUnits + 1)

View File

@ -75,7 +75,7 @@ class NextTurnAutomation{
} }
} }
if(civInfo.happiness < 5){ if(civInfo.getHappiness() < 5){
for(cityState in civInfo.gameInfo.civilizations for(cityState in civInfo.gameInfo.civilizations
.filter { it.isCityState() && it.getCityStateType()==CityStateType.Mercantile }){ .filter { it.isCityState() && it.getCityStateType()==CityStateType.Mercantile }){
val diploManager = cityState.getDiplomacyManager(civInfo) val diploManager = cityState.getDiplomacyManager(civInfo)
@ -300,7 +300,7 @@ class NextTurnAutomation{
else { else {
if (enemy.victoryType()!=VictoryType.Cultural if (enemy.victoryType()!=VictoryType.Cultural
&& enemy.getCivUnits().filter { !it.type.isCivilian() }.size > enemy.cities.size && enemy.getCivUnits().filter { !it.type.isCivilian() }.size > enemy.cities.size
&& enemy.happiness > 0) { && enemy.getHappiness() > 0) {
continue //enemy AI has too large army and happiness. It continues to fight for profit. continue //enemy AI has too large army and happiness. It continues to fight for profit.
} }
tradeLogic.acceptTrade() tradeLogic.acceptTrade()
@ -334,7 +334,7 @@ class NextTurnAutomation{
if (civInfo.cities.isNotEmpty() && civInfo.diplomacy.isNotEmpty()) { if (civInfo.cities.isNotEmpty() && civInfo.diplomacy.isNotEmpty()) {
val ourMilitaryUnits = civInfo.getCivUnits().filter { !it.type.isCivilian() }.size val ourMilitaryUnits = civInfo.getCivUnits().filter { !it.type.isCivilian() }.size
if (!civInfo.isAtWar() && civInfo.happiness > 0 if (!civInfo.isAtWar() && civInfo.getHappiness() > 0
&& ourMilitaryUnits >= civInfo.cities.size) { //evaluate war && ourMilitaryUnits >= civInfo.cities.size) { //evaluate war
val ourCombatStrength = Automation().evaluteCombatStrength(civInfo) val ourCombatStrength = Automation().evaluteCombatStrength(civInfo)
val enemyCivsByDistanceToOurs = civInfo.getKnownCivs() val enemyCivsByDistanceToOurs = civInfo.getKnownCivs()
@ -404,7 +404,7 @@ class NextTurnAutomation{
if(civInfo.isAtWar()) return // don't train settlers when you could be training troops. if(civInfo.isAtWar()) return // don't train settlers when you could be training troops.
if(civInfo.victoryType()==VictoryType.Cultural && civInfo.cities.size >3) return if(civInfo.victoryType()==VictoryType.Cultural && civInfo.cities.size >3) return
if (civInfo.cities.any() if (civInfo.cities.any()
&& civInfo.happiness > civInfo.cities.size + 5 && civInfo.getHappiness() > civInfo.cities.size + 5
&& civInfo.getCivUnits().none { it.name == Constants.settler } && civInfo.getCivUnits().none { it.name == Constants.settler }
&& civInfo.cities.none { it.cityConstructions.currentConstruction == Constants.settler }) { && civInfo.cities.none { it.cityConstructions.currentConstruction == Constants.settler }) {

View File

@ -47,8 +47,9 @@ class BattleDamage{
} }
//https://www.carlsguides.com/strategy/civilization5/war/combatbonuses.php //https://www.carlsguides.com/strategy/civilization5/war/combatbonuses.php
if (combatant.getCivInfo().happiness < 0) val civHappiness = combatant.getCivInfo().getHappiness()
modifiers["Unhappiness"] = max(0.02f * combatant.getCivInfo().happiness,-0.9f) // otherwise it could exceed -100% and start healing enemy units... if (civHappiness < 0)
modifiers["Unhappiness"] = max(0.02f * civHappiness,-0.9f) // otherwise it could exceed -100% and start healing enemy units...
if(combatant.getCivInfo().policies.isAdopted("Populism") && combatant.getHealth() < 100){ if(combatant.getCivInfo().policies.isAdopted("Populism") && combatant.getHealth() < 100){
modifiers["Populism"] = 0.25f modifiers["Populism"] = 0.25f

View File

@ -17,7 +17,7 @@ class CityConstructions {
@Transient lateinit var cityInfo: CityInfo @Transient lateinit var cityInfo: CityInfo
@Transient private var builtBuildingObjects=ArrayList<Building>() @Transient private var builtBuildingObjects=ArrayList<Building>()
var builtBuildings = ArrayList<String>() var builtBuildings = HashSet<String>()
val inProgressConstructions = HashMap<String, Int>() val inProgressConstructions = HashMap<String, Int>()
var currentConstruction: String = "Monument" // default starting building! var currentConstruction: String = "Monument" // default starting building!
var currentConstructionIsUserSet = false var currentConstructionIsUserSet = false
@ -213,7 +213,9 @@ class CityConstructions {
getConstruction(buildingName).postBuildEvent(this) getConstruction(buildingName).postBuildEvent(this)
if (currentConstruction == buildingName) if (currentConstruction == buildingName)
cancelCurrentConstruction() cancelCurrentConstruction()
cityInfo.cityStats.update() cityInfo.cityStats.update()
cityInfo.civInfo.updateDetailedCivResources() // this building could be a resource-requiring one
} }
fun addCultureBuilding() { fun addCultureBuilding() {

View File

@ -95,6 +95,9 @@ class CityExpansionManager {
if(cityInfo.workedTiles.contains(tileInfo.position)) if(cityInfo.workedTiles.contains(tileInfo.position))
cityInfo.workedTiles = cityInfo.workedTiles.withoutItem(tileInfo.position) cityInfo.workedTiles = cityInfo.workedTiles.withoutItem(tileInfo.position)
tileInfo.owningCity=null tileInfo.owningCity=null
cityInfo.civInfo.updateDetailedCivResources()
cityInfo.cityStats.update()
} }
fun takeOwnership(tileInfo: TileInfo){ fun takeOwnership(tileInfo: TileInfo){
@ -105,6 +108,7 @@ class CityExpansionManager {
cityInfo.tiles = cityInfo.tiles.withItem(tileInfo.position) cityInfo.tiles = cityInfo.tiles.withItem(tileInfo.position)
tileInfo.owningCity = cityInfo tileInfo.owningCity = cityInfo
cityInfo.population.autoAssignPopulation() cityInfo.population.autoAssignPopulation()
cityInfo.civInfo.updateDetailedCivResources()
cityInfo.cityStats.update() cityInfo.cityStats.update()
for(unit in tileInfo.getUnits()) for(unit in tileInfo.getUnits())

View File

@ -166,7 +166,7 @@ class CityStats {
// needs to be a separate function because we need to know the global happiness state // needs to be a separate function because we need to know the global happiness state
// in order to determine how much food is produced in a city! // in order to determine how much food is produced in a city!
// -3 happiness per city // -3 happiness per city
fun getCityHappiness(): LinkedHashMap<String, Float> { fun updateCityHappiness(){
val civInfo = cityInfo.civInfo val civInfo = cityInfo.civInfo
val newHappinessList = LinkedHashMap<String,Float>() val newHappinessList = LinkedHashMap<String,Float>()
var unhappinessModifier = civInfo.getDifficulty().unhappinessModifier var unhappinessModifier = civInfo.getDifficulty().unhappinessModifier
@ -212,7 +212,6 @@ class CityStats {
// we don't want to modify the existing happiness list because that leads // we don't want to modify the existing happiness list because that leads
// to concurrency problems if we iterate on it while changing // to concurrency problems if we iterate on it while changing
happinessList=newHappinessList happinessList=newHappinessList
return newHappinessList
} }
fun getStatsOfSpecialist(stat:Stat, policies: HashSet<String>): Stats { fun getStatsOfSpecialist(stat:Stat, policies: HashSet<String>): Stats {
@ -305,7 +304,7 @@ class CityStats {
stats.culture += 33f stats.culture += 33f
if (policies.contains("Commerce") && cityInfo.isCapital()) if (policies.contains("Commerce") && cityInfo.isCapital())
stats.gold += 25f stats.gold += 25f
if (policies.contains("Sovereignty") && cityInfo.civInfo.happiness >= 0) if (policies.contains("Sovereignty") && cityInfo.civInfo.getHappiness() >= 0)
stats.science += 15f stats.science += 15f
if (policies.contains("Total War") && currentConstruction is BaseUnit && !currentConstruction.unitType.isCivilian() ) if (policies.contains("Total War") && currentConstruction is BaseUnit && !currentConstruction.unitType.isCivilian() )
stats.production += 15f stats.production += 15f
@ -369,6 +368,7 @@ class CityStats {
} }
fun update() { fun update() {
updateCityHappiness()
updateBaseStatList() updateBaseStatList()
updateStatPercentBonusList() updateStatPercentBonusList()
@ -389,7 +389,7 @@ class CityStats {
newCurrentCityStats.science *= 1 + statPercentBonusesSum.science / 100 newCurrentCityStats.science *= 1 + statPercentBonusesSum.science / 100
newCurrentCityStats.culture *= 1 + statPercentBonusesSum.culture / 100 newCurrentCityStats.culture *= 1 + statPercentBonusesSum.culture / 100
val isUnhappy = cityInfo.civInfo.happiness < 0 val isUnhappy = cityInfo.civInfo.getHappiness() < 0
if (!isUnhappy) // Regular food bonus revoked when unhappy per https://forums.civfanatics.com/resources/complete-guide-to-happiness-vanilla.25584/ if (!isUnhappy) // Regular food bonus revoked when unhappy per https://forums.civfanatics.com/resources/complete-guide-to-happiness-vanilla.25584/
newCurrentCityStats.food *= 1 + statPercentBonusesSum.food / 100 newCurrentCityStats.food *= 1 + statPercentBonusesSum.food / 100
@ -424,6 +424,8 @@ class CityStats {
if (cityInfo.resistanceCounter > 0) if (cityInfo.resistanceCounter > 0)
currentCityStats = Stats().add(Stat.Production,1f) // You get nothing, Jon Snow currentCityStats = Stats().add(Stat.Production,1f) // You get nothing, Jon Snow
else currentCityStats = newCurrentCityStats else currentCityStats = newCurrentCityStats
cityInfo.civInfo.updateStatsForNextTurn()
} }
private fun updateFoodEaten() { private fun updateFoodEaten() {

View File

@ -44,9 +44,10 @@ class CivilizationInfo {
/** This is for performance since every movement calculation depends on this, see MapUnit comment */ /** This is for performance since every movement calculation depends on this, see MapUnit comment */
@Transient var hasActiveGreatWall = false @Transient var hasActiveGreatWall = false
@Transient var statsForNextTurn = Stats()
@Transient var detailedCivResources = ResourceSupplyList()
var gold = 0 var gold = 0
var happiness = 15
@Deprecated("As of 2.11.1") var difficulty = "Chieftain" @Deprecated("As of 2.11.1") var difficulty = "Chieftain"
var playerType = PlayerType.AI var playerType = PlayerType.AI
var civName = "" var civName = ""
@ -78,7 +79,6 @@ class CivilizationInfo {
fun clone(): CivilizationInfo { fun clone(): CivilizationInfo {
val toReturn = CivilizationInfo() val toReturn = CivilizationInfo()
toReturn.gold = gold toReturn.gold = gold
toReturn.happiness = happiness
toReturn.playerType = playerType toReturn.playerType = playerType
toReturn.civName = civName toReturn.civName = civName
toReturn.tech = tech.clone() toReturn.tech = tech.clone()
@ -132,7 +132,9 @@ class CivilizationInfo {
else return VictoryType.Neutral else return VictoryType.Neutral
} }
fun getStatsForNextTurn():Stats = getStatMapForNextTurn().values.toList().reduce{a,b->a+b} fun updateStatsForNextTurn(){
statsForNextTurn = getStatMapForNextTurn().values.toList().reduce{a,b->a+b}
}
fun getStatMapForNextTurn(): HashMap<String, Stats> { fun getStatMapForNextTurn(): HashMap<String, Stats> {
val statMap = HashMap<String,Stats>() val statMap = HashMap<String,Stats>()
@ -154,7 +156,7 @@ class CivilizationInfo {
} }
} }
for (entry in getHappinessForNextTurn()) { for (entry in getHappinessBreakdown()) {
if (!statMap.containsKey(entry.key)) if (!statMap.containsKey(entry.key))
statMap[entry.key] = Stats() statMap[entry.key] = Stats()
statMap[entry.key]!!.happiness += entry.value statMap[entry.key]!!.happiness += entry.value
@ -220,7 +222,9 @@ class CivilizationInfo {
return transportationUpkeep return transportationUpkeep
} }
fun getHappinessForNextTurn(): HashMap<String, Float> { fun getHappiness() = getHappinessBreakdown().values.sum().roundToInt()
fun getHappinessBreakdown(): HashMap<String, Float> {
val statMap = HashMap<String,Float>() val statMap = HashMap<String,Float>()
statMap["Base happiness"] = getDifficulty().baseHappiness.toFloat() statMap["Base happiness"] = getDifficulty().baseHappiness.toFloat()
@ -230,7 +234,7 @@ class CivilizationInfo {
.count { it.resourceType === ResourceType.Luxury } * happinessPerUniqueLuxury .count { it.resourceType === ResourceType.Luxury } * happinessPerUniqueLuxury
for(city in cities.toList()){ for(city in cities.toList()){
for(keyvalue in city.cityStats.getCityHappiness()){ for(keyvalue in city.cityStats.happinessList){
if(statMap.containsKey(keyvalue.key)) if(statMap.containsKey(keyvalue.key))
statMap[keyvalue.key] = statMap[keyvalue.key]!!+keyvalue.value statMap[keyvalue.key] = statMap[keyvalue.key]!!+keyvalue.value
else statMap[keyvalue.key] = keyvalue.value else statMap[keyvalue.key] = keyvalue.value
@ -258,23 +262,23 @@ class CivilizationInfo {
} }
/** /**
* Returns list of all resources and their origin - for most usages you'll want getCivResources().totalSupply(), * Returns list of all resources and their origin - for most usages you'll want updateDetailedCivResources().totalSupply(),
* which unifies al the different sources * which unifies al the different sources
*/ */
fun getCivResources(): ResourceSupplyList { fun getCivResources(): ResourceSupplyList {
val newResourceSupplyList=ResourceSupplyList() val newResourceSupplyList=ResourceSupplyList()
for(resourceSupply in getDetailedCivResources()) for(resourceSupply in detailedCivResources)
newResourceSupplyList.add(resourceSupply.resource,resourceSupply.amount,"All") newResourceSupplyList.add(resourceSupply.resource,resourceSupply.amount,"All")
return newResourceSupplyList return newResourceSupplyList
} }
fun getDetailedCivResources(): ResourceSupplyList { fun updateDetailedCivResources() {
val civResources = ResourceSupplyList() val newDetailedCivResources = ResourceSupplyList()
for (city in cities) civResources.add(city.getCityResources()) for (city in cities) newDetailedCivResources.add(city.getCityResources())
for (dip in diplomacy.values) civResources.add(dip.resourcesFromTrade()) for (dip in diplomacy.values) newDetailedCivResources.add(dip.resourcesFromTrade())
for(resource in getCivUnits().mapNotNull { it.baseUnit.requiredResource }.map { GameBasics.TileResources[it]!! }) for(resource in getCivUnits().mapNotNull { it.baseUnit.requiredResource }.map { GameBasics.TileResources[it]!! })
civResources.add(resource,-1,"Units") newDetailedCivResources.add(resource,-1,"Units")
return civResources detailedCivResources = newDetailedCivResources
} }
/** /**
@ -294,16 +298,24 @@ class CivilizationInfo {
fun getCivUnits(): List<MapUnit> = units fun getCivUnits(): List<MapUnit> = units
fun addUnit(mapUnit: MapUnit){ fun addUnit(mapUnit: MapUnit, updateCivInfo:Boolean=true){
val newList = ArrayList(units) val newList = ArrayList(units)
newList.add(mapUnit) newList.add(mapUnit)
units=newList units=newList
if(updateCivInfo) {
// Not relevant when updating tileinfo transients, since some info of the civ itself isn't yet available,
// and in any case it'll be updated once civ info transients are
updateStatsForNextTurn() // unit upkeep
updateDetailedCivResources()
}
} }
fun removeUnit(mapUnit: MapUnit){ fun removeUnit(mapUnit: MapUnit){
val newList = ArrayList(units) val newList = ArrayList(units)
newList.remove(mapUnit) newList.remove(mapUnit)
units=newList units=newList
updateStatsForNextTurn() // unit upkeep
} }
fun getIdleUnits() = getCivUnits().filter { it.isIdle() } fun getIdleUnits() = getCivUnits().filter { it.isIdle() }
@ -431,6 +443,7 @@ class CivilizationInfo {
setCitiesConnectedToCapitalTransients() setCitiesConnectedToCapitalTransients()
updateViewableTiles() updateViewableTiles()
updateHasActiveGreatWall() updateHasActiveGreatWall()
updateDetailedCivResources()
} }
fun updateHasActiveGreatWall(){ fun updateHasActiveGreatWall(){
@ -439,6 +452,8 @@ class CivilizationInfo {
} }
fun startTurn(){ fun startTurn(){
updateStatsForNextTurn() // for things that change when turn passes e.g. golden age, city state influence
// Generate great people at the start of the turn, // 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 // 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 if (cities.isNotEmpty()) { //if no city available, addGreatPerson will throw exception
@ -450,14 +465,13 @@ class CivilizationInfo {
setCitiesConnectedToCapitalTransients() setCitiesConnectedToCapitalTransients()
for (city in cities) city.startTurn() for (city in cities) city.startTurn()
happiness = getHappinessForNextTurn().values.sum().roundToInt()
getCivUnits().toList().forEach { it.startTurn() } getCivUnits().toList().forEach { it.startTurn() }
} }
fun endTurn() { fun endTurn() {
notifications.clear() notifications.clear()
val nextTurnStats = getStatsForNextTurn() val nextTurnStats = statsForNextTurn
policies.endTurn(nextTurnStats.culture.toInt()) policies.endTurn(nextTurnStats.culture.toInt())
@ -484,7 +498,7 @@ class CivilizationInfo {
city.endTurn() city.endTurn()
} }
goldenAges.endTurn(happiness) goldenAges.endTurn(getHappiness())
getCivUnits().forEach { it.endTurn() } getCivUnits().forEach { it.endTurn() }
diplomacy.values.forEach{it.nextTurn()} diplomacy.values.forEach{it.nextTurn()}
updateHasActiveGreatWall() updateHasActiveGreatWall()
@ -604,6 +618,7 @@ class CivilizationInfo {
val currentPlayerCiv = UnCivGame.Current.gameInfo.getCurrentPlayerCivilization() val currentPlayerCiv = UnCivGame.Current.gameInfo.getCurrentPlayerCivilization()
currentPlayerCiv.gold -= giftAmount currentPlayerCiv.gold -= giftAmount
otherCiv.getDiplomacyManager(currentPlayerCiv).influence += giftAmount/10 otherCiv.getDiplomacyManager(currentPlayerCiv).influence += giftAmount/10
updateStatsForNextTurn()
} }
//endregion //endregion

View File

@ -92,7 +92,7 @@ class PolicyManager {
} }
for (cityInfo in civInfo.cities) for (cityInfo in civInfo.cities)
cityInfo.cityStats.update() cityInfo.cityStats.update() // This ALSO has the side-effect of updating the CivInfo startForNextTurn so we don't need to call it explicitly
if(!canAdoptPolicy()) shouldOpenPolicyPicker=false if(!canAdoptPolicy()) shouldOpenPolicyPicker=false
} }

View File

@ -61,7 +61,7 @@ class TechManager {
fun turnsToTech(techName: String): Int { fun turnsToTech(techName: String): Int {
return Math.ceil( remainingScienceToTech(techName).toDouble() return Math.ceil( remainingScienceToTech(techName).toDouble()
/ civInfo.getStatsForNextTurn().science).toInt() / civInfo.statsForNextTurn.science).toInt()
} }
fun isResearched(TechName: String): Boolean = techsResearched.contains(TechName) fun isResearched(TechName: String): Boolean = techsResearched.contains(TechName)

View File

@ -221,8 +221,12 @@ class DiplomacyManager() {
for(trade in trades.toList()){ for(trade in trades.toList()){
for(offer in trade.ourOffers.union(trade.theirOffers).filter { it.duration>0 }) { for(offer in trade.ourOffers.union(trade.theirOffers).filter { it.duration>0 }) {
offer.duration-- offer.duration--
if(offer.duration==0) if(offer.duration==0) {
civInfo.addNotification("["+offer.name+"] from [$otherCivName] has ended",null, Color.GOLD) civInfo.addNotification("[" + offer.name + "] from [$otherCivName] has ended", null, Color.GOLD)
civInfo.updateStatsForNextTurn() // if they were bringing us gold per turn
civInfo.updateDetailedCivResources() // if they were giving us resources
}
} }
if(trade.ourOffers.all { it.duration<=0 } && trade.theirOffers.all { it.duration<=0 }) { if(trade.ourOffers.all { it.duration<=0 } && trade.theirOffers.all { it.duration<=0 }) {

View File

@ -522,10 +522,10 @@ class MapUnit {
(actions.random())() (actions.random())()
} }
fun assignOwner(civInfo:CivilizationInfo){ fun assignOwner(civInfo:CivilizationInfo, updateCivInfo:Boolean=true){
owner=civInfo.civName owner=civInfo.civName
this.civInfo=civInfo this.civInfo=civInfo
civInfo.addUnit(this) civInfo.addUnit(this,updateCivInfo)
} }
//endregion //endregion

View File

@ -296,7 +296,7 @@ open class TileInfo {
if(civilianUnit!=null) civilianUnit!!.currentTile = this if(civilianUnit!=null) civilianUnit!!.currentTile = this
for (unit in getUnits()) { for (unit in getUnits()) {
unit.assignOwner(tileMap.gameInfo.getCivilization(unit.owner)) unit.assignOwner(tileMap.gameInfo.getCivilization(unit.owner),false)
unit.setTransients() unit.setTransients()
} }
} }

View File

@ -106,7 +106,7 @@ class TradeEvaluation{
TradeType.City -> { TradeType.City -> {
val city = tradePartner.cities.first { it.name==offer.name } val city = tradePartner.cities.first { it.name==offer.name }
val stats = city.cityStats.currentCityStats val stats = city.cityStats.currentCityStats
if(civInfo.happiness + city.cityStats.happinessList.values.sum() < 0) if(civInfo.getHappiness() + city.cityStats.happinessList.values.sum() < 0)
return 0 // we can't really afford to go into negative happiness because of buying a city return 0 // we can't really afford to go into negative happiness because of buying a city
val sumOfStats = stats.culture+stats.gold+stats.science+stats.production+stats.happiness+stats.food val sumOfStats = stats.culture+stats.gold+stats.science+stats.production+stats.happiness+stats.food
return sumOfStats.toInt() * 100 return sumOfStats.toInt() * 100

View File

@ -45,7 +45,7 @@ class TradeLogic(val ourCivilization:CivilizationInfo, val otherCivilization: Ci
} }
offers.add(TradeOffer("Gold".tr(), TradeType.Gold, 0, civInfo.gold)) offers.add(TradeOffer("Gold".tr(), TradeType.Gold, 0, civInfo.gold))
offers.add(TradeOffer("Gold per turn".tr(), TradeType.Gold_Per_Turn, 30, civInfo.getStatsForNextTurn().gold.toInt())) offers.add(TradeOffer("Gold per turn".tr(), TradeType.Gold_Per_Turn, 30, civInfo.statsForNextTurn.gold.toInt()))
if (!civInfo.isCityState() && !otherCivilization.isCityState()) { if (!civInfo.isCityState() && !otherCivilization.isCityState()) {
for (city in civInfo.cities.filterNot { it.isCapital() }) for (city in civInfo.cities.filterNot { it.isCapital() })
offers.add(TradeOffer(city.name, TradeType.City, 0)) offers.add(TradeOffer(city.name, TradeType.City, 0))
@ -111,6 +111,8 @@ class TradeLogic(val ourCivilization:CivilizationInfo, val otherCivilization: Ci
from.getDiplomacyManager(nameOfCivToDeclareWarOn).declareWar() from.getDiplomacyManager(nameOfCivToDeclareWarOn).declareWar()
} }
} }
to.updateStatsForNextTurn()
to.updateDetailedCivResources()
} }
transferTrade(ourCivilization,otherCivilization,currentTrade) transferTrade(ourCivilization,otherCivilization,currentTrade)

View File

@ -89,7 +89,7 @@ class EmpireOverviewScreen : CameraStageBaseScreen(){
centerTable.pack() centerTable.pack()
} }
topTable.add(setResourcesButton) topTable.add(setResourcesButton)
if(currentPlayerCivInfo.getDetailedCivResources().isEmpty()) if(currentPlayerCivInfo.detailedCivResources.isEmpty())
setResourcesButton.disable() setResourcesButton.disable()
topTable.pack() topTable.pack()
@ -140,12 +140,12 @@ class EmpireOverviewScreen : CameraStageBaseScreen(){
happinessTable.defaults().pad(5f) happinessTable.defaults().pad(5f)
happinessTable.add("Happiness".toLabel().setFontSize(24)).colspan(2).row() happinessTable.add("Happiness".toLabel().setFontSize(24)).colspan(2).row()
happinessTable.addSeparator() happinessTable.addSeparator()
for (entry in currentPlayerCivInfo.getHappinessForNextTurn()) { for (entry in currentPlayerCivInfo.getHappinessBreakdown()) {
happinessTable.add(entry.key.tr()) happinessTable.add(entry.key.tr())
happinessTable.add(entry.value.roundToInt().toString()).row() happinessTable.add(entry.value.roundToInt().toString()).row()
} }
happinessTable.add("Total".tr()) happinessTable.add("Total".tr())
happinessTable.add(currentPlayerCivInfo.getHappinessForNextTurn().values.sum().roundToInt().toString()) happinessTable.add(currentPlayerCivInfo.getHappinessBreakdown().values.sum().roundToInt().toString())
happinessTable.pack() happinessTable.pack()
return happinessTable return happinessTable
} }
@ -349,7 +349,7 @@ class EmpireOverviewScreen : CameraStageBaseScreen(){
private fun getResourcesTable(): Table { private fun getResourcesTable(): Table {
val resourcesTable=Table().apply { defaults().pad(10f) } val resourcesTable=Table().apply { defaults().pad(10f) }
val resourceDrilldown = currentPlayerCivInfo.getDetailedCivResources() val resourceDrilldown = currentPlayerCivInfo.detailedCivResources
// First row of table has all the icons // First row of table has all the icons
resourcesTable.add() resourcesTable.add()

View File

@ -133,7 +133,7 @@ class CityInfoTable(private val cityScreen: CityScreen) : Table(CameraStageBaseS
for(stats in unifiedStatList.values) stats.happiness=0f for(stats in unifiedStatList.values) stats.happiness=0f
// add happiness to stat list // add happiness to stat list
for(entry in cityStats.getCityHappiness().filter { it.value!=0f }){ for(entry in cityStats.happinessList.filter { it.value!=0f }){
if(!unifiedStatList.containsKey(entry.key)) if(!unifiedStatList.containsKey(entry.key))
unifiedStatList[entry.key]= Stats() unifiedStatList[entry.key]= Stats()
unifiedStatList[entry.key]!!.happiness=entry.value unifiedStatList[entry.key]!!.happiness=entry.value

View File

@ -106,7 +106,6 @@ class WorldScreen : CameraStageBaseScreen() {
gameClone.setTransients() gameClone.setTransients()
val cloneCivilization = gameClone.getCurrentPlayerCivilization() val cloneCivilization = gameClone.getCurrentPlayerCivilization()
kotlin.concurrent.thread { kotlin.concurrent.thread {
currentPlayerCiv.happiness = gameClone.getCurrentPlayerCivilization().getHappinessForNextTurn().values.sum().toInt()
gameInfo.civilizations.forEach { it.setCitiesConnectedToCapitalTransients() } gameInfo.civilizations.forEach { it.setCitiesConnectedToCapitalTransients() }
} }
@ -336,7 +335,7 @@ class WorldScreen : CameraStageBaseScreen() {
&& currentPlayerCiv.viewableTiles.any { it.getUnits().any { unit -> unit.civInfo.isBarbarianCivilization() } }) && currentPlayerCiv.viewableTiles.any { it.getUnits().any { unit -> unit.civInfo.isBarbarianCivilization() } })
displayTutorials("BarbarianEncountered") displayTutorials("BarbarianEncountered")
if(currentPlayerCiv.cities.size > 2) displayTutorials("SecondCity") if(currentPlayerCiv.cities.size > 2) displayTutorials("SecondCity")
if(currentPlayerCiv.happiness < 0) displayTutorials("Unhappiness") if(currentPlayerCiv.getHappiness() < 0) displayTutorials("Unhappiness")
if(currentPlayerCiv.goldenAges.isGoldenAge()) displayTutorials("GoldenAge") if(currentPlayerCiv.goldenAges.isGoldenAge()) displayTutorials("GoldenAge")
if(gameInfo.turns >= 100) displayTutorials("ContactMe") if(gameInfo.turns >= 100) displayTutorials("ContactMe")
val resources = currentPlayerCiv.getCivResources() val resources = currentPlayerCiv.getCivResources()

View File

@ -128,7 +128,7 @@ class WorldScreenTopBar(val screen: WorldScreen) : Table() {
turnsLabel.setText("Turn".tr()+" " + civInfo.gameInfo.turns + " | "+ abs(year)+(if (year<0) " BC" else " AD")) turnsLabel.setText("Turn".tr()+" " + civInfo.gameInfo.turns + " | "+ abs(year)+(if (year<0) " BC" else " AD"))
val nextTurnStats = civInfo.getStatsForNextTurn() val nextTurnStats = civInfo.statsForNextTurn
val goldPerTurn = "(" + (if (nextTurnStats.gold > 0) "+" else "") + Math.round(nextTurnStats.gold) + ")" val goldPerTurn = "(" + (if (nextTurnStats.gold > 0) "+" else "") + Math.round(nextTurnStats.gold) + ")"
goldLabel.setText(Math.round(civInfo.gold.toFloat()).toString() + goldPerTurn) goldLabel.setText(Math.round(civInfo.gold.toFloat()).toString() + goldPerTurn)
@ -136,7 +136,7 @@ class WorldScreenTopBar(val screen: WorldScreen) : Table() {
happinessLabel.setText(getHappinessText(civInfo)) happinessLabel.setText(getHappinessText(civInfo))
if (civInfo.happiness < 0) { if (civInfo.getHappiness() < 0) {
happinessLabel.setFontColor(malcontentColor) happinessLabel.setFontColor(malcontentColor)
happinessImage.clearChildren() happinessImage.clearChildren()
happinessImage.addActor(malcontentGroup) happinessImage.addActor(malcontentGroup)
@ -160,7 +160,7 @@ class WorldScreenTopBar(val screen: WorldScreen) : Table() {
} }
private fun getHappinessText(civInfo: CivilizationInfo): String { private fun getHappinessText(civInfo: CivilizationInfo): String {
var happinessText = civInfo.happiness.toString() var happinessText = civInfo.getHappiness().toString()
if (civInfo.goldenAges.isGoldenAge()) if (civInfo.goldenAges.isGoldenAge())
happinessText += " "+"GOLDEN AGE".tr()+"(${civInfo.goldenAges.turnsLeftForCurrentGoldenAge})" happinessText += " "+"GOLDEN AGE".tr()+"(${civInfo.goldenAges.turnsLeftForCurrentGoldenAge})"
else else