My personal thread: Clarification of certain game mechanics

Thanks, Tachy! As usual, it's going to take me a few read-throughs to assimilate the info. But I greatly appreciate your taking the time to respond.

I do have a follow up question though, based on the comments of kossin, Sun Tzu Wu, and yourself:

The normal behaviour is simply to instill a 10t temporary peace after which the AI will start the war as it had initially planned.

Agreed. The beg simply delays the plotted DoW for up to 10t. If one is (cautiously) tracking the AI's SoD toward one's border, it may help to wait until its within 1t of entering one's border to beg, thus delaying the DoW exactly 10t (maximizing the DoW delaying effect of the beg). Use the extra time to prepare a defense or offense to decimate the AI's SoD.

Final word, what does it have to do with that small begging?
Beggings don't change their mind at all. Easy to prove, have you ever seen their WHEEOH mode dropped instantly after beg...no.

... after starting WHEOOH, and a forced peace treaty from begging pity is only 10 turns.

Perhaps I fundamentally misunderstand something, but how else can you explain the following:

Spoiler :









I would consider this to be the standard follow-up to a successful beg from an AI that is plotting war against the player. Maybe I am playing on easy mode. :(
 

Attachments

  • DDD Charlie BC-0475WHEOOHRN.CivBeyondSwordSave
    172.6 KB · Views: 113
Wow, I begged so many times and never such thing happened within a turn!

I certainly have seen much more cases where the AI still pursued their death intent.

Now you caught my interest at 120%. A pleasurable mystery. :)
 
A couple of further examples from recent DDD games, since my memory there is fairly fresh:

Spoiler :










I used Worldbuilder here to give me a tech to bring Gandhi to Pleased. In the actual game, I stopped Pacal from declaring war through a 1 gold beg. But I can't recall the turn in which that occurred.

...or:

Spoiler :








Again, this didn't actually occur in the game, I just reloaded to the turn before Frederick declared war against Hatchet, since she was in WHEOOHRN mode at the time (and I assumed, correctly I guess, that one of my vassals was the target).
 
So what do you see in the very definition of enforced peace (peace treaty)?
Find it and I'll elaborate about it. :smug::D

Code:
void CvTeam::setForcePeace(TeamTypes eIndex, bool bNewValue)
{
	FAssertMsg(eIndex >= 0, "eIndex is expected to be non-negative (invalid Index)");
	FAssertMsg(eIndex < MAX_TEAMS, "eIndex is expected to be within maximum bounds (invalid Index)");
	m_abForcePeace[eIndex] = bNewValue;

	if (isForcePeace(eIndex))
	{
		if (AI_isSneakAttackPreparing(eIndex))
		{
			AI_setWarPlan(eIndex, NO_WARPLAN);
		}

		for (int iTeam = 0; iTeam < MAX_TEAMS; ++iTeam)
		{
			if (GET_TEAM((TeamTypes)iTeam).isVassal(eIndex))
			{
				if (AI_isSneakAttackPreparing((TeamTypes)iTeam))
				{
					AI_setWarPlan((TeamTypes)iTeam, NO_WARPLAN);
				}
			}
		}
	}
}

Getting late....here.

EDIT: Ok, Gandhi's case is not closed after all. I suppose there's more to know...
 
Argh. Don't make me try to leave my comfort zone. :cry:

Ok. I think you're suggesting that the AIs in question were preparing sneak attacks due to rejected demands, and that a successful beg in this situation will then enforce peace. But Gandhi and Hatchet both have an iDemandRebukedSneakProb of 0, right? So it can't be that?
 
Begging for gold worked in 80% of cases for me. And more so with AIs like Ragnar or Montezuma.
My feeling is that less aggressive AIs are prone to daggers and daggers can only be stopped if you invlove that AI in some war. But daggers have no warning . Aggresive AIs will plot wars of conquest instead of daggers and can't afford to wait 10 turns with their SoDs so they find different target. That is actually something resembling a try to win.

Piece of advice: If you see AI building CR units, that AI is up to no good. Managed to anticipate few daggers like that (not that hard in semi-isolation :)).

Waiting for Tachy's official interpretation.
 
I simply don't understand peacevassaling conditions of parting from the master.

Yes, there is this:

