Softcoding Inquisition Civics

phungus420

Deity
Joined
Mar 1, 2003
Messages
6,296
Currently the inquistions code that everyone seems to use hardcodes the civics in the python itself. I've moved some of that over to the XML in terms of the inquisitor trainability, which removed the need for the canTrain callback. Now I'd like to finish things up, and use the new XML in UnitInfos to define when inquisitions can be done.

Currently this is how things work:

Code:
...
if ( ( pCityPlayer.getCivics(gc.getInfoTypeForString('CIVICOPTION_RELIGION')) == gc.getInfoTypeForString('CIVIC_THEOCRACY') ) or ( pCityPlayer.getCivics(gc.getInfoTypeForString('CIVICOPTION_RELIGION')) == gc.getInfoTypeForString('CIVIC_ORGANIZED_RELIGION') ) ):
...
and
Code:
...
if ( pPlayer.getCivics(gc.getInfoTypeForString('CIVICOPTION_RELIGION')) == gc.getInfoTypeForString('CIVIC_PAGANISM') or if pPlayer.getCivics(gc.getInfoTypeForString('CIVICOPTION_RELIGION')) == gc.getInfoTypeForString('CIVIC_PACIFISM') ):
...

These are checks to see if inquisitions can be performed. The former is a can do check (if the player is running one of these civics you can conduct inquitions), the latter is a negative check (if the player is running either of these civics inquisitions can not be performed). I'm forgoeing the rest of the code as it's not important, I'm just interested in this piece. Basically I want to take the new PrereqOrCivic code that looks like this in UnitInfos:
Code:
			<PrereqOrCivics>
				<PrereqCivic>
					<CivicOption>CIVIC_THEOCRACY</CivicOption>
					<bPrereqCivic>1</bPrereqCivic>
				</PrereqCivic>
				<PrereqCivic>
					<CivicOption>CIVIC_ORGANIZED_RELIGION</CivicOption>
					<bPrereqCivic>1</bPrereqCivic>
				</PrereqCivic>
			</PrereqOrCivics>
And use that as the check for if inquisitions can be conducted.

I have exposed this tag to python through CyInfoInterface here:
Code:
		//RevolutionDCM canTrain
		.def("getPrereqOrCivics", &CvUnitInfo::getPrereqOrCivics, "bool (int iCivic)")
		//RevolutionDCM end

How should I begin to change this check so that it pulls the civics from the XML in unitinfos? In pseudo code I'd like it to be like this:

if ( pUnit.getPrereqOrCivics() == pUnit.getPlayer().getCivics(AnyCivic in getPrereqOrCivicArray for pUnit) )

but I do not know how to write this so it's syntatically correct. Also do I need to add the PrereqOrCivic exposure to CyUnit?
 
I agree this is a good project.

1. Is the removal of can/cannotTrain already part of a production release of RevDCM? This will be a nice speedup. These functions are called millions of times.

2. I don't quite understand the existing code from the fragments you included. Basically what you said is, if civic == a or civic == b then enable inquisitions, if civic == c or civic == d then disable inquisitions. The negative test part seems redundant. Also, what if the civic has some other value altogether, such as "none" which is the default? All you need is to test for a or b. Do you agree?

3. In your new xml, I don't understand how your sdk cannotTrain knows to check the CIVICOPTION_RELIGION for the requested CIVIC values. Is this still hard coded there? If that is given somewhere else in the xml, please show the details.

4. I assume that the data structure for the civic you added is a boolean array. So what you expose from sdk to python can only be something like "bool CyUnit.isPrereqOr(int eCivicType)". This by itself does not seem to solve the problem, since you still need to loop over all the civic types in python, and you still don't really know what CIVICOPTION to ask the player object for.

So, it seems some details are missing, before we can come up with a full solution.
 
All python callbacks have been removed from the current RevDCM SVN.

