Settler Starting Bonus

fdrpi

Prince
Joined
Feb 14, 2012
Messages
373
Location
Massachusetts
I think that this promotion (a bonus to your initial settler at game start) is genius, and want to integrate this promotion into Diversity, my mod. What exactly do I have to do to achieve this?

I am mainly concerned about getting it to apply, and then only to the initial settler.
 
Kael seems to have set it up is that the promotion is applied in the SDK/DLL.

I do not think it is worth the effort to try to implement it the same way, unless you intend to use the FfH2 DLL or compile your own DLL already.


I would suggest the simpler approach of placing this bit of code in CvEventManager.py under def onUnitCreated(self, argsList):
Code:
		if unit.getUnitType() == gc.getInfoTypeForString('UNIT_SETTLER'):
			if gc.getPlayer(unit.getOwner()).getNumCities() < 1:
				unit.setHasPromotion(gc.getInfoTypeForString('PROMOTION_STARTING_SETTLER'), True)
Technically this won't only apply only to the starting settler, but to any settler you get when you do not have any cities. Since you cannot train a settler without a city, this could only apply to the starting settler, a settler acquired by exploring a tribal village (which I believe is only a possible result at low difficulty levels) before founding a city/after loosing all of your cities, or a settler added by cheating through worldbuilder.
 
If you add the promotion in python, then the units UNITCOMBAT (which is what I think you meant by class) is not at all relevant.

You can of course add promotions without carrying about unitclass or unitcombat in the C++ code of the DLL too, but most of time such checks are performed anyway. That check is certainly performed before allowing a unit to purchase a promotion the old fashioned way.

You would need to give Settlers a unitcombat if you want hem to be able to purchase the Starting Settler promotion with their xp, but I don't think you care about that.


The thought just came to mind that your mod might not actually use onUnitCreated. I don't think it in used in basic BtS, but it can be turned on by editing Assets\XML\PythonCallbackDefines.xml so that USE_ON_UNIT_CREATED_CALLBACK is set to 1 instead of 0.
 
That is what the code I included in my first post in this thread does.


The function you want is VOID setHasPromotion (PromotionType eIndex, BOOL bNewValue)

(You an find this and other helpful functions here. It does not include all the new functions that the custom DLLs of various mods add, I am not certain that it includes all of the python functions of base BtS either, and occasionally a function does something other than what you except. Still, I often find it quite helpful, and much more convenient than searching through the various Cy____.cpp source files for the DLL.)

This function belongs only to CyUnit type objects. That is what represents the actual units in the game. In most places these objects are referred to as pUnit (or pCaster, pOpponet, ect), but under def onUnitCreated(self, argsList): in CvEventManager.py the unit which was just created is called unit.

To call a function you write the name of the object (technically variable that serves as a pointer for the object), a . and then the name of the function.


VOID means that this function does not return anything, so you cannot define a variable as equaling the result of this function.

BOOL bNewValue is a boolean (true or false) value. True tells the function to give the unit the promotion (if it does not already have it). Falls tells the function to remove the promotion (if it has it already).

PromotionType eIndex is an integer, which refers to the promotion's location in the file CIV4PromotionInfos.xml. The first promotion is 0, the second is 1, etc. If you know the location of the promotion in question (and are sure that you will never rearrange the file), then you could just type that integer. You would not normally want to do that, however,


In order to find the index of a promotion (or religion, or unit, or building, or almost anything else) you generally use INT getInfoTypeForString (STRING szInfoType)

This is a function of the CyGlobalContext() object. Since this is used so frequently, most files define gc = CyGlobalContext() so you can just type things like gc.getInfoTypeForString( )

INT means that this function returns an integer, specifically the index defining the location of the promotion/unit/whatever in the file where it is found.

STRING szInfoType refers to the string (which is an immutable list of characters, usually letters) whose location you wish to find. You want it to be whatever is found between <Type> & </Type> for the promotion (or whatever). (It could also be things like the unitclass found between <Class> & </Class> or the unitcombat found between <Combat> & </Combat>, but that is not relevant to what you want to do here.)

The starting settler promotion is added by the line unit.setHasPromotion(gc.getInfoTypeForString('PROMOTION_STARTING_SETTLER'), True)


The two conditional statements I placed above that are there to narrow down which units get the promotions. Without them, every unit that is ever created would get the promotion for free. The first conditional limits that to only settlers, and the second one makes it apply only when the unit's owner does now control any cities.