Code:
bool CvTeam::canVassalRevolt(TeamTypes eMaster) const
{
	FAssert(NO_TEAM != eMaster);

	CvTeam& kMaster = GET_TEAM(eMaster);

	if (isVassal(eMaster))
	{
		if (100 * getTotalLand(false) < GC.getDefineINT("VASSAL_REVOLT_OWN_LOSSES_FACTOR") * getVassalPower())
		{
			return true;
		}

		if (100 * kMaster.getTotalLand() < GC.getDefineINT("VASSAL_REVOLT_MASTER_LOSSES_FACTOR") * getMasterPower())
		{
			return true;
		}
	}

	if (GC.getDefineINT("FREE_VASSAL_LAND_PERCENT") < 0 || 
		100 * getTotalLand(false) < kMaster.getTotalLand(false) * GC.getDefineINT("FREE_VASSAL_LAND_PERCENT"))
	{
		return false;
	}

	if (GC.getDefineINT("FREE_VASSAL_POPULATION_PERCENT") < 0 || 
		100 * getTotalPopulation(false) < kMaster.getTotalPopulation(false) * GC.getDefineINT("FREE_VASSAL_POPULATION_PERCENT"))
	{
		return false;
	}

	return true;
}

4 conditions permitting a vassal to revolt out of master's grip.

But getting overly annoyed not seeing in the code some checks like doTurn(), trying to check at a regular fixed period to see if freeing the vassal should happen.


In this test game, my vassal is overkill in soldier count, land and population.

In this canVassalRevolt() function, it is supposed to return true at the end of the function. Because the vassal has at least over 50% in both and land (calculated in a way the vassal and master are not master and vassal).

Izzy in my test game never revolts.

I know if we eat her land or pop or she loses in the same via a war, she'll break from the two first conditional structures.

But if everything goes well, it seems the vassal never break. WTF!

I know TMIT has demonstrated this in his deity game...but why is this happening in the first place.
In real like, unless I'm a masochist, I wouldn't want to be vassal/slave of someone 1/10000 my power.
 

Attachments

  • Tachywaxon AD-0820 - That makes no sense!.CivBeyondSwordSave
    209.3 KB · Views: 195
Code laid down for reference:

Spoiler :
Code:
void CvCity::doGrowth()
{
	int iDiff;

	CyCity* pyCity = new CyCity(this);
	CyArgsList argsList;
	argsList.add(gDLL->getPythonIFace()->makePythonObject(pyCity));	// pass in city class
	long lResult=0;
	gDLL->getPythonIFace()->callFunction(PYGameModule, "doGrowth", argsList.makeFunctionArgs(), &lResult);
	delete pyCity;	// python fxn must not hold on to this pointer 
	if (lResult == 1)
	{
		return;
	}

	iDiff = foodDifference();

	changeFood(iDiff);
	changeFoodKept(iDiff);

	setFoodKept(range(getFoodKept(), 0, ((growthThreshold() * getMaxFoodKeptPercent()) / 100)));

	if (getFood() >= growthThreshold())
	{
		if (AI_isEmphasizeAvoidGrowth())
		{
			setFood(growthThreshold());
		}
		else
		{
			changeFood(-(std::max(0, (growthThreshold() - getFoodKept()))));
			changePopulation(1);

			// ONEVENT - City growth
			CvEventReporter::getInstance().cityGrowth(this, getOwnerINLINE());
		}
	}
	else if (getFood() < 0)    
	{
		changeFood(-(getFood()));

		if (getPopulation() > 1)
		{
			changePopulation(-1);
		}
	}
}

================================================


int CvCity::getFoodKept() const
{
	return m_iFoodKept;
}


void CvCity::setFoodKept(int iNewValue)
{
	m_iFoodKept = iNewValue;
}


