Proper implementation of pledge to protect (#5165)

* penalties for attacking

* proper timers on penalties

* template.properties

* space

* Player can pick sides when protected civs attacked/bullied
Protect gives resting point 10 for influence

* correct string
This commit is contained in:
SimonCeder
2021-09-11 20:10:45 +02:00
committed by GitHub
parent 45e87688cf
commit d59fe45f51
8 changed files with 216 additions and 15 deletions

View File

@ -103,6 +103,11 @@ Do you want to break your promise to [leaderName]? =
We promised not to settle near them ([count] turns remaining) =
They promised not to settle near us ([count] turns remaining) =
[civName] is upset that you demanded tribute from [cityState], whom they have pledged to protect! =
[civName] is upset that you attacked [cityState], whom they have pledged to protect! =
[civName] is outraged that you destroyed [cityState], whom they had pledged to protect! =
[civName] has destroyed [cityState], whom you had pledged to protect! =
Unforgivable =
Afraid =
Enemy =
@ -139,12 +144,20 @@ Your arrogant demands are in bad taste =
Your use of nuclear weapons is disgusting! =
You have stolen our lands! =
You gave us units! =
You destroyed City States that were under our protection! =
You attacked City States that were under our protection! =
You demanded tribute from City States that were under our protection! =
You sided with a City State over us =
Demands =
Please don't settle new cities near us. =
Very well, we shall look for new lands to settle. =
We shall do as we please. =
We noticed your new city near our borders, despite your promise. This will have....implications. =
I've been informed that my armies have taken tribute from [civName], a city-state under your protection.\nI assure you, this was quite unintentional, and I hope that this does not serve to drive us apart. =
We asked [civName] for a tribute recently and they gave in.\nYou promised to protect them from such things, but we both know you cannot back that up. =
It's come to my attention that I may have attacked [civName], a city-state under your protection.\nWhile it was not my goal to be at odds with your empire, this was deemed a necessary course of action. =
I thought you might like to know that I've launched an invasion of one of your little pet states.\nThe lands of [civName] will make a fine addition to my own. =
Enter the amount of gold =
@ -168,6 +181,7 @@ Gift Improvement =
Diplomatic Marriage ([amount] Gold) =
We have married into the ruling family of [civName], bringing them under our control. =
[civName] has married into the ruling family of [civName2], bringing them under their control. =
You have broken your Pledge to Protect [civName]! =
Cultured =
Maritime =

View File

@ -495,6 +495,8 @@ object Battle {
fun destroyIfDefeated(attackedCiv: CivilizationInfo, attacker: CivilizationInfo) {
if (attackedCiv.isDefeated()) {
if (attackedCiv.isCityState())
attackedCiv.cityStateDestroyed(attacker)
attackedCiv.destroy()
attacker.popupAlerts.add(PopupAlert(AlertType.Defeated, attackedCiv.civName))
}

View File

@ -3,6 +3,7 @@ package com.unciv.logic.civilization
import com.unciv.Constants
import com.unciv.logic.automation.NextTurnAutomation
import com.unciv.logic.civilization.diplomacy.DiplomacyFlags
import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers
import com.unciv.logic.civilization.diplomacy.DiplomaticStatus
import com.unciv.logic.civilization.diplomacy.RelationshipLevel
import com.unciv.models.metadata.GameSpeed
@ -152,8 +153,8 @@ class CityStateFunctions(val civInfo: CivilizationInfo) {
diplomacy.setFlag(DiplomacyFlags.RecentlyPledgedProtection, 10) // Can't break for 10 turns
}
fun removeProtectorCiv(otherCiv: CivilizationInfo) {
if(!otherCivCanWithdrawProtection(otherCiv))
fun removeProtectorCiv(otherCiv: CivilizationInfo, forced: Boolean = false) {
if(!otherCivCanWithdrawProtection(otherCiv) && !forced)
return
val diplomacy = civInfo.getDiplomacyManager(otherCiv)
@ -378,6 +379,7 @@ class CityStateFunctions(val civInfo: CivilizationInfo) {
val goldAmount = goldGainedByTribute()
demandingCiv.addGold(goldAmount)
civInfo.getDiplomacyManager(demandingCiv).addInfluence(-15f)
cityStateBullied(demandingCiv)
civInfo.addFlag(CivFlags.RecentlyBullied.name, 20)
updateAllyCivForCityState()
civInfo.updateStatsForNextTurn()
@ -395,6 +397,7 @@ class CityStateFunctions(val civInfo: CivilizationInfo) {
demandingCiv.placeUnitNearTile(civInfo.getCapital().location, buildableWorkerLikeUnits.keys.random())
civInfo.getDiplomacyManager(demandingCiv).addInfluence(-50f)
cityStateBullied(demandingCiv)
civInfo.addFlag(CivFlags.RecentlyBullied.name, 20)
updateAllyCivForCityState()
}
@ -478,4 +481,74 @@ class CityStateFunctions(val civInfo: CivilizationInfo) {
diplomacy.setFlag(DiplomacyFlags.AngerFreeIntrusion, 5)
}
/** A city state was bullied. What are its protectors going to do about it??? */
private fun cityStateBullied(bully: CivilizationInfo) {
if (!civInfo.isCityState()) return // What are we doing here?
for (protector in civInfo.getProtectorCivs()) {
if (!protector.knows(bully)) // Who?
continue
val protectorDiplomacy = protector.getDiplomacyManager(bully)
if (protectorDiplomacy.hasModifier(DiplomaticModifiers.BulliedProtectedMinor)
&& protectorDiplomacy.getFlag(DiplomacyFlags.RememberBulliedProtectedMinor) > 50)
protectorDiplomacy.addModifier(DiplomaticModifiers.BulliedProtectedMinor, -10f) // Penalty less severe for second offence
else
protectorDiplomacy.addModifier(DiplomaticModifiers.BulliedProtectedMinor, -15f)
protectorDiplomacy.setFlag(DiplomacyFlags.RememberBulliedProtectedMinor, 75) // Reset their memory
if (protector.playerType != PlayerType.Human) // Humans can have their own emotions
bully.addNotification("[${protector.civName}] is upset that you demanded tribute from [${civInfo.civName}], whom they have pledged to protect!",
NotificationIcon.Diplomacy, protector.civName)
else // Let humans choose who to side with
protector.popupAlerts.add(PopupAlert(AlertType.BulliedProtectedMinor,
bully.civName + "@" + civInfo.civName)) // we need to pass both civs as argument, hence the horrible chimera
}
}
/** A city state was attacked. What are its protectors going to do about it??? */
fun cityStateAttacked(attacker: CivilizationInfo) {
if (!civInfo.isCityState()) return // What are we doing here?
for (protector in civInfo.getProtectorCivs()) {
if (!protector.knows(attacker)) // Who?
continue
val protectorDiplomacy = protector.getDiplomacyManager(attacker)
if (protectorDiplomacy.hasModifier(DiplomaticModifiers.AttackedProtectedMinor)
&& protectorDiplomacy.getFlag(DiplomacyFlags.RememberAttackedProtectedMinor) > 50)
protectorDiplomacy.addModifier(DiplomaticModifiers.AttackedProtectedMinor, -15f) // Penalty less severe for second offence
else
protectorDiplomacy.addModifier(DiplomaticModifiers.AttackedProtectedMinor, -20f)
protectorDiplomacy.setFlag(DiplomacyFlags.RememberAttackedProtectedMinor, 75) // Reset their memory
if (protector.playerType != PlayerType.Human) // Humans can have their own emotions
attacker.addNotification("[${protector.civName}] is upset that you attacked [${civInfo.civName}], whom they have pledged to protect!",
NotificationIcon.Diplomacy, protector.civName)
else // Let humans choose who to side with
protector.popupAlerts.add(PopupAlert(AlertType.AttackedProtectedMinor,
attacker.civName + "@" + civInfo.civName)) // we need to pass both civs as argument, hence the horrible chimera
}
}
/** A city state was destroyed. Its protectors are going to be upset! */
fun cityStateDestroyed(attacker: CivilizationInfo) {
if (!civInfo.isCityState()) return // What are we doing here?
for (protector in civInfo.getProtectorCivs()) {
if (!protector.knows(attacker)) // Who?
continue
val protectorDiplomacy = protector.getDiplomacyManager(attacker)
if (protectorDiplomacy.hasModifier(DiplomaticModifiers.DestroyedProtectedMinor))
protectorDiplomacy.addModifier(DiplomaticModifiers.DestroyedProtectedMinor, -10f) // Penalty less severe for second offence
else
protectorDiplomacy.addModifier(DiplomaticModifiers.DestroyedProtectedMinor, -40f) // Oof
protectorDiplomacy.setFlag(DiplomacyFlags.RememberDestroyedProtectedMinor, 125) // Reset their memory
if (protector.playerType != PlayerType.Human) // Humans can have their own emotions
attacker.addNotification("[${protector.civName}] is outraged that you destroyed [${civInfo.civName}], whom they had pledged to protect!",
NotificationIcon.Diplomacy, protector.civName)
protector.addNotification("[${attacker.civName}] has destroyed [${civInfo.civName}], whom you had pledged to protect!", attacker.civName,
NotificationIcon.Death, civInfo.civName)
}
}
}

View File

@ -916,8 +916,8 @@ class CivilizationInfo {
fun addProtectorCiv(otherCiv: CivilizationInfo) {
cityStateFunctions.addProtectorCiv(otherCiv)
}
fun removeProtectorCiv(otherCiv: CivilizationInfo) {
cityStateFunctions.removeProtectorCiv(otherCiv)
fun removeProtectorCiv(otherCiv: CivilizationInfo, forced: Boolean = false) {
cityStateFunctions.removeProtectorCiv(otherCiv, forced)
}
fun otherCivCanPledgeProtection(otherCiv: CivilizationInfo) = cityStateFunctions.otherCivCanPledgeProtection(otherCiv)
fun otherCivCanWithdrawProtection(otherCiv: CivilizationInfo) = cityStateFunctions.otherCivCanWithdrawProtection(otherCiv)
@ -954,6 +954,13 @@ class CivilizationInfo {
fun getAllyCiv() = allyCivName
fun setAllyCiv(newAllyName: String?) { allyCivName = newAllyName }
fun cityStateAttacked(attacker: CivilizationInfo) {
cityStateFunctions.cityStateAttacked(attacker)
}
fun cityStateDestroyed(attacker: CivilizationInfo) {
cityStateFunctions.cityStateDestroyed(attacker)
}
//endregion
}

View File

@ -14,6 +14,8 @@ enum class AlertType {
DeclarationOfFriendship,
StartIntro,
DiplomaticMarriage,
BulliedProtectedMinor,
AttackedProtectedMinor,
}
class PopupAlert {

View File

@ -45,6 +45,10 @@ enum class DiplomacyFlags {
RecentlyPledgedProtection,
RecentlyWithdrewProtection,
AngerFreeIntrusion,
RememberDestroyedProtectedMinor,
RememberAttackedProtectedMinor,
RememberBulliedProtectedMinor,
RememberSidedWithProtectedMinor,
Denunciation
}
@ -70,7 +74,11 @@ enum class DiplomaticModifiers {
DenouncedOurEnemies,
OpenBorders,
FulfilledPromiseToNotSettleCitiesNearUs,
GaveUsUnits
GaveUsUnits,
DestroyedProtectedMinor,
AttackedProtectedMinor,
BulliedProtectedMinor,
SidedWithProtectedMinor,
}
class DiplomacyManager() {
@ -140,7 +148,13 @@ class DiplomacyManager() {
return 0
}
fun opinionOfOtherCiv() = diplomaticModifiers.values.sum()
fun opinionOfOtherCiv(): Float {
var modifierSum = diplomaticModifiers.values.sum()
// Angry about attacked CS and destroyed CS do not stack
if (hasModifier(DiplomaticModifiers.DestroyedProtectedMinor) && hasModifier(DiplomaticModifiers.AttackedProtectedMinor))
modifierSum -= getModifier(DiplomaticModifiers.AttackedProtectedMinor)
return modifierSum
}
fun relationshipLevel(): RelationshipLevel {
if (civInfo.isPlayerCivilization() && otherCiv().isPlayerCivilization())
@ -223,7 +237,7 @@ class DiplomacyManager() {
if (otherCiv().religionManager.religion?.name == civInfo.getCapital().religion.getMajorityReligionName())
restingPoint += unique.params[0].toInt()
if (diplomaticStatus == DiplomaticStatus.Protector) restingPoint += 5
if (diplomaticStatus == DiplomaticStatus.Protector) restingPoint += 10
return restingPoint
}
@ -458,6 +472,18 @@ class DiplomacyManager() {
if (flag == DiplomacyFlags.ResearchAgreement.name)
totalOfScienceDuringRA += civInfo.statsForNextTurn.science.toInt()
// These modifiers decrease slightly @ 50
if (flagsCountdown[flag] == 50) {
when (flag) {
DiplomacyFlags.RememberAttackedProtectedMinor.name -> {
addModifier(DiplomaticModifiers.AttackedProtectedMinor, 5f)
}
DiplomacyFlags.RememberBulliedProtectedMinor.name -> {
addModifier(DiplomaticModifiers.BulliedProtectedMinor, 5f)
}
}
}
// Only when flag is expired
if (flagsCountdown[flag] == 0) {
when (flag) {
@ -476,6 +502,19 @@ class DiplomacyManager() {
DiplomacyFlags.AgreedToNotSettleNearUs.name -> {
addModifier(DiplomaticModifiers.FulfilledPromiseToNotSettleCitiesNearUs, 10f)
}
// These modifiers don't tick down normally, instead there is a threshold number of turns
DiplomacyFlags.RememberDestroyedProtectedMinor.name -> { // 125
removeModifier(DiplomaticModifiers.DestroyedProtectedMinor)
}
DiplomacyFlags.RememberAttackedProtectedMinor.name -> { // 75
removeModifier(DiplomaticModifiers.AttackedProtectedMinor)
}
DiplomacyFlags.RememberBulliedProtectedMinor.name -> { // 75
removeModifier(DiplomaticModifiers.BulliedProtectedMinor)
}
DiplomacyFlags.RememberSidedWithProtectedMinor.name -> { // 25
removeModifier(DiplomaticModifiers.SidedWithProtectedMinor)
}
}
flagsCountdown.remove(flag)
@ -601,7 +640,10 @@ class DiplomacyManager() {
}
otherCivDiplomacy.setModifier(DiplomaticModifiers.DeclaredWarOnUs, -20f)
if (otherCiv.isCityState()) otherCivDiplomacy.setInfluence(-60f)
if (otherCiv.isCityState()) {
otherCivDiplomacy.setInfluence(-60f)
otherCiv.cityStateAttacked(civInfo)
}
for (thirdCiv in civInfo.getKnownCivs()) {
if (thirdCiv.isAtWarWith(otherCiv)) {
@ -785,6 +827,10 @@ class DiplomacyManager() {
otherCiv().addNotification("[${civInfo.civName}] refused to stop settling cities near us!", NotificationIcon.Diplomacy, civInfo.civName)
}
fun sideWithCityState() {
otherCivDiplomacy().setModifier(DiplomaticModifiers.SidedWithProtectedMinor, -5f)
otherCivDiplomacy().setFlag(DiplomacyFlags.RememberSidedWithProtectedMinor, 25)
}
//endregion
}

View File

@ -8,11 +8,8 @@ import com.badlogic.gdx.utils.Align
import com.unciv.Constants
import com.unciv.UncivGame
import com.unciv.logic.civilization.*
import com.unciv.logic.civilization.diplomacy.DiplomacyFlags
import com.unciv.logic.civilization.diplomacy.DiplomacyManager
import com.unciv.logic.civilization.diplomacy.*
import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers.*
import com.unciv.logic.civilization.diplomacy.DiplomaticStatus
import com.unciv.logic.civilization.diplomacy.RelationshipLevel
import com.unciv.logic.trade.TradeLogic
import com.unciv.logic.trade.TradeOffer
import com.unciv.logic.trade.TradeType
@ -667,6 +664,11 @@ class DiplomacyScreen(val viewingCiv:CivilizationInfo):CameraStageBaseScreen() {
private fun getDiplomacyModifiersTable(otherCivDiplomacyManager: DiplomacyManager): Table {
val diplomacyModifiersTable = Table()
for (modifier in otherCivDiplomacyManager.diplomaticModifiers) {
// Angry about attacked CS and destroyed CS do not stack
if (modifier.key == AttackedProtectedMinor.name
&& otherCivDiplomacyManager.hasModifier(DestroyedProtectedMinor))
continue
var text = when (valueOf(modifier.key)) {
DeclaredWarOnUs -> "You declared war on us!"
WarMongerer -> "Your warmongering ways are unacceptable to us."
@ -689,6 +691,10 @@ class DiplomacyScreen(val viewingCiv:CivilizationInfo):CameraStageBaseScreen() {
UsedNuclearWeapons -> "Your use of nuclear weapons is disgusting!"
StealingTerritory -> "You have stolen our lands!"
GaveUsUnits -> "You gave us units!"
DestroyedProtectedMinor -> "You destroyed City States that were under our protection!"
AttackedProtectedMinor -> "You attacked City States that were under our protection!"
BulliedProtectedMinor -> "You demanded tribute from City States that were under our protection!"
SidedWithProtectedMinor -> "You sided with a City State over us"
}
text = text.tr() + " "
if (modifier.value > 0) text += "+"

View File

@ -4,9 +4,8 @@ import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.scenes.scene2d.ui.TextButton
import com.unciv.Constants
import com.unciv.logic.civilization.AlertType
import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.civilization.PopupAlert
import com.unciv.logic.civilization.*
import com.unciv.logic.civilization.diplomacy.RelationshipLevel
import com.unciv.models.translations.fillPlaceholders
import com.unciv.models.translations.tr
import com.unciv.ui.trade.LeaderIntroTable
@ -262,6 +261,58 @@ class AlertPopup(val worldScreen: WorldScreen, val popupAlert: PopupAlert): Popu
addPuppetOption(puppetAction)
}
}
AlertType.BulliedProtectedMinor -> {
val involvedCivs = popupAlert.value.split('@')
val bully = worldScreen.gameInfo.getCivilization(involvedCivs[0])
val cityState = worldScreen.gameInfo.getCivilization(involvedCivs[1])
val player = worldScreen.viewingCiv
addLeaderName(bully)
val text = if (bully.getDiplomacyManager(player).relationshipLevel() >= RelationshipLevel.Neutral) // Nice message
"I've been informed that my armies have taken tribute from [${cityState.civName}], a city-state under your protection.\nI assure you, this was quite unintentional, and I hope that this does not serve to drive us apart."
else // Nasty message
"We asked [${cityState.civName}] for a tribute recently and they gave in.\nYou promised to protect them from such things, but we both know you cannot back that up."
addGoodSizedLabel(text).row()
add(getCloseButton("You'll pay for this!", 'y') {
player.getDiplomacyManager(bully).sideWithCityState()
}).row()
add(getCloseButton("Very well.", 'n') {
if(cityState.cities.isEmpty())
player.addNotification("You have broken your Pledge to Protect [${cityState.civName}]!", cityState.civName)
else {
val capitalLocation = LocationAction(listOf(cityState.getCapital().location))
player.addNotification("You have broken your Pledge to Protect [${cityState.civName}]!", capitalLocation, cityState.civName)
}
cityState.removeProtectorCiv(player, forced = true)
}).row()
}
AlertType.AttackedProtectedMinor -> {
val involvedCivs = popupAlert.value.split('@')
val attacker = worldScreen.gameInfo.getCivilization(involvedCivs[0])
val cityState = worldScreen.gameInfo.getCivilization(involvedCivs[1])
val player = worldScreen.viewingCiv
addLeaderName(attacker)
val text = if (attacker.getDiplomacyManager(player).relationshipLevel() >= RelationshipLevel.Neutral) // Nice message
"It's come to my attention that I may have attacked [${cityState.civName}], a city-state under your protection.\nWhile it was not my goal to be at odds with your empire, this was deemed a necessary course of action."
else // Nasty message
"I thought you might like to know that I've launched an invasion of one of your little pet states.\nThe lands of [${cityState.civName}] will make a fine addition to my own."
addGoodSizedLabel(text).row()
add(getCloseButton("You'll pay for this!", 'y') {
player.getDiplomacyManager(attacker).sideWithCityState()
}).row()
add(getCloseButton("Very well.", 'n') {
if(cityState.cities.isEmpty())
player.addNotification("You have broken your Pledge to Protect [${cityState.civName}]!", cityState.civName)
else {
val capitalLocation = LocationAction(listOf(cityState.getCapital().location))
player.addNotification("You have broken your Pledge to Protect [${cityState.civName}]!", capitalLocation, cityState.civName)
}
cityState.removeProtectorCiv(player, forced = true)
}).row()
}
}
}