mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-04 23:40:01 +07:00
chore: Extracted all 'next turn' logic from DiplomacyManager to DiplomacyTurnManager
This commit is contained in:
@ -16,7 +16,6 @@ import com.unciv.models.ruleset.unique.UniqueType
|
||||
import com.unciv.ui.components.extensions.toPercent
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.math.sign
|
||||
|
||||
enum class RelationshipLevel(val color: Color) {
|
||||
@ -128,7 +127,7 @@ class DiplomacyManager() : IsPartOfGameInfoSerialization {
|
||||
/** Contains various flags (declared war, promised to not settle, declined luxury trade) and the number of turns in which they will expire.
|
||||
* The JSON serialize/deserialize REFUSES to deserialize hashmap keys as Enums, so I'm forced to use strings instead =(
|
||||
* This is so sad Alexa play Despacito */
|
||||
private var flagsCountdown = HashMap<String, Int>()
|
||||
internal var flagsCountdown = HashMap<String, Int>()
|
||||
|
||||
/** For AI. Positive is good relations, negative is bad.
|
||||
* Baseline is 1 point for each turn of peace - so declaring a war upends 40 years of peace, and e.g. capturing a city can be another 30 or 40.
|
||||
@ -139,7 +138,7 @@ class DiplomacyManager() : IsPartOfGameInfoSerialization {
|
||||
* Access via getInfluence() and setInfluence() unless you know what you're doing.
|
||||
* Note that not using the setter skips recalculating the ally and bounds checks,
|
||||
* and skipping the getter bypasses the modified value when at war */
|
||||
private var influence = 0f
|
||||
internal var influence = 0f
|
||||
|
||||
/** Total of each turn Science during Research Agreement */
|
||||
internal var totalOfScienceDuringRA = 0
|
||||
@ -318,7 +317,7 @@ class DiplomacyManager() : IsPartOfGameInfoSerialization {
|
||||
fun getInfluence() = if (civInfo.isAtWarWith(otherCiv())) MINIMUM_INFLUENCE else influence
|
||||
|
||||
// To be run from City-State DiplomacyManager, which holds the influence. Resting point for every major civ can be different.
|
||||
private fun getCityStateInfluenceRestingPoint(): Float {
|
||||
internal fun getCityStateInfluenceRestingPoint(): Float {
|
||||
var restingPoint = 0f
|
||||
|
||||
for (unique in otherCiv().getMatchingUniques(UniqueType.CityStateRestingPoint))
|
||||
@ -336,7 +335,7 @@ class DiplomacyManager() : IsPartOfGameInfoSerialization {
|
||||
return restingPoint
|
||||
}
|
||||
|
||||
private fun getCityStateInfluenceDegrade(): Float {
|
||||
internal fun getCityStateInfluenceDegrade(): Float {
|
||||
if (getInfluence() <= getCityStateInfluenceRestingPoint())
|
||||
return 0f
|
||||
|
||||
@ -364,24 +363,6 @@ class DiplomacyManager() : IsPartOfGameInfoSerialization {
|
||||
return max(0f, decrement) * max(-100f, modifierPercent).toPercent()
|
||||
}
|
||||
|
||||
private fun getCityStateInfluenceRecovery(): Float {
|
||||
if (getInfluence() >= getCityStateInfluenceRestingPoint())
|
||||
return 0f
|
||||
|
||||
val increment = 1f // sic: personality does not matter here
|
||||
|
||||
var modifierPercent = 0f
|
||||
|
||||
if (otherCiv().hasUnique(UniqueType.CityStateInfluenceRecoversTwiceNormalRate))
|
||||
modifierPercent += 100f
|
||||
|
||||
val religion = if (civInfo.cities.isEmpty() || civInfo.getCapital() == null) null
|
||||
else civInfo.getCapital()!!.religion.getMajorityReligionName()
|
||||
if (religion != null && religion == otherCiv().religionManager.religion?.name)
|
||||
modifierPercent += 50f // 50% quicker recovery when sharing a religion
|
||||
|
||||
return max(0f, increment) * max(0f, modifierPercent).toPercent()
|
||||
}
|
||||
|
||||
fun canDeclareWar() = turnsToPeaceTreaty() == 0 && diplomaticStatus != DiplomaticStatus.War
|
||||
|
||||
@ -401,14 +382,6 @@ class DiplomacyManager() : IsPartOfGameInfoSerialization {
|
||||
return goldPerTurnForUs
|
||||
}
|
||||
|
||||
private fun scienceFromResearchAgreement() {
|
||||
// https://forums.civfanatics.com/resources/research-agreements-bnw.25568/
|
||||
val scienceFromResearchAgreement = min(totalOfScienceDuringRA, otherCivDiplomacy().totalOfScienceDuringRA)
|
||||
civInfo.tech.scienceFromResearchAgreements += scienceFromResearchAgreement
|
||||
otherCiv().tech.scienceFromResearchAgreements += scienceFromResearchAgreement
|
||||
totalOfScienceDuringRA = 0
|
||||
otherCivDiplomacy().totalOfScienceDuringRA = 0
|
||||
}
|
||||
|
||||
fun resourcesFromTrade(): ResourceSupplyList {
|
||||
val newResourceSupplyList = ResourceSupplyList()
|
||||
@ -444,48 +417,6 @@ class DiplomacyManager() : IsPartOfGameInfoSerialization {
|
||||
//endregion
|
||||
|
||||
//region state-changing functions
|
||||
private fun removeUntenableTrades() {
|
||||
for (trade in trades.toList()) {
|
||||
|
||||
// Every cancelled trade can change this - if 1 resource is missing,
|
||||
// don't cancel all trades of that resource, only cancel one (the first one, as it happens, since they're added chronologically)
|
||||
val negativeCivResources = civInfo.getCivResourceSupply()
|
||||
.filter { it.amount < 0 && !it.resource.isStockpiled() }.map { it.resource.name }
|
||||
|
||||
for (offer in trade.ourOffers) {
|
||||
if (offer.type in listOf(TradeType.Luxury_Resource, TradeType.Strategic_Resource)
|
||||
&& (offer.name in negativeCivResources || !civInfo.gameInfo.ruleset.tileResources.containsKey(offer.name))
|
||||
) {
|
||||
|
||||
trades.remove(trade)
|
||||
val otherCivTrades = otherCiv().getDiplomacyManager(civInfo).trades
|
||||
otherCivTrades.removeAll { it.equalTrade(trade.reverse()) }
|
||||
|
||||
// Can't cut short peace treaties!
|
||||
if (trade.theirOffers.any { it.name == Constants.peaceTreaty }) {
|
||||
remakePeaceTreaty(trade.theirOffers.first { it.name == Constants.peaceTreaty }.duration)
|
||||
}
|
||||
|
||||
civInfo.addNotification("One of our trades with [$otherCivName] has been cut short", NotificationCategory.Trade, NotificationIcon.Trade, otherCivName)
|
||||
otherCiv().addNotification("One of our trades with [${civInfo.civName}] has been cut short", NotificationCategory.Trade, NotificationIcon.Trade, civInfo.civName)
|
||||
civInfo.cache.updateCivResources()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun remakePeaceTreaty(durationLeft: Int) {
|
||||
val treaty = Trade()
|
||||
treaty.ourOffers.add(
|
||||
TradeOffer(Constants.peaceTreaty, TradeType.Treaty, duration = durationLeft)
|
||||
)
|
||||
treaty.theirOffers.add(
|
||||
TradeOffer(Constants.peaceTreaty, TradeType.Treaty, duration = durationLeft)
|
||||
)
|
||||
trades.add(treaty)
|
||||
otherCiv().getDiplomacyManager(civInfo).trades.add(treaty)
|
||||
}
|
||||
|
||||
// for performance reasons we don't want to call this every time we want to see if a unit can move through a tile
|
||||
fun updateHasOpenBorders() {
|
||||
// City-states can enter ally's territory (the opposite is true anyway even without open borders)
|
||||
@ -503,229 +434,6 @@ class DiplomacyManager() : IsPartOfGameInfoSerialization {
|
||||
}
|
||||
}
|
||||
|
||||
fun nextTurn() {
|
||||
nextTurnTrades()
|
||||
removeUntenableTrades()
|
||||
updateHasOpenBorders()
|
||||
nextTurnDiplomaticModifiers()
|
||||
nextTurnFlags()
|
||||
if (civInfo.isCityState() && otherCiv().isMajorCiv())
|
||||
nextTurnCityStateInfluence()
|
||||
}
|
||||
|
||||
private fun nextTurnCityStateInfluence() {
|
||||
val initialRelationshipLevel = relationshipIgnoreAfraid() // Enough since only >= Friend is notified
|
||||
|
||||
val restingPoint = getCityStateInfluenceRestingPoint()
|
||||
// We don't use `getInfluence()` here, as then during war with the ally of this CS,
|
||||
// our influence would be set to -59, overwriting the old value, which we want to keep
|
||||
// as it should be restored once the war ends (though we keep influence degradation from time during the war)
|
||||
if (influence > restingPoint) {
|
||||
val decrement = getCityStateInfluenceDegrade()
|
||||
setInfluence(max(restingPoint, influence - decrement))
|
||||
} else if (influence < restingPoint) {
|
||||
val increment = getCityStateInfluenceRecovery()
|
||||
setInfluence(min(restingPoint, influence + increment))
|
||||
}
|
||||
|
||||
if (!civInfo.isDefeated()) { // don't display city state relationship notifications when the city state is currently defeated
|
||||
val notificationActions = civInfo.cityStateFunctions.getNotificationActions()
|
||||
if (getTurnsToRelationshipChange() == 1) {
|
||||
val text = "Your relationship with [${civInfo.civName}] is about to degrade"
|
||||
otherCiv().addNotification(text, notificationActions, NotificationCategory.Diplomacy, civInfo.civName, NotificationIcon.Diplomacy)
|
||||
}
|
||||
|
||||
if (initialRelationshipLevel >= RelationshipLevel.Friend && initialRelationshipLevel != relationshipIgnoreAfraid()) {
|
||||
val text = "Your relationship with [${civInfo.civName}] degraded"
|
||||
otherCiv().addNotification(text, notificationActions, NotificationCategory.Diplomacy, civInfo.civName, NotificationIcon.Diplomacy)
|
||||
}
|
||||
|
||||
// Potentially notify about afraid status
|
||||
if (getInfluence() < 30 // We usually don't want to bully our friends
|
||||
&& !hasFlag(DiplomacyFlags.NotifiedAfraid)
|
||||
&& civInfo.cityStateFunctions.getTributeWillingness(otherCiv()) > 0
|
||||
&& otherCiv().isMajorCiv()
|
||||
) {
|
||||
setFlag(DiplomacyFlags.NotifiedAfraid, 20) // Wait 20 turns until next reminder
|
||||
val text = "[${civInfo.civName}] is afraid of your military power!"
|
||||
otherCiv().addNotification(text, notificationActions, NotificationCategory.Diplomacy, civInfo.civName, NotificationIcon.Diplomacy)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun nextTurnFlags() {
|
||||
loop@ for (flag in flagsCountdown.keys.toList()) {
|
||||
// No need to decrement negative countdown flags: they do not expire
|
||||
if (flagsCountdown[flag]!! > 0)
|
||||
flagsCountdown[flag] = flagsCountdown[flag]!! - 1
|
||||
|
||||
// If we have uniques that make city states grant military units faster when at war with a common enemy, add higher numbers to this flag
|
||||
if (flag == DiplomacyFlags.ProvideMilitaryUnit.name && civInfo.isMajorCiv() && otherCiv().isCityState() &&
|
||||
civInfo.gameInfo.civilizations.any { civInfo.isAtWarWith(it) && otherCiv().isAtWarWith(it) }) {
|
||||
for (unique in civInfo.getMatchingUniques(UniqueType.CityStateMoreGiftedUnits)) {
|
||||
flagsCountdown[DiplomacyFlags.ProvideMilitaryUnit.name] =
|
||||
flagsCountdown[DiplomacyFlags.ProvideMilitaryUnit.name]!! - unique.params[0].toInt() + 1
|
||||
if (flagsCountdown[DiplomacyFlags.ProvideMilitaryUnit.name]!! <= 0) {
|
||||
flagsCountdown[DiplomacyFlags.ProvideMilitaryUnit.name] = 0
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// At the end of every turn
|
||||
if (flag == DiplomacyFlags.ResearchAgreement.name)
|
||||
totalOfScienceDuringRA += civInfo.stats.statsForNextTurn.science.toInt()
|
||||
|
||||
// These modifiers decrease slightly @ 50
|
||||
if (flagsCountdown[flag] == 50) {
|
||||
when (flag) {
|
||||
DiplomacyFlags.RememberAttackedProtectedMinor.name -> {
|
||||
addModifier(DiplomaticModifiers.AttackedProtectedMinor, 5f)
|
||||
}
|
||||
DiplomacyFlags.RememberBulliedProtectedMinor.name -> {
|
||||
addModifier(DiplomaticModifiers.BulliedProtectedMinor, 5f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only when flag is expired
|
||||
if (flagsCountdown[flag] == 0) {
|
||||
when (flag) {
|
||||
DiplomacyFlags.ResearchAgreement.name -> {
|
||||
if (!otherCivDiplomacy().hasFlag(DiplomacyFlags.ResearchAgreement))
|
||||
scienceFromResearchAgreement()
|
||||
}
|
||||
DiplomacyFlags.DefensivePact.name -> {
|
||||
diplomaticStatus = DiplomaticStatus.Peace
|
||||
}
|
||||
// This is confusingly named - in fact, the civ that has the flag set is the MAJOR civ
|
||||
DiplomacyFlags.ProvideMilitaryUnit.name -> {
|
||||
// Do not unset the flag - they may return soon, and we'll continue from that point on
|
||||
if (civInfo.cities.isEmpty() || otherCiv().cities.isEmpty())
|
||||
continue@loop
|
||||
else
|
||||
otherCiv().cityStateFunctions.giveMilitaryUnitToPatron(civInfo)
|
||||
}
|
||||
DiplomacyFlags.AgreedToNotSettleNearUs.name -> {
|
||||
addModifier(DiplomaticModifiers.FulfilledPromiseToNotSettleCitiesNearUs, 10f)
|
||||
}
|
||||
DiplomacyFlags.RecentlyAttacked.name -> {
|
||||
civInfo.cityStateFunctions.askForUnitGifts(otherCiv())
|
||||
}
|
||||
// These modifiers don't tick down normally, instead there is a threshold number of turns
|
||||
DiplomacyFlags.RememberDestroyedProtectedMinor.name -> { // 125
|
||||
removeModifier(DiplomaticModifiers.DestroyedProtectedMinor)
|
||||
}
|
||||
DiplomacyFlags.RememberAttackedProtectedMinor.name -> { // 75
|
||||
removeModifier(DiplomaticModifiers.AttackedProtectedMinor)
|
||||
}
|
||||
DiplomacyFlags.RememberBulliedProtectedMinor.name -> { // 75
|
||||
removeModifier(DiplomaticModifiers.BulliedProtectedMinor)
|
||||
}
|
||||
DiplomacyFlags.RememberSidedWithProtectedMinor.name -> { // 25
|
||||
removeModifier(DiplomaticModifiers.SidedWithProtectedMinor)
|
||||
}
|
||||
}
|
||||
|
||||
flagsCountdown.remove(flag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun nextTurnTrades() {
|
||||
for (trade in trades.toList()) {
|
||||
for (offer in trade.ourOffers.union(trade.theirOffers).filter { it.duration > 0 }) {
|
||||
offer.duration--
|
||||
}
|
||||
|
||||
if (trade.ourOffers.all { it.duration <= 0 } && trade.theirOffers.all { it.duration <= 0 }) {
|
||||
trades.remove(trade)
|
||||
for (offer in trade.ourOffers.union(trade.theirOffers).filter { it.duration == 0 }) { // this was a timed trade
|
||||
if (offer in trade.theirOffers)
|
||||
civInfo.addNotification("[${offer.name}] from [$otherCivName] has ended", NotificationCategory.Trade, otherCivName, NotificationIcon.Trade)
|
||||
else civInfo.addNotification("[${offer.name}] to [$otherCivName] has ended", NotificationCategory.Trade, otherCivName, NotificationIcon.Trade)
|
||||
|
||||
civInfo.updateStatsForNextTurn() // if they were bringing us gold per turn
|
||||
if (trade.theirOffers.union(trade.ourOffers) // if resources were involved
|
||||
.any { it.type == TradeType.Luxury_Resource || it.type == TradeType.Strategic_Resource })
|
||||
civInfo.cache.updateCivResources()
|
||||
}
|
||||
}
|
||||
|
||||
for (offer in trade.theirOffers.filter { it.duration <= 3 })
|
||||
{
|
||||
if (offer.duration == 3)
|
||||
civInfo.addNotification("[${offer.name}] from [$otherCivName] will end in [3] turns", NotificationCategory.Trade, otherCivName, NotificationIcon.Trade)
|
||||
else if (offer.duration == 1)
|
||||
civInfo.addNotification("[${offer.name}] from [$otherCivName] will end next turn", NotificationCategory.Trade, otherCivName, NotificationIcon.Trade)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun nextTurnDiplomaticModifiers() {
|
||||
if (diplomaticStatus == DiplomaticStatus.Peace) {
|
||||
if (getModifier(DiplomaticModifiers.YearsOfPeace) < 30)
|
||||
addModifier(DiplomaticModifiers.YearsOfPeace, 0.5f)
|
||||
} else revertToZero(DiplomaticModifiers.YearsOfPeace, 0.5f) // war makes you forget the good ol' days
|
||||
|
||||
var openBorders = 0
|
||||
if (hasOpenBorders) openBorders += 1
|
||||
|
||||
if (otherCivDiplomacy().hasOpenBorders) openBorders += 1
|
||||
if (openBorders > 0) addModifier(DiplomaticModifiers.OpenBorders, openBorders / 8f) // so if we both have open borders it'll grow by 0.25 per turn
|
||||
else revertToZero(DiplomaticModifiers.OpenBorders, 1 / 8f)
|
||||
|
||||
// Negatives
|
||||
revertToZero(DiplomaticModifiers.DeclaredWarOnUs, 1 / 8f) // this disappears real slow - it'll take 160 turns to really forget, this is war declaration we're talking about
|
||||
revertToZero(DiplomaticModifiers.WarMongerer, 1 / 2f) // warmongering gives a big negative boost when it happens but they're forgotten relatively quickly, like WWII amirite
|
||||
revertToZero(DiplomaticModifiers.CapturedOurCities, 1 / 4f) // if you captured our cities, though, that's harder to forget
|
||||
revertToZero(DiplomaticModifiers.BetrayedDeclarationOfFriendship, 1 / 8f) // That's a bastardly thing to do
|
||||
revertToZero(DiplomaticModifiers.BetrayedDefensivePact, 1 / 16f) // That's an outrageous thing to do
|
||||
revertToZero(DiplomaticModifiers.RefusedToNotSettleCitiesNearUs, 1 / 4f)
|
||||
revertToZero(DiplomaticModifiers.BetrayedPromiseToNotSettleCitiesNearUs, 1 / 8f) // That's a bastardly thing to do
|
||||
revertToZero(DiplomaticModifiers.UnacceptableDemands, 1 / 4f)
|
||||
revertToZero(DiplomaticModifiers.StealingTerritory, 1 / 4f)
|
||||
revertToZero(DiplomaticModifiers.DenouncedOurAllies, 1 / 4f)
|
||||
revertToZero(DiplomaticModifiers.DenouncedOurEnemies, 1 / 4f)
|
||||
revertToZero(DiplomaticModifiers.Denunciation, 1 / 8f) // That's personal, it'll take a long time to fade
|
||||
|
||||
// Positives
|
||||
revertToZero(DiplomaticModifiers.GaveUsUnits, 1 / 4f)
|
||||
revertToZero(DiplomaticModifiers.LiberatedCity, 1 / 8f)
|
||||
revertToZero(DiplomaticModifiers.GaveUsGifts, 1 / 4f)
|
||||
|
||||
setFriendshipBasedModifier()
|
||||
|
||||
setDefensivePactBasedModifier()
|
||||
|
||||
if (!hasFlag(DiplomacyFlags.DeclarationOfFriendship))
|
||||
revertToZero(DiplomaticModifiers.DeclarationOfFriendship, 1 / 2f) //decreases slowly and will revert to full if it is declared later
|
||||
|
||||
if (!hasFlag(DiplomacyFlags.DefensivePact))
|
||||
revertToZero(DiplomaticModifiers.DefensivePact, 1f)
|
||||
|
||||
if (!otherCiv().isCityState()) return
|
||||
|
||||
if (isRelationshipLevelLT(RelationshipLevel.Friend)) {
|
||||
if (hasFlag(DiplomacyFlags.ProvideMilitaryUnit))
|
||||
removeFlag(DiplomacyFlags.ProvideMilitaryUnit)
|
||||
return
|
||||
}
|
||||
|
||||
val variance = listOf(-1, 0, 1).random()
|
||||
|
||||
val provideMilitaryUnitUniques = civInfo.cityStateFunctions.getCityStateBonuses(otherCiv().cityStateType, relationshipIgnoreAfraid(), UniqueType.CityStateMilitaryUnits)
|
||||
.filter { it.conditionalsApply(civInfo) }.toList()
|
||||
if (provideMilitaryUnitUniques.isEmpty()) removeFlag(DiplomacyFlags.ProvideMilitaryUnit)
|
||||
|
||||
for (unique in provideMilitaryUnitUniques) {
|
||||
// Reset the countdown if it has ended, or if we have longer to go than the current maximum (can happen when going from friend to ally)
|
||||
if (!hasFlag(DiplomacyFlags.ProvideMilitaryUnit) || getFlag(DiplomacyFlags.ProvideMilitaryUnit) > unique.params[0].toInt()) {
|
||||
setFlag(DiplomacyFlags.ProvideMilitaryUnit, unique.params[0].toInt() + variance)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Should only be called from makePeace */
|
||||
private fun makePeaceOneSide() {
|
||||
diplomaticStatus = DiplomaticStatus.Peace
|
||||
@ -778,7 +486,7 @@ class DiplomacyManager() : IsPartOfGameInfoSerialization {
|
||||
diplomaticModifiers[modifier.name] = amount
|
||||
}
|
||||
|
||||
private fun getModifier(modifier: DiplomaticModifiers): Float {
|
||||
internal fun getModifier(modifier: DiplomaticModifiers): Float {
|
||||
if (!hasModifier(modifier)) return 0f
|
||||
return diplomaticModifiers[modifier.name]!!
|
||||
}
|
||||
@ -786,14 +494,6 @@ class DiplomacyManager() : IsPartOfGameInfoSerialization {
|
||||
internal fun removeModifier(modifier: DiplomaticModifiers) = diplomaticModifiers.remove(modifier.name)
|
||||
fun hasModifier(modifier: DiplomaticModifiers) = diplomaticModifiers.containsKey(modifier.name)
|
||||
|
||||
/** @param amount always positive, so you don't need to think about it */
|
||||
private fun revertToZero(modifier: DiplomaticModifiers, amount: Float) {
|
||||
if (!hasModifier(modifier)) return
|
||||
val currentAmount = getModifier(modifier)
|
||||
if (currentAmount > 0) addModifier(modifier, -amount)
|
||||
else addModifier(modifier, amount)
|
||||
}
|
||||
|
||||
fun signDeclarationOfFriendship() {
|
||||
setModifier(DiplomaticModifiers.DeclarationOfFriendship, 35f)
|
||||
otherCivDiplomacy().setModifier(DiplomaticModifiers.DeclarationOfFriendship, 35f)
|
||||
@ -816,7 +516,7 @@ class DiplomacyManager() : IsPartOfGameInfoSerialization {
|
||||
UniqueTriggerActivation.triggerCivwideUnique(unique, otherCiv())
|
||||
}
|
||||
|
||||
private fun setFriendshipBasedModifier() {
|
||||
internal fun setFriendshipBasedModifier() {
|
||||
removeModifier(DiplomaticModifiers.DeclaredFriendshipWithOurAllies)
|
||||
removeModifier(DiplomaticModifiers.DeclaredFriendshipWithOurEnemies)
|
||||
for (thirdCiv in getCommonKnownCivs()
|
||||
@ -863,7 +563,7 @@ class DiplomacyManager() : IsPartOfGameInfoSerialization {
|
||||
UniqueTriggerActivation.triggerCivwideUnique(unique, otherCiv())
|
||||
}
|
||||
|
||||
private fun setDefensivePactBasedModifier() {
|
||||
internal fun setDefensivePactBasedModifier() {
|
||||
removeModifier(DiplomaticModifiers.SignedDefensivePactWithOurAllies)
|
||||
removeModifier(DiplomaticModifiers.SignedDefensivePactWithOurEnemies)
|
||||
for (thirdCiv in getCommonKnownCivs()
|
||||
|
@ -0,0 +1,320 @@
|
||||
package com.unciv.logic.civilization.diplomacy
|
||||
|
||||
import com.unciv.Constants
|
||||
import com.unciv.logic.civilization.NotificationCategory
|
||||
import com.unciv.logic.civilization.NotificationIcon
|
||||
import com.unciv.logic.trade.Trade
|
||||
import com.unciv.logic.trade.TradeOffer
|
||||
import com.unciv.logic.trade.TradeType
|
||||
import com.unciv.models.ruleset.unique.UniqueType
|
||||
import com.unciv.ui.components.extensions.toPercent
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
object DiplomacyTurnManager {
|
||||
|
||||
fun DiplomacyManager.nextTurn() {
|
||||
nextTurnTrades()
|
||||
removeUntenableTrades()
|
||||
updateHasOpenBorders()
|
||||
nextTurnDiplomaticModifiers()
|
||||
nextTurnFlags()
|
||||
if (civInfo.isCityState() && otherCiv().isMajorCiv())
|
||||
nextTurnCityStateInfluence()
|
||||
}
|
||||
|
||||
private fun DiplomacyManager.removeUntenableTrades() {
|
||||
for (trade in trades.toList()) {
|
||||
|
||||
// Every cancelled trade can change this - if 1 resource is missing,
|
||||
// don't cancel all trades of that resource, only cancel one (the first one, as it happens, since they're added chronologically)
|
||||
val negativeCivResources = civInfo.getCivResourceSupply()
|
||||
.filter { it.amount < 0 && !it.resource.isStockpiled() }.map { it.resource.name }
|
||||
|
||||
for (offer in trade.ourOffers) {
|
||||
if (offer.type in listOf(TradeType.Luxury_Resource, TradeType.Strategic_Resource)
|
||||
&& (offer.name in negativeCivResources || !civInfo.gameInfo.ruleset.tileResources.containsKey(offer.name))
|
||||
) {
|
||||
|
||||
trades.remove(trade)
|
||||
val otherCivTrades = otherCiv().getDiplomacyManager(civInfo).trades
|
||||
otherCivTrades.removeAll { it.equalTrade(trade.reverse()) }
|
||||
|
||||
// Can't cut short peace treaties!
|
||||
if (trade.theirOffers.any { it.name == Constants.peaceTreaty }) {
|
||||
remakePeaceTreaty(trade.theirOffers.first { it.name == Constants.peaceTreaty }.duration)
|
||||
}
|
||||
|
||||
civInfo.addNotification("One of our trades with [$otherCivName] has been cut short", NotificationCategory.Trade, NotificationIcon.Trade, otherCivName)
|
||||
otherCiv().addNotification("One of our trades with [${civInfo.civName}] has been cut short", NotificationCategory.Trade, NotificationIcon.Trade, civInfo.civName)
|
||||
civInfo.cache.updateCivResources()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun DiplomacyManager.remakePeaceTreaty(durationLeft: Int) {
|
||||
val treaty = Trade()
|
||||
treaty.ourOffers.add(
|
||||
TradeOffer(Constants.peaceTreaty, TradeType.Treaty, duration = durationLeft)
|
||||
)
|
||||
treaty.theirOffers.add(
|
||||
TradeOffer(Constants.peaceTreaty, TradeType.Treaty, duration = durationLeft)
|
||||
)
|
||||
trades.add(treaty)
|
||||
otherCiv().getDiplomacyManager(civInfo).trades.add(treaty)
|
||||
}
|
||||
|
||||
|
||||
private fun DiplomacyManager.nextTurnCityStateInfluence() {
|
||||
val initialRelationshipLevel = relationshipIgnoreAfraid() // Enough since only >= Friend is notified
|
||||
|
||||
val restingPoint = getCityStateInfluenceRestingPoint()
|
||||
// We don't use `getInfluence()` here, as then during war with the ally of this CS,
|
||||
// our influence would be set to -59, overwriting the old value, which we want to keep
|
||||
// as it should be restored once the war ends (though we keep influence degradation from time during the war)
|
||||
if (influence > restingPoint) {
|
||||
val decrement = getCityStateInfluenceDegrade()
|
||||
setInfluence(max(restingPoint, influence - decrement))
|
||||
} else if (influence < restingPoint) {
|
||||
val increment = getCityStateInfluenceRecovery()
|
||||
setInfluence(min(restingPoint, influence + increment))
|
||||
}
|
||||
|
||||
if (!civInfo.isDefeated()) { // don't display city state relationship notifications when the city state is currently defeated
|
||||
val notificationActions = civInfo.cityStateFunctions.getNotificationActions()
|
||||
if (getTurnsToRelationshipChange() == 1) {
|
||||
val text = "Your relationship with [${civInfo.civName}] is about to degrade"
|
||||
otherCiv().addNotification(text, notificationActions, NotificationCategory.Diplomacy, civInfo.civName, NotificationIcon.Diplomacy)
|
||||
}
|
||||
|
||||
if (initialRelationshipLevel >= RelationshipLevel.Friend && initialRelationshipLevel != relationshipIgnoreAfraid()) {
|
||||
val text = "Your relationship with [${civInfo.civName}] degraded"
|
||||
otherCiv().addNotification(text, notificationActions, NotificationCategory.Diplomacy, civInfo.civName, NotificationIcon.Diplomacy)
|
||||
}
|
||||
|
||||
// Potentially notify about afraid status
|
||||
if (getInfluence() < 30 // We usually don't want to bully our friends
|
||||
&& !hasFlag(DiplomacyFlags.NotifiedAfraid)
|
||||
&& civInfo.cityStateFunctions.getTributeWillingness(otherCiv()) > 0
|
||||
&& otherCiv().isMajorCiv()
|
||||
) {
|
||||
setFlag(DiplomacyFlags.NotifiedAfraid, 20) // Wait 20 turns until next reminder
|
||||
val text = "[${civInfo.civName}] is afraid of your military power!"
|
||||
otherCiv().addNotification(text, notificationActions, NotificationCategory.Diplomacy, civInfo.civName, NotificationIcon.Diplomacy)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun DiplomacyManager.getCityStateInfluenceRecovery(): Float {
|
||||
if (getInfluence() >= getCityStateInfluenceRestingPoint())
|
||||
return 0f
|
||||
|
||||
val increment = 1f // sic: personality does not matter here
|
||||
|
||||
var modifierPercent = 0f
|
||||
|
||||
if (otherCiv().hasUnique(UniqueType.CityStateInfluenceRecoversTwiceNormalRate))
|
||||
modifierPercent += 100f
|
||||
|
||||
val religion = if (civInfo.cities.isEmpty() || civInfo.getCapital() == null) null
|
||||
else civInfo.getCapital()!!.religion.getMajorityReligionName()
|
||||
if (religion != null && religion == otherCiv().religionManager.religion?.name)
|
||||
modifierPercent += 50f // 50% quicker recovery when sharing a religion
|
||||
|
||||
return max(0f, increment) * max(0f, modifierPercent).toPercent()
|
||||
}
|
||||
|
||||
private fun DiplomacyManager.nextTurnFlags() {
|
||||
loop@ for (flag in flagsCountdown.keys.toList()) {
|
||||
// No need to decrement negative countdown flags: they do not expire
|
||||
if (flagsCountdown[flag]!! > 0)
|
||||
flagsCountdown[flag] = flagsCountdown[flag]!! - 1
|
||||
|
||||
// If we have uniques that make city states grant military units faster when at war with a common enemy, add higher numbers to this flag
|
||||
if (flag == DiplomacyFlags.ProvideMilitaryUnit.name && civInfo.isMajorCiv() && otherCiv().isCityState() &&
|
||||
civInfo.gameInfo.civilizations.any { civInfo.isAtWarWith(it) && otherCiv().isAtWarWith(it) }) {
|
||||
for (unique in civInfo.getMatchingUniques(UniqueType.CityStateMoreGiftedUnits)) {
|
||||
flagsCountdown[DiplomacyFlags.ProvideMilitaryUnit.name] =
|
||||
flagsCountdown[DiplomacyFlags.ProvideMilitaryUnit.name]!! - unique.params[0].toInt() + 1
|
||||
if (flagsCountdown[DiplomacyFlags.ProvideMilitaryUnit.name]!! <= 0) {
|
||||
flagsCountdown[DiplomacyFlags.ProvideMilitaryUnit.name] = 0
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// At the end of every turn
|
||||
if (flag == DiplomacyFlags.ResearchAgreement.name)
|
||||
totalOfScienceDuringRA += civInfo.stats.statsForNextTurn.science.toInt()
|
||||
|
||||
// These modifiers decrease slightly @ 50
|
||||
if (flagsCountdown[flag] == 50) {
|
||||
when (flag) {
|
||||
DiplomacyFlags.RememberAttackedProtectedMinor.name -> {
|
||||
addModifier(DiplomaticModifiers.AttackedProtectedMinor, 5f)
|
||||
}
|
||||
DiplomacyFlags.RememberBulliedProtectedMinor.name -> {
|
||||
addModifier(DiplomaticModifiers.BulliedProtectedMinor, 5f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only when flag is expired
|
||||
if (flagsCountdown[flag] == 0) {
|
||||
when (flag) {
|
||||
DiplomacyFlags.ResearchAgreement.name -> {
|
||||
if (!otherCivDiplomacy().hasFlag(DiplomacyFlags.ResearchAgreement))
|
||||
scienceFromResearchAgreement()
|
||||
}
|
||||
DiplomacyFlags.DefensivePact.name -> {
|
||||
diplomaticStatus = DiplomaticStatus.Peace
|
||||
}
|
||||
// This is confusingly named - in fact, the civ that has the flag set is the MAJOR civ
|
||||
DiplomacyFlags.ProvideMilitaryUnit.name -> {
|
||||
// Do not unset the flag - they may return soon, and we'll continue from that point on
|
||||
if (civInfo.cities.isEmpty() || otherCiv().cities.isEmpty())
|
||||
continue@loop
|
||||
else
|
||||
otherCiv().cityStateFunctions.giveMilitaryUnitToPatron(civInfo)
|
||||
}
|
||||
DiplomacyFlags.AgreedToNotSettleNearUs.name -> {
|
||||
addModifier(DiplomaticModifiers.FulfilledPromiseToNotSettleCitiesNearUs, 10f)
|
||||
}
|
||||
DiplomacyFlags.RecentlyAttacked.name -> {
|
||||
civInfo.cityStateFunctions.askForUnitGifts(otherCiv())
|
||||
}
|
||||
// These modifiers don't tick down normally, instead there is a threshold number of turns
|
||||
DiplomacyFlags.RememberDestroyedProtectedMinor.name -> { // 125
|
||||
removeModifier(DiplomaticModifiers.DestroyedProtectedMinor)
|
||||
}
|
||||
DiplomacyFlags.RememberAttackedProtectedMinor.name -> { // 75
|
||||
removeModifier(DiplomaticModifiers.AttackedProtectedMinor)
|
||||
}
|
||||
DiplomacyFlags.RememberBulliedProtectedMinor.name -> { // 75
|
||||
removeModifier(DiplomaticModifiers.BulliedProtectedMinor)
|
||||
}
|
||||
DiplomacyFlags.RememberSidedWithProtectedMinor.name -> { // 25
|
||||
removeModifier(DiplomaticModifiers.SidedWithProtectedMinor)
|
||||
}
|
||||
}
|
||||
|
||||
flagsCountdown.remove(flag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun DiplomacyManager.scienceFromResearchAgreement() {
|
||||
// https://forums.civfanatics.com/resources/research-agreements-bnw.25568/
|
||||
val scienceFromResearchAgreement = min(totalOfScienceDuringRA, otherCivDiplomacy().totalOfScienceDuringRA)
|
||||
civInfo.tech.scienceFromResearchAgreements += scienceFromResearchAgreement
|
||||
otherCiv().tech.scienceFromResearchAgreements += scienceFromResearchAgreement
|
||||
totalOfScienceDuringRA = 0
|
||||
otherCivDiplomacy().totalOfScienceDuringRA = 0
|
||||
}
|
||||
|
||||
private fun DiplomacyManager.nextTurnTrades() {
|
||||
for (trade in trades.toList()) {
|
||||
for (offer in trade.ourOffers.union(trade.theirOffers).filter { it.duration > 0 }) {
|
||||
offer.duration--
|
||||
}
|
||||
|
||||
if (trade.ourOffers.all { it.duration <= 0 } && trade.theirOffers.all { it.duration <= 0 }) {
|
||||
trades.remove(trade)
|
||||
for (offer in trade.ourOffers.union(trade.theirOffers).filter { it.duration == 0 }) { // this was a timed trade
|
||||
if (offer in trade.theirOffers)
|
||||
civInfo.addNotification("[${offer.name}] from [$otherCivName] has ended", NotificationCategory.Trade, otherCivName, NotificationIcon.Trade)
|
||||
else civInfo.addNotification("[${offer.name}] to [$otherCivName] has ended", NotificationCategory.Trade, otherCivName, NotificationIcon.Trade)
|
||||
|
||||
civInfo.updateStatsForNextTurn() // if they were bringing us gold per turn
|
||||
if (trade.theirOffers.union(trade.ourOffers) // if resources were involved
|
||||
.any { it.type == TradeType.Luxury_Resource || it.type == TradeType.Strategic_Resource })
|
||||
civInfo.cache.updateCivResources()
|
||||
}
|
||||
}
|
||||
|
||||
for (offer in trade.theirOffers.filter { it.duration <= 3 })
|
||||
{
|
||||
if (offer.duration == 3)
|
||||
civInfo.addNotification("[${offer.name}] from [$otherCivName] will end in [3] turns", NotificationCategory.Trade, otherCivName, NotificationIcon.Trade)
|
||||
else if (offer.duration == 1)
|
||||
civInfo.addNotification("[${offer.name}] from [$otherCivName] will end next turn", NotificationCategory.Trade, otherCivName, NotificationIcon.Trade)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun DiplomacyManager.nextTurnDiplomaticModifiers() {
|
||||
if (diplomaticStatus == DiplomaticStatus.Peace) {
|
||||
if (getModifier(DiplomaticModifiers.YearsOfPeace) < 30)
|
||||
addModifier(DiplomaticModifiers.YearsOfPeace, 0.5f)
|
||||
} else revertToZero(DiplomaticModifiers.YearsOfPeace, 0.5f) // war makes you forget the good ol' days
|
||||
|
||||
var openBorders = 0
|
||||
if (hasOpenBorders) openBorders += 1
|
||||
|
||||
if (otherCivDiplomacy().hasOpenBorders) openBorders += 1
|
||||
if (openBorders > 0) addModifier(DiplomaticModifiers.OpenBorders, openBorders / 8f) // so if we both have open borders it'll grow by 0.25 per turn
|
||||
else revertToZero(DiplomaticModifiers.OpenBorders, 1 / 8f)
|
||||
|
||||
// Negatives
|
||||
revertToZero(DiplomaticModifiers.DeclaredWarOnUs, 1 / 8f) // this disappears real slow - it'll take 160 turns to really forget, this is war declaration we're talking about
|
||||
revertToZero(DiplomaticModifiers.WarMongerer, 1 / 2f) // warmongering gives a big negative boost when it happens but they're forgotten relatively quickly, like WWII amirite
|
||||
revertToZero(DiplomaticModifiers.CapturedOurCities, 1 / 4f) // if you captured our cities, though, that's harder to forget
|
||||
revertToZero(DiplomaticModifiers.BetrayedDeclarationOfFriendship, 1 / 8f) // That's a bastardly thing to do
|
||||
revertToZero(DiplomaticModifiers.BetrayedDefensivePact, 1 / 16f) // That's an outrageous thing to do
|
||||
revertToZero(DiplomaticModifiers.RefusedToNotSettleCitiesNearUs, 1 / 4f)
|
||||
revertToZero(DiplomaticModifiers.BetrayedPromiseToNotSettleCitiesNearUs, 1 / 8f) // That's a bastardly thing to do
|
||||
revertToZero(DiplomaticModifiers.UnacceptableDemands, 1 / 4f)
|
||||
revertToZero(DiplomaticModifiers.StealingTerritory, 1 / 4f)
|
||||
revertToZero(DiplomaticModifiers.DenouncedOurAllies, 1 / 4f)
|
||||
revertToZero(DiplomaticModifiers.DenouncedOurEnemies, 1 / 4f)
|
||||
revertToZero(DiplomaticModifiers.Denunciation, 1 / 8f) // That's personal, it'll take a long time to fade
|
||||
|
||||
// Positives
|
||||
revertToZero(DiplomaticModifiers.GaveUsUnits, 1 / 4f)
|
||||
revertToZero(DiplomaticModifiers.LiberatedCity, 1 / 8f)
|
||||
revertToZero(DiplomaticModifiers.GaveUsGifts, 1 / 4f)
|
||||
|
||||
setFriendshipBasedModifier()
|
||||
|
||||
setDefensivePactBasedModifier()
|
||||
|
||||
if (!hasFlag(DiplomacyFlags.DeclarationOfFriendship))
|
||||
revertToZero(DiplomaticModifiers.DeclarationOfFriendship, 1 / 2f) //decreases slowly and will revert to full if it is declared later
|
||||
|
||||
if (!hasFlag(DiplomacyFlags.DefensivePact))
|
||||
revertToZero(DiplomaticModifiers.DefensivePact, 1f)
|
||||
|
||||
if (!otherCiv().isCityState()) return
|
||||
|
||||
if (isRelationshipLevelLT(RelationshipLevel.Friend)) {
|
||||
if (hasFlag(DiplomacyFlags.ProvideMilitaryUnit))
|
||||
removeFlag(DiplomacyFlags.ProvideMilitaryUnit)
|
||||
return
|
||||
}
|
||||
|
||||
val variance = listOf(-1, 0, 1).random()
|
||||
|
||||
val provideMilitaryUnitUniques = civInfo.cityStateFunctions.getCityStateBonuses(otherCiv().cityStateType, relationshipIgnoreAfraid(), UniqueType.CityStateMilitaryUnits)
|
||||
.filter { it.conditionalsApply(civInfo) }.toList()
|
||||
if (provideMilitaryUnitUniques.isEmpty()) removeFlag(DiplomacyFlags.ProvideMilitaryUnit)
|
||||
|
||||
for (unique in provideMilitaryUnitUniques) {
|
||||
// Reset the countdown if it has ended, or if we have longer to go than the current maximum (can happen when going from friend to ally)
|
||||
if (!hasFlag(DiplomacyFlags.ProvideMilitaryUnit) || getFlag(DiplomacyFlags.ProvideMilitaryUnit) > unique.params[0].toInt()) {
|
||||
setFlag(DiplomacyFlags.ProvideMilitaryUnit, unique.params[0].toInt() + variance)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @param amount always positive, so you don't need to think about it */
|
||||
private fun DiplomacyManager.revertToZero(modifier: DiplomaticModifiers, amount: Float) {
|
||||
if (!hasModifier(modifier)) return
|
||||
val currentAmount = getModifier(modifier)
|
||||
if (currentAmount > 0) addModifier(modifier, -amount)
|
||||
else addModifier(modifier, amount)
|
||||
}
|
||||
|
||||
}
|
@ -11,6 +11,7 @@ import com.unciv.logic.civilization.NotificationCategory
|
||||
import com.unciv.logic.civilization.NotificationIcon
|
||||
import com.unciv.logic.civilization.PlayerType
|
||||
import com.unciv.logic.civilization.PopupAlert
|
||||
import com.unciv.logic.civilization.diplomacy.DiplomacyTurnManager.nextTurn
|
||||
import com.unciv.logic.map.mapunit.UnitTurnManager
|
||||
import com.unciv.logic.map.tile.Tile
|
||||
import com.unciv.logic.trade.TradeEvaluation
|
||||
|
@ -1,4 +1,5 @@
|
||||
package com.unciv.logic.civilization
|
||||
import com.unciv.logic.civilization.diplomacy.DiplomacyTurnManager.nextTurn
|
||||
import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers
|
||||
import com.unciv.logic.civilization.diplomacy.DiplomaticStatus
|
||||
import com.unciv.testing.GdxTestRunner
|
||||
@ -24,8 +25,8 @@ class DefensivePactTests {
|
||||
a.diplomacyFunctions.makeCivilizationsMeet(c)
|
||||
b.diplomacyFunctions.makeCivilizationsMeet(c)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@Test
|
||||
fun `Civs with Defensive Pacts are called in`() {
|
||||
meetAll()
|
||||
a.getDiplomacyManager(b).signDefensivePact(100)
|
||||
@ -59,7 +60,7 @@ class DefensivePactTests {
|
||||
Assert.assertFalse(a.getDiplomacyManager(b).diplomaticStatus == DiplomaticStatus.DefensivePact
|
||||
|| b.getDiplomacyManager(a).diplomaticStatus == DiplomaticStatus.DefensivePact)
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun `Breaking Defensive Pact`() {
|
||||
meetAll()
|
||||
@ -70,7 +71,7 @@ class DefensivePactTests {
|
||||
Assert.assertTrue(abDiploManager.hasModifier(DiplomaticModifiers.DefensivePact)) // Aggressor should still be friendly to the defender, they did nothing wrong
|
||||
Assert.assertFalse(abDiploManager.otherCivDiplomacy().hasModifier(DiplomaticModifiers.DefensivePact)) // Defender should not be friendly to the aggressor.
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun `Breaking Defensive Pact with friends`() {
|
||||
meetAll()
|
||||
|
@ -2,6 +2,7 @@ package com.unciv.logic.civilization.diplomacy
|
||||
|
||||
import com.badlogic.gdx.math.Vector2
|
||||
import com.unciv.logic.civilization.Civilization
|
||||
import com.unciv.logic.civilization.diplomacy.DiplomacyTurnManager.nextTurn
|
||||
import com.unciv.logic.map.tile.Tile
|
||||
import com.unciv.models.ruleset.BeliefType
|
||||
import com.unciv.testing.GdxTestRunner
|
||||
|
Reference in New Issue
Block a user