Liberating, trading and gifting cities

gorilla

Chieftain
Joined
Dec 8, 2010
Messages
16
I'm trying to address some annoying ai issues, namely:

i) A computer based somewhere in the middle of the map sends an army to attack a barbarian city on the Arctic/Antarctic coastline. This city will never grow above size 2or contribute anything meaningful, so it just wastes resources and lots of maintenance to keep it. Occasionally there is a strategic resource (in which case I'll let it off), but often there isn't. Visa versa, you get computers near the poles trying to capture a desert city near the equator.

ii) Computer 1 attacks computer 2, takes a city and makes him a vassal, this city is miles away from computer 1's main area and gets dwarfed in culture, it then revolts back to the original owners and starts the war up again. I've seen this repeating in endless cycles, which means a would be powerful computer wastes the entire game just fighting one other civilization, over and over again.

So my questions are:

1) How can I tell the computer to trade away, free as a colony or even gift cities that are a long way from it's main borders and of no strategic value?
2) Once a computer has vassalled another empire, get it to liberate any useless captured cities back to the vassal (rather than having them repeatedly revolt)?

At the moment I've being trying to put some code into 'void setVassal' within CvTeam, but I'm still on a rather steep learning curve and am probably commiting some obvious errors! To start with I'm just trying to do a simple distance check. I know the code is incomplete to do the above, but I'm just trying to get the simplest bit into the DLL to start with (note obviously brackets out of position in the spoiler!):

Spoiler :
CvCity* pLoopCity;
int iI;
int iLoop;
for (iI = 0; iI < MAX_PLAYERS; iI++)
{
if (!isHuman())
{
if (GET_PLAYER((PlayerTypes)iI).isAlive())
{
for (pLoopCity = GET_PLAYER((PlayerTypes)iI).firstCity(&iLoop); pLoopCity != NULL; pLoopCity = GET_PLAYER((PlayerTypes)iI).nextCity(&iLoop))
{
if (plotDistance(pLoopCity->getX_INLINE(), pLoopCity->getY_INLINE(), pCapital->getX_INLINE(), pCapital->getY_INLINE()) / GC.getMapINLINE().maxPlotDistance() * 100 > 30)
{
pCity.liberate(false);
}
}
}
}
}


When compiling the DLL it's telling me things like 'plotDistance' : is not a member of 'CvCity' and 'getY_INLINE': identifier not found, even with argument-dependent lookup.

In any case, is this the best way for me to achieve what I've listed above or is there a better way? I still can't see in the code where the ai evaluates whether or not it should liberate a city or gift it (does it do this at all in the default code?).

All guidance welcome!
 
Spoiler :
CvCity* pLoopCity;
int iI;
int iLoop;
for (iI = 0; iI < MAX_PLAYERS; iI++)
{
if (!isHuman())
{
if (GET_PLAYER((PlayerTypes)iI).isAlive())
{
for (pLoopCity = GET_PLAYER((PlayerTypes)iI).firstCity(&iLoop); pLoopCity != NULL; pLoopCity = GET_PLAYER((PlayerTypes)iI).nextCity(&iLoop))
{
if (plotDistance(pLoopCity->getX_INLINE(), pLoopCity->getY_INLINE(), pCapital->getX_INLINE(), pCapital->getY_INLINE()) / GC.getMapINLINE().maxPlotDistance() * 100 > 30)
{
pCity.liberate(false);
}
}
}
}
}

Is pCapital declared and initialized somewhere in your code? It should also be a CvCity* like pLoopCity. I think there is a getCapitol function somewhere (possibly CvPlayer?) you could call to initialize it.

Also I think you need to call isHuman() on the player, and you used pCity in one place where it should have been pLoopCity. Another possible problem is that you are looping through all players and not just the ones on the team. You should add in a check to make sure the player is on the team.

Below is how I would touch up your code. I'm writing this off the top of my head so I don't know the names of some of the functions. Particularly, the function to check if a player is on the team. The bold parts are my changes. The red part is a place where I don't know what you're trying to do and looks questionable to me.

Spoiler :