I don't see why knowing more of the code is needed. All I need to know is how code an "if the player is running any of the civics defined for pUnit (pUnit will always be the inquisitor in this code) in the PrereqOrCivic array". The rest of the code is set up fine, all i need to do is change the current hardcoded specific civic code with a check that sees if the player is running a civic in the inquisitor unit PrereqOrCivic array. I have no idea how to code that.

If you'd like to take a look at the code, the current RevDCM SVN is here:
https://revolutiondcm.svn.sourceforge.net/svnroot/revolutiondcm/Trunk/RevolutionDCM

The two checks that need changing are in CvMainInterface.py and RevInqUtilities.py
 
Your sdk code now has the ability to tell if the player can train the unit. It seems like you want to use the *same* test to see if the unit can perform an inquisition. So, you could just call CyPlayer.canTrain(iUnitType). Is that true?
 
That would work. But it's not the test I want to do. For instance, what if a mod adds resource or unit upgrade paths for inquisitors, checking the trainability of the unit will mess things up in such cases. I would like the python test here to simply check the civic. What I want is to check the civics of the player, and compare them against the PrereqOrCivics of the unit in the XML. It seems I will need to expose this tag in CyUnit then. Is this correct? And once I do, what would be the code to do such a civic's check?
 
With arrays, you need some kind of loop. So, loop over the civics, check if the player has the civic, and then check if the civic matches the one in the array. Hopefully that makes sense.
 
Oh, that makes sense, but I don't know how to write that.

Also onto another problem. I have just finished adding a new tag to UnitInfos, bInquisitor. Going to use this instead of hardcoding the inquisitor unit in the Python. Works fine and dandy in CvMainInterface.py, but I can't figure out how to use it in the other inquisition python files. The reason is because there is no clear unit to use the newly created isInquisitor() call on. For instance here is a function that hardcodes the Inquisitor unit in the python:

Code:
	def AI_chooseProduction(self, argsList):
		if game.isOption(GameOptionTypes.GAMEOPTION_INQUISITIONS):
			#This code is quite computationally heavy!
			pCity = argsList[0]
			iOwner = pCity.getOwner()
			pPlayer = gc.getPlayer(iOwner)
			iStateReligion = pPlayer.getStateReligion()
			iInquisitor = CvUtil.findInfoTypeNum( gc.getUnitInfo, gc.getNumUnitInfos(), "UNIT_INQUISITOR" )
		
			if utils.isInquisitionConditions(iOwner):
				hasRevoltCity = inquisitor.getInquistionRevoltCity(iOwner, 0, utils.getMinRevIndex(), 0)
				#Inquistor production logic is based purely on potential domestic instability because
				#as yet the AI doesn't know how else to strategically use inquisitors.
				if inquisitor.isAI_InquisitionConditions(iOwner):
					if inquisitor.getInquistionRevoltCity(iOwner, 0, utils.getMinRevIndex()*2/3, 0):
						if pCity.canTrain(iInquisitor, 0, 0):
							buildOdds = 50
							#Increase the odds of building an inquisitor if the city has the state religion and the 
							#the owner of that city has a dangerously unstable city. Note that even 100% odds here
							#does not guarantee an inquisitor is built because the SDK will override this code.
							#For some reason, the question pCity.getRevolutionIndex() cannot be asked in this function
							#because a python error can ensue.
							if pCity.isHasReligion(iStateReligion) and hasRevoltCity:
								buildOdds = 100
							# If there are any inquisitors, don't start building another one unless urgent. The SDK
							# may decide to build an inquisitor in any case, but not if the maximum allowable number of
							# inquistors defined in XML has been exceeded. If for some obscure reason, it has been exceeded, 
							# excessive numbers they will be culled in AI_unitUpdate()
							lUnits = PyPlayer( pPlayer.getID( ) ).getUnitList( )
							for iUnit in range( len( lUnits) ):
								if pPlayer.getUnit( lUnits[ iUnit ].getID( ) ).getUnitType( ) == iInquisitor:
									if buildOdds < 100:
										return false
							# Build an inquisitor at odds relative to other low priority builds that
							# a city might think about in the SDK.
							iInquisitorClass = PyInfo.UnitInfo(iInquisitor).getUnitClassType()
							# check if the maximum amount of Inquisitors is already reached 
							if not pPlayer.isUnitClassMaxedOut(iInquisitorClass, -1):
								if utils.getRandomNumber(100/buildOdds) == 0:
									gc.getMap( ).plot( pCity.getX( ), pCity.getY( ) ).getPlotCity( ).pushOrder( OrderTypes.ORDER_TRAIN, iInquisitor, -1, False, False, False, True )
									return True
			return False
