Project: Changing Culture Spread

Davinci Fan

Chieftain
Joined
Jun 6, 2007
Messages
86
First I'd like to reintroduce myself to the community. Hi everyone. I played this game almost religiously when it first came out, but gradually real life encroached on my time and I had to set aside this wonderful game. Recently I began taking an online C++ programming course (still in progress, just beginning), and I was looking for a practical application that mattered to me. I decided to reapply myself to this game, from a modding perspective.

Anywho... One thing that I loved when I began the game was the concept of an empire's cultural boundaries. What I didn't like, though, was how circular and symmetrical it was. So here's my pitch: I'd like to see culture boundaries expand more in keeping with the physical terrain (for example, an empire ought to expand over mountain ranges and to off shore islands much more slowly than down a river through the great plains.)

Draft 1:
Each turn, every square that an empire owns 'emits' culture across the four sides of that square to the spaces immediately adjacent to that square that the empire doesn't control. If two cultures are butting up against each other, whichever is producing more culture emits the difference between the two to the other side. Once an empire has X more culture than any other in a particular square (say, 5), that empire gains control of that square.

How does the computer determine how much culture is 'emitted' across a line? Essentially, there is a base culture emission rate (bear with me for a moment) that is modified by the proximity to a the nearest city and the terrain of the square to which the culture is being transferred. Are the two squares on the the same side of a single river? +100% spread. Are you transferring to a hilly square? -50%. Across a river? -50% Along the coast? +50% spread. The numbers still need to be balanced (assuming this is possible), but I think you understand the idea. The base culture emission rate (BCER) is equal to the total culture produced by all cities divided by the perimeter of the culture footprint (discounting ocean squares or something I think)

Let me walk through how this would work with the simplest scenario, the center of pure grassland (5 culture gets you a square for now):

Turn 0 Settle your first city, +4 Culture per Turn (CpT) perimeter of 4 (beginning with no cultural boundaries)

Turn 5 Gain control of the four squares surrounding your city, new perimeter 12 (think of a 5 square cross and count the lines)

Turn 13 Gain control of the four squares in the corners of the cross (being fed by two perimeter lines at 1/3 CpT for 2/3 CpT 5/.66 = 7.5ish for 8 turns till expansion)

Turn 20 Gain control of four squares in the center of each side.

OOOXOOO
OOXXXOO
OXXXXXO
OOXXXOO
OOOXOOO


Turn 27 Gain control of the remaining squares in the BFC (2.33 culture on turn 20 plus .4 CpT (4/20*2 sides) for 7 turns to get 5.13)

It gets more complicated with terrain, but that's the basic idea. Is this possible to implement? I want to say yes, but I am not sure. Any feedback on the viability/feasibility? Any tips?


Cool, but why?

I decided to post a follow-up, since it occurred to me that it might appear that the changes I proposed, while interesting visually, serve little actual purpose. One mod I used for my own games the last time I played this game made it impossible (in the beginning of the game) to settle a city outside your cultural borders. Empires grew much more organically, with real definition to them - what with the contrasting no-man's-land that persisted until well into the middle ages.

I'd like to include this feature - with a few modifications regarding exploration, growth, and maintainance - in this mod, allowing nations to grow organically with regard to geography and topography.

Some basic ideas to improve the functionality of this change:

1. Cities may be settled closer than before (min 1 square instead of 2)
2. More bonuses to yields with technology and improvements as well as improved base yields for some tiles like coast (ie. a size twelve city can subsist on fewer squares than previously necessary)
3. Rebalanced bonuses from running specialists, including a general shift towards the countryside providing food and specialists providing production.
4. With particular accompaniment or technologies, settling on unowned plots becomes a possibility (such as settling on a foreign continent with the advent of navigation)
 
This must be possible. I would start by investigating CvPlayer::doCulture() and CvCity::doCulture(). Another idea is that tiles being worked by a city should generate more culture than those not being worked, and improved tiles more than unimproved tiles. Forts could also generate their own culture as well (I think this is an existing mod).
 
The whole concept looks interesting :9.

I don't know the name, but there's a modmod for RoM, which makes the cultural borders static in the endgame, you should also look at this.
And on RevDCM, the included mod "influence driven war" changes the existence of culture in the different plots based on the fights and won and lost battles on the plot.
 
The whole concept looks interesting :9.

