diff --git a/core/src/com/unciv/logic/GameInfo.kt b/core/src/com/unciv/logic/GameInfo.kt index fc64e51795..8835abd1f3 100644 --- a/core/src/com/unciv/logic/GameInfo.kt +++ b/core/src/com/unciv/logic/GameInfo.kt @@ -24,6 +24,7 @@ class UncivShowableException(missingMods: String) : Exception(missingMods) class GameInfo { @Transient lateinit var difficultyObject: Difficulty // Since this is static game-wide, and was taking a large part of nextTurn + @Transient lateinit var currentPlayerCiv: CivilizationInfo // this is called thousands of times, no reason to search for it with a find{} every time @@ -31,6 +32,7 @@ class GameInfo { * that is inconsistent with the saved game on the cloud */ @Transient var isUpToDate = false + @Transient lateinit var ruleSet: Ruleset @@ -356,6 +358,9 @@ class GameInfo { for (tech in civinfo.tech.techsResearched.toList()) if (!ruleSet.technologies.containsKey(tech)) civinfo.tech.techsResearched.remove(tech) + for (policy in civinfo.policies.adoptedPolicies.toList()) + if (!ruleSet.policies.containsKey(policy)) + civinfo.policies.adoptedPolicies.remove(policy) } } diff --git a/core/src/com/unciv/logic/automation/NextTurnAutomation.kt b/core/src/com/unciv/logic/automation/NextTurnAutomation.kt index 48093fccfd..e4311a9d62 100644 --- a/core/src/com/unciv/logic/automation/NextTurnAutomation.kt +++ b/core/src/com/unciv/logic/automation/NextTurnAutomation.kt @@ -27,8 +27,8 @@ object NextTurnAutomation { respondToPopupAlerts(civInfo) respondToTradeRequests(civInfo) - if(civInfo.isMajorCiv()) { - if(!civInfo.gameInfo.ruleSet.modOptions.uniques.contains(ModOptionsConstants.diplomaticRelationshipsCannotChange)) { + if (civInfo.isMajorCiv()) { + if (!civInfo.gameInfo.ruleSet.modOptions.uniques.contains(ModOptionsConstants.diplomaticRelationshipsCannotChange)) { declareWar(civInfo) offerPeaceTreaty(civInfo) // offerDeclarationOfFriendship(civInfo) @@ -52,7 +52,7 @@ object NextTurnAutomation { } private fun respondToTradeRequests(civInfo: CivilizationInfo) { - for(tradeRequest in civInfo.tradeRequests.toList()){ + for (tradeRequest in civInfo.tradeRequests.toList()) { val otherCiv = civInfo.gameInfo.getCivilization(tradeRequest.requestingCiv) val tradeLogic = TradeLogic(civInfo, otherCiv) tradeLogic.currentTrade.set(tradeRequest.trade) @@ -62,11 +62,10 @@ object NextTurnAutomation { * the same resource to ANOTHER civ in this turn. Complicated! */ civInfo.tradeRequests.remove(tradeRequest) - if(TradeEvaluation().isTradeAcceptable(tradeLogic.currentTrade,civInfo,otherCiv)){ + if (TradeEvaluation().isTradeAcceptable(tradeLogic.currentTrade, civInfo, otherCiv)) { tradeLogic.acceptTrade() otherCiv.addNotification("[${civInfo.civName}] has accepted your trade request", Color.GOLD) - } - else { + } else { otherCiv.addNotification("[${civInfo.civName}] has denied your trade request", Color.GOLD) } } @@ -74,7 +73,7 @@ object NextTurnAutomation { } private fun respondToPopupAlerts(civInfo: CivilizationInfo) { - for(popupAlert in civInfo.popupAlerts) { + for (popupAlert in civInfo.popupAlerts) { if (popupAlert.type == AlertType.DemandToStopSettlingCitiesNear) { // we're called upon to make a decision val demandingCiv = civInfo.gameInfo.getCivilization(popupAlert.value) val diploManager = civInfo.getDiplomacyManager(demandingCiv) @@ -96,7 +95,7 @@ object NextTurnAutomation { civInfo.popupAlerts.clear() // AIs don't care about popups. } - private fun tryGainInfluence(civInfo: CivilizationInfo, cityState:CivilizationInfo) { + private fun tryGainInfluence(civInfo: CivilizationInfo, cityState: CivilizationInfo) { if (civInfo.gold < 250) return // save up if (cityState.getDiplomacyManager(civInfo).influence < 20) { civInfo.giveGoldGift(cityState, 250) @@ -180,8 +179,7 @@ object NextTurnAutomation { private fun adoptPolicy(civInfo: CivilizationInfo) { while (civInfo.policies.canAdoptPolicy()) { - val adoptablePolicies = civInfo.gameInfo.ruleSet.policyBranches.values - .flatMap { it.policies.union(listOf(it)) } + val adoptablePolicies = civInfo.gameInfo.ruleSet.policies.values .filter { civInfo.policies.isAdoptable(it) } // This can happen if the player is crazy enough to have the game continue forever and he disabled cultural victory @@ -209,7 +207,7 @@ object NextTurnAutomation { } } - private fun potentialLuxuryTrades(civInfo:CivilizationInfo, otherCivInfo:CivilizationInfo): ArrayList { + private fun potentialLuxuryTrades(civInfo: CivilizationInfo, otherCivInfo: CivilizationInfo): ArrayList { val tradeLogic = TradeLogic(civInfo, otherCivInfo) val ourTradableLuxuryResources = tradeLogic.ourAvailableOffers .filter { it.type == TradeType.Luxury_Resource && it.amount > 1 } @@ -245,8 +243,10 @@ object NextTurnAutomation { // We should A. add some sort of timer (20? 30 turns?) between luxury trade requests if they're denied - see DeclinedLuxExchange // B. have a way for the AI to keep track of the "pending offers" - see DiplomacyManager.resourcesFromTrade - for (otherCiv in knownCivs.filter { it.isMajorCiv() && !it.isAtWarWith(civInfo) - && !civInfo.getDiplomacyManager(it).hasFlag(DiplomacyFlags.DeclinedLuxExchange)}) { + for (otherCiv in knownCivs.filter { + it.isMajorCiv() && !it.isAtWarWith(civInfo) + && !civInfo.getDiplomacyManager(it).hasFlag(DiplomacyFlags.DeclinedLuxExchange) + }) { val relationshipLevel = civInfo.getDiplomacyManager(otherCiv).relationshipLevel() if (relationshipLevel <= RelationshipLevel.Enemy) @@ -263,11 +263,12 @@ object NextTurnAutomation { private fun offerDeclarationOfFriendship(civInfo: CivilizationInfo) { val civsThatWeCanDeclareFriendshipWith = civInfo.getKnownCivs() .asSequence() - .filter { it.isMajorCiv() } - .filter { !it.isAtWarWith(civInfo) } - .filter { it.getDiplomacyManager(civInfo).relationshipLevel() > RelationshipLevel.Neutral } - .filter { !civInfo.getDiplomacyManager(it).hasFlag(DiplomacyFlags.DeclarationOfFriendship) } - .filter { !civInfo.getDiplomacyManager(it).hasFlag(DiplomacyFlags.Denunceation) } + .filter { + it.isMajorCiv() && !it.isAtWarWith(civInfo) + && it.getDiplomacyManager(civInfo).relationshipLevel() > RelationshipLevel.Neutral + && !civInfo.getDiplomacyManager(it).hasFlag(DiplomacyFlags.DeclarationOfFriendship) + && !civInfo.getDiplomacyManager(it).hasFlag(DiplomacyFlags.Denunceation) + } .sortedByDescending { it.getDiplomacyManager(civInfo).relationshipLevel() } for (civ in civsThatWeCanDeclareFriendshipWith) { // Default setting is 5, this will be changed according to different civ. @@ -281,8 +282,10 @@ object NextTurnAutomation { val canSignResearchAgreementCiv = civInfo.getKnownCivs() .asSequence() - .filter { civInfo.canSignResearchAgreementsWith(it) - && !civInfo.getDiplomacyManager(it).hasFlag(DiplomacyFlags.DeclinedResearchAgreement) } + .filter { + civInfo.canSignResearchAgreementsWith(it) + && !civInfo.getDiplomacyManager(it).hasFlag(DiplomacyFlags.DeclinedResearchAgreement) + } .sortedByDescending { it.statsForNextTurn.science } for (otherCiv in canSignResearchAgreementCiv) { @@ -306,13 +309,13 @@ object NextTurnAutomation { .filterNot { it == civInfo || it.isBarbarian() || it.cities.isEmpty() } .filter { !civInfo.getDiplomacyManager(it).hasFlag(DiplomacyFlags.DeclinedPeace) } // Don't allow AIs to offer peace to ity states allied with their enemies - .filterNot { it.isCityState() && it.getAllyCiv()!="" && civInfo.isAtWarWith(civInfo.gameInfo.getCivilization(it.getAllyCiv())) } + .filterNot { it.isCityState() && it.getAllyCiv() != "" && civInfo.isAtWarWith(civInfo.gameInfo.getCivilization(it.getAllyCiv())) } for (enemy in enemiesCiv) { val enemiesStrength = Automation.evaluteCombatStrength(enemy) // If we don't have an unobstructed path to the enemy, offer peace even though we can beat them - val canReachEnemy = BFS(civInfo.getCapital().getCenterTile()){ it.canCivEnter(civInfo) } + val canReachEnemy = BFS(civInfo.getCapital().getCenterTile()) { it.canCivEnter(civInfo) } canReachEnemy.stepToEnd() if (enemy.cities.any { canReachEnemy.hasReachedTile(it.getCenterTile()) } @@ -368,8 +371,10 @@ object NextTurnAutomation { //evaluate war val enemyCivs = civInfo.getKnownCivs() - .filterNot { it == civInfo || it.cities.isEmpty() || !civInfo.getDiplomacyManager(it).canDeclareWar() - || (it.cities.none { civInfo.exploredTiles.contains(it.location) }) } + .filterNot { + it == civInfo || it.cities.isEmpty() || !civInfo.getDiplomacyManager(it).canDeclareWar() + || (it.cities.none { civInfo.exploredTiles.contains(it.location) }) + } // If the AI declares war on a civ without knowing the location of any cities, it'll just keep amassing an army and not sending it anywhere, // and end up at a massive disadvantage @@ -383,7 +388,7 @@ object NextTurnAutomation { civInfo.getDiplomacyManager(civWithBestMotivationToAttack.first).declareWar() } - private fun motivationToAttack(civInfo:CivilizationInfo, otherCiv:CivilizationInfo): Int { + private fun motivationToAttack(civInfo: CivilizationInfo, otherCiv: CivilizationInfo): Int { val ourCombatStrength = Automation.evaluteCombatStrength(civInfo).toFloat() val theirCombatStrength = Automation.evaluteCombatStrength(otherCiv) if (theirCombatStrength > ourCombatStrength) return 0 @@ -417,13 +422,13 @@ object NextTurnAutomation { modifierMap["No land path"] = -10 val diplomacyManager = civInfo.getDiplomacyManager(otherCiv) - if(diplomacyManager.hasFlag(DiplomacyFlags.ResearchAgreement)) + if (diplomacyManager.hasFlag(DiplomacyFlags.ResearchAgreement)) modifierMap["Research Agreement"] = -5 - if(diplomacyManager.hasFlag(DiplomacyFlags.DeclarationOfFriendship)) + if (diplomacyManager.hasFlag(DiplomacyFlags.DeclarationOfFriendship)) modifierMap["Declaration of Friendship"] = -10 - val relationshipModifier = when(diplomacyManager.relationshipLevel()){ + val relationshipModifier = when (diplomacyManager.relationshipLevel()) { RelationshipLevel.Unforgivable -> 10 RelationshipLevel.Enemy -> 5 RelationshipLevel.Ally -> -5 // this is so that ally + DoF is not too unbalanced - @@ -432,10 +437,10 @@ object NextTurnAutomation { } modifierMap["Relationship"] = relationshipModifier - if(diplomacyManager.resourcesFromTrade().any { it.amount > 0 }) + if (diplomacyManager.resourcesFromTrade().any { it.amount > 0 }) modifierMap["Receiving trade resources"] = -5 - if(theirCity.getTiles().none { it.neighbors.any { it.getOwner()==theirCity.civInfo && it.getCity() != theirCity } }) + if (theirCity.getTiles().none { it.neighbors.any { it.getOwner() == theirCity.civInfo && it.getCity() != theirCity } }) modifierMap["Isolated city"] = 15 return modifierMap.values.sum() @@ -458,7 +463,7 @@ object NextTurnAutomation { unit.type.isRanged() -> rangedUnits.add(unit) unit.type.isMelee() -> meleeUnits.add(unit) unit.hasUnique("Bonus for units in 2 tile radius 15%") - -> generals.add(unit) //generals move after military units + -> generals.add(unit) //generals move after military units else -> civilianUnits.add(unit) } } @@ -518,18 +523,19 @@ object NextTurnAutomation { } } - private fun onCitySettledNearBorders(civInfo: CivilizationInfo, otherCiv:CivilizationInfo){ + private fun onCitySettledNearBorders(civInfo: CivilizationInfo, otherCiv: CivilizationInfo) { val diplomacyManager = civInfo.getDiplomacyManager(otherCiv) when { - diplomacyManager.hasFlag(DiplomacyFlags.IgnoreThemSettlingNearUs) -> {} + diplomacyManager.hasFlag(DiplomacyFlags.IgnoreThemSettlingNearUs) -> { + } diplomacyManager.hasFlag(DiplomacyFlags.AgreedToNotSettleNearUs) -> { otherCiv.popupAlerts.add(PopupAlert(AlertType.CitySettledNearOtherCivDespiteOurPromise, civInfo.civName)) - diplomacyManager.setFlag(DiplomacyFlags.IgnoreThemSettlingNearUs,100) - diplomacyManager.setModifier(DiplomaticModifiers.BetrayedPromiseToNotSettleCitiesNearUs,-20f) + diplomacyManager.setFlag(DiplomacyFlags.IgnoreThemSettlingNearUs, 100) + diplomacyManager.setModifier(DiplomaticModifiers.BetrayedPromiseToNotSettleCitiesNearUs, -20f) } else -> { - val threatLevel = Automation.threatAssessment(civInfo,otherCiv) - if(threatLevel() @@ -551,4 +557,4 @@ object NextTurnAutomation { return cityDistances.minBy { it.aerialDistance }!! } -} +} \ No newline at end of file diff --git a/core/src/com/unciv/logic/civilization/PolicyManager.kt b/core/src/com/unciv/logic/civilization/PolicyManager.kt index 6d57f3b90d..a506dbf612 100644 --- a/core/src/com/unciv/logic/civilization/PolicyManager.kt +++ b/core/src/com/unciv/logic/civilization/PolicyManager.kt @@ -1,10 +1,8 @@ package com.unciv.logic.civilization -import com.unciv.Constants import com.unciv.models.ruleset.Policy import com.unciv.models.ruleset.UniqueMap import com.unciv.models.ruleset.UniqueTriggerActivation -import com.unciv.models.ruleset.VictoryType import kotlin.math.min import kotlin.math.pow import kotlin.math.roundToInt @@ -12,17 +10,20 @@ import kotlin.math.roundToInt class PolicyManager { - @Transient lateinit var civInfo: CivilizationInfo + @Transient + lateinit var civInfo: CivilizationInfo + // Needs to be separate from the actual adopted policies, so that // in different game versions, policies can have different effects - @Transient internal val policyUniques = UniqueMap() + @Transient + internal val policyUniques = UniqueMap() var freePolicies = 0 var storedCulture = 0 internal val adoptedPolicies = HashSet() var numberOfAdoptedPolicies = 0 var shouldOpenPolicyPicker = false - get() = field && canAdoptPolicy() + get() = field && canAdoptPolicy() var legalismState = HashMap() var autocracyCompletedTurns = 0 @@ -38,26 +39,23 @@ class PolicyManager { return toReturn } - fun getPolicyByName(name:String): Policy = getAllPolicies().first { it.name==name } - + fun getPolicyByName(name: String): Policy = civInfo.gameInfo.ruleSet.policies[name]!! + fun setTransients() { for (policyName in adoptedPolicies) addPolicyToTransients(getPolicyByName(policyName)) } - fun addPolicyToTransients(policy: Policy){ - for(unique in policy.uniqueObjects) + fun addPolicyToTransients(policy: Policy) { + for (unique in policy.uniqueObjects) policyUniques.addUnique(unique) } - private fun getAllPolicies() = civInfo.gameInfo.ruleSet.policyBranches.values.asSequence() - .flatMap { it.policies.asSequence()+sequenceOf(it) } - fun startTurn() { tryAddLegalismBuildings() } - fun addCulture(culture: Int){ + fun addCulture(culture: Int) { val couldAdoptPolicyBefore = canAdoptPolicy() storedCulture += culture if (!couldAdoptPolicyBefore && canAdoptPolicy()) @@ -80,7 +78,7 @@ class PolicyManager { // Use "Culture cost of adopting new Policies reduced by [10]%" and "Each city founded increases culture cost of policies [33]% less than normal" instead if (civInfo.hasUnique("Each city founded increases culture cost of policies 33% less than normal")) cityModifier *= (2 / 3f) - for(unique in civInfo.getMatchingUniques("Culture cost of adopting new Policies reduced by 10%")) + for (unique in civInfo.getMatchingUniques("Culture cost of adopting new Policies reduced by 10%")) policyCultureCost *= 0.9 for (unique in civInfo.getMatchingUniques("Each city founded increases culture cost of policies []% less than normal")) @@ -99,7 +97,7 @@ class PolicyManager { fun isAdopted(policyName: String): Boolean = adoptedPolicies.contains(policyName) fun isAdoptable(policy: Policy): Boolean { - if(isAdopted(policy.name)) return false + if (isAdopted(policy.name)) return false if (policy.name.endsWith("Complete")) return false if (!getAdoptedPolicies().containsAll(policy.requires!!)) return false if (civInfo.gameInfo.ruleSet.getEraNumber(policy.branch.era) > civInfo.getEraNumber()) return false @@ -110,7 +108,7 @@ class PolicyManager { if (freePolicies == 0 && storedCulture < getCultureNeededForNextPolicy()) return false - val hasAdoptablePolicies = getAllPolicies() + val hasAdoptablePolicies = civInfo.gameInfo.ruleSet.policies.values .any { civInfo.policies.isAdoptable(it) } return hasAdoptablePolicies } @@ -138,8 +136,6 @@ class PolicyManager { } } - val hasCapital = civInfo.cities.any { it.isCapital() } - for (unique in policy.uniqueObjects) UniqueTriggerActivation.triggerCivwideUnique(unique, civInfo) @@ -153,15 +149,17 @@ class PolicyManager { } fun tryAddLegalismBuildings() { - if(!civInfo.hasUnique("Immediately creates a cheapest available cultural building in each of your first 4 cities for free")) + if (!civInfo.hasUnique("Immediately creates a cheapest available cultural building in each of your first 4 cities for free")) return - if(legalismState.size >= 4) return + if (legalismState.size >= 4) return val candidateCities = civInfo.cities .sortedBy { it.turnAcquired } .subList(0, min(4, civInfo.cities.size)) - .filter { it.id !in legalismState - && it.cityConstructions.hasBuildableCultureBuilding() } + .filter { + it.id !in legalismState + && it.cityConstructions.hasBuildableCultureBuilding() + } for (city in candidateCities) { val builtBuilding = city.cityConstructions.addCultureBuilding() legalismState[city.id] = builtBuilding!! diff --git a/core/src/com/unciv/models/ruleset/Ruleset.kt b/core/src/com/unciv/models/ruleset/Ruleset.kt index 19c049999a..075412909b 100644 --- a/core/src/com/unciv/models/ruleset/Ruleset.kt +++ b/core/src/com/unciv/models/ruleset/Ruleset.kt @@ -51,6 +51,7 @@ class Ruleset { val quests = LinkedHashMap() val specialists = LinkedHashMap() val policyBranches = LinkedHashMap() + val policies = LinkedHashMap() val difficulties = LinkedHashMap() val mods = LinkedHashSet() var modOptions = ModOptions() @@ -74,6 +75,7 @@ class Ruleset { difficulties.putAll(ruleset.difficulties) nations.putAll(ruleset.nations) policyBranches.putAll(ruleset.policyBranches) + policies.putAll(ruleset.policies) quests.putAll(ruleset.quests) specialists.putAll(ruleset.specialists) technologies.putAll(ruleset.technologies) @@ -93,6 +95,7 @@ class Ruleset { difficulties.clear() nations.clear() policyBranches.clear() + policies.clear() quests.clear() technologies.clear() buildings.clear() @@ -159,9 +162,11 @@ class Ruleset { for (branch in policyBranches.values) { branch.requires = ArrayList() branch.branch = branch + policies[branch.name] = branch for (policy in branch.policies) { policy.branch = branch if (policy.requires == null) policy.requires = arrayListOf(branch.name) + policies[policy.name] = policy } branch.policies.last().name = branch.name + " Complete" } diff --git a/core/src/com/unciv/ui/pickerscreens/PolicyPickerScreen.kt b/core/src/com/unciv/ui/pickerscreens/PolicyPickerScreen.kt index 4af812649f..c4688fd310 100644 --- a/core/src/com/unciv/ui/pickerscreens/PolicyPickerScreen.kt +++ b/core/src/com/unciv/ui/pickerscreens/PolicyPickerScreen.kt @@ -25,28 +25,26 @@ class PolicyPickerScreen(val worldScreen: WorldScreen, civInfo: CivilizationInfo rightSideButton.setText("{Adopt policy}\r\n(".tr() + policies.storedCulture + "/" + policies.getCultureNeededForNextPolicy() + ")") - if (viewingCiv.gameInfo.ruleSet.policyBranches.values.flatMap { it.policies }.all { it.name in policies.adoptedPolicies}) + if (viewingCiv.gameInfo.ruleSet.policies.values.all { it.name in policies.adoptedPolicies }) rightSideButton.setText("All policies adopted".tr()) setDefaultCloseAction() if (policies.freePolicies > 0) { rightSideButton.setText("Adopt free policy".tr()) if (policies.canAdoptPolicy()) closeButton.disable() - } - else onBackButtonClicked { UncivGame.Current.setWorldScreen() } + } else onBackButtonClicked { UncivGame.Current.setWorldScreen() } rightSideButton.onClick(UncivSound.Policy) { viewingCiv.policies.adopt(pickedPolicy!!) // If we've moved to another screen in the meantime (great person pick, victory screen) ignore this - if(game.screen !is PolicyPickerScreen || !policies.canAdoptPolicy()){ + if (game.screen !is PolicyPickerScreen || !policies.canAdoptPolicy()) { game.setWorldScreen() dispose() - } - else game.setScreen(PolicyPickerScreen(worldScreen)) // update policies + } else game.setScreen(PolicyPickerScreen(worldScreen)) // update policies } - if(!UncivGame.Current.worldScreen.canChangeState) + if (!UncivGame.Current.worldScreen.canChangeState) rightSideButton.disable() topTable.row().pad(30f) @@ -107,7 +105,7 @@ class PolicyPickerScreen(val worldScreen: WorldScreen, civInfo: CivilizationInfo if (!policy.name.endsWith("Complete")) { if (policy.requires!!.isNotEmpty()) - policyText += "Requires [" + policy.requires!!.joinToString { it.tr() }+"]" + policyText += "Requires [" + policy.requires!!.joinToString { it.tr() } + "]" else policyText += "{Unlocked at} {" + policy.branch.era + "}" }