Resolved #601 - gold luxury not accepted in trades
Organized trade buy/sell logic - should be much easier to understand now! Split into Evaluation and the current trade deal, evaluation split into buying and selling
This commit is contained in:
parent
81d8dda111
commit
0b47289c3d
4 changed files with 173 additions and 126 deletions
|
@ -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
|
||||
}
|
||||
|
|
165
core/src/com/unciv/logic/trade/TradeEvaluation.kt
Normal file
165
core/src/com/unciv/logic/trade/TradeEvaluation.kt
Normal file
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue