Modders Guide to FfH2

So the following event result from CvRandomEventInterface.py attempts to fire another event (an internal, non-existant in CIV4EventInfos.xml or CIV4EventTriggerInfos.xml ?) to apply the trait change, do I get it right?
Code:
def doMinorAggressive(argsList):
	iEvent = argsList[0]
	kTriggeredData = argsList[1]
	iPlayer = kTriggeredData.ePlayer
	CyMessageControl().sendApplyEvent(5013, EventContextTypes.EVENTCONTEXT_ALL, (iPlayer,gc.getInfoTypeForString('TRAIT_AGGRESSIVE'),True))
	CyMessageControl().sendApplyEvent(5013, EventContextTypes.EVENTCONTEXT_ALL, (iPlayer,gc.getInfoTypeForString('TRAIT_MINOR'),False))
I do not need any random chances here as I want these to work every time (random chances are non-existant or just set in python). These should happen every time.

So, I need to bypass every requirements, just make it happen. I am tempted to just import scriptable traits as I think these would work.
It is the most common bug reported in the FfH bug thread so I guess it will have to be fixed.
 
You would have to write new code for sendApplyEvent. That one doesn't exist any more at all.

What MIGHT work would be if you used:

CyMessageControl().sendEventTriggered(ePlayer, gc.getInfoTypeForString('EVENT_TRAIT_AGGRESSIVE'), INSERT TRIGGER ID HERE)


You would have to expose this to python yourself though, and the last piece is the one I can't quite figure out. Near as I can tell though it indicates which events have triggered for this player in the course of the game, and it MUST indicate a valid trigger (something which already happened). So for it to work, you would first have to launch a trigger for that player which allows this event as an option, then somehow get the ID for that event trigger on that player and use that number and basically force a choice for the player. That assumes the code doesn't pause and wait for a decision from the player before executing your forced decision though.


Overall, looking at it this time around it seems that it needs written from scratch to work effectively for multiplayer. What I saw of Scriptable Leader Traits is that it would work decently well as long as no player interaction is required (like choosing to accept/decline the trait gain for your minor leaders), and would require ModNetMessage to work if there is player interaction, OR a re-writing of the ability to control events from python like we used to. But that was (to my knowledge) a work-around measure because no better MP compatible method was known/understood at the time.
 
I just realized that making a Dimensional III spell that lets you create a Portal between 2 plots of your choosing actually isn't that hard, if you split it up into 2 stages. The actual Dimensional III spell should summon a SPECIALUNIT_SPELL ICBM-like unit, which can move instantly to any tile on the map. (If you wanted to limit it more, you could instead make it like a tactical nuke with limited range.) When that summon has moved to the tile of your choosing, it can cast the spell that actually creates a portal between its tile and the tile of its caster, which can be found the same way here as it is in the Severed Soul's Call Form spell. To reduce micromanagement, you could put the spell's actual effect in its <PyRequirement> instead of its <PyResult. That kind of coding is generally considered bad form, but I've found it to be quite useful.



I'm thinking I may use this in my version instead of the Tesseract summon that can move only a few units at a time, and which risks killing them all if the tile it moves to is inhabited. I decided to post it here too, in case anyone else wants to use it. I recall Kael saying that he would consider reimplementing the Dimensional sphere if he had 3 good spells and that he liked Escape for Dimensional I and a portal creating spell for Dimensional III, so maybe posting this code will move him closer to reimplementing the sphere.

