Project: Changing Culture Spread

I really like the idea of culture spreading faster near worked and improved land. One thing that worries me, like with what Opera suggested, is whether or not 'islands' of uncontrolled land will appear.

That is, if I have a city like this:

[M]
[M][M][M]
[M][X][M][O][O]
[M][M][M][M][O][O]
[M][F][O][C][O][O]
[M][F][F][O][O][O][O]
[F][F][O][O]

O Controlled
[M] Peak
[C] City
[F] Forest


And the border pops once or twice, will I gain control of the spot marked X despite not controlling the mountain peaks. If I need to gain control of the peaks first, how difficult can we make that?

If it can be made difficult enough that gaining control of peaks will be reserved for all but the most impressive cities (ie make the tweaks so that even peaks in the close 8 remain uncontrolled for a while), then I'd love to incorporate Afforess's mod that makes the peaks workable. It seems to me that mountain mines could offer an interesting degree of additional resources that would remain unavailable for all but the largest cities (Ice could be a nice new resource... maybe one with limited range of influence if that's possible- oops, topic for another time).
 
Probably not... and certainly, a city that has a bad starting location will control fewer tiles as well. At least until it gets towards the higher culture levels. So certainly this isn't just cosmetic. It will have gameplay ramifications. But give it a shot.

I didn't mean to imply this was a bad thing. Just because a city hits "Devolping" culture, it doesn't mean it gets access to all 21 plots, if there are other competing cities.
Some ways to counter this would be to make culture spread 1 more range than they currently do. So instead of the first border popping resulting in a fat cross, it would go to the next largest size... but some of the squares would be shaved off because of the terrain resulting in a much more ragged (and interesting) border area.

Well, RevDCM has Influence Driven Culture in it, and I use RevDCM as a base, so that alone causes some interesting culture effects...
I also think that incorporating my improvement upgrades modcomp would be useful too since that affects the upgrade rates of improvements based on the surrounding terrain. These two things together I think would give a much more natural and organic flow to the game with borders filling favorable land and improvements built on that land growing faster than on less desirable tiles. It essentially simulates a rural population without having to actually code such a game mechanic into the game.

I didn't even know that you had a improvement modcomp. That shows just how much I know...
 
How does the 'culture resistance' work? How is it measured and compared to the city's cultural output?

By default, when the city's border pops, it cycles through all the plots around it. The distance it cycles is dependent on the culture level. It then uses a formula to calculate the culture amount to put into each plot. The only time it puts nothing is when it's an ocean tile.

With my code, the computer adds up all the resistance values mentioned above for each plot then compares that total value to the intended culture amount times 10. Why ten? It just let's you use a little more precision in the resistances. So if the city would add 2 culture to a plot 2 tiles away, and the tile is across a river, and the river crossing resistance is 30, the resistance will be higher than the culture * 10 (20).

Could you add a check that's like 'is it behind a peak'? You know, getting culture over one isolated peak might be okay... but not over too many peaks. I will throw in a check to see if the civilization can work peaks, in which case it won't matter, but I will take care of this myself.
Great idea. Going in.

Hmm... Could you make it so if the culture should expand in the direction the river is flowing, it's easier for it to expand?

I may ask for too much
That's pushing it ;)
 
then I'd love to incorporate Afforess's mod that makes the peaks workable. It seems to me that mountain mines could offer an interesting degree of additional resources that would remain unavailable for all but the largest cities (Ice could be a nice new resource... maybe one with limited range of influence if that's possible- oops, topic for another time).

Not my mod, but I have heavily changed the Mountains Back to Service modcomp, which I felt was really half-finished. I just added new tech prereqs for crossing mountains, tech prereq for faster mountain travel, and tech prereq for Cities on peaks, along with a special promotion that allows GG's to cross mountains before the normal tech, so certain buildings are required to be built in a city on peak (Think Ski Resorts)...Oh, and made it a gameoption, and... I think there's more changes too, but I can't remember them all... I guess in a way, it is my own mod now, but it's debatable.

EDIT: slow to the draw here, as usual.
 
By default, when the city's border pops, it cycles through all the plots around it. The distance it cycles is dependent on the culture level. It then uses a formula to calculate the culture amount to put into each plot. The only time it puts nothing is when it's an ocean tile.
Is it only controlled by the CvCity::doCulture() function? Because I don't see any mention of ocean... unless NULL == Ocean? But I don't think so... I might be missing something. I never found where this was handled :lol:

With my code, the computer adds up all the resistance values mentioned above for each plot then compares that total value to the intended culture amount times 10. Why ten? It just let's you use a little more precision in the resistances. So if the city would add 2 culture to a plot 2 tiles away, and the tile is across a river, and the river crossing resistance is 30, the resistance will be higher than the culture * 10 (20).
Okay. Hmmm. But is there a way ingame to see how many culture points are given to a plot? I think it's only a 'hidden' number right now, so it might be interesting to make it show up somehow.

Great idea. Going in.
Awesome!

That's pushing it ;)
I knew it :p
 
Is it only controlled by the CvCity::doCulture() function? Because I don't see any mention of ocean... unless NULL == Ocean? But I don't think so... I might be missing something. I never found where this was handled :lol:
It's handled in CvCity::doPlotCulture(...), right below doCulture() ;)

Then there's a function called there: pLoopPlot->isPotentialCityWorkForArea(area())

That's where it determines if the tile is valid for ownership, but right now, it's pretty much only screening out Ocean tiles since all other tiles can be owned.

Okay. Hmmm. But is there a way ingame to see how many culture points are given to a plot? I think it's only a 'hidden' number right now, so it might be interesting to make it show up somehow.
Well, it'd certainly be possible. I'm just not sure how useful it'd be. The base values aren't really that significant.

One of the problems is that as soon as you have any culture on a tile with no other player culture, it becomes yours. Because of that, if the tile's culture spread resistance is greater than the culture being given (times 10), the tile is given nothing. So each time the city's culture pops, it's a totally fresh start. But what would probably be more realistic would be to have some kind of threshold on the tile. So instead of all those different factors adding up to some kind of a resistance value, they would be a threshold required for ownership.

So a tile would then be able to have some culture on it, but it wouldn't be enough to be claimed by a player. The more inhospitable the terrain, the higher the threshold and thus the more culture that has to accumulate there before the tile will be owned by the player. The problem is that this will be more resource intensive. I'm going to stick with current system for now and see how it goes.
 
Ok, so to help prevent those "culture islands", I've decided to have the game take the highest of the two resistance values between a tile and the tile between it and the city. It should solve the problem, but I'll run some tests to be certain.
 
For my own edification, what exactly are you modifying here? You said it was all tweakable in the XML; did you add new tags in the terrain infos?
 
For my own edification, what exactly are you modifying here? You said it was all tweakable in the XML; did you add new tags in the terrain infos?

New tags for Terrains, Features, Bonuses, and Improvements, yes. And some new values in the GlobalDefines.xml.
 
Is there a preliminary version you have thrown together, or are you waiting until it's more polished? I'm still digging around in the cpp, but it will be some time before I get the know-how to really effect significant changes. If you spend like three minutes commenting on your logic (in english, not C++ :)) in the files, I'd really appreciate it. Any thing to help me understand what you're doing when you do it. I can't wait to see what you've got! Thanks.
 
I decided to have a go at the method you suggested, but I'm having some difficulties in conceptualization.

Is the IsPotentialWorkForArea fxn where you made the adjustments?

Code:
bool CvPlot::isPotentialCityWorkForArea(CvArea* pArea) const
{
	PROFILE_FUNC();

	CvPlot* pLoopPlot;
	int iI;

	for (iI = 0; iI < NUM_CITY_PLOTS; ++iI)
	{
		pLoopPlot = plotCity(getX_INLINE(), getY_INLINE(), iI);

		if (pLoopPlot != NULL)
		{
			if (!(pLoopPlot->isWater()) || GC.getDefineINT("WATER_POTENTIAL_CITY_WORK_FOR_AREA"))
			{
				if (pLoopPlot->area() == pArea)
				{
					return true;
				}
			}
		}
	}

	return false;
}

If so, I'd assume it was with some more nested If statements, yes?

This next question is probably just my incompetence, but how do you drag the XML values out of the XML and into the SDK? I think Python's involved somewhere, but other than that I'm clueless. I'm guessing the various checks you were putting (near an improvement? neighboring plots worked?) in can be found in CvPlot somewhere, so I'll look there for a bit.

Thanks,
 
Ok, now that I've finally got a dll to compile, here's what I'm working on:

First, a threshold. Dom Pedro was speaking of how once you have any culture on a plot, that plot belongs to the person with the most culture. How about we change it so that once you have at least X (say 10) culture on a plot, that plot belongs to whomever has at least X more culture than any other civ. That is, you need to beat the current owner by 10 culture to steal a plot, and need at least 10 culture to take control of an unowned plot. Let me quickly trace how I got to what I'm modifying.

Spoiler doTurn :
Code:
void CvCity::doTurn()
{
	PROFILE("CvCity::doTurn()");

	CvPlot* pLoopPlot;
	int iI;

	if (!isBombarded())
	{
		changeDefenseDamage(-(GC.getDefineINT("CITY_DEFENSE_DAMAGE_HEAL_RATE")));
	}

	setLastDefenseDamage(getDefenseDamage());
	setBombarded(false);
	setPlundered(false);
	setDrafted(false);
	setAirliftTargeted(false);
	setCurrAirlift(0);

	AI_doTurn();

	bool bAllowNoProduction = !doCheckProduction();

	doGrowth();

	[B]doCulture();[/B]

	doPlotCulture(false, getOwnerINLINE(), getCommerceRate(COMMERCE_CULTURE)); 

	doProduction(bAllowNoProduction);

	doDecay();

	doReligion();

	doGreatPeople();

	doMeltdown();

	updateEspionageVisibility(true);

...
}

Spoiler doCulture :
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;
	}

	[B]changeCultureTimes100(getOwnerINLINE(), getCommerceRateTimes100(COMMERCE_CULTURE), false, true);[/B]
}
Spoiler changeCultureTimes100 :
Code:
void CvCity::changeCultureTimes100(PlayerTypes eIndex, int iChange, bool bPlots, bool bUpdatePlotGroups)
{
	[B]setCultureTimes100(eIndex, (getCultureTimes100(eIndex) + iChange), bPlots, bUpdatePlotGroups);[/B]
}

