Softcoding Inquisition Civics

You guys are wrong on read/write files. I also had no problem with save games adding the NotGameOption/GameOption, and bStateReligion tags to UnitInfos. There have been some other tags I've added in 3.19 that also caused no issue with save game compatibility. The only one that has broken it outright from the dll was adding the civic array. You guys used to be correct with 3.17, but things are different with 3.19. Another change is that certain new XML entities no longer break save game, such as adding a new model in an ArtDefinesX file, in 3.17 that broke save game, but in 3.19 it no longer does. So I guess the answer is, no one knows.
 
You guys are wrong on read/write files. I also had no problem with save games adding the NotGameOption/GameOption, and bStateReligion tags to UnitInfos. There have been some other tags I've added in 3.19 that also caused no issue with save game compatibility. The only one that has broken it outright from the dll was adding the civic array. You guys used to be correct with 3.17, but things are different with 3.19. Another change is that certain new XML entities no longer break save game, such as adding a new model in an ArtDefinesX file, in 3.17 that broke save game, but in 3.19 it no longer does. So I guess the answer is, no one knows.

I wasn't modding back with 3.17 (I missed that whole patch-update horror), so I can't say for sure, but perhaps it has to do with the DllExport tags that got removed.
 
DllExport marks functions available to the EXE. You should not remove them from existing functions nor add them to new functions. It's just a Microsoft DLL thing.
 
DllExport marks functions available to the EXE. You should not remove them from existing functions nor add them to new functions. It's just a Microsoft DLL thing.

Yah, I know. I was just saying that it was the biggest change from 3.17 to 3.19, so perhaps it was the cause of the changes Phungus was seeing.
 
You guys are wrong on read/write files. I also had no problem with save games adding the NotGameOption/GameOption, and bStateReligion tags to UnitInfos.

I am not sure how to confirm this, but are you sure that *nothing was different* when you loaded the save? The data structure is a different size, and I do not see how reading a data structure of the wrong size could result in a full, correct restore. This is actually a worse kind of error than "failed to decompress", because if the game let you restore with wrong size data structures, some subtle things could be wrong which would be impossible to diagnose.
 
You can use the Flags at the start of the read/write functions to maintain save compatability even with massive changes to the structure of the savefile. Bloats the function if you do it too often, but is an interesting exercise for logic in some cases.
 
Are there any examples of these save game flags? Since I'm adding 3 entirely new XML tags (and probably more as there are 3 other requested tags by Afforess and Zappara I know will be used in Rise of Mankind), I'd like to know how to do this, and maintain save game compatibility for my own purposes with LoR. I'd drop them for RevDCM itself, but I'd like to keep LoR's 0.9.8d patch updated with the RevDCM core, at least functionally, and I need it to stay save game compatible with the other 0.9.8 versions. This would be especially useful since davidlallen seems to think that even though save game isn't lost there are other problems involved when changing the read/write stream.
 
Firaxis used them in 3.19, so if you can find the 3.17 source, compare it with 3.19 to see how they pulled it off.

The specific bits of code would be:

Code:
	uint uiFlag=0;
	pStream->Read(&uiFlag);	// flags for expansion

and

Code:
	uint uiFlag=2;
	pStream->Write(uiFlag);		// flag for expansion

In the initial release, all fllags were 0, this example is from my CvUnit, but I think from a code set where I haven't modified it. Anyway, one place it was used is:

Code:
	if (uiFlag > 0)
	{
		pStream->Read(&m_bAirCombat);
	}

Which states that in initial release, there was no m_bAirCombat variable in CvUnit, so only people who have a save from after the first patch will try to load that boolean.
 
I have worked with other binary interfaces where "spare fields" have been placed in order to hopefully ease the addition of fields in the future. However, in my experience this has always just led to more problems. Sure, the save is compatible, in the sense that the data is all loaded correctly. But, there is trouble lurking if two versions of the mod use these "spare fields" for different things. Also if a new version of the save file is loaded into an old version of the mod, it probably should not be allowed to work, but it will.

