General DLL programming questions

Thunderbrd

C2C War Dog
Joined
Jan 2, 2010
Messages
29,991
Location
Las Vegas
I'm starting this thread for LS612 and I, and even for you more advanced guys so we can transmit our questions to each other regarding basic dll programming issues. I figured we were getting a bit spread out and one generic place for us to discuss these matters could be helpful.

Now then, to start, I have a question regarding Building AI tags. On Promotion values and Unit tag values it seems pretty straight forward how to let the ai know how good or bad a tag's values are. But I'm looking through the CityAI here and I'm a bit lost as to how to include consideration for some new building tags.

So, I'm kindly requesting a short generic overview of the method and some instruction on where to include this information for the AI's consideration on what to build or avoid building based on the values in new tags?
 
I'm starting this thread for LS612 and I, and even for you more advanced guys so we can transmit our questions to each other regarding basic dll programming issues. I figured we were getting a bit spread out and one generic place for us to discuss these matters could be helpful.

Now then, to start, I have a question regarding Building AI tags. On Promotion values and Unit tag values it seems pretty straight forward how to let the ai know how good or bad a tag's values are. But I'm looking through the CityAI here and I'm a bit lost as to how to include consideration for some new building tags.

So, I'm kindly requesting a short generic overview of the method and some instruction on where to include this information for the AI's consideration on what to build or avoid building based on the values in new tags?

This is an extremely complex subject, so I can't give you a detailed answer now. However, there are two sister routines in CvCityAI.cpp. One is essentially the original bts evaluation method for buildings, and is called something like getBuildingValueThresholdOriginal(). It is a very long and convoluted routine, but is essentially fairly simple in that it has multiple sections, which are executed or not, based on the flags passed in (which control what aspects you want to evaluate - food production, military value, ...). Individually each of these sections Gould be fairly easy t understand (values are normalized to around the equivalent value gold per turn gain I think, and for those purposes yields are considered around four times more valuable than an extra gold as I recall). New tags should for into tha structure fairly easily.

The sister routine is a bit harder. I can't remember what it's called, but if you start from getBuildingVlaue() you'll see it branches between a cached version, which is used by the city production ai mostly for efficiency, and the original non cached version when asked questions the cache cannot answer. The reason this is dome is that the production ai will tend to ask questions many times about the same building with varying settings of the flags, and glign thron the original routine each time is very expensive. Consequently the caching system which relies on the sister evaluation routine was written about version 15 or so. What it does is evaluate the contributions for each flag all in one call, and caches those, so that any flag request result value can be cnstructed quickly from the cache. The sister evaluation routine it uses roughly parallels the structure of the original, except that whe the original has conditional evaluation of aspects based on the flags, the sister always evaluates all aspects and caches the different partial exults against flag values. Broadl this means that wherever the original has an if state,net with a cpnditional that is based on the flags, the sister has an unconditional call to accrues some sub total not the cache for those flags.

Both of these routines must result in the same end value for an given building and flag combination, so the have to be modified together.
 
Huh... ok, thanks for the answer. I could tell, by looking through that file, that things had been adjusted dramatically since I'd worked on the city build ai last. I'll try to take that understanding and see if the code makes as much sense ;)
 
From the SVN discussion:

This is the part I wasn't sure on how to handle properly in CvUnit as opposed to the methods used in CvInfos. Where to put the deconstruction?
There is a reset method in which you should clear the vector. Apart from that the vector destructor will be automatically called by the destructor of the CvUnit class.

Also, in CvUnit.h there are no declarations of vectors. Should they go here nevertheless? Or is there a reason for that not to take place as opposed to the way its handled in CvInfos?
No reason. I think there are just no vectors used in there yet.

I'm thinking ALL of my arrays would probably be better off as vectors.
In general use arrays if you know exactly how long it will be at the time of construction, use vectors otherwise.