Spoiler setCultureTime100 :
Code:
void CvCity::setCultureTimes100(PlayerTypes eIndex, int iNewValue, bool bPlots, bool bUpdatePlotGroups)
{
	FAssertMsg(eIndex >= 0, "eIndex expected to be >= 0");
	FAssertMsg(eIndex < MAX_PLAYERS, "eIndex expected to be < MAX_PLAYERS");

	if (getCultureTimes100(eIndex) != iNewValue)
	{
		m_aiCulture[eIndex] = iNewValue;
		FAssert(getCultureTimes100(eIndex) >= 0);

		updateCultureLevel(bUpdatePlotGroups);

		if (bPlots)
		{
			[B]doPlotCulture(true, eIndex, 0);[/B]
		}
	}
}

Spoiler doPlotCulture :
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->[B]changeCulture(ePlayer, (((eCultureLevel - iCultureRange) * iFreeCultureRate) + iCultureRate + 1), (bUpdate || !(pLoopPlot->isOwned())));[/B]
							}
						}
					}
				}
			}
		}
	}
}

Spoiler changeCulture :
Code:
void CvPlot::changeCulture(PlayerTypes eIndex, int iChange, bool bUpdate)
{
	if (0 != iChange)
	{
		[B]setCulture(eIndex, (getCulture(eIndex) + iChange), bUpdate, true);[/B]
	}
}

