diff --git a/core/src/com/unciv/logic/automation/NextTurnAutomation.kt b/core/src/com/unciv/logic/automation/NextTurnAutomation.kt index 28f690b9..724f61db 100644 --- a/core/src/com/unciv/logic/automation/NextTurnAutomation.kt +++ b/core/src/com/unciv/logic/automation/NextTurnAutomation.kt @@ -2,11 +2,12 @@ package com.unciv.logic.automation import com.unciv.logic.civilization.CivilizationInfo import com.unciv.logic.civilization.PlayerType +import com.unciv.logic.civilization.diplomacy.DiplomaticStatus import com.unciv.logic.map.MapUnit +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.logic.civilization.diplomacy.DiplomaticStatus import com.unciv.models.gamebasics.GameBasics import com.unciv.models.gamebasics.tech.Technology import com.unciv.models.gamebasics.tr @@ -55,9 +56,9 @@ class NextTurnAutomation{ .filter { it.type == TradeType.Technology } for (theirOffer in theirTradableTechs) { - val theirValue = tradeLogic.evaluateOffer(theirOffer, false) + val theirValue = TradeEvaluation().evaluateBuyCost(theirOffer, civInfo, otherCiv) val ourOfferList = ourTradableTechs.filter{ - tradeLogic.evaluateOffer(it, false) == theirValue + TradeEvaluation().evaluateBuyCost(it, otherCiv, civInfo) == theirValue && !tradeLogic.currentTrade.ourOffers.contains(it) } if (ourOfferList.isNotEmpty()) { @@ -165,7 +166,7 @@ class NextTurnAutomation{ //pay for peace val tradeLogic = TradeLogic(civInfo, enemy) - var moneyWeNeedToPay = -tradeLogic.evaluatePeaceCostForThem() + var moneyWeNeedToPay = -TradeEvaluation().evaluatePeaceCostForThem(civInfo,enemy) if (moneyWeNeedToPay > tradeLogic.ourAvailableOffers.first { it.type == TradeType.Gold }.amount) { moneyWeNeedToPay = tradeLogic.ourAvailableOffers.first { it.type == TradeType.Gold }.amount } diff --git a/core/src/com/unciv/logic/trade/TradeEvaluation.kt b/core/src/com/unciv/logic/trade/TradeEvaluation.kt new file mode 100644 index 00000000..e84f4702 --- /dev/null +++ b/core/src/com/unciv/logic/trade/TradeEvaluation.kt @@ -0,0 +1,165 @@ +package com.unciv.logic.trade + +import com.unciv.logic.automation.Automation +import com.unciv.logic.automation.ThreatLevel +import com.unciv.logic.civilization.CivilizationInfo +import com.unciv.logic.civilization.diplomacy.DiplomaticStatus +import com.unciv.models.gamebasics.GameBasics +import com.unciv.models.gamebasics.tile.ResourceType +import kotlin.math.min +import kotlin.math.sqrt + +class TradeEvaluation{ + fun isTradeAcceptable(trade: Trade, evaluator: CivilizationInfo, tradePartner: CivilizationInfo): Boolean { + val sumOfTheirOffers = trade.theirOffers.asSequence() + .filter { it.type!= TradeType.Treaty } // since treaties should only be evaluated once for 2 sides + .map { evaluateBuyCost(it,evaluator,tradePartner) }.sum() + val sumOfOurOffers = trade.ourOffers.map { evaluateSellCost(it, evaluator, tradePartner)}.sum() + return sumOfOurOffers <= sumOfTheirOffers + } + + fun evaluateBuyCost(offer: TradeOffer, civInfo: CivilizationInfo, tradePartner: CivilizationInfo): Int { + when (offer.type) { + TradeType.Gold -> return offer.amount + TradeType.Gold_Per_Turn -> return offer.amount * offer.duration + TradeType.Treaty -> { + if (offer.name == "Peace Treaty") + return evaluatePeaceCostForThem(civInfo,tradePartner) // Since it will be evaluated twice, once when they evaluate our offer and once when they evaluate theirs + else return 1000 + } + + TradeType.Luxury_Resource -> { + val weAreMissingThisLux = !civInfo.hasResource(offer.name) // first off - do we want this for ourselves? + + val civsWhoWillTradeUsForTheLux = civInfo.diplomacy.values.map { it.civInfo } // secondly - should we buy this in order to resell it? + .filter { it != tradePartner } + .filter { !it.hasResource(offer.name) } //they don't have + val ourResourceNames = civInfo.getCivResources().map { it.key.name } + val civsWithLuxToTrade = civsWhoWillTradeUsForTheLux.filter { + // these are other civs who we could trade this lux away to, in order to get a different lux + it.getCivResources().any { + it.value > 1 && it.key.resourceType == ResourceType.Luxury //they have a lux we don't and will be willing to trade it + && !ourResourceNames.contains(it.key.name) + } + } + var numberOfCivsWhoWouldTradeUsForTheLux = civsWithLuxToTrade.count() + + var numberOfLuxesWeAreWillingToBuy = 0 + var cost = 0 + if (weAreMissingThisLux) { // for ourselves + numberOfLuxesWeAreWillingToBuy += 1 + cost += 250 + } + + while (numberOfLuxesWeAreWillingToBuy < offer.amount && numberOfCivsWhoWouldTradeUsForTheLux > 0) { + numberOfLuxesWeAreWillingToBuy += 1 // for reselling + cost += 50 + numberOfCivsWhoWouldTradeUsForTheLux -= 1 + } + + return cost + } + + TradeType.Strategic_Resource -> { + val resources = civInfo.getCivResourcesByName() + val amountWillingToBuy = resources[offer.name]!! - 2 + if (amountWillingToBuy <= 0) return 0 // we already have enough. + val amountToBuyInOffer = min(amountWillingToBuy, offer.amount) + + val canUseForBuildings = civInfo.cities + .any { city -> city.cityConstructions.getBuildableBuildings().any { it.requiredResource == offer.name } } + val canUseForUnits = civInfo.cities + .any { city -> city.cityConstructions.getConstructableUnits().any { it.requiredResource == offer.name } } + if (!canUseForBuildings && !canUseForUnits) return 0 + + return 50 * amountToBuyInOffer + } + + TradeType.Technology -> return sqrt(GameBasics.Technologies[offer.name]!!.cost.toDouble()).toInt()*20 + TradeType.Introduction -> return 250 + TradeType.WarDeclaration -> { + val nameOfCivToDeclareWarOn = offer.name.split(' ').last() + val civToDeclareWarOn = civInfo.gameInfo.getCivilization(nameOfCivToDeclareWarOn) + val threatToThem = Automation().threatAssessment(civInfo,civToDeclareWarOn) + + if(civInfo.diplomacy[nameOfCivToDeclareWarOn]!!.diplomaticStatus== DiplomaticStatus.War){ + when (threatToThem) { + ThreatLevel.VeryLow -> return 0 + ThreatLevel.Low -> return 0 + ThreatLevel.Medium -> return 100 + ThreatLevel.High -> return 500 + ThreatLevel.VeryHigh -> return 1000 + } + } + else return 0 // why should we pay you to go fight someone...? + } + TradeType.City -> { + val city = tradePartner.cities.first { it.name==offer.name } + val stats = city.cityStats.currentCityStats + if(civInfo.happiness + city.cityStats.happinessList.values.sum() < 0) + return 0 // we can't really afford to go into negative happiness because of buying a city + val sumOfStats = stats.culture+stats.gold+stats.science+stats.production+stats.happiness+stats.food + return sumOfStats.toInt() * 100 + } + } + } + + fun evaluateSellCost(offer: TradeOffer, civInfo: CivilizationInfo, tradePartner: CivilizationInfo): Int { + when (offer.type) { + TradeType.Gold -> return offer.amount + TradeType.Gold_Per_Turn -> return offer.amount * offer.duration + TradeType.Treaty -> { + if (offer.name == "Peace Treaty") + return evaluatePeaceCostForThem(civInfo,tradePartner) // Since it will be evaluated twice, once when they evaluate our offer and once when they evaluate theirs + else return 1000 + } + TradeType.Luxury_Resource -> { + if(civInfo.getCivResourcesByName()[offer.name]!!>1) + return 250 // fair price + else return 500 // you want to take away our last lux of this type?! + } + TradeType.Strategic_Resource -> return 50*offer.amount + TradeType.Technology -> return sqrt(GameBasics.Technologies[offer.name]!!.cost.toDouble()).toInt()*20 + TradeType.Introduction -> return 250 + TradeType.WarDeclaration -> { + val nameOfCivToDeclareWarOn = offer.name.split(' ').last() + val civToDeclareWarOn = civInfo.gameInfo.getCivilization(nameOfCivToDeclareWarOn) + val threatToUs = Automation().threatAssessment(civInfo, civToDeclareWarOn) + + when (threatToUs) { + ThreatLevel.VeryLow -> return 100 + ThreatLevel.Low -> return 250 + ThreatLevel.Medium -> return 500 + ThreatLevel.High -> return 1000 + ThreatLevel.VeryHigh -> return 10000 // no way boyo + } + } + + TradeType.City -> { + val city = civInfo.cities.first { it.name==offer.name } + val stats = city.cityStats.currentCityStats + val sumOfStats = stats.culture+stats.gold+stats.science+stats.production+stats.happiness+stats.food + return sumOfStats.toInt() * 100 + } + } + } + + fun evaluatePeaceCostForThem(ourCivilization: CivilizationInfo, otherCivilization: CivilizationInfo): Int { + val ourCombatStrength = Automation().evaluteCombatStrength(ourCivilization) + val theirCombatStrength = Automation().evaluteCombatStrength(otherCivilization) + if(ourCombatStrength==theirCombatStrength) return 0 + if(ourCombatStrength==0) return -1000 + if(theirCombatStrength==0) return 1000 // Chumps got no cities or units + if(ourCombatStrength>theirCombatStrength){ + val absoluteAdvantage = ourCombatStrength-theirCombatStrength + val percentageAdvantage = absoluteAdvantage / theirCombatStrength.toFloat() + return (absoluteAdvantage*percentageAdvantage).toInt() * 10 + } + else{ + val absoluteAdvantage = theirCombatStrength-ourCombatStrength + val percentageAdvantage = absoluteAdvantage / ourCombatStrength.toFloat() + return -(absoluteAdvantage*percentageAdvantage).toInt() * 10 + } + } + +} \ No newline at end of file diff --git a/core/src/com/unciv/logic/trade/TradeLogic.kt b/core/src/com/unciv/logic/trade/TradeLogic.kt index 7aafd788..5942f3e3 100644 --- a/core/src/com/unciv/logic/trade/TradeLogic.kt +++ b/core/src/com/unciv/logic/trade/TradeLogic.kt @@ -1,14 +1,9 @@ package com.unciv.logic.trade -import com.unciv.logic.automation.Automation -import com.unciv.logic.automation.ThreatLevel import com.unciv.logic.civilization.CivilizationInfo import com.unciv.logic.civilization.diplomacy.DiplomaticStatus -import com.unciv.models.gamebasics.GameBasics import com.unciv.models.gamebasics.tile.ResourceType import com.unciv.models.gamebasics.tr -import kotlin.math.min -import kotlin.math.sqrt class TradeLogic(val ourCivilization:CivilizationInfo, val otherCivilization: CivilizationInfo){ @@ -54,123 +49,7 @@ class TradeLogic(val ourCivilization:CivilizationInfo, val otherCivilization: Ci return offers } - fun isTradeAcceptable(): Boolean { - val sumOfTheirOffers = currentTrade.theirOffers.asSequence() - .filter { it.type!= TradeType.Treaty } // since treaties should only be evaluated once for 2 sides - .map { evaluateOffer(it,false) }.sum() - val sumOfOurOffers = currentTrade.ourOffers.map { evaluateOffer(it,true)}.sum() - return sumOfOurOffers >= sumOfTheirOffers - } - - fun evaluateOffer(offer: TradeOffer, otherCivIsRecieving:Boolean): Int { - when(offer.type) { - TradeType.Gold -> return offer.amount - TradeType.Gold_Per_Turn -> return offer.amount*offer.duration - TradeType.Luxury_Resource -> { - if(!otherCivIsRecieving){ // they're giving us - var value = 300*offer.amount - if(!theirAvailableOffers.any { it.name==offer.name }) // We want to take away their last luxury or give them one they don't have - value += 400 - return value - } - else{ - val civsWhoWillTradeUsForTheLux = ourCivilization.diplomacy.values.map { it.civInfo } - .filter { it!= otherCivilization } - .filter { !it.hasResource(offer.name) } //they don't have - val ourResourceNames = ourCivilization.getCivResources().map { it.key.name } - val civsWithLuxToTrade = civsWhoWillTradeUsForTheLux.filter { - it.getCivResources().any { - it.value > 1 && it.key.resourceType == ResourceType.Luxury //they have a lux we don't and will be willing to trade it - && !ourResourceNames.contains(it.key.name) - } - } - - var value = 50*min(offer.amount,civsWithLuxToTrade.size) // they'll buy at 50 each only, and that's so they can trade it away - if(!theirAvailableOffers.any { it.name==offer.name }) - value+=200 // only if they're lacking will they buy the first one at 240 (Civ V standard, see https://www.reddit.com/r/civ/comments/1go7i9/luxury_and_strategic_resource_pricing/ & others) - return value - } - } - TradeType.Technology -> return sqrt(GameBasics.Technologies[offer.name]!!.cost.toDouble()).toInt()*20 - TradeType.Strategic_Resource -> { - if(otherCivIsRecieving) { - val resources = ourCivilization.getCivResourcesByName() - if (resources[offer.name]!! >= 2) return 0 // we already have enough. - val canUseForBuildings = ourCivilization.cities - .any { city-> city.cityConstructions.getBuildableBuildings().any { it.requiredResource==offer.name } } - val canUseForUnits = ourCivilization.cities - .any { city-> city.cityConstructions.getConstructableUnits().any { it.requiredResource==offer.name } } - if(!canUseForBuildings && !canUseForUnits) return 0 - return 50 * offer.amount - } - else return 50 * offer.amount - } - TradeType.City -> { - val civ = if(otherCivIsRecieving) ourCivilization else otherCivilization - val city = civ.cities.first { it.name==offer.name } - val stats = city.cityStats.currentCityStats - val sumOfStats = stats.culture+stats.gold+stats.science+stats.production+stats.happiness+stats.food - return sumOfStats.toInt() * 100 - } - TradeType.Treaty -> { - if(offer.name=="Peace Treaty") - return evaluatePeaceCostForThem() // Since it will be evaluated twice, once when they evaluate our offer and once when they evaluate theirs - else return 1000 - } - TradeType.Introduction -> return 250 - TradeType.WarDeclaration -> { - val nameOfCivToDeclareWarOn = offer.name.split(' ').last() - val civToDeclareWarOn = ourCivilization.gameInfo.getCivilization(nameOfCivToDeclareWarOn) - val threatToThem = Automation().threatAssessment(otherCivilization,civToDeclareWarOn) - - if(!otherCivIsRecieving) { // we're getting this from them, that is, they're declaring war - when (threatToThem) { - ThreatLevel.VeryLow -> return 100 - ThreatLevel.Low -> return 250 - ThreatLevel.Medium -> return 500 - ThreatLevel.High -> return 1000 - ThreatLevel.VeryHigh -> return 10000 // no way - } - } - else{ - if(otherCivilization.diplomacy[nameOfCivToDeclareWarOn]!!.diplomaticStatus== DiplomaticStatus.War){ - when (threatToThem) { - ThreatLevel.VeryLow -> return 0 - ThreatLevel.Low -> return 0 - ThreatLevel.Medium -> return 100 - ThreatLevel.High -> return 500 - ThreatLevel.VeryHigh -> return 1000 - } - } - else return 0 // why should we pay you to go fight someone...? - } - - } - // Dunno what this is? - else -> return 1000 - } - } - - fun evaluatePeaceCostForThem(): Int { - val ourCombatStrength = Automation().evaluteCombatStrength(ourCivilization) - val theirCombatStrength = Automation().evaluteCombatStrength(otherCivilization) - if(ourCombatStrength==theirCombatStrength) return 0 - if(ourCombatStrength==0) return -1000 - if(theirCombatStrength==0) return 1000 // Chumps got no cities or units - if(ourCombatStrength>theirCombatStrength){ - val absoluteAdvantage = ourCombatStrength-theirCombatStrength - val percentageAdvantage = absoluteAdvantage / theirCombatStrength.toFloat() - return (absoluteAdvantage*percentageAdvantage).toInt() * 10 - } - else{ - val absoluteAdvantage = theirCombatStrength-ourCombatStrength - val percentageAdvantage = absoluteAdvantage / ourCombatStrength.toFloat() - return -(absoluteAdvantage*percentageAdvantage).toInt() * 10 - } - } - fun acceptTrade() { - ourCivilization.diplomacy[otherCivilization.civName]!!.trades.add(currentTrade) otherCivilization.diplomacy[ourCivilization.civName]!!.trades.add(currentTrade.reverse()) @@ -212,3 +91,4 @@ class TradeLogic(val ourCivilization:CivilizationInfo, val otherCivilization: Ci transferTrade(otherCivilization,ourCivilization,currentTrade.reverse()) } } + diff --git a/core/src/com/unciv/ui/trade/TradeTable.kt b/core/src/com/unciv/ui/trade/TradeTable.kt index bb48dfe0..9054eaa4 100644 --- a/core/src/com/unciv/ui/trade/TradeTable.kt +++ b/core/src/com/unciv/ui/trade/TradeTable.kt @@ -5,6 +5,7 @@ import com.badlogic.gdx.scenes.scene2d.ui.Label import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.scenes.scene2d.ui.TextButton import com.unciv.logic.civilization.CivilizationInfo +import com.unciv.logic.trade.TradeEvaluation import com.unciv.logic.trade.TradeLogic import com.unciv.models.gamebasics.tr import com.unciv.ui.utils.CameraStageBaseScreen @@ -32,7 +33,7 @@ class TradeTable(val otherCivilization: CivilizationInfo, stage: Stage, onTradeC if(tradeLogic.currentTrade.theirOffers.size==0 && tradeLogic.currentTrade.ourOffers.size==0){ tradeText.setText(otherCivilization.getTranslatedNation().neutralLetsHearIt.random().tr()) } - else if (tradeLogic.isTradeAcceptable()){ + else if (TradeEvaluation().isTradeAcceptable(tradeLogic.currentTrade.reverse(),otherCivilization,currentPlayerCiv)){ tradeText.setText(otherCivilization.getTranslatedNation().neutralYes.random().tr()) offerButton.setText("Accept".tr()) }