Add Defensive pact (#9900)

* Defensive Pacts can now be offered.

* Signing a defensive pact now makes the Civs join all defensive wars. Any future defensive wars will force the other civ to join.

* Removed Popup Alert Defensive Pact.

* Defensive pact and Research pact now are only on the trade screen.

* AI now offers defensive pacts.

* Added AI evaluating sending and receiving Defensive Pact offers.

* Reverted some temporary changes

* Reduced the chance an AI offers a defensive pact

* Starting an offensive war now cancels all Defensive Pacts with other civilisations.

* Removed extra requirements before an AI will sign or offer a defensive pact.

* Added Defensive Pacts to the Civilopedia.

* Fixed the AI counter offering with treaties.

* Fixed a test using the old method of checking if a civ is at war.

* Fixed a previous refactor error.

* Deleted commented out Research Agreement button code.

* Fixed some spelling errors and remnant debugging code.

* Removed signing a defensive pact brings both Civ's into each others previous defensive wars.

* Refactored setFriendshipBasedModifier to look better

* Starting an offensive war now removes the defensive pact form both sides.

* Reverted changes to DiplomaticStatus

* Removed extra technology check to sign a defensive pact.

* Removed DiplomacyManager.isAtWar() completely.

* Moved setting defensivePact flags from  TradeLogic.transferTrade() to DiplomacyManager.signDefensivePact.

* Changed diplomatic modifiers related to Defensive Pacts to be less extreme.

* Fixed canceling Defensive Pacts when declaring war and notifying other Civs.

* Updated the Defensive Pact entry in the Civilopedia and fixed some spelling.

* Fixed Defensive Pact behavior while attacking and defending.

* Changed a variable to a more readable name.

* Improved readability of setFriendshipBasedModifier().

* Moved the important onWarDeclared functionality to their own functions.

* Added a notification for the attacking Civ when a Civ joins war through a Defensive Pact.

* Refactored setDefensivePactBasedModifier() to be more readable.

* Increased DeclinedDefensivePact time.

* Deleted old commented code that removed the research agreement button.

* Fixed having reverting changes errors in UnitMovementTests.

* Refactored breaking treaties when declaring war.

* Removed unnecessary semicolons.
This commit is contained in:
Oskar Niesen 2023-08-16 09:59:41 -05:00 committed by GitHub
parent 65f884cb54
commit 548b536818
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 287 additions and 77 deletions

View File

@ -234,7 +234,8 @@
"name": "Chivalry",
"row": 6,
"prerequisites": ["Civil Service","Guilds"],
"quote": "'Whoso pulleth out this sword of this stone and anvil, is rightwise king born of all England.' - Malory"
"uniques": ["Enables Defensive Pacts"],
"quote": "'Whoso pulleth out this sword of this stone and anvil, is rightwise king born of all England.' - Malory"
},
{
"name": "Machinery",

View File

@ -214,8 +214,9 @@
"name": "Chivalry",
"row": 5,
"prerequisites": ["Civil Service","Currency","Horseback Riding"],
"uniques": ["Enables Defensive Pacts"],
"quote": "'Whoso pulleth out this sword of this stone and anvil, is rightwise king born of all England.' - Malory"
}, // Allows for defensive pacts
},
{
"name": "Machinery",
"row": 8,

View File

@ -198,6 +198,14 @@
"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!"
]
},
{
"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 attitude."
]
},
{
"name": "City-States",
"steps": [

View File

@ -43,6 +43,7 @@ object Constants {
const val peaceTreaty = "Peace Treaty"
const val researchAgreement = "Research Agreement"
const val openBorders = "Open Borders"
const val defensivePact = "Defensive Pact"
/** 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

@ -69,6 +69,7 @@ object NextTurnAutomation {
ReligionAutomation.spendFaithOnReligion(civInfo)
}
offerResearchAgreement(civInfo)
offerDefensivePact(civInfo)
exchangeLuxuries(civInfo)
issueRequests(civInfo)
adoptPolicy(civInfo) // todo can take a second - why?
@ -172,6 +173,8 @@ object NextTurnAutomation {
if (tradeLogic.currentTrade.theirOffers.any { it.type == offer.type && it.name == offer.name })
continue // So you don't get double offers of open borders declarations of war etc.
if (offer.type == TradeType.Treaty)
continue // Don't try to counter with a defensive pact or research pact
val value = evaluation.evaluateBuyCost(offer, civInfo, otherCiv)
if (value > 0)
@ -801,6 +804,29 @@ object NextTurnAutomation {
}
}
private 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).relationshipIgnoreAfraid() == RelationshipLevel.Ally
}
.sortedByDescending { it.stats.statsForNextTurn.science }
for (otherCiv in canSignDefensivePactCiv) {
// Default setting is 1, this will be changed according to different civ.
if ((1..10).random() > 1) continue
//todo: Add more in depth evaluation here
val tradeLogic = TradeLogic(civInfo, otherCiv)
tradeLogic.currentTrade.ourOffers.add(TradeOffer(Constants.defensivePact, TradeType.Treaty))
tradeLogic.currentTrade.theirOffers.add(TradeOffer(Constants.defensivePact, TradeType.Treaty))
otherCiv.tradeRequests.add(TradeRequest(civInfo.civName, tradeLogic.currentTrade.reverse()))
}
}
private fun declareWar(civInfo: Civilization) {
if (civInfo.wantsToFocusOn(Victory.Focus.Culture)) return
if (civInfo.cities.isEmpty() || civInfo.diplomacy.isEmpty()) return

View File

@ -87,7 +87,6 @@ class DiplomacyFunctions(val civInfo: Civilization){
}
}
fun canSignResearchAgreement(): Boolean {
if (!civInfo.isMajorCiv()) return false
if (!civInfo.hasUnique(UniqueType.EnablesResearchAgreements)) return false
@ -113,6 +112,21 @@ class DiplomacyFunctions(val civInfo: Civilization){
).toInt()
}
fun canSignDefensivePact(): Boolean {
if (!civInfo.isMajorCiv()) return false
if (!civInfo.hasUnique(UniqueType.EnablesDefensivePacts)) return false
return true
}
fun canSignDefensivePactWith(otherCiv: Civilization): Boolean {
val diplomacyManager = civInfo.getDiplomacyManager(otherCiv)
return canSignDefensivePact() && otherCiv.diplomacyFunctions.canSignDefensivePact()
&& diplomacyManager.hasFlag(DiplomacyFlags.DeclarationOfFriendship)
&& !diplomacyManager.hasFlag(DiplomacyFlags.DefensivePact)
&& !diplomacyManager.otherCivDiplomacy().hasFlag(DiplomacyFlags.DefensivePact)
&& diplomacyManager.diplomaticStatus != DiplomaticStatus.DefensivePact
}
/**
* @returns whether units of this civilization can pass through the tiles owned by [otherCiv],

View File

@ -44,6 +44,8 @@ enum class DiplomacyFlags {
DeclinedResearchAgreement,
DeclaredWar,
DeclarationOfFriendship,
DefensivePact,
DeclinedDefensivePact,
ResearchAgreement,
BorderConflict,
SettledCitiesNearUs,
@ -72,6 +74,8 @@ enum class DiplomaticModifiers(val text:String) {
CapturedOurCities("You have captured our cities!"),
DeclaredFriendshipWithOurEnemies("You have declared friendship with our enemies!"),
BetrayedDeclarationOfFriendship("Your so-called 'friendship' is worth nothing."),
SignedDefensivePactWithOurEnemies("You have declared a defensive pact with our enemies!"),
BetrayedDefensivePact("Your so-called 'defensive pact' is worth nothing."),
Denunciation("You have publicly denounced us!"),
DenouncedOurAllies("You have denounced our allies"),
RefusedToNotSettleCitiesNearUs("You refused to stop settling cities near us"),
@ -90,6 +94,8 @@ enum class DiplomaticModifiers(val text:String) {
LiberatedCity("We applaud your liberation of conquered cities!"),
DeclarationOfFriendship("We have signed a public declaration of friendship"),
DeclaredFriendshipWithOurAllies("You have declared friendship with our allies"),
DefensivePact("We have signed a promise to protect each other."),
SignedDefensivePactWithOurAllies("You have declared a defensive pact with our allies"),
DenouncedOurEnemies("You have denounced our enemies"),
OpenBorders("Our open borders have brought us closer together."),
FulfilledPromiseToNotSettleCitiesNearUs("You fulfilled your promise to stop settling cities near us!"),
@ -376,7 +382,7 @@ class DiplomacyManager() : IsPartOfGameInfoSerialization {
return max(0f, increment) * max(0f, modifierPercent).toPercent()
}
fun canDeclareWar() = turnsToPeaceTreaty() == 0 && diplomaticStatus != DiplomaticStatus.War
//Used for nuke
@ -591,6 +597,9 @@ class DiplomacyManager() : IsPartOfGameInfoSerialization {
if (!otherCivDiplomacy().hasFlag(DiplomacyFlags.ResearchAgreement))
scienceFromResearchAgreement()
}
DiplomacyFlags.DefensivePact.name -> {
diplomaticStatus = DiplomaticStatus.Peace
}
// This is confusingly named - in fact, the civ that has the flag set is the MAJOR civ
DiplomacyFlags.ProvideMilitaryUnit.name -> {
// Do not unset the flag - they may return soon, and we'll continue from that point on
@ -673,6 +682,7 @@ class DiplomacyManager() : IsPartOfGameInfoSerialization {
revertToZero(DiplomaticModifiers.WarMongerer, 1 / 2f) // warmongering gives a big negative boost when it happens but they're forgotten relatively quickly, like WWII amirite
revertToZero(DiplomaticModifiers.CapturedOurCities, 1 / 4f) // if you captured our cities, though, that's harder to forget
revertToZero(DiplomaticModifiers.BetrayedDeclarationOfFriendship, 1 / 8f) // That's a bastardly thing to do
revertToZero(DiplomaticModifiers.BetrayedDefensivePact, 1 / 16f) // That's an outrageous thing to do
revertToZero(DiplomaticModifiers.RefusedToNotSettleCitiesNearUs, 1 / 4f)
revertToZero(DiplomaticModifiers.BetrayedPromiseToNotSettleCitiesNearUs, 1 / 8f) // That's a bastardly thing to do
revertToZero(DiplomaticModifiers.UnacceptableDemands, 1 / 4f)
@ -688,9 +698,14 @@ class DiplomacyManager() : IsPartOfGameInfoSerialization {
setFriendshipBasedModifier()
setDefensivePactBasedModifier()
if (!hasFlag(DiplomacyFlags.DeclarationOfFriendship))
revertToZero(DiplomaticModifiers.DeclarationOfFriendship, 1 / 2f) //decreases slowly and will revert to full if it is declared later
if (!hasFlag(DiplomacyFlags.DefensivePact))
revertToZero(DiplomaticModifiers.DefensivePact, 1f)
if (!otherCiv().isCityState()) return
if (isRelationshipLevelLT(RelationshipLevel.Friend)) {
@ -714,41 +729,101 @@ class DiplomacyManager() : IsPartOfGameInfoSerialization {
}
/** Everything that happens to both sides equally when war is declared by one side on the other */
private fun onWarDeclared() {
private fun onWarDeclared(isOffensiveWar: Boolean) {
// Cancel all trades.
for (trade in trades)
for (offer in trade.theirOffers.filter { it.duration > 0 })
for (offer in trade.theirOffers.filter { it.duration > 0 && it.name != Constants.defensivePact})
civInfo.addNotification("[${offer.name}] from [$otherCivName] has ended",
NotificationCategory.Trade, otherCivName, NotificationIcon.Trade)
trades.clear()
updateHasOpenBorders()
val civAtWarWith = otherCiv()
if (civInfo.isCityState() && civInfo.cityStateFunctions.getProtectorCivs().contains(civAtWarWith)) {
civInfo.cityStateFunctions.removeProtectorCiv(civAtWarWith, forced = true)
// If we attacked, then we need to end all of our defensive pacts acording to Civ 5
if (isOffensiveWar) {
removeDefensivePacts()
}
diplomaticStatus = DiplomaticStatus.War
if (civInfo.isMajorCiv()) {
if (!isOffensiveWar) callInDefensivePactAllies()
callInCityStateAllies()
}
if (civInfo.isCityState() && civInfo.cityStateFunctions.getProtectorCivs().contains(civAtWarWith)) {
civInfo.cityStateFunctions.removeProtectorCiv(civAtWarWith, forced = true)
}
updateHasOpenBorders()
removeModifier(DiplomaticModifiers.YearsOfPeace)
setFlag(DiplomacyFlags.DeclinedPeace, 10)/// AI won't propose peace for 10 turns
setFlag(DiplomacyFlags.DeclaredWar, 10) // AI won't agree to trade for 10 turns
removeFlag(DiplomacyFlags.BorderConflict)
}
/**
* Removes all defensive Pacts and trades. Notifies other civs.
* Note: Does not remove the flags and modifiers of the otherCiv if there is a defensive pact.
* This is so that we can apply more negative modifiers later.
*/
private fun removeDefensivePacts() {
val civAtWarWith = otherCiv()
civInfo.diplomacy.values.filter { it.diplomaticStatus == DiplomaticStatus.DefensivePact }.forEach {
// We already removed the trades and we don't want to remove the flags yet.
if (it.otherCiv() != civAtWarWith) {
// Trades with defensive pact are now invalid
val defensivePactOffer = it.trades.firstOrNull { trade -> trade.ourOffers.any { offer -> offer.name == Constants.defensivePact } }
it.trades.remove(defensivePactOffer)
val theirDefensivePactOffer = it.otherCivDiplomacy().trades.firstOrNull { trade -> trade.ourOffers.any { offer -> offer.name == Constants.defensivePact } }
it.otherCivDiplomacy().trades.remove(theirDefensivePactOffer)
it.removeFlag(DiplomacyFlags.DefensivePact)
it.otherCivDiplomacy().removeFlag(DiplomacyFlags.DefensivePact)
it.diplomaticStatus = DiplomaticStatus.Peace
it.otherCivDiplomacy().diplomaticStatus = DiplomaticStatus.Peace
}
for (civ in getCommonKnownCivs().filter { civ -> civ.isMajorCiv() }) {
civ.addNotification("[${civInfo.civName}] canceled their Defensive Pact with [${it.otherCivName}]!",
NotificationCategory.Diplomacy, civInfo.civName, NotificationIcon.Diplomacy, it.otherCivName)
}
civInfo.addNotification("We have canceled our Defensive Pact with [${it.otherCivName}]!",
NotificationCategory.Diplomacy, NotificationIcon.Diplomacy, it.otherCivName)
it.otherCiv().addNotification("[${civInfo.civName}] has canceled our Defensive Pact with us!",
NotificationCategory.Diplomacy, civInfo.civName, NotificationIcon.Diplomacy)
}
}
// Go through city state allies.
if (!civInfo.isCityState()) {
for (thirdCiv in civInfo.getKnownCivs()
.filter { it.isCityState() && it.getAllyCiv() == civInfo.civName }) {
/**
* Goes through each DiplomacyManager with a defensive pact that is not already in the war.
* The civ that we are calling them in against should no longer have a defensive pact with us.
*/
private fun callInDefensivePactAllies() {
val civAtWarWith = otherCiv()
for (ourDefensivePact in civInfo.diplomacy.values.filter { ourDipManager ->
ourDipManager.diplomaticStatus == DiplomaticStatus.DefensivePact
&& !ourDipManager.otherCiv().isDefeated()
&& !ourDipManager.otherCiv().isAtWarWith(civAtWarWith)
}) {
val ally = ourDefensivePact.otherCiv()
// Have the aggressor declare war on the ally.
civAtWarWith.getDiplomacyManager(ally).declareWar(true)
// Notify the aggressor
civAtWarWith.addNotification("[${ally.civName}] has joined the defensive war with [${civInfo.civName}]!",
NotificationCategory.Diplomacy, ally.civName, NotificationIcon.Diplomacy, civInfo.civName)
}
}
private fun callInCityStateAllies() {
val civAtWarWith = otherCiv()
for (thirdCiv in civInfo.getKnownCivs()
.filter { it.isCityState() && it.getAllyCiv() == civInfo.civName }) {
if (thirdCiv.knows(civAtWarWith) && !thirdCiv.isAtWarWith(civAtWarWith))
thirdCiv.getDiplomacyManager(civAtWarWith).declareWar(true)
else if (!thirdCiv.knows(civAtWarWith)) {
// Our city state ally has not met them yet, so they have to meet first
thirdCiv.diplomacyFunctions.makeCivilizationsMeet(civAtWarWith, warOnContact = true)
thirdCiv.getDiplomacyManager(civAtWarWith).declareWar(true)
}
if (thirdCiv.knows(civAtWarWith) && !thirdCiv.isAtWarWith(civAtWarWith))
thirdCiv.getDiplomacyManager(civAtWarWith).declareWar(true)
else if (!thirdCiv.knows(civAtWarWith)) {
// Our city state ally has not met them yet, so they have to meet first
thirdCiv.diplomacyFunctions.makeCivilizationsMeet(civAtWarWith, warOnContact = true)
thirdCiv.getDiplomacyManager(civAtWarWith).declareWar(true)
}
}
}
@ -780,8 +855,8 @@ class DiplomacyManager() : IsPartOfGameInfoSerialization {
}
}
onWarDeclared()
otherCivDiplomacy.onWarDeclared()
onWarDeclared(true)
otherCivDiplomacy.onWarDeclared(false)
otherCiv.addNotification("[${civInfo.civName}] has declared war on us!",
NotificationCategory.Diplomacy, NotificationIcon.War, civInfo.civName)
@ -801,29 +876,57 @@ class DiplomacyManager() : IsPartOfGameInfoSerialization {
else thirdCiv.getDiplomacyManager(civInfo).addModifier(DiplomaticModifiers.SharedEnemy, 5f)
} else thirdCiv.getDiplomacyManager(civInfo).addModifier(DiplomaticModifiers.WarMongerer, -5f)
}
breakTreaties()
if (otherCiv.isMajorCiv())
for (unique in civInfo.getTriggeredUniques(UniqueType.TriggerUponDeclaringWar))
UniqueTriggerActivation.triggerCivwideUnique(unique, civInfo)
}
private fun breakTreaties() {
val otherCiv = otherCiv()
val otherCivDiplomacy = otherCivDiplomacy()
var betrayedFriendship = false
var betrayedDefensivePact = false
if (hasFlag(DiplomacyFlags.DeclarationOfFriendship)) {
betrayedFriendship = true
removeFlag(DiplomacyFlags.DeclarationOfFriendship)
otherCivDiplomacy.removeModifier(DiplomaticModifiers.DeclarationOfFriendship)
for (knownCiv in civInfo.getKnownCivs()) {
val amount = if (knownCiv == otherCiv) -40f else -20f
val diploManager = knownCiv.getDiplomacyManager(civInfo)
diploManager.addModifier(DiplomaticModifiers.BetrayedDeclarationOfFriendship, amount)
diploManager.removeModifier(DiplomaticModifiers.DeclaredFriendshipWithOurAllies) // obviously this guy's declarations of friendship aren't worth much.
}
}
otherCivDiplomacy.removeFlag(DiplomacyFlags.DeclarationOfFriendship)
if (hasFlag(DiplomacyFlags.DefensivePact)) {
betrayedDefensivePact = true
removeFlag(DiplomacyFlags.DefensivePact)
otherCivDiplomacy.removeModifier(DiplomaticModifiers.DefensivePact)
}
otherCivDiplomacy.removeFlag(DiplomacyFlags.DefensivePact)
if (betrayedFriendship || betrayedDefensivePact) {
for (knownCiv in civInfo.getKnownCivs()) {
val diploManager = knownCiv.getDiplomacyManager(civInfo)
if (betrayedFriendship) {
val amount = if (knownCiv == otherCiv) -40f else -20f
diploManager.addModifier(DiplomaticModifiers.BetrayedDeclarationOfFriendship, amount)
}
if (betrayedDefensivePact) {
//Note: this stacks with Declaration of Friendship
val amount = if (knownCiv == otherCiv) -20f else -10f
diploManager.addModifier(DiplomaticModifiers.BetrayedDefensivePact, amount)
}
diploManager.removeModifier(DiplomaticModifiers.DeclaredFriendshipWithOurAllies) // obviously this guy's declarations of friendship aren't worth much.
diploManager.removeModifier(DiplomaticModifiers.SignedDefensivePactWithOurAllies)
}
}
if (hasFlag(DiplomacyFlags.ResearchAgreement)) {
removeFlag(DiplomacyFlags.ResearchAgreement)
totalOfScienceDuringRA = 0
otherCivDiplomacy.totalOfScienceDuringRA = 0
}
otherCivDiplomacy.removeFlag(DiplomacyFlags.ResearchAgreement)
if (otherCiv.isMajorCiv())
for (unique in civInfo.getTriggeredUniques(UniqueType.TriggerUponDeclaringWar))
UniqueTriggerActivation.triggerCivwideUnique(unique, civInfo)
}
/** Should only be called from makePeace */
@ -919,16 +1022,70 @@ class DiplomacyManager() : IsPartOfGameInfoSerialization {
removeModifier(DiplomaticModifiers.DeclaredFriendshipWithOurEnemies)
for (thirdCiv in getCommonKnownCivs()
.filter { it.getDiplomacyManager(civInfo).hasFlag(DiplomacyFlags.DeclarationOfFriendship) }) {
when (otherCiv().getDiplomacyManager(thirdCiv).relationshipIgnoreAfraid()) {
RelationshipLevel.Unforgivable -> addModifier(DiplomaticModifiers.DeclaredFriendshipWithOurEnemies, -15f)
RelationshipLevel.Enemy -> addModifier(DiplomaticModifiers.DeclaredFriendshipWithOurEnemies, -5f)
RelationshipLevel.Friend -> addModifier(DiplomaticModifiers.DeclaredFriendshipWithOurAllies, 5f)
RelationshipLevel.Ally -> addModifier(DiplomaticModifiers.DeclaredFriendshipWithOurAllies, 15f)
else -> {}
val relationshipLevel = otherCiv().getDiplomacyManager(thirdCiv).relationshipIgnoreAfraid()
val modifierType = when (relationshipLevel) {
RelationshipLevel.Unforgivable, RelationshipLevel.Enemy -> DiplomaticModifiers.DeclaredFriendshipWithOurEnemies
else -> DiplomaticModifiers.DeclaredFriendshipWithOurAllies
}
val modifierValue = when (relationshipLevel) {
RelationshipLevel.Unforgivable -> -15f
RelationshipLevel.Enemy -> -5f
RelationshipLevel.Friend -> 5f
RelationshipLevel.Ally -> 15f
else -> 0f
}
addModifier(modifierType, modifierValue)
}
}
fun signDefensivePact(duration: Int) {
//Note: These modifiers are additive to the friendship modifiers
setModifier(DiplomaticModifiers.DefensivePact, 10f)
otherCivDiplomacy().setModifier(DiplomaticModifiers.DefensivePact, 10f)
setFlag(DiplomacyFlags.DefensivePact, duration)
otherCivDiplomacy().setFlag(DiplomacyFlags.DefensivePact, duration)
diplomaticStatus = DiplomaticStatus.DefensivePact
otherCivDiplomacy().diplomaticStatus = DiplomaticStatus.DefensivePact
for (thirdCiv in getCommonKnownCivs().filter { it.isMajorCiv() }) {
thirdCiv.addNotification("[${civInfo.civName}] and [$otherCivName] have signed the Defensive Pact!",
NotificationCategory.Diplomacy, civInfo.civName, NotificationIcon.Diplomacy, otherCivName)
thirdCiv.getDiplomacyManager(civInfo).setDefensivePactBasedModifier()
}
// Ignore contitionals as triggerCivwideUnique will check again, and that would break
// UniqueType.ConditionalChance - 25% declared chance would work as 6% actual chance
for (unique in civInfo.getTriggeredUniques(UniqueType.TriggerUponSigningDefensivePact, StateForConditionals.IgnoreConditionals))
UniqueTriggerActivation.triggerCivwideUnique(unique, civInfo)
for (unique in otherCiv().getTriggeredUniques(UniqueType.TriggerUponSigningDefensivePact, StateForConditionals.IgnoreConditionals))
UniqueTriggerActivation.triggerCivwideUnique(unique, otherCiv())
}
private fun setDefensivePactBasedModifier() {
removeModifier(DiplomaticModifiers.SignedDefensivePactWithOurAllies)
removeModifier(DiplomaticModifiers.SignedDefensivePactWithOurEnemies)
for (thirdCiv in getCommonKnownCivs()
.filter { it.getDiplomacyManager(civInfo).hasFlag(DiplomacyFlags.DefensivePact) }) {
//Note: These modifiers are additive to the friendship modifiers
val relationshipLevel = otherCiv().getDiplomacyManager(thirdCiv).relationshipIgnoreAfraid()
val modifierType = when (relationshipLevel) {
RelationshipLevel.Unforgivable, RelationshipLevel.Enemy -> DiplomaticModifiers.SignedDefensivePactWithOurEnemies
else -> DiplomaticModifiers.SignedDefensivePactWithOurAllies
}
val modifierValue = when (relationshipLevel) {
RelationshipLevel.Unforgivable -> -15f
RelationshipLevel.Enemy -> -10f
RelationshipLevel.Friend -> 2f
RelationshipLevel.Ally -> 5f
else -> 0f
}
addModifier(modifierType, modifierValue)
}
}
fun denounce() {
setModifier(DiplomaticModifiers.Denunciation, -35f)
otherCivDiplomacy().setModifier(DiplomaticModifiers.Denunciation, -35f)

View File

@ -5,5 +5,6 @@ import com.unciv.logic.IsPartOfGameInfoSerialization
enum class DiplomaticStatus : IsPartOfGameInfoSerialization {
Peace,
Protector, //city state's diplomacy for major civ can be marked as Protector, not vice versa.
War
War,
DefensivePact,
}

View File

@ -60,6 +60,9 @@ class TradeRequest : IsPartOfGameInfoSerialization {
diplomacyManager.setFlag(DiplomacyFlags.DeclinedLuxExchange,20)
if (trade.ourOffers.any { it.name == Constants.researchAgreement })
diplomacyManager.setFlag(DiplomacyFlags.DeclinedResearchAgreement,20)
if (trade.ourOffers.any { it.name == Constants.defensivePact })
diplomacyManager.setFlag(DiplomacyFlags.DeclinedDefensivePact,10)
if (trade.isPeaceTreaty()) diplomacyManager.setFlag(DiplomacyFlags.DeclinedPeace, 5)
requestingCivInfo.addNotification("[${decliningCiv.civName}] has denied your trade request",

View File

@ -5,6 +5,7 @@ import com.unciv.logic.automation.Automation
import com.unciv.logic.automation.ThreatLevel
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.RelationshipLevel
import com.unciv.logic.map.tile.Tile
import com.unciv.models.ruleset.ModOptionsConstants
@ -74,13 +75,20 @@ class TradeEvaluation {
var sumOfOurOffers = trade.ourOffers.sumOf { evaluateSellCost(it, evaluator, tradePartner) }
val relationshipLevel = evaluator.getDiplomacyManager(tradePartner).relationshipIgnoreAfraid()
// If we're making a peace treaty, don't try to up the bargain for people you don't like.
// Leads to spartan behaviour where you demand more, the more you hate the enemy...unhelpful
if (trade.ourOffers.none { it.name == Constants.peaceTreaty || it.name == Constants.researchAgreement }) {
val relationshipLevel = evaluator.getDiplomacyManager(tradePartner).relationshipIgnoreAfraid()
if (trade.ourOffers.none { it.name == Constants.peaceTreaty || it.name == Constants.researchAgreement}) {
if (relationshipLevel == RelationshipLevel.Enemy) sumOfOurOffers = (sumOfOurOffers * 1.5).toInt()
else if (relationshipLevel == RelationshipLevel.Unforgivable) sumOfOurOffers *= 2
}
if (trade.ourOffers.firstOrNull { it.name == Constants.defensivePact } != null) {
if (relationshipLevel == RelationshipLevel.Ally) {
//todo: Add more in depth evaluation here
} else {
return Int.MIN_VALUE
}
}
return sumOfTheirOffers - sumOfOurOffers
}
@ -195,6 +203,7 @@ class TradeEvaluation {
Constants.peaceTreaty -> evaluatePeaceCostForThem(civInfo, tradePartner)
Constants.researchAgreement -> -offer.amount
else -> 1000
//Todo:AddDefensiveTreatyHere
}
}
TradeType.Luxury_Resource -> {

View File

@ -7,6 +7,7 @@ import com.unciv.logic.civilization.PopupAlert
import com.unciv.logic.civilization.diplomacy.CityStateFunctions
import com.unciv.logic.civilization.diplomacy.DiplomacyFlags
import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers
import com.unciv.logic.civilization.diplomacy.DiplomaticStatus
import com.unciv.models.ruleset.ModOptionsConstants
import com.unciv.models.ruleset.tile.ResourceType
import com.unciv.models.ruleset.unique.UniqueType
@ -31,6 +32,12 @@ class TradeLogic(val ourCivilization:Civilization, val otherCivilization: Civili
offers.add(TradeOffer(Constants.openBorders, TradeType.Agreement))
}
if (civInfo.diplomacyFunctions.canSignResearchAgreementsWith(otherCivilization))
offers.add(TradeOffer(Constants.researchAgreement, TradeType.Treaty, civInfo.diplomacyFunctions.getResearchAgreementCost()))
if (civInfo.diplomacyFunctions.canSignDefensivePactWith(otherCivilization))
offers.add(TradeOffer(Constants.defensivePact, TradeType.Treaty))
for (entry in civInfo.getCivResourcesWithOriginsForTrade()
.filterNot { it.resource.resourceType == ResourceType.Bonus }
.filter { it.origin == Constants.tradable }
@ -123,6 +130,7 @@ class TradeLogic(val ourCivilization:Civilization, val otherCivilization: Civili
to.getDiplomacyManager(from)
.setFlag(DiplomacyFlags.ResearchAgreement, offer.duration)
}
if (offer.name == Constants.defensivePact) from.getDiplomacyManager(to).signDefensivePact(offer.duration);
}
TradeType.Introduction -> to.diplomacyFunctions.makeCivilizationsMeet(to.gameInfo.getCivilization(offer.name))
TradeType.WarDeclaration -> {

View File

@ -197,6 +197,7 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags:
// Should the 'R' in 'Research agreements' be capitalized?
EnablesResearchAgreements("Enables Research agreements", UniqueTarget.Global),
ScienceFromResearchAgreements("Science gained from research agreements [relativeAmount]%", UniqueTarget.Global),
EnablesDefensivePacts("Enables Defensive Pacts", UniqueTarget.Global),
GreatPersonBoostWithFriendship("When declaring friendship, both parties gain a [relativeAmount]% boost to great person generation", UniqueTarget.Global),
/// City State Influence
@ -730,6 +731,7 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags:
TriggerUponAdoptingPolicyOrBelief("upon adopting [policy/belief]", UniqueTarget.TriggerCondition),
TriggerUponDeclaringWar("upon declaring war with a major Civilization", UniqueTarget.TriggerCondition),
TriggerUponDeclaringFriendship("upon declaring friendship", UniqueTarget.TriggerCondition),
TriggerUponSigningDefensivePact("upon declaring a defensive pact", UniqueTarget.TriggerCondition),
TriggerUponEnteringGoldenAge("upon entering a Golden Age", UniqueTarget.TriggerCondition),
/** Can be placed upon both units and as global */
TriggerUponConqueringCity("upon conquering a city", UniqueTarget.TriggerCondition, UniqueTarget.UnitTriggerCondition),

View File

@ -658,10 +658,6 @@ class DiplomacyScreen(
if (!diplomacyManager.hasFlag(DiplomacyFlags.DeclarationOfFriendship))
diplomacyTable.add(getDeclareFriendshipButton(otherCiv)).row()
if (viewingCiv.diplomacyFunctions.canSignResearchAgreementsWith(otherCiv))
diplomacyTable.add(getResearchAgreementButton(otherCiv)).row()
if (!diplomacyManager.hasFlag(DiplomacyFlags.Denunciation)
&& !diplomacyManager.hasFlag(DiplomacyFlags.DeclarationOfFriendship)
) diplomacyTable.add(getDenounceButton(otherCiv, diplomacyManager)).row()
@ -744,30 +740,7 @@ class DiplomacyScreen(
if (isNotPlayersTurn()) denounceButton.disable()
return denounceButton
}
private fun getResearchAgreementButton(otherCiv: Civilization): TextButton {
val researchAgreementButton = "Research Agreement".toTextButton()
val requiredGold = viewingCiv.diplomacyFunctions.getResearchAgreementCost()
researchAgreementButton.onClick {
val tradeTable = setTrade(otherCiv)
val researchAgreement =
TradeOffer(Constants.researchAgreement, TradeType.Treaty, requiredGold)
val goldCostOfSignResearchAgreement =
TradeOffer("Gold".tr(), TradeType.Gold, -requiredGold)
tradeTable.tradeLogic.currentTrade.theirOffers.add(researchAgreement)
tradeTable.tradeLogic.ourAvailableOffers.add(researchAgreement)
tradeTable.tradeLogic.ourAvailableOffers.add(goldCostOfSignResearchAgreement)
tradeTable.tradeLogic.currentTrade.ourOffers.add(researchAgreement)
tradeTable.tradeLogic.theirAvailableOffers.add(researchAgreement)
tradeTable.tradeLogic.theirAvailableOffers.add(goldCostOfSignResearchAgreement)
tradeTable.offerColumnsTable.update()
tradeTable.enableOfferButton(true)
}
if (isNotPlayersTurn()) researchAgreementButton.disable()
return researchAgreementButton
}
private fun getDeclareFriendshipButton(otherCiv: Civilization): TextButton {
val declareFriendshipButton =
"Offer Declaration of Friendship ([30] turns)".toTextButton()
@ -791,8 +764,6 @@ class DiplomacyScreen(
val tradeButton = "Trade".toTextButton()
tradeButton.onClick {
setTrade(otherCiv).apply {
tradeLogic.ourAvailableOffers.apply { remove(firstOrNull { it.type == TradeType.Treaty }) }
tradeLogic.theirAvailableOffers.apply { remove(firstOrNull { it.type == TradeType.Treaty }) }
offerColumnsTable.update()
}
}

View File

@ -103,11 +103,13 @@ class OffersListScroll(
}
val amountPerClick =
if (offer.type == Gold) 50
else 1
when (offer.type) {
Gold -> 50
Treaty -> Int.MAX_VALUE
else -> 1
}
if (offer.isTradable() && offer.name != Constants.peaceTreaty && // can't disable peace treaty!
offer.name != Constants.researchAgreement) {
if (offer.isTradable() && offer.name != Constants.peaceTreaty) { // can't disable peace treaty!
// highlight unique suggestions
if (offerType in listOf(Luxury_Resource, Strategic_Resource)

View File

@ -629,6 +629,9 @@ Simple unique parameters are explained by mouseover. Complex parameters are expl
Applicable to: Global
??? example "Enables Defensive Pacts"
Applicable to: Global
??? example "When declaring friendship, both parties gain a [relativeAmount]% boost to great person generation"
Example: "When declaring friendship, both parties gain a [+20]% boost to great person generation"
@ -2022,6 +2025,9 @@ Simple unique parameters are explained by mouseover. Complex parameters are expl
??? example "<upon declaring friendship>"
Applicable to: TriggerCondition
??? example "<upon declaring a defensive pact>"
Applicable to: TriggerCondition
??? example "<upon entering a Golden Age>"
Applicable to: TriggerCondition