mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-04 07:17:50 +07:00
Giving the AI good trades is stored as credit (#11326)
* AI Civs now are happy for good trades * Each gift point is worth 100 gold without inflation * Gifts can now be used as credit for future trades * Fixed giftAmount conversions * Fixed get inflation returning NAN when gpt is negative * diplomatic gifts are now rounded when checking trade acceptability * Changed gold gift scaling to account for relationship level * Fixed percent based value reduction * Added gold gifting functions to DiplomacyManager * Added tests * Declaring war removes gold gifts * Reversed trade evaluation * Added more tests for trading * Fixed who the gifts are given to * Added more comments * Added more tests and fixed stuff * Gifting does not occur with trade treaties * Renamed handleGoldGifted to GiftGold * Added two more tests * Improved comments * Liberating a civ no longer gives positive relations from open borders
This commit is contained in:
@ -55,7 +55,7 @@ object TradeAutomation {
|
||||
if (otherCiv.playerType == PlayerType.AI)
|
||||
return null
|
||||
val evaluation = TradeEvaluation()
|
||||
var deltaInOurFavor = evaluation.getTradeAcceptability(tradeRequest.trade, civInfo, otherCiv)
|
||||
var deltaInOurFavor = evaluation.getTradeAcceptability(tradeRequest.trade, civInfo, otherCiv, true)
|
||||
if (deltaInOurFavor > 0) deltaInOurFavor = (deltaInOurFavor / 1.1f).toInt() // They seem very interested in this deal, let's push it a bit.
|
||||
val tradeLogic = TradeLogic(civInfo, otherCiv)
|
||||
|
||||
|
@ -225,7 +225,7 @@ class CityConquestFunctions(val city: City) {
|
||||
.addModifier(DiplomaticModifiers.CapturedOurCities, respectForLiberatingOurCity)
|
||||
val openBordersTrade = TradeLogic(foundingCiv, conqueringCiv)
|
||||
openBordersTrade.currentTrade.ourOffers.add(TradeOffer(Constants.openBorders, TradeType.Agreement))
|
||||
openBordersTrade.acceptTrade()
|
||||
openBordersTrade.acceptTrade(false)
|
||||
} else {
|
||||
//Liberating a city state gives a large amount of influence, and peace
|
||||
foundingCiv.getDiplomacyManager(conqueringCiv).setInfluence(90f)
|
||||
@ -233,7 +233,7 @@ class CityConquestFunctions(val city: City) {
|
||||
val tradeLogic = TradeLogic(foundingCiv, conqueringCiv)
|
||||
tradeLogic.currentTrade.ourOffers.add(TradeOffer(Constants.peaceTreaty, TradeType.Treaty))
|
||||
tradeLogic.currentTrade.theirOffers.add(TradeOffer(Constants.peaceTreaty, TradeType.Treaty))
|
||||
tradeLogic.acceptTrade()
|
||||
tradeLogic.acceptTrade(false)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -237,6 +237,10 @@ object DeclareWar {
|
||||
otherCivDiplomacy.totalOfScienceDuringRA = 0
|
||||
}
|
||||
otherCivDiplomacy.removeFlag(DiplomacyFlags.ResearchAgreement)
|
||||
|
||||
// The other civ should keep any gifts we gave them
|
||||
// But we should not nesessarily take away their gifts
|
||||
otherCivDiplomacy.removeModifier(DiplomaticModifiers.GaveUsGifts)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -7,6 +7,7 @@ import com.unciv.logic.civilization.Civilization
|
||||
import com.unciv.logic.civilization.NotificationCategory
|
||||
import com.unciv.logic.civilization.NotificationIcon
|
||||
import com.unciv.logic.trade.Trade
|
||||
import com.unciv.logic.trade.TradeEvaluation
|
||||
import com.unciv.logic.trade.TradeOffer
|
||||
import com.unciv.logic.trade.TradeType
|
||||
import com.unciv.models.ruleset.tile.ResourceSupplyList
|
||||
@ -674,5 +675,41 @@ class DiplomacyManager() : IsPartOfGameInfoSerialization {
|
||||
NotificationCategory.Diplomacy, civInfo.civName)
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves adding gifts with negative gold values.
|
||||
* Prioritises reducing gifts given to the other civ before increasing our gift value.
|
||||
* Does not take the gold from either civ's stockpile
|
||||
* @param gold the amount of gold without inflation, can be negative
|
||||
*/
|
||||
fun giftGold(gold: Int) {
|
||||
val otherGold = otherCivDiplomacy().getGoldGifts()
|
||||
if (otherGold > gold) {
|
||||
otherCivDiplomacy().recieveGoldGifts(-gold)
|
||||
} else {
|
||||
otherCivDiplomacy().removeModifier(DiplomaticModifiers.GaveUsGifts)
|
||||
recieveGoldGifts(gold - otherGold)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a gift from the other civilization of the value of [gold] that will deteriate over time.
|
||||
* Does not take into account how much gold we have given to the other civ. Use [giftGold] for that.
|
||||
* Does not take the gold from either civ's stockpile.
|
||||
* @param gold the amount of gold without inflation, cannot be negative
|
||||
*/
|
||||
fun recieveGoldGifts(gold: Int) {
|
||||
val diplomaticValueOfTrade = (gold * TradeEvaluation().getGoldInflation(civInfo)) / (civInfo.gameInfo.speed.goldGiftModifier * 100)
|
||||
addModifier(DiplomaticModifiers.GaveUsGifts, diplomaticValueOfTrade.toFloat())
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the total value of the gold gifts the other civilization has given us
|
||||
*/
|
||||
fun getGoldGifts(): Int {
|
||||
// The inverse of howe we calculate GaveUsGifts in TradeLogic.acceptTrade gives us how much gold it is worth
|
||||
val giftAmount = getModifier(DiplomaticModifiers.GaveUsGifts)
|
||||
return ((giftAmount * civInfo.gameInfo.speed.goldGiftModifier * 100) / TradeEvaluation().getGoldInflation(civInfo)).toInt()
|
||||
}
|
||||
|
||||
//endregion
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import com.unciv.logic.trade.TradeOffer
|
||||
import com.unciv.logic.trade.TradeType
|
||||
import com.unciv.models.ruleset.unique.UniqueType
|
||||
import com.unciv.ui.components.extensions.toPercent
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
@ -291,7 +292,25 @@ object DiplomacyTurnManager {
|
||||
// Positives
|
||||
revertToZero(DiplomaticModifiers.GaveUsUnits, 1 / 4f)
|
||||
revertToZero(DiplomaticModifiers.LiberatedCity, 1 / 8f)
|
||||
revertToZero(DiplomaticModifiers.GaveUsGifts, 1 / 4f)
|
||||
if (hasModifier(DiplomaticModifiers.GaveUsGifts)) {
|
||||
val giftLoss = when {
|
||||
relationshipLevel() == RelationshipLevel.Ally -> .5f
|
||||
relationshipLevel() == RelationshipLevel.Friend -> 1f
|
||||
relationshipLevel() == RelationshipLevel.Favorable -> 1.5f
|
||||
relationshipLevel() == RelationshipLevel.Competitor -> 5f
|
||||
relationshipLevel() == RelationshipLevel.Enemy -> 7.5f
|
||||
relationshipLevel() == RelationshipLevel.Unforgivable -> 10f
|
||||
else -> 2f // Neutral
|
||||
}
|
||||
// We should subtract a certain amount from this balanced each turn
|
||||
// Assuming neutral relations we will subtract the higher of either:
|
||||
// 2% of the total amount or
|
||||
// roughly 40 gold per turn (a value of ~.4 without inflation)
|
||||
// This ensures that the amount can be reduced to zero but scales with larger numbers
|
||||
val amountLost = (getModifier(DiplomaticModifiers.GaveUsGifts).absoluteValue * giftLoss / 100)
|
||||
.coerceAtLeast(giftLoss / 5)
|
||||
revertToZero(DiplomaticModifiers.GaveUsGifts, amountLost) // Roughly worth 20 GPT without inflation
|
||||
}
|
||||
|
||||
setFriendshipBasedModifier()
|
||||
|
||||
@ -331,7 +350,8 @@ object DiplomacyTurnManager {
|
||||
private fun DiplomacyManager.revertToZero(modifier: DiplomaticModifiers, amount: Float) {
|
||||
if (!hasModifier(modifier)) return
|
||||
val currentAmount = getModifier(modifier)
|
||||
if (currentAmount > 0) addModifier(modifier, -amount)
|
||||
if (amount >= currentAmount.absoluteValue) diplomaticModifiers.remove(modifier.name)
|
||||
else if (currentAmount > 0) addModifier(modifier, -amount)
|
||||
else addModifier(modifier, amount)
|
||||
}
|
||||
|
||||
|
@ -60,10 +60,10 @@ class TradeEvaluation {
|
||||
}
|
||||
|
||||
fun isTradeAcceptable(trade: Trade, evaluator: Civilization, tradePartner: Civilization): Boolean {
|
||||
return getTradeAcceptability(trade, evaluator, tradePartner) >= 0
|
||||
return getTradeAcceptability(trade, evaluator, tradePartner, true) >= 0
|
||||
}
|
||||
|
||||
fun getTradeAcceptability(trade: Trade, evaluator: Civilization, tradePartner: Civilization): Int {
|
||||
fun getTradeAcceptability(trade: Trade, evaluator: Civilization, tradePartner: Civilization, includeDiplomaticGifts:Boolean = false): Int {
|
||||
val citiesAskedToSurrender = trade.ourOffers.count { it.type == TradeType.City }
|
||||
val maxCitiesToSurrender = ceil(evaluator.cities.size.toFloat() / 5).toInt()
|
||||
if (citiesAskedToSurrender > maxCitiesToSurrender) {
|
||||
@ -90,8 +90,8 @@ class TradeEvaluation {
|
||||
return Int.MIN_VALUE
|
||||
}
|
||||
}
|
||||
|
||||
return sumOfTheirOffers - sumOfOurOffers
|
||||
val diplomaticGifts: Int = if (includeDiplomaticGifts) evaluator.getDiplomacyManager(tradePartner).getGoldGifts() else 0
|
||||
return sumOfTheirOffers - sumOfOurOffers + diplomaticGifts
|
||||
}
|
||||
|
||||
fun evaluateBuyCostWithInflation(offer: TradeOffer, civInfo: Civilization, tradePartner: Civilization, trade: Trade): Int {
|
||||
@ -320,7 +320,7 @@ class TradeEvaluation {
|
||||
* This returns how much one gold is worth now in comparison to starting out the game
|
||||
* Gold is worth less as the civilization has a higher income
|
||||
*/
|
||||
private fun getGoldInflation(civInfo: Civilization): Double {
|
||||
fun getGoldInflation(civInfo: Civilization): Double {
|
||||
val modifier = 1000.0
|
||||
val goldPerTurn = civInfo.stats.statsForNextTurn.gold.toDouble()
|
||||
// To visualise the function, plug this into a 2d graphing calculator \frac{1000}{x^{1.2}+1.11*1000}
|
||||
|
@ -4,10 +4,8 @@ import com.unciv.Constants
|
||||
import com.unciv.logic.civilization.AlertType
|
||||
import com.unciv.logic.civilization.Civilization
|
||||
import com.unciv.logic.civilization.PopupAlert
|
||||
import com.unciv.logic.civilization.diplomacy.CityStateFunctions
|
||||
import com.unciv.logic.civilization.diplomacy.DeclareWarReason
|
||||
import com.unciv.logic.civilization.diplomacy.DiplomacyFlags
|
||||
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
|
||||
@ -81,12 +79,15 @@ class TradeLogic(val ourCivilization: Civilization, val otherCivilization: Civil
|
||||
return offers
|
||||
}
|
||||
|
||||
fun acceptTrade() {
|
||||
ourCivilization.getDiplomacyManager(otherCivilization).apply {
|
||||
fun acceptTrade(applyGifts: Boolean = true) {
|
||||
val ourDiploManager = ourCivilization.getDiplomacyManager(otherCivilization)
|
||||
val theirDiploManger = otherCivilization.getDiplomacyManager(ourCivilization)
|
||||
|
||||
ourDiploManager.apply {
|
||||
trades.add(currentTrade)
|
||||
updateHasOpenBorders()
|
||||
}
|
||||
otherCivilization.getDiplomacyManager(ourCivilization).apply {
|
||||
theirDiploManger.apply {
|
||||
trades.add(currentTrade.reverse())
|
||||
updateHasOpenBorders()
|
||||
}
|
||||
@ -150,10 +151,17 @@ class TradeLogic(val ourCivilization: Civilization, val otherCivilization: Civil
|
||||
}
|
||||
}
|
||||
|
||||
if (currentTrade.ourOffers.isEmpty()) { // Must evaluate before moving, or else cities have already moved and we get an exception
|
||||
val goldValueOfTrade = TradeEvaluation().getTradeAcceptability(currentTrade, ourCivilization, otherCivilization)
|
||||
val diplomaticValueOfTrade = CityStateFunctions(ourCivilization).influenceGainedByGift(otherCivilization, goldValueOfTrade) / 10
|
||||
ourCivilization.getDiplomacyManager(otherCivilization).addModifier(DiplomaticModifiers.GaveUsGifts, diplomaticValueOfTrade.toFloat())
|
||||
// We shouldn't evaluate trades if we are doing a peace treaty
|
||||
// Their value can be so big it throws the gift system out of wack
|
||||
if (applyGifts && !currentTrade.ourOffers.any { it.name == Constants.peaceTreaty }) {
|
||||
// Must evaluate before moving, or else cities have already moved and we get an exception
|
||||
val ourGoldValueOfTrade = TradeEvaluation().getTradeAcceptability(currentTrade, ourCivilization, otherCivilization, includeDiplomaticGifts = false)
|
||||
val theirGoldValueOfTrade = TradeEvaluation().getTradeAcceptability(currentTrade.reverse(), otherCivilization, ourCivilization, includeDiplomaticGifts = false)
|
||||
if (ourGoldValueOfTrade > theirGoldValueOfTrade) {
|
||||
ourDiploManager.giftGold(ourGoldValueOfTrade - theirGoldValueOfTrade.coerceAtLeast(0))
|
||||
} else if (theirGoldValueOfTrade > ourGoldValueOfTrade) {
|
||||
theirDiploManger.giftGold(theirGoldValueOfTrade - ourGoldValueOfTrade.coerceAtLeast(0))
|
||||
}
|
||||
}
|
||||
|
||||
// Transfer of cities needs to happen before peace treaty, to avoid our units teleporting out of areas that soon will be ours
|
||||
|
@ -0,0 +1,138 @@
|
||||
package com.unciv.logic.civilization.diplomacy
|
||||
|
||||
import com.unciv.logic.civilization.diplomacy.DiplomacyTurnManager.nextTurn
|
||||
import com.unciv.logic.trade.TradeEvaluation
|
||||
import com.unciv.logic.trade.TradeLogic
|
||||
import com.unciv.logic.trade.TradeOffer
|
||||
import com.unciv.logic.trade.TradeType
|
||||
import com.unciv.testing.GdxTestRunner
|
||||
import com.unciv.testing.TestGame
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(GdxTestRunner::class)
|
||||
class GoldGiftingTests {
|
||||
|
||||
private val testGame = TestGame()
|
||||
|
||||
private val a = testGame.addCiv()
|
||||
private val b = testGame.addCiv()
|
||||
lateinit var aDiplomacy: DiplomacyManager
|
||||
lateinit var bDiplomacy: DiplomacyManager
|
||||
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
a.diplomacyFunctions.makeCivilizationsMeet(b)
|
||||
aDiplomacy = a.getDiplomacyManager(b)
|
||||
bDiplomacy = b.getDiplomacyManager(a)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Gold Gift Recieve Test` () {
|
||||
assertEquals(0, aDiplomacy.getGoldGifts())
|
||||
assertEquals(0, bDiplomacy.getGoldGifts())
|
||||
aDiplomacy.recieveGoldGifts(10)
|
||||
assertTrue(aDiplomacy.getGoldGifts() > 0)
|
||||
assertEquals(0, bDiplomacy.getGoldGifts())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Gold Gift Test` () {
|
||||
assertEquals(0, aDiplomacy.getGoldGifts())
|
||||
assertEquals(0, bDiplomacy.getGoldGifts())
|
||||
aDiplomacy.giftGold(10)
|
||||
assertTrue(aDiplomacy.getGoldGifts() > 0)
|
||||
assertEquals(0, bDiplomacy.getGoldGifts())
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun `Gifted Gold Disapears` () {
|
||||
aDiplomacy.recieveGoldGifts(10)
|
||||
assertTrue(aDiplomacy.getGoldGifts() > 0)
|
||||
val gold = aDiplomacy.getGoldGifts()
|
||||
aDiplomacy.nextTurn()
|
||||
val gold2 = aDiplomacy.getGoldGifts()
|
||||
assertTrue(gold > gold2)
|
||||
assertTrue(gold2 >= 0) // Gold should not be negative
|
||||
// We don't actually test if the gift has completely run out
|
||||
// since that may change in the future
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Gifted Gold is reduced less than 10 percent` () {
|
||||
aDiplomacy.recieveGoldGifts(1000)
|
||||
assertTrue(aDiplomacy.getGoldGifts() > 0)
|
||||
val gold = aDiplomacy.getGoldGifts()
|
||||
aDiplomacy.nextTurn()
|
||||
val gold2 = aDiplomacy.getGoldGifts()
|
||||
assertTrue(gold > gold2)
|
||||
assertTrue(gold2 >= gold * .9) // We shoulden't loose more than 10% of the value in one turn
|
||||
assertTrue(gold2 >= 0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Gold gifted is lost during war` () {
|
||||
aDiplomacy.recieveGoldGifts(1000)
|
||||
bDiplomacy.recieveGoldGifts(1000)
|
||||
assertTrue(aDiplomacy.getGoldGifts() > 0)
|
||||
assertTrue(bDiplomacy.getGoldGifts() > 0)
|
||||
bDiplomacy.declareWar()
|
||||
assertEquals(0, aDiplomacy.getGoldGifts())
|
||||
assertTrue(bDiplomacy.getGoldGifts() > 0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Gifting gold reduces previous gifts taken` () {
|
||||
aDiplomacy.giftGold(1000)
|
||||
bDiplomacy.giftGold(500)
|
||||
assertTrue(aDiplomacy.getGoldGifts() > 0)
|
||||
assertTrue(aDiplomacy.getGoldGifts() < 1000)
|
||||
assertTrue(bDiplomacy.getGoldGifts() == 0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Excess gold from a trade become a gift` () {
|
||||
a.addGold(1000)
|
||||
assertEquals(0, bDiplomacy.getGoldGifts())
|
||||
val tradeOffer = TradeLogic(a,b)
|
||||
tradeOffer.currentTrade.ourOffers.add(tradeOffer.ourAvailableOffers.first { it.type == TradeType.Gold })
|
||||
assertTrue(TradeEvaluation().getTradeAcceptability(tradeOffer.currentTrade.reverse(), b,a,false) > 0)
|
||||
tradeOffer.acceptTrade()
|
||||
assertEquals(0, aDiplomacy.getGoldGifts())
|
||||
assertTrue(bDiplomacy.getGoldGifts() > 0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Can ask for 90 percent of gold gift back again a turn later` () {
|
||||
// Due to rounding, we aren't going to assume we can get 100% of the gold back
|
||||
// Therefore we only test for 90%
|
||||
a.addGold(1000)
|
||||
val tradeOffer = TradeLogic(a,b)
|
||||
tradeOffer.currentTrade.ourOffers.add(tradeOffer.ourAvailableOffers.first { it.type == TradeType.Gold })
|
||||
tradeOffer.acceptTrade()
|
||||
bDiplomacy.nextTurn()
|
||||
val tradeOffer2 = TradeLogic(a,b)
|
||||
tradeOffer2.currentTrade.theirOffers.add(TradeOffer("Gold", TradeType.Gold, 900))
|
||||
assertTrue(TradeEvaluation().getTradeAcceptability(tradeOffer.currentTrade.reverse(), b,a,false) > 0)
|
||||
tradeOffer2.acceptTrade()
|
||||
assertTrue(bDiplomacy.getGoldGifts() >= 0) // Must not be negative
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Gold gifted impact trade acceptability`() {
|
||||
a.addGold(1000)
|
||||
val tradeOffer = TradeLogic(a,b)
|
||||
tradeOffer.currentTrade.ourOffers.add(tradeOffer.ourAvailableOffers.first { it.type == TradeType.Gold })
|
||||
assertTrue(TradeEvaluation().getTradeAcceptability(tradeOffer.currentTrade, b,a,true) < 0)
|
||||
tradeOffer.acceptTrade()
|
||||
val tradeOffer2 = TradeLogic(a,b)
|
||||
assertTrue(TradeEvaluation().getTradeAcceptability(tradeOffer2.currentTrade.reverse(), b,a,true) > 0)
|
||||
}
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user