Code:
        <UnitInfo>
            <Class>UNITCLASS_SEVERED_SOUL</Class>
            <Type>UNIT_RIFT</Type>
            <UniqueNames>
            </UniqueNames>
            <Special>SPECIALUNIT_SPELL</Special>
            <Capture>NONE</Capture>
            <Combat>NONE</Combat>
            <Domain>DOMAIN_LAND</Domain>
            <DefaultUnitAI>UNITAI_ATTACK</DefaultUnitAI>
            <Invisible>INVISIBLE_ALL</Invisible>
            <SeeInvisible>NONE</SeeInvisible>
            <Description>TXT_KEY_UNIT_RIFT</Description>
            <Civilopedia>TXT_KEY_UNIT_PLACEHOLDER_PEDIA</Civilopedia>
            <Strategy>TXT_KEY_UNIT_RIFT_STRATEGY</Strategy>
            <Advisor>ADVISOR_MILITARY</Advisor>
            <bAnimal>0</bAnimal>
            <bFood>0</bFood>
            <bNoBadGoodies>0</bNoBadGoodies>
            <bOnlyDefensive>1</bOnlyDefensive>
            <bNoCapture>0</bNoCapture>
            <bQuickCombat>0</bQuickCombat>
            <bRivalTerritory>0</bRivalTerritory>
            <bMilitaryHappiness>0</bMilitaryHappiness>
            <bMilitarySupport>0</bMilitarySupport>
            <bMilitaryProduction>0</bMilitaryProduction>
            <bPillage>0</bPillage>
            <bSpy>0</bSpy>
            <bSabotage>0</bSabotage>
            <bDestroy>0</bDestroy>
            <bStealPlans>0</bStealPlans>
            <bInvestigate>0</bInvestigate>
            <bCounterSpy>0</bCounterSpy>
            <bFound>0</bFound>
            <bGoldenAge>0</bGoldenAge>
            <bInvisible>0</bInvisible>
            <bFirstStrikeImmune>0</bFirstStrikeImmune>
            <bNoDefensiveBonus>0</bNoDefensiveBonus>
            <bIgnoreBuildingDefense>0</bIgnoreBuildingDefense>
            <bCanMoveImpassable>0</bCanMoveImpassable>
            <bCanMoveAllTerrain>0</bCanMoveAllTerrain>
            <bFlatMovementCost>0</bFlatMovementCost>
            <bIgnoreTerrainCost>0</bIgnoreTerrainCost>
            <bNukeImmune>0</bNukeImmune>
            <bPrereqBonuses>0</bPrereqBonuses>
            <bPrereqReligion>0</bPrereqReligion>
            <bMechanized>0</bMechanized>
            <bSuicide>0</bSuicide>
            <bHiddenNationality>0</bHiddenNationality>
            <bAlwaysHostile>0</bAlwaysHostile>
            <UnitClassUpgrades>
            </UnitClassUpgrades>
            <UnitClassTargets>
            </UnitClassTargets>
            <UnitCombatTargets>
            </UnitCombatTargets>
            <UnitClassDefenders>
            </UnitClassDefenders>
            <UnitCombatDefenders>
            </UnitCombatDefenders>
            <FlankingStrikes>
            </FlankingStrikes>
            <UnitAIs>
                <UnitAI>
                    <UnitAIType>UNITAI_EXPLORE</UnitAIType>
                    <bUnitAI>1</bUnitAI>
                </UnitAI>
            </UnitAIs>
            <NotUnitAIs>
            </NotUnitAIs>
            <Builds>
            </Builds>
            <ReligionSpreads>
            </ReligionSpreads>
            <CorporationSpreads>
            </CorporationSpreads>
            <GreatPeoples>
            </GreatPeoples>
            <Buildings>
            </Buildings>
            <ForceBuildings>
            </ForceBuildings>
            <HolyCity>NONE</HolyCity>
            <ReligionType>NONE</ReligionType>
            <StateReligion>NONE</StateReligion>
            <PrereqReligion>NONE</PrereqReligion>
            <PrereqCorporation>NONE</PrereqCorporation>
            <PrereqBuilding>NONE</PrereqBuilding>
            <PrereqTech>NONE</PrereqTech>
            <TechTypes>
            </TechTypes>
            <BonusType>NONE</BonusType>
            <PrereqBonuses>
            </PrereqBonuses>
            <ProductionTraits>
            </ProductionTraits>
            <Flavors>
            </Flavors>
            <iAIWeight>0</iAIWeight>
            <iCost>-1</iCost>
            <iHurryCostModifier>0</iHurryCostModifier>
            <iAdvancedStartCost>-1</iAdvancedStartCost>
            <iAdvancedStartCostIncrease>0</iAdvancedStartCostIncrease>
            <iMinAreaSize>-1</iMinAreaSize>
            <iMoves>1</iMoves>
            <bNoRevealMap>0</bNoRevealMap>
            <iAirRange>0</iAirRange>
            <iAirUnitCap>0</iAirUnitCap>
            <iDropRange>0</iDropRange>
            <iNukeRange>1</iNukeRange>
            <iWorkRate>0</iWorkRate>
            <iBaseDiscover>0</iBaseDiscover>
            <iDiscoverMultiplier>0</iDiscoverMultiplier>
            <iBaseHurry>0</iBaseHurry>
            <iHurryMultiplier>0</iHurryMultiplier>
            <iBaseTrade>0</iBaseTrade>
            <iTradeMultiplier>0</iTradeMultiplier>
            <iGreatWorkCulture>0</iGreatWorkCulture>
            <iEspionagePoints>0</iEspionagePoints>
            <TerrainImpassables>
            </TerrainImpassables>
            <FeatureImpassables>
            </FeatureImpassables>
            <TerrainPassableTechs>
            </TerrainPassableTechs>
            <FeaturePassableTechs>
            </FeaturePassableTechs>
            <iCombat>0</iCombat>
            <iCombatLimit>100</iCombatLimit>
            <iAirCombat>0</iAirCombat>
            <iAirCombatLimit>0</iAirCombatLimit>
            <iXPValueAttack>8</iXPValueAttack>
            <iXPValueDefense>4</iXPValueDefense>
            <iFirstStrikes>0</iFirstStrikes>
            <iChanceFirstStrikes>0</iChanceFirstStrikes>
            <iInterceptionProbability>0</iInterceptionProbability>
            <iEvasionProbability>0</iEvasionProbability>
            <iWithdrawalProb>0</iWithdrawalProb>
            <iCollateralDamage>0</iCollateralDamage>
            <iCollateralDamageLimit>0</iCollateralDamageLimit>
            <iCollateralDamageMaxUnits>0</iCollateralDamageMaxUnits>
            <iCityAttack>0</iCityAttack>
            <iCityDefense>0</iCityDefense>
            <iAnimalCombat>0</iAnimalCombat>
            <iHillsAttack>0</iHillsAttack>
            <iHillsDefense>0</iHillsDefense>
            <TerrainNatives>
            </TerrainNatives>
            <FeatureNatives>
            </FeatureNatives>
            <TerrainAttacks>
            </TerrainAttacks>
            <TerrainDefenses>
            </TerrainDefenses>
            <FeatureAttacks>
            </FeatureAttacks>
            <FeatureDefenses>
            </FeatureDefenses>
            <UnitClassAttackMods>
            </UnitClassAttackMods>
            <UnitClassDefenseMods>
            </UnitClassDefenseMods>
            <UnitCombatMods>
            </UnitCombatMods>
            <UnitCombatCollateralImmunes>
            </UnitCombatCollateralImmunes>
            <DomainMods>
            </DomainMods>
            <BonusProductionModifiers>
            </BonusProductionModifiers>
            <iBombRate>0</iBombRate>
            <iBombardRate>0</iBombardRate>
            <SpecialCargo>NONE</SpecialCargo>
            <DomainCargo>NONE</DomainCargo>
            <iCargo>0</iCargo>
            <iConscription>0</iConscription>
            <iCultureGarrison>0</iCultureGarrison>
            <iExtraCost>0</iExtraCost>
            <iAsset>2</iAsset>
            <iPower>4</iPower>
            <UnitMeshGroups>
            <iGroupSize>1</iGroupSize>
            <fMaxSpeed>1.75</fMaxSpeed>
            <fPadTime>1</fPadTime>
            <iMeleeWaveSize>1</iMeleeWaveSize>
            <iRangedWaveSize>0</iRangedWaveSize>
                <UnitMeshGroup>
                    <iRequired>1</iRequired>
                    <EarlyArtDefineTag>ART_DEF_UNIT_SEVERED_SOUL</EarlyArtDefineTag>
                </UnitMeshGroup>
            </UnitMeshGroups>
            <FormationType>FORMATION_TYPE_DEFAULT</FormationType>
            <HotKey></HotKey>
            <bAltDown>0</bAltDown>
            <bShiftDown>0</bShiftDown>
            <bCtrlDown>0</bCtrlDown>
            <bGraphicalOnly>0</bGraphicalOnly>
            <iHotKeyPriority>0</iHotKeyPriority>
            <FreePromotions>
                <FreePromotion>
                    <PromotionType>PROMOTION_ELEMENTAL</PromotionType>
                    <bFreePromotion>1</bFreePromotion>
                </FreePromotion>
            </FreePromotions>
            <LeaderPromotion>NONE</LeaderPromotion>
            <iLeaderExperience>0</iLeaderExperience>
            <iTier>2</iTier>
        </UnitInfo>

