Trade deals price and value

tu_79

Deity
Joined
Feb 11, 2016
Messages
7,376
Location
Malaga (Spain)
Hello everyone.
I'd like to discuss how the price is currently calculated for any trade deal, and how to improve it, if possible.

Relevant code:
Code:
/// How much GPT should be provided if we're trying to make it worth iValue?
int CvDealAI::GetGPTforForValueExchange(int iGPTorValue, bool bNumGPTFromValue, int iNumTurns, bool bFromMe, PlayerTypes eOtherPlayer, bool bUseEvenValue, bool bRoundUp, bool bLogging)
{
    CvAssertMsg(GetPlayer()->GetID() != eOtherPlayer, "DEAL_AI: Trying to check value of GPT with oneself.  Please send Jon this with your last 5 autosaves and what changelist # you're playing.");

    int iValueTimes100 = 0;

    // We passed in Value, we want to know how much GPT we get for it
    if(bNumGPTFromValue)
    {
        iValueTimes100 = iGPTorValue;
        iValueTimes100 /= iNumTurns;

        if (iGPTorValue > (GetPlayer()->calculateGoldRate() - 2))
        {
            iGPTorValue = (GetPlayer()->calculateGoldRate() - 2);
        }
    }
    else
    {
        if (!bLogging && bFromMe && iGPTorValue > (GetPlayer()->calculateGoldRate() - 2))
            return MAX_INT;

        iValueTimes100 = (iGPTorValue * iNumTurns);

        //let's assume an interest rate of 0.1% per turn, no compounding
        int iInterestPercent = 100 * (iNumTurns * /*1*/ GC.getEACH_GOLD_PER_TURN_VALUE_PERCENT()) / 1000;

        //subtract interest. 100 gold now is better than 100 gold in the future
        iValueTimes100 -= (iValueTimes100*iInterestPercent) / 100;
    }

    // Sometimes we want to round up. Let's say the AI offers a deal to the human. We have to ensure that the human can also offer that deal back and the AI will accept (and vice versa)
    if(bRoundUp)
    {
        iValueTimes100 += 99;
    }

    int iReturnValue = iValueTimes100;

    // Are we trying to find the middle point between what we think this item is worth and what another player thinks it's worth?
    if(bUseEvenValue)
    {
        iReturnValue += GET_PLAYER(eOtherPlayer).GetDealAI()->GetGPTforForValueExchange(iGPTorValue, bNumGPTFromValue, iNumTurns, !bFromMe, GetPlayer()->GetID(), /*bUseEvenValue*/ false, bRoundUp);

        iReturnValue /= 2;
    }

    return iReturnValue;
}
This function gives an amount of gold per turn which is equivalent to the trade value. You throw a value for the deal, the deal duration, and it says how much it is worth in gold per turn for this player.
As I see it, the function assumes that 1 point of value is worth 1 gpt when using the logic bNumGPTFromValue. The only safeguard is for preventing the player from paying more than he can afford, so after the deal the player will still net at least 2 gpt.
When using the no bNumGPTFromValue logic, it considers that the money has an interest rate of 0.1% (not sure if it is WAD) and throw a smaller amount of gpt that matches the deal value.
And then it has a few extra options, like rounding up the final value and finding the average between what both players consider that the deal is worth.

As we see, any inflation on the trade value comes from the value that is given to the non monetary part. Let's look at how it calculates the value for resources.
Code:
int CvDealAI::GetResourceValue(ResourceTypes eResource, int iResourceQuantity, int iNumTurns, bool bFromMe, PlayerTypes eOtherPlayer, int iCurrentNetGoldOfReceivingPlayer)
{
    const CvResourceInfo* pkResourceInfo = GC.getResourceInfo(eResource);
    CvAssert(pkResourceInfo != NULL);
    if (pkResourceInfo == NULL)
        return 0;

    ResourceUsageTypes eUsage = pkResourceInfo->getResourceUsage();
    if (eUsage == RESOURCEUSAGE_LUXURY)
        return GetLuxuryResourceValue(eResource, iNumTurns, bFromMe, eOtherPlayer, iCurrentNetGoldOfReceivingPlayer);
    else
        return GetStrategicResourceValue(eResource, iResourceQuantity, iNumTurns, bFromMe, eOtherPlayer, iCurrentNetGoldOfReceivingPlayer);
}
Ok, here it just separates Luxuries from Strategics.

