Is it possible to add a new spell through altering the XML?

Yes. You only need to edit XML\Units\CIV4SpellInfos.xml, and to add a new <SpellInfo> entry. You may need to add new TXT_KEY strings for the new spell's name and description. The XML tags already let do you do many things, but for more complex spells it is required to write python requirements and/or results.
 
Success!
I managed to find the \Mods\Fall from Heaven 2\Assets\XML\Text\CIV4GameText_FFH2.xml file to complete the description in the wikipedia also.

Could you help me out with one more question (or perhaps point me in the direction of a FAQ, where all my noob mod questions are already answered?):

How do I replace the button art of the spell?

Thanks a lot!
 
Changing button art for spells is easy. You don't need to edit a separate art defines file (like you do for units, buildings, bonuses, and improvements), but merely place the path of the new art file between the <Button> </Button> tags.

Most of the button tags are like <Button>Art/Interface/Buttons/Spells/Consumesoul.dds</Button>, pointing to something within the Spells folder within the Buttons Folder within the Interface folder within the Art folder within the Assets folder of the modmod. Some spells share the art with units, promotions, buildings, or promotions though, and so have their art in a different folder. It does not really matter where within the assets folder the art is placed, so long as its location matched the path given in the spell define.

Note: Most of the artwork in the mod is actually stored within Pak0.FPK, so you won't be able to find it just by looking in the location given. The full file structure exists within that archive though, and can be extracted using the PakBuild utility program. (Kael made his Pak0.FPK so big that the program often freezes before fully extracting though, so there might be more art than you can extract before it crashes.) The game engine is supposedly faster at loading art stored within a FPK archive than art simply placed in a folder, but the difference is not huge. You can add your art to a new .fpk file if you like, but unless you are adding a whole lot of new art I would not bother.


Button art (no just for spell buttons, but for any buttons) must be saved in the .dds format, with dimensions of 64 x 64 pixels.

There are multiple programs that can turn a .jpeg, .bmp, etc., into a .dds. I use GIMP, which I think required I install a plug-in first.