(edit: I noticed that it looks like there is a space within the word PROMOTION, but there should not be. That is a fault of the parser on this forum. You should copy the code form my previous post, which was preserved properly thanks to the [code][/code] tags. That also kept it indented correctly, which is important in python. The first conditional should line up with unit = argsList[0] (indented one tab more than the header def onUnitCreated(self, argsList):), the second be indented once more, and the line actually setting the promotion indented once more within that.)
 
I've added the first bit of code, and turned the callback so that it is used. I have added the promotion itself in XML because I am not comfortable in Python yet. The starting settler still does not have the promotion.

Is this because it is intelligible to receive the promotion because it has no class, or for another reason?
 
Oh, by "add the promotion" you meant add it to the game rather than add it to the xml? I seem to have misunderstood you. You cannot add a promotion to the game without xml.

I'm still not sure what you mean by "class."

Here is the xml define for the promotion used in FfH2.
Code:
        <PromotionInfo>
            <Type>PROMOTION_STARTING_SETTLER</Type>
            <Description>TXT_KEY_PROMOTION_STARTING_SETTLER</Description>
            <Sound>AS2D_IF_LEVELUP</Sound>
            <TechPrereq>TECH_NEVER</TechPrereq>
            <iVisibilityChange>3</iVisibilityChange>
            <iMovesChange>2</iMovesChange>
            <Button>Art/Interface/Buttons/Units/Settler.dds</Button>
            <iMinLevel>-1</iMinLevel>
        </PromotionInfo>
The portions I put in red are new tags that Kael added to the schema/DLL. They won't work and should not be included in a mod not using such a custom CvGameCoreDLL.dll,

<iMinLevel> normally does not allow the unit to purchase a promotion unless its level is higher than the value put there, but a value of -1 never allows it to be purchased



TECH_NEVER is also something he added in order to prevent the promotion from ever being purchased. This does not require any DLL/schema changes, only the addition of a tech by that name to CIV4TechInfos.xml. The tech is disabled, so it can never be researched and anything with it as a prereq can never be built/trained/created/purchased/etc.

Considering that it has nothing under <UnitCombats>, no unit woul be able to purchase the promotion anyway.


The thought came to mind that in vanilla BtS the various promotion tags might have been mandatory rather than optional. If that is the case, then the promotion define in CIV4PromotionInfos.xml should look like this
Code:
		<PromotionInfo>
			<Type>PROMOTION_STARTING_SETTLER</Type>
			<Description>TXT_KEY_PROMOTION_STARTING_SETTLER</Description>
			<Sound>AS2D_IF_LEVELUP</Sound>
			<LayerAnimationPath>NONE</LayerAnimationPath>
			<PromotionPrereq>NONE</PromotionPrereq>
			<PromotionPrereqOr1>NONE</PromotionPrereqOr1>
			<PromotionPrereqOr2>NONE</PromotionPrereqOr2>
			<TechPrereq>NONE</TechPrereq>
			<StateReligionPrereq>NONE</StateReligionPrereq>
			<bLeader>0</bLeader>
			<bBlitz>0</bBlitz>
			<bAmphib>0</bAmphib>
			<bRiver>0</bRiver>
			<bEnemyRoute>0</bEnemyRoute>
			<bAlwaysHeal>0</bAlwaysHeal>
			<bHillsDoubleMove>0</bHillsDoubleMove>
			<bImmuneToFirstStrikes>0</bImmuneToFirstStrikes>
			<iVisibilityChange>3</iVisibilityChange>
			<iMovesChange>2</iMovesChange>
			<iMoveDiscountChange>0</iMoveDiscountChange>
			<iAirRangeChange>0</iAirRangeChange>
			<iInterceptChange>0</iInterceptChange>
			<iEvasionChange>0</iEvasionChange>
			<iWithdrawalChange>0</iWithdrawalChange>
			<iCargoChange>0</iCargoChange>
			<iCollateralDamageChange>0</iCollateralDamageChange>
			<iBombardRateChange>0</iBombardRateChange>
			<iFirstStrikesChange>0</iFirstStrikesChange>
			<iChanceFirstStrikesChange>0</iChanceFirstStrikesChange>
			<iEnemyHealChange>0</iEnemyHealChange>
			<iNeutralHealChange>0</iNeutralHealChange>
			<iFriendlyHealChange>0</iFriendlyHealChange>
			<iSameTileHealChange>0</iSameTileHealChange>
			<iAdjacentTileHealChange>0</iAdjacentTileHealChange>
			<iCombatPercent>0</iCombatPercent>
			<iCityAttack>0</iCityAttack>
			<iCityDefense>0</iCityDefense>
			<iHillsAttack>0</iHillsAttack>
			<iHillsDefense>0</iHillsDefense>
			<iKamikazePercent>0</iKamikazePercent>
			<iRevoltProtection>0</iRevoltProtection>
			<iCollateralDamageProtection>0</iCollateralDamageProtection>
			<iPillageChange>0</iPillageChange>
			<iUpgradeDiscount>0</iUpgradeDiscount>
			<iExperiencePercent>0</iExperiencePercent>
			<TerrainAttacks/>
			<TerrainDefenses/>
			<FeatureAttacks/>
			<FeatureDefenses/>
			<UnitCombatMods/>
			<DomainMods/>
			<TerrainDoubleMoves/>
			<FeatureDoubleMoves/>
			<UnitCombats>
			</UnitCombats>
			<HotKey/>
			<bAltDown>0</bAltDown>
			<bShiftDown>0</bShiftDown>
			<bCtrlDown>0</bCtrlDown>
			<iHotKeyPriority>0</iHotKeyPriority>
			<Button>Art/Interface/Buttons/Units/Settler.dds</Button>
		</PromotionInfo>
 
