Implementation for establishing embassies in diplomacy (#13625)

* G&K feature - Embassy

* fix spotted errors

* add icon and gameplay test

* fix invalid trade logic

* make embassies work for G&K only

* remove introduced bug

* trade evaluation and decline embassy

* update civilopedia for embassies

* cleanup

* diplomatic modifiers

* fix embassies unique

* make embassies unique global and bugfix

* fix AI can not trade with cs

* add embassies unique for mods

* mods require uniques to enable embassies

* fix mods require uniques to enable embassies

* update uniques.md

* apply reviewed changes

* apply reviewed change 2

* bugfix and cleanup

* remove obsolete import

* null pointer bugfix

* remove toList() and function rename
This commit is contained in:
metablaster 2025-07-21 22:30:17 +02:00 committed by GitHub
parent 368bf8c84d
commit 0ddbb46c79
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 385 additions and 115 deletions

View File

@ -1,6 +1,7 @@
{ {
"name": "Global uniques", "name": "Global uniques",
"uniques": [ "uniques": [
"Requires establishing embassies to conduct advanced diplomacy",
"[-75]% growth [in all cities] <when between [-10] and [-1] [Happiness]>", "[-75]% growth [in all cities] <when between [-10] and [-1] [Happiness]>",
"Nullifies Growth [in all cities] <when below [-10] [Happiness]>", "Nullifies Growth [in all cities] <when below [-10] [Happiness]>",
"[-50]% [Production] [in all cities] <when below [-10] [Happiness]>", "[-50]% [Production] [in all cities] <when below [-10] [Happiness]>",

View File

@ -76,6 +76,7 @@
"name": "Writing", "name": "Writing",
"row": 3, "row": 3,
"prerequisites": ["Pottery"], "prerequisites": ["Pottery"],
"uniques": ["Enables establishment of embassies"],
"quote": "'He who destroys a good book kills reason itself.' - John Milton" "quote": "'He who destroys a good book kills reason itself.' - John Milton"
}, },
{ {

View File

@ -269,21 +269,49 @@
"Ranged attacks can be done from a distance, dependent on the 'Range' value of the unit.\nWhile melee attacks allow the defender to damage the attacker in retaliation, ranged attacks do not." "Ranged attacks can be done from a distance, dependent on the 'Range' value of the unit.\nWhile melee attacks allow the defender to damage the attacker in retaliation, ranged attacks do not."
] ]
}, },
{
"name": "Embassies",
"steps": [
"Establishing an embassy in other players' Capital city shows the location of their Capital, likewise other players who establish an embassy in your Capital can see location of your Capital.",
"Embassies thus allow Spies to be placed in another players' Capital City if Espionage is enabled.",
"Mutual embassies are required to allow trading open borders and to sign Research Agreement and Defensive Pact.",
"Declaring war on another player or denouncing them will remove embassies from both players' Capitals. You will have to re-establish them to use their effect.",
"Re-establishing an embasy after denounciation from either side becomes available next turn, while re-establishing them when at war requires peacy treaty.",
"Be cautious when accepting embassy from other players because they can spy on your capital, or worse may start planning to attack you.",
"Embassies are available using the Gods and Kings ruleset."
]
},
{
"name": "Open Borders",
"civilopediaText": [
{ text: "Open Borders from other player and vice versa allow units to freely pass trough their territoy." },
{ text: "They also let Missionaries to enter other players' territory without suffering attrition." },
{ text: "Open Borders expire depending on game speed and are canceled upon Declaration of War, the closure however takes effect only after war ends and need to be traded again." },
{ text: "You gain diplomatic boost with other AI player if you both have Open Borders to each other" },
{ text: "Be cautious when giving Open Borders because other player may settle a City near your lands in an area you are planning to use, they may also use your territory to attack your friends or City State allies." },
{ text: "When playing with the Gods and Kings ruleset mutual embassies need to be established before Open Borders can be traded." },
{ text: "See also: Embassies", "link": "Tutorials/Embassies" }
]
},
{ {
"name": "Research Agreements", "name": "Research Agreements",
"steps": [ "civilopediaText": [
"In Research Agreements, you and another civilization decide to jointly research technology.\nAt the end of the agreement, you will both receive a 'lump sum' of ⍾Science, which will go towards one of your unresearched technologies.", { text: "In Research Agreements, you and another civilization decide to jointly research technology.\nAt the end of the agreement, you will both receive a 'lump sum' of ⍾Science, which will go towards one of your unresearched technologies." },
"The amount of ⍾Science you receive at the end is dependent on the ⍾Science generated by your cities and the other civilization's cities during the agreement - the more, the better!", { text: "The amount of ⍾Science you receive at the end is dependent on the ⍾Science generated by your cities and the other civilization's cities during the agreement - the more, the better!" },
"Note that before you can invest in a research agreement, you must have a Declaraction of Friendship, both Nations need the required Technology, and both Nations need enough Gold on hand for the agreement." { text: "Note that before you can invest in a research agreement, you must have a Declaraction of Friendship, both Nations need the required Technology, and both Nations need enough Gold on hand for the agreement." },
{ text: "When playing with the Gods and Kings ruleset mutual embassies need to be established before Research Agreement can be signed." },
{ text: "See also: Embassies", "link": "Tutorials/Embassies" }
] ]
}, },
{ {
"name": "Defensive Pacts", "name": "Defensive Pacts",
"steps": [ "civilopediaText": [
"Defensive pacts allow you and another civ to protect one another from aggressors.\nOnce the defensive pact is signed, you will be drawn into their future defensive wars, just as they will be drawn into your future defensive wars. Declaring war on any Civ will remove all of your defensive pacts. You will have to re-sign them to use their effect.", { text: "Defensive pacts allow you and another civ to protect one another from aggressors.\nOnce the defensive pact is signed, you will be drawn into their future defensive wars, just as they will be drawn into your future defensive wars. Declaring war on any Civ will remove all of your defensive pacts. You will have to re-sign them to use their effect." },
"Be cautious when signing defensive pacts because they can bring you into wars that you might not want to be in.", { text: "Be cautious when signing defensive pacts because they can bring you into wars that you might not want to be in." },
"The AI is very careful and will not accept defensive pacts with less than 80 influence." { text: "The AI is very careful and will not accept defensive pacts with less than 80 influence." },
] { text: "When playing with the Gods and Kings ruleset mutual embassies need to be established before Defensive Pact can be signed." },
{ text: "See also: Embassies", "link": "Tutorials/Embassies" }
]
}, },
{ {
"name": "City-States", "name": "City-States",

View File

@ -329,6 +329,7 @@ Cities =
Technologies = Technologies =
Declarations of war = Declarations of war =
Peace Proposals = Peace Proposals =
Accept Embassy =
Introduction to [nation] = Introduction to [nation] =
Declare war on [nation] = Declare war on [nation] =
Make peace with [nation] = Make peace with [nation] =

View File

@ -51,6 +51,11 @@ object Constants {
// Agreements // Agreements
const val openBorders = "Open Borders" const val openBorders = "Open Borders"
// Other trade items
const val acceptEmbassy = "Accept Embassy"
const val goldPerTurn = "Gold per turn"
const val flatGold = "Gold"
/** Used as origin in StatMap or ResourceSupplyList, or the toggle button in DiplomacyOverviewTab */ /** Used as origin in StatMap or ResourceSupplyList, or the toggle button in DiplomacyOverviewTab */
const val cityStates = "City-States" const val cityStates = "City-States"
/** Used as origin in ResourceSupplyList */ /** Used as origin in ResourceSupplyList */

View File

@ -108,20 +108,67 @@ object DiplomacyAutomation {
return motivation > 0 return motivation > 0
} }
/**
* Try establishing embassy in other civs' capitals
*
* @param civInfo Civilization which initiates trade
*/
internal fun offerToEstablishEmbassy(civInfo: Civilization) {
val civsThatWeCanEstablishEmbassyWith = civInfo.getKnownCivs().filter {
civInfo.diplomacyFunctions.canEstablishEmbassyWith(it)
&& !civInfo.getDiplomacyManager(it)!!.hasFlag(DiplomacyFlags.DeclinedEmbassy)
&& !areWeOfferingTrade(civInfo, it, Constants.acceptEmbassy)
}.sortedByDescending { it.getDiplomacyManager(civInfo)!!.relationshipLevel() }
for (otherCiv in civsThatWeCanEstablishEmbassyWith) {
// Default setting is 3
if ((1..10).random() < 7) continue
if (wantsToAcceptEmbassy(civInfo, otherCiv)) {
val tradeLogic = TradeLogic(civInfo, otherCiv)
val embassyOffer = TradeOffer(Constants.acceptEmbassy, TradeOfferType.Embassy, speed = civInfo.gameInfo.speed)
tradeLogic.currentTrade.theirOffers.add(embassyOffer)
// If possible offer mutual embassies (Civ V behavior) so we don't waste gold
if (otherCiv.diplomacyFunctions.canEstablishEmbassyWith(civInfo)) {
tradeLogic.currentTrade.ourOffers.add(embassyOffer)
}
else { // Otherwise offer GPT (prefered) or flat gold for embassy in their capital
val embassyValue = TradeEvaluation().evaluateBuyCostWithInflation(embassyOffer, civInfo, otherCiv, tradeLogic.currentTrade)
val embassyGptValue = embassyValue / civInfo.gameInfo.speed.dealDuration
val ourGpt = civInfo.stats.statsForNextTurn.gold.toInt()
if (embassyGptValue in 1..ourGpt)
tradeLogic.currentTrade.ourOffers.add(TradeOffer(Constants.goldPerTurn, TradeOfferType.Gold_Per_Turn, embassyGptValue, civInfo.gameInfo.speed))
else if (civInfo.gold >= embassyValue && ourGpt >= 0)
tradeLogic.currentTrade.ourOffers.add(TradeOffer(Constants.flatGold, TradeOfferType.Gold, embassyValue, civInfo.gameInfo.speed))
// else let them make counter offer
}
otherCiv.tradeRequests.add(TradeRequest(civInfo.civName, tradeLogic.currentTrade.reverse()))
}
else {
// Remember this for a few turns to save computation power
civInfo.getDiplomacyManager(otherCiv)!!.setFlag(DiplomacyFlags.DeclinedEmbassy, 5)
}
}
}
internal fun offerOpenBorders(civInfo: Civilization) { internal fun offerOpenBorders(civInfo: Civilization) {
if (!civInfo.hasUnique(UniqueType.EnablesOpenBorders)) return if (!civInfo.hasUnique(UniqueType.EnablesOpenBorders)) return
val civsThatWeCanOpenBordersWith = civInfo.getKnownCivs()
.filter { val civsThatWeCanOpenBordersWith = civInfo.getKnownCivs().filter {
it.isMajorCiv() && !civInfo.isAtWarWith(it) val ourDiploManager = civInfo.getDiplomacyManager(it)!!
&& it.hasUnique(UniqueType.EnablesOpenBorders) it.isMajorCiv()
&& !civInfo.getDiplomacyManager(it)!!.hasOpenBorders && !civInfo.isAtWarWith(it)
&& !it.getDiplomacyManager(civInfo)!!.hasOpenBorders && it.hasUnique(UniqueType.EnablesOpenBorders)
&& !civInfo.getDiplomacyManager(it)!!.hasFlag(DiplomacyFlags.DeclinedOpenBorders) && !ourDiploManager.hasOpenBorders
&& !areWeOfferingTrade(civInfo, it, Constants.openBorders) && !ourDiploManager.otherCivDiplomacy().hasOpenBorders
}.sortedByDescending { it.getDiplomacyManager(civInfo)!!.relationshipLevel() }.toList() && civInfo.diplomacyFunctions.hasMutualEmbassyWith(it)
&& !ourDiploManager.hasFlag(DiplomacyFlags.DeclinedOpenBorders)
&& !areWeOfferingTrade(civInfo, it, Constants.openBorders)
}.sortedByDescending { it.getDiplomacyManager(civInfo)!!.relationshipLevel() }
for (otherCiv in civsThatWeCanOpenBordersWith) { for (otherCiv in civsThatWeCanOpenBordersWith) {
// Default setting is 3, this will be changed according to different civ. // Default setting is 3
if ((1..10).random() < 7) continue if ((1..10).random() < 7) continue
if (wantsToOpenBorders(civInfo, otherCiv)) { if (wantsToOpenBorders(civInfo, otherCiv)) {
val tradeLogic = TradeLogic(civInfo, otherCiv) val tradeLogic = TradeLogic(civInfo, otherCiv)
@ -136,10 +183,37 @@ object DiplomacyAutomation {
} }
} }
/**
* Test if [otherCiv] wants to accept our embassy in their capital
*/
fun wantsToAcceptEmbassy(civInfo: Civilization, otherCiv: Civilization): Boolean {
val theirDiploManager = otherCiv.getDiplomacyManager(civInfo)!!
if (civInfo.getDiplomacyManager(otherCiv)!!.hasFlag(DiplomacyFlags.DeclinedEmbassy)) return false
if (theirDiploManager.isRelationshipLevelLT(RelationshipLevel.Afraid)) return false
// Being able to see their capital can give us an advantage later on, especially with espionage enabled
if (!civInfo.getCapital()!!.getCenterTile().isExplored(otherCiv)) return true
// Did they not discovered our capital yet?
if (!otherCiv.getCapital()!!.getCenterTile().isExplored(civInfo)) {
// If we're afraid of them deny embassy
if (theirDiploManager.relationshipLevel() == RelationshipLevel.Afraid) return false
// If they're much stronger than us deny embassy
val ourCombatStrength = civInfo.getStatForRanking(RankingType.Force)
val theirCombatStrength = otherCiv.getStatForRanking(RankingType.Force)
val ourAbsoluteAdvantage = ourCombatStrength - theirCombatStrength
val percentageAdvantage = ourAbsoluteAdvantage / theirCombatStrength.toFloat()
if (percentageAdvantage > 0.5) return false
}
return true // Relationship is Afraid or greater
}
fun wantsToOpenBorders(civInfo: Civilization, otherCiv: Civilization): Boolean { fun wantsToOpenBorders(civInfo: Civilization, otherCiv: Civilization): Boolean {
val diploManager = civInfo.getDiplomacyManager(otherCiv)!! val ourDiploManager = civInfo.getDiplomacyManager(otherCiv)!!
if (diploManager.hasFlag(DiplomacyFlags.DeclinedOpenBorders)) return false if (ourDiploManager.hasFlag(DiplomacyFlags.DeclinedOpenBorders)) return false
if (diploManager.isRelationshipLevelLT(RelationshipLevel.Favorable)) return false if (ourDiploManager.isRelationshipLevelLT(RelationshipLevel.Favorable)) return false
// Don't accept if they are at war with our friends, they might use our land to attack them // Don't accept if they are at war with our friends, they might use our land to attack them
if (civInfo.diplomacy.values.any { it.isRelationshipLevelGE(RelationshipLevel.Friend) && it.otherCiv().isAtWarWith(otherCiv) }) if (civInfo.diplomacy.values.any { it.isRelationshipLevelGE(RelationshipLevel.Friend) && it.otherCiv().isAtWarWith(otherCiv) })
return false return false
@ -147,7 +221,7 @@ object DiplomacyAutomation {
if (otherCiv.cities.count { !it.getCenterTile().isVisible(civInfo) } < otherCiv.cities.count() * .8f) if (otherCiv.cities.count { !it.getCenterTile().isVisible(civInfo) } < otherCiv.cities.count() * .8f)
return true return true
if (hasAtLeastMotivationToAttack(civInfo, otherCiv, if (hasAtLeastMotivationToAttack(civInfo, otherCiv,
diploManager.opinionOfOtherCiv() * civInfo.getPersonality().modifierFocus(PersonalityValue.Commerce, .3f) / 2) > 0) ourDiploManager.opinionOfOtherCiv() * civInfo.getPersonality().modifierFocus(PersonalityValue.Commerce, .3f) / 2) > 0)
return false return false
return true return true
} }
@ -155,22 +229,21 @@ object DiplomacyAutomation {
internal fun offerResearchAgreement(civInfo: Civilization) { internal fun offerResearchAgreement(civInfo: Civilization) {
if (!civInfo.diplomacyFunctions.canSignResearchAgreement()) return // don't waste your time if (!civInfo.diplomacyFunctions.canSignResearchAgreement()) return // don't waste your time
val canSignResearchAgreementCiv = civInfo.getKnownCivs() val civsThatWeCanSignResearchAgreementWith = civInfo.getKnownCivs().filter {
.filter { civInfo.diplomacyFunctions.canSignResearchAgreementWith(it)
civInfo.diplomacyFunctions.canSignResearchAgreementsWith(it) && !civInfo.getDiplomacyManager(it)!!.hasFlag(DiplomacyFlags.DeclinedResearchAgreement)
&& !civInfo.getDiplomacyManager(it)!!.hasFlag(DiplomacyFlags.DeclinedResearchAgreement) && !areWeOfferingTrade(civInfo, it, Constants.researchAgreement)
&& !areWeOfferingTrade(civInfo, it, Constants.researchAgreement) }.sortedByDescending { it.stats.statsForNextTurn.science }
}
.sortedByDescending { it.stats.statsForNextTurn.science }
for (otherCiv in canSignResearchAgreementCiv) { for (otherCiv in civsThatWeCanSignResearchAgreementWith) {
// Default setting is 5, this will be changed according to different civ. // Default setting is 5, this will be changed according to different civ.
if ((1..10).random() <= 5 * civInfo.getPersonality().modifierFocus(PersonalityValue.Science, .3f)) continue if ((1..10).random() <= 5 * civInfo.getPersonality().modifierFocus(PersonalityValue.Science, .3f)) continue
val tradeLogic = TradeLogic(civInfo, otherCiv) val tradeLogic = TradeLogic(civInfo, otherCiv)
val cost = civInfo.diplomacyFunctions.getResearchAgreementCost(otherCiv) val cost = civInfo.diplomacyFunctions.getResearchAgreementCost(otherCiv)
tradeLogic.currentTrade.ourOffers.add(TradeOffer(Constants.researchAgreement, TradeOfferType.Treaty, cost, civInfo.gameInfo.speed)) val tradeOffer = TradeOffer(Constants.researchAgreement, TradeOfferType.Treaty, cost, civInfo.gameInfo.speed)
tradeLogic.currentTrade.theirOffers.add(TradeOffer(Constants.researchAgreement, TradeOfferType.Treaty, cost, civInfo.gameInfo.speed))
tradeLogic.currentTrade.ourOffers.add(tradeOffer)
tradeLogic.currentTrade.theirOffers.add(tradeOffer)
otherCiv.tradeRequests.add(TradeRequest(civInfo.civName, tradeLogic.currentTrade.reverse())) otherCiv.tradeRequests.add(TradeRequest(civInfo.civName, tradeLogic.currentTrade.reverse()))
} }
} }
@ -178,23 +251,24 @@ object DiplomacyAutomation {
internal fun offerDefensivePact(civInfo: Civilization) { internal fun offerDefensivePact(civInfo: Civilization) {
if (!civInfo.diplomacyFunctions.canSignDefensivePact()) return // don't waste your time if (!civInfo.diplomacyFunctions.canSignDefensivePact()) return // don't waste your time
val canSignDefensivePactCiv = civInfo.getKnownCivs() val civsThatWeCanSignDefensivePactWith = civInfo.getKnownCivs().filter {
.filter { val ourDiploManager = civInfo.getDiplomacyManager(it)!!
civInfo.diplomacyFunctions.canSignDefensivePactWith(it) civInfo.diplomacyFunctions.canSignDefensivePactWith(it)
&& !civInfo.getDiplomacyManager(it)!!.hasFlag(DiplomacyFlags.DeclinedDefensivePact) && !ourDiploManager.hasFlag(DiplomacyFlags.DeclinedDefensivePact)
&& civInfo.getDiplomacyManager(it)!!.opinionOfOtherCiv() < 70f * civInfo.getPersonality().inverseModifierFocus(PersonalityValue.Aggressive, .2f) && ourDiploManager.opinionOfOtherCiv() < 70f * civInfo.getPersonality().inverseModifierFocus(PersonalityValue.Aggressive, .2f)
&& !areWeOfferingTrade(civInfo, it, Constants.defensivePact) && !areWeOfferingTrade(civInfo, it, Constants.defensivePact)
} }
for (otherCiv in canSignDefensivePactCiv) { for (otherCiv in civsThatWeCanSignDefensivePactWith) {
// Default setting is 3, this will be changed according to different civ. // Default setting is 3, this will be changed according to different civ.
if ((1..10).random() <= 7 * civInfo.getPersonality().inverseModifierFocus(PersonalityValue.Loyal, .3f)) continue if ((1..10).random() <= 7 * civInfo.getPersonality().inverseModifierFocus(PersonalityValue.Loyal, .3f)) continue
if (wantsToSignDefensivePact(civInfo, otherCiv)) { if (wantsToSignDefensivePact(civInfo, otherCiv)) {
//todo: Add more in depth evaluation here //todo: Add more in depth evaluation here
val tradeLogic = TradeLogic(civInfo, otherCiv) val tradeLogic = TradeLogic(civInfo, otherCiv)
tradeLogic.currentTrade.ourOffers.add(TradeOffer(Constants.defensivePact, TradeOfferType.Treaty, speed = civInfo.gameInfo.speed)) val tradeOffer = TradeOffer(Constants.defensivePact, TradeOfferType.Treaty, speed = civInfo.gameInfo.speed)
tradeLogic.currentTrade.theirOffers.add(TradeOffer(Constants.defensivePact, TradeOfferType.Treaty, speed = civInfo.gameInfo.speed))
tradeLogic.currentTrade.ourOffers.add(tradeOffer)
tradeLogic.currentTrade.theirOffers.add(tradeOffer)
otherCiv.tradeRequests.add(TradeRequest(civInfo.civName, tradeLogic.currentTrade.reverse())) otherCiv.tradeRequests.add(TradeRequest(civInfo.civName, tradeLogic.currentTrade.reverse()))
} else { } else {
// Remember this for a few turns to save computation power // Remember this for a few turns to save computation power
@ -204,10 +278,11 @@ object DiplomacyAutomation {
} }
fun wantsToSignDefensivePact(civInfo: Civilization, otherCiv: Civilization): Boolean { fun wantsToSignDefensivePact(civInfo: Civilization, otherCiv: Civilization): Boolean {
val diploManager = civInfo.getDiplomacyManager(otherCiv)!! val ourDiploManager = civInfo.getDiplomacyManager(otherCiv)!!
if (diploManager.hasFlag(DiplomacyFlags.DeclinedDefensivePact)) return false if (ourDiploManager.hasFlag(DiplomacyFlags.DeclinedDefensivePact)) return false
if (diploManager.opinionOfOtherCiv() < 65f * civInfo.getPersonality().inverseModifierFocus(PersonalityValue.Aggressive, .3f)) return false if (ourDiploManager.opinionOfOtherCiv() < 65f * civInfo.getPersonality().inverseModifierFocus(PersonalityValue.Aggressive, .3f)) return false
val commonknownCivs = diploManager.getCommonKnownCivs() val commonknownCivs = ourDiploManager.getCommonKnownCivs()
for (thirdCiv in commonknownCivs) { for (thirdCiv in commonknownCivs) {
// If they have bad relations with any of our friends, don't consider it // If they have bad relations with any of our friends, don't consider it
if (civInfo.getDiplomacyManager(thirdCiv)!!.hasFlag(DiplomacyFlags.DeclarationOfFriendship) if (civInfo.getDiplomacyManager(thirdCiv)!!.hasFlag(DiplomacyFlags.DeclarationOfFriendship)
@ -230,11 +305,11 @@ object DiplomacyAutomation {
val allAliveCivs = allCivs - deadCivs val allAliveCivs = allCivs - deadCivs
// We have to already be at RelationshipLevel.Ally, so we must have 80 oppinion of them // We have to already be at RelationshipLevel.Ally, so we must have 80 oppinion of them
var motivation = diploManager.opinionOfOtherCiv() - 80 var motivation = ourDiploManager.opinionOfOtherCiv() - 80
// Warmongerers don't make good allies // Warmongerers don't make good allies
if (diploManager.hasModifier(DiplomaticModifiers.WarMongerer)) { if (ourDiploManager.hasModifier(DiplomaticModifiers.WarMongerer)) {
motivation -= diploManager.getModifier(DiplomaticModifiers.WarMongerer) * civInfo.getPersonality().modifierFocus(PersonalityValue.Diplomacy, .5f) motivation -= ourDiploManager.getModifier(DiplomaticModifiers.WarMongerer) * civInfo.getPersonality().modifierFocus(PersonalityValue.Diplomacy, .5f)
} }
// If they are stronger than us, then we value it a lot more // If they are stronger than us, then we value it a lot more
@ -323,9 +398,10 @@ object DiplomacyAutomation {
// pay for peace // pay for peace
val tradeLogic = TradeLogic(civInfo, enemy) val tradeLogic = TradeLogic(civInfo, enemy)
val tradeOffer = TradeOffer(Constants.peaceTreaty, TradeOfferType.Treaty, speed = civInfo.gameInfo.speed)
tradeLogic.currentTrade.ourOffers.add(TradeOffer(Constants.peaceTreaty, TradeOfferType.Treaty, speed = civInfo.gameInfo.speed)) tradeLogic.currentTrade.ourOffers.add(tradeOffer)
tradeLogic.currentTrade.theirOffers.add(TradeOffer(Constants.peaceTreaty, TradeOfferType.Treaty, speed = civInfo.gameInfo.speed)) tradeLogic.currentTrade.theirOffers.add(tradeOffer)
if (enemy.isMajorCiv()) { if (enemy.isMajorCiv()) {
var moneyWeNeedToPay = -TradeEvaluation().evaluatePeaceCostForThem(civInfo, enemy) var moneyWeNeedToPay = -TradeEvaluation().evaluatePeaceCostForThem(civInfo, enemy)

View File

@ -51,6 +51,7 @@ object NextTurnAutomation {
ReligionAutomation.spendFaithOnReligion(civInfo) ReligionAutomation.spendFaithOnReligion(civInfo)
} }
DiplomacyAutomation.offerToEstablishEmbassy(civInfo)
DiplomacyAutomation.offerOpenBorders(civInfo) DiplomacyAutomation.offerOpenBorders(civInfo)
DiplomacyAutomation.offerResearchAgreement(civInfo) DiplomacyAutomation.offerResearchAgreement(civInfo)
DiplomacyAutomation.offerDefensivePact(civInfo) DiplomacyAutomation.offerDefensivePact(civInfo)

View File

@ -164,6 +164,9 @@ object DeclareWar {
diplomacyManager.setFlag(DiplomacyFlags.DeclinedPeace, diplomacyManager.civInfo.gameInfo.ruleset.modOptions.constants.minimumWarDuration) // AI won't propose peace for 10 turns diplomacyManager.setFlag(DiplomacyFlags.DeclinedPeace, diplomacyManager.civInfo.gameInfo.ruleset.modOptions.constants.minimumWarDuration) // AI won't propose peace for 10 turns
diplomacyManager.setFlag(DiplomacyFlags.DeclaredWar, diplomacyManager.civInfo.gameInfo.ruleset.modOptions.constants.minimumWarDuration) // AI won't agree to trade for 10 turns diplomacyManager.setFlag(DiplomacyFlags.DeclaredWar, diplomacyManager.civInfo.gameInfo.ruleset.modOptions.constants.minimumWarDuration) // AI won't agree to trade for 10 turns
diplomacyManager.removeFlag(DiplomacyFlags.BorderConflict) diplomacyManager.removeFlag(DiplomacyFlags.BorderConflict)
// War results in removal of embassies for both sides
diplomacyManager.civInfo.diplomacyFunctions.removeEmbassies(civAtWarWith)
} }
private fun changeOpinions(diplomacyManager: DiplomacyManager, declareWarReason: DeclareWarReason) { private fun changeOpinions(diplomacyManager: DiplomacyManager, declareWarReason: DeclareWarReason) {

View File

@ -88,7 +88,77 @@ class DiplomacyFunctions(val civInfo: Civilization) {
} }
} }
/**
* If denounciation happened this turn from either side, establishing embassy again is possible only from next turn.
*/
@Readonly @Readonly
private fun isDenouncedThisTurn(diploManager: DiplomacyManager): Boolean {
return diploManager.getFlag(DiplomacyFlags.Denunciation) == 30
|| diploManager.otherCivDiplomacy().getFlag(DiplomacyFlags.Denunciation) == 30
}
/**
* Basic check if we can trade embassies, does not check all prerequisities
* Use [canOfferEmbassyTo] and [canEstablishEmbassyWith] instead
*/
@Readonly
private fun canTradeEmbassies(): Boolean {
return civInfo.isMajorCiv()
&& civInfo.hasUnique(UniqueType.EnablesEmbassies)
&& civInfo.hasUnique(UniqueType.RequiresEmbassiesForDiplomacy)
}
/**
* Test if we can offer our embassy to [otherCiv]
*/
@Readonly
fun canOfferEmbassyTo(otherCiv: Civilization): Boolean {
if (!canTradeEmbassies() || !otherCiv.isMajorCiv()) return false
val theirDiploManager = otherCiv.getDiplomacyManager(civInfo)!!
return !civInfo.isAtWarWith(otherCiv) && !isDenouncedThisTurn(theirDiploManager)
&& !theirDiploManager.hasModifier(DiplomaticModifiers.EstablishedEmbassy)
&& !theirDiploManager.hasModifier(DiplomaticModifiers.SharedEmbassies)
}
/**
* Test if we can establish embassy in [otherCiv] capital
*/
@Readonly
fun canEstablishEmbassyWith(otherCiv: Civilization): Boolean {
if (!canTradeEmbassies() || !otherCiv.isMajorCiv()) return false
val ourDiploManager = civInfo.getDiplomacyManager(otherCiv)!!
return !civInfo.isAtWarWith(otherCiv) && !isDenouncedThisTurn(ourDiploManager)
&& !ourDiploManager.hasModifier(DiplomaticModifiers.EstablishedEmbassy)
&& !ourDiploManager.hasModifier(DiplomaticModifiers.SharedEmbassies)
}
/**
* Test if both civs have embassies established in each others' capital
* Returns true if base ruleset or mods don't enable embassies
*/
@Readonly
fun hasMutualEmbassyWith(otherCiv: Civilization): Boolean {
return if (civInfo.hasUnique(UniqueType.EnablesEmbassies)
&& civInfo.hasUnique(UniqueType.RequiresEmbassiesForDiplomacy))
civInfo.getDiplomacyManager(otherCiv)!!.hasModifier(DiplomaticModifiers.SharedEmbassies)
else true // Embassies are not enabled
}
/**
* Remove mutual embassies from both civs
*/
fun removeEmbassies(otherCiv: Civilization) {
val ourDiploManager = civInfo.getDiplomacyManager(otherCiv)!!
ourDiploManager.removeModifier(DiplomaticModifiers.EstablishedEmbassy)
ourDiploManager.removeModifier(DiplomaticModifiers.ReceivedEmbassy)
ourDiploManager.removeModifier(DiplomaticModifiers.SharedEmbassies)
val theirDiploManager = ourDiploManager.otherCivDiplomacy()
theirDiploManager.removeModifier(DiplomaticModifiers.EstablishedEmbassy)
theirDiploManager.removeModifier(DiplomaticModifiers.ReceivedEmbassy)
theirDiploManager.removeModifier(DiplomaticModifiers.SharedEmbassies)
}
fun canSignDeclarationOfFriendshipWith(otherCiv: Civilization): Boolean { fun canSignDeclarationOfFriendshipWith(otherCiv: Civilization): Boolean {
return otherCiv.isMajorCiv() && !otherCiv.isAtWarWith(civInfo) return otherCiv.isMajorCiv() && !otherCiv.isAtWarWith(civInfo)
&& !civInfo.getDiplomacyManager(otherCiv)!!.hasFlag(DiplomacyFlags.Denunciation) && !civInfo.getDiplomacyManager(otherCiv)!!.hasFlag(DiplomacyFlags.Denunciation)
@ -105,15 +175,17 @@ class DiplomacyFunctions(val civInfo: Civilization) {
@Readonly @Readonly
fun canSignResearchAgreementNoCostWith (otherCiv: Civilization): Boolean { fun canSignResearchAgreementNoCostWith (otherCiv: Civilization): Boolean {
val diplomacyManager = civInfo.getDiplomacyManager(otherCiv)!! val ourDiploManager = civInfo.getDiplomacyManager(otherCiv)!!
return canSignResearchAgreement() && otherCiv.diplomacyFunctions.canSignResearchAgreement() return canSignResearchAgreement()
&& diplomacyManager.hasFlag(DiplomacyFlags.DeclarationOfFriendship) && otherCiv.diplomacyFunctions.canSignResearchAgreement()
&& !diplomacyManager.hasFlag(DiplomacyFlags.ResearchAgreement) && hasMutualEmbassyWith(otherCiv)
&& !diplomacyManager.otherCivDiplomacy().hasFlag(DiplomacyFlags.ResearchAgreement) && ourDiploManager.hasFlag(DiplomacyFlags.DeclarationOfFriendship)
&& !ourDiploManager.hasFlag(DiplomacyFlags.ResearchAgreement)
&& !ourDiploManager.otherCivDiplomacy().hasFlag(DiplomacyFlags.ResearchAgreement)
} }
@Readonly @Readonly
fun canSignResearchAgreementsWith(otherCiv: Civilization): Boolean { fun canSignResearchAgreementWith(otherCiv: Civilization): Boolean {
val cost = getResearchAgreementCost(otherCiv) val cost = getResearchAgreementCost(otherCiv)
return canSignResearchAgreementNoCostWith(otherCiv) return canSignResearchAgreementNoCostWith(otherCiv)
&& civInfo.gold >= cost && otherCiv.gold >= cost && civInfo.gold >= cost && otherCiv.gold >= cost
@ -136,16 +208,16 @@ class DiplomacyFunctions(val civInfo: Civilization) {
@Readonly @Readonly
fun canSignDefensivePactWith(otherCiv: Civilization): Boolean { fun canSignDefensivePactWith(otherCiv: Civilization): Boolean {
val diplomacyManager = civInfo.getDiplomacyManager(otherCiv)!! val ourDiplomacyManager = civInfo.getDiplomacyManager(otherCiv)!!
return canSignDefensivePact() && otherCiv.diplomacyFunctions.canSignDefensivePact() return canSignDefensivePact()
&& (diplomacyManager.hasFlag(DiplomacyFlags.DeclarationOfFriendship) && otherCiv.diplomacyFunctions.canSignDefensivePact()
|| diplomacyManager.otherCivDiplomacy().hasFlag(DiplomacyFlags.DeclarationOfFriendship)) && hasMutualEmbassyWith(otherCiv)
&& !diplomacyManager.hasFlag(DiplomacyFlags.DefensivePact) && ourDiplomacyManager.hasFlag(DiplomacyFlags.DeclarationOfFriendship)
&& !diplomacyManager.otherCivDiplomacy().hasFlag(DiplomacyFlags.DefensivePact) && !ourDiplomacyManager.hasFlag(DiplomacyFlags.DefensivePact)
&& diplomacyManager.diplomaticStatus != DiplomaticStatus.DefensivePact && !ourDiplomacyManager.otherCivDiplomacy().hasFlag(DiplomacyFlags.DefensivePact)
&& ourDiplomacyManager.diplomaticStatus != DiplomaticStatus.DefensivePact
} }
/** /**
* @returns whether units of this civilization can pass through the tiles owned by [otherCiv], * @returns whether units of this civilization can pass through the tiles owned by [otherCiv],
* considering only civ-wide filters. * considering only civ-wide filters.
@ -167,7 +239,4 @@ class DiplomacyFunctions(val civInfo: Civilization) {
if (!civInfo.isAIOrAutoPlaying() && otherCiv.isCityState) return true if (!civInfo.isAIOrAutoPlaying() && otherCiv.isCityState) return true
return false return false
} }
} }

View File

@ -46,6 +46,7 @@ enum class DiplomacyFlags {
DeclinedLuxExchange, DeclinedLuxExchange,
DeclinedPeace, DeclinedPeace,
DeclinedResearchAgreement, DeclinedResearchAgreement,
DeclinedEmbassy,
DeclinedOpenBorders, DeclinedOpenBorders,
DeclaredWar, DeclaredWar,
DeclarationOfFriendship, DeclarationOfFriendship,
@ -118,6 +119,9 @@ enum class DiplomaticModifiers(val text: String) {
StoleOurAlly("You took the alliance we had with a City-State"), StoleOurAlly("You took the alliance we had with a City-State"),
// Positive // Positive
EstablishedEmbassy("We have an embassy in your capital"),
ReceivedEmbassy("You have an embassy in our capital"),
SharedEmbassies("We have shared embassies"),
YearsOfPeace("Years of peace have strengthened our relations."), YearsOfPeace("Years of peace have strengthened our relations."),
SharedEnemy("Our mutual military struggle brings us closer together."), SharedEnemy("Our mutual military struggle brings us closer together."),
LiberatedCity("We applaud your liberation of conquered cities!"), LiberatedCity("We applaud your liberation of conquered cities!"),
@ -570,6 +574,11 @@ class DiplomacyManager() : IsPartOfGameInfoSerialization {
@Readonly @Readonly
fun hasModifier(modifier: DiplomaticModifiers) = diplomaticModifiers.containsKey(modifier.name) fun hasModifier(modifier: DiplomaticModifiers) = diplomaticModifiers.containsKey(modifier.name)
fun replaceModifier(oldModifier: DiplomaticModifiers, newModifier: DiplomaticModifiers, amount: Float) {
removeModifier(oldModifier)
addModifier(newModifier, amount)
}
fun signDeclarationOfFriendship() { fun signDeclarationOfFriendship() {
setModifier(DiplomaticModifiers.DeclarationOfFriendship, 35f) setModifier(DiplomaticModifiers.DeclarationOfFriendship, 35f)
otherCivDiplomacy().setModifier(DiplomaticModifiers.DeclarationOfFriendship, 35f) otherCivDiplomacy().setModifier(DiplomaticModifiers.DeclarationOfFriendship, 35f)
@ -679,6 +688,9 @@ class DiplomacyManager() : IsPartOfGameInfoSerialization {
setFlag(DiplomacyFlags.Denunciation, 30) setFlag(DiplomacyFlags.Denunciation, 30)
otherCivDiplomacy().setFlag(DiplomacyFlags.Denunciation, 30) otherCivDiplomacy().setFlag(DiplomacyFlags.Denunciation, 30)
// Denounciation results in removal of embasies for both sides
civInfo.diplomacyFunctions.removeEmbassies(otherCiv())
otherCiv().addNotification("[${civInfo.civName}] has denounced us!", otherCiv().addNotification("[${civInfo.civName}] has denounced us!",
NotificationCategory.Diplomacy, NotificationIcon.Diplomacy, civInfo.civName) NotificationCategory.Diplomacy, NotificationIcon.Diplomacy, civInfo.civName)

View File

@ -63,6 +63,9 @@ class TradeRequest : IsPartOfGameInfoSerialization {
requestingCivDiploManager.setFlag(DiplomacyFlags.DeclinedDefensivePact,10) requestingCivDiploManager.setFlag(DiplomacyFlags.DeclinedDefensivePact,10)
if (trade.ourOffers.any { it.name == Constants.openBorders }) if (trade.ourOffers.any { it.name == Constants.openBorders })
requestingCivDiploManager.setFlag(DiplomacyFlags.DeclinedOpenBorders, if (decliningCiv.isAI()) 5 else 10) requestingCivDiploManager.setFlag(DiplomacyFlags.DeclinedOpenBorders, if (decliningCiv.isAI()) 5 else 10)
if (trade.ourOffers.any { it.name == Constants.acceptEmbassy }
|| trade.theirOffers.any { it.name == Constants.acceptEmbassy })
requestingCivDiploManager.setFlag(DiplomacyFlags.DeclinedEmbassy, if (decliningCiv.isAI()) 5 else 10)
if (trade.theirOffers.any { it.type == TradeOfferType.WarDeclaration }) if (trade.theirOffers.any { it.type == TradeOfferType.WarDeclaration })
requestingCivDiploManager.setFlag(DiplomacyFlags.DeclinedJoinWarOffer, if (decliningCiv.isAI()) 5 else 10) requestingCivDiploManager.setFlag(DiplomacyFlags.DeclinedJoinWarOffer, if (decliningCiv.isAI()) 5 else 10)
if (trade.ourOffers.any { it.type == TradeOfferType.WarDeclaration }) if (trade.ourOffers.any { it.type == TradeOfferType.WarDeclaration })

View File

@ -7,6 +7,7 @@ import com.unciv.logic.automation.civilization.DeclareWarPlanEvaluator
import com.unciv.logic.city.City import com.unciv.logic.city.City
import com.unciv.logic.civilization.Civilization import com.unciv.logic.civilization.Civilization
import com.unciv.logic.civilization.diplomacy.DiplomacyFlags import com.unciv.logic.civilization.diplomacy.DiplomacyFlags
import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers
import com.unciv.logic.civilization.diplomacy.RelationshipLevel import com.unciv.logic.civilization.diplomacy.RelationshipLevel
import com.unciv.logic.map.tile.Tile import com.unciv.logic.map.tile.Tile
import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.Ruleset
@ -31,6 +32,23 @@ class TradeEvaluation {
return false return false
} }
// No way to tell who offers what in isOfferValid()
val embassyOffer = TradeOffer(Constants.acceptEmbassy, TradeOfferType.Embassy, speed = offerer.gameInfo.speed)
val theirDiploManager = tradePartner.getDiplomacyManager(offerer)!!
val ourDiploManager = offerer.getDiplomacyManager(tradePartner)!!
if (trade.ourOffers.contains(embassyOffer)
&& (offerer.getCapital() == null
|| theirDiploManager.hasModifier(DiplomaticModifiers.EstablishedEmbassy)
|| theirDiploManager.hasModifier(DiplomaticModifiers.SharedEmbassies)))
return false
if (trade.theirOffers.contains(embassyOffer)
&& (tradePartner.getCapital() == null
|| ourDiploManager.hasModifier(DiplomaticModifiers.EstablishedEmbassy)
|| ourDiploManager.hasModifier(DiplomaticModifiers.SharedEmbassies)))
return false
for (offer in trade.ourOffers) for (offer in trade.ourOffers)
if (!isOfferValid(offer, offerer, tradePartner)) { if (!isOfferValid(offer, offerer, tradePartner)) {
return false return false
@ -51,6 +69,7 @@ class TradeEvaluation {
} }
return when (tradeOffer.type) { return when (tradeOffer.type) {
TradeOfferType.Embassy -> true // Already checked
// if they go a little negative it's okay, but don't allowing going overboard (promising same gold to many) // if they go a little negative it's okay, but don't allowing going overboard (promising same gold to many)
TradeOfferType.Gold -> tradeOffer.amount * 0.9f < offerer.gold TradeOfferType.Gold -> tradeOffer.amount * 0.9f < offerer.gold
TradeOfferType.Gold_Per_Turn -> tradeOffer.amount * 0.9f < offerer.stats.statsForNextTurn.gold TradeOfferType.Gold_Per_Turn -> tradeOffer.amount * 0.9f < offerer.stats.statsForNextTurn.gold
@ -122,6 +141,7 @@ class TradeEvaluation {
*/ */
private fun evaluateBuyCost(offer: TradeOffer, civInfo: Civilization, tradePartner: Civilization, trade: Trade): Int { private fun evaluateBuyCost(offer: TradeOffer, civInfo: Civilization, tradePartner: Civilization, trade: Trade): Int {
when (offer.type) { when (offer.type) {
TradeOfferType.Embassy -> return (30 * civInfo.gameInfo.speed.goldCostModifier).toInt()
TradeOfferType.Gold -> return offer.amount TradeOfferType.Gold -> return offer.amount
// GPT loses value for each 'future' turn, meaning: gold now is more valuable than gold in the future // GPT loses value for each 'future' turn, meaning: gold now is more valuable than gold in the future
// Empire-wide production tends to grow at roughly 2% per turn (quick speed), so let's take that as a base line // Empire-wide production tends to grow at roughly 2% per turn (quick speed), so let's take that as a base line
@ -259,11 +279,9 @@ class TradeEvaluation {
*/ */
fun isPeaceProposalEnabled(thirdCiv: Civilization, civInfo: Civilization): Boolean { fun isPeaceProposalEnabled(thirdCiv: Civilization, civInfo: Civilization): Boolean {
val diploManager = civInfo.getDiplomacyManager(thirdCiv)!! val diploManager = civInfo.getDiplomacyManager(thirdCiv)!!
val warCountDown = if (diploManager.hasFlag(DiplomacyFlags.DeclaredWar))
diploManager.getFlag(DiplomacyFlags.DeclaredWar) else 0
// On standard speed 10 turns must pass before peace can be proposed // On standard speed 10 turns must pass before peace can be proposed
if (warCountDown > 0) return false if (diploManager.getFlag(DiplomacyFlags.DeclaredWar) > 0) return false
// TODO: We don't know if other human player would agree to peace // TODO: We don't know if other human player would agree to peace
if (thirdCiv.isHuman()) return false if (thirdCiv.isHuman()) return false
@ -322,6 +340,13 @@ class TradeEvaluation {
*/ */
private fun evaluateSellCost(offer: TradeOffer, civInfo: Civilization, tradePartner: Civilization, trade: Trade): Int { private fun evaluateSellCost(offer: TradeOffer, civInfo: Civilization, tradePartner: Civilization, trade: Trade): Int {
when (offer.type) { when (offer.type) {
TradeOfferType.Embassy -> {
val tradePartnerDiplo = civInfo.getDiplomacyManager(tradePartner)!!
if (tradePartnerDiplo.isRelationshipLevelLE(RelationshipLevel.Enemy)) return Int.MIN_VALUE
else if (tradePartnerDiplo.isRelationshipLevelLE(RelationshipLevel.Competitor))
return (60 * civInfo.gameInfo.speed.goldCostModifier).toInt()
return (30 * civInfo.gameInfo.speed.goldCostModifier).toInt() // 30 is Civ V default (on standard only?)
}
TradeOfferType.Gold -> return offer.amount TradeOfferType.Gold -> return offer.amount
TradeOfferType.Gold_Per_Turn -> return offer.amount * offer.duration TradeOfferType.Gold_Per_Turn -> return offer.amount * offer.duration
TradeOfferType.Treaty -> { TradeOfferType.Treaty -> {

View File

@ -7,6 +7,8 @@ import com.unciv.logic.civilization.Civilization
import com.unciv.logic.civilization.PopupAlert import com.unciv.logic.civilization.PopupAlert
import com.unciv.logic.civilization.diplomacy.DeclareWarReason import com.unciv.logic.civilization.diplomacy.DeclareWarReason
import com.unciv.logic.civilization.diplomacy.DiplomacyFlags import com.unciv.logic.civilization.diplomacy.DiplomacyFlags
import com.unciv.logic.civilization.diplomacy.DiplomacyManager
import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers
import com.unciv.logic.civilization.diplomacy.WarType import com.unciv.logic.civilization.diplomacy.WarType
import com.unciv.models.ruleset.tile.ResourceType import com.unciv.models.ruleset.tile.ResourceType
import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.ruleset.unique.UniqueType
@ -14,28 +16,43 @@ import com.unciv.models.ruleset.unique.UniqueType
class TradeLogic(val ourCivilization: Civilization, val otherCivilization: Civilization) { class TradeLogic(val ourCivilization: Civilization, val otherCivilization: Civilization) {
/** Contains everything we could offer the other player, whether we've actually offered it or not */ /** Contains everything we could offer the other player, whether we've actually offered it or not */
val ourAvailableOffers = getAvailableOffers(ourCivilization, otherCivilization) var ourAvailableOffers = TradeOffersList()
val theirAvailableOffers = getAvailableOffers(otherCivilization, ourCivilization) var theirAvailableOffers = TradeOffersList()
val currentTrade = Trade() val currentTrade = Trade()
private fun getAvailableOffers(civInfo: Civilization, otherCivilization: Civilization): TradeOffersList { init {
// Embassy trade availability depends solely on our ability to trade it
val embassyOffer = TradeOffer(Constants.acceptEmbassy, TradeOfferType.Embassy, speed = ourCivilization.gameInfo.speed)
if (ourCivilization.diplomacyFunctions.canEstablishEmbassyWith(otherCivilization))
theirAvailableOffers.add(embassyOffer)
if (ourCivilization.diplomacyFunctions.canOfferEmbassyTo(otherCivilization))
ourAvailableOffers.add(embassyOffer)
// Other trade items are added as usual for both sides
ourAvailableOffers += getAvailableOffers(ourCivilization, otherCivilization)
theirAvailableOffers += getAvailableOffers(otherCivilization, ourCivilization)
}
private fun getAvailableOffers(civInfo: Civilization, otherCiv: Civilization): TradeOffersList {
val offers = TradeOffersList() val offers = TradeOffersList()
if (civInfo.isCityState && otherCivilization.isCityState) return offers if (civInfo.isCityState || otherCiv.isCityState) return offers
if (civInfo.isAtWarWith(otherCivilization))
if (civInfo.isAtWarWith(otherCiv))
offers.add(TradeOffer(Constants.peaceTreaty, TradeOfferType.Treaty, speed = civInfo.gameInfo.speed)) offers.add(TradeOffer(Constants.peaceTreaty, TradeOfferType.Treaty, speed = civInfo.gameInfo.speed))
if (!otherCivilization.getDiplomacyManager(civInfo)!!.hasOpenBorders if (civInfo.diplomacyFunctions.hasMutualEmbassyWith(otherCiv)
&& !otherCivilization.isCityState && !otherCiv.getDiplomacyManager(civInfo)!!.hasOpenBorders
&& civInfo.hasUnique(UniqueType.EnablesOpenBorders) && civInfo.hasUnique(UniqueType.EnablesOpenBorders)
&& otherCivilization.hasUnique(UniqueType.EnablesOpenBorders)) { && otherCiv.hasUnique(UniqueType.EnablesOpenBorders)) {
offers.add(TradeOffer(Constants.openBorders, TradeOfferType.Agreement, speed = civInfo.gameInfo.speed)) offers.add(TradeOffer(Constants.openBorders, TradeOfferType.Agreement, speed = civInfo.gameInfo.speed))
} }
if (civInfo.diplomacyFunctions.canSignResearchAgreementNoCostWith(otherCivilization)) if (civInfo.diplomacyFunctions.canSignResearchAgreementNoCostWith(otherCiv))
offers.add(TradeOffer(Constants.researchAgreement, TradeOfferType.Treaty, offers.add(TradeOffer(Constants.researchAgreement, TradeOfferType.Treaty,
civInfo.diplomacyFunctions.getResearchAgreementCost(otherCivilization), civInfo.gameInfo.speed)) civInfo.diplomacyFunctions.getResearchAgreementCost(otherCiv), civInfo.gameInfo.speed))
if (civInfo.diplomacyFunctions.canSignDefensivePactWith(otherCivilization)) if (civInfo.diplomacyFunctions.canSignDefensivePactWith(otherCiv))
offers.add(TradeOffer(Constants.defensivePact, TradeOfferType.Treaty, speed = civInfo.gameInfo.speed)) offers.add(TradeOffer(Constants.defensivePact, TradeOfferType.Treaty, speed = civInfo.gameInfo.speed))
for (entry in civInfo.getPerTurnResourcesWithOriginsForTrade() for (entry in civInfo.getPerTurnResourcesWithOriginsForTrade()
@ -51,31 +68,27 @@ class TradeLogic(val ourCivilization: Civilization, val otherCivilization: Civil
offers.add(TradeOffer(entry.resource.name, TradeOfferType.Stockpiled_Resource, entry.amount, speed = civInfo.gameInfo.speed)) offers.add(TradeOffer(entry.resource.name, TradeOfferType.Stockpiled_Resource, entry.amount, speed = civInfo.gameInfo.speed))
} }
offers.add(TradeOffer("Gold", TradeOfferType.Gold, civInfo.gold, speed = civInfo.gameInfo.speed)) offers.add(TradeOffer(Constants.flatGold, TradeOfferType.Gold, civInfo.gold, speed = civInfo.gameInfo.speed))
offers.add(TradeOffer("Gold per turn", TradeOfferType.Gold_Per_Turn, civInfo.stats.statsForNextTurn.gold.toInt(), civInfo.gameInfo.speed)) offers.add(TradeOffer(Constants.goldPerTurn, TradeOfferType.Gold_Per_Turn, civInfo.stats.statsForNextTurn.gold.toInt(), civInfo.gameInfo.speed))
if (!civInfo.isOneCityChallenger() && !otherCivilization.isOneCityChallenger() if (!civInfo.isOneCityChallenger() && !otherCiv.isOneCityChallenger())
&& !civInfo.isCityState && !otherCivilization.isCityState
) {
for (city in civInfo.cities.filterNot { it.isCapital() || it.isInResistance() }) for (city in civInfo.cities.filterNot { it.isCapital() || it.isInResistance() })
offers.add(TradeOffer(city.id, TradeOfferType.City, speed = civInfo.gameInfo.speed)) offers.add(TradeOffer(city.id, TradeOfferType.City, speed = civInfo.gameInfo.speed))
}
val otherCivsWeKnow = civInfo.getKnownCivs() val otherCivsWeKnow = civInfo.getKnownCivs()
.filter { it.civName != otherCivilization.civName && it.isMajorCiv() && !it.isDefeated() } .filter { it.civName != otherCiv.civName && it.isMajorCiv() && !it.isDefeated() }
if (civInfo.gameInfo.ruleset.modOptions.hasUnique(UniqueType.TradeCivIntroductions)) { if (civInfo.gameInfo.ruleset.modOptions.hasUnique(UniqueType.TradeCivIntroductions)) {
val civsWeKnowAndTheyDont = otherCivsWeKnow val civsWeKnowAndTheyDont = otherCivsWeKnow
.filter { !otherCivilization.diplomacy.containsKey(it.civName) && !it.isDefeated() } .filter { !otherCiv.diplomacy.containsKey(it.civName) && !it.isDefeated() }
for (thirdCiv in civsWeKnowAndTheyDont) { for (thirdCiv in civsWeKnowAndTheyDont) {
offers.add(TradeOffer(thirdCiv.civName, TradeOfferType.Introduction, speed = civInfo.gameInfo.speed)) offers.add(TradeOffer(thirdCiv.civName, TradeOfferType.Introduction, speed = civInfo.gameInfo.speed))
} }
} }
if (!civInfo.isCityState && !otherCivilization.isCityState if (!civInfo.gameInfo.ruleset.modOptions.hasUnique(UniqueType.DiplomaticRelationshipsCannotChange)) {
&& !civInfo.gameInfo.ruleset.modOptions.hasUnique(UniqueType.DiplomaticRelationshipsCannotChange)) {
val civsWeBothKnow = otherCivsWeKnow val civsWeBothKnow = otherCivsWeKnow
.filter { otherCivilization.diplomacy.containsKey(it.civName) } .filter { otherCiv.diplomacy.containsKey(it.civName) }
val civsWeArentAtWarWith = civsWeBothKnow val civsWeArentAtWarWith = civsWeBothKnow
.filter { civInfo.getDiplomacyManager(it)!!.canDeclareWar() } .filter { civInfo.getDiplomacyManager(it)!!.canDeclareWar() }
for (thirdCiv in civsWeArentAtWarWith) { for (thirdCiv in civsWeArentAtWarWith) {
@ -83,16 +96,15 @@ class TradeLogic(val ourCivilization: Civilization, val otherCivilization: Civil
} }
} }
if (!civInfo.isCityState && !otherCivilization.isCityState) { val thirdCivsAtWarTheyKnow = otherCiv.getKnownCivs().filter {
val thirdCivsAtWarTheyKnow = otherCivilization.getKnownCivs() it.isAtWarWith(civInfo) && !it.isDefeated()
.filter { it.isAtWarWith(civInfo) && !it.isDefeated() && !it.gameInfo.ruleset.modOptions.hasUnique(UniqueType.DiplomaticRelationshipsCannotChange)
&& !it.gameInfo.ruleset.modOptions.hasUnique(UniqueType.DiplomaticRelationshipsCannotChange) } }
for (thirdCiv in thirdCivsAtWarTheyKnow) { for (thirdCiv in thirdCivsAtWarTheyKnow) {
// Setting amount to 0 makes TradeOffer.isTradable() return false and also disables the button in trade window // Setting amount to 0 makes TradeOffer.isTradable() return false and also disables the button in trade window
val amount = if (TradeEvaluation().isPeaceProposalEnabled(thirdCiv, civInfo)) 1 else 0 val amount = if (TradeEvaluation().isPeaceProposalEnabled(thirdCiv, civInfo)) 1 else 0
offers.add(TradeOffer(thirdCiv.civName, TradeOfferType.PeaceProposal, amount, civInfo.gameInfo.speed)) offers.add(TradeOffer(thirdCiv.civName, TradeOfferType.PeaceProposal, amount, civInfo.gameInfo.speed))
}
} }
return offers return offers
@ -100,13 +112,13 @@ class TradeLogic(val ourCivilization: Civilization, val otherCivilization: Civil
fun acceptTrade(applyGifts: Boolean = true) { fun acceptTrade(applyGifts: Boolean = true) {
val ourDiploManager = ourCivilization.getDiplomacyManager(otherCivilization)!! val ourDiploManager = ourCivilization.getDiplomacyManager(otherCivilization)!!
val theirDiploManger = otherCivilization.getDiplomacyManager(ourCivilization)!! val theirDiploManager = otherCivilization.getDiplomacyManager(ourCivilization)!!
ourDiploManager.apply { ourDiploManager.apply {
trades.add(currentTrade) trades.add(currentTrade)
updateHasOpenBorders() updateHasOpenBorders()
} }
theirDiploManger.apply { theirDiploManager.apply {
trades.add(currentTrade.reverse()) trades.add(currentTrade.reverse())
updateHasOpenBorders() updateHasOpenBorders()
} }
@ -114,6 +126,10 @@ class TradeLogic(val ourCivilization: Civilization, val otherCivilization: Civil
// instant transfers // instant transfers
fun transferTrade(from: Civilization, to: Civilization, offer: TradeOffer) { fun transferTrade(from: Civilization, to: Civilization, offer: TradeOffer) {
when (offer.type) { when (offer.type) {
TradeOfferType.Embassy -> {
for (tile in from.getCapital()!!.getCenterTile().getTilesInDistance(2))
tile.setExplored(to, true)
}
TradeOfferType.Gold -> { TradeOfferType.Gold -> {
to.addGold(offer.amount) to.addGold(offer.amount)
from.addGold(-offer.amount) from.addGold(-offer.amount)
@ -204,7 +220,7 @@ class TradeLogic(val ourCivilization: Civilization, val otherCivilization: Civil
ourDiploManager.giftGold(ourGoldValueOfTrade - theirGoldValueOfTrade.coerceAtLeast(0), isPureGift) ourDiploManager.giftGold(ourGoldValueOfTrade - theirGoldValueOfTrade.coerceAtLeast(0), isPureGift)
} else if (theirGoldValueOfTrade > ourGoldValueOfTrade) { } else if (theirGoldValueOfTrade > ourGoldValueOfTrade) {
val isPureGift = currentTrade.theirOffers.isEmpty() val isPureGift = currentTrade.theirOffers.isEmpty()
theirDiploManger.giftGold(theirGoldValueOfTrade - ourGoldValueOfTrade.coerceAtLeast(0), isPureGift) theirDiploManager.giftGold(theirGoldValueOfTrade - ourGoldValueOfTrade.coerceAtLeast(0), isPureGift)
} }
} }
@ -218,6 +234,23 @@ class TradeLogic(val ourCivilization: Civilization, val otherCivilization: Civil
for (offer in currentTrade.theirOffers.filter { it.type == TradeOfferType.Treaty }) for (offer in currentTrade.theirOffers.filter { it.type == TradeOfferType.Treaty })
transferTrade(otherCivilization, ourCivilization, offer) transferTrade(otherCivilization, ourCivilization, offer)
fun applyEmbassyOffer(diploManager: DiplomacyManager) {
if (diploManager.hasModifier(DiplomaticModifiers.EstablishedEmbassy)) {
diploManager.replaceModifier(DiplomaticModifiers.EstablishedEmbassy, DiplomaticModifiers.SharedEmbassies, 3f)
diploManager.otherCivDiplomacy().replaceModifier(DiplomaticModifiers.ReceivedEmbassy, DiplomaticModifiers.SharedEmbassies, 3f)
}
else {
diploManager.addModifier(DiplomaticModifiers.ReceivedEmbassy, 1f)
diploManager.otherCivDiplomacy().addModifier(DiplomaticModifiers.EstablishedEmbassy, 2f)
}
}
// Diplomatic modifiers for embassy depend on whether a civ gives its embassy, accepts it or both
if (currentTrade.ourOffers.any { it.type == TradeOfferType.Embassy })
applyEmbassyOffer(ourDiploManager)
if (currentTrade.theirOffers.any { it.type == TradeOfferType.Embassy })
applyEmbassyOffer(theirDiploManager)
ourCivilization.cache.updateCivResources() ourCivilization.cache.updateCivResources()
ourCivilization.updateStatsForNextTurn() ourCivilization.updateStatsForNextTurn()

View File

@ -6,6 +6,7 @@ package com.unciv.logic.trade
*/ */
@Suppress("EnumEntryName") // We do want the underscores in our names @Suppress("EnumEntryName") // We do want the underscores in our names
enum class TradeOfferType(val numberType: TradeTypeNumberType, val isImmediate: Boolean) { enum class TradeOfferType(val numberType: TradeTypeNumberType, val isImmediate: Boolean) {
Embassy (TradeTypeNumberType.None, true),
Gold (TradeTypeNumberType.Gold, true), Gold (TradeTypeNumberType.Gold, true),
Gold_Per_Turn (TradeTypeNumberType.Gold, false), Gold_Per_Turn (TradeTypeNumberType.Gold, false),
/** Treaties are shared by both sides - like peace treaty and defensive pact */ /** Treaties are shared by both sides - like peace treaty and defensive pact */

View File

@ -215,6 +215,10 @@ enum class UniqueType(
@Deprecated("As of 4.16.18", ReplaceWith("[+100]% [resource] resource production")) @Deprecated("As of 4.16.18", ReplaceWith("[+100]% [resource] resource production"))
DoubleResourceProduced("Double quantity of [resource] produced", UniqueTarget.Global), DoubleResourceProduced("Double quantity of [resource] produced", UniqueTarget.Global),
/// Diplomacy
EnablesEmbassies("Enables establishment of embassies", UniqueTarget.Tech),
RequiresEmbassiesForDiplomacy("Requires establishing embassies to conduct advanced diplomacy", UniqueTarget.Global),
/// Agreements /// Agreements
EnablesOpenBorders("Enables Open Borders agreements", UniqueTarget.Global), EnablesOpenBorders("Enables Open Borders agreements", UniqueTarget.Global),
// Should the 'R' in 'Research agreements' be capitalized? // Should the 'R' in 'Research agreements' be capitalized?

View File

@ -57,7 +57,7 @@ class OffersListScroll(
for (offerType in TradeOfferType.entries) { for (offerType in TradeOfferType.entries) {
val labelName = when(offerType) { val labelName = when(offerType) {
Gold, Gold_Per_Turn, Treaty, Agreement, Introduction -> "" Embassy, Gold, Gold_Per_Turn, Treaty, Agreement, Introduction -> ""
Luxury_Resource -> "Luxury resources" Luxury_Resource -> "Luxury resources"
Strategic_Resource -> "Strategic resources" Strategic_Resource -> "Strategic resources"
Stockpiled_Resource -> "Stockpiled resources" Stockpiled_Resource -> "Stockpiled resources"
@ -89,6 +89,7 @@ class OffersListScroll(
for (offer in offersOfType) { for (offer in offersOfType) {
val tradeLabel = offer.getOfferText(untradableOffers.sumBy(offer.name)) val tradeLabel = offer.getOfferText(untradableOffers.sumBy(offer.name))
val tradeIcon = when (offer.type) { val tradeIcon = when (offer.type) {
Embassy -> ImageGetter.getImage("OtherIcons/Star")
Luxury_Resource, Strategic_Resource -> Luxury_Resource, Strategic_Resource ->
ImageGetter.getResourcePortrait(offer.name, 30f) ImageGetter.getResourcePortrait(offer.name, 30f)
WarDeclaration, PeaceProposal -> WarDeclaration, PeaceProposal ->

View File

@ -808,6 +808,9 @@ Simple unique parameters are explained by mouseover. Complex parameters are expl
Applicable to: Global Applicable to: Global
??? example "Requires establishing embassies to conduct advanced diplomacy"
Applicable to: Global
??? example "Enables Open Borders agreements" ??? example "Enables Open Borders agreements"
Applicable to: Global Applicable to: Global
@ -1148,6 +1151,9 @@ Simple unique parameters are explained by mouseover. Complex parameters are expl
Applicable to: Era Applicable to: Era
## Tech uniques ## Tech uniques
??? example "Enables establishment of embassies"
Applicable to: Tech
??? example "Starting tech" ??? example "Starting tech"
Applicable to: Tech Applicable to: Tech