The second method you noted there seems more appropriate for combining, say, subcombats from unitinfo and from promos on the unit and other possible sources, as each unit will need its own definition here that will rarely list off zeros. However, I'm not even sure what exactly you're referring to by 'map or hash map'. (we should probably go back to my noob coding questions thread for more discussion on this )
A map (also called dictionary) stores key/value pairs in a way that is efficient to access. If you use a vector, in the worst case you have to go through the entire vector before you find an element (or to find out if it is present at all). That grows linearly with the size of the vector, which means O(n) for access. The data structure behind a map on the other hand guarantees O(log(n)) access.
This is currently used for the lookup of the numeric ID belonging to an ID string (getInfoTypeForString and similar).
 
If you'd like me to convert ONE of the arrays in CvUnit for you as a template for the others let m know, and I'll do that.
 
AIAndy said:
This is currently used for the lookup of the numeric ID belonging to an ID string (getInfoTypeForString and similar).
Ah... yeah, the stuff I completely gloss over as I can't seem to garner even a basic understanding. Nevertheless, now that you've named the process I can look for some help files on the subject and see if I can figure out how to employ, or at least interpret for now.

@Koshling: YES please! That would help tremendously and its all I should need is one example and I can propagate that example onto the rest of the methods. Please just somehow mark each segment with a simple comment like //Koshling Convert or something so a search can take me from one step to the next. Thank you in advance!

I was thinking on this today and realized I'd really be lost in converting these where the Read/Write wrappers are concerned (more than anywhere else anyhow.)
 
@Koshling/AIAndy:

Looking at the Realistic Culture issue Shenryyr posted, I found that the culture distance was calculated in CvCity::calculateCultureDistance. It has four routines, one for each direction, that are like this.

PHP:
	int iPlotIndex = HASH_RELATIVE_CLOSE_DIST(iDX-1,iDY);
	int iEastDist = m_aCultureDistances[iPlotIndex];
	if(iEastDist != 0 && iEastDist != MAX_INT)
	{
		iEastDist += pPlot->isRiverCrossing(DIRECTION_EAST);
		distance = std::min(distance, iEastDist+1);
	}

Would replacing that with

PHP:
	int iPlotIndex = HASH_RELATIVE_CLOSE_DIST(iDX-1,iDY);
	int iEastDist = m_aCultureDistances[iPlotIndex];
	if(iEastDist != 0 && iEastDist != MAX_INT && pPlot->isAdjacentOwned())
	{
		iEastDist += pPlot->isRiverCrossing(DIRECTION_EAST);
		distance = std::min(distance, iEastDist+1);
	}

cause it to only calculate the distance to plots adjacent to plots that already have your culture on it? Or am I misunderstanding how it works?
 
@Koshling/AIAndy:

Looking at the Realistic Culture issue Shenryyr posted, I found that the culture distance was calculated in CvCity::calculateCultureDistance. It has four routines, one for each direction, that are like this.

PHP:
	int iPlotIndex = HASH_RELATIVE_CLOSE_DIST(iDX-1,iDY);
	int iEastDist = m_aCultureDistances[iPlotIndex];
	if(iEastDist != 0 && iEastDist != MAX_INT)
	{
		iEastDist += pPlot->isRiverCrossing(DIRECTION_EAST);
		distance = std::min(distance, iEastDist+1);
	}

Would replacing that with

PHP:
	int iPlotIndex = HASH_RELATIVE_CLOSE_DIST(iDX-1,iDY);
	int iEastDist = m_aCultureDistances[iPlotIndex];
	if(iEastDist != 0 && iEastDist != MAX_INT && pPlot->isAdjacentOwned())
	{
		iEastDist += pPlot->isRiverCrossing(DIRECTION_EAST);
		distance = std::min(distance, iEastDist+1);
	}

cause it to only calculate the distance to plots adjacent to plots that already have your culture on it? Or am I misunderstanding how it works?

It would, but the net effect would be to not calculate culture distances correctly beyond your borders, which would completely prevent the entire algorithms from working to determine which non-owned plots your culture should spread to next.
 