CvCity* pLoopCity;
CvCity* pCapitol;
int iI;
int iLoop;
for (iI = 0; iI < MAX_PLAYERS; iI++)
{
if (!GET_PLAYER((PlayerTypes)iI).isHuman())
{
if (GET_PLAYER((PlayerTypes)iI).isAlive() && isOnTeam((PlayerTypes)iI))
{
pCapitol = GET_PLAYER((PlayerTypes)iI).getCapitol();
for (pLoopCity = GET_PLAYER((PlayerTypes)iI).firstCity(&iLoop); pLoopCity != NULL; pLoopCity = GET_PLAYER((PlayerTypes)iI).nextCity(&iLoop))
{
if (plotDistance(pLoopCity->getX_INLINE(), pLoopCity->getY_INLINE(), pCapital->getX_INLINE(), pCapital->getY_INLINE()) / GC.getMapINLINE().maxPlotDistance() * 100 > 30)
{
pLoopCity.liberate(false);
}
}
}
}
}


Also since GET_PLAYER is being used so often you might want to create a CvPlayer* variable so you don't have to use it so many times.
 
Well, I don't know anything about modding or worst, coding.


But I am pretty sure the game manual itself explicity talk about the possibility to destroy cities you find useless.


If the AIs get stuck capturing the same useless cities in the artic or antarctic poles miles far away from its borders without any usefulness... it is the case it should have razed it instead.

It is better to make the AI aware of the disavantage of such a politic (endless waste of resources and time) so long computer players will more properly choose what to do with a city each time they conquer one.

So instead to mess with vassals, trade, gift etc which would be only palliative in this case, I would correct that.


IMO eh...
 
That's reduced the errors a bit. I've tried numerous combinations of calling pCapital, getCapitalCity, CapitalCity, also tried with brackets. And tried with CvCity*, CvPlayer* and CvPlot*. No luck so far. Inserting CvCity* pCapital = getCapitalCity(); gives me the least errors.

The DLL gives four errors in total, although the first three of these seem to be basically the same thing:

Spoiler :
error C3861: 'getCapitalCity': identifier not found, even with argument-dependent lookup
error C2065: 'pCapitol' : undeclared identifier
error C2039: 'getCapitol' : is not a member of 'CvPlayerAI'
error C2228: left of '.liberate' must have class/struct/union type
1> type is 'CvCity *'
1> did you intend to use '->' instead?

In terms of which players it is looping through, I had thought that because this code is running within the SetVassal function, that the only players that would be running through it are the new vassal and it's master? Presumably it wouldn't be called in any other situation so wouldn't affect any other players?

And as for the bit at the end, I just put in a check that would say how far away is this compared to the maximum distance on the map. If it's more than 30% then liberate it. It is only there as a stop gap whilst try and get the basic code working, then I can insert something a bit more appropriate. From your comment your comment I take it I've done this wrong as well! :) Luckily this hasn't caused an error though.

So instead to mess with vassals, trade, gift etc which would be only palliative in this case, I would correct that.

I have a fundamental problem with City Razing in this game. The problem is that the game destroys the city completely as if it never existed. This is totally unlike real life! Imagine if London was occupied and the occupying force decided to raze it. They could knock a load of buildings down, start lots of fires and probably use high explosives. However it is just so vast that they could never remove all the buildings. And as for the people, are they really going to be able to kill 6m inhabitants? Of course not. And yet in Civ, if you captured a city the size of London and raze it, it just completely disappears! As if by magic! Instantly!

It's utter nonsence, therefore I play with it off. That asside, your point is valid!
 
Exactly.

It is perfectly realistic. ;)

And in same cases in the game, it is a better solution than occuping and/or recapturing the same useless cities again and again...
 
A couple of things first off.

1) Wrap code inside of
Code:
 tags. This makes it easier to read (you can even do this inside of spoiler tags)

2) If you're trying to alter the AI, you should be messing with the xxAI.cpp files. Rather than trying to shoehorn some AI code into the setVassal() function, you should instead be looking at the code where the AI makes these decisions (I dont know the exact spot offhand - and actually, after a quick search in the DLL, I dont see anywhere that the AI considers liberating cities, so you would be doing a lot of work from scratch. I would probably look at adding the code to AI_doTurnPost(). This is where the AI wraps things up at the end of its turn)

As for your code, try this:

