Modders Guide to FfH2

The following code is executed as part of onBeginPlayerTurn.

If a player's units are in a specific Feature and damaged there's a chance they lose their current Racial promotion and pick up a different one instead.

After spewing the Feature involved over a test map there was a noticeable slowdown. Any suggestions on how to speed things up? (Like: Could it be set at onBeginGameTurn, and would that help? Do I need all the "if" statements before the command to remove promotions, and is that relevant?)

Code:
iUndead = gc.getInfoTypeForString('PROMOTION_UNDEAD')
py = PyPlayer(iPlayer)
for pUnit in py.getUnitList():
	pPlot = pUnit.plot()
	if pPlot.getFeatureType() == gc.getInfoTypeForString('FEATURE_SC1') and pUnit.getDamage() >= 25 and CyGame().getSorenRandNum(100, "Turn") < 16:
		if pUnit.isHasPromotion(gc.getInfoTypeForString('PROMOTION_WINTERBORN')):
			pUnit.setHasPromotion(gc.getInfoTypeForString('PROMOTION_WINTERBORN'), False)
		if pUnit.isHasPromotion(gc.getInfoTypeForString('PROMOTION_NOMAD')):
			pUnit.setHasPromotion(gc.getInfoTypeForString('PROMOTION_NOMAD'), False)				
		if pUnit.isHasPromotion(gc.getInfoTypeForString('PROMOTION_DWARF')):
			pUnit.setHasPromotion(gc.getInfoTypeForString('PROMOTION_DWARF'), False)
		if pUnit.isHasPromotion(gc.getInfoTypeForString('PROMOTION_ELF')):
			pUnit.setHasPromotion(gc.getInfoTypeForString('PROMOTION_ELF'), False)
		if pUnit.isHasPromotion(gc.getInfoTypeForString('PROMOTION_ORC')):
			pUnit.setHasPromotion(gc.getInfoTypeForString('PROMOTION_ORC'), False)	
		if pUnit.isHasPromotion(gc.getInfoTypeForString('PROMOTION_DARK_ELF')):
			pUnit.setHasPromotion(gc.getInfoTypeForString('PROMOTION_DARK_ELF'), False)	
		if pUnit.isHasPromotion(gc.getInfoTypeForString('PROMOTION_LIZARDMAN')):
			pUnit.setHasPromotion(gc.getInfoTypeForString('PROMOTION_LIZARDMAN'), False)	
		pUnit.setHasPromotion(iUndead, True)
 
Anything that cycles through the entire map will wind up being VERY slow. If it cycles through every unit it will initially be a bit faster, but as the game advances be MUCH slower.

You might be able to simplify the code for removal of promotions by using the .isRace() identifier for the promotions. Then it winds up being in a single statement which is generic for all races. But that winds up having you cycle through all of the promotions on the unit, which could be slower than checking specific promotions you already know are races.

Have you tried just applying Undead? I am pretty sure that it won't force the other racial promotions off of the unit, but I personally don't see any problem with an Undead Dwarven unit TBH. Not that the promotion removal portion is likely the culprit for your speed issues.
 
I know of no real reason for you to check to see if a unit has a promotion before setting it not to have it. Those if statements are thus unnecessary.


Kael actually set it up so that any promotion with <bRace>1</bRace> is automatically removed when any other promotion with <bRace>1</bRace> is granted. These are not the same thing as default races though. Winterborn and Nomad are not technically races (although they are default races) and so would not be automatically removed, but the rest would.


I believe that if you moved this to onBeginGameTurn it would only run once, when the game begins. That would speed things up, but it would also make the code completely pointless, since I doubt that any units would be either on this feature or injured when the game first starts. Edit: ok, I just checked, and I was wrong about that. Using onBeginGameTurn would probably work better.

I'd be inclined to model this after doHellTerrain(self) in Custom Functions.py, adding:
Code:
	def doUndead(self):
		iSC1 = gc.getInfoTypeForString('FEATURE_SC1')
		iUndead = gc.getInfoTypeForString('PROMOTION_UNDEAD')
		for i in range (CyMap().numPlots()):
			pPlot = CyMap().plotByIndex(i)
			if pPlot.getFeatureType() == iSC1
				for i in range(pPlot.getNumUnits()):
					pUnit = pPlot.getUnit(i)
					if pUnit.isAlive():
						if pUnit.getDamage() >= 25:
							if CyGame().getSorenRandNum(100, "Turn") < 16:
								pUnit.setHasPromotion(iUndead, True)
to CustomFunctions.py and adding a cf.doUndead() in def onBeginGameTurn(self, argsList) (or just run the code directly from CvEventManager.py if you prefer)