Code:
        <SpellInfo>
            <Type>SPELL_SUMMON_RIFT</Type>
            <Description>TXT_KEY_SPELL_RIFT</Description>
            <Civilopedia>TXT_KEY_SPELL_PLACEHOLDER_PEDIA</Civilopedia>
            <PromotionPrereq1>PROMOTION_DIMENSIONAL3</PromotionPrereq1>
            <bAllowAI>1</bAllowAI>
            <bDisplayWhenDisabled>1</bDisplayWhenDisabled>
            <bHasCasted>1</bHasCasted>
            <CreateUnitType>UNIT_RIFT</CreateUnitType>
            <iCreateUnitNum>1</iCreateUnitNum>
            <Effect>EFFECT_ENTROPY_SUMMON</Effect>
            <Sound>AS3D_SPELL_DEFILE</Sound>
            <Button>Art/Interface/Buttons/Units/Severed Soul.dds</Button>
        </SpellInfo>
        <SpellInfo>
            <Type>SPELL_OPEN_PORTAL</Type>
            <Description>TXT_KEY_SPELL_OPEN_PORTAL</Description>
            <Civilopedia>TXT_KEY_SPELL_PLACEHOLDER_PEDIA</Civilopedia>
            <UnitPrereq>UNIT_RIFT</UnitPrereq>
            <bAllowAI>1</bAllowAI>
            <iAIWeight>1000</iAIWeight>
            <bIgnoreHasCasted>1</bIgnoreHasCasted>
            <bAbility>1</bAbility>
            <PyRequirement>reqOpenPortal(pCaster)</PyRequirement>
            <Effect>EFFECT_ENTROPY_SUMMON</Effect>
            <Sound>AS3D_SPELL_DEFILE</Sound>
            <Button>Art/Interface/Buttons/Improvements/Portal.dds</Button>
        </SpellInfo>

Code:
def reqOpenPortal(caster):
	if caster.getSummoner() == -1:
		return false
	pPlayer = gc.getPlayer(caster.getOwner())
	pUnit = pPlayer.getUnit(caster.getSummoner())
	pPlot1 = caster.plot()
	pPlot2 = pUnit.plot()
	if pPlot1.getX() == pPlot2.getX() and pPlot1.getY() == pPlot2.getY():
		return false
	if pPlot1.getImprovementType() != -1:
		if gc.getImprovementInfo(pPlot1.getImprovementType()).isPermanent():
			return false
	if pPlot2.getImprovementType() != -1:
		if gc.getImprovementInfo(pPlot2.getImprovementType()).isPermanent():
			return false
	iPortal = gc.getInfoTypeForString('IMPROVEMENT_PORTAL')
	pPlot1.setImprovementType(iPortal)
	pPlot1.setPortalExitX(pPlot2.getX())
	pPlot1.setPortalExitY(pPlot2.getY())
	pPlot2.setImprovementType(iPortal)
	pPlot2.setPortalExitX(pPlot1.getX())
	pPlot2.setPortalExitY(pPlot1.getY())
	caster.kill(True, PlayerTypes.NO_PLAYER)
	return true