It would, but the net effect would be to not calculate culture distances correctly beyond your borders, which would completely prevent the entire algorithms from working to determine which non-owned plots your culture should spread to next.

I thought it would only exclude tiles more than one square from your territory. Because, it would still calculate the culture distance to tiles adjacent to your borders (If I'm understanding it correctly) but it wouldn't check tiles that are two or more squares away from any of your borders. How would that mess up the culture spread calculations?
 
I thought it would only exclude tiles more than one square from your territory. Because, it would still calculate the culture distance to tiles adjacent to your borders (If I'm understanding it correctly) but it wouldn't check tiles that are two or more squares away from any of your borders. How would that mess up the culture spread calculations?

Oh I see. You might be right, but how do you explain a tile more than 1 away from your border getting a closer distance than the one between it and the border under the CURRENT system anyway. Trying to fix without understanding the cause of the problem is usually not a good idea - do you have a test case that shows the bad spread actually happening (as opposed to having happened sometime in the past) yet?
 
Oh I see. You might be right, but how do you explain a tile more than 1 away from your border getting a closer distance than the one between it and the border under the CURRENT system anyway. Trying to fix without understanding the cause of the problem is usually not a good idea - do you have a test case that shows the bad spread actually happening (as opposed to having happened sometime in the past) yet?

Not unless Shen finds and posts a save where it is right about to happen. As you said, almost any save will just show the isolated culture, and not how it got there.
 
Not unless Shen finds and posts a save where it is right about to happen. As you said, almost any save will just show the isolated culture, and not how it got there.

Right, so we don't even know that realistic culture (as opposed to some fixed borders effect) is responsible, so unless you can see a specific hole in the realistic culture spread logic I advise against changing it blind.
 
@Koshling/AIAndy:

I was wondering what functions in the DLL would show determining the amount of building yields/commerce/happiness/healthiness in a city. I need that as a base for a new function I want to add to determine commerce from orbital buildings, which will work sort of like in SMAC. Thanks.:goodjob:
 
@Koshling/AIAndy:

I was wondering what functions in the DLL would show determining the amount of building yields/commerce/happiness/healthiness in a city. I need that as a base for a new function I want to add to determine commerce from orbital buildings, which will work sort of like in SMAC. Thanks.:goodjob:

Strictly building? If so (apart from yields which I'll come onto later):

BASE values (i.e. - direct +/- s):
getBuildingGoodHappiness() [smiley people from buildings]
getBuildingBadHappiness() [unhappy people from buildings]
getBuildingGoodHealth()
getBuildingBadHealth()
getBuildingCommerce()

However, those don't take account of modifiers on the buildings (e.g. - health per pop modifier on things like sewers). For those you need

totalGoodBuildingHealth() [there is no 'bad' equivalent since there isn't that kind of modifier]
calculateBuildingCommerceModifier() - gives the modifier due to buildings that you would need to apply to a base commerce to get the actual. Note that such a modifier applies to ALL commerce, whether the base is generated from buildings or not, so it's not possible to entirely separate buildings from everything else, and the appropriate formulation depends on exactly what you want to achieve.

Yields are much harder, and there are no functions that split out the contribution from buildings. There is just a single total base yield and total yield modifier that gets changes as buildings (and civics and other things) come and go. Again, if you were to try to write such functions, you'd have problems because modifiers from non-building sources apply to building-based yields, and visa versa so a purely building-based total would be hard to arrive at. The best you could do would be calculate the total base yield and the total modifier from buildings and using that calculate what the total city yield would be with and without the buildings, attributing the difference to be the due-to-buildings component - however that quantity would change without any changes to the buildings (e.g. - on a civic change or a population increase, or a change to the set of tiles worked) due to the way the modifiers interact.
 
@Koshling:

OK, I'm still kinda confused, so I'll ask in another manner. If I wanted to do something like this;

pesudcode for all building commerces:

Code:
if (isOrbital)
	numorbital = getNumOrbitalBuilding (this building)

	if (cityHasOrbitalInfra)
		CommercefromOrbit = std::min(buildingcommerce*numorbital, getCityPop())

	if (!cityHasOrbitalInfra)
		CommercefromOrbit = (std::min(buildingcommerce*numorbital, getCityPop())) / 2

for building commerces if a tag on the building was set to true, what would be the best way to go about it?
 
@Koshling:

OK, I'm still kinda confused, so I'll ask in another manner. If I wanted to do something like this;

pesudcode for all building commerces:

Code:
if (isOrbital)
	numorbital = getNumOrbitalBuilding (this building)

	if (cityHasOrbitalInfra)
		CommercefromOrbit = std::min(buildingcommerce*numorbital, getCityPop())

	if (!cityHasOrbitalInfra)
		CommercefromOrbit = (std::min(buildingcommerce*numorbital, getCityPop())) / 2

for building commerces if a tag on the building was set to true, what would be the best way to go about it?

You need to clarify the pseudo-code before I could answer really:

1) What does getNumOrbitalBuilding (this building) do? Is it a method on the city or the player? (i.e. - is it 0 or 1 depending on if this city has the building in question, or is it the total number the player has?)

2) What is buildingcommerce? Is it the total commerce of the city from all buildings, or the base commerce for this building or what? What about commerce modifiers?
 
You need to clarify the pseudo-code before I could answer really:

1) What does getNumOrbitalBuilding (this building) do? Is it a method on the city or the player? (i.e. - is it 0 or 1 depending on if this city has the building in question, or is it the total number the player has?)

2) What is buildingcommerce? Is it the total commerce of the city from all buildings, or the base commerce for this building or what? What about commerce modifiers?

