Game can handle policies "disappearing" between mod versions

Added policy map to ruleset for simpler policy location
This commit is contained in:
Yair Morgenstern 2021-01-12 17:10:38 +02:00
parent 67bcc26ab7
commit a8e0f270a0
5 changed files with 82 additions and 70 deletions

View File

@ -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)
}
}

View File

@ -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<Trade> {
private fun potentialLuxuryTrades(civInfo: CivilizationInfo, otherCivInfo: CivilizationInfo): ArrayList<Trade> {
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<ThreatLevel.High) // don't piss them off for no reason please.
val threatLevel = Automation.threatAssessment(civInfo, otherCiv)
if (threatLevel < ThreatLevel.High) // don't piss them off for no reason please.
otherCiv.popupAlerts.add(PopupAlert(AlertType.DemandToStopSettlingCitiesNear, civInfo.civName))
}
}
@ -537,10 +543,10 @@ object NextTurnAutomation {
}
fun getMinDistanceBetweenCities(civ1: CivilizationInfo, civ2: CivilizationInfo): Int {
return getClosestCities(civ1,civ2).aerialDistance
return getClosestCities(civ1, civ2).aerialDistance
}
data class CityDistance(val city1:CityInfo, val city2:CityInfo, val aerialDistance: Int)
data class CityDistance(val city1: CityInfo, val city2: CityInfo, val aerialDistance: Int)
fun getClosestCities(civ1: CivilizationInfo, civ2: CivilizationInfo): CityDistance {
val cityDistances = arrayListOf<CityDistance>()
@ -551,4 +557,4 @@ object NextTurnAutomation {
return cityDistances.minBy { it.aerialDistance }!!
}
}
}

View File

@ -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<String>()
var numberOfAdoptedPolicies = 0
var shouldOpenPolicyPicker = false
get() = field && canAdoptPolicy()
get() = field && canAdoptPolicy()
var legalismState = HashMap<String, String>()
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!!

View File

@ -51,6 +51,7 @@ class Ruleset {
val quests = LinkedHashMap<String, Quest>()
val specialists = LinkedHashMap<String, Specialist>()
val policyBranches = LinkedHashMap<String, PolicyBranch>()
val policies = LinkedHashMap<String, Policy>()
val difficulties = LinkedHashMap<String, Difficulty>()
val mods = LinkedHashSet<String>()
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"
}

View File

@ -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 + "}"
}