(Actually, I'll probably alter it slightly, to use the TempImprovementType function I added in my DLL, based on Kael's TempTerrainType function. That way you wouldn't risk permanently filling up the whole world with portals. Having these portals close after a random number of turns seems more interesting, and more balanced. That way would also prevent you from permanently destroying unique features, without stopping you from using the spell there.)

Code:
def reqOpenPortal(caster):
	if caster.getSummoner() == -1:
		return false
	pPlayer = gc.getPlayer(caster.getOwner())
	pUnit = pPlayer.getUnit(caster.getSummoner())
	pPlot1 = caster.plot()
	pPlot2 = pUnit.plot()
	iPortal = gc.getInfoTypeForString('IMPROVEMENT_PORTAL')
	if pPlot1.getImprovementType() == iPortal or pPlot2.getImprovementType() == iPortal:
		return false
	if pPlot1.getX() == pPlot2.getX() and pPlot1.getY() == pPlot2.getY():
		return false
	iRnd = CyGame().getSorenRandNum(8, "Portal")
	pPlot1.setTempImprovementType(iPortal, iRnd)
	pPlot1.setPortalExitX(pPlot2.getX())
	pPlot1.setPortalExitY(pPlot2.getY())
	pPlot2.setTempImprovementType(iPortal, iRnd)
	pPlot2.setPortalExitX(pPlot1.getX())
	pPlot2.setPortalExitY(pPlot1.getY())
	caster.kill(True, PlayerTypes.NO_PLAYER)
	return true

I may also add a <PythonPostCombatLost> function, for in case the Rift unit tries to enter a tile occupied by an enemy and thus dies since it has no combat strength. It would probably be best to make it find the nearest open plot, rather than make a portal there and let your units move straight to that tile, knocking their units off. Adding a block preventing portals from being created on city tiles is probably a good idea too.



Nota bene:
1. Nukes like this still cause diplomatic penalties, and display the text about the unit exploding when it hits it target. You'd probably want to change the text in CIV4GameText_FFH2.xml.
2. Also bear in mind that it cannot be used to close to neutral units or territory.
3. Nukes ignore the level prereqs for entering tiles. If you use a spell like this, you should probably change how freeing Brigit works. I'd recommend getting rid of UNIT_BRIGIT_HELD (possibly adding her graphics to the Ring of Carcer) and making it so that Brigit s created though a <iCasterMinLevel>15</iCasterMinLevel> spell that can only be cast on the Ring of Carcer improvement.
4. The way I have the spell autocasting now would cause the portals to be created if the dimensional III unit that summoned the rift moved. I'm considering changing it so that the original caster is immobilized by the spell, and that the portal's duration would be equal to the original caster's immobility counter. If immobile units could cast spells and promotions could have <PythonPostCombatLost> (or, better yet, in FF, <PythonOnDeath>) functions, I'd recommend making the spell grant a "Maintaining Portal" promotion that immobilizes the caster, allows another spell to close the portal and remove the promotion, and had a <PythonPostCombatLost> (or <PythonOnDeath>) call that would close the portal when the unit was killed. (Actually, I'm thinking that is already possible in FF.)

Edit: I just found it rather easy to edit the dll to make it so that you could nuke neutral lands (making you declare war) but be unable to nuke tiles without the prerequisite minimum level.

Also, since the <PyRequirement> functions are only run when the unit is selected, you can manage to get the caster several tiles away before creating a portal to the summon. If you fortify the unit there then you could go several turns before making the portal, or rather you would be able to if the rift unit would stck around that long, but it won't as FfH's tracking mechanism (unlike FF's) can't track it that long.
 
I've been getting this weird bug with a spell I put into Rise of Darkness. The spell should go to a unit called the Retialius but instead warriors get it.
Civ4ScreenShot0076.jpg


Civ4ScreenShot0077.jpg


Civ4ScreenShot0078.jpg


The code I am using is just simple XML so I can't see what is wrong.
Code:
        <SpellInfo>
            <Type>SPELL_CAST_NET</Type>
            <Description>TXT_KEY_SPELL_CAST_NET</Description>
            <Civilopedia>TXT_KEY_SPELL_CAST_NET_PEDIA</Civilopedia>
            <UnitPrereq>UNIT_RETIALIUS</UnitPrereq>
            <bAllowAI>1</bAllowAI>
            <bDisplayWhenDisabled>1</bDisplayWhenDisabled>
            <bHasCasted>1</bHasCasted>
            <bResistable>1</bResistable>
            <iRange>1</iRange>
            <iResistModify>20</iResistModify>
            <AddPromotionType1>PROMOTION_NETTED</AddPromotionType1>
            <bImmuneTeam>1</bImmuneTeam>
            <bImmuneNeutral>1</bImmuneNeutral>
            <bImmuneNotAlive>1</bImmuneNotAlive>
            <Effect>EFFECT_SPELL1</Effect>
            <Sound>AS3D_SPELL_CHARM_PERSON</Sound>
            <Button>Art/Interface/Buttons/Promotions/Charmed.dds</Button>
        </SpellInfo>
 
That is weird. I don't know what could be causing it. The first thing that came to mind is that maybe you used a UNIT tag where you should have used a UNITCLASS tag, but it doesn't look like you did. Maybe there is some error in the unit's xml defines instead of the spell's?

Maybe you're using modular loading? Often tags that take strings and then turn them into indexes don't work well with modular loading. If thatisi the case, then the Retialius unit probably has the same index in your modular file as a Warrior has in the non-modular version.

Also, shouldn't the unit be called Retiarius instead of Retialius? Retiarius is a well known type of gladiator who fights with a net, while retialius just seems like gibberish (although I suppose it theoretically could b used to mean the same thing).



So, no opinion about my new dimensional 3 spell?
 
That is weird. I don't know what could be causing it. The first thing that came to mind is that maybe you used a UNIT tag where you should have used a UNITCLASS tag, but it doesn't look like you did. Maybe there is some error in the unit's xml defines instead of the spell's?

Maybe you're using modular loading? Often tags that take strings and then turn them into indexes don't work well with modular loading. If thatisi the case, then the Retialius unit probably has the same index in your modular file as a Warrior has in the non-modular version.

Also, shouldn't the unit be called Retiarius instead of Retialius? Retiarius is a well known type of gladiator who fights with a net, while retialius just seems like gibberish (although I suppose it theoretically could b used to mean the same thing).



So, no opinion about my new dimensional 3 spell?

I'll try turning off Modular loading but the file the Retiarius (Retialius was a typo) was located in the main spellinfos.

I like your Dimensional III spell but the PyReq might cause problems. I would put something in the python that checks the nuke landing that would summon the caster instead of there.
 
The it isn't suppose to summon the caster, it is just suppose to find the caster's coordinates so it can know where the other end of the portal should be. If I could find a way to find both the pre and post nuking coordinates of the rift it would work just as well.

Now that I think of it, it does seem like using def onNukeExplosion(self, argsList): instead could be a good idea (checking for the specific unit type, since I let Auric Ascended act an ICBM too but he shouldn't make portals or especially die after doing so). Of course, I think that would risk letting you create portals straight into heavily defended tiles, letting you take enemy cities without combat. I guess I'd need to run a check to make it find the nearest empty tile rather than placing that end of the portal where the rift lands. Another upside of this would be that I could go ahead and remove the text string normally displayed when nukes explode.

Edit: I just tried it that way, but it didn't work. I don't think that function is actually exposed, and I don't feel like compiling the dll again to expose it.






What kind of problems were you predicting the PyPrereq could cause?








Completely unrelated, but I find it really annoying that the Unyielding Order spell can end a revolt in a city but doesn't stop it from revolting again, and can't be cast to stop another revolt without moving the caster out fo the city for a turn, which would also cancel the caster's other spells like Hope and Inspiration. I think that this should be added to def onCityDoTurn(self, argsList):
Code:
		if pCity.getNumBuilding(gc.getInfoTypeForString('BUILDING_UNYIELDING_ORDER')) > 0:
			pCity.setOccupationTimer(0)
			pCity.changeHurryAngerTimer(-9)
 
Also, since the <PyRequirement> functions are only run when the unit is selected, you can manage to get the caster several tiles away before creating a portal to the summon. If you fortify the unit there then you could go several turns before making the portal, or rather you would be able to if the rift unit would stck around that long, but it won't as FfH's tracking mechanism (unlike FF's) can't track it that long.

That is the main problem I see. FfH would not be able to track it and it would possibly confuse some players and might remove a bit of AI use because it probably wouldn't know to select the unit again.
 
Now that I have solved my own crash I have time to look at simpler things in detail. But not MUCH time. So for the case of Insane, it never needed to use CyMessageControl in the first place. Just use pPlayer.trigger(gc.getInfoTypeForString("EVENTTRIGGER_INSANE") to launch it, then in def doInsane or whatnot, use pPlayer.setHasTrait(i, False) to clear, and pPlayer.setHasTrait(gc.getInfoTypeForString(Trait[iRnd1]),True) to set the traits. Tested this in multiplayer and it works flawlessly.

I haven't rewritten and tested out Adaptive yet (bedtime for me now). It could be slightly more complicated because some player interaction is required for the choices, but the event system itself should broadcast the choice that a player makes, so as long as the python is written not to depend on ACTIVEPLAYER anywhere, it should still work just as easily.
 
Xienwolf, let me be the first to say THANKS! :D

I knew it has to something simple, and here it is. Did not test it in multiplayer, but works fine in SP wherever I have tested it.

I have changed the code I had problem above (well, it was just an example) to:

Code:
def doMinorAggressive(argsList):
	iEvent = argsList[0]
	kTriggeredData = argsList[1]
	pPlayer = gc.getPlayer(kTriggeredData.ePlayer)
	pPlayer.setHasTrait(gc.getInfoTypeForString('TRAIT_MINOR'),False)
	pPlayer.setHasTrait(gc.getInfoTypeForString('TRAIT_AGGRESSIVE'),True)
and it works flawlessly.
In fact, all the bugs reported come from this stage, the applying itself. So, simple changing
Code:
	CyMessageControl().sendApplyEvent(5013, EventContextTypes.EVENTCONTEXT_ALL, (iPlayer,gc.getInfoTypeForString('TRAIT_AGGRESSIVE'),True))
to
Code:
	pPlayer.setHasTrait(gc.getInfoTypeForString('TRAIT_AGGRESSIVE'),True)
for every trait change should be enough.
 
I remember reading a suggestion to make Auric Ascended have a passive effect of spreading ice terrain (like his special spell does after you cast it), and so added code to onUnitMove. I figured out afterwards that I can't do this, because onUnitMove will never trigger, as defined in the PythonCallbackDefines.xml file.

If I remove the python callback, what kind of performance impact will this have on my game? I know that OnUnitMove is not a callback in vanilla BTS... but this isn't vanilla BTS.
 
I just added your trait change fix, and then found the game crashes while loading. Of course, I had made a few changes a couple days ago which I hadn't tested yet, so your code is probably not to blame.


@ TC01:
I'm not sure how to activate the callback or what performance impact it would have, but I can tell you that the way I did it in my version is easier. Give Auric Ascended a spell that has its python effect in <PyRequirement> instead of <PyResult>. You could simply move the prereq in Snowfall_greater, but then the effect would not happen if he had already casted. Instead, I'd recommend a <bAllowAI>1</bAllowAI>, <bDisplayWhenDisabled>0</bDisplayWhenDisabled>, <bIgnoreHasCasted>1</bIgnoreHasCasted>, <bGraphicalOnly>1</bGraphicalOnly> spell. After your python code you would add a return false, so the spell could never be used and would not appear to exist, but the code would be run whenever it tries to see if he can cast it. That means it would run whenever it moves, and also whenever you select it and (I think) whenever the AI considers moving or casting with him. That should mean it would run in all cases that you want it too, plus a few when you don't but where it wouldn't really do any harm. (Well, unless you move him multiple spaces at a time into water tiles and the code changes water to land, as that mysteriously kills him.) The few extra times it runs for a unit that is rarely in the game is probably nothing compared to having to run an If statement when any unit moves to determine it that unit it Auric, so this should be a far more efficient code.


I was taught in AP Computer Science back in high school that having prereq functions like this actually do stuff is generally considered bad form, but I've found it can be quite useful. Since you aren't modding to impress a CS teacher, I don't see why you wouldn't just use this shortcut.
 
Best reason I can see not to use the prereq checks instead of some other function is that when you are programming something you want to be able to predict exactly when and why every function will run. The prereqs are an area where it is very hard to decide precisely how/when they will run in all situations, especially in terms of Human control vs AI Control.

But also it is a matter of future code conflicts. You need to know what is safe to run and what is not. In general you assume that a prereq function won't change anything, so you don't feel any hesitation to call it in places where changing the data can be a VERY bad thing. If you forget that this is one of the prereqs you happened to abuse and use it anyway, then you might have a crash situation which is insanely hard to debug (unless you were being careful and testing after evey single code addition you make and thus knew it was the new code, but then we return to the "can't always predict when a prereq check will run" issue in that during your test you might not think to cause that code to happen)
 
The prereq function works well in this instance, though, from what I can tell. But I do see how it could cause problems.

I used the Temple of the Hand code instead of the SnowfallGreator code. I added a special case to do if the plot IS water- to add a permanent ice feature on the plot AA's standing/flying on (since in my unmodded DLL I don't have .setTempFeatureType()... at least to my knowledge).

--

Another question: Between .40z and .41b, were any other schema changes made besides the LeaderHead favorite techs and favorite unitcombats? I started making some fairly straightforward XML changes a while ago, and never got around to upgrading them from .40z to .41.
 
I understand you need to be careful with it and I don't code like that often, but in this particular case would not the <UnitClassPrereq>UNITCLASS_AURIC_ASCENDED</UnitClassPrereq> tag narrow things down quite a bit?

Code:
<SpellInfo>
            <Type>SPELL_SNOWFALL_PASSIVE</Type>
            <Description>TXT_KEY_SPELL_SNOWFALL</Description>
            <Civilopedia>TXT_KEY_SPELL_PLACEHOLDER_PEDIA</Civilopedia>
            <Help>TXT_KEY_SPELL_SNOWFALL_HELP</Help>
            <UnitClassPrereq>UNITCLASS_AURIC_ASCENDED</UnitClassPrereq>
            <bAllowAI>1</bAllowAI>
            <bDisplayWhenDisabled>0</bDisplayWhenDisabled>
            <bIgnoreHasCasted>1</bIgnoreHasCasted>
            <PyRequirement>spellSnowfallPassive(pCaster)</PyRequirement>
            <Effect>EFFECT_SNOWFALL_GREATOR</Effect>
            <Sound>AS3D_SPELL_SPRING</Sound>
            <bGraphicalOnly>1</bGraphicalOnly>
            <Button>Art/Interface/Buttons/Spells/Snowfall.dds</Button>
        </SpellInfo>
Code:
def spellSnowfallPassive(caster):
	iX = caster.getX()
	iY = caster.getY()
	iFlames = gc.getInfoTypeForString('FEATURE_FLAMES')
	iBlizzard = gc.getInfoTypeForString('FEATURE_BLIZZARD')
	iSmoke = gc.getInfoTypeForString('IMPROVEMENT_SMOKE')
	iSnow = gc.getInfoTypeForString('TERRAIN_SNOW')
	for iiX in range(iX-2, iX+3, 1):
		for iiY in range(iY-2, iY+3, 1):
			pPlot = CyMap().plot(iiX,iiY)
			if not pPlot.isNone():
				iRnd = CyGame().getSorenRandNum(12, "Snowfall") + 6
				if not pPlot.isWater():
					if pPlot.getTerrainType() != iSnow:
						pPlot.setTempTerrainType(iSnow, iRnd)
						if pPlot.getFeatureType() == iFlames:
							pPlot.setFeatureType(-1, -1)
						if pPlot.getImprovementType() == iSmoke:
							pPlot.setImprovementType(-1)
				if pPlot.getFeatureType() == -1:
					pPlot.setTempFeatureType(iBizzard, iRnd, 0)
	return false

(The setTempFeatureType part of course depends on my custom dll; setTempImprovementType is also a function I added, both essentially copies of Kael's setTempTerrainType function but with the feature one needing to take the feature's variety into account as well.)

Edit: Ok, now I'm a little embarrassed, as in this version I accidentally used setTempImprovementType with a feature at first. I would of course have been more embarrassed if fixing that had been all I needed to do to stop that CtD durring loading.
-------

Completely unrelated, but I'd just like to request that Kael please consistently use tabs instead of spaces in the python files and get rid of the stray spaces in places like the def reqSteal, def spellSteal, and def doCrusade. It doesn't make a difference as far as the code runs, but these spaces drive IDLE crazy and make it difficult to find any real bugs I might introduce into my code. I tend to edit them out before starting my actual changes, but whenever there is a new patch or I mess up and start over I have to take the time to redo it. Often I end up messing up the Somnium code near the bottom of CvEventManager.py when I change the spaces to tabs. Getting rid of the spaces should also make the files smaller and thus the download faster, albeit only very slightly.
 
I've been getting this weird bug in my mod. When I hit the end turn button it seems that the turn is not changing or ending. It leap from about 30 second between turns to endless (I've gotten to about 7 minutes). I looked at a log and it seems that the the game is ending my turn without other players without end.



Spoiler Log :
[156845.063] DBG: HOSTING a SINGLEPLAYER, SAVED, LAN game!
[156845.063] DBG: Adding active connection-level player info for NetID 0
[156845.234] DBG: Adding NetID 0 to Portal!
[156845.281] DBG: Got NetworkReady from NetID 0
[156845.281] WRN: Not responding to NetID 0 NetworkReady (state == INIT_SENT_READY)
[156845.281] DBG: Checking if we're connect complete! PeersReady=yes :: AlreadyComplete=no
[156845.281] DBG: Got our last outstanding network ready message! Sending Version Verification! (m_bConnectComplete=true)
[156848.156] DBG: Got Verify Version message from NetID 0!
[156848.156] DBG: Only Slot 0 is left - sending response with NetID 0 for this civ!
[156848.156] DBG: Civ 0 already claimed! No password required, sending ack to NetID 0
[156848.188] DBG: Received CivChoice Ack Message from NetID 0
[156848.188] DBG: Sending CivChosen claim on AppID 0
[156848.188] DBG: SYNCLOG: Sending Civ Chosen Message
[156848.188] DBG: SYNCLOG: CivChosen: 0, 0, Florina, , 32
[156848.234] DBG: Received CivChosen Claim on AppID 0 from NetID 0
[156848.234] DBG: Sending game info to NetID 0
[156848.234] DBG: Sending player info about NetID 0 to everyone
[156848.234] DBG: Sending player info about civ at slot 1 to NetID 0
[156848.234] DBG: Sending player info about civ at slot 2 to NetID 0
[156848.234] DBG: Sending player info about civ at slot 3 to NetID 0
[156848.234] DBG: Sending player info about civ at slot 4 to NetID 0
[156848.234] DBG: Sending player info about civ at slot 5 to NetID 0
[156848.234] DBG: Sending player info about civ at slot 6 to NetID 0
[156848.234] DBG: Sending player info about civ at slot 7 to NetID 0
[156848.234] DBG: Sending player info about civ at slot 8 to NetID 0
[156848.234] DBG: Sending player info about civ at slot 9 to NetID 0
[156848.234] DBG: Sending player info about civ at slot 10 to NetID 0
[156848.234] DBG: Sending player info about civ at slot 11 to NetID 0
[156848.234] DBG: Sending player info about civ at slot 12 to NetID 0
[156848.234] DBG: Sending player info about civ at slot 13 to NetID 0
[156848.234] DBG: Sending player info about civ at slot 14 to NetID 0
[156848.234] DBG: Sending player info about civ at slot 15 to NetID 0
[156848.234] DBG: Sending player info about civ at slot 16 to NetID 0
[156848.234] DBG: Sending player info about civ at slot 17 to NetID 0
[156848.234] DBG: Sending player info about civ at slot 18 to NetID 0
[156848.234] DBG: Sending player info about civ at slot 19 to NetID 0
[156848.234] DBG: Sending player info about civ at slot 20 to NetID 0
[156848.234] DBG: Sending player info about civ at slot 21 to NetID 0
[156848.234] DBG: Sending player info about civ at slot 22 to NetID 0
[156848.234] DBG: Sending player info about civ at slot 23 to NetID 0
[156848.234] DBG: Sending player info about civ at slot 24 to NetID 0
[156848.234] DBG: Sending player info about civ at slot 25 to NetID 0
[156848.234] DBG: Sending player info about civ at slot 26 to NetID 0
[156848.234] DBG: Sending player info about civ at slot 27 to NetID 0
[156848.234] DBG: Sending player info about civ at slot 28 to NetID 0
[156848.234] DBG: Sending player info about civ at slot 29 to NetID 0
[156848.234] DBG: Sending player info about civ at slot 30 to NetID 0
[156848.234] DBG: Sending player info about civ at slot 31 to NetID 0
[156848.234] DBG: Sending player info about civ at slot 32 to NetID 0
[156848.234] DBG: Sending player info about civ at slot 33 to NetID 0
[156848.234] DBG: Sending player info about civ at slot 34 to NetID 0
[156848.297] DBG: Sending Information about SaveFile "C:\Documents and Settings\JANICE\My Documents\My Games\Beyond the Sword\Saves\single\Karalithiam AD-0271.CivBeyondSwordSave" to NetID 0
[156848.906] DBG: Received information about SaveFile "C:\Documents and Settings\JANICE\My Documents\My Games\Beyond the Sword\Saves\single\Karalithiam AD-0271.CivBeyondSwordSave" from NetID 0
[156848.906] DBG: Sending positive file ack to NetID 0
[156916.500] DBG: Sending sync start message everyone
[156916.609] DBG: Got a sync start from NetID 0
[156923.469] DBG: SYNCLOG: Hot turn set to 1
[156923.578] DBG: SYNCLOG: STARTING TURN 1
[156930.234] DBG: SYNCLOG: Received EndTurn for Player 0 (NetID 0) - Checking in for Turn 1
[156930.234] DBG: SYNCLOG: All Checked In for Turn 1
[156930.234] DBG: SYNCLOG: Hot turn has been reset on turn 1
[156930.328] DBG: SYNCLOG: STARTING TURN 2
[156952.859] DBG: SYNCLOG: Received EndTurn for Player 0 (NetID 0) - Checking in for Turn 2
[156953.094] DBG: SYNCLOG: All Checked In for Turn 2
[156953.406] DBG: SYNCLOG: STARTING TURN 3
[156959.219] DBG: SYNCLOG: Received EndTurn for Player 0 (NetID 0) - Checking in for Turn 3
[156959.219] DBG: SYNCLOG: All Checked In for Turn 3
[156959.219] DBG: SYNCLOG: STARTING TURN 4
[156959.703] DBG: SYNCLOG: Received EndTurn for Player 0 (NetID 0) - Checking in for Turn 4
[156959.719] DBG: SYNCLOG: All Checked In for Turn 4
[156959.719] DBG: SYNCLOG: STARTING TURN 5
[156960.000] DBG: SYNCLOG: Received EndTurn for Player 0 (NetID 0) - Checking in for Turn 5
[156960.000] DBG: SYNCLOG: All Checked In for Turn 5
[156960.000] DBG: SYNCLOG: STARTING TURN 6
[156960.219] DBG: SYNCLOG: Received EndTurn for Player 0 (NetID 0) - Checking in for Turn 6
[156960.438] DBG: SYNCLOG: All Checked In for Turn 6
[156960.438] DBG: SYNCLOG: STARTING TURN 7
[156960.641] DBG: SYNCLOG: Received EndTurn for Player 0 (NetID 0) - Checking in for Turn 7
[156961.234] DBG: SYNCLOG: All Checked In for Turn 7
[156961.234] DBG: SYNCLOG: STARTING TURN 8
[156961.438] DBG: SYNCLOG: Received EndTurn for Player 0 (NetID 0) - Checking in for Turn 8
[156961.563] DBG: SYNCLOG: All Checked In for Turn 8
[156961.563] DBG: SYNCLOG: STARTING TURN 9
[156961.703] DBG: SYNCLOG: Received EndTurn for Player 0 (NetID 0) - Checking in for Turn 9
[156961.859] DBG: SYNCLOG: All Checked In for Turn 9
[156961.859] DBG: SYNCLOG: STARTING TURN 10
[156962.000] DBG: SYNCLOG: Received EndTurn for Player 0 (NetID 0) - Checking in for Turn 10
[156962.125] DBG: SYNCLOG: All Checked In for Turn 10
[156962.125] DBG: SYNCLOG: STARTING TURN 11
[156962.281] DBG: SYNCLOG: Received EndTurn for Player 0 (NetID 0) - Checking in for Turn 11
[156962.422] DBG: SYNCLOG: All Checked In for Turn 11
[156962.422] DBG: SYNCLOG: STARTING TURN 12
[156962.563] DBG: SYNCLOG: Received EndTurn for Player 0 (NetID 0) - Checking in for Turn 12
[156962.703] DBG: SYNCLOG: All Checked In for Turn 12
[156962.703] DBG: SYNCLOG: STARTING TURN 13
[156962.844] DBG: SYNCLOG: Received EndTurn for Player 0 (NetID 0) - Checking in for Turn 13
[156962.984] DBG: SYNCLOG: All Checked In for Turn 13
[156962.984] DBG: SYNCLOG: STARTING TURN 14
[156963.125] DBG: SYNCLOG: Received EndTurn for Player 0 (NetID 0) - Checking in for Turn 14



It goes on like that for a while until whenever I end the program. The furthest point that I have hit was TURN 12649. Is this anything that is fixable?
 
How would I go about adding a PromotionTargets field that (similar to BtS's UnitCombatTargets field) makes a unit target units with specific promotions in stacks of stronger units?

Also, how would I go about letting promotions modify the UnitClassTargets, UnitCombatTargets, and new PromotionTargets fields?

Oh, and how would I go about adding TerrainImpassabless, FeatureImpassables, TerrainPassableTechs, and FeaturePassableTechs to promotions instead of just units?
 
Im trying to create a unique unit for the Hippus which replaces the normal Champion unit. I have no problem actually creating the unit in Civ4UnitInfos (I can place it in world builder), but I cant build the unit in game or upgrade to it from an axeman. Instead it just upgrades to the normal champion. What other file do I need to change?
 
Back
Top Bottom