Spoiler setCulture :
Code:
void CvPlot::setCulture(PlayerTypes eIndex, int iNewValue, bool bUpdate, bool bUpdatePlotGroups)
{
	PROFILE_FUNC();

	CvCity* pCity;

	FAssertMsg(eIndex >= 0, "iIndex is expected to be non-negative (invalid Index)");
	FAssertMsg(eIndex < MAX_PLAYERS, "iIndex is expected to be within maximum bounds (invalid Index)");

	if (getCulture(eIndex) != iNewValue)
	{
		if(NULL == m_aiCulture)
		{
			m_aiCulture = new int[MAX_PLAYERS];
			for (int iI = 0; iI < MAX_PLAYERS; ++iI)
			{
				m_aiCulture[iI] = 0;
			}
		}

		m_aiCulture[eIndex] = iNewValue;
		FAssert(getCulture(eIndex) >= 0);

		if (bUpdate)
		{
			updateCulture(true, bUpdatePlotGroups);
		}

		pCity = getPlotCity();

		if (pCity != NULL)
		{
			pCity->AI_setAssignWorkDirty(true);
		}
	}
}

Spoiler updateCulture :
Code:
void CvPlot::updateCulture(bool bBumpUnits, bool bUpdatePlotGroups)
{
	if (!isCity())
	{
		[B]setOwner(calculateCulturalOwner(), bBumpUnits, bUpdatePlotGroups);[/B]
	}
}

