Nationality tweaks

vincentz

Programmer
Joined
Feb 4, 2009
Messages
3,614
Location
Denmark
Last night I was thinking of a way to reduce the steam rolling that happens in civ games. Most of this comes from, imho, getting lots of cities. Lots of cities means lots of commerce and production.
So I thought about a way to reduce or at least slow down this effect, and since most cities late game is captured, I considered the options available and came to a conclusion that, imho, would both be semi-realistic as well as good gameplay.

My idea was to :
1) Reduce commerce (and maybe even production) depending on nationality of city. So own civ nationality would work 100% while foreign nationality would only give 50%.
As a formula it would look like this : Commerce=commerce - (commerce * foreign nationality% / 2)

Examples : Commerce 50 and 50% foreign nat. = 50 - (50*0.5/2) = 37.5
or when newly captured : commerce 50 and 100% foreign = 50 - (50*1/2) = 25

2) and most importantly, when civ is killed, instead of automatically make it 100% own civ nationality, it either (A) becomes neutral nationality (barb) or (B) it remains as original civ nationality (could be an issue, with "dead" civ not being removed from scoreboard or similar effect)

I really believe these two (minor?) edits could make a huge impact on especially player taking over the world steam rolling effect, and at the same time encourage new tactics such as sending spies to spread culture or using vassals more often (I never use vassals normally, but if I only got half commerce from a city, I might consider creating a vassal state, to control newly captured territory).

Problem is.... I have no idea where to start :(
Are there anyone who have bulbed codewriting, who would care to assist me in making this come true?
 
I agree that conquest-based strategies are too powerful, and that conquered cities should be less valuable or costlier to acquire (like with your proposed change to healing); ideally both.

I can't assist immediately. I could probably do 2) later this year; it's on my to-do list anyway. There could well be some unforeseen side-effects and questions to be clarified (e.g. whether a city with 80% of a dead culture, 1% of the new owner and 19% of a nearby third civ should have a positive revolt probability), but it still shouldn't be much work. No AI or user interface changes, I think. If someone else did it, that would be all the better of course (for me).

1) sounds promising too, but I wonder if an established mechanism could be used instead. I'd interpret the lowered (commercial) productivity as some form of resistance against the conquerers or corruption among the old elites. That's currently represented by (a) angry citizens, (b) loss of workable tiles, (c) maintenance or (d) revolt.
a. Angry citizens don't work well in this case because a player that conquers a lot usually has a lot of luxury resources. (The root of that evil is that each resource is needed only once regardless of the number of cities and citizens.)
b. Higher maintenance for cities with foreign culture might work so long as you only want to reduce commerce (and not production or food).
On that note, if production is unaffected, conquered cities could be developed as production cities to work around the lowered commerce. If production is lowered as well, cities could still be optimized for food and specialists or exploited for Slavery.
c. Lost tiles only affect some of the conquered cities (if any). That said, with your change, these cities would be punished twice.
d. I've been considering to allow revolts even in cities that aren't near a foreign city (or that only have culture of a dead civ). This sounds easy, but would involve resurrecting dead civs, and would arguably require some additional changes to have the desired effect. I've also considered charging support costs for units in tiles with a foreign majority culture.

Your formula should be equivalent to
commerce = base commerce * (1 + city owner's relative tile culture) * 0.5.
Not really more intuitive this way, but can be read as "apply
m = (1 + city owner's relative tile culture) * 0.5
as a modifier". The Bureaucracy modifier could then by added to m.

1) may necessitate some AI changes. For one, building up culture in conquered cities would become more rewarding. I suppose the AI already tends to play rather peacefully, so, perhaps the high-level strategy wouldn't have to change, just the chooseProduction function.

Anyway, even without further changes, I see two advantages in 2):
* The conquerer suffers from cultural unhappiness (the explanation "yearn to join our motherland" should be changed though).
* It's more realistic for the culture to persist.
Well, like you wrote: semi-realistic and better gameplay.
 
thanks. i would certainly appreciate the help :)
I tried looking trough the code, and I guess it should be done in cvcity.cpp around line'ish 8900, though I dont know what city-nationality is called.
Im also not sure what order commerce is being calculated in, and whether it should be final commerce that is adjusted or simply the tile yield commerce (Im thinking the first).
Its true that if commerce alone, there would be work-arounds for the player, by f.ex. make plenty of production tiles and then convert it to science/gold, so perhaps it would be best for production to take a hit too.
With regards to food and slavery, I think it would somehow fit the "semi-realism".