It is very complicated to get binary compatibility working for "just one small change". I recommend to just announce that your changes break save games. At worst, this may cause a user to delay adoption of a new version by the amount of time it takes that user to finish that game.
 
It is very complicated to get binary compatibility working for "just one small change". I recommend to just announce that your changes break save games. At worst, this may cause a user to delay adoption of a new version by the amount of time it takes that user to finish that game.

For what's it's worth, I completely agree with DavidAllen.
 
For the RevDCM core, I'll just add the changes normally, as the next update is going to break save game for all you guys anyway. So don't worry guys. I just wanted to know how to do this because when I merge the new updates into LoR I don't want to break save game for the next update, so I'll need to use these.
 
I have worked with other binary interfaces where "spare fields" have been placed in order to hopefully ease the addition of fields in the future. However, in my experience this has always just led to more problems. Sure, the save is compatible, in the sense that the data is all loaded correctly. But, there is trouble lurking if two versions of the mod use these "spare fields" for different things. Also if a new version of the save file is loaded into an old version of the mod, it probably should not be allowed to work, but it will.

It is very complicated to get binary compatibility working for "just one small change". I recommend to just announce that your changes break save games. At worst, this may cause a user to delay adoption of a new version by the amount of time it takes that user to finish that game.

If you are talking about what I posted, it isn't a spare field, it is actually used. But on any unit existing before the patch that added this field, the value would be false, thus not reading it means they retain the value they should after loading. Future saves would have the new tag included, and thus load it.
 
Anyone see anything wrong with the following code? I replaced the old hardcoded civic inquisitions code in CvMainInterface.py (player is running OR or Theo) with this and lost the inquisition button. I have set these new tags (bAllowInquisitions, and bDisallowInquisitions) in CivicInfos correctly, and the information shows up in the civilopedia.

Code:
							for j in range(gc.getNumCivicInfos()):
								eCivic = CivicTypes(j)
								if gc.getCivicInfo(eCivic).isAllowInquisitions():
									if pCityPlayer.getCivics(eCivic):
										pCityCivicConditions = True
							for j in range(gc.getNumCivicInfos()):
								eCivic = CivicTypes(j)
								if gc.getCivicInfo(eCivic).isAllowInquisitions():
									if pUnitPlayer.getCivics(eCivic):
										pUnitCivicConditions = True
							if ( ( pUnitCivicConditions == True ) and ( pCityCivicConditions == True ) ):
								CivicConditions = True
							for j in range(gc.getNumCivicInfos()):
								eCivic = CivicTypes(j)
								if gc.getCivicInfo(eCivic).isDisallowInquisitions():
									if pUnitPlayer.getCivics(eCivic):
										CivicConditions = False
							for j in range(gc.getNumCivicInfos()):
								eCivic = CivicTypes(j)
								if gc.getCivicInfo(eCivic).isDisallowInquisitions():
									if pCityPlayer.getCivics(eCivic):
										CivicConditions = False
 
Anyone see anything wrong with the following code? I replaced the old hardcoded civic inquisitions code in CvMainInterface.py (player is running OR or Theo) with this and lost the inquisition button. I have set these new tags (bAllowInquisitions, and bDisallowInquisitions) in CivicInfos correctly, and the information shows up in the civilopedia.