void CvCity::changeFoodKept(int iChange)
{
	setFoodKept(getFoodKept() + iChange);

int CvCity::getFood() const
{
	return m_iFood;
}


void CvCity::changeFood(int iChange)
{
	setFood(getFood() + iChange);
}



FIRST CASE: Without Granary

That one seems easy:

Let's call m_iFoodKept our variable that represents the inner property of a granary to stock food per turn up to its defined max (0.5*threshold). m_iFood is the variable dealing how far we got in food bar.
Second point, we have to know this section of code is compiled each turn, which makes more than 1 update.
Third point, granary starts stocking food during the IBT AFTER the turn being built. There is no food stockage timing with granary being built given the granary is built after food is put in the bin.

First, m_iFoodKept is like a pig coin bank that receives an allowance, which is the same value as the food rate (iDiff).

Then, m_iFoodKept is processed through a cropping function:setFoodKept, which maxes out at growthThreshold()/2. It is simply a restrictive range function where m_iFoodKept can go from 0 to growthThreshold()/2 (e.g. 22/2=11 for size one). If it goes below, the min is imposed otherwise over max, max is imposed.

But given there is no granary, then MAX value becomes 0. So the range is 0 to 0, thus only acceptable new m_iFoodKept is 0.

Lastly, the food rate (iDiff=foodDifference()) is added to m_iFood
. And then the new m_iFood
is reduced by growthThreshold() and gained m_iFoodKept. Nonetheless, there is none in the granary due to its absence, thus we end up with a simple growth.
E.G. We end up with m_iFood
=26/24. Substract growthThreshold() =24 and then the food bin is 2/26. Denominator is different because it is adjusted to the new size and the code doesn't process fractions but numerators as integers.

Conclusion: Matched game experience with the code.


Picture for next cases:




SECOND CASE: With Granary while being lower than half growthThreshold()

That's the green section in the picture. In a nutshell, we reach the max stockage capacity of the granary because "citizens had time to stock food".

Experience told us if we finish the granary and the same turn we end up with exact growthThreshold()/2 or less, then after growth we get max stockage of granary.

That's normal point of view of the code as when we finish the granary halfway of growthThreshold(), we have the other halfway to stock food in the granary and then the "citizens have time enough to stock full the granary". A granary in the middle of the picture (yellow line) means while the food rate adds to both m_iFood
and m_iFoodKept at the same time.
If the food rate makes some "surplus", then the cropping function will do its job by returning max value, which is growthThreshold()/2.

Thus, game experience is now compatible with code.

THIRD CASE: With Granary while being higher than half growthThreshold()

That is the most interesting case and that one where we see less granary stock after growth and what we call doubling "surplus" food.

Experience told us if we finish the granary and the food rate meanwhile goes over half of growthThreshold(), then the granary stock is lower than expected half.

Let imagine a situation where the food rate allows an exact growth without "surplus" food. If the granary was built in the red section, then the "citizens didn't have time to fully fill the granary". As already said, each turn, the food rate is added to both m_iFood
and m_iFoodKept at the same time and if there is less than growthThreshold()/2, then the cropping function will certainly return a lower value than half-growthThreshold().
Of course, under condition there is no "surplus" food that makes m_iFood
= (>growthThreshold())/growthThreshold().

When we have surplus due to a food rate allowing that, then because adjusted m_iFoodKept is processed before substracting food, this helps to catch up the "lack of food not gathered by citizens". And indeed, if the food rate makes the m_iFoodKept go over growthThreshold()/2, again we see the cropping function doing its job and thus the interesting part where "too much surplus food ended with no more doubling surplus".

Now, a fictitious example to illustrate the logics:

We're size one (threshold=22 food). We built granary at 13/22. And we have now a constant food rate of 6 :food: per turn. Whatever the food rate before the granary being built, it doesn't matter because it is not stocked. Now, in the IBT, we start stocking food. Next turn, food bin is at 19/22 and food in granary is at 6. Next turn, we reach m_iFood
=25, which is over 22, but it doesn't matter. Concept of surplus doesn't exist. Food in granary is now at 12, but being size 1, the cropping function does its job and crops at 11.
Finally, the code does the changePopulation function:
m_iFood-growthThreshold()+m_iFoodKept=
25-22+11=14 food. Yes, thanks to that immense food rate, we made up the loss of finishing the granary AFTER the yellow line in the picture.
The doubling factor came from both food bin and granary bin got their same share of food rate at the last turn before growth and when the cropping function starts to crop, bam granary stock stops to double "surplus" food.

Again, we have experiences combined with code reality.


This resumes the whole knowledge we can get from granaries.
Now, a whole micro hell comes from this because plenty of finishing granary optimal points rise. With many plots, it can be hard to choose which is the best combination of tiles to get most. Waiting to get more organic flow from plot outputs or simply whip now to get max from granary max stockage. Not counting situational reasons like getting OF for another build, or emergencies or bigger priorities. :crazyeye:
 
Nice explanations! One question, when is the best time to whip a granary? Before, on or after threshold (food bin half way full)? Does it matter? Is it situational? It feels like it should coincide with "First Case" in your explanation, but maybe is something more to it? I'd very much appreciate the explanation!
 
This granary thing was figured a while back also, but I can't find the old thread.

Practical lessons:
1) Building a granary before being half-full on the foodbar is pointless; so you can delay the build for something more useful (typically you can't build anything in the few turns you can delay the granary but well.) When whipping a granary this is more relevant. You want to whip the granary when the food bar for the NEW (lower) population size is half-full. Of course, the deficit in the filling of a granary arising from building it at a slightly fuller foodbar can be made up by having a high food overflow in the turn of growth, but that is a marginal consideration.

2) Since food counts double while the granary is not full; it seems sometimes it might be useful to delay growth 1 turn (by growth advisor). This will allow you the partial use of the granary for an extra turn.

For example if you are size 1, and at 18/22, have 5 food surplus/turn and a freshly build (empty) granary. Moreover suppose you can grow into a 3F tile (farmed grassland, just for arguments sake).

- Doing nothing you will end up after 1 turn at 23/22, granary fills to 5 F and you end up at size 2 and 6/24 food. At size 2 you have 6 surplus food so you end at 12/24.