Now the problem lies in this line:
iInquisitor = CvUtil.findInfoTypeNum( gc.getUnitInfo, gc.getNumUnitInfos(), "UNIT_INQUISITOR" )
How can I use the CyUnit isInquisitor() call here?
 
Expose the bInquistor to python, then just check if the unit is an inquisitor, by looping over all the units and checking to see if bInquisitor is true. Not sure about the exact code (I avoid python most of the time, but it can be useful...)

Like this:
Loop Over units
if gc.getUnitInfo(eLoopUnit).isInquisitor()
do stuff...
 
All the above (I skimmed it only) will work, I'd just like to throw out a different option. It's probably too late, so take this only as some design advice from the pulpit. ;)

I would have added these prereqs to CvReligionInfo rather than the units. An inquisition from my limited understanding of your posts requires three things:

  1. Player doing it must be running prereq civics
  2. Player doing it must train an Inquisitor (requires 1, may require other things in future)
  3. Target player must not be running "unprereq" civics
All of these things are player-related (except the future part of 2), and players have state religions and run civics. This would tell me to attach the prereq civics to something about the player (state religion) rather than something about the unit. A good example would be Theocracy's ability to block foreign spread of non-state religions.

This would allow a modder to set up certain religions to be more susceptible to inquisitions than others if they wanted. Sure, you can do that with the data on the units, but logically this seems more a thing about the religion than the unit.
 
All the above (I skimmed it only) will work, I'd just like to throw out a different option. It's probably too late, so take this only as some design advice from the pulpit. ;)

I would have added these prereqs to CvReligionInfo rather than the units. An inquisition from my limited understanding of your posts requires three things:

  1. Player doing it must be running prereq civics
  2. Player doing it must train an Inquisitor (requires 1, may require other things in future)
  3. Target player must not be running "unprereq" civics
All of these things are player-related (except the future part of 2), and players have state religions and run civics. This would tell me to attach the prereq civics to something about the player (state religion) rather than something about the unit. A good example would be Theocracy's ability to block foreign spread of non-state religions.

This would allow a modder to set up certain religions to be more susceptible to inquisitions than others if they wanted. Sure, you can do that with the data on the units, but logically this seems more a thing about the religion than the unit.

I agree with EmperorFool, but I see one design flaw with EF's suggestion. ReligionInfos are loaded way before civics or units. Wouldn't that entail a bunch of readpass3's?
 
Now the problem lies in this line:
iInquisitor = CvUtil.findInfoTypeNum( gc.getUnitInfo, gc.getNumUnitInfos(), "UNIT_INQUISITOR" )
How can I use the CyUnit isInquisitor() call here?

One way is to loop over all units as Afforess suggested. I support the goal of removing hard-coding for inquisitors, but another goal is keeping the python fast, and these may be mutually exclusive. You might consider a GlobalDefine for the unit. Set it in assets/xml/GlobalDefines.xml like this:
Code:
	<Define>
		<DefineName>GLOBAL_WARMING_TERRAIN</DefineName>
		<DefineTextVal>TERRAIN_DESERT</DefineTextVal>
	</Define>