[code]
	CvCity* pLoopCity;
	CvCity* pCapital;
	int iLoop;
	PlayerTypes ePlayer = NO_PLAYER;
	ePlayer = getLeaderID();

	pCapital = GET_PLAYER(ePlayer).getCapitalCity();

	for (pLoopCity = GET_PLAYER(ePlayer).firstCity(&iLoop); pLoopCity != NULL; pLoopCity = GET_PLAYER(ePlayer).nextCity(&iLoop))
	{
		if (plotDistance(pLoopCity->getX_INLINE(), pLoopCity->getY_INLINE(), pCapital->getX_INLINE(), pCapital->getY_INLINE()) / GC.getMapINLINE().maxPlotDistance() * 100 > 30)
		{
			pLoopCity->liberate(false);
		}
	}

Since you're in the team code, you need to get a player ID first. I didnt quite understand why you were looping through all the players, so I took that code out.

Also, be careful about spelling. Red's code was using both capitol and capital, which was causing some of your errors.
 
In modern times- you can still completely wipe the city out - its called nuking...
And yet the only two cities to ever have been "nuked", Hiroshima and Nagasaki, Still stand today. Approximately 69% of the city's buildings were completely destroyed, and about 7% severely damaged in Hiroshima and about the same in Nagasaki.
in medieval times it was totally possible to Completely burn down the city - such events has happened, even by accidental fires, when some cities were burn to the ground with NOTHING left
Which?!
I can think of plenty that were sacked or burnt by fire but none that were completely erased by such a process.

:think:
Better surely to have the ability to abandon the city, it could then become a barbarian city say, or an open city with no owner. let us know what you come up with regarding a working code, can't help much on that score yet but the project is very interesting and I would like to incorporate something like it, into my Mod.
 
It is possible to completely raze a city in real life and it's been done before. But generally, cities are rebuilt on top of the rubble.

Also, it's important to realize that in Civ, the city is not just the city center, it also encompasses the the nearby towns and improvements. Though from a gameplay standpoint, those improvements are generally a lot less useful without a city working them, they are part of the city infrastructure.
 
Tholal. Thanks for your thoughts on the code. I had initially looked in the ai files but I couldn't find where the computer considers liberating or trading cities either. That's what drove me to the forums for help!

You're adjustments meant that the code went through the DLL first time. I ran a test game and it didn't cause any crashes either. I set up a situation using world builder so that one large nation was attacking a smaller one on the other side of the world with a large force. It vassalled it a few turns later, but unfortunately no liberation!! :undecide: So the ai was still holding onto cities which several turns later revolted and started the war off again.

Does this just mean that I now need to adjust my criteria for when it should liberate? Am I using maxPlotDistance correctly or have I totally misunderstood what this means? Would I be better off putting in a check against average distance to capital?


In terms of razing cities, I probably should have been a bit more specific in my post, as I think it is actually a question of scale. I think it is possible to raze small villages and towns. But to raze a fully fledged large city (like London or New York), I don't think this is possible and can't think of any examples of this in history - surely only multiple nukes and high residual radiation could accomplish such a thing?

However, 4lexanders post has been very useful, as it's given me an idea to solve part 1 of my initial post. Namely that I could slightly amend the raze code, so that only cities below size 3 (say) could be razed. That way, a very small city (eg a village or town in real life), could be razed, but a large city or metropolis couldn't. It should be fairly easy to tell the ai to just raze those useless barbarian arctic cities, but for this not to have a big effect on the rest of the game. As an alternative to population, it could check for number of buildings. Beyond a point, razing would just be impossible.
 
In History we have hundreds of examples of cities being completely destroyed.

Some times they were rebuilt upon, sometimes it never happened (try looking for Carthago...).

Those are historical facts, not opinions.


So the ingame option to raze cities it IS perfectly realistic and that' s yet another point on CIV IV depth.


If you don' t want either you or AI make ever use of this feature it's up to you to find an alternate solution to such problems enlisted,

I also state maybe a Never Raze Cities mod could also be fun to play as a variation,

but the core of the problem is the fact in certain conditions is better to completely wipe out the city, at least rebuilding it on the same location or nearly, both for convenience and to avoid such issues.


IMO
 
as example: Moscow was burned down 5 times. 2 times intentionally, 3 times due to accidental fires.
3 times entire city was burned down, 2 times only Kremlin survived cause it was already build out of stone.
However in every case the majority of population have survived/fled during the fires, so they could return and rebuild..

Some other cities were burned out COMPLETELY and their population slaughtered - at least 20 cities were completely burned out during early mongol invasion with entire population killed -- some of them were rebuild and repopulated (like Smolenks), others disappeared forever.