- Halting growth for 1 turn you end the first turn at 22/22, with 5 food in the granary. The next turn you do grow, get 27/22 with 10 in the granary, so 15/24 food.

In the situations where your granary has room to spare, halting growth will, at the cost of 1 turn of working an extra tile, give you an extra turn of food surplus (at the lower size) minus the overflow you would have had if you grew naturally. If the natural overflow is small, this is a good deal.
 
I simply don't understand peacevassaling conditions of parting from the master.
Isn't this function only used to set a variable that defines whether AI will even consider breaking off? The way it's written, I'd expect there to be another function where canVassalRevolt functions as input parameter that handles the actual break off. Especially since there seems to be no handling of vassal - master diplo, which definitely plays a part in it in my experience.
 
Granary micro is even more complicated than this: you also have to consider the tiles you could grow on earlier. (Like, a 2nd corn tile for example)

Of course, if you only have <= 2F tiles, then it's an easy choice.

I've made charts for optimal whipping times as a quick reference but in-game, they're almost never helpful as theoretical situations more often than not don't match real ones.
 
Isn't this function only used to set a variable that defines whether AI will even consider breaking off? The way it's written, I'd expect there to be another function where canVassalRevolt functions as input parameter that handles the actual break off. Especially since there seems to be no handling of vassal - master diplo, which definitely plays a part in it in my experience.

That the details that unnerve me. I haven't seen this function used many times and in pertinent files. There is no clear cut function using it and telling right away: ah the AI will cancel the vassal deal.

And in the test game I released, that makes no sense Izzy never breaks her vassality.
 
@Tachywaxon
I don't have any proper C/C++ ide/editor, so I used the browser to see the code. Some quick findings:
Peace vassals are deals similar to defensive packs and resource trades.
Overall deals incl. vassal ones are considered in a different way for cancellation between humans and AI vs AI and AI.
Cancelation vs Human depends only on the fact if the deal has been for over 20turns (2*peace length) and if the tradevalue offered by the human *110>=AI's tradevalue. Since the TRADE_VASSAL deals have zero value...

(I have not double-checked, yet I do not think peace-vassal use the mentioned code)
 
@Tachywaxon
I don't have any proper C/C++ ide/editor, so I used the browser to see the code. Some quick findings:
Peace vassals are deals similar to defensive packs and resource trades.
Overall deals incl. vassal ones are considered in a different way for cancellation between humans and AI vs AI and AI.
Cancelation vs Human depends only on the fact if the deal has been for over 20turns (2*peace length) and if the tradevalue offered by the human *110>=AI's tradevalue. Since the TRADE_VASSAL deals have zero value...

(I have not double-checked, yet I do not think peace-vassal use the mentioned code)

Yeah, I looked into situation and through AI_doDiplo() and AI_considerOffer() and lastly the value in AI_dealVal() of vassals is simply 0.
So impossible for getting 1.1 time more value over time.

Then I am asking myself why occasionally AI peacevassals reject master protection to beg for protection in short period.

I suspect too AI considers (iChange=-1, reconsidering existing trades) trades differently than with a human player. TMIT once rants about cheaty the AI gets iron from a friend for a cheap bonus like a health/happy resource...unless there was GPT involved...I didn't check his assumptions.

Peacevassals sure are an enigma to me. Many time I tried to crack this tough nut. I've seen CvPlayerAI, CvTeamAI, CvDeal, etc. Nope. No much trace how the AI evaluates the situation.
 
I did mention AI-2-AI relations.
They do not use AI_considerOffer() but getTradeDenial. So canceling deals between ai-ai and ai-human is different.

AI-Human:: AI_considerOffer()
AI-AI:: getTradeDenial

I've not seen *peace* vassal AI of human breaks out free on its right own.
about AI-AI just follow Player_AI::getTradeDenial that calls CvTeam_AI::AI_vassalTrade(TeamTypes)
(happy hacking :D )
 
I have never seen a capitulated vassal of mine break free unless I abused them by demanding resources, regardless of how much bigger they got. In an extreme case on deity one of them had 200% of my land/pop.

Peacevassals I remember R_Rolo1 gave a breakdown of...not sure on the thread.
 
In some recent games, I've been trying to settle a city near an AI's borders for the 1.5 Diplomacy for liberating to the AI. I try to settle the city near the AI's border, but not near any other Civ's border, but the liberation action is not always available. What conditions must be met for liberation to be an option?

I tried a game without the Lock Modified Assets option, so I could enter World Builder and modify city culture. I thought the city's plot culture had some effect, but I could not divine (detect and characterize) it.

I'd just like to know for sure whether settling a city on a specific plot will allow it to be liberated to the (nearest) AI. Do you have any suggestions or formula for determining this? Or even just an means of analyzing the problem?

Thanks,

Sun Tzu Wu
 
Top Bottom