Code:
                            for j in range(gc.getNumCivicInfos()):
                                eCivic = CivicTypes(j)
                                if gc.getCivicInfo(eCivic).isAllowInquisitions():
                                    if pCityPlayer.getCivics(eCivic):
                                        pCityCivicConditions = True
                            for j in range(gc.getNumCivicInfos()):
                                eCivic = CivicTypes(j)
                                if gc.getCivicInfo(eCivic).isAllowInquisitions():
                                    if pUnitPlayer.getCivics(eCivic):
                                        pUnitCivicConditions = True
                            if ( ( pUnitCivicConditions == True ) and ( pCityCivicConditions == True ) ):
                                CivicConditions = True
                            for j in range(gc.getNumCivicInfos()):
                                eCivic = CivicTypes(j)
                                if gc.getCivicInfo(eCivic).isDisallowInquisitions():
                                    if pUnitPlayer.getCivics(eCivic):
                                        CivicConditions = False
                            for j in range(gc.getNumCivicInfos()):
                                eCivic = CivicTypes(j)
                                if gc.getCivicInfo(eCivic).isDisallowInquisitions():
                                    if pCityPlayer.getCivics(eCivic):
                                        CivicConditions = False


I see the problem. You aren't initiating civic Conditions as False, nor are you setting the city or unit conditions as false to start with. Try this code, it's a bit more efficient and shorter, but should work:
Code:
                            pUnitCivicConditions = False
                            pCityCivicConditions = False
                            CivicConditions = False
                            for j in range(gc.getNumCivicInfos()):
                                eCivic = CivicTypes(j)
                                if pCityPlayer.getCivics(eCivic):
                                    if gc.getCivicInfo(eCivic).isAllowInquisitions():
                                            pCityCivicConditions = True
                                    if gc.getCivicInfo(eCivic).isAllowInquisitions():
                                            pUnitCivicConditions = True
                                    # Afforess Note: A False Overrides a True
                                    if gc.getCivicInfo(eCivic).isDisallowInquisitions():
                                            pCityCivicConditions = False
                                    if gc.getCivicInfo(eCivic).isDisallowInquisitions():
                                            pUnitCivicConditions = False
                            if ( ( pUnitCivicConditions == True ) and ( pCityCivicConditions == True ) ):
                                CivicConditions = True
 
Well I already had the variables initialized, otherwise I'd get a python exception. Your code is cleaner though, so I tried it, with a couple minor tweaks. Here is what I have so far:

Code:
					pUnit = g_pSelectedUnit
					bInquistor = False
					if ( pUnit.isInquisitor() == True ):
						bInquistor = True
					pUnitOwner = gc.getPlayer(pUnit.getOwner())

					if pUnitOwner.isTurnActive():
						# Inquisitor
						if ( ( bInquistor == True ) and ( gc.getMap().plot( g_pSelectedUnit.getX(), g_pSelectedUnit.getY() ).isCity() ) ):
							pCity = gc.getMap().plot( g_pSelectedUnit.getX(), g_pSelectedUnit.getY() ).getPlotCity( )
							pCityPlayer = gc.getPlayer(pCity.getOwner())
							pUnitPlayer = gc.getPlayer(pUnit.getOwner())
							pUnitCivicConditions = False
							pCityCivicConditions = False
							CivicConditions = False
							VassalConditions = False
							ProceedWithInquisition = False

							for j in range(gc.getNumCivicInfos()):
								eCivic = CivicTypes(j)
								if pCityPlayer.getCivics(eCivic):
									if gc.getCivicInfo(eCivic).isAllowInquisitions():
										pCityCivicConditions = True
								if pUnitPlayer.getCivics(eCivic):
									if gc.getCivicInfo(eCivic).isAllowInquisitions():
										pUnitCivicConditions = True
								if pCityPlayer.getCivics(eCivic):
									if gc.getCivicInfo(eCivic).isDisallowInquisitions():
										pCityCivicConditions = False
								if pUnitPlayer.getCivics(eCivic):
									if gc.getCivicInfo(eCivic).isDisallowInquisitions():
										pCityCivicConditions = False
							if ( ( pUnitCivicConditions == True ) and ( pCityCivicConditions == True ) ):
								CivicConditions = True
...

Still no dice. Not getting an inquisition mission button.
 