Regarding the formula, its ofcourse easier to calculate own culture than to add several of other cultures ;)

I think the AI, if we are lucky, already have some code for wanting to gain culture. I know when it captures cities, it will build culture until it reaches 3rd level of culture, and it is also pretty adapt at culture bombing/swapping.

2) I really missed this feature from the beginning of playing civ IV, as it seems strange the foreign culture would suddenly disappear completely when taking the last city. Donno how difficult it is to "catch" the moment where a civ is destroyed and all the culture is lost.

edit. lol, just realized that city nationality % is culture %.... I'll try to see what I can come up with ;)
 
2) and most importantly, when civ is killed, instead of automatically make it 100% own civ nationality, it either (A) becomes neutral nationality (barb) or (B) it remains as original civ nationality (could be an issue, with "dead" civ not being removed from scoreboard or similar effect)

This is done in Chronis' Tweaks for LoR. The (few) changes for a FfH adaption can be found here. Actually, the culture of old civs isn't removed, it's just not displayed anymore.
 
This is done in Chronis' Tweaks for LoR. The (few) changes for a FfH adaption can be found here. Actually, the culture of old civs isn't removed, it's just not displayed anymore.
Thank you indeed. That diff is very convenient. I've copied the changes into a fresh DLL, and found some oversights (see below). Still, I think I've fixed them, and part 2 is thus already done. I've attached the modified files. (The DLL is too large to attach; if someone needs it, I can put it into the database as a mini mod.) Haven't really tested it though: just killed a civ and hit "end turn" a couple of times.

Edit1: Oh, changes are marked with "ImmortalCulture" in the code.
Edit2: Two more functions changed in CvCity.cpp. I've attached the updated version of that file separately and marked the new changes with "v0.2".
Edit3: Another in CvCity.cpp (looks unimportant) and one in CvMainInterface.py. Tag: "v0.3".

The help text on plots didn't show culture at all because the game tried to include info about players that have never been alive. I've replaced the three isAlive checks with isEverAlive (instead of removing the checks). The crucial one was in CvGameTextMgr. An additional is-alive check in CvCity prevented anger from foreign culture. Perhaps it's debatable whether such anger should occur; I think it should. Another is-alive in CvPlayer prevented dead culture from showing up in the global culture layers view (when you zoom out). And another one in CvPlot and CvGameTextMgr, but these rarely matter and aren't really worth mentioning.

The safest would be to go through all is-alive checks in the code, but there might be a three-digit number of those. I think I got most of the relevant ones by working backwards from mentions of "culture" in CvPlot.
I tried looking trough the code, and I guess it should be done in cvcity.cpp around line'ish 8900, though I dont know what city-nationality is called.
I have CvCity::setName in line 8900 of the original BtS file. At any rate, CvCity should only deal with city culture, while nationality is a matter of plot culture (which is, in turn, driven by city culture).
Im also not sure what order commerce is being calculated in, and whether it should be final commerce that is adjusted or simply the tile yield commerce (Im thinking the first).
I'd go for CvCity::getBaseYieldRateModifier; that's where the Bureaucracy modifier gets added (capitalYieldRateModifier). Per-tile would lead to losses through rounding I think. A single modifier is also less obtrusive in the mouse-over help text (assuming that you add help text). Edit: Maybe I misunderstood; you probably didn't intend to apply a penalty to every single worked tile ... Still, that function should be a good starting point.
Its true that if commerce alone, there would be work-arounds for the player, by f.ex. make plenty of production tiles and then convert it to science/gold, so perhaps it would be best for production to take a hit too.
That's probably CvCity::getProductionModifier. Handicap and game speed factor into getProductionNeeded; these affect production costs rather than production rate. Perhaps this is preferrable because rounding is then less of a factor. (Per-turn production can be quite low.)
I think the AI, if we are lucky, already have some code for wanting to gain culture. I know when it captures cities, it will build culture until it reaches 3rd level of culture, and it is also pretty adapt at culture bombing/swapping.
That's a nice side-effect of strengthening the impact of culture and such: some of the nonsense that the AI does suddenly makes sense.
 

Attachments

Thanks :D

Spoiler :
JWj4XBq.jpg

Spoiler :
u1oUwLW.jpg

Spoiler :
c4VsfSh.jpg


I have missed this since day 1 playing civ IV

:goodjob:
 
Thank you all for this "Immortal Culture". I also wanted this in my mod for quite some time! :)
 

I have missed this since day 1 playing civ IV