For example, define REVDCM_INQUISITOR_UNIT. Then in python, you can fetch it with
Code:
iUnitType = gc.getDefineINT("REVDCM_INQUISITOR_UNIT")
Clearly, this limits you to one inquisitor unit. You would need to decide whether you think the overhead of looping the entire unit list to find one or more units with bInquisitor set, is worth the possible flexibility for future users to have multiple inquisitor units.
 
Yes, because they would reference objects loaded after them, readpass3() would be required I think.
 
I would have added these prereqs to CvReligionInfo rather than the units.

Adding it to religions seems to be much more complex. It may be a nice future enhancement to allow different *religions* to handle inquisition differently, but the present feature allows different *civics* to handle inquisition differently. Another design alternative may be to use flags on civics, such as bEnableInquisition and bPreventInquisition. This also avoids any need for readpass3's, and the value for a player can be computed relatively easily with a short loop over CIVICOPTIONS.
 
Another design alternative may be to use flags on civics, such as bEnableInquisition and bPreventInquisition.

This is the best option I think, and it's also the easiest to implement. This is why I posted my quick idea even though phungus had already gone down the road he did: it can spur good discussion that ultimately leads to much better choices.
 
You could still put iInquisitionChanceSuccess on ReligionInfos without readpass3's or any other shenanigans too.
 
I will go that route then, it's probably the easiest to code, and simplest to implement. I just hope it doesn't break save game at this point, but for most mods that use a RevDCM core that doesn't matter anyway, as the next release will break save game regardless, so no reason to hold off on implementing it.

Does anyone know what causes a loss of save game, in terms of the dll? Adding in the bInquisitor tag to UnitInfos did not break save game. But adding in the Prereq Civic Array did. I know adding entirely new entities in the XML breaks save game. And in 3.17 adding in a new tag in the dll also broke it, but with 3.19 it seems to be a coin flip. Has anyone figured out what exactly does it now?
 
I would think that changing the size of any data structure would break a save game. Assuming you updated the binary read/write functions, then performing a read of the new flag on a save which doesn't have it should definitely cause a problem. I am surprised that you found adding bInquisitor did not break save games.
 
I will go that route then, it's probably the easiest to code, and simplest to implement. I just hope it doesn't break save game at this point, but for most mods that use a RevDCM core that doesn't matter anyway, as the next release will break save game regardless, so no reason to hold off on implementing it.

Does anyone know what causes a loss of save game, in terms of the dll? Adding in the bInquisitor tag to UnitInfos did not break save game. But adding in the Prereq Civic Array did. I know adding entirely new entities in the XML breaks save game. And in 3.17 adding in a new tag in the dll also broke it, but with 3.19 it seems to be a coin flip. Has anyone figured out what exactly does it now?

You can edit the dll to your hearts content, provided you do not create any more read/write flags in any of the functions (Not just CvInfos, but CvCity, CvPlayer, etc...). If I were a betting man, I'd say that you either forgot the read/write flags in CvInfos, or you're wrong, and it did break older saves.

PS. Who cares about save functionality? This isn't a BUG mod, you can break saves with each release; complaint free. I Do. :mischief:
 
This isn't a BUG mod, you can break saves with each release; complaint free. I Do. :mischief:

You guys have it so easy! :D Yes, as long as you don't modify read()/write() calls, you should be fine. These functions are what make the saved game file. BTW, are CvInfos actually written to the saved game? That seems odd since they don't change during the game and are quite large. I thought they would only be written for caching XML to some other file.
 
You guys have it so easy! :D

I know, because when I have to write patches, they have to be save compatible, and I always cringe. :p

Yes, as long as you don't modify read()/write() calls, you should be fine. These functions are what make the saved game file. BTW, are CvInfos actually written to the saved game? That seems odd since they don't change during the game and are quite large. I thought they would only be written for caching XML to some other file.

AFAIK, CvInfos are in saves too. Because screwing them up causes Failure to Decompress.
 
Top Bottom