I don't know the name, but there's a modmod for RoM, which makes the cultural borders static in the endgame, you should also look at this.

Don't bother. The modmoder released his sources, but they are for 3.17 RevDCM 2.0, and he DIDN'T comment anything. He admitted as much. Oh, and he won't be back modding until winter, he said.
 
Code:
void CvCity::doCulture()
{
	CyCity* pyCity = new CyCity(this);
	CyArgsList argsList;
	argsList.add(gDLL->getPythonIFace()->makePythonObject(pyCity));	// pass in city class
	long lResult=0;
	gDLL->getPythonIFace()->callFunction(PYGameModule, "doCulture", argsList.makeFunctionArgs(), &lResult);
	delete pyCity;	// python fxn must not hold on to this pointer 
	if (lResult == 1)
	{
		return;
	}

	changeCultureTimes100(getOwnerINLINE(), getCommerceRateTimes100(COMMERCE_CULTURE), false, true);
}

This is the code to which you were referring? I am still learning C++, so there are a couple things here of whose meaning I'm unsure.

What is the meaning of the first line 'CvCity::doCulture()'? I get the void and the doCulture(), but what precisely is CvCity::? Is the entire thing the name of the function (with the CvCity part denoting that it is particular to this cpp file)?

What is ->? It appears to be telling some other program to execute a subroutine, but I don't really know.

in the third line, what is the meaning of (this) in CyCity(this)?

Any help you could give would be great. Thank you.
 
What is the meaning of the first line 'CvCity::doCulture()'? I get the void and the doCulture(), but what precisely is CvCity::? Is the entire thing the name of the function (with the CvCity part denoting that it is particular to this cpp file)?

It just references the filename.

What is ->? It appears to be telling some other program to execute a subroutine, but I don't really know.

It's calling some python file.
in the third line, what is the meaning of (this) in CyCity(this)?