I chose to cycle through the ties instead of units because I figured you could eliminate all the tiles without the feature pretty quickly, which could have a big effect if you have a stack on the features. I'm pretty sure that doing it this way will make the early game slower but not be nearly a bad latter on.

Although you didn't add any such limit in your code, I figured you would probably not want ships, siege units, golems, angels, demons, or units that are already undead to become undead. Thus, I added if pUnit.isAlive:


In my experience it seems that CyGame().getSorenRandNum slows things down more than almost anything else. I've added a couple of python spells that take a few seconds to work with random numbers but seem instant without it. (I don't mind having a level 3 spell like Blizzard be slow, since they are rarely used and having the frozen lands slowly revert to normal looks so much better. It doesn't seem wise to use this for things that are called every turn though.) If you don't specifically care about having each unit having a seperate chance to become undead I would highly recommend that if CyGame().getSorenRandNum(100, "Turn") < 16: be take out of the loops. If it were generated first and the rest of the function was all within its if statement (meaning that either all units that meet the criteria that turn would turn undead or none of them would) then this would run much faster (especially on turns where it doesn't have to do anything :) ). An intermediate version would be to put this check just before you cycle through the units, so that it would be determined separately for each plot instead of per unit or globally. Choosing a smaller number of "die rolls" could also help.
 
Have you tried just applying Undead? I am pretty sure that it won't force the other racial promotions off of the unit, but I personally don't see any problem with an Undead Dwarven unit TBH.

You're right - I thought I had, but I just tried it again and it doesn't force off some of the promotions.

Kael actually set it up so that any promotion with <bRace>1</bRace> is automatically removed when any other promotion with <bRace>1</bRace> is granted. These are not the same thing as default races though.

Ah... that explains another problem elsewhere - thanks. :)

Winterborn and Nomad are not technically races (although they are default races) and so would not be automatically removed, but the rest would.

That fits - I was trying Doviello and Barbarian units - Winterborn says but Orc is replaced.

I'd be inclined to model this after doHellTerrain(self) in Custom Functions.py, adding...

Thanks! I tried it and on one, limited test at least it was quick.

