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",
"uniques": [
"Requires establishing embassies to conduct advanced diplomacy",
"[-75]% growth [in all cities] <when between [-10] and [-1] [Happiness]>",
"Nullifies Growth [in all cities] <when below [-10] [Happiness]>",
"[-50]% [Production] [in all cities] <when below [-10] [Happiness]>",

View File

@ -76,6 +76,7 @@
"name": "Writing",
"row": 3,
"prerequisites": ["Pottery"],
"uniques": ["Enables establishment of embassies"],
"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."
]
},
{
"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",
"steps": [
"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!",
"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."
"civilopediaText": [
{ 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." },
{ 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!" },
{ 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",
"steps": [
"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.",
"The AI is very careful and will not accept defensive pacts with less than 80 influence."
]
"civilopediaText": [
{ 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." },
{ text: "Be cautious when signing defensive pacts because they can bring you into wars that you might not want to be in." },
{ 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",

View File

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

View File

@ -51,6 +51,11 @@ object Constants {
// Agreements
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 */
const val cityStates = "City-States"
/** Used as origin in ResourceSupplyList */

View File

@ -108,20 +108,67 @@ object DiplomacyAutomation {
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) {
if (!civInfo.hasUnique(UniqueType.EnablesOpenBorders)) return
val civsThatWeCanOpenBordersWith = civInfo.getKnownCivs()
.filter {
it.isMajorCiv() && !civInfo.isAtWarWith(it)
&& it.hasUnique(UniqueType.EnablesOpenBorders)
&& !civInfo.getDiplomacyManager(it)!!.hasOpenBorders
&& !it.getDiplomacyManager(civInfo)!!.hasOpenBorders
&& !civInfo.getDiplomacyManager(it)!!.hasFlag(DiplomacyFlags.DeclinedOpenBorders)
&& !areWeOfferingTrade(civInfo, it, Constants.openBorders)
}.sortedByDescending { it.getDiplomacyManager(civInfo)!!.relationshipLevel() }.toList()
val civsThatWeCanOpenBordersWith = civInfo.getKnownCivs().filter {
val ourDiploManager = civInfo.getDiplomacyManager(it)!!
it.isMajorCiv()
&& !civInfo.isAtWarWith(it)
&& it.hasUnique(UniqueType.EnablesOpenBorders)
&& !ourDiploManager.hasOpenBorders
&& !ourDiploManager.otherCivDiplomacy().hasOpenBorders
&& civInfo.diplomacyFunctions.hasMutualEmbassyWith(it)
&& !ourDiploManager.hasFlag(DiplomacyFlags.DeclinedOpenBorders)
&& !areWeOfferingTrade(civInfo, it, Constants.openBorders)
}.sortedByDescending { it.getDiplomacyManager(civInfo)!!.relationshipLevel() }
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 (wantsToOpenBorders(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 {
val diploManager = civInfo.getDiplomacyManager(otherCiv)!!
if (diploManager.hasFlag(DiplomacyFlags.DeclinedOpenBorders)) return false
if (diploManager.isRelationshipLevelLT(RelationshipLevel.Favorable)) return false
val ourDiploManager = civInfo.getDiplomacyManager(otherCiv)!!
if (ourDiploManager.hasFlag(DiplomacyFlags.DeclinedOpenBorders)) 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
if (civInfo.diplomacy.values.any { it.isRelationshipLevelGE(RelationshipLevel.Friend) && it.otherCiv().isAtWarWith(otherCiv) })
return false
@ -147,7 +221,7 @@ object DiplomacyAutomation {
if (otherCiv.cities.count { !it.getCenterTile().isVisible(civInfo) } < otherCiv.cities.count() * .8f)
return true
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 true
}
@ -155,22 +229,21 @@ object DiplomacyAutomation {
internal fun offerResearchAgreement(civInfo: Civilization) {
if (!civInfo.diplomacyFunctions.canSignResearchAgreement()) return // don't waste your time
val canSignResearchAgreementCiv = civInfo.getKnownCivs()
.filter {
civInfo.diplomacyFunctions.canSignResearchAgreementsWith(it)
&& !civInfo.getDiplomacyManager(it)!!.hasFlag(DiplomacyFlags.DeclinedResearchAgreement)
&& !areWeOfferingTrade(civInfo, it, Constants.researchAgreement)
}
.sortedByDescending { it.stats.statsForNextTurn.science }
val civsThatWeCanSignResearchAgreementWith = civInfo.getKnownCivs().filter {
civInfo.diplomacyFunctions.canSignResearchAgreementWith(it)
&& !civInfo.getDiplomacyManager(it)!!.hasFlag(DiplomacyFlags.DeclinedResearchAgreement)
&& !areWeOfferingTrade(civInfo, it, Constants.researchAgreement)
}.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.
if ((1..10).random() <= 5 * civInfo.getPersonality().modifierFocus(PersonalityValue.Science, .3f)) continue
val tradeLogic = TradeLogic(civInfo, otherCiv)
val cost = civInfo.diplomacyFunctions.getResearchAgreementCost(otherCiv)
tradeLogic.currentTrade.ourOffers.add(TradeOffer(Constants.researchAgreement, TradeOfferType.Treaty, cost, civInfo.gameInfo.speed))
tradeLogic.currentTrade.theirOffers.add(TradeOffer(Constants.researchAgreement, TradeOfferType.Treaty, cost, civInfo.gameInfo.speed))
val tradeOffer = 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()))
}
}
@ -178,23 +251,24 @@ object DiplomacyAutomation {
internal fun offerDefensivePact(civInfo: Civilization) {
if (!civInfo.diplomacyFunctions.canSignDefensivePact()) return // don't waste your time
val canSignDefensivePactCiv = civInfo.getKnownCivs()
.filter {
civInfo.diplomacyFunctions.canSignDefensivePactWith(it)
&& !civInfo.getDiplomacyManager(it)!!.hasFlag(DiplomacyFlags.DeclinedDefensivePact)
&& civInfo.getDiplomacyManager(it)!!.opinionOfOtherCiv() < 70f * civInfo.getPersonality().inverseModifierFocus(PersonalityValue.Aggressive, .2f)
&& !areWeOfferingTrade(civInfo, it, Constants.defensivePact)
}
val civsThatWeCanSignDefensivePactWith = civInfo.getKnownCivs().filter {
val ourDiploManager = civInfo.getDiplomacyManager(it)!!
civInfo.diplomacyFunctions.canSignDefensivePactWith(it)
&& !ourDiploManager.hasFlag(DiplomacyFlags.DeclinedDefensivePact)
&& ourDiploManager.opinionOfOtherCiv() < 70f * civInfo.getPersonality().inverseModifierFocus(PersonalityValue.Aggressive, .2f)
&& !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.
if ((1..10).random() <= 7 * civInfo.getPersonality().inverseModifierFocus(PersonalityValue.Loyal, .3f)) continue
if (wantsToSignDefensivePact(civInfo, otherCiv)) {
//todo: Add more in depth evaluation here
val tradeLogic = TradeLogic(civInfo, otherCiv)
tradeLogic.currentTrade.ourOffers.add(TradeOffer(Constants.defensivePact, TradeOfferType.Treaty, speed = civInfo.gameInfo.speed))
tradeLogic.currentTrade.theirOffers.add(TradeOffer(Constants.defensivePact, TradeOfferType.Treaty, speed = civInfo.gameInfo.speed))
val tradeOffer = 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()))
} else {
// Remember this for a few turns to save computation power
@ -204,10 +278,11 @@ object DiplomacyAutomation {
}
fun wantsToSignDefensivePact(civInfo: Civilization, otherCiv: Civilization): Boolean {
val diploManager = civInfo.getDiplomacyManager(otherCiv)!!
if (diploManager.hasFlag(DiplomacyFlags.DeclinedDefensivePact)) return false
if (diploManager.opinionOfOtherCiv() < 65f * civInfo.getPersonality().inverseModifierFocus(PersonalityValue.Aggressive, .3f)) return false
val commonknownCivs = diploManager.getCommonKnownCivs()
val ourDiploManager = civInfo.getDiplomacyManager(otherCiv)!!
if (ourDiploManager.hasFlag(DiplomacyFlags.DeclinedDefensivePact)) return false
if (ourDiploManager.opinionOfOtherCiv() < 65f * civInfo.getPersonality().inverseModifierFocus(PersonalityValue.Aggressive, .3f)) return false
val commonknownCivs = ourDiploManager.getCommonKnownCivs()
for (thirdCiv in commonknownCivs) {
// If they have bad relations with any of our friends, don't consider it
if (civInfo.getDiplomacyManager(thirdCiv)!!.hasFlag(DiplomacyFlags.DeclarationOfFriendship)
@ -230,11 +305,11 @@ object DiplomacyAutomation {
val allAliveCivs = allCivs - deadCivs
// 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
if (diploManager.hasModifier(DiplomaticModifiers.WarMongerer)) {
motivation -= diploManager.getModifier(DiplomaticModifiers.WarMongerer) * civInfo.getPersonality().modifierFocus(PersonalityValue.Diplomacy, .5f)
if (ourDiploManager.hasModifier(DiplomaticModifiers.WarMongerer)) {
motivation -= ourDiploManager.getModifier(DiplomaticModifiers.WarMongerer) * civInfo.getPersonality().modifierFocus(PersonalityValue.Diplomacy, .5f)
}
// If they are stronger than us, then we value it a lot more
@ -323,9 +398,10 @@ object DiplomacyAutomation {
// pay for peace
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.theirOffers.add(TradeOffer(Constants.peaceTreaty, TradeOfferType.Treaty, speed = civInfo.gameInfo.speed))
tradeLogic.currentTrade.ourOffers.add(tradeOffer)
tradeLogic.currentTrade.theirOffers.add(tradeOffer)
if (enemy.isMajorCiv()) {
var moneyWeNeedToPay = -TradeEvaluation().evaluatePeaceCostForThem(civInfo, enemy)

View File

@ -51,6 +51,7 @@ object NextTurnAutomation {
ReligionAutomation.spendFaithOnReligion(civInfo)
}
DiplomacyAutomation.offerToEstablishEmbassy(civInfo)
DiplomacyAutomation.offerOpenBorders(civInfo)
DiplomacyAutomation.offerResearchAgreement(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.DeclaredWar, diplomacyManager.civInfo.gameInfo.ruleset.modOptions.constants.minimumWarDuration) // AI won't agree to trade for 10 turns
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) {

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
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 {
return otherCiv.isMajorCiv() && !otherCiv.isAtWarWith(civInfo)
&& !civInfo.getDiplomacyManager(otherCiv)!!.hasFlag(DiplomacyFlags.Denunciation)
@ -105,15 +175,17 @@ class DiplomacyFunctions(val civInfo: Civilization) {
@Readonly
fun canSignResearchAgreementNoCostWith (otherCiv: Civilization): Boolean {
val diplomacyManager = civInfo.getDiplomacyManager(otherCiv)!!
return canSignResearchAgreement() && otherCiv.diplomacyFunctions.canSignResearchAgreement()
&& diplomacyManager.hasFlag(DiplomacyFlags.DeclarationOfFriendship)
&& !diplomacyManager.hasFlag(DiplomacyFlags.ResearchAgreement)
&& !diplomacyManager.otherCivDiplomacy().hasFlag(DiplomacyFlags.ResearchAgreement)
val ourDiploManager = civInfo.getDiplomacyManager(otherCiv)!!
return canSignResearchAgreement()
&& otherCiv.diplomacyFunctions.canSignResearchAgreement()
&& hasMutualEmbassyWith(otherCiv)
&& ourDiploManager.hasFlag(DiplomacyFlags.DeclarationOfFriendship)
&& !ourDiploManager.hasFlag(DiplomacyFlags.ResearchAgreement)
&& !ourDiploManager.otherCivDiplomacy().hasFlag(DiplomacyFlags.ResearchAgreement)
}
@Readonly
fun canSignResearchAgreementsWith(otherCiv: Civilization): Boolean {
fun canSignResearchAgreementWith(otherCiv: Civilization): Boolean {
val cost = getResearchAgreementCost(otherCiv)
return canSignResearchAgreementNoCostWith(otherCiv)
&& civInfo.gold >= cost && otherCiv.gold >= cost
@ -136,16 +208,16 @@ class DiplomacyFunctions(val civInfo: Civilization) {
@Readonly
fun canSignDefensivePactWith(otherCiv: Civilization): Boolean {
val diplomacyManager = civInfo.getDiplomacyManager(otherCiv)!!
return canSignDefensivePact() && otherCiv.diplomacyFunctions.canSignDefensivePact()
&& (diplomacyManager.hasFlag(DiplomacyFlags.DeclarationOfFriendship)
|| diplomacyManager.otherCivDiplomacy().hasFlag(DiplomacyFlags.DeclarationOfFriendship))
&& !diplomacyManager.hasFlag(DiplomacyFlags.DefensivePact)
&& !diplomacyManager.otherCivDiplomacy().hasFlag(DiplomacyFlags.DefensivePact)
&& diplomacyManager.diplomaticStatus != DiplomaticStatus.DefensivePact
val ourDiplomacyManager = civInfo.getDiplomacyManager(otherCiv)!!
return canSignDefensivePact()
&& otherCiv.diplomacyFunctions.canSignDefensivePact()
&& hasMutualEmbassyWith(otherCiv)
&& ourDiplomacyManager.hasFlag(DiplomacyFlags.DeclarationOfFriendship)
&& !ourDiplomacyManager.hasFlag(DiplomacyFlags.DefensivePact)
&& !ourDiplomacyManager.otherCivDiplomacy().hasFlag(DiplomacyFlags.DefensivePact)
&& ourDiplomacyManager.diplomaticStatus != DiplomaticStatus.DefensivePact
}
/**
* @returns whether units of this civilization can pass through the tiles owned by [otherCiv],
* considering only civ-wide filters.
@ -167,7 +239,4 @@ class DiplomacyFunctions(val civInfo: Civilization) {
if (!civInfo.isAIOrAutoPlaying() && otherCiv.isCityState) return true
return false
}
}

View File

@ -46,6 +46,7 @@ enum class DiplomacyFlags {
DeclinedLuxExchange,
DeclinedPeace,
DeclinedResearchAgreement,
DeclinedEmbassy,
DeclinedOpenBorders,
DeclaredWar,
DeclarationOfFriendship,
@ -118,6 +119,9 @@ enum class DiplomaticModifiers(val text: String) {
StoleOurAlly("You took the alliance we had with a City-State"),
// 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."),
SharedEnemy("Our mutual military struggle brings us closer together."),
LiberatedCity("We applaud your liberation of conquered cities!"),
@ -570,6 +574,11 @@ class DiplomacyManager() : IsPartOfGameInfoSerialization {
@Readonly
fun hasModifier(modifier: DiplomaticModifiers) = diplomaticModifiers.containsKey(modifier.name)
fun replaceModifier(oldModifier: DiplomaticModifiers, newModifier: DiplomaticModifiers, amount: Float) {
removeModifier(oldModifier)
addModifier(newModifier, amount)
}
fun signDeclarationOfFriendship() {
setModifier(DiplomaticModifiers.DeclarationOfFriendship, 35f)
otherCivDiplomacy().setModifier(DiplomaticModifiers.DeclarationOfFriendship, 35f)
@ -679,6 +688,9 @@ class DiplomacyManager() : IsPartOfGameInfoSerialization {
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!",
NotificationCategory.Diplomacy, NotificationIcon.Diplomacy, civInfo.civName)

View File

@ -63,6 +63,9 @@ class TradeRequest : IsPartOfGameInfoSerialization {
requestingCivDiploManager.setFlag(DiplomacyFlags.DeclinedDefensivePact,10)
if (trade.ourOffers.any { it.name == Constants.openBorders })
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 })
requestingCivDiploManager.setFlag(DiplomacyFlags.DeclinedJoinWarOffer, if (decliningCiv.isAI()) 5 else 10)
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.civilization.Civilization
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.map.tile.Tile
import com.unciv.models.ruleset.Ruleset
@ -31,6 +32,23 @@ class TradeEvaluation {
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)
if (!isOfferValid(offer, offerer, tradePartner)) {
return false
@ -51,6 +69,7 @@ class TradeEvaluation {
}
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)
TradeOfferType.Gold -> tradeOffer.amount * 0.9f < offerer.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 {
when (offer.type) {
TradeOfferType.Embassy -> return (30 * civInfo.gameInfo.speed.goldCostModifier).toInt()
TradeOfferType.Gold -> return offer.amount
// 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
@ -259,11 +279,9 @@ class TradeEvaluation {
*/
fun isPeaceProposalEnabled(thirdCiv: Civilization, civInfo: Civilization): Boolean {
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
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
if (thirdCiv.isHuman()) return false
@ -322,6 +340,13 @@ class TradeEvaluation {
*/
private fun evaluateSellCost(offer: TradeOffer, civInfo: Civilization, tradePartner: Civilization, trade: Trade): Int {
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_Per_Turn -> return offer.amount * offer.duration
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.diplomacy.DeclareWarReason
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.models.ruleset.tile.ResourceType
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) {
/** Contains everything we could offer the other player, whether we've actually offered it or not */
val ourAvailableOffers = getAvailableOffers(ourCivilization, otherCivilization)
val theirAvailableOffers = getAvailableOffers(otherCivilization, ourCivilization)
var ourAvailableOffers = TradeOffersList()
var theirAvailableOffers = TradeOffersList()
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()
if (civInfo.isCityState && otherCivilization.isCityState) return offers
if (civInfo.isAtWarWith(otherCivilization))
if (civInfo.isCityState || otherCiv.isCityState) return offers
if (civInfo.isAtWarWith(otherCiv))
offers.add(TradeOffer(Constants.peaceTreaty, TradeOfferType.Treaty, speed = civInfo.gameInfo.speed))
if (!otherCivilization.getDiplomacyManager(civInfo)!!.hasOpenBorders
&& !otherCivilization.isCityState
&& civInfo.hasUnique(UniqueType.EnablesOpenBorders)
&& otherCivilization.hasUnique(UniqueType.EnablesOpenBorders)) {
if (civInfo.diplomacyFunctions.hasMutualEmbassyWith(otherCiv)
&& !otherCiv.getDiplomacyManager(civInfo)!!.hasOpenBorders
&& civInfo.hasUnique(UniqueType.EnablesOpenBorders)
&& otherCiv.hasUnique(UniqueType.EnablesOpenBorders)) {
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,
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))
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("Gold", 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.flatGold, TradeOfferType.Gold, civInfo.gold, speed = 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()
&& !civInfo.isCityState && !otherCivilization.isCityState
) {
if (!civInfo.isOneCityChallenger() && !otherCiv.isOneCityChallenger())
for (city in civInfo.cities.filterNot { it.isCapital() || it.isInResistance() })
offers.add(TradeOffer(city.id, TradeOfferType.City, speed = civInfo.gameInfo.speed))
}
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)) {
val civsWeKnowAndTheyDont = otherCivsWeKnow
.filter { !otherCivilization.diplomacy.containsKey(it.civName) && !it.isDefeated() }
.filter { !otherCiv.diplomacy.containsKey(it.civName) && !it.isDefeated() }
for (thirdCiv in civsWeKnowAndTheyDont) {
offers.add(TradeOffer(thirdCiv.civName, TradeOfferType.Introduction, speed = civInfo.gameInfo.speed))
}
}
if (!civInfo.isCityState && !otherCivilization.isCityState
&& !civInfo.gameInfo.ruleset.modOptions.hasUnique(UniqueType.DiplomaticRelationshipsCannotChange)) {
if (!civInfo.gameInfo.ruleset.modOptions.hasUnique(UniqueType.DiplomaticRelationshipsCannotChange)) {
val civsWeBothKnow = otherCivsWeKnow
.filter { otherCivilization.diplomacy.containsKey(it.civName) }
.filter { otherCiv.diplomacy.containsKey(it.civName) }
val civsWeArentAtWarWith = civsWeBothKnow
.filter { civInfo.getDiplomacyManager(it)!!.canDeclareWar() }
for (thirdCiv in civsWeArentAtWarWith) {
@ -83,16 +96,15 @@ class TradeLogic(val ourCivilization: Civilization, val otherCivilization: Civil
}
}
if (!civInfo.isCityState && !otherCivilization.isCityState) {
val thirdCivsAtWarTheyKnow = otherCivilization.getKnownCivs()
.filter { it.isAtWarWith(civInfo) && !it.isDefeated()
&& !it.gameInfo.ruleset.modOptions.hasUnique(UniqueType.DiplomaticRelationshipsCannotChange) }
val thirdCivsAtWarTheyKnow = otherCiv.getKnownCivs().filter {
it.isAtWarWith(civInfo) && !it.isDefeated()
&& !it.gameInfo.ruleset.modOptions.hasUnique(UniqueType.DiplomaticRelationshipsCannotChange)
}
for (thirdCiv in thirdCivsAtWarTheyKnow) {
// 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
offers.add(TradeOffer(thirdCiv.civName, TradeOfferType.PeaceProposal, amount, civInfo.gameInfo.speed))
}
for (thirdCiv in thirdCivsAtWarTheyKnow) {
// 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
offers.add(TradeOffer(thirdCiv.civName, TradeOfferType.PeaceProposal, amount, civInfo.gameInfo.speed))
}
return offers
@ -100,13 +112,13 @@ class TradeLogic(val ourCivilization: Civilization, val otherCivilization: Civil
fun acceptTrade(applyGifts: Boolean = true) {
val ourDiploManager = ourCivilization.getDiplomacyManager(otherCivilization)!!
val theirDiploManger = otherCivilization.getDiplomacyManager(ourCivilization)!!
val theirDiploManager = otherCivilization.getDiplomacyManager(ourCivilization)!!
ourDiploManager.apply {
trades.add(currentTrade)
updateHasOpenBorders()
}
theirDiploManger.apply {
theirDiploManager.apply {
trades.add(currentTrade.reverse())
updateHasOpenBorders()
}
@ -114,6 +126,10 @@ class TradeLogic(val ourCivilization: Civilization, val otherCivilization: Civil
// instant transfers
fun transferTrade(from: Civilization, to: Civilization, offer: TradeOffer) {
when (offer.type) {
TradeOfferType.Embassy -> {
for (tile in from.getCapital()!!.getCenterTile().getTilesInDistance(2))
tile.setExplored(to, true)
}
TradeOfferType.Gold -> {
to.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)
} else if (theirGoldValueOfTrade > ourGoldValueOfTrade) {
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 })
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.updateStatsForNextTurn()

View File

@ -6,6 +6,7 @@ package com.unciv.logic.trade
*/
@Suppress("EnumEntryName") // We do want the underscores in our names
enum class TradeOfferType(val numberType: TradeTypeNumberType, val isImmediate: Boolean) {
Embassy (TradeTypeNumberType.None, true),
Gold (TradeTypeNumberType.Gold, true),
Gold_Per_Turn (TradeTypeNumberType.Gold, false),
/** 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"))
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
EnablesOpenBorders("Enables Open Borders agreements", UniqueTarget.Global),
// Should the 'R' in 'Research agreements' be capitalized?

View File

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

View File

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