Spoiler calculateCulturalOwner :
Code:
PlayerTypes CvPlot::calculateCulturalOwner() const
{
	PROFILE("CvPlot::calculateCulturalOwner()")

	CvCity* pLoopCity;
	CvCity* pBestCity;
	CvPlot* pLoopPlot;
	PlayerTypes eBestPlayer;
	bool bValid;
	int iCulture;
	int iBestCulture;
	int iPriority;
	int iBestPriority;
	int iThreshold = 10; //This is a new variable, see below
	int iI;

	if (isForceUnowned())
	{
		return NO_PLAYER;
	}

	iBestCulture = 0;
	eBestPlayer = NO_PLAYER;

	for (iI = 0; iI < MAX_PLAYERS; ++iI)
	{
		if (GET_PLAYER((PlayerTypes)iI).isAlive())
		{
			iCulture = getCulture((PlayerTypes)iI);

			if (iCulture > 0)
			{
				if (isWithinCultureRange((PlayerTypes)iI))
				{
[B]					//changed this...
					//if ((iCulture > iBestCulture) || ((iCulture == iBestCulture) && (getOwnerINLINE() == iI)))
					//to this...
					if ((iCulture > iBestCulture + iThreshold) || ((iCulture + iThreshold >= iBestCulture) && (getOwnerINLINE() == iI)))[/B]
					{
						iBestCulture = iCulture;
						eBestPlayer = ((PlayerTypes)iI);
					}
				}
			}
		}
	}

	if (!isCity())
	{
		if (eBestPlayer != NO_PLAYER)
		{
			iBestPriority = MAX_INT;
			pBestCity = NULL;

			for (iI = 0; iI < NUM_CITY_PLOTS; ++iI)
			{
				pLoopPlot = plotCity(getX_INLINE(), getY_INLINE(), iI);

				if (pLoopPlot != NULL)
				{
					pLoopCity = pLoopPlot->getPlotCity();

					if (pLoopCity != NULL)
					{
						if (pLoopCity->getTeam() == GET_PLAYER(eBestPlayer).getTeam() || GET_TEAM(GET_PLAYER(eBestPlayer).getTeam()).isVassal(pLoopCity->getTeam()))
						{
							if (getCulture(pLoopCity->getOwnerINLINE()) > 0)
							{
								if (isWithinCultureRange(pLoopCity->getOwnerINLINE()))
								{
									iPriority = GC.getCityPlotPriority()[iI];

									if (pLoopCity->getTeam() == GET_PLAYER(eBestPlayer).getTeam())
									{
										iPriority += 5; // priority ranges from 0 to 4 -> give priority to Masters of a Vassal
									}

									if ((iPriority < iBestPriority) || ((iPriority == iBestPriority) && (pLoopCity->getOwnerINLINE() == eBestPlayer)))
									{
										iBestPriority = iPriority;
										pBestCity = pLoopCity;
									}
								}
							}
						}
					}
				}
			}

			if (pBestCity != NULL)
			{
				eBestPlayer = pBestCity->getOwnerINLINE();
			}
		}
	}

	if (eBestPlayer == NO_PLAYER)
	{
		bValid = true;

		for (iI = 0; iI < NUM_CARDINALDIRECTION_TYPES; ++iI)
		{
			pLoopPlot = plotCardinalDirection(getX_INLINE(), getY_INLINE(), ((CardinalDirectionTypes)iI));

			if (pLoopPlot != NULL)
			{
				if (pLoopPlot->isOwned())
				{
					if (eBestPlayer == NO_PLAYER)
					{
						eBestPlayer = pLoopPlot->getOwnerINLINE();
					}
					else if (eBestPlayer != pLoopPlot->getOwnerINLINE())
					{
						bValid = false;
						break;
					}
				}
				else
				{
					bValid = false;
					break;
				}
			}
		}

		if (!bValid)
		{
			eBestPlayer = NO_PLAYER;
		}
	}

	return eBestPlayer;
}

Thoughts?
 
Hrm, that didn't work. Need to find the place in the code where the city adds plots to it's borders; apparently not that, or else I'm just messing up the code.
 
I'm just about there, it works, sort of. The formula I want to use is currently dependent on the iCultureRange value, but is called in CvPlot. Does anyone know how to redefine changeCulture to include an additional parameter with a default value of 1 for the other times it's called? if not, should I just define a global variable in a header file common to CvPlot and CvCity and initialize it in CvCity and call it in CvPlot? Thoughts?
 
If the function declaration has DllExport attached to it in the header file, you cannot modify its signature since the EXE won't know about your change. If not, you can add a new parameter with a default value like this:

Code:
// CvPlot.h
void changeCulture(int iValue[B][COLOR="Red"], int iNewValue = 1[/COLOR][/B]);

// CvPlot.cpp
void CvPlot::changeCulture(int iValue[COLOR="Red"][B], int iNewValue[/B][/COLOR]) { ... }
 
I'd love to understand better how Dom Pedro implemented this, and perhaps work on the code, but I cannot find the place where he uploaded it. Can anyone provide it to me?? Is DaVinci Fan still around and have you ever pursued this project any further?

By the way, one approach that I could picture is to calculate city cultural ranges not on the basis of plot distance, but on the basis of movement points. Culture would increase the amount of "culture spread movement points" available to a city, and this city would then spread its culture to any plots that can be reached via terrain that consumes less than those pseudo movement points. Would this be implementable? And would the drain on game speed from pathfinding be tolerable?

Any revival of these discussions would be more than welcome!
 
Top Bottom