1. That method is on the player, and returns the number of the specific building already built in your civ (or team, whichever is easier).

2. Building commerce the info that comes from

Code:
			<CommerceChanges>
				<iCommerce>0</iCommerce>
				<iCommerce>0</iCommerce>
				<iCommerce>0</iCommerce>
				<iCommerce>4</iCommerce>
			</CommerceChanges>

in the XML. So, for instance if you build an Orbital Culture Center (which let's say has +2 culture in the XML) it would have a tag signifying it as orbital, and therefore would provide culture equal to the number of culture centers you have times the number in the XML, or the population of the city, whichever is lower. If you don't have a building in the city with the orbital infrastrucure tag, then that number is halved.
 
1. That method is on the player, and returns the number of the specific building already built in your civ (or team, whichever is easier).

2. Building commerce the info that comes from

Code:
			<CommerceChanges>
				<iCommerce>0</iCommerce>
				<iCommerce>0</iCommerce>
				<iCommerce>0</iCommerce>
				<iCommerce>4</iCommerce>
			</CommerceChanges>

in the XML. So, for instance if you build an Orbital Culture Center (which let's say has +2 culture in the XML) it would have a tag signifying it as orbital, and therefore would provide culture equal to the number of culture centers you have times the number in the XML, or the population of the city, whichever is lower. If you don't have a building in the city with the orbital infrastrucure tag, then that number is halved.


How would modifiers on a building tagged as orbital be handled?
 
How would modifiers on a building tagged as orbital be handled?

If a building is taged as orbital, the commerce modifiers would look like

(XML commerce*number of that building in your empire) (and on the same map, that will be important) OR pop of city, whichever is lower.

For anything else on the building it will not be affected by the orbital tag.
 
If a building is taged as orbital, the commerce modifiers would look like

(XML commerce*number of that building in your empire) (and on the same map, that will be important) OR pop of city, whichever is lower.

For anything else on the building it will not be affected by the orbital tag.

So if a building that gives +10% research is tagged as orbital, and you build 10 of them you get +100% research in all cities? Seems like it could get out of hand fast if it applies to modifiers, no??
 
Back
Top Bottom