:goodjob:[/QUOTE]Stylish. :yeah: I had used mere Navy Seals in my test.

The text key for culture anger is called TXT_KEY_ANGER_OCCUPIED by the way, located in Vanilla/CIV4GameTextInfos.xml. Maybe "we yearn to join our motherland" is OK even when that land no longer exists; "we yearn for our motherland" might be a bit better. Both can be odd for barbarians. "We dislike being ruled by a foreign nation/culture"? Perhaps too similar to "down with foreign influence", though only vassal civs get that one ...
 
"We will NOT be ruled (by foreigners)!" ?
or
"We will NOT be ruled by foreign scum!"

Btw, I had a second look at the code regarding the lowered commerce:

PHP:
void CvCity::updateCommerce(CommerceTypes eIndex)
{
	int iOldCommerce;
	int iNewCommerce;

	FAssertMsg(eIndex >= 0, "eIndex expected to be >= 0");
	FAssertMsg(eIndex < NUM_COMMERCE_TYPES, "eIndex expected to be < NUM_COMMERCE_TYPES");

	iOldCommerce = m_aiCommerceRate[eIndex];

	if (isDisorder())
	{
		iNewCommerce = 0;
	}
	else
	{
		iNewCommerce = (getBaseCommerceRateTimes100(eIndex) * getTotalCommerceRateModifier(eIndex)) / 100;
		iNewCommerce += getYieldRate(YIELD_PRODUCTION) * getProductionToCommerceModifier(eIndex);
	}

	if (iOldCommerce != iNewCommerce)
	{
		m_aiCommerceRate[eIndex] = iNewCommerce;
		FAssert(m_aiCommerceRate[eIndex] >= 0);

		GET_PLAYER(getOwnerINLINE()).invalidateCommerceRankCache(eIndex);

		GET_PLAYER(getOwnerINLINE()).changeCommerceRate(eIndex, (iNewCommerce - iOldCommerce));

		if (isCitySelected())
		{
			gDLL->getInterfaceIFace()->setDirty(InfoPane_DIRTY_BIT, true );
			gDLL->getInterfaceIFace()->setDirty(CityScreen_DIRTY_BIT, true);
		}
	}
}

how about:
{
iNewCommerce = (getBaseCommerceRateTimes100(eIndex) * getTotalCommerceRateModifier(eIndex)) / 100;
iNewCommerce += getYieldRate(YIELD_PRODUCTION) * getProductionToCommerceModifier(eIndex);
iNewCommerce *= (1 + iTeamCulturePercent) * 0.5;
}

ps. I havent tested yet. Im scared my computer might explode, or the universe implode. ;)

edit: went ahead anyway. computer still intact, universe shaken up a bit, code error : error C2065: 'iTeamCulturePercent' : undeclared identifier

edit2: added an int iTeamCulturePercent, but got :
1>CvCity.cpp(8356) : warning C4244: '*=' : conversion from 'double' to 'int', possible loss of data
1>c:\dev\cvgamecoredll\cvcity.cpp(8356) : warning C4700: local variable 'iTeamCulturePercent' used without having been initialized

Spoiler :
z4vwbt_4.jpg
 
Now I see what you mean: The modifier could apply in at least three different places:
Spoiler :
0B-kvZk88WboLZWFRcG0xYkwwN3c

(i) corresponds to getBaseYieldRateModifier, (ii) getTotalCommerceRateModifier and (iii) updateCommerce. They're all reasonable to me. (iii) seems to have the disadvantage that a few AI functions rely on getTotalCommerceRateModifier rather than getCommerceRate, especially AI_juggleCitizens. As far as I understand the code, the AI assignment of citizens would be unaware of a culture-based penalty in updateCommerce.

About the compiler errors: iTeamCulturePercent is only defined within calculateTeamCulturePercent; you'd have to call that function instead. Also, using fractional numbers is troublesome because the original developers have used only integer arithmetic. :shake:
This is working for me:
iNewCommerce *= 100 + calculateTeamCulturePercent(getTeam());
iNewCommerce /= 200;
That is, only if I change another isAlive in CvCity::calculateTeamCulturePercent and countTotalCultureTimes100. I've added these two to my earlier post (the attached sources).

You can't see the effect in the city help text, but, after ending the turn to force culture values to be updated, the debugger showed iNewCommerce to decrease from 720 to 403. That looked like (didn't verify) the research output of a city with 10% of my own culture. Also, the main-screen gold deficit went up from 48 to 60 upon ending my turn.