I've added the promotion without trouble I'm fairly sure, but initial settlers still don't start with the promotion.

I've added the Python from MC's first post, and I have changed the USE_ON_UNIT_CREATED_CALLBACK in the Python callback to 1. What else do I need to do?
 
I dont think the starting units go through onUnitCreated. In FFH, the Starting Settler promotion is assigned in the DLL in the addFreeUnit() function. This function does not have a python callback in it that you can use.

You'd probably have to do something in BeginPlayerTurn instead. Check to see if cities = 0, then loop through player units and assign the promotion to the first settler it finds.
 
I am pretty sure that the starting units do go through onUnitCreated in FfH2. In my modmod I use that call to give adepts the first level promotion of their civilization's patron spell sphere regardless of how much mana their owner controls (which would be 0 before the first city is built). The starting adepts of the Amurite and Sheaim civilizations certainly get their free Matamagic I and Dimensional I promotions.

However, I could not say whether the starting units go through onUnitCreated in mods not based on FfH2. It is quite possible that this is something Kael changed rather than standard Civ IV/BtS behavior.


If you place the code under onBeginPlayerTurn, it should look more like this:
Code:
 	def onBeginPlayerTurn(self, argsList):
		'Called at the beginning of a players turn'
		iGameTurn, iPlayer = argsList
		
		pPlayer = gc.getPlayer(iPlayer)
		if pPlayer.getNumCities() < 1:
			listUnits = PyPlayer(iPlayer).getUnitList()
			iUnit = gc.getInfoTypeForString('UNIT_SETTLER')
			iProm = gc.getInfoTypeForString('PROMOTION_STARTING_SETTLER')
			for pUnit in listUnits:
				if pUnit.getUnitType() == iUnit:
					pUnit.setHasPromotion(iProm, True)


The thought just crossed my mind that it might be better to place this under def onGameStart(self, argsList):
Code:
		iUnit = gc.getInfoTypeForString('UNIT_SETTLER')
		iProm = gc.getInfoTypeForString('PROMOTION_STARTING_SETTLER')
		for iPlayer in range(gc.getMAX_PLAYERS()):
			listUnits = PyPlayer(iPlayer).getUnitList()
			for pUnit in listUnits:
				if pUnit.getUnitType() == iUnit:
					pUnit.setHasPromotion(iProm, True)
I don't know whether this is called before or after starting units are given. If it is called before, then it will be useless. If it is called afterwards, then it is probably your best approach.

It would only be called once, which would be more efficient than checking all of each player's units every turn of the game. It would not grant the starting settler bonus to settlers added in other ways (such as worldbuilder) at later stages of the game once you've lost cities. It would not grant the promotion to an extra settler gained from a triv=bal village before you found you first city.
 
Adding the second excerpt of code from the above post made the promotion appear. Yay! :D

However, the promotion does not appear if you regenerate the map. I will experiment with using the first excerpt of code tomorrow.

Update: The promotion is still not applied after a regeneration.
 
Back
Top Bottom