Cy is a heads-up that it has something to do with python. Not sure what (I'm terrible at python), but something.
 
Ah Afforess... Need more programming classes...
Or just experience (I've never had a class on C++ myself - or most other computer science related things, for that matter, even though I'm a professonal programmer).

The "CvCity::" part means that it is defining a method for the CvCity class. If you look in CvCity.h you can see that there is a class definition for CvCity that includes a line that says "void doCulture();". This is part of "object oriented programming". It's not just a standalone function, it is part of the class - every instance of the CvCity class (an object of the class, if you prefer), say "theCityInQuestion", has associated with it a doCulture method that you can access via "theCityInQuestion.doCulture()". The fact that it is the same as the file name is not actually relevant (unlike in Python, which does use the file name for namespace purposes) - you can define class Bob in files named Joe.h and Joe.cpp with no effect other than potential confusion for anyone working on it. For that matter, they could have done the whole thing in two files, one with all the suff in all the .h files and one with everything from all the .cpp files.

The "->" is related to pointers. It is not a coincidence that this looks like it is pointing at the thing next to it. It is used to get the value of something past a pointer, so to speak. The exact use of this thing is relatively hard to explain in just a couple of sentences (well - for me right now).

The "this" is a variable that exists in C++ that isn't actually declared anywhere. It is created automatically. It always refers to the object that the method is part of. In this case, it would be the CvCity class object for which you are running the method. If you have a class called MyThing which has a method called doThing, have an instance of that class called Bob, and then run a method of Bob's like "Bob.doThing()" then inside the function declared as "MyThing::doThing" the variable "this" is a pointer to Bob. (Got that?) What that first line in CvCity::doCulture() is doing is creating a new object of class CyCity with "this" passed to the class's constructor method which stores it (used to link the CyCity with the CvCity it is related to, I expect).

And, if I'm lucky, that might even all be correct. Am I lucky? :dunno:
 
Ok, I've been busy with school the past couple days, but fortunately that involves more practice with coding, so let's see if I can translate this all the way now.
Code:
void CvCity::doCulture() [B]//the declaration of the function.  It doesn't return a value and is intrinsic to the CvCity class (all things that could be called a CvCity)[/B]
{
	CyCity* pyCity = new CyCity(this); [B]//declaring a pointer of the type CyCity named pyCity equal to the current city?[/B]
CyArgsList argsList; [B]//Declaring an CyArgsList variable (unsure what that is) named argslist[/B]
	argsList.add(gDLL->getPythonIFace()->makePythonObject(pyCity));	// pass in city class
	long lResult=0; [B]//declaring a long interger named lResult and setting it to default at 0[/B]
	gDLL->getPythonIFace()->callFunction(PYGameModule, "doCulture", argsList.makeFunctionArgs(), &lResult); [B]//not sure, but whatever happens to the last parameter modifies the value of lResult?[/B]
	delete pyCity;	// python fxn must not hold on to this pointer 
	if (lResult == 1)
	{
		return; [B]//if the modified value is 1, skip this next part[/B]
	}

	changeCultureTimes100(getOwnerINLINE(), getCommerceRateTimes100(COMMERCE_CULTURE), false, true); [B]//call some other function changeCultureTimes100[/B]
}
 
I followed the chain of called functions, and I found one that looks promising...

Code:
void CvCity::doPlotCulture(bool bUpdate, PlayerTypes ePlayer, int iCultureRate)
{
	CvPlot* pLoopPlot;
	int iDX, iDY;
	int iCultureRange;
	CultureLevelTypes eCultureLevel = (CultureLevelTypes)0;

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

	FAssert(NO_PLAYER != ePlayer);

	if (getOwnerINLINE() == ePlayer)
	{
		eCultureLevel = getCultureLevel();
	}
	else
	{
		for (int iI = (GC.getNumCultureLevelInfos() - 1); iI > 0; iI--)
		{
			if (getCultureTimes100(ePlayer) >= 100 * GC.getGameINLINE().getCultureThreshold((CultureLevelTypes)iI))
			{
				eCultureLevel = (CultureLevelTypes)iI;
				break;
			}
		}
	}

	int iFreeCultureRate = GC.getDefineINT("CITY_FREE_CULTURE_GROWTH_FACTOR");
	if (getCultureTimes100(ePlayer) > 0)
	{
		if (eCultureLevel != NO_CULTURELEVEL)
		{
			for (iDX = -eCultureLevel; iDX <= eCultureLevel; iDX++)
			{
				for (iDY = -eCultureLevel; iDY <= eCultureLevel; iDY++)
				{
					iCultureRange = cultureDistance(iDX, iDY);

					if (iCultureRange <= eCultureLevel)
					{
						pLoopPlot = plotXY(getX_INLINE(), getY_INLINE(), iDX, iDY);

						if (pLoopPlot != NULL)
						{
							if (pLoopPlot->isPotentialCityWorkForArea(area()))
							{
								pLoopPlot->changeCulture(ePlayer, (((eCultureLevel - iCultureRange) * iFreeCultureRate) + iCultureRate + 1), (bUpdate || !(pLoopPlot->isOwned())));
							}
						}
					}
				}
			}
		}
	}
}

I'm still working on translating this one though, if anyone has hints...
 
This section of code (two posts up as you posted this new larger block while I was posting this) is part of Civ's callback mechanism that allows the modder to alter some game behavior in Python. It calls the doCulture() function in CvGameInterface.py, and if it returns True (1 when converted to a long integer) it skips its default behavior and assumes the callback function handled the task. The default callback function shipped with Civ returns False, and so changeCultureTimes100() gets called for the city's owner, adding the culture produced by the city each turn to the city and surrounding plots.

What this means is that you could implement your mod in Python, the culture spreading part anyway. It would be slower, but it would only be called once per city per turn--not a huge time penalty.

When a Python function is called from the DLL to fire events or use callbacks, the arguments passed to the function are placed inside a CyArgsList object. CyArgsList is a class just like CvCity or CvString: it contains data and functions that operate on it. It's main purpose is to convert C++ types to the types that Python expects.

All of the CyFoo classes are wrapper classes for their paired CvFoo classes. The SDK uses Boost (you can Google it for more information) to talk to the Python modules. Instead of passes entire CvCity object with all its data to Python which would require copying a lot of arrays and values, a tiny temporary CyCity wrapper object is created--called instantiation in object oriented programming because you create an instance of the class--whose only data member is a pointer to the wrapped CvCity object. All of the CyCity functions pass through the parameters to a function with the same name on the underlying CvCity object. The only way to get to the CvCity is through the CyCity, thus the term wrapper.

So the code above wraps the CvCity with a CyCity, creates a set of arguments for the callback, and calls out to the Python layer, passing the module name, function name, CyCity, argument list, and a pointer to the place to store the function's return value. Once it returns it deletes the wrapper class as it was temporary and checks the result.
 
It's true that python could handle it, but I'm not sure if that's the best idea. Here's my preliminary algorithm for when I find where to put it;

Loop through each perimeter plot from a list of owned plots for the civilization (the plots directly adjacent to owned plots) and apply culture growth (using whatever formula) to the plots.
Add any plots that reach the threshold to the list of owned plots (for use next turn) and switch them to the empire's control.
Remove from the list any plot that's completely surrounded by friendly plots.

Other bits of code that would be necessary

Whenever a settler founds a city, if the plot isn't on the list, add it to the list.

Whenever an empire1 loses a plot due to cultural overflow from empire2, add to the list all plots owned by empire1 that border the plots taken over by empire2.

This may be executable in python, but frankly I'd rather it be as fast as possible since it seems a little more involved than the original city modifier. If you have a suggestion for a better method though, which wouldn't surprise me, I'd be happy to hear it.
 
I did some more code chasing, and found the following:

Code:
def doCulture(argsList):
	#CvUtil.pyPrint( "CvGameInterface.doCulture" )
	return [B]gameUtils[/B]().doCulture(argsList)
Code:
def doPlotCulture(argsList):
	#CvUtil.pyPrint( "CvGameInterface.doPlotCulture" )
	return [B]gameUtils[/B]().doPlotCulture(argsList)

in CvGameInterface.py and
Code:
	def doCulture(self,argsList):
		pCity = argsList[0]
		return False
Code:
	def doPlotCulture(self,argsList):
		pCity = argsList[0]
		bUpdate = argsList[1]
		ePlayer = argsList[2]
		iCultureRate = argsList[3]
		return False
in GameUtils.py

Are these related? They appear to simply declare some arguments for the argslist, but I'm not sure what they mean... Wow I'm bad at this :blush:
 
I wasn't recommending that you code this in Python--only pointing out that you could because the SDK used a callback for it. The code you posted is how all callbacks are handled. CvGameInterface.py is called directly by the SDK, and as you can see each callback function calls a function with the same name in a gameUtils object which normally is an instance of the CvGameUtils class.

All of the functions in CvGameUtils are placeholders. They copy the arguments from the argsList passed to the functions into local variables so that modders can more easily see what arguments are passed in. Both of those functions return False which tells the SDK to use its normal culture spreading algorithm.

Your logic seems sound. The game has the concept of plot groups to track national borders, trade areas, and other possibly-overlapping areas. These are distinct from CvArea which groups plots into non-overlapping, contiguous land masses and bodies of water. You may be able to make use of CvPlotGroup for your culture frontiers.
 
They do very little in CvGameInterface.py - the functions are implemented in other files, so all it does is call the function from the other file. You can change the file by swapping a definition at the beginning of CvGameInterface, or by adding a CvGameInterfaceFile.py to your mod with a different definition in that.

In CvGameUtils.py these two functions do nothing but return "False" to indicate to the calling DLL that they did nothing, but they do unpack the arguments from the argsList list for you so that you can see what they actually are (via the more decriptive names) should you modify the functions.

Edit:

EF beat me to it. And he's right - this would be better done in the DLL, I think. You have more options and it won't slow the game down.
 
Ok, so I think this is the code I need to modify, but I am still trying to translate.

Code:
void CvCity::doPlotCulture(bool bUpdate, PlayerTypes ePlayer, int iCultureRate)
{
	CvPlot* pLoopPlot;
	int iDX, iDY;
	int iCultureRange;
	CultureLevelTypes eCultureLevel = (CultureLevelTypes)0;
// [B]^ Declaration of variables?[/B]
	CyCity* pyCity = new CyCity(this);
	CyArgsList argsList;
	argsList.add(gDLL->getPythonIFace()->makePythonObject(pyCity));	// pass in city class
	argsList.add(bUpdate);
	argsList.add(ePlayer);
	argsList.add(iCultureRate);
	long lResult=0;
	gDLL->getPythonIFace()->callFunction(PYGameModule, "doPlotCulture", argsList.makeFunctionArgs(), &lResult);
	delete pyCity;	// python fxn must not hold on to this pointer 
	if (lResult == 1)
	{
		return;
	}
//[B] ^That python callback system you were talking about?[/B]
	FAssert(NO_PLAYER != ePlayer); //[B]Not sure...something about skipping if the player doesn't exist?[/B]

	if (getOwnerINLINE() == ePlayer)
	{
		eCultureLevel = getCultureLevel();
	} //[B]something about the variable being set to the current level if the player owns the plot?[/B]
	else
	{
		for (int iI = (GC.getNumCultureLevelInfos() - 1); iI > 0; iI--)
		{
			if (getCultureTimes100(ePlayer) >= 100 * GC.getGameINLINE().getCultureThreshold((CultureLevelTypes)iI))
			{
				eCultureLevel = (CultureLevelTypes)iI;
				break;  //[B]not sure about this nest, but there's some sort of cop out here according to my professor.  'break' is a Bad Thing(TM), yes?[/B]
			}
		}
	}

	int iFreeCultureRate = GC.getDefineINT("CITY_FREE_CULTURE_GROWTH_FACTOR"); //[B]another variable definition based on XML growth factor, I think.[/B]
	if (getCultureTimes100(ePlayer) > 0) //[B]If the player has a stake in the territory...?[/B]
	{
		if (eCultureLevel != NO_CULTURELEVEL)  //[B]and the culture level exists, whatever that is.[/B]
		{
			for (iDX = -eCultureLevel; iDX <= eCultureLevel; iDX++)
			{
				for (iDY = -eCultureLevel; iDY <= eCultureLevel; iDY++) //[B]Then for each coordinate in some culture level zone...?[/B]
				{
					iCultureRange = cultureDistance(iDX, iDY); //[B]get some sort of distance value[/B]

					if (iCultureRange <= eCultureLevel) //[B]If that value is lower than that mysterious 'culture level'[/B]
					{
						pLoopPlot = plotXY(getX_INLINE(), getY_INLINE(), iDX, iDY); //[B]check some plots (not sure what inline means)[/B]

						if (pLoopPlot != NULL) // [B]and if those plots exist...[/B]
						{
							if (pLoopPlot->isPotentialCityWorkForArea(area()))//and fit some criteria regarding where they are...
							{
								pLoopPlot->changeCulture(ePlayer, (((eCultureLevel - iCultureRange) * iFreeCultureRate) + iCultureRate + 1), (bUpdate || !(pLoopPlot->isOwned()))); //[B]then weight those plots more heavily towards the ePlayer's control?[/B]
							}
						}
					}
				}
			}
		}
	}
}

it looks like I also need to investigate the change culture function, but though it's declared in City.h, I can't find it in the .cpp files. Am I near the mark with my translation guesses? It also looks like it's based off of particular cities. I don't want that, but I'd like to address that later
 
Code:
				break;  //[B]not sure about this nest, but there's some sort of cop out here according to my professor.  'break' is a Bad Thing(TM), yes?[/B]

No.

It is a good thing.

Without it you can do one of two things:
  1. Keep looping through every possible value of the loop variable even after you have done what you wanted to do (which is a complete waste of time and energy) with some additional code needed to avoid doing things in the iterations after you've already done what you wanted to do.
  2. Directly manipulate the loop variable by setting it to a value that will make it stop looping, which really is a bad thing.
 
Your understanding is pretty close on most of those. Essentially what it does is check the current culture level of the city and then increase the culture in the area that level allows: as level goes up, area gets bigger which you can see as the city borders expand.

God-Emporer is correct about break. Your teacher is being a bit reactionary. When I was learning to code the big taboo was goto which lets you jump to another part of a function at will. Once you understand how to use break, continue--and yes even goto--they can make your programs more efficient and readable.

You can always rewrite a loop to not need break by introducing an arbitrary "done" boolean that you set to false to exit the loop or modify the loop counter if possible, but that requires more thought to comprehend when reading the code while break is obvious and always means the same thing.

For a real-world example, say you forgot where you parked in a huge parking structure and had to going up and down all the rows and floors until you found your car. Once you found your car, would you continue checking the rest of the cars? Of course not. You would break out of your search loop and drive away.
 
Ok, I have a couple questions before I get started on the code.

Is there an easy way to find the file in which a function is declared? changeCultureTimes100, getCulture, setCulture, changeCulture, setCultureTimes100 are all called multiple times in different files, but I can't find out what they do...

Does the Player_INLINE thing mean the player whose turn it is?
 
Also I was wondering if CvCity is now the best place to put this function, since it depends on the aggregate culture instead of the individual city's. Or would it be too much modification to put the function elsewhere?
 
Back
Top Bottom