(I think you may have to deal with minimaps for promotion button art to always display properly, but it does not matter for spell buttons. Also, for promotion/building button art to appear properly in unit/city mouse overs, you need to make sure that there are no spaces in the file's name. Underscores are fine. This does not matter at all if your new button is only being used for a spell though.)

When I did a quick search in the forum I found this button making tutorial if you prefer.
 
Guys, I have question concerning this theme. I want to create a spell for Priests of Leaves allowing them to replace forest with ancient forest. Sort of Bloom II. I made the spell in CIV4Spellinfos and in CIV4EffectInfos PY files. The spell does appear on Priest's spellbar but it's disabled all the time. I assume I have to define the spell also in CvSpellInterface but this is beyond me. Can anybody help me with that?
 
Guys, I have question concerning this theme. I want to create a spell for Priests of Leaves allowing them to replace forest with ancient forest. Sort of Bloom II. I made the spell in CIV4Spellinfos and in CIV4EffectInfos PY files. The spell does appear on Priest's spellbar but it's disabled all the time. I assume I have to define the spell also in CvSpellInterface but this is beyond me. Can anybody help me with that?

You either used impossible requirements for the spell or made an error in the python requirement code. To debug this, enable the display of python errors ingame by editing your CivilizationIV.ini file and setting HidePythonExceptions to 0.
 
You either used impossible requirements for the spell or made an error in the python requirement code. To debug this, enable the display of python errors ingame by editing your CivilizationIV.ini file and setting HidePythonExceptions to 0.

Nope. Nothing happend after setting it to 0.
 
Show the XML code here and someone might spot an obvious flag that's blocking you.
To check it yourself, I'd comment out the python requirement in the XML (<!-- -->) (or else temporarily make the first line in your python requirements function "return True") to see if the python is giving you grief or if the problem is somewhere else in your XML.
Make sure that each of the pre-requisites you understand is met when trying to test the spell. After that try switching the XML tags you are not sure of to other values.
 
Ok, here it is. I name the spell simply "Grow". Here is the CIV4SpellInfo PY:

<SpellInfo>
<Type>SPELL_GROW</Type>
<Description>TXT_KEY_SPELL_GROW</Description>
<Civilopedia>TXT_KEY_SPELL_PLACEHOLDER_PEDIA</Civilopedia>
<PromotionPrereq1>PROMOTION_CHANNELING2</PromotionPrereq1>
<PromotionPrereq2>PROMOTION_DIVINE</PromotionPrereq2>
<FeatureOrPrereq1>FEATURE_FOREST</FeatureOrPrereq1>
<ReligionPrereq>RELIGION_FELLOWSHIP_OF_LEAVES</ReligionPrereq>
<bAllowAI>1</bAllowAI>
<bAllowAutomateTerrain>1</bAllowAutomateTerrain>
<bInBordersOnly>1</bInBordersOnly>
<bDisplayWhenDisabled>1</bDisplayWhenDisabled>
<bHasCasted>1</bHasCasted>
<CreateFeatureType>FEATURE_FOREST_ANCIENT</CreateFeatureType>
<iDelay>2</iDelay>
<Effect>EFFECT_GROW</Effect>
<Sound>AS3D_SPELL_BLOOM</Sound>
<Button>Art/Interface/Buttons/Spells/Bloom.dds</Button>
</SpellInfo>



Here is the <Effect> for the spell from CIV4EffectInfo. I suppose this one should also be defined in CvSpellInterface, correct?

<EffectInfo>
<Type>EFFECT_GROW</Type>
<Description>Grow</Description>
<fScale>1.0</fScale>
<fUpdateRate>1.0</fUpdateRate>
<Path>Art/Effects/Spell Effects/leafswirl1_sfx.nif</Path>
<bIsProjectile>0</bIsProjectile>
</EffectInfo>
 
I am pretty sure that spells with a value in <CreateFeatureType> are set to only be available when there is no feature on the tile already, unless the spell also does something in python.

CvSpellInterface.py is just where where the python for a spell's <PyResult> and <PyRequirement> (and a promotion's <PyPerTurn>, and a unit's <PythonPostCombatWon> and <PythonPostCombatLost>) are defined. This python file can be used to trigger effects (such as if you want an effect to be shown on every tile where a ranged spell damages enemy units), but this is not necessary.

You don't actually have to put the python code in a separate file if it is only one line long though.

In your case you could simply add the line <PyResult>pCaster.plot().setFeatureType(gc.getInfoTypeForString('FEATURE_FOREST_ANCIENT'), 0)</PyResult> after <CreateFeatureType>.

Even if the effects of the spell are all done in python, you might want to leave the <CreateFeatureType> line in place just to make it show up in the 'pedia as you like it.

I'm not sure right now without testing, but you might also need to add a <PyRequirement> to get it to work. You may just be able to write 1 or True here, as the feature prereq is already handled in xml.


I personally have never bothered creating a new effect for a spell. Even Kael mostly just reused effects from older spells when making new ones.
 
Wow, it actually works. It took me a while to realize there shouldn't be any spaces between words but now it runs perfectly. Many thanks.
While we're at it, where can I find the rule from Fellowship of Leaves for generating ancient forests? I couldn't find it anywhere...
 
It is handled in CvPlot.cpp, a C++ file that is compiled in the CvGameCoreDll.dll and cannot eb altered without compiling a whole new DLL.

Spoiler :
Code:
void CvPlot::doFeature()
{
	PROFILE("CvPlot::doFeature()")

	CvCity* pCity;
	CvPlot* pLoopPlot;
	CvWString szBuffer;
	int iProbability;
	int iI, iJ;

	if (getFeatureType() != NO_FEATURE)
	{

//FfH: Added by Kael 03/20/2008
        if (GC.getFeatureInfo(getFeatureType()).getFeatureUpgrade() != NO_FEATURE)
        {
			if (GC.getFeatureInfo((FeatureTypes)GC.getFeatureInfo(getFeatureType()).getFeatureUpgrade()).isTerrain(getTerrainType()))
			{
				if (GC.getFeatureInfo((FeatureTypes)GC.getFeatureInfo(getFeatureType()).getFeatureUpgrade()).getPrereqStateReligion() == NO_RELIGION
				  || isOwned() && GC.getFeatureInfo((FeatureTypes)GC.getFeatureInfo(getFeatureType()).getFeatureUpgrade()).getPrereqStateReligion() == GET_PLAYER(getOwnerINLINE()).getStateReligion())
				{
	/************************************************************************************************/
	/* UNOFFICIAL_PATCH                       10/22/09                                jdog5000      */
	/*                                                                                              */
	/* Gamespeed scaling                                                                            */
	/************************************************************************************************/
	/* original bts code
					if (GC.getGameINLINE().getSorenRandNum(100, "Feature Upgrade") < GC.getDefineINT("FEATURE_UPGRADE_CHANCE"))
	*/
					int iOdds = (100 * GC.getGameSpeedInfo(GC.getGameINLINE().getGameSpeedType()).getFeatureProductionPercent())/100;
					if (GC.getGameINLINE().getSorenRandNum(iOdds, "Feature Upgrade") < GC.getDefineINT("FEATURE_UPGRADE_CHANCE"))
	/************************************************************************************************/
	/* UNOFFICIAL_PATCH                        END                                                  */
	/************************************************************************************************/

					{
						setFeatureType((FeatureTypes)GC.getFeatureInfo(getFeatureType()).getFeatureUpgrade());
					}
				}
            }
        }
        if (GC.getDefineINT("FLAMES_FEATURE") != -1  && GC.getDefineINT("FLAMES_SPREAD_EFFECT") != -1)
        {
            if (getFeatureType() == GC.getDefineINT("FLAMES_FEATURE"))
            {
                if (GC.getGameINLINE().getSorenRandNum(100, "Flames Spread") < GC.getDefineINT("FLAMES_SPREAD_CHANCE"))
                {
                    CvPlot* pAdjacentPlot;
                    for (int iI = 0; iI < NUM_DIRECTION_TYPES; ++iI)
                    {
                        pAdjacentPlot = plotDirection(getX_INLINE(), getY_INLINE(), ((DirectionTypes)iI));
                        if (pAdjacentPlot != NULL)
                        {
                            if (pAdjacentPlot->getFeatureType() != NO_FEATURE)
                            {
                                if (GC.getFeatureInfo((FeatureTypes)pAdjacentPlot->getFeatureType()).isFlammable())
                                {
                                    if (pAdjacentPlot->getImprovementType() == NO_IMPROVEMENT)
                                    {
                                        pAdjacentPlot->setImprovementType((ImprovementTypes)GC.getDefineINT("FLAMES_SPREAD_EFFECT"));
                                    }
                                }
                            }
                        }
                    }
                }
                if (!GC.getFeatureInfo((FeatureTypes)GC.getDefineINT("FLAMES_FEATURE")).isTerrain(getTerrainType()))
                {
                    if (GC.getGameINLINE().getSorenRandNum(100, "Flames Spread") < GC.getDefineINT("FLAMES_EXPIRE_CHANCE"))
                    {
                        setFeatureType(NO_FEATURE);
                        if (GC.getDefineINT("FLAMES_EXPIRE_EFFECT") != -1)
                        {
                            if (canHaveFeature((FeatureTypes)GC.getDefineINT("FLAMES_EXPIRE_EFFECT")))
                            {
                                setFeatureType((FeatureTypes)GC.getDefineINT("FLAMES_EXPIRE_EFFECT"), -1);
                            }
                        }
                        if (getFeatureType() == NO_FEATURE)
                        {
                            return;
                        }
                    }
                }
            }
        }
//FfH: End Add

		iProbability = GC.getFeatureInfo(getFeatureType()).getDisappearanceProbability();
		if (iProbability > 0)
		{
/************************************************************************************************/
/* UNOFFICIAL_PATCH                       03/04/10                                jdog5000      */
/*                                                                                              */
/* Gamespeed scaling                                                                            */
/************************************************************************************************/
/* original bts code
			if (GC.getGameINLINE().getSorenRandNum(10000, "Feature Disappearance") < iProbability)
*/
			int iOdds = (10000*GC.getGameSpeedInfo(GC.getGameINLINE().getGameSpeedType()).getVictoryDelayPercent())/100;
			if (GC.getGameINLINE().getSorenRandNum(iOdds, "Feature Disappearance") < iProbability)
/************************************************************************************************/
/* UNOFFICIAL_PATCH                        END                                                  */
/************************************************************************************************/
			{
				setFeatureType(NO_FEATURE);
			}
		}
	}
	else
	{
		if (!isUnit())
		{
			if (getImprovementType() == NO_IMPROVEMENT)
			{
				for (iI = 0; iI < GC.getNumFeatureInfos(); ++iI)
				{
					if (canHaveFeature((FeatureTypes)iI))
					{
						if ((getBonusType() == NO_BONUS) || (GC.getBonusInfo(getBonusType()).isFeature(iI)))
						{
							iProbability = 0;

							for (iJ = 0; iJ < NUM_CARDINALDIRECTION_TYPES; iJ++)
							{
								pLoopPlot = plotCardinalDirection(getX_INLINE(), getY_INLINE(), ((CardinalDirectionTypes)iJ));

								if (pLoopPlot != NULL)
								{
									if (pLoopPlot->getFeatureType() == ((FeatureTypes)iI))
									{
										if (pLoopPlot->getImprovementType() == NO_IMPROVEMENT)
										{
											iProbability += GC.getFeatureInfo((FeatureTypes)iI).getGrowthProbability();
										}
										else
										{
											iProbability += GC.getImprovementInfo(pLoopPlot->getImprovementType()).getFeatureGrowthProbability();
										}
									}
								}
							}

							iProbability *= std::max(0, (GC.getFEATURE_GROWTH_MODIFIER() + 100));
							iProbability /= 100;

							if (isRoute())
							{
								iProbability *= std::max(0, (GC.getROUTE_FEATURE_GROWTH_MODIFIER() + 100));
								iProbability /= 100;
							}

							if (iProbability > 0)
							{
/************************************************************************************************/
/* UNOFFICIAL_PATCH                       03/04/10                                jdog5000      */
/*                                                                                              */
/* Gamespeed scaling                                                                            */
/************************************************************************************************/
/* original bts code
								if (GC.getGameINLINE().getSorenRandNum(10000, "Feature Growth") < iProbability)
*/
								int iOdds = (10000*GC.getGameSpeedInfo(GC.getGameINLINE().getGameSpeedType()).getVictoryDelayPercent())/100;
								if( GC.getGameINLINE().getSorenRandNum(iOdds, "Feature Growth") < iProbability )
/************************************************************************************************/
/* UNOFFICIAL_PATCH                        END                                                  */
/************************************************************************************************/

								{
									setFeatureType((FeatureTypes)iI);

									pCity = GC.getMapINLINE().findCity(getX_INLINE(), getY_INLINE(), getOwnerINLINE(), NO_TEAM, false);

									if (pCity != NULL)
									{
										// Tell the owner of this city.
										szBuffer = gDLL->getText("TXT_KEY_MISC_FEATURE_GROWN_NEAR_CITY", GC.getFeatureInfo((FeatureTypes) iI).getTextKeyWide(), pCity->getNameKey());
										gDLL->getInterfaceIFace()->addMessage(getOwnerINLINE(), false, GC.getEVENT_MESSAGE_TIME(), szBuffer, "AS2D_FEATUREGROWTH", MESSAGE_TYPE_INFO, GC.getFeatureInfo((FeatureTypes) iI).getButton(), (ColorTypes)GC.getInfoTypeForString("COLOR_WHITE"), getX_INLINE(), getY_INLINE(), true, true);
									}

									break;
								}
							}
						}
					}
				}
			}
		}
	}
}

The XML portions are all in CIV4FeatureInfos.xml.

Near the bottom of the <Type>FEATURE_FOREST_ANCIENT</Type> define is the line <PrereqStateReligion>RELIGION_FELLOWSHIP_OF_LEAVES</PrereqStateReligion>, which means that the feature will only grow in territory owned by a player with that state religion.

Under <Type>FEATURE_FOREST</Type> there is the line <FeatureUpgrade>FEATURE_FOREST_ANCIENT</FeatureUpgrade>, which means that the forest may randomly upgrade to an ancient forest if the ancient forest prereqs are met. New forests upgrade to normal Forests the same way.
 
That looks rather complicated. If I want to take the path of least resistance, is it ok just to change<FeatureUpgrade>FEATURE_FOREST_ANCIENT</FeatureUpgrade> to <FeatureUpgrade>FEATURE_FOREST</FeatureUpgrade>?
 
Why would you want a forest to upgrade to itself?

I think all that would do is waste resources as unneeded random numbers are generated, and maybe cause the alternate graphics types of forests (like those with snow) to revert to the default style.

If you want to disable the automatic upgrading, then you should make the Forest defines like those of most features in the game and use <FeatureUpgrade>NONE</FeatureUpgrade>
 
Top Bottom