Given your further advice I also changed the "SorenRandNum" to 6 rather than 100. (And the "=<" # to 1.)

If it were generated first and the rest of the function was all within its if statement (meaning that either all units that meet the criteria that turn would turn undead or none of them would) then this would run much faster (especially on turns where it doesn't have to do anything :) ).

I think each unit should be checked individually - if that turns out to be too slow though I'll try what you describe above.

Thinking that races had to be removed I didn't test Demon and Angel units - they should be excluded.

MagisterCultuum's code takes the "ifs" one check at a time... a good rule of thumb? And, given what xienwolf said, I'm guessing the check for angel and demon should be before the check for damage and the random "roll"?

So the last section would be...?
Code:
for i in range(pPlot.getNumUnits()):
	pUnit = pPlot.getUnit(i)
        if (pUnit.getRace() != iDemon or pUnit.getRace() != iAngel):	
                if pUnit.getDamage() >= 25:
		        if CyGame().getSorenRandNum(6, "Turn") =< 1:
			        pUnit.setHasPromotion(iUndead, True)
 
Assuming that iDemon and iAngel are properly defined, that should work. It is however quite pointless in this case. Edit: If you copied the code before I added if pUnit.isAlive(): then it would matter. That is a better implementation.

Angels, Demons, Elementals, Golems, and Undead actually are all races, so they would be automatically be removed when Undead is added just as Orc was replaced. However, Angels, Demons, Elementals, Golems, and the Undead are not living units. When I added the if pUnit.isAlive(): statement I took care of those, as well as other non living units like catapults, equipment, and naval units. (I assume you'd want all of these excluded too.)


I'm not sure I'd say that you always need to take things one at a time (I usually use and and or far more than Kael does), but it does make it a little easier to read and to debug. I'm not sure, but I believe that python does not "short circuit" its and statements. That means it will check all the conditions even if the first one is false. If it does short circuit, then there is really no difference between having them in the same or different if statements. If it doesn't, then using and will slow things down. This normally won't be significant, but when one of the checks is something like CyGame().getSorenRandNum then it can make a big difference.


Edit: I just checked wikipedia, and found that and and or are short-circuit operators in python. (If you don't want to short circuit you use & or | instead.) Thus, it really doesn't matter if you put things in the same if statement or seperate ones. It also appears that it isn't limited to booleans, as short circuit operators return the last value. I assume it is like MatLab, in which any positive integer is evaluated as True and only 0 is False (and negative numbers null maybe? I don't remember). I don't think civIV really takes advantage of the abilities this adds in Python, although such tricks are used in its C++ code.
 
Thanks for all the help! The function seems to be up and running. Faster and better than before, too. :goodjob:
 
I noticed prereqcivilization in improvementinfos only affects improvementupgrades not restricts wether a civ can build a certain improvement.
I figure it might be useful for other modmodders as well to enhance the functionality of this tag. So might this code be enough?
Code:
bool CvUnit::canBuild(const CvPlot* pPlot, BuildTypes eBuild, bool bTestVisible) const
{
    FAssertMsg(eBuild < GC.getNumBuildInfos(), "Index out of bounds");

//WH:Ploeperpengel modify
	ImprovementTypes eImprovement;
//WH:end modify
...
//WH:Ploeperpengel so only workers of prereqciv build improvements with prereqciv
	if (GC.getBuildInfo(eBuild).getImprovement() != NO_IMPROVEMENT)
	{
		eImprovement = ((ImprovementTypes)(GC.getBuildInfo(eBuild).getImprovement()));
		if (GC.getImprovementInfo(eImprovement).getPrereqCivilization() != NO_CIVILIZATION)
		{
			if (GC.getImprovementInfo(eImprovement).getPrereqCivilization() != GET_PLAYER(getOwnerINLINE()).getCivilizationType())
			{
				return false;
			}
		}
	}
//WH:Ploeperpengel end modify
 
Any word on an ETA for the 0.32g patch? (Or 0.32h if that is out by then?)

It will be today. The only reason I didnt upload "g" yesterday is because I knew "h" was coming today.
 
Where is the marksman code handled? What would I have to do to make it never target workers, hawks, equipment (I would say 0 str units, but Coridale is definitely a good target), and also maybe ships in cities?



What would I have to edit to make it so that cities don't remove improvements, don't grant access to resources, and don't stop you from building improvements on the plot (so you can still get the recourse, or build a fort there)?
 
Picking off hawks and workers I think is okay actually. It's equipment and Boats that is just silly.
 
Can't recall precisely where the code was located, but it is the only place where the tag is actually used in the SDK, so do a search for targetweakestcounter and you should find it instantly.

For the cities, there should be something which causes that in the SDK (vice the exe), and I think I had seen it before. Do a search for code lines which remove improvements and that's probably the narrowest you can manage. I imagine that'll cut it down to less than 50 lines to sort through.
 
Could I get SDK of CvGameCoreDLL032"e" still?

I'm Japanese and always play BtS of Japanese edition.
Latest version of BtS Japanese edition is 3.13, not 3.17.
So we can't play current version of FfH2 on Japanese edition, can only version "e".
(there is localize patch by volunteer.)

I want to try converting latest version of FfH2 for BtS 3.13.
Therefore, I need CvGameCoreDLL032"e".

FfH2 is great mod thx!!
 
Could I get SDK of CvGameCoreDLL032"e" still?

I'm Japanese and always play BtS of Japanese edition.
Latest version of BtS Japanese edition is 3.13, not 3.17.
So we can't play current version of FfH2 on Japanese edition, can only version "e".
(there is localize patch by volunteer.)

I want to try converting latest version of FfH2 for BtS 3.13.
Therefore, I need CvGameCoreDLL032"e".

FfH2 is great mod thx!!

Here you go: CvGameCoreDLL032e.zip

Welcome to CivFanatics!
 
Does anyone know a nice and easy way to make the DLL print something to a file?

I'd love to run a few of the AI "value" functions to output the value for every possibility. I could set it up to do so in-game fairly easily, but I haven't dealt with creating new files so far. If I could just create a .txt file to dump to I could do things like running CvUnitAI::AI_promotionValue(PromotionTypes ePromotion) for each possible UnitAI on a loop for all current promotions to see if it makes appropriate value selections for FfH (like does it think that Drill is far better than Combat?)

EDIT: There are some mitigating factors based on Unit Stats and current situtation, but by and large it is all dependant mostly on Unit AI type. Thus doing a file dump like this ought to help for groundwork at the least.

I am hoping that this tutorial mostly holds true. If so I ought to be able to accomplish the task fairly easily.
 
In CvDLLUtilityIFaceBase.h:
virtual void logMsg(const TCHAR* pLogFileName, const TCHAR* pBuf, bool bWriteToConsole=false, bool bTimeStamp=true) = 0;


Example of usage, from CvGlobals.cpp:
CvString szError;
szError.Format("info type %s not found, Current XML file is: %s", szType, GC.getCurrentXMLFile().GetCString());
gDLL->logMsg("xml.log", szError);
 
Back
Top Bottom