A signification notion here, why completely destroyed cities were rebuild, is that they stood on a beneficial location, which was either: had fertile lands around it, good forest game, set on a path of major trade route, or was of a strategic military value. So city positioning was not invented in Civ out of the blue ;) Hence, due to that beneficial location, people returned and re-build those cities again.
 
Code:
		if (plotDistance(pLoopCity->getX_INLINE(), pLoopCity->getY_INLINE(), pCapital->getX_INLINE(), pCapital->getY_INLINE()) / GC.getMapINLINE().maxPlotDistance() * 100 > 30)
		{
			pLoopCity->liberate(false);
		}

You're adjustments meant that the code went through the DLL first time. I ran a test game and it didn't cause any crashes either. I set up a situation using world builder so that one large nation was attacking a smaller one on the other side of the world with a large force. It vassalled it a few turns later, but unfortunately no liberation!!

I expect the reason the liberation never happens is that the condition is not true.

It is doing integer math. The plot distance is never greater than the max plot distance. Therefore plotDistance/maxPlotDistance is (almost) always 0 (it is 0 unless plotDistance is exactly maxPlotDistance, which would be a pretty rare situation). 0 times 100 is 0. 0 is never greater than 30. Condition is (almost) never true. City is never liberated unless you have arranged for it to be at a location on the map that is exactly at the maximum possible distance away from your capitol.

Try (plotDistance*100)/maxPlotDistance > 30.

(Not going into whether or not this is a good plan... I think you need a more complex condition than that, at least.)

By the way, unless you have selected the "cities can flip back to their old owner" game option, they can't flip back to their old owner. Revolt over and over? Sure. Change ownership back? Not so much. So you could just stop turning that option on (it is off by default) and cities won't flip back like you say they are.
 
The city revolting is caused by the RevDCM mod. As the cities are so far away they are being hit with huge negative penalties from distance to capital and nationality, and are then revolting several turns later. This then causes the newly vassaled civilization to go straight back to war again. I have no problem with the revolutions (this seems realistic to me). But what would make sense would be for the distant power to liberate the cities back, and then just effectively control the government.

That's very interesting about the integer logic. I tried changing the test as suggested, but unfortunately it didn't work. I then made the test easier, which also didn't do it. I then made the test impossible to fail, putting 100* distancetoCapital > 0. Still no good.

I then got irritated and removed the check completely! So the code was going directly from loopcity and into liberate. But still nothing! I'm beginning to think that the computer really doesn't like liberating its cities! :rolleyes: So the code below liberates nothing.....

Spoiler :
Code:
		CvCity* pLoopCity;
		CvCity* pCapital;
		int iLoop;
		PlayerTypes ePlayer = NO_PLAYER;
		ePlayer = getLeaderID();

		pCapital = GET_PLAYER(ePlayer).getCapitalCity();

		for (pLoopCity = GET_PLAYER(ePlayer).firstCity(&iLoop); pLoopCity != NULL; pLoopCity = GET_PLAYER(ePlayer).nextCity(&iLoop))
		{
				pLoopCity->liberate(false);
		}
It feels like we're very close to getting this working (although as you said, later on I will need to improve the checks). This has been a bigger job than I thought - all the help is greatly appreciated!



@ 4lexander: I give up - you win!! :)
 
Are you sure the liberate call is happening after the other civ is actually completely a vassal? (I.E. all data that says "player X is a vassal of player Y" is set.) If not, the liberate function won't work.
 
Are you sure the liberate call is happening after the other civ is actually completely a vassal? (I.E. all data that says "player X is a vassal of player Y" is set.) If not, the liberate function won't work.

I've got it as the very last bit of code at the end of 'SetVassal'. Just tried it moving up a set of brackets (although still the last code) but still doesn't seem to like it. :(
 
Are you sure the liberate call is happening after the other civ is actually completely a vassal? (I.E. all data that says "player X is a vassal of player Y" is set.) If not, the liberate function won't work.

I've fixed it!! :D

God-Emperor was spot on with his guess. The solution is actually quite simple - I've moved the code from CvTeam and into CvTeamAI. It's now inserted as part of AI_doTurnPost() function and it works a perfectly!

In fact this is even better than hoped, as now the code perfroms the check every turn and picks up all AI's - not just those who have a newly conquered vassal. This givs me much more options in terms of performing checks to ensure the ai makes good strategic decisions.

Magic! Thanks to Red_Key, Tholal and God-Emperror for all your help with this one. :goodjob:
 
Back
Top Bottom