Making code aware of UBs

deanej

Deity
Joined
Apr 8, 2006
Messages
4,859
Location
New York State
In the Final Frontier mod, one of the additions with solar systems is a requirement that buildings that require another building need to be on the same planet as the building they require. I feel that this is more realistic than having the building anywhere in the system. It is done via the following code in CvGameUtils in the cannotConstruct method:
Code:
		if (eBuilding > -1):
			pBuildingInfo = gc.getBuildingInfo(eBuilding)
			for iNeededBuildingLoop in range(gc.getNumBuildingInfos()):
				if (pBuildingInfo.isBuildingClassNeededInCity(iNeededBuildingLoop)):
					if (not pPlanet.isHasBuilding(iNeededBuildingLoop)):
						return True

Just one problem: this code is not aware of unique buildings and does not compensate for them. This is not a problem in Final Frontier, which does not use unique buildings. However, it is a problem in Star Trek. If a civ has a unique building whose buildingclass is a requirement for another building, that other building can't be built, ever. Is there a way I could fix this?
 
You check what civilization type the city is, then check that civilization for what building type they use for this building class, then proceed with a check for that building. OR you change to checking for buildingclass in the city instead of building. You'll have to check the API to see if that last one is exposed to python, but it ought to be. The first will work for certain.
 
Unfortunatley that last pPlanet.isHasBuilding isn't even in the SDK, it's another method in CvSolarSystem.py and all it does is query an array with the id number of the building, so I don't see how to make a new one for buildingclass.

