Resolved #506 - Added real diplomatic relations!

Doesn't affect much now, but the platform is there to add what we want!
This commit is contained in:
Yair Morgenstern 2019-04-30 23:56:47 +03:00
parent 2ab8caeaea
commit 5952056518
5 changed files with 105 additions and 31 deletions

View File

@ -6,10 +6,12 @@ import com.unciv.logic.automation.UnitAutomation
import com.unciv.logic.city.CityInfo import com.unciv.logic.city.CityInfo
import com.unciv.logic.civilization.AlertType import com.unciv.logic.civilization.AlertType
import com.unciv.logic.civilization.PopupAlert import com.unciv.logic.civilization.PopupAlert
import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers
import com.unciv.logic.map.TileInfo import com.unciv.logic.map.TileInfo
import com.unciv.models.gamebasics.unit.UnitType import com.unciv.models.gamebasics.unit.UnitType
import java.util.* import java.util.*
import kotlin.math.max import kotlin.math.max
import kotlin.math.roundToInt
/** /**
* Damage calculations according to civ v wiki and https://steamcommunity.com/sharedfiles/filedetails/?id=170194443 * Damage calculations according to civ v wiki and https://steamcommunity.com/sharedfiles/filedetails/?id=170194443
@ -160,20 +162,37 @@ class Battle(val gameInfo:GameInfo) {
} }
private fun conquerCity(city: CityInfo, attacker: ICombatant) { private fun conquerCity(city: CityInfo, attacker: ICombatant) {
val enemyCiv = city.civInfo val cityCiv = city.civInfo
attacker.getCivInfo().addNotification("We have conquered the city of [${city.name}]!",city.location, Color.RED) val attackerCiv = attacker.getCivInfo()
attacker.getCivInfo().popupAlerts.add(PopupAlert(AlertType.CityConquered,city.name))
attackerCiv.addNotification("We have conquered the city of [${city.name}]!",city.location, Color.RED)
attackerCiv.popupAlerts.add(PopupAlert(AlertType.CityConquered,city.name))
city.getCenterTile().apply { city.getCenterTile().apply {
if(militaryUnit!=null) militaryUnit!!.destroy() if(militaryUnit!=null) militaryUnit!!.destroy()
if(civilianUnit!=null) captureCivilianUnit(attacker,MapUnitCombatant(civilianUnit!!)) if(civilianUnit!=null) captureCivilianUnit(attacker,MapUnitCombatant(civilianUnit!!))
} }
if (attacker.getCivInfo().isBarbarianCivilization() || attacker.getCivInfo().isCityState()){ if (!attackerCiv.isMajorCiv()){
city.destroyCity() city.destroyCity()
} }
else { else {
val currentPopulation = city.population.population val currentPopulation = city.population.population
val percentageOfCivPopulationInThatCity = currentPopulation*100f / cityCiv.cities.sumBy { it.population.population }
val aggroGenerated = 10f+percentageOfCivPopulationInThatCity.roundToInt()
cityCiv.getDiplomacyManager(attacker.getCivInfo())
.addModifier(DiplomaticModifiers.CapturedOurCities, -aggroGenerated)
for(thirdPartyCiv in attackerCiv.getKnownCivs().filter { it.isMajorCiv() }){
val aggroGeneratedForOtherCivs = (aggroGenerated/10).roundToInt().toFloat()
if(thirdPartyCiv.isAtWarWith(cityCiv)) // You annoyed our enemy?
thirdPartyCiv.getDiplomacyManager(attackerCiv)
.addModifier(DiplomaticModifiers.CapturedOurEnemiesCities,aggroGeneratedForOtherCivs) // Cool, keep at at! =D
else thirdPartyCiv.getDiplomacyManager(attackerCiv)
.addModifier(DiplomaticModifiers.WarMongerer, -aggroGeneratedForOtherCivs) // Uncool bro.
}
if(currentPopulation>1) city.population.population -= 1 + currentPopulation/4 // so from 2-4 population, remove 1, from 5-8, remove 2, etc. if(currentPopulation>1) city.population.population -= 1 + currentPopulation/4 // so from 2-4 population, remove 1, from 5-8, remove 2, etc.
city.population.unassignExtraPopulation() city.population.unassignExtraPopulation()
@ -195,12 +214,12 @@ class Battle(val gameInfo:GameInfo) {
if(city.cityConstructions.isBuilt("Palace")){ if(city.cityConstructions.isBuilt("Palace")){
city.cityConstructions.removeBuilding("Palace") city.cityConstructions.removeBuilding("Palace")
if(enemyCiv.isDefeated()) { if(cityCiv.isDefeated()) {
enemyCiv.destroy() cityCiv.destroy()
attacker.getCivInfo().popupAlerts.add(PopupAlert(AlertType.Defeated,enemyCiv.civName)) attacker.getCivInfo().popupAlerts.add(PopupAlert(AlertType.Defeated,cityCiv.civName))
} }
else if(enemyCiv.cities.isNotEmpty()){ else if(cityCiv.cities.isNotEmpty()){
enemyCiv.cities.first().cityConstructions.addBuilding("Palace") // relocate palace cityCiv.cities.first().cityConstructions.addBuilding("Palace") // relocate palace
} }
} }

View File

@ -123,7 +123,6 @@ class CivilizationInfo {
return translatedNation return translatedNation
} }
fun isCityState(): Boolean = getNation().isCityState()
fun getDiplomacyManager(civInfo: CivilizationInfo) = diplomacy[civInfo.civName]!! fun getDiplomacyManager(civInfo: CivilizationInfo) = diplomacy[civInfo.civName]!!
fun getKnownCivs() = diplomacy.values.map { it.otherCiv() } fun getKnownCivs() = diplomacy.values.map { it.otherCiv() }
@ -131,6 +130,8 @@ class CivilizationInfo {
fun isPlayerCivilization() = playerType==PlayerType.Human fun isPlayerCivilization() = playerType==PlayerType.Human
fun isCurrentPlayer() = gameInfo.getCurrentPlayerCivilization()==this fun isCurrentPlayer() = gameInfo.getCurrentPlayerCivilization()==this
fun isBarbarianCivilization() = gameInfo.getBarbarianCivilization()==this fun isBarbarianCivilization() = gameInfo.getBarbarianCivilization()==this
fun isCityState(): Boolean = getNation().isCityState()
fun isMajorCiv() = !isBarbarianCivilization() && !isCityState()
fun getStatsForNextTurn():Stats = getStatMapForNextTurn().values.toList().reduce{a,b->a+b} fun getStatsForNextTurn():Stats = getStatMapForNextTurn().values.toList().reduce{a,b->a+b}
fun getStatMapForNextTurn(): HashMap<String, Stats> { fun getStatMapForNextTurn(): HashMap<String, Stats> {

View File

@ -16,6 +16,14 @@ enum class DiplomacyFlags{
DeclinedPeace DeclinedPeace
} }
enum class DiplomaticModifiers{
DeclaredWarOnUs,
WarMongerer,
CapturedOurCities,
YearsOfPeace,
CapturedOurEnemiesCities
}
class DiplomacyManager() { class DiplomacyManager() {
@Transient lateinit var civInfo: CivilizationInfo @Transient lateinit var civInfo: CivilizationInfo
// since this needs to be checked a lot during travel, putting it in a transient is a good performance booster // since this needs to be checked a lot during travel, putting it in a transient is a good performance booster
@ -24,11 +32,19 @@ class DiplomacyManager() {
lateinit var otherCivName:String lateinit var otherCivName:String
var trades = ArrayList<Trade>() var trades = ArrayList<Trade>()
var diplomaticStatus = DiplomaticStatus.War var diplomaticStatus = DiplomaticStatus.War
/** Contains various flags (declared war, promised to not settle, declined luxury trade) and the number of turns in which they will expire. /** Contains various flags (declared war, promised to not settle, declined luxury trade) and the number of turns in which they will expire.
* The JSON serialize/deserialize REFUSES to deserialize hashmap keys as Enums, so I'm forced to use strings instead =( * The JSON serialize/deserialize REFUSES to deserialize hashmap keys as Enums, so I'm forced to use strings instead =(
* This is so sad Alexa play Despacito */ * This is so sad Alexa play Despacito */
var flagsCountdown = HashMap<String,Int>() var flagsCountdown = HashMap<String,Int>()
var influence = 0f // For city states
/** For AI. Positive is good relations, negative is bad.
* Baseline is 1 point for each turn of peace - so declaring a war upends 40 years of peace, and e.g. capturing a city can be another 30 or 40.
* As for why it's String and not DiplomaticModifier see FlagsCountdown comment */
var diplomaticModifiers = HashMap<String,Float>()
/** For city states */
var influence = 0f
fun clone(): DiplomacyManager { fun clone(): DiplomacyManager {
val toReturn = DiplomacyManager() val toReturn = DiplomacyManager()
@ -48,6 +64,8 @@ class DiplomacyManager() {
} }
//region pure functions //region pure functions
fun otherCiv() = civInfo.gameInfo.getCivilization(otherCivName)
fun turnsToPeaceTreaty(): Int { fun turnsToPeaceTreaty(): Int {
for(trade in trades) for(trade in trades)
for(offer in trade.ourOffers) for(offer in trade.ourOffers)
@ -55,9 +73,10 @@ class DiplomacyManager() {
return 0 return 0
} }
fun opinionOfOtherCiv() = diplomaticModifiers.values.sum()
fun canDeclareWar() = (turnsToPeaceTreaty()==0 && diplomaticStatus != DiplomaticStatus.War) fun canDeclareWar() = (turnsToPeaceTreaty()==0 && diplomaticStatus != DiplomaticStatus.War)
fun otherCiv() = civInfo.gameInfo.getCivilization(otherCivName)
fun goldPerTurn():Int{ fun goldPerTurn():Int{
var goldPerTurnForUs = 0 var goldPerTurnForUs = 0
@ -140,9 +159,14 @@ class DiplomacyManager() {
removeUntenebleTrades() removeUntenebleTrades()
updateHasOpenBorders() updateHasOpenBorders()
if(diplomaticStatus==DiplomaticStatus.Peace)
addModifier(DiplomaticModifiers.YearsOfPeace,1f)
for(flag in flagsCountdown.keys.toList()) { for(flag in flagsCountdown.keys.toList()) {
flagsCountdown[flag] = flagsCountdown[flag]!! - 1 flagsCountdown[flag] = flagsCountdown[flag]!! - 1
if(flagsCountdown[flag]==0) flagsCountdown.remove(flag) if(flagsCountdown[flag]==0) {
flagsCountdown.remove(flag)
}
} }
if (influence > 1) { if (influence > 1) {
@ -179,6 +203,29 @@ class DiplomacyManager() {
/// AI won't propose peace for 10 turns /// AI won't propose peace for 10 turns
flagsCountdown[DiplomacyFlags.DeclinedPeace.toString()]=10 flagsCountdown[DiplomacyFlags.DeclinedPeace.toString()]=10
otherCiv.getDiplomacyManager(civInfo).flagsCountdown[DiplomacyFlags.DeclinedPeace.toString()]=10 otherCiv.getDiplomacyManager(civInfo).flagsCountdown[DiplomacyFlags.DeclinedPeace.toString()]=10
otherCivDiplomacy.diplomaticModifiers[DiplomaticModifiers.DeclaredWarOnUs.toString()] = -20f
for(thirdCiv in civInfo.getKnownCivs()){
thirdCiv.getDiplomacyManager(civInfo).addModifier(DiplomaticModifiers.WarMongerer,-5f)
}
}
fun makePeace(){
diplomaticStatus= DiplomaticStatus.Peace
val otherCiv = otherCiv()
// We get out of their territory
for(unit in civInfo.getCivUnits().filter { it.getTile().getOwner()== otherCiv})
unit.movementAlgs().teleportToClosestMoveableTile()
// And we get out of theirs
for(unit in otherCiv.getCivUnits().filter { it.getTile().getOwner()== civInfo})
unit.movementAlgs().teleportToClosestMoveableTile()
}
fun addModifier(modifier: DiplomaticModifiers, amount:Float){
val modifierString = modifier.toString()
if(!diplomaticModifiers.containsKey(modifierString)) diplomaticModifiers[modifierString]=0f
diplomaticModifiers[modifierString] = diplomaticModifiers[modifierString]!!+amount
} }
//endregion //endregion
} }

View File

@ -96,11 +96,7 @@ class TradeLogic(val ourCivilization:CivilizationInfo, val otherCivilization: Ci
from.updateViewableTiles() from.updateViewableTiles()
} }
if(offer.type== TradeType.Treaty){ if(offer.type== TradeType.Treaty){
if(offer.name=="Peace Treaty"){ if(offer.name=="Peace Treaty") to.getDiplomacyManager(from).makePeace()
to.getDiplomacyManager(from).diplomaticStatus= DiplomaticStatus.Peace
for(unit in to.getCivUnits().filter { it.getTile().getOwner()==from })
unit.movementAlgs().teleportToClosestMoveableTile()
}
} }
if(offer.type==TradeType.Introduction) if(offer.type==TradeType.Introduction)
to.meetCivilization(to.gameInfo.getCivilization(offer.name.split(" ")[2])) to.meetCivilization(to.gameInfo.getCivilization(offer.name.split(" ")[2]))

View File

@ -66,44 +66,44 @@ class DiplomacyScreen:CameraStageBaseScreen() {
return tradeTable return tradeTable
} }
private fun getDiplomacyTable(civ: CivilizationInfo): Table { private fun getDiplomacyTable(otherCiv: CivilizationInfo): Table {
val currentPlayerCiv = UnCivGame.Current.gameInfo.getCurrentPlayerCivilization() val currentPlayerCiv = UnCivGame.Current.gameInfo.getCurrentPlayerCivilization()
val diplomacyTable = Table() val diplomacyTable = Table()
diplomacyTable.defaults().pad(10f) diplomacyTable.defaults().pad(10f)
val leaderName: String val leaderName: String
if (civ.isCityState()) { if (otherCiv.isCityState()) {
leaderName = "City State [" + civ.civName + "]" leaderName = otherCiv.civName
} else { } else {
leaderName = "[" + civ.getNation().leaderName + "] of [" + civ.civName + "]" leaderName = "[" + otherCiv.getNation().leaderName + "] of [" + otherCiv.civName + "]"
} }
diplomacyTable.add(leaderName.toLabel()) diplomacyTable.add(leaderName.toLabel())
diplomacyTable.addSeparator() diplomacyTable.addSeparator()
if(!civ.isCityState()) { if(!otherCiv.isCityState()) {
val tradeButton = TextButton("Trade".tr(), skin) val tradeButton = TextButton("Trade".tr(), skin)
tradeButton.onClick { setTrade(civ) } tradeButton.onClick { setTrade(otherCiv) }
diplomacyTable.add(tradeButton).row() diplomacyTable.add(tradeButton).row()
} }
val civDiplomacy = currentPlayerCiv.getDiplomacyManager(civ) val diplomacyManager = currentPlayerCiv.getDiplomacyManager(otherCiv)
if (!currentPlayerCiv.isAtWarWith(civ)) { if (!currentPlayerCiv.isAtWarWith(otherCiv)) {
val declareWarButton = TextButton("Declare war".tr(), skin) val declareWarButton = TextButton("Declare war".tr(), skin)
declareWarButton.color = Color.RED declareWarButton.color = Color.RED
val turnsToPeaceTreaty = civDiplomacy.turnsToPeaceTreaty() val turnsToPeaceTreaty = diplomacyManager.turnsToPeaceTreaty()
if (turnsToPeaceTreaty > 0) { if (turnsToPeaceTreaty > 0) {
declareWarButton.disable() declareWarButton.disable()
declareWarButton.setText(declareWarButton.text.toString() + " ($turnsToPeaceTreaty)") declareWarButton.setText(declareWarButton.text.toString() + " ($turnsToPeaceTreaty)")
} }
declareWarButton.onClick { declareWarButton.onClick {
YesNoPopupTable("Declare war on [${civ.civName}]?".tr(), { YesNoPopupTable("Declare war on [${otherCiv.civName}]?".tr(), {
civDiplomacy.declareWar() diplomacyManager.declareWar()
val responsePopup = PopupTable(this) val responsePopup = PopupTable(this)
val otherCivLeaderName = civ.getNation().leaderName + " of " + civ.civName val otherCivLeaderName = otherCiv.getNation().leaderName + " of " + otherCiv.civName
responsePopup.add(otherCivLeaderName.toLabel()) responsePopup.add(otherCivLeaderName.toLabel())
responsePopup.addSeparator() responsePopup.addSeparator()
responsePopup.addGoodSizedLabel(civ.getNation().attacked).row() responsePopup.addGoodSizedLabel(otherCiv.getNation().attacked).row()
responsePopup.addButton("Very well.".tr()) { responsePopup.remove() } responsePopup.addButton("Very well.".tr()) { responsePopup.remove() }
responsePopup.open() responsePopup.open()
@ -112,6 +112,17 @@ class DiplomacyScreen:CameraStageBaseScreen() {
} }
diplomacyTable.add(declareWarButton).row() diplomacyTable.add(declareWarButton).row()
} }
if(!otherCiv.isCityState()){
val diplomacyModifiersTable = Table()
val otherCivDiplomacyManager = otherCiv.getDiplomacyManager(currentPlayerCiv)
diplomacyModifiersTable.add(("Current opinion: "+otherCivDiplomacyManager.opinionOfOtherCiv()).toLabel()).row()
for(modifier in otherCivDiplomacyManager.diplomaticModifiers){
diplomacyModifiersTable.add((modifier.key+" "+modifier.value).toLabel()).row()
}
diplomacyTable.add(diplomacyModifiersTable).row()
}
return diplomacyTable return diplomacyTable
} }
} }