My personal thread: Clarification of certain game mechanics

Originally started in this thread, finally finished.

How much gold will an AI trade to the player?

First, the whole code block, its all from CVPlayerAI.ccp
Spoiler :
Code:
int CvPlayerAI::AI_maxGoldTrade(PlayerTypes ePlayer) const
{
	int iMaxGold;
	int iResearchBuffer;

	FAssert(ePlayer != getID());

	if (isHuman() || (GET_PLAYER(ePlayer).getTeam() == getTeam()))
	{
		iMaxGold = getGold();
	}
	else
	{
		iMaxGold = getTotalPopulation();

		iMaxGold *= (GET_TEAM(getTeam()).AI_getHasMetCounter(GET_PLAYER(ePlayer).getTeam()) + 10);

		iMaxGold *= GC.getLeaderHeadInfo(getPersonalityType()).getMaxGoldTradePercent();
		iMaxGold /= 100;

		iMaxGold -= AI_getGoldTradedTo(ePlayer);

        iResearchBuffer = -calculateGoldRate() * 12;
        iResearchBuffer *= GC.getGameSpeedInfo(GC.getGameINLINE().getGameSpeedType()).getResearchPercent();
        iResearchBuffer /= 100;

		iMaxGold = std::min(iMaxGold, getGold() - iResearchBuffer);

		iMaxGold = std::min(iMaxGold, getGold());

		iMaxGold -= (iMaxGold % GC.getDefineINT("DIPLOMACY_VALUE_REMAINDER"));
	}

	return std::max(0, iMaxGold);
And now how it works.

The AI calculates two values, iMaxGold and iResearch buffer, and the lower value of iMaxGold and (current gold - iReseachBuffer) gives the final gold they are willing to trade.
Code:
iMaxGold = std::min(iMaxGold, getGold() - iResearchBuffer)
the this is rounded down to the nearest 10
Code:
		iMaxGold -= (iMaxGold % GC.getDefineINT("DIPLOMACY_VALUE_REMAINDER"));

iMaxGold is basically a cap that builds up over the game, and reduces as the AI trades gold to the player.
Code:
		iMaxGold = getTotalPopulation();

		iMaxGold *= (GET_TEAM(getTeam()).AI_getHasMetCounter(GET_PLAYER(ePlayer).getTeam()) + 10);

		iMaxGold *= GC.getLeaderHeadInfo(getPersonalityType()).getMaxGoldTradePercent();
		iMaxGold /= 100;

		iMaxGold -= AI_getGoldTradedTo(ePlayer);
The calculation is ((AI Total Population * (Turns since you met the AI + 10) * iMaxGoldTradePercent)/100) - total of all gold that AI traded to player previously
The term MaxGoldTradePercent is an xml value individual to AIs (Leaderheads), as may be expected some AIs are willing to trade more of their gold than others.

iResearchBuffer comes from,
Code:
        iResearchBuffer = -calculateGoldRate() * 12;
        iResearchBuffer *= GC.getGameSpeedInfo(GC.getGameINLINE().getGameSpeedType()).getResearchPercent();
        iResearchBuffer /= 100;
Which is basically (12*deficit * Speed modifier)/100.

So to give the short numbers. The AI will trade the minimum value out of;
  1. ((AI Total Population * (Turns since you met the AI + 10) * iMaxGoldTradePercent)/100) - total of all gold that AI traded to player previously
  2. AIs current gold - (12*deficit * Speed modifier)/100.

Of particular note is the cause of the thread linked at the start which originally led to this codedive,
Code:
        iResearchBuffer = -calculateGoldRate() * 12;
The term calculategoldrate is quite literally income - expenses.
When the AI is losing 0 gold, or a surplus (which, sensibly rounds to 0 here) the iResearchBuffer becomes 0 allowing the AI to trade all of its gold to a player provided this is less than the iMaxGold value. The notable part is that we can force an AIs expenses drop to 0 or therabouts by causing Anarchy :p




EDIT - I also looked into the maximum amount of gold per turn the AI is willing to trade at any point later in the thread

Omitting the code here for brevity

iMaxGoldPerTurn = getTotalPopulation();
iMaxGoldPerTurn *= GC.getLeaderHeadInfo(getPersonalityType()).getMaxGoldPerTurnTradePercent();
iMaxGoldPerTurn /= 100;

Which is (Total pop of civ * Leader XML attribute)/100
That leader attribute is 10 for all but 3 AIs, Lincoln, Sury and Pacal who have 15.
In short its a tenth of the pop of most civs and 1.5 times that for the above 3.
Obviously if the AI doesn't have that much GPT then its current GPT is all it can trade.
Of note, unlike bulk gold there isn't an absolute cap on the amount of GPt an AI can trade in a given turn, if an AI has 16 GPT and is willing to trade only 8GPT at once, doing so will reveal, ad make available the other 8GPT.
 
Tachywaxon said:
How war tribute works in detail?
If what your talking about is the price of peace then i've found something that looks related, but its freaking enormous!
Spoiler :
void CvPlayerAI::AI_doPeace()
{
PROFILE_FUNC();

CvDiploParameters* pDiplo;
CvCity* pBestReceiveCity;
CvCity* pBestGiveCity;
CvCity* pLoopCity;
CLinkList<TradeData> ourList;
CLinkList<TradeData> theirList;
bool abContacted[MAX_TEAMS];
TradeData item;
TechTypes eBestReceiveTech;
TechTypes eBestGiveTech;
int iReceiveGold;
int iGiveGold;
int iGold;
int iValue;
int iBestValue;
int iOurValue;
int iTheirValue;
int iLoop;
int iI, iJ;

FAssert(!isHuman());
FAssert(!isMinorCiv());
FAssert(!isBarbarian());

for (iI = 0; iI < MAX_TEAMS; iI++)
{
abContacted[iI] = false;
}

for (iI = 0; iI < MAX_CIV_PLAYERS; iI++)
{
if (GET_PLAYER((PlayerTypes)iI).isAlive())
{
if (iI != getID())
{
if (canContact((PlayerTypes)iI) && AI_isWillingToTalk((PlayerTypes)iI))
{
if (!(GET_TEAM(getTeam()).isHuman()) && (GET_PLAYER((PlayerTypes)iI).isHuman() || !(GET_TEAM(GET_PLAYER((PlayerTypes)iI).getTeam()).isHuman())))
{
if (GET_TEAM(getTeam()).isAtWar(GET_PLAYER((PlayerTypes)iI).getTeam()))
{
if (!(GET_PLAYER((PlayerTypes)iI).isHuman()) || (GET_TEAM(getTeam()).getLeaderID() == getID()))
{
FAssertMsg(!(GET_PLAYER((PlayerTypes)iI).isBarbarian()), "(GET_PLAYER((PlayerTypes)iI).isBarbarian()) did not return false as expected");
FAssertMsg(iI != getID(), "iI is not expected to be equal with getID()");
FAssert(GET_PLAYER((PlayerTypes)iI).getTeam() != getTeam());

if (GET_TEAM(getTeam()).AI_getAtWarCounter(GET_PLAYER((PlayerTypes)iI).getTeam()) > 10)
{
if (AI_getContactTimer(((PlayerTypes)iI), CONTACT_PEACE_TREATY) == 0)
{
bool bOffered = false;

setTradeItem(&item, TRADE_SURRENDER);

if (canTradeItem((PlayerTypes)iI, item, true))
{
ourList.clear();
theirList.clear();

ourList.insertAtEnd(item);

bOffered = true;

if (GET_PLAYER((PlayerTypes)iI).isHuman())
{
if (!(abContacted[GET_PLAYER((PlayerTypes)iI).getTeam()]))
{
AI_changeContactTimer(((PlayerTypes)iI), CONTACT_PEACE_TREATY, GC.getLeaderHeadInfo(getPersonalityType()).getContactDelay(CONTACT_PEACE_TREATY));
pDiplo = new CvDiploParameters(getID());
FAssertMsg(pDiplo != NULL, "pDiplo must be valid");
pDiplo->setDiploComment((DiploCommentTypes)GC.getInfoTypeForString("AI_DIPLOCOMMENT_OFFER_PEACE"));
pDiplo->setAIContact(true);
pDiplo->setOurOfferList(theirList);
pDiplo->setTheirOfferList(ourList);
gDLL->beginDiplomacy(pDiplo, (PlayerTypes)iI);
abContacted[GET_PLAYER((PlayerTypes)iI).getTeam()] = true;
}
}
else
{
GC.getGameINLINE().implementDeal(getID(), ((PlayerTypes)iI), &ourList, &theirList);
}
}

if (!bOffered)
{
if (GC.getGameINLINE().getSorenRandNum(GC.getLeaderHeadInfo(getPersonalityType()).getContactRand(CONTACT_PEACE_TREATY), "AI Diplo Peace Treaty") == 0)
{
setTradeItem(&item, TRADE_PEACE_TREATY);

if (canTradeItem(((PlayerTypes)iI), item, true) && GET_PLAYER((PlayerTypes)iI).canTradeItem(getID(), item, true))
{
iOurValue = GET_TEAM(getTeam()).AI_endWarVal(GET_PLAYER((PlayerTypes)iI).getTeam());
iTheirValue = GET_TEAM(GET_PLAYER((PlayerTypes)iI).getTeam()).AI_endWarVal(getTeam());

eBestReceiveTech = NO_TECH;
eBestGiveTech = NO_TECH;

iReceiveGold = 0;
iGiveGold = 0;

pBestReceiveCity = NULL;
pBestGiveCity = NULL;

if (iTheirValue > iOurValue)
{
if (iTheirValue > iOurValue)
{
iBestValue = 0;

for (iJ = 0; iJ < GC.getNumTechInfos(); iJ++)
{
setTradeItem(&item, TRADE_TECHNOLOGIES, iJ);

if (GET_PLAYER((PlayerTypes)iI).canTradeItem(getID(), item, true))
{
iValue = (1 + GC.getGameINLINE().getSorenRandNum(10000, "AI Peace Trading (Tech #1)"));

if (iValue > iBestValue)
{
iBestValue = iValue;
eBestReceiveTech = ((TechTypes)iJ);
}
}
}

if (eBestReceiveTech != NO_TECH)
{
iOurValue += GET_TEAM(getTeam()).AI_techTradeVal(eBestReceiveTech, GET_PLAYER((PlayerTypes)iI).getTeam());
}
}

iGold = std::min((iTheirValue - iOurValue), GET_PLAYER((PlayerTypes)iI).AI_maxGoldTrade(getID()));

if (iGold > 0)
{
setTradeItem(&item, TRADE_GOLD, iGold);

if (GET_PLAYER((PlayerTypes)iI).canTradeItem(getID(), item, true))
{
iReceiveGold = iGold;
iOurValue += iGold;
}
}

if (iTheirValue > iOurValue)
{
iBestValue = 0;

for (pLoopCity = GET_PLAYER((PlayerTypes)iI).firstCity(&iLoop); pLoopCity != NULL; pLoopCity = GET_PLAYER((PlayerTypes)iI).nextCity(&iLoop))
{
setTradeItem(&item, TRADE_CITIES, pLoopCity->getID());

if (GET_PLAYER((PlayerTypes)iI).canTradeItem(getID(), item, true))
{
iValue = pLoopCity->plot()->calculateCulturePercent(getID());

if (iValue > iBestValue)
{
iBestValue = iValue;
pBestReceiveCity = pLoopCity;
}
}
}

if (pBestReceiveCity != NULL)
{
iOurValue += AI_cityTradeVal(pBestReceiveCity);
}
}
}
else if (iOurValue > iTheirValue)
{
iBestValue = 0;

for (iJ = 0; iJ < GC.getNumTechInfos(); iJ++)
{
setTradeItem(&item, TRADE_TECHNOLOGIES, iJ);

if (canTradeItem(((PlayerTypes)iI), item, true))
{
if (GET_TEAM(GET_PLAYER((PlayerTypes)iI).getTeam()).AI_techTradeVal((TechTypes)iJ, getTeam()) <= (iOurValue - iTheirValue))
{
iValue = (1 + GC.getGameINLINE().getSorenRandNum(10000, "AI Peace Trading (Tech #2)"));

if (iValue > iBestValue)
{
iBestValue = iValue;
eBestGiveTech = ((TechTypes)iJ);
}
}
}
}

if (eBestGiveTech != NO_TECH)
{
iTheirValue += GET_TEAM(GET_PLAYER((PlayerTypes)iI).getTeam()).AI_techTradeVal(eBestGiveTech, getTeam());
}

iGold = std::min((iOurValue - iTheirValue), AI_maxGoldTrade((PlayerTypes)iI));

if (iGold > 0)
{
setTradeItem(&item, TRADE_GOLD, iGold);

if (canTradeItem(((PlayerTypes)iI), item, true))
{
iGiveGold = iGold;
iTheirValue += iGold;
}
}

iBestValue = 0;

for (pLoopCity = firstCity(&iLoop); pLoopCity != NULL; pLoopCity = nextCity(&iLoop))
{
setTradeItem(&item, TRADE_CITIES, pLoopCity->getID());

if (canTradeItem(((PlayerTypes)iI), item, true))
{
if (GET_PLAYER((PlayerTypes)iI).AI_cityTradeVal(pLoopCity) <= (iOurValue - iTheirValue))
{
iValue = pLoopCity->plot()->calculateCulturePercent((PlayerTypes)iI);

if (iValue > iBestValue)
{
iBestValue = iValue;
pBestGiveCity = pLoopCity;
}
}
}
}

if (pBestGiveCity != NULL)
{
iTheirValue += GET_PLAYER((PlayerTypes)iI).AI_cityTradeVal(pBestGiveCity);
}
}

if ((GET_PLAYER((PlayerTypes)iI).isHuman()) ? (iOurValue >= iTheirValue) : ((iOurValue > ((iTheirValue * 3) / 5)) && (iTheirValue > ((iOurValue * 3) / 5))))
{
ourList.clear();
theirList.clear();

setTradeItem(&item, TRADE_PEACE_TREATY);

ourList.insertAtEnd(item);
theirList.insertAtEnd(item);

if (eBestGiveTech != NO_TECH)
{
setTradeItem(&item, TRADE_TECHNOLOGIES, eBestGiveTech);
ourList.insertAtEnd(item);
}

if (eBestReceiveTech != NO_TECH)
{
setTradeItem(&item, TRADE_TECHNOLOGIES, eBestReceiveTech);
theirList.insertAtEnd(item);
}

if (iGiveGold != 0)
{
setTradeItem(&item, TRADE_GOLD, iGiveGold);
ourList.insertAtEnd(item);
}

if (iReceiveGold != 0)
{
setTradeItem(&item, TRADE_GOLD, iReceiveGold);
theirList.insertAtEnd(item);
}

if (pBestGiveCity != NULL)
{
setTradeItem(&item, TRADE_CITIES, pBestGiveCity->getID());
ourList.insertAtEnd(item);
}

if (pBestReceiveCity != NULL)
{
setTradeItem(&item, TRADE_CITIES, pBestReceiveCity->getID());
theirList.insertAtEnd(item);
}

if (GET_PLAYER((PlayerTypes)iI).isHuman())
{
if (!(abContacted[GET_PLAYER((PlayerTypes)iI).getTeam()]))
{
AI_changeContactTimer(((PlayerTypes)iI), CONTACT_PEACE_TREATY, GC.getLeaderHeadInfo(getPersonalityType()).getContactDelay(CONTACT_PEACE_TREATY));
pDiplo = new CvDiploParameters(getID());
FAssertMsg(pDiplo != NULL, "pDiplo must be valid");
pDiplo->setDiploComment((DiploCommentTypes)GC.getInfoTypeForString("AI_DIPLOCOMMENT_OFFER_PEACE"));
pDiplo->setAIContact(true);
pDiplo->setOurOfferList(theirList);
pDiplo->setTheirOfferList(ourList);
gDLL->beginDiplomacy(pDiplo, (PlayerTypes)iI);
abContacted[GET_PLAYER((PlayerTypes)iI).getTeam()] = true;
}
}
else
{
GC.getGameINLINE().implementDeal(getID(), ((PlayerTypes)iI), &ourList, &theirList);
It'll take quite a while to decipher and i'm unfortunately tied up with work for at least the next week or two, but its probably beyond me anyway...
 
I just had a city of mine flip to a vassal, and I was under the impression that that could not happen. I guess i was wrong?

I did found this city myself if that makes a difference.
 
I just had a city of mine flip to a vassal, and I was under the impression that that could not happen. I guess i was wrong?

I did found this city myself if that makes a difference.
Vassals can culture flip a city belonging to their master, its unusual however as most vassals tend to be battered enemies whos border and/or core cities are taken, and captured cities cannot flip back to the original owner in a normal game.
In game if you haven't seen it before then it doesn't really look like it would be able to happen, as contested tiles in a masters cities BFC are given to the master regardless of how much of the tile the vassal would usually control. City fipping however works on a different mechanic.
 
OK, that makes sense. I left Saladin with his capitol and two junk cities a ways off. He had neglected to settle a pretty nice coastal spot roughly 5 tiles east of his capitol so i grabbed that after the war. One of the Arabian cities that i had captured was revolting constantly, but i needed to keep it as a buffer so that the nicer, holy city i captured wouldn't revolt. The other city was revolting so often that i was just completely ignoring it, and then suddenly Snaketown flipped to him. I guess a couple of those revolts i ignored were there and not the Arabian city!
 
Synopsis

A change of ruling class is never swallowed easily by population unless they were expecting or used to it. Conquering cities in Civ4 doesn't bend people people to be serviceable on the spot. There is almost always a period where the city suffers a period of occupation called "Viva la Resistance" where that city suffers nor population anger, population losses nor growth, production, science output, etc. Basically, the city activities are put on hold and the culture is simply limited to the city square.
Depending of several factors, such city or another will take different time to quell the resistance.

Note: Although culture is limited to the city square, it is still possible to use a GArtist while an occupation and observe a normal huge border pop. A nice way to hasten some domination victory.

How it works?​

The number of occupation turns is determined by two checks.

1) If you got a culture ratio of more or equal to "OCCUPATION_CULTURE_PERCENT_THRESHOLD", which is 75%, then there is no occupation period at all. Intuitively, that city's population was sufficiently influenced culturally to readily accept the new government and needed a push to overthrow the erstwhile incumbent government.

2)The second stage is determined by
a)City Size
b)Your culture influence over theirs
The exact formula is:

Turns of Occupation = BASE_OCCUPATION_TURNS*(CityPopulation*OCCUPATION_TURNS_POPULATION_PERCENT/100)*((100-YourCulture%)/100)

Getting the XML values in place, that leads to:


Turns of Occupation = 3*(CityPopulation/2)*((100-YourCulture%)/100)


When the calculus is done, all you have to do is to round down and you got the number of turns needed for quelling the city.


Code Excerpts

Spoiler :
Code:
In [COLOR="Green"][B]CvPlayer.cpp[/B][/COLOR] and in the function [B][COLOR="Green"]CvPlayer::acquireCity()[/COLOR][/B]
if (bConquest)
	{
		iTeamCulturePercent = pNewCity->calculateTeamCulturePercent(getTeam());

		if (iTeamCulturePercent < GC.getDefineINT("OCCUPATION_CULTURE_PERCENT_THRESHOLD"))
		{
			pNewCity->changeOccupationTimer(((GC.getDefineINT("BASE_OCCUPATION_TURNS") + ((pNewCity->getPopulation() * GC.getDefineINT("OCCUPATION_TURNS_POPULATION_PERCENT")) / 100)) * (100 - iTeamCulturePercent)) / 100);
		}

		GC.getMapINLINE().verifyUnitValidPlot();
	}

Now, some XML values in [COLOR="Green"][B]GlobalDefines.xml[/B][/COLOR]

<Define>
	<DefineName>OCCUPATION_CULTURE_PERCENT_THRESHOLD</DefineName>
	<iDefineIntVal>75</iDefineIntVal>
</Define>

<Define>
	<DefineName>BASE_OCCUPATION_TURNS</DefineName>
	<iDefineIntVal>3</iDefineIntVal>
</Define>

<Define>
	<DefineName>OCCUPATION_TURNS_POPULATION_PERCENT</DefineName>
	<iDefineIntVal>50</iDefineIntVal>
</Define>
 
^^Very nice :thumbsup: Don't ever close this thread, Tachywaxon :)
 
Is the Population # pre-capture or post-capture?

With a population of 1 and no culture, the formula gives 1t disorder; I don't believe that is correct. Perhaps kossin's correction fixes that. That would imply a minimum of 3t of disorder after capture which seems correct in my experience.

I've seen disorder less the 3t, but those were probably cultural revolutions which are no doubt handled by different code.

Sun Tzu Wu
 
Does the formula change with game speed?
 
As with many things that should scale with game speed, like open borders diplomacy bonus, traded resources diplomacy bonus, years of peace diplomacy bonus, favorite civic diplomacy bonus, shared war diplomacy bonus, defensive pact diplomacy bonus, etc., the turns of disorder after capturing a city do not vary by game speed. In this sense, it highly favors marathon speed where even long disorders do not matter very much relatively speaking.

Sun Tzu Wu
 
Will make the corrections and specifications once the OP post will be finalized.
 
Synopsis

Most of the diplomatic communications are initiated by the human player. More active and more aware of the world situation than the hopeless AI. Still, the AI has their own diplo contact implemented, although completely randomized. For Opening Borders, trading techs, gold, maps and resources, close borders, bribe, sign defense pacts, etc.
With the human or with other AI fellows. We will shed some light on the how the AI starts interaction.

How it works?​

To understand the trigger for an AI to start the contact by their volition, we have to spend some time on two preliminary concepts: a timer and the RNG factor. Finally, an explanation of each type of motive like willing tech trading, open border, bribe, etc.

The timer counted in turns is the non-random factor in determining the frequency the AI will start to nag you again. If the timer hasn't expired yet, he can't contact you again. Of course, this apply for a leader at a time. For instance, if the contact AI demands something to someone, the timer will not work for all leaders; just for the victim of the bully. Depending of the type of motive, the timer is a fixed number and is not leader dependent. This could have been leader dependant as it is an XML value defined for each leader, but the devs decided to uniformize this attribute. The XML attribute in question is:

Code:
<ContactDelay>
<ContactType>
The motive of the contact...either techs, demands, etc. </ContactType>
<iContactDelay>Between 10 to 50 turns</iContactDelay>
</ContactDelay>

Here is a list of the motives:

ASK_FOR_HELP...............:50 turns.
CIVIC_PRESSURE.............:50 turns
DEFENSIVE_PACT............:20 turns
DEMAND_TRIBUTE............:50 turns
GIVE_HELP.....................:50 turns
JOIN_WAR......................:20 turns
OPEN_BORDERS...............:20 turns
PEACE_TREATY...............:10 turns
PERMANENT_ALLIANCE.....:20 turns
RELIGION_PRESSURE........:50 turns
STOP_TRADING...............:20 turns
TRADE_BONUS................:20 turns
TRADE_MAP....................:50 turns
TRADE_TECH..................:30 turns

Now, the randomizer part is activated once the timer has expired. It means the AI won't commence a new contact every such number of turns in a perfect cyclic manner. Like asking open borders every 20 turns exactly until you comply to their whim. No, the contact will be started once the rolled virtual dice gives a green light.
And this is where the difference between leaders are visible. A flavour was given to each of them. Of course, again, the RNG are different with the motives.
An excerpt from DanF5771 complete XML variables is given to cover all leaders.
What do those numbers represent? The virtual dice number of faces. For instance, if Catherine has 50 for demands of tribute, that means once the timer is expired, she has 1 chance out of 50 per turn to nag you for an annoying whimsical demand. Make the division and you have the odd percentage per turn for that particular motive to happen. For a quick glance without exactitude, the biggest numbers are indicator of low probability that this particular motive will happen and the lowest numbers are indicator of high odds of occurence.

Spoiler :


At last, here is the complete set of contact motives.


And several others with a slighty different mechanics:


An important point to note: This is not scaled with the gamespeed or anything. Thus for all conditions, the AI volition to contact remains the same.

Code Excerpts

In CvPlayerAI.cpp and the global variable is AI_doDiplo(). A huge 2000 lines of code.

Spoiler :


You really think I'm gonna put this here. Trololol!

 
An AI can cancel a continuous trade, like a resource, OB, defense pact, etc. if that one deems the deal is obsolete in their sense of comparing the value.

Although the human is capable of cancelling after 10 turns, the AI does it only starting the 20th turns after the deal was agreed. Thus, the deal is sworn for a 19 turns unless an external force cancel the deal like a resource disconnection.

The global variable (or call it function) that deals the intricacies and valuing of the AI on a deal is AI_considerOffer(). There is compared the offered object and received one in a deal and is returned either an approbation or refusal.

In this small code excerpt lies the deep difference between the AI-AI considerations and AI-human ones:

Spoiler :
Code:
if ((pLoopDeal->getFirstPlayer() == getID()) && (pLoopDeal->getSecondPlayer() == ((PlayerTypes)iI)))
										{
											if (GET_PLAYER((PlayerTypes)iI).isHuman())
											{
												if (!AI_considerOffer(((PlayerTypes)iI), pLoopDeal->getSecondTrades(), pLoopDeal->getFirstTrades(), -1))
												{
													bCancelDeal = true;
												}
											}
											else
											{
												for (pNode = pLoopDeal->getFirstTrades()->head(); pNode; pNode = pLoopDeal->getFirstTrades()->next(pNode))
												{
													if (getTradeDenial(((PlayerTypes)iI), pNode->m_data) != NO_DENIAL)
													{
														bCancelDeal = true;
														break;
													}
												}
											}
										}
Basically, it says if the contact is a human player, the AI will compare items of a deal and approve or refuse it. If the contact is an AI just like the instigator of the contact, then what cancels a deal is simply the denial value.

AI_considerOffer() applying for the human player ponders for most kind of deals the exact values of items in a deal.

A denial, on the other hand, is a higher level of refusal, having a prime priority over just comparing stuff values. For instance, you could trade this resource for that AI resource, but the AI replies it doesn't like you enough. That's a denial.
But if the AI is refusing after putting on table his iron for your corn, then that's not a denial in the sense of the code. It's simply that AI_considerOffer() returns false to that trade for being disadvantageous to the AI.

In summary, an AI may cancel a deal with the human because its resource is getting more worthy or yours is depleting in value (yes, resource values are dynamic).
BUT won't cancel because he starts hating you!
Note that happened in the previous versions of Civ4 before BTS where an AI starting to hate you will cancel, say, the Open Border pacts. In BTS, that was changed and you can keep the open borders even if the AI attitude degenerates over time.

Still, for AI-AI deals, attitude is everything for keeping the deal intact over time once it is agreed. For open borders, if one AI starts to hate the other below the attitude threshold for OB, the deal will be cancelled after 19 turns. Same for resources and defense pacts.

A special note is given to vassal cancelling. For a particular reason, when an AI has a vassal deal with the human, if you don't dare asking too much of resources under the attitude threshold or without defense pact with another player, the vassal WILL NEVER revoke the deal. Peace Vassal or Capitulee, it doesn't matter.

It only works on AI-AI vassal deals. Why? Omission from the devs to add a code block for this particular case. We'll never know, but that mystery wasn't even solved by far better code diver than I like Bestsss or DanF5771.
 
Never found suspicious that an AI vassal gets so many resources out of nowhere. Well, this has to do with the master pity for its vassal. Like tech giting, an AI vassal gets a bunch of resources.

In the same AI_doDiplo(), here is the code excerpt:

Spoiler :
Code:
if (GET_PLAYER((PlayerTypes)iI).getTeam() == getTeam() || GET_TEAM(GET_PLAYER((PlayerTypes)iI).getTeam()).isVassal(getTeam()))
							{
								// XXX will it cancel this deal if it loses it's first resource???

								iBestValue = 0;
								eBestGiveBonus = NO_BONUS;

								for (iJ = 0; iJ < GC.getNumBonusInfos(); iJ++)
								{
									if (getNumTradeableBonuses((BonusTypes)iJ) > 1)
									{
										if ((GET_PLAYER((PlayerTypes)iI).AI_bonusTradeVal(((BonusTypes)iJ), getID(), 1) > 0)
											&& (GET_PLAYER((PlayerTypes)iI).AI_bonusVal((BonusTypes)iJ, 1) > AI_bonusVal((BonusTypes)iJ, -1)))
										{
											setTradeItem(&item, TRADE_RESOURCES, iJ);

											if (canTradeItem(((PlayerTypes)iI), item, true))
											{
												iValue = (1 + GC.getGameINLINE().getSorenRandNum(10000, "AI Bonus Trading #1"));

												if (iValue > iBestValue)
												{
													iBestValue = iValue;
													eBestGiveBonus = ((BonusTypes)iJ);
												}
											}
										}
									}
								}

								if (eBestGiveBonus != NO_BONUS)
								{
									ourList.clear();
									theirList.clear();

									setTradeItem(&item, TRADE_RESOURCES, eBestGiveBonus);
									ourList.insertAtEnd(item);

									if (GET_PLAYER((PlayerTypes)iI).isHuman())
									{
										if (!(abContacted[GET_PLAYER((PlayerTypes)iI).getTeam()]))
										{
											pDiplo = new CvDiploParameters(getID());
											FAssertMsg(pDiplo != NULL, "pDiplo must be valid");
											pDiplo->setDiploComment((DiploCommentTypes)GC.getInfoTypeForString("AI_DIPLOCOMMENT_GIVE_HELP"));
											pDiplo->setAIContact(true);
											pDiplo->setTheirOfferList(ourList);
											gDLL->beginDiplomacy(pDiplo, (PlayerTypes)iI);
											abContacted[GET_PLAYER((PlayerTypes)iI).getTeam()] = true;
										}
									}
									else
									{
										GC.getGameINLINE().implementDeal(getID(), ((PlayerTypes)iI), &ourList, &theirList);
									}
								}
							}


In a nutshell, if the master has supplementary resources available and those resources are worth something (it seems theoritically a resource could be worth nothing), it will give it to its vassal. All in one turn? No, one resource at a turn and there is no randomizer here except the order of resources given are randomized without possibility of prediction. But it doesn't really matter...

Sadly, I wasn't able to understand that line yet.

Code:
if ((GET_PLAYER((PlayerTypes)iI).AI_bonusTradeVal(((BonusTypes)iJ), getID(), 1) > 0)
											&& (GET_PLAYER((PlayerTypes)iI).AI_bonusVal((BonusTypes)iJ, 1) > AI_bonusVal((BonusTypes)iJ, -1)))

A condition that goes after "Does the master have more than 1 resource?" condition and seems to deal with augmentation of a resource value after trading it. I'm really not sure what the deal with that thing.
 
The typical motive ruled by the timer and randomizer.

Only thing to know about PAlliances except it is defined outside AI_doDiplo() is you need 40 turns (on every speed, no scaling job again!) of either defensive pacts or sharing wars...or both!
As in getting 12 turns of defensive pact and 28 turns of sharing war for instance.
Again, that is just the denial layer. That is not the trigger to push the AI to propose it by itself.
 
The typical motive ruled by the timer and randomizer.


If the player (human or AI) cannot convert to the demanded religion, the contact obviously won't happen. The AI conversion to its state relgion, not favorite religion.
 
The typical motive ruled by a timer and randomizer.

We also add:

  • If you previously declared war to that AI, it will never ask you to assist it to its war. Interesting fact I did not know!
  • If you were a war ally in the past, he won't ask you again...supposedly.
  • If there are multiple AI's with which the asker is at war, it will ask you to join war with one of them randomly.
  • You need to agree two of its war invitations to get that rare diplo modifier: "You agreed to come to our aid in war-time."
 
The typical motive ruled by the timer and randomizer.


We can add:
  • Demanding to stop trading is not limited to Open Borders. It goes with every kind of continuous type of deal like defensive pact, resources, etc.
  • The asker will never demand you stop trading with your own vassal nor some AI master's vassals.
  • Won't ask if the deal object isn't cancellable yet.
  • Won't ask you again if your previously agreed. And this concept joins along another concept, which is the AI memory decay. Several type of your actions are not recalled from the beginning to the end of the game like war declaration. Stop trading agreement suffers each turn of a chance to be forgotten and once forgotten, that AI will start nagging you again.
  • Will ask closing borders with its most hated foe, the worst enemy in other words.
  • If you agreed to close borders twice, then you get the rare diplo modifier: "You agreed to stop trading with our worst enemy."
 
Typical motive with a timer and a randomizer.

  • Like the AI gifting techs when you're behind in score, AI asking techs is a mechanics only applied to the human. The AI doesn't give or ask technologies to other AI's.
  • The scoreboard is everything in determining who will beg some techs: if the beggard doesn't even have the half of your score, then prepare to be nagged.
  • The beggard doesn't particularly target your monopoly techs; the chosen tech undergoes the typical RNG
  • All leaders have their attitude increased by one point once the begged tech is given.
 
Top Bottom