"We will NOT be ruled (by foreigners)!" ?
or
"We will NOT be ruled by foreign scum!"
Feisty. :lol: A bit much for the case where the cultures just mix at close borders. I'll use "resent" instead of "dislike" for now (for my own mod); "dislike" is too tame.
 
It needed the plot before it works, otherwise it would just divide with 2, which is the drop you had. So...
iNewCommerce *= 100 + (plot()->calculateTeamCulturePercent(getTeam()));

I tested it by adding foreign culture in WB. It insta changed gold output :D

Thanks. I appreciate it very much :goodjob:

Spoiler :
AjKUpeI.jpg

Spoiler :
mhIRumw.jpg


Feisty. :lol: A bit much for the case where the cultures just mix at close borders. I'll use "resent" instead of "dislike" for now (for my own mod); "dislike" is too tame.

Isnt the unhappiness only showing when in war (or have War Weariness)?
Iirc the unhappiness disappears when no longer WW, which is why I spiced it up a bit ;)
 
It needed the plot before it works, otherwise it would just divide with 2, which is the drop you had. So...
iNewCommerce *= 100 + (plot()->calculateTeamCulturePercent(getTeam()));
Oh, right. Plot culture instead of city culture. :o Is guess there's no point in changing the isAlive checks in CvCity then (no harm either).
Isnt the unhappiness only showing when in war (or have War Weariness)?
Iirc the unhappiness disappears when no longer WW, which is why I spiced it up a bit ;)
You scared me for a moment. Here's the code in getCulturePercentAnger, slightly simplified:
Code:
iAngryCulture = 0;
for all players p on other teams {
  iCulture = plot()->getCulture(p);
  if(iCulture > 0) {
    if(atWar(getTeam(), getTeam(p))) {
      iCulture *= 150;
      iCulture /= 100;
    }
  iAngryCulture += iCulture;
}
return ((400 * iAngryCulture) / iTotalCulture);
I.e. the foreign citizens are always angry, but 150% angrier when at war (which I didn't know, but sure makes sense). So, "TXT_KEY_ANGER_OCCUPIED" is a rather misleading name. I've also dug up a savegame where I have 61% own culture and 38% from a civ I've never been at war with. This results in 1 anger, as it should: 0.38 * 4 is 1 after rounding. Edit (one week later, for posterity): It's actually more complicated. The return value gets multiplied by population (which happens to be 10 in my game) and divided by 1000, i.e. 0.38 * 400 * 10 / 1000 (= 0.38 * 4).
 
Oh, right. Plot culture instead of city culture. :o Is guess there's no point in changing the isAlive checks in CvCity then (no harm either)..

I think it might have fixed the city culture bar, which was showing grey when dead city culture was present, so I think its good its there.

You scared me for a moment. Here's the code in getCulturePercentAnger, slightly simplified:
Code:
iAngryCulture = 0;
for all players p on other teams {
  iCulture = plot()->getCulture(p);
  if(iCulture > 0) {
    if(atWar(getTeam(), getTeam(p))) {
      iCulture *= 150;
      iCulture /= 100;
    }
  iAngryCulture += iCulture;
}
return ((400 * iAngryCulture) / iTotalCulture);
I.e. the foreign citizens are always angry, but 150% angrier when at war (which I didn't know, but sure makes sense). So, "TXT_KEY_ANGER_OCCUPIED" is a rather misleading name. I've also dug up a savegame where I have 61% own culture and 38% from a civ I've never been at war with. This results in 1 anger, as it should: 0.38 * 4 is 1 after rounding

Ah ok. well, I guess I'll leave mine as vanilla. I guess, even as stateless, its still possible to yearn for "motherland" ;)

Once again, thanks for the help. Funny thing is, whenever I make changes to the DLL and it actually works, I feel like a mix between a Hollywood movie hacker, and a 7 year old kid on Christmas eve :)
 
I think it might have fixed the city culture bar, which was showing grey when dead city culture was present, so I think its good its there.
But I'm still not seeing the dead culture there, and, apparently, that's due to an is-alive check in Python, namely in CvMainInterface.py. Added that one to the attached files above.
Once again, thanks for the help. Funny thing is, whenever I make changes to the DLL and it actually works, I feel like a mix between a Hollywood movie hacker, and a 7 year old kid on Christmas eve :)
"What is happiness? The feeling that power is growing, that resistance is overcome." (scnr)
Thank you for instigating the matter.
 
Back
Top Bottom