I believe you want to loop over getNumCivicOptionInfos(). CyPlayer.getCivics(CivicOptionTypes) returns the CivicTypes that the player is running for that option group. BTW, you don't need to cast from int to FooTypes when passing values from Python to C++.

Code:
for j in range(gc.getNumCivicOptionInfos()):
    eCivic = pCityPlayer.getCivics(j)
    if gc.getCivicInfo(eCivic).isAllowInquisitions():
        pCityCivicConditions = True
 
EF is right, loop over CivicOptionTypes. Also, You could remove that if pCityPlayer.getCivics(eCivic): for the other three times, no need to check 4 times on the same civic...

At the very end of the code, put CivicConditions = True, and see if the button appears. If it does, then you've got a logic issue of some kind. If not, then there is a bigger issue.
 
Thanks EF. It works now. Here is the code, if anyone sees a way to improve it let me know:

Code:
					# Inquisition Mod
					###### Unit Buttons ######
					pUnit = g_pSelectedUnit
					bInquistor = False
					if ( pUnit.isInquisitor() == True ):
						bInquistor = True
					pUnitOwner = gc.getPlayer(pUnit.getOwner())

					if pUnitOwner.isTurnActive():
						# Inquisitor
						if ( ( bInquistor == True ) and ( gc.getMap().plot( g_pSelectedUnit.getX(), g_pSelectedUnit.getY() ).isCity() ) ):
							pCity = gc.getMap().plot( g_pSelectedUnit.getX(), g_pSelectedUnit.getY() ).getPlotCity( )
							pCityPlayer = gc.getPlayer(pCity.getOwner())
							pUnitPlayer = gc.getPlayer(pUnit.getOwner())
							pUnitCivicConditions = False
							pCityCivicConditions = False
							CivicConditions = False
							VassalConditions = False
							ProceedWithInquisition = False

							for j in range(gc.getNumCivicOptionInfos()):
								eCivic = pCityPlayer.getCivics(j)
								if gc.getCivicInfo(eCivic).isAllowInquisitions():
									pCityCivicConditions = True
								eCivic = pUnitPlayer.getCivics(j)
								if gc.getCivicInfo(eCivic).isAllowInquisitions():
									pUnitCivicConditions = True
							for j in range(gc.getNumCivicOptionInfos()):
								eCivic = pCityPlayer.getCivics(j)
								if gc.getCivicInfo(eCivic).isDisallowInquisitions():
									pCityCivicConditions = False
								eCivic = pUnitPlayer.getCivics(j)
								if gc.getCivicInfo(eCivic).isDisallowInquisitions():
									pUnitCivicConditions = False

							if ( pCityCivicConditions and pUnitCivicConditions ):
								CivicConditions = True
							
							if ( gc.getTeam(pCityPlayer.getTeam()).isVassal(pUnitPlayer.getTeam()) ):
								if ( pCityPlayer.getStateReligion() == pUnitPlayer.getStateReligion() ):
									VassalConditions = True

							if (CivicConditions == True):
								if ( ( pCity.getOwner() == pUnit.getOwner() ) or ( VassalConditions == True ) ):
									ProceedWithInquisition = True

Now I need to go through and do this for the python in the RevInquisitions folder. This is alot harder though because in that code there usually isn't a pUnit or pCity or even pPlayer defined from which to reference :(
 
This is from an AI choosing production function. So I can't tell if it's working correctly if I change it:

Code:
			iInquisitor = CvUtil.findInfoTypeNum( gc.getUnitInfo, gc.getNumUnitInfos(), "UNIT_INQUISITOR" )

I have changed it to:
Code:
			for j in range(gc.getNumUnitInfos()):
				eUnit = gc.getUnitType(j)
				if gc.getUnitInfo(eUnit).isInquisitor():
					iInquisitor = gc.getUnitInfo(eUnit).isInquisitor()

No python exceptions fire. But I'd like to know if this would work, ie, does the code look OK?
 
Back
Top Bottom