Reading your post, I thought of the idea of overriding this behavior for particular civs when a particular building is involved. Hate to have to hardcode that stuff in though (as whenever I make a scenario it's a separate mod and generally has to have stuf like this changed/removed. Might be easier just to remove the requirement (for most of the buildings they'll probably wind up that way anyways, I'm guessing), which I didn't even know about until the bug was discovered so it's not a huge loss.
 
Unfortunately the documentation that most people use for the python interface is not quite up to date. You can use pCity.getNumRealBuilding(iBuildingType), and you can use gc.getBuildingInfo(iBuildingType).getBuildingClassType() to get from a building to a class.

Since the code inside cannotConstruct must be as fast as possible, you would want to precompute as much as possible. I suggest to create a list of building types at the start of the game, which is a list of all building types of the required building class. Then in cannotConstruct, you can loop this list and call getNumRealBuilding on each; if any of them return nonzero then there is a building of the proper class.
 
Ask all the questions you need to in the Idiot's Guide when you do get to moving things to new XML tags instead of python hardcoding. I am trying to force myself to devote a little time to it each week at the least so that I can get the thing done at some point and have examples for each tag type available.

For the time while you do remain in python, you can also possibly speed up your check by doing a check against the player first to verify that the player has any of that buildingclass at all (getBuildingClassCount), THEN check what type of building is appropriate to the player and if the city has one.
 
@davidlannen: I'm not sure how that would help me determine if that's on a particular planet, though (a city is the whole system). pPlanet calls a python object. The isHasBuilding method looks like this:
Code:
	def isHasBuilding(self, iID):
		return self.abHasBuilding[iID]

I'm thinking I'll just remove that section of code, to make it act the BtS does (if the building required is in the city, the building that requires it can be built; the code adds the requirement that they be located on the same planet).

@xienwolf: Most of what I would do would involve booleans (which you've already written about); some of it is actually part of the same cannotContruct method (the limitation that some buildings can only be built once in a system).
 
@davidlannen: I'm not sure how that would help me determine if that's on a particular planet, though (a city is the whole system).

What's that? A city represents a whole system, and a planet is just part of that? I have never played the mod, but how does the game keep track of subdivisions of cities? That seems very complicated.
 
Why not just look up the UB building type given the building class using the civilization info? That seems like it should be a snap:

Code:
if (eBuilding > -1):
	pBuildingInfo = gc.getBuildingInfo(eBuilding)
	[B]pCivInfo = gc.getCivilizationInfo(pPlayer.getCivilizationType())[/B]
	for iNeededBuildingLoop in range(gc.getNumBuilding[B]Class[/B]Infos()):
		if (pBuildingInfo.isBuildingClassNeededInCity(iNeededBuildingLoop)):
			[B]iNeededBuildingType = pCivInfo.getCivilizationBuildings(iNeededBuildingLoop)[/B]
			if (not pPlanet.isHasBuilding([B]iNeededBuildingType[/B])):
				return True

This code assumes you have pPlayer or that you know how to get it. davidlallen's suggestion about prebuilding a data structure of which buildings are needed so you can check just what's necessary is a good one and can be used with this method.

Code:
# do this once at startup

NEEDED_BUILDING_CLASSES_BY_TYPE = {}
for iType in range(gc.getNumBuildingClassInfos()):
	classes = []
	NEEDED_BUILDING_CLASSES_BY_TYPE[iType] = classes
	pType = gc.getBuildingInfo(iType)
	for iClass in range(gc.getNumBuildingClassInfos()):
		if (pType.isBuildingClassNeededInCity(iClass)):
			classes.append(iClass)

...

# do this in cannotConstruct()

if (eBuilding > -1):
	[B]pCivInfo = gc.getCivilizationInfo(pPlayer.getCivilizationType())[/B]
	for iClass in NEEDED_BUILDING_CLASSES_BY_TYPE[eBuilding]:
		[B]iType = pCivInfo.getCivilizationBuildings(iClass)[/B]
		if (not pPlanet.isHasBuilding([B]iType[/B])):
			return True
 
I'll have to look at Final Frontier someday again since I know far more about how Civ works now than I had when I played it the whole 2 times which I have played so far. I am guessing that the city is the sun, and that th eplanets are improvements? Then it assigns buildings to certain planets probably, though how it does so I am not quite certain of. I would guess it takes advantage of the fact you can have multiples of a building in a single city and just uses the improvement being in the workable radius as a prereq for the building, then auto-assigns them to the closes appropriate planet until all valid planets are full. At least that's how I would approach it if stuck with python only for accomplishing anything. In the DLL you can invent a few new concepts and control it much better and more directly, even allow someone to assign a different building to be constructed on each planet at once. Though making each planet have a city screen to itself would be tricky, unless you just went ahead and made them BE cities that is. Could invent a super-city framework for the star to dictate actions to the whole system and then a standard city system for each planet to control individual production and characteristics...

Anyway... I like the "figuring out how to do it" part of modding the most, so I'd quickly get too carried away with these kind of thoughts :) Copying an Array is halfway written now in the guide, should finish it tonight I hope, and possibly get most of the second Array done tomorrow if I am lucky. Arrays will serve you quite well if you get as elaborate as I would when you move to the SDK.
 
@EmperorFool: That code did the trick. Thanks!

@davidlallen, xienwolf: It is complicated. Here's what I know of how it all works:
Each solar system is located on a single plot. The star/planets are stored via script data on the plot. When a city is on the system, it functions as if the planets are the plots; population/buildings can be assigned to a planet (exactly how this is done I'm not sure, but I do know that the buildings on each planet are stored in an array for each planet object). The yield of each planet with population on it is granted to the city the same way a worked tile's yield is given to the city. The city screen is largely the same, but where specialist management is done in civ, there is the interface to view what buildings are on a planet, set which planet receives newly constructed buildings, and move around the population. Since this is all done via python, it does slow the game down. It also breaks stuff easily (basically anything Jon Shafer didn't put into Final Frontier). Corporation and specialist yields (food, production, commerce) are not taken into account when the city does anything, making this broken. It's for this reason that I only briefly put corporations in Star Trek (removing them with the next version) and changed a bunch of specialists when I found out about it (for example, the engineer grants 1xp instead of production). Also, building capture and unique buildings were a major headache. Because of the arrays, the SDK's management of building destruction on city capture breaks the entire mod if used. To get around this, Final Frontier has each building set to survive capture in the XML and does all that stuff in python (another area where I'll try to remove hardcoding). The code was not written with unique buildings in mind, requiring that extra buildingclass entries with no default building be added (as the number of buildingclasses and buildings must be exactly the same or the code breaks and no buildings can be constructed).

Needless to say, I try to avoid CvSolarSystem.py as much as possible.
 
Hey.

As I've been doing a little modding of FF I thought I'd add to this.

Corporation and specialist yields (food, production, commerce) are not taken into account when the city does anything, making this broken. It's for this reason that I only briefly put corporations in Star Trek (removing them with the next version) and changed a bunch of specialists when I found out about it (for example, the engineer grants 1xp instead of production).

The only reason that these things don't work is that the only yield a FF city (AKA system) gets is from the one plot the city is on. The 3 yields of this plot are calculated entirely in python and then updated back to the regular game data. To get other things taken into account, such a great people, you'd just need to add them to the calculation.

The calculation is done in CvFinalFrontierEvents.py in updatePlotYield. So to get an engineer to add 2 production, you'd add a section in there that gets the count of the engineers in the city, multiplies it by 2, and adds the result to aiSystemYield[1]. That's pretty much all it would take for that specific thing.

Also, building capture and unique buildings were a major headache. Because of the arrays, the SDK's management of building destruction on city capture breaks the entire mod if used. To get around this, Final Frontier has each building set to survive capture in the XML and does all that stuff in python (another area where I'll try to remove hardcoding). The code was not written with unique buildings in mind, requiring that extra buildingclass entries with no default building be added (as the number of buildingclasses and buildings must be exactly the same or the code breaks and no buildings can be constructed).

Needless to say, I try to avoid CvSolarSystem.py as much as possible.

I have not had a problem having more BuildingInfos than BuildingClassInfos (I have 2 more and it has broken nothing). The hard-coded count for number of building types at the end of CvSolarSystem.py is the count of the buildings not the building classes and any modifications done via those arrays (to the per-population food, production, or commerce or the population limit) is per building, not building class. As a perhaps important caveat you should note that my 2 extra buildings are not implemented as actual UB's - they are related to a trait I added (I doubled up the leaders for the civs and added second minor traits that are basically half strength versions of the normal Civ traits, more or less; the Financial trait causes the Capitol to produce 4 extra commerce and the "Office of the Ombudsman", my Forbidden Palace replacement, to produce 4 commerce as well - these could have been done by altering the plot yield calculation but I decided to try them as alternate versions of the buildings so that the data on the city screen was correct - any time one of them is built I swap in the alternate version if the civ has the financial trait, likewise in the post map generation fiddling near where the other trait modifications are done for that).

One thing that does work fine is the yields produced by buildings - it is extracted from the standard building data and applied just fine.

In essence, FF knows nothign about building classes, everythign it does is based purely on the specific building. Likewise, the python code never seems to use unit classes for anything either, only specific unit types (which complicates UU related things, you keep having to add new units to lists of units).

There are a variety of misconceptions about the FF mod. One big one is the belief that the AI is done in python. It isn't. The AI is the standard one but various of the python routines are enabled allowing some things to be overridden, such as requiring a prereq building to be on the same planet. One main thing is that the city production is overridden fairly often (there is a flat 85% chance that the FF python override will be attempted, then it checks various things which may, or may not, result in an override choice being entered into the production queue as the next thing to be built). The net result is that the bulk of the AI is still the normal AI.

This is why the AI has more problems than usual. It simply has no knowledge of anything that is done via python. For example: the millitary civics. The default military civic is low cost and has no bonuses or penalties. Pacifism is usually discovered first and the AI pretty much always immediately switches to it to get the 15% production boost, for which it is willing to pay the extra military unit maintenance costs because it thinks this is still better than no bonuses. As the game goes on this will tend to slowly strangle the AI's economy as it never switches to any of the 3 other military civics, all of which have the same base "low" cost but have no penalties. Sadly they also have no bonuses, as far as the AI is concerned because the build bonus for the various unit types that they give are all implemented entirely in python - there is nothing in the civic data that gives the AI any clue that they actually do anything. It's a pity that the civic XML data doesn't have more options, like perhaps a "UnitClassProductionModifiers" section or, probably better, that the UnitInfo XML doesn't have a CivicProductionModifier section (likewise for the BuildingInfos).
 
I raised the building count to 200 so I don't have to deal with it. As far as I can tell there aren't any bad effects from having more in the count than the number of buildings.

Thanks for the mention on the specialists. I knew there had to be a way to include them, just wasn't sure how.

As for the units, I don't have to deal with that in Star Trek because I changed the unit sound code to use unitcombat instead of unit for most cases. I didn't keep the Final Frontier civics though, so there might be something there that I don't remember.
 
Have a new issue. I'm trying to make it so that when a specific python event fires (based on turn number), a couple of solar systems are destroyed. I've used this code in it's own method:
Code:
            pPlotHorbus = CyMap().plot(77, 35)
            pHorbus = pPlotHorbus.getPlotCity()
            pPlotRomulus = CyMap().plot(77, 28)
            pRomulus = pPlotRomulus.getPlotCity()

            pHorbus.kill()
            pRomulus.kill()

            pPlotHorbus.setScriptData("")
            pPlotHorbus.setFeatureType(-1, -1)
            pPlotRomulus.setScriptData("")
            pPlotRomulus.setFeatureType(-1, -1)
The method runs fine, the code is running (as stuff both before and after it works just fine), the cities are killed, but the solar systems are not removed. What is going on?
 
The method runs fine, the code is running (as stuff both before and after it works just fine), the cities are killed, but the solar systems are not removed. What is going on?

My guess: it is the old "things are stored in python locally as well as in the regular game data" sutuation common that comes up in FF related things a lot.

Note that each star system is added to a list that is part of the CvFinalFrontierEvents object, done via the addSystem method of the class. I don't see any corresponding "removeSystem" method (juast a resetSystems that clears the list completely).

The updateSystemsDisplay and/or the updateNeededSystemsDisplay method is presumably used to display all the star systems, possibly making the planets orbit the stars in there somewhere, and they loop over the list.

In order to completely get rid of a system I'm guessing that in addition to what you have done you you may need to clear all the planet data, the system data, and remove the correct system entry from that list and adjust the iNumSystems count (not necessarily in that order). With any luck, there won't be any pieces of data left over to make the system keep appearing after that.

Am I right? Do I win a cookie?
 
I knew about the script data, but not about the list. How would I go about removing the entry from the list (or deleting it and reconstructing it)?

I think it would be something along the lines of:

Code:
		pSystem = self.getSystemAt( X, Y)
		self.apSystems.remove(pSystem)
		self.iNumSystems -= 1

Give or take. Assuming this is in something with the appropriate "self", otherwise provide the correct parent (or add a "removeSystemAt( X, Y)" method to the CvFinalFrontierEvents class and call it from wherever).
 
Interesting. I wrote a bit of code that turned a solar system into a black hole, without doing any of that script data/list clearing stuff:

Code:
if pPlot.isCity():
	pCity = pPlot.getPlotCity()
	pCity.kill()
if pPlot.getFeatureType() == gc.getInfoTypeForString('FEATURE_SOLAR_SYSTEM'):
	pPlot.setFeatureType(gc.getInfoTypeForString('FEATURE_OASIS'),0)

And it works fine. Perhaps this is because I'm overwriting the solar system with another feature, a black hole, rather than simply removing the feature?


God-Emperor: would that specialist code in updatePlotYield work for corporations as well? Finding if a corporation was present in a city, and then if it was, adding X yield?
 
Would that specialist code in updatePlotYield work for corporations as well? Finding if a corporation was present in a city, and then if it was, adding X yield?

I don't see why not, but you need to be careful because normal corporations in BTS give different yields as your access to resources change. You need to make sure you adjust the system's yield appropriately.
 
Back
Top Bottom