Code:
/// How much is a Resource worth?
int CvDealAI::GetLuxuryResourceValue(ResourceTypes eResource, int iNumTurns, bool bFromMe, PlayerTypes eOtherPlayer, int iCurrentNetGoldOfReceivingPlayer)
{
    CvAssertMsg(GetPlayer()->GetID() != eOtherPlayer, "DEAL_AI: Trying to check value of a Resource with oneself.  Please send Jon this with your last 5 autosaves and what changelist # you're playing.");

    const CvResourceInfo* pkResourceInfo = GC.getResourceInfo(eResource);
    CvAssert(pkResourceInfo != NULL);
    if (pkResourceInfo == NULL)
        return 0;

    //Integer zero check...
    if (iNumTurns <= 0)
        iNumTurns = 1;

    //how much happiness from one additional luxury?
    int iBaseHappiness = 0;
    if (bFromMe)
        iBaseHappiness += GetPlayer()->GetHappinessFromLuxury(eResource);
    else
        iBaseHappiness += GET_PLAYER(eOtherPlayer).GetHappinessFromLuxury(eResource);

    int iItemValue = max(1, iBaseHappiness) * iNumTurns;

    //Let's look at flavors for resources
    int iFlavorResult = 0;
    int iFlavors = 0;
    for (int i = 0; i < GC.getNumFlavorTypes(); i++)
    {
        int iResourceFlavor = pkResourceInfo->getFlavorValue((FlavorTypes)i);
        if (iResourceFlavor > 0)
        {
            int iPersonalityFlavorValue = GetPlayer()->GetFlavorManager()->GetPersonalityIndividualFlavor((FlavorTypes)i);
            //Has to be above average to affect price. Will usually result in a x2-x3 modifier
            iFlavorResult += ((iResourceFlavor + iPersonalityFlavorValue) / 6);
            iFlavors++;
        }
    }
    if ((iFlavorResult > 0) && (iFlavors > 0))
    {
        //Get the average multiplier from the number of Flavors being considered.
        iItemValue *= (iFlavorResult / iFlavors);
    }
            
    if (bFromMe)
    {
        //Every x gold in net GPT will increase resource value by 1, up to the value of the item itself (so never more than double).
        int iGPT = int(0.5+sqrt(iCurrentNetGoldOfReceivingPlayer/3.));
        if (iGPT > 0)
            iItemValue += min(iGPT,iItemValue);
    }
    else
    {
        //Every x gold in net GPT will increase resource value by 1, up to the value of the item itself (so never more than double).
        int iGPT = int(0.5+sqrt(iCurrentNetGoldOfReceivingPlayer/4.));
        if (iGPT > 0)
            iItemValue += min(iGPT,iItemValue);
    }

    if (bFromMe)
    {
        if (GetPlayer()->IsEmpireUnhappy() && GetPlayer()->getNumResourceAvailable(eResource) == 1)
        {
            return INT_MAX;
        }
        if (GC.getGame().GetGameLeagues()->IsLuxuryHappinessBanned(GetPlayer()->GetID(), eResource))
        {
            return INT_MAX;
        }
        if (GetPlayer()->getNumResourceAvailable(eResource) == 1)
        {
            int iFactor = GetPlayer()->GetPlayerTraits()->GetLuxuryHappinessRetention() ? 2 : 3;
            const CvReligion* pReligion = GC.getGame().GetGameReligions()->GetReligion(GetPlayer()->GetReligions()->GetCurrentReligion(), GetPlayer()->GetID());
            if (pReligion)
            {
                for (int iJ = 0; iJ < NUM_YIELD_TYPES; iJ++)
                {
                    if (pReligion->m_Beliefs.GetYieldPerLux((YieldTypes)iJ, GetPlayer()->GetID(), GetPlayer()->getCapitalCity()) > 0)
                    {
                        iFactor += 1;
                    }
                }
            }

            iItemValue *= iFactor; //last one is x as valuable
        }

        //Let's consider how many resources each player has - if he has more than us, ours is worth more (and vice-versa).
        int iOtherHappiness = GET_PLAYER(eOtherPlayer).GetHappinessFromResources() + GET_PLAYER(eOtherPlayer).GetHappinessFromResourceVariety();
        int iOurHappiness = GetPlayer()->GetHappinessFromResources() + GetPlayer()->GetHappinessFromResourceVariety();
        //He's happier than us?
        if (iOtherHappiness >= iOurHappiness)
        {
            iItemValue *= 10;
            iItemValue /= 11;
        }
        //He is less happy than we are?
        else
        {
            iItemValue *= 11;
            iItemValue /= 10;
        }

        //How much is OUR stuff worth?
        switch (GetPlayer()->GetDiplomacyAI()->GetMajorCivApproach(eOtherPlayer, /*bHideTrueFeelings*/ false))
        {
        case MAJOR_CIV_APPROACH_FRIENDLY:
            iItemValue *= 90;
            iItemValue /= 100;
            break;
        case MAJOR_CIV_APPROACH_AFRAID:
            iItemValue *= 90;
            iItemValue /= 100;
            break;
        case MAJOR_CIV_APPROACH_NEUTRAL:
            iItemValue *= 100;
            iItemValue /= 100;
            break;
        case MAJOR_CIV_APPROACH_GUARDED:
            iItemValue *= 150;
            iItemValue /= 100;
            break;
        case MAJOR_CIV_APPROACH_DECEPTIVE:
            iItemValue *= 100;
            iItemValue /= 100;
            break;
        case MAJOR_CIV_APPROACH_HOSTILE:
            iItemValue *= 200;
            iItemValue /= 100;
            break;
        case MAJOR_CIV_APPROACH_WAR:
            iItemValue *= 250;
            iItemValue /= 100;
            break;
        }

        return iItemValue;
    }
    else
    {
        if (GC.getGame().GetGameLeagues()->IsLuxuryHappinessBanned(GetPlayer()->GetID(), eResource))
            return 0;
        if (GetPlayer()->getNumResourceAvailable(eResource) > 0 && !GetPlayer()->GetPlayerTraits()->IsImportsCountTowardsMonopolies())
            return 0;

        if (GetPlayer()->IsEmpireUnhappy())
        {
            iItemValue += GetPlayer()->GetUnhappiness() * 2;
        }

        if (GetPlayer()->GetPlayerTraits()->IsImportsCountTowardsMonopolies() && GetPlayer()->GetMonopolyPercent(eResource) < GC.getGame().GetGreatestPlayerResourceMonopolyValue(eResource))
        {
            //we don't want resources that won't get us a bonus.
            int iNumResourceOwned = GetPlayer()->getNumResourceTotal(eResource, false);
            int iNumResourceImported = GetPlayer()->getNumResourceTotal(eResource, true);
            //FIXME: does this make sense?
            if (iNumResourceOwned == 0 && iNumResourceImported > 0)
            {
                bool bBad = false;
                for (int iJ = 0; iJ < NUM_YIELD_TYPES; iJ++)
                {
                    if (pkResourceInfo->getYieldChangeFromMonopoly((YieldTypes)iJ) > 0)
                    {
                        bBad = true;

                        if (GET_PLAYER(eOtherPlayer).isHuman())
                        {
                            return 5;
                        }
                        else
                        {
                            return INT_MAX;
                        }
                    }
                }
                if (!bBad)
                {
                    iItemValue *= (100 + GetPlayer()->GetMonopolyPercent(eResource));
                    iItemValue /= 100;
                }
            }
        }
        
        int iFactor = 1;
        const CvReligion* pReligion = GC.getGame().GetGameReligions()->GetReligion(GetPlayer()->GetReligions()->GetCurrentReligion(), GetPlayer()->GetID());
        if (pReligion)
        {
            for (int iJ = 0; iJ < NUM_YIELD_TYPES; iJ++)
            {
                if (pReligion->m_Beliefs.GetYieldPerLux((YieldTypes)iJ, GetPlayer()->GetID(), GetPlayer()->getCapitalCity()) > 0)
                {
                    iFactor += 1;
                }
            }
            if (iFactor > 1)
            {
                iItemValue *= iFactor; //last one is x as valuable
                iItemValue /= 2;
            }
        }

        //Let's consider how many resources each player has - if he has more than us, ours is worth more (and vice-versa).
        int iOtherHappiness = GET_PLAYER(eOtherPlayer).GetHappinessFromResources() + GET_PLAYER(eOtherPlayer).GetHappinessFromResourceVariety();
        int iOurHappiness = GetPlayer()->GetHappinessFromResources() + GetPlayer()->GetHappinessFromResourceVariety();
        //He's happier than us?
        if (iOtherHappiness >= iOurHappiness)
        {
            iItemValue *= 11;
            iItemValue /= 10;
        }
        //He is less happy than we are?
        else
        {
            iItemValue *= 10;
            iItemValue /= 11;
        }

        //How much is THEIR stuff worth?
        switch (GetPlayer()->GetDiplomacyAI()->GetMajorCivApproach(eOtherPlayer, /*bHideTrueFeelings*/ false))
        {
        case MAJOR_CIV_APPROACH_FRIENDLY:
            iItemValue *= 110;
            iItemValue /= 100;
            break;
        case MAJOR_CIV_APPROACH_AFRAID:
            iItemValue *= 110;
            iItemValue /= 100;
            break;
        case MAJOR_CIV_APPROACH_NEUTRAL:
            iItemValue *= 100;
            iItemValue /= 100;
            break;
        case MAJOR_CIV_APPROACH_GUARDED:
            iItemValue *= 75;
            iItemValue /= 100;
            break;
        case MAJOR_CIV_APPROACH_DECEPTIVE:
            iItemValue *= 100;
            iItemValue /= 100;
            break;
        case MAJOR_CIV_APPROACH_HOSTILE:
            iItemValue *= 75;
            iItemValue /= 100;
            break;
        case MAJOR_CIV_APPROACH_WAR:
            iItemValue *= 50;
            iItemValue /= 100;
            break;
        }

        return iItemValue;
    }
}

