diff --git a/core/src/com/unciv/logic/battle/Battle.kt b/core/src/com/unciv/logic/battle/Battle.kt index 99dc22ab57..f1e0458ec6 100644 --- a/core/src/com/unciv/logic/battle/Battle.kt +++ b/core/src/com/unciv/logic/battle/Battle.kt @@ -6,10 +6,12 @@ import com.unciv.logic.automation.UnitAutomation import com.unciv.logic.city.CityInfo import com.unciv.logic.civilization.AlertType import com.unciv.logic.civilization.PopupAlert +import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers import com.unciv.logic.map.TileInfo import com.unciv.models.gamebasics.unit.UnitType import java.util.* import kotlin.math.max +import kotlin.math.roundToInt /** * 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) { - val enemyCiv = city.civInfo - attacker.getCivInfo().addNotification("We have conquered the city of [${city.name}]!",city.location, Color.RED) - attacker.getCivInfo().popupAlerts.add(PopupAlert(AlertType.CityConquered,city.name)) + val cityCiv = city.civInfo + val attackerCiv = attacker.getCivInfo() + + 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 { if(militaryUnit!=null) militaryUnit!!.destroy() if(civilianUnit!=null) captureCivilianUnit(attacker,MapUnitCombatant(civilianUnit!!)) } - if (attacker.getCivInfo().isBarbarianCivilization() || attacker.getCivInfo().isCityState()){ + if (!attackerCiv.isMajorCiv()){ city.destroyCity() } else { 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. city.population.unassignExtraPopulation() @@ -195,12 +214,12 @@ class Battle(val gameInfo:GameInfo) { if(city.cityConstructions.isBuilt("Palace")){ city.cityConstructions.removeBuilding("Palace") - if(enemyCiv.isDefeated()) { - enemyCiv.destroy() - attacker.getCivInfo().popupAlerts.add(PopupAlert(AlertType.Defeated,enemyCiv.civName)) + if(cityCiv.isDefeated()) { + cityCiv.destroy() + attacker.getCivInfo().popupAlerts.add(PopupAlert(AlertType.Defeated,cityCiv.civName)) } - else if(enemyCiv.cities.isNotEmpty()){ - enemyCiv.cities.first().cityConstructions.addBuilding("Palace") // relocate palace + else if(cityCiv.cities.isNotEmpty()){ + cityCiv.cities.first().cityConstructions.addBuilding("Palace") // relocate palace } } diff --git a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt index fb79cc8595..01cfec7e7d 100644 --- a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt +++ b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt @@ -123,7 +123,6 @@ class CivilizationInfo { return translatedNation } - fun isCityState(): Boolean = getNation().isCityState() fun getDiplomacyManager(civInfo: CivilizationInfo) = diplomacy[civInfo.civName]!! fun getKnownCivs() = diplomacy.values.map { it.otherCiv() } @@ -131,6 +130,8 @@ class CivilizationInfo { fun isPlayerCivilization() = playerType==PlayerType.Human fun isCurrentPlayer() = gameInfo.getCurrentPlayerCivilization()==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 getStatMapForNextTurn(): HashMap { diff --git a/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt b/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt index da44fe9f53..f669f4d0c1 100644 --- a/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt +++ b/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt @@ -16,6 +16,14 @@ enum class DiplomacyFlags{ DeclinedPeace } +enum class DiplomaticModifiers{ + DeclaredWarOnUs, + WarMongerer, + CapturedOurCities, + YearsOfPeace, + CapturedOurEnemiesCities +} + class DiplomacyManager() { @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 @@ -24,11 +32,19 @@ class DiplomacyManager() { lateinit var otherCivName:String var trades = ArrayList() 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. * 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 */ var flagsCountdown = HashMap() - 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() + + /** For city states */ + var influence = 0f fun clone(): DiplomacyManager { val toReturn = DiplomacyManager() @@ -48,6 +64,8 @@ class DiplomacyManager() { } //region pure functions + fun otherCiv() = civInfo.gameInfo.getCivilization(otherCivName) + fun turnsToPeaceTreaty(): Int { for(trade in trades) for(offer in trade.ourOffers) @@ -55,9 +73,10 @@ class DiplomacyManager() { return 0 } + fun opinionOfOtherCiv() = diplomaticModifiers.values.sum() + fun canDeclareWar() = (turnsToPeaceTreaty()==0 && diplomaticStatus != DiplomaticStatus.War) - fun otherCiv() = civInfo.gameInfo.getCivilization(otherCivName) fun goldPerTurn():Int{ var goldPerTurnForUs = 0 @@ -140,9 +159,14 @@ class DiplomacyManager() { removeUntenebleTrades() updateHasOpenBorders() + if(diplomaticStatus==DiplomaticStatus.Peace) + addModifier(DiplomaticModifiers.YearsOfPeace,1f) + for(flag in flagsCountdown.keys.toList()) { flagsCountdown[flag] = flagsCountdown[flag]!! - 1 - if(flagsCountdown[flag]==0) flagsCountdown.remove(flag) + if(flagsCountdown[flag]==0) { + flagsCountdown.remove(flag) + } } if (influence > 1) { @@ -179,6 +203,29 @@ class DiplomacyManager() { /// AI won't propose peace for 10 turns 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 } diff --git a/core/src/com/unciv/logic/trade/TradeLogic.kt b/core/src/com/unciv/logic/trade/TradeLogic.kt index 7a637f1b48..6c21bc6ce0 100644 --- a/core/src/com/unciv/logic/trade/TradeLogic.kt +++ b/core/src/com/unciv/logic/trade/TradeLogic.kt @@ -96,11 +96,7 @@ class TradeLogic(val ourCivilization:CivilizationInfo, val otherCivilization: Ci from.updateViewableTiles() } if(offer.type== TradeType.Treaty){ - if(offer.name=="Peace Treaty"){ - to.getDiplomacyManager(from).diplomaticStatus= DiplomaticStatus.Peace - for(unit in to.getCivUnits().filter { it.getTile().getOwner()==from }) - unit.movementAlgs().teleportToClosestMoveableTile() - } + if(offer.name=="Peace Treaty") to.getDiplomacyManager(from).makePeace() } if(offer.type==TradeType.Introduction) to.meetCivilization(to.gameInfo.getCivilization(offer.name.split(" ")[2])) diff --git a/core/src/com/unciv/ui/trade/DiplomacyScreen.kt b/core/src/com/unciv/ui/trade/DiplomacyScreen.kt index c5af13f938..08859e2e60 100644 --- a/core/src/com/unciv/ui/trade/DiplomacyScreen.kt +++ b/core/src/com/unciv/ui/trade/DiplomacyScreen.kt @@ -66,44 +66,44 @@ class DiplomacyScreen:CameraStageBaseScreen() { return tradeTable } - private fun getDiplomacyTable(civ: CivilizationInfo): Table { + private fun getDiplomacyTable(otherCiv: CivilizationInfo): Table { val currentPlayerCiv = UnCivGame.Current.gameInfo.getCurrentPlayerCivilization() val diplomacyTable = Table() diplomacyTable.defaults().pad(10f) val leaderName: String - if (civ.isCityState()) { - leaderName = "City State [" + civ.civName + "]" + if (otherCiv.isCityState()) { + leaderName = otherCiv.civName } else { - leaderName = "[" + civ.getNation().leaderName + "] of [" + civ.civName + "]" + leaderName = "[" + otherCiv.getNation().leaderName + "] of [" + otherCiv.civName + "]" } diplomacyTable.add(leaderName.toLabel()) diplomacyTable.addSeparator() - if(!civ.isCityState()) { + if(!otherCiv.isCityState()) { val tradeButton = TextButton("Trade".tr(), skin) - tradeButton.onClick { setTrade(civ) } + tradeButton.onClick { setTrade(otherCiv) } 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) declareWarButton.color = Color.RED - val turnsToPeaceTreaty = civDiplomacy.turnsToPeaceTreaty() + val turnsToPeaceTreaty = diplomacyManager.turnsToPeaceTreaty() if (turnsToPeaceTreaty > 0) { declareWarButton.disable() declareWarButton.setText(declareWarButton.text.toString() + " ($turnsToPeaceTreaty)") } declareWarButton.onClick { - YesNoPopupTable("Declare war on [${civ.civName}]?".tr(), { - civDiplomacy.declareWar() + YesNoPopupTable("Declare war on [${otherCiv.civName}]?".tr(), { + diplomacyManager.declareWar() 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.addSeparator() - responsePopup.addGoodSizedLabel(civ.getNation().attacked).row() + responsePopup.addGoodSizedLabel(otherCiv.getNation().attacked).row() responsePopup.addButton("Very well.".tr()) { responsePopup.remove() } responsePopup.open() @@ -112,6 +112,17 @@ class DiplomacyScreen:CameraStageBaseScreen() { } 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 } } \ No newline at end of file