isau
Deity
- Joined
- Jan 15, 2007
- Messages
- 3,071
Overview
Modifying the SDK can be an incredibly rewarding experience. By working with the C++ code you can rewrite portions of Civ IV. This power should not be balked at; it's a part of what makes Civ IV one of the best strategy game ever released.
But with power comes responsibility. The ability to modify the code means the ability to break the code in ways you never imagined.
Below is a list of mistakes that I made over the course of building my first mod. I've compiled it hoping I can help you overcome similar errors. This list is far from comprehensive, and I'm sure I and others will have many more things to add it as we continue to mess up... err, mod files.
Mistake 1: Not Keeping Backups of Your Work
This one seems so obvious, but I cannot stress it enough. If you plan to work with the SDK, you must make regular backups of your work, and label them effectively. A single missed keystroke can bring your code to its knees; lots of times code that used to work will suddenly stop, and identifying what might have changed is incredibly important. Keep backups. KEEP backups! It will seem like a waste of time until your code breaks the game. This happened to me once and it cost me 3 months of dev time.
If you modifying the SDK, there is a 100% guarantee that at some point you will break the game. KEEP BACKUPS!
Mistake 2: Including Unmodified XML in your Mod Folders
Ok so this one has nothing to do with the SDK, but it's a mistake I made early in the modding process that caused my mod to take minutes, instead of seconds, to load.
You only need to include XML in your mod if you've done something to change the file versus what would've been included in the standard game. Including an XML file forces the game to reload the XML data, and since your data is exactly the same as the core game, all you're doing is making the game take a longer time to load.
Remember that to create a new mod in Civ IV, all you have to is add a folder with the mod's name in the mod directory. Unless you add files to this folder, the mod will load and function exactly like a standard game of Civ IV. Only add XML files that are different than what's in the core game.
The one XML file type that's a little unusual is the stuff in the Text folder. Remember that unlike other XML files, the Text files follow an add-or-replace logic. What this means is that if you can create a new XML file with a unique name and place it in this folder, Civ IV will load it and add any new tags you've created to the available strings resource, and replace any pre-existing tags, regardless of what file they came from. Don't bother including entire copies of original Text XML files just to change one or two lines of text; just copy the tags into a new XML file and the game will override the original values.
Mistake 3: Adding Variables without Total Recompiles
Adding a new variable to the game requires you to edit the .h (header) file that controls the list of available properties/variables and functions/methods. Anytime you add a new variable to the game or make other significant changes to the .h files, you must force a full recompile. This is done by deleting everything from your output folder (the folder where your dll compiles, which contains, among other things, your dll and all the obj files). Full recompiles are a lengthy process, often taking as long as 20 minutes.
But you must do this when you add or delete variables. The consequences of forgetting are random, untraceable, annoying crashes to the desktop.
Mistake 4: Forgetting that CvCity::acquireCity Erases the Original City
Now we're really getting into the weeds. Remember that CvCity::acquireCity actually deletes the original city and replaces it with a new one. What this means for modders is if you don't specifically tell Civ IV to copy a value from the old city to the new, the value disappears when someone invades, culture flips, or is given the city. Also, any pointers to the city will vanish. This should all be accounted for somewhere in this method.
Mistake 5: Making Boolean Values Truly Boolean Behind the Scenes
When adding booleans, remember that just because something will ultimately result in a bool value doesn't mean the best way to store the data is to literally use a bool. Here's an example of a much more powerful way to set up bools (using a real example derrived from my mod) that took me much too long to figure out and start using:
CvPlayer.h
CvPlayer.cpp
When I want to grant the player the ability to upgrade outside borders:
...and when I want to take it away:
The power of this logic is that you can use setCanUpgradeOutsideBordersCount to count the number of sources that are granting the ability to perform this action. So the ability to upgrade outside my borders might be coming from a Civic (+1), a Trait (+1), a special building (+1), and a Golden Age (+1) all at the same time. If I switch civics, lose the building, and the golden age ends, I still have the ability to upgrade because of the Trait. Using this logic makes complex relationships far easier to set up than trying to figure out on a case-by-case basis whether the player should be able to perform an action.
Mistake 6: Adding XML Tags that Reference Values that are Not Yet Loaded
The ability to add new XML tags to Civ IV is incredibly powerful. There's also a trap herein. Before you spend time adding new tags that will cross-reference between XML files (say, cross-referencing between Civics and Promotions, for example) you must verify the order in which the XML files load. Make sure that whatever file you will be editing to cross-reference the other file loads after the file you're attempting to reference, or the game won't work. You can find a list of the load orders on these forums.
(The details of adding new XML tags are beyond the scope of this guide. Check out Kael's excellent tutorial on these forums to get yourself started.)
Mistake 7: Looping through Players without Checking for Dead or Barbarian Players
Its often useful to loop through all of the players in the game. Just keep in mind that doing so can cause problems if you forget to check, as I often have, for Barbarians and dead/inactive player slots.
Mistake 8: Forgetting that Players != Teams
A mistake I constantly make is coding something at the Player level (often a Civic or Trait), getting almost done with it, going to add the logic for it, and then realizing that what I'm trying to affect applies to Teams and not Players.
Remember that in Civ IV, everyone is a member of a team, even if it's a team of 1 (as it is in a standard game). Some things that might appear to happen on the Player level that in fact happen at the team level are:
Mistake 9: Referencing a Pointer without Checking to See If It's NULL
This is the classic C++ mistake that every programming manual warns you about. If you attempt to reference a property or method on a pointer without checking to see if the pointer is NULL first you will without fail crash the game. Always build pointer-checking logic into your code.
Mistake 10: Hard-Coding Values
A temptation that will strike repeatedly is to hardcode values into the SDK. Civ IV has a great way to avoid this.
In the XML folder (in the main folder, not in the subfolders) there's convenient file called GlobalDefines.xml. No mod has an excuse for not using it. Place a copy of this file in your mod, and add tags that define values you will be referencing in code. Below is an example of a define I used to determine the changes of burning population resulting in a religion being removed:
To reference this in code, use:
There's also a getDefineFloat and getDefineString function with similar use.
Mistake 11: Dividing by Zero
Another classic. Trying to divide something by zero will crash the game. While none of us would ever type something like x = 3/0, divide-by-zero errors can creep in when we obtain numbers from methods or as the result of a formula. Check to make sure denominators are not equal to zero before trying to divide.
Modifying the SDK can be an incredibly rewarding experience. By working with the C++ code you can rewrite portions of Civ IV. This power should not be balked at; it's a part of what makes Civ IV one of the best strategy game ever released.
But with power comes responsibility. The ability to modify the code means the ability to break the code in ways you never imagined.
Below is a list of mistakes that I made over the course of building my first mod. I've compiled it hoping I can help you overcome similar errors. This list is far from comprehensive, and I'm sure I and others will have many more things to add it as we continue to mess up... err, mod files.
Mistake 1: Not Keeping Backups of Your Work
This one seems so obvious, but I cannot stress it enough. If you plan to work with the SDK, you must make regular backups of your work, and label them effectively. A single missed keystroke can bring your code to its knees; lots of times code that used to work will suddenly stop, and identifying what might have changed is incredibly important. Keep backups. KEEP backups! It will seem like a waste of time until your code breaks the game. This happened to me once and it cost me 3 months of dev time.
If you modifying the SDK, there is a 100% guarantee that at some point you will break the game. KEEP BACKUPS!
Mistake 2: Including Unmodified XML in your Mod Folders
Ok so this one has nothing to do with the SDK, but it's a mistake I made early in the modding process that caused my mod to take minutes, instead of seconds, to load.
You only need to include XML in your mod if you've done something to change the file versus what would've been included in the standard game. Including an XML file forces the game to reload the XML data, and since your data is exactly the same as the core game, all you're doing is making the game take a longer time to load.
Remember that to create a new mod in Civ IV, all you have to is add a folder with the mod's name in the mod directory. Unless you add files to this folder, the mod will load and function exactly like a standard game of Civ IV. Only add XML files that are different than what's in the core game.
The one XML file type that's a little unusual is the stuff in the Text folder. Remember that unlike other XML files, the Text files follow an add-or-replace logic. What this means is that if you can create a new XML file with a unique name and place it in this folder, Civ IV will load it and add any new tags you've created to the available strings resource, and replace any pre-existing tags, regardless of what file they came from. Don't bother including entire copies of original Text XML files just to change one or two lines of text; just copy the tags into a new XML file and the game will override the original values.
Mistake 3: Adding Variables without Total Recompiles
Adding a new variable to the game requires you to edit the .h (header) file that controls the list of available properties/variables and functions/methods. Anytime you add a new variable to the game or make other significant changes to the .h files, you must force a full recompile. This is done by deleting everything from your output folder (the folder where your dll compiles, which contains, among other things, your dll and all the obj files). Full recompiles are a lengthy process, often taking as long as 20 minutes.
But you must do this when you add or delete variables. The consequences of forgetting are random, untraceable, annoying crashes to the desktop.
Mistake 4: Forgetting that CvCity::acquireCity Erases the Original City
Now we're really getting into the weeds. Remember that CvCity::acquireCity actually deletes the original city and replaces it with a new one. What this means for modders is if you don't specifically tell Civ IV to copy a value from the old city to the new, the value disappears when someone invades, culture flips, or is given the city. Also, any pointers to the city will vanish. This should all be accounted for somewhere in this method.
Mistake 5: Making Boolean Values Truly Boolean Behind the Scenes
When adding booleans, remember that just because something will ultimately result in a bool value doesn't mean the best way to store the data is to literally use a bool. Here's an example of a much more powerful way to set up bools (using a real example derrived from my mod) that took me much too long to figure out and start using:
CvPlayer.h
Code:
public:
bool CvPlayer::getIsCanUpgradeOutsideBorders()
void CvPlayer::changeCanUpgradeOutsideBordersCount(int iChange)
protected:
int m_iCanUpgradeOutsideBordersCount;
CvPlayer.cpp
Code:
bool CvPlayer::getIsCanUpgradeOutsideBorders()
{
// return true if the count is greater than 1
return m_iCanUpgradeOutsideBordersCount > 0;
}
void CvPlayer::changeCanUpgradeOutsideBordersCount(int iChange)
{
m_iCanUpgradeOutsideBordersCount += iChange;
}
When I want to grant the player the ability to upgrade outside borders:
Code:
pPlayer->changeCanUpgradeOutsideBordersCount(1);
...and when I want to take it away:
Code:
pPlayer->changeCanUpgradeOutsideBordersCount(-1);
The power of this logic is that you can use setCanUpgradeOutsideBordersCount to count the number of sources that are granting the ability to perform this action. So the ability to upgrade outside my borders might be coming from a Civic (+1), a Trait (+1), a special building (+1), and a Golden Age (+1) all at the same time. If I switch civics, lose the building, and the golden age ends, I still have the ability to upgrade because of the Trait. Using this logic makes complex relationships far easier to set up than trying to figure out on a case-by-case basis whether the player should be able to perform an action.
Mistake 6: Adding XML Tags that Reference Values that are Not Yet Loaded
The ability to add new XML tags to Civ IV is incredibly powerful. There's also a trap herein. Before you spend time adding new tags that will cross-reference between XML files (say, cross-referencing between Civics and Promotions, for example) you must verify the order in which the XML files load. Make sure that whatever file you will be editing to cross-reference the other file loads after the file you're attempting to reference, or the game won't work. You can find a list of the load orders on these forums.
(The details of adding new XML tags are beyond the scope of this guide. Check out Kael's excellent tutorial on these forums to get yourself started.)
Mistake 7: Looping through Players without Checking for Dead or Barbarian Players
Its often useful to loop through all of the players in the game. Just keep in mind that doing so can cause problems if you forget to check, as I often have, for Barbarians and dead/inactive player slots.
Code:
int iI;
for (iI = 0; iI < MAX_CIV_PLAYERS; iI++)
{
CvPlayer& kPlayer = GET_PLAYER((PlayerTypes)iI)
// make sure the player is alive and not a barb before doing anything
[COLOR="Blue"]if (kPlayer.isAlive() && !kPlayer.isBarbarian())
[/COLOR] {
// do something
}
}
Mistake 8: Forgetting that Players != Teams
A mistake I constantly make is coding something at the Player level (often a Civic or Trait), getting almost done with it, going to add the logic for it, and then realizing that what I'm trying to affect applies to Teams and not Players.
Remember that in Civ IV, everyone is a member of a team, even if it's a team of 1 (as it is in a standard game). Some things that might appear to happen on the Player level that in fact happen at the team level are:
- War and Peace Status
- Technology
- Open Borders Agreements
- Visibility
- Victories
- Espionage Points
- Score
Mistake 9: Referencing a Pointer without Checking to See If It's NULL
This is the classic C++ mistake that every programming manual warns you about. If you attempt to reference a property or method on a pointer without checking to see if the pointer is NULL first you will without fail crash the game. Always build pointer-checking logic into your code.
Code:
if (pPlayer != NULL)
{
// do something
}
Mistake 10: Hard-Coding Values
A temptation that will strike repeatedly is to hardcode values into the SDK. Civ IV has a great way to avoid this.
In the XML folder (in the main folder, not in the subfolders) there's convenient file called GlobalDefines.xml. No mod has an excuse for not using it. Place a copy of this file in your mod, and add tags that define values you will be referencing in code. Below is an example of a define I used to determine the changes of burning population resulting in a religion being removed:
Code:
<Define>
<DefineName>BURN_WITCH_REMOVE_RELIGION_CHANCE</DefineName>
<!--Threshold below which religions disappear for leaders with negative MissionarySpreadModifier, bigger number = bigger chance-->
<iDefineIntVal>25</iDefineIntVal>
</Define>
To reference this in code, use:
Code:
GC.getDefineInt("BURN_WITCH_REMOVE_RELIGION_CHANCE");
There's also a getDefineFloat and getDefineString function with similar use.
Mistake 11: Dividing by Zero
Another classic. Trying to divide something by zero will crash the game. While none of us would ever type something like x = 3/0, divide-by-zero errors can creep in when we obtain numbers from methods or as the result of a formula. Check to make sure denominators are not equal to zero before trying to divide.
Code:
int iResult;
if (GC.getNumThingees() == 0)
{
// dividing something by zero does not really produce zero, but it's usually the answer I wanted
iResult = 0;
}
else
{
iResult = 10/GC.getNumThingees();
}