Now this is big. Luxuries. It starts by calculating how many happiness the player will gain during the deal. 3 happiness for 20 turns is 60 points of value, and it is never less than 20 points (minimum 1 point per turn).
It then modifies the value upon the flavor of the player (some players have an increased appeal for luxuries), adding even more value.

When selling, for every net GPT that the buyer has, it increases the final value by 1, up to doubling the base value. Then it makes some considerations about it being the last luxury we can sell, and whether the deal will make us or our trade partner happier. If the other player is currently happier than us, then the trade value is decreased by 10%, assuming the other player is willing to accept a reduction in its happiness.
When buying, it first avoids purchasing a luxury we already have. Then it looks at how good that luxury will be for us, if it will give a monopoly or how much happiness we will get, doubling the value in case we suffer from unhappiness. Adds in whether we get some bonus from religion.

And finally the value is increased or decreased by a percentage depending on our relationship.
(NOTE: I didn't see anything related to We Love The King Days).

Code:
int CvDealAI::GetResourceRatio(PlayerTypes ePlayer, PlayerTypes eOtherPlayer, ResourceTypes eResource, int iNumInTrade)
{
    bool bImSelling = ePlayer == GetPlayer()->GetID();
    int iBase = bImSelling ? 5 : 1;
    //Ratio between 0 and 100.
    int iPlayer1 = GET_PLAYER(ePlayer).getNumResourceAvailable(eResource, false);
    int iPlayer2 = GET_PLAYER(eOtherPlayer).getNumResourceAvailable(eResource, false);
    if (bImSelling)
        iPlayer1 -= iNumInTrade;
    else
        iPlayer2 -= iNumInTrade;

    int iValue = (iPlayer1 * 100) / max(1, iPlayer2);

    //I'm selling? Lower ratio means I have fewer (and mine are worth way more!)
    if (bImSelling)
        iBase *= (100 - iValue);
    //I'm buying? Lower ratio means I have more (and theirs are worth way less!)
    else
        iBase *= iValue;
    return max(0, iBase);
}

/// How much is a Resource worth?
int CvDealAI::GetStrategicResourceValue(ResourceTypes eResource, int iResourceQuantity, int iNumTurns, bool bFromMe, PlayerTypes eOtherPlayer, int iCurrentNetGoldOfReceivingPlayer)
{
    CvAssertMsg(GetPlayer()->GetID() != eOtherPlayer, "DEAL_AI: Trying to check value of a Resource with oneself.  Please send Jon this with your last 5 autosaves and what changelist # you're playing.");

    int iItemValue = 10 + (2 * GC.getGame().getCurrentEra());

    const CvResourceInfo* pkResourceInfo = GC.getResourceInfo(eResource);
    CvAssert(pkResourceInfo != NULL);
    if (pkResourceInfo == NULL)
        return 0;

    if (iResourceQuantity == 0)
        return 0;

    //Integer zero check...
    if (iNumTurns <= 0)
        iNumTurns = 1;

    int iFlavorResult = 0;
    int iFlavors = 0;

    //Let's look at flavors for resources
    for (int i = 0; i < GC.getNumFlavorTypes(); i++)
    {
        int iResourceFlavor = pkResourceInfo->getFlavorValue((FlavorTypes)i);
        if (iResourceFlavor > 0)
        {
            int iPersonalityFlavorValue = GetPlayer()->GetFlavorManager()->GetPersonalityIndividualFlavor((FlavorTypes)i);
            //Has to be above average to affect price. Will usually result in a x2-x3 modifier
            iFlavorResult += ((iResourceFlavor + iPersonalityFlavorValue) / 5);
            iFlavors++;
        }
    }
    //Get the average multiplier from the number of Flavors being considered.
    if ((iFlavorResult > 0) && (iFlavors > 0))
        iItemValue *= (iFlavorResult / iFlavors);

    if (bFromMe)
    {
        //Every x gold in net GPT will increase resource value by 1, up to the value of the item itself (so never more than double).
        int iGPT = int(0.5+sqrt(iCurrentNetGoldOfReceivingPlayer/4.));
        if (iGPT > 0)
            iItemValue += min(iGPT,iItemValue);
    }
    else
    {
        //Every x gold in net GPT will increase resource value by 1, up to the value of the item itself (so never more than double).
        int iGPT = int(0.5+sqrt(iCurrentNetGoldOfReceivingPlayer/5.));
        if (iGPT > 0)
            iItemValue += min(iGPT,iItemValue);
    }
    
    if (bFromMe)
    {
        if (!GET_TEAM(GetPlayer()->getTeam()).IsResourceObsolete(eResource))
        {
            //Never trade away everything.
            int iNumRemaining = (GetPlayer()->getNumResourceAvailable(eResource, false) - iResourceQuantity);
            if (iNumRemaining <= 0)
                return INT_MAX;

            //If they're stronger than us, strategic resources are valuable.
            if (GetPlayer()->GetMilitaryMight() < GET_PLAYER(eOtherPlayer).GetMilitaryMight())
            {
                iItemValue *= 5;
                iItemValue /= 3;
            }
            else
            {
                iItemValue *= 9;
                iItemValue /= 10;
            }
            //Good target? Don't sell to them!
            bool bGood = false;
            PlayerTypes eLoopPlayer;
            for (int iPlayerLoop = 0; iPlayerLoop < MAX_CIV_PLAYERS; iPlayerLoop++)
            {
                eLoopPlayer = (PlayerTypes)iPlayerLoop;
                if (GET_PLAYER(eLoopPlayer).isAlive() && !GET_PLAYER(eLoopPlayer).isMinorCiv() && eLoopPlayer != eOtherPlayer && eLoopPlayer != GetPlayer()->GetID())
                {
                    if (GetPlayer()->GetDiplomacyAI()->IsWantsSneakAttack(eLoopPlayer))
                    {
                        bGood = true;
                    }
                }
            }
            if (bGood)
            {
                iItemValue *= 3;
            }
            //Are they close, or far away? We should always be a bit less eager to sell war resources from neighbors.
            if (GetPlayer()->GetProximityToPlayer(eOtherPlayer) >= PLAYER_PROXIMITY_CLOSE)
            {
                iItemValue *= 3;
                iItemValue /= 2;
            }
            //Are we going for science win? Don't sell aluminum!
            ProjectTypes eApolloProgram = (ProjectTypes)GC.getInfoTypeForString("PROJECT_APOLLO_PROGRAM", true);
            if (eApolloProgram != NO_PROJECT)
            {
                if (GetPlayer()->GetDiplomacyAI()->IsGoingForSpaceshipVictory() || GET_TEAM(GET_PLAYER(eOtherPlayer).getTeam()).getProjectCount(eApolloProgram) > 0)
                {
                    ResourceTypes eAluminumResource = (ResourceTypes)GC.getInfoTypeForString("RESOURCE_ALUMINUM", true);
                    if (eResource == eAluminumResource)
                    {
                        return INT_MAX;
                    }
                }
            }

            if (MOD_DIPLOMACY_CITYSTATES)
            {
                ResourceTypes ePaper = (ResourceTypes)GC.getInfoTypeForString("RESOURCE_PAPER", true);
                if (eResource == ePaper)
                {
                    if (GetPlayer()->GetDiplomacyAI()->IsGoingForDiploVictory())
                    {
                        return INT_MAX;
                    }
                }
            }

            //Increase value based on number remaining (up to 10).
            iItemValue += ((10 - min(10, iNumRemaining)) * (10 - min(10, iNumRemaining)) * 10);

            //How much do we have compared to them?
            int iResourceRatio = GetResourceRatio(GetPlayer()->GetID(), eOtherPlayer, eResource, iResourceQuantity);

            //More we have compared to them, the less what we have is worth,and vice-versa!
            iItemValue *= (100 + iResourceRatio);
            iItemValue /= 100;

            // Approach is important
            switch (GetPlayer()->GetDiplomacyAI()->GetMajorCivApproach(eOtherPlayer, /*bHideTrueFeelings*/ false))
            {
            case MAJOR_CIV_APPROACH_FRIENDLY:
                iItemValue *= 90;
                iItemValue /= 100;
                break;
            case MAJOR_CIV_APPROACH_AFRAID:
                iItemValue *= 90;
                iItemValue /= 100;
                break;
            case MAJOR_CIV_APPROACH_NEUTRAL:
                iItemValue *= 100;
                iItemValue /= 100;
                break;
            case MAJOR_CIV_APPROACH_GUARDED:
                iItemValue *= 125;
                iItemValue /= 100;
                break;
            case MAJOR_CIV_APPROACH_DECEPTIVE:
                iItemValue *= 150;
                iItemValue /= 100;
                break;
            case MAJOR_CIV_APPROACH_HOSTILE:
                iItemValue *= 200;
                iItemValue /= 100;
                break;
            case MAJOR_CIV_APPROACH_WAR:
                iItemValue *= 300;
                iItemValue /= 100;
                break;
            }

            //And now speed/quantity.
            iItemValue *= (iResourceQuantity * iNumTurns);
            iItemValue /= 10;

            return iItemValue;
        }
        else
        {
            return INT_MAX;
        }
    }
    else
    {
        if (!GET_TEAM(GetPlayer()->getTeam()).IsResourceObsolete(eResource))
        {
            //If they're stronger than us, strategic resources are less valuable, as we might war soon.
            if (GetPlayer()->GetMilitaryMight() < GET_PLAYER(eOtherPlayer).GetMilitaryMight())
            {
                iItemValue *= 3;
                iItemValue /= 5;
            }
            else
            {
                iItemValue *= 10;
                iItemValue /= 9;
            }
            //Good target? Don't buy from them!
            bool bGood = false;
            PlayerTypes eLoopPlayer;
            for (int iPlayerLoop = 0; iPlayerLoop < MAX_CIV_PLAYERS; iPlayerLoop++)
            {
                eLoopPlayer = (PlayerTypes)iPlayerLoop;
                if (GET_PLAYER(eLoopPlayer).isAlive() && !GET_PLAYER(eLoopPlayer).isMinorCiv() && eLoopPlayer != eOtherPlayer && eLoopPlayer != GetPlayer()->GetID())
                {
                    if (GetPlayer()->GetDiplomacyAI()->IsWantsSneakAttack(eLoopPlayer))
                    {
                        bGood = true;
                    }
                }
            }
            if (bGood)
            {
                iItemValue /= 2;
            }
            //Are they close, or far away? We should always be a bit less eager to buy war resources from neighbors.
            if (GetPlayer()->GetProximityToPlayer(eOtherPlayer) >= PLAYER_PROXIMITY_CLOSE)
            {
                iItemValue *= 2;
                iItemValue /= 3;
            }
            //Are we going for science win? Buy aluminum!
            ProjectTypes eApolloProgram = (ProjectTypes)GC.getInfoTypeForString("PROJECT_APOLLO_PROGRAM", true);
            if (!bFromMe && eApolloProgram != NO_PROJECT)
            {
                if (GET_TEAM(GET_PLAYER(eOtherPlayer).getTeam()).getProjectCount(eApolloProgram) > 0)
                {
                    ResourceTypes eAluminumResource = (ResourceTypes)GC.getInfoTypeForString("RESOURCE_ALUMINUM", true);
                    if (eResource == eAluminumResource)
                    {
                        iItemValue *= 3;
                        iItemValue /= 2;
                    }
                }
            }

            if (MOD_DIPLOMACY_CITYSTATES)
            {
                ResourceTypes ePaper = (ResourceTypes)GC.getInfoTypeForString("RESOURCE_PAPER", true);
                if (eResource == ePaper)
                {
                    if (GetPlayer()->GetDiplomacyAI()->IsGoingForDiploVictory())
                    {
                        iItemValue *= 3;
                        iItemValue /= 2;
                    }
                }
            }
            if (GetPlayer()->GetPlayerTraits()->IsImportsCountTowardsMonopolies() && GetPlayer()->GetMonopolyPercent(eResource) < GC.getGame().GetGreatestPlayerResourceMonopolyValue(eResource))
            {
                int iNumResourceOwned = GetPlayer()->getNumResourceTotal(eResource, false);
                //we don't want resources that won't get us a bonus.
                bool bBad = false;
                for (int iJ = 0; iJ < NUM_YIELD_TYPES; iJ++)
                {
                    if (pkResourceInfo->getYieldChangeFromMonopoly((YieldTypes)iJ) > 0 && iNumResourceOwned <= 0)
                    {
                        return 0;
                    }
                }
                if (!bBad)
                {
                    //More we have compared to them, the less what they have is worth, and vice-versa!
                    iItemValue *= (100 + (GetPlayer()->GetMonopolyPercent(eResource) * 2));
                    iItemValue /= 100;
                }
            }
            else
            {
                //How much do they have compared to us?
                int iResourceRatio = GetResourceRatio(eOtherPlayer, GetPlayer()->GetID(), eResource, iResourceQuantity);
                //More we have compared to them, the less what they have is worth, and vice-versa!
                if (iResourceRatio <= 100)
                {
                    iItemValue *= max(1, iResourceRatio);
                    iItemValue /= 100;
                }
                else
                {
                    iResourceRatio /= 25;
                    //the AI needs to be stingy otherwise it'll get played.
                    if (iResourceRatio > 25)
                        iResourceRatio = 25;

                    iItemValue *= (100 + iResourceRatio);
                    iItemValue /= 100;
                }
            }

            // Approach is important
            switch (GetPlayer()->GetDiplomacyAI()->GetMajorCivApproach(eOtherPlayer, /*bHideTrueFeelings*/ false))
            {
            case MAJOR_CIV_APPROACH_FRIENDLY:
                iItemValue *= 110;
                iItemValue /= 100;
                break;
            case MAJOR_CIV_APPROACH_AFRAID:
                iItemValue *= 110;
                iItemValue /= 100;
                break;
            case MAJOR_CIV_APPROACH_NEUTRAL:
                iItemValue *= 100;
                iItemValue /= 100;
                break;
            case MAJOR_CIV_APPROACH_GUARDED:
                iItemValue *= 80;
                iItemValue /= 100;
                break;
            case MAJOR_CIV_APPROACH_DECEPTIVE:
                iItemValue *= 100;
                iItemValue /= 100;
                break;
            case MAJOR_CIV_APPROACH_HOSTILE:
                iItemValue *= 50;
                iItemValue /= 100;
                break;
            case MAJOR_CIV_APPROACH_WAR:
                iItemValue *= 50;
                iItemValue /= 100;
                break;
            }

            //And now speed/quantity.
            iItemValue *= (iResourceQuantity * iNumTurns);
            iItemValue /= 10;

            return iItemValue;
        }
        else
        {
            return 0;
        }
    }
}

Now sit and relax, this is looong. The first function, GetResourceRatio(), gets the relative abundance for this strategic between buyer and seller. The ratio is how many resources the seller is left with after the deal, divided by how many resources the buyer currently has (consider it 1 if it has none).
The second function is what sets the trade value of strategic resources.
It boldly gives a base value of 10 + (2*iEra) to every strategic. So in industrial any strategic will be valued at 22 gpt, before other considerations.
These considerations are: the player flavors, the current net GPT of the buyer (may double the price), whether the other player is militarily stronger (increases or decreases the value by a fixed percentage), whether the buyer is considered a target (don't sell strategics to a target), +50% if the buyer is considered to be close, prevent selling paper when going for diplomatic victory and aluminum when going for science victory, consider the relative abundance (see later) and finally the diplomatic approach will increase or decrease it by a fixed percentage.

So, how is the relative abundance taken into account?
First part is to look at how many resources we have:
iItemValue += ((10 - min(10, iNumRemaining)) * (10 - min(10, iNumRemaining)) * 10);
If we have 0 remaining, this adds 1000 to the itemValue (expressed in hundreds?). If we have 5 remaining, this adds 250, while having more than 10 remaining adds nothing to value.
Second part is to look at the relative abundance:
iItemValue *= (100 + iResourceRatio);
Remember that iResourceRatio is the base value multiplied by the proportion between the resource available for buyer and seller. Let's say buyer has 2 and seller has 20, and base value is 22 (in industrial). If we trade 4 strategics, the ratio is 2:16, so the base value is increased by +12.5% (it looks like having less resources that the seller makes the purchase more expensive, and it is more expensive as the buyer has more and the seller has less).
 
So, how does value and price work in reality?

The seller and the buyer will both give a different value for the product in terms of money. If the seller can sell above this value, then he will sell. If the buyer can buy under the value, then he will buy. The final price will be determined by who needs more the deal. If it is water what is in the deal and we are in a desert, then the buyer will pay all he can. If it is bottled water and we are next to a cool safe water spring, then the seller will accept the minimum price. If we don't know the rigidity, then we can assume that the price will be just in the middle of what each actor wants.

What I think current code is not doing right is that each player is not considering how much gold they really have. A seller will consider how much gold the buyer has, but he will ignore what he himself has. In other words, if I am buried in gold, I should have less incentive for selling anything.
GPT is considered to increase by 0.1% each turn, making flat gold more valuable than GPT, but by a fixed value that ignores many things.

Also, I've noticed that there's no value change for luxuries if they are demanded by cities.

And my one last irk, is that trade deals always happen because AI wants something, a resource or a treaty, but never because it wants gold.

Proposals

1. Since the item value roughly corresponds to GPT, let's convert flat money into GPT for comparison purposes. We can use three values: player's RAW gold production, net GPT and current GOLD. RAW will serve as reference. SPARE GOLD is (GOLD - 10 turns of RAW), so AI will try to keep some cash money for last minute purchases. Then convert SPARE GOLD into GPT, assuming that there is going to be inflation. Use GPT/RAW as the interest. Then sum GPT to SPARE GOLD INTO GPT to obtain the REAL GPT. REAL GPT is what the player really has available for purchasing.

2. When getting any ItemValue, consider REAL GPT of the player. A player who has and/or produces plenty of money should value money less, thus he would be willing to pay more. Maybe something like:
ItemValue += 0.1 * REAL_GPT / ItemValue

3. Increase value of luxuries for every city that demands them.

4. When AI earnings are low compared to the owned resources, set an order for selling things they don't need.
 
I don't even try to read and comprehend it all, I'm too simple for this. But I'm sure it will be a great food for thought for the developers, so thumbs